diff --git a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt index 6032d67..5ec44bc 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt @@ -1,5 +1,6 @@ package cn.elytra.mod.bandit.client +import cn.elytra.mod.bandit.common.player_data.VeinMiningNoticeType import cn.elytra.mod.bandit.common.util.HasUnlocalizedName import cn.elytra.mod.bandit.mining.BlockFilterRegistry import cn.elytra.mod.bandit.mining.ExecutorGeneratorRegistry @@ -24,6 +25,31 @@ object VeinMiningHUD { private val executors: CircleList> = ExecutorGeneratorCircleList private val blockFilters: CircleList> = BlockFilterCircleList + /** + * Represents a HUD (Heads-Up Display) notification message. + * + * HUD notices are time-based display elements that can fade in/out and contain + * various types of game notifications. + * + * @property id Unique identifier for tracking and stopping specific notices. + * @property noticeType Type of notice. Use predefined constants for specific notice types by [VeinMiningNoticeType]. + * @property startTick The fade-out animation start tick. + * @property fadeDelay Number of ticks to wait before starting the fade-out animation. + * @property fadeTicks Duration (in ticks) of the fade-out animation. + * @property extraData Additional structured data for notice-specific information. + * @property isEnded Flag indicating whether this notice is about to end. + */ + data class HudNotice( + val id: Long, + var noticeType: Int = 0, + var startTick: Long, + var fadeDelay: Int = 0, + var fadeTicks: Int = 20, + var extraData: Map = emptyMap(), + var isEnded: Boolean = false + ) + private val notices = mutableListOf() + /** * Reference to the active menu, used to either show the menu and handle the mouse scroll inputs. */ @@ -78,6 +104,107 @@ object VeinMiningHUD { } } + internal fun postNotice( + noticeType: Int, + id: Long, + extraData: Map = emptyMap(), + fadeDelay: Int = 0, + fadeTicks: Int = 0 + ): Long { + val isEnd = !(fadeDelay == 0 && fadeTicks == 0) + var now: Long = 0 + if (isEnd) { + now = Minecraft.getMinecraft().theWorld.totalWorldTime + } + notices += HudNotice( + id = id, + noticeType = noticeType, + extraData = extraData, + startTick = now, + fadeDelay = fadeDelay, + fadeTicks = fadeTicks, + isEnded = isEnd + ) + VeinMiningHandlerClient.noticeActive = true + return id + } + + internal fun cancelNotice(id: Long, fadeDelay: Int = 0, fadeTicks: Int = 20) { + if (fadeTicks == 0 && fadeDelay == 0) { + notices.removeIf { it.id == id } + return + } + val now = Minecraft.getMinecraft().theWorld.totalWorldTime + notices.find { it.id == id && !it.isEnded }?.apply { + isEnded = true + startTick = now + this.fadeDelay = fadeDelay + this.fadeTicks = fadeTicks + } + } + + private fun getActiveNotices(): List> { + val now = Minecraft.getMinecraft().theWorld.totalWorldTime + val iter = notices.iterator() + val result = mutableListOf>() + + while (iter.hasNext()) { + val n = iter.next() + val age = (now - n.startTick).toInt() + + val alpha = when { + !n.isEnded -> 1f + age <= n.fadeDelay -> 1f + age < n.fadeDelay + n.fadeTicks -> 1f - (age - n.fadeDelay) / n.fadeTicks.toFloat() + else -> { + iter.remove() + continue + } + } + + result += n to alpha + } + + if (result.isEmpty()) VeinMiningHandlerClient.noticeActive = false + + return result + } + + internal fun renderNotice() { + val mc = Minecraft.getMinecraft() + if(mc.currentScreen == null && mc.theWorld != null) { + glDisable(GL_RESCALE_NORMAL) + glDisable(GL_LIGHTING) + + glDisable(GL_DEPTH_TEST) + + var y = marginY + 45 // list height + font height + for ((notice, alpha) in getActiveNotices()) { + val color = (alpha * 255).toInt() shl 24 or 0xFFFFFF + val text = when(VeinMiningNoticeType.fromId(notice.noticeType)) { + VeinMiningNoticeType.TASK_STARTING -> I18n.format("bandit.message.task-starting") + VeinMiningNoticeType.TASK_HALT_HINT -> I18n.format("bandit.message.task-halt-hint") + VeinMiningNoticeType.TASK_STOP_KEY_RELEASE -> I18n.format("bandit.message.task-stop.key-release") + VeinMiningNoticeType.TASK_STOP_FOR_COMMAND -> I18n.format("bandit.message.task-stop.for-command") + VeinMiningNoticeType.TASK_DONE -> { + val statBlocksMined = notice.extraData["statBlocksMined"] ?: 0 + val statItemDropped = notice.extraData["statItemDropped"] ?: 0 + I18n.format( + "bandit.message.task-done", + statBlocksMined, + statItemDropped + ) + } + null -> "" + } + drawStringWithMargin(text, marginX, y, color) + y += 9 // font height + } + + glEnable(GL_RESCALE_NORMAL) + } + } + private fun getPosXWithMargin(margin: Int, str: String): Int { val mc = Minecraft.getMinecraft() val textRender = mc.fontRenderer diff --git a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHandlerClient.kt b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHandlerClient.kt index 0ba8604..c04e7b5 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHandlerClient.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHandlerClient.kt @@ -43,6 +43,7 @@ object VeinMiningHandlerClient { var veinMiningBlockFilterId = 0 var keyPressed = false + var noticeActive = false val statusKey = KeyBinding("keybinding.bandit.trigger", Keyboard.KEY_NONE, "keybinding.bandit.category") @@ -101,6 +102,9 @@ object VeinMiningHandlerClient { if(keyPressed) { VeinMiningHUD.render() } + if(noticeActive) { + VeinMiningHUD.renderNotice() + } } @JvmStatic diff --git a/src/main/kotlin/cn/elytra/mod/bandit/common/command/BanditCommand.kt b/src/main/kotlin/cn/elytra/mod/bandit/common/command/BanditCommand.kt index ee84a85..4f6719f 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/common/command/BanditCommand.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/common/command/BanditCommand.kt @@ -6,6 +6,7 @@ import cn.elytra.mod.bandit.common.player_data.veinMiningData import cn.elytra.mod.bandit.common.util.parseValueToEnum import cn.elytra.mod.bandit.mining.BlockFilterRegistry import cn.elytra.mod.bandit.mining.ExecutorGeneratorRegistry +import cn.elytra.mod.bandit.mining.exception.CommandCancellation import net.minecraft.command.CommandBase import net.minecraft.command.ICommandSender import net.minecraft.entity.player.EntityPlayerMP @@ -35,6 +36,56 @@ object BanditCommand : CommandBase() { } } + override fun addTabCompletionOptions(sender: ICommandSender, args: Array): List? { + if (args.isEmpty()) return super.addTabCompletionOptions(sender, args) + + val argsList = args.toMutableList() + + return when (argsList.removeFirstOrNull()) { + null, "" -> getListOfStringsMatchingLastWord( + args, + "stop", + "help", + "drop_pos", + "drop_timing", + "stop_on_release", + "block-filter", + "executor-generator" + ) + + "executor-generator", "executor", "block-filter", "filter" -> { + // TODO: add support to names and update this completion + emptyList() + } + + "drop_pos" -> { + getListOfStringsMatchingLastWord( + args, + *getValidEnumValues().toTypedArray() + ) + } + + "drop_timing" -> { + getListOfStringsMatchingLastWord( + args, + *getValidEnumValues().toTypedArray() + ) + } + + "stop_on_release" -> { + getListOfStringsMatchingLastWord( + args, + "true", + "false" + ) + } + + "stop", "help" -> emptyList() + + else -> super.addTabCompletionOptions(sender, args) + } + } + private fun ICommandSender.withEntityPlayer(block: (EntityPlayerMP) -> Unit) { if(this is EntityPlayerMP) { block(this) @@ -118,7 +169,7 @@ object BanditCommand : CommandBase() { private fun handleHaltRequest(sender: ICommandSender) { sender.withEntityPlayer { p -> - p.veinMiningData.stopJob("stop command") + p.veinMiningData.cancelJob(CommandCancellation()) } } diff --git a/src/main/kotlin/cn/elytra/mod/bandit/common/listener/VeinMiningEventListener.kt b/src/main/kotlin/cn/elytra/mod/bandit/common/listener/VeinMiningEventListener.kt index 140d69f..802ef16 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/common/listener/VeinMiningEventListener.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/common/listener/VeinMiningEventListener.kt @@ -2,6 +2,7 @@ package cn.elytra.mod.bandit.common.listener import cn.elytra.mod.bandit.common.player_data.veinMiningData import cn.elytra.mod.bandit.mining.HarvestCollector +import cn.elytra.mod.bandit.mining.exception.PlayerLeftCancellation import cn.elytra.mod.bandit.network.BanditNetwork import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber import cpw.mods.fml.common.eventhandler.SubscribeEvent @@ -100,7 +101,7 @@ class VeinMiningEventListener { @JvmStatic @SubscribeEvent fun onPlayerLeave(e: PlayerEvent.PlayerLoggedOutEvent) { - e.player.veinMiningData.stopAndClear() + e.player.veinMiningData.stopAndClear(PlayerLeftCancellation()) } } } diff --git a/src/main/kotlin/cn/elytra/mod/bandit/common/player_data/VeinMiningNoticeType.kt b/src/main/kotlin/cn/elytra/mod/bandit/common/player_data/VeinMiningNoticeType.kt new file mode 100644 index 0000000..c7991d9 --- /dev/null +++ b/src/main/kotlin/cn/elytra/mod/bandit/common/player_data/VeinMiningNoticeType.kt @@ -0,0 +1,52 @@ +package cn.elytra.mod.bandit.common.player_data + +/** + * Vein mining notification types. + * + * Enumerates different states and events in the vein mining process + * that can be displayed as HUD notifications to the player. + */ +enum class VeinMiningNoticeType(val id: Int) { + /** + * Mining task has started. + * Triggered when player initiates a vein mining sequence. + */ + TASK_STARTING(1), + + /** + * Hint that the task stopped. + * Indicates the player can voluntarily end the mining chain. + */ + TASK_HALT_HINT(2), + + /** + * Task stopped due to key release. + * Mining chain was terminated because the player released the activation key. + */ + TASK_STOP_KEY_RELEASE(3), + + /** + * Task stopped by command. + * Mining chain was terminated via a game command or external trigger. + */ + TASK_STOP_FOR_COMMAND(4), + + /** + * Task completed successfully. + * Mining chain finished naturally and requires statistical processing. + */ + TASK_DONE(5); + + companion object { + /** + * Retrieves a [VeinMiningNoticeType] by its numeric identifier. + * + * @param id The numeric identifier of the notice type. + * @return The corresponding [VeinMiningNoticeType], or `null` if no match is found. + */ + fun fromId(id: Int): VeinMiningNoticeType? { + return entries.firstOrNull { it.id == id } + } + } +} + diff --git a/src/main/kotlin/cn/elytra/mod/bandit/common/player_data/VeinMiningPlayerData.kt b/src/main/kotlin/cn/elytra/mod/bandit/common/player_data/VeinMiningPlayerData.kt index 0319404..2c43a34 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/common/player_data/VeinMiningPlayerData.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/common/player_data/VeinMiningPlayerData.kt @@ -10,8 +10,10 @@ import cn.elytra.mod.bandit.common.mining.VeinMiningHandler import cn.elytra.mod.bandit.common.util.parseValueToEnum import cn.elytra.mod.bandit.mining.BlockFilterRegistry import cn.elytra.mod.bandit.mining.ExecutorGeneratorRegistry +import cn.elytra.mod.bandit.mining.exception.CommandCancellation import cn.elytra.mod.bandit.mining.exception.FriendlyCancellationException import cn.elytra.mod.bandit.mining.exception.KeyReleaseCancellation +import cn.elytra.mod.bandit.mining.exception.PlayerLeftCancellation import cn.elytra.mod.bandit.mining.executor.BlockPosCacheableExecutorGenerator import cn.elytra.mod.bandit.mining.executor.VeinMiningExecutorGenerator import cn.elytra.mod.bandit.mining.filter.VeinMiningBlockFilter @@ -26,7 +28,6 @@ import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.nbt.NBTTagCompound import net.minecraft.server.MinecraftServer -import net.minecraft.util.ChatComponentTranslation import net.minecraft.util.MovingObjectPosition import java.util.* import kotlin.coroutines.cancellation.CancellationException @@ -243,7 +244,7 @@ data class VeinMiningPlayerData( BanditMod.logger.info("Cached #${executionId}") if(it != null) { when(it) { - is KeyReleaseCancellation -> { + is KeyReleaseCancellation, is CommandCancellation, is PlayerLeftCancellation -> { /* ignored */ } @@ -297,8 +298,9 @@ data class VeinMiningPlayerData( BanditMod.logger.info("Executing Vein Mining #${executionId}") BanditMod.logger.debug("Executing Vein Mining #${executionId} at (${pos.x}, ${pos.y}, ${pos.z} @ ${world.provider.dimensionId}) ref ${blockAndMeta.first.unlocalizedName} @ ${blockAndMeta.second} te ${blockTileEntity?.toString() ?: "null"}") - player.addChatMessage(ChatComponentTranslation("bandit.message.task-starting")) - player.addChatMessage(ChatComponentTranslation("bandit.message.task-halt-hint")) + val notices = mutableListOf() + notices.add(BanditNetwork.pushSimpleNoticeToClient(player.asMP, VeinMiningNoticeType.TASK_STARTING)) + notices.add(BanditNetwork.pushSimpleNoticeToClient(player.asMP, VeinMiningNoticeType.TASK_HALT_HINT)) val job = BanditCoroutines.VeinMiningScope.launch(start = CoroutineStart.LAZY) { world.playSoundEffect(player.posX, player.posY, player.posZ, "note.harp", 3.0F, 1.0F) @@ -306,22 +308,45 @@ data class VeinMiningPlayerData( } job.invokeOnCompletion { BanditMod.logger.info("VeinMining #${executionId} has ended") - if(it != null) { - when(it) { - is KeyReleaseCancellation -> { - getPlayer().addChatMessage(ChatComponentTranslation("bandit.message.task-stop.key-release")) - } - else -> BanditMod.logger.warn("Job was cancelled because of an throwable", it) + for (noticeId in notices) { + BanditNetwork.endNoticeToClient(player.asMP, noticeId, fadeDelay = 0, fadeTicks = 0) + } + + when(it) { + is KeyReleaseCancellation -> { + BanditNetwork.pushSimpleNoticeToClient(player.asMP, + noticeType = VeinMiningNoticeType.TASK_STOP_KEY_RELEASE, + fadeDelay = 10, + fadeTicks = 20 + ) + } + + is CommandCancellation -> { + BanditNetwork.pushSimpleNoticeToClient(player.asMP, + noticeType = VeinMiningNoticeType.TASK_STOP_FOR_COMMAND, + fadeDelay = 10, + fadeTicks = 20 + ) } + + is PlayerLeftCancellation -> { + BanditMod.logger.info("VeinMining #${executionId} has ended because the player left") + } + + else -> BanditMod.logger.warn("Job was cancelled because of an throwable", it) } - player.addChatMessage( - ChatComponentTranslation( - "bandit.message.task-done", - context.statBlocksMined.get(), - context.statItemDropped.values.sum() - ) + + BanditNetwork.pushCompletionNoticeToClient( + player.asMP, + extraData = mapOf( + "statBlocksMined" to context.statBlocksMined.get(), + "statItemDropped" to context.statItemDropped.values.sum() + ), + fadeDelay = 40, + fadeTicks = 20 ) + // play sound effects world.playSoundEffect(player.posX, player.posY, player.posZ, "note.harp", 3.0F, 3.5F) // clear caches @@ -347,8 +372,13 @@ data class VeinMiningPlayerData( currentJob?.job?.cancel(cause) } - fun stopAndClear() { - stopJob() + fun stopAndClear(cause: CancellationException? = null) { + if (cause != null) { + cancelJob(cause) + } + else{ + stopJob() + } veinMiningKeyPressed = false InstanceMap -= this.uuid } diff --git a/src/main/kotlin/cn/elytra/mod/bandit/mining/exception/FriendlyCancellationException.kt b/src/main/kotlin/cn/elytra/mod/bandit/mining/exception/FriendlyCancellationException.kt index e6ffed7..6409997 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/mining/exception/FriendlyCancellationException.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/mining/exception/FriendlyCancellationException.kt @@ -15,3 +15,13 @@ sealed class FriendlyCancellationException : CancellationException { * The exception represents the vein-mining task was cancelled by the player because they released the key while the task is executing. */ class KeyReleaseCancellation : FriendlyCancellationException("Cancelled because the vein-mining key was released") + +/** + * The exception represents the vein-mining task was cancelled by the player because they typed the command + */ +class CommandCancellation : FriendlyCancellationException("Cancelled because of the stop command") + +/** + * The exception represents the vein-mining task was cancelled by the player because the player left + */ +class PlayerLeftCancellation : FriendlyCancellationException("Cancelled because the player left") diff --git a/src/main/kotlin/cn/elytra/mod/bandit/mining/executor/LargeScanExecutorGenerator.kt b/src/main/kotlin/cn/elytra/mod/bandit/mining/executor/LargeScanExecutorGenerator.kt index 795d4ea..6057056 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/mining/executor/LargeScanExecutorGenerator.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/mining/executor/LargeScanExecutorGenerator.kt @@ -5,7 +5,9 @@ import cn.elytra.mod.bandit.common.mining.VeinMiningContext import com.gtnewhorizon.gtnhlib.blockpos.BlockPos import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.withContext +import kotlinx.coroutines.flow.flowOn +import kotlin.math.max +import kotlin.math.min class LargeScanExecutorGenerator( private val radiusXZ: Int, @@ -22,18 +24,16 @@ class LargeScanExecutorGenerator( for (x in x - radiusXZ until x + radiusXZ) { for (z in z - radiusXZ until z + radiusXZ) { // y iteration reversed from up to down - for (y in y + radiusY downTo y - radiusY) { - // FIX: the block can possibly not loaded, causing a CME when not loading it in the server thread. - // this may impact on the performance of executing large-scan. - withContext(BanditCoroutines.ServerThreadDispatcher) { - if (isBlockMatchingVein(context, BlockPos(x, y, z))) { - emit(BlockPos(x, y, z)) - } + for (y in max((y + radiusY), 256) downTo min((y - radiusY), 0)) { + if (isBlockMatchingVein(context, BlockPos(x, y, z))) { + emit(BlockPos(x, y, z)) } } } } - } + // FIX: the block can possibly not loaded, causing a CME when not loading it in the server thread. + // this may impact on the performance of executing large-scan. + }.flowOn(BanditCoroutines.ServerThreadDispatcher) override fun getUnlocalizedName(): String = "bandit.executor.large-scan" } diff --git a/src/main/kotlin/cn/elytra/mod/bandit/network/BanditNetwork.kt b/src/main/kotlin/cn/elytra/mod/bandit/network/BanditNetwork.kt index 507b6ec..6a60504 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/network/BanditNetwork.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/network/BanditNetwork.kt @@ -1,6 +1,7 @@ package cn.elytra.mod.bandit.network import cn.elytra.mod.bandit.BanditMod +import cn.elytra.mod.bandit.common.player_data.VeinMiningNoticeType import com.gtnewhorizon.gtnhlib.blockpos.BlockPos import cpw.mods.fml.common.event.FMLPreInitializationEvent import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper @@ -35,6 +36,13 @@ object BanditNetwork { 3, Side.CLIENT ) + + networkW.registerMessage( + S2CNoticePacket.Handler::class.java, + S2CNoticePacket::class.java, + 4, + Side.CLIENT + ) } fun syncStatusToServer(status: Boolean) { @@ -66,4 +74,108 @@ object BanditNetwork { fun syncBlockCacheToClient(p: EntityPlayerMP, posList: List?) { networkW.sendTo(S2CSelectedBlockCachePacket().apply { this.posList = posList ?: emptyList() }, p) } + + /** + * Sends a simple notification to the client. + * + * Used for: task starting, stop hint, and key-release stop notifications. + * If fadeTicks or fadeDelay have not 0 value, this notification automatically fades out + * and does not require manual termination. + * Otherwise, notifications require manual termination via [endNoticeToClient]. + * + * @param p The target player to receive the notification. + * @param noticeType The type of notification to display. + * @param fadeDelay Ticks to wait before starting fade-out animation (0 for immediate fade). + * @param fadeTicks Duration of fade-out animation in ticks. + * @return Unique notice ID that can be used to terminate this notification later. + * + * @see endNoticeToClient for terminating notifications. + * @see VeinMiningNoticeType for available notification types. + */ + fun pushSimpleNoticeToClient( + p: EntityPlayerMP, + noticeType: VeinMiningNoticeType, + fadeDelay: Int = 0, + fadeTicks: Int = 0 + ): Long { + val noticeId = System.nanoTime() + networkW.sendTo( + S2CNoticePacket().apply { + action = S2CNoticePacket.NoticeAction.PUSH + this.noticeId = noticeId + this.noticeType = noticeType.id + this.extraData = emptyMap() + this.fadeDelay = fadeDelay + this.fadeTicks = fadeTicks + }, + p + ) + return noticeId + } + + /** + * Sends a task completion notification to the client. + * + * Used for: task completion with statistical data. + * If fadeTicks or fadeDelay have not 0 value, this notification automatically fades out + * and does not require manual termination. + * Otherwise, notifications require manual termination via [endNoticeToClient]. + * + * @param p The target player to receive the notification. + * @param extraData Statistical data to display with the completion notification. + * Contains metrics like blocks mined, experience gained, etc. + * @param fadeDelay Ticks to wait before starting fade-out animation. + * @param fadeTicks Duration of fade-out animation in ticks. + * @return Unique notice ID (primarily for logging/tracking purposes). + */ + fun pushCompletionNoticeToClient( + p: EntityPlayerMP, + extraData: Map, + fadeDelay: Int = 40, + fadeTicks: Int = 20 + ): Long { + val noticeId = System.nanoTime() + networkW.sendTo( + S2CNoticePacket().apply { + action = S2CNoticePacket.NoticeAction.PUSH + this.noticeId = noticeId + this.noticeType = VeinMiningNoticeType.TASK_DONE.id + this.extraData = extraData + this.fadeDelay = fadeDelay + this.fadeTicks = fadeTicks + }, + p + ) + return noticeId + } + + /** + * Terminates a previously sent notification on the client. + * + * This method stops the rendering of an ongoing notification with a fade-out animation. + * Should be called for notifications created with [pushSimpleNoticeToClient]. + * + * @param p The target player whose notification should be terminated. + * @param noticeId The unique ID of the notification to terminate. + * @param fadeDelay Ticks to wait before starting the termination fade-out. + * @param fadeTicks Duration of termination fade-out animation in ticks. + * + * @throws NoSuchElementException if no notification with the given ID exists (handled client-side). + */ + fun endNoticeToClient( + p: EntityPlayerMP, + noticeId: Long, + fadeDelay: Int = 0, + fadeTicks: Int = 0 + ) { + networkW.sendTo( + S2CNoticePacket().apply { + action = S2CNoticePacket.NoticeAction.END + this.noticeId = noticeId + this.fadeDelay = fadeDelay + this.fadeTicks = fadeTicks + }, + p + ) + } } diff --git a/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt b/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt new file mode 100644 index 0000000..cedcdb2 --- /dev/null +++ b/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt @@ -0,0 +1,94 @@ +package cn.elytra.mod.bandit.network + +import cn.elytra.mod.bandit.client.VeinMiningHUD +import cpw.mods.fml.common.network.simpleimpl.IMessage +import cpw.mods.fml.common.network.simpleimpl.IMessageHandler +import cpw.mods.fml.common.network.simpleimpl.MessageContext +import io.netty.buffer.ByteBuf + +class S2CNoticePacket : IMessage { + + enum class NoticeAction { + PUSH, + END + } + + var action: NoticeAction = NoticeAction.PUSH + var noticeId: Long = 0 + var noticeType: Int = 0 + var extraData: Map = emptyMap() + var fadeDelay: Int = 0 + var fadeTicks: Int = 20 + + override fun fromBytes(buf: ByteBuf) { + action = NoticeAction.entries.toTypedArray()[buf.readByte().toInt()] + noticeId = buf.readLong() + noticeType = buf.readInt() + + val extraDataSize = buf.readInt() + extraData = if (extraDataSize > 0) { + buildMap { + repeat(extraDataSize) { + val keyLength = buf.readInt() + val keyBytes = ByteArray(keyLength) + buf.readBytes(keyBytes) + val key = String(keyBytes, Charsets.UTF_8) + val value = buf.readInt() + put(key, value) + } + } + } else { + emptyMap() + } + + fadeDelay = buf.readInt() + fadeTicks = buf.readInt() + } + + override fun toBytes(buf: ByteBuf) { + buf.writeByte(action.ordinal) + buf.writeLong(noticeId) + buf.writeInt(noticeType) + + buf.writeInt(extraData.size) + if (extraData.isNotEmpty()) { + extraData.forEach { (key, value) -> + val keyBytes = key.toByteArray(Charsets.UTF_8) + buf.writeInt(keyBytes.size) + buf.writeBytes(keyBytes) + buf.writeInt(value) + } + } + + buf.writeInt(fadeDelay) + buf.writeInt(fadeTicks) + } + + class Handler : IMessageHandler { + override fun onMessage( + message: S2CNoticePacket, + ctx: MessageContext, + ): IMessage? { + when (message.action) { + NoticeAction.PUSH -> { + VeinMiningHUD.postNotice( + message.noticeType, + message.noticeId, + message.extraData, + message.fadeDelay, + message.fadeTicks + ) + } + NoticeAction.END -> { + VeinMiningHUD.cancelNotice( + message.noticeId, + message.fadeDelay, + message.fadeTicks + ) + } + } + return null + } + } +} + diff --git a/src/main/resources/assets/bandit/lang/en_US.lang b/src/main/resources/assets/bandit/lang/en_US.lang index 68c77cf..79d7bfe 100644 --- a/src/main/resources/assets/bandit/lang/en_US.lang +++ b/src/main/resources/assets/bandit/lang/en_US.lang @@ -12,6 +12,7 @@ bandit.message.task-starting=§aStarting Vein Mining task bandit.message.task-halt-hint=§aYou can type §6/bandit stop §ato halt a ongoing task bandit.message.task-done=§aVein Mining done! §7Harvested %s §7blocks, and dropped %s §7items. bandit.message.task-stop.key-release=§aVein Mining stopped because the key was released. +bandit.message.task-stop.for-command=§aVein Mining stopped because the command. bandit.message.sync-from-server=§7Updated your settings to %s §7and %s diff --git a/src/main/resources/assets/bandit/lang/zh_CN.lang b/src/main/resources/assets/bandit/lang/zh_CN.lang index 22a629b..bfcbf04 100644 --- a/src/main/resources/assets/bandit/lang/zh_CN.lang +++ b/src/main/resources/assets/bandit/lang/zh_CN.lang @@ -12,6 +12,7 @@ bandit.message.task-starting=§a开始连锁挖掘任务 bandit.message.task-halt-hint=§a你可以输入 §6/bandit stop §a来停止正在执行的任务 bandit.message.task-done=§a连锁挖掘完成! §7连锁了 %s §7个方块, 获得 %s §7个掉落物. bandit.message.task-stop.key-release=§a按键已释放,连锁挖掘已停止. +bandit.message.task-stop.for-command=§a指令已生效,连锁挖掘已停止. bandit.message.sync-from-server=§7更新设置至 %s §7和 %s @@ -51,5 +52,5 @@ bandit.drop_timing.immediately=开采时掉落 bandit.drop_timing.item_immediately=物品开采时掉落,经验球在连锁完成合并后掉落 bandit.drop_timing.eventually=在连锁完成合并后掉落 -keybinding.bandit.category=Bandit Vein Mining -keybinding.bandit.trigger=连锁挖掘触发器 +keybinding.bandit.category=Bandit 连锁挖掘 +keybinding.bandit.trigger=连锁挖掘