Add buying and selling

This commit is contained in:
Timothy Warren 2022-01-13 11:29:20 -05:00
parent 6d4f87b184
commit 93a1c30b4a
8 changed files with 318 additions and 7 deletions

View File

@ -348,6 +348,11 @@ pub struct Chasing {
pub target: Entity,
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Vendor {
pub categories: Vec<String>,
}
// Serialization helper code. We need to implement ConvertSaveLoad for each type that contains an
// Entity.

View File

@ -4,12 +4,12 @@ use ::rltk::{Point, Rltk, VirtualKeyCode, RGB};
use ::specs::prelude::*;
use crate::components::{
Attribute, Attributes, Consumable, HungerClock, HungerState, InBackpack, Name, Pools, Position,
Viewshed,
Attribute, Attributes, Consumable, HungerClock, HungerState, InBackpack, Item, Name, Pools,
Position, Vendor, Viewshed,
};
use crate::game_log::GameLog;
use crate::rex_assets::RexAssets;
use crate::{camera, Equipped, Hidden, Map, RunState, State};
use crate::{camera, Equipped, Hidden, Map, RunState, State, VendorMode};
pub fn draw_hollow_box(
console: &mut Rltk,
@ -987,3 +987,211 @@ pub fn show_cheat_mode(_gs: &mut State, ctx: &mut Rltk) -> CheatMenuResult {
},
}
}
#[derive(PartialEq, Copy, Clone)]
pub enum VendorResult {
NoResponse,
Cancel,
Sell,
BuyMode,
SellMode,
Buy,
}
fn vendor_sell_menu(
gs: &mut State,
ctx: &mut Rltk,
_vendor: Entity,
_mode: VendorMode,
) -> (VendorResult, Option<Entity>, Option<String>, Option<f32>) {
let player_entity = gs.ecs.fetch::<Entity>();
let names = gs.ecs.read_storage::<Name>();
let backpack = gs.ecs.read_storage::<InBackpack>();
let items = gs.ecs.read_storage::<Item>();
let entities = gs.ecs.entities();
let inventory = (&backpack, &names)
.join()
.filter(|item| item.0.owner == *player_entity);
let count = inventory.count();
let mut y = (25 - (count / 2)) as i32;
ctx.draw_box(
15,
y - 2,
51,
(count + 3) as i32,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
);
ctx.print_color(
18,
y - 2,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
"Sell Which Item? (space to switch to buy mode)",
);
ctx.print_color(
18,
y + count as i32 + 1,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
"ESCAPE to cancel",
);
let mut equippable: Vec<Entity> = Vec::new();
for (j, (entity, _pack, name, item)) in (&entities, &backpack, &names, &items)
.join()
.filter(|item| item.1.owner == *player_entity)
.enumerate()
{
ctx.set(
17,
y,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
rltk::to_cp437('('),
);
ctx.set(
18,
y,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
97 + j as rltk::FontCharType,
);
ctx.set(
19,
y,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
rltk::to_cp437(')'),
);
ctx.print(21, y, &name.name.to_string());
ctx.print(50, y, &format!("{:.1} gp", item.base_value * 0.8));
equippable.push(entity);
y += 1;
}
match ctx.key {
None => (VendorResult::NoResponse, None, None, None),
Some(key) => match key {
VirtualKeyCode::Space => (VendorResult::BuyMode, None, None, None),
VirtualKeyCode::Escape => (VendorResult::Cancel, None, None, None),
_ => {
let selection = rltk::letter_to_option(key);
if selection > -1 && selection < count as i32 {
return (
VendorResult::Sell,
Some(equippable[selection as usize]),
None,
None,
);
}
(VendorResult::NoResponse, None, None, None)
}
},
}
}
fn vendor_buy_menu(
gs: &mut State,
ctx: &mut Rltk,
vendor: Entity,
_mode: VendorMode,
) -> (VendorResult, Option<Entity>, Option<String>, Option<f32>) {
use crate::raws::*;
let vendors = gs.ecs.read_storage::<Vendor>();
let inventory = get_vendor_items(
&vendors.get(vendor).unwrap().categories,
&RAWS.lock().unwrap(),
);
let count = inventory.len();
let mut y = (25 - (count / 2)) as i32;
ctx.draw_box(
15,
y - 2,
51,
(count + 3) as i32,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
);
ctx.print_color(
18,
y - 2,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
"Buy Which Item? (space to switch to sell mode)",
);
ctx.print_color(
18,
y + count as i32 + 1,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
"ESCAPE to cancel",
);
for (j, sale) in inventory.iter().enumerate() {
ctx.set(
17,
y,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
rltk::to_cp437('('),
);
ctx.set(
18,
y,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
97 + j as rltk::FontCharType,
);
ctx.set(
19,
y,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
rltk::to_cp437(')'),
);
ctx.print(21, y, &sale.0);
ctx.print(50, y, &format!("{:.1} gp", sale.1 * 1.2));
y += 1;
}
match ctx.key {
None => (VendorResult::NoResponse, None, None, None),
Some(key) => match key {
VirtualKeyCode::Space => (VendorResult::SellMode, None, None, None),
VirtualKeyCode::Escape => (VendorResult::Cancel, None, None, None),
_ => {
let selection = ::rltk::letter_to_option(key);
if selection > -1 && selection < count as i32 {
return (
VendorResult::Buy,
None,
Some(inventory[selection as usize].0.clone()),
Some(inventory[selection as usize].1),
);
}
(VendorResult::NoResponse, None, None, None)
}
},
}
}
pub fn show_vendor_menu(
gs: &mut State,
ctx: &mut Rltk,
vendor: Entity,
mode: VendorMode,
) -> (VendorResult, Option<Entity>, Option<String>, Option<f32>) {
match mode {
VendorMode::Buy => vendor_buy_menu(gs, ctx, vendor, mode),
VendorMode::Sell => vendor_sell_menu(gs, ctx, vendor, mode),
}
}

