mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'trade-pets-food' into 'master'
Allow pets to be traded with. See merge request veloren/veloren!3633
This commit is contained in:
commit
95edd377f1
@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
extra ghost slider cursor when set above the limit (instead of snapping back to the limit).
|
||||
Limits on the view distance by the server no longer affect the settings saved on the client.
|
||||
- HQX upscaling shader for people playing on low internal resolutions
|
||||
- Pets can now be traded with.
|
||||
|
||||
### Changed
|
||||
- Use fluent for translations
|
||||
@ -54,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Moderators and admins are no longer blocked from logging in when there are too many players.
|
||||
- FXAA now behaves correctly at non-1.0x internal resolutions
|
||||
- Pets no longer aggro on pet owners after being healed
|
||||
- Pets no longer lose their intrinsic weapons/armour when loaded on login.
|
||||
|
||||
## [0.13.0] - 2022-07-23
|
||||
|
||||
|
@ -86,6 +86,7 @@ bitflags::bitflags! {
|
||||
#[derive(Default)]
|
||||
pub struct BehaviorCapability: u8 {
|
||||
const SPEAK = 0b00000001;
|
||||
const TRADE = 0b00000010;
|
||||
}
|
||||
}
|
||||
bitflags::bitflags! {
|
||||
@ -96,6 +97,26 @@ bitflags::bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug)]
|
||||
pub enum TradingBehavior {
|
||||
#[default]
|
||||
None,
|
||||
RequireBalanced {
|
||||
trade_site: SiteId,
|
||||
},
|
||||
AcceptFood,
|
||||
}
|
||||
|
||||
impl TradingBehavior {
|
||||
fn can_trade(&self, alignment: Option<Alignment>, counterparty: Uid) -> bool {
|
||||
match self {
|
||||
TradingBehavior::RequireBalanced { .. } => true,
|
||||
TradingBehavior::AcceptFood => alignment == Some(Alignment::Owned(counterparty)),
|
||||
TradingBehavior::None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Behavior Component
|
||||
/// This component allow an Entity to register one or more behavior tags.
|
||||
/// These tags act as flags of what an Entity can do, or what it is doing.
|
||||
@ -105,7 +126,7 @@ bitflags::bitflags! {
|
||||
pub struct Behavior {
|
||||
capabilities: BehaviorCapability,
|
||||
state: BehaviorState,
|
||||
pub trade_site: Option<SiteId>,
|
||||
pub trading_behavior: TradingBehavior,
|
||||
}
|
||||
|
||||
impl From<BehaviorCapability> for Behavior {
|
||||
@ -113,7 +134,7 @@ impl From<BehaviorCapability> for Behavior {
|
||||
Behavior {
|
||||
capabilities,
|
||||
state: BehaviorState::default(),
|
||||
trade_site: None,
|
||||
trading_behavior: TradingBehavior::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,7 +157,9 @@ impl Behavior {
|
||||
/// Set trade_site if Option is Some
|
||||
#[must_use]
|
||||
pub fn with_trade_site(mut self, trade_site: Option<SiteId>) -> Self {
|
||||
self.trade_site = trade_site;
|
||||
if let Some(trade_site) = trade_site {
|
||||
self.trading_behavior = TradingBehavior::RequireBalanced { trade_site };
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
@ -156,7 +179,9 @@ impl Behavior {
|
||||
}
|
||||
|
||||
/// Check if the Behavior is able to trade
|
||||
pub fn can_trade(&self) -> bool { self.trade_site.is_some() }
|
||||
pub fn can_trade(&self, alignment: Option<Alignment>, counterparty: Uid) -> bool {
|
||||
self.trading_behavior.can_trade(alignment, counterparty)
|
||||
}
|
||||
|
||||
/// Set a state to the Behavior
|
||||
pub fn set(&mut self, state: BehaviorState) { self.state.set(state, true) }
|
||||
@ -166,6 +191,15 @@ impl Behavior {
|
||||
|
||||
/// Check if the Behavior has a specific state
|
||||
pub fn is(&self, state: BehaviorState) -> bool { self.state.contains(state) }
|
||||
|
||||
/// Get the trade site at which this behavior evaluates prices, if it does
|
||||
pub fn trade_site(&self) -> Option<SiteId> {
|
||||
if let TradingBehavior::RequireBalanced { trade_site } = self.trading_behavior {
|
||||
Some(trade_site)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
@ -55,7 +55,10 @@ pub use self::{
|
||||
MAX_ABILITIES,
|
||||
},
|
||||
admin::{Admin, AdminRole},
|
||||
agent::{Agent, Alignment, Behavior, BehaviorCapability, BehaviorState, PidController},
|
||||
agent::{
|
||||
Agent, Alignment, Behavior, BehaviorCapability, BehaviorState, PidController,
|
||||
TradingBehavior,
|
||||
},
|
||||
anchor::Anchor,
|
||||
aura::{Aura, AuraChange, AuraKind, Auras},
|
||||
beam::{Beam, BeamSegment},
|
||||
|
@ -7,9 +7,9 @@ use common::{
|
||||
aura::{Aura, AuraKind, AuraTarget},
|
||||
beam,
|
||||
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
|
||||
shockwave, Agent, Alignment, Anchor, Body, Health, Inventory, ItemDrop, LightEmitter,
|
||||
Object, Ori, PidController, Poise, Pos, Projectile, Scale, SkillSet, Stats, Vel,
|
||||
WaypointArea,
|
||||
shockwave, Agent, Alignment, Anchor, BehaviorCapability, Body, Health, Inventory, ItemDrop,
|
||||
LightEmitter, Object, Ori, PidController, Poise, Pos, Projectile, Scale, SkillSet, Stats,
|
||||
TradingBehavior, Vel, WaypointArea,
|
||||
},
|
||||
event::{EventBus, UpdateCharacterMetadata},
|
||||
lottery::LootSpec,
|
||||
@ -98,10 +98,19 @@ pub fn handle_create_npc(
|
||||
let entity = server
|
||||
.state
|
||||
.create_npc(pos, stats, skill_set, health, poise, inventory, body)
|
||||
.with(scale)
|
||||
.with(alignment);
|
||||
.with(scale);
|
||||
|
||||
let entity = if let Some(agent) = agent.into() {
|
||||
let mut agent = agent.into();
|
||||
if let Some(agent) = &mut agent {
|
||||
if let Alignment::Owned(_) = &alignment {
|
||||
agent.behavior.allow(BehaviorCapability::TRADE);
|
||||
agent.behavior.trading_behavior = TradingBehavior::AcceptFood;
|
||||
}
|
||||
}
|
||||
|
||||
let entity = entity.with(alignment);
|
||||
|
||||
let entity = if let Some(agent) = agent {
|
||||
entity.with(agent)
|
||||
} else {
|
||||
entity
|
||||
|
@ -246,13 +246,13 @@ pub fn handle_invite_accept(server: &mut Server, entity: Entity) {
|
||||
.get(inviter)
|
||||
.and_then(|a| {
|
||||
a.behavior
|
||||
.trade_site
|
||||
.trade_site()
|
||||
.and_then(|id| index.get_site_prices(id))
|
||||
})
|
||||
.or_else(|| {
|
||||
agents.get(entity).and_then(|a| {
|
||||
a.behavior
|
||||
.trade_site
|
||||
.trade_site()
|
||||
.and_then(|id| index.get_site_prices(id))
|
||||
})
|
||||
});
|
||||
|
@ -35,20 +35,18 @@ fn notify_agent_prices(
|
||||
entity: EcsEntity,
|
||||
event: AgentEvent,
|
||||
) {
|
||||
if let Some((Some(site_id), agent)) = agents.get_mut(entity).map(|a| (a.behavior.trade_site, a))
|
||||
{
|
||||
let prices = index.get_site_prices(site_id);
|
||||
if let Some((site_id, agent)) = agents.get_mut(entity).map(|a| (a.behavior.trade_site(), a)) {
|
||||
if let AgentEvent::UpdatePendingTrade(boxval) = event {
|
||||
// Prefer using this Agent's price data, but use the counterparty's price
|
||||
// data if we don't have price data
|
||||
let prices = site_id
|
||||
.and_then(|site_id| index.get_site_prices(site_id))
|
||||
.unwrap_or(boxval.2);
|
||||
// Box<(tid, pend, _, inventories)>) = event {
|
||||
agent
|
||||
.inbox
|
||||
.push_back(AgentEvent::UpdatePendingTrade(Box::new((
|
||||
// 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,
|
||||
boxval.0, boxval.1, prices, boxval.3,
|
||||
))));
|
||||
}
|
||||
}
|
||||
@ -127,7 +125,7 @@ pub(super) fn handle_process_trade_action(
|
||||
prices = prices.or_else(|| {
|
||||
agents
|
||||
.get(e)
|
||||
.and_then(|a| a.behavior.trade_site)
|
||||
.and_then(|a| a.behavior.trade_site())
|
||||
.and_then(|id| server.index.get_site_prices(id))
|
||||
});
|
||||
}
|
||||
|
@ -349,7 +349,7 @@ pub fn load_character_list(player_uuid_: &str, connection: &Connection) -> Chara
|
||||
Ok(CharacterItem {
|
||||
character: char,
|
||||
body: char_body,
|
||||
inventory: Inventory::with_loadout_humanoid(loadout),
|
||||
inventory: Inventory::with_loadout(loadout, char_body),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
|
@ -1,6 +1,9 @@
|
||||
use crate::{client::Client, events::update_map_markers};
|
||||
use common::{
|
||||
comp::{self, anchor::Anchor, group::GroupManager, Agent, Alignment, Pet},
|
||||
comp::{
|
||||
self, anchor::Anchor, group::GroupManager, Agent, Alignment, Behavior, BehaviorCapability,
|
||||
Pet, TradingBehavior,
|
||||
},
|
||||
uid::Uid,
|
||||
};
|
||||
use common_net::msg::ServerGeneral;
|
||||
@ -51,9 +54,11 @@ fn tame_pet_internal(ecs: &specs::World, pet_entity: Entity, owner: Entity, pet:
|
||||
|
||||
// Create an agent for this entity using its body
|
||||
if let Some(body) = ecs.read_storage().get(pet_entity) {
|
||||
let _ = ecs
|
||||
.write_storage()
|
||||
.insert(pet_entity, Agent::from_body(body));
|
||||
let mut agent = Agent::from_body(body).with_behavior(
|
||||
Behavior::default().maybe_with_capabilities(Some(BehaviorCapability::TRADE)),
|
||||
);
|
||||
agent.behavior.trading_behavior = TradingBehavior::AcceptFood;
|
||||
let _ = ecs.write_storage().insert(pet_entity, agent);
|
||||
}
|
||||
|
||||
// Add to group system
|
||||
|
@ -26,7 +26,7 @@ use common::{
|
||||
resources::{Time, TimeOfDay},
|
||||
slowjob::SlowJobPool,
|
||||
uid::{Uid, UidAllocator},
|
||||
ViewDistances,
|
||||
LoadoutBuilder, ViewDistances,
|
||||
};
|
||||
use common_net::{
|
||||
msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral},
|
||||
@ -651,7 +651,10 @@ impl StateExt for State {
|
||||
comp::SkillSet::default(),
|
||||
Some(comp::Health::new(body, DEFAULT_PET_HEALTH_LEVEL)),
|
||||
Poise::new(body),
|
||||
Inventory::with_empty(),
|
||||
Inventory::with_loadout(
|
||||
LoadoutBuilder::from_default(&body).build(),
|
||||
body,
|
||||
),
|
||||
body,
|
||||
)
|
||||
.with(comp::Scale(1.0))
|
||||
|
@ -108,17 +108,21 @@ impl BehaviorTree {
|
||||
/// events.
|
||||
pub fn interaction(agent: &Agent) -> Self {
|
||||
let is_in_combat = agent.target.map_or(false, |t| t.hostile);
|
||||
if !is_in_combat && agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||
Self {
|
||||
tree: vec![
|
||||
increment_timer_deltatime,
|
||||
handle_inbox_talk,
|
||||
handle_inbox_trade_invite,
|
||||
handle_inbox_trade_accepted,
|
||||
handle_inbox_finished_trade,
|
||||
handle_inbox_update_pending_trade,
|
||||
],
|
||||
if !is_in_combat
|
||||
&& (agent.behavior.can(BehaviorCapability::SPEAK)
|
||||
|| agent.behavior.can(BehaviorCapability::TRADE))
|
||||
{
|
||||
let mut tree: Vec<BehaviorFn> = vec![increment_timer_deltatime];
|
||||
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||
tree.push(handle_inbox_talk);
|
||||
}
|
||||
tree.extend_from_slice(&[
|
||||
handle_inbox_trade_invite,
|
||||
handle_inbox_trade_accepted,
|
||||
handle_inbox_finished_trade,
|
||||
handle_inbox_update_pending_trade,
|
||||
]);
|
||||
Self { tree }
|
||||
} else {
|
||||
Self {
|
||||
tree: vec![handle_inbox_cancel_interactions],
|
||||
|
@ -3,8 +3,10 @@ use common::{
|
||||
agent::{AgentEvent, Target, TimerAction},
|
||||
compass::{Direction, Distance},
|
||||
dialogue::{MoodContext, MoodState, Subject},
|
||||
inventory::item::{ItemTag, MaterialStatManifest},
|
||||
invite::{InviteKind, InviteResponse},
|
||||
BehaviorState, ControlAction, UnresolvedChatMsg, UtteranceKind,
|
||||
tool::AbilityMap,
|
||||
BehaviorState, ControlAction, Item, TradingBehavior, UnresolvedChatMsg, UtteranceKind,
|
||||
},
|
||||
event::ServerEvent,
|
||||
rtsim::{Memory, MemoryItem, RtSimEvent},
|
||||
@ -167,7 +169,10 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
standard_response_msg()
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
} else if agent.behavior.can_trade() {
|
||||
} else if agent
|
||||
.behavior
|
||||
.can_trade(agent_data.alignment.copied(), by)
|
||||
{
|
||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||
controller.push_initiate_invite(by, InviteKind::Trade);
|
||||
agent_data.chat_npc(
|
||||
@ -250,21 +255,29 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
}
|
||||
},
|
||||
Subject::Trade => {
|
||||
if agent.behavior.can_trade() {
|
||||
if agent.behavior.can_trade(agent_data.alignment.copied(), by) {
|
||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||
controller.push_initiate_invite(by, InviteKind::Trade);
|
||||
agent_data.chat_npc(
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_advertisement",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
} else {
|
||||
agent_data.chat_npc("npc-speech-merchant_busy", event_emitter);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_busy",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// TODO: maybe make some travellers willing to trade with
|
||||
// simpler goods like potions
|
||||
agent_data
|
||||
.chat_npc("npc-speech-villager_decline_trade", event_emitter);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-villager_decline_trade",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
}
|
||||
},
|
||||
Subject::Mood => {
|
||||
@ -387,7 +400,10 @@ pub fn handle_inbox_trade_invite(bdata: &mut BehaviorData) -> bool {
|
||||
}
|
||||
|
||||
if let Some(AgentEvent::TradeInvite(with)) = agent.inbox.pop_front() {
|
||||
if agent.behavior.can_trade() {
|
||||
if agent
|
||||
.behavior
|
||||
.can_trade(agent_data.alignment.copied(), with)
|
||||
{
|
||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||
// stand still and looking towards the trading player
|
||||
controller.push_action(ControlAction::Stand);
|
||||
@ -458,10 +474,18 @@ pub fn handle_inbox_finished_trade(bdata: &mut BehaviorData) -> bool {
|
||||
if agent.behavior.is(BehaviorState::TRADING) {
|
||||
match result {
|
||||
TradeResult::Completed => {
|
||||
agent_data.chat_npc("npc-speech-merchant_trade_successful", event_emitter);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_trade_successful",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
},
|
||||
_ => {
|
||||
agent_data.chat_npc("npc-speech-merchant_trade_declined", event_emitter);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_trade_declined",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
},
|
||||
}
|
||||
agent.behavior.unset(BehaviorState::TRADING);
|
||||
@ -489,58 +513,100 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
|
||||
let (tradeid, pending, prices, inventories) = *boxval;
|
||||
if agent.behavior.is(BehaviorState::TRADING) {
|
||||
let who = usize::from(!agent.behavior.is(BehaviorState::TRADING_ISSUER));
|
||||
let balance0: f32 = prices.balance(&pending.offers, &inventories, 1 - who, true);
|
||||
let balance1: f32 = prices.balance(&pending.offers, &inventories, who, false);
|
||||
if balance0 >= balance1 {
|
||||
// If the trade is favourable to us, only send an accept message if we're
|
||||
// not already accepting (since otherwise, spam-clicking the accept button
|
||||
// results in lagging and moving to the review phase of an unfavorable trade
|
||||
// (although since the phase is included in the message, this shouldn't
|
||||
// result in fully accepting an unfavourable trade))
|
||||
if !pending.accept_flags[who] && !pending.is_empty_trade() {
|
||||
event_emitter.emit(ServerEvent::ProcessTradeAction(
|
||||
*agent_data.entity,
|
||||
tradeid,
|
||||
TradeAction::Accept(pending.phase),
|
||||
));
|
||||
tracing::trace!(?tradeid, ?balance0, ?balance1, "Accept Pending Trade");
|
||||
}
|
||||
} else {
|
||||
if balance1 > 0.0 {
|
||||
let msg = format!(
|
||||
"That only covers {:.0}% of my costs!",
|
||||
(balance0 / balance1 * 100.0).floor()
|
||||
);
|
||||
if let Some(tgt_data) = &agent.target {
|
||||
// If talking with someone in particular, "tell" it only to them
|
||||
if let Some(with) = read_data.uids.get(tgt_data.target) {
|
||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_tell(
|
||||
*agent_data.uid,
|
||||
*with,
|
||||
msg,
|
||||
)));
|
||||
} else {
|
||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||
*agent_data.uid,
|
||||
msg,
|
||||
)));
|
||||
match agent.behavior.trading_behavior {
|
||||
TradingBehavior::RequireBalanced { .. } => {
|
||||
let balance0: f32 =
|
||||
prices.balance(&pending.offers, &inventories, 1 - who, true);
|
||||
let balance1: f32 = prices.balance(&pending.offers, &inventories, who, false);
|
||||
if balance0 >= balance1 {
|
||||
// If the trade is favourable to us, only send an accept message if we're
|
||||
// not already accepting (since otherwise, spam-clicking the accept button
|
||||
// results in lagging and moving to the review phase of an unfavorable trade
|
||||
// (although since the phase is included in the message, this shouldn't
|
||||
// result in fully accepting an unfavourable trade))
|
||||
if !pending.accept_flags[who] && !pending.is_empty_trade() {
|
||||
event_emitter.emit(ServerEvent::ProcessTradeAction(
|
||||
*agent_data.entity,
|
||||
tradeid,
|
||||
TradeAction::Accept(pending.phase),
|
||||
));
|
||||
tracing::trace!(?tradeid, ?balance0, ?balance1, "Accept Pending Trade");
|
||||
}
|
||||
} else {
|
||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||
*agent_data.uid,
|
||||
msg,
|
||||
)));
|
||||
if balance1 > 0.0 {
|
||||
let msg = format!(
|
||||
"That only covers {:.0}% of my costs!",
|
||||
(balance0 / balance1 * 100.0).floor()
|
||||
);
|
||||
if let Some(tgt_data) = &agent.target {
|
||||
// If talking with someone in particular, "tell" it only to them
|
||||
if let Some(with) = read_data.uids.get(tgt_data.target) {
|
||||
event_emitter.emit(ServerEvent::Chat(
|
||||
UnresolvedChatMsg::npc_tell(*agent_data.uid, *with, msg),
|
||||
));
|
||||
} else {
|
||||
event_emitter.emit(ServerEvent::Chat(
|
||||
UnresolvedChatMsg::npc_say(*agent_data.uid, msg),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||
*agent_data.uid,
|
||||
msg,
|
||||
)));
|
||||
}
|
||||
}
|
||||
if pending.phase != TradePhase::Mutate {
|
||||
// we got into the review phase but without balanced goods, decline
|
||||
agent.behavior.unset(BehaviorState::TRADING);
|
||||
event_emitter.emit(ServerEvent::ProcessTradeAction(
|
||||
*agent_data.entity,
|
||||
tradeid,
|
||||
TradeAction::Decline,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if pending.phase != TradePhase::Mutate {
|
||||
// we got into the review phase but without balanced goods, decline
|
||||
},
|
||||
TradingBehavior::AcceptFood => {
|
||||
let mut only_food = true;
|
||||
let ability_map = AbilityMap::load().read();
|
||||
let msm = MaterialStatManifest::load().read();
|
||||
if let Some(ri) = &inventories[1 - who] {
|
||||
for (slot, _) in pending.offers[1 - who].iter() {
|
||||
if let Some(item) = ri.inventory.get(slot) {
|
||||
if let Ok(item) = Item::new_from_item_definition_id(
|
||||
item.name.as_ref(),
|
||||
&ability_map,
|
||||
&msm,
|
||||
) {
|
||||
if !item.tags().contains(&ItemTag::Food) {
|
||||
only_food = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !pending.accept_flags[who]
|
||||
&& pending.offers[who].is_empty()
|
||||
&& !pending.offers[1 - who].is_empty()
|
||||
&& only_food
|
||||
{
|
||||
event_emitter.emit(ServerEvent::ProcessTradeAction(
|
||||
*agent_data.entity,
|
||||
tradeid,
|
||||
TradeAction::Accept(pending.phase),
|
||||
));
|
||||
}
|
||||
},
|
||||
TradingBehavior::None => {
|
||||
agent.behavior.unset(BehaviorState::TRADING);
|
||||
event_emitter.emit(ServerEvent::ProcessTradeAction(
|
||||
*agent_data.entity,
|
||||
tradeid,
|
||||
TradeAction::Decline,
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -587,7 +653,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
||||
{
|
||||
// in combat, speak to players that aren't the current target
|
||||
if !target.hostile || target.target != speaker {
|
||||
if agent.behavior.can_trade() {
|
||||
if agent.behavior.can_trade(agent_data.alignment.copied(), *by) {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_busy",
|
||||
agent,
|
||||
@ -610,12 +676,18 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
||||
if agent.behavior.is(BehaviorState::TRADING) {
|
||||
match result {
|
||||
TradeResult::Completed => {
|
||||
agent_data
|
||||
.chat_npc("npc-speech-merchant_trade_successful", event_emitter);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_trade_successful",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
},
|
||||
_ => {
|
||||
agent_data
|
||||
.chat_npc("npc-speech-merchant_trade_declined", event_emitter);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_trade_declined",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
},
|
||||
}
|
||||
agent.behavior.unset(BehaviorState::TRADING);
|
||||
@ -631,7 +703,11 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
||||
*tradeid,
|
||||
TradeAction::Decline,
|
||||
));
|
||||
agent_data.chat_npc("npc-speech-merchant_trade_cancelled_hostile", event_emitter);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_trade_cancelled_hostile",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
true
|
||||
},
|
||||
AgentEvent::ServerSound(_) | AgentEvent::Hurt => false,
|
||||
|
@ -517,6 +517,7 @@ impl NpcData {
|
||||
.with_behavior(
|
||||
Behavior::default()
|
||||
.maybe_with_capabilities(can_speak.then_some(BehaviorCapability::SPEAK))
|
||||
.maybe_with_capabilities(trade_for_site.map(|_| BehaviorCapability::TRADE))
|
||||
.with_trade_site(trade_for_site),
|
||||
)
|
||||
.with_patrol_origin(pos)
|
||||
|
@ -2103,12 +2103,18 @@ impl Hud {
|
||||
},
|
||||
Some(comp::Alignment::Owned(owner))
|
||||
if Some(*owner) == client.uid()
|
||||
&& !client.is_riding()
|
||||
&& is_mount.is_none()
|
||||
&& is_mountable(body, bodies.get(client.entity()))
|
||||
&& dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2) =>
|
||||
{
|
||||
vec![(GameInput::Mount, i18n.get_msg("hud-mount").to_string())]
|
||||
let mut options =
|
||||
vec![(GameInput::Trade, i18n.get_msg("hud-trade").to_string())];
|
||||
if !client.is_riding()
|
||||
&& is_mount.is_none()
|
||||
&& is_mountable(body, bodies.get(client.entity()))
|
||||
{
|
||||
options
|
||||
.push((GameInput::Mount, i18n.get_msg("hud-mount").to_string()))
|
||||
}
|
||||
options
|
||||
},
|
||||
_ => Vec::new(),
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user