use crate::{
ai::Action,
data::{Reports, Sentiments},
gen::name,
};
pub use common::rtsim::{NpcId, Profession};
use common::{
character::CharacterId,
comp,
grid::Grid,
rtsim::{
Actor, ChunkResource, FactionId, NpcAction, NpcActivity, NpcInput, Personality, ReportId,
Role, SiteId,
},
store::Id,
terrain::CoordinateConversions,
util::Dir,
};
use hashbrown::{HashMap, HashSet};
use rand::prelude::*;
use serde::{Deserialize, Serialize};
use slotmap::HopSlotMap;
use std::{
collections::VecDeque,
ops::{Deref, DerefMut},
};
use tracing::error;
use vek::*;
use world::{
civ::Track,
site::Site as WorldSite,
util::{RandomPerm, LOCALITY},
};
#[derive(Copy, Clone, Debug, Default)]
pub enum SimulationMode {
#[default]
Simulated,
Loaded,
}
#[derive(Clone)]
pub struct PathData<P, N> {
pub end: N,
pub path: VecDeque<P>,
pub repoll: bool,
}
#[derive(Clone, Default)]
pub struct PathingMemory {
pub intrasite_path: Option<(PathData<Vec2<i32>, Vec2<i32>>, Id<WorldSite>)>,
pub intersite_path: Option<(PathData<(Id<Track>, bool), SiteId>, usize)>,
}
#[derive(Default)]
pub struct Controller {
pub actions: Vec<NpcAction>,
pub activity: Option<NpcActivity>,
pub new_home: Option<SiteId>,
pub look_dir: Option<Dir>,
}
impl Controller {
pub fn do_idle(&mut self) { self.activity = None; }
pub fn do_goto(&mut self, wpos: Vec3<f32>, speed_factor: f32) {
self.activity = Some(NpcActivity::Goto(wpos, speed_factor));
}
pub fn do_gather(&mut self, resources: &'static [ChunkResource]) {
self.activity = Some(NpcActivity::Gather(resources));
}
pub fn do_hunt_animals(&mut self) { self.activity = Some(NpcActivity::HuntAnimals); }
pub fn do_dance(&mut self, dir: Option<Dir>) { self.activity = Some(NpcActivity::Dance(dir)); }
pub fn do_cheer(&mut self, dir: Option<Dir>) { self.activity = Some(NpcActivity::Cheer(dir)); }
pub fn do_sit(&mut self, dir: Option<Dir>, pos: Option<Vec3<i32>>) {
self.activity = Some(NpcActivity::Sit(dir, pos));
}
pub fn say(&mut self, target: impl Into<Option<Actor>>, content: comp::Content) {
self.actions.push(NpcAction::Say(target.into(), content));
}
pub fn attack(&mut self, target: impl Into<Actor>) {
self.actions.push(NpcAction::Attack(target.into()));
}
pub fn set_new_home(&mut self, new_home: SiteId) { self.new_home = Some(new_home); }
}
pub struct Brain {
pub action: Box<dyn Action<(), !>>,
}
#[derive(Serialize, Deserialize)]
pub struct Npc {
pub uid: u64,
pub seed: u32,
pub wpos: Vec3<f32>,
pub dir: Vec2<f32>,
pub body: comp::Body,
pub role: Role,
pub home: Option<SiteId>,
pub faction: Option<FactionId>,
pub health_fraction: f32,
pub known_reports: HashSet<ReportId>,
#[serde(default)]
pub personality: Personality,
#[serde(default)]
pub sentiments: Sentiments,
#[serde(skip)]
pub chunk_pos: Option<Vec2<i32>>,
#[serde(skip)]
pub current_site: Option<SiteId>,
#[serde(skip)]
pub controller: Controller,
#[serde(skip)]
pub inbox: VecDeque<NpcInput>,
#[serde(skip)]
pub mode: SimulationMode,
#[serde(skip)]
pub brain: Option<Brain>,
}
impl Clone for Npc {
fn clone(&self) -> Self {
Self {
uid: self.uid,
seed: self.seed,
wpos: self.wpos,
dir: self.dir,
role: self.role.clone(),
home: self.home,
faction: self.faction,
health_fraction: self.health_fraction,
known_reports: self.known_reports.clone(),
body: self.body,
personality: self.personality,
sentiments: self.sentiments.clone(),
chunk_pos: None,
current_site: Default::default(),
controller: Default::default(),
inbox: Default::default(),
mode: Default::default(),
brain: Default::default(),
}
}
}
impl Npc {
pub const PERM_ENTITY_CONFIG: u32 = 1;
const PERM_NAME: u32 = 0;
pub fn new(seed: u32, wpos: Vec3<f32>, body: comp::Body, role: Role) -> Self {
Self {
uid: 0,
seed,
wpos,
dir: Vec2::unit_x(),
body,
personality: Default::default(),
sentiments: Default::default(),
role,
home: None,
faction: None,
health_fraction: 1.0,
known_reports: Default::default(),
chunk_pos: None,
current_site: None,
controller: Default::default(),
inbox: Default::default(),
mode: SimulationMode::Simulated,
brain: None,
}
}
pub fn is_dead(&self) -> bool { self.health_fraction <= 0.0 }
pub fn with_personality(mut self, personality: Personality) -> Self {
self.personality = personality;
self
}
pub fn with_home(mut self, home: impl Into<Option<SiteId>>) -> Self {
self.home = home.into();
self
}
pub fn with_faction(mut self, faction: impl Into<Option<FactionId>>) -> Self {
self.faction = faction.into();
self
}
pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed.wrapping_add(perm)) }
pub fn get_name(&self) -> String { name::generate(&mut self.rng(Self::PERM_NAME)) }
pub fn profession(&self) -> Option<Profession> {
match &self.role {
Role::Civilised(profession) => profession.clone(),
Role::Monster | Role::Wild | Role::Vehicle => None,
}
}
pub fn cleanup(&mut self, reports: &Reports) {
self.sentiments
.cleanup(crate::data::sentiment::NPC_MAX_SENTIMENTS);
self.known_reports
.retain(|report| reports.contains_key(*report));
}
}
#[derive(Default, Clone, Serialize, Deserialize)]
pub struct GridCell {
pub npcs: Vec<NpcId>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct NpcLink {
pub mount: NpcId,
pub rider: Actor,
pub is_steering: bool,
}
#[derive(Clone, Default, Serialize, Deserialize)]
struct Riders {
steerer: Option<MountId>,
riders: Vec<MountId>,
}
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(
from = "HopSlotMap<MountId, NpcLink>",
into = "HopSlotMap<MountId, NpcLink>"
)]
pub struct NpcLinks {
links: HopSlotMap<MountId, NpcLink>,
mount_map: slotmap::SecondaryMap<NpcId, Riders>,
rider_map: HashMap<Actor, MountId>,
}
impl NpcLinks {
pub fn remove_mount(&mut self, mount: NpcId) {
if let Some(riders) = self.mount_map.remove(mount) {
for link in riders
.riders
.into_iter()
.chain(riders.steerer)
.filter_map(|link_id| self.links.get(link_id))
{
self.rider_map.remove(&link.rider);
}
}
}
fn remove_rider(&mut self, id: MountId, link: &NpcLink) {
if let Some(riders) = self.mount_map.get_mut(link.mount) {
if link.is_steering && riders.steerer == Some(id) {
riders.steerer = None;
} else if let Some((i, _)) = riders.riders.iter().enumerate().find(|(_, i)| **i == id) {
riders.riders.remove(i);
}
if riders.steerer.is_none() && riders.riders.is_empty() {
self.mount_map.remove(link.mount);
}
}
}
pub fn remove_link(&mut self, link_id: MountId) {
if let Some(link) = self.links.remove(link_id) {
self.rider_map.remove(&link.rider);
self.remove_rider(link_id, &link);
}
}
pub fn dismount(&mut self, rider: impl Into<Actor>) {
if let Some(id) = self.rider_map.remove(&rider.into()) {
if let Some(link) = self.links.remove(id) {
self.remove_rider(id, &link);
}
}
}
pub fn add_mounting(
&mut self,
mount: NpcId,
rider: impl Into<Actor>,
steering: bool,
) -> Result<MountId, MountingError> {
let rider = rider.into();
if Actor::Npc(mount) == rider {
return Err(MountingError::MountSelf);
}
if let Actor::Npc(rider) = rider
&& self.mount_map.contains_key(rider)
{
return Err(MountingError::RiderIsMounted);
}
if self.rider_map.contains_key(&Actor::Npc(mount)) {
return Err(MountingError::MountIsRiding);
}
if let Some(mount_entry) = self.mount_map.entry(mount) {
if let hashbrown::hash_map::Entry::Vacant(rider_entry) = self.rider_map.entry(rider) {
let riders = mount_entry.or_insert(Riders::default());
if steering {
if riders.steerer.is_none() {
let id = self.links.insert(NpcLink {
mount,
rider,
is_steering: true,
});
riders.steerer = Some(id);
rider_entry.insert(id);
Ok(id)
} else {
Err(MountingError::HasSteerer)
}
} else {
let id = self.links.insert(NpcLink {
mount,
rider,
is_steering: false,
});
riders.riders.push(id);
rider_entry.insert(id);
Ok(id)
}
} else {
Err(MountingError::AlreadyRiding)
}
} else {
Err(MountingError::MountDead)
}
}
pub fn steer(
&mut self,
mount: NpcId,
rider: impl Into<Actor>,
) -> Result<MountId, MountingError> {
self.add_mounting(mount, rider, true)
}
pub fn ride(
&mut self,
mount: NpcId,
rider: impl Into<Actor>,
) -> Result<MountId, MountingError> {
self.add_mounting(mount, rider, false)
}
pub fn get_mount_link(&self, rider: impl Into<Actor>) -> Option<&NpcLink> {
self.rider_map
.get(&rider.into())
.and_then(|link| self.links.get(*link))
}
pub fn get_steerer_link(&self, mount: NpcId) -> Option<&NpcLink> {
self.mount_map
.get(mount)
.and_then(|mount| self.links.get(mount.steerer?))
}
pub fn get(&self, id: MountId) -> Option<&NpcLink> { self.links.get(id) }
pub fn ids(&self) -> impl Iterator<Item = MountId> + '_ { self.links.keys() }
pub fn iter(&self) -> impl Iterator<Item = &NpcLink> + '_ { self.links.values() }
pub fn iter_mounts(&self) -> impl Iterator<Item = NpcId> + '_ { self.mount_map.keys() }
}
impl From<HopSlotMap<MountId, NpcLink>> for NpcLinks {
fn from(mut value: HopSlotMap<MountId, NpcLink>) -> Self {
let mut from_map = slotmap::SecondaryMap::new();
let mut to_map = HashMap::with_capacity(value.len());
let mut delete = Vec::new();
for (id, link) in value.iter() {
if let Some(entry) = from_map.entry(link.mount) {
let riders = entry.or_insert(Riders::default());
if link.is_steering {
if let Some(old) = riders.steerer.replace(id) {
error!("Replaced steerer {old:?} with {id:?}");
}
} else {
riders.riders.push(id);
}
} else {
delete.push(id);
}
to_map.insert(link.rider, id);
}
for id in delete {
value.remove(id);
}
Self {
links: value,
mount_map: from_map,
rider_map: to_map,
}
}
}
impl From<NpcLinks> for HopSlotMap<MountId, NpcLink> {
fn from(other: NpcLinks) -> Self { other.links }
}
slotmap::new_key_type! {
pub struct MountId;
}
#[derive(Clone, Serialize, Deserialize)]
pub struct MountData {
is_steering: bool,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Npcs {
pub uid_counter: u64,
pub npcs: HopSlotMap<NpcId, Npc>,
pub mounts: NpcLinks,
#[serde(skip, default = "construct_npc_grid")]
pub npc_grid: Grid<GridCell>,
#[serde(skip)]
pub character_map: HashMap<Vec2<i32>, Vec<(CharacterId, Vec3<f32>)>>,
}
impl Default for Npcs {
fn default() -> Self {
Self {
uid_counter: 0,
npcs: Default::default(),
mounts: Default::default(),
npc_grid: construct_npc_grid(),
character_map: Default::default(),
}
}
}
fn construct_npc_grid() -> Grid<GridCell> { Grid::new(Vec2::zero(), Default::default()) }
#[derive(Debug)]
pub enum MountingError {
MountDead,
RiderDead,
HasSteerer,
AlreadyRiding,
MountIsRiding,
RiderIsMounted,
MountSelf,
}
impl Npcs {
pub fn create_npc(&mut self, mut npc: Npc) -> NpcId {
npc.uid = self.uid_counter;
self.uid_counter += 1;
self.npcs.insert(npc)
}
pub fn nearby(
&self,
this_npc: Option<NpcId>,
wpos: Vec3<f32>,
radius: f32,
) -> impl Iterator<Item = Actor> + '_ {
let chunk_pos = wpos.xy().as_().wpos_to_cpos();
let r_sqr = radius * radius;
LOCALITY
.into_iter()
.flat_map(move |neighbor| {
self.npc_grid.get(chunk_pos + neighbor).map(move |cell| {
cell.npcs
.iter()
.copied()
.filter(move |npc| {
self.npcs
.get(*npc)
.map_or(false, |npc| npc.wpos.distance_squared(wpos) < r_sqr)
&& Some(*npc) != this_npc
})
.map(Actor::Npc)
})
})
.flatten()
.chain(
self.character_map
.get(&chunk_pos)
.map(|characters| {
characters.iter().filter_map(move |(character, c_wpos)| {
if c_wpos.distance_squared(wpos) < r_sqr {
Some(Actor::Character(*character))
} else {
None
}
})
})
.into_iter()
.flatten(),
)
}
}
impl Deref for Npcs {
type Target = HopSlotMap<NpcId, Npc>;
fn deref(&self) -> &Self::Target { &self.npcs }
}
impl DerefMut for Npcs {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.npcs }
}