Add optional target argument to look command

look with no args still shows the room. With an argument, it inspects
NPCs (showing stats/attitude), objects (description), exits (destination),
players, or inventory items. Replaces the need to use examine for quick
inspection while keeping full room view as the default.

Made-with: Cursor
This commit is contained in:
AI Agent
2026-03-14 15:42:35 -06:00
parent 005c4faf08
commit bdd1a85ea2

View File

@@ -89,7 +89,7 @@ pub async fn execute(
} }
let result = match cmd.as_str() { let result = match cmd.as_str() {
"look" | "l" => cmd_look(player_id, state).await, "look" | "l" => cmd_look(player_id, &args, state).await,
"go" => cmd_go(player_id, &args, state).await, "go" => cmd_go(player_id, &args, state).await,
"north" | "south" | "east" | "west" | "up" | "down" | "n" | "s" | "e" | "w" | "u" "north" | "south" | "east" | "west" | "up" | "down" | "n" | "s" | "e" | "w" | "u"
| "d" => cmd_go(player_id, resolve_dir(&cmd), state).await, | "d" => cmd_go(player_id, resolve_dir(&cmd), state).await,
@@ -233,15 +233,139 @@ pub fn render_room_view(
out out
} }
async fn cmd_look(pid: usize, state: &SharedState) -> CommandResult { async fn cmd_look(pid: usize, target: &str, state: &SharedState) -> CommandResult {
let st = state.lock().await; let st = state.lock().await;
let rid = match st.players.get(&pid) { let conn = match st.players.get(&pid) {
Some(c) => c.player.room_id.clone(), Some(c) => c,
None => return simple("Error\r\n"), None => return simple("Error\r\n"),
}; };
let rid = conn.player.room_id.clone();
let pname = conn.player.name.clone();
if !target.is_empty() {
let low = target.to_lowercase();
// Check NPCs in the room
if let Some(room) = st.world.get_room(&rid) {
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 att = st.npc_attitude_toward(nid, &pname);
let mut out = format!(
"\r\n{}\r\n {}\r\n",
ansi::bold(&npc.name),
npc.description
);
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);
out.push_str(&format!(
" HP: {}/{} | ATK: {} | DEF: {}\r\n",
hp, c.max_hp, c.attack, c.defense
));
}
out.push_str(&format!(
" Attitude: {}\r\n",
ansi::color(attitude_color(att), att.label())
));
return CommandResult {
output: out,
broadcasts: Vec::new(),
kick_targets: Vec::new(),
quit: false,
};
}
}
}
// Check objects in the room
for oid in &room.objects {
if let Some(obj) = st.world.get_object(oid) {
if obj.name.to_lowercase().contains(&low) {
return CommandResult {
output: format!(
"\r\n{}\r\n {}\r\n",
ansi::bold(&obj.name),
obj.description
),
broadcasts: Vec::new(),
kick_targets: Vec::new(),
quit: false,
};
}
}
}
// Check exits
for (dir, dest_id) in &room.exits {
if dir.to_lowercase().contains(&low) {
let dest_name = st.world.get_room(dest_id)
.map(|r| r.name.as_str())
.unwrap_or("???");
return CommandResult {
output: format!(
"{} leads to {}.\r\n",
ansi::direction(dir),
ansi::room_name(dest_name),
),
broadcasts: Vec::new(),
kick_targets: Vec::new(),
quit: false,
};
}
}
}
// Check inventory
for obj in &conn.player.inventory {
if obj.name.to_lowercase().contains(&low) {
return CommandResult {
output: format!(
"\r\n{}\r\n {}\r\n",
ansi::bold(&obj.name),
obj.description
),
broadcasts: Vec::new(),
kick_targets: Vec::new(),
quit: false,
};
}
}
// Check other players in the room
for other in st.players_in_room(&rid, pid) {
if other.player.name.to_lowercase().contains(&low) {
let race_name = st.world.races.iter()
.find(|r| r.id == other.player.race_id)
.map(|r| r.name.as_str())
.unwrap_or("???");
let combat_str = if other.combat.is_some() { " [in combat]" } else { "" };
return CommandResult {
output: format!(
"\r\n{} — a {} {}{}\r\n",
ansi::player_name(&other.player.name),
race_name,
ansi::system_msg(&format!("(level {})", other.player.stats.level)),
ansi::color(ansi::RED, combat_str),
),
broadcasts: Vec::new(),
kick_targets: Vec::new(),
quit: false,
};
}
}
return simple(&format!(
"{}\r\n",
ansi::error_msg(&format!("You don't see '{target}' here."))
));
}
// No target — show the room
let mut out = render_room_view(&rid, pid, &st); let mut out = render_room_view(&rid, pid, &st);
// Show combat status if in combat
if let Some(conn) = st.players.get(&pid) { if let Some(conn) = st.players.get(&pid) {
if let Some(ref combat) = conn.combat { if let Some(ref combat) = conn.combat {
if let Some(npc) = st.world.get_npc(&combat.npc_id) { if let Some(npc) = st.world.get_npc(&combat.npc_id) {
@@ -1211,7 +1335,7 @@ async fn cmd_help(pid: usize, state: &SharedState) -> CommandResult {
let mut out = format!("\r\n{}\r\n", ansi::bold("=== Commands ===")); let mut out = format!("\r\n{}\r\n", ansi::bold("=== Commands ==="));
let cmds = [ let cmds = [
("look, l", "Look around the current room"), ("look [target], l", "Look at room, NPC, object, exit, or player"),
("go <dir>, n/s/e/w/u/d", "Move in a direction"), ("go <dir>, n/s/e/w/u/d", "Move in a direction"),
("say <msg>", "Say something to the room"), ("say <msg>", "Say something to the room"),
("who", "See who's online"), ("who", "See who's online"),