mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Get SitePricing information to clients, and use it to display coin-denominated prices in voxygen on tooltips during a trade. Also boost merchant spawn rate slightly.
This commit is contained in:
parent
28952f6d7b
commit
8d90548331
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Pickaxes (can be used to collect gems and mine weak rock)
|
||||
- You can now jump out of rolls for a slight jump boost
|
||||
- Dungeons now have multiple kinds of stairs.
|
||||
- Trades now display item prices in tooltips.
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -35,7 +35,7 @@ use common::{
|
||||
recipe::RecipeBook,
|
||||
resources::PlayerEntity,
|
||||
terrain::{block::Block, neighbors, BiomeKind, SitesKind, TerrainChunk, TerrainChunkSize},
|
||||
trade::{PendingTrade, TradeAction, TradeId, TradeResult},
|
||||
trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
|
||||
uid::{Uid, UidAllocator},
|
||||
vol::RectVolSize,
|
||||
};
|
||||
@ -156,7 +156,7 @@ pub struct Client {
|
||||
// Pending invites that this client has sent out
|
||||
pending_invites: HashSet<Uid>,
|
||||
// The pending trade the client is involved in, and it's id
|
||||
pending_trade: Option<(TradeId, PendingTrade)>,
|
||||
pending_trade: Option<(TradeId, PendingTrade, Option<SitePrices>)>,
|
||||
|
||||
_network: Network,
|
||||
participant: Option<Participant>,
|
||||
@ -694,7 +694,7 @@ impl Client {
|
||||
}
|
||||
|
||||
pub fn perform_trade_action(&mut self, action: TradeAction) {
|
||||
if let Some((id, _)) = self.pending_trade {
|
||||
if let Some((id, _, _)) = self.pending_trade {
|
||||
if let TradeAction::Decline = action {
|
||||
self.pending_trade.take();
|
||||
}
|
||||
@ -833,7 +833,9 @@ impl Client {
|
||||
|
||||
pub fn pending_invites(&self) -> &HashSet<Uid> { &self.pending_invites }
|
||||
|
||||
pub fn pending_trade(&self) -> &Option<(TradeId, PendingTrade)> { &self.pending_trade }
|
||||
pub fn pending_trade(&self) -> &Option<(TradeId, PendingTrade, Option<SitePrices>)> {
|
||||
&self.pending_trade
|
||||
}
|
||||
|
||||
pub fn send_invite(&mut self, invitee: Uid, kind: InviteKind) {
|
||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InitiateInvite(
|
||||
@ -1637,12 +1639,12 @@ impl Client {
|
||||
impulse,
|
||||
});
|
||||
},
|
||||
ServerGeneral::UpdatePendingTrade(id, trade) => {
|
||||
ServerGeneral::UpdatePendingTrade(id, trade, pricing) => {
|
||||
tracing::trace!("UpdatePendingTrade {:?} {:?}", id, trade);
|
||||
self.pending_trade = Some((id, trade));
|
||||
self.pending_trade = Some((id, trade, pricing));
|
||||
},
|
||||
ServerGeneral::FinishedTrade(result) => {
|
||||
if let Some((_, trade)) = self.pending_trade.take() {
|
||||
if let Some((_, trade, _)) = self.pending_trade.take() {
|
||||
self.update_available_recipes();
|
||||
frontend_events.push(Event::TradeComplete { result, trade })
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use common::{
|
||||
recipe::RecipeBook,
|
||||
resources::TimeOfDay,
|
||||
terrain::{Block, TerrainChunk},
|
||||
trade::{PendingTrade, TradeId, TradeResult},
|
||||
trade::{PendingTrade, SitePrices, TradeId, TradeResult},
|
||||
uid::Uid,
|
||||
uuid::Uuid,
|
||||
};
|
||||
@ -126,7 +126,7 @@ pub enum ServerGeneral {
|
||||
Disconnect(DisconnectReason),
|
||||
/// Send a popup notification such as "Waypoint Saved"
|
||||
Notification(Notification),
|
||||
UpdatePendingTrade(TradeId, PendingTrade),
|
||||
UpdatePendingTrade(TradeId, PendingTrade, Option<SitePrices>),
|
||||
FinishedTrade(TradeResult),
|
||||
/// Economic information about sites
|
||||
SiteEconomy(EconomyInfo),
|
||||
@ -237,7 +237,7 @@ impl ServerMsg {
|
||||
| ServerGeneral::SetViewDistance(_)
|
||||
| ServerGeneral::Outcomes(_)
|
||||
| ServerGeneral::Knockback(_)
|
||||
| ServerGeneral::UpdatePendingTrade(_, _)
|
||||
| ServerGeneral::UpdatePendingTrade(_, _, _)
|
||||
| ServerGeneral::FinishedTrade(_)
|
||||
| ServerGeneral::SiteEconomy(_) => {
|
||||
c_type == ClientType::Game && presence.is_some()
|
||||
|
@ -320,6 +320,21 @@ impl Default for Good {
|
||||
}
|
||||
}
|
||||
|
||||
impl Good {
|
||||
/// The discounting factor applied when selling goods back to a merchant
|
||||
pub fn trade_margin(&self) -> f32 {
|
||||
match self {
|
||||
Good::Tools | Good::Armor => 0.5,
|
||||
Good::Food | Good::Potions | Good::Ingredients => 0.75,
|
||||
Good::Coin => 1.0,
|
||||
// Certain abstract goods (like Territory) shouldn't be attached to concrete items;
|
||||
// give a sale price of 0 if the player is trying to sell a concrete item that somehow
|
||||
// has one of these categories
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ideally this would be a real Id<Site> but that is from the world crate
|
||||
pub type SiteId = u64;
|
||||
|
||||
@ -329,7 +344,7 @@ pub struct SiteInformation {
|
||||
pub unconsumed_stock: HashMap<Good, f32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct SitePrices {
|
||||
pub values: HashMap<Good, f32>,
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ impl Client {
|
||||
| ServerGeneral::SiteEconomy(_)
|
||||
| ServerGeneral::Outcomes(_)
|
||||
| ServerGeneral::Knockback(_)
|
||||
| ServerGeneral::UpdatePendingTrade(_, _)
|
||||
| ServerGeneral::UpdatePendingTrade(_, _, _)
|
||||
| ServerGeneral::FinishedTrade(_) => {
|
||||
self.in_game_stream.lock().unwrap().send(g)
|
||||
},
|
||||
@ -162,7 +162,7 @@ impl Client {
|
||||
| ServerGeneral::Outcomes(_)
|
||||
| ServerGeneral::Knockback(_)
|
||||
| ServerGeneral::SiteEconomy(_)
|
||||
| ServerGeneral::UpdatePendingTrade(_, _)
|
||||
| ServerGeneral::UpdatePendingTrade(_, _, _)
|
||||
| ServerGeneral::FinishedTrade(_) => {
|
||||
PreparedMsg::new(2, &g, &self.in_game_stream)
|
||||
},
|
||||
|
@ -3,7 +3,7 @@ use crate::{client::Client, Server};
|
||||
use common::{
|
||||
comp::{
|
||||
self,
|
||||
agent::AgentEvent,
|
||||
agent::{Agent, AgentEvent},
|
||||
group::GroupManager,
|
||||
invite::{Invite, InviteKind, InviteResponse, PendingInvites},
|
||||
ChatType,
|
||||
@ -162,9 +162,11 @@ pub fn handle_invite_response(
|
||||
}
|
||||
|
||||
pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
|
||||
let index = server.index.clone();
|
||||
let state = server.state_mut();
|
||||
let clients = state.ecs().read_storage::<Client>();
|
||||
let uids = state.ecs().read_storage::<Uid>();
|
||||
let agents = state.ecs().read_storage::<Agent>();
|
||||
let mut invites = state.ecs().write_storage::<Invite>();
|
||||
if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| {
|
||||
let Invite { inviter, kind } = invite;
|
||||
@ -216,12 +218,20 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
|
||||
let mut trades = state.ecs().write_resource::<Trades>();
|
||||
let id = trades.begin_trade(inviter_uid, invitee_uid);
|
||||
let trade = trades.trades[&id].clone();
|
||||
clients
|
||||
let pricing = agents
|
||||
.get(inviter)
|
||||
.map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade.clone())));
|
||||
.and_then(|a| index.get_site_prices(a))
|
||||
.or_else(|| agents.get(entity).and_then(|a| index.get_site_prices(a)));
|
||||
clients.get(inviter).map(|c| {
|
||||
c.send(ServerGeneral::UpdatePendingTrade(
|
||||
id,
|
||||
trade.clone(),
|
||||
pricing.clone(),
|
||||
))
|
||||
});
|
||||
clients
|
||||
.get(entity)
|
||||
.map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade)));
|
||||
.map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade, pricing)));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -33,19 +33,18 @@ fn notify_agent_prices(
|
||||
event: AgentEvent,
|
||||
) {
|
||||
if let Some(agent) = agents.get_mut(entity) {
|
||||
let prices = index.get_site_prices(agent);
|
||||
if let AgentEvent::UpdatePendingTrade(boxval) = event {
|
||||
// Box<(tid, pend, _, inventories)>) = event {
|
||||
let prices = agent
|
||||
.trade_for_site
|
||||
.map(|i| index.sites.recreate_id(i))
|
||||
.flatten()
|
||||
.map(|i| index.sites.get(i))
|
||||
.map(|s| s.economy.get_site_prices())
|
||||
.unwrap_or_default();
|
||||
agent
|
||||
.inbox
|
||||
.push_front(AgentEvent::UpdatePendingTrade(Box::new((
|
||||
boxval.0, boxval.1, prices, boxval.3,
|
||||
// Prefer using this Agent's price data, but use the counterparty's price data
|
||||
// if we don't have price data
|
||||
boxval.0,
|
||||
boxval.1,
|
||||
prices.unwrap_or(boxval.2),
|
||||
boxval.3,
|
||||
))));
|
||||
}
|
||||
}
|
||||
@ -103,6 +102,8 @@ pub fn handle_process_trade_action(
|
||||
} else {
|
||||
let mut entities: [Option<specs::Entity>; 2] = [None, None];
|
||||
let mut inventories: [Option<ReducedInventory>; 2] = [None, None];
|
||||
let mut prices = None;
|
||||
let agents = server.state.ecs().read_storage::<Agent>();
|
||||
// sadly there is no map and collect on arrays
|
||||
for i in 0..2 {
|
||||
// parties.len()) {
|
||||
@ -114,13 +115,23 @@ pub fn handle_process_trade_action(
|
||||
.read_component::<Inventory>()
|
||||
.get(e)
|
||||
.map(|i| ReducedInventory::from(i));
|
||||
// Get price info from the first Agent in the trade (currently, an
|
||||
// Agent will never initiate a trade with another agent though)
|
||||
prices = prices.or_else(|| {
|
||||
agents.get(e).and_then(|a| server.index.get_site_prices(a))
|
||||
});
|
||||
}
|
||||
}
|
||||
drop(agents);
|
||||
for party in entities.iter() {
|
||||
if let Some(e) = *party {
|
||||
server.notify_client(
|
||||
e,
|
||||
ServerGeneral::UpdatePendingTrade(trade_id, entry.get().clone()),
|
||||
ServerGeneral::UpdatePendingTrade(
|
||||
trade_id,
|
||||
entry.get().clone(),
|
||||
prices.clone(),
|
||||
),
|
||||
);
|
||||
notify_agent_prices(
|
||||
server.state.ecs().write_storage::<Agent>(),
|
||||
@ -129,7 +140,7 @@ pub fn handle_process_trade_action(
|
||||
AgentEvent::UpdatePendingTrade(Box::new((
|
||||
trade_id,
|
||||
entry.get().clone(),
|
||||
Default::default(),
|
||||
prices.clone().unwrap_or_default(),
|
||||
inventories.clone(),
|
||||
))),
|
||||
);
|
||||
|
@ -22,7 +22,7 @@ use common::{
|
||||
rtsim::{Memory, MemoryItem, RtSimEntity, RtSimEvent},
|
||||
terrain::{Block, TerrainGrid},
|
||||
time::DayPeriod,
|
||||
trade::{Good, TradeAction, TradePhase, TradeResult},
|
||||
trade::{TradeAction, TradePhase, TradeResult},
|
||||
uid::{Uid, UidAllocator},
|
||||
util::Dir,
|
||||
vol::ReadVol,
|
||||
@ -947,14 +947,6 @@ impl<'a> AgentData<'a> {
|
||||
// This needs revisiting when agents can initiate trades (e.g. to offer
|
||||
// mercenary contracts as quests)
|
||||
const WHO: usize = 1;
|
||||
fn trade_margin(g: Good) -> f32 {
|
||||
match g {
|
||||
Good::Tools | Good::Armor => 0.5,
|
||||
Good::Food | Good::Potions | Good::Ingredients => 0.75,
|
||||
Good::Coin => 1.0,
|
||||
_ => 0.0, // what is this?
|
||||
}
|
||||
}
|
||||
let balance = |who: usize, reduce: bool| {
|
||||
pending.offers[who]
|
||||
.iter()
|
||||
@ -972,7 +964,7 @@ impl<'a> AgentData<'a> {
|
||||
.unwrap_or_default()
|
||||
* factor
|
||||
* (*amount as f32)
|
||||
* if reduce { trade_margin(material) } else { 1.0 }
|
||||
* if reduce { material.trade_margin() } else { 1.0 }
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
|
@ -64,6 +64,7 @@ pub struct InventoryScrollerState {
|
||||
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct InventoryScroller<'a> {
|
||||
client: &'a Client,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
fonts: &'a Fonts,
|
||||
@ -87,6 +88,7 @@ pub struct InventoryScroller<'a> {
|
||||
impl<'a> InventoryScroller<'a> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
client: &'a Client,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
fonts: &'a Fonts,
|
||||
@ -105,6 +107,7 @@ impl<'a> InventoryScroller<'a> {
|
||||
bg_ids: &'a BackgroundIds,
|
||||
) -> Self {
|
||||
InventoryScroller {
|
||||
client,
|
||||
imgs,
|
||||
item_imgs,
|
||||
fonts,
|
||||
@ -302,6 +305,11 @@ impl<'a> InventoryScroller<'a> {
|
||||
Quality::Artifact => self.imgs.inv_slot_orange,
|
||||
_ => self.imgs.inv_slot_red,
|
||||
};
|
||||
let mut desc = desc.to_string();
|
||||
if let Some((_, _, prices)) = self.client.pending_trade() {
|
||||
super::util::append_price_desc(&mut desc, prices, item.item_definition_id());
|
||||
}
|
||||
|
||||
slot_widget
|
||||
.filled_slot(quality_col_img)
|
||||
.with_tooltip(
|
||||
@ -571,6 +579,7 @@ impl<'a> Widget for Bag<'a> {
|
||||
.desc_text_color(TEXT_COLOR);
|
||||
|
||||
InventoryScroller::new(
|
||||
self.client,
|
||||
self.imgs,
|
||||
self.item_imgs,
|
||||
self.fonts,
|
||||
|
@ -20,7 +20,7 @@ use common::{
|
||||
inventory::item::{MaterialStatManifest, Quality},
|
||||
Inventory,
|
||||
},
|
||||
trade::{PendingTrade, TradeAction, TradePhase},
|
||||
trade::{PendingTrade, SitePrices, TradeAction, TradePhase},
|
||||
};
|
||||
use common_net::sync::WorldSyncExt;
|
||||
use conrod_core::{
|
||||
@ -156,6 +156,7 @@ impl<'a> Trade<'a> {
|
||||
state: &mut ConrodState<'_, State>,
|
||||
ui: &mut UiCell<'_>,
|
||||
trade: &'a PendingTrade,
|
||||
prices: &'a Option<SitePrices>,
|
||||
ours: bool,
|
||||
) -> <Self as Widget>::Event {
|
||||
let inventories = self.client.inventories();
|
||||
@ -233,7 +234,17 @@ impl<'a> Trade<'a> {
|
||||
.collect();
|
||||
|
||||
if matches!(trade.phase(), TradePhase::Mutate) {
|
||||
self.phase1_itemwidget(state, ui, inventory, who, ours, entity, name, &tradeslots);
|
||||
self.phase1_itemwidget(
|
||||
state,
|
||||
ui,
|
||||
inventory,
|
||||
who,
|
||||
ours,
|
||||
entity,
|
||||
name,
|
||||
prices,
|
||||
&tradeslots,
|
||||
);
|
||||
} else {
|
||||
self.phase2_itemwidget(state, ui, inventory, who, ours, entity, &tradeslots);
|
||||
}
|
||||
@ -250,6 +261,7 @@ impl<'a> Trade<'a> {
|
||||
ours: bool,
|
||||
entity: EcsEntity,
|
||||
name: String,
|
||||
prices: &'a Option<SitePrices>,
|
||||
tradeslots: &[TradeSlot],
|
||||
) {
|
||||
let item_tooltip = Tooltip::new({
|
||||
@ -272,6 +284,7 @@ impl<'a> Trade<'a> {
|
||||
|
||||
if !ours {
|
||||
InventoryScroller::new(
|
||||
self.client,
|
||||
self.imgs,
|
||||
self.item_imgs,
|
||||
self.fonts,
|
||||
@ -353,6 +366,8 @@ impl<'a> Trade<'a> {
|
||||
Quality::Artifact => self.imgs.inv_slot_orange,
|
||||
_ => self.imgs.inv_slot_red,
|
||||
};
|
||||
let mut desc = desc.to_string();
|
||||
super::util::append_price_desc(&mut desc, prices, item.item_definition_id());
|
||||
slot_widget
|
||||
.filled_slot(quality_col_img)
|
||||
.with_tooltip(
|
||||
@ -496,8 +511,8 @@ impl<'a> Widget for Trade<'a> {
|
||||
let widget::UpdateArgs { mut state, ui, .. } = args;
|
||||
|
||||
let mut event = None;
|
||||
let trade = match self.client.pending_trade() {
|
||||
Some((_, trade)) => trade,
|
||||
let (trade, prices) = match self.client.pending_trade() {
|
||||
Some((_, trade, prices)) => (trade, prices),
|
||||
None => return Some(TradeAction::Decline),
|
||||
};
|
||||
|
||||
@ -523,8 +538,12 @@ impl<'a> Widget for Trade<'a> {
|
||||
self.title(&mut state, ui);
|
||||
self.phase_indicator(&mut state, ui, &trade);
|
||||
|
||||
event = self.item_pane(&mut state, ui, &trade, false).or(event);
|
||||
event = self.item_pane(&mut state, ui, &trade, true).or(event);
|
||||
event = self
|
||||
.item_pane(&mut state, ui, &trade, &prices, false)
|
||||
.or(event);
|
||||
event = self
|
||||
.item_pane(&mut state, ui, &trade, &prices, true)
|
||||
.or(event);
|
||||
event = self
|
||||
.accept_decline_buttons(&mut state, ui, &trade)
|
||||
.or(event);
|
||||
|
@ -1,5 +1,6 @@
|
||||
use common::{
|
||||
comp::{
|
||||
inventory::trade_pricing::TradePricing,
|
||||
item::{
|
||||
armor::{Armor, ArmorKind, Protection},
|
||||
tool::{Hands, StatKind, Stats, Tool, ToolKind},
|
||||
@ -8,6 +9,7 @@ use common::{
|
||||
BuffKind,
|
||||
},
|
||||
effect::Effect,
|
||||
trade::{Good, SitePrices},
|
||||
};
|
||||
use std::{borrow::Cow, fmt::Write};
|
||||
|
||||
@ -64,6 +66,20 @@ pub fn item_text<'a>(
|
||||
(item.name(), desc)
|
||||
}
|
||||
|
||||
pub fn append_price_desc(desc: &mut String, prices: &Option<SitePrices>, item_definition_id: &str) {
|
||||
if let Some(prices) = prices {
|
||||
let (material, factor) = TradePricing::get_material(item_definition_id);
|
||||
let coinprice = prices.values.get(&Good::Coin).cloned().unwrap_or(1.0);
|
||||
let buyprice = prices.values.get(&material).cloned().unwrap_or_default() * factor;
|
||||
let sellprice = buyprice * material.trade_margin();
|
||||
*desc += &format!(
|
||||
"\n\nBuy price: {:0.1} coins\nSell price: {:0.1} coins",
|
||||
buyprice / coinprice,
|
||||
sellprice / coinprice
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: localization
|
||||
fn modular_component_desc(
|
||||
mc: &ModularComponent,
|
||||
|
@ -4,7 +4,9 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
assets::{AssetExt, AssetHandle},
|
||||
comp::Agent,
|
||||
store::Store,
|
||||
trade::SitePrices,
|
||||
};
|
||||
use core::ops::Deref;
|
||||
use noise::{Seedable, SuperSimplex};
|
||||
@ -69,6 +71,15 @@ impl Index {
|
||||
}
|
||||
|
||||
pub fn colors(&self) -> AssetHandle<Arc<Colors>> { self.colors }
|
||||
|
||||
pub fn get_site_prices(&self, agent: &Agent) -> Option<SitePrices> {
|
||||
agent
|
||||
.trade_for_site
|
||||
.map(|i| self.sites.recreate_id(i))
|
||||
.flatten()
|
||||
.map(|i| self.sites.get(i))
|
||||
.map(|s| s.economy.get_site_prices())
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexOwned {
|
||||
|
@ -944,7 +944,7 @@ impl Settlement {
|
||||
.do_if(!is_dummy, |e| e.with_automatic_name())
|
||||
.do_if(is_dummy, |e| e.with_name("Training Dummy"))
|
||||
.do_if(is_human && dynamic_rng.gen(), |entity| {
|
||||
match dynamic_rng.gen_range(0..5) {
|
||||
match dynamic_rng.gen_range(0..6) {
|
||||
0 => entity
|
||||
.with_main_tool(Item::new_from_asset_expect(
|
||||
"common.items.weapons.sword.iron-4",
|
||||
@ -955,7 +955,7 @@ impl Settlement {
|
||||
.with_skillset_config(
|
||||
common::skillset_builder::SkillSetConfig::Guard,
|
||||
),
|
||||
1 => entity
|
||||
1 | 2 => entity
|
||||
.with_main_tool(Item::new_from_asset_expect(
|
||||
"common.items.weapons.bow.eldwood-0",
|
||||
))
|
||||
|
Loading…
Reference in New Issue
Block a user