mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
parent
636bf7d343
commit
8a9d4012fa
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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>();
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user