Skip to main content

proof_engine/crafting/
economy.rs

1// crafting/economy.rs — Gold, currency, shops, supply/demand, and trade system
2
3use std::collections::HashMap;
4
5// ---------------------------------------------------------------------------
6// Currency
7// ---------------------------------------------------------------------------
8
9/// Three-tier currency: gold, silver, copper.
10/// 100 copper = 1 silver, 100 silver = 1 gold.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Currency {
13    pub gold: u64,
14    pub silver: u64,
15    pub copper: u64,
16}
17
18impl Currency {
19    pub fn new(gold: u64, silver: u64, copper: u64) -> Self {
20        let mut c = Self { gold, silver, copper };
21        c.normalize();
22        c
23    }
24
25    /// Create currency from a total copper amount.
26    pub fn from_copper(total: u64) -> Self {
27        let gold = total / 10_000;
28        let rem = total % 10_000;
29        let silver = rem / 100;
30        let copper = rem % 100;
31        Self { gold, silver, copper }
32    }
33
34    /// Create currency from a gold amount (no silver/copper).
35    pub fn gold(amount: u64) -> Self {
36        Self { gold: amount, silver: 0, copper: 0 }
37    }
38
39    /// Create currency from a silver amount (normalizes automatically).
40    pub fn silver(amount: u64) -> Self {
41        Self::from_copper(amount * 100)
42    }
43
44    /// Create currency from a copper amount (normalizes automatically).
45    pub fn copper(amount: u64) -> Self {
46        Self::from_copper(amount)
47    }
48
49    pub fn zero() -> Self {
50        Self { gold: 0, silver: 0, copper: 0 }
51    }
52
53    /// Convert 100 copper → 1 silver, 100 silver → 1 gold.
54    pub fn normalize(&mut self) {
55        let carry_silver = self.copper / 100;
56        self.copper %= 100;
57        self.silver += carry_silver;
58
59        let carry_gold = self.silver / 100;
60        self.silver %= 100;
61        self.gold += carry_gold;
62    }
63
64    /// Total value expressed in copper.
65    pub fn to_copper_total(&self) -> u64 {
66        self.gold * 10_000 + self.silver * 100 + self.copper
67    }
68
69    /// Check if this wallet has at least `amount`.
70    pub fn has_at_least(&self, amount: &Currency) -> bool {
71        self.to_copper_total() >= amount.to_copper_total()
72    }
73
74    /// Attempt to subtract `amount` from this currency.
75    /// Returns true on success (and modifies self), false if insufficient funds.
76    pub fn try_subtract(&mut self, amount: &Currency) -> bool {
77        let total = self.to_copper_total();
78        let cost = amount.to_copper_total();
79        if total < cost {
80            return false;
81        }
82        *self = Self::from_copper(total - cost);
83        true
84    }
85
86    /// Add `amount` to this currency.
87    pub fn add(&mut self, amount: &Currency) {
88        let total = self.to_copper_total() + amount.to_copper_total();
89        *self = Self::from_copper(total);
90    }
91
92    /// Multiply the currency by a scalar (e.g. for quantity pricing).
93    pub fn multiply(&self, factor: u32) -> Self {
94        Self::from_copper(self.to_copper_total() * factor as u64)
95    }
96
97    /// Scale by a floating-point factor (for discounts / surcharges).
98    pub fn scale(&self, factor: f32) -> Self {
99        let scaled = (self.to_copper_total() as f32 * factor).round() as u64;
100        Self::from_copper(scaled)
101    }
102
103    pub fn is_zero(&self) -> bool {
104        self.to_copper_total() == 0
105    }
106}
107
108impl Default for Currency {
109    fn default() -> Self {
110        Self::zero()
111    }
112}
113
114impl std::fmt::Display for Currency {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        write!(f, "{}g {}s {}c", self.gold, self.silver, self.copper)
117    }
118}
119
120impl PartialOrd for Currency {
121    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
122        self.to_copper_total().partial_cmp(&other.to_copper_total())
123    }
124}
125
126impl Ord for Currency {
127    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
128        self.to_copper_total().cmp(&other.to_copper_total())
129    }
130}
131
132// ---------------------------------------------------------------------------
133// PriceModifier
134// ---------------------------------------------------------------------------
135
136/// Computes a final price from base price with supply/demand and reputation factors.
137#[derive(Debug, Clone)]
138pub struct PriceModifier {
139    pub base_price: u64,
140    /// > 1.0 means high demand (raises price).
141    pub demand_factor: f32,
142    /// < 1.0 means high supply (lowers price).
143    pub supply_factor: f32,
144    /// > 1.0 means good rep with seller (lowers price via discount); < 1.0 means bad rep.
145    pub player_rep_factor: f32,
146}
147
148impl PriceModifier {
149    pub fn new(base_price: u64) -> Self {
150        Self {
151            base_price,
152            demand_factor: 1.0,
153            supply_factor: 1.0,
154            player_rep_factor: 1.0,
155        }
156    }
157
158    /// Compute the final price applying all modifiers.
159    pub fn final_price(&self) -> u64 {
160        let base = self.base_price as f32;
161        // Demand raises price, supply lowers it, high rep gives discount
162        let rep_discount = 1.0 + (1.0 - self.player_rep_factor.clamp(0.5, 1.5)) * 0.3;
163        let modified = base
164            * self.demand_factor.clamp(0.5, 3.0)
165            * self.supply_factor.clamp(0.25, 2.0)
166            * rep_discount;
167        modified.round().max(1.0) as u64
168    }
169
170    /// Compute price as a Currency value.
171    pub fn final_currency(&self) -> Currency {
172        Currency::from_copper(self.final_price())
173    }
174}
175
176// ---------------------------------------------------------------------------
177// PlayerReputation
178// ---------------------------------------------------------------------------
179
180/// A player's standing with a specific faction, affecting shop prices.
181#[derive(Debug, Clone)]
182pub struct PlayerReputation {
183    pub faction_id: String,
184    /// -1.0 (hostile) to 1.0 (exalted).
185    pub value: f32,
186}
187
188impl PlayerReputation {
189    pub fn new(faction_id: impl Into<String>) -> Self {
190        Self { faction_id: faction_id.into(), value: 0.0 }
191    }
192
193    /// Adjust reputation by delta, clamped to [-1.0, 1.0].
194    pub fn adjust(&mut self, delta: f32) {
195        self.value = (self.value + delta).clamp(-1.0, 1.0);
196    }
197
198    /// Price factor: higher rep = cheaper prices.
199    /// Returns a value between 0.7 (exalted) and 1.4 (hostile).
200    pub fn price_factor(&self) -> f32 {
201        // value=1 → 0.7, value=0 → 1.0, value=-1 → 1.4
202        1.0 - self.value * 0.3
203    }
204
205    pub fn label(&self) -> &'static str {
206        match self.value {
207            v if v >= 0.8  => "Exalted",
208            v if v >= 0.5  => "Revered",
209            v if v >= 0.2  => "Honored",
210            v if v >= -0.2 => "Neutral",
211            v if v >= -0.5 => "Unfriendly",
212            v if v >= -0.8 => "Hostile",
213            _              => "Hated",
214        }
215    }
216}
217
218// ---------------------------------------------------------------------------
219// TaxSystem
220// ---------------------------------------------------------------------------
221
222/// Region-based sales tax and black market premium.
223#[derive(Debug, Clone)]
224pub struct TaxSystem {
225    /// Tax rate per region id (0.0–1.0).
226    region_taxes: HashMap<String, f32>,
227    /// Black market premium (multiplier on top of base).
228    pub black_market_premium: f32,
229}
230
231impl TaxSystem {
232    pub fn new() -> Self {
233        let mut t = Self {
234            region_taxes: HashMap::new(),
235            black_market_premium: 1.35,
236        };
237        // Default regions
238        t.region_taxes.insert("capital".into(), 0.08);
239        t.region_taxes.insert("frontier".into(), 0.03);
240        t.region_taxes.insert("merchant_district".into(), 0.12);
241        t.region_taxes.insert("black_market".into(), 0.00);
242        t
243    }
244
245    /// Set the tax rate for a region.
246    pub fn set_region_tax(&mut self, region_id: impl Into<String>, rate: f32) {
247        self.region_taxes.insert(region_id.into(), rate.clamp(0.0, 0.5));
248    }
249
250    /// Compute the final price after tax and black market premium for a region.
251    pub fn apply_tax(&self, base_price: u64, region_id: &str) -> u64 {
252        let tax_rate = self.region_taxes.get(region_id).copied().unwrap_or(0.05);
253        let taxed = base_price as f32 * (1.0 + tax_rate);
254        let black_market = if region_id == "black_market" {
255            taxed * self.black_market_premium
256        } else {
257            taxed
258        };
259        black_market.round() as u64
260    }
261
262    /// Tax amount (not total) for a transaction.
263    pub fn tax_amount(&self, base_price: u64, region_id: &str) -> u64 {
264        let final_price = self.apply_tax(base_price, region_id);
265        final_price.saturating_sub(base_price)
266    }
267}
268
269impl Default for TaxSystem {
270    fn default() -> Self {
271        Self::new()
272    }
273}
274
275// ---------------------------------------------------------------------------
276// ShopItem
277// ---------------------------------------------------------------------------
278
279/// A single item stocked in a shop.
280#[derive(Debug, Clone)]
281pub struct ShopItem {
282    pub item_id: String,
283    /// Current stock (-1 means unlimited).
284    pub stock: i32,
285    /// Maximum stock (0 means sell-only / no auto-restock).
286    pub max_stock: i32,
287    /// Buy price (what the player pays to buy from shop).
288    pub price: Currency,
289    /// How many units restock per real second.
290    pub restock_rate: f32,
291    /// Game time of last restock.
292    pub last_restock: f32,
293}
294
295impl ShopItem {
296    pub fn new(
297        item_id: impl Into<String>,
298        stock: i32,
299        max_stock: i32,
300        price: Currency,
301        restock_rate: f32,
302    ) -> Self {
303        Self {
304            item_id: item_id.into(),
305            stock,
306            max_stock,
307            price,
308            restock_rate,
309            last_restock: 0.0,
310        }
311    }
312
313    /// Price the shop pays the player to buy an item (typically 40% of sell price).
314    pub fn buy_back_price(&self) -> Currency {
315        self.price.scale(0.40)
316    }
317
318    pub fn is_in_stock(&self) -> bool {
319        self.stock == -1 || self.stock > 0
320    }
321
322    pub fn is_unlimited(&self) -> bool {
323        self.stock == -1
324    }
325}
326
327// ---------------------------------------------------------------------------
328// ShopInventory
329// ---------------------------------------------------------------------------
330
331/// A merchant's shop with items, buying, selling, and restocking.
332#[derive(Debug, Clone)]
333pub struct ShopInventory {
334    pub shop_id: String,
335    pub shop_name: String,
336    pub faction_id: String,
337    items: HashMap<String, ShopItem>,
338    /// Region the shop is in (for tax purposes).
339    pub region_id: String,
340    pub tax_system: TaxSystem,
341}
342
343impl ShopInventory {
344    pub fn new(
345        shop_id: impl Into<String>,
346        shop_name: impl Into<String>,
347        faction_id: impl Into<String>,
348        region_id: impl Into<String>,
349    ) -> Self {
350        Self {
351            shop_id: shop_id.into(),
352            shop_name: shop_name.into(),
353            faction_id: faction_id.into(),
354            items: HashMap::new(),
355            region_id: region_id.into(),
356            tax_system: TaxSystem::new(),
357        }
358    }
359
360    /// Add or replace an item in the shop.
361    pub fn add_item(&mut self, item: ShopItem) {
362        self.items.insert(item.item_id.clone(), item);
363    }
364
365    /// Get a shop item by id.
366    pub fn get_item(&self, item_id: &str) -> Option<&ShopItem> {
367        self.items.get(item_id)
368    }
369
370    /// All items in the shop.
371    pub fn all_items(&self) -> Vec<&ShopItem> {
372        self.items.values().collect()
373    }
374
375    /// Player buys `qty` units of `item_id` from the shop.
376    ///
377    /// `player_currency` is modified in place.
378    /// Returns Ok(total_cost) or Err with a description.
379    pub fn buy(
380        &mut self,
381        item_id: &str,
382        qty: u32,
383        player_currency: &mut Currency,
384        player_rep: f32,
385    ) -> Result<Currency, String> {
386        let item = self.items.get_mut(item_id)
387            .ok_or_else(|| format!("Item '{}' not found in shop", item_id))?;
388
389        if !item.is_unlimited() && (item.stock as u32) < qty {
390            return Err(format!("Not enough stock: have {}, need {}", item.stock, qty));
391        }
392
393        // Apply reputation discount
394        let rep_scale = 1.0 - player_rep.clamp(-1.0, 1.0) * 0.3;
395        let unit_price = item.price.scale(rep_scale);
396        let total_cost = unit_price.multiply(qty);
397
398        // Apply tax
399        let taxed_total_copper = self.tax_system.apply_tax(total_cost.to_copper_total(), &self.region_id);
400        let taxed_total = Currency::from_copper(taxed_total_copper);
401
402        if !player_currency.has_at_least(&taxed_total) {
403            return Err(format!("Insufficient funds: need {}, have {}", taxed_total, player_currency));
404        }
405
406        player_currency.try_subtract(&taxed_total);
407        if !item.is_unlimited() {
408            item.stock -= qty as i32;
409        }
410
411        Ok(taxed_total)
412    }
413
414    /// Player sells `qty` units of `item_id` to the shop.
415    ///
416    /// `player_currency` is credited the buy-back price.
417    /// Returns Ok(total_received) or Err with a description.
418    pub fn sell(
419        &mut self,
420        item_id: &str,
421        qty: u32,
422        player_currency: &mut Currency,
423    ) -> Result<Currency, String> {
424        let item = self.items.get_mut(item_id)
425            .ok_or_else(|| format!("Shop does not buy '{}'", item_id))?;
426
427        // Check if shop has room
428        if item.max_stock > 0 && item.stock >= item.max_stock {
429            return Err(format!("Shop is full for item '{}'", item_id));
430        }
431
432        let payout = item.buy_back_price().multiply(qty);
433        player_currency.add(&payout);
434
435        // Add stock to shop (limited by max_stock)
436        if item.max_stock > 0 {
437            item.stock = (item.stock + qty as i32).min(item.max_stock);
438        }
439
440        Ok(payout)
441    }
442
443    /// Advance time, restocking items that have restock_rate > 0.
444    pub fn restock(&mut self, dt: f32, current_time: f32) {
445        for item in self.items.values_mut() {
446            if item.restock_rate <= 0.0 || item.max_stock <= 0 {
447                continue;
448            }
449            if item.stock >= item.max_stock {
450                continue;
451            }
452            let time_since = current_time - item.last_restock;
453            let units_to_add = (time_since * item.restock_rate).floor() as i32;
454            if units_to_add >= 1 {
455                item.stock = (item.stock + units_to_add).min(item.max_stock);
456                item.last_restock = current_time;
457            }
458        }
459    }
460}
461
462// ---------------------------------------------------------------------------
463// TradeOffer
464// ---------------------------------------------------------------------------
465
466/// A barter proposal between two parties.
467#[derive(Debug, Clone)]
468pub struct TradeOffer {
469    pub id: u64,
470    /// Items offered by the proposer (item_id, quantity).
471    pub from_items: Vec<(String, u32)>,
472    /// Items requested in return (item_id, quantity).
473    pub to_items: Vec<(String, u32)>,
474    /// Gold component: positive means gold flows from proposer to receiver,
475    /// negative means gold flows from receiver to proposer.
476    pub gold_delta: i64,
477    /// Game time at which this offer expires.
478    pub expires_at: f32,
479    pub proposer_id: String,
480    pub receiver_id: String,
481    pub accepted: bool,
482    pub rejected: bool,
483}
484
485impl TradeOffer {
486    pub fn new(
487        id: u64,
488        proposer_id: impl Into<String>,
489        receiver_id: impl Into<String>,
490        from_items: Vec<(String, u32)>,
491        to_items: Vec<(String, u32)>,
492        gold_delta: i64,
493        expires_at: f32,
494    ) -> Self {
495        Self {
496            id,
497            from_items,
498            to_items,
499            gold_delta,
500            expires_at,
501            proposer_id: proposer_id.into(),
502            receiver_id: receiver_id.into(),
503            accepted: false,
504            rejected: false,
505        }
506    }
507
508    pub fn is_expired(&self, current_time: f32) -> bool {
509        current_time > self.expires_at
510    }
511
512    pub fn is_pending(&self) -> bool {
513        !self.accepted && !self.rejected
514    }
515}
516
517// ---------------------------------------------------------------------------
518// Moving average ring buffer for price smoothing
519// ---------------------------------------------------------------------------
520
521const PRICE_HISTORY_SIZE: usize = 20;
522
523/// Ring buffer storing the last N prices for moving average computation.
524#[derive(Debug, Clone)]
525struct PriceRingBuffer {
526    values: [u64; PRICE_HISTORY_SIZE],
527    head: usize,
528    count: usize,
529}
530
531impl PriceRingBuffer {
532    fn new() -> Self {
533        Self { values: [0u64; PRICE_HISTORY_SIZE], head: 0, count: 0 }
534    }
535
536    fn push(&mut self, price: u64) {
537        self.values[self.head] = price;
538        self.head = (self.head + 1) % PRICE_HISTORY_SIZE;
539        if self.count < PRICE_HISTORY_SIZE {
540            self.count += 1;
541        }
542    }
543
544    fn moving_average(&self) -> f32 {
545        if self.count == 0 {
546            return 0.0;
547        }
548        let sum: u64 = self.values[..self.count].iter().sum();
549        sum as f32 / self.count as f32
550    }
551
552    fn last(&self) -> Option<u64> {
553        if self.count == 0 {
554            return None;
555        }
556        let last_idx = if self.head == 0 { PRICE_HISTORY_SIZE - 1 } else { self.head - 1 };
557        Some(self.values[last_idx])
558    }
559}
560
561// ---------------------------------------------------------------------------
562// Economy — global supply/demand simulation
563// ---------------------------------------------------------------------------
564
565/// Per-item economic state.
566#[derive(Debug, Clone)]
567struct ItemEconomy {
568    /// Units sold recently (demand pressure).
569    recent_sales: f32,
570    /// Units purchased from producers recently (supply pressure).
571    recent_supply: f32,
572    /// Current equilibrium price in copper.
573    equilibrium_price: u64,
574    /// Base (floor) price in copper.
575    base_price: u64,
576    price_history: PriceRingBuffer,
577}
578
579impl ItemEconomy {
580    fn new(base_price: u64) -> Self {
581        Self {
582            recent_sales: 0.0,
583            recent_supply: 1.0,
584            equilibrium_price: base_price,
585            base_price,
586            price_history: PriceRingBuffer::new(),
587        }
588    }
589
590    /// Demand factor: sales / supply, clamped.
591    fn demand_factor(&self) -> f32 {
592        if self.recent_supply <= 0.0 {
593            return 3.0;
594        }
595        (self.recent_sales / self.recent_supply).clamp(0.25, 3.0)
596    }
597
598    /// Update equilibrium price based on current supply/demand.
599    fn update_price(&mut self) {
600        let factor = self.demand_factor();
601        // Smoothly adjust equilibrium price toward target
602        let target = (self.base_price as f32 * factor).round() as u64;
603        // Lerp form: eq = eq + t * (target - eq) with t = 0.1
604        let t = 0.10_f32;
605        let eq = self.equilibrium_price as f32;
606        let new_eq = eq + t * (target as f32 - eq);
607        self.equilibrium_price = new_eq.round().max(1.0) as u64;
608        self.price_history.push(self.equilibrium_price);
609    }
610
611    /// Decay sales/supply figures toward zero (time decay).
612    fn decay(&mut self, dt: f32) {
613        let decay_rate = 0.02_f32 * dt;
614        self.recent_sales = (self.recent_sales * (1.0 - decay_rate)).max(0.0);
615        self.recent_supply = (self.recent_supply * (1.0 - decay_rate * 0.5)).max(0.01);
616    }
617}
618
619/// The global economy — tracks supply, demand, and prices for all items.
620#[derive(Debug, Clone)]
621pub struct Economy {
622    items: HashMap<String, ItemEconomy>,
623    pub tax_system: TaxSystem,
624    /// Accumulated dt for periodic price updates (updates every ~30s).
625    update_accumulator: f32,
626    update_interval: f32,
627}
628
629impl Economy {
630    pub fn new() -> Self {
631        Self {
632            items: HashMap::new(),
633            tax_system: TaxSystem::new(),
634            update_accumulator: 0.0,
635            update_interval: 30.0,
636        }
637    }
638
639    /// Register an item with a base price (in copper).
640    pub fn register_item(&mut self, item_id: impl Into<String>, base_price_copper: u64) {
641        self.items.insert(item_id.into(), ItemEconomy::new(base_price_copper));
642    }
643
644    /// Get the current equilibrium price for an item (in copper).
645    pub fn current_price(&self, item_id: &str) -> Option<u64> {
646        self.items.get(item_id).map(|e| e.equilibrium_price)
647    }
648
649    /// Get the current price as Currency.
650    pub fn current_price_currency(&self, item_id: &str) -> Option<Currency> {
651        self.current_price(item_id).map(Currency::from_copper)
652    }
653
654    /// Get the moving average price for an item.
655    pub fn average_price(&self, item_id: &str) -> Option<f32> {
656        self.items.get(item_id).map(|e| e.price_history.moving_average())
657    }
658
659    /// Get the demand factor for an item.
660    pub fn demand_factor(&self, item_id: &str) -> f32 {
661        self.items.get(item_id).map(|e| e.demand_factor()).unwrap_or(1.0)
662    }
663
664    /// Record a sale event (player buys item), increasing demand pressure.
665    pub fn record_sale(&mut self, item_id: &str, qty: u32, _price_copper: u64) {
666        if let Some(item) = self.items.get_mut(item_id) {
667            item.recent_sales += qty as f32;
668        }
669    }
670
671    /// Record a supply event (item produced or imported), increasing supply pressure.
672    pub fn record_purchase(&mut self, item_id: &str, qty: u32) {
673        if let Some(item) = self.items.get_mut(item_id) {
674            item.recent_supply += qty as f32;
675        }
676    }
677
678    /// Update all item prices based on supply/demand, called each game tick.
679    pub fn update_prices(&mut self, dt: f32) {
680        // Decay all items every tick
681        for item in self.items.values_mut() {
682            item.decay(dt);
683        }
684
685        // Periodically recompute equilibrium prices
686        self.update_accumulator += dt;
687        if self.update_accumulator >= self.update_interval {
688            self.update_accumulator = 0.0;
689            for item in self.items.values_mut() {
690                item.update_price();
691            }
692        }
693    }
694
695    /// Build a PriceModifier for an item and player reputation factor.
696    pub fn price_modifier(&self, item_id: &str, player_rep_factor: f32) -> PriceModifier {
697        let item = self.items.get(item_id);
698        let base_price = item.map(|e| e.base_price).unwrap_or(100);
699        let demand_factor = item.map(|e| e.demand_factor()).unwrap_or(1.0);
700        let supply_factor = if let Some(e) = item {
701            if e.recent_sales > 0.0 {
702                (e.recent_supply / e.recent_sales).clamp(0.25, 2.0)
703            } else {
704                1.0
705            }
706        } else {
707            1.0
708        };
709        PriceModifier {
710            base_price,
711            demand_factor,
712            supply_factor,
713            player_rep_factor,
714        }
715    }
716
717    /// Populate the economy with default items and prices.
718    pub fn register_defaults(&mut self) {
719        // Smithing materials
720        self.register_item("iron_ingot", 200);
721        self.register_item("steel_ingot", 600);
722        self.register_item("coal", 30);
723        self.register_item("leather_strip", 50);
724        self.register_item("wooden_plank", 40);
725        // Equipment
726        self.register_item("iron_sword", 1500);
727        self.register_item("iron_shield", 1800);
728        self.register_item("steel_sword", 4500);
729        self.register_item("iron_helmet", 1200);
730        // Alchemy
731        self.register_item("red_herb", 60);
732        self.register_item("blue_herb", 80);
733        self.register_item("clean_water", 10);
734        self.register_item("spring_water", 25);
735        self.register_item("golden_root", 250);
736        self.register_item("health_potion_minor", 500);
737        self.register_item("health_potion_major", 1500);
738        self.register_item("mana_potion", 700);
739        // Cooking
740        self.register_item("raw_meat", 80);
741        self.register_item("salt", 20);
742        self.register_item("roasted_meat", 180);
743        self.register_item("hearty_stew", 400);
744        // Enchanting
745        self.register_item("magic_dust", 300);
746        self.register_item("fire_essence", 800);
747        self.register_item("light_rune", 600);
748        // Jeweling
749        self.register_item("silver_ingot", 500);
750        self.register_item("gold_chain", 2000);
751        self.register_item("ruby_gem", 3000);
752        self.register_item("sapphire_gem", 3500);
753        self.register_item("silver_ring", 1200);
754        self.register_item("ruby_necklace", 8000);
755    }
756}
757
758impl Default for Economy {
759    fn default() -> Self {
760        let mut e = Self::new();
761        e.register_defaults();
762        e
763    }
764}