客户端护甲渲染替换

[!INFO] 实现类:lain.mods.cos.client.PlayerRenderHandler(仅客户端,@SideOnly 通过 InventoryManagerClient 间接保证)

基本信息

属性 值
挂载点 MinecraftForge.EVENT_BUS(客户端事件总线)
事件 RenderPlayerEvent.Pre / .Post、RenderHandEvent、WorldEvent.Unload
全局开关 InventoryManagerClient.isRenderActive,默认 true
切换方式 成品界面 5×5 按钮 ID 77,或见 界面与按钮
服务端可见性 否,纯客户端视觉行为

功能

不改玩家数据,而是在渲染前把 player.inventory.armorInventory 临时替换成时装护甲,渲染后原样还原。核心是「换入 → 渲染 → 换回」三段式,缓存 Map<EntityPlayer, ItemStack[]> realArmorsCache 保存真实护甲快照。

事件与优先级

事件 优先级 receiveCanceled 动作
RenderPlayerEvent.Pre HIGH 否 restorePlayersRealArmor() 后 swapForCosmeticArmor()
RenderPlayerEvent.Post LOW 否 restorePlayersRealArmor()
RenderHandEvent(本地玩家) HIGH 否 restorePlayersRealArmor() 后 swapForCosmeticArmor()
RenderHandEvent(本地玩家) LOWEST 是 仅 restorePlayersRealArmor()
RenderPlayerEvent.Pre LOWEST 是 事件被取消时补一次 restorePlayersRealArmor()
WorldEvent.Unload — — world.isRemote 时清空 realArmorsCache

后两个 LOWEST + receiveCanceled 订阅是安全网:如果别的 mod 在 Pre 阶段 setCanceled(),本 mod 的换入可能没走完 Post,因此补一次还原,避免玩家护甲被永久留在 null 状态。

换入逻辑

ItemStack[] armor = player.inventory.armorInventory;
realArmorsCache.put(player, Arrays.copyOf(armor, armor.length));   // 深拷贝快照
for (int i = 0; i < cosArmor.length; i++) {
    if (invCosArmor.isSkinArmor(i)) armor[i] = null;
    else if (cosArmor[i] != null)    armor[i] = cosArmor[i];
}

要点:

  • 用 Arrays.copyOf 做快照,因此渲染期间原版护甲数据仍在,只是数组引用被临时改写。
  • 时装槽为空时不覆盖原版护甲——armor[i] 保持原值,实现「逐件替换」。
  • 判定 皮肤模式开关 时优先于时装物品。
  • 缓存是 HashMap,每次渲染后即被 remove 清空(restorePlayersRealArmor 首行即 realArmorsCache.remove(player)),因此 WorldEvent.Unload 的清理实际是冗余的(源码注释也这么说明)。

还原逻辑

ItemStack[] cachedArmor = realArmorsCache.remove(player);
if (cachedArmor != null && armor != null && cachedArmor.length == armor.length)
    System.arraycopy(cachedArmor, 0, armor, 0, cachedArmor.length);

长度不匹配时放弃还原(静默),这是防御性写法:若中途有其他 mod 改动了 armorInventory 的长度,原数组会被丢弃而不是写入越界。

开关的注册与状态

InventoryManagerClient.init() 里直接调用一次 toggleRenderer(),把 isRenderActive 从 false 翻成 true——默认开启。toggleRenderer() 同时负责向 MinecraftForge.EVENT_BUS 注册/反注册整个 PlayerRenderHandler,因此关闭后所有渲染钩子彻底停摆(不只是 if 判定跳过)。

ClientDisconnectionFromServerEvent 中:若 !isRenderActive 则补一次 toggleRenderer(),并 cacheClient.clear() 清空客户端护甲影子缓存——避免重连服务器后沿用旧玩家数据。

限制

  • 完全客户端,服务端玩家看不到自己以外的任何差异之外的东西——其他玩家的皮肤模式可见性依赖 网络封包 下发的 isSkinArmor 标志。
  • 不改变任何护甲数值、伤害减免或摔落判定,仅影响渲染。
  • 不渲染到手持物(RenderHandEvent 只还原本地玩家护甲,不替换手上物品)。

相关条目