Compare commits
2 Commits
2cb547e16a
...
b467c32acb
Author | SHA1 | Date |
---|---|---|
jeff | b467c32acb | |
jeff | 14863353f0 |
|
@ -2,6 +2,24 @@ 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 {
|
||||||
|
@ -72,8 +90,10 @@ message CofdSheet {
|
||||||
repeated Attack attacks = 26;
|
repeated Attack attacks = 26;
|
||||||
|
|
||||||
map<string, string> other_data = 27;
|
map<string, string> other_data = 27;
|
||||||
}
|
|
||||||
|
|
||||||
message ChangelingSheet {
|
oneof system_fields {
|
||||||
CofdSheet base = 1;
|
CoreFields core = 28;
|
||||||
}
|
MageFields mage = 29;
|
||||||
|
ChangelingFields changeling = 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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_v1', 'changeling_v1'))"#;
|
let db_enum = r#"CHECK(data_type IN ('chronicles_of_darkness', 'changeling'))"#;
|
||||||
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,5 +1,4 @@
|
||||||
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;
|
||||||
|
@ -46,13 +45,17 @@ 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. Enum variants are also versioned, in case of drastic
|
/// uniqueness. Usually, systems based on the same ruleset will have
|
||||||
/// rewrites or migrations in the future.
|
/// one character sheet type, with a oneof field for game-specific
|
||||||
|
/// 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 {
|
||||||
ChroniclesOfDarknessV1,
|
/// A character for the core Chronicles of Darkness rules.
|
||||||
ChangelingV1,
|
ChroniclesOfDarkness,
|
||||||
|
|
||||||
|
/// A character for Changeling 2E rules.
|
||||||
|
Changeling,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterDataType {
|
impl CharacterDataType {
|
||||||
|
@ -62,14 +65,8 @@ impl CharacterDataType {
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use CharacterDataType::*;
|
use CharacterDataType::*;
|
||||||
let data: BytesMut = match self {
|
let data: BytesMut = match self {
|
||||||
ChroniclesOfDarknessV1 => {
|
ChroniclesOfDarkness | Changeling => {
|
||||||
let sheet = CofdSheet::default_sheet();
|
let sheet = CofdSheet::default_sheet(*self);
|
||||||
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
|
||||||
|
@ -78,6 +75,15 @@ 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
|
||||||
|
@ -121,8 +127,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 {
|
||||||
ChroniclesOfDarknessV1 => Box::new(self.try_deserialize::<CofdSheet>()?),
|
ChroniclesOfDarkness => Box::new(self.try_deserialize::<CofdSheet>()?),
|
||||||
ChangelingV1 => Box::new(self.try_deserialize::<ChangelingSheet>()?),
|
Changeling => Box::new(self.try_deserialize::<CofdSheet>()?),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(decoded)
|
Ok(decoded)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
//! 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.
|
||||||
|
@ -12,6 +13,8 @@ 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",
|
||||||
|
@ -23,6 +26,8 @@ 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",
|
||||||
|
@ -34,6 +39,8 @@ 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",
|
||||||
|
@ -56,26 +63,24 @@ fn create_skill_list(skill_names: &[&str]) -> BTreeMap<String, cofd_sheet::Skill
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CofdSheet {
|
impl CofdSheet {
|
||||||
/// Create the default (blank) character sheet for a core
|
/// Create the default (blank) character sheet for a Chronicles of
|
||||||
/// Chronicles of Darkness character. This fills in skills and
|
/// Darkness-based character. This fills in skills and other
|
||||||
/// other information that needs to be pre-populated.
|
/// information that needs to be pre-populated. System specifics
|
||||||
pub fn default_sheet() -> CofdSheet {
|
/// are set based on the given character data type (aka game
|
||||||
|
/// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChangelingSheet {
|
use crate::models::proto::cofd::cofd_sheet::SystemFields;
|
||||||
/// Create the default (blank) character sheet for a Changeling
|
let specifics: SystemFields = match system {
|
||||||
/// character. This fills in skills and other information that
|
CharacterDataType::Changeling => SystemFields::Changeling(ChangelingFields::default()),
|
||||||
/// needs to be pre-populated.
|
CharacterDataType::ChroniclesOfDarkness => SystemFields::Core(CoreFields::default()),
|
||||||
pub fn default_sheet() -> ChangelingSheet {
|
};
|
||||||
let mut sheet = Self::default();
|
|
||||||
sheet.base = Some(CofdSheet::default_sheet());
|
sheet.system_fields = Some(specifics);
|
||||||
//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()?,
|
||||||
};
|
};
|
||||||
|
|
||||||
use CharacterDataType::*;
|
let template = if character.data_type.is_cofd_system() {
|
||||||
let template = match character.data_type {
|
Template::render("characters/view_character", context)
|
||||||
ChroniclesOfDarknessV1 => Template::render("characters/view_character", context),
|
} else {
|
||||||
ChangelingV1 => Template::render("characters/view_changeling_character", context),
|
return Err(Error::InvalidInput);
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(template)
|
Ok(template)
|
||||||
|
|
|
@ -33,10 +33,10 @@ fn edit_character_template(user: &User, character: Character) -> Result<Template
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use CharacterDataType::*;
|
let template = if character.data_type.is_cofd_system() {
|
||||||
let template = match character.data_type {
|
Template::render("characters/edit_character", context)
|
||||||
ChroniclesOfDarknessV1 => Template::render("characters/edit_character", context),
|
} else {
|
||||||
ChangelingV1 => Template::render("characters/edit_changeling_character", context),
|
return Err(Error::InvalidInput);
|
||||||
};
|
};
|
||||||
|
|
||||||
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::ChroniclesOfDarknessV1,
|
selected_system: CharacterDataType::ChroniclesOfDarkness,
|
||||||
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::ChroniclesOfDarknessV1);
|
.unwrap_or(&CharacterDataType::ChroniclesOfDarkness);
|
||||||
|
|
||||||
NewCharacterContext {
|
NewCharacterContext {
|
||||||
name: form.name.clone(),
|
name: form.name.clone(),
|
||||||
|
|
|
@ -1,134 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{% 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