mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Started adding wandering rtsim monsters
This commit is contained in:
parent
ba8984e1a7
commit
0f92f38967
11
assets/common/entity/wild/aggressive/cyclops.ron
Normal file
11
assets/common/entity/wild/aggressive/cyclops.ron
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
name: Automatic,
|
||||||
|
body: RandomWith("cyclops"),
|
||||||
|
alignment: Alignment(Enemy),
|
||||||
|
loot: LootTable("common.loot_tables.creature.biped_large.default"),
|
||||||
|
inventory: (
|
||||||
|
loadout: FromBody,
|
||||||
|
),
|
||||||
|
meta: [],
|
||||||
|
)
|
11
assets/common/entity/wild/aggressive/werewolf.ron
Normal file
11
assets/common/entity/wild/aggressive/werewolf.ron
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
name: Automatic,
|
||||||
|
body: RandomWith("werewolf"),
|
||||||
|
alignment: Alignment(Enemy),
|
||||||
|
loot: LootTable("common.loot_tables.creature.biped_large.default"),
|
||||||
|
inventory: (
|
||||||
|
loadout: FromBody,
|
||||||
|
),
|
||||||
|
meta: [],
|
||||||
|
)
|
4
assets/voxygen/i18n/en/body.ftl
Normal file
4
assets/voxygen/i18n/en/body.ftl
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
body-generic = creature
|
||||||
|
body-biped_large-cyclops = cyclops
|
||||||
|
body-biped_large-wendigo = wendigo
|
||||||
|
body-biped_large-werewolf = werewolf
|
@ -253,7 +253,11 @@ npc-speech-merchant_sell_directed =
|
|||||||
npc-speech-tell_site =
|
npc-speech-tell_site =
|
||||||
.a0 = Have you visited { $site }? It's just { $dir } of here!
|
.a0 = Have you visited { $site }? It's just { $dir } of here!
|
||||||
.a1 = You should visit { $site } some time.
|
.a1 = You should visit { $site } some time.
|
||||||
.a2 = If you travel { $dir }, you can get to { $site }.
|
.a2 = If you travel { $dist } to the { $dir }, you can get to { $site }.
|
||||||
|
.a3 = To the { $dir } you'll find { $site }, it's { $dist }.
|
||||||
|
npc-speech-tell_monster =
|
||||||
|
.a0 = They say there's a { $body } to the { $dir }, { $dist }...
|
||||||
|
.a1 = You think you're tough? To the { $dir } there's a { $body }.
|
||||||
npc-speech-witness_murder =
|
npc-speech-witness_murder =
|
||||||
.a0 = Murderer!
|
.a0 = Murderer!
|
||||||
.a1 = How could you do this?
|
.a1 = How could you do this?
|
||||||
@ -273,4 +277,10 @@ npc-speech-dir_south_east = south-east
|
|||||||
npc-speech-dir_south = south
|
npc-speech-dir_south = south
|
||||||
npc-speech-dir_south_west = south-west
|
npc-speech-dir_south_west = south-west
|
||||||
npc-speech-dir_west = west
|
npc-speech-dir_west = west
|
||||||
npc-speech-dir_north_west = north-west
|
npc-speech-dir_north_west = very far away
|
||||||
|
|
||||||
|
npc-speech-dist_very_far = very far away
|
||||||
|
npc-speech-dist_far = far away
|
||||||
|
npc-speech-dist_ahead = some way away
|
||||||
|
npc-speech-dist_near = nearby
|
||||||
|
npc-speech-dist_near_to = very close
|
||||||
|
@ -18,6 +18,7 @@ pub mod theropod;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
assets::{self, Asset},
|
assets::{self, Asset},
|
||||||
|
comp::Content,
|
||||||
consts::{HUMAN_DENSITY, WATER_DENSITY},
|
consts::{HUMAN_DENSITY, WATER_DENSITY},
|
||||||
make_case_elim,
|
make_case_elim,
|
||||||
npc::NpcKind,
|
npc::NpcKind,
|
||||||
@ -1076,6 +1077,13 @@ impl Body {
|
|||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn localize(&self) -> Content {
|
||||||
|
match self {
|
||||||
|
Self::BipedLarge(biped_large) => biped_large.localize(),
|
||||||
|
_ => Content::localized("body-generic"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Body {
|
impl Component for Body {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{make_case_elim, make_proj_elim};
|
use crate::{comp::Content, make_case_elim, make_proj_elim};
|
||||||
use rand::{seq::SliceRandom, thread_rng};
|
use rand::{seq::SliceRandom, thread_rng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -23,6 +23,15 @@ impl Body {
|
|||||||
let body_type = *ALL_BODY_TYPES.choose(rng).unwrap();
|
let body_type = *ALL_BODY_TYPES.choose(rng).unwrap();
|
||||||
Self { species, body_type }
|
Self { species, body_type }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn localize(&self) -> Content {
|
||||||
|
Content::localized(match &self.species {
|
||||||
|
Species::Cyclops => "body-biped_large-cyclops",
|
||||||
|
Species::Wendigo => "body-biped_large-wendigo",
|
||||||
|
Species::Werewolf => "body-biped_large-werewolf",
|
||||||
|
_ => "body-generic",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Body> for super::Body {
|
impl From<Body> for super::Body {
|
||||||
|
@ -102,4 +102,14 @@ impl Distance {
|
|||||||
Distance::NextTo => "just around",
|
Distance::NextTo => "just around",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn localize_npc(&self) -> Content {
|
||||||
|
Content::localized(match self {
|
||||||
|
Self::VeryFar => "npc-speech-dist_very_far",
|
||||||
|
Self::Far => "npc-speech-dist_far",
|
||||||
|
Self::Ahead => "npc-speech-dist_ahead",
|
||||||
|
Self::Near => "npc-speech-dist_near",
|
||||||
|
Self::NextTo => "npc-speech-dist_near_to",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,6 +286,15 @@ pub enum ChunkResource {
|
|||||||
Ore, // Iron, copper, etc.
|
Ore, // Iron, copper, etc.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: the `serde(name = "...")` is to minimise the length of field
|
||||||
|
// identifiers for the sake of rtsim persistence
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub enum Role {
|
||||||
|
Civilised(Option<Profession>),
|
||||||
|
Wild,
|
||||||
|
Monster,
|
||||||
|
}
|
||||||
|
|
||||||
// Note: the `serde(name = "...")` is to minimise the length of field
|
// Note: the `serde(name = "...")` is to minimise the length of field
|
||||||
// identifiers for the sake of rtsim persistence
|
// identifiers for the sake of rtsim persistence
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
@ -80,6 +80,7 @@ impl TerrainChunkSize {
|
|||||||
pub trait CoordinateConversions {
|
pub trait CoordinateConversions {
|
||||||
fn wpos_to_cpos(&self) -> Self;
|
fn wpos_to_cpos(&self) -> Self;
|
||||||
fn cpos_to_wpos(&self) -> Self;
|
fn cpos_to_wpos(&self) -> Self;
|
||||||
|
fn cpos_to_wpos_center(&self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoordinateConversions for Vec2<i32> {
|
impl CoordinateConversions for Vec2<i32> {
|
||||||
@ -90,6 +91,13 @@ impl CoordinateConversions for Vec2<i32> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as i32) }
|
fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as i32) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn cpos_to_wpos_center(&self) -> Self {
|
||||||
|
self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
|
||||||
|
e * sz as i32 + sz as i32 / 2
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoordinateConversions for Vec2<f32> {
|
impl CoordinateConversions for Vec2<f32> {
|
||||||
@ -98,6 +106,13 @@ impl CoordinateConversions for Vec2<f32> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as f32) }
|
fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as f32) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn cpos_to_wpos_center(&self) -> Self {
|
||||||
|
self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
|
||||||
|
e * sz as f32 + sz as f32 / 2.0
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoordinateConversions for Vec2<f64> {
|
impl CoordinateConversions for Vec2<f64> {
|
||||||
@ -106,6 +121,13 @@ impl CoordinateConversions for Vec2<f64> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as f64) }
|
fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as f64) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn cpos_to_wpos_center(&self) -> Self {
|
||||||
|
self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
|
||||||
|
e * sz as f64 + sz as f64 / 2.0
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TerrainChunkMeta
|
// TerrainChunkMeta
|
||||||
|
@ -7,6 +7,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use common::resources::{Time, TimeOfDay};
|
use common::resources::{Time, TimeOfDay};
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
|
use itertools::Either;
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
use std::{any::Any, collections::VecDeque, marker::PhantomData, ops::ControlFlow};
|
use std::{any::Any, collections::VecDeque, marker::PhantomData, ops::ControlFlow};
|
||||||
use world::{IndexRef, World};
|
use world::{IndexRef, World};
|
||||||
@ -227,6 +228,22 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
{
|
{
|
||||||
Debug(self, mk_info, PhantomData)
|
Debug(self, mk_info, PhantomData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn l<Rhs>(self) -> Either<Self, Rhs>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Either::Left(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn r<Lhs>(self) -> Either<Lhs, Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Either::Right(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: 'static> Action<R> for Box<dyn Action<R>> {
|
impl<R: 'static> Action<R> for Box<dyn Action<R>> {
|
||||||
@ -246,11 +263,11 @@ impl<R: 'static> Action<R> for Box<dyn Action<R>> {
|
|||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { (**self).tick(ctx) }
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { (**self).tick(ctx) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: 'static, A: Action<R>, B: Action<R>> Action<R> for itertools::Either<A, B> {
|
impl<R: 'static, A: Action<R>, B: Action<R>> Action<R> for Either<A, B> {
|
||||||
fn is_same(&self, other: &Self) -> bool {
|
fn is_same(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(itertools::Either::Left(x), itertools::Either::Left(y)) => x.is_same(y),
|
(Either::Left(x), Either::Left(y)) => x.is_same(y),
|
||||||
(itertools::Either::Right(x), itertools::Either::Right(y)) => x.is_same(y),
|
(Either::Right(x), Either::Right(y)) => x.is_same(y),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,22 +276,22 @@ impl<R: 'static, A: Action<R>, B: Action<R>> Action<R> for itertools::Either<A,
|
|||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
match self {
|
match self {
|
||||||
itertools::Either::Left(x) => x.backtrace(bt),
|
Either::Left(x) => x.backtrace(bt),
|
||||||
itertools::Either::Right(x) => x.backtrace(bt),
|
Either::Right(x) => x.backtrace(bt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
itertools::Either::Left(x) => x.reset(),
|
Either::Left(x) => x.reset(),
|
||||||
itertools::Either::Right(x) => x.reset(),
|
Either::Right(x) => x.reset(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
|
||||||
match self {
|
match self {
|
||||||
itertools::Either::Left(x) => x.tick(ctx),
|
Either::Left(x) => x.tick(ctx),
|
||||||
itertools::Either::Right(x) => x.tick(ctx),
|
Either::Right(x) => x.tick(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ use common::{
|
|||||||
comp,
|
comp,
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
rtsim::{
|
rtsim::{
|
||||||
Actor, ChunkResource, FactionId, NpcAction, NpcActivity, Personality, SiteId, VehicleId,
|
Actor, ChunkResource, FactionId, NpcAction, NpcActivity, Personality, Role, SiteId,
|
||||||
|
VehicleId,
|
||||||
},
|
},
|
||||||
store::Id,
|
store::Id,
|
||||||
terrain::CoordinateConversions,
|
terrain::CoordinateConversions,
|
||||||
@ -96,7 +97,7 @@ pub struct Npc {
|
|||||||
pub wpos: Vec3<f32>,
|
pub wpos: Vec3<f32>,
|
||||||
|
|
||||||
pub body: comp::Body,
|
pub body: comp::Body,
|
||||||
pub profession: Option<Profession>,
|
pub role: Role,
|
||||||
pub home: Option<SiteId>,
|
pub home: Option<SiteId>,
|
||||||
pub faction: Option<FactionId>,
|
pub faction: Option<FactionId>,
|
||||||
pub riding: Option<Riding>,
|
pub riding: Option<Riding>,
|
||||||
@ -138,7 +139,7 @@ impl Clone for Npc {
|
|||||||
Self {
|
Self {
|
||||||
seed: self.seed,
|
seed: self.seed,
|
||||||
wpos: self.wpos,
|
wpos: self.wpos,
|
||||||
profession: self.profession.clone(),
|
role: self.role.clone(),
|
||||||
home: self.home,
|
home: self.home,
|
||||||
faction: self.faction,
|
faction: self.faction,
|
||||||
riding: self.riding.clone(),
|
riding: self.riding.clone(),
|
||||||
@ -162,14 +163,14 @@ impl Npc {
|
|||||||
pub const PERM_ENTITY_CONFIG: u32 = 1;
|
pub const PERM_ENTITY_CONFIG: u32 = 1;
|
||||||
const PERM_NAME: u32 = 0;
|
const PERM_NAME: u32 = 0;
|
||||||
|
|
||||||
pub fn new(seed: u32, wpos: Vec3<f32>, body: comp::Body) -> Self {
|
pub fn new(seed: u32, wpos: Vec3<f32>, body: comp::Body, role: Role) -> Self {
|
||||||
Self {
|
Self {
|
||||||
seed,
|
seed,
|
||||||
wpos,
|
wpos,
|
||||||
body,
|
body,
|
||||||
personality: Default::default(),
|
personality: Default::default(),
|
||||||
sentiments: Default::default(),
|
sentiments: Default::default(),
|
||||||
profession: None,
|
role,
|
||||||
home: None,
|
home: None,
|
||||||
faction: None,
|
faction: None,
|
||||||
riding: None,
|
riding: None,
|
||||||
@ -190,11 +191,15 @@ impl Npc {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: have a dedicated `NpcBuilder` type for this.
|
// // TODO: have a dedicated `NpcBuilder` type for this.
|
||||||
pub fn with_profession(mut self, profession: impl Into<Option<Profession>>) -> Self {
|
// pub fn with_profession(mut self, profession: impl Into<Option<Profession>>)
|
||||||
self.profession = profession.into();
|
// -> Self { if let Role::Humanoid(p) = &mut self.role {
|
||||||
self
|
// *p = profession.into();
|
||||||
}
|
// } else {
|
||||||
|
// panic!("Tried to assign profession {:?} to NPC, but has role {:?},
|
||||||
|
// which cannot have a profession", profession.into(), self.role); }
|
||||||
|
// self
|
||||||
|
// }
|
||||||
|
|
||||||
// TODO: have a dedicated `NpcBuilder` type for this.
|
// TODO: have a dedicated `NpcBuilder` type for this.
|
||||||
pub fn with_home(mut self, home: impl Into<Option<SiteId>>) -> Self {
|
pub fn with_home(mut self, home: impl Into<Option<SiteId>>) -> Self {
|
||||||
@ -232,6 +237,13 @@ impl Npc {
|
|||||||
// once we've decided that we want to
|
// once we've decided that we want to
|
||||||
pub fn get_name(&self) -> String { name::generate(&mut self.rng(Self::PERM_NAME)) }
|
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 => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cleanup(&mut self, reports: &Reports) {
|
pub fn cleanup(&mut self, reports: &Reports) {
|
||||||
// Clear old or superfluous sentiments
|
// Clear old or superfluous sentiments
|
||||||
// TODO: It might be worth giving more important NPCs a higher sentiment
|
// TODO: It might be worth giving more important NPCs a higher sentiment
|
||||||
|
@ -12,8 +12,8 @@ use common::{
|
|||||||
comp::{self, Body},
|
comp::{self, Body},
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
resources::TimeOfDay,
|
resources::TimeOfDay,
|
||||||
rtsim::{Personality, WorldSettings},
|
rtsim::{Personality, Role, WorldSettings},
|
||||||
terrain::TerrainChunkSize,
|
terrain::{CoordinateConversions, TerrainChunkSize},
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
@ -124,11 +124,7 @@ impl Data {
|
|||||||
rng.gen(),
|
rng.gen(),
|
||||||
rand_wpos(&mut rng, matches_buildings),
|
rand_wpos(&mut rng, matches_buildings),
|
||||||
random_humanoid(&mut rng),
|
random_humanoid(&mut rng),
|
||||||
)
|
Role::Civilised(Some(match rng.gen_range(0..20) {
|
||||||
.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,
|
0 => Profession::Hunter,
|
||||||
1 => Profession::Blacksmith,
|
1 => Profession::Blacksmith,
|
||||||
2 => Profession::Chef,
|
2 => Profession::Chef,
|
||||||
@ -137,7 +133,11 @@ impl Data {
|
|||||||
9..=10 => Profession::Herbalist,
|
9..=10 => Profession::Herbalist,
|
||||||
11..=16 => Profession::Guard,
|
11..=16 => Profession::Guard,
|
||||||
_ => Profession::Adventurer(rng.gen_range(0..=3)),
|
_ => Profession::Adventurer(rng.gen_range(0..=3)),
|
||||||
}),
|
})),
|
||||||
|
)
|
||||||
|
.with_faction(site.faction)
|
||||||
|
.with_home(site_id)
|
||||||
|
.with_personality(Personality::random(&mut rng)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -147,11 +147,11 @@ impl Data {
|
|||||||
rng.gen(),
|
rng.gen(),
|
||||||
rand_wpos(&mut rng, matches_buildings),
|
rand_wpos(&mut rng, matches_buildings),
|
||||||
random_humanoid(&mut rng),
|
random_humanoid(&mut rng),
|
||||||
|
Role::Civilised(Some(Profession::Cultist)),
|
||||||
)
|
)
|
||||||
.with_personality(Personality::random_evil(&mut rng))
|
.with_personality(Personality::random_evil(&mut rng))
|
||||||
.with_faction(site.faction)
|
.with_faction(site.faction)
|
||||||
.with_home(site_id)
|
.with_home(site_id),
|
||||||
.with_profession(Profession::Cultist),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,10 +163,10 @@ impl Data {
|
|||||||
rng.gen(),
|
rng.gen(),
|
||||||
rand_wpos(&mut rng, matches_plazas),
|
rand_wpos(&mut rng, matches_plazas),
|
||||||
random_humanoid(&mut rng),
|
random_humanoid(&mut rng),
|
||||||
|
Role::Civilised(Some(Profession::Merchant)),
|
||||||
)
|
)
|
||||||
.with_home(site_id)
|
.with_home(site_id)
|
||||||
.with_personality(Personality::random_good(&mut rng))
|
.with_personality(Personality::random_good(&mut rng)),
|
||||||
.with_profession(Profession::Merchant),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,9 +178,13 @@ impl Data {
|
|||||||
.create_vehicle(Vehicle::new(wpos, comp::body::ship::Body::DefaultAirship));
|
.create_vehicle(Vehicle::new(wpos, comp::body::ship::Body::DefaultAirship));
|
||||||
|
|
||||||
this.npcs.create_npc(
|
this.npcs.create_npc(
|
||||||
Npc::new(rng.gen(), wpos, random_humanoid(&mut rng))
|
Npc::new(
|
||||||
|
rng.gen(),
|
||||||
|
wpos,
|
||||||
|
random_humanoid(&mut rng),
|
||||||
|
Role::Civilised(Some(Profession::Captain)),
|
||||||
|
)
|
||||||
.with_home(site_id)
|
.with_home(site_id)
|
||||||
.with_profession(Profession::Captain)
|
|
||||||
.with_personality(Personality::random_good(&mut rng))
|
.with_personality(Personality::random_good(&mut rng))
|
||||||
.steering(vehicle_id),
|
.steering(vehicle_id),
|
||||||
);
|
);
|
||||||
@ -211,10 +215,41 @@ impl Data {
|
|||||||
rng.gen(),
|
rng.gen(),
|
||||||
rand_wpos(&mut rng),
|
rand_wpos(&mut rng),
|
||||||
Body::BirdLarge(comp::body::bird_large::Body::random_with(&mut rng, species)),
|
Body::BirdLarge(comp::body::bird_large::Body::random_with(&mut rng, species)),
|
||||||
|
Role::Wild,
|
||||||
)
|
)
|
||||||
.with_home(site_id),
|
.with_home(site_id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spawn monsters into the world
|
||||||
|
for _ in 0..100 {
|
||||||
|
// Try a few times to find a location that's not underwater
|
||||||
|
if let Some(pos) = (0..10)
|
||||||
|
.map(|_| world.sim().get_size().map(|sz| rng.gen_range(0..sz as i32)))
|
||||||
|
.find(|pos| world.sim().get(*pos).map_or(false, |c| !c.is_underwater()))
|
||||||
|
{
|
||||||
|
let wpos2d = pos.cpos_to_wpos_center();
|
||||||
|
let wpos = wpos2d
|
||||||
|
.map(|e| e as f32 + 0.5)
|
||||||
|
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0));
|
||||||
|
|
||||||
|
let species = match rng.gen_range(0..3) {
|
||||||
|
0 => comp::body::biped_large::Species::Cyclops,
|
||||||
|
1 => comp::body::biped_large::Species::Wendigo,
|
||||||
|
_ => comp::body::biped_large::Species::Werewolf,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.npcs.create_npc(Npc::new(
|
||||||
|
rng.gen(),
|
||||||
|
wpos,
|
||||||
|
Body::BipedLarge(comp::body::biped_large::Body::random_with(
|
||||||
|
&mut rng, &species,
|
||||||
|
)),
|
||||||
|
Role::Monster,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
info!("Generated {} rtsim NPCs.", this.npcs.len());
|
info!("Generated {} rtsim NPCs.", this.npcs.len());
|
||||||
|
|
||||||
this
|
this
|
||||||
|
@ -11,9 +11,12 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
astar::{Astar, PathResult},
|
astar::{Astar, PathResult},
|
||||||
comp::{compass::Direction, Content},
|
comp::{
|
||||||
|
compass::{Direction, Distance},
|
||||||
|
Content,
|
||||||
|
},
|
||||||
path::Path,
|
path::Path,
|
||||||
rtsim::{Actor, ChunkResource, Profession, SiteId},
|
rtsim::{Actor, ChunkResource, Profession, Role, SiteId},
|
||||||
spiral::Spiral2d,
|
spiral::Spiral2d,
|
||||||
store::Id,
|
store::Id,
|
||||||
terrain::{CoordinateConversions, SiteKindMeta, TerrainChunkSize},
|
terrain::{CoordinateConversions, SiteKindMeta, TerrainChunkSize},
|
||||||
@ -543,15 +546,16 @@ fn socialize() -> impl Action {
|
|||||||
now(|ctx| {
|
now(|ctx| {
|
||||||
// Skip most socialising actions if we're not loaded
|
// Skip most socialising actions if we're not loaded
|
||||||
if matches!(ctx.npc.mode, SimulationMode::Loaded) && ctx.rng.gen_bool(0.002) {
|
if matches!(ctx.npc.mode, SimulationMode::Loaded) && ctx.rng.gen_bool(0.002) {
|
||||||
|
// Sometimes dance
|
||||||
if ctx.rng.gen_bool(0.15) {
|
if ctx.rng.gen_bool(0.15) {
|
||||||
return Either::Left(
|
return just(|ctx| ctx.controller.do_dance())
|
||||||
just(|ctx| ctx.controller.do_dance())
|
|
||||||
.repeat()
|
.repeat()
|
||||||
.stop_if(timeout(6.0))
|
.stop_if(timeout(6.0))
|
||||||
.debug(|| "dancing")
|
.debug(|| "dancing")
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.boxed(),
|
.l()
|
||||||
);
|
.l();
|
||||||
|
// Talk to nearby NPCs
|
||||||
} else if let Some(other) = ctx
|
} else if let Some(other) = ctx
|
||||||
.state
|
.state
|
||||||
.data()
|
.data()
|
||||||
@ -559,8 +563,8 @@ fn socialize() -> impl Action {
|
|||||||
.nearby(Some(ctx.npc_id), ctx.npc.wpos, 8.0)
|
.nearby(Some(ctx.npc_id), ctx.npc.wpos, 8.0)
|
||||||
.choose(&mut ctx.rng)
|
.choose(&mut ctx.rng)
|
||||||
{
|
{
|
||||||
return Either::Left(
|
// Mention nearby sites
|
||||||
just(move |ctx| ctx.controller.say(other, if ctx.rng.gen_bool(0.3)
|
let comment = if ctx.rng.gen_bool(0.3)
|
||||||
&& let Some(current_site) = ctx.npc.current_site
|
&& let Some(current_site) = ctx.npc.current_site
|
||||||
&& let Some(current_site) = ctx.state.data().sites.get(current_site)
|
&& let Some(current_site) = ctx.state.data().sites.get(current_site)
|
||||||
&& let Some(mention_site) = current_site.nearby_sites_by_size.choose(&mut ctx.rng)
|
&& let Some(mention_site) = current_site.nearby_sites_by_size.choose(&mut ctx.rng)
|
||||||
@ -571,19 +575,32 @@ fn socialize() -> impl Action {
|
|||||||
Content::localized_with_args("npc-speech-tell_site", [
|
Content::localized_with_args("npc-speech-tell_site", [
|
||||||
("site", Content::Plain(mention_site_name)),
|
("site", Content::Plain(mention_site_name)),
|
||||||
("dir", Direction::from_dir(mention_site.wpos.as_() - ctx.npc.wpos.xy()).localize_npc()),
|
("dir", Direction::from_dir(mention_site.wpos.as_() - ctx.npc.wpos.xy()).localize_npc()),
|
||||||
|
("dist", Distance::from_length(mention_site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32).localize_npc()),
|
||||||
|
])
|
||||||
|
// Mention nearby monsters
|
||||||
|
} else if ctx.rng.gen_bool(0.3)
|
||||||
|
&& let Some(monster) = ctx.state.data().npcs
|
||||||
|
.values()
|
||||||
|
.filter(|other| matches!(&other.role, Role::Monster))
|
||||||
|
.min_by_key(|other| other.wpos.xy().distance(ctx.npc.wpos.xy()) as i32)
|
||||||
|
{
|
||||||
|
Content::localized_with_args("npc-speech-tell_monster", [
|
||||||
|
("body", monster.body.localize()),
|
||||||
|
("dir", Direction::from_dir(monster.wpos.xy() - ctx.npc.wpos.xy()).localize_npc()),
|
||||||
|
("dist", Distance::from_length(monster.wpos.xy().distance(ctx.npc.wpos.xy()) as i32).localize_npc()),
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
ctx.npc.personality.get_generic_comment(&mut ctx.rng)
|
ctx.npc.personality.get_generic_comment(&mut ctx.rng)
|
||||||
}))
|
};
|
||||||
// After greeting the actor, wait for a while
|
return just(move |ctx| ctx.controller.say(other, comment.clone()))
|
||||||
|
// After talking, wait for a while
|
||||||
.then(idle().repeat().stop_if(timeout(4.0)))
|
.then(idle().repeat().stop_if(timeout(4.0)))
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.boxed(),
|
.r().l();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Either::Right(idle())
|
idle().r()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -611,7 +628,7 @@ fn adventure() -> impl Action {
|
|||||||
.min_by_key(|(_, site)| site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32)
|
.min_by_key(|(_, site)| site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32)
|
||||||
.map(|(site_id, _)| site_id)
|
.map(|(site_id, _)| site_id)
|
||||||
{
|
{
|
||||||
let wait_time = if matches!(ctx.npc.profession, Some(Profession::Merchant)) {
|
let wait_time = if matches!(ctx.npc.profession(), Some(Profession::Merchant)) {
|
||||||
60.0 * 15.0
|
60.0 * 15.0
|
||||||
} else {
|
} else {
|
||||||
60.0 * 3.0
|
60.0 * 3.0
|
||||||
@ -729,7 +746,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if DayPeriod::from(ctx.time_of_day.0).is_dark()
|
if DayPeriod::from(ctx.time_of_day.0).is_dark()
|
||||||
&& !matches!(ctx.npc.profession, Some(Profession::Guard))
|
&& !matches!(ctx.npc.profession(), Some(Profession::Guard))
|
||||||
{
|
{
|
||||||
return important(
|
return important(
|
||||||
now(move |ctx| {
|
now(move |ctx| {
|
||||||
@ -769,7 +786,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
.debug(|| "find somewhere to sleep"),
|
.debug(|| "find somewhere to sleep"),
|
||||||
);
|
);
|
||||||
// Villagers with roles should perform those roles
|
// Villagers with roles should perform those roles
|
||||||
} else if matches!(ctx.npc.profession, Some(Profession::Herbalist)) && ctx.rng.gen_bool(0.8)
|
} else if matches!(ctx.npc.profession(), Some(Profession::Herbalist)) && ctx.rng.gen_bool(0.8)
|
||||||
{
|
{
|
||||||
if let Some(forest_wpos) = find_forest(ctx) {
|
if let Some(forest_wpos) = find_forest(ctx) {
|
||||||
return casual(
|
return casual(
|
||||||
@ -782,7 +799,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
.map(|_| ()),
|
.map(|_| ()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if matches!(ctx.npc.profession, Some(Profession::Hunter)) && ctx.rng.gen_bool(0.8) {
|
} else if matches!(ctx.npc.profession(), Some(Profession::Hunter)) && ctx.rng.gen_bool(0.8) {
|
||||||
if let Some(forest_wpos) = find_forest(ctx) {
|
if let Some(forest_wpos) = find_forest(ctx) {
|
||||||
return casual(
|
return casual(
|
||||||
just(|ctx| {
|
just(|ctx| {
|
||||||
@ -798,7 +815,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
.map(|_| ()),
|
.map(|_| ()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if matches!(ctx.npc.profession, Some(Profession::Guard)) && ctx.rng.gen_bool(0.7) {
|
} else if matches!(ctx.npc.profession(), Some(Profession::Guard)) && ctx.rng.gen_bool(0.7) {
|
||||||
if let Some(plaza_wpos) = choose_plaza(ctx, visiting_site) {
|
if let Some(plaza_wpos) = choose_plaza(ctx, visiting_site) {
|
||||||
return casual(
|
return casual(
|
||||||
travel_to_point(plaza_wpos, 0.4)
|
travel_to_point(plaza_wpos, 0.4)
|
||||||
@ -816,7 +833,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
.map(|_| ()),
|
.map(|_| ()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if matches!(ctx.npc.profession, Some(Profession::Merchant)) && ctx.rng.gen_bool(0.8)
|
} else if matches!(ctx.npc.profession(), Some(Profession::Merchant)) && ctx.rng.gen_bool(0.8)
|
||||||
{
|
{
|
||||||
return casual(
|
return casual(
|
||||||
just(|ctx| {
|
just(|ctx| {
|
||||||
@ -1049,7 +1066,7 @@ fn humanoid() -> impl Action {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let action = if matches!(
|
let action = if matches!(
|
||||||
ctx.npc.profession,
|
ctx.npc.profession(),
|
||||||
Some(Profession::Adventurer(_) | Profession::Merchant)
|
Some(Profession::Adventurer(_) | Profession::Merchant)
|
||||||
) {
|
) {
|
||||||
adventure().boxed()
|
adventure().boxed()
|
||||||
|
@ -5,7 +5,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{self, Body},
|
comp::{self, Body},
|
||||||
rtsim::{Actor, NpcAction, NpcActivity, Personality},
|
rtsim::{Actor, NpcAction, NpcActivity, Personality, Role},
|
||||||
terrain::CoordinateConversions,
|
terrain::CoordinateConversions,
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
@ -82,11 +82,15 @@ fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
|||||||
Body::Humanoid(comp::humanoid::Body::random_with(rng, species))
|
Body::Humanoid(comp::humanoid::Body::random_with(rng, species))
|
||||||
};
|
};
|
||||||
let npc_id = data.spawn_npc(
|
let npc_id = data.spawn_npc(
|
||||||
Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng))
|
Npc::new(
|
||||||
|
rng.gen(),
|
||||||
|
rand_wpos(&mut rng),
|
||||||
|
random_humanoid(&mut rng),
|
||||||
|
npc.role.clone(),
|
||||||
|
)
|
||||||
.with_personality(Personality::random(&mut rng))
|
.with_personality(Personality::random(&mut rng))
|
||||||
.with_home(site_id)
|
.with_home(site_id)
|
||||||
.with_faction(npc.faction)
|
.with_faction(npc.faction),
|
||||||
.with_profession(npc.profession.clone()),
|
|
||||||
);
|
);
|
||||||
Some((npc_id, site_id))
|
Some((npc_id, site_id))
|
||||||
} else {
|
} else {
|
||||||
@ -126,6 +130,7 @@ fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
|||||||
Body::BirdLarge(comp::body::bird_large::Body::random_with(
|
Body::BirdLarge(comp::body::bird_large::Body::random_with(
|
||||||
&mut rng, species,
|
&mut rng, species,
|
||||||
)),
|
)),
|
||||||
|
Role::Wild,
|
||||||
)
|
)
|
||||||
.with_home(site_id),
|
.with_home(site_id),
|
||||||
);
|
);
|
||||||
|
@ -45,7 +45,7 @@ use common::{
|
|||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
parse_cmd_args,
|
parse_cmd_args,
|
||||||
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay},
|
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay},
|
||||||
rtsim::Actor,
|
rtsim::{Actor, Role},
|
||||||
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
|
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
|
||||||
uid::{Uid, UidAllocator},
|
uid::{Uid, UidAllocator},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
@ -1250,7 +1250,7 @@ fn handle_rtsim_info(
|
|||||||
|
|
||||||
let _ = writeln!(&mut info, "-- General Information --");
|
let _ = writeln!(&mut info, "-- General Information --");
|
||||||
let _ = writeln!(&mut info, "Seed: {}", npc.seed);
|
let _ = writeln!(&mut info, "Seed: {}", npc.seed);
|
||||||
let _ = writeln!(&mut info, "Profession: {:?}", npc.profession);
|
let _ = writeln!(&mut info, "Role: {:?}", npc.role);
|
||||||
let _ = writeln!(&mut info, "Home: {:?}", npc.home);
|
let _ = writeln!(&mut info, "Home: {:?}", npc.home);
|
||||||
let _ = writeln!(&mut info, "Faction: {:?}", npc.faction);
|
let _ = writeln!(&mut info, "Faction: {:?}", npc.faction);
|
||||||
let _ = writeln!(&mut info, "Personality: {:?}", npc.personality);
|
let _ = writeln!(&mut info, "Personality: {:?}", npc.personality);
|
||||||
@ -1302,10 +1302,14 @@ fn handle_rtsim_npc(
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(idx, npc)| {
|
.filter(|(idx, npc)| {
|
||||||
let tags = [
|
let tags = [
|
||||||
npc.profession
|
npc.profession()
|
||||||
.as_ref()
|
|
||||||
.map(|p| format!("{:?}", p))
|
.map(|p| format!("{:?}", p))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
match &npc.role {
|
||||||
|
Role::Civilised(_) => "civilised".to_string(),
|
||||||
|
Role::Wild => "wild".to_string(),
|
||||||
|
Role::Monster => "monster".to_string(),
|
||||||
|
},
|
||||||
format!("{:?}", npc.mode),
|
format!("{:?}", npc.mode),
|
||||||
format!("{}", idx),
|
format!("{}", idx),
|
||||||
];
|
];
|
||||||
|
@ -138,13 +138,13 @@ fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo
|
|||||||
let pos = comp::Pos(npc.wpos);
|
let pos = comp::Pos(npc.wpos);
|
||||||
|
|
||||||
let mut rng = npc.rng(Npc::PERM_ENTITY_CONFIG);
|
let mut rng = npc.rng(Npc::PERM_ENTITY_CONFIG);
|
||||||
if let Some(ref profession) = npc.profession {
|
if let Some(profession) = npc.profession() {
|
||||||
let economy = npc.home.and_then(|home| {
|
let economy = npc.home.and_then(|home| {
|
||||||
let site = sites.get(home)?.world_site?;
|
let site = sites.get(home)?.world_site?;
|
||||||
index.sites.get(site).trade_information(site.id())
|
index.sites.get(site).trade_information(site.id())
|
||||||
});
|
});
|
||||||
|
|
||||||
let config_asset = humanoid_config(profession);
|
let config_asset = humanoid_config(&profession);
|
||||||
|
|
||||||
let entity_config = EntityConfig::from_asset_expect_owned(config_asset)
|
let entity_config = EntityConfig::from_asset_expect_owned(config_asset)
|
||||||
.with_body(BodyBuilder::Exact(npc.body));
|
.with_body(BodyBuilder::Exact(npc.body));
|
||||||
@ -156,9 +156,9 @@ fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo
|
|||||||
comp::Alignment::Npc
|
comp::Alignment::Npc
|
||||||
})
|
})
|
||||||
.with_economy(economy.as_ref())
|
.with_economy(economy.as_ref())
|
||||||
.with_lazy_loadout(profession_extra_loadout(npc.profession.as_ref()))
|
.with_lazy_loadout(profession_extra_loadout(Some(&profession)))
|
||||||
.with_alias(npc.get_name())
|
.with_alias(npc.get_name())
|
||||||
.with_agent_mark(profession_agent_mark(npc.profession.as_ref()))
|
.with_agent_mark(profession_agent_mark(Some(&profession)))
|
||||||
} else {
|
} else {
|
||||||
let config_asset = match npc.body {
|
let config_asset = match npc.body {
|
||||||
Body::BirdLarge(body) => match body.species {
|
Body::BirdLarge(body) => match body.species {
|
||||||
@ -169,14 +169,18 @@ fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo
|
|||||||
// which limits what species are used
|
// which limits what species are used
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
},
|
},
|
||||||
|
Body::BipedLarge(body) => match body.species {
|
||||||
|
comp::biped_large::Species::Cyclops => "common.entity.wild.aggressive.cyclops",
|
||||||
|
comp::biped_large::Species::Wendigo => "common.entity.wild.aggressive.wendigo",
|
||||||
|
comp::biped_large::Species::Werewolf => "common.entity.wild.aggressive.werewolf",
|
||||||
|
_ => unimplemented!(),
|
||||||
|
},
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
};
|
};
|
||||||
let entity_config = EntityConfig::from_asset_expect_owned(config_asset)
|
let entity_config = EntityConfig::from_asset_expect_owned(config_asset)
|
||||||
.with_body(BodyBuilder::Exact(npc.body));
|
.with_body(BodyBuilder::Exact(npc.body));
|
||||||
|
|
||||||
EntityInfo::at(pos.0)
|
EntityInfo::at(pos.0).with_entity_config(entity_config, Some(config_asset), &mut rng)
|
||||||
.with_entity_config(entity_config, Some(config_asset), &mut rng)
|
|
||||||
.with_alignment(comp::Alignment::Wild)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user