修复:!!hat 副手缓冲交换与 !!share 展示名 (26.9.5)
This commit is contained in:
1 parent
41638e8037
commit
367962d45b
7 files changed
+82
-36
No files matched your search
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
本文件记录 Iridium 的重要变更。
|
本文件记录 Iridium 的重要变更。
|
||||||
|
|
||||||
|
## [26.9.5] - 2026-09-13
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `!!hat`(1.17+):改用副手缓冲做真正交换,避免复杂 NBT 重建失败导致原头盔消失;副手非空时拒绝并提示
|
||||||
|
- `!!share`:展示名优先取 `custom_name` / `display.Name` / translate,不再只显示物品 ID
|
||||||
|
|
||||||
## [26.9.4] - 2026-09-13
|
## [26.9.4] - 2026-09-13
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ MCDReforged 插件:物品展示 / 戴头 / 获取头颅 / 游戏内计算器 /
|
|||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 版本 | **26.9.4** |
|
| 版本 | **26.9.5** |
|
||||||
| 作者 | 云镜之端/SKYMirror |
|
| 作者 | 云镜之端/SKYMirror |
|
||||||
| 许可 | 保留所有权益(All Rights Reserved) |
|
| 许可 | 保留所有权益(All Rights Reserved) |
|
||||||
| 前置 | MCDReforged `>= 2.6.0` |
|
| 前置 | MCDReforged `>= 2.6.0` |
|
||||||
|
|||||||
+42
-30
@@ -258,47 +258,59 @@ def _extract_item_payload(payload: str, version: Version) -> Optional[str]:
|
|||||||
|
|
||||||
|
|
||||||
def _do_swap(server: PluginServerInterface, src: CommandSource, player: str, version: Version) -> bool:
|
def _do_swap(server: PluginServerInterface, src: CommandSource, player: str, version: Version) -> bool:
|
||||||
if not is_at_least(version, "1.13"):
|
name = sanitize_name(player)
|
||||||
# Single entitydata dump contains both SelectedItem and head Inventory slot
|
head_slot = _head_slot(version)
|
||||||
|
hand_slot = _hand_slot(version, 0)
|
||||||
|
|
||||||
|
# 1.17+: true swap via offhand buffer — no need to rebuild item NBT
|
||||||
|
if is_at_least(version, "1.17"):
|
||||||
|
hand_raw = rcon_query(server, _selected_query(player, version))
|
||||||
|
if hand_raw is None:
|
||||||
|
src.reply(RText(tr("hat_query_failed"), RColor.red))
|
||||||
|
return False
|
||||||
|
hand = _extract_item_payload(hand_raw, version)
|
||||||
|
if hand is None or _is_air(hand):
|
||||||
|
src.reply(RText(tr("hand_is_empty"), RColor.red))
|
||||||
|
return False
|
||||||
|
|
||||||
|
off_raw = rcon_query(server, f"data get entity {name} weapon.offhand")
|
||||||
|
off = None
|
||||||
|
if off_raw is not None:
|
||||||
|
off = _extract_item_payload(off_raw, version)
|
||||||
|
if off and not _is_air(off):
|
||||||
|
src.reply(RText(tr("hat_offhand_busy"), RColor.red))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 1) save head -> offhand 2) hand -> head 3) offhand -> hand 4) clear offhand
|
||||||
|
safe_execute(
|
||||||
|
server,
|
||||||
|
f"item replace entity {name} weapon.offhand from entity {name} {head_slot}",
|
||||||
|
)
|
||||||
|
safe_execute(
|
||||||
|
server,
|
||||||
|
f"item replace entity {name} {head_slot} from entity {name} weapon.mainhand",
|
||||||
|
)
|
||||||
|
safe_execute(
|
||||||
|
server,
|
||||||
|
f"item replace entity {name} weapon.mainhand from entity {name} weapon.offhand",
|
||||||
|
)
|
||||||
|
safe_execute(server, f"item replace entity {name} weapon.offhand with air")
|
||||||
|
src.reply(RText(tr("hat_success"), RColor.green))
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 1.8–1.16: rebuild via parsed SNBT
|
||||||
dump = rcon_query(server, _entitydata_query(player))
|
dump = rcon_query(server, _entitydata_query(player))
|
||||||
if dump is None:
|
if dump is None:
|
||||||
src.reply(RText(tr("hat_query_failed"), RColor.red))
|
src.reply(RText(tr("hat_query_failed"), RColor.red))
|
||||||
return False
|
return False
|
||||||
hotbar_index = _extract_selected_slot(dump)
|
hotbar_index = _extract_selected_slot(dump)
|
||||||
|
hand_slot = _hand_slot(version, hotbar_index)
|
||||||
hand = _extract_item_payload(dump, version)
|
hand = _extract_item_payload(dump, version)
|
||||||
if hand is None or _is_air(hand):
|
if hand is None or _is_air(hand):
|
||||||
src.reply(RText(tr("hand_is_empty"), RColor.red))
|
src.reply(RText(tr("hand_is_empty"), RColor.red))
|
||||||
return False
|
return False
|
||||||
head = _extract_head_item(dump, version)
|
head = _extract_head_item(dump, version)
|
||||||
else:
|
|
||||||
hand_raw = rcon_query(server, _selected_query(player, version))
|
|
||||||
if hand_raw is None:
|
|
||||||
src.reply(RText(tr("hat_query_failed"), RColor.red))
|
|
||||||
return False
|
|
||||||
hotbar_index = 0
|
|
||||||
hand = _extract_item_payload(hand_raw, version)
|
|
||||||
if hand is None or _is_air(hand):
|
|
||||||
src.reply(RText(tr("hand_is_empty"), RColor.red))
|
|
||||||
return False
|
|
||||||
head = None
|
|
||||||
head_raw = rcon_query(server, _head_query(player, version))
|
|
||||||
if head_raw is not None:
|
|
||||||
head = _extract_head_item(head_raw, version)
|
|
||||||
|
|
||||||
head_slot = _head_slot(version)
|
|
||||||
hand_slot = _hand_slot(version, hotbar_index)
|
|
||||||
name = sanitize_name(player)
|
|
||||||
|
|
||||||
if is_at_least(version, "1.17"):
|
|
||||||
safe_execute(
|
|
||||||
server,
|
|
||||||
f"item replace entity {name} {head_slot} from entity {name} weapon.mainhand",
|
|
||||||
)
|
|
||||||
if head and not _is_air(head):
|
|
||||||
safe_execute(server, _replace_with(player, hand_slot, head, version))
|
|
||||||
else:
|
|
||||||
safe_execute(server, f"item replace entity {name} {hand_slot} with air")
|
|
||||||
else:
|
|
||||||
safe_execute(server, _replace_with(player, head_slot, hand, version))
|
safe_execute(server, _replace_with(player, head_slot, hand, version))
|
||||||
if head and not _is_air(head):
|
if head and not _is_air(head):
|
||||||
safe_execute(server, _replace_with(player, hand_slot, head, version))
|
safe_execute(server, _replace_with(player, hand_slot, head, version))
|
||||||
|
|||||||
+30
-4
@@ -63,6 +63,35 @@ def _json_str(value: str) -> str:
|
|||||||
return json.dumps(value, ensure_ascii=False)[1:-1]
|
return json.dumps(value, ensure_ascii=False)[1:-1]
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_display_name(item_snbt: str) -> str:
|
||||||
|
"""Best-effort human-readable name from item SNBT."""
|
||||||
|
# JSON text component: "text":"Name"
|
||||||
|
m = re.search(r'"text"\s*:\s*"([^"]+)"', item_snbt)
|
||||||
|
if m and m.group(1).strip():
|
||||||
|
return _strip_mc_codes(m.group(1).strip())
|
||||||
|
# display.Name / custom_name as plain string
|
||||||
|
m = re.search(r'(?:Name|custom_name)\s*:\s*"([^"]+)"', item_snbt)
|
||||||
|
if m and m.group(1).strip():
|
||||||
|
return _strip_mc_codes(m.group(1).strip())
|
||||||
|
m = re.search(r"(?:Name|custom_name)\s*:\s*'([^']+)'", item_snbt)
|
||||||
|
if m and m.group(1).strip():
|
||||||
|
return _strip_mc_codes(m.group(1).strip())
|
||||||
|
# translation key fallback (e.g. block.ae2.fluix_covered_cable)
|
||||||
|
m = re.search(r'"translate"\s*:\s*"([^"]+)"', item_snbt)
|
||||||
|
if m and m.group(1).strip():
|
||||||
|
key = m.group(1).strip()
|
||||||
|
short = key.rsplit(".", 1)[-1]
|
||||||
|
return short.replace("_", " ")
|
||||||
|
id_match = re.search(r'id\s*:\s*"([^"]+)"', item_snbt)
|
||||||
|
if id_match:
|
||||||
|
return id_match.group(1)
|
||||||
|
return "?"
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_mc_codes(text: str) -> str:
|
||||||
|
return re.sub(r"§[0-9a-fk-orA-FK-OR]", "", text)
|
||||||
|
|
||||||
|
|
||||||
def _build_tellraw(player: str, item_snbt: str, version: Version) -> str:
|
def _build_tellraw(player: str, item_snbt: str, version: Version) -> str:
|
||||||
# Truncate pathological NBT (huge books etc.) so the command stays valid
|
# Truncate pathological NBT (huge books etc.) so the command stays valid
|
||||||
if len(item_snbt) > MAX_SNBT_LEN:
|
if len(item_snbt) > MAX_SNBT_LEN:
|
||||||
@@ -88,10 +117,7 @@ def _build_tellraw(player: str, item_snbt: str, version: Version) -> str:
|
|||||||
click = '{"action":"suggest_command","value":"' + _json_str(suggest) + '"}'
|
click = '{"action":"suggest_command","value":"' + _json_str(suggest) + '"}'
|
||||||
click_label = _json_str(tr("click_suggest"))
|
click_label = _json_str(tr("click_suggest"))
|
||||||
|
|
||||||
id_match = re.search(r'id\s*:\s*"([^"]+)"', item_snbt)
|
display = _extract_display_name(item_snbt)
|
||||||
item_id = id_match.group(1) if id_match else "?"
|
|
||||||
name_match = re.search(r'"text"\s*:\s*"([^"]+)"', item_snbt)
|
|
||||||
display = name_match.group(1) if name_match else item_id
|
|
||||||
display_js = _json_str(display)
|
display_js = _json_str(display)
|
||||||
|
|
||||||
json_text = (
|
json_text = (
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ iridium.hat_success: 'Swapped hand item with head equipment'
|
|||||||
iridium.hat_unsupported_version: 'Version {0} does not support !!hat (needs Minecraft 1.8+)'
|
iridium.hat_unsupported_version: 'Version {0} does not support !!hat (needs Minecraft 1.8+)'
|
||||||
iridium.hat_need_rcon: '!!hat requires RCON enabled to read and swap item data'
|
iridium.hat_need_rcon: '!!hat requires RCON enabled to read and swap item data'
|
||||||
iridium.hat_query_failed: 'Failed to query player item data'
|
iridium.hat_query_failed: 'Failed to query player item data'
|
||||||
|
iridium.hat_offhand_busy: 'Off-hand is occupied; clear it before using !!hat'
|
||||||
iridium.head_help: 'Get one player head (optional player name, cooldown {0}s)'
|
iridium.head_help: 'Get one player head (optional player name, cooldown {0}s)'
|
||||||
iridium.head_help_short: 'Get one player head (optional player name)'
|
iridium.head_help_short: 'Get one player head (optional player name)'
|
||||||
iridium.head_success: 'You received a head of {0}'
|
iridium.head_success: 'You received a head of {0}'
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ iridium.hat_success: '已交换手上物品与头部装备'
|
|||||||
iridium.hat_unsupported_version: '当前版本 {0} 不支持 !!hat(需要 Minecraft 1.8+)'
|
iridium.hat_unsupported_version: '当前版本 {0} 不支持 !!hat(需要 Minecraft 1.8+)'
|
||||||
iridium.hat_need_rcon: '!!hat 需要启用 RCON 才能读取并交换物品数据'
|
iridium.hat_need_rcon: '!!hat 需要启用 RCON 才能读取并交换物品数据'
|
||||||
iridium.hat_query_failed: '读取玩家物品数据失败'
|
iridium.hat_query_failed: '读取玩家物品数据失败'
|
||||||
|
iridium.hat_offhand_busy: '副手有物品,无法安全交换;请先清空副手再使用 !!hat'
|
||||||
iridium.head_help: '获取一个玩家头颅(可指定玩家名,冷却 {0} 秒)'
|
iridium.head_help: '获取一个玩家头颅(可指定玩家名,冷却 {0} 秒)'
|
||||||
iridium.head_help_short: '获取一个玩家头颅(可指定玩家名)'
|
iridium.head_help_short: '获取一个玩家头颅(可指定玩家名)'
|
||||||
iridium.head_success: '已给予你 {0} 的头颅'
|
iridium.head_success: '已给予你 {0} 的头颅'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"id": "iridium",
|
"id": "iridium",
|
||||||
"version": "26.9.4",
|
"version": "26.9.5",
|
||||||
"name": "Iridium",
|
"name": "Iridium",
|
||||||
"description": {
|
"description": {
|
||||||
"zh_cn": "物品展示 / 戴头 / 获取头颅 / 游戏内计算器 / 进服 MOTD",
|
"zh_cn": "物品展示 / 戴头 / 获取头颅 / 游戏内计算器 / 进服 MOTD",
|
||||||
|
|||||||
Reference in new issue
Block a user