新增用户组药效等级上限配置,与Java端双向打通

配合 Java 端 6f92028(原版药效强度上限 256)。原版 /effect 的
amplifier 硬上限是 255,即药效强度 1~256,此前 Web 侧封顶写死
255 且各建表点口径不一,本次统一为 max_effect_level 配置项。

改动:
- web_user_groups 增加 max_effect_level 列(默认 256)
  · sync.php / land_api.php / core.php 全部 8 处建表块补列
  · 3 处迁移表补 ALTER 条目,老库自动升级
- api/sync.php syncUserGroups:Java 推送时接收并写入 max_effect_level,
  <=0 或超过 256 一律归一到 256(原版硬上限不可突破)
- api/land_api.php 新增 getLandMaxEffectLevel():按领地所有者所属
  最高优先级用户组取上限,无组则 256;handleUpdateLandField 写入
  give_effects 时改用该上限 clamp(原先硬编码 255)
- api/land_api.php update_user_group 读写补该字段
- admin.php 用户组新建/编辑弹窗补「药效等级上限」输入框
  (1~256,默认 256),并附原版范围说明;两处提交参数同步带上

规则:普通玩家(未加入任何用户组)固定按 256 封顶,严禁超限;
加入用户组后按组配置封顶,填 256 即不额外限制。
This commit is contained in:
ypsdf1
2026-10-04 19:26:05 +08:00
parent 419b677aa9
commit 4160c60aa6
4 changed files with 70 additions and 11 deletions
+15 -5
View File
@@ -3105,12 +3105,17 @@ function showAddUserGroup() {
<input id="ugMaxLands" type="number" value="-1" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:6px">
<div>
<label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">领地增益效果上限(5=普通玩家, &lt;=0=不限)</label>
<label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">领地增益效果数量上限(5=普通玩家, &lt;=0=不限)</label>
<input id="ugMaxEffects" type="number" value="5" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)">
</div>
<div>
<label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">药效等级上限(默认256=原版)</label>
<input id="ugMaxEffectLevel" type="number" value="256" min="1" max="256" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)">
</div>
</div>
<div style="font-size:11px;color:var(--dim);margin-bottom:10px">药效强度原版范围 1~256,普通玩家(未加入任何用户组)固定按 256 封顶,严禁超限。</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">
<div>
<label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">Home上限(0=跟随默认)</label>
@@ -3150,6 +3155,7 @@ async function doAddUserGroup() {
const price = document.getElementById('ugPrice')?.value || '-1';
const maxLands = document.getElementById('ugMaxLands')?.value || '-1';
const maxEffects = document.getElementById('ugMaxEffects')?.value || '5';
const maxEffectLevel = document.getElementById('ugMaxEffectLevel')?.value || '256';
const homeLimit = document.getElementById('ugHomeLimit')?.value || '0';
const joinPrice = document.getElementById('ugJoinPrice')?.value || '0';
const renewPrice = document.getElementById('ugRenewPrice')?.value || '0';
@@ -3164,7 +3170,7 @@ async function doAddUserGroup() {
try {
const res = await apiCall('update_user_group', {
name, display_name: displayName, display_color: color,
priority, land_price_per_sqm: price, max_lands: maxLands, max_effects: maxEffects,
priority, land_price_per_sqm: price, max_lands: maxLands, max_effects: maxEffects, max_effect_level: maxEffectLevel,
home_limit: homeLimit, join_price: joinPrice, renew_price: renewPrice,
duration_minutes: duration, auto_renew: autoRenew,
default_perms: '{}'
@@ -3211,9 +3217,12 @@ async function showEditUserGroup(groupName) {
<input id="eugMaxLands" type="number" value="${g.max_lands}" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)"></div>
</div>`;
html += `<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px">
<div><label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">领地增益效果上限(&lt;=0=不限)</label>
<div><label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">领地增益效果数量上限(&lt;=0=不限)</label>
<input id="eugMaxEffects" type="number" value="${g.max_effects ?? 5}" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)"></div>
<div><label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">药效等级上限(默认256=原版)</label>
<input id="eugMaxEffectLevel" type="number" min="1" max="256" value="${(g.max_effect_level > 0 ? g.max_effect_level : 256)}" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)"></div>
</div>`;
html += `<div style="font-size:11px;color:var(--dim);margin:-4px 0 12px 0">说明:药效强度原版范围 1~256,普通玩家(未加入任何用户组)固定按 256 封顶,严禁超限。加入本组后按上面的值封顶,填 256 即不额外限制。</div>`;
html += `<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px">
<div><label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">Home上限(0=默认)</label>
<input id="eugHomeLimit" type="number" value="${g.home_limit || 0}" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)"></div>
@@ -3259,6 +3268,7 @@ async function doEditUserGroup(groupName) {
const price = document.getElementById('eugPrice')?.value || '-1';
const maxLands = document.getElementById('eugMaxLands')?.value || '-1';
const maxEffects = document.getElementById('eugMaxEffects')?.value || '5';
const maxEffectLevel = document.getElementById('eugMaxEffectLevel')?.value || '256';
const homeLimit = document.getElementById('eugHomeLimit')?.value || '0';
const duration = document.getElementById('eugDuration')?.value || '0';
const joinPrice = document.getElementById('eugJoinPrice')?.value || '0';
@@ -3274,7 +3284,7 @@ async function doEditUserGroup(groupName) {
try {
const res = await apiCall('update_user_group', {
name: groupName, display_name: displayName, display_color: color,
priority, land_price_per_sqm: price, max_lands: maxLands, max_effects: maxEffects,
priority, land_price_per_sqm: price, max_lands: maxLands, max_effects: maxEffects, max_effect_level: maxEffectLevel,
home_limit: homeLimit, duration_minutes: duration,
join_price: joinPrice, renew_price: renewPrice, auto_renew: autoRenew,
default_perms: JSON.stringify(perms)
+46 -4
View File
@@ -1680,6 +1680,7 @@ function getLandMaxEffects($db, $land) {
land_price_per_sqm INTEGER DEFAULT -1,
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -1711,6 +1712,33 @@ function getLandMaxEffects($db, $land) {
return $fallback;
}
/**
* ★ 领地增益【药效等级上限】(2026-10-04 任务4)
* 取领地所有者所属最高优先级用户组的 max_effect_level,默认 256(原版最大药效强度)。
* <=0 或非法值一律归一到 256 —— 原版 /effect 的 amplifier 硬上限是 255(= 强度256),
* 任何情况下都不允许突破原版。
*/
function getLandMaxEffectLevel($db, $land) {
$fallback = 256;
$owner = $land['owner'] ?? '';
if (empty($owner)) return $fallback;
try {
$st = $db->prepare("SELECT g.max_effect_level FROM web_user_group_members m
LEFT JOIN web_user_groups g ON m.group_name = g.group_name
WHERE m.player_name = :p AND (m.expiry_time = 0 OR m.expiry_time > :now)
ORDER BY g.priority DESC LIMIT 1");
$st->bindValue(':p', $owner, SQLITE3_TEXT);
$st->bindValue(':now', time(), SQLITE3_INTEGER);
$row = $st->execute()->fetchArray(SQLITE3_ASSOC);
if ($row && $row['max_effect_level'] !== null) {
$v = (int)$row['max_effect_level'];
if ($v <= 0 || $v > $fallback) return $fallback;
return $v;
}
} catch (\Throwable $e) { /* 表缺失等 */ }
return $fallback;
}
function handleUpdateLandField($db, $playerName, $post) {
$name = $post['name'] ?? '';
$field = $post['field'] ?? '';
@@ -1772,18 +1800,20 @@ function handleUpdateLandField($db, $playerName, $post) {
$arr = json_decode($value, true);
if (is_array($arr)) {
if ($field === 'give_effects') {
// ★ 2026-10-04 任务3:数量上限(按领地所有者用户组 max_effects,默认5)+ 等级封顶1~255
// ★ 2026-10-04 任务3:数量上限(按领地所有者用户组 max_effects,默认5)
// ★ 2026-10-04 任务4:等级封顶(按 max_effect_level,普通玩家=原版256)
$maxEff = getLandMaxEffects($db, $land);
if (count($arr) > $maxEff) {
$arr = array_slice($arr, 0, $maxEff);
}
$maxLv = getLandMaxEffectLevel($db, $land);
// [["夜视","1","99999"]] → "夜视:1:99999"
$parts = [];
foreach ($arr as $e) {
if (is_array($e)) {
if (isset($e[1]) && $e[1] !== '') {
$lv = (int)$e[1];
$e[1] = (string)max(1, min(255, $lv));
$e[1] = (string)max(1, min($maxLv, $lv));
}
$parts[] = implode(':', $e);
} else {
@@ -2371,6 +2401,7 @@ function handleListUserGroups($db) {
land_price_per_sqm INTEGER DEFAULT -1,
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2392,6 +2423,7 @@ function handleListUserGroups($db) {
'renew_price' => 'ALTER TABLE web_user_groups ADD COLUMN renew_price INTEGER DEFAULT 0',
'duration_minutes' => 'ALTER TABLE web_user_groups ADD COLUMN duration_minutes INTEGER DEFAULT 0',
'max_effects' => 'ALTER TABLE web_user_groups ADD COLUMN max_effects INTEGER DEFAULT 5',
'max_effect_level' => 'ALTER TABLE web_user_groups ADD COLUMN max_effect_level INTEGER DEFAULT 256',
];
foreach ($migrations as $col => $sql) {
if (!in_array($col, $columns)) {
@@ -2430,6 +2462,7 @@ function handleUpdateUserGroup($db, $data) {
land_price_per_sqm INTEGER DEFAULT -1,
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2446,6 +2479,9 @@ function handleUpdateUserGroup($db, $data) {
$pricePerSqm = (int)($data['land_price_per_sqm'] ?? -1);
$maxLands = (int)($data['max_lands'] ?? -1);
$maxEffects = (int)($data['max_effects'] ?? 5); // 领地增益效果上限,<=0=不限
// ★ 药效等级上限:<=0 或超过原版 256 一律归一到 256
$maxEffectLevel = (int)($data['max_effect_level'] ?? 256);
if ($maxEffectLevel <= 0 || $maxEffectLevel > 256) $maxEffectLevel = 256;
$homeLimit = (int)($data['home_limit'] ?? 0);
$joinPrice = (int)($data['join_price'] ?? 0);
$renewPrice = (int)($data['renew_price'] ?? 0);
@@ -2455,9 +2491,9 @@ function handleUpdateUserGroup($db, $data) {
$stmt = $db->prepare("INSERT OR REPLACE INTO web_user_groups
(group_name, display_name, display_color, display_emoji, priority,
land_price_per_sqm, max_lands, max_effects, home_limit, join_price, auto_renew, renew_price, duration_minutes, default_perms, synced_at)
land_price_per_sqm, max_lands, max_effects, max_effect_level, home_limit, join_price, auto_renew, renew_price, duration_minutes, default_perms, synced_at)
VALUES (:name, :display, :color, :emoji, :priority,
:price, :maxlands, :maxEffects, :homeLimit, :joinPrice, :autoRenew, :renewPrice, :duration, :perms, :synced)");
:price, :maxlands, :maxEffects, :maxEffectLevel, :homeLimit, :joinPrice, :autoRenew, :renewPrice, :duration, :perms, :synced)");
$stmt->bindValue(':name', $name, SQLITE3_TEXT);
$stmt->bindValue(':display', $displayName, SQLITE3_TEXT);
$stmt->bindValue(':color', $displayColor, SQLITE3_TEXT);
@@ -2466,6 +2502,7 @@ function handleUpdateUserGroup($db, $data) {
$stmt->bindValue(':price', $pricePerSqm, SQLITE3_INTEGER);
$stmt->bindValue(':maxlands', $maxLands, SQLITE3_INTEGER);
$stmt->bindValue(':maxEffects', $maxEffects, SQLITE3_INTEGER);
$stmt->bindValue(':maxEffectLevel', $maxEffectLevel, SQLITE3_INTEGER);
$stmt->bindValue(':homeLimit', $homeLimit, SQLITE3_INTEGER);
$stmt->bindValue(':joinPrice', $joinPrice, SQLITE3_INTEGER);
$stmt->bindValue(':autoRenew', $autoRenew, SQLITE3_INTEGER);
@@ -2654,6 +2691,7 @@ function handleGetPlayerGroups($db, $player) {
land_price_per_sqm INTEGER DEFAULT -1,
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2673,6 +2711,7 @@ function handleGetPlayerGroups($db, $player) {
'renew_price' => 'ALTER TABLE web_user_groups ADD COLUMN renew_price INTEGER DEFAULT 0',
'duration_minutes' => 'ALTER TABLE web_user_groups ADD COLUMN duration_minutes INTEGER DEFAULT 0',
'max_effects' => 'ALTER TABLE web_user_groups ADD COLUMN max_effects INTEGER DEFAULT 5',
'max_effect_level' => 'ALTER TABLE web_user_groups ADD COLUMN max_effect_level INTEGER DEFAULT 256',
];
foreach ($migrations as $col => $sql) {
if (!in_array($col, $columns)) {
@@ -2697,6 +2736,7 @@ function handleGetPlayerGroups($db, $player) {
land_price_per_sqm INTEGER DEFAULT -1,
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2733,6 +2773,7 @@ function handleListAvailableGroups($db) {
land_price_per_sqm INTEGER DEFAULT -1,
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2750,6 +2791,7 @@ function handleListAvailableGroups($db) {
'renew_price' => 'ALTER TABLE web_user_groups ADD COLUMN renew_price INTEGER DEFAULT 0',
'duration_minutes' => 'ALTER TABLE web_user_groups ADD COLUMN duration_minutes INTEGER DEFAULT 0',
'max_effects' => 'ALTER TABLE web_user_groups ADD COLUMN max_effects INTEGER DEFAULT 5',
'max_effect_level' => 'ALTER TABLE web_user_groups ADD COLUMN max_effect_level INTEGER DEFAULT 256',
];
foreach ($migrations as $col => $sql) {
if (!in_array($col, $columns)) {
+8 -2
View File
@@ -914,6 +914,7 @@ function syncUserGroups() {
land_price_per_sqm INTEGER DEFAULT -1,
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -936,6 +937,7 @@ function syncUserGroups() {
'renew_price' => 'ALTER TABLE web_user_groups ADD COLUMN renew_price INTEGER DEFAULT 0',
'duration_minutes' => 'ALTER TABLE web_user_groups ADD COLUMN duration_minutes INTEGER DEFAULT 0',
'max_effects' => 'ALTER TABLE web_user_groups ADD COLUMN max_effects INTEGER DEFAULT 5',
'max_effect_level' => 'ALTER TABLE web_user_groups ADD COLUMN max_effect_level INTEGER DEFAULT 256',
];
foreach ($migrations as $col => $sql) {
if (!in_array($col, $columns)) {
@@ -946,10 +948,10 @@ function syncUserGroups() {
$now = time();
$stmt = $db->prepare("INSERT OR REPLACE INTO web_user_groups
(group_name, display_name, display_color, display_emoji, priority,
land_price_per_sqm, max_lands, max_effects, home_limit, join_price, auto_renew,
land_price_per_sqm, max_lands, max_effects, max_effect_level, home_limit, join_price, auto_renew,
renew_price, duration_minutes, default_perms, synced_at)
VALUES (:name, :display, :color, :emoji, :priority,
:price, :maxlands, :maxeffects, :homelimit, :joinprice, :autorenew,
:price, :maxlands, :maxeffects, :maxeffectlevel, :homelimit, :joinprice, :autorenew,
:renewprice, :duration, :perms, :synced)");
$count = 0;
foreach ($groups as $g) {
@@ -961,6 +963,10 @@ function syncUserGroups() {
$stmt->bindValue(':price', (int)($g['land_price_per_sqm'] ?? -1), SQLITE3_INTEGER);
$stmt->bindValue(':maxlands', (int)($g['max_lands'] ?? -1), SQLITE3_INTEGER);
$stmt->bindValue(':maxeffects', (int)($g['max_effects'] ?? 5), SQLITE3_INTEGER);
// ★ 药效等级上限(2026-10-04 任务4):<=0 归一到原版上限 256
$mel = (int)($g['max_effect_level'] ?? 256);
if ($mel <= 0 || $mel > 256) $mel = 256;
$stmt->bindValue(':maxeffectlevel', $mel, SQLITE3_INTEGER);
$stmt->bindValue(':homelimit', (int)($g['home_limit'] ?? 0), SQLITE3_INTEGER);
$stmt->bindValue(':joinprice', (int)($g['join_price'] ?? 0), SQLITE3_INTEGER);
$stmt->bindValue(':autorenew', (int)($g['auto_renew'] ?? 0), SQLITE3_INTEGER);
+1
View File
@@ -645,6 +645,7 @@ function initTables(SQLite3 $db) {
land_price_per_sqm INTEGER DEFAULT -1,
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,