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 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

View File

@ -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"
} }
} }

View File

@ -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,

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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;

View File

@ -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(())
}
}

View File

@ -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>();

View File

@ -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,