adding defaults, modifiers, traits and attributes

This commit is contained in:
evilchili 2024-02-08 23:04:28 -08:00
parent da6255a86a
commit 8bde2ab5f3
8 changed files with 178 additions and 40 deletions

View File

@ -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() {
return parseInt(document.getElementById('proficiency_bonus').innerHTML);
}
@ -12,6 +29,19 @@ function setStatBonus(stat) {
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() {
var score = document.getElementById('level').value;
var bonus = Math.ceil(1 + (0.25 * score));
@ -25,7 +55,9 @@ function setSpellSaveDC() {
(function () {
const stats = ['str', 'dex', 'con', 'int', 'wis', 'cha'];
stats.forEach(applyStatModifiers);
stats.forEach(setStatBonus);
setProficiencyBonus();
setSpellSaveDC();
})();

View File

@ -18,6 +18,7 @@
{% block content %}{% endblock %}
</div>
{% block debug %}{% endblock %}
{% block script %}{% endblock %}
{% for resource in c.resources %}
{% if resource['type'] == 'script' %}
<script type="text/javascript" src="{{c.routes.static}}/{{resource['uri']}}"></script>

View File

@ -11,7 +11,7 @@
<div class='banner'>
<div><img id='portrait' /></div>
<div>
<h1>{{ c.record.name }}</h1>
<h1>{{ c.form.name }}</h1>
{{ c.form.ancestry }} {{ c.record.character_class|join(' / ') }} &nbsp; Level {{ c.form.level }}
</div>
</div>
@ -73,3 +73,25 @@
{{ c }}
</code>
{% 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 %}

View File

@ -9,6 +9,7 @@ from sqlalchemy.exc import IntegrityError
data = {
'CharacterClass': [
{
'id': 1,
'name': 'fighter',
'hit_dice': '1d10',
'hit_dice_stat': 'CON',
@ -17,6 +18,7 @@ data = {
'skills': ['Acrobatics', 'Animal Handling', 'Athletics', 'History', 'Insight', 'Intimidation', 'Perception', 'Survival'],
},
{
'id': 2,
'name': 'rogue',
'hit_dice': '1d8',
'hit_dice_stat': 'DEX',
@ -25,6 +27,7 @@ data = {
'skills': ['Acrobatics', 'Athletics', 'Deception', 'Insight', 'Intimidation', 'Investigation', 'Perception', 'Performance', 'Persuasion', 'Sleight of Hand', 'Stealth'],
},
],
'Skill': [
{'name': 'Acrobatics'},
{'name': 'Animal Handling'},
@ -41,25 +44,24 @@ data = {
{'name': 'Stealth'},
{'name': 'Survival'},
],
'Ancestry': [
{'name': 'human', 'creature_type': 'humanoid'},
{'name': 'dragonborn', 'creature_type': 'humanoid'},
{'name': 'tiefling', 'creature_type': 'humanoid'},
{'id': 1, 'name': 'human', 'creature_type': 'humanoid'},
{'id': 2, 'name': 'dragonborn', 'creature_type': 'humanoid'},
{'id': 3, 'name': 'tiefling', 'creature_type': 'humanoid'},
],
'Character': [
{
'id': 1,
'name': 'Sabetha',
'ancestry': 'tiefling',
'ancestry': 'human',
'character_class': ['fighter', 'rogue'],
'level': 1,
'armor_class': 10,
'max_hit_points': 14,
'hit_points': 14,
'temp_hit_points': 0,
'passive_perception': 10,
'passive_insight': 10,
'passive_investigation': 10,
'speed': '30 ft.',
'str': 16,
'dex': 12,
@ -71,7 +73,36 @@ data = {
'saving_throws': ['STR', 'CON'],
'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'},
],
}

View File

@ -6,6 +6,7 @@ from sqlalchemy import String
from sqlalchemy import ForeignKey
from sqlalchemy import Enum
from sqlalchemy import Text
from sqlalchemy import UniqueConstraint
from ttfrog.db.base import Bases, BaseObject, IterableMixin
from ttfrog.db.base import multivalue_string_factory
@ -54,6 +55,18 @@ class Ancestry(*Bases):
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):
__tablename__ = "character_class"
id = Column(Integer, primary_key=True, autoincrement=True)
@ -66,29 +79,52 @@ class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
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):
__tablename__ = "character"
id = Column(Integer, primary_key=True, autoincrement=True)
ancestry = Column(String, ForeignKey("ancestry.name"), nullable=False)
name = Column(String(255), nullable=False)
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20})
armor_class = Column(Integer, nullable=False, info={'min': 1, 'max': 99})
hit_points = Column(Integer, nullable=False, info={'min': 0, 'max': 999})
max_hit_points = Column(Integer, nullable=False, info={'min': 0, 'max': 999})
temp_hit_points = Column(Integer, nullable=False, info={'min': 0})
passive_perception = Column(Integer, nullable=False)
passive_insight = Column(Integer, nullable=False)
passive_investigation = Column(Integer, nullable=False)
name = Column(String, default='New Character', nullable=False)
level = Column(Integer, default=1, nullable=False, info={'min': 1, 'max': 20})
armor_class = Column(Integer, default=10, nullable=False, info={'min': 1, 'max': 99})
hit_points = Column(Integer, default=1, 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, default=0, nullable=False, info={'min': 0})
speed = Column(String, nullable=False, default="30 ft.")
str = Column(Integer, info={'min': 0, 'max': 30})
dex = Column(Integer, info={'min': 0, 'max': 30})
con = Column(Integer, info={'min': 0, 'max': 30})
int = Column(Integer, info={'min': 0, 'max': 30})
wis = Column(Integer, info={'min': 0, 'max': 30})
cha = Column(Integer, info={'min': 0, 'max': 30})
str = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
dex = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
con = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
int = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
wis = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
cha = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
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):
__tablename__ = "transaction_log"
id = Column(Integer, primary_key=True, autoincrement=True)

View File

@ -6,7 +6,6 @@ from collections import defaultdict
from pyramid.httpexceptions import HTTPFound
from pyramid.interfaces import IRoutesMapper
from ttfrog.attribute_map import AttributeMap
from ttfrog.db.manager import db
from ttfrog.db import transaction_log
@ -69,6 +68,8 @@ class BaseController:
self._form = self.model_form(self.request.POST, obj=self.record)
else:
self._form = self.model_form(obj=self.record)
if not self.record.id:
self._form.process()
return self._form
@property
@ -82,18 +83,16 @@ class BaseController:
self.attrs['all_records'] = db.query(self.model).all()
def template_context(self, **kwargs) -> dict:
return AttributeMap.from_dict({
'c': dict(
config=self.config,
request=self.request,
form=self.form,
record=self.record,
routes=get_all_routes(self.request),
resources=self.resources,
**self.attrs,
**kwargs,
)
})
return dict(
config=self.config,
request=self.request,
form=self.form,
record=self.record,
routes=get_all_routes(self.request),
resources=self.resources,
**self.attrs,
**kwargs,
)
def save(self):
if not self.form.save.data:

View File

@ -1,6 +1,9 @@
from ttfrog.webserver.controllers.base import BaseController
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.fields import SubmitField, SelectMultipleField
from wtforms.widgets import Select
@ -35,3 +38,14 @@ class CharacterSheet(BaseController):
return super().resources + [
{'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

View File

@ -2,6 +2,10 @@ from pyramid.response import Response
from pyramid.view import view_config
from ttfrog.db.manager import db
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')
@ -12,5 +16,4 @@ def index(request):
@view_config(route_name='sheet', renderer='character_sheet.html')
def sheet(request):
controller = request.context
return controller.response() or controller.template_context()
return response_from(request.context)