Allow spawning individual species with /spawn.

To get the old behavior, you now need to refer to the internal keywords
(like "bird_medium" or "quadruped_small") rather than the old friendly
names (like "duck" or "pig"), as the latter generate single species now.
This commit is contained in:
Joshua Yanovski 2020-01-29 17:56:28 +01:00
parent 636bf7d343
commit 8a9d4012fa
13 changed files with 329 additions and 53 deletions

View File

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added zoomable and rotatable minimap
- Added rotating orientation marker to main-map
- Added daily Mac builds
- Allow spawning individual pet species, not just generic body kinds.
### Changed
- Brighter / higher contrast main-map

View File

@ -107,28 +107,34 @@
},
"species": {
"danari": {
"keyword": "danari",
"generic": "Danari"
},
"dwarf": {
"keyword": "dwarf",
"generic": "Dwarf"
},
"elf": {
"keyword": "elf",
"generic": "Elf"
},
"human": {
"keyword": "human",
"generic": "Human"
},
"orc": {
"keyword": "orc",
"generic": "Orc"
},
"undead": {
"keyword": "undead",
"generic": "Undead"
}
}
},
"quadruped_medium": {
"body": {
"keyword": "wolf",
"keyword": "quadruped_medium",
"names": [
"Achak",
"Adalwolf",
@ -237,34 +243,42 @@
},
"species": {
"wolf": {
"keyword": "wolf",
"generic": "Wolf"
},
"saber": {
"keyword": "sabertooth",
"generic": "Sabertooth Tiger"
},
"viper": {
"keyword": "viper",
"generic": "Lizard"
},
"tuskram": {
"keyword": "tuskram",
"generic": "Tusk Ram"
},
"alligator": {
"keyword": "alligator",
"generic": "Alligator"
},
"monitor": {
"keyword": "monitor",
"generic": "Monitor Lizard"
},
"lion": {
"keyword": "lion",
"generic": "Lion"
},
"tarasque": {
"keyword": "tarasque",
"generic": "Tarasque"
}
}
},
"quadruped_small": {
"body": {
"keyword": "pig",
"keyword": "quadruped_small",
"names": [
"Acorn",
"Adeline",
@ -372,102 +386,125 @@
},
"species": {
"pig": {
"keyword": "pig",
"generic": "Pig"
},
"fox": {
"keyword": "fox",
"generic": "Fox"
},
"sheep": {
"keyword": "sheep",
"generic": "Sheep"
},
"boar": {
"keyword": "boar",
"generic": "Boar"
},
"jackalope": {
"keyword": "jackalope",
"generic": "Jackalope"
},
"skunk": {
"keyword": "skunk",
"generic": "Skunk"
},
"cat": {
"keyword": "cat",
"generic": "Cat"
},
"batfox": {
"keyword": "batfox",
"generic": "Bat Fox"
},
"raccoon": {
"keyword": "raccoon",
"generic": "Raccoon"
},
"quokka": {
"keyword": "quokka",
"generic": "Quokka"
},
"dodarock": {
"keyword": "dodarock",
"generic": "Dodarock"
},
"holladon": {
"keyword": "holladon",
"generic": "Holladon"
}
}
},
"bird_medium": {
"body": {
"keyword": "duck",
"keyword": "bird_medium",
"names": [
"Donald"
]
},
"species": {
"duck": {
"keyword": "duck",
"generic": "Duck"
},
"chicken": {
"keyword": "chicken",
"generic": "Chicken"
},
"goose": {
"keyword": "goose",
"generic": "Goose"
},
"peacock": {
"keyword": "peacock",
"generic": "Peacock"
}
}
},
"biped_large": {
"body": {
"keyword": "giant",
"keyword": "biped_large",
"names": [
"Leroy Brown"
]
},
"species": {
"giant": {
"keyword": "giant",
"generic": "Giant"
}
}
},
"critter": {
"body": {
"keyword": "rat",
"keyword": "critter",
"names": [
"Remy"
]
},
"species": {
"rat": {
"keyword": "rat",
"generic": "Rat"
},
"axolotl": {
"keyword": "axolotl",
"generic": "Axolotl"
},
"gecko": {
"keyword": "gecko",
"generic": "Gecko"
},
"turtle": {
"keyword": "turtle",
"generic": "Turtle"
},
"squirrel": {
"keyword": "squirrel",
"generic": "Squirrel"
},
"fungome": {
"keyword": "fungome",
"generic": "Fungome"
}
}

View File

@ -63,6 +63,7 @@ pub struct AllBodies<BodyMeta, SpeciesMeta> {
impl<BodyMeta, SpeciesMeta> core::ops::Index<NpcKind> for AllBodies<BodyMeta, SpeciesMeta> {
type Output = BodyMeta;
#[inline]
fn index(&self, index: NpcKind) -> &Self::Output {
match index {
NpcKind::Humanoid => &self.humanoid.body,

View File

@ -10,11 +10,20 @@ impl Body {
pub fn random() -> Self {
let mut rng = thread_rng();
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap();
Self::random_with(&mut rng, &species)
}
#[inline]
pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self {
let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap();
Self { species, body_type }
}
}
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::BipedLarge(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Species {
@ -29,10 +38,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub giant: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output {
#[inline]
fn index(&self, &index: &'a Species) -> &Self::Output {
match index {
Species::Giant => &self.giant,
}
@ -41,6 +51,14 @@ impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
pub const ALL_SPECIES: [Species; 1] = [Species::Giant];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Species;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum BodyType {

View File

@ -10,11 +10,20 @@ impl Body {
pub fn random() -> Self {
let mut rng = thread_rng();
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap();
Self::random_with(&mut rng, &species)
}
#[inline]
pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self {
let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap();
Self { species, body_type }
}
}
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::BirdMedium(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Species {
@ -35,10 +44,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub peacock: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output {
#[inline]
fn index(&self, &index: &'a Species) -> &Self::Output {
match index {
Species::Duck => &self.duck,
Species::Chicken => &self.chicken,
@ -55,6 +65,14 @@ pub const ALL_SPECIES: [Species; 4] = [
Species::Peacock,
];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Species;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum BodyType {

View File

@ -10,11 +10,20 @@ impl Body {
pub fn random() -> Self {
let mut rng = thread_rng();
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap();
Self::random_with(&mut rng, &species)
}
#[inline]
pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self {
let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap();
Self { species, body_type }
}
}
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::Critter(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Species {
@ -37,10 +46,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub fungome: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output {
#[inline]
fn index(&self, &index: &'a Species) -> &Self::Output {
match index {
Species::Rat => &self.rat,
Species::Axolotl => &self.axolotl,
@ -61,6 +71,14 @@ pub const ALL_SPECIES: [Species; 6] = [
Species::Fungome,
];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Species;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum BodyType {

View File

@ -24,19 +24,24 @@ impl Body {
pub fn random() -> Self {
let mut rng = thread_rng();
let race = *(&ALL_RACES).choose(&mut rng).unwrap();
let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap();
Self::random_with(&mut rng, &race)
}
#[inline]
pub fn random_with(rng: &mut impl Rng, &race: &Race) -> Self {
let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap();
Self {
race,
body_type,
chest: *(&ALL_CHESTS).choose(&mut rng).unwrap(),
belt: *(&ALL_BELTS).choose(&mut rng).unwrap(),
pants: *(&ALL_PANTS).choose(&mut rng).unwrap(),
hand: *(&ALL_HANDS).choose(&mut rng).unwrap(),
foot: *(&ALL_FEET).choose(&mut rng).unwrap(),
shoulder: *(&ALL_SHOULDERS).choose(&mut rng).unwrap(),
chest: *(&ALL_CHESTS).choose(rng).unwrap(),
belt: *(&ALL_BELTS).choose(rng).unwrap(),
pants: *(&ALL_PANTS).choose(rng).unwrap(),
hand: *(&ALL_HANDS).choose(rng).unwrap(),
foot: *(&ALL_FEET).choose(rng).unwrap(),
shoulder: *(&ALL_SHOULDERS).choose(rng).unwrap(),
hair_style: rng.gen_range(0, race.num_hair_styles(body_type)),
beard: rng.gen_range(0, race.num_beards(body_type)),
eyebrows: *(&ALL_EYEBROWS).choose(&mut rng).unwrap(),
eyebrows: *(&ALL_EYEBROWS).choose(rng).unwrap(),
accessory: rng.gen_range(0, race.num_accessories(body_type)),
hair_color: rng.gen_range(0, race.num_hair_colors()) as u8,
skin: rng.gen_range(0, race.num_skin_colors()) as u8,
@ -58,6 +63,10 @@ impl Body {
}
}
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::Humanoid(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Race {
@ -82,10 +91,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub undead: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Race> for AllSpecies<SpeciesMeta> {
impl<'a, SpeciesMeta> core::ops::Index<&'a Race> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Race) -> &Self::Output {
#[inline]
fn index(&self, &index: &'a Race) -> &Self::Output {
match index {
Race::Danari => &self.danari,
Race::Dwarf => &self.dwarf,
@ -106,6 +116,14 @@ pub const ALL_RACES: [Race; 6] = [
Race::Undead,
];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Race;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_RACES.iter().copied() }
}
// Hair Colors
pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 11] = [
(198, 169, 113), // Philosopher's Grey

View File

@ -10,11 +10,20 @@ impl Body {
pub fn random() -> Self {
let mut rng = thread_rng();
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap();
Self::random_with(&mut rng, &species)
}
#[inline]
pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self {
let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap();
Self { species, body_type }
}
}
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::QuadrupedMedium(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Species {
@ -43,10 +52,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub tarasque: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output {
#[inline]
fn index(&self, &index: &'a Species) -> &Self::Output {
match index {
Species::Wolf => &self.wolf,
Species::Saber => &self.saber,
@ -71,6 +81,14 @@ pub const ALL_SPECIES: [Species; 8] = [
Species::Tarasque,
];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Species;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum BodyType {

View File

@ -10,11 +10,20 @@ impl Body {
pub fn random() -> Self {
let mut rng = thread_rng();
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap();
Self::random_with(&mut rng, &species)
}
#[inline]
pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self {
let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap();
Self { species, body_type }
}
}
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::QuadrupedSmall(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Species {
@ -51,10 +60,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub holladon: SpeciesMeta,
}
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output {
#[inline]
fn index(&self, &index: &'a Species) -> &Self::Output {
match index {
Species::Pig => &self.pig,
Species::Fox => &self.fox,
@ -87,6 +97,14 @@ pub const ALL_SPECIES: [Species; 12] = [
Species::Holladon,
];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Species;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum BodyType {

View File

@ -1,6 +1,12 @@
#![deny(unsafe_code)]
#![type_length_limit = "1664759"]
#![feature(trait_alias, arbitrary_enum_discriminant, label_break_value)]
#![feature(
arbitrary_enum_discriminant,
bool_to_option,
label_break_value,
trait_alias,
type_alias_impl_trait
)]
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate log;

View File

@ -1,4 +1,7 @@
use crate::{assets, comp::AllBodies};
use crate::{
assets,
comp::{self, AllBodies, Body},
};
use lazy_static::lazy_static;
use rand::seq::SliceRandom;
use std::{str::FromStr, sync::Arc};
@ -41,6 +44,10 @@ pub struct BodyNames {
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)]
pub struct SpeciesNames {
/// The keyword used to refer to this species (e.g. via the command
/// console). Should be unique per species and distinct from all body
/// types (maybe in the future, it will just be unique per body type).
pub keyword: String,
/// The generic name for NPCs of this species.
pub generic: String,
}
@ -56,11 +63,11 @@ impl FromStr for NpcKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
let npc_names_json = &*NPC_NAMES;
let npc_names = &*NPC_NAMES;
ALL_NPCS
.iter()
.copied()
.find(|&npc| npc_names_json[npc].keyword == s)
.find(|&npc| npc_names[npc].keyword == s)
.ok_or(())
}
}
@ -71,3 +78,129 @@ pub fn get_npc_name(npc_type: NpcKind) -> &'static str {
// If no pretty name is found, fall back to the keyword.
names.choose(&mut rand::thread_rng()).unwrap_or(keyword)
}
/// Randomly generates a body associated with this NPC kind.
pub fn kind_to_body(kind: NpcKind) -> Body {
match kind {
NpcKind::Humanoid => comp::humanoid::Body::random().into(),
NpcKind::Pig => comp::quadruped_small::Body::random().into(),
NpcKind::Wolf => comp::quadruped_medium::Body::random().into(),
NpcKind::Duck => comp::bird_medium::Body::random().into(),
NpcKind::Giant => comp::biped_large::Body::random().into(),
NpcKind::Rat => comp::critter::Body::random().into(),
}
}
/// A combination of an NpcKind (representing an outer species to generate), and
/// a function that generates a fresh Body of a species that is part of that
/// NpcKind each time it's called. The reason things are done this way is that
/// when parsing spawn strings, we'd like to be able to randomize features that
/// haven't already been specified; for instance, if no species is specified we
/// should randomize species, while if a species is specified we can still
/// randomize other attributes like gender or clothing.
///
/// TODO: Now that we return a closure, consider having the closure accept a
/// source of randomness explicitly, rather than always using ThreadRng.
pub struct NpcBody(pub NpcKind, pub Box<dyn FnMut() -> Body>);
impl FromStr for NpcBody {
type Err = ();
/// Get an NPC kind from a string. If a body kind is matched without an
/// associated species, generate the species randmly within it; if an
/// explicit species is found, generate a random member of the species;
/// otherwise, return Err(()).
fn from_str(s: &str) -> Result<Self, ()> { Self::from_str_with(s, kind_to_body) }
}
impl NpcBody {
/// If there is an exact name match for a body kind, call kind_to_body on
/// it. Otherwise, if an explicit species is found, generate a random
/// member of the species; otherwise, return Err(()).
pub fn from_str_with(s: &str, kind_to_body: fn(NpcKind) -> Body) -> Result<Self, ()> {
fn parse<
'a,
B: Into<Body> + 'static,
// NOTE: Should be cheap in all cases, but if it weren't we should revamp the indexing
// method to take references instead of owned values.
Species: 'static,
BodyMeta,
SpeciesData: for<'b> core::ops::Index<&'b Species, Output = SpeciesNames>,
>(
s: &str,
npc_kind: NpcKind,
body_data: &'a comp::BodyData<BodyMeta, SpeciesData>,
conv_func: for<'d> fn(&mut rand::rngs::ThreadRng, &'d Species) -> B,
) -> Option<NpcBody>
where
&'a SpeciesData: IntoIterator<Item = Species>,
{
let npc_names = &body_data.species;
body_data
.species
.into_iter()
.find(|species| npc_names[species].keyword == s)
.map(|species| {
NpcBody(
npc_kind,
Box::new(move || conv_func(&mut rand::thread_rng(), &species).into()),
)
})
}
let npc_names = &NPC_NAMES;
// First, parse npc kind names.
NpcKind::from_str(s)
.map(|kind| NpcBody(kind, Box::new(move || kind_to_body(kind))))
.ok()
// Otherwise, npc kind names aren't sufficient; we parse species names instead.
.or_else(|| {
parse(
s,
NpcKind::Humanoid,
&npc_names.humanoid,
comp::humanoid::Body::random_with,
)
})
.or_else(|| {
parse(
s,
NpcKind::Pig,
&npc_names.quadruped_small,
comp::quadruped_small::Body::random_with,
)
})
.or_else(|| {
parse(
s,
NpcKind::Wolf,
&npc_names.quadruped_medium,
comp::quadruped_medium::Body::random_with,
)
})
.or_else(|| {
parse(
s,
NpcKind::Duck,
&npc_names.bird_medium,
comp::bird_medium::Body::random_with,
)
})
.or_else(|| {
parse(
s,
NpcKind::Giant,
&npc_names.biped_large,
comp::biped_large::Body::random_with,
)
})
.or_else(|| {
parse(
s,
NpcKind::Rat,
&npc_names.critter,
comp::critter::Body::random_with,
)
})
.ok_or(())
}
}

View File

@ -8,7 +8,7 @@ use common::{
assets, comp,
event::{EventBus, ServerEvent},
msg::{PlayerListUpdate, ServerMsg},
npc::{get_npc_name, NpcKind},
npc::{self, get_npc_name},
state::TimeOfDay,
sync::{Uid, WorldSyncExt},
terrain::TerrainChunkSize,
@ -462,8 +462,8 @@ fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &Chat
}
fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) {
(Some(opt_align), Some(id), opt_amount) => {
match scan_fmt_some!(&args, action.arg_fmt, String, npc::NpcBody, String) {
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount) => {
if let Some(alignment) = parse_alignment(entity, &opt_align) {
let amount = opt_amount
.and_then(|a| a.parse().ok())
@ -487,7 +487,7 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
10.0,
);
let body = kind_to_body(id);
let body = body();
let new_entity = server
.state
@ -600,17 +600,6 @@ fn parse_alignment(owner: EcsEntity, alignment: &str) -> Option<comp::Alignment>
}
}
fn kind_to_body(kind: NpcKind) -> comp::Body {
match kind {
NpcKind::Humanoid => comp::Body::Humanoid(comp::humanoid::Body::random()),
NpcKind::Pig => comp::Body::QuadrupedSmall(comp::quadruped_small::Body::random()),
NpcKind::Wolf => comp::Body::QuadrupedMedium(comp::quadruped_medium::Body::random()),
NpcKind::Duck => comp::Body::BirdMedium(comp::bird_medium::Body::random()),
NpcKind::Giant => comp::Body::BipedLarge(comp::biped_large::Body::random()),
NpcKind::Rat => comp::Body::Critter(comp::critter::Body::random()),
}
}
fn handle_killnpcs(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) {
let ecs = server.state.ecs();
let mut stats = ecs.write_storage::<comp::Stats>();

View File

@ -103,13 +103,14 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos));
} else {
fn get_npc_name<
'a,
Species,
SpeciesData: core::ops::Index<Species, Output = npc::SpeciesNames>,
SpeciesData: for<'b> core::ops::Index<&'b Species, Output = npc::SpeciesNames>,
>(
body_data: &comp::BodyData<npc::BodyNames, SpeciesData>,
body_data: &'a comp::BodyData<npc::BodyNames, SpeciesData>,
species: Species,
) -> &str {
&body_data.species[species].generic
) -> &'a str {
&body_data.species[&species].generic
}
const SPAWN_NPCS: &'static [fn() -> (
String,