diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c8e074a..2d02c151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### 1.2.0 +* Added support for the Guild Codex magic system (disabled by default) + ### 1.1.1 * Automatically updates the prototype token name when an actors name is changed (if both were identical). Can be disabled in the system settings. diff --git a/src/fatex.ts b/src/fatex.ts index a90240cb..63e099cf 100644 --- a/src/fatex.ts +++ b/src/fatex.ts @@ -35,6 +35,7 @@ import { FateCombat } from "./module/combat/FateCombat"; import { FateXSettings } from "./module/helper/Settings"; import { ChatActionsFeature } from "./module/features/ChatActionsFeature"; import { PrototypeTokenNameSyncFeature } from "./module/features/PrototypeTokenNameSyncFeature"; +import { MagicSystem } from "./module/features/MagicSystem"; /* -------------------------------- */ /* System initialization */ @@ -128,6 +129,7 @@ TemplateActorsFeature.hooks(); ActorGroupFeature.hooks(); ChatActionsFeature.hooks(); PrototypeTokenNameSyncFeature.hooks(); +MagicSystem.hooks(); /* -------------------------------- */ /* Webpack HMR */ diff --git a/src/module/chat/FateRoll.ts b/src/module/chat/FateRoll.ts index d3304dba..a9370522 100644 --- a/src/module/chat/FateRoll.ts +++ b/src/module/chat/FateRoll.ts @@ -8,6 +8,14 @@ export class FateRoll extends FateRollDataModel { static createFromSkill(skill: SkillItemData & ItemDataProperties, { magic = false } = {}) { const options = { magic }; + if (game.settings.get("fatex", "guildCodexMagicSystemEnabled") && magic) { + options["magicCount"] = this.determineMagicCount(skill); + + if (options["magicCount"] === false) { + return false; + } + } + return new FateRoll({ _id: foundry.utils.randomID(), name: skill.name, @@ -17,7 +25,13 @@ export class FateRoll extends FateRollDataModel { } async roll(userId = "") { - const roll = new Roll(`4dF${this.options.magic && "m"}`).roll({ async: false }); + if (game.settings.get("fatex", "guildCodexMagicSystemEnabled")) { + if (this.options.magic && this.options.magicCount > 0) { + return this.rollMagic(userId, this.options.magicCount); + } + } + + const roll = new Roll(`4dF`).roll({ async: false }); this.updateSource({ faces: roll.terms[0].results.map((r) => r.count ?? r.result) }); if (game.modules.get("dice-so-nice")?.active) { @@ -28,6 +42,32 @@ export class FateRoll extends FateRollDataModel { return this; } + async rollMagic(userId = "", magicCount: number) { + const normalCount = 4 - magicCount; + + const magicRoll = new Roll(`${this.options.magicCount}dFm`).roll({ async: false }); + const normalRoll = new Roll(`${normalCount}dF`).roll({ async: false }); + + this.updateSource({ + faces: [...magicRoll.terms[0].results, ...normalRoll.terms[0].results].map((r) => r.count ?? r.result), + }); + + magicRoll.terms[0].options.sfx = { + specialEffect: "PlayAnimationParticleSparkles", + }; + + if (game.modules.get("dice-so-nice")?.active) { + const user = userId ? game.users.get(userId) : game.user; + + await Promise.all([ + game.dice3d.showForRoll(magicRoll, user, true), + game.dice3d.showForRoll(normalRoll, user, true), + ]); + } + + return this; + } + async reroll(userId = "") { const history = this.addHistoryEntry(userId, { type: "reroll", previousRoll: this.faces }); await this.roll(userId); @@ -82,4 +122,30 @@ export class FateRoll extends FateRollDataModel { return await renderTemplate(template, this); } + + static determineMagicCount(skill: SkillItemData & ItemDataProperties) { + const magicSkills = skill.parent.items.filter((i) => i.type === "skill" && i.system.options.isMagicSkill); + + if (magicSkills.length === 0) { + ui.notifications.error(game.i18n.localize("FAx.Item.Skill.Roll.NoMagicSkills")); + return false; + } + + if (magicSkills.length > 1) { + ui.notifications.warn( + game.i18n.format("FAx.Item.Skill.Roll.MultipleMagicSkills", { + skills: magicSkills.map((s) => s.name).join(", "), + }) + ); + } + + const magicRank = Math.clamped(Number(magicSkills[0].system.rank ?? 0), 0, 4); + + if (magicRank < 1) { + ui.notifications.error(game.i18n.localize("FAx.Item.Skill.Roll.MagicSkillTooLow")); + return false; + } + + return magicRank; + } } diff --git a/src/module/features/MagicSystem.ts b/src/module/features/MagicSystem.ts new file mode 100644 index 00000000..ff3428bc --- /dev/null +++ b/src/module/features/MagicSystem.ts @@ -0,0 +1,17 @@ +// @ts-nocheck + +export class MagicSystem { + static hooks() { + Hooks.once("init", () => { + game.settings.register("fatex", "guildCodexMagicSystemEnabled", { + name: game.i18n.localize("FAx.Settings.magicSystemEnabled.Name"), + hint: game.i18n.localize("FAx.Settings.magicSystemEnabled.Hint"), + scope: "world", + config: true, + default: false, + reload: true, + type: Boolean, + }); + }); + } +} diff --git a/src/module/item/ItemSheetFate.ts b/src/module/item/ItemSheetFate.ts index 004923e3..bbafc0cd 100644 --- a/src/module/item/ItemSheetFate.ts +++ b/src/module/item/ItemSheetFate.ts @@ -14,6 +14,7 @@ export class ItemSheetFate extends ItemSheet { // enforce data to ensure compatability between 0.7 and 0.8 // @ts-ignore data.data = this.object.system; + data.system = data.data; // Set owner name if possible data.isOwnedBy = this.actor ? this.actor.name : false; diff --git a/src/module/item/skill/SkillItem.ts b/src/module/item/skill/SkillItem.ts index 72588a7a..0529badb 100644 --- a/src/module/item/skill/SkillItem.ts +++ b/src/module/item/skill/SkillItem.ts @@ -129,13 +129,20 @@ export class SkillItem extends BaseItem { } } - static async rollSkill(sheet, item, event) { - const skill = this.prepareItemData(duplicate(item), item); + static async rollSkill(sheet, skill, event) { const actor = sheet.actor; - const fateRoll = await FateRoll.createFromSkill(skill, { magic: event.shiftKey }).roll(); - const fateChatCard = FateChatCard.create(actor, [fateRoll]); + const fateRoll = FateRoll.createFromSkill(skill, { + magic: event.shiftKey, + }); + + if (!fateRoll) { + return; + } + + await fateRoll.roll(); + const fateChatCard = FateChatCard.create(actor, [fateRoll]); await fateChatCard.sendToChat(); } } diff --git a/src/module/item/skill/SkillSheet.ts b/src/module/item/skill/SkillSheet.ts index 4e08c567..ad77a6fa 100644 --- a/src/module/item/skill/SkillSheet.ts +++ b/src/module/item/skill/SkillSheet.ts @@ -1,3 +1,11 @@ import { ItemSheetFate } from "../ItemSheetFate"; -export class SkillSheet extends ItemSheetFate {} +export class SkillSheet extends ItemSheetFate { + getData(): any { + const data = super.getData(); + + data.magicSystemEnabled = game.settings.get("fatex", "guildCodexMagicSystemEnabled"); + + return data; + } +} diff --git a/src/styles/abstract/variables.scss b/src/styles/abstract/variables.scss index 941881f7..1ea8e540 100644 --- a/src/styles/abstract/variables.scss +++ b/src/styles/abstract/variables.scss @@ -11,6 +11,7 @@ $primary-text-color: #191813; $light-text-color: #fff; $caution-color: rgb(242, 22, 40); +$magic-color: rgb(160, 25, 31); $shadow-color: rgb(160, 160, 160); // Fonts diff --git a/src/styles/components/chat.scss b/src/styles/components/chat.scss index d59c2ac2..5761e6a6 100644 --- a/src/styles/components/chat.scss +++ b/src/styles/components/chat.scss @@ -147,5 +147,9 @@ width: 24px; height: 24px; text-align: center; + + &.fatex-dice__magic_die { + color: $magic-color; + } } } diff --git a/src/styles/components/settings.scss b/src/styles/components/settings.scss index 08a7c12b..337853f4 100644 --- a/src/styles/components/settings.scss +++ b/src/styles/components/settings.scss @@ -6,6 +6,27 @@ min-height: 70px; } + &__checkbox { + label { + display: flex; + gap: calc($component-padding / 2); + + div { + display: flex; + flex-direction: column; + } + } + + &__name { + font-weight: bold; + padding: 5px 0; + } + + &__help { + font-size: 12px; + } + } + &__editor-wrapper { height: 250px; overflow: hidden; diff --git a/system/languages/en.json b/system/languages/en.json index 7ef16110..87d88b6f 100644 --- a/system/languages/en.json +++ b/system/languages/en.json @@ -111,7 +111,17 @@ "EditItem": "Edit Skill", "Rank": "Rank", "Description": "Description", - "DescriptionHelp": "Is only displayed when sending to chat" + "DescriptionHelp": "Is only displayed when sending to chat", + "Options": { + "Headline": "Options", + "IsMagicSkill": "Magic Skill", + "IsMagicSkillHelp": "If checked, this skills rank will be used to determine the number of magic dice to roll." + }, + "Roll": { + "MultipleMagicSkills": "Multiple magic skills found ({skills}). Using the first one to roll.", + "NoMagicSkills": "No magic skill found. Please add a magic skill to your character sheet and roll again.", + "MagicSkillTooLow": "The magic skill rank is zero or less. Please increase the rank to roll magic dice." + } }, "Stunt": { "Name": "Stunt title", @@ -217,6 +227,10 @@ "autoUpdateTokenName": { "Name": "Automatically update prototype token name", "Hint": "Automatically updates the prototype token name when an actors name is changed (if both were identical)." + }, + "magicSystemEnabled": { + "Name": "Enable the magic system from Guild Codex", + "Hint": "This will add a magic skill to the character sheet and allow you to use magic dice." } }, "Template": { diff --git a/system/system.json b/system/system.json index 51efa3a8..fc6f299b 100644 --- a/system/system.json +++ b/system/system.json @@ -2,7 +2,7 @@ "id": "fatex", "title": "FateX - Fate Extended", "description": "An extended implementation of Fate and its derivative systems", - "version": "1.1.1", + "version": "1.2.0", "authors": [ { "name": "Patrick Bauer", diff --git a/system/template.json b/system/template.json index a9b77af9..18bc2303 100644 --- a/system/template.json +++ b/system/template.json @@ -54,7 +54,10 @@ }, "skill": { "templates": ["base"], - "rank": 0 + "rank": 0, + "options": { + "isMagicSkill": false + } }, "stunt": { "templates": ["base"], diff --git a/system/templates/chat/roll.hbs b/system/templates/chat/roll.hbs index 9a700613..be4da978 100644 --- a/system/templates/chat/roll.hbs +++ b/system/templates/chat/roll.hbs @@ -9,8 +9,8 @@