mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
rtsim personalities
This commit is contained in:
parent
7ac6c6b453
commit
1c0fdf9228
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -7032,6 +7032,7 @@ dependencies = [
|
||||
"veloren-common-base",
|
||||
"veloren-common-dynlib",
|
||||
"veloren-common-ecs",
|
||||
"veloren-rtsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3,8 +3,10 @@
|
||||
// `Agent`). When possible, this should be moved to the `rtsim`
|
||||
// module in `server`.
|
||||
|
||||
use rand::{Rng, seq::IteratorRandom};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::Component;
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
use vek::*;
|
||||
|
||||
use crate::comp::dialogue::MoodState;
|
||||
@ -54,6 +56,121 @@ pub enum MemoryItem {
|
||||
Mood { state: MoodState },
|
||||
}
|
||||
|
||||
|
||||
#[derive(EnumIter, Clone, Copy)]
|
||||
pub enum PersonalityTrait {
|
||||
Open,
|
||||
Adventurous,
|
||||
Closed,
|
||||
Conscientious,
|
||||
Busybody,
|
||||
Unconscientious,
|
||||
Extroverted,
|
||||
Introverted,
|
||||
Agreeable,
|
||||
Sociable,
|
||||
Disagreeable,
|
||||
Neurotic,
|
||||
Seeker,
|
||||
Worried,
|
||||
SadLoner,
|
||||
Stable,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
||||
pub struct Personality {
|
||||
openness: u8,
|
||||
conscientiousness: u8,
|
||||
extraversion: u8,
|
||||
agreeableness: u8,
|
||||
neuroticism: u8,
|
||||
}
|
||||
|
||||
fn distributed(min: u8, max: u8, rng: &mut impl Rng) -> u8 {
|
||||
let l = max - min;
|
||||
min + rng.gen_range(0..=l / 3) + rng.gen_range(0..=l / 3 + l % 3 % 2) + rng.gen_range(0..=l / 3 + l % 3 / 2)
|
||||
}
|
||||
|
||||
impl Personality {
|
||||
pub const HIGH_THRESHOLD: u8 = Self::MAX - Self::LOW_THRESHOLD;
|
||||
pub const LITTLE_HIGH: u8 = Self::MID + (Self::MAX - Self::MIN) / 20;
|
||||
pub const LITTLE_LOW: u8 = Self::MID - (Self::MAX - Self::MIN) / 20;
|
||||
pub const LOW_THRESHOLD: u8 = (Self::MAX - Self::MIN) / 5 * 2 + Self::MIN;
|
||||
const MIN: u8 = 0;
|
||||
pub const MID: u8 = (Self::MAX - Self::MIN) / 2;
|
||||
const MAX: u8 = 255;
|
||||
|
||||
fn distributed_value(rng: &mut impl Rng) -> u8 {
|
||||
distributed(Self::MIN, Self::MAX, rng)
|
||||
}
|
||||
|
||||
pub fn random(rng: &mut impl Rng) -> Self {
|
||||
Self {
|
||||
openness: Self::distributed_value(rng),
|
||||
conscientiousness: Self::distributed_value(rng),
|
||||
extraversion: Self::distributed_value(rng),
|
||||
agreeableness: Self::distributed_value(rng),
|
||||
neuroticism: Self::distributed_value(rng),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_evil(rng: &mut impl Rng) -> Self {
|
||||
Self {
|
||||
openness: Self::distributed_value(rng),
|
||||
extraversion: Self::distributed_value(rng),
|
||||
neuroticism: Self::distributed_value(rng),
|
||||
agreeableness: distributed(0, Self::LOW_THRESHOLD - 1, rng),
|
||||
conscientiousness: distributed(0, Self::LOW_THRESHOLD - 1, rng),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_good(rng: &mut impl Rng) -> Self {
|
||||
Self {
|
||||
openness: Self::distributed_value(rng),
|
||||
extraversion: Self::distributed_value(rng),
|
||||
neuroticism: Self::distributed_value(rng),
|
||||
agreeableness: Self::distributed_value(rng),
|
||||
conscientiousness: distributed(Self::LOW_THRESHOLD, Self::MAX, rng),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is(&self, trait_: PersonalityTrait) -> bool {
|
||||
match trait_ {
|
||||
PersonalityTrait::Open => self.openness > Personality::HIGH_THRESHOLD,
|
||||
PersonalityTrait::Adventurous => self.openness > Personality::HIGH_THRESHOLD && self.neuroticism < Personality::MID,
|
||||
PersonalityTrait::Closed => self.openness < Personality::LOW_THRESHOLD,
|
||||
PersonalityTrait::Conscientious => self.conscientiousness > Personality::HIGH_THRESHOLD,
|
||||
PersonalityTrait::Busybody => self.agreeableness < Personality::LOW_THRESHOLD,
|
||||
PersonalityTrait::Unconscientious => self.conscientiousness < Personality::LOW_THRESHOLD,
|
||||
PersonalityTrait::Extroverted => self.extraversion > Personality::HIGH_THRESHOLD,
|
||||
PersonalityTrait::Introverted => self.extraversion < Personality::LOW_THRESHOLD,
|
||||
PersonalityTrait::Agreeable => self.agreeableness > Personality::HIGH_THRESHOLD,
|
||||
PersonalityTrait::Sociable => self.agreeableness > Personality::HIGH_THRESHOLD && self.extraversion > Personality::MID,
|
||||
PersonalityTrait::Disagreeable => self.agreeableness < Personality::LOW_THRESHOLD,
|
||||
PersonalityTrait::Neurotic => self.neuroticism > Personality::HIGH_THRESHOLD,
|
||||
PersonalityTrait::Seeker => self.neuroticism > Personality::HIGH_THRESHOLD && self.openness > Personality::LITTLE_HIGH,
|
||||
PersonalityTrait::Worried => self.neuroticism > Personality::HIGH_THRESHOLD && self.agreeableness > Personality::LITTLE_HIGH,
|
||||
PersonalityTrait::SadLoner => self.neuroticism > Personality::HIGH_THRESHOLD && self.extraversion < Personality::LITTLE_LOW,
|
||||
PersonalityTrait::Stable => self.neuroticism < Personality::LOW_THRESHOLD,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chat_trait(&self, rng: &mut impl Rng) -> Option<PersonalityTrait> {
|
||||
PersonalityTrait::iter().filter(|t| self.is(*t)).choose(rng)
|
||||
}
|
||||
|
||||
pub fn will_ambush(&self) -> bool {
|
||||
self.agreeableness < Self::LOW_THRESHOLD
|
||||
&& self.conscientiousness < Self::LOW_THRESHOLD
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Personality {
|
||||
fn default() -> Self {
|
||||
Self { openness: Personality::MID, conscientiousness: Personality::MID, extraversion: Personality::MID, agreeableness: Personality::MID, neuroticism: Personality::MID }
|
||||
}
|
||||
}
|
||||
|
||||
/// This type is the map route through which the rtsim (real-time simulation)
|
||||
/// aspect of the game communicates with the rest of the game. It is analagous
|
||||
/// to `comp::Controller` in that it provides a consistent interface for
|
||||
@ -69,6 +186,7 @@ pub struct RtSimController {
|
||||
/// toward the given location, accounting for obstacles and other
|
||||
/// high-priority situations like being attacked.
|
||||
pub travel_to: Option<Vec3<f32>>,
|
||||
pub personality: Personality,
|
||||
pub heading_to: Option<String>,
|
||||
/// Proportion of full speed to move
|
||||
pub speed_factor: f32,
|
||||
@ -80,6 +198,7 @@ impl Default for RtSimController {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
travel_to: None,
|
||||
personality:Personality::default(),
|
||||
heading_to: None,
|
||||
speed_factor: 1.0,
|
||||
events: Vec::new(),
|
||||
@ -91,6 +210,7 @@ impl RtSimController {
|
||||
pub fn with_destination(pos: Vec3<f32>) -> Self {
|
||||
Self {
|
||||
travel_to: Some(pos),
|
||||
personality:Personality::default(),
|
||||
heading_to: None,
|
||||
speed_factor: 0.5,
|
||||
events: Vec::new(),
|
||||
|
@ -3,7 +3,7 @@ pub use common::rtsim::{NpcId, Profession};
|
||||
use common::{
|
||||
comp,
|
||||
grid::Grid,
|
||||
rtsim::{FactionId, SiteId, VehicleId},
|
||||
rtsim::{FactionId, SiteId, VehicleId, Personality},
|
||||
store::Id,
|
||||
vol::RectVolSize,
|
||||
};
|
||||
@ -80,9 +80,10 @@ pub struct Npc {
|
||||
pub profession: Option<Profession>,
|
||||
pub home: Option<SiteId>,
|
||||
pub faction: Option<FactionId>,
|
||||
|
||||
pub riding: Option<Riding>,
|
||||
|
||||
pub personality: Personality,
|
||||
|
||||
// Unpersisted state
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub chunk_pos: Option<Vec2<i32>>,
|
||||
@ -113,6 +114,7 @@ impl Clone for Npc {
|
||||
faction: self.faction,
|
||||
riding: self.riding.clone(),
|
||||
body: self.body,
|
||||
personality: self.personality,
|
||||
// Not persisted
|
||||
chunk_pos: None,
|
||||
current_site: Default::default(),
|
||||
@ -129,6 +131,7 @@ impl Npc {
|
||||
seed,
|
||||
wpos,
|
||||
body,
|
||||
personality: Personality::default(),
|
||||
profession: None,
|
||||
home: None,
|
||||
faction: None,
|
||||
@ -141,6 +144,11 @@ impl Npc {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_personality(mut self, personality: Personality) -> Self {
|
||||
self.personality = personality;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_profession(mut self, profession: impl Into<Option<Profession>>) -> Self {
|
||||
self.profession = profession.into();
|
||||
self
|
||||
|
@ -11,7 +11,7 @@ use common::{
|
||||
comp::{self, Body},
|
||||
grid::Grid,
|
||||
resources::TimeOfDay,
|
||||
rtsim::WorldSettings,
|
||||
rtsim::{WorldSettings, Personality},
|
||||
terrain::TerrainChunkSize,
|
||||
vol::RectVolSize,
|
||||
};
|
||||
@ -103,6 +103,7 @@ impl Data {
|
||||
Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng))
|
||||
.with_faction(site.faction)
|
||||
.with_home(site_id)
|
||||
.with_personality(Personality::random(&mut rng))
|
||||
.with_profession(match rng.gen_range(0..20) {
|
||||
0 => Profession::Hunter,
|
||||
1 => Profession::Blacksmith,
|
||||
@ -119,6 +120,7 @@ impl Data {
|
||||
for _ in 0..15 {
|
||||
this.npcs.create_npc(
|
||||
Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng))
|
||||
.with_personality(Personality::random_evil(&mut rng))
|
||||
.with_faction(site.faction)
|
||||
.with_home(site_id)
|
||||
.with_profession(match rng.gen_range(0..20) {
|
||||
@ -130,6 +132,7 @@ impl Data {
|
||||
this.npcs.create_npc(
|
||||
Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng))
|
||||
.with_home(site_id)
|
||||
.with_personality(Personality::random_good(&mut rng))
|
||||
.with_profession(Profession::Merchant),
|
||||
);
|
||||
|
||||
@ -143,6 +146,7 @@ impl Data {
|
||||
Npc::new(rng.gen(), wpos, random_humanoid(&mut rng))
|
||||
.with_home(site_id)
|
||||
.with_profession(Profession::Captain)
|
||||
.with_personality(Personality::random_good(&mut rng))
|
||||
.steering(vehicle_id),
|
||||
);
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ common = {package = "veloren-common", path = "../../common"}
|
||||
common-base = { package = "veloren-common-base", path = "../../common/base" }
|
||||
common-ecs = { package = "veloren-common-ecs", path = "../../common/ecs" }
|
||||
common-dynlib = { package = "veloren-common-dynlib", path = "../../common/dynlib", optional = true}
|
||||
rtsim = { package = "veloren-rtsim", path = "../../rtsim" }
|
||||
|
||||
specs = { version = "0.18", features = ["shred-derive"] }
|
||||
vek = { version = "0.15.8", features = ["serde"] }
|
||||
|
@ -650,7 +650,6 @@ impl<'a> AgentData<'a> {
|
||||
controller: &mut Controller,
|
||||
read_data: &ReadData,
|
||||
event_emitter: &mut Emitter<ServerEvent>,
|
||||
will_ambush: bool,
|
||||
) {
|
||||
enum ActionStateTimers {
|
||||
TimerChooseTarget = 0,
|
||||
@ -673,7 +672,7 @@ impl<'a> AgentData<'a> {
|
||||
.get(entity)
|
||||
.map_or(false, |eu| eu != self.uid)
|
||||
};
|
||||
if will_ambush
|
||||
if agent.rtsim_controller.personality.will_ambush()
|
||||
&& self_different_from_entity()
|
||||
&& !self.passive_towards(entity, read_data)
|
||||
{
|
||||
|
@ -364,6 +364,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Update entity state
|
||||
if let Some(agent) = agent {
|
||||
agent.rtsim_controller.personality = npc.personality;
|
||||
if let Some(action) = npc.action {
|
||||
match action {
|
||||
rtsim2::data::npc::NpcAction::Goto(wpos, sf) => {
|
||||
|
@ -498,7 +498,6 @@ fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
|
||||
bdata.controller,
|
||||
bdata.read_data,
|
||||
bdata.event_emitter,
|
||||
will_ambush(/* bdata.rtsim_entity */ None, &bdata.agent_data),
|
||||
);
|
||||
} else {
|
||||
bdata.agent_data.handle_sounds_heard(
|
||||
@ -747,7 +746,6 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
|
||||
controller,
|
||||
read_data,
|
||||
event_emitter,
|
||||
will_ambush(agent_data.rtsim_entity, agent_data),
|
||||
);
|
||||
}
|
||||
|
||||
@ -775,15 +773,6 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn will_ambush(rtsim_entity: Option<&RtSimEntity>, agent_data: &AgentData) -> bool {
|
||||
// TODO: implement for rtsim2
|
||||
// agent_data
|
||||
// .health
|
||||
// .map_or(false, |h| h.current() / h.maximum() > 0.7)
|
||||
// && rtsim_entity.map_or(false, |re| re.brain.personality.will_ambush)
|
||||
false
|
||||
}
|
||||
|
||||
fn remembers_fight_with(
|
||||
rtsim_entity: Option<&RtSimEntity>,
|
||||
read_data: &ReadData,
|
||||
|
@ -2,14 +2,14 @@ use common::{
|
||||
comp::{
|
||||
agent::{AgentEvent, Target, TimerAction},
|
||||
compass::{Direction, Distance},
|
||||
dialogue::{MoodContext, MoodState, Subject},
|
||||
dialogue::Subject,
|
||||
inventory::item::{ItemTag, MaterialStatManifest},
|
||||
invite::{InviteKind, InviteResponse},
|
||||
tool::AbilityMap,
|
||||
BehaviorState, ControlAction, Item, TradingBehavior, UnresolvedChatMsg, UtteranceKind,
|
||||
},
|
||||
event::ServerEvent,
|
||||
rtsim::{Memory, MemoryItem, RtSimEvent},
|
||||
rtsim::{Memory, MemoryItem, RtSimEvent, PersonalityTrait},
|
||||
trade::{TradeAction, TradePhase, TradeResult},
|
||||
};
|
||||
use rand::{thread_rng, Rng};
|
||||
@ -105,44 +105,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
|
||||
match subject {
|
||||
Subject::Regular => {
|
||||
if let Some(destination_name) = &agent.rtsim_controller.heading_to {
|
||||
let msg = format!(
|
||||
"I'm heading to {}! Want to come along?",
|
||||
destination_name
|
||||
);
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
}
|
||||
/*if let (
|
||||
Some((_travel_to, destination_name)),
|
||||
Some(rtsim_entity),
|
||||
) = (&agent.rtsim_controller.travel_to, &agent_data.rtsim_entity)
|
||||
{
|
||||
let personality = &rtsim_entity.brain.personality;
|
||||
let standard_response_msg = || -> String {
|
||||
if personality.will_ambush {
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along? We'll make \
|
||||
great travel buddies, hehe.",
|
||||
destination_name
|
||||
)
|
||||
} else if personality
|
||||
.personality_traits
|
||||
.contains(PersonalityTrait::Extroverted)
|
||||
{
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along?",
|
||||
destination_name
|
||||
)
|
||||
} else if personality
|
||||
.personality_traits
|
||||
.contains(PersonalityTrait::Disagreeable)
|
||||
{
|
||||
"Hrm.".to_string()
|
||||
} else {
|
||||
"Hello!".to_string()
|
||||
}
|
||||
};
|
||||
let msg = if let Some(tgt_stats) = read_data.stats.get(target) {
|
||||
if let Some(tgt_stats) = read_data.stats.get(target) {
|
||||
agent.rtsim_controller.events.push(RtSimEvent::AddMemory(
|
||||
Memory {
|
||||
item: MemoryItem::CharacterInteraction {
|
||||
@ -151,16 +114,34 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
time_to_forget: read_data.time.0 + 600.0,
|
||||
},
|
||||
));
|
||||
if rtsim_entity.brain.remembers_character(&tgt_stats.name) {
|
||||
if personality.will_ambush {
|
||||
"Just follow me a bit more, hehe.".to_string()
|
||||
} else if personality
|
||||
.personality_traits
|
||||
.contains(PersonalityTrait::Extroverted)
|
||||
if let Some(destination_name) = &agent.rtsim_controller.heading_to {
|
||||
let personality = &agent.rtsim_controller.personality;
|
||||
let standard_response_msg = || -> String {
|
||||
if personality.will_ambush() {
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along? We'll make \
|
||||
great travel buddies, hehe.",
|
||||
destination_name
|
||||
)
|
||||
} else if personality.is(PersonalityTrait::Extroverted)
|
||||
{
|
||||
if personality
|
||||
.personality_traits
|
||||
.contains(PersonalityTrait::Extroverted)
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along?",
|
||||
destination_name
|
||||
)
|
||||
} else if personality.is(PersonalityTrait::Disagreeable)
|
||||
{
|
||||
"Hrm.".to_string()
|
||||
} else {
|
||||
"Hello!".to_string()
|
||||
}
|
||||
};
|
||||
let msg = if false /* TODO: Remembers character */ {
|
||||
if personality.will_ambush() {
|
||||
"Just follow me a bit more, hehe.".to_string()
|
||||
} else if personality.is(PersonalityTrait::Extroverted)
|
||||
{
|
||||
if personality.is(PersonalityTrait::Extroverted)
|
||||
{
|
||||
format!(
|
||||
"Greetings fair {}! It has been far \
|
||||
@ -168,9 +149,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
going to {} right now.",
|
||||
&tgt_stats.name, destination_name
|
||||
)
|
||||
} else if personality
|
||||
.personality_traits
|
||||
.contains(PersonalityTrait::Disagreeable)
|
||||
} else if personality.is(PersonalityTrait::Disagreeable)
|
||||
{
|
||||
"Oh. It's you again.".to_string()
|
||||
} else {
|
||||
@ -187,8 +166,8 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
standard_response_msg()
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
} else*/
|
||||
else if agent.behavior.can_trade(agent_data.alignment.copied(), by) {
|
||||
}
|
||||
/*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(
|
||||
@ -197,25 +176,16 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
);
|
||||
} else {
|
||||
let default_msg = "npc-speech-merchant_busy";
|
||||
let msg = default_msg/*agent_data.rtsim_entity.map_or(default_msg, |e| {
|
||||
if e.brain
|
||||
.personality
|
||||
.personality_traits
|
||||
.contains(PersonalityTrait::Disagreeable)
|
||||
{
|
||||
let msg = if agent.rtsim_controller.personality.is(PersonalityTrait::Disagreeable) {
|
||||
"npc-speech-merchant_busy_rude"
|
||||
} else {
|
||||
default_msg
|
||||
}
|
||||
})*/;
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
}
|
||||
} else {
|
||||
}*/ else {
|
||||
let mut rng = thread_rng();
|
||||
/*if let Some(extreme_trait) =
|
||||
agent_data.rtsim_entity.and_then(|e| {
|
||||
e.brain.personality.random_chat_trait(&mut rng)
|
||||
})
|
||||
if let Some(extreme_trait) = agent.rtsim_controller.personality.chat_trait(&mut rng)
|
||||
{
|
||||
let msg = match extreme_trait {
|
||||
PersonalityTrait::Open => {
|
||||
@ -268,11 +238,11 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
},
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
} else*/
|
||||
{
|
||||
} else {
|
||||
agent_data.chat_npc("npc-speech-villager", event_emitter);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Subject::Trade => {
|
||||
if agent.behavior.can_trade(agent_data.alignment.copied(), by) {
|
||||
|
Loading…
Reference in New Issue
Block a user