From 0ca23b46c66fcea03fc9aa60b591bac13679bf57 Mon Sep 17 00:00:00 2001 From: projectmoon Date: Fri, 1 Jan 2021 23:34:50 +0000 Subject: [PATCH] Switch to oneof field for game-specific information and values. Having specific protobuf types for different game systems using the same rule set (e.g. all the Chronicles of Darkness games) is untenable because protobuf does not have inheritance or mixins. Instead, we have one generic character sheet type, with a oneof field for the game specifics. This will be used in conjunction with the character's game system (stored in db) to render different stuff on the character templates. Without this, we'd wind up having duplicate templates, a lot more code for handling specifics of each game system, and so on. --- proto/cofd.proto | 28 +++- src/migrator/migrations/V2__characters.rs | 2 +- src/models/characters.rs | 30 ++-- src/models/proto/cofd.rs | 35 +++-- src/routes/characters.rs | 8 +- src/routes/characters/edit.rs | 8 +- .../edit_changeling_character.html.tera | 134 ------------------ .../view_changeling_character.html.tera | 15 -- 8 files changed, 71 insertions(+), 189 deletions(-) delete mode 100644 templates/characters/edit_changeling_character.html.tera delete mode 100644 templates/characters/view_changeling_character.html.tera diff --git a/proto/cofd.proto b/proto/cofd.proto index 2cdf07d..323e73f 100644 --- a/proto/cofd.proto +++ b/proto/cofd.proto @@ -2,6 +2,24 @@ syntax = "proto3"; package models.proto.cofd; +//TODO do we want a single "morality" value, or keep it separate +//inside the system-specific fields? + +//Information and values specific to the core game. +message CoreFields { + int32 integrity = 1; +} + +//Information and values specific to Mage 2E. +message MageFields { + int32 widsom = 1; +} + +//Information and values specific to Changeling 2E. +message ChangelingFields { + int32 clarity = 1; +} + //Base sheet for Chronicles of Darkness systems. message CofdSheet { message Merit { @@ -72,8 +90,10 @@ message CofdSheet { repeated Attack attacks = 26; map other_data = 27; -} -message ChangelingSheet { - CofdSheet base = 1; -} \ No newline at end of file + oneof system_fields { + CoreFields core = 28; + MageFields mage = 29; + ChangelingFields changeling = 30; + } +} diff --git a/src/migrator/migrations/V2__characters.rs b/src/migrator/migrations/V2__characters.rs index 0d52a7f..74cf727 100644 --- a/src/migrator/migrations/V2__characters.rs +++ b/src/migrator/migrations/V2__characters.rs @@ -6,7 +6,7 @@ pub fn migration() -> String { println!("Applying migration: {}", file!()); m.create_table("characters", move |t| { - let db_enum = r#"CHECK(data_type IN ('chronicles_of_darkness_v1', 'changeling_v1'))"#; + let db_enum = r#"CHECK(data_type IN ('chronicles_of_darkness', 'changeling'))"#; t.add_column("id", types::primary()); t.add_column("user_id", types::integer()); t.add_column("viewable", types::boolean()); diff --git a/src/models/characters.rs b/src/models/characters.rs index 3f39f06..b6155c4 100644 --- a/src/models/characters.rs +++ b/src/models/characters.rs @@ -1,5 +1,4 @@ use crate::errors::Error; -use crate::models::proto::cofd::cofd_sheet::*; use crate::models::proto::cofd::*; use crate::models::users::User; use prost::bytes::BytesMut; @@ -46,12 +45,16 @@ pub(crate) trait Visibility { /// games use the same (or similar) character sheets. This is because /// of the possibility for slight differences in rules and data /// between even similar systems. It's simpler to err on the side of -/// uniqueness. Enum variants are also versioned, in case of drastic -/// rewrites or migrations in the future. +/// uniqueness. Usually, systems based on the same ruleset will have +/// one character sheet type, with a oneof field for game-specific +/// information. #[derive(Debug, Serialize, PartialEq, Clone, Copy, EnumIter, EnumString, sqlx::Type)] #[sqlx(rename_all = "snake_case")] pub enum CharacterDataType { + /// A character for the core Chronicles of Darkness rules. ChroniclesOfDarkness, + + /// A character for Changeling 2E rules. Changeling, } @@ -62,14 +65,8 @@ impl CharacterDataType { use prost::Message; use CharacterDataType::*; let data: BytesMut = match self { - ChroniclesOfDarkness => { - let sheet = CofdSheet::default_sheet(); - let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&sheet)); - sheet.encode(&mut buf)?; - buf - } - Changeling => { - let sheet = ChangelingSheet::default_sheet(); + ChroniclesOfDarkness | Changeling => { + let sheet = CofdSheet::default_sheet(*self); let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&sheet)); sheet.encode(&mut buf)?; buf @@ -78,6 +75,15 @@ impl CharacterDataType { Ok(data) } + + /// Returns whether or not this enum variant represents a Chronicles + /// of Darkness game system. + pub fn is_cofd_system(&self) -> bool { + use CharacterDataType::*; + match self { + ChroniclesOfDarkness | Changeling => true, + } + } } /// An entry that appears in a user's character list. Properties are @@ -122,7 +128,7 @@ impl Character { use CharacterDataType::*; let decoded: Box = match self.data_type { ChroniclesOfDarkness => Box::new(self.try_deserialize::()?), - Changeling => Box::new(self.try_deserialize::()?), + Changeling => Box::new(self.try_deserialize::()?), }; Ok(decoded) diff --git a/src/models/proto/cofd.rs b/src/models/proto/cofd.rs index 5b4b75d..6ad0dd5 100644 --- a/src/models/proto/cofd.rs +++ b/src/models/proto/cofd.rs @@ -2,6 +2,7 @@ //! buffer types, as well as utilities and extensions for working with //! them. +use crate::models::characters::CharacterDataType; use std::collections::BTreeMap; //Add the generated protobuf code into this module. @@ -12,6 +13,8 @@ pub mod api { include!(concat!(env!("OUT_DIR"), "/models.proto.cofd.api.rs")); } +/// Default mental skill names for a regular Chronicles of Darkness +/// (or derivative system) game. const MENTAL_SKILLS: &'static [&'static str] = &[ "Academics", "Computer", @@ -23,6 +26,8 @@ const MENTAL_SKILLS: &'static [&'static str] = &[ "Science", ]; +/// Default physical skill names for a regular Chronicles of Darkness +/// (or derivative system) game. const PHYSICAL_SKILLS: &'static [&'static str] = &[ "Athletics", "Brawl", @@ -34,6 +39,8 @@ const PHYSICAL_SKILLS: &'static [&'static str] = &[ "Weaponry", ]; +/// Default social skill names for a regular Chronicles of Darkness +/// (or derivative system) game. const SOCIAL_SKILLS: &'static [&'static str] = &[ "Animal Ken", "Empathy", @@ -56,26 +63,24 @@ fn create_skill_list(skill_names: &[&str]) -> BTreeMap CofdSheet { + /// Create the default (blank) character sheet for a Chronicles of + /// Darkness-based character. This fills in skills and other + /// information that needs to be pre-populated. System specifics + /// are set based on the given character data type (aka game + /// system). + pub fn default_sheet(system: CharacterDataType) -> CofdSheet { let mut sheet = Self::default(); sheet.mental_skills = create_skill_list(&MENTAL_SKILLS); sheet.physical_skills = create_skill_list(&PHYSICAL_SKILLS); sheet.social_skills = create_skill_list(&SOCIAL_SKILLS); - sheet - } -} -impl ChangelingSheet { - /// Create the default (blank) character sheet for a Changeling - /// character. This fills in skills and other information that - /// needs to be pre-populated. - pub fn default_sheet() -> ChangelingSheet { - let mut sheet = Self::default(); - sheet.base = Some(CofdSheet::default_sheet()); - //TODO fill in changeling-specific stuff + use crate::models::proto::cofd::cofd_sheet::SystemFields; + let specifics: SystemFields = match system { + CharacterDataType::Changeling => SystemFields::Changeling(ChangelingFields::default()), + CharacterDataType::ChroniclesOfDarkness => SystemFields::Core(CoreFields::default()), + }; + + sheet.system_fields = Some(specifics); sheet } } diff --git a/src/routes/characters.rs b/src/routes/characters.rs index d61a960..53ceb32 100644 --- a/src/routes/characters.rs +++ b/src/routes/characters.rs @@ -37,10 +37,10 @@ fn view_character_template(user: &User, character: Character) -> Result