Add buying and selling
This commit is contained in:
parent
6d4f87b184
commit
93a1c30b4a
@ -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.
|
||||
|
||||
|
214
src/gui.rs
214
src/gui.rs
@ -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),
|
||||
}
|
||||
}
|
||||
|
66
src/main.rs
66
src/main.rs
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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)]
|
||||
|
@ -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)]
|
||||
|
@ -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 });
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user