diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dbe722639..26d7c26394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/assets/common/npc_names.json b/assets/common/npc_names.json index 4fec15bf01..eade9f7ae3 100644 --- a/assets/common/npc_names.json +++ b/assets/common/npc_names.json @@ -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" } } diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 0a8a5217fe..f75acce151 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -63,6 +63,7 @@ pub struct AllBodies { impl core::ops::Index for AllBodies { type Output = BodyMeta; + #[inline] fn index(&self, index: NpcKind) -> &Self::Output { match index { NpcKind::Humanoid => &self.humanoid.body, diff --git a/common/src/comp/body/biped_large.rs b/common/src/comp/body/biped_large.rs index 2b70d721ff..e32e647b04 100644 --- a/common/src/comp/body/biped_large.rs +++ b/common/src/comp/body/biped_large.rs @@ -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 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 { pub giant: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { 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 core::ops::Index for AllSpecies { pub const ALL_SPECIES: [Species; 1] = [Species::Giant]; +impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies { + type Item = Species; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum BodyType { diff --git a/common/src/comp/body/bird_medium.rs b/common/src/comp/body/bird_medium.rs index b09d8cc562..477250c767 100644 --- a/common/src/comp/body/bird_medium.rs +++ b/common/src/comp/body/bird_medium.rs @@ -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 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 { pub peacock: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { 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 { + type Item = Species; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum BodyType { diff --git a/common/src/comp/body/critter.rs b/common/src/comp/body/critter.rs index 6849db47e7..d72145a7e9 100644 --- a/common/src/comp/body/critter.rs +++ b/common/src/comp/body/critter.rs @@ -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 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 { pub fungome: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { 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 { + type Item = Species; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum BodyType { diff --git a/common/src/comp/body/humanoid.rs b/common/src/comp/body/humanoid.rs index 9e77190436..3239141073 100644 --- a/common/src/comp/body/humanoid.rs +++ b/common/src/comp/body/humanoid.rs @@ -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 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 { pub undead: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Race> for AllSpecies { 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 { + type Item = Race; + + type IntoIter = impl Iterator; + + 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 diff --git a/common/src/comp/body/quadruped_medium.rs b/common/src/comp/body/quadruped_medium.rs index 94c42ebe1e..cfcf0aafc6 100644 --- a/common/src/comp/body/quadruped_medium.rs +++ b/common/src/comp/body/quadruped_medium.rs @@ -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 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 { pub tarasque: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { 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 { + type Item = Species; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum BodyType { diff --git a/common/src/comp/body/quadruped_small.rs b/common/src/comp/body/quadruped_small.rs index e3b86612d9..3b8dd5a26c 100644 --- a/common/src/comp/body/quadruped_small.rs +++ b/common/src/comp/body/quadruped_small.rs @@ -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 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 { pub holladon: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { 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 { + type Item = Species; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum BodyType { diff --git a/common/src/lib.rs b/common/src/lib.rs index 0595cc384d..45e2ce2404 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -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; diff --git a/common/src/npc.rs b/common/src/npc.rs index be378c9bc5..dd6f31dee9 100644 --- a/common/src/npc.rs +++ b/common/src/npc.rs @@ -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 { - 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 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::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 { + fn parse< + 'a, + B: Into + '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, + conv_func: for<'d> fn(&mut rand::rngs::ThreadRng, &'d Species) -> B, + ) -> Option + where + &'a SpeciesData: IntoIterator, + { + 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(()) + } +} diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b3b12a5ea3..eaa3d5e39c 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -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 } } -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::(); diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 33867ae075..1cbcfe62f3 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -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, + SpeciesData: for<'b> core::ops::Index<&'b Species, Output = npc::SpeciesNames>, >( - body_data: &comp::BodyData, + body_data: &'a comp::BodyData, species: Species, - ) -> &str { - &body_data.species[species].generic + ) -> &'a str { + &body_data.species[&species].generic } const SPAWN_NPCS: &'static [fn() -> ( String,