mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sharp/spawn-single-species' into 'master'
Allow spawning individual species with /spawn. See merge request veloren/veloren!780
This commit is contained in:
commit
6424ca7947
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added zoomable and rotatable minimap
|
- Added zoomable and rotatable minimap
|
||||||
- Added rotating orientation marker to main-map
|
- Added rotating orientation marker to main-map
|
||||||
- Added daily Mac builds
|
- Added daily Mac builds
|
||||||
|
- Allow spawning individual pet species, not just generic body kinds.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Brighter / higher contrast main-map
|
- Brighter / higher contrast main-map
|
||||||
|
@ -107,28 +107,34 @@
|
|||||||
},
|
},
|
||||||
"species": {
|
"species": {
|
||||||
"danari": {
|
"danari": {
|
||||||
|
"keyword": "danari",
|
||||||
"generic": "Danari"
|
"generic": "Danari"
|
||||||
},
|
},
|
||||||
"dwarf": {
|
"dwarf": {
|
||||||
|
"keyword": "dwarf",
|
||||||
"generic": "Dwarf"
|
"generic": "Dwarf"
|
||||||
},
|
},
|
||||||
"elf": {
|
"elf": {
|
||||||
|
"keyword": "elf",
|
||||||
"generic": "Elf"
|
"generic": "Elf"
|
||||||
},
|
},
|
||||||
"human": {
|
"human": {
|
||||||
|
"keyword": "human",
|
||||||
"generic": "Human"
|
"generic": "Human"
|
||||||
},
|
},
|
||||||
"orc": {
|
"orc": {
|
||||||
|
"keyword": "orc",
|
||||||
"generic": "Orc"
|
"generic": "Orc"
|
||||||
},
|
},
|
||||||
"undead": {
|
"undead": {
|
||||||
|
"keyword": "undead",
|
||||||
"generic": "Undead"
|
"generic": "Undead"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quadruped_medium": {
|
"quadruped_medium": {
|
||||||
"body": {
|
"body": {
|
||||||
"keyword": "wolf",
|
"keyword": "quadruped_medium",
|
||||||
"names": [
|
"names": [
|
||||||
"Achak",
|
"Achak",
|
||||||
"Adalwolf",
|
"Adalwolf",
|
||||||
@ -237,34 +243,42 @@
|
|||||||
},
|
},
|
||||||
"species": {
|
"species": {
|
||||||
"wolf": {
|
"wolf": {
|
||||||
|
"keyword": "wolf",
|
||||||
"generic": "Wolf"
|
"generic": "Wolf"
|
||||||
},
|
},
|
||||||
"saber": {
|
"saber": {
|
||||||
|
"keyword": "sabertooth",
|
||||||
"generic": "Sabertooth Tiger"
|
"generic": "Sabertooth Tiger"
|
||||||
},
|
},
|
||||||
"viper": {
|
"viper": {
|
||||||
|
"keyword": "viper",
|
||||||
"generic": "Lizard"
|
"generic": "Lizard"
|
||||||
},
|
},
|
||||||
"tuskram": {
|
"tuskram": {
|
||||||
|
"keyword": "tuskram",
|
||||||
"generic": "Tusk Ram"
|
"generic": "Tusk Ram"
|
||||||
},
|
},
|
||||||
"alligator": {
|
"alligator": {
|
||||||
|
"keyword": "alligator",
|
||||||
"generic": "Alligator"
|
"generic": "Alligator"
|
||||||
},
|
},
|
||||||
"monitor": {
|
"monitor": {
|
||||||
|
"keyword": "monitor",
|
||||||
"generic": "Monitor Lizard"
|
"generic": "Monitor Lizard"
|
||||||
},
|
},
|
||||||
"lion": {
|
"lion": {
|
||||||
|
"keyword": "lion",
|
||||||
"generic": "Lion"
|
"generic": "Lion"
|
||||||
},
|
},
|
||||||
"tarasque": {
|
"tarasque": {
|
||||||
|
"keyword": "tarasque",
|
||||||
"generic": "Tarasque"
|
"generic": "Tarasque"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quadruped_small": {
|
"quadruped_small": {
|
||||||
"body": {
|
"body": {
|
||||||
"keyword": "pig",
|
"keyword": "quadruped_small",
|
||||||
"names": [
|
"names": [
|
||||||
"Acorn",
|
"Acorn",
|
||||||
"Adeline",
|
"Adeline",
|
||||||
@ -372,102 +386,125 @@
|
|||||||
},
|
},
|
||||||
"species": {
|
"species": {
|
||||||
"pig": {
|
"pig": {
|
||||||
|
"keyword": "pig",
|
||||||
"generic": "Pig"
|
"generic": "Pig"
|
||||||
},
|
},
|
||||||
"fox": {
|
"fox": {
|
||||||
|
"keyword": "fox",
|
||||||
"generic": "Fox"
|
"generic": "Fox"
|
||||||
},
|
},
|
||||||
"sheep": {
|
"sheep": {
|
||||||
|
"keyword": "sheep",
|
||||||
"generic": "Sheep"
|
"generic": "Sheep"
|
||||||
},
|
},
|
||||||
"boar": {
|
"boar": {
|
||||||
|
"keyword": "boar",
|
||||||
"generic": "Boar"
|
"generic": "Boar"
|
||||||
},
|
},
|
||||||
"jackalope": {
|
"jackalope": {
|
||||||
|
"keyword": "jackalope",
|
||||||
"generic": "Jackalope"
|
"generic": "Jackalope"
|
||||||
},
|
},
|
||||||
"skunk": {
|
"skunk": {
|
||||||
|
"keyword": "skunk",
|
||||||
"generic": "Skunk"
|
"generic": "Skunk"
|
||||||
},
|
},
|
||||||
"cat": {
|
"cat": {
|
||||||
|
"keyword": "cat",
|
||||||
"generic": "Cat"
|
"generic": "Cat"
|
||||||
},
|
},
|
||||||
"batfox": {
|
"batfox": {
|
||||||
|
"keyword": "batfox",
|
||||||
"generic": "Bat Fox"
|
"generic": "Bat Fox"
|
||||||
},
|
},
|
||||||
"raccoon": {
|
"raccoon": {
|
||||||
|
"keyword": "raccoon",
|
||||||
"generic": "Raccoon"
|
"generic": "Raccoon"
|
||||||
},
|
},
|
||||||
"quokka": {
|
"quokka": {
|
||||||
|
"keyword": "quokka",
|
||||||
"generic": "Quokka"
|
"generic": "Quokka"
|
||||||
},
|
},
|
||||||
"dodarock": {
|
"dodarock": {
|
||||||
|
"keyword": "dodarock",
|
||||||
"generic": "Dodarock"
|
"generic": "Dodarock"
|
||||||
},
|
},
|
||||||
"holladon": {
|
"holladon": {
|
||||||
|
"keyword": "holladon",
|
||||||
"generic": "Holladon"
|
"generic": "Holladon"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bird_medium": {
|
"bird_medium": {
|
||||||
"body": {
|
"body": {
|
||||||
"keyword": "duck",
|
"keyword": "bird_medium",
|
||||||
"names": [
|
"names": [
|
||||||
"Donald"
|
"Donald"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"species": {
|
"species": {
|
||||||
"duck": {
|
"duck": {
|
||||||
|
"keyword": "duck",
|
||||||
"generic": "Duck"
|
"generic": "Duck"
|
||||||
},
|
},
|
||||||
"chicken": {
|
"chicken": {
|
||||||
|
"keyword": "chicken",
|
||||||
"generic": "Chicken"
|
"generic": "Chicken"
|
||||||
},
|
},
|
||||||
"goose": {
|
"goose": {
|
||||||
|
"keyword": "goose",
|
||||||
"generic": "Goose"
|
"generic": "Goose"
|
||||||
},
|
},
|
||||||
"peacock": {
|
"peacock": {
|
||||||
|
"keyword": "peacock",
|
||||||
"generic": "Peacock"
|
"generic": "Peacock"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"biped_large": {
|
"biped_large": {
|
||||||
"body": {
|
"body": {
|
||||||
"keyword": "giant",
|
"keyword": "biped_large",
|
||||||
"names": [
|
"names": [
|
||||||
"Leroy Brown"
|
"Leroy Brown"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"species": {
|
"species": {
|
||||||
"giant": {
|
"giant": {
|
||||||
|
"keyword": "giant",
|
||||||
"generic": "Giant"
|
"generic": "Giant"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"critter": {
|
"critter": {
|
||||||
"body": {
|
"body": {
|
||||||
"keyword": "rat",
|
"keyword": "critter",
|
||||||
"names": [
|
"names": [
|
||||||
"Remy"
|
"Remy"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"species": {
|
"species": {
|
||||||
"rat": {
|
"rat": {
|
||||||
|
"keyword": "rat",
|
||||||
"generic": "Rat"
|
"generic": "Rat"
|
||||||
},
|
},
|
||||||
"axolotl": {
|
"axolotl": {
|
||||||
|
"keyword": "axolotl",
|
||||||
"generic": "Axolotl"
|
"generic": "Axolotl"
|
||||||
},
|
},
|
||||||
"gecko": {
|
"gecko": {
|
||||||
|
"keyword": "gecko",
|
||||||
"generic": "Gecko"
|
"generic": "Gecko"
|
||||||
},
|
},
|
||||||
"turtle": {
|
"turtle": {
|
||||||
|
"keyword": "turtle",
|
||||||
"generic": "Turtle"
|
"generic": "Turtle"
|
||||||
},
|
},
|
||||||
"squirrel": {
|
"squirrel": {
|
||||||
|
"keyword": "squirrel",
|
||||||
"generic": "Squirrel"
|
"generic": "Squirrel"
|
||||||
},
|
},
|
||||||
"fungome": {
|
"fungome": {
|
||||||
|
"keyword": "fungome",
|
||||||
"generic": "Fungome"
|
"generic": "Fungome"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ pub struct AllBodies<BodyMeta, SpeciesMeta> {
|
|||||||
impl<BodyMeta, SpeciesMeta> core::ops::Index<NpcKind> for AllBodies<BodyMeta, SpeciesMeta> {
|
impl<BodyMeta, SpeciesMeta> core::ops::Index<NpcKind> for AllBodies<BodyMeta, SpeciesMeta> {
|
||||||
type Output = BodyMeta;
|
type Output = BodyMeta;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn index(&self, index: NpcKind) -> &Self::Output {
|
fn index(&self, index: NpcKind) -> &Self::Output {
|
||||||
match index {
|
match index {
|
||||||
NpcKind::Humanoid => &self.humanoid.body,
|
NpcKind::Humanoid => &self.humanoid.body,
|
||||||
|
@ -10,11 +10,20 @@ impl Body {
|
|||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
|
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 }
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Species {
|
pub enum Species {
|
||||||
@ -29,10 +38,11 @@ pub struct AllSpecies<SpeciesMeta> {
|
|||||||
pub giant: 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;
|
type Output = SpeciesMeta;
|
||||||
|
|
||||||
fn index(&self, index: Species) -> &Self::Output {
|
#[inline]
|
||||||
|
fn index(&self, &index: &'a Species) -> &Self::Output {
|
||||||
match index {
|
match index {
|
||||||
Species::Giant => &self.giant,
|
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];
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum BodyType {
|
pub enum BodyType {
|
||||||
|
@ -10,11 +10,20 @@ impl Body {
|
|||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
|
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 }
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Species {
|
pub enum Species {
|
||||||
@ -35,10 +44,11 @@ pub struct AllSpecies<SpeciesMeta> {
|
|||||||
pub peacock: 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;
|
type Output = SpeciesMeta;
|
||||||
|
|
||||||
fn index(&self, index: Species) -> &Self::Output {
|
#[inline]
|
||||||
|
fn index(&self, &index: &'a Species) -> &Self::Output {
|
||||||
match index {
|
match index {
|
||||||
Species::Duck => &self.duck,
|
Species::Duck => &self.duck,
|
||||||
Species::Chicken => &self.chicken,
|
Species::Chicken => &self.chicken,
|
||||||
@ -55,6 +65,14 @@ pub const ALL_SPECIES: [Species; 4] = [
|
|||||||
Species::Peacock,
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum BodyType {
|
pub enum BodyType {
|
||||||
|
@ -10,11 +10,20 @@ impl Body {
|
|||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
|
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 }
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Species {
|
pub enum Species {
|
||||||
@ -37,10 +46,11 @@ pub struct AllSpecies<SpeciesMeta> {
|
|||||||
pub fungome: 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;
|
type Output = SpeciesMeta;
|
||||||
|
|
||||||
fn index(&self, index: Species) -> &Self::Output {
|
#[inline]
|
||||||
|
fn index(&self, &index: &'a Species) -> &Self::Output {
|
||||||
match index {
|
match index {
|
||||||
Species::Rat => &self.rat,
|
Species::Rat => &self.rat,
|
||||||
Species::Axolotl => &self.axolotl,
|
Species::Axolotl => &self.axolotl,
|
||||||
@ -61,6 +71,14 @@ pub const ALL_SPECIES: [Species; 6] = [
|
|||||||
Species::Fungome,
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum BodyType {
|
pub enum BodyType {
|
||||||
|
@ -24,19 +24,24 @@ impl Body {
|
|||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let race = *(&ALL_RACES).choose(&mut rng).unwrap();
|
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 {
|
Self {
|
||||||
race,
|
race,
|
||||||
body_type,
|
body_type,
|
||||||
chest: *(&ALL_CHESTS).choose(&mut rng).unwrap(),
|
chest: *(&ALL_CHESTS).choose(rng).unwrap(),
|
||||||
belt: *(&ALL_BELTS).choose(&mut rng).unwrap(),
|
belt: *(&ALL_BELTS).choose(rng).unwrap(),
|
||||||
pants: *(&ALL_PANTS).choose(&mut rng).unwrap(),
|
pants: *(&ALL_PANTS).choose(rng).unwrap(),
|
||||||
hand: *(&ALL_HANDS).choose(&mut rng).unwrap(),
|
hand: *(&ALL_HANDS).choose(rng).unwrap(),
|
||||||
foot: *(&ALL_FEET).choose(&mut rng).unwrap(),
|
foot: *(&ALL_FEET).choose(rng).unwrap(),
|
||||||
shoulder: *(&ALL_SHOULDERS).choose(&mut rng).unwrap(),
|
shoulder: *(&ALL_SHOULDERS).choose(rng).unwrap(),
|
||||||
hair_style: rng.gen_range(0, race.num_hair_styles(body_type)),
|
hair_style: rng.gen_range(0, race.num_hair_styles(body_type)),
|
||||||
beard: rng.gen_range(0, race.num_beards(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)),
|
accessory: rng.gen_range(0, race.num_accessories(body_type)),
|
||||||
hair_color: rng.gen_range(0, race.num_hair_colors()) as u8,
|
hair_color: rng.gen_range(0, race.num_hair_colors()) as u8,
|
||||||
skin: rng.gen_range(0, race.num_skin_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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Race {
|
pub enum Race {
|
||||||
@ -82,10 +91,11 @@ pub struct AllSpecies<SpeciesMeta> {
|
|||||||
pub undead: 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;
|
type Output = SpeciesMeta;
|
||||||
|
|
||||||
fn index(&self, index: Race) -> &Self::Output {
|
#[inline]
|
||||||
|
fn index(&self, &index: &'a Race) -> &Self::Output {
|
||||||
match index {
|
match index {
|
||||||
Race::Danari => &self.danari,
|
Race::Danari => &self.danari,
|
||||||
Race::Dwarf => &self.dwarf,
|
Race::Dwarf => &self.dwarf,
|
||||||
@ -106,6 +116,14 @@ pub const ALL_RACES: [Race; 6] = [
|
|||||||
Race::Undead,
|
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
|
// Hair Colors
|
||||||
pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 11] = [
|
pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 11] = [
|
||||||
(198, 169, 113), // Philosopher's Grey
|
(198, 169, 113), // Philosopher's Grey
|
||||||
|
@ -10,11 +10,20 @@ impl Body {
|
|||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
|
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 }
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Species {
|
pub enum Species {
|
||||||
@ -43,10 +52,11 @@ pub struct AllSpecies<SpeciesMeta> {
|
|||||||
pub tarasque: 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;
|
type Output = SpeciesMeta;
|
||||||
|
|
||||||
fn index(&self, index: Species) -> &Self::Output {
|
#[inline]
|
||||||
|
fn index(&self, &index: &'a Species) -> &Self::Output {
|
||||||
match index {
|
match index {
|
||||||
Species::Wolf => &self.wolf,
|
Species::Wolf => &self.wolf,
|
||||||
Species::Saber => &self.saber,
|
Species::Saber => &self.saber,
|
||||||
@ -71,6 +81,14 @@ pub const ALL_SPECIES: [Species; 8] = [
|
|||||||
Species::Tarasque,
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum BodyType {
|
pub enum BodyType {
|
||||||
|
@ -10,11 +10,20 @@ impl Body {
|
|||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
|
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 }
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Species {
|
pub enum Species {
|
||||||
@ -51,10 +60,11 @@ pub struct AllSpecies<SpeciesMeta> {
|
|||||||
pub holladon: 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;
|
type Output = SpeciesMeta;
|
||||||
|
|
||||||
fn index(&self, index: Species) -> &Self::Output {
|
#[inline]
|
||||||
|
fn index(&self, &index: &'a Species) -> &Self::Output {
|
||||||
match index {
|
match index {
|
||||||
Species::Pig => &self.pig,
|
Species::Pig => &self.pig,
|
||||||
Species::Fox => &self.fox,
|
Species::Fox => &self.fox,
|
||||||
@ -87,6 +97,14 @@ pub const ALL_SPECIES: [Species; 12] = [
|
|||||||
Species::Holladon,
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum BodyType {
|
pub enum BodyType {
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
#![type_length_limit = "1664759"]
|
#![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 serde_derive;
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use crate::{assets, comp::AllBodies};
|
use crate::{
|
||||||
|
assets,
|
||||||
|
comp::{self, AllBodies, Body},
|
||||||
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use std::{str::FromStr, sync::Arc};
|
use std::{str::FromStr, sync::Arc};
|
||||||
@ -41,6 +44,10 @@ pub struct BodyNames {
|
|||||||
/// NOTE: Deliberately don't (yet?) implement serialize.
|
/// NOTE: Deliberately don't (yet?) implement serialize.
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct SpeciesNames {
|
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.
|
/// The generic name for NPCs of this species.
|
||||||
pub generic: String,
|
pub generic: String,
|
||||||
}
|
}
|
||||||
@ -56,11 +63,11 @@ impl FromStr for NpcKind {
|
|||||||
type Err = ();
|
type Err = ();
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, ()> {
|
fn from_str(s: &str) -> Result<Self, ()> {
|
||||||
let npc_names_json = &*NPC_NAMES;
|
let npc_names = &*NPC_NAMES;
|
||||||
ALL_NPCS
|
ALL_NPCS
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.find(|&npc| npc_names_json[npc].keyword == s)
|
.find(|&npc| npc_names[npc].keyword == s)
|
||||||
.ok_or(())
|
.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.
|
// If no pretty name is found, fall back to the keyword.
|
||||||
names.choose(&mut rand::thread_rng()).unwrap_or(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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ use common::{
|
|||||||
assets, comp,
|
assets, comp,
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
msg::{PlayerListUpdate, ServerMsg},
|
msg::{PlayerListUpdate, ServerMsg},
|
||||||
npc::{get_npc_name, NpcKind},
|
npc::{self, get_npc_name},
|
||||||
state::TimeOfDay,
|
state::TimeOfDay,
|
||||||
sync::{Uid, WorldSyncExt},
|
sync::{Uid, WorldSyncExt},
|
||||||
terrain::TerrainChunkSize,
|
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) {
|
fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
|
||||||
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) {
|
match scan_fmt_some!(&args, action.arg_fmt, String, npc::NpcBody, String) {
|
||||||
(Some(opt_align), Some(id), opt_amount) => {
|
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount) => {
|
||||||
if let Some(alignment) = parse_alignment(entity, &opt_align) {
|
if let Some(alignment) = parse_alignment(entity, &opt_align) {
|
||||||
let amount = opt_amount
|
let amount = opt_amount
|
||||||
.and_then(|a| a.parse().ok())
|
.and_then(|a| a.parse().ok())
|
||||||
@ -487,7 +487,7 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
|
|||||||
10.0,
|
10.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
let body = kind_to_body(id);
|
let body = body();
|
||||||
|
|
||||||
let new_entity = server
|
let new_entity = server
|
||||||
.state
|
.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) {
|
fn handle_killnpcs(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) {
|
||||||
let ecs = server.state.ecs();
|
let ecs = server.state.ecs();
|
||||||
let mut stats = ecs.write_storage::<comp::Stats>();
|
let mut stats = ecs.write_storage::<comp::Stats>();
|
||||||
|
@ -103,13 +103,14 @@ impl<'a> System<'a> for Sys {
|
|||||||
server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos));
|
server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos));
|
||||||
} else {
|
} else {
|
||||||
fn get_npc_name<
|
fn get_npc_name<
|
||||||
|
'a,
|
||||||
Species,
|
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,
|
species: Species,
|
||||||
) -> &str {
|
) -> &'a str {
|
||||||
&body_data.species[species].generic
|
&body_data.species[&species].generic
|
||||||
}
|
}
|
||||||
const SPAWN_NPCS: &'static [fn() -> (
|
const SPAWN_NPCS: &'static [fn() -> (
|
||||||
String,
|
String,
|
||||||
|
Loading…
Reference in New Issue
Block a user