Compare commits

..

No commits in common. "b467c32acb970ffd7c5d056c60fdceadcf83ed58" and "2cb547e16ab9760211ba2686a1d5dc2fe47dc1ab" have entirely different histories.

9 changed files with 194 additions and 76 deletions

View File

@ -2,24 +2,6 @@ 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 {
@ -90,10 +72,8 @@ message CofdSheet {
repeated Attack attacks = 26;
map<string, string> other_data = 27;
oneof system_fields {
CoreFields core = 28;
MageFields mage = 29;
ChangelingFields changeling = 30;
}
}
message ChangelingSheet {
CofdSheet base = 1;
}

View File

@ -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', 'changeling'))"#;
let db_enum = r#"CHECK(data_type IN ('chronicles_of_darkness_v1', 'changeling_v1'))"#;
t.add_column("id", types::primary());
t.add_column("user_id", types::integer());
t.add_column("viewable", types::boolean());

View File

@ -1,4 +1,5 @@
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;
@ -45,17 +46,13 @@ 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. Usually, systems based on the same ruleset will have
/// one character sheet type, with a oneof field for game-specific
/// information.
/// uniqueness. Enum variants are also versioned, in case of drastic
/// rewrites or migrations in the future.
#[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,
ChroniclesOfDarknessV1,
ChangelingV1,
}
impl CharacterDataType {
@ -65,8 +62,14 @@ impl CharacterDataType {
use prost::Message;
use CharacterDataType::*;
let data: BytesMut = match self {
ChroniclesOfDarkness | Changeling => {
let sheet = CofdSheet::default_sheet(*self);
ChroniclesOfDarknessV1 => {
let sheet = CofdSheet::default_sheet();
let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&sheet));
sheet.encode(&mut buf)?;
buf
}
ChangelingV1 => {
let sheet = ChangelingSheet::default_sheet();
let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&sheet));
sheet.encode(&mut buf)?;
buf
@ -75,15 +78,6 @@ 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
@ -127,8 +121,8 @@ impl Character {
pub fn dyn_deserialize(&self) -> Result<Box<DynCharacterData>, Error> {
use CharacterDataType::*;
let decoded: Box<dyn erased_serde::Serialize> = match self.data_type {
ChroniclesOfDarkness => Box::new(self.try_deserialize::<CofdSheet>()?),
Changeling => Box::new(self.try_deserialize::<CofdSheet>()?),
ChroniclesOfDarknessV1 => Box::new(self.try_deserialize::<CofdSheet>()?),
ChangelingV1 => Box::new(self.try_deserialize::<ChangelingSheet>()?),
};
Ok(decoded)

View File

@ -2,7 +2,6 @@
//! 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.
@ -13,8 +12,6 @@ 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",
@ -26,8 +23,6 @@ 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",
@ -39,8 +34,6 @@ 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",
@ -63,24 +56,26 @@ fn create_skill_list(skill_names: &[&str]) -> BTreeMap<String, cofd_sheet::Skill
}
impl 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 {
/// Create the default (blank) character sheet for a core
/// Chronicles of Darkness character. This fills in skills and
/// other information that needs to be pre-populated.
pub fn default_sheet() -> 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
}
}
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);
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
sheet
}
}

View File

