玩法机制
这一页说明 LyCamp 的阵营战斗、声望、称号、属性、掉落、传送和数据存储规则。配置键的完整说明见 配置说明。
玩家数据
LyCamp 会为玩家记录以下状态:
| 数据 | 说明 |
|---|---|
| 所属阵营 | 对应 camp 下的阵营名称,未加入阵营时为空 |
| 当前声望 | 可被死亡或管理操作扣除的声望 |
| 累计声望 | 用于匹配称号和称号属性,只会被指定管理操作或退出阵营降低 |
| 今日剩余声望 | 当天还可以获得的当前声望数量 |
| 上次每日刷新时间 | 用于判断今日剩余额度是否需要刷新 |
| 上次击杀时间 | 用于判断当天的首次击杀是否完成 |
玩家数据未加载完成时,阵营战斗、传送检查和部分死亡结算不会正常进行。
阵营战斗规则
阵营 PVP 只在 pvp-world 中配置的世界生效。普通世界中的玩家伤害不会被 LyCamp 接管。
玩家攻击另一名玩家时,会依次检查:
- 攻击者所在世界是否为已配置的 PVP 世界。
- 攻击者和目标的玩家数据是否均已加载。
- 攻击者是否加入了一个仍然存在于配置中的阵营。
- 目标是否加入了一个仍然存在于配置中的阵营。
- 目标阵营是否存在于攻击者阵营的
hostiles列表中。 - 当前时间是否处于该世界的 PVP 开放时间内。
任意条件不满足时,本次伤害会被取消。
敌对关系是单向的
hostiles 只读取攻击者阵营的配置,不会自动建立双向敌对关系。
如果阵营 A 可以攻击阵营 B,而阵营 B 也需要攻击阵营 A,两个阵营都必须配置对方:
yaml
camp:
阵营A:
hostiles:
- '阵营B'
阵营B:
hostiles:
- '阵营A'同阵营玩家以及未被列入 hostiles 的阵营玩家无法互相造成直接玩家伤害。
ProtocolLib 拦截
插件始终注册 Bukkit 玩家伤害监听。如果服务器安装并启用了 ProtocolLib,插件还会注册玩家攻击数据包监听,对 USE_ENTITY 攻击动作执行相同的阵营和时间检查。
ProtocolLib 未启用时不会注册数据包层拦截,但 Bukkit 伤害监听仍然存在。
PVP 开放时间
每个 PVP 世界拥有独立的开始和结束时间:
yaml
pvp-world:
pvp_world:
pvp-start-time: '0 0 9 * * ?'
pvp-end-time: '0 0 18 * * ?'默认示例表示每天 9 点开始、18 点结束。非开放时间攻击玩家时,伤害会被取消,并向攻击者发送该世界的 not-pvp-message。
时间表达式使用六段格式:
text
秒 分 时 日 月 周当前时间范围解析支持数字以及以下基础符号:
| 符号 | 作用 |
|---|---|
* | 任意值 |
/ | 步长 |
, | 多个值 |
- | 连续范围 |
? | 不指定日期或星期,只能用于“日”或“周”字段 |
“日”和“周”字段不能同时使用 ?。开始时间晚于结束时间时,插件会按跨日时间段处理。
时间判断使用严格边界比较,恰好命中开始时间或结束时间的那一秒不属于开放区间。无法解析表达式时,本次检查会返回非 PVP 时间。
如果开始或结束表达式配置为每秒触发:
text
* * * * * ?该端时间限制会被视为不限制。
进入 PVP 世界
当玩家通过传送事件从其它世界进入 PVP 世界时,插件会读取 must-have-camp-to-teleport。
设置为 true 时,玩家必须同时满足:
| 条件 | 说明 |
|---|---|
| 玩家数据已加载 | 玩家必须存在于 LyCamp 的在线数据中 |
| 已加入有效阵营 | 阵营不能为空,并且应存在于当前阵营配置中 |
| 存在阵营位置 | 目标世界的 respawn-location 必须包含该阵营名称 |
任意条件不满足时,传送会被取消,并发送 message.must-have-camp-to-teleport。
设置为 false 时,进入该世界不会进行上述阵营位置检查。
这里检查的是跨世界传送事件。插件配置注释仍建议阻止玩家通过其它未受控方式进入 PVP 地图。
离开 PVP 世界
非 OP 玩家从任意已配置的 PVP 世界传送到另一个世界时,传送会被直接取消。
此限制不读取 allow-command,也不检查目标世界。即使某条传送命令被允许执行,最终产生的跨世界传送仍会被阻止。
OP 玩家不受离开限制。管理指令 /lyzy tp 由具有 OP 状态的命令发送者执行,用于把指定玩家传送到目标 PVP 世界的阵营位置。
阵营重生点
玩家在 PVP 世界死亡并触发重生时,插件会根据玩家当前阵营查找该世界的 respawn-location。
| 状态 | 重生结果 |
|---|---|
| 找到玩家阵营对应的位置 | 将重生点设置为该位置 |
| 玩家数据未加载 | 不修改重生点 |
| 玩家没有阵营 | 不修改重生点 |
| 该世界没有对应阵营位置 | 不修改重生点 |
位置格式为:
text
x,y,z,yaw,pitch世界必须在插件加载 PVP 世界配置时已经存在,否则该世界的阵营位置不会载入。
声望体系
LyCamp 分别记录“当前声望”和“累计声望”。两者用途不同,不能互相替代。
| 类型 | 主要用途 | 是否会因死亡扣除 |
|---|---|---|
| 当前声望 | 记录玩家当前可用的阵营声望 | 会 |
| 累计声望 | 匹配称号、称号属性和声望上限 | 不会 |
阵营的声望上限取该阵营 prestige 配置中最大的区间上限。当前声望和累计声望都不会高于这个值,也不会低于 0。
如果阵营没有有效的 prestige 配置,其最大声望为 0,玩家无法正常增加当前声望或累计声望。
声望变化
| 场景 | 当前声望 | 累计声望 | 今日剩余额度 |
|---|---|---|---|
| 在开放时间击杀玩家 | 增加 | 增加 | 扣除实际增加量 |
| 在开放时间被玩家击杀 | 扣除 | 不变 | 不变 |
| 在开放时间非玩家原因死亡 | 扣除 | 不变 | 不变 |
| PVP 世界内退出服务器 | 不变 | 不变 | 不变 |
/lyzy given | 增加 | 增加 | 扣除实际增加量 |
/lyzy taken | 扣除 | 不变 | 不变 |
/lyzy setn 向上设置 | 增加 | 增加 | 扣除实际增加量 |
/lyzy setn 向下设置 | 扣除 | 不变 | 不变 |
/lyzy givec | 不变 | 增加 | 不变 |
/lyzy takec | 不变 | 扣除 | 不变 |
/lyzy setc | 不变 | 设置为目标累计声望 | 不变 |
/lyzy setcamp 退出阵营 | 清零 | 清零 | 清零 |
击杀奖励读取当前 PVP 世界的 kill-give-prestige,死亡扣除读取 dead-take-prestige。
退出阵营时,还会把上次每日刷新时间清零。上次击杀时间不会在退出阵营时清零。
增加当前声望
增加当前声望时,会同时受到三项限制:
- 本次增加量不能超过今日剩余额度。
- 当前声望不能超过阵营最大声望。
- 累计声望不能超过阵营最大声望。
最终实际增加量可能小于配置值或管理指令输入值。只有实际增加的部分会从今日剩余额度中扣除,并同步加入累计声望。
今日额度耗尽时,会发送 message.today-max-prestige。累计声望达到上限、无法继续增加时,会发送 message.cumulative-max-prestige。
扣除当前声望
死亡和 /lyzy taken 只扣除当前声望,不减少累计声望,也不会返还今日剩余额度。
实际扣除量不会超过玩家已有的当前声望,因此当前声望最低为 0。
管理操作的声望限制
管理指令不会绕过声望上下限。
/lyzy given和向上调整的/lyzy setn仍受今日额度、当前声望上限和累计声望上限限制。/lyzy givec和/lyzy setc不消耗今日额度,但累计声望仍会被限制在0至阵营最大声望之间。/lyzy takec只能把累计声望扣到0。- 累计声望降低时,不会执行称号区间中的奖励指令。
每日声望额度
every-day-max-get-prestage-value 用于设置玩家每天最多还能获得多少当前声望。
插件计算额度时以 1000 为基础值,然后检查:
- 名为
default的配置。 - 玩家拥有的权限配置。
- 所有满足条件的配置中数值最高的一项。
只有高于当前结果的数值才会覆盖。因此,将 default 配置为低于 1000 不会降低基础额度。
权限键中的下划线会在加载时替换为点:
yaml
camp:
潜伏者:
every-day-max-get-prestage-value:
default: 1000
vip_1: 2000其中 vip_1 实际检查的权限为 vip.1。如果玩家同时拥有多个已配置权限,会使用数值最高的一项。
每日刷新时机
插件使用服务器默认时区,以当天 00:00:00 作为跨日边界。
每日额度通过两种方式刷新:
| 来源 | 说明 |
|---|---|
| 玩家数据加载 | 玩家数据读取完成后立即检查是否已经跨日 |
| 在线定时检查 | 异步任务每秒检查一次;只有服务器当前秒数为 0 时才遍历在线玩家并判断是否跨日 |
满足跨日条件后,今日剩余额度会设置为玩家当前阵营计算出的每日上限,并把上次刷新时间更新为当天零点。
每日首杀
玩家在 PVP 世界的开放时间内完成玩家击杀后,插件会比较上次击杀时间和服务器当天零点。
如果上次击杀发生在今天之前,会执行玩家阵营的 every-day-first-kill-run-command。结算完成后,无论是否属于今日首杀,都会把上次击杀时间更新为当前时间。
首杀指令支持:
| 写法 | 执行方式 |
|---|---|
[console]指令 | 由控制台执行 |
[op]指令 | 临时以 OP 身份让击杀玩家执行,完成后恢复原状态 |
| 无前缀指令 | 由击杀玩家直接执行 |
执行前会解析 PlaceholderAPI 变量。PlaceholderAPI 未安装时,涉及变量解析的功能无法按预期工作。
%lyzy_first_kill% 的判断与该时间记录一致:当天尚未完成首杀时返回 placeholder.has-first-kill,完成后返回 placeholder.not-first-kill。
死亡结算范围
死亡结算监听不会再次检查双方是否为敌对阵营。正常直接战斗会先经过伤害监听,因此通常只有敌对阵营能够完成击杀;由其它插件制造的伤害归属或特殊死亡流程仍可能直接进入结算。
称号区间
阵营称号由累计声望匹配 camp.<阵营>.prestige 下的数值或范围键。
支持两种键格式:
| 格式 | 说明 |
|---|---|
100 | 只匹配累计声望 100 |
1-1000 | 匹配包含边界的 1 至 1000 |
玩家累计声望位于区间内时,该区间的 tag 会作为当前称号。
建议保证区间连续且互不重叠。区间重叠时,属性系统会加入所有命中区间的属性,而称号变量只会返回遍历过程中第一个命中的称号。
首次达到区间
累计声望从低值上升并越过某个区间的最小值时,会执行该区间的 command。
如果一次增加跨过多个区间的最小值,所有被跨过区间的指令都会执行。累计声望降低后再次上升并重新越过最小值时,对应指令可以再次执行。
区间指令执行前会:
- 将
{tag}替换为该区间的称号。 - 解析 PlaceholderAPI 变量。
- 按
[console]、[op]或玩家身份执行。
仅在区间内部变动、没有从下方向上越过最小值时,不会执行该区间指令。
阵营属性
玩家加入有效阵营后,最终提交给属性插件的属性由以下内容组成:
camp.<阵营>.attribute中的阵营基础属性。- 当前累计声望命中的所有
prestige.<区间>.attribute称号属性。
当前代码支持以下 attribute-plugin 值:
| 配置值 | 对接目标 |
|---|---|
AttributePlus | AttributePlus |
SX-Attribute2 | SX-Attribute 2 |
SX-Attribute3 | SX-Attribute 3 |
AttributeSystem | AttributeSystem |
ItemLoreOrigin | ItemLoreOrigin |
| 空字符串 | 不向属性插件提交属性 |
属性会在以下时机刷新:
| 时机 | 说明 |
|---|---|
| 玩家数据加载后 | 数据读取流程会请求更新属性 |
| 玩家关闭背包界面后 | 每次触发 InventoryCloseEvent 都会重新计算 |
属性计算和提交在异步任务中执行。当前声望不会参与称号属性判断,只有累计声望参与。
attribute-plugin 的对接初始化发生在插件启动阶段。默认配置也明确要求修改该项后重启服务器。
死亡与离线掉落
特殊掉落只在已配置的 PVP 世界中,并且当前处于该世界 PVP 开放时间时处理。
| 场景 | 声望扣除 | 特殊掉落 |
|---|---|---|
| 被玩家击杀,双方数据均已加载 | 扣除当前声望 | 处理 |
| 非玩家原因死亡,玩家数据已加载 | 扣除当前声望 | 处理 |
| PVP 世界开放时间内退出服务器 | 不扣除声望 | 处理 |
| 非 PVP 世界死亡或退出 | 不处理 | 不处理 |
| PVP 世界非开放时间死亡或退出 | 不处理 | 不处理 |
插件只扫描玩家背包索引 0 至 35,即背包和快捷栏的前 36 个格子。装备栏、副手等其它位置不在扫描范围内。
Lore 判断采用“包含”匹配。配置中的 & 颜色代码会先转换为 Minecraft 颜色代码,再与物品 Lore 的每一行比较。
随机掉落
随机掉落使用全局 force-drop-item-lore 和当前世界的 force-drop-item-amount。
处理流程:
- 扫描前 36 格。
- 将 Lore 包含任意
force-drop-item-lore文本的格子加入候选池。 - 从候选格子中随机选择指定数量。
- 将选中格子的整组物品从背包移除。
- 在玩家当前位置自然生成掉落物。
实际随机掉落格数为 force-drop-item-amount 和候选格子数量中的较小值。数量按格子计算,不按单个物品计算,一个格子中的整组物品会一起掉落。
force-drop-item-amount 小于或等于 0 时,不执行随机掉落。
必定掉落
随机掉落完成后,插件会再次扫描前 36 格。Lore 包含任意 independent-of-the-world-force-drop-item-lore 文本的物品会全部掉落。
必定掉落具有以下特点:
- 不受
force-drop-item-amount限制。 - 每个匹配格子的整组物品都会掉落。
- 如果某件物品已在随机阶段掉落并从背包移除,不会再次掉落。
- 掉落记录后会附带“必定掉落”标记。
掉落提示
只有实际掉落了至少一个匹配物品时,插件才会发送掉落信息。
| 场景 | 使用的消息 |
|---|---|
| 被玩家击杀 | drop-item-message |
| 非玩家死亡或退出服务器 | abnormal-death-drop-item-message |
被玩家击杀时支持:
| 占位文本 | 内容 |
|---|---|
{killer} | 击杀者名称 |
{camp} | 击杀者阵营名称 |
消息之后会逐行列出掉落物名称和数量。存在自定义显示名称时使用显示名称,否则使用 Bukkit 材质名称。
PVP 世界命令限制
非 OP 玩家在 PVP 世界内执行命令时,插件会将完整命令文本与该世界的 allow-command 逐项比较。
匹配方式为区分大小写的字符串前缀匹配:
yaml
pvp-world:
pvp_world:
allow-command:
- '/say'配置 /say 后,以下文本都会匹配:
text
/say
/say 内容
/saytest因此,白名单应尽量写成不会误放行其它命令的前缀。插件不会自动检查命令名称后的空格边界。
没有任何前缀匹配时,命令事件会被取消。OP 玩家不受该限制。
消息键差异
当前命令拦截代码读取的是 message.allow-message,默认配置提供的却是 message.allow-command。未额外添加 message.allow-message 时,会使用代码内置的“PVP地图内禁止使用该指令”提示;修改默认的 message.allow-command 不会改变此处提示。
数据加载与保存
插件支持本地 YAML 和 MySQL 两种玩家数据存储方式。mysql.enable 的修改需要重启服务器,因为玩家加载、退出保存监听器会在插件启动时按该开关选择注册。
本地 YAML
MySQL 未建立连接时,玩家数据保存到:
text
plugins/LyCamp/data/玩家UUID.yml本地数据使用 UTF-8 无 BOM 读写,并采用以下保护流程:
- 同一玩家文件的读取和写入使用文件锁串行处理。
- 保存时先写入唯一的临时文件。
- 已存在的主文件会移动到同名
.bak备份。 - 临时文件再替换为主文件。
- 文件系统不支持原子移动时,会回退为普通覆盖移动。
- 读取到损坏主文件时,插件会尝试从
.bak恢复。 - 主文件和备份均不可用时,返回空玩家数据。
- 数据目录中的历史
.tmp文件会被清理。
MySQL
启用 MySQL 时,必须安装 LyMySQLCore,并确保 LyMySQLCore 已成功连接数据库,相关玩家加载与保存功能才会生效。
LyCamp 自身还必须使用 mysql 下的连接信息成功建立连接池。连接未完成时,启用了 MySQL 的服务器会拒绝玩家登录,并提示服务器尚未加载完成。
数据库表名使用插件名称,玩家名称存入 name 字段,序列化后的玩家数据存入 data 字段。
保存时机
| 时机 | 行为 |
|---|---|
| 玩家数据发生需要保存的声望变化 | 标记数据为已修改 |
| 自动保存任务 | 每 50 tick 检查在线玩家,只保存已标记修改的数据 |
| 玩家正常退出 | 保存数据并移除内存数据 |
| 玩家被踢出 | 保存数据并移除内存数据 |
| 插件关闭 | 保存所有在线玩家数据 |
MySQL 模式通过 LyMySQLCore 提供的安全加载、保存事件处理正常登录和退出数据;玩家被踢出时也会直接执行保存。
重载行为
执行 /lyzy reload 后,插件会重新读取配置并更新:
- 阵营数据和声望区间。
- PVP 世界规则和阵营位置。
- PlaceholderAPI 默认文本。
- 自动保存任务。
- 每日额度检查任务。
- 已启用的 MySQL 连接。
重载不会重新选择或注册 YAML、MySQL 玩家事件监听器,也不会重新执行属性插件和 ProtocolLib 的启动初始化。因此,修改 mysql.enable 或 attribute-plugin 后应重启服务器。