Compare commits
No commits in common. "b467c32acb970ffd7c5d056c60fdceadcf83ed58" and "2cb547e16ab9760211ba2686a1d5dc2fe47dc1ab" have entirely different histories.
b467c32acb
...
2cb547e16a
|
@ -2,24 +2,6 @@ syntax = "proto3";
|
||||||
|
|
||||||
package models.proto.cofd;
|
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.
|
//Base sheet for Chronicles of Darkness systems.
|
||||||
message CofdSheet {
|
message CofdSheet {
|
||||||
message Merit {
|
message Merit {
|
||||||
|
@ -90,10 +72,8 @@ message CofdSheet {
|
||||||
repeated Attack attacks = 26;
|
repeated Attack attacks = 26;
|
||||||
|
|
||||||
map<string, string> other_data = 27;
|
map<string, string> other_data = 27;
|
||||||
|
|
||||||
oneof system_fields {
|
|
||||||
CoreFields core = 28;
|
|
||||||
MageFields mage = 29;
|
|
||||||
ChangelingFields changeling = 30;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ChangelingSheet {
|
||||||
|
CofdSheet base = 1;
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ pub fn migration() -> String {
|
||||||
println!("Applying migration: {}", file!());
|
println!("Applying migration: {}", file!());
|
||||||
|
|
||||||
m.create_table("characters", move |t| {
|
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("id", types::primary());
|
||||||
t.add_column("user_id", types::integer());
|
t.add_column("user_id", types::integer());
|
||||||
t.add_column("viewable", types::boolean());
|
t.add_column("viewable", types::boolean());
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
|
use crate::models::proto::cofd::cofd_sheet::*;
|
||||||
use crate::models::proto::cofd::*;
|
use crate::models::proto::cofd::*;
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
use prost::bytes::BytesMut;
|
use prost::bytes::BytesMut;
|
||||||
|
@ -45,17 +46,13 @@ pub(crate) trait Visibility {
|
||||||
/// games use the same (or similar) character sheets. This is because
|
/// games use the same (or similar) character sheets. This is because
|
||||||
/// of the possibility for slight differences in rules and data
|
/// of the possibility for slight differences in rules and data
|
||||||
/// between even similar systems. It's simpler to err on the side of
|
/// between even similar systems. It's simpler to err on the side of
|
||||||
/// uniqueness. Usually, systems based on the same ruleset will have
|
/// uniqueness. Enum variants are also versioned, in case of drastic
|
||||||
/// one character sheet type, with a oneof field for game-specific
|
/// rewrites or migrations in the future.
|
||||||
/// information.
|
|
||||||
#[derive(Debug, Serialize, PartialEq, Clone, Copy, EnumIter, EnumString, sqlx::Type)]
|
#[derive(Debug, Serialize, PartialEq, Clone, Copy, EnumIter, EnumString, sqlx::Type)]
|
||||||
#[sqlx(rename_all = "snake_case")]
|
#[sqlx(rename_all = "snake_case")]
|
||||||
pub enum CharacterDataType {
|
pub enum CharacterDataType {
|
||||||
/// A character for the core Chronicles of Darkness rules.
|
ChroniclesOfDarknessV1,
|
||||||
ChroniclesOfDarkness,
|
ChangelingV1,
|
||||||
|
|
||||||
/// A character for Changeling 2E rules.
|
|
||||||
Changeling,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterDataType {
|
impl CharacterDataType {
|
||||||
|
@ -65,8 +62,14 @@ impl CharacterDataType {
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use CharacterDataType::*;
|
use CharacterDataType::*;
|
||||||
let data: BytesMut = match self {
|
let data: BytesMut = match self {
|
||||||
ChroniclesOfDarkness | Changeling => {
|
ChroniclesOfDarknessV1 => {
|
||||||
let sheet = CofdSheet::default_sheet(*self);
|
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));
|
let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&sheet));
|
||||||
sheet.encode(&mut buf)?;
|
sheet.encode(&mut buf)?;
|
||||||
buf
|
buf
|
||||||
|
@ -75,15 +78,6 @@ impl CharacterDataType {
|
||||||
|
|
||||||
Ok(data)
|
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
|
/// 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> {
|
pub fn dyn_deserialize(&self) -> Result<Box<DynCharacterData>, Error> {
|
||||||
use CharacterDataType::*;
|
use CharacterDataType::*;
|
||||||
let decoded: Box<dyn erased_serde::Serialize> = match self.data_type {
|
let decoded: Box<dyn erased_serde::Serialize> = match self.data_type {
|
||||||
ChroniclesOfDarkness => Box::new(self.try_deserialize::<CofdSheet>()?),
|
ChroniclesOfDarknessV1 => Box::new(self.try_deserialize::<CofdSheet>()?),
|
||||||
Changeling => Box::new(self.try_deserialize::<CofdSheet>()?),
|
ChangelingV1 => Box::new(self.try_deserialize::<ChangelingSheet>()?),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(decoded)
|
Ok(decoded)
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
//! buffer types, as well as utilities and extensions for working with
|
//! buffer types, as well as utilities and extensions for working with
|
||||||
//! them.
|
//! them.
|
||||||
|
|
||||||
use crate::models::characters::CharacterDataType;
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
//Add the generated protobuf code into this module.
|
//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"));
|
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] = &[
|
const MENTAL_SKILLS: &'static [&'static str] = &[
|
||||||
"Academics",
|
"Academics",
|
||||||
"Computer",
|
"Computer",
|
||||||
|
@ -26,8 +23,6 @@ const MENTAL_SKILLS: &'static [&'static str] = &[
|
||||||
"Science",
|
"Science",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Default physical skill names for a regular Chronicles of Darkness
|
|
||||||
/// (or derivative system) game.
|
|
||||||
const PHYSICAL_SKILLS: &'static [&'static str] = &[
|
const PHYSICAL_SKILLS: &'static [&'static str] = &[
|
||||||
"Athletics",
|
"Athletics",
|
||||||
"Brawl",
|
"Brawl",
|
||||||
|
@ -39,8 +34,6 @@ const PHYSICAL_SKILLS: &'static [&'static str] = &[
|
||||||
"Weaponry",
|
"Weaponry",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Default social skill names for a regular Chronicles of Darkness
|
|
||||||
/// (or derivative system) game.
|
|
||||||
const SOCIAL_SKILLS: &'static [&'static str] = &[
|
const SOCIAL_SKILLS: &'static [&'static str] = &[
|
||||||
"Animal Ken",
|
"Animal Ken",
|
||||||
"Empathy",
|
"Empathy",
|
||||||
|
@ -63,24 +56,26 @@ fn create_skill_list(skill_names: &[&str]) -> BTreeMap<String, cofd_sheet::Skill
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CofdSheet {
|
impl CofdSheet {
|
||||||
/// Create the default (blank) character sheet for a Chronicles of
|
/// Create the default (blank) character sheet for a core
|
||||||
/// Darkness-based character. This fills in skills and other
|
/// Chronicles of Darkness character. This fills in skills and
|
||||||
/// information that needs to be pre-populated. System specifics
|
/// other information that needs to be pre-populated.
|
||||||
/// are set based on the given character data type (aka game
|
pub fn default_sheet() -> CofdSheet {
|
||||||
/// system).
|
|
||||||
pub fn default_sheet(system: CharacterDataType) -> CofdSheet {
|
|
||||||
let mut sheet = Self::default();
|
let mut sheet = Self::default();
|
||||||
sheet.mental_skills = create_skill_list(&MENTAL_SKILLS);
|
sheet.mental_skills = create_skill_list(&MENTAL_SKILLS);
|
||||||
sheet.physical_skills = create_skill_list(&PHYSICAL_SKILLS);
|
sheet.physical_skills = create_skill_list(&PHYSICAL_SKILLS);
|
||||||
sheet.social_skills = create_skill_list(&SOCIAL_SKILLS);
|
sheet.social_skills = create_skill_list(&SOCIAL_SKILLS);
|
||||||
|
sheet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use crate::models::proto::cofd::cofd_sheet::SystemFields;
|
impl ChangelingSheet {
|
||||||
let specifics: SystemFields = match system {
|
/// Create the default (blank) character sheet for a Changeling
|
||||||
CharacterDataType::Changeling => SystemFields::Changeling(ChangelingFields::default()),
|
/// character. This fills in skills and other information that
|
||||||
CharacterDataType::ChroniclesOfDarkness => SystemFields::Core(CoreFields::default()),
|
/// needs to be pre-populated.
|
||||||
};
|
pub fn default_sheet() -> ChangelingSheet {
|
||||||
|
let mut sheet = Self::default();
|
||||||
sheet.system_fields = Some(specifics);
|
sheet.base = Some(CofdSheet::default_sheet());
|
||||||
|
//TODO fill in changeling-specific stuff
|
||||||
sheet
|
sheet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,10 +37,10 @@ fn view_character_template(user: &User, character: Character) -> Result<Template
|
||||||
sheet: character.dyn_deserialize()?,
|
sheet: character.dyn_deserialize()?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let template = if character.data_type.is_cofd_system() {
|
use CharacterDataType::*;
|
||||||
Template::render("characters/view_character", context)
|
let template = match character.data_type {
|
||||||
} else {
|
ChroniclesOfDarknessV1 => Template::render("characters/view_character", context),
|
||||||
return Err(Error::InvalidInput);
|
ChangelingV1 => Template::render("characters/view_changeling_character", context),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(template)
|
Ok(template)
|
||||||
|
|
|
@ -33,10 +33,10 @@ fn edit_character_template(user: &User, character: Character) -> Result<Template
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let template = if character.data_type.is_cofd_system() {
|
use CharacterDataType::*;
|
||||||
Template::render("characters/edit_character", context)
|
let template = match character.data_type {
|
||||||
} else {
|
ChroniclesOfDarknessV1 => Template::render("characters/edit_character", context),
|
||||||
return Err(Error::InvalidInput);
|
ChangelingV1 => Template::render("characters/edit_changeling_character", context),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(template)
|
Ok(template)
|
||||||
|
|
|
@ -32,7 +32,7 @@ impl NewCharacterContext {
|
||||||
NewCharacterContext {
|
NewCharacterContext {
|
||||||
name: "".to_string(),
|
name: "".to_string(),
|
||||||
error_message: "".to_string(),
|
error_message: "".to_string(),
|
||||||
selected_system: CharacterDataType::ChroniclesOfDarkness,
|
selected_system: CharacterDataType::ChroniclesOfDarknessV1,
|
||||||
systems: CharacterDataType::iter().collect(),
|
systems: CharacterDataType::iter().collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ impl NewCharacterContext {
|
||||||
let system: CharacterDataType = *form
|
let system: CharacterDataType = *form
|
||||||
.system
|
.system
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&CharacterDataType::ChroniclesOfDarkness);
|
.unwrap_or(&CharacterDataType::ChroniclesOfDarknessV1);
|
||||||
|
|
||||||
NewCharacterContext {
|
NewCharacterContext {
|
||||||
name: form.name.clone(),
|
name: form.name.clone(),
|
||||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
Loading…
Reference in New Issue