BodyTemplate: JSON-serializable entity body definition. - parts: Vec<BodyPart> with x, y, color, label - constraints: Vec<(usize, usize)> connecting parts - half_w/half_h/radius: collider and physics params Built-in templates: - humanoid_player: purple wizard (15 parts) - humanoid_goblin: green (15 parts) - boulder: 4x4 rock (16 parts) API for AI agents: - BodyTemplate::to_json() / from_json() — serialize/deserialize - BodyTemplate::preview_ascii() — text preview of silhouette - BodyTemplate::apply_to(&mut entity, cx, cy) — build entity from template - template_for_kind(EntityKind) — get template by entity type - Custom templates: create any shape (snake, dragon, boss) from code or JSON Entity::build_humanoid() now delegates to template_for_kind().apply_to() — single source of truth for body layout. 7 new tests for template system (116 total, 0 failures)
117 lines
3.1 KiB
Rust
117 lines
3.1 KiB
Rust
use verbatim::entity::body_template::{BodyPart, BodyTemplate};
|
|
|
|
#[test]
|
|
fn player_template_has_correct_shape() {
|
|
let t = BodyTemplate::humanoid_player();
|
|
assert_eq!(t.name, "player");
|
|
assert_eq!(t.parts.len(), 15);
|
|
assert_eq!(t.half_w, 2.5);
|
|
assert_eq!(t.half_h, 3.5);
|
|
}
|
|
|
|
#[test]
|
|
fn goblin_template_has_correct_shape() {
|
|
let t = BodyTemplate::humanoid_goblin();
|
|
assert_eq!(t.name, "goblin");
|
|
assert_eq!(t.parts.len(), 15);
|
|
}
|
|
|
|
#[test]
|
|
fn boulder_template_has_correct_shape() {
|
|
let t = BodyTemplate::boulder();
|
|
assert_eq!(t.name, "boulder");
|
|
assert_eq!(t.parts.len(), 16);
|
|
assert!(t.parts.iter().all(|p| p.label == "rock"));
|
|
}
|
|
|
|
#[test]
|
|
fn template_json_roundtrip() {
|
|
let t = BodyTemplate::humanoid_player();
|
|
let json = t.to_json();
|
|
let t2 = BodyTemplate::from_json(&json).expect("parse");
|
|
assert_eq!(t2.name, t.name);
|
|
assert_eq!(t2.parts.len(), t.parts.len());
|
|
assert_eq!(t2.half_w, t.half_w);
|
|
assert_eq!(t2.half_h, t.half_h);
|
|
}
|
|
|
|
#[test]
|
|
fn preview_ascii_not_empty() {
|
|
let t = BodyTemplate::humanoid_player();
|
|
let preview = t.preview_ascii();
|
|
assert!(!preview.is_empty());
|
|
assert!(preview.contains("O"));
|
|
}
|
|
|
|
#[test]
|
|
fn custom_template_works() {
|
|
let t = BodyTemplate {
|
|
name: "snake".to_string(),
|
|
half_w: 3.0,
|
|
half_h: 0.5,
|
|
radius: 0.5,
|
|
parts: vec![
|
|
BodyPart {
|
|
x: -3.0,
|
|
y: 0.0,
|
|
color: [100, 200, 50, 255],
|
|
label: "body".into(),
|
|
},
|
|
BodyPart {
|
|
x: -2.0,
|
|
y: 0.0,
|
|
color: [100, 200, 50, 255],
|
|
label: "body".into(),
|
|
},
|
|
BodyPart {
|
|
x: -1.0,
|
|
y: 0.0,
|
|
color: [100, 200, 50, 255],
|
|
label: "body".into(),
|
|
},
|
|
BodyPart {
|
|
x: 0.0,
|
|
y: 0.0,
|
|
color: [100, 200, 50, 255],
|
|
label: "body".into(),
|
|
},
|
|
BodyPart {
|
|
x: 1.0,
|
|
y: 0.0,
|
|
color: [100, 200, 50, 255],
|
|
label: "body".into(),
|
|
},
|
|
BodyPart {
|
|
x: 2.0,
|
|
y: 0.0,
|
|
color: [100, 200, 50, 255],
|
|
label: "body".into(),
|
|
},
|
|
BodyPart {
|
|
x: 3.0,
|
|
y: 0.0,
|
|
color: [80, 180, 40, 255],
|
|
label: "head".into(),
|
|
},
|
|
],
|
|
constraints: vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)],
|
|
};
|
|
assert_eq!(t.parts.len(), 7);
|
|
let json = t.to_json();
|
|
let t2 = BodyTemplate::from_json(&json).expect("parse");
|
|
assert_eq!(t2.parts.len(), 7);
|
|
assert_eq!(t2.name, "snake");
|
|
}
|
|
|
|
#[test]
|
|
fn template_apply_to_entity() {
|
|
use verbatim::entity::entity::{Entity, EntityKind};
|
|
let mut e = Entity::new(0, EntityKind::Goblin);
|
|
let t = BodyTemplate::humanoid_goblin();
|
|
t.apply_to(&mut e, 100.0, 100.0);
|
|
assert_eq!(e.bodies.len(), 15);
|
|
assert_eq!(e.cx, 100.0);
|
|
assert_eq!(e.cy, 100.0);
|
|
assert!(!e.constraints.is_empty());
|
|
}
|