生命值与硬核模式 (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)。