From 87baaee46f4de30bdece8d0450dba71d93344441 Mon Sep 17 00:00:00 2001 From: AI Agent Date: Thu, 19 Mar 2026 08:12:16 -0600 Subject: [PATCH] Finalize shops, interactions and world data --- src/commands.rs | 109 +++++++++++++++++++---------------- src/db.rs | 24 ++++---- world/lawold/npcs/breda.toml | 2 +- 3 files changed, 72 insertions(+), 63 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 7a1552d..9fc2f91 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1476,11 +1476,8 @@ 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) - } + let rid = match st.players.get(&pid) { + Some(c) => c.player.room_id.clone(), None => return simple("Error\r\n"), }; @@ -1543,42 +1540,48 @@ async fn cmd_shop(pid: usize, args: &str, state: &SharedState) -> CommandResult 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 (shop, _npc_name) = { + let npc = st.world.get_npc(&merchant_id).unwrap(); + (npc.shop.as_ref().unwrap().clone(), npc.name.clone()) + }; - let item_to_buy = shop.sells.iter().find(|id| { + let item_id = shop.sells.iter().find(|id| { if let Some(obj) = st.world.get_object(*id) { obj.name.to_lowercase().contains(&subargs.to_lowercase()) } else { false } - }); + }).cloned(); - if let Some(item_id) = item_to_buy { - let obj = st.world.get_object(item_id).unwrap().clone(); + if let Some(id) = item_id { + let obj = st.world.get_object(&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"); + if let Some(conn) = st.players.get_mut(&pid) { + 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("Error\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") } @@ -1588,14 +1591,16 @@ async fn cmd_shop(pid: usize, args: &str, state: &SharedState) -> CommandResult 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 shop = st.world.get_npc(&merchant_id).unwrap().shop.as_ref().unwrap().clone(); - let item_idx = conn.player.inventory.iter().position(|o| o.name.to_lowercase().contains(&subargs.to_lowercase())); + let item_info = if let Some(conn) = st.players.get(&pid) { + conn.player.inventory.iter().position(|o| o.name.to_lowercase().contains(&subargs.to_lowercase())) + .map(|idx| (idx, conn.player.inventory[idx].clone())) + } else { + None + }; - if let Some(idx) = item_idx { - let obj = conn.player.inventory[idx].clone(); - + if let Some((idx, obj)) = item_info { // 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 { @@ -1612,22 +1617,26 @@ async fn cmd_shop(pid: usize, args: &str, state: &SharedState) -> CommandResult 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; + if let Some(conn) = st.players.get_mut(&pid) { + // 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); + // 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 - )) + simple(&format!( + "You sell {} for {} copper equivalents.\r\n", + ansi::color(ansi::CYAN, &obj.name), + price_copper + )) + } else { + simple("Error\r\n") + } } else { simple("You don't have that in your inventory.\r\n") } diff --git a/src/db.rs b/src/db.rs index 41a659d..532223f 100644 --- a/src/db.rs +++ b/src/db.rs @@ -78,7 +78,7 @@ impl SqliteDb { .map_err(|e| format!("Failed to set pragmas: {e}"))?; conn.execute_batch( - CREATE TABLE IF NOT EXISTS players ( + r#"CREATE TABLE IF NOT EXISTS players ( name TEXT PRIMARY KEY, race_id TEXT NOT NULL, class_id TEXT NOT NULL, @@ -89,8 +89,8 @@ impl SqliteDb { max_hp INTEGER NOT NULL, attack INTEGER NOT NULL, defense INTEGER NOT NULL, - inventory_json TEXT NOT NULL DEFAULT '[]', - equipped_json TEXT NOT NULL DEFAULT '{}', + inventory_json TEXT NOT NULL DEFAULT "[]", + equipped_json TEXT NOT NULL DEFAULT "{}", is_admin INTEGER NOT NULL DEFAULT 0, mana INTEGER NOT NULL DEFAULT 0, max_mana INTEGER NOT NULL DEFAULT 0, @@ -127,13 +127,13 @@ impl SqliteDb { guild_id TEXT NOT NULL, level INTEGER NOT NULL DEFAULT 1, PRIMARY KEY (player_name, guild_id) - );", + );"#, ) .map_err(|e| format!("Failed to create tables: {e}"))?; // Migration: add is_admin column if missing let has_admin: bool = conn - .prepare("SELECT COUNT(*) FROM pragma_table_info('players') WHERE name='is_admin'") + .prepare(r#"SELECT COUNT(*) FROM pragma_table_info("players") WHERE name="is_admin""#) .and_then(|mut s| s.query_row([], |r| r.get::<_, i32>(0))) .map(|c| c > 0) .unwrap_or(false); @@ -146,34 +146,34 @@ impl SqliteDb { // Migration: equipped_weapon_json/equipped_armor_json -> equipped_json let has_old_weapon: bool = conn - .prepare("SELECT COUNT(*) FROM pragma_table_info('players') WHERE name='equipped_weapon_json'") + .prepare(r#"SELECT COUNT(*) FROM pragma_table_info("players") WHERE name="equipped_weapon_json""#) .and_then(|mut s| s.query_row([], |r| r.get::<_, i32>(0))) .map(|c| c > 0) .unwrap_or(false); let has_equipped: bool = conn - .prepare("SELECT COUNT(*) FROM pragma_table_info('players') WHERE name='equipped_json'") + .prepare(r#"SELECT COUNT(*) FROM pragma_table_info("players") WHERE name="equipped_json""#) .and_then(|mut s| s.query_row([], |r| r.get::<_, i32>(0))) .map(|c| c > 0) .unwrap_or(false); if has_old_weapon && !has_equipped { let _ = conn.execute( - "ALTER TABLE players ADD COLUMN equipped_json TEXT NOT NULL DEFAULT '{}'", + r#"ALTER TABLE players ADD COLUMN equipped_json TEXT NOT NULL DEFAULT "{}"#, [], ); log::info!("Migrating equipped_weapon_json/equipped_armor_json to equipped_json..."); let _ = conn.execute_batch( - "UPDATE players SET equipped_json = '{}' WHERE equipped_weapon_json IS NULL AND equipped_armor_json IS NULL;" + r#"UPDATE players SET equipped_json = "{}" WHERE equipped_weapon_json IS NULL AND equipped_armor_json IS NULL;"# ); } else if !has_equipped { let _ = conn.execute( - "ALTER TABLE players ADD COLUMN equipped_json TEXT NOT NULL DEFAULT '{}'", + r#"ALTER TABLE players ADD COLUMN equipped_json TEXT NOT NULL DEFAULT "{}"#, [], ); } // Migration: add mana/endurance columns let has_mana: bool = conn - .prepare("SELECT COUNT(*) FROM pragma_table_info('players') WHERE name='mana'") + .prepare(r#"SELECT COUNT(*) FROM pragma_table_info("players") WHERE name="mana""#) .and_then(|mut s| s.query_row([], |r| r.get::<_, i32>(0))) .map(|c| c > 0) .unwrap_or(false); @@ -186,7 +186,7 @@ impl SqliteDb { // Migration: add currency columns let has_gold: bool = conn - .prepare("SELECT COUNT(*) FROM pragma_table_info('players') WHERE name='gold'") + .prepare(r#"SELECT COUNT(*) FROM pragma_table_info("players") WHERE name="gold""#) .and_then(|mut s| s.query_row([], |r| r.get::<_, i32>(0))) .map(|c| c > 0) .unwrap_or(false); diff --git a/world/lawold/npcs/breda.toml b/world/lawold/npcs/breda.toml index a8232a3..75a56e0 100644 --- a/world/lawold/npcs/breda.toml +++ b/world/lawold/npcs/breda.toml @@ -2,7 +2,7 @@ name = "Breda" description = "Breda is haughty in bearing, with thin auburn hair and narrow blue eyes. She wears simple clothing and several small tools hang from her belt. Breda will purchase monster teeth for a silver coin each." room = "lawold:well_market_trade_stalls" race = "race:human" -base_attitude = "aggressive" +base_attitude = "neutral" [dialogue] greeting = "What do you want? I'm busy. Unless you have some teeth to sell?"