@ -37,10 +37,10 @@ fn view_character_template(user: &User, character: Character) -> Result<Template
sheet: character.dyn_deserialize()?,
};
let template = if character.data_type.is_cofd_system() {
Template::render("characters/view_character", context)
} else {
return Err(Error::InvalidInput);
use CharacterDataType::*;
let template = match character.data_type {
ChroniclesOfDarknessV1 => Template::render("characters/view_character", context),
ChangelingV1 => Template::render("characters/view_changeling_character", context),
};
Ok(template)

View File

@ -33,10 +33,10 @@ fn edit_character_template(user: &User, character: Character) -> Result<Template
},
};
let template = if character.data_type.is_cofd_system() {
Template::render("characters/edit_character", context)
} else {
return Err(Error::InvalidInput);
use CharacterDataType::*;
let template = match character.data_type {
ChroniclesOfDarknessV1 => Template::render("characters/edit_character", context),
ChangelingV1 => Template::render("characters/edit_changeling_character", context),
};
Ok(template)

View File

@ -32,7 +32,7 @@ impl NewCharacterContext {
NewCharacterContext {
name: "".to_string(),
error_message: "".to_string(),
selected_system: CharacterDataType::ChroniclesOfDarkness,
selected_system: CharacterDataType::ChroniclesOfDarknessV1,
systems: CharacterDataType::iter().collect(),
}
}
@ -43,7 +43,7 @@ impl NewCharacterContext {
let system: CharacterDataType = *form
.system
.as_ref()
.unwrap_or(&CharacterDataType::ChroniclesOfDarkness);
.unwrap_or(&CharacterDataType::ChroniclesOfDarknessV1);
NewCharacterContext {
name: form.name.clone(),

View File

@ -0,0 +1,134 @@
{% import "characters/edit_character_macros" as macros %}
{% extends "base" %}
{% block content %}
<style type="text/css">
body {
font-family: Liberation Sans, Arial;
}
#attributes {
padding: 4px;
border-collapse: collapse;
}
#attributes .attributes-section {
border: 1px solid gray;
border-collapse: collapse;
display: table-cell;
}
.attribute {
margin: 0;
padding: 0;
display: flex;
}
.attribute label {
display: inline-block;
float: left;
clear: left;
width: 10em;
text-align: right;
vertical-align: text-bottom;
padding: 8px;
margin: 0;
}
.attribute input {
max-width: 4em;
display: inline-block;
float: left;
padding: 8px;
border: none;
background-color: lightgray;
margin: 0;
}
#skills {
padding: 4px;
border-collapse: collapse;
}
#skills .skills-section {
border: 1px solid gray;
border-collapse: collapse;
display: table-cell;
}
.skill {
margin: 0;
padding: 0;
display: flex;
}
.skill label {
display: inline-block;
float: left;
clear: left;
width: 10em;
text-align: right;
vertical-align: text-bottom;
padding: 8px;
margin: 0;
}
.skill input {
max-width: 4em;
display: inline-block;
float: left;
padding: 8px;
border: none;
background-color: lightgray;
margin: 0;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/protobufjs@6.10.2/dist/protobuf.min.js"></script>
<script defer type="text/javascript" src="/scripts/api.js"></script>
<script defer type="text/javascript" src="/scripts/characters/edit.js"></script>
<h1>Core Sheet</h1>
<div>
<h1>Name: <input type="text" value="{{name}}" /></h1>
<p>System: {{data_type}}</p>
<div id="attributes">
<div class="attributes-section" id="mentalAttributes">
{{ macros::attribute(name="Intelligence", value=sheet.base.intelligence) }}
{{ macros::attribute(name="Wits", value=sheet.base.wits) }}
{{ macros::attribute(name="Resolve", value=sheet.base.resolve) }}
</div>
<div class="attributes-section" id="physicalAttributes">
{{ macros::attribute(name="Strength", value=sheet.base.strength) }}
{{ macros::attribute(name="Dexterity", value=sheet.base.dexterity) }}
{{ macros::attribute(name="Stamina", value=sheet.base.stamina) }}
</div>
<div class="attributes-section" id="socicalAttributes">
{{ macros::attribute(name="Presence", value=sheet.base.presence) }}
{{ macros::attribute(name="Manipulation", value=sheet.base.manipulation) }}
{{ macros::attribute(name="Composure", value=sheet.base.composure) }}
</div>
</div>
<div id="skills">
<div class="skills-section" id="mentalSkills">
{% for skill_name, skill in sheet.base.mentalSkills %}
{{ macros::skill(name=skill_name, value=skill.dots) }}
{% endfor %}
</div>
<div class="skills-section" id="physicalSkills">
{% for skill_name, skill in sheet.base.physicalSkills %}
{{ macros::skill(name=skill_name, value=skill.dots) }}
{% endfor %}
</div>
<div class="skills-section" id="socialSkills">
{% for skill_name, skill in sheet.base.socialSkills %}
{{ macros::skill(name=skill_name, value=skill.dots) }}
{% endfor %}
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,15 @@
{% extends "base" %}
{% block content %}
<h1>Changeling Sheet</h1>
<div>
<h1>Character {{name}}</h1>
<h3>User: {{username}}</h3>
<p>System: {{data_type}}</h3>
<p>Strength: {{sheet.base.strength}}</p>
</div>
<div>
<a href="/characters/{{username}}/{{id}}/edit">Edit Character</a>
</div>
{% endblock content %}