Give every NPC a race and class, resolved at spawn time
NPCs can now optionally specify race and class in their TOML. When omitted, race is randomly selected from non-hidden races and class is determined by the race's default_class or picked randomly from compatible non-hidden classes. Race/class are re-rolled on each respawn for NPCs without fixed values, so killing the barkeep may bring back a different race next time. New hidden content (excluded from character creation): - Beast race: for animals (rats, etc.) with feral stats and no equipment slots - Peasant class: weak default for humanoid NPCs - Creature class: default for beasts/animals Existing races gain default_class fields (humanoids → peasant, beast → creature, dragon → random compatible). Look and examine commands now display NPC race and class. Made-with: Cursor
This commit is contained in:
@@ -254,17 +254,23 @@ async fn cmd_look(pid: usize, target: &str, state: &SharedState) -> CommandResul
|
||||
for nid in &room.npcs {
|
||||
if let Some(npc) = st.world.get_npc(nid) {
|
||||
if npc.name.to_lowercase().contains(&low) {
|
||||
let alive = st.npc_instances.get(nid).map(|i| i.alive).unwrap_or(true);
|
||||
let inst = st.npc_instances.get(nid);
|
||||
let alive = inst.map(|i| i.alive).unwrap_or(true);
|
||||
let att = st.npc_attitude_toward(nid, &pname);
|
||||
let mut out = format!(
|
||||
"\r\n{}\r\n {}\r\n",
|
||||
ansi::bold(&npc.name),
|
||||
npc.description
|
||||
);
|
||||
if let Some(inst) = inst {
|
||||
let rname = st.world.races.iter().find(|r| r.id == inst.race_id).map(|r| r.name.as_str()).unwrap_or("???");
|
||||
let cname = st.world.classes.iter().find(|c| c.id == inst.class_id).map(|c| c.name.as_str()).unwrap_or("???");
|
||||
out.push_str(&format!(" {} {}\r\n", ansi::color(ansi::CYAN, rname), ansi::color(ansi::DIM, cname)));
|
||||
}
|
||||
if !alive {
|
||||
out.push_str(&format!(" {}\r\n", ansi::color(ansi::RED, "(dead)")));
|
||||
} else if let Some(ref c) = npc.combat {
|
||||
let hp = st.npc_instances.get(nid).map(|i| i.hp).unwrap_or(c.max_hp);
|
||||
let hp = inst.map(|i| i.hp).unwrap_or(c.max_hp);
|
||||
out.push_str(&format!(
|
||||
" HP: {}/{} | ATK: {} | DEF: {}\r\n",
|
||||
hp, c.max_hp, c.attack, c.defense
|
||||
@@ -897,14 +903,20 @@ async fn cmd_examine(pid: usize, target: &str, state: &SharedState) -> CommandRe
|
||||
for nid in &room.npcs {
|
||||
if let Some(npc) = st.world.get_npc(nid) {
|
||||
if npc.name.to_lowercase().contains(&low) {
|
||||
let alive = st.npc_instances.get(nid).map(|i| i.alive).unwrap_or(true);
|
||||
let inst = st.npc_instances.get(nid);
|
||||
let alive = inst.map(|i| i.alive).unwrap_or(true);
|
||||
let att = st.npc_attitude_toward(nid, pname);
|
||||
let mut out =
|
||||
format!("\r\n{}\r\n {}\r\n", ansi::bold(&npc.name), npc.description);
|
||||
if let Some(inst) = inst {
|
||||
let rname = st.world.races.iter().find(|r| r.id == inst.race_id).map(|r| r.name.as_str()).unwrap_or("???");
|
||||
let cname = st.world.classes.iter().find(|c| c.id == inst.class_id).map(|c| c.name.as_str()).unwrap_or("???");
|
||||
out.push_str(&format!(" {} {}\r\n", ansi::color(ansi::CYAN, rname), ansi::color(ansi::DIM, cname)));
|
||||
}
|
||||
if !alive {
|
||||
out.push_str(&format!(" {}\r\n", ansi::color(ansi::RED, "(dead)")));
|
||||
} else if let Some(ref c) = npc.combat {
|
||||
let hp = st.npc_instances.get(nid).map(|i| i.hp).unwrap_or(c.max_hp);
|
||||
let hp = inst.map(|i| i.hp).unwrap_or(c.max_hp);
|
||||
out.push_str(&format!(
|
||||
" HP: {}/{} | ATK: {} | DEF: {}\r\n",
|
||||
hp, c.max_hp, c.attack, c.defense
|
||||
|
||||
Reference in New Issue
Block a user