Implement currency, shops, and enhanced NPC interaction system

This commit is contained in:
AI Agent
2026-03-17 13:31:33 -06:00
parent 2e1794b799
commit 0722a2f1d7
9 changed files with 353 additions and 22 deletions

View File

@@ -79,10 +79,22 @@ pub struct RoomFile {
pub exits: HashMap<String, String>,
}
#[derive(Deserialize)]
#[derive(Deserialize, Clone)]
pub struct ShopFile {
pub buys: Vec<String>, // List of item kinds or IDs the shop buys
pub sells: Vec<String>, // List of item IDs the shop sells
#[serde(default)]
pub markup: f32, // Multiplier for sell price (default 1.0)
#[serde(default)]
pub markdown: f32, // Multiplier for buy price (default 0.5)
}
#[derive(Deserialize, Clone)]
pub struct NpcDialogue {
#[serde(default)]
pub greeting: Option<String>,
#[serde(default)]
pub keywords: HashMap<String, String>, // keyword -> response
}
#[derive(Deserialize)]
@@ -113,6 +125,14 @@ pub struct NpcFile {
pub dialogue: Option<NpcDialogue>,
#[serde(default)]
pub combat: Option<NpcCombatFile>,
#[serde(default)]
pub shop: Option<ShopFile>,
#[serde(default)]
pub gold: i32,
#[serde(default)]
pub silver: i32,
#[serde(default)]
pub copper: i32,
}
fn default_attitude() -> Attitude {
@@ -143,6 +163,12 @@ pub struct ObjectFile {
pub takeable: bool,
#[serde(default)]
pub stats: Option<ObjectStatsFile>,
#[serde(default)]
pub value_gold: i32,
#[serde(default)]
pub value_silver: i32,
#[serde(default)]
pub value_copper: i32,
}
// --- Race TOML schema ---
@@ -409,7 +435,12 @@ pub struct Npc {
pub fixed_class: Option<String>,
pub respawn_secs: Option<u64>,
pub greeting: Option<String>,
pub keywords: HashMap<String, String>,
pub combat: Option<NpcCombatStats>,
pub shop: Option<ShopFile>,
pub gold: i32,
pub silver: i32,
pub copper: i32,
}
#[derive(Clone, Serialize, Deserialize)]
@@ -429,6 +460,9 @@ pub struct Object {
pub slot: Option<String>,
pub takeable: bool,
pub stats: ObjectStats,
pub value_gold: i32,
pub value_silver: i32,
pub value_copper: i32,
}
pub const DEFAULT_HUMANOID_SLOTS: &[&str] = &[
@@ -637,14 +671,18 @@ impl World {
load_entities_from_dir(&region_path.join("npcs"), &region_name, &mut |id, content| {
let nf: NpcFile = toml::from_str(content).map_err(|e| format!("Bad npc {id}: {e}"))?;
let (greeting, keywords) = match nf.dialogue {
Some(d) => (d.greeting, d.keywords),
None => (None, HashMap::new()),
};
let combat = Some(nf.combat.map(|c| NpcCombatStats { max_hp: c.max_hp, attack: c.attack, defense: c.defense, xp_reward: c.xp_reward })
.unwrap_or(NpcCombatStats { max_hp: 20, attack: 4, defense: 2, xp_reward: 5 }));
let greeting = nf.dialogue.and_then(|d| d.greeting);
npcs.insert(id.clone(), Npc {
id: id.clone(), name: nf.name, description: nf.description, room: nf.room,
base_attitude: nf.base_attitude, faction: nf.faction,
fixed_race: nf.race, fixed_class: nf.class,
respawn_secs: nf.respawn_secs, greeting, combat,
respawn_secs: nf.respawn_secs, greeting, keywords, combat,
shop: nf.shop, gold: nf.gold, silver: nf.silver, copper: nf.copper,
});
Ok(())
})?;
@@ -656,6 +694,7 @@ impl World {
id: id.clone(), name: of.name, description: of.description, room: of.room,
kind: of.kind, slot: of.slot, takeable: of.takeable,
stats: ObjectStats { damage: stats.damage, armor: stats.armor, heal_amount: stats.heal_amount },
value_gold: of.value_gold, value_silver: of.value_silver, value_copper: of.value_copper,
});
Ok(())
})?;