Compare commits
2 Commits
2cb547e16a
...
b467c32acb
Author | SHA1 | Date |
---|---|---|
jeff | b467c32acb | |
jeff | 14863353f0 |
|
@ -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<string, string> other_data = 27;
|
||||
}
|
||||
|
||||
message ChangelingSheet {
|
||||
CofdSheet base = 1;
|
||||
}
|
||||
oneof system_fields {
|
||||
CoreFields core = 28;
|
||||
MageFields mage = 29;
|
||||
ChangelingFields changeling = 30;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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,13 +45,17 @@ 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 {
|
||||
ChroniclesOfDarknessV1,
|
||||
ChangelingV1,
|
||||
/// A character for the core Chronicles of Darkness rules.
|
||||
ChroniclesOfDarkness,
|
||||
|
||||
/// A character for Changeling 2E rules.
|
||||
Changeling,
|
||||
}
|
||||
|
||||
impl CharacterDataType {
|
||||
|
@ -62,14 +65,8 @@ impl CharacterDataType {
|
|||
use prost::Message;
|
||||
use CharacterDataType::*;
|
||||
let data: BytesMut = match 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();
|
||||
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
|
||||
|
@ -121,8 +127,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 {
|
||||
ChroniclesOfDarknessV1 => Box::new(self.try_deserialize::<CofdSheet>()?),
|
||||
ChangelingV1 => Box::new(self.try_deserialize::<ChangelingSheet>()?),
|
||||
ChroniclesOfDarkness => Box::new(self.try_deserialize::<CofdSheet>()?),
|
||||
Changeling => Box::new(self.try_deserialize::<CofdSheet>()?),
|
||||
};
|
||||
|
||||
Ok(decoded)
|
||||
|
|
|
@ -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<String, cofd_sheet::Skill
|
|||
}
|
||||
|
||||
impl 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 {
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,10 +37,10 @@ fn view_character_template(user: &User, character: Character) -> Result<Template
|
|||
sheet: character.dyn_deserialize()?,
|
||||
};
|
||||
|
||||
use CharacterDataType::*;
|
||||
let template = match character.data_type {
|
||||
ChroniclesOfDarknessV1 => Template::render("characters/view_character", context),
|
||||
ChangelingV1 => Template::render("characters/view_changeling_character", context),
|
||||
let template = if character.data_type.is_cofd_system() {
|
||||
Template::render("characters/view_character", context)
|
||||
} else {
|
||||
return Err(Error::InvalidInput);
|
||||
};
|
||||
|
||||
Ok(template)
|
||||
|
|
|
@ -33,10 +33,10 @@ fn edit_character_template(user: &User, character: Character) -> Result<Template
|
|||
},
|
||||
};
|
||||
|
||||
use CharacterDataType::*;
|
||||
let template = match character.data_type {
|
||||
ChroniclesOfDarknessV1 => Template::render("characters/edit_character", context),
|
||||
ChangelingV1 => Template::render("characters/edit_changeling_character", context),
|
||||
let template = if character.data_type.is_cofd_system() {
|
||||
Template::render("characters/edit_character", context)
|
||||
} else {
|
||||
return Err(Error::InvalidInput);
|
||||
};
|
||||
|
||||
Ok(template)
|
||||
|
|
|
@ -32,7 +32,7 @@ impl NewCharacterContext {
|
|||
NewCharacterContext {
|
||||
name: "".to_string(),
|
||||
error_message: "".to_string(),
|
||||
selected_system: CharacterDataType::ChroniclesOfDarknessV1,
|
||||
selected_system: CharacterDataType::ChroniclesOfDarkness,
|
||||
systems: CharacterDataType::iter().collect(),
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ impl NewCharacterContext {
|
|||
let system: CharacterDataType = *form
|
||||
.system
|
||||
.as_ref()
|
||||
.unwrap_or(&CharacterDataType::ChroniclesOfDarknessV1);
|
||||
.unwrap_or(&CharacterDataType::ChroniclesOfDarkness);
|
||||
|
||||
NewCharacterContext {
|
||||
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