Traders come through the portal and sell rune plans, rare creatures, wood and mana
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
74837f1c34
commit
4c9fbe47e4
24 files changed
+406
-3
No files matched your search
+170
@@ -0,0 +1,170 @@
|
||||
// Traders: now and then a merchant comes through one of the master's portals and waits there
|
||||
// for a minute. A click with the hand buys what he offers - the plans of a rune forge tower, a
|
||||
// rare creature, a bundle of wood or a potion that fills the mana. Single player only (in a
|
||||
// match the masters would have to agree on who buys).
|
||||
|
||||
use crate::game::*;
|
||||
use crate::map::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// seconds until the first trader, between two traders, and how long one waits
|
||||
pub const TRADER_FIRST: f32 = 420.0;
|
||||
pub const TRADER_GAP: f32 = 540.0;
|
||||
pub const TRADER_WAIT: f32 = 60.0;
|
||||
|
||||
// what a trader may offer: (language key, price)
|
||||
pub const OFFERS: [(&str, f32); 5] = [("trader.rune", 450.0), ("trader.wyrm", 1200.0), ("trader.chainbreaker", 900.0), ("trader.wood", 250.0), ("trader.mana", 200.0)];
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct Trading {
|
||||
pub t: f32, // seconds until the next trader comes
|
||||
pub here: Option<(u32, u8, f32)>, // the trader on the map: unit id, offer, seconds left
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub(crate) fn update_traders(&mut self, dt: f32) {
|
||||
if self.netplay.is_some() || self.demo.is_some() || self.state != 0 {
|
||||
return;
|
||||
}
|
||||
if self.trading.t == 0.0 && self.trading.here.is_none() {
|
||||
self.trading.t = TRADER_FIRST;
|
||||
}
|
||||
if let Some((id, offer, left)) = self.trading.here {
|
||||
let left = left - dt;
|
||||
if left <= 0.0 || self.find(id).is_none() {
|
||||
self.trader_leaves(true);
|
||||
} else {
|
||||
self.trading.here = Some((id, offer, left));
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.trading.t -= dt;
|
||||
if self.trading.t > 0.0 {
|
||||
return;
|
||||
}
|
||||
self.trading.t = TRADER_GAP + self.rng.f() * 180.0;
|
||||
// he comes through an own portal
|
||||
let portals: Vec<(i32, i32)> = (0..self.map.portals.len()).filter(|&k| self.map.portal_owned(k)).map(|k| self.map.portals[k]).collect();
|
||||
let Some(&(px, py)) = portals.get((self.rng.next() as usize) % portals.len().max(1)) else { return };
|
||||
let spot = DIR8.iter().map(|&(dx, dy)| (px + 1 + dx * 2, py + 1 + dy * 2)).find(|&(x, y)| self.map.walk(x, y));
|
||||
let Some((x, y)) = spot else { return };
|
||||
let offer = (self.rng.next() % OFFERS.len() as u32) as u8;
|
||||
let id = self.spawn(EK::Trader, false, x as f32 + 0.5, y as f32 + 0.5, 1);
|
||||
if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) {
|
||||
e.side = self.me_side;
|
||||
e.home = Some((x as f32 + 0.5, y as f32 + 0.5));
|
||||
e.settled = true;
|
||||
e.happy = 1.0;
|
||||
}
|
||||
self.trading.here = Some((id, offer, TRADER_WAIT));
|
||||
let (key, price) = OFFERS[offer as usize];
|
||||
self.msg("msg.trader_arrives", vec![format!("@{}", key), (price as i32).to_string()]);
|
||||
self.sfx_ui(Sfx::Deposit);
|
||||
}
|
||||
|
||||
// the trader goes back through the portal
|
||||
fn trader_leaves(&mut self, unsold: bool) {
|
||||
if let Some((id, _, _)) = self.trading.here.take() {
|
||||
if let Some(j) = self.find(id) {
|
||||
let (x, y) = (self.ents[j].x, self.ents[j].y);
|
||||
self.ents[j].alive = false;
|
||||
self.fx(7, x, y, 0.0, 0.0, 0.8, 0xffff60c0);
|
||||
}
|
||||
if unsold {
|
||||
self.msg("msg.trader_left", vec![]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the hand on the trader: buy his offer (true: it was the trader)
|
||||
pub(crate) fn buy_from_trader(&mut self, j: usize) -> bool {
|
||||
let Some((id, offer, _)) = self.trading.here else { return false };
|
||||
if self.ents[j].id != id {
|
||||
return false;
|
||||
}
|
||||
let (key, price) = OFFERS[offer as usize];
|
||||
if self.gold < price {
|
||||
self.msg("msg.no_gold", vec![]);
|
||||
self.sfx_ui(Sfx::Error);
|
||||
return true;
|
||||
}
|
||||
self.gold -= price;
|
||||
let (x, y) = (self.ents[j].x, self.ents[j].y);
|
||||
match offer {
|
||||
0 => self.rune_free += 1,
|
||||
1 | 2 => {
|
||||
let (kind, level) = if offer == 1 { (EK::Wyrm, 3) } else { (EK::Chainbreaker, 4) };
|
||||
let nid = self.spawn(kind, false, x, y, level);
|
||||
if let Some(e) = self.ents.iter_mut().find(|e| e.id == nid) {
|
||||
e.side = self.me_side;
|
||||
e.settled = false;
|
||||
e.job = Job::Register;
|
||||
e.timer = 1.0;
|
||||
e.happy = 1.0;
|
||||
}
|
||||
}
|
||||
3 => self.wood += 30.0,
|
||||
_ => self.mana = self.mana_max(),
|
||||
}
|
||||
self.msg("msg.trader_sold", vec![format!("@{}", key)]);
|
||||
self.sfx_ui(Sfx::Payday);
|
||||
self.trader_leaves(false);
|
||||
true
|
||||
}
|
||||
|
||||
// a trader just stands by the portal and waits
|
||||
pub(crate) fn trader_ai(&mut self, e: &mut Ent, dt: f32) {
|
||||
e.hunger = 0.0;
|
||||
e.energy = 1.0;
|
||||
e.happy = 1.0;
|
||||
if let Some((hx, hy)) = e.home {
|
||||
if (e.x - hx).abs() + (e.y - hy).abs() > 0.3 && e.path.is_empty() {
|
||||
e.x = hx;
|
||||
e.y = hy;
|
||||
}
|
||||
}
|
||||
let _ = dt;
|
||||
}
|
||||
|
||||
// what the trader offers (hover information): (offer key, price, seconds left)
|
||||
pub fn trader_offer(&self, id: u32) -> Option<(&'static str, f32, f32)> {
|
||||
self.trading.here.filter(|h| h.0 == id).map(|(_, o, left)| (OFFERS[o as usize].0, OFFERS[o as usize].1, left))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_trader_comes_and_sells() {
|
||||
let mut g = Game::new(7, 64, 64);
|
||||
// own the portal
|
||||
let (px, py) = g.map.portals[0];
|
||||
for y in py - 2..py + 5 {
|
||||
for x in px - 2..px + 5 {
|
||||
if inb(x, y) && g.map.t[idx(x, y)].kind != Kind::Portal {
|
||||
g.map.t[idx(x, y)].kind = Kind::Floor;
|
||||
}
|
||||
if inb(x, y) {
|
||||
g.map.t[idx(x, y)].owner = Owner::Player;
|
||||
}
|
||||
}
|
||||
}
|
||||
g.trading.t = 0.01;
|
||||
g.update_traders(0.05);
|
||||
let (id, offer, _) = g.trading.here.expect("a trader came");
|
||||
let j = g.find(id).unwrap();
|
||||
assert!(!g.ents[j].creature(), "not one of the household");
|
||||
g.gold = 5000.0;
|
||||
let (gold, runes, wood) = (g.gold, g.rune_free, g.wood);
|
||||
assert!(g.buy_from_trader(j));
|
||||
assert!(g.gold < gold);
|
||||
match offer {
|
||||
0 => assert_eq!(g.rune_free, runes + 1),
|
||||
3 => assert!(g.wood > wood),
|
||||
_ => {}
|
||||
}
|
||||
assert!(g.trading.here.is_none() && !g.ents[j].alive, "he leaves");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user