adding defaults, modifiers, traits and attributes
This commit is contained in:
parent
da6255a86a
commit
8bde2ab5f3
|
@ -1,3 +1,20 @@
|
||||||
|
function getTraitModifiersForStat(stat) {
|
||||||
|
var mods = {};
|
||||||
|
for (const prop in TRAITS) {
|
||||||
|
var props = [];
|
||||||
|
for (const desc in TRAITS[prop]) {
|
||||||
|
trait = TRAITS[prop][desc]
|
||||||
|
if (trait.type == "stat" && trait.target == stat) {
|
||||||
|
props.push(trait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (props) {
|
||||||
|
mods[prop] = props;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
function proficiency() {
|
function proficiency() {
|
||||||
return parseInt(document.getElementById('proficiency_bonus').innerHTML);
|
return parseInt(document.getElementById('proficiency_bonus').innerHTML);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +29,19 @@ function setStatBonus(stat) {
|
||||||
document.getElementById(stat + '_bonus').innerHTML = bonus;
|
document.getElementById(stat + '_bonus').innerHTML = bonus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyStatModifiers(stat) {
|
||||||
|
var score = parseInt(document.getElementById(stat).value);
|
||||||
|
var modsForStat = getTraitModifiersForStat(stat);
|
||||||
|
for (desc in modsForStat) {
|
||||||
|
for (idx in modsForStat[desc]) {
|
||||||
|
var value = modsForStat[desc][idx].value;
|
||||||
|
console.log(`Ancestry Trait "${desc}" grants ${value} to ${stat}`);
|
||||||
|
score += parseInt(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById(stat).value = score;
|
||||||
|
}
|
||||||
|
|
||||||
function setProficiencyBonus() {
|
function setProficiencyBonus() {
|
||||||
var score = document.getElementById('level').value;
|
var score = document.getElementById('level').value;
|
||||||
var bonus = Math.ceil(1 + (0.25 * score));
|
var bonus = Math.ceil(1 + (0.25 * score));
|
||||||
|
@ -25,7 +55,9 @@ function setSpellSaveDC() {
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
const stats = ['str', 'dex', 'con', 'int', 'wis', 'cha'];
|
const stats = ['str', 'dex', 'con', 'int', 'wis', 'cha'];
|
||||||
|
stats.forEach(applyStatModifiers);
|
||||||
stats.forEach(setStatBonus);
|
stats.forEach(setStatBonus);
|
||||||
setProficiencyBonus();
|
setProficiencyBonus();
|
||||||
setSpellSaveDC();
|
setSpellSaveDC();
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% block debug %}{% endblock %}
|
{% block debug %}{% endblock %}
|
||||||
|
{% block script %}{% endblock %}
|
||||||
{% for resource in c.resources %}
|
{% for resource in c.resources %}
|
||||||
{% if resource['type'] == 'script' %}
|
{% if resource['type'] == 'script' %}
|
||||||
<script type="text/javascript" src="{{c.routes.static}}/{{resource['uri']}}"></script>
|
<script type="text/javascript" src="{{c.routes.static}}/{{resource['uri']}}"></script>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<div class='banner'>
|
<div class='banner'>
|
||||||
<div><img id='portrait' /></div>
|
<div><img id='portrait' /></div>
|
||||||
<div>
|
<div>
|
||||||
<h1>{{ c.record.name }}</h1>
|
<h1>{{ c.form.name }}</h1>
|
||||||
{{ c.form.ancestry }} {{ c.record.character_class|join(' / ') }} Level {{ c.form.level }}
|
{{ c.form.ancestry }} {{ c.record.character_class|join(' / ') }} Level {{ c.form.level }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,3 +73,25 @@
|
||||||
{{ c }}
|
{{ c }}
|
||||||
</code>
|
</code>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script type='text/javascript'>
|
||||||
|
{% for field, msg in c.form.errors.items() %}
|
||||||
|
console.log("{{ field }}: {{ msg }}");
|
||||||
|
{% endfor %}
|
||||||
|
const TRAITS = {
|
||||||
|
{% for trait_desc, traits in c.traits.items() %}
|
||||||
|
'{{ trait_desc }}': [
|
||||||
|
{% for trait in traits %}
|
||||||
|
{
|
||||||
|
"type": "{{ trait['type'] }}",
|
||||||
|
"target": "{{ trait.target }}",
|
||||||
|
"value": "{{ trait.value }}",
|
||||||
|
},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
{% endfor %}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -9,6 +9,7 @@ from sqlalchemy.exc import IntegrityError
|
||||||
data = {
|
data = {
|
||||||
'CharacterClass': [
|
'CharacterClass': [
|
||||||
{
|
{
|
||||||
|
'id': 1,
|
||||||
'name': 'fighter',
|
'name': 'fighter',
|
||||||
'hit_dice': '1d10',
|
'hit_dice': '1d10',
|
||||||
'hit_dice_stat': 'CON',
|
'hit_dice_stat': 'CON',
|
||||||
|
@ -17,6 +18,7 @@ data = {
|
||||||
'skills': ['Acrobatics', 'Animal Handling', 'Athletics', 'History', 'Insight', 'Intimidation', 'Perception', 'Survival'],
|
'skills': ['Acrobatics', 'Animal Handling', 'Athletics', 'History', 'Insight', 'Intimidation', 'Perception', 'Survival'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
'id': 2,
|
||||||
'name': 'rogue',
|
'name': 'rogue',
|
||||||
'hit_dice': '1d8',
|
'hit_dice': '1d8',
|
||||||
'hit_dice_stat': 'DEX',
|
'hit_dice_stat': 'DEX',
|
||||||
|
@ -25,6 +27,7 @@ data = {
|
||||||
'skills': ['Acrobatics', 'Athletics', 'Deception', 'Insight', 'Intimidation', 'Investigation', 'Perception', 'Performance', 'Persuasion', 'Sleight of Hand', 'Stealth'],
|
'skills': ['Acrobatics', 'Athletics', 'Deception', 'Insight', 'Intimidation', 'Investigation', 'Perception', 'Performance', 'Persuasion', 'Sleight of Hand', 'Stealth'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
'Skill': [
|
'Skill': [
|
||||||
{'name': 'Acrobatics'},
|
{'name': 'Acrobatics'},
|
||||||
{'name': 'Animal Handling'},
|
{'name': 'Animal Handling'},
|
||||||
|
@ -41,25 +44,24 @@ data = {
|
||||||
{'name': 'Stealth'},
|
{'name': 'Stealth'},
|
||||||
{'name': 'Survival'},
|
{'name': 'Survival'},
|
||||||
],
|
],
|
||||||
|
|
||||||
'Ancestry': [
|
'Ancestry': [
|
||||||
{'name': 'human', 'creature_type': 'humanoid'},
|
{'id': 1, 'name': 'human', 'creature_type': 'humanoid'},
|
||||||
{'name': 'dragonborn', 'creature_type': 'humanoid'},
|
{'id': 2, 'name': 'dragonborn', 'creature_type': 'humanoid'},
|
||||||
{'name': 'tiefling', 'creature_type': 'humanoid'},
|
{'id': 3, 'name': 'tiefling', 'creature_type': 'humanoid'},
|
||||||
],
|
],
|
||||||
|
|
||||||
'Character': [
|
'Character': [
|
||||||
{
|
{
|
||||||
'id': 1,
|
'id': 1,
|
||||||
'name': 'Sabetha',
|
'name': 'Sabetha',
|
||||||
'ancestry': 'tiefling',
|
'ancestry': 'human',
|
||||||
'character_class': ['fighter', 'rogue'],
|
'character_class': ['fighter', 'rogue'],
|
||||||
'level': 1,
|
'level': 1,
|
||||||
'armor_class': 10,
|
'armor_class': 10,
|
||||||
'max_hit_points': 14,
|
'max_hit_points': 14,
|
||||||
'hit_points': 14,
|
'hit_points': 14,
|
||||||
'temp_hit_points': 0,
|
'temp_hit_points': 0,
|
||||||
'passive_perception': 10,
|
|
||||||
'passive_insight': 10,
|
|
||||||
'passive_investigation': 10,
|
|
||||||
'speed': '30 ft.',
|
'speed': '30 ft.',
|
||||||
'str': 16,
|
'str': 16,
|
||||||
'dex': 12,
|
'dex': 12,
|
||||||
|
@ -71,7 +73,36 @@ data = {
|
||||||
'saving_throws': ['STR', 'CON'],
|
'saving_throws': ['STR', 'CON'],
|
||||||
'skills': ['Acrobatics', 'Animal Handling'],
|
'skills': ['Acrobatics', 'Animal Handling'],
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
|
||||||
|
'ClassAttribute': [
|
||||||
|
{
|
||||||
|
'character_class_id': 1,
|
||||||
|
'name': 'Fighting Style',
|
||||||
|
'value': 'Archery',
|
||||||
|
'level': 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'AncestryTrait': [
|
||||||
|
{
|
||||||
|
'id': 1,
|
||||||
|
'ancestry_id': 1,
|
||||||
|
'description': '+1 to All Ability Scores',
|
||||||
|
'level': 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'Modifier': [
|
||||||
|
{'source_table_name': 'ancestry_trait', 'source_table_id': 1, 'value': '+1', 'type': 'stat', 'target': 'str'},
|
||||||
|
{'source_table_name': 'ancestry_trait', 'source_table_id': 1, 'value': '+1', 'type': 'stat', 'target': 'dex'},
|
||||||
|
{'source_table_name': 'ancestry_trait', 'source_table_id': 1, 'value': '+1', 'type': 'stat', 'target': 'con'},
|
||||||
|
{'source_table_name': 'ancestry_trait', 'source_table_id': 1, 'value': '+1', 'type': 'stat', 'target': 'int'},
|
||||||
|
{'source_table_name': 'ancestry_trait', 'source_table_id': 1, 'value': '+1', 'type': 'stat', 'target': 'wis'},
|
||||||
|
{'source_table_name': 'ancestry_trait', 'source_table_id': 1, 'value': '+1', 'type': 'stat', 'target': 'cha'},
|
||||||
|
{'source_table_name': 'class_attribute', 'source_table_id': 1, 'value': '+2', 'type': 'weapon ', 'target': 'ranged'},
|
||||||
|
],
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from sqlalchemy import String
|
||||||
from sqlalchemy import ForeignKey
|
from sqlalchemy import ForeignKey
|
||||||
from sqlalchemy import Enum
|
from sqlalchemy import Enum
|
||||||
from sqlalchemy import Text
|
from sqlalchemy import Text
|
||||||
|
from sqlalchemy import UniqueConstraint
|
||||||
|
|
||||||
from ttfrog.db.base import Bases, BaseObject, IterableMixin
|
from ttfrog.db.base import Bases, BaseObject, IterableMixin
|
||||||
from ttfrog.db.base import multivalue_string_factory
|
from ttfrog.db.base import multivalue_string_factory
|
||||||
|
@ -54,6 +55,18 @@ class Ancestry(*Bases):
|
||||||
return str(self.name)
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
|
class AncestryTrait(BaseObject, IterableMixin):
|
||||||
|
__tablename__ = "ancestry_trait"
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
ancestry_id = Column(Integer, ForeignKey("ancestry.id"), nullable=False)
|
||||||
|
name = Column(String, nullable="False")
|
||||||
|
description = Column(Text)
|
||||||
|
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20})
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
|
class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
|
||||||
__tablename__ = "character_class"
|
__tablename__ = "character_class"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
@ -66,29 +79,52 @@ class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
|
||||||
return str(self.name)
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
|
class ClassAttribute(BaseObject, IterableMixin):
|
||||||
|
__tablename__ = "class_attribute"
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
character_class_id = Column(Integer, ForeignKey("character_class.id"), nullable=False)
|
||||||
|
name = Column(String, nullable="False")
|
||||||
|
value = Column(String, nullable="False")
|
||||||
|
description = Column(Text)
|
||||||
|
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20})
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
class Character(*Bases, CharacterClassMixin, SavingThrowsMixin, SkillsMixin):
|
class Character(*Bases, CharacterClassMixin, SavingThrowsMixin, SkillsMixin):
|
||||||
__tablename__ = "character"
|
__tablename__ = "character"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
ancestry = Column(String, ForeignKey("ancestry.name"), nullable=False)
|
ancestry = Column(String, ForeignKey("ancestry.name"), nullable=False)
|
||||||
name = Column(String(255), nullable=False)
|
name = Column(String, default='New Character', nullable=False)
|
||||||
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20})
|
level = Column(Integer, default=1, nullable=False, info={'min': 1, 'max': 20})
|
||||||
armor_class = Column(Integer, nullable=False, info={'min': 1, 'max': 99})
|
armor_class = Column(Integer, default=10, nullable=False, info={'min': 1, 'max': 99})
|
||||||
hit_points = Column(Integer, nullable=False, info={'min': 0, 'max': 999})
|
hit_points = Column(Integer, default=1, nullable=False, info={'min': 0, 'max': 999})
|
||||||
max_hit_points = Column(Integer, nullable=False, info={'min': 0, 'max': 999})
|
max_hit_points = Column(Integer, default=1, nullable=False, info={'min': 0, 'max': 999})
|
||||||
temp_hit_points = Column(Integer, nullable=False, info={'min': 0})
|
temp_hit_points = Column(Integer, default=0, nullable=False, info={'min': 0})
|
||||||
passive_perception = Column(Integer, nullable=False)
|
|
||||||
passive_insight = Column(Integer, nullable=False)
|
|
||||||
passive_investigation = Column(Integer, nullable=False)
|
|
||||||
speed = Column(String, nullable=False, default="30 ft.")
|
speed = Column(String, nullable=False, default="30 ft.")
|
||||||
str = Column(Integer, info={'min': 0, 'max': 30})
|
str = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||||
dex = Column(Integer, info={'min': 0, 'max': 30})
|
dex = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||||
con = Column(Integer, info={'min': 0, 'max': 30})
|
con = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||||
int = Column(Integer, info={'min': 0, 'max': 30})
|
int = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||||
wis = Column(Integer, info={'min': 0, 'max': 30})
|
wis = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||||
cha = Column(Integer, info={'min': 0, 'max': 30})
|
cha = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||||
proficiencies = Column(String)
|
proficiencies = Column(String)
|
||||||
|
|
||||||
|
|
||||||
|
class Modifier(BaseObject, IterableMixin):
|
||||||
|
__tablename__ = "modifier"
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint('source_table_name', 'source_table_id', 'value', 'type', 'target'),
|
||||||
|
)
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
source_table_name = Column(String, index=True, nullable=False)
|
||||||
|
source_table_id = Column(Integer, index=True, nullable=False)
|
||||||
|
value = Column(String, nullable=False)
|
||||||
|
type = Column(String, nullable=False)
|
||||||
|
target = Column(String, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class TransactionLog(BaseObject, IterableMixin):
|
class TransactionLog(BaseObject, IterableMixin):
|
||||||
__tablename__ = "transaction_log"
|
__tablename__ = "transaction_log"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
|
|
@ -6,7 +6,6 @@ from collections import defaultdict
|
||||||
from pyramid.httpexceptions import HTTPFound
|
from pyramid.httpexceptions import HTTPFound
|
||||||
from pyramid.interfaces import IRoutesMapper
|
from pyramid.interfaces import IRoutesMapper
|
||||||
|
|
||||||
from ttfrog.attribute_map import AttributeMap
|
|
||||||
from ttfrog.db.manager import db
|
from ttfrog.db.manager import db
|
||||||
from ttfrog.db import transaction_log
|
from ttfrog.db import transaction_log
|
||||||
|
|
||||||
|
@ -69,6 +68,8 @@ class BaseController:
|
||||||
self._form = self.model_form(self.request.POST, obj=self.record)
|
self._form = self.model_form(self.request.POST, obj=self.record)
|
||||||
else:
|
else:
|
||||||
self._form = self.model_form(obj=self.record)
|
self._form = self.model_form(obj=self.record)
|
||||||
|
if not self.record.id:
|
||||||
|
self._form.process()
|
||||||
return self._form
|
return self._form
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -82,18 +83,16 @@ class BaseController:
|
||||||
self.attrs['all_records'] = db.query(self.model).all()
|
self.attrs['all_records'] = db.query(self.model).all()
|
||||||
|
|
||||||
def template_context(self, **kwargs) -> dict:
|
def template_context(self, **kwargs) -> dict:
|
||||||
return AttributeMap.from_dict({
|
return dict(
|
||||||
'c': dict(
|
config=self.config,
|
||||||
config=self.config,
|
request=self.request,
|
||||||
request=self.request,
|
form=self.form,
|
||||||
form=self.form,
|
record=self.record,
|
||||||
record=self.record,
|
routes=get_all_routes(self.request),
|
||||||
routes=get_all_routes(self.request),
|
resources=self.resources,
|
||||||
resources=self.resources,
|
**self.attrs,
|
||||||
**self.attrs,
|
**kwargs,
|
||||||
**kwargs,
|
)
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
if not self.form.save.data:
|
if not self.form.save.data:
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
from ttfrog.webserver.controllers.base import BaseController
|
from ttfrog.webserver.controllers.base import BaseController
|
||||||
from ttfrog.webserver.forms import DeferredSelectField, DeferredSelectMultipleField
|
from ttfrog.webserver.forms import DeferredSelectField, DeferredSelectMultipleField
|
||||||
from ttfrog.db.schema import Character, Ancestry, CharacterClass, STATS
|
from ttfrog.db.schema import Character, Ancestry, CharacterClass, AncestryTrait, Modifier, STATS
|
||||||
|
from ttfrog.db.manager import db
|
||||||
|
from ttfrog.attribute_map import AttributeMap
|
||||||
|
|
||||||
from wtforms_alchemy import ModelForm
|
from wtforms_alchemy import ModelForm
|
||||||
from wtforms.fields import SubmitField, SelectMultipleField
|
from wtforms.fields import SubmitField, SelectMultipleField
|
||||||
from wtforms.widgets import Select
|
from wtforms.widgets import Select
|
||||||
|
@ -35,3 +38,14 @@ class CharacterSheet(BaseController):
|
||||||
return super().resources + [
|
return super().resources + [
|
||||||
{'type': 'script', 'uri': 'js/character_sheet.js'},
|
{'type': 'script', 'uri': 'js/character_sheet.js'},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def template_context(self, **kwargs) -> dict:
|
||||||
|
ctx = super().template_context(**kwargs)
|
||||||
|
if self.record.ancestry:
|
||||||
|
ancestry = db.query(Ancestry).filter_by(name=self.record.ancestry).one()
|
||||||
|
ctx['traits'] = {}
|
||||||
|
for trait in db.query(AncestryTrait).filter_by(ancestry_id=ancestry.id).all():
|
||||||
|
ctx['traits'][trait.description] = db.query(Modifier).filter_by(source_table_name=trait.__tablename__, source_table_id=trait.id).all()
|
||||||
|
else:
|
||||||
|
ctx['traits'] = {};
|
||||||
|
return ctx
|
||||||
|
|
|
@ -2,6 +2,10 @@ from pyramid.response import Response
|
||||||
from pyramid.view import view_config
|
from pyramid.view import view_config
|
||||||
from ttfrog.db.manager import db
|
from ttfrog.db.manager import db
|
||||||
from ttfrog.db.schema import Ancestry
|
from ttfrog.db.schema import Ancestry
|
||||||
|
from ttfrog.attribute_map import AttributeMap
|
||||||
|
|
||||||
|
def response_from(controller):
|
||||||
|
return controller.response() or AttributeMap.from_dict({'c': controller.template_context()})
|
||||||
|
|
||||||
|
|
||||||
@view_config(route_name='index')
|
@view_config(route_name='index')
|
||||||
|
@ -12,5 +16,4 @@ def index(request):
|
||||||
|
|
||||||
@view_config(route_name='sheet', renderer='character_sheet.html')
|
@view_config(route_name='sheet', renderer='character_sheet.html')
|
||||||
def sheet(request):
|
def sheet(request):
|
||||||
controller = request.context
|
return response_from(request.context)
|
||||||
return controller.response() or controller.template_context()
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user