pub mod recipes;
pub mod workbench;
pub mod economy;
pub mod market;
pub use recipes::{
Recipe,
RecipeBook,
RecipeCategory,
Ingredient,
CraftResult,
CraftingCalculator,
MasterySystem,
CategoryMastery,
RecipeDiscovery,
QualityTier,
};
pub use workbench::{
Workbench,
WorkbenchType,
WorkbenchTier,
WorkbenchState,
CraftingQueue,
CraftingJob,
WorkbenchEvent,
FuelType,
FuelSystem,
CraftingStation,
AutoCrafter,
AutoCraftConfig,
};
pub use economy::{
Economy,
Currency,
PriceModifier,
PlayerReputation,
ShopItem,
ShopInventory,
TradeOffer,
TaxSystem,
};
pub use market::{
AuctionHouse,
Listing,
Bid,
MarketHistory,
MarketBoard,
TradeWindow,
MailMessage,
};
#[derive(Debug, Clone)]
pub struct CraftingContext {
pub recipe_book: recipes::RecipeBook,
pub mastery: recipes::MasterySystem,
pub discovery: recipes::RecipeDiscovery,
pub economy: economy::Economy,
pub market: market::MarketBoard,
}
impl CraftingContext {
pub fn new() -> Self {
Self {
recipe_book: recipes::RecipeBook::default_recipes(),
mastery: recipes::MasterySystem::new(),
discovery: recipes::RecipeDiscovery::new(),
economy: economy::Economy::default(),
market: market::MarketBoard::new(),
}
}
pub fn tick(&mut self, dt: f32, current_time: f32) {
self.economy.update_prices(dt);
self.market.tick(current_time);
}
pub fn award_crafting_xp(
&mut self,
category: &recipes::RecipeCategory,
xp: u32,
) -> Vec<String> {
self.mastery.award_xp(category, xp)
}
pub fn attempt_discovery(
&mut self,
ingredient_ids: &[String],
rng: f32,
) -> Option<String> {
self.discovery.attempt_discovery(ingredient_ids, &self.recipe_book, rng)
}
pub fn get_recipe(&self, id: &str) -> Option<&recipes::Recipe> {
self.recipe_book.get(id)
}
pub fn market_price(&self, item_id: &str) -> Option<u64> {
self.economy.current_price(item_id)
}
pub fn market_currency(&self, item_id: &str) -> Option<currency::CurrencyAlias> {
self.economy.current_price_currency(item_id)
}
}
mod currency {
pub type CurrencyAlias = crate::crafting::economy::Currency;
}
impl Default for CraftingContext {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CraftOutcome {
pub recipe_id: String,
pub items_produced: Vec<(String, u32, u8)>,
pub experience_gained: u32,
pub legendary_proc: bool,
pub actual_duration: f32,
}
impl CraftOutcome {
pub fn new(recipe_id: impl Into<String>) -> Self {
Self {
recipe_id: recipe_id.into(),
items_produced: Vec::new(),
experience_gained: 0,
legendary_proc: false,
actual_duration: 0.0,
}
}
pub fn with_item(mut self, item_id: impl Into<String>, quantity: u32, quality: u8) -> Self {
if quality >= recipes::QualityTier::Legendary.threshold() {
self.legendary_proc = true;
}
self.items_produced.push((item_id.into(), quantity, quality));
self
}
pub fn with_xp(mut self, xp: u32) -> Self {
self.experience_gained = xp;
self
}
pub fn with_duration(mut self, secs: f32) -> Self {
self.actual_duration = secs;
self
}
pub fn total_items(&self) -> u32 {
self.items_produced.iter().map(|(_, qty, _)| qty).sum()
}
pub fn best_quality_tier(&self) -> Option<recipes::QualityTier> {
self.items_produced
.iter()
.map(|(_, _, q)| recipes::QualityTier::from_value(*q))
.max()
}
}
#[derive(Debug, Clone)]
pub enum CraftingError {
RecipeNotFound { recipe_id: String },
InsufficientSkill { required: u32, actual: u32 },
MissingIngredient { item_id: String, required: u32, available: u32 },
MissingTool { tool_id: String },
WorkbenchWrongType { expected: WorkbenchType, actual: WorkbenchType },
WorkbenchBroken { repair_cost: u64 },
QueueFull,
InsufficientFuel,
}
impl std::fmt::Display for CraftingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CraftingError::RecipeNotFound { recipe_id } =>
write!(f, "Recipe not found: {}", recipe_id),
CraftingError::InsufficientSkill { required, actual } =>
write!(f, "Skill too low: need {}, have {}", required, actual),
CraftingError::MissingIngredient { item_id, required, available } =>
write!(f, "Missing {}: need {}, have {}", item_id, required, available),
CraftingError::MissingTool { tool_id } =>
write!(f, "Missing required tool: {}", tool_id),
CraftingError::WorkbenchWrongType { expected, actual } =>
write!(f, "Wrong workbench: need {:?}, have {:?}", expected, actual),
CraftingError::WorkbenchBroken { repair_cost } =>
write!(f, "Workbench broken (repair cost: {} copper)", repair_cost),
CraftingError::QueueFull =>
write!(f, "Crafting queue is full"),
CraftingError::InsufficientFuel =>
write!(f, "Workbench has no fuel"),
}
}
}
pub fn validate_craft(
recipe_id: &str,
player_skill: u32,
player_inventory: &std::collections::HashMap<String, u32>,
player_tools: &[String],
bench_type: &WorkbenchType,
recipe_book: &recipes::RecipeBook,
) -> Result<(), CraftingError> {
let recipe = recipe_book.get(recipe_id).ok_or_else(|| {
CraftingError::RecipeNotFound { recipe_id: recipe_id.to_string() }
})?;
if player_skill < recipe.required_level {
return Err(CraftingError::InsufficientSkill {
required: recipe.required_level,
actual: player_skill,
});
}
for ing in &recipe.ingredients {
let available = player_inventory.get(&ing.item_id).copied().unwrap_or(0);
if available < ing.quantity {
return Err(CraftingError::MissingIngredient {
item_id: ing.item_id.clone(),
required: ing.quantity,
available,
});
}
}
for tool in &recipe.required_tools {
if !player_tools.contains(tool) {
return Err(CraftingError::MissingTool { tool_id: tool.clone() });
}
}
let expected_bench = category_to_bench_type(&recipe.category);
if let Some(expected) = expected_bench {
if bench_type != &expected {
return Err(CraftingError::WorkbenchWrongType {
expected,
actual: bench_type.clone(),
});
}
}
Ok(())
}
pub fn category_to_bench_type(category: &recipes::RecipeCategory) -> Option<WorkbenchType> {
match category {
recipes::RecipeCategory::Smithing => Some(WorkbenchType::Forge),
recipes::RecipeCategory::Alchemy => Some(WorkbenchType::AlchemyTable),
recipes::RecipeCategory::Cooking => Some(WorkbenchType::CookingPot),
recipes::RecipeCategory::Enchanting => Some(WorkbenchType::EnchantingTable),
recipes::RecipeCategory::Engineering => Some(WorkbenchType::Workbench),
recipes::RecipeCategory::Tailoring => Some(WorkbenchType::Loom),
recipes::RecipeCategory::Woodworking => Some(WorkbenchType::Workbench),
recipes::RecipeCategory::Jeweling => Some(WorkbenchType::Jeweler),
}
}