玩家互传(TPA)
玩家之间互相发起、接受传送请求的完整系统,含玩家列表、拒绝与超时处理
玩家互传(TPA)让任意两名玩家互相发起传送请求:一方在菜单里选中目标玩家,另一方抬头同意即可传送,低头或超时则取消。本文档整理自原作者分享的命令组,并补充了原理解释。
如果你只需要「发起 → 接受 → 传送」的简化版,可参考
/docs/community/3/(雪球菜单)中的发起/接受传送部分。
原理解释
三个核心状态
-
传送菜单(菜单状态机)—— 记录每位玩家「当前在菜单里做什么」:40= 菜单基态(已打开、未选中任何行)4110 行50= 正在浏览列表第 151= 选中「刷新列表」100= 已被选为传送目标(等待回应)101= 已接受请求(接受方)1000+ = 取消/超时标记,2000= 清理标记5= 从上级菜单选中「互传」后的入口状态(指令 1 把它归位成40)
-
传送(配对 ID)—— 每位玩家的「槽位号」:0~39= 在线玩家的列表编号(第 0 位、第 1 位……)40+ = 已发起请求的玩家(持有正槽位)-40- = 等待回应的目标(持有负槽位)
发起方和目标持有同一个槽位号,只是目标取反:谁把正负相加抵消成
0,谁就是发起方,用这一个数记住「谁和谁是一对」。 -
rx/rxm(视角)—— 抬头rx=-88= 确认/选择,低头rxm=88= 拒绝/取消,与雪球菜单一致。
+41 位移技巧
列表第 1 行对应 传送菜单=41,而这一行玩家的槽位号是 0,中间正好差 41。选择时先 add @a 传送 41(指令 7)把所有人整体 +41,于是第 N 行玩家的槽位临时变成 41+N,恰好等于他的 传送菜单。这样就能用 if score @s 传送 = @s 传送菜单 判断「选的是不是自己」(指令 8),用 if score @s 传送 = @p[...] 传送菜单 找出被点中的目标(指令 9~11)。判断完 remove @a 传送 41(指令 13)还原。
配对与接受
发起请求后:目标从 互传记录 领一个新槽位(指令 14),发号器 +1(指令 15),发起方抄下同一个槽位(指令 16),目标再取反为负(指令 18)。接受时目标抬头 → 传送菜单=101(指令 41),把目标的负槽位加到所有人(指令 42):发起方的正槽位被抵消成 0,于是 tp @a[scores={传送=0}] 精确命中发起方并传送到目标(指令 43),最后用相反操作撤销(指令 46)。
为什么用「假玩家」计数器
互传记录(配对发号器,从 40 起发)、负(常量 -1,用于取反)、检测/在线人员/互传(一致性检查)。其中 在线人员 每刻统计当前在线人数,与上一刻的基准 互传 比较:人数一旦变化就清理离线玩家残留的配对,随后把 互传 更新为最新值(指令 28)。用假玩家(fake player)而不是实体,是因为它们永远存在、不占实体,且分数能直接参与 scoreboard players operation 运算。
前置指令
在聊天栏中依次执行(创建计分板与常量):
/scoreboard objectives add 传送菜单 dummy
/scoreboard objectives add 传送 dummy
/scoreboard players set 负 传送 -1
/scoreboard players set 互传记录 传送 40
/gamerule commandblockoutput false
/gamerule sendcommandfeedback false
负是常量 -1(用于取反标记);互传记录是配对槽位发号器,从 40 起发,避免与列表编号 0~39 冲突。
本组指令是原作者「传送菜单」系统的互传模块,有两处依赖上级菜单,独立使用前需先补齐:
- 入口——玩家以
传送菜单=5的状态进入本菜单(指令 1 负责把它归位)。独立使用时需要自行设计一个触发器把玩家的传送菜单设为5(例如投掷雪球时scoreboard players set @p 传送菜单 5)。 - 列表槽位——玩家列表按每位玩家的
传送槽位号 0~39 排序显示,需保证在线玩家持有互不重复的编号。参考实现见文末「常见问题」。
命令方块摆放
按分组顺序摆放:每组以循环命令方块开头(每游戏刻持续监测),其余用连锁命令方块按箭头方向连接、在同一游戏刻内跟随执行。所有条件判断都写在 execute 的 if/unless 里,因此无需「有条件」命令方块。
一、菜单初始化与显示
指令 1 — 从上级菜单进入(把入口状态 传送菜单=5 归位为 40)
/scoreboard players set @a[scores={传送菜单=5,传送=!..-40},rx=-88] 传送菜单 40
指令 2 — 初始化传送分数
/scoreboard players add @a 传送 0
指令 3 — 清理非法状态
/scoreboard players set @a[scores={传送=!0..39,传送菜单=40..52}] 传送菜单 0
指令 4 — 显示菜单(抬头逐行高亮)
/execute as @a[scores={传送菜单=40..51}] run titleraw @s actionbar {"rawtext":[{"text":"§b▎ §f传送菜单 §d۞ §f申请传送 §b▎\n§7 - Select player for TPA -§r§7\n# -----------------------\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=41}]"},{"text":"§e■ "},{"text":"§b□§7 "}]}},{"selector":"@a[scores={传送=0}]"},{"text":"§7\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=42}]"},{"text":"§e■ "},{"text":"§b□§7 "}]}},{"selector":"@a[scores={传送=1}]"},{"text":"§7\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=43}]"},{"text":"§e■ "},{"text":"§b□§7 "}]}},{"selector":"@a[scores={传送=2}]"},{"text":"§7\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=44}]"},{"text":"§e■ "},{"text":"§b□§7 "}]}},{"selector":"@a[scores={传送=3}]"},{"text":"§7\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=45}]"},{"text":"§e■ "},{"text":"§b□§7 "}]}},{"selector":"@a[scores={传送=4}]"},{"text":"§7\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=46}]"},{"text":"§e■ "},{"text":"§b□§7 "}]}},{"selector":"@a[scores={传送=5}]"},{"text":"§7\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=47}]"},{"text":"§e■ "},{"text":"§b□§7 "}]}},{"selector":"@a[scores={传送=6}]"},{"text":"§7\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=48}]"},{"text":"§e■ "},{"text":"§b□§7 "}]}},{"selector":"@a[scores={传送=7}]"},{"text":"§7\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=49}]"},{"text":"§e■ "},{"text":"§b□§7 "}]}},{"selector":"@a[scores={传送=8}]"},{"text":"§7\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=50}]"},{"text":"§e■ "},{"text":"§b□§7 "}]}},{"selector":"@a[scores={传送=9}]"},{"text":"\n"},{"translate":"%%2","with":{"rawtext":[{"selector":"@s[scores={传送菜单=51}]"},{"text":"§e■"},{"text":"§6□§7"}]}},{"text":" Refresh - 刷新列表"},{"text":"§7\n--------------------------\n§l〈§r§a抬头确定§7§l〉§r§b ● §l§7〈§r§c低头关闭§7§l〉\n\n\n\n\n\n\n\n\n\n"}]}
指令 5 — 刷新列表(交换槽位)
/execute as @a[scores={传送菜单=51},rx=-88] unless entity @p[scores={传送菜单=41..50},rx=-88] run scoreboard players operation @a[scores={传送=1}] 传送 >< @a[scores={传送=0..39}] 传送
指令 6 — 刷新后重置光标
/execute as @a[scores={传送菜单=51},rx=-88] unless entity @p[scores={传送菜单=41..50},rx=-88] run scoreboard players set @a[scores={传送菜单=41..52}] 传送菜单 40
二、选择目标并发起请求
指令 7 — 全体槽位 +41 位移(把行号对齐到槽位号)
/execute as @p[scores={传送菜单=41..50},rx=-88] run scoreboard players add @a 传送 41
指令 8 — 检测「自己传自己」
/execute as @p[scores={传送菜单=41..50},rx=-88] if score @s 传送 = @s 传送菜单 run tellraw @s {"rawtext":[{"text":"§c▎§r 玩家互传 §7不可以向自己申请互传"}]}
指令 9 — 告知发起方已发送请求
/execute as @p[scores={传送菜单=41..50},rx=-88] unless score @s 传送 = @s 传送菜单 as @a if score @s 传送 = @p[scores={传送菜单=41..50},rx=-88] 传送菜单 run tellraw @p[scores={传送菜单=41..50},rx=-88] {"rawtext":[{"text":"§b▎ §r玩家互传 §7已向§e"},{"selector":"@s"},{"text":"§7申请互传\n - 低头可取消互传申请§f"}]}
指令 10 — 告知目标收到请求
/execute as @p[scores={传送菜单=41..50},rx=-88] unless score @s 传送 = @s 传送菜单 as @a if score @s 传送 = @p[scores={传送菜单=41..50},rx=-88] 传送菜单 run tellraw @s {"rawtext":[{"text":"§b▎§r 玩家互传 §7玩家§e"},{"selector":"@p[scores={传送菜单=41..50},rx=-88]"},{"text":"§7向你发出互传申请\n §a抬头同意 §7- §c低头拒绝"}]}
指令 11 — 标记目标(传送菜单=100)
/execute as @p[scores={传送菜单=41..50},rx=-88] unless score @s 传送 = @s 传送菜单 as @a if score @s 传送 = @p[scores={传送菜单=41..50},rx=-88] 传送菜单 run scoreboard players set @s 传送菜单 100
指令 12 — 目标不存在时提示
/execute as @p[scores={传送菜单=41..50},rx=-88] unless score @s 传送 = @s 传送菜单 unless entity @a[scores={传送菜单=100}] run tellraw @s {"rawtext":[{"text":"§c▎§r 玩家互传 §7你要申请的玩家不存在"}]}
指令 13 — 撤销 +41 位移
/execute as @p[scores={传送菜单=41..50},rx=-88] run scoreboard players remove @a 传送 41
三、配对记录
指令 14 — 目标领新槽位
/execute as @a[scores={传送菜单=100}] run scoreboard players operation @s 传送 = 互传记录 传送
指令 15 — 发号器 +1
/execute as @a[scores={传送菜单=100}] run scoreboard players add 互传记录 传送 1
指令 16 — 发起方抄下同一槽位
/scoreboard players operation @p[scores={传送菜单=41..50},rx=-88] 传送 = @a[scores={传送菜单=100}] 传送
指令 17 — 无目标时重置光标
/execute as @p[scores={传送菜单=41..50},rx=-88] unless entity @a[scores={传送菜单=100}] run scoreboard players set @s 传送菜单 40
指令 18 — 目标槽位取反(标记等待回应)
/scoreboard players operation @a[scores={传送菜单=100}] 传送 *= 负 传送
四、一致性维护与离线清理
指令 19 — 检测计数器归零
/scoreboard players set 检测 传送 0
指令 20 — 统计槽位 0 的玩家数
/execute as @a[scores={传送=0}] run scoreboard players add 检测 传送 1
指令 21 — 槽位 0 重复 → 标记为 -1
/execute if score 检测 传送 matches 2..40 run scoreboard players set @a[scores={传送=0..39}] 传送 -1
指令 22 — 槽位重复 → 扣除修正
/execute if score 检测 传送 matches 2..40 run scoreboard players operation @a[scores={传送=-1..39}] 传送 -= @a[scores={传送=-1}] 传送
指令 23 — 槽位重复 → 重置光标
/execute if score 检测 传送 matches 2..40 run scoreboard players set @a[scores={传送菜单=41..52,传送=0..39}] 传送菜单 40
指令 24 — 在线人数计数器归零
/scoreboard players set 在线人员 传送 0
指令 25 — 统计在线人数
/execute as @a[scores={传送=0..39}] run scoreboard players add 在线人员 传送 1
指令 26 — 在线人数变化 → 标记为 -1
/execute unless score 互传 传送 = 在线人员 传送 run scoreboard players set @a[scores={传送=0..39}] 传送 -1
指令 27 — 在线人数变化 → 扣除修正
/execute unless score 互传 传送 = 在线人员 传送 run scoreboard players operation @a[scores={传送=-1..39}] 传送 -= @a[scores={传送=-1}] 传送
指令 28 — 更新在线人数基准(互传 ← 在线人员)
/scoreboard players operation 互传 传送 = 在线人员 传送
五、取消 / 拒绝 / 离线
指令 29 — 目标低头 → 已拒绝提示
/tellraw @a[scores={传送=..-40},rxm=88] {"rawtext":[{"text":"§c▎§r 玩家互传 §7已拒绝对方的互传申请"}]}
指令 30 — 发起方低头 → 已取消提示
/tellraw @a[scores={传送=40..},rxm=88] {"rawtext":[{"text":"§c▎§r 玩家互传 §7已取消互传"}]}
指令 31 — 低头重置分数
/scoreboard players set @a[scores={传送=!0..39},rxm=88] 传送 0
指令 32 — 取消者标记 传送菜单=1000
/scoreboard players add @r[scores={传送=!0..39}] 传送菜单 1000
指令 33 — 抵消取消者分数
/execute as @a[scores={传送菜单=1000..}] run scoreboard players operation @a[scores={传送菜单=!1000..}] 传送 += @s 传送
指令 34 — 发起方侧:对方拒绝/离线提示
/execute as @a[scores={传送菜单=1000..,传送=40..}] unless entity @a[scores={传送=0}] run tellraw @s {"rawtext":[{"text":"§c▎§r 玩家互传 §7对方拒绝互传或离线"}]}
指令 35 — 目标侧:对方取消/离线提示
/execute as @a[scores={传送菜单=1000..,传送=..-40}] unless entity @a[scores={传送=0}] run tellraw @s {"rawtext":[{"text":"§c▎§r 玩家互传 §7对方取消互传或离线"}]}
指令 36 — 未配对成功 → 追加标记
/execute as @a[scores={传送菜单=1000..}] unless entity @a[scores={传送=0}] run scoreboard players add @s 传送菜单 1000
指令 37 — 撤销抵消
/execute as @a[scores={传送菜单=1000..}] run scoreboard players operation @a[scores={传送菜单=!1000..}] 传送 -= @s 传送
指令 38 — 清理 传送菜单=2000 残留
/scoreboard players set @a[scores={传送菜单=2000..}] 传送 0
指令 39 — 移除取消标记
/scoreboard players remove @a[scores={传送菜单=1000..}] 传送菜单 1000
六、接受并传送
指令 40 — 清理旧状态
/scoreboard players set @a[scores={传送菜单=100..}] 传送菜单 0
指令 41 — 目标抬头 → 接受(传送菜单=101)
/scoreboard players set @p[scores={传送=..-40},rx=-88] 传送菜单 101
指令 42 — 抵消配对(发起方分数归 0)
/execute as @p[scores={传送菜单=101}] run scoreboard players operation @a[scores={传送菜单=!101}] 传送 += @s 传送
指令 43 — 执行传送
/tp @a[scores={传送=0}] @p[scores={传送菜单=101}]
指令 44 — 告知接受方
/tellraw @p[scores={传送菜单=101}] {"rawtext":[{"text":"§a▎ §f玩家互传 §7已接受§e"},{"selector":"@a[scores={传送=0}]"},{"text":"§7的互传申请"}]}
指令 45 — 告知发起方
/execute as @p[scores={传送菜单=101}] run tellraw @a[scores={传送=0}] {"rawtext":[{"text":"§a▎§f 玩家互传 §7已将您传送至§e"},{"selector":"@s"}]}
指令 46 — 撤销抵消
/execute as @p[scores={传送菜单=101}] run scoreboard players operation @a[scores={传送菜单=!101,传送=!0}] 传送 -= @s 传送
指令 47 — 重置接受方分数
/scoreboard players set @p[scores={传送菜单=101}] 传送 0
常见问题
为什么「自己传自己」会被拒绝?
指令 8 用 if score @s 传送 = @s 传送菜单 检测选中行是否对应自己,若为自己则提示「不可以向自己申请互传」。这是通过 +41 位移后比较「自己的槽位」与「自己的光标」实现的。
为什么点列表没反应 / 提示玩家不存在?
通常是「列表槽位」没维护好:列表按每位玩家的 传送 槽位号(0~39)排序显示,若所有人都停在 传送=0,列表只会显示第一行、无法选中他人。请先按下面的方法给在线玩家分配互不重复的槽位号。
如何给玩家分配列表槽位?
原系统的列表编号由上级「传送菜单」统一发号。独立使用时,可加两个循环命令方块,每刻给最近一个「尚未分配」的玩家发下一个编号:
/execute as @p[scores={传送=0}] run scoreboard players operation @s 传送 = 列表编号 传送
/execute as @p[scores={传送=0}] run scoreboard players add 列表编号 传送 1
并把 列表编号 初始化为 1:
/scoreboard players set 列表编号 传送 1
注意:传送=0 被当作「待分配」的初始值,因此 0 号不会被真正分配出去,列表实际从 1 号开始显示(第一行留空属正常现象)。玩家离线的槽位不会自动回收,长期运行需结合指令 21~23 的一致性检查做清理。
离线玩家会残留导致列表错乱吗?
系统用 检测/在线人员 两个假玩家计数做一致性检查:槽位 0 重复、在线人数变化时,把受影响玩家的 传送 标记为 -1 并扣除修正(指令 19~27),随后指令 28 把在线人数基准 互传 更新为最新值,供下一轮比较。
标题里的「传送菜单」能改吗?
可以,那是原作者的菜单名,纯展示文案。把指令 4 里的 传送菜单 替换成你服务器的名字即可,不影响逻辑。
基岩版注意
- 全部为基岩版语法:
titleraw/actionbar/rawtext、scoreboard players operation的><(交换)、*=(乘)等运算、以及 selector 里的scores={传送=!0..39}(!表示「非」,即分数不在该区间)均为基岩版特有写法 - 涉及
/scoreboard、/execute、/tp、/tellraw、/title、/gamerule均需 OP 权限 - 本组指令原为「传送菜单」系统的互传模块:入口状态
传送菜单=5与列表槽位发号(传送0~39)由上级菜单维护,独立使用时需自行补齐这两处(见上文说明与常见问题)