mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Initial mercenary hiring work
This commit is contained in:
parent
a37174d6f2
commit
2b7e9ceec0
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
comp::{humanoid, quadruped_low, quadruped_medium, quadruped_small, Body},
|
||||
comp::{chat::GenericChatMsg, humanoid, quadruped_low, quadruped_medium, quadruped_small, Body},
|
||||
path::Chaser,
|
||||
rtsim::RtSimController,
|
||||
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
|
||||
@ -8,6 +8,7 @@ use crate::{
|
||||
use specs::{Component, Entity as EcsEntity};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::collections::VecDeque;
|
||||
use hashbrown::HashMap;
|
||||
use vek::*;
|
||||
|
||||
use super::dialogue::Subject;
|
||||
@ -266,6 +267,7 @@ impl<'a> From<&'a Body> for Psyche {
|
||||
#[derive(Clone, Debug)]
|
||||
/// Events that affect agent behavior from other entities/players/environment
|
||||
pub enum AgentEvent {
|
||||
IncomingChat(GenericChatMsg<String>),
|
||||
/// Engage in conversation with entity with Uid
|
||||
Talk(Uid, Subject),
|
||||
TradeInvite(Uid),
|
||||
@ -290,6 +292,14 @@ pub struct Target {
|
||||
pub selected_at: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Occupation {
|
||||
Traveler,
|
||||
TravelingMercenary,
|
||||
Merchant,
|
||||
TravelingMerchant,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Agent {
|
||||
pub rtsim_controller: RtSimController,
|
||||
@ -301,6 +311,19 @@ pub struct Agent {
|
||||
pub inbox: VecDeque<AgentEvent>,
|
||||
pub action_timer: f32,
|
||||
pub bearing: Vec2<f32>,
|
||||
pub offer: Option<InteractionOffer>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum InteractionOffer {
|
||||
Trade,
|
||||
MercenaryHire,
|
||||
FetchQuest(FetchResult),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FetchResult {
|
||||
items: HashMap<String, u32>,
|
||||
}
|
||||
|
||||
impl Agent {
|
||||
|
@ -6,6 +6,7 @@ use specs_idvs::IdvStorage;
|
||||
pub enum InviteKind {
|
||||
Group,
|
||||
Trade,
|
||||
JoinGroup,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -37,6 +37,8 @@ pub enum MemoryItem {
|
||||
// such as clothing worn, weapon used, etc.
|
||||
CharacterInteraction { name: String },
|
||||
CharacterFight { name: String },
|
||||
/// A contract with another character to join their group and fight for them
|
||||
MercenaryContract { name: String },
|
||||
Mood { state: MoodState },
|
||||
}
|
||||
|
||||
|
@ -211,6 +211,27 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
|
||||
},
|
||||
);
|
||||
},
|
||||
InviteKind::JoinGroup => {
|
||||
let mut group_manager = state.ecs().write_resource::<GroupManager>();
|
||||
group_manager.add_group_member(
|
||||
entity,
|
||||
inviter,
|
||||
&state.ecs().entities(),
|
||||
&mut state.ecs().write_storage(),
|
||||
&state.ecs().read_storage(),
|
||||
&uids,
|
||||
|entity, group_change| {
|
||||
clients
|
||||
.get(entity)
|
||||
.and_then(|c| {
|
||||
group_change
|
||||
.try_map(|e| uids.get(e).copied())
|
||||
.map(|g| (g, c))
|
||||
})
|
||||
.map(|(g, c)| c.send(ServerGeneral::GroupUpdate(g)));
|
||||
},
|
||||
);
|
||||
},
|
||||
InviteKind::Trade => {
|
||||
if let (Some(inviter_uid), Some(invitee_uid)) =
|
||||
(uids.get(inviter).copied(), uids.get(entity).copied())
|
||||
|
@ -32,21 +32,37 @@ 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 AgentEvent::UpdatePendingTrade(boxval) = event {
|
||||
// Box<(tid, pend, _, inventories)>) = event {
|
||||
agent
|
||||
.inbox
|
||||
.push_front(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,
|
||||
))));
|
||||
if let Some(agent) = agents.get_mut(entity) {
|
||||
if let Some(site_id) = agent.behavior.trade_site
|
||||
{
|
||||
let prices = index.get_site_prices(site_id);
|
||||
if let AgentEvent::UpdatePendingTrade(boxval) = event {
|
||||
// Box<(tid, pend, _, inventories)>) = event {
|
||||
agent
|
||||
.inbox
|
||||
.push_front(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,
|
||||
))));
|
||||
}
|
||||
} else if agent.offer.is_some() {
|
||||
if let AgentEvent::UpdatePendingTrade(boxval) = event {
|
||||
// Box<(tid, pend, _, inventories)>) = event {
|
||||
agent
|
||||
.inbox
|
||||
.push_front(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,
|
||||
boxval.2,
|
||||
boxval.3,
|
||||
))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,6 +74,7 @@ pub fn handle_process_trade_action(
|
||||
trade_id: TradeId,
|
||||
action: TradeAction,
|
||||
) {
|
||||
println!("trade action: {:?}", action);
|
||||
if let Some(uid) = server.state.ecs().uid_from_entity(entity) {
|
||||
let mut trades = server.state.ecs().write_resource::<Trades>();
|
||||
if let TradeAction::Decline = action {
|
||||
@ -86,8 +103,10 @@ pub fn handle_process_trade_action(
|
||||
trades.process_trade_action(trade_id, uid, action, get_inventory);
|
||||
}
|
||||
if let Entry::Occupied(entry) = trades.trades.entry(trade_id) {
|
||||
println!("trade entry occupied: {:?}", entry);
|
||||
let parties = entry.get().parties;
|
||||
if entry.get().should_commit() {
|
||||
println!("trade should be committed");
|
||||
let result = commit_trade(server.state.ecs(), entry.get());
|
||||
entry.remove();
|
||||
for party in parties.iter() {
|
||||
@ -101,6 +120,7 @@ pub fn handle_process_trade_action(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("trade is not ready to be committed");
|
||||
let mut entities: [Option<specs::Entity>; 2] = [None, None];
|
||||
let mut inventories: [Option<ReducedInventory>; 2] = [None, None];
|
||||
let mut prices = None;
|
||||
@ -132,6 +152,7 @@ pub fn handle_process_trade_action(
|
||||
drop(agents);
|
||||
for party in entities.iter() {
|
||||
if let Some(e) = *party {
|
||||
println!("sending trade update");
|
||||
server.notify_client(
|
||||
e,
|
||||
ServerGeneral::UpdatePendingTrade(
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use common::{
|
||||
comp::inventory::loadout_builder::LoadoutBuilder,
|
||||
comp::{agent::Occupation, inventory::loadout_builder::LoadoutBuilder},
|
||||
resources::Time,
|
||||
rtsim::{Memory, MemoryItem},
|
||||
store::Id,
|
||||
@ -23,6 +23,7 @@ pub struct Entity {
|
||||
pub controller: RtSimController,
|
||||
|
||||
pub brain: Brain,
|
||||
pub occupation: Option<Occupation>,
|
||||
}
|
||||
|
||||
const PERM_SPECIES: u32 = 0;
|
||||
@ -30,6 +31,7 @@ const PERM_BODY: u32 = 1;
|
||||
const PERM_LOADOUT: u32 = 2;
|
||||
const PERM_LEVEL: u32 = 3;
|
||||
const PERM_GENUS: u32 = 4;
|
||||
const PERM_OCCUPATION: u32 = 5;
|
||||
|
||||
impl Entity {
|
||||
pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed + perm) }
|
||||
@ -59,6 +61,18 @@ impl Entity {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_occupation(&self) -> Option<Occupation> {
|
||||
// If the body is humanoid
|
||||
if self.rng(PERM_GENUS).gen::<f32>() > 0.50 {
|
||||
match self.rng(PERM_OCCUPATION).gen::<f32>() {
|
||||
x if x < 0.5 => Some(Occupation::TravelingMercenary),
|
||||
_ => Some(Occupation::Traveler),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> String {
|
||||
use common::{generation::get_npc_name, npc::NPC_NAMES};
|
||||
let npc_names = NPC_NAMES.read();
|
||||
@ -120,9 +134,15 @@ impl Entity {
|
||||
)),
|
||||
};
|
||||
|
||||
let chest = Some(comp::Item::new_from_asset_expect(
|
||||
let chest = if matches!(self.get_occupation(), Some(Occupation::TravelingMercenary)) {
|
||||
Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.armor.plate.chest",
|
||||
))
|
||||
} else {
|
||||
Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.npc_armor.chest.leather_blue",
|
||||
));
|
||||
))
|
||||
};
|
||||
let pants = Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.npc_armor.pants.leather_blue",
|
||||
));
|
||||
|
@ -120,6 +120,7 @@ pub fn init(state: &mut State, #[cfg(feature = "worldgen")] world: &world::World
|
||||
controller: RtSimController::default(),
|
||||
last_tick: 0,
|
||||
brain: Default::default(),
|
||||
occupation: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ use common::{
|
||||
combat,
|
||||
comp::{
|
||||
self,
|
||||
Agent, agent::AgentEvent,
|
||||
skills::{GeneralSkill, Skill},
|
||||
Group, Inventory,
|
||||
},
|
||||
@ -581,6 +582,11 @@ impl StateExt for State {
|
||||
client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||
}
|
||||
}
|
||||
for (agent, pos) in (&mut ecs.write_storage::<Agent>(), &positions).join() {
|
||||
if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) {
|
||||
agent.inbox.push_front(AgentEvent::IncomingChat(resolved_msg.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
comp::ChatType::Region(uid) => {
|
||||
|
@ -2,10 +2,11 @@ use crate::rtsim::{Entity as RtSimData, RtSim};
|
||||
use common::{
|
||||
comp::{
|
||||
self,
|
||||
agent::{AgentEvent, Tactic, Target, DEFAULT_INTERACTION_TIME, TRADE_INTERACTION_TIME},
|
||||
agent::{InteractionOffer, Occupation, AgentEvent, Tactic, Target, DEFAULT_INTERACTION_TIME, TRADE_INTERACTION_TIME},
|
||||
buff::{BuffKind, Buffs},
|
||||
compass::{Direction, Distance},
|
||||
dialogue::{MoodContext, MoodState, Subject},
|
||||
chat::ChatType,
|
||||
group,
|
||||
inventory::{item::ItemTag, slot::EquipSlot},
|
||||
invite::{InviteKind, InviteResponse},
|
||||
@ -562,6 +563,7 @@ impl<'a> AgentData<'a> {
|
||||
{
|
||||
self.interact(agent, controller, &read_data, event_emitter);
|
||||
} else {
|
||||
agent.offer = None;
|
||||
agent.action_timer = 0.0;
|
||||
agent.target = None;
|
||||
controller.actions.push(ControlAction::Stand);
|
||||
@ -710,119 +712,121 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
|
||||
agent.action_timer = 0.0;
|
||||
if let Some((travel_to, _destination)) = &agent.rtsim_controller.travel_to {
|
||||
// if it has an rtsim destination and can fly then it should
|
||||
// if it is flying and bumps something above it then it should move down
|
||||
if self.traversal_config.can_fly
|
||||
&& !read_data
|
||||
.terrain
|
||||
.ray(self.pos.0, self.pos.0 + (Vec3::unit_z() * 3.0))
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.1
|
||||
.map_or(true, |b| b.is_some())
|
||||
{
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Fly));
|
||||
} else {
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::CancelInput(InputKind::Fly))
|
||||
}
|
||||
|
||||
if let Some((bearing, speed)) = agent.chaser.chase(
|
||||
&*read_data.terrain,
|
||||
self.pos.0,
|
||||
self.vel.0,
|
||||
*travel_to,
|
||||
TraversalConfig {
|
||||
min_tgt_dist: 1.25,
|
||||
..self.traversal_config
|
||||
},
|
||||
) {
|
||||
controller.inputs.move_dir =
|
||||
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero)
|
||||
* speed.min(agent.rtsim_controller.speed_factor);
|
||||
self.jump_if(controller, bearing.z > 1.5 || self.traversal_config.can_fly);
|
||||
controller.inputs.climb = Some(comp::Climb::Up);
|
||||
//.filter(|_| bearing.z > 0.1 || self.physics_state.in_liquid().is_some());
|
||||
|
||||
controller.inputs.move_z = bearing.z
|
||||
+ if self.traversal_config.can_fly {
|
||||
// NOTE: costs 4 us (imbris)
|
||||
let obstacle_ahead = read_data
|
||||
.terrain
|
||||
.ray(
|
||||
self.pos.0 + Vec3::unit_z(),
|
||||
self.pos.0
|
||||
+ bearing.try_normalized().unwrap_or_else(Vec3::unit_y) * 80.0
|
||||
+ Vec3::unit_z(),
|
||||
)
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.1
|
||||
.map_or(true, |b| b.is_some());
|
||||
|
||||
let mut ground_too_close = self
|
||||
.body
|
||||
.map(|body| {
|
||||
#[cfg(feature = "worldgen")]
|
||||
let height_approx = self.pos.0.y
|
||||
- read_data
|
||||
.world
|
||||
.sim()
|
||||
.get_alt_approx(self.pos.0.xy().map(|x: f32| x as i32))
|
||||
.unwrap_or(0.0);
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
let height_approx = self.pos.0.y;
|
||||
|
||||
height_approx < body.flying_height()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
const NUM_RAYS: usize = 5;
|
||||
|
||||
// NOTE: costs 15-20 us (imbris)
|
||||
for i in 0..=NUM_RAYS {
|
||||
let magnitude = self.body.map_or(20.0, |b| b.flying_height());
|
||||
// Lerp between a line straight ahead and straight down to detect a
|
||||
// wedge of obstacles we might fly into (inclusive so that both vectors
|
||||
// are sampled)
|
||||
if let Some(dir) = Lerp::lerp(
|
||||
-Vec3::unit_z(),
|
||||
Vec3::new(bearing.x, bearing.y, 0.0),
|
||||
i as f32 / NUM_RAYS as f32,
|
||||
)
|
||||
.try_normalized()
|
||||
{
|
||||
ground_too_close |= read_data
|
||||
.terrain
|
||||
.ray(self.pos.0, self.pos.0 + magnitude * dir)
|
||||
.until(|b: &Block| b.is_solid() || b.is_liquid())
|
||||
.cast()
|
||||
.1
|
||||
.map_or(false, |b| b.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
if obstacle_ahead || ground_too_close {
|
||||
1.0 //fly up when approaching obstacles
|
||||
} else {
|
||||
-0.1
|
||||
} //flying things should slowly come down from the stratosphere
|
||||
} else {
|
||||
0.05 //normal land traveller offset
|
||||
};
|
||||
|
||||
// Put away weapon
|
||||
if thread_rng().gen_bool(0.1)
|
||||
&& matches!(
|
||||
read_data.char_states.get(*self.entity),
|
||||
Some(CharacterState::Wielding)
|
||||
)
|
||||
if agent.rtsim_controller.travel_to.is_some() && !matches!(self.alignment, Some(Alignment::Owned(_))) {
|
||||
if let Some((travel_to, _destination)) = &agent.rtsim_controller.travel_to {
|
||||
// if it has an rtsim destination and can fly then it should
|
||||
// if it is flying and bumps something above it then it should move down
|
||||
if self.traversal_config.can_fly
|
||||
&& !read_data
|
||||
.terrain
|
||||
.ray(self.pos.0, self.pos.0 + (Vec3::unit_z() * 3.0))
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.1
|
||||
.map_or(true, |b| b.is_some())
|
||||
{
|
||||
controller.actions.push(ControlAction::Unwield);
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Fly));
|
||||
} else {
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::CancelInput(InputKind::Fly))
|
||||
}
|
||||
|
||||
if let Some((bearing, speed)) = agent.chaser.chase(
|
||||
&*read_data.terrain,
|
||||
self.pos.0,
|
||||
self.vel.0,
|
||||
*travel_to,
|
||||
TraversalConfig {
|
||||
min_tgt_dist: 1.25,
|
||||
..self.traversal_config
|
||||
},
|
||||
) {
|
||||
controller.inputs.move_dir =
|
||||
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero)
|
||||
* speed.min(agent.rtsim_controller.speed_factor);
|
||||
self.jump_if(controller, bearing.z > 1.5 || self.traversal_config.can_fly);
|
||||
controller.inputs.climb = Some(comp::Climb::Up);
|
||||
//.filter(|_| bearing.z > 0.1 || self.physics_state.in_liquid().is_some());
|
||||
|
||||
controller.inputs.move_z = bearing.z
|
||||
+ if self.traversal_config.can_fly {
|
||||
// NOTE: costs 4 us (imbris)
|
||||
let obstacle_ahead = read_data
|
||||
.terrain
|
||||
.ray(
|
||||
self.pos.0 + Vec3::unit_z(),
|
||||
self.pos.0
|
||||
+ bearing.try_normalized().unwrap_or_else(Vec3::unit_y) * 80.0
|
||||
+ Vec3::unit_z(),
|
||||
)
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.1
|
||||
.map_or(true, |b| b.is_some());
|
||||
|
||||
let mut ground_too_close = self
|
||||
.body
|
||||
.map(|body| {
|
||||
#[cfg(feature = "worldgen")]
|
||||
let height_approx = self.pos.0.y
|
||||
- read_data
|
||||
.world
|
||||
.sim()
|
||||
.get_alt_approx(self.pos.0.xy().map(|x: f32| x as i32))
|
||||
.unwrap_or(0.0);
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
let height_approx = self.pos.0.y;
|
||||
|
||||
height_approx < body.flying_height()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
const NUM_RAYS: usize = 5;
|
||||
|
||||
// NOTE: costs 15-20 us (imbris)
|
||||
for i in 0..=NUM_RAYS {
|
||||
let magnitude = self.body.map_or(20.0, |b| b.flying_height());
|
||||
// Lerp between a line straight ahead and straight down to detect a
|
||||
// wedge of obstacles we might fly into (inclusive so that both vectors
|
||||
// are sampled)
|
||||
if let Some(dir) = Lerp::lerp(
|
||||
-Vec3::unit_z(),
|
||||
Vec3::new(bearing.x, bearing.y, 0.0),
|
||||
i as f32 / NUM_RAYS as f32,
|
||||
)
|
||||
.try_normalized()
|
||||
{
|
||||
ground_too_close |= read_data
|
||||
.terrain
|
||||
.ray(self.pos.0, self.pos.0 + magnitude * dir)
|
||||
.until(|b: &Block| b.is_solid() || b.is_liquid())
|
||||
.cast()
|
||||
.1
|
||||
.map_or(false, |b| b.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
if obstacle_ahead || ground_too_close {
|
||||
1.0 //fly up when approaching obstacles
|
||||
} else {
|
||||
-0.1
|
||||
} //flying things should slowly come down from the stratosphere
|
||||
} else {
|
||||
0.05 //normal land traveller offset
|
||||
};
|
||||
|
||||
// Put away weapon
|
||||
if thread_rng().gen_bool(0.1)
|
||||
&& matches!(
|
||||
read_data.char_states.get(*self.entity),
|
||||
Some(CharacterState::Wielding)
|
||||
)
|
||||
{
|
||||
controller.actions.push(ControlAction::Unwield);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -860,7 +864,11 @@ impl<'a> AgentData<'a> {
|
||||
};
|
||||
|
||||
if agent.bearing.magnitude_squared() > 0.5f32.powi(2) {
|
||||
controller.inputs.move_dir = agent.bearing * 0.65;
|
||||
if matches!(Some(Alignment::Owned(_)), data.alignment) {
|
||||
controller.inputs.move_dir = agent.bearing * 0.30;
|
||||
} else {
|
||||
controller.inputs.move_dir = agent.bearing * 0.65;
|
||||
}
|
||||
}
|
||||
|
||||
// Put away weapon
|
||||
@ -901,9 +909,71 @@ impl<'a> AgentData<'a> {
|
||||
// .events
|
||||
// .push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
||||
// }
|
||||
agent.action_timer += read_data.dt.0;
|
||||
// TODO allow npcs to handle more than one msg per tick
|
||||
if agent.target.is_some() {
|
||||
agent.action_timer += read_data.dt.0 / 10.0;
|
||||
} else {
|
||||
agent.action_timer += read_data.dt.0;
|
||||
}
|
||||
let msg = agent.inbox.pop_back();
|
||||
if msg.is_some() {
|
||||
println!("agent message: {:?}", msg);
|
||||
}
|
||||
match msg {
|
||||
Some(AgentEvent::IncomingChat(chat_msg)) => {
|
||||
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||
if let ChatType::Say(by) = chat_msg.chat_type {
|
||||
if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id())
|
||||
{
|
||||
agent.target = Some(Target {
|
||||
target,
|
||||
hostile: false,
|
||||
selected_at: read_data.time.0,
|
||||
});
|
||||
match agent.offer {
|
||||
Some(InteractionOffer::Trade) => {
|
||||
if chat_msg.message == "yes".to_string() {
|
||||
let msg = "Come see my wares".to_string();
|
||||
event_emitter.emit(ServerEvent::Chat(
|
||||
UnresolvedChatMsg::npc(*self.uid, msg),
|
||||
));
|
||||
controller.events.push(ControlEvent::InitiateInvite(
|
||||
by,
|
||||
InviteKind::Trade,
|
||||
));
|
||||
}
|
||||
},
|
||||
Some(InteractionOffer::MercenaryHire) => {
|
||||
if chat_msg.message == "yes".to_string() {
|
||||
if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id()) {
|
||||
if let Some(tgt_stats) = read_data.stats.get(target) {
|
||||
agent.rtsim_controller.events.push(
|
||||
RtSimEvent::AddMemory(Memory {
|
||||
item: MemoryItem::MercenaryContract {
|
||||
name: tgt_stats.name.clone(),
|
||||
},
|
||||
time_to_forget: read_data.time.0 + 1440.0,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
controller.events.push(ControlEvent::InitiateInvite(
|
||||
by,
|
||||
InviteKind::Trade,
|
||||
));
|
||||
agent.behavior.set(BehaviorState::TRADING);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
event_emitter.emit(ServerEvent::Chat(
|
||||
UnresolvedChatMsg::npc(*self.uid, chat_msg.message),
|
||||
));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(AgentEvent::Talk(by, subject)) => {
|
||||
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||
if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id())
|
||||
@ -934,21 +1004,29 @@ impl<'a> AgentData<'a> {
|
||||
time_to_forget: read_data.time.0 + 600.0,
|
||||
}),
|
||||
);
|
||||
if rtsim_entity
|
||||
.brain
|
||||
.remembers_character(&tgt_stats.name)
|
||||
{
|
||||
format!(
|
||||
"Greetings fair {}! It has been far too \
|
||||
long since last I saw you. I'm going to \
|
||||
{} right now.",
|
||||
&tgt_stats.name, destination_name
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along?",
|
||||
destination_name
|
||||
)
|
||||
match rtsim_entity.get_occupation() {
|
||||
Some(Occupation::TravelingMercenary) => {
|
||||
agent.offer = Some(InteractionOffer::MercenaryHire);
|
||||
"You look like an adventurer in need of an armed consort. For 1000 c. I will protect you for the next 24 hours.".to_string()
|
||||
},
|
||||
_ => {
|
||||
if rtsim_entity
|
||||
.brain
|
||||
.remembers_character(&tgt_stats.name)
|
||||
{
|
||||
format!(
|
||||
"Greetings fair {}! It has been far too \
|
||||
long since last I saw you. I'm going to \
|
||||
{} right now.",
|
||||
&tgt_stats.name, destination_name
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along?",
|
||||
destination_name
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
@ -964,6 +1042,7 @@ impl<'a> AgentData<'a> {
|
||||
event_emitter.emit(ServerEvent::Chat(
|
||||
UnresolvedChatMsg::npc(*self.uid, msg),
|
||||
));
|
||||
agent.offer = Some(InteractionOffer::Trade);
|
||||
} else {
|
||||
let msg = "npc.speech.villager".to_string();
|
||||
event_emitter.emit(ServerEvent::Chat(
|
||||
@ -1165,6 +1244,14 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
},
|
||||
Some(AgentEvent::TradeAccepted(with)) => {
|
||||
//let msg = "I will guard you with my life until the contract ends or your coffers run dry".to_string();
|
||||
//event_emitter.emit(ServerEvent::Chat(
|
||||
// UnresolvedChatMsg::npc(*self.uid, msg),
|
||||
//));
|
||||
//controller.events.push(ControlEvent::InitiateInvite(
|
||||
// by,
|
||||
// InviteKind::JoinGroup,
|
||||
//));
|
||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||
if let Some(target) =
|
||||
read_data.uid_allocator.retrieve_entity_internal(with.id())
|
||||
@ -1183,10 +1270,26 @@ impl<'a> AgentData<'a> {
|
||||
if agent.behavior.is(BehaviorState::TRADING) {
|
||||
match result {
|
||||
TradeResult::Completed => {
|
||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
||||
*self.uid,
|
||||
"npc.speech.merchant_trade_successful".to_string(),
|
||||
)))
|
||||
if matches!(agent.offer, Some(InteractionOffer::MercenaryHire)) {
|
||||
agent.offer = None;
|
||||
let msg = "I will guard you with my life until the contract ends or your coffers run dry".to_string();
|
||||
event_emitter.emit(ServerEvent::Chat(
|
||||
UnresolvedChatMsg::npc(*self.uid, msg),
|
||||
));
|
||||
if let Some(target) = &agent.target {
|
||||
if let Some(by) = read_data.uids.get(target.target) {
|
||||
controller.events.push(ControlEvent::InitiateInvite(
|
||||
*by,
|
||||
InviteKind::JoinGroup,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
||||
*self.uid,
|
||||
"npc.speech.merchant_trade_successful".to_string(),
|
||||
)));
|
||||
}
|
||||
},
|
||||
_ => event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
||||
*self.uid,
|
||||
@ -1194,6 +1297,7 @@ impl<'a> AgentData<'a> {
|
||||
))),
|
||||
}
|
||||
agent.behavior.unset(BehaviorState::TRADING);
|
||||
agent.action_timer = DEFAULT_INTERACTION_TIME + read_data.dt.0;
|
||||
}
|
||||
},
|
||||
Some(AgentEvent::UpdatePendingTrade(boxval)) => {
|
||||
|
@ -789,6 +789,10 @@ impl<'a> Widget for Group<'a> {
|
||||
.localized_strings
|
||||
.get("hud.group.invite_to_join")
|
||||
.replace("{name}", &name),
|
||||
InviteKind::JoinGroup => self
|
||||
.localized_strings
|
||||
.get("hud.group.invite_to_join_your_group")
|
||||
.replace("{name}", &name),
|
||||
InviteKind::Trade => self
|
||||
.localized_strings
|
||||
.get("hud.group.invite_to_trade")
|
||||
|
@ -146,6 +146,7 @@ impl SessionState {
|
||||
// not be grammatical in some languages)
|
||||
let kind_str = match kind {
|
||||
InviteKind::Group => "Group",
|
||||
InviteKind::JoinGroup => "Join Group",
|
||||
InviteKind::Trade => "Trade",
|
||||
};
|
||||
let target_name = match client.player_list().get(&target) {
|
||||
|
Loading…
Reference in New Issue
Block a user