Add some carnival features
This commit is contained in:
@@ -28,7 +28,7 @@ hotswap_agent = "1.4.2-SNAPSHOT"
|
|||||||
mixinextras = "0.3.5"
|
mixinextras = "0.3.5"
|
||||||
jarvis = "1.1.3"
|
jarvis = "1.1.3"
|
||||||
nealisp = "1.0.0"
|
nealisp = "1.0.0"
|
||||||
moulconfig = "3.0.0-beta.13"
|
moulconfig = "3.0.0-beta.14"
|
||||||
manninghamMills = "2.4.1"
|
manninghamMills = "2.4.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package moe.nea.firmament.mixins;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.sugar.Local;
|
||||||
|
import moe.nea.firmament.events.EntityUpdateEvent;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.network.ClientCommonNetworkHandler;
|
||||||
|
import net.minecraft.client.network.ClientConnectionState;
|
||||||
|
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||||
|
import net.minecraft.client.world.ClientWorld;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.network.ClientConnection;
|
||||||
|
import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket;
|
||||||
|
import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(ClientPlayNetworkHandler.class)
|
||||||
|
public abstract class EntityUpdateEventListener extends ClientCommonNetworkHandler {
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private ClientWorld world;
|
||||||
|
|
||||||
|
protected EntityUpdateEventListener(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) {
|
||||||
|
super(client, connection, connectionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "onEntityAttributes", at = @At("TAIL"))
|
||||||
|
private void onAttributeUpdate(EntityAttributesS2CPacket packet, CallbackInfo ci) {
|
||||||
|
EntityUpdateEvent.Companion.publish(new EntityUpdateEvent.AttributeUpdate(
|
||||||
|
(LivingEntity) world.getEntityById(packet.getEntityId()), packet.getEntries()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "onEntityTrackerUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/data/DataTracker;writeUpdatedEntries(Ljava/util/List;)V", shift = At.Shift.AFTER))
|
||||||
|
private void onEntityTracker(EntityTrackerUpdateS2CPacket packet, CallbackInfo ci, @Local Entity entity) {
|
||||||
|
EntityUpdateEvent.Companion.publish(new EntityUpdateEvent.TrackedDataUpdate(entity, packet.trackedValues()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package moe.nea.firmament.mixins;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.sugar.Local;
|
||||||
|
import moe.nea.firmament.events.PlayerInventoryUpdate;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.network.ClientCommonNetworkHandler;
|
||||||
|
import net.minecraft.client.network.ClientConnectionState;
|
||||||
|
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||||
|
import net.minecraft.client.network.ClientPlayerEntity;
|
||||||
|
import net.minecraft.network.ClientConnection;
|
||||||
|
import net.minecraft.network.packet.s2c.play.InventoryS2CPacket;
|
||||||
|
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Mixin(ClientPlayNetworkHandler.class)
|
||||||
|
public abstract class SlotUpdateListener extends ClientCommonNetworkHandler {
|
||||||
|
protected SlotUpdateListener(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) {
|
||||||
|
super(client, connection, connectionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "onScreenHandlerSlotUpdate",
|
||||||
|
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/tutorial/TutorialManager;onSlotUpdate(Lnet/minecraft/item/ItemStack;)V"))
|
||||||
|
private void onSingleSlotUpdate(
|
||||||
|
ScreenHandlerSlotUpdateS2CPacket packet,
|
||||||
|
CallbackInfo ci) {
|
||||||
|
var player = this.client.player;
|
||||||
|
assert player != null;
|
||||||
|
if (packet.getSyncId() == ScreenHandlerSlotUpdateS2CPacket.UPDATE_PLAYER_INVENTORY_SYNC_ID
|
||||||
|
|| packet.getSyncId() == 0) {
|
||||||
|
PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Single(packet.getSlot(), packet.getStack()));
|
||||||
|
} else if (packet.getSyncId() == player.currentScreenHandler.syncId) {
|
||||||
|
// TODO: dispatch single chest slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "onInventory",
|
||||||
|
at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V",
|
||||||
|
shift = At.Shift.AFTER))
|
||||||
|
private void onMultiSlotUpdate(InventoryS2CPacket packet, CallbackInfo ci) {
|
||||||
|
var player = this.client.player;
|
||||||
|
assert player != null;
|
||||||
|
if (packet.getSyncId() == 0) {
|
||||||
|
PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Multi(packet.getContents()));
|
||||||
|
} else if (packet.getSyncId() == player.currentScreenHandler.syncId) {
|
||||||
|
// TODO: dispatch multi chest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,10 +14,10 @@ import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
|
|||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import moe.nea.firmament.apis.UrsaManager
|
import moe.nea.firmament.apis.UrsaManager
|
||||||
import moe.nea.firmament.events.CommandEvent
|
import moe.nea.firmament.events.CommandEvent
|
||||||
|
import moe.nea.firmament.features.debug.PowerUserTools
|
||||||
import moe.nea.firmament.features.inventory.buttons.InventoryButtons
|
import moe.nea.firmament.features.inventory.buttons.InventoryButtons
|
||||||
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
|
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
|
||||||
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverviewScreen
|
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverviewScreen
|
||||||
import moe.nea.firmament.features.world.FairySouls
|
|
||||||
import moe.nea.firmament.gui.config.AllConfigsGui
|
import moe.nea.firmament.gui.config.AllConfigsGui
|
||||||
import moe.nea.firmament.gui.config.BooleanHandler
|
import moe.nea.firmament.gui.config.BooleanHandler
|
||||||
import moe.nea.firmament.gui.config.ManagedOption
|
import moe.nea.firmament.gui.config.ManagedOption
|
||||||
@@ -182,11 +182,6 @@ fun firmamentCommand() = literal("firmament") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
thenLiteral("dev") {
|
thenLiteral("dev") {
|
||||||
thenLiteral("config") {
|
|
||||||
thenExecute {
|
|
||||||
FairySouls.TConfig.showConfigEditor()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
thenLiteral("simulate") {
|
thenLiteral("simulate") {
|
||||||
thenArgument("message", RestArgumentType) { message ->
|
thenArgument("message", RestArgumentType) { message ->
|
||||||
thenExecute {
|
thenExecute {
|
||||||
@@ -208,6 +203,12 @@ fun firmamentCommand() = literal("firmament") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
thenLiteral("copyEntities") {
|
||||||
|
thenExecute {
|
||||||
|
val player = MC.player ?: return@thenExecute
|
||||||
|
player.world.getOtherEntities(player, player.boundingBox.expand(12.0)).forEach(PowerUserTools::showEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
thenLiteral("callUrsa") {
|
thenLiteral("callUrsa") {
|
||||||
thenArgument("path", string()) { path ->
|
thenArgument("path", string()) { path ->
|
||||||
thenExecute {
|
thenExecute {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package moe.nea.firmament.events
|
||||||
|
|
||||||
|
import net.minecraft.entity.Entity
|
||||||
|
import net.minecraft.entity.LivingEntity
|
||||||
|
import net.minecraft.entity.data.DataTracker
|
||||||
|
import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is fired when some entity properties are updated.
|
||||||
|
* It is not fired for common changes like position, but is for less common ones,
|
||||||
|
* like health, tracked data, names, equipment. It is always fired
|
||||||
|
* *after* the values have been applied to the entity.
|
||||||
|
*/
|
||||||
|
sealed class EntityUpdateEvent : FirmamentEvent() {
|
||||||
|
companion object : FirmamentEventBus<EntityUpdateEvent>()
|
||||||
|
|
||||||
|
abstract val entity: Entity
|
||||||
|
|
||||||
|
data class AttributeUpdate(
|
||||||
|
override val entity: LivingEntity,
|
||||||
|
val attributes: List<EntityAttributesS2CPacket.Entry>,
|
||||||
|
) : EntityUpdateEvent()
|
||||||
|
|
||||||
|
data class TrackedDataUpdate(
|
||||||
|
override val entity: Entity,
|
||||||
|
val trackedValues: List<DataTracker.SerializedEntry<*>>,
|
||||||
|
) : EntityUpdateEvent()
|
||||||
|
|
||||||
|
// TODO: onEntityPassengersSet, onEntityAttach?, onEntityEquipmentUpdate, onEntityStatusEffect
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package moe.nea.firmament.events
|
||||||
|
|
||||||
|
import net.minecraft.item.ItemStack
|
||||||
|
|
||||||
|
sealed class PlayerInventoryUpdate : FirmamentEvent() {
|
||||||
|
companion object : FirmamentEventBus<PlayerInventoryUpdate>()
|
||||||
|
data class Single(val slot: Int, val stack: ItemStack) : PlayerInventoryUpdate()
|
||||||
|
data class Multi(val contents: List<ItemStack>) : PlayerInventoryUpdate()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import moe.nea.firmament.features.debug.MinorTrolling
|
|||||||
import moe.nea.firmament.features.debug.PowerUserTools
|
import moe.nea.firmament.features.debug.PowerUserTools
|
||||||
import moe.nea.firmament.features.diana.DianaWaypoints
|
import moe.nea.firmament.features.diana.DianaWaypoints
|
||||||
import moe.nea.firmament.features.events.anniversity.AnniversaryFeatures
|
import moe.nea.firmament.features.events.anniversity.AnniversaryFeatures
|
||||||
|
import moe.nea.firmament.features.events.carnival.CarnivalFeatures
|
||||||
import moe.nea.firmament.features.fixes.CompatibliltyFeatures
|
import moe.nea.firmament.features.fixes.CompatibliltyFeatures
|
||||||
import moe.nea.firmament.features.fixes.Fixes
|
import moe.nea.firmament.features.fixes.Fixes
|
||||||
import moe.nea.firmament.features.inventory.CraftingOverlay
|
import moe.nea.firmament.features.inventory.CraftingOverlay
|
||||||
@@ -80,6 +81,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
|
|||||||
loadFeature(DianaWaypoints)
|
loadFeature(DianaWaypoints)
|
||||||
loadFeature(ItemRarityCosmetics)
|
loadFeature(ItemRarityCosmetics)
|
||||||
loadFeature(PickaxeAbility)
|
loadFeature(PickaxeAbility)
|
||||||
|
loadFeature(CarnivalFeatures)
|
||||||
if (Firmament.DEBUG) {
|
if (Firmament.DEBUG) {
|
||||||
loadFeature(DeveloperFeatures)
|
loadFeature(DeveloperFeatures)
|
||||||
loadFeature(DebugView)
|
loadFeature(DebugView)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ package moe.nea.firmament.features
|
|||||||
import moe.nea.firmament.events.subscription.SubscriptionOwner
|
import moe.nea.firmament.events.subscription.SubscriptionOwner
|
||||||
import moe.nea.firmament.gui.config.ManagedConfig
|
import moe.nea.firmament.gui.config.ManagedConfig
|
||||||
|
|
||||||
|
// TODO: remove this entire feature system and revamp config
|
||||||
interface FirmamentFeature : SubscriptionOwner {
|
interface FirmamentFeature : SubscriptionOwner {
|
||||||
val identifier: String
|
val identifier: String
|
||||||
val defaultEnabled: Boolean
|
val defaultEnabled: Boolean
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package moe.nea.firmament.features.debug
|
||||||
|
|
||||||
|
import net.minecraft.text.Text
|
||||||
|
import moe.nea.firmament.util.MC
|
||||||
|
|
||||||
|
class DebugLogger(val tag: String) {
|
||||||
|
fun isEnabled() = DeveloperFeatures.isEnabled // TODO: allow filtering by tag
|
||||||
|
fun log(text: () -> String) {
|
||||||
|
if (!isEnabled()) return
|
||||||
|
MC.sendChat(Text.literal(text()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,6 +88,7 @@ object PowerUserTools : FirmamentFeature {
|
|||||||
fun showEntity(target: Entity) {
|
fun showEntity(target: Entity) {
|
||||||
MC.sendChat(Text.translatable("firmament.poweruser.entity.type", target.type))
|
MC.sendChat(Text.translatable("firmament.poweruser.entity.type", target.type))
|
||||||
MC.sendChat(Text.translatable("firmament.poweruser.entity.name", target.name))
|
MC.sendChat(Text.translatable("firmament.poweruser.entity.name", target.name))
|
||||||
|
MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.position", target.pos))
|
||||||
if (target is LivingEntity) {
|
if (target is LivingEntity) {
|
||||||
MC.sendChat(Text.translatable("firmament.poweruser.entity.armor"))
|
MC.sendChat(Text.translatable("firmament.poweruser.entity.armor"))
|
||||||
for (armorItem in target.armorItems) {
|
for (armorItem in target.armorItems) {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
|
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
|
||||||
|
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
@@ -34,4 +35,6 @@ enum class LegacyFormattingCode(val label: String, val char: Char, val index: In
|
|||||||
|
|
||||||
val modern = Formatting.byCode(char)!!
|
val modern = Formatting.byCode(char)!!
|
||||||
|
|
||||||
|
val formattingCode = "§$char"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import net.minecraft.component.DataComponentTypes
|
import net.minecraft.component.DataComponentTypes
|
||||||
|
import net.minecraft.component.type.NbtComponent
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
import net.minecraft.nbt.NbtCompound
|
import net.minecraft.nbt.NbtCompound
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
|
import moe.nea.firmament.repo.set
|
||||||
import moe.nea.firmament.util.json.DashlessUUIDSerializer
|
import moe.nea.firmament.util.json.DashlessUUIDSerializer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,7 +87,14 @@ data class HypixelPetInfo(
|
|||||||
private val jsonparser = Json { ignoreUnknownKeys = true }
|
private val jsonparser = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
val ItemStack.extraAttributes: NbtCompound
|
val ItemStack.extraAttributes: NbtCompound
|
||||||
get() = get(DataComponentTypes.CUSTOM_DATA)?.nbt ?: NbtCompound()
|
get() {
|
||||||
|
val customData = get(DataComponentTypes.CUSTOM_DATA) ?: run {
|
||||||
|
val component = NbtComponent.of(NbtCompound())
|
||||||
|
set(DataComponentTypes.CUSTOM_DATA, component)
|
||||||
|
component
|
||||||
|
}
|
||||||
|
return customData.nbt
|
||||||
|
}
|
||||||
|
|
||||||
val ItemStack.skyblockUUIDString: String?
|
val ItemStack.skyblockUUIDString: String?
|
||||||
get() = extraAttributes.getString("uuid")?.takeIf { it.isNotBlank() }
|
get() = extraAttributes.getString("uuid")?.takeIf { it.isNotBlank() }
|
||||||
@@ -101,6 +110,12 @@ val ItemStack.petData: HypixelPetInfo?
|
|||||||
.getOrElse { return null }
|
.getOrElse { return null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ItemStack.setSkyBlockFirmamentUiId(uiId: String) = setSkyBlockId(SkyblockId("FIRMAMENT_UI_$uiId"))
|
||||||
|
fun ItemStack.setSkyBlockId(skyblockId: SkyblockId): ItemStack {
|
||||||
|
this.extraAttributes["id"] = skyblockId.neuItem
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
val ItemStack.skyBlockId: SkyblockId?
|
val ItemStack.skyBlockId: SkyblockId?
|
||||||
get() {
|
get() {
|
||||||
return when (val id = extraAttributes.getString("id")) {
|
return when (val id = extraAttributes.getString("id")) {
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1")
|
val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1")
|
||||||
|
fun createSkullItem(uuid: UUID, url: String) = ItemStack(Items.PLAYER_HEAD)
|
||||||
|
.also { it.setSkullOwner(uuid, url) }
|
||||||
|
|
||||||
fun ItemStack.setSkullOwner(uuid: UUID, url: String) {
|
fun ItemStack.setSkullOwner(uuid: UUID, url: String) {
|
||||||
assert(this.item == Items.PLAYER_HEAD)
|
assert(this.item == Items.PLAYER_HEAD)
|
||||||
val gameProfile = GameProfile(uuid, "nea89")
|
val gameProfile = GameProfile(uuid, "nea89")
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
-->
|
||||||
|
|
||||||
|
<Root xmlns="http://notenoughupdates.org/moulconfig"
|
||||||
|
>
|
||||||
|
<Center>
|
||||||
|
<Panel background="VANILLA" insets="10">
|
||||||
|
<Column>
|
||||||
|
<Scale scale="2">
|
||||||
|
<Text text="§aFruit§7 Digging"/>
|
||||||
|
</Scale>
|
||||||
|
<Text
|
||||||
|
text="The goal of the fruit digging minigame is to find as many§a fruits§r as possible on a §b7x7§e sand§r grid."
|
||||||
|
width="300"/>
|
||||||
|
<Text
|
||||||
|
text="To do so, you break§e sand blocks§r to reveal what is hidden underneath: a §afruit§r, a §cbomb, or §erum§r."
|
||||||
|
width="300"/>
|
||||||
|
<Text
|
||||||
|
text="When you break a block, you can also get some extra information based on your §7dowsing mode§r."
|
||||||
|
width="300"/>
|
||||||
|
<Scale scale="1.5">
|
||||||
|
<Text text="§7Dowsing Modes"/>
|
||||||
|
</Scale>
|
||||||
|
<Array data="@modes">
|
||||||
|
<Row>
|
||||||
|
<ItemStack value="@itemType"/>
|
||||||
|
<Text text="@feature" width="80"/>
|
||||||
|
<Text text="@description" width="220"/>
|
||||||
|
</Row>
|
||||||
|
</Array>
|
||||||
|
|
||||||
|
<Scale scale="1.5">
|
||||||
|
<Text text="§aTiles"/>
|
||||||
|
</Scale>
|
||||||
|
<ScrollPanel width="300" height="120">
|
||||||
|
<Array data="@pieces">
|
||||||
|
<Row>
|
||||||
|
<Center>
|
||||||
|
<ItemStack value="@getIcon"/>
|
||||||
|
</Center>
|
||||||
|
<Text text="@pieceLabel" width="80"/>
|
||||||
|
<Text text="@description" width="145"/>
|
||||||
|
<Spacer width="5"/>
|
||||||
|
<Text text="@boardLabel" width="50"/>
|
||||||
|
</Row>
|
||||||
|
</Array>
|
||||||
|
</ScrollPanel>
|
||||||
|
</Column>
|
||||||
|
</Panel>
|
||||||
|
</Center>
|
||||||
|
</Root>
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"firmament.poweruser.entity.fail": "No entity found under cursor",
|
"firmament.poweruser.entity.fail": "No entity found under cursor",
|
||||||
"firmament.poweruser.entity.type": "Entity Type: %s",
|
"firmament.poweruser.entity.type": "Entity Type: %s",
|
||||||
"firmament.poweruser.entity.name": "Entity Name: %s",
|
"firmament.poweruser.entity.name": "Entity Name: %s",
|
||||||
|
"firmament.poweruser.entity.position": "Position: %s",
|
||||||
"firmament.poweruser.entity.armor": "Entity Armor:",
|
"firmament.poweruser.entity.armor": "Entity Armor:",
|
||||||
"firmament.poweruser.entity.armor.item": " - %s",
|
"firmament.poweruser.entity.armor.item": " - %s",
|
||||||
"firmament.poweruser.entity.passengers": "%s Passengers",
|
"firmament.poweruser.entity.passengers": "%s Passengers",
|
||||||
@@ -57,6 +58,10 @@
|
|||||||
"firmament.config.auto-completions.warp-complete": "Auto Complete /warp",
|
"firmament.config.auto-completions.warp-complete": "Auto Complete /warp",
|
||||||
"firmament.config.auto-completions.warp-is": "Redirect /warp is to /warp island",
|
"firmament.config.auto-completions.warp-is": "Redirect /warp is to /warp island",
|
||||||
"firmanent.config.edit": "Edit",
|
"firmanent.config.edit": "Edit",
|
||||||
|
"firmament.config.carnival": "Carnival Features",
|
||||||
|
"firmament.config.carnival.bombs-solver": "Bombs Solver",
|
||||||
|
"firmament.config.carnival.tutorials": "Tutorial Reminder",
|
||||||
|
"firmament.carnival.tutorial.minesweeper": "§eClick here to check out Firmaments Tutorial for this minigame!",
|
||||||
"firmament.config.repo": "Firmament Repo Settings",
|
"firmament.config.repo": "Firmament Repo Settings",
|
||||||
"firmament.config.repo.autoUpdate": "Auto Update",
|
"firmament.config.repo.autoUpdate": "Auto Update",
|
||||||
"firmament.config.repo.username": "Repo Username",
|
"firmament.config.repo.username": "Repo Username",
|
||||||
|
|||||||
Reference in New Issue
Block a user