Implement currency, shops, and enhanced NPC interaction system
This commit is contained in:
238
src/commands.rs
238
src/commands.rs
@@ -134,6 +134,7 @@ pub async fn execute(
|
||||
"spells" | "skills" => cmd_spells(player_id, state).await,
|
||||
"guild" => cmd_guild(player_id, &args, state).await,
|
||||
"stats" | "st" => cmd_stats(player_id, state).await,
|
||||
"shop" => cmd_shop(player_id, &args, state).await,
|
||||
"admin" => cmd_admin(player_id, &args, state).await,
|
||||
"help" | "h" | "?" => cmd_help(player_id, state).await,
|
||||
"quit" | "exit" => CommandResult {
|
||||
@@ -999,9 +1000,9 @@ async fn cmd_examine(pid: usize, target: &str, state: &SharedState) -> CommandRe
|
||||
))
|
||||
}
|
||||
|
||||
async fn cmd_talk(pid: usize, target: &str, state: &SharedState) -> CommandResult {
|
||||
if target.is_empty() {
|
||||
return simple("Talk to whom?\r\n");
|
||||
async fn cmd_talk(pid: usize, input: &str, state: &SharedState) -> CommandResult {
|
||||
if input.is_empty() {
|
||||
return simple("Talk to whom? (Usage: talk <npc> [keyword])\r\n");
|
||||
}
|
||||
let st = state.lock().await;
|
||||
let conn = match st.players.get(&pid) {
|
||||
@@ -1012,12 +1013,17 @@ async fn cmd_talk(pid: usize, target: &str, state: &SharedState) -> CommandResul
|
||||
Some(r) => r,
|
||||
None => return simple("Void\r\n"),
|
||||
};
|
||||
let low = target.to_lowercase();
|
||||
|
||||
let (target, keyword) = match input.split_once(' ') {
|
||||
Some((t, k)) => (t.to_lowercase(), k.trim().to_lowercase()),
|
||||
None => (input.to_lowercase(), String::new()),
|
||||
};
|
||||
|
||||
let pname = &conn.player.name;
|
||||
|
||||
for nid in &room.npcs {
|
||||
if let Some(npc) = st.world.get_npc(nid) {
|
||||
if npc.name.to_lowercase().contains(&low) {
|
||||
if npc.name.to_lowercase().contains(&target) {
|
||||
if !st.npc_instances.get(nid).map(|i| i.alive).unwrap_or(true) {
|
||||
return simple(&format!(
|
||||
"{}\r\n",
|
||||
@@ -1031,13 +1037,54 @@ async fn cmd_talk(pid: usize, target: &str, state: &SharedState) -> CommandResul
|
||||
ansi::color(ansi::RED, &npc.name)
|
||||
));
|
||||
}
|
||||
|
||||
if !keyword.is_empty() {
|
||||
if let Some(response) = npc.keywords.get(&keyword) {
|
||||
return CommandResult {
|
||||
output: format!(
|
||||
"\r\n{} says: \"{}\"\r\n",
|
||||
ansi::color(ansi::YELLOW, &npc.name),
|
||||
ansi::color(ansi::WHITE, response)
|
||||
),
|
||||
broadcasts: Vec::new(),
|
||||
kick_targets: Vec::new(),
|
||||
quit: false,
|
||||
};
|
||||
} else {
|
||||
return simple(&format!(
|
||||
"{} looks at you blankly, not understanding '{}'.\r\n",
|
||||
ansi::color(ansi::YELLOW, &npc.name),
|
||||
keyword
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let greeting = npc.greeting.as_deref().unwrap_or("...");
|
||||
let mut output = format!(
|
||||
"\r\n{} says: \"{}\"\r\n",
|
||||
ansi::color(ansi::YELLOW, &npc.name),
|
||||
ansi::color(ansi::WHITE, greeting)
|
||||
);
|
||||
|
||||
if !npc.keywords.is_empty() {
|
||||
let mut keys: Vec<_> = npc.keywords.keys().cloned().collect();
|
||||
keys.sort();
|
||||
output.push_str(&format!(
|
||||
" {} {}\r\n",
|
||||
ansi::color(ansi::DIM, "You can ask about:"),
|
||||
keys.join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
if npc.shop.is_some() {
|
||||
output.push_str(&format!(
|
||||
" {}\r\n",
|
||||
ansi::color(ansi::CYAN, "This person appears to be a merchant. Try 'shop list'.")
|
||||
));
|
||||
}
|
||||
|
||||
return CommandResult {
|
||||
output: format!(
|
||||
"\r\n{} says: \"{}\"\r\n",
|
||||
ansi::color(ansi::YELLOW, &npc.name),
|
||||
ansi::color(ansi::WHITE, greeting)
|
||||
),
|
||||
output,
|
||||
broadcasts: Vec::new(),
|
||||
kick_targets: Vec::new(),
|
||||
quit: false,
|
||||
@@ -1427,6 +1474,169 @@ async fn cmd_spells(pid: usize, state: &SharedState) -> CommandResult {
|
||||
}
|
||||
}
|
||||
|
||||
async fn cmd_shop(pid: usize, args: &str, state: &SharedState) -> CommandResult {
|
||||
let mut st = state.lock().await;
|
||||
let (conn, rid) = match st.players.get_mut(&pid) {
|
||||
Some(c) => {
|
||||
let rid = c.player.room_id.clone();
|
||||
(c, rid)
|
||||
}
|
||||
None => return simple("Error\r\n"),
|
||||
};
|
||||
|
||||
// Find a merchant in the room
|
||||
let mut merchant_id = None;
|
||||
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.shop.is_some() {
|
||||
merchant_id = Some(nid.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let merchant_id = match merchant_id {
|
||||
Some(id) => id,
|
||||
None => return simple("There is no merchant here.\r\n"),
|
||||
};
|
||||
|
||||
let (subcmd, subargs) = match args.split_once(' ') {
|
||||
Some((c, a)) => (c.to_lowercase(), a.trim()),
|
||||
None => (args.to_lowercase(), ""),
|
||||
};
|
||||
|
||||
match subcmd.as_str() {
|
||||
"list" | "ls" | "" => {
|
||||
let npc = st.world.get_npc(&merchant_id).unwrap();
|
||||
let shop = npc.shop.as_ref().unwrap();
|
||||
let mut out = format!(
|
||||
"\r\n{}'s Shop Inventory (Markup: x{:.1})\r\n",
|
||||
ansi::bold(&npc.name),
|
||||
shop.markup
|
||||
);
|
||||
|
||||
if shop.sells.is_empty() {
|
||||
out.push_str(" (nothing for sale)\r\n");
|
||||
} else {
|
||||
for item_id in &shop.sells {
|
||||
if let Some(obj) = st.world.get_object(item_id) {
|
||||
let total_copper = (obj.value_gold * 10000 + obj.value_silver * 100 + obj.value_copper) as f32;
|
||||
let price_copper = (total_copper * shop.markup).ceil() as i32;
|
||||
let g = price_copper / 10000;
|
||||
let s = (price_copper % 10000) / 100;
|
||||
let c = price_copper % 100;
|
||||
|
||||
out.push_str(&format!(
|
||||
" - {} [{}g {}s {}c]\r\n",
|
||||
ansi::color(ansi::CYAN, &obj.name),
|
||||
g, s, c
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
simple(&out)
|
||||
}
|
||||
|
||||
"buy" => {
|
||||
if subargs.is_empty() {
|
||||
return simple("Buy what?\r\n");
|
||||
}
|
||||
let npc = st.world.get_npc(&merchant_id).unwrap();
|
||||
let shop = npc.shop.as_ref().unwrap().clone();
|
||||
|
||||
let item_to_buy = shop.sells.iter().find(|id| {
|
||||
if let Some(obj) = st.world.get_object(*id) {
|
||||
obj.name.to_lowercase().contains(&subargs.to_lowercase())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(item_id) = item_to_buy {
|
||||
let obj = st.world.get_object(item_id).unwrap().clone();
|
||||
let total_copper = (obj.value_gold * 10000 + obj.value_silver * 100 + obj.value_copper) as f32;
|
||||
let price_copper = (total_copper * shop.markup).ceil() as i32;
|
||||
|
||||
let player_total_copper = conn.player.gold * 10000 + conn.player.silver * 100 + conn.player.copper;
|
||||
if player_total_copper < price_copper {
|
||||
return simple("You don't have enough money.\r\n");
|
||||
}
|
||||
|
||||
// Deduct money
|
||||
let mut remaining = player_total_copper - price_copper;
|
||||
conn.player.gold = remaining / 10000;
|
||||
remaining %= 10000;
|
||||
conn.player.silver = remaining / 100;
|
||||
conn.player.copper = remaining % 100;
|
||||
|
||||
// Add to inventory
|
||||
conn.player.inventory.push(obj.clone());
|
||||
|
||||
simple(&format!(
|
||||
"You buy {} for {} copper equivalents.\r\n",
|
||||
ansi::color(ansi::CYAN, &obj.name),
|
||||
price_copper
|
||||
))
|
||||
} else {
|
||||
simple("The merchant doesn't sell that.\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
"sell" => {
|
||||
if subargs.is_empty() {
|
||||
return simple("Sell what?\r\n");
|
||||
}
|
||||
let npc = st.world.get_npc(&merchant_id).unwrap();
|
||||
let shop = npc.shop.as_ref().unwrap().clone();
|
||||
|
||||
let item_idx = conn.player.inventory.iter().position(|o| o.name.to_lowercase().contains(&subargs.to_lowercase()));
|
||||
|
||||
if let Some(idx) = item_idx {
|
||||
let obj = conn.player.inventory[idx].clone();
|
||||
|
||||
// Check if merchant buys this kind of item
|
||||
let can_sell = shop.buys.is_empty() || shop.buys.iter().any(|k| {
|
||||
if let Some(kind) = &obj.kind {
|
||||
kind.to_lowercase() == k.to_lowercase()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if !can_sell {
|
||||
return simple("The merchant isn't interested in that kind of item.\r\n");
|
||||
}
|
||||
|
||||
let total_copper = (obj.value_gold * 10000 + obj.value_silver * 100 + obj.value_copper) as f32;
|
||||
let price_copper = (total_copper * shop.markdown).floor() as i32;
|
||||
|
||||
// Add money to player
|
||||
let mut player_total_copper = conn.player.gold * 10000 + conn.player.silver * 100 + conn.player.copper;
|
||||
player_total_copper += price_copper;
|
||||
conn.player.gold = player_total_copper / 10000;
|
||||
player_total_copper %= 10000;
|
||||
conn.player.silver = player_total_copper / 100;
|
||||
conn.player.copper = player_total_copper % 100;
|
||||
|
||||
// Remove from inventory
|
||||
conn.player.inventory.remove(idx);
|
||||
|
||||
simple(&format!(
|
||||
"You sell {} for {} copper equivalents.\r\n",
|
||||
ansi::color(ansi::CYAN, &obj.name),
|
||||
price_copper
|
||||
))
|
||||
} else {
|
||||
simple("You don't have that in your inventory.\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
_ => simple("Usage: shop list | shop buy <item> | shop sell <item>\r\n"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn cmd_guild(pid: usize, args: &str, state: &SharedState) -> CommandResult {
|
||||
let (subcmd, subargs) = match args.split_once(' ') {
|
||||
Some((c, a)) => (c.to_lowercase(), a.trim().to_string()),
|
||||
@@ -1665,6 +1875,14 @@ async fn cmd_stats(pid: usize, state: &SharedState) -> CommandResult {
|
||||
s.xp,
|
||||
s.xp_to_next
|
||||
));
|
||||
out.push_str(&format!(
|
||||
" {} {}{}g {}{}s {}{}c{}\r\n",
|
||||
ansi::color(ansi::DIM, "Money:"),
|
||||
ansi::YELLOW, p.gold,
|
||||
ansi::WHITE, p.silver,
|
||||
ansi::RED, p.copper,
|
||||
ansi::RESET,
|
||||
));
|
||||
if !p.guilds.is_empty() {
|
||||
out.push_str(&format!(" {}\r\n", ansi::color(ansi::DIM, "Guilds:")));
|
||||
let mut guild_list: Vec<_> = p.guilds.iter().collect();
|
||||
|
||||
Reference in New Issue
Block a user