View File

@ -42,6 +42,7 @@ use map_indexing_system::MapIndexingSystem;
use melee_combat_system::MeleeCombatSystem;
use particle_system::ParticleSpawnSystem;
use player::*;
use raws::*;
pub use rect::Rect;
use trigger_system::TriggerSystem;
use visibility_system::VisibilitySystem;
@ -59,6 +60,12 @@ macro_rules! register {
const SHOW_MAPGEN_VISUALIZER: bool = false;
#[derive(PartialEq, Copy, Clone)]
pub enum VendorMode {
Buy,
Sell,
}
#[derive(PartialEq, Copy, Clone)]
pub enum RunState {
AwaitingInput,
@ -83,6 +90,10 @@ pub enum RunState {
},
MapGeneration,
ShowCheatMenu,
ShowVendor {
vendor: Entity,
mode: VendorMode,
},
}
pub struct State {
@ -392,6 +403,60 @@ impl GameState for State {
newrunstate = RunState::MapGeneration
}
},
RunState::ShowVendor { vendor, mode } => {
let result = gui::show_vendor_menu(self, ctx, vendor, mode);
match result.0 {
gui::VendorResult::Cancel => newrunstate = RunState::AwaitingInput,
gui::VendorResult::NoResponse => {}
gui::VendorResult::Sell => {
let price = self
.ecs
.read_storage::<Item>()
.get(result.1.unwrap())
.unwrap()
.base_value
* 0.8;
self.ecs
.write_storage::<Pools>()
.get_mut(*self.ecs.fetch::<Entity>())
.unwrap()
.gold += price;
self.ecs
.delete_entity(result.1.unwrap())
.expect("Unable to delete sold item");
}
gui::VendorResult::Buy => {
let tag = result.2.unwrap();
let price = result.3.unwrap();
let mut pools = self.ecs.write_storage::<Pools>();
let player_pools = pools.get_mut(*self.ecs.fetch::<Entity>()).unwrap();
if player_pools.gold >= price {
player_pools.gold -= price;
std::mem::drop(pools);
let player_entity = *self.ecs.fetch::<Entity>();
spawn_named_item(
&RAWS.lock().unwrap(),
&mut self.ecs,
&tag,
SpawnType::Carried { by: player_entity },
);
}
}
gui::VendorResult::BuyMode => {
newrunstate = RunState::ShowVendor {
vendor,
mode: VendorMode::Buy,
}
}
gui::VendorResult::SellMode => {
newrunstate = RunState::ShowVendor {
vendor,
mode: VendorMode::Sell,
}
}
}
}
}
{
@ -509,6 +574,7 @@ fn main() -> ::rltk::BError {
SingleActivation,
Skills,
SufferDamage,
Vendor,
Viewshed,
WantsToApproach,
WantsToDropItem,

View File

@ -5,11 +5,11 @@ use ::specs::prelude::*;
use crate::components::{
Attributes, BlocksTile, BlocksVisibility, Door, EntityMoved, Faction, HungerClock, HungerState,
Item, Player, Pools, Position, Renderable, Viewshed, WantsToMelee, WantsToPickupItem,
Item, Player, Pools, Position, Renderable, Vendor, Viewshed, WantsToMelee, WantsToPickupItem,
};
use crate::game_log::GameLog;
use crate::raws::{self, Reaction, RAWS};
use crate::{spatial, Map, RunState, State, TileType};
use crate::{spatial, Map, RunState, State, TileType, VendorMode};
pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState {
let mut positions = ecs.write_storage::<Position>();
@ -25,6 +25,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState
let mut blocks_movement = ecs.write_storage::<BlocksTile>();
let mut renderables = ecs.write_storage::<Renderable>();
let factions = ecs.read_storage::<Faction>();
let vendors = ecs.read_storage::<Vendor>();
let mut result = RunState::AwaitingInput;
let mut swap_entities: Vec<(Entity, i32, i32)> = Vec::new();
@ -43,6 +44,13 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState
result =
spatial::for_each_tile_content_with_gamemode(destination_idx, |potential_target| {
if vendors.get(potential_target).is_some() {
return Some(RunState::ShowVendor {
vendor: potential_target,
mode: VendorMode::Sell,
});
}
let mut hostile = true;
if combat_stats.get(potential_target).is_some() {
if let Some(faction) = factions.get(potential_target) {

View File

@ -12,6 +12,7 @@ pub struct Item {
pub initiative_penalty: Option<f32>,
pub weight_lbs: Option<f32>,
pub base_value: Option<f32>,
pub vendor_category: Option<String>,
}
#[derive(Deserialize, Debug)]

View File

@ -23,6 +23,7 @@ pub struct Mob {
pub light: Option<MobLight>,
pub faction: Option<String>,
pub gold: Option<String>,
pub vendor: Option<Vec<String>>,
}
#[derive(Deserialize, Debug)]

View File

@ -166,6 +166,20 @@ fn find_slot_for_equippable_item(tag: &str, raws: &RawMaster) -> EquipmentSlot {
panic!("Trying to equip {}, but it has not slot tag", tag);
}
pub fn get_vendor_items(categories: &[String], raws: &RawMaster) -> Vec<(String, f32)> {
let mut result = Vec::new();
for item in raws.raws.items.iter() {
if let Some(cat) = &item.vendor_category {
if categories.contains(cat) && item.base_value.is_some() {
result.push((item.name.clone(), item.base_value.unwrap()))
}
}
}
result
}
fn spawn_position<'a>(
pos: SpawnType,
new_entity: EntityBuilder<'a>,
@ -406,7 +420,7 @@ pub fn spawn_named_mob(
total_initiative_penalty: 0.,
gold: if let Some(gold) = &mob_template.gold {
let mut rng = RandomNumberGenerator::new();
let (n, d, b) = parse_dice_string(&gold);
let (n, d, b) = parse_dice_string(gold);
(rng.roll_dice(n, d) + b) as f32
} else {
0.0
@ -481,9 +495,15 @@ pub fn spawn_named_mob(
eb = eb.with(Faction::from("Mindless"));
}
if let Some(vendor) = &mob_template.vendor {
eb = eb.with(Vendor {
categories: vendor.clone(),
})
}
let new_mob = eb.build();
// Are they weilding anything?
// Are they wielding anything?
if let Some(wielding) = &mob_template.equipped {
for tag in wielding.iter() {
spawn_named_entity(raws, ecs, tag, SpawnType::Equipped { by: new_mob });

View File

@ -104,6 +104,7 @@ pub fn save_game(ecs: &mut World) {
SingleActivation,
Skills,
SufferDamage,
Vendor,
Viewshed,
WantsToApproach,
WantsToDropItem,
@ -212,6 +213,7 @@ pub fn load_game(ecs: &mut World) {
SingleActivation,
Skills,
SufferDamage,
Vendor,
Viewshed,
WantsToApproach,
WantsToDropItem,