The party sheets in assets/characters/ changed: Darrah_Oakwanderer.pdf is gone, replaced by Darragh Fermwyr (same halfling ranger, renamed and updated), Owayn Fermwyr (stout halfling fighter, BJ) and Dunnchadh (ghostwise halfling druid, Circle of the Blighted, Rick). - app:character:seed now carries those three sheets, read from the PDFs' form fields. Darragh's portrait is renamed to match his new name. - New skill expertise field (double proficiency bonus), since Darragh's Deft Explorer gives him +7 Stealth and the sheet had nowhere to show it. Expertise implies proficiency; the sheet marks it with two dots, the edit form gets a checkbox list next to skill proficiencies. - Migration adds skill_expertise, backfilling existing rows with [] so the NOT NULL JSON column works on tables that already have characters. Tests: expertise math on the entity, and a catalog test that every seed sheet hydrates into a Character that passes validation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
152 lines
7.4 KiB
Twig
152 lines
7.4 KiB
Twig
{% extends 'base.html.twig' %}
|
|
|
|
{% block title %}{{ is_new ? 'New character' : 'Edit ' ~ character.name }} · Characters · dmtools{% endblock %}
|
|
|
|
{% block body %}
|
|
<div class="mb-3">
|
|
<a href="{{ is_new ? path('app_characters') : path('app_characters_show', {id: character.id}) }}"
|
|
class="text-decoration-none small">← {{ is_new ? 'All characters' : 'Back to ' ~ character.name }}</a>
|
|
<h1 class="h3 mb-0">{{ is_new ? 'New character' : 'Edit ' ~ character.name }}</h1>
|
|
<span class="text-body-secondary">Character sheet — game notes live on the character page.</span>
|
|
</div>
|
|
|
|
{{ form_start(form) }}
|
|
<div class="row g-4" style="max-width: 70rem;">
|
|
<div class="col-md-6">
|
|
<div class="card shadow-sm h-100">
|
|
<div class="card-header">Identity</div>
|
|
<div class="card-body">
|
|
{{ form_row(form.name) }}
|
|
{{ form_row(form.playerName) }}
|
|
<div class="row">
|
|
<div class="col-8">{{ form_row(form.race) }}</div>
|
|
<div class="col-4">{{ form_row(form.level) }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-6">{{ form_row(form.characterClass) }}</div>
|
|
<div class="col-6">{{ form_row(form.background) }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-6">{{ form_row(form.alignment) }}</div>
|
|
<div class="col-6">{{ form_row(form.experiencePoints) }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card shadow-sm h-100">
|
|
<div class="card-header">Combat</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-4">{{ form_row(form.armorClass) }}</div>
|
|
<div class="col-4">{{ form_row(form.initiative) }}</div>
|
|
<div class="col-4">{{ form_row(form.speed) }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-4">{{ form_row(form.hitPointMaximum) }}</div>
|
|
<div class="col-4">{{ form_row(form.currentHitPoints) }}</div>
|
|
<div class="col-4">{{ form_row(form.temporaryHitPoints) }}</div>
|
|
</div>
|
|
<div class="row align-items-center">
|
|
<div class="col-4">{{ form_row(form.hitDice) }}</div>
|
|
<div class="col-4">{{ form_row(form.proficiencyBonus) }}</div>
|
|
<div class="col-4 pt-4">{{ form_row(form.inspiration) }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header">Ability scores & proficiencies</div>
|
|
<div class="card-body">
|
|
<div class="row row-cols-3 row-cols-md-6 g-2 mb-2">
|
|
<div>{{ form_row(form.strength) }}</div>
|
|
<div>{{ form_row(form.dexterity) }}</div>
|
|
<div>{{ form_row(form.constitution) }}</div>
|
|
<div>{{ form_row(form.intelligence) }}</div>
|
|
<div>{{ form_row(form.wisdom) }}</div>
|
|
<div>{{ form_row(form.charisma) }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-4">{{ form_row(form.savingThrowProficiencies) }}</div>
|
|
<div class="col-md-4">{{ form_row(form.skillProficiencies) }}</div>
|
|
<div class="col-md-4">{{ form_row(form.skillExpertise) }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header">Attacks & spellcasting</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-sm align-middle mb-2">
|
|
<thead>
|
|
<tr><th>Name</th><th>Atk bonus</th><th>Damage/type</th><th>Range</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for i in 0..5 %}
|
|
{% set atk = character.attacks[i] ?? null %}
|
|
<tr>
|
|
<td><input type="text" class="form-control form-control-sm" name="attacks[{{ i }}][name]" value="{{ atk.name ?? '' }}" placeholder="Weapon or spell"></td>
|
|
<td><input type="text" class="form-control form-control-sm" name="attacks[{{ i }}][bonus]" value="{{ atk.bonus ?? '' }}" placeholder="+5" style="max-width: 6rem"></td>
|
|
<td><input type="text" class="form-control form-control-sm" name="attacks[{{ i }}][damage]" value="{{ atk.damage ?? '' }}" placeholder="1d4+5 piercing"></td>
|
|
<td><input type="text" class="form-control form-control-sm" name="attacks[{{ i }}][range]" value="{{ atk.range ?? '' }}" placeholder="20/60 ft."></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="form-text mb-3">Blank rows are dropped when you save.</div>
|
|
{{ form_row(form.spellcasting) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card shadow-sm h-100">
|
|
<div class="card-header">Traits & equipment</div>
|
|
<div class="card-body">
|
|
{{ form_row(form.featuresTraits) }}
|
|
{{ form_row(form.otherProficienciesLanguages) }}
|
|
{{ form_row(form.equipment) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card shadow-sm h-100">
|
|
<div class="card-header">Personality</div>
|
|
<div class="card-body">
|
|
{{ form_row(form.personalityTraits) }}
|
|
{{ form_row(form.ideals) }}
|
|
{{ form_row(form.bonds) }}
|
|
{{ form_row(form.flaws) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header">Appearance & backstory</div>
|
|
<div class="card-body">
|
|
{{ form_row(form.appearance) }}
|
|
{{ form_row(form.backstory) }}
|
|
{{ form_row(form.alliesOrganizations) }}
|
|
{{ form_row(form.treasure) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3 d-flex gap-2">
|
|
<button class="btn btn-primary">{{ is_new ? 'Add character' : 'Save' }}</button>
|
|
<a href="{{ is_new ? path('app_characters') : path('app_characters_show', {id: character.id}) }}"
|
|
class="btn btn-outline-secondary">Cancel</a>
|
|
</div>
|
|
{{ form_end(form) }}
|
|
{% endblock %}
|