Boss sheet
This commit is contained in:
parent
d1d8bb8516
commit
31f29772f1
12
hench.mjs
12
hench.mjs
@ -1,4 +1,4 @@
|
||||
import { HenchDataModel } from "./module/data-models.mjs";
|
||||
import { BossDataModel, HenchDataModel } from "./module/data-models.mjs";
|
||||
|
||||
import { HenchActorSheet } from "./module/sheets/hench-actor-sheet.mjs";
|
||||
|
||||
@ -8,6 +8,15 @@ Handlebars.registerHelper('int2checkbox', (size, threshold, options) => {
|
||||
).reduce((prev, next) => (prev + next), "");
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('partialint2checkbox', (size, threshold, start, end, options) => {
|
||||
const indexBase = start + 1;
|
||||
const arrSize = Math.max(end - start, 0);
|
||||
|
||||
return Array(arrSize).fill(0).map(
|
||||
(e, i) => options.fn({ index: i + indexBase, marked: (i + start) < threshold })
|
||||
).reduce((prev, next) => (prev + next), "");
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('partialList', (list, start, end, options) => {
|
||||
return list.slice(start, end).map(
|
||||
(e, i) => options.fn({ item: e, index: (start + i)})
|
||||
@ -28,6 +37,7 @@ Handlebars.registerHelper('decrement', (value) => (value - 1));
|
||||
Hooks.once("init", () => {
|
||||
CONFIG.Actor.dataModels = {
|
||||
hench: HenchDataModel,
|
||||
boss: BossDataModel,
|
||||
};
|
||||
|
||||
Actors.unregisterSheet('core', ActorSheet);
|
||||
|
142
module/boss.mjs
Normal file
142
module/boss.mjs
Normal file
@ -0,0 +1,142 @@
|
||||
export const nullStorylineKey = "FREEFORM";
|
||||
|
||||
export const storylineKeys = [
|
||||
nullStorylineKey,
|
||||
"BOOTSTRAPPER",
|
||||
"VENGEANCE",
|
||||
"DOWNWARD SPIRAL",
|
||||
"DOMINION",
|
||||
"CLEANUP CREW"
|
||||
];
|
||||
|
||||
export function getBossMutation() {
|
||||
return bossData;
|
||||
}
|
||||
|
||||
const bossData = {
|
||||
details: [
|
||||
{
|
||||
question: "What fuels your quest for villainy?",
|
||||
answer: "",
|
||||
},
|
||||
{
|
||||
question: "How do you dress your henches?",
|
||||
answer: "",
|
||||
},
|
||||
{
|
||||
question: "How is your lair designed? ",
|
||||
answer: "",
|
||||
},
|
||||
{
|
||||
question: "Where do you draw the line to what you will do? ",
|
||||
answer: "",
|
||||
},
|
||||
],
|
||||
moves: [
|
||||
{
|
||||
marked: true,
|
||||
name: "Force of Will",
|
||||
description: `Whenever you act, do not draw. Simply declare what you do, and what happens after.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Extra Armorment",
|
||||
description: `Write in an additional gear type on each of your henches' sheets. They can take it on any future mission.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Rotating Armorment I",
|
||||
description: `Erase the write-in gear from any number of your henches' sheets, and write in a new equipment.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Rotating Armorment II",
|
||||
description: `Erase the write-in gear from any number of your henches' sheets, and write in a new equipment.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Rotating Armorment III",
|
||||
description: `Erase the write-in gear from any number of your henches' sheets, and write in a new equipment.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Specialized Training",
|
||||
description: `Write a new inclination below. It applies for all of your henches.`,
|
||||
hasWriteIn: true,
|
||||
writeIn: "",
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "General Training",
|
||||
description: `When you take this ability, fill the experience track of each of your henches immediately.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Exceptional Planning",
|
||||
description: `At the start of each mission, remove one card from the deck. Any time one of your henches draws, you may replace the card they play with the removed card.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Limelight Lovers",
|
||||
description: `If your heat is in Tier III, your henches can take 1 stress to take +1 card to any draw <em>(possibly drawing 4)</em>.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Plot Armor",
|
||||
description: `Once per mission, one of your henches can ignore any single instance of harm they would take.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Play it Off",
|
||||
description: `Your henches can resist consequences by taking 1 harm "Laughing Stock", instead of marking stress. They can do this even if all of their stress is marked.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Guild Bigwig",
|
||||
description: `During fallout, you may draw a second time and use that card instead. Explain who intervenes on your behalf, and how.`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
name: "Exit Plan",
|
||||
description: `When you take this move, pick a hench to pass the mantle on to. You retire to peace, and they take your place. Is there any ceremony to the transfer? Who knows, and who doesn't?`,
|
||||
hasWriteIn: false,
|
||||
writeIn: null,
|
||||
},
|
||||
],
|
||||
experienceTriggers: [
|
||||
{
|
||||
marked: false,
|
||||
description: "Your henches followed even your most superfluous orders.",
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
description: "You achieved your evil ambitions.",
|
||||
},
|
||||
{
|
||||
marked: false,
|
||||
description: "News of your deeds spreads far and wide.",
|
||||
},
|
||||
]
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
const { HTMLField, SchemaField, NumberField, StringField, BooleanField, FilePathField, ArrayField } = foundry.data.fields;
|
||||
|
||||
import { getBossMutation, nullStorylineKey, storylineKeys } from './boss.mjs';
|
||||
import { nullPlaybookKey, playbookKeys, lookupPlaybook, getPlaybookMutation } from './playbooks.mjs';
|
||||
|
||||
const textField = () => new StringField({ required: true, blank: true });
|
||||
@ -110,4 +111,42 @@ export class HenchDataModel extends foundry.abstract.TypeDataModel {
|
||||
this.customGear,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export class BossDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
return {
|
||||
look: textField(),
|
||||
details: cappedArrayField(promptField(), 4),
|
||||
storyline: new StringField({ required: true, blank: false, initial: nullStorylineKey, options: storylineKeys}),
|
||||
|
||||
heat: new NumberField({ required: true, integer: true, min: 0, initial: 0, max: 18}),
|
||||
|
||||
experienceTriggers: cappedArrayField(markableField(), 4),
|
||||
experience: new NumberField({ required: true, integer: true, min: 0, initial: 0, max: 5 }),
|
||||
|
||||
moves: cappedArrayField(moveField(), 13),
|
||||
};
|
||||
}
|
||||
|
||||
static migrateData(source) {
|
||||
// no migrations yet
|
||||
|
||||
return super.migrateData(source);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _preCreate(data, options, user) {
|
||||
await super._preCreate(data, options, user);
|
||||
|
||||
const initMutation = getBossMutation();
|
||||
|
||||
return this.updateSource(initMutation);
|
||||
}
|
||||
|
||||
get tier() {
|
||||
const divisor = 18 / 3;
|
||||
|
||||
return Math.ceil(this.heat / divisor);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { playbookKeys, validatePlaybookKey, getPlaybookMutation } from "../playbooks.mjs";
|
||||
import { updateField } from "../helpers/mutation-helper.mjs";
|
||||
import { storylineKeys } from "../boss.mjs";
|
||||
|
||||
export class HenchActorSheet extends ActorSheet {
|
||||
/** @override */
|
||||
@ -13,10 +14,12 @@ export class HenchActorSheet extends ActorSheet {
|
||||
const context = super.getData();
|
||||
|
||||
context.playbookKeys = playbookKeys.map((k) => ({ key: k, selected: k === this.actor.system.playbook}));
|
||||
context.storylineKeys = storylineKeys.map((k) => ({ key: k, selected: k === this.actor.system.storyline}));
|
||||
|
||||
// TODO define system constants for these
|
||||
context.maxStress = 12;
|
||||
context.maxExp = 5;
|
||||
context.maxHeat = 18;
|
||||
|
||||
context.minGear = 3;
|
||||
context.maxGear = 5;
|
||||
@ -54,6 +57,14 @@ export class HenchActorSheet extends ActorSheet {
|
||||
updateField(this.actor, path, value);
|
||||
});
|
||||
|
||||
// normal dropdowns
|
||||
html.find('.hench-hench-sheet-dropdown').on('change', (event) => {
|
||||
const value = event.target.value;
|
||||
const path = event.currentTarget.dataset.fieldPath;
|
||||
|
||||
updateField(this.actor, path, value);
|
||||
});
|
||||
|
||||
// text fields
|
||||
html.find('.hench-text-input').on('change', async (event) => {
|
||||
const element = event.currentTarget;
|
||||
|
@ -48,6 +48,11 @@
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.hench-huge {
|
||||
font-size: 3em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Flexbox */
|
||||
.hench-row {
|
||||
display: flex;
|
||||
|
169
templates/actors/boss.hbs
Normal file
169
templates/actors/boss.hbs
Normal file
@ -0,0 +1,169 @@
|
||||
<form>
|
||||
<div class="hench-sheet-container hench-padding-wide hench-gap-wide hench-white">
|
||||
<!-- ID row -->
|
||||
<div class="hench-row hench-gap-wide">
|
||||
<!-- core-->
|
||||
<div class="hench-box hench-flex-resizeable hench-l-grey hench-padding-wide hench-gap-narrow">
|
||||
<!-- Name -->
|
||||
<div class="hench-field hench-row hench-gap-narrow">
|
||||
<label class="hench-flex-fixed" for="hench-name">Name: </label>
|
||||
<input type="text" name="hench-name" class="hench-text-input hench-flex-resizeable" value="{{actor.name}}" data-field-path="name" />
|
||||
</div>
|
||||
<!-- Look -->
|
||||
<div class="hench-field hench-row hench-gap-narrow">
|
||||
<label class="hench-flex-fixed" for="hench-look">Look: </label>
|
||||
<input type="text" name="hench-look" class="hench-text-input hench-flex-resizeable" value="{{actor.system.look}}" data-field-path="system.look" />
|
||||
</div>
|
||||
<!-- Storyline -->
|
||||
<div class="hench-field hench-row hench-gap-narrow">
|
||||
<label class="hench-flex-fixed" for="hench-storyline">Storyline: </label>
|
||||
<select name="hench-storyline" class="hench-hench-sheet-dropdown hench-flex-resizeable" data-field-path="system.storyline">
|
||||
{{#each storylineKeys}}
|
||||
<option value="{{this.key}}" {{#if this.selected}}selected{{/if}}>{{this.key}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hench-box hench-d-grey hench-flex-resizeable hench-centered hench-huge">
|
||||
THE BOSS
|
||||
</div>
|
||||
<!-- icon -->
|
||||
<div class="hench-box hench-l-grey hench-flex-fixed">
|
||||
<img src="{{actor.img}}" data-edit="img" class="hench-icon" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Big Box -->
|
||||
<div class="hench-row hench-gap-wide">
|
||||
<!-- Column -->
|
||||
<div class="hench-box hench-box-stretch hench-flex-fixed hench-gap-wide">
|
||||
<!-- Details -->
|
||||
<div class="hench-row hench-flex-resizeable">
|
||||
<div class="hench-box hench-l-grey hench-flex-resizeable hench-padding-wide hench-gap-narrow">
|
||||
<div class="hench-row hench-flex-resizeable">
|
||||
<div class="hench-centered hench-title hench-flex-resizeable">
|
||||
Details
|
||||
</div>
|
||||
</div>
|
||||
{{#each actor.system.details}}
|
||||
<div class="hench-row">
|
||||
<label for="hench-detail-{{@index}}">{{this.question}}</label>
|
||||
</div>
|
||||
<div class="hench-row">
|
||||
<input name="hench-detail-{{@index}}" type="text" class="hench-text-input" data-field-path="system.details[{{@index}}].answer" value="{{this.answer}}" />
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Heat -->
|
||||
<div class="hench-row hench-flex-resizeable">
|
||||
<div class="hench-box hench-l-grey hench-flex-resizeable hench-padding-wide hench-gap-narrow">
|
||||
<div class="hench-row hench-flex-resizeable">
|
||||
<div class="hench-centered hench-title hench-flex-resizeable">
|
||||
Heat
|
||||
</div>
|
||||
</div>
|
||||
<div class="hench-row hench-centered hench-flex-resizeable">
|
||||
<div class="hench-flex-resizeable hench-centered">
|
||||
<strong>Tier I</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hench-row hench-centered hench-flex-resizeable">
|
||||
<div class="hench-flex-resizeable hench-centered">
|
||||
<em>(Nuisance, low-stakes, local cops)</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hench-row hench-flex-resizeable hench-row-even hench-gap-narrow">
|
||||
{{#partialint2checkbox maxHeat actor.system.heat 0 6}}
|
||||
<input type="checkbox" class="hench-checkbox-int-field" data-field-path="system.heat" data-value="{{index}}" {{#if marked}} checked {{/if}} />
|
||||
{{/partialint2checkbox}}
|
||||
</div>
|
||||
<div class="hench-row hench-centered hench-flex-resizeable">
|
||||
<div class="hench-flex-resizeable hench-centered">
|
||||
<strong>Tier II</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hench-row hench-centered hench-flex-resizeable">
|
||||
<div class="hench-flex-resizeable hench-centered">
|
||||
<em>(Full-time, classic stakes, local super)</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hench-row hench-flex-resizeable hench-row-even hench-gap-narrow">
|
||||
{{#partialint2checkbox maxHeat actor.system.heat 6 12}}
|
||||
<input type="checkbox" class="hench-checkbox-int-field" data-field-path="system.heat" data-value="{{index}}" {{#if marked}} checked {{/if}} />
|
||||
{{/partialint2checkbox}}
|
||||
</div>
|
||||
<div class="hench-row hench-centered hench-flex-resizeable">
|
||||
<div class="hench-flex-resizeable hench-centered">
|
||||
<strong>Tier III</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hench-row hench-centered hench-flex-resizeable">
|
||||
<div class="hench-flex-resizeable hench-centered">
|
||||
<em>(Top-class, high-stakes, world's finest heroes)</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hench-row hench-flex-resizeable hench-row-even hench-gap-narrow">
|
||||
{{#partialint2checkbox maxHeat actor.system.heat 12 18}}
|
||||
<input type="checkbox" class="hench-checkbox-int-field" data-field-path="system.heat" data-value="{{index}}" {{#if marked}} checked {{/if}} />
|
||||
{{/partialint2checkbox}}
|
||||
</div>
|
||||
<!-- maybe add guidelines down here? -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- Experience -->
|
||||
<div class="hench-row hench-flex-resizeable">
|
||||
<div class="hench-box hench-l-grey hench-flex-resizeable hench-padding-narrow hench-gap-narrow">
|
||||
<div class="hench-row hench-flex-resizeable">
|
||||
<div class="hench-centered hench-title hench-flex-resizeable">
|
||||
Experience
|
||||
</div>
|
||||
</div>
|
||||
<div class="hench-row hench-row-even hench-gap-narrow hench-flex-resizeable">
|
||||
{{#int2checkbox maxExp actor.system.experience}}
|
||||
<input type="checkbox" class="hench-checkbox-int-field" data-field-path="system.experience" data-value="{{index}}" {{#if marked}} checked {{/if}} />
|
||||
{{/int2checkbox}}
|
||||
</div>
|
||||
{{#each actor.system.experienceTriggers}}
|
||||
<div class="hench-row hench-m-grey hench-flex-resizeable hench-padding-narrow hench-gap-narrow">
|
||||
<div class="hench-box hench-flex-fixed">
|
||||
<input type="checkbox" class="hench-checkbox hench-checkbox-toggle-field" data-field-path="system.experienceTriggers[{{@index}}].marked" {{#if this.marked}} checked {{/if}} />
|
||||
</div>
|
||||
<div class="hench-box hench-flex-resizeable">
|
||||
{{this.description}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Moves -->
|
||||
<div class="hench-box hench-flex-resizeable hench-m-grey hench-padding-narrow hench-gap-narrow">
|
||||
<div class="hench-row">
|
||||
<div class="hench-centered hench-title hench-flex-resizeable">
|
||||
Abilities
|
||||
</div>
|
||||
</div>
|
||||
{{#each actor.system.moves}}
|
||||
<div class="hench-row hench-l-grey hench-flex-resizeable hench-gap-narrow">
|
||||
<div class="hench-box hench-flex-fixed">
|
||||
<input type="checkbox" name="hench-move-checkbox-{{@index}}" class="hench-checkbox-toggle-field" data-field-path="system.moves[{{@index}}].marked" {{#if this.marked}}checked{{/if}} />
|
||||
</div>
|
||||
<div class="hench-box hench-flex-resizeable hench-gap-narrow hench-padding-narrow">
|
||||
<div>
|
||||
<label for="hench-move-checkbox-{{@index}}"><em><strong>{{this.name}}</strong></em></label>
|
||||
</div>
|
||||
<div>
|
||||
{{{this.description}}}
|
||||
</div>
|
||||
{{#if this.hasWriteIn}}
|
||||
<div>
|
||||
<input type="text" name="hench-move-writein-{{$index}}" class="hench-text-field" data-field-path="system.moves[{{@index}}].writein" value="{{this.writein}}" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
Loading…
x
Reference in New Issue
Block a user