Implement gender, kind of

This commit is contained in:
juliancoffee 2024-02-03 02:05:38 +02:00
parent 273ac621a9
commit eeb7cab8c3
3 changed files with 103 additions and 66 deletions

View File

@ -2911,7 +2911,7 @@ impl Client {
) )
.join() .join()
.find(|(_, u)| u == &uid) .find(|(_, u)| u == &uid)
.map(|(c, _)| c.original_body.gender()) .and_then(|(c, _)| c.original_body.humanoid_gender())
}; };
let mut add_data_of = |uid| { let mut add_data_of = |uid| {

View File

@ -198,7 +198,14 @@ impl<
const EXTENSION: &'static str = "ron"; const EXTENSION: &'static str = "ron";
} }
// Grammatical gender // Semantic gender aka body_type
//
// Should be used for localization with extreme care.
// For basically everything except *maybe* humanoids, it's simply wrong to
// assume that this may be used as grammatical gender.
//
// TODO: remove this and instead add GUI for players to choose preferred gender.
// Read a comment for `gender_str` in voxygen/i18n-helpers/src/lib.rs.
pub enum Gender { pub enum Gender {
Masculine, Masculine,
Feminine, Feminine,
@ -1359,71 +1366,14 @@ impl Body {
} }
} }
pub fn gender(&self) -> Gender { // Read comment on `Gender` for more
pub fn humanoid_gender(&self) -> Option<Gender> {
match self { match self {
Body::Humanoid(b) => match b.body_type { Body::Humanoid(b) => match b.body_type {
humanoid::BodyType::Male => Gender::Masculine, humanoid::BodyType::Male => Some(Gender::Masculine),
humanoid::BodyType::Female => Gender::Feminine, humanoid::BodyType::Female => Some(Gender::Feminine),
},
Body::QuadrupedSmall(b) => match b.body_type {
quadruped_small::BodyType::Male => Gender::Masculine,
quadruped_small::BodyType::Female => Gender::Feminine,
},
Body::QuadrupedMedium(b) => match b.body_type {
quadruped_medium::BodyType::Male => Gender::Masculine,
quadruped_medium::BodyType::Female => Gender::Feminine,
},
Body::BirdMedium(b) => match b.body_type {
bird_medium::BodyType::Male => Gender::Masculine,
bird_medium::BodyType::Female => Gender::Feminine,
},
Body::FishMedium(b) => match b.body_type {
fish_medium::BodyType::Male => Gender::Masculine,
fish_medium::BodyType::Female => Gender::Feminine,
},
Body::Dragon(b) => match b.body_type {
dragon::BodyType::Male => Gender::Masculine,
dragon::BodyType::Female => Gender::Feminine,
},
Body::BirdLarge(b) => match b.body_type {
bird_large::BodyType::Male => Gender::Masculine,
bird_large::BodyType::Female => Gender::Feminine,
},
Body::FishSmall(b) => match b.body_type {
fish_small::BodyType::Male => Gender::Masculine,
fish_small::BodyType::Female => Gender::Feminine,
},
Body::BipedLarge(b) => match b.body_type {
biped_large::BodyType::Male => Gender::Masculine,
biped_large::BodyType::Female => Gender::Feminine,
},
Body::BipedSmall(b) => match b.body_type {
biped_small::BodyType::Male => Gender::Masculine,
biped_small::BodyType::Female => Gender::Feminine,
},
Body::Golem(b) => match b.body_type {
golem::BodyType::Male => Gender::Masculine,
golem::BodyType::Female => Gender::Feminine,
},
Body::Theropod(b) => match b.body_type {
theropod::BodyType::Male => Gender::Masculine,
theropod::BodyType::Female => Gender::Feminine,
},
Body::QuadrupedLow(b) => match b.body_type {
quadruped_low::BodyType::Male => Gender::Masculine,
quadruped_low::BodyType::Female => Gender::Feminine,
},
Body::Arthropod(b) => match b.body_type {
arthropod::BodyType::Male => Gender::Masculine,
arthropod::BodyType::Female => Gender::Feminine,
},
Body::Crustacean(b) => match b.body_type {
crustacean::BodyType::Male => Gender::Masculine,
crustacean::BodyType::Female => Gender::Feminine,
},
Body::ItemDrop(_) | Body::Object(_) | Body::Ship(_) => {
panic!("we don't care about item gender")
}, },
_ => None,
} }
} }
} }

View File

@ -3,6 +3,7 @@ use std::borrow::Cow;
use common::{ use common::{
comp::{ comp::{
body::Gender,
chat::{KillSource, KillType}, chat::{KillSource, KillType},
BuffKind, ChatMsg, ChatType, Content, BuffKind, ChatMsg, ChatType, Content,
}, },
@ -38,8 +39,91 @@ pub fn localize_chat_message(
// This includes every notification-like message, like death. // This includes every notification-like message, like death.
let name_format = |uid: &Uid| name_format_or_complex(false, uid); let name_format = |uid: &Uid| name_format_or_complex(false, uid);
// FIXME: this shouldn't pass the review! // This is a hack, kind of.
let gender_str = |uid: &Uid| "he".to_owned(); //
// Current implementation just checks if our player is humanoid, and if so,
// we take the body_type of its character and assume it as grammatical gender.
//
// In short,
// body_type of character
// -> sex of character
// -> gender of player.
// -> grammatical gender for use in messages.
//
// This is obviously, wrong, but it's good enough approximation, after all,
// players do choose their characters.
//
// In the future, we will want special GUI where players can specify their
// gender (and change it!), and we do want to handle more genders than just
// male and female.
//
// Ideally, the system should handle following (if we exclude plurals):
// - Female
// - Male
// - Neuter (or fallback Female)
// - Neuter (or fallback Male)
// - Intermediate (or fallback Female)
// - Intermediate (or fallback Male)
// and maybe more, not sure.
//
// What is supported by language and what is not, as well as maybe how to
// convert genders into strings to match, should go into _manifest.ron file
//
// So let's say language only supports male and female, we will convert all
// genders to these, using some fallbacks, and pass it.
//
// If the language can represent Female, Male and Neuter, we can pass these.
//
// Exact design of such a complex system is honestly up to discussion.
let gender_str = |uid: &Uid| match info.gender.get(uid) {
Some(Gender::Feminine) => "she".to_owned(),
Some(Gender::Masculine) => "he".to_owned(),
None => "??".to_owned(),
};
// This is where the most fun begings.
//
// Unlike people, "items" can have their own gender, which is completely
// independent of everything, including common sense.
//
// For example, word "masculinity" can be feminine in some languages,
// as well as word "boy", and vice versa.
//
// So we can't rely on body_type, at all. And even if we did try, our
// body_type isn't even always represents animal sex, there are some
// animals that use body_type to represent their kind, like different
// types of Fox ("male" fox is forest, "female" is arctic one).
// And what about Mindflayer? They do have varied body_type, but do they
// even have concept of gender?
//
// Our use case is probably less cryptic, after all we are limited by
// mostly sentient things, but that doesn't help at all.
//
// Common example is word "spider", which can be feminine in one languages
// and masculine in other, and sometimes even neuter.
//
// Oh, and I want to add that we are talking about grammatical genders, and
// languages define their own grammar. There are languages that have more
// than three grammatical genders, there are languages that don't have
// male/female distinction and instead realy on animacy/non-animacy.
// What has an animacy and what doesn't is for language to decide.
// There are languages as well that mix these concepts and may have neuter,
// female, masculine with animacy, masculine with animacy. Some languages
// have their own scheme of things that arbitrarily picks noun-class per
// noun.
// Don't get me wrong. *All* languages do pick the gender for the word
// arbitrary as I showed at the beginning, it's just some languages have
// not just different mapping, but different gender set as well.
//
// The *only* option we have is fetch the gender per each name entry from
// localization files.
//
// I'm not 100% sure what should be the implementation of it, but I imagine
// that Stats::name() should be changed to include a way to reference where
// to grab the gender associated with this name, so translation then can
// pick right article or use right adjective/verb connected with NPC in the
// context of the message.
let _gender_str_npc = || "idk".to_owned();
let message_format = |from: &Uid, content: &Content, group: Option<&String>| { let message_format = |from: &Uid, content: &Content, group: Option<&String>| {
let alias = name_format_or_complex(true, from); let alias = name_format_or_complex(true, from);
@ -325,6 +409,9 @@ fn get_buff_ident(buff: BuffKind) -> &'static str {
// TODO: consider fetching "you" string from localization and somehow fetching // TODO: consider fetching "you" string from localization and somehow fetching
// user's gender and putting it as argument. // user's gender and putting it as argument.
//
// Altenatively, which would be a better design, pass 'is_you' to
// hud-chat-message and provide $mod_spacing attribute to use.
fn insert_alias(_replace_you: bool, info: PlayerInfo, _localization: &Localization) -> String { fn insert_alias(_replace_you: bool, info: PlayerInfo, _localization: &Localization) -> String {
// Leave space for a mod badge icon. // Leave space for a mod badge icon.
const MOD_SPACING: &str = " "; const MOD_SPACING: &str = " ";