Admin rules: starting balance, guild claim/member limits, market listing limit; panel UI

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjMbLQujBHunCCu5GpsHaT
This commit is contained in:
Claude committed 2026-10-01 01:06:51 +00:00
1 parent c859628e93
commit 19d111eeff
8 files changed
+166 -12

No files matched your search

+50
View File
@@ -48,6 +48,32 @@ pub struct Progression {
pub global_xp_multiplier: f64,
pub server_xp_multiplier: f64,
pub xp_rates: XpRates,
/// Economy and guild rules that apply on every server.
pub rules: Rules,
}
/// Network-wide limits an admin can change without touching a server. 0 means "no limit" where it says so.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(default)]
pub struct Rules {
/// Balance a player starts with on each server economy.
pub starting_balance: f64,
/// Chunks a new guild may claim.
pub guild_base_claims: i64,
/// Extra claimable chunks for every member beyond the leader.
pub guild_claims_per_member: i64,
/// Extra claimable chunks for every guild level above 1.
pub guild_claims_per_level: i64,
/// Most members a guild may have (0 = no limit).
pub guild_max_members: i64,
/// Most listings one player may have on a server's market (0 = no limit).
pub market_max_listings: i64,
}
impl Default for Rules {
fn default() -> Self {
Self { starting_balance: 1000.0, guild_base_claims: 16, guild_claims_per_member: 0, guild_claims_per_level: 0, guild_max_members: 0, market_max_listings: 0 }
}
}
impl Default for Progression {
@@ -62,6 +88,7 @@ impl Default for Progression {
global_xp_multiplier: 1.0,
server_xp_multiplier: 1.0,
xp_rates: XpRates::default(),
rules: Rules::default(),
}
}
}
@@ -89,6 +116,21 @@ impl Progression {
return Err(AppError::bad_request(format!("{name} must be between 0 and 100.")));
}
}
let rules = &self.rules;
if !rules.starting_balance.is_finite() || !(0.0..=1e9).contains(&rules.starting_balance) {
return bad("Starting balance must be between 0 and 1,000,000,000.");
}
for (name, v, max) in [
("Guild base claims", rules.guild_base_claims, 100_000),
("Claims per member", rules.guild_claims_per_member, 10_000),
("Claims per guild level", rules.guild_claims_per_level, 10_000),
("Guild member limit", rules.guild_max_members, 10_000),
("Market listing limit", rules.market_max_listings, 10_000),
] {
if !(0..=max).contains(&v) {
return Err(AppError::bad_request(format!("{name} must be between 0 and {max}.")));
}
}
let r = &self.xp_rates;
for (name, v) in [
("Playtime XP", r.playtime_per_hour),
@@ -196,6 +238,14 @@ pub async fn save(pool: &sqlx::SqlitePool, p: &Progression) -> AppResult<()> {
.bind(serde_json::to_string(p)?)
.execute(pool)
.await?;
// The same rules as plain rows, so SQL can use them (guild claim limits are worked out in queries).
for (key, value) in [
("rule_claims_per_member", p.rules.guild_claims_per_member),
("rule_claims_per_level", p.rules.guild_claims_per_level),
("rule_guild_max_members", p.rules.guild_max_members),
] {
sqlx::query("INSERT INTO kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").bind(key).bind(value.to_string()).execute(pool).await?;
}
Ok(())
}