生命值与硬核模式 (Lives & Hardcore)

基本信息

属性 值
生命数据库 betterquesting.storage.LifeDatabase(单例 INSTANCE,final class)
API 键 ApiReference.LIFE_DB
存档标签 playerLives(NBTTagList)
硬核开关 世界属性 NativeProps.HARDCORE,默认 false
初始生命 NativeProps.LIVES_DEF,默认 3
生命上限 NativeProps.LIVES_MAX,默认 10
事件处理 betterquesting.handlers.EventHandler(注册于 FMLCommonHandler 与 MinecraftForge 两个总线)
恢复道具 额外生命
相关封包 betterquesting:life_sync

功能

硬核模式把 BetterQuesting 从「任务追踪器」升级为「死亡惩罚系统」。开启后:

  • 玩家每次死亡扣 1 条命
  • 生命耗尽即被永久封禁
  • 单人游戏下则直接删除世界并关闭服务器

生命数据库

LifeDatabase 是一个极简的 HashMap<UUID, Integer>,三个方法的语义:

方法 行为
getLives(UUID) computeIfAbsent(uuid, k -> QuestSettings.getProperty(LIVES_DEF)) —— 未记录的玩家首次查询时才写入默认生命,不是注册时写入
setLives(UUID, value) put(uuid, MathHelper.clamp_int(value, 0, LIVES_MAX)) —— 硬夹在 0..LIVES_MAX
reset() playerLives.clear()

存档格式(LifeDatabase.writeToNBT / readFromNBT):一个 playerLives NBTTagList,每项含 uuid(字符串)与 lives(整数)两个字段。writeToNBT 的 users 参数有实际作用 —— if (users != null && !users.contains(entry.getKey())) continue; 可只写出指定玩家的生命(用于队伍等局部同步场景)。这一点与 奖励框架 中 RewardStorage.writeToNBT 的 subset 参数形成对照 —— 后者未使用该参数。

readFromNBT(NBTTagCompound, boolean merge) 的 merge 为假时先 playerLives.clear() 再读;单个条目解析失败(UUID.fromString 抛异常)被 catch (Exception ignored) {} 静默跳过。

死亡扣命

EventHandler.onLivingDeath(LivingDeathEvent)(EventHandler.java:472-482):

if (event.entityLiving.worldObj.isRemote || !QuestSettings.INSTANCE.getProperty(NativeProps.HARDCORE)) return;
if (event.entityLiving instanceof EntityPlayer) {
    UUID uuid = QuestingAPI.getQuestingUUID(((EntityPlayer) event.entityLiving));
    int lives = LifeDatabase.INSTANCE.getLives(uuid);
    LifeDatabase.INSTANCE.setLives(uuid, lives - 1);
}

扣命发生在死亡瞬间(LivingDeathEvent),扣命数经 setLives 的 clamp_int 兜底,不会变负。

复活结算

EventHandler.onPlayerRespawn(PlayerRespawnEvent)(EventHandler.java:428-466)在复活时结算。开头有三重短路:HARDCORE 为假、或事件方不是 EntityPlayerMP、或 playerConqueredTheEnd(已通关)—— 三者任一成立则完全不处理。

结算逻辑:

  • 生命 > 0 → 聊天提示。恰好 == 1 时发 This is your last life!(英文硬编码),否则发 <n> lives remaining!(EventHandler.java:459-463)
  • 生命 <= 0 → 游戏结束,分两种情形:
    • 单人游戏且玩家是服主(server.isSinglePlayer() && player.getCommandSenderName().equals(server.getServerOwner())):kickPlayerFromServer("You have died. Game over, man, it\'s game over!") 后调 server.deleteWorldAndStopServer() —— 存档被删除
    • 多人或其他情形:构造 UserListBansEntry(gameProfile, null, "(You just lost the game)", null, "Death in Hardcore") 加入封禁名单,再踢出

⚠️ 上述两条提示语与封禁理由 Death in Hardcore 均为英文硬编码字符串,不走 lang 文件,因此无法本地化。

指令控制

指令 效果 来源
/bq_admin hardcore 取反切换 HARDCORE 并回显 options.on / options.off QuestCommandHardcore.java:48, 64, 71
/bq_user hardcore 单向置 true QuestCommandSPHardcore.java:33
/bq_admin lives 调整生命值 commands/admin/QuestCommandLives.java
/bq_admin default 载入默认任务库时保留硬核状态(hardMode 变量在载入前后各读一次) QuestCommandDefaults.java:364, 466, 488, 502

客户端界面也会读 HARDCORE 决定队伍面板的表现(client/gui2/party/GuiPartyManage.java:198)。

数值

数值名 值 来源
初始生命(LIVES_DEF) 3 NativeProps.java:165-167
生命上限(LIVES_MAX) 10 NativeProps.java:168-170
setLives 夹取范围 0 .. LIVES_MAX LifeDatabase.java:32-33
生命默认值写入时机 首次 getLives 的 computeIfAbsent LifeDatabase.java:26-27
死亡扣命量 1 EventHandler.java:479-480
「剩最后一条命」阈值 恰好 lives == 1 EventHandler.java:459
免结算情形 硬核关闭 / 非 EntityPlayerMP / playerConqueredTheEnd EventHandler.java:428-430
单人游戏结束方式 server.deleteWorldAndStopServer()(删档) EventHandler.java:444-445
多人游戏结束方式 加入 UserListBansEntry 封禁名单 EventHandler.java:447-456
可本地化的结束提示 0 条(全英文硬编码) EventHandler.java:442, 456, 460-462

死代码与隐患(据实记录)

  • EventHandler 在两个总线上各注册一次同一个实例(CommonProxy.registerHandlers():FMLCommonHandler.instance().bus().register(evHandle) 与 MinecraftForge.EVENT_BUS.register(evHandle),bq_standard/core/proxies/CommonProxy.java:48-54)。这是 Forge 1.7.10 的常规做法,不构成重复触发,此处仅作事实记录。
  • LifeDatabase.readFromNBT 中 catch (Exception ignored) {} 会静默吞掉损坏的 UUID 条目,不产生任何日志(LifeDatabase.java:69)。

相关条目

  • 额外生命 - 硬核模式下唯一的回命道具
  • 任务属性 - HARDCORE / LIVES / LIVES_DEF / LIVES_MAX 的定义
  • 世界任务设置 - 存世界存档的 12 项设置中的 3 项
  • 指令 - 4 条控制硬核与生命的指令
  • 队伍系统 - LifeDatabase.writeToNBT 的 users 参数支持只同步部分成员