Add world validation command to mudtool
Some checks failed
Smoke tests / Build and smoke test (push) Failing after 16s
Some checks failed
Smoke tests / Build and smoke test (push) Failing after 16s
- Introduced a new command `validate` to check the integrity of world data, ensuring all referenced entities (NPCs, objects, guilds, races, classes, spells) exist and have valid attributes. - Updated help message to include usage of the new command and its options. - Added support for specifying a world directory via command line argument.
This commit is contained in:
@@ -12,7 +12,9 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
run: sudo apt install -y cargo
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y cargo
|
||||
|
||||
- name: Build
|
||||
run: cargo build
|
||||
|
||||
@@ -9,11 +9,12 @@ use ratatui::prelude::*;
|
||||
use ratatui::widgets::*;
|
||||
|
||||
use mudserver::db::{GameDb, NpcAttitudeRow, SavedPlayer, SqliteDb};
|
||||
use mudserver::world::Attitude;
|
||||
use mudserver::world::{Attitude, World};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let mut db_path = PathBuf::from("./mudserver.db");
|
||||
let mut world_path = PathBuf::from("./world");
|
||||
let mut cmd_args: Vec<String> = Vec::new();
|
||||
|
||||
let mut i = 1;
|
||||
@@ -23,6 +24,10 @@ fn main() {
|
||||
i += 1;
|
||||
db_path = PathBuf::from(args.get(i).expect("--db requires a path"));
|
||||
}
|
||||
"--world" | "-w" => {
|
||||
i += 1;
|
||||
world_path = PathBuf::from(args.get(i).expect("--world requires a path"));
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
print_help();
|
||||
return;
|
||||
@@ -32,6 +37,16 @@ fn main() {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if cmd_args.is_empty() {
|
||||
print_help();
|
||||
return;
|
||||
}
|
||||
|
||||
if cmd_args[0] == "validate" {
|
||||
cmd_validate(&world_path);
|
||||
return;
|
||||
}
|
||||
|
||||
let db = match SqliteDb::open(&db_path) {
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
@@ -40,11 +55,6 @@ fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
if cmd_args.is_empty() {
|
||||
print_help();
|
||||
return;
|
||||
}
|
||||
|
||||
match cmd_args[0].as_str() {
|
||||
"tui" => run_tui(db),
|
||||
"players" => cmd_players(&db, &cmd_args[1..]),
|
||||
@@ -60,9 +70,10 @@ fn main() {
|
||||
fn print_help() {
|
||||
eprintln!("mudtool - MUD Server Database Manager");
|
||||
eprintln!();
|
||||
eprintln!("Usage: mudtool [--db <path>] <command> [args...]");
|
||||
eprintln!("Usage: mudtool [OPTIONS] <command> [args...]");
|
||||
eprintln!();
|
||||
eprintln!("Commands:");
|
||||
eprintln!(" validate Validate world data (schemas, references, values)");
|
||||
eprintln!(" tui Interactive TUI editor");
|
||||
eprintln!(" players list List all players");
|
||||
eprintln!(" players show <name> Show player details");
|
||||
@@ -76,6 +87,98 @@ fn print_help() {
|
||||
eprintln!();
|
||||
eprintln!("Options:");
|
||||
eprintln!(" --db, -d <path> Database path (default: ./mudserver.db)");
|
||||
eprintln!(" --world, -w <path> World directory for validate (default: ./world)");
|
||||
}
|
||||
|
||||
fn cmd_validate(world_path: &std::path::Path) {
|
||||
let world = match World::load(world_path) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
eprintln!("Validation failed (load): {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for npc in world.npcs.values() {
|
||||
if !world.rooms.contains_key(&npc.room) {
|
||||
errors.push(format!("NPC {} room '{}' does not exist", npc.id, npc.room));
|
||||
}
|
||||
if let Some(ref rid) = npc.fixed_race {
|
||||
if !world.races.iter().any(|r| r.id == *rid) {
|
||||
errors.push(format!("NPC {} race '{}' does not exist", npc.id, rid));
|
||||
}
|
||||
}
|
||||
if let Some(ref cid) = npc.fixed_class {
|
||||
if !world.classes.iter().any(|c| c.id == *cid) {
|
||||
errors.push(format!("NPC {} class '{}' does not exist", npc.id, cid));
|
||||
}
|
||||
}
|
||||
if let Some(ref c) = npc.combat {
|
||||
if c.max_hp <= 0 || c.attack < 0 || c.defense < 0 || c.xp_reward < 0 {
|
||||
errors.push(format!("NPC {} has invalid combat stats (hp>0, atk/def/xp>=0)", npc.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for obj in world.objects.values() {
|
||||
if let Some(ref rid) = obj.room {
|
||||
if !world.rooms.contains_key(rid) {
|
||||
errors.push(format!("Object {} room '{}' does not exist", obj.id, rid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for guild in world.guilds.values() {
|
||||
for sid in &guild.spells {
|
||||
if !world.spells.contains_key(sid) {
|
||||
errors.push(format!("Guild {} spell '{}' does not exist", guild.id, sid));
|
||||
}
|
||||
}
|
||||
for rid in &guild.race_restricted {
|
||||
if !world.races.iter().any(|r| r.id == *rid) {
|
||||
errors.push(format!("Guild {} race_restricted '{}' does not exist", guild.id, rid));
|
||||
}
|
||||
}
|
||||
if guild.resource != "mana" && guild.resource != "endurance" {
|
||||
errors.push(format!("Guild {} resource '{}' must be 'mana' or 'endurance'", guild.id, guild.resource));
|
||||
}
|
||||
}
|
||||
|
||||
for class in &world.classes {
|
||||
if let Some(ref gid) = class.guild {
|
||||
if !world.guilds.contains_key(gid) {
|
||||
errors.push(format!("Class {} guild '{}' does not exist", class.id, gid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for race in &world.races {
|
||||
if let Some(ref cid) = race.default_class {
|
||||
if !world.classes.iter().any(|c| c.id == *cid) {
|
||||
errors.push(format!("Race {} default_class '{}' does not exist", race.id, cid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for spell in world.spells.values() {
|
||||
if !["offensive", "heal", "utility"].contains(&spell.spell_type.as_str()) {
|
||||
errors.push(format!("Spell {} spell_type '{}' must be offensive/heal/utility", spell.id, spell.spell_type));
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
println!("World validation OK: {} rooms, {} npcs, {} objects, {} races, {} classes, {} guilds, {} spells",
|
||||
world.rooms.len(), world.npcs.len(), world.objects.len(),
|
||||
world.races.len(), world.classes.len(), world.guilds.len(), world.spells.len());
|
||||
} else {
|
||||
for e in &errors {
|
||||
eprintln!("Error: {e}");
|
||||
}
|
||||
eprintln!("\n{} validation error(s)", errors.len());
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ CLI Commands ============
|
||||
|
||||
Reference in New Issue
Block a user