mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Allow fast stacking into and out of a trade with {ctrl,shift} click.
Shift click goes 1 at a time, Ctrl click automatically balances the trade w.r.t. that quantity.
This commit is contained in:
parent
aafdc209d3
commit
0122dca3c3
@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Trades now display item prices in tooltips.
|
- Trades now display item prices in tooltips.
|
||||||
- Admin designated build areas
|
- Admin designated build areas
|
||||||
- Indicator text to collectable terrain sprites
|
- Indicator text to collectable terrain sprites
|
||||||
|
- You can now autorequest exact change by ctrl-clicking in a trade, and can quick-add individual items with shift-click.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comp::inventory::{slot::InvSlotId, Inventory},
|
comp::inventory::{slot::InvSlotId, trade_pricing::TradePricing, Inventory},
|
||||||
terrain::BiomeKind,
|
terrain::BiomeKind,
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
};
|
};
|
||||||
@ -349,6 +349,35 @@ pub struct SitePrices {
|
|||||||
pub values: HashMap<Good, f32>,
|
pub values: HashMap<Good, f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SitePrices {
|
||||||
|
pub fn balance(
|
||||||
|
&self,
|
||||||
|
offers: &[HashMap<InvSlotId, u32>; 2],
|
||||||
|
inventories: &[Option<ReducedInventory>; 2],
|
||||||
|
who: usize,
|
||||||
|
reduce: bool,
|
||||||
|
) -> f32 {
|
||||||
|
offers[who]
|
||||||
|
.iter()
|
||||||
|
.map(|(slot, amount)| {
|
||||||
|
inventories[who]
|
||||||
|
.as_ref()
|
||||||
|
.map(|ri| {
|
||||||
|
ri.inventory.get(slot).map(|item| {
|
||||||
|
let (material, factor) = TradePricing::get_material(&item.name);
|
||||||
|
self.values.get(&material).cloned().unwrap_or_default()
|
||||||
|
* factor
|
||||||
|
* (*amount as f32)
|
||||||
|
* if reduce { material.trade_margin() } else { 1.0 }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct ReducedInventoryItem {
|
pub struct ReducedInventoryItem {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -7,7 +7,7 @@ use common::{
|
|||||||
compass::{Direction, Distance},
|
compass::{Direction, Distance},
|
||||||
dialogue::{MoodContext, MoodState, Subject},
|
dialogue::{MoodContext, MoodState, Subject},
|
||||||
group,
|
group,
|
||||||
inventory::{item::ItemTag, slot::EquipSlot, trade_pricing::TradePricing},
|
inventory::{item::ItemTag, slot::EquipSlot},
|
||||||
invite::{InviteKind, InviteResponse},
|
invite::{InviteKind, InviteResponse},
|
||||||
item::{
|
item::{
|
||||||
tool::{ToolKind, UniqueKind},
|
tool::{ToolKind, UniqueKind},
|
||||||
@ -1150,33 +1150,9 @@ impl<'a> AgentData<'a> {
|
|||||||
let (tradeid, pending, prices, inventories) = *boxval;
|
let (tradeid, pending, prices, inventories) = *boxval;
|
||||||
if agent.trading {
|
if agent.trading {
|
||||||
let who: usize = if agent.trading_issuer { 0 } else { 1 };
|
let who: usize = if agent.trading_issuer { 0 } else { 1 };
|
||||||
let balance = |who: usize, reduce: bool| {
|
let balance0: f32 =
|
||||||
pending.offers[who]
|
prices.balance(&pending.offers, &inventories, 1 - who, true);
|
||||||
.iter()
|
let balance1: f32 = prices.balance(&pending.offers, &inventories, who, false);
|
||||||
.map(|(slot, amount)| {
|
|
||||||
inventories[who]
|
|
||||||
.as_ref()
|
|
||||||
.map(|ri| {
|
|
||||||
ri.inventory.get(slot).map(|item| {
|
|
||||||
let (material, factor) =
|
|
||||||
TradePricing::get_material(&item.name);
|
|
||||||
prices
|
|
||||||
.values
|
|
||||||
.get(&material)
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default()
|
|
||||||
* factor
|
|
||||||
* (*amount as f32)
|
|
||||||
* if reduce { material.trade_margin() } else { 1.0 }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.unwrap_or_default()
|
|
||||||
})
|
|
||||||
.sum()
|
|
||||||
};
|
|
||||||
let balance0: f32 = balance(1 - who, true);
|
|
||||||
let balance1: f32 = balance(who, false);
|
|
||||||
tracing::debug!("UpdatePendingTrade({}, {})", balance0, balance1);
|
tracing::debug!("UpdatePendingTrade({}, {})", balance0, balance1);
|
||||||
if balance0 >= balance1 {
|
if balance0 >= balance1 {
|
||||||
// If the trade is favourable to us, only send an accept message if we're
|
// If the trade is favourable to us, only send an accept message if we're
|
||||||
|
@ -30,6 +30,7 @@ use conrod_core::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::hud::slots::SlotKind;
|
use crate::hud::slots::SlotKind;
|
||||||
|
use specs::Entity as EcsEntity;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vek::Vec2;
|
use vek::Vec2;
|
||||||
|
|
||||||
@ -78,6 +79,7 @@ pub struct InventoryScroller<'a> {
|
|||||||
on_right: bool,
|
on_right: bool,
|
||||||
item_tooltip: &'a ItemTooltip<'a>,
|
item_tooltip: &'a ItemTooltip<'a>,
|
||||||
playername: String,
|
playername: String,
|
||||||
|
entity: EcsEntity,
|
||||||
is_us: bool,
|
is_us: bool,
|
||||||
inventory: &'a Inventory,
|
inventory: &'a Inventory,
|
||||||
bg_ids: &'a BackgroundIds,
|
bg_ids: &'a BackgroundIds,
|
||||||
@ -99,6 +101,7 @@ impl<'a> InventoryScroller<'a> {
|
|||||||
on_right: bool,
|
on_right: bool,
|
||||||
item_tooltip: &'a ItemTooltip<'a>,
|
item_tooltip: &'a ItemTooltip<'a>,
|
||||||
playername: String,
|
playername: String,
|
||||||
|
entity: EcsEntity,
|
||||||
is_us: bool,
|
is_us: bool,
|
||||||
inventory: &'a Inventory,
|
inventory: &'a Inventory,
|
||||||
bg_ids: &'a BackgroundIds,
|
bg_ids: &'a BackgroundIds,
|
||||||
@ -118,6 +121,7 @@ impl<'a> InventoryScroller<'a> {
|
|||||||
on_right,
|
on_right,
|
||||||
item_tooltip,
|
item_tooltip,
|
||||||
playername,
|
playername,
|
||||||
|
entity,
|
||||||
is_us,
|
is_us,
|
||||||
inventory,
|
inventory,
|
||||||
bg_ids,
|
bg_ids,
|
||||||
@ -274,6 +278,7 @@ impl<'a> InventoryScroller<'a> {
|
|||||||
InventorySlot {
|
InventorySlot {
|
||||||
slot: pos,
|
slot: pos,
|
||||||
ours: self.is_us,
|
ours: self.is_us,
|
||||||
|
entity: self.entity,
|
||||||
},
|
},
|
||||||
[40.0; 2],
|
[40.0; 2],
|
||||||
)
|
)
|
||||||
@ -616,6 +621,7 @@ impl<'a> Widget for Bag<'a> {
|
|||||||
true,
|
true,
|
||||||
&item_tooltip,
|
&item_tooltip,
|
||||||
self.stats.name.to_string(),
|
self.stats.name.to_string(),
|
||||||
|
self.client.entity(),
|
||||||
true,
|
true,
|
||||||
&inventory,
|
&inventory,
|
||||||
&state.bg_ids,
|
&state.bg_ids,
|
||||||
|
@ -66,19 +66,23 @@ use common::{
|
|||||||
combat,
|
combat,
|
||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
|
inventory::trade_pricing::TradePricing,
|
||||||
item::{tool::ToolKind, ItemDesc, MaterialStatManifest, Quality},
|
item::{tool::ToolKind, ItemDesc, MaterialStatManifest, Quality},
|
||||||
skills::{Skill, SkillGroupKind},
|
skills::{Skill, SkillGroupKind},
|
||||||
BuffKind, Item,
|
BuffKind, Item,
|
||||||
},
|
},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
terrain::TerrainChunk,
|
terrain::TerrainChunk,
|
||||||
trade::TradeAction,
|
trade::{ReducedInventory, TradeAction},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
util::srgba_to_linear,
|
util::srgba_to_linear,
|
||||||
vol::RectRasterableVol,
|
vol::RectRasterableVol,
|
||||||
};
|
};
|
||||||
use common_base::span;
|
use common_base::span;
|
||||||
use common_net::msg::{world_msg::SiteId, Notification, PresenceKind};
|
use common_net::{
|
||||||
|
msg::{world_msg::SiteId, Notification, PresenceKind},
|
||||||
|
sync::WorldSyncExt,
|
||||||
|
};
|
||||||
use conrod_core::{
|
use conrod_core::{
|
||||||
text::cursor::Index,
|
text::cursor::Index,
|
||||||
widget::{self, Button, Image, Text},
|
widget::{self, Button, Image, Text},
|
||||||
@ -2901,11 +2905,13 @@ impl Hud {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Maintain slot manager
|
// Maintain slot manager
|
||||||
for event in self.slot_manager.maintain(ui_widgets) {
|
'slot_events: for event in self.slot_manager.maintain(ui_widgets) {
|
||||||
use comp::slot::Slot;
|
use comp::slot::Slot;
|
||||||
use slots::{InventorySlot, SlotKind::*};
|
use slots::{InventorySlot, SlotKind::*};
|
||||||
let to_slot = |slot_kind| match slot_kind {
|
let to_slot = |slot_kind| match slot_kind {
|
||||||
Inventory(InventorySlot { slot, ours: true }) => Some(Slot::Inventory(slot)),
|
Inventory(InventorySlot {
|
||||||
|
slot, ours: true, ..
|
||||||
|
}) => Some(Slot::Inventory(slot)),
|
||||||
Inventory(InventorySlot { ours: false, .. }) => None,
|
Inventory(InventorySlot { ours: false, .. }) => None,
|
||||||
Equip(e) => Some(Slot::Equip(e)),
|
Equip(e) => Some(Slot::Equip(e)),
|
||||||
Hotbar(_) => None,
|
Hotbar(_) => None,
|
||||||
@ -2920,8 +2926,12 @@ impl Hud {
|
|||||||
slot_b: b,
|
slot_b: b,
|
||||||
bypass_dialog: false,
|
bypass_dialog: false,
|
||||||
});
|
});
|
||||||
} else if let (Inventory(InventorySlot { slot, ours: true }), Hotbar(h)) =
|
} else if let (
|
||||||
(a, b)
|
Inventory(InventorySlot {
|
||||||
|
slot, ours: true, ..
|
||||||
|
}),
|
||||||
|
Hotbar(h),
|
||||||
|
) = (a, b)
|
||||||
{
|
{
|
||||||
self.hotbar.add_inventory_link(h, slot);
|
self.hotbar.add_inventory_link(h, slot);
|
||||||
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
|
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
|
||||||
@ -3042,6 +3052,127 @@ impl Hud {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
slot::Event::Request {
|
||||||
|
slot,
|
||||||
|
auto_quantity,
|
||||||
|
} => {
|
||||||
|
if let Some((_, trade, prices)) = client.pending_trade() {
|
||||||
|
let ecs = client.state().ecs();
|
||||||
|
let inventories = ecs.read_component::<common::comp::Inventory>();
|
||||||
|
let get_inventory = |uid: Uid| {
|
||||||
|
if let Some(entity) = ecs.entity_from_uid(uid.0) {
|
||||||
|
inventories.get(entity)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut r_inventories = [None, None];
|
||||||
|
for (i, party) in trade.parties.iter().enumerate() {
|
||||||
|
match get_inventory(*party) {
|
||||||
|
Some(inventory) => {
|
||||||
|
r_inventories[i] = Some(ReducedInventory::from(inventory))
|
||||||
|
},
|
||||||
|
None => continue 'slot_events,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let who = match ecs
|
||||||
|
.uid_from_entity(client.entity())
|
||||||
|
.and_then(|uid| trade.which_party(uid))
|
||||||
|
{
|
||||||
|
Some(who) => who,
|
||||||
|
None => continue 'slot_events,
|
||||||
|
};
|
||||||
|
let do_auto_quantity =
|
||||||
|
|inventory: &common::comp::Inventory,
|
||||||
|
slot,
|
||||||
|
ours,
|
||||||
|
remove,
|
||||||
|
quantity: &mut u32| {
|
||||||
|
if let Some(prices) = prices {
|
||||||
|
let balance0 =
|
||||||
|
prices.balance(&trade.offers, &r_inventories, who, true);
|
||||||
|
let balance1 = prices.balance(
|
||||||
|
&trade.offers,
|
||||||
|
&r_inventories,
|
||||||
|
1 - who,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
if let Some(item) = inventory.get(slot) {
|
||||||
|
let (material, factor) =
|
||||||
|
TradePricing::get_material(item.item_definition_id());
|
||||||
|
let mut unit_price = prices
|
||||||
|
.values
|
||||||
|
.get(&material)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default()
|
||||||
|
* factor;
|
||||||
|
if ours {
|
||||||
|
unit_price *= material.trade_margin();
|
||||||
|
}
|
||||||
|
let mut float_delta = if ours ^ remove {
|
||||||
|
(balance1 - balance0) / unit_price
|
||||||
|
} else {
|
||||||
|
(balance0 - balance1) / unit_price
|
||||||
|
};
|
||||||
|
if ours ^ remove {
|
||||||
|
float_delta = float_delta.ceil();
|
||||||
|
} else {
|
||||||
|
float_delta = float_delta.floor();
|
||||||
|
}
|
||||||
|
*quantity = float_delta.max(0.0) as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match slot {
|
||||||
|
Inventory(i) => {
|
||||||
|
if let Some(inventory) = inventories.get(i.entity) {
|
||||||
|
let mut quantity = 1;
|
||||||
|
if auto_quantity {
|
||||||
|
do_auto_quantity(
|
||||||
|
inventory,
|
||||||
|
i.slot,
|
||||||
|
i.ours,
|
||||||
|
false,
|
||||||
|
&mut quantity,
|
||||||
|
);
|
||||||
|
let inv_quantity = i.amount(inventory).unwrap_or(1);
|
||||||
|
quantity = quantity.min(inv_quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
events.push(Event::TradeAction(TradeAction::AddItem {
|
||||||
|
item: i.slot,
|
||||||
|
quantity,
|
||||||
|
ours: i.ours,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Trade(t) => {
|
||||||
|
if let Some(inventory) = inventories.get(t.entity) {
|
||||||
|
if let Some(invslot) = t.invslot {
|
||||||
|
let mut quantity = 1;
|
||||||
|
if auto_quantity {
|
||||||
|
do_auto_quantity(
|
||||||
|
inventory,
|
||||||
|
invslot,
|
||||||
|
t.ours,
|
||||||
|
true,
|
||||||
|
&mut quantity,
|
||||||
|
);
|
||||||
|
let inv_quantity = t.amount(inventory).unwrap_or(1);
|
||||||
|
quantity = quantity.min(inv_quantity);
|
||||||
|
}
|
||||||
|
events.push(Event::TradeAction(TradeAction::RemoveItem {
|
||||||
|
item: invslot,
|
||||||
|
quantity,
|
||||||
|
ours: t.ours,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.hotbar.maintain_ability3(client);
|
self.hotbar.maintain_ability3(client);
|
||||||
@ -3096,6 +3227,7 @@ impl Hud {
|
|||||||
if let Some(slots::SlotKind::Inventory(InventorySlot {
|
if let Some(slots::SlotKind::Inventory(InventorySlot {
|
||||||
slot: i,
|
slot: i,
|
||||||
ours: true,
|
ours: true,
|
||||||
|
..
|
||||||
})) = slot_manager.selected()
|
})) = slot_manager.selected()
|
||||||
{
|
{
|
||||||
hotbar.add_inventory_link(slot, i);
|
hotbar.add_inventory_link(slot, i);
|
||||||
|
@ -31,6 +31,7 @@ pub type SlotManager = slot::SlotManager<SlotKind>;
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct InventorySlot {
|
pub struct InventorySlot {
|
||||||
pub slot: InvSlotId,
|
pub slot: InvSlotId,
|
||||||
|
pub entity: EcsEntity,
|
||||||
pub ours: bool,
|
pub ours: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ impl SlotKey<Inventory, ItemImgs> for TradeSlot {
|
|||||||
InventorySlot {
|
InventorySlot {
|
||||||
slot: inv_id,
|
slot: inv_id,
|
||||||
ours: self.ours,
|
ours: self.ours,
|
||||||
|
entity: self.entity,
|
||||||
}
|
}
|
||||||
.image_key(source)
|
.image_key(source)
|
||||||
})
|
})
|
||||||
@ -96,6 +98,7 @@ impl SlotKey<Inventory, ItemImgs> for TradeSlot {
|
|||||||
InventorySlot {
|
InventorySlot {
|
||||||
slot: inv_id,
|
slot: inv_id,
|
||||||
ours: self.ours,
|
ours: self.ours,
|
||||||
|
entity: self.entity,
|
||||||
}
|
}
|
||||||
.amount(source)
|
.amount(source)
|
||||||
})
|
})
|
||||||
|
@ -305,6 +305,7 @@ impl<'a> Trade<'a> {
|
|||||||
false,
|
false,
|
||||||
&item_tooltip,
|
&item_tooltip,
|
||||||
name,
|
name,
|
||||||
|
entity,
|
||||||
false,
|
false,
|
||||||
&inventory,
|
&inventory,
|
||||||
&state.bg_ids,
|
&state.bg_ids,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
use crate::hud::animate_by_pulse;
|
use crate::hud::animate_by_pulse;
|
||||||
use conrod_core::{
|
use conrod_core::{
|
||||||
builder_methods, image,
|
builder_methods, image,
|
||||||
input::state::mouse,
|
input::{keyboard::ModifierKey, state::mouse},
|
||||||
text::font,
|
text::font,
|
||||||
widget::{self, Image, Text},
|
widget::{self, Image, Text},
|
||||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||||
@ -122,6 +122,8 @@ pub enum Event<K> {
|
|||||||
SplitDragged(K, K),
|
SplitDragged(K, K),
|
||||||
// Clicked while selected
|
// Clicked while selected
|
||||||
Used(K),
|
Used(K),
|
||||||
|
// {Shift,Ctrl}-clicked
|
||||||
|
Request { slot: K, auto_quantity: bool },
|
||||||
}
|
}
|
||||||
// Handles interactions with slots
|
// Handles interactions with slots
|
||||||
pub struct SlotManager<S: SumSlot> {
|
pub struct SlotManager<S: SumSlot> {
|
||||||
@ -369,6 +371,30 @@ where
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translate ctrl-clicks to stack-requests and shift-clicks to
|
||||||
|
// individual-requests
|
||||||
|
if let Some(click) = input.clicks().left().next() {
|
||||||
|
if !matches!(self.state, ManagerState::Dragging(_, _, _, _)) {
|
||||||
|
match click.modifiers {
|
||||||
|
ModifierKey::CTRL => {
|
||||||
|
self.events.push(Event::Request {
|
||||||
|
slot,
|
||||||
|
auto_quantity: true,
|
||||||
|
});
|
||||||
|
self.state = ManagerState::Idle;
|
||||||
|
},
|
||||||
|
ModifierKey::SHIFT => {
|
||||||
|
self.events.push(Event::Request {
|
||||||
|
slot,
|
||||||
|
auto_quantity: false,
|
||||||
|
});
|
||||||
|
self.state = ManagerState::Idle;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use on right click if not dragging
|
// Use on right click if not dragging
|
||||||
if input.clicks().right().next().is_some() {
|
if input.clicks().right().next().is_some() {
|
||||||
match self.state {
|
match self.state {
|
||||||
|
Loading…
Reference in New Issue
Block a user