diff --git a/README.md b/README.md index 4425b9e..50f6710 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -# hench \ No newline at end of file +# HENCH + +An implementation of *HENCH (2025)* by Chloe Herd (myself) and Cassandra Grove. + +## Current Release Version: +N/A (still in development). + +## Next Target Release Version: +Draft 0 + +Discord Link: N/A \ No newline at end of file diff --git a/hench.mjs b/hench.mjs new file mode 100644 index 0000000..b8b5c29 --- /dev/null +++ b/hench.mjs @@ -0,0 +1,15 @@ +import { HenchDataModel } from "./module/data-models.mjs"; + +import { HenchDebugSheet } from "./module/sheets/hench-debug.mjs"; + +Hooks.once("init", () => { + CONFIG.Actor.dataModels = { + hench: HenchDataModel, + }; + + Actors.unregisterSheet('core', ActorSheet); + Actors.registerSheet('henchDebug', HenchDebugSheet, { + makeDefault: true, + label: 'Hench Debug Sheet', + }); +}); \ No newline at end of file diff --git a/module/data-models.mjs b/module/data-models.mjs new file mode 100644 index 0000000..850daa2 --- /dev/null +++ b/module/data-models.mjs @@ -0,0 +1,105 @@ +const { HTMLField, SchemaField, NumberField, StringField, BooleanField, FilePathField, ArrayField } = foundry.data.fields; + +import { nullPlaybookKey, playbookKeys, lookupPlaybook } from './playbooks.mjs'; + +const textField = () => new StringField({ required: true, blank: true }); + +const promptField = () => new SchemaField({ + question: textField(), + answer: textField() +}); + +const harmField = () => new SchemaField({ + marked: new BooleanField({ required: true }), + description: textField(), +}); + +export class HenchDataModel extends foundry.abstract.TypeDataModel { + static defineSchema() { + return { + name: textField(), + look: textField(), + detailAnswers: new SchemaField({ + one: textField(), + two: textField(), + }), + customInclination: textField(), + harm: new SchemaField({ + levelOne: new SchemaField({ + one: harmField(), + two: harmField(), + }), + levelTwo: new SchemaField({ + one: harmField(), + two: harmField(), + }), + levelThree: new SchemaField({ + one: harmField(), + }), + levelFour: new SchemaField({ + one: harmField(), + }), + }), + stress: new NumberField({ required: true, integer: true, min: 0, initial: 0, max: 12 }), + experience: new NumberField({ required: true, integer: true, min: 0, initial: 0, max: 5 }), + + playbook: new StringField({ required: true, blank: false, initial: nullPlaybookKey, options: playbookKeys }), + }; + } + + static migrateData(source) { + // No migrations yet - base case. + + return super.migrateData(source); + } + + get dead() { + return !!this.harm.levelFour.marked; + } + + get playbookDetails() { + return lookupPlaybook(this.playbook); + } + + // TODO IMPLEMENT advancements + get gearLimit() { + return 3; + } + + get hasPlaybookSelected() { + return this.playbook !== nullPlaybookKey; + } + + get detailQuestions() { + return this.playbookDetails?.detailQuestions; + } + + get inclinations() { + const base = this.playbookDetails?.inclinations ?? {}; + + return { + ...base, + custom: this.customInclination + }; + } + + get missionPlanningQuestions() { + return this.playbookDetails?.missionPlanningQuestions; + } + + get expTriggers() { + const fromPlaybook = this.playbookDetails?.expTrigger; + + let triggers = { + one: "You acted on your inclinations.", + two: "You made the boss proud.", + three: "Your home life interfered with the mission." + }; + + if(fromPlaybook) { + triggers.four = fromPlaybook; + } + + return triggers; + } +} \ No newline at end of file diff --git a/module/playbooks.mjs b/module/playbooks.mjs new file mode 100644 index 0000000..3673703 --- /dev/null +++ b/module/playbooks.mjs @@ -0,0 +1,132 @@ +export const nullPlaybookKey = "NONE"; + +export const playbookKeys = [ + nullPlaybookKey, + "SUPERFAN", + "BADASS", + "LABMAN", + "DEMOTED", + "OUTCAST", + "USURPER", + "TIMECARD", +]; + +const playbooks = { + NONE: null, + SUPERFAN: { + detailQuestions: { + one: "What's the coolest thing you've ever done?", + two: "What's your favorite story about the boss? ", + }, + inclinations: { + one: "Drawing on your knowledge of comic lore", + two: "Acting with naive optimism", + }, + missionPlanningQuestions: { + one: "You've read an issue about the target. Choose a power or a weakness of the target, and explain it.", + two: "Confidently state a poorly thought out plan to achieve the goal.", + three: "Ask the boss: is the target really as cool as it's made out to be?", + }, + expTrigger: "You faced the grim realities of the job.", + }, + BADASS: { + detailQuestions: { + one: "Have you killed on the job? If so, what was your first? ", + two: "How did the boss earn your respect?", + }, + inclinations: { + one: "Applying brute force", + two: "Making a threat", + }, + missionPlanningQuestions: { + one: "You've fought the target before. State where they left a scar, and how they did it.", + two: "The target is an old friend. State how you met, and how it ended.", + three: "Ask the boss: can we kill the target?", + }, + expTrigger: "Your strength couldn't solve the problem at hand.", + }, + LABMAM: { + detailQuestions: { + one: "What was your first invention? ", + two: "What secret project does the boss fund? Why is it doomed? ", + }, + inclinations: { + one: "Using your intellect", + two: "Doing something unexpected", + }, + missionPlanningQuestions: { + one: "Present your new prototype weapon, which the target is probably weak to.", + two: "State a fact about the target's defense systems.", + three: "Ask the boss: what secret research is the target hiding?", + }, + expTrigger: "Your invention failed fantastically.", + }, + DEMOTED: { + detailQuestions: { + one: "What was your old villain name? Why are you just a hench, now?", + two: "What leverage does the boss have on you?", + }, + inclinations: { + one: "Acting with excessive flair", + two: "Attempting to reclaim your status", + }, + missionPlanningQuestions: { + one: "The target's clearly a rookie. State an obvious flaw with their approach or gimmick.", + two: "You've bested the target before. State how, and why you can't just do that again.", + three: "Ask the boss: why hasn't the Guild taken out the target?", + }, + expTrigger: "Your old tricks weren't cut out for the job.", + }, + OUTCAST: { + detailQuestions: { + one: "Why has society rejected you? ", + two: "Why has the boss taken you in? ", + }, + inclinations: { + one: "Attempting to impress someone", + two: "Acting against all social norms", + }, + missionPlanningQuestions: { + one: "The target has treated you cruelly. State when, and how.", + two: "You and the target have something in common. State what, and how that makes you feel.", + three: "Ask the boss: Why do you hate the target?", + }, + expTrigger: "An attempt at connection was rejected.", + }, + USURPER: { + detailQuestions: { + one: "What is your ultimate ambition? ", + two: "What habit of the boss infuriates you the most? ", + }, + inclinations: { + one: "Acting on hidden agendas.", + two: "Putting yourself before anyone else.", + }, + missionPlanningQuestions: { + one: "Describe how the target is uniquely equipped to defeat the boss.", + two: "Describe how you are uniquely equipped to defeat the target.", + three: "Ask the boss: are you really prepared for this mission?", + }, + expTrigger: "You had to act selflessly.", + }, + TIMECARD: { + detailQuestions: { + one: "Who relies on you? ", + two: "How did the boss convince you to take the job?", + }, + inclinations: { + one: "Invoking rules or regulations.", + two: "Taking the laziest approach.", + }, + missionPlanningQuestions: { + one: "State something that, by Guild rule, the target cannot do to you this mission.", + two: "State something you refuse to do this mission.", + three: "Ask the boss: How can we get a bonus on this job?", + }, + expTrigger: "You had to act above and beyond your usual duties.", + }, +}; + +export function lookupPlaybook(key) { + return playbooks[key]; +} \ No newline at end of file diff --git a/module/sheets/hench-debug.mjs b/module/sheets/hench-debug.mjs new file mode 100644 index 0000000..5ef686c --- /dev/null +++ b/module/sheets/hench-debug.mjs @@ -0,0 +1,11 @@ +export class HenchDebugSheet extends ActorSheet { + /** @override */ + get template() { + return `systems/hench/templates/hench-debug.hbs`; + } + + /** @override */ + getData() { + return super.getData(); + } +} \ No newline at end of file diff --git a/system.json b/system.json new file mode 100644 index 0000000..61a458d --- /dev/null +++ b/system.json @@ -0,0 +1,33 @@ +{ + "id": "hench", + "title": "HENCH", + "description": "HENCH (2025)", + "version": "0.0.0", + "compatibility": { + "minimum": "11", + "verified": "11" + }, + "authors": [ + { + "id": "walcutt", + "name": "walcutt", + "discord": "walcutt" + } + ], + "esmodules": [ + "hench.mjs" + ], + "styles": [], + "packs": [], + "languages": [], + "documentTypes": { + "Actor": { + "hench": {}, + "boss": {} + }, + "Item": { + "move": {} + } + }, + "url": "https://github.com/walcutt/hench" +} \ No newline at end of file diff --git a/templates/hench-debug.hbs b/templates/hench-debug.hbs new file mode 100644 index 0000000..72e374b --- /dev/null +++ b/templates/hench-debug.hbs @@ -0,0 +1,71 @@ +
\ No newline at end of file diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..da23960 --- /dev/null +++ b/todo.md @@ -0,0 +1,10 @@ +- [] Define Move Model +- [] Define Hench Model +- [] Define Boss Model +- [x] Define Prompt Model (DEFUNCT) +- [] Define Storyline Model +- [] ? Define Table Model +- [] Design Hench sheet +- [] Design Boss sheet +- [] Implement Hench sheet +- [] Implement Boss sheet \ No newline at end of file