Per-speces NPC names!

Is also able to refactor some of the uglier code and introduces a
framework that (suitably extended) could be useful in removing
boilerplate elsewhere.
This commit is contained in:
Joshua Yanovski 2020-01-29 04:38:45 +01:00
parent 2d9e60e566
commit 3fa21b3dc7
19 changed files with 816 additions and 464 deletions

View File

@ -91,6 +91,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Various performance improvements to world generation.
- Nametags now a fixed size and shown in a limited range
- Non-humanoid skeletons now utilize configs for hotloading, and skeletal attributes.
- Names of NPCs spawned in the wild now include their species.
### Removed

View File

@ -1,5 +1,8 @@
{
"humanoid": [
"humanoid": {
"body": {
"keyword": "humanoid",
"names": [
"Adon",
"Agro",
"Arlo",
@ -100,8 +103,33 @@
"Zaden",
"Zagaroth",
"Zenner"
],
"wolf": [
]
},
"species": {
"danari": {
"generic": "Danari"
},
"dwarf": {
"generic": "Dwarf"
},
"elf": {
"generic": "Elf"
},
"human": {
"generic": "Human"
},
"orc": {
"generic": "Orc"
},
"undead": {
"generic": "Undead"
}
}
},
"quadruped_medium": {
"body": {
"keyword": "wolf",
"names": [
"Achak",
"Adalwolf",
"Akela",
@ -205,8 +233,39 @@
"Zeus",
"Ziva",
"Zylo"
],
"pig": [
]
},
"species": {
"wolf": {
"generic": "Wolf"
},
"saber": {
"generic": "Sabertooth Tiger"
},
"viper": {
"generic": "Lizard"
},
"tuskram": {
"generic": "Tusk Ram"
},
"alligator": {
"generic": "Alligator"
},
"monitor": {
"generic": "Monitor Lizard"
},
"lion": {
"generic": "Lion"
},
"tarasque": {
"generic": "Tarasque"
}
}
},
"quadruped_small": {
"body": {
"keyword": "pig",
"names": [
"Acorn",
"Adeline",
"Ajna",
@ -309,14 +368,108 @@
"Ziggy",
"Zoe",
"Zoinks"
],
"duck": [
"ducky"
],
"giant": [
"leroybrown"
],
"rat": [
"rat"
]
},
"species": {
"pig": {
"generic": "Pig"
},
"fox": {
"generic": "Fox"
},
"sheep": {
"generic": "Sheep"
},
"boar": {
"generic": "Boar"
},
"jackalope": {
"generic": "Jackalope"
},
"skunk": {
"generic": "Skunk"
},
"cat": {
"generic": "Cat"
},
"batfox": {
"generic": "Bat Fox"
},
"raccoon": {
"generic": "Raccoon"
},
"quokka": {
"generic": "Quokka"
},
"dodarock": {
"generic": "Dodarock"
},
"holladon": {
"generic": "Holladon"
}
}
},
"bird_medium": {
"body": {
"keyword": "duck",
"names": [
"Donald"
]
},
"species": {
"duck": {
"generic": "Duck"
},
"chicken": {
"generic": "Chicken"
},
"goose": {
"generic": "Goose"
},
"peacock": {
"generic": "Peacock"
}
}
},
"biped_large": {
"body": {
"keyword": "giant",
"names": [
"Leroy Brown"
]
},
"species": {
"giant": {
"generic": "Giant"
}
}
},
"critter": {
"body": {
"keyword": "rat",
"names": [
"Remy"
]
},
"species": {
"rat": {
"generic": "Rat"
},
"axolotl": {
"generic": "Axolotl"
},
"gecko": {
"generic": "Gecko"
},
"turtle": {
"generic": "Turtle"
},
"squirrel": {
"generic": "Squirrel"
},
"fungome": {
"generic": "Fungome"
}
}
}
}

View File

@ -9,6 +9,7 @@ use log::error;
use serde_json::Value;
use std::{
any::Any,
fmt,
fs::{self, File, ReadDir},
io::{BufReader, Read},
path::PathBuf,
@ -18,12 +19,27 @@ use std::{
/// The error returned by asset loading functions
#[derive(Debug, Clone)]
pub enum Error {
/// An internal error occurred.
Internal(Arc<dyn std::error::Error>),
/// An asset of a different type has already been loaded with this specifier.
InvalidType,
/// Asset does not exist.
NotFound(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Internal(err) => err.fmt(f),
Error::InvalidType => write!(
f,
"an asset of a different type has already been loaded with this specifier."
),
Error::NotFound(s) => write!(f, "{}", s),
}
}
}
impl From<Arc<dyn Any + 'static + Sync + Send>> for Error {
fn from(_: Arc<dyn Any + 'static + Sync + Send>) -> Self {
Error::InvalidType
@ -32,7 +48,7 @@ impl From<Arc<dyn Any + 'static + Sync + Send>> for Error {
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::NotFound(format!("{:?}", err))
Error::NotFound(format!("{}", err))
}
}
@ -135,7 +151,12 @@ pub fn load_cloned<A: Asset + Clone + 'static>(specifier: &str) -> Result<A, Err
/// let my_image = assets::load_expect::<DynamicImage>("core.ui.backgrounds.city");
/// ```
pub fn load_expect<A: Asset + 'static>(specifier: &str) -> Arc<A> {
load(specifier).unwrap_or_else(|_| panic!("Failed loading essential asset: {}", specifier))
load(specifier).unwrap_or_else(|err| {
panic!(
"Failed loading essential asset: {} (error={})",
specifier, err
)
})
}
/// Function used to load essential assets from the filesystem or the cache and return a clone. It will panic if the asset is not found.

View File

@ -10,8 +10,13 @@ pub mod object;
pub mod quadruped_medium;
pub mod quadruped_small;
use crate::{
assets::{self, Asset},
npc::NpcKind,
};
use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage;
use std::{fs::File, io::BufReader, sync::Arc};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
@ -29,6 +34,58 @@ pub enum Body {
Critter(critter::Body) = 10,
}
/// Data representing data generic to the body together with per-species data.
///
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)]
pub struct BodyData<BodyMeta, SpeciesData> {
/// Shared metadata for this whole body type.
pub body: BodyMeta,
/// All the metadata for species with this body type.
pub species: SpeciesData,
}
/// Metadata intended to be stored per-body, together with data intended to be stored for each
/// species for each body.
///
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)]
pub struct AllBodies<BodyMeta, SpeciesMeta> {
pub humanoid: BodyData<BodyMeta, humanoid::AllSpecies<SpeciesMeta>>,
pub quadruped_small: BodyData<BodyMeta, quadruped_small::AllSpecies<SpeciesMeta>>,
pub quadruped_medium: BodyData<BodyMeta, quadruped_medium::AllSpecies<SpeciesMeta>>,
pub bird_medium: BodyData<BodyMeta, bird_medium::AllSpecies<SpeciesMeta>>,
pub biped_large: BodyData<BodyMeta, biped_large::AllSpecies<SpeciesMeta>>,
pub critter: BodyData<BodyMeta, critter::AllSpecies<SpeciesMeta>>,
}
/// Can only retrieve body metadata by direct index.
impl<BodyMeta, SpeciesMeta> core::ops::Index<NpcKind> for AllBodies<BodyMeta, SpeciesMeta> {
type Output = BodyMeta;
fn index(&self, index: NpcKind) -> &Self::Output {
match index {
NpcKind::Humanoid => &self.humanoid.body,
NpcKind::Pig => &self.quadruped_small.body,
NpcKind::Wolf => &self.quadruped_medium.body,
NpcKind::Duck => &self.bird_medium.body,
NpcKind::Giant => &self.biped_large.body,
NpcKind::Rat => &self.critter.body,
}
}
}
impl<
BodyMeta: Send + Sync + for<'de> serde::Deserialize<'de>,
SpeciesMeta: Send + Sync + for<'de> serde::Deserialize<'de>,
> Asset for AllBodies<BodyMeta, SpeciesMeta>
{
const ENDINGS: &'static [&'static str] = &["json"];
fn parse(buf_reader: BufReader<File>) -> Result<Self, assets::Error> {
serde_json::de::from_reader(buf_reader).map_err(|e| assets::Error::Internal(Arc::new(e)))
}
}
impl Body {
pub fn is_humanoid(&self) -> bool {
match self {

View File

@ -1,7 +1,6 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub species: Species,
pub body_type: BodyType,
@ -21,6 +20,25 @@ impl Body {
pub enum Species {
Giant = 0,
}
/// Data representing per-species generic data.
///
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)]
pub struct AllSpecies<SpeciesMeta> {
pub giant: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output {
match index {
Species::Giant => &self.giant,
}
}
}
pub const ALL_SPECIES: [Species; 1] = [Species::Giant];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]

View File

@ -1,7 +1,6 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub species: Species,
pub body_type: BodyType,
@ -24,6 +23,31 @@ pub enum Species {
Goose = 2,
Peacock = 3,
}
/// Data representing per-species generic data.
///
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)]
pub struct AllSpecies<SpeciesMeta> {
pub duck: SpeciesMeta,
pub chicken: SpeciesMeta,
pub goose: SpeciesMeta,
pub peacock: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output {
match index {
Species::Duck => &self.duck,
Species::Chicken => &self.chicken,
Species::Goose => &self.goose,
Species::Peacock => &self.peacock,
}
}
}
pub const ALL_SPECIES: [Species; 4] = [
Species::Duck,
Species::Chicken,

View File

@ -1,7 +1,6 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub head: Head,
pub torso: Torso,

View File

@ -1,7 +1,6 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub species: Species,
pub body_type: BodyType,
@ -26,6 +25,33 @@ pub enum Species {
Squirrel = 4,
Fungome = 5,
}
/// Data representing per-species generic data.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AllSpecies<SpeciesMeta> {
pub rat: SpeciesMeta,
pub axolotl: SpeciesMeta,
pub gecko: SpeciesMeta,
pub turtle: SpeciesMeta,
pub squirrel: SpeciesMeta,
pub fungome: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output {
match index {
Species::Rat => &self.rat,
Species::Axolotl => &self.axolotl,
Species::Gecko => &self.gecko,
Species::Turtle => &self.turtle,
Species::Squirrel => &self.squirrel,
Species::Fungome => &self.fungome,
}
}
}
pub const ALL_SPECIES: [Species; 6] = [
Species::Rat,
Species::Axolotl,

View File

@ -1,7 +1,6 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub head: Head,
pub chest_front: ChestFront,

View File

@ -1,7 +1,6 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub head: Head,
pub torso: Torso,

View File

@ -1,7 +1,6 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub torso: Torso,
pub tail: Tail,

View File

@ -1,77 +0,0 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub head: Head,
pub shoulder: Shoulder,
pub chest: Chest,
pub hand: Hand,
pub belt: Belt,
pub pants: Pants,
pub foot: Foot,
}
impl Body {
pub fn random() -> Self {
let mut rng = thread_rng();
Self {
head: *(&ALL_HEADS).choose(&mut rng).unwrap(),
shoulder: *(&ALL_SHOULDERS).choose(&mut rng).unwrap(),
chest: *(&ALL_CHESTS).choose(&mut rng).unwrap(),
hand: *(&ALL_HANDS).choose(&mut rng).unwrap(),
belt: *(&ALL_BELTS).choose(&mut rng).unwrap(),
pants: *(&ALL_PANTS).choose(&mut rng).unwrap(),
foot: *(&ALL_FEET).choose(&mut rng).unwrap(),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Head {
Default,
}
const ALL_HEADS: [Head; 1] = [Head::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Shoulder {
Default,
}
const ALL_SHOULDERS: [Shoulder; 1] = [Shoulder::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Chest {
Default,
}
const ALL_CHESTS: [Chest; 1] = [Chest::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Hand {
Default,
}
const ALL_HANDS: [Hand; 1] = [Hand::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Belt {
Default,
}
const ALL_BELTS: [Belt; 1] = [Belt::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Pants {
Default,
}
const ALL_FEET: [Foot; 1] = [Foot::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Foot {
Default,
}

View File

@ -2,7 +2,6 @@ use rand::{seq::SliceRandom, thread_rng, Rng};
use vek::Rgb;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub race: Race,
pub body_type: BodyType,
@ -69,6 +68,35 @@ pub enum Race {
Orc = 4,
Undead = 5,
}
/// Data representing per-species generic data.
///
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AllSpecies<SpeciesMeta> {
pub danari: SpeciesMeta,
pub dwarf: SpeciesMeta,
pub elf: SpeciesMeta,
pub human: SpeciesMeta,
pub orc: SpeciesMeta,
pub undead: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Race> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Race) -> &Self::Output {
match index {
Race::Danari => &self.danari,
Race::Dwarf => &self.dwarf,
Race::Elf => &self.elf,
Race::Human => &self.human,
Race::Orc => &self.orc,
Race::Undead => &self.undead,
}
}
}
pub const ALL_RACES: [Race; 6] = [
Race::Danari,
Race::Dwarf,

View File

@ -1,7 +1,6 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub species: Species,
pub body_type: BodyType,
@ -28,6 +27,39 @@ pub enum Species {
Lion = 6,
Tarasque = 7,
}
/// Data representing per-species generic data.
///
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)]
pub struct AllSpecies<SpeciesMeta> {
pub wolf: SpeciesMeta,
pub saber: SpeciesMeta,
pub viper: SpeciesMeta,
pub tuskram: SpeciesMeta,
pub alligator: SpeciesMeta,
pub monitor: SpeciesMeta,
pub lion: SpeciesMeta,
pub tarasque: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output {
match index {
Species::Wolf => &self.wolf,
Species::Saber => &self.saber,
Species::Viper => &self.viper,
Species::Tuskram => &self.tuskram,
Species::Alligator => &self.alligator,
Species::Monitor => &self.monitor,
Species::Lion => &self.lion,
Species::Tarasque => &self.tarasque,
}
}
}
pub const ALL_SPECIES: [Species; 8] = [
Species::Wolf,
Species::Saber,

View File

@ -1,7 +1,6 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(C)]
pub struct Body {
pub species: Species,
pub body_type: BodyType,
@ -32,6 +31,47 @@ pub enum Species {
Dodarock = 10,
Holladon = 11,
}
/// Data representing per-species generic data.
///
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)]
pub struct AllSpecies<SpeciesMeta> {
pub pig: SpeciesMeta,
pub fox: SpeciesMeta,
pub sheep: SpeciesMeta,
pub boar: SpeciesMeta,
pub jackalope: SpeciesMeta,
pub skunk: SpeciesMeta,
pub cat: SpeciesMeta,
pub batfox: SpeciesMeta,
pub raccoon: SpeciesMeta,
pub quokka: SpeciesMeta,
pub dodarock: SpeciesMeta,
pub holladon: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output {
match index {
Species::Pig => &self.pig,
Species::Fox => &self.fox,
Species::Sheep => &self.sheep,
Species::Boar => &self.boar,
Species::Jackalope => &self.jackalope,
Species::Skunk => &self.skunk,
Species::Cat => &self.cat,
Species::Batfox => &self.batfox,
Species::Raccoon => &self.raccoon,
Species::Quokka => &self.quokka,
Species::Dodarock => &self.dodarock,
Species::Holladon => &self.holladon,
}
}
}
pub const ALL_SPECIES: [Species; 12] = [
Species::Pig,
Species::Fox,

View File

@ -19,7 +19,7 @@ pub use admin::Admin;
pub use agent::{Agent, Alignment};
pub use body::{
biped_large, bird_medium, bird_small, critter, dragon, fish_medium, fish_small, humanoid,
object, quadruped_medium, quadruped_small, Body,
object, quadruped_medium, quadruped_small, AllBodies, Body, BodyData,
};
pub use character_state::{ActionState, CharacterState, MovementState};
pub use controller::{

View File

@ -1,7 +1,6 @@
use crate::assets;
use crate::{assets, comp::AllBodies};
use lazy_static::lazy_static;
use rand::seq::SliceRandom;
use serde_json;
use std::str::FromStr;
use std::sync::Arc;
@ -15,50 +14,61 @@ pub enum NpcKind {
Rat,
}
impl NpcKind {
fn as_str(self) -> &'static str {
match self {
NpcKind::Humanoid => "humanoid",
NpcKind::Wolf => "wolf",
NpcKind::Pig => "pig",
NpcKind::Duck => "duck",
NpcKind::Giant => "giant",
NpcKind::Rat => "rat",
}
}
pub const ALL_NPCS: [NpcKind; 6] = [
NpcKind::Humanoid,
NpcKind::Wolf,
NpcKind::Pig,
NpcKind::Duck,
NpcKind::Giant,
NpcKind::Rat,
];
/// Body-specific NPC name metadata.
///
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)]
pub struct BodyNames {
/// The keyword used to refer to this body type (e.g. via the command console). Should be
/// unique per body type.
pub keyword: String,
/// A list of canonical names for NPCs with this body types (currently used when spawning this
/// kind of NPC from the console). Going forward, these names will likely be split up by
/// species.
pub names: Vec<String>,
}
/// Species-specific NPC name metadata.
///
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)]
pub struct SpeciesNames {
/// The generic name for NPCs of this species.
pub generic: String,
}
/// Type holding configuration data for NPC names.
pub type NpcNames = AllBodies<BodyNames, SpeciesNames>;
lazy_static! {
pub static ref NPC_NAMES: Arc<NpcNames> = assets::load_expect("common.npc_names");
}
impl FromStr for NpcKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
match s {
"humanoid" => Ok(NpcKind::Humanoid),
"wolf" => Ok(NpcKind::Wolf),
"pig" => Ok(NpcKind::Pig),
"duck" => Ok(NpcKind::Duck),
"giant" => Ok(NpcKind::Giant),
"rat" => Ok(NpcKind::Rat),
_ => Err(()),
}
let npc_names_json = &*NPC_NAMES;
ALL_NPCS
.iter()
.copied()
.find(|&npc| npc_names_json[npc].keyword == s)
.ok_or(())
}
}
lazy_static! {
static ref NPC_NAMES_JSON: Arc<serde_json::Value> = assets::load_expect("common.npc_names");
}
pub fn get_npc_name(npc_type: NpcKind) -> &'static str {
let BodyNames { keyword, names } = &NPC_NAMES[npc_type];
pub fn get_npc_name(npc_type: NpcKind) -> String {
let npc_names = NPC_NAMES_JSON
.get(npc_type.as_str())
.expect("accessing json using NPC type provided as key")
.as_array()
.expect("parsing accessed json value into an array");
let npc_name = npc_names
.choose(&mut rand::thread_rng())
.expect("getting a random NPC name")
.as_str()
.expect("parsing NPC name json value into a &str");
String::from(npc_name)
// If no pretty name is found, fall back to the keyword.
names.choose(&mut rand::thread_rng()).unwrap_or(keyword)
}

View File

@ -487,7 +487,7 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
.state
.create_npc(
pos,
comp::Stats::new(get_npc_name(id), body, None),
comp::Stats::new(get_npc_name(id).into(), body, None),
body,
)
.with(comp::Vel(vel))

View File

@ -6,6 +6,7 @@ use common::{
event::{EventBus, ServerEvent},
generation::EntityKind,
msg::ServerMsg,
npc::{self, NPC_NAMES},
state::TerrainChanges,
terrain::TerrainGrid,
};
@ -100,6 +101,15 @@ impl<'a> System<'a> for Sys {
if let EntityKind::Waypoint = entity.kind {
server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos));
} else {
fn get_npc_name<
Species,
SpeciesData: core::ops::Index<Species, Output = npc::SpeciesNames>,
>(
body_data: &comp::BodyData<npc::BodyNames, SpeciesData>,
species: Species,
) -> &str {
&body_data.species[species].generic
}
const SPAWN_NPCS: &'static [fn() -> (
String,
comp::Body,
@ -107,49 +117,58 @@ impl<'a> System<'a> for Sys {
comp::Alignment,
)] = &[
(|| {
let body = comp::humanoid::Body::random();
(
"Traveler".into(),
comp::Body::Humanoid(comp::humanoid::Body::random()),
format!(
"{} Traveler",
get_npc_name(&NPC_NAMES.humanoid, body.race)
),
comp::Body::Humanoid(body),
Some(assets::load_expect_cloned("common.items.weapons.staff_1")),
comp::Alignment::Npc,
)
}) as _,
(|| {
let body = comp::humanoid::Body::random();
(
"Bandit".into(),
comp::Body::Humanoid(comp::humanoid::Body::random()),
format!("{} Bandit", get_npc_name(&NPC_NAMES.humanoid, body.race)),
comp::Body::Humanoid(body),
Some(assets::load_expect_cloned("common.items.weapons.staff_1")),
comp::Alignment::Enemy,
)
}) as _,
(|| {
let body = comp::quadruped_medium::Body::random();
(
"Wolf".into(),
comp::Body::QuadrupedMedium(comp::quadruped_medium::Body::random()),
get_npc_name(&NPC_NAMES.quadruped_medium, body.species).into(),
comp::Body::QuadrupedMedium(body),
None,
comp::Alignment::Enemy,
)
}) as _,
(|| {
let body = comp::bird_medium::Body::random();
(
"Duck".into(),
comp::Body::BirdMedium(comp::bird_medium::Body::random()),
get_npc_name(&NPC_NAMES.bird_medium, body.species).into(),
comp::Body::BirdMedium(body),
None,
comp::Alignment::Wild,
)
}) as _,
(|| {
let body = comp::critter::Body::random();
(
"Rat".into(),
comp::Body::Critter(comp::critter::Body::random()),
get_npc_name(&NPC_NAMES.critter, body.species).into(),
comp::Body::Critter(body),
None,
comp::Alignment::Wild,
)
}) as _,
(|| {
let body = comp::quadruped_small::Body::random();
(
"Pig".into(),
comp::Body::QuadrupedSmall(comp::quadruped_small::Body::random()),
get_npc_name(&NPC_NAMES.quadruped_small, body.species).into(),
comp::Body::QuadrupedSmall(body),
None,
comp::Alignment::Wild,
)
@ -168,10 +187,14 @@ impl<'a> System<'a> for Sys {
if let EntityKind::Boss = entity.kind {
if rand::random::<f32>() < 0.65 {
body = comp::Body::Humanoid(comp::humanoid::Body::random());
let body_new = comp::humanoid::Body::random();
body = comp::Body::Humanoid(body_new);
alignment = comp::Alignment::Npc;
stats = comp::Stats::new(
"Fearless Giant".to_string(),
format!(
"Fearless Giant {}",
get_npc_name(&NPC_NAMES.humanoid, body_new.race)
),
body,
Some(assets::load_expect_cloned("common.items.weapons.hammer_1")),
);