From fcb1a4c634b5102d6343f690d9f9c73085db043d Mon Sep 17 00:00:00 2001 From: Pink YuDeer Date: Sun, 27 Jul 2025 20:31:48 +0800 Subject: [PATCH 01/19] Localization of zh CN --- src/main/resources/assets/bandit/lang/zh_CN | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/resources/assets/bandit/lang/zh_CN diff --git a/src/main/resources/assets/bandit/lang/zh_CN b/src/main/resources/assets/bandit/lang/zh_CN new file mode 100644 index 0000000..5463f62 --- /dev/null +++ b/src/main/resources/assets/bandit/lang/zh_CN @@ -0,0 +1,51 @@ +bandit.executor.manhattan=曼哈顿(仅直接相连) +bandit.executor.manhattan-plus=曼哈顿+(周边3x3搜索) +bandit.executor.manhattan-large=曼哈顿-L(扩大范围的曼哈顿+) +bandit.executor.large-scan=大范围扫描(范围内所有) + +bandit.block-filter.all=匹配所有 +bandit.block-filter.none=无 +bandit.block-filter.match-block=仅匹配方块 +bandit.block-filter.match-block-and-meta=匹配方块和Meta + +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.sync-from-server=§7更新设置至 %s §7和 %s + +bandit.message.activating=§a已激活 +bandit.message.precalculated=§3连锁数: %s + +command.bandit.usage=/bandit help +command.bandit.help.0=§6/bandit executor [执行器Id] - §7列出或设置执行器 +command.bandit.help.1=§6/bandit filter [方块过滤器Id] - §7列出或设置方块过滤器 +command.bandit.help.2=§6/bandit drop_pos [掉落物出现位置] - §7列出或设置掉落物出现位置 +command.bandit.help.3=§6/bandit drop_timing [掉落物出现时机] - §7列出或设置掉落物出现时机 +command.bandit.executor.current=您当前正在使用 %s +command.bandit.executor.list=可能的执行器: +command.bandit.executor.list.entry=%s %s +command.bandit.executor.set.ok=OK! +command.bandit.executor.set.fail=失败了! 可能这是个无效的ID. +command.bandit.filter.current=您当前正在使用 %s +command.bandit.filter.list=可能的过滤器: +command.bandit.filter.list.entry=%s %s +command.bandit.filter.set.ok=OK! +command.bandit.filter.set.fail=失败了! 可能这是个无效的ID. +command.bandit.drop_pos=当前 %s, 有效的值是 %s +command.bandit.drop_pos.ok=OK, 新的值是 %s! +command.bandit.drop_pos.invalid_argument=无效 %s, 有效的值是 %s +command.bandit.drop_timing=当前 %s, 有效的值是 %s +command.bandit.drop_timing.ok=OK, 新的值是 %s! +command.bandit.drop_timing.invalid_argument=Invalid %s, 有效的值是 %s + + +bandit.drop_pos.start=掉落在起点 +bandit.drop_pos.player=掉落在玩家处 + +bandit.drop_timing.immediately=立刻完成 +bandit.drop_timing.item_immediately=物品立刻完成,经验球延时完成 +bandit.drop_timing.eventually=延时完成 + +keybinding.bandit.category=Bandit Vein Mining +keybinding.bandit.trigger=连锁挖掘触发器 From 1a770b16cf68a27dd51999454abc17b0aefdf24e Mon Sep 17 00:00:00 2001 From: Pink YuDeer Date: Sun, 27 Jul 2025 20:32:20 +0800 Subject: [PATCH 02/19] zh_CN.lang --- src/main/resources/assets/bandit/lang/zh_CN | 51 ------------------- .../resources/assets/bandit/lang/zh_CN.lang | 2 +- 2 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 src/main/resources/assets/bandit/lang/zh_CN diff --git a/src/main/resources/assets/bandit/lang/zh_CN b/src/main/resources/assets/bandit/lang/zh_CN deleted file mode 100644 index 5463f62..0000000 --- a/src/main/resources/assets/bandit/lang/zh_CN +++ /dev/null @@ -1,51 +0,0 @@ -bandit.executor.manhattan=曼哈顿(仅直接相连) -bandit.executor.manhattan-plus=曼哈顿+(周边3x3搜索) -bandit.executor.manhattan-large=曼哈顿-L(扩大范围的曼哈顿+) -bandit.executor.large-scan=大范围扫描(范围内所有) - -bandit.block-filter.all=匹配所有 -bandit.block-filter.none=无 -bandit.block-filter.match-block=仅匹配方块 -bandit.block-filter.match-block-and-meta=匹配方块和Meta - -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.sync-from-server=§7更新设置至 %s §7和 %s - -bandit.message.activating=§a已激活 -bandit.message.precalculated=§3连锁数: %s - -command.bandit.usage=/bandit help -command.bandit.help.0=§6/bandit executor [执行器Id] - §7列出或设置执行器 -command.bandit.help.1=§6/bandit filter [方块过滤器Id] - §7列出或设置方块过滤器 -command.bandit.help.2=§6/bandit drop_pos [掉落物出现位置] - §7列出或设置掉落物出现位置 -command.bandit.help.3=§6/bandit drop_timing [掉落物出现时机] - §7列出或设置掉落物出现时机 -command.bandit.executor.current=您当前正在使用 %s -command.bandit.executor.list=可能的执行器: -command.bandit.executor.list.entry=%s %s -command.bandit.executor.set.ok=OK! -command.bandit.executor.set.fail=失败了! 可能这是个无效的ID. -command.bandit.filter.current=您当前正在使用 %s -command.bandit.filter.list=可能的过滤器: -command.bandit.filter.list.entry=%s %s -command.bandit.filter.set.ok=OK! -command.bandit.filter.set.fail=失败了! 可能这是个无效的ID. -command.bandit.drop_pos=当前 %s, 有效的值是 %s -command.bandit.drop_pos.ok=OK, 新的值是 %s! -command.bandit.drop_pos.invalid_argument=无效 %s, 有效的值是 %s -command.bandit.drop_timing=当前 %s, 有效的值是 %s -command.bandit.drop_timing.ok=OK, 新的值是 %s! -command.bandit.drop_timing.invalid_argument=Invalid %s, 有效的值是 %s - - -bandit.drop_pos.start=掉落在起点 -bandit.drop_pos.player=掉落在玩家处 - -bandit.drop_timing.immediately=立刻完成 -bandit.drop_timing.item_immediately=物品立刻完成,经验球延时完成 -bandit.drop_timing.eventually=延时完成 - -keybinding.bandit.category=Bandit Vein Mining -keybinding.bandit.trigger=连锁挖掘触发器 diff --git a/src/main/resources/assets/bandit/lang/zh_CN.lang b/src/main/resources/assets/bandit/lang/zh_CN.lang index 22a629b..d9b7cb0 100644 --- a/src/main/resources/assets/bandit/lang/zh_CN.lang +++ b/src/main/resources/assets/bandit/lang/zh_CN.lang @@ -52,4 +52,4 @@ bandit.drop_timing.item_immediately=物品开采时掉落,经验球在连锁 bandit.drop_timing.eventually=在连锁完成合并后掉落 keybinding.bandit.category=Bandit Vein Mining -keybinding.bandit.trigger=连锁挖掘触发器 +keybinding.bandit.trigger=连锁挖掘键 From 8921fc4745b846e5b16a57ba0e7662346c8e049d Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sat, 24 Jan 2026 18:08:36 +0800 Subject: [PATCH 03/19] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=B1=89=E5=8C=96?= =?UTF-8?q?=E3=80=81=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=A1=A5=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bandit/common/command/BanditCommand.kt | 22 +++++++++++++++++++ .../resources/assets/bandit/lang/zh_CN.lang | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) 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..d4a5c9a 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 @@ -35,6 +35,28 @@ object BanditCommand : CommandBase() { } } + override fun addTabCompletionOptions(sender: ICommandSender?, args: Array?): List? { + val list = args?.toMutableList() + if (list != null && list.size == 1) { + return getListOfStringsMatchingLastWord( + args, + *listOf( + "help", + "executor", + "executor-generator", + "filter", + "block-filter", + "stop", + "halt", + "drop_pos", + "drop_timing", + "stop_on_release" + ).toTypedArray() + ) + } + return super.addTabCompletionOptions(sender, args) + } + private fun ICommandSender.withEntityPlayer(block: (EntityPlayerMP) -> Unit) { if(this is EntityPlayerMP) { block(this) diff --git a/src/main/resources/assets/bandit/lang/zh_CN.lang b/src/main/resources/assets/bandit/lang/zh_CN.lang index d9b7cb0..de9dcc3 100644 --- a/src/main/resources/assets/bandit/lang/zh_CN.lang +++ b/src/main/resources/assets/bandit/lang/zh_CN.lang @@ -51,5 +51,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=连锁挖掘 From 94774631aaf513c0faf7a21d2bc2dee6cdbee71a Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sat, 24 Jan 2026 19:28:21 +0800 Subject: [PATCH 04/19] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E7=9A=84=E4=BD=8D=E7=BD=AE=EF=BC=8C=E5=87=8F=E5=B0=91=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E6=A1=86=E4=B8=AD=E7=9A=84=E8=B6=85=E7=BA=A7=E5=88=B7?= =?UTF-8?q?=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../elytra/mod/bandit/client/VeinMiningHUD.kt | 79 +++++++++++++++++++ .../bandit/client/VeinMiningHandlerClient.kt | 4 + .../bandit/common/command/BanditCommand.kt | 3 - .../player_data/VeinMiningPlayerData.kt | 21 ++--- 4 files changed, 95 insertions(+), 12 deletions(-) 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..ec4e8ba 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt @@ -24,6 +24,16 @@ object VeinMiningHUD { private val executors: CircleList> = ExecutorGeneratorCircleList private val blockFilters: CircleList> = BlockFilterCircleList + data class HudNotice( + val id: Long, // 唯一 ID + val text: String, + var startTick: Long, + var fadeDelay: Int = 0, // 延迟渐隐 tick + var fadeTicks: Int = 20, // 渐隐时长 + 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 +88,75 @@ object VeinMiningHUD { } } + fun pushNotice(text: String): Long { + val id = System.nanoTime() + notices += HudNotice( + id = id, + text = text, + startTick = Minecraft.getMinecraft().theWorld.totalWorldTime, + fadeDelay = 0, + fadeTicks = 20, + isEnded = false + ) + VeinMiningHandlerClient.noticeActive = true + return id + } + + fun endNotice(id: Long, fadeDelay: Int = 30) { + val now = Minecraft.getMinecraft().theWorld.totalWorldTime + notices.find { it.id == id && !it.isEnded }?.apply { + isEnded = true + startTick = now + this.fadeDelay = fadeDelay + } + } + + 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 + drawStringWithMargin(notice.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 d4a5c9a..516952d 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 @@ -42,12 +42,9 @@ object BanditCommand : CommandBase() { args, *listOf( "help", - "executor", "executor-generator", - "filter", "block-filter", "stop", - "halt", "drop_pos", "drop_timing", "stop_on_release" 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..f5c4e49 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 @@ -1,6 +1,7 @@ package cn.elytra.mod.bandit.common.player_data import cn.elytra.mod.bandit.BanditMod +import cn.elytra.mod.bandit.client.VeinMiningHUD import cn.elytra.mod.bandit.common.BanditCoroutines import cn.elytra.mod.bandit.common.mining.VeinMiningContext import cn.elytra.mod.bandit.common.mining.VeinMiningContext.DropPosition @@ -22,11 +23,11 @@ import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import net.minecraft.block.Block +import net.minecraft.client.resources.I18n 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 @@ -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(VeinMiningHUD.pushNotice(I18n.format("bandit.message.task-starting"))) + notices.add(VeinMiningHUD.pushNotice(I18n.format("bandit.message.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) @@ -309,19 +311,20 @@ data class VeinMiningPlayerData( if(it != null) { when(it) { is KeyReleaseCancellation -> { - getPlayer().addChatMessage(ChatComponentTranslation("bandit.message.task-stop.key-release")) + notices.add(VeinMiningHUD.pushNotice(I18n.format("bandit.message.task-stop.key-release"))) } else -> BanditMod.logger.warn("Job was cancelled because of an throwable", it) } } - player.addChatMessage( - ChatComponentTranslation( + notices.add(VeinMiningHUD.pushNotice(I18n.format( "bandit.message.task-done", context.statBlocksMined.get(), - context.statItemDropped.values.sum() - ) - ) + context.statItemDropped.values.sum()))) + for (notice in notices) { + VeinMiningHUD.endNotice(notice, fadeDelay = 40) + } + // play sound effects world.playSoundEffect(player.posX, player.posY, player.posZ, "note.harp", 3.0F, 3.5F) // clear caches From b84051f5616cd673a7e32eadedc4ac97338fcdac Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sat, 24 Jan 2026 20:01:22 +0800 Subject: [PATCH 05/19] fix server can't use i18n --- .../cn/elytra/mod/bandit/client/VeinMiningHUD.kt | 13 ++++++++++++- .../common/player_data/VeinMiningPlayerData.kt | 12 +++++------- 2 files changed, 17 insertions(+), 8 deletions(-) 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 ec4e8ba..e4d039d 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt @@ -88,8 +88,19 @@ object VeinMiningHUD { } } - fun pushNotice(text: String): Long { + fun pushNotice(noticeType: Int, statBlocksMined: Int = 0, statItemDropped: Int = 0): Long { val id = System.nanoTime() + val text = when(noticeType) { + 1 -> I18n.format("bandit.message.task-starting") + 2 -> I18n.format("bandit.message.task-halt-hint") + 3 -> I18n.format("bandit.message.task-stop.key-release") + 4 -> I18n.format( + "bandit.message.task-done", + statBlocksMined, + statItemDropped) + + else -> "" + } notices += HudNotice( id = id, text = text, 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 f5c4e49..f5d88e3 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 @@ -23,7 +23,6 @@ import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import net.minecraft.block.Block -import net.minecraft.client.resources.I18n import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.nbt.NBTTagCompound @@ -299,8 +298,8 @@ 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"}") val notices = mutableListOf() - notices.add(VeinMiningHUD.pushNotice(I18n.format("bandit.message.task-starting"))) - notices.add(VeinMiningHUD.pushNotice(I18n.format("bandit.message.task-halt-hint"))) + notices.add(VeinMiningHUD.pushNotice(1)) + notices.add(VeinMiningHUD.pushNotice(2)) val job = BanditCoroutines.VeinMiningScope.launch(start = CoroutineStart.LAZY) { world.playSoundEffect(player.posX, player.posY, player.posZ, "note.harp", 3.0F, 1.0F) @@ -311,16 +310,15 @@ data class VeinMiningPlayerData( if(it != null) { when(it) { is KeyReleaseCancellation -> { - notices.add(VeinMiningHUD.pushNotice(I18n.format("bandit.message.task-stop.key-release"))) + notices.add(VeinMiningHUD.pushNotice(3)) } else -> BanditMod.logger.warn("Job was cancelled because of an throwable", it) } } - notices.add(VeinMiningHUD.pushNotice(I18n.format( - "bandit.message.task-done", + notices.add(VeinMiningHUD.pushNotice(4, context.statBlocksMined.get(), - context.statItemDropped.values.sum()))) + context.statItemDropped.values.sum())) for (notice in notices) { VeinMiningHUD.endNotice(notice, fadeDelay = 40) } From 4d07a12bb2c097e48880e425fa75a8088f46ad0f Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sun, 25 Jan 2026 03:59:04 +0800 Subject: [PATCH 06/19] notice by S2C --- .../elytra/mod/bandit/client/VeinMiningHUD.kt | 64 +++++++++---- .../bandit/common/command/BanditCommand.kt | 53 +++++++--- .../player_data/VeinMiningNoticeType.kt | 33 +++++++ .../player_data/VeinMiningPlayerData.kt | 41 +++++--- .../mod/bandit/network/BanditNetwork.kt | 67 +++++++++++++ .../mod/bandit/network/S2CNoticePacket.kt | 96 +++++++++++++++++++ 6 files changed, 303 insertions(+), 51 deletions(-) create mode 100644 src/main/kotlin/cn/elytra/mod/bandit/common/player_data/VeinMiningNoticeType.kt create mode 100644 src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt 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 e4d039d..130808d 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 @@ -26,10 +27,11 @@ object VeinMiningHUD { data class HudNotice( val id: Long, // 唯一 ID - val text: String, + var noticeType: Int = 0, var startTick: Long, var fadeDelay: Int = 0, // 延迟渐隐 tick var fadeTicks: Int = 20, // 渐隐时长 + var extraData: Map = emptyMap(), // 额外数据 var isEnded: Boolean = false ) private val notices = mutableListOf() @@ -88,37 +90,42 @@ object VeinMiningHUD { } } - fun pushNotice(noticeType: Int, statBlocksMined: Int = 0, statItemDropped: Int = 0): Long { - val id = System.nanoTime() - val text = when(noticeType) { - 1 -> I18n.format("bandit.message.task-starting") - 2 -> I18n.format("bandit.message.task-halt-hint") - 3 -> I18n.format("bandit.message.task-stop.key-release") - 4 -> I18n.format( - "bandit.message.task-done", - statBlocksMined, - statItemDropped) - - else -> "" + fun pushNotice( + 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, - text = text, - startTick = Minecraft.getMinecraft().theWorld.totalWorldTime, - fadeDelay = 0, - fadeTicks = 20, - isEnded = false + noticeType = noticeType, + extraData = extraData, + startTick = now, + fadeDelay = fadeDelay, + fadeTicks = fadeTicks, + isEnded = isEnd ) VeinMiningHandlerClient.noticeActive = true return id } - fun endNotice(id: Long, fadeDelay: Int = 30) { + fun endNotice(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 } } @@ -134,7 +141,7 @@ object VeinMiningHUD { val alpha = when { !n.isEnded -> 1f age <= n.fadeDelay -> 1f - age <= n.fadeDelay + n.fadeTicks -> 1f - (age - n.fadeDelay) / n.fadeTicks.toFloat() + age < n.fadeDelay + n.fadeTicks -> 1f - (age - n.fadeDelay) / n.fadeTicks.toFloat() else -> { iter.remove() continue @@ -160,7 +167,22 @@ object VeinMiningHUD { var y = marginY + 45 // list height + font height for ((notice, alpha) in getActiveNotices()) { val color = (alpha * 255).toInt() shl 24 or 0xFFFFFF - drawStringWithMargin(notice.text, marginX, y, color) + 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_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 } 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 516952d..585fc79 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 @@ -36,22 +36,45 @@ object BanditCommand : CommandBase() { } override fun addTabCompletionOptions(sender: ICommandSender?, args: Array?): List? { - val list = args?.toMutableList() - if (list != null && list.size == 1) { - return getListOfStringsMatchingLastWord( - args, - *listOf( - "help", - "executor-generator", - "block-filter", - "stop", - "drop_pos", - "drop_timing", - "stop_on_release" - ).toTypedArray() - ) + if (args.isNullOrEmpty()) return super.addTabCompletionOptions(sender, args) + return when (args.size) { + + // ===== 第一级:/bandit <这里> ===== + 1 -> getListOfStringsMatchingLastWord( + args, + "help", + "executor-generator", + "block-filter", + "stop", + "drop_pos", + "drop_timing", + "stop_on_release" + ) + + // ===== 第二级:/bandit <这里> ===== + 2 -> when (args[0]) { + "executor-generator", "executor" -> listOf("[Leave it blank to view the prompt]") + "block-filter", "filter" -> listOf("[Leave it blank to view the prompt]") + "drop_pos" -> + getListOfStringsMatchingLastWord( + args, + *getValidEnumValues().toTypedArray() + ) + "drop_timing" -> + getListOfStringsMatchingLastWord( + args, + *getValidEnumValues().toTypedArray() + ) + "stop_on_release" -> + getListOfStringsMatchingLastWord( + args, + "true", + "false" + ) + else -> super.addTabCompletionOptions(sender, args) + } + else -> super.addTabCompletionOptions(sender, args) } - return super.addTabCompletionOptions(sender, args) } private fun ICommandSender.withEntityPlayer(block: (EntityPlayerMP) -> Unit) { 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..b0a6272 --- /dev/null +++ b/src/main/kotlin/cn/elytra/mod/bandit/common/player_data/VeinMiningNoticeType.kt @@ -0,0 +1,33 @@ +package cn.elytra.mod.bandit.common.player_data + +/** + * 连锁挖掘通知类型 + */ +enum class VeinMiningNoticeType(val id: Int) { + /** + * 任务开始 + */ + TASK_STARTING(1), + + /** + * 提示可以停止任务 + */ + TASK_HALT_HINT(2), + + /** + * 按键释放导致停止 + */ + TASK_STOP_KEY_RELEASE(3), + + /** + * 任务完成(需要统计数据) + */ + TASK_DONE(4); + + companion object { + 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 f5d88e3..6d804cf 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 @@ -1,7 +1,6 @@ package cn.elytra.mod.bandit.common.player_data import cn.elytra.mod.bandit.BanditMod -import cn.elytra.mod.bandit.client.VeinMiningHUD import cn.elytra.mod.bandit.common.BanditCoroutines import cn.elytra.mod.bandit.common.mining.VeinMiningContext import cn.elytra.mod.bandit.common.mining.VeinMiningContext.DropPosition @@ -298,8 +297,8 @@ 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"}") val notices = mutableListOf() - notices.add(VeinMiningHUD.pushNotice(1)) - notices.add(VeinMiningHUD.pushNotice(2)) + 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) @@ -307,22 +306,34 @@ data class VeinMiningPlayerData( } job.invokeOnCompletion { BanditMod.logger.info("VeinMining #${executionId} has ended") - if(it != null) { - when(it) { - is KeyReleaseCancellation -> { - notices.add(VeinMiningHUD.pushNotice(3)) - } - 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) } - notices.add(VeinMiningHUD.pushNotice(4, - context.statBlocksMined.get(), - context.statItemDropped.values.sum())) - for (notice in notices) { - VeinMiningHUD.endNotice(notice, fadeDelay = 40) + + when(it) { + is KeyReleaseCancellation -> { + BanditNetwork.pushSimpleNoticeToClient(player.asMP, + noticeType = VeinMiningNoticeType.TASK_STOP_KEY_RELEASE, + fadeDelay = 10, + fadeTicks = 20 + ) + } + + else -> BanditMod.logger.warn("Job was cancelled because of an throwable", it) } + // 完成:发送任务完成通知,40tick后渐隐,渐隐20tick + 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 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..9b7c833 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,63 @@ object BanditNetwork { fun syncBlockCacheToClient(p: EntityPlayerMP, posList: List?) { networkW.sendTo(S2CSelectedBlockCachePacket().apply { this.posList = posList ?: emptyList() }, p) } + + /** + * 发送简单通知 + * 用于:任务开始、提示可以停止、按键释放停止 + * 返回 noticeId,用于后续调用 endNoticeToClient 来停止渲染 + */ + 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 + } + + /** + * 发送任务完成通知 + * 用于:任务完成 + * 此通知会自动渐隐消失,不需要额外调用清除方法 + */ + 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 + } + + 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..80a2112 --- /dev/null +++ b/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt @@ -0,0 +1,96 @@ +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.pushNotice( + message.noticeType, + message.noticeId, + message.extraData, + message.fadeDelay, + message.fadeTicks + ) + } + NoticeAction.END -> { + VeinMiningHUD.endNotice( + message.noticeId, + message.fadeDelay, + message.fadeTicks + ) + } + } + return null + } + } +} + From 50c51d3a5c1b9bcfefd2db768e9673ecc72de326 Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sun, 25 Jan 2026 04:15:29 +0800 Subject: [PATCH 07/19] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=8C=87=E4=BB=A4?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=EF=BC=8C=E8=AE=A9stop=E5=A4=84=E4=BA=8E?= =?UTF-8?q?=E7=AC=AC=E4=B8=80=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/elytra/mod/bandit/common/command/BanditCommand.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 585fc79..5f25078 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 @@ -42,13 +42,13 @@ object BanditCommand : CommandBase() { // ===== 第一级:/bandit <这里> ===== 1 -> getListOfStringsMatchingLastWord( args, - "help", - "executor-generator", - "block-filter", "stop", + "help", "drop_pos", "drop_timing", - "stop_on_release" + "stop_on_release", + "block-filter", + "executor-generator", ) // ===== 第二级:/bandit <这里> ===== From ff03043ed2e9c7309f30d9a7da50b57c03cfdd9f Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sun, 25 Jan 2026 05:34:47 +0800 Subject: [PATCH 08/19] fix Flow was collected in Coroutine A but emission happened in Coroutine B --- .../executor/LargeScanExecutorGenerator.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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" } From 99bab95d022dec70d7370ed72a720383e7787ff0 Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sun, 25 Jan 2026 15:22:20 +0800 Subject: [PATCH 09/19] Update the comments and handle two expected exceptions --- .../elytra/mod/bandit/client/VeinMiningHUD.kt | 23 +++++-- .../bandit/common/command/BanditCommand.kt | 7 ++- .../listener/VeinMiningEventListener.kt | 3 +- .../player_data/VeinMiningNoticeType.kt | 31 +++++++-- .../player_data/VeinMiningPlayerData.kt | 26 ++++++-- .../FriendlyCancellationException.kt | 10 +++ .../mod/bandit/network/BanditNetwork.kt | 63 ++++++++++++++++--- .../mod/bandit/network/S2CNoticePacket.kt | 4 +- .../resources/assets/bandit/lang/en_US.lang | 1 + .../resources/assets/bandit/lang/zh_CN.lang | 1 + 10 files changed, 139 insertions(+), 30 deletions(-) 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 130808d..271f4ac 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt @@ -25,13 +25,27 @@ 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, // 唯一 ID + val id: Long, var noticeType: Int = 0, var startTick: Long, - var fadeDelay: Int = 0, // 延迟渐隐 tick - var fadeTicks: Int = 20, // 渐隐时长 - var extraData: Map = emptyMap(), // 额外数据 + var fadeDelay: Int = 0, + var fadeTicks: Int = 20, + var extraData: Map = emptyMap(), var isEnded: Boolean = false ) private val notices = mutableListOf() @@ -171,6 +185,7 @@ object VeinMiningHUD { 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 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 5f25078..ef5eff2 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 @@ -39,7 +40,7 @@ object BanditCommand : CommandBase() { if (args.isNullOrEmpty()) return super.addTabCompletionOptions(sender, args) return when (args.size) { - // ===== 第一级:/bandit <这里> ===== + // ===== /bandit ===== 1 -> getListOfStringsMatchingLastWord( args, "stop", @@ -51,7 +52,7 @@ object BanditCommand : CommandBase() { "executor-generator", ) - // ===== 第二级:/bandit <这里> ===== + // ===== /bandit ===== 2 -> when (args[0]) { "executor-generator", "executor" -> listOf("[Leave it blank to view the prompt]") "block-filter", "filter" -> listOf("[Leave it blank to view the prompt]") @@ -160,7 +161,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 index b0a6272..c7991d9 100644 --- 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 @@ -1,30 +1,49 @@ 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_DONE(4); + 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 6d804cf..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 @@ -242,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 */ } @@ -320,10 +322,21 @@ data class VeinMiningPlayerData( ) } + 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) } - // 完成:发送任务完成通知,40tick后渐隐,渐隐20tick BanditNetwork.pushCompletionNoticeToClient( player.asMP, extraData = mapOf( @@ -359,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..6f33b11 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 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/network/BanditNetwork.kt b/src/main/kotlin/cn/elytra/mod/bandit/network/BanditNetwork.kt index 9b7c833..6a60504 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/network/BanditNetwork.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/network/BanditNetwork.kt @@ -76,11 +76,28 @@ object BanditNetwork { } /** - * 发送简单通知 - * 用于:任务开始、提示可以停止、按键释放停止 - * 返回 noticeId,用于后续调用 endNoticeToClient 来停止渲染 + * 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 { + fun pushSimpleNoticeToClient( + p: EntityPlayerMP, + noticeType: VeinMiningNoticeType, + fadeDelay: Int = 0, + fadeTicks: Int = 0 + ): Long { val noticeId = System.nanoTime() networkW.sendTo( S2CNoticePacket().apply { @@ -97,16 +114,26 @@ object BanditNetwork { } /** - * 发送任务完成通知 - * 用于:任务完成 - * 此通知会自动渐隐消失,不需要额外调用清除方法 + * 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{ + ): Long { val noticeId = System.nanoTime() networkW.sendTo( S2CNoticePacket().apply { @@ -122,7 +149,25 @@ object BanditNetwork { return noticeId } - fun endNoticeToClient(p: EntityPlayerMP, noticeId: Long, fadeDelay: Int = 0, fadeTicks: Int = 0) { + /** + * 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 diff --git a/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt b/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt index 80a2112..b94d4de 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt @@ -16,7 +16,7 @@ class S2CNoticePacket : IMessage { var action: NoticeAction = NoticeAction.PUSH var noticeId: Long = 0 var noticeType: Int = 0 - var extraData: Map = emptyMap() // 额外数据,键值对形式 + var extraData: Map = emptyMap() var fadeDelay: Int = 0 var fadeTicks: Int = 20 @@ -25,7 +25,6 @@ class S2CNoticePacket : IMessage { noticeId = buf.readLong() noticeType = buf.readInt() - // 读取额外数据 val extraDataSize = buf.readInt() extraData = if (extraDataSize > 0) { buildMap { @@ -51,7 +50,6 @@ class S2CNoticePacket : IMessage { buf.writeLong(noticeId) buf.writeInt(noticeType) - // 写入额外数据 buf.writeInt(extraData.size) if (extraData.isNotEmpty()) { extraData.forEach { (key, value) -> 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 de9dcc3..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 From ec98611e31342801ac0b542c66dee35d54da5941 Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Mon, 26 Jan 2026 00:26:38 +0800 Subject: [PATCH 10/19] Correct grammar errors and reconstruct the instruction prompt framework --- .../elytra/mod/bandit/client/VeinMiningHUD.kt | 4 +-- .../bandit/common/command/BanditCommand.kt | 33 +++++++++++++------ .../FriendlyCancellationException.kt | 2 +- 3 files changed, 26 insertions(+), 13 deletions(-) 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 271f4ac..53aa75e 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt @@ -104,7 +104,7 @@ object VeinMiningHUD { } } - fun pushNotice( + internal fun pushNotice( noticeType: Int, id: Long, extraData: Map = emptyMap(), @@ -129,7 +129,7 @@ object VeinMiningHUD { return id } - fun endNotice(id: Long, fadeDelay: Int = 0, fadeTicks: Int = 20) { + internal fun endNotice(id: Long, fadeDelay: Int = 0, fadeTicks: Int = 20) { if (fadeTicks == 0 && fadeDelay == 0) { notices.removeIf { it.id == id } return 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 ef5eff2..bb4daee 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 @@ -38,10 +38,12 @@ object BanditCommand : CommandBase() { override fun addTabCompletionOptions(sender: ICommandSender?, args: Array?): List? { if (args.isNullOrEmpty()) return super.addTabCompletionOptions(sender, args) - return when (args.size) { + val argsList = args.toMutableList() + + return when (val arg0 = argsList.removeFirstOrNull()) { // ===== /bandit ===== - 1 -> getListOfStringsMatchingLastWord( + null, "" -> getListOfStringsMatchingLastWord( args, "stop", "help", @@ -49,31 +51,42 @@ object BanditCommand : CommandBase() { "drop_timing", "stop_on_release", "block-filter", - "executor-generator", + "executor-generator" ) // ===== /bandit ===== - 2 -> when (args[0]) { - "executor-generator", "executor" -> listOf("[Leave it blank to view the prompt]") - "block-filter", "filter" -> listOf("[Leave it blank to view the prompt]") - "drop_pos" -> + "executor-generator", "executor" -> { + listOf("[Leave it blank to view the prompt]") + } + + "block-filter", "filter" -> { + listOf("[Leave it blank to view the prompt]") + } + + "drop_pos" -> { getListOfStringsMatchingLastWord( args, *getValidEnumValues().toTypedArray() ) - "drop_timing" -> + } + + "drop_timing" -> { getListOfStringsMatchingLastWord( args, *getValidEnumValues().toTypedArray() ) - "stop_on_release" -> + } + + "stop_on_release" -> { getListOfStringsMatchingLastWord( args, "true", "false" ) - else -> super.addTabCompletionOptions(sender, args) } + + "stop", "help" -> emptyList() + else -> super.addTabCompletionOptions(sender, args) } } 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 6f33b11..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 @@ -19,7 +19,7 @@ class KeyReleaseCancellation : FriendlyCancellationException("Cancelled because /** * The exception represents the vein-mining task was cancelled by the player because they typed the command */ -class CommandCancellation : FriendlyCancellationException("Cancelled because the stop 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 From 9f32643711e511539f8bcc226fc4207c0eb6888d Mon Sep 17 00:00:00 2001 From: Taskeren Date: Mon, 26 Jan 2026 16:20:19 +0800 Subject: [PATCH 11/19] Rename function --- src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt | 4 ++-- .../kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 53aa75e..5ec44bc 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt @@ -104,7 +104,7 @@ object VeinMiningHUD { } } - internal fun pushNotice( + internal fun postNotice( noticeType: Int, id: Long, extraData: Map = emptyMap(), @@ -129,7 +129,7 @@ object VeinMiningHUD { return id } - internal fun endNotice(id: Long, fadeDelay: Int = 0, fadeTicks: Int = 20) { + internal fun cancelNotice(id: Long, fadeDelay: Int = 0, fadeTicks: Int = 20) { if (fadeTicks == 0 && fadeDelay == 0) { notices.removeIf { it.id == id } return diff --git a/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt b/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt index b94d4de..cedcdb2 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/network/S2CNoticePacket.kt @@ -71,7 +71,7 @@ class S2CNoticePacket : IMessage { ): IMessage? { when (message.action) { NoticeAction.PUSH -> { - VeinMiningHUD.pushNotice( + VeinMiningHUD.postNotice( message.noticeType, message.noticeId, message.extraData, @@ -80,7 +80,7 @@ class S2CNoticePacket : IMessage { ) } NoticeAction.END -> { - VeinMiningHUD.endNotice( + VeinMiningHUD.cancelNotice( message.noticeId, message.fadeDelay, message.fadeTicks From dc8e446357e6c267b8019cfa530f8d551d05478b Mon Sep 17 00:00:00 2001 From: Taskeren Date: Mon, 26 Jan 2026 16:27:36 +0800 Subject: [PATCH 12/19] Remove invalid tab completion --- .../mod/bandit/common/command/BanditCommand.kt | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) 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 bb4daee..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 @@ -36,13 +36,12 @@ object BanditCommand : CommandBase() { } } - override fun addTabCompletionOptions(sender: ICommandSender?, args: Array?): List? { - if (args.isNullOrEmpty()) return super.addTabCompletionOptions(sender, args) + override fun addTabCompletionOptions(sender: ICommandSender, args: Array): List? { + if (args.isEmpty()) return super.addTabCompletionOptions(sender, args) val argsList = args.toMutableList() - return when (val arg0 = argsList.removeFirstOrNull()) { - // ===== /bandit ===== + return when (argsList.removeFirstOrNull()) { null, "" -> getListOfStringsMatchingLastWord( args, "stop", @@ -54,13 +53,9 @@ object BanditCommand : CommandBase() { "executor-generator" ) - // ===== /bandit ===== - "executor-generator", "executor" -> { - listOf("[Leave it blank to view the prompt]") - } - - "block-filter", "filter" -> { - listOf("[Leave it blank to view the prompt]") + "executor-generator", "executor", "block-filter", "filter" -> { + // TODO: add support to names and update this completion + emptyList() } "drop_pos" -> { From f40bba6d5de0d44e5435c2a181a01b27a5a36fc5 Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sun, 1 Feb 2026 16:03:26 +0800 Subject: [PATCH 13/19] Improve the command system --- .../elytra/mod/bandit/client/VeinMiningHUD.kt | 1 + .../bandit/common/command/BanditCommand.kt | 103 +++++++++++------- .../player_data/VeinMiningNoticeType.kt | 7 +- .../player_data/VeinMiningPlayerData.kt | 10 +- .../mod/bandit/mining/BlockFilterRegistry.kt | 8 ++ .../mining/ExecutorGeneratorRegistry.kt | 8 ++ .../resources/assets/bandit/lang/en_US.lang | 5 +- .../resources/assets/bandit/lang/zh_CN.lang | 5 +- 8 files changed, 99 insertions(+), 48 deletions(-) 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 5ec44bc..3e5809a 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHUD.kt @@ -182,6 +182,7 @@ object VeinMiningHUD { for ((notice, alpha) in getActiveNotices()) { val color = (alpha * 255).toInt() shl 24 or 0xFFFFFF val text = when(VeinMiningNoticeType.fromId(notice.noticeType)) { + VeinMiningNoticeType.TASK_BLOCKED -> I18n.format("bandit.message.task-blocked") 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") 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 4f6719f..7114208 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 @@ -42,47 +42,60 @@ object BanditCommand : CommandBase() { 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" -> { + getListOfStringsMatchingLastWord( + args, + *ExecutorGeneratorRegistry.all().map { (id, gen) -> + "$id:(${gen.getUnlocalizedName().substringAfterLast('.')})" + }.toTypedArray() + ) + } - "executor-generator", "executor", "block-filter", "filter" -> { - // TODO: add support to names and update this completion - emptyList() + "block-filter", "filter" -> { + getListOfStringsMatchingLastWord( + args, + *BlockFilterRegistry.all().map { (id, filter) -> + "$id:(${filter.getUnlocalizedName().substringAfterLast('.')})" + }.toTypedArray() + ) } "drop_pos" -> { - getListOfStringsMatchingLastWord( - args, - *getValidEnumValues().toTypedArray() - ) + getListOfStringsMatchingLastWord( + args, + *getValidEnumValues().toTypedArray() + ) } "drop_timing" -> { - getListOfStringsMatchingLastWord( - args, - *getValidEnumValues().toTypedArray() - ) + getListOfStringsMatchingLastWord( + args, + *getValidEnumValues().toTypedArray() + ) } "stop_on_release" -> { - getListOfStringsMatchingLastWord( - args, - "true", - "false" - ) + getListOfStringsMatchingLastWord( + args, + "true", + "false" + ) } "stop", "help" -> emptyList() - else -> super.addTabCompletionOptions(sender, args) + else -> { + getListOfStringsMatchingLastWord( + args, + "stop", + "help", + "drop_pos", + "drop_timing", + "stop_on_release", + "block-filter", + "executor-generator" + ).takeIf { it.isNotEmpty() } ?: super.addTabCompletionOptions(sender, args) + } } } @@ -99,8 +112,8 @@ object BanditCommand : CommandBase() { strings: MutableList, ) { sender.withEntityPlayer { p -> - val id = strings.removeFirstOrNull() - if(id == null) { + val raw = strings.removeFirstOrNull() + if(raw == null) { val execId = p.veinMiningData.veinMiningExecutorId sender.addChatMessage( ChatComponentTranslation( @@ -109,7 +122,7 @@ object BanditCommand : CommandBase() { ) ) sender.addChatMessage(ChatComponentTranslation("command.bandit.executor.list")) - ExecutorGeneratorRegistry.all().forEach { id, _ -> + ExecutorGeneratorRegistry.all().forEach { (id, _) -> sender.addChatMessage( ChatComponentTranslation( "command.bandit.executor.list.entry", @@ -119,10 +132,14 @@ object BanditCommand : CommandBase() { ) } } else { - val id = parseInt(sender, id) - val flag = true // TODO: validation - p.veinMiningData.veinMiningExecutorId = id - if(flag) sender.addChatMessage(ChatComponentTranslation("command.bandit.executor.set.ok")) + val id = parseInt(sender, raw.substringBefore(":")) + if(ExecutorGeneratorRegistry.isRegistered(id)){ + p.veinMiningData.veinMiningExecutorId = id + sender.addChatMessage( + ChatComponentTranslation("command.bandit.executor.set.ok", + ChatComponentTranslation(ExecutorGeneratorRegistry.getUnlocalizedName(id))) + ) + } else sender.addChatMessage(ChatComponentTranslation("command.bandit.executor.set.fail")) } } @@ -133,8 +150,8 @@ object BanditCommand : CommandBase() { strings: MutableList, ) { sender.withEntityPlayer { p -> - val id = strings.removeFirstOrNull() - if(id == null) { + val raw = strings.removeFirstOrNull() + if(raw == null) { val filterId = p.veinMiningData.veinMiningBlockFilterId sender.addChatMessage( ChatComponentTranslation( @@ -143,19 +160,21 @@ object BanditCommand : CommandBase() { ) ) sender.addChatMessage(ChatComponentTranslation("command.bandit.filter.list")) - BlockFilterRegistry.all().forEach { id, _ -> + BlockFilterRegistry.all().forEach { (id, _) -> sender.addChatMessage( ChatComponentTranslation( "command.bandit.filter.list.entry", - ChatComponentTranslation("bandit.filter.$filterId") + ChatComponentTranslation("bandit.filter.$id") ) ) } } else { - val id = parseInt(sender, id) - val flag = true // TODO: validation - p.veinMiningData.veinMiningBlockFilterId = id - if(flag) sender.addChatMessage(ChatComponentTranslation("command.bandit.filter.set.ok")) + val id = parseInt(sender, raw.substringBefore(":")) + if (BlockFilterRegistry.isRegistered(id)){ + p.veinMiningData.veinMiningBlockFilterId = id + sender.addChatMessage(ChatComponentTranslation("command.bandit.filter.set.ok", + ChatComponentTranslation(BlockFilterRegistry.getUnlocalizedName(id)))) + } else sender.addChatMessage(ChatComponentTranslation("command.bandit.filter.set.fail")) } } 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 index c7991d9..cd2f99f 100644 --- 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 @@ -35,7 +35,12 @@ enum class VeinMiningNoticeType(val id: Int) { * Task completed successfully. * Mining chain finished naturally and requires statistical processing. */ - TASK_DONE(5); + TASK_DONE(5), + + /** + * Task not start cause there is another vein mining job. + */ + TASK_BLOCKED(6); companion object { /** 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 2c43a34..1930a7c 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 @@ -264,7 +264,15 @@ data class VeinMiningPlayerData( // stop precalculate job and continue to start vein mining TypedJob.JobType.VeinMiningPrecalculating -> currentJob = null // stop if there is another vein mining job - TypedJob.JobType.VeinMining -> return + TypedJob.JobType.VeinMining -> { + val player = getPlayer() + BanditNetwork.pushSimpleNoticeToClient(player.asMP, + noticeType = VeinMiningNoticeType.TASK_BLOCKED, + fadeDelay = 10, + fadeTicks = 20 + ) + return + } } } diff --git a/src/main/kotlin/cn/elytra/mod/bandit/mining/BlockFilterRegistry.kt b/src/main/kotlin/cn/elytra/mod/bandit/mining/BlockFilterRegistry.kt index abfa16d..0509656 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/mining/BlockFilterRegistry.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/mining/BlockFilterRegistry.kt @@ -29,4 +29,12 @@ object BlockFilterRegistry { } fun all(): Map = blockFilterMap.toMap() + + fun isRegistered(id: Int): Boolean { + return id in blockFilterMap + } + + fun getUnlocalizedName(id: Int): String? { + return blockFilterMap[id]?.getUnlocalizedName() + } } diff --git a/src/main/kotlin/cn/elytra/mod/bandit/mining/ExecutorGeneratorRegistry.kt b/src/main/kotlin/cn/elytra/mod/bandit/mining/ExecutorGeneratorRegistry.kt index 183b59e..9604766 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/mining/ExecutorGeneratorRegistry.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/mining/ExecutorGeneratorRegistry.kt @@ -33,4 +33,12 @@ object ExecutorGeneratorRegistry { } fun all(): Map = executorGeneratorMap.toMap() + + fun isRegistered(id: Int): Boolean { + return id in executorGeneratorMap + } + + fun getUnlocalizedName(id: Int): String? { + return executorGeneratorMap[id]?.getUnlocalizedName() + } } diff --git a/src/main/resources/assets/bandit/lang/en_US.lang b/src/main/resources/assets/bandit/lang/en_US.lang index 79d7bfe..4fdb249 100644 --- a/src/main/resources/assets/bandit/lang/en_US.lang +++ b/src/main/resources/assets/bandit/lang/en_US.lang @@ -8,6 +8,7 @@ bandit.block-filter.none=None bandit.block-filter.match-block=Matching Block bandit.block-filter.match-block-and-meta=Matching Block and Metadata +bandit.message.task-blocked=§There is another vein mining job. 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. @@ -27,12 +28,12 @@ command.bandit.help.3=§6/bandit drop_timing [dropTiming] - §7list or set the t command.bandit.executor.current=You are currently using %s command.bandit.executor.list=Possible Executors: command.bandit.executor.list.entry=%s %s -command.bandit.executor.set.ok=OK! +command.bandit.executor.set.ok=OK! Executor has now been changed to %s. command.bandit.executor.set.fail=Failed! Possibly it is an invalid id. command.bandit.filter.current=You are currently using %s command.bandit.filter.list=Possible Filters: command.bandit.filter.list.entry=%s %s -command.bandit.filter.set.ok=OK! +command.bandit.filter.set.ok=OK! Filter has now been changed to %s. command.bandit.filter.set.fail=Failed! Possibly it is an invalid id. command.bandit.drop_pos=Currently %s, valid values are %s command.bandit.drop_pos.ok=OK, new value is %s! diff --git a/src/main/resources/assets/bandit/lang/zh_CN.lang b/src/main/resources/assets/bandit/lang/zh_CN.lang index bfcbf04..7d9e996 100644 --- a/src/main/resources/assets/bandit/lang/zh_CN.lang +++ b/src/main/resources/assets/bandit/lang/zh_CN.lang @@ -8,6 +8,7 @@ bandit.block-filter.none=无 bandit.block-filter.match-block=仅匹配方块 bandit.block-filter.match-block-and-meta=匹配方块和Meta +bandit.message.task-blocked=§a有另一挖掘任务进行中. 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个掉落物. @@ -27,12 +28,12 @@ command.bandit.help.3=§6/bandit drop_timing [掉落物出现时机] - §7列出 command.bandit.executor.current=您当前正在使用 %s command.bandit.executor.list=可能的执行器: command.bandit.executor.list.entry=%s %s -command.bandit.executor.set.ok=OK! +command.bandit.executor.set.ok=OK! 执行器更改为 %s. command.bandit.executor.set.fail=失败了! 可能这是个无效的ID. command.bandit.filter.current=您当前正在使用 %s command.bandit.filter.list=可能的过滤器: command.bandit.filter.list.entry=%s %s -command.bandit.filter.set.ok=OK! +command.bandit.filter.set.ok=OK! 过滤器更改为: %s. command.bandit.filter.set.fail=失败了! 可能这是个无效的ID. command.bandit.drop_pos=当前 %s, 有效的值是 %s command.bandit.drop_pos.ok=OK, 新的值是 %s! From 5eda40927ce2629f1bec8549e2d821ff6d7ccde5 Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sun, 1 Feb 2026 16:22:59 +0800 Subject: [PATCH 14/19] make non-op user can use command --- .../cn/elytra/mod/bandit/common/command/BanditCommand.kt | 4 ++++ 1 file changed, 4 insertions(+) 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 7114208..915393d 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 @@ -19,6 +19,10 @@ object BanditCommand : CommandBase() { override fun getRequiredPermissionLevel(): Int = 0 + override fun canCommandSenderUseCommand(sender: ICommandSender?): Boolean { + return true + } + override fun processCommand( sender: ICommandSender, argsArray: Array, From 0bd5694afaeec9045210504a9dffbf7f98e14141 Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sun, 1 Feb 2026 16:29:38 +0800 Subject: [PATCH 15/19] Enable the underline to continue to be completed --- .../mod/bandit/common/command/BanditCommand.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 915393d..ae47227 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 @@ -46,7 +46,7 @@ object BanditCommand : CommandBase() { val argsList = args.toMutableList() return when (argsList.removeFirstOrNull()) { - "executor-generator", "executor" -> { + "executor-generator ", "executor " -> { getListOfStringsMatchingLastWord( args, *ExecutorGeneratorRegistry.all().map { (id, gen) -> @@ -55,7 +55,7 @@ object BanditCommand : CommandBase() { ) } - "block-filter", "filter" -> { + "block-filter ", "filter " -> { getListOfStringsMatchingLastWord( args, *BlockFilterRegistry.all().map { (id, filter) -> @@ -64,21 +64,21 @@ object BanditCommand : CommandBase() { ) } - "drop_pos" -> { + "drop_pos " -> { getListOfStringsMatchingLastWord( args, *getValidEnumValues().toTypedArray() ) } - "drop_timing" -> { + "drop_timing " -> { getListOfStringsMatchingLastWord( args, *getValidEnumValues().toTypedArray() ) } - "stop_on_release" -> { + "stop_on_release " -> { getListOfStringsMatchingLastWord( args, "true", @@ -86,7 +86,7 @@ object BanditCommand : CommandBase() { ) } - "stop", "help" -> emptyList() + "stop ", "help " -> emptyList() else -> { getListOfStringsMatchingLastWord( From 07f2730c40b6df004dbf64f85c5dfe782be9698e Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sun, 1 Feb 2026 16:44:43 +0800 Subject: [PATCH 16/19] Increase the disappearance delay of the excavation completion notification to five seconds --- .../mod/bandit/common/player_data/VeinMiningPlayerData.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1930a7c..735342e 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 @@ -351,7 +351,7 @@ data class VeinMiningPlayerData( "statBlocksMined" to context.statBlocksMined.get(), "statItemDropped" to context.statItemDropped.values.sum() ), - fadeDelay = 40, + fadeDelay = 100, fadeTicks = 20 ) From fbf9a1085051396414757c2e0345e51d32f81f09 Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Sun, 1 Feb 2026 17:15:00 +0800 Subject: [PATCH 17/19] Fix the bug that occasionally causes the mouse scroll wheel to get stuck --- .../mod/bandit/client/VeinMiningHandlerClient.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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 c04e7b5..b675dd4 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHandlerClient.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/client/VeinMiningHandlerClient.kt @@ -83,13 +83,15 @@ object VeinMiningHandlerClient { } fun onMouseInput(d: Int): Boolean { - VeinMiningHUD.withActiveMenu { - if(d < 0) { - this.move(1) - return true - } else if(d > 0) { - this.move(-1) - return true + if (Keyboard.isKeyDown(statusKey.keyCode)){ + VeinMiningHUD.withActiveMenu { + if(d < 0) { + this.move(1) + return true + } else if(d > 0) { + this.move(-1) + return true + } } } return false From e9d8466581210c780b2e4de313a45a4eed55241b Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Mon, 2 Feb 2026 16:33:29 +0800 Subject: [PATCH 18/19] Fix the bug of command and change style --- .../bandit/common/command/BanditCommand.kt | 69 +++++++++++-------- .../mod/bandit/mining/BlockFilterRegistry.kt | 12 ++++ .../mining/ExecutorGeneratorRegistry.kt | 12 ++++ 3 files changed, 65 insertions(+), 28 deletions(-) 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 ae47227..672876b 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 @@ -46,39 +46,39 @@ object BanditCommand : CommandBase() { val argsList = args.toMutableList() return when (argsList.removeFirstOrNull()) { - "executor-generator ", "executor " -> { + "executor-generator", "executor" -> { getListOfStringsMatchingLastWord( args, - *ExecutorGeneratorRegistry.all().map { (id, gen) -> - "$id:(${gen.getUnlocalizedName().substringAfterLast('.')})" + *ExecutorGeneratorRegistry.all().map { (_, gen) -> + gen.getUnlocalizedName().substringAfterLast('.') }.toTypedArray() ) } - "block-filter ", "filter " -> { + "block-filter", "filter" -> { getListOfStringsMatchingLastWord( args, - *BlockFilterRegistry.all().map { (id, filter) -> - "$id:(${filter.getUnlocalizedName().substringAfterLast('.')})" + *BlockFilterRegistry.all().map { (_, filter) -> + filter.getUnlocalizedName().substringAfterLast('.') }.toTypedArray() ) } - "drop_pos " -> { + "drop_pos" -> { getListOfStringsMatchingLastWord( args, *getValidEnumValues().toTypedArray() ) } - "drop_timing " -> { + "drop_timing" -> { getListOfStringsMatchingLastWord( args, *getValidEnumValues().toTypedArray() ) } - "stop_on_release " -> { + "stop_on_release" -> { getListOfStringsMatchingLastWord( args, "true", @@ -86,7 +86,7 @@ object BanditCommand : CommandBase() { ) } - "stop ", "help " -> emptyList() + "stop ", "help" -> emptyList() else -> { getListOfStringsMatchingLastWord( @@ -122,7 +122,7 @@ object BanditCommand : CommandBase() { sender.addChatMessage( ChatComponentTranslation( "command.bandit.executor.current", - ChatComponentTranslation("bandit.executor.$execId") + ExecutorGeneratorRegistry.get(execId)?.toChatComponent() ) ) sender.addChatMessage(ChatComponentTranslation("command.bandit.executor.list")) @@ -131,20 +131,25 @@ object BanditCommand : CommandBase() { ChatComponentTranslation( "command.bandit.executor.list.entry", id, - ChatComponentTranslation("bandit.executor.$id") + ExecutorGeneratorRegistry.get(id)?.toChatComponent() ) ) } } else { - val id = parseInt(sender, raw.substringBefore(":")) - if(ExecutorGeneratorRegistry.isRegistered(id)){ - p.veinMiningData.veinMiningExecutorId = id - sender.addChatMessage( - ChatComponentTranslation("command.bandit.executor.set.ok", - ChatComponentTranslation(ExecutorGeneratorRegistry.getUnlocalizedName(id))) - ) + val executorId = ExecutorGeneratorRegistry.resolveExecutorId(raw) + + if (executorId == null) { + sender.addChatMessage(ChatComponentTranslation("command.bandit.executor.set.fail")) + return@withEntityPlayer } - else sender.addChatMessage(ChatComponentTranslation("command.bandit.executor.set.fail")) + + p.veinMiningData.veinMiningExecutorId = executorId + sender.addChatMessage( + ChatComponentTranslation( + "command.bandit.executor.set.ok", + ExecutorGeneratorRegistry.get(executorId)?.toChatComponent() + ) + ) } } } @@ -160,7 +165,7 @@ object BanditCommand : CommandBase() { sender.addChatMessage( ChatComponentTranslation( "command.bandit.filter.current", - ChatComponentTranslation("bandit.filter.$filterId") + BlockFilterRegistry.get(filterId)?.toChatComponent() ) ) sender.addChatMessage(ChatComponentTranslation("command.bandit.filter.list")) @@ -168,18 +173,26 @@ object BanditCommand : CommandBase() { sender.addChatMessage( ChatComponentTranslation( "command.bandit.filter.list.entry", - ChatComponentTranslation("bandit.filter.$id") + id, + BlockFilterRegistry.get(id)?.toChatComponent() ) ) } } else { - val id = parseInt(sender, raw.substringBefore(":")) - if (BlockFilterRegistry.isRegistered(id)){ - p.veinMiningData.veinMiningBlockFilterId = id - sender.addChatMessage(ChatComponentTranslation("command.bandit.filter.set.ok", - ChatComponentTranslation(BlockFilterRegistry.getUnlocalizedName(id)))) + val filterId = BlockFilterRegistry.resolveFilterId(raw) + + if (filterId == null) { + sender.addChatMessage(ChatComponentTranslation("command.bandit.filter.set.fail")) + return@withEntityPlayer } - else sender.addChatMessage(ChatComponentTranslation("command.bandit.filter.set.fail")) + + p.veinMiningData.veinMiningBlockFilterId = filterId + sender.addChatMessage( + ChatComponentTranslation( + "command.bandit.filter.set.ok", + BlockFilterRegistry.get(filterId)?.toChatComponent() + ) + ) } } } diff --git a/src/main/kotlin/cn/elytra/mod/bandit/mining/BlockFilterRegistry.kt b/src/main/kotlin/cn/elytra/mod/bandit/mining/BlockFilterRegistry.kt index 0509656..e465ed8 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/mining/BlockFilterRegistry.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/mining/BlockFilterRegistry.kt @@ -37,4 +37,16 @@ object BlockFilterRegistry { fun getUnlocalizedName(id: Int): String? { return blockFilterMap[id]?.getUnlocalizedName() } + + fun resolveFilterId(raw: String): Int? { + val key = raw.trim().lowercase() + + key.toIntOrNull()?.takeIf(::isRegistered) + ?.let { return it } + + return blockFilterMap.entries.firstOrNull { (_, filter) -> + val name = filter.getUnlocalizedName().lowercase() + name == key || name.substringAfterLast('.') == key + }?.key + } } diff --git a/src/main/kotlin/cn/elytra/mod/bandit/mining/ExecutorGeneratorRegistry.kt b/src/main/kotlin/cn/elytra/mod/bandit/mining/ExecutorGeneratorRegistry.kt index 9604766..c3fdc8a 100644 --- a/src/main/kotlin/cn/elytra/mod/bandit/mining/ExecutorGeneratorRegistry.kt +++ b/src/main/kotlin/cn/elytra/mod/bandit/mining/ExecutorGeneratorRegistry.kt @@ -41,4 +41,16 @@ object ExecutorGeneratorRegistry { fun getUnlocalizedName(id: Int): String? { return executorGeneratorMap[id]?.getUnlocalizedName() } + + fun resolveExecutorId(raw: String): Int? { + val key = raw.trim().lowercase() + + key.toIntOrNull()?.takeIf(::isRegistered) + ?.let { return it } + + return executorGeneratorMap.entries.firstOrNull { (_, executor) -> + val name = executor.getUnlocalizedName().lowercase() + name == key || name.substringAfterLast('.') == key + }?.key + } } From cd71a5bd6125859741791c419da6b2838260ad5f Mon Sep 17 00:00:00 2001 From: PinkYuDeer Date: Mon, 2 Feb 2026 23:28:31 +0800 Subject: [PATCH 19/19] Reconstruct the command completion function --- .../bandit/common/command/BanditCommand.kt | 108 +++++++++--------- 1 file changed, 54 insertions(+), 54 deletions(-) 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 672876b..a3caad7 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 @@ -40,69 +40,69 @@ 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()) { - "executor-generator", "executor" -> { - getListOfStringsMatchingLastWord( - args, - *ExecutorGeneratorRegistry.all().map { (_, gen) -> - gen.getUnlocalizedName().substringAfterLast('.') - }.toTypedArray() - ) - } - - "block-filter", "filter" -> { - getListOfStringsMatchingLastWord( - args, - *BlockFilterRegistry.all().map { (_, filter) -> - filter.getUnlocalizedName().substringAfterLast('.') - }.toTypedArray() - ) - } + class CommandNode(val name: String) { + private val _sub = mutableListOf() + val sub: List get() = _sub - "drop_pos" -> { - getListOfStringsMatchingLastWord( - args, - *getValidEnumValues().toTypedArray() - ) + fun sub(vararg nodesOrNames: Any): CommandNode { + nodesOrNames.forEach { + when (it) { + is CommandNode -> _sub.add(it) + is String -> _sub.add(CommandNode(it)) + is Iterable<*> -> it.forEach { n -> if (n is String) _sub.add(CommandNode(n)) } + } } + return this + } - "drop_timing" -> { - getListOfStringsMatchingLastWord( - args, - *getValidEnumValues().toTypedArray() - ) - } + fun complete(args: List): List { + if (args.isEmpty()) return listOf(name) - "stop_on_release" -> { - getListOfStringsMatchingLastWord( - args, - "true", - "false" - ) - } + val head = args.first() + val tail = args.drop(1) - "stop ", "help" -> emptyList() - - else -> { - getListOfStringsMatchingLastWord( - args, - "stop", - "help", - "drop_pos", - "drop_timing", - "stop_on_release", - "block-filter", - "executor-generator" - ).takeIf { it.isNotEmpty() } ?: super.addTabCompletionOptions(sender, args) + return when { + !name.startsWith(head) -> emptyList() + tail.isEmpty() -> listOf(name) + name == head -> sub.flatMap { it.complete(tail) } + else -> emptyList() } } } + + override fun addTabCompletionOptions(sender: ICommandSender, args: Array): List { + val commandTree = listOf( +// Example: +// CommandNode("complex").sub( +// CommandNode("subLevel").sub( +// CommandNode("subLevel2a").sub("foo", "bar"), +// CommandNode("subLevel2b").sub(stringListFun()), +// CommandNode("subLevel2c") +// ), +// ), + CommandNode("stop"), + CommandNode("help"), + + CommandNode("stop_on_release").sub("true", "false"), + CommandNode("drop_pos").sub(getValidEnumValues()), + CommandNode("drop_timing").sub(getValidEnumValues()), + + CommandNode("executor-generator").sub( + ExecutorGeneratorRegistry.all().values.map { + it.getUnlocalizedName().substringAfterLast('.') + } + ), + CommandNode("block-filter").sub( + BlockFilterRegistry.all().values.map { + it.getUnlocalizedName().substringAfterLast('.') + } + ) + ) + + return commandTree.flatMap { it.complete(args.toList()) } + } + private fun ICommandSender.withEntityPlayer(block: (EntityPlayerMP) -> Unit) { if(this is EntityPlayerMP) { block(this)