diff --git a/src/migrations/2022_01_03_05_08_25_active-effects-for-traits.ts b/src/migrations/2022_01_03_05_08_25_active-effects-for-traits.ts new file mode 100644 index 000000000..3bece3bf7 --- /dev/null +++ b/src/migrations/2022_01_03_05_08_25_active-effects-for-traits.ts @@ -0,0 +1,21 @@ +async function applyActiveEffects(p, actor) { + await p; + return Promise.all(actor.items.filter(item => item.type === "trait").map(async item => { + if (!item.data.data.effectId) { + const effects = await actor.createEmbeddedDocuments("ActiveEffect", [{ + origin: item.uuid, + icon: "systems/twodsix/assets/icons/science.svg" + }]); + await item.update({ "data.effectId": effects[0].id }); + } + return Promise.resolve(); + })); +} + +export async function migrate(): Promise { + const tokenActor = game.scenes?.reduce((memo: TokenDocument[], scene: Scene) => memo.concat(scene.tokens.contents), []) + .filter((token: TokenDocument) => token.actor?.type === "traveller" && !token.data.actorLink) + .map((token: TokenDocument) => token.actor); + await game.actors?.filter(actor => actor.type === "traveller").reduce(applyActiveEffects, Promise.resolve()); + await tokenActor?.reduce(applyActiveEffects, Promise.resolve()); +} diff --git a/src/module/entities/TwodsixActor.ts b/src/module/entities/TwodsixActor.ts index 6e84a1e36..5db9c8063 100644 --- a/src/module/entities/TwodsixActor.ts +++ b/src/module/entities/TwodsixActor.ts @@ -12,6 +12,7 @@ import Weapon = dataTwodsix.Weapon; import Gear = dataTwodsix.Gear; import Traveller = dataTwodsix.Traveller; import Skills = dataTwodsix.Skills; +import Trait = dataTwodsix.Trait; import Characteristic = dataTwodsix.Characteristic; export default class TwodsixActor extends Actor { @@ -73,7 +74,17 @@ export default class TwodsixActor extends Actor { data.skills = new Proxy(Object.fromEntries(actorSkills), handler); } - protected async _onCreate() { + _onUpdateEmbeddedDocuments(embeddedName:string, documents): void { + if (embeddedName === "ActiveEffect") { + (documents).forEach((element:ActiveEffect) => { + const item = (element.parent)?.items.find((itm:TwodsixItem) => (itm.data.data).effectId === element.id); + item?.update({"data.changes": element.data.changes}); + }); + } + this.render(); + } + + protected async _onCreate(): Promise { switch (this.data.type) { case "traveller": await this.createUntrainedSkill(); diff --git a/src/module/entities/TwodsixItem.ts b/src/module/entities/TwodsixItem.ts index 2a066da81..f9ada96a8 100644 --- a/src/module/entities/TwodsixItem.ts +++ b/src/module/entities/TwodsixItem.ts @@ -31,6 +31,11 @@ export default class TwodsixItem extends Item { if (this.getFlag("twodsix", "untrainedSkill")) { this.data.name = game.i18n.localize("TWODSIX.Actor.Skills.Untrained"); } + + if (this.data.data.effectId && this.actor?.data) { + const effect = this.actor.effects.get(this.data.data.effectId); + this.data.data.effect = effect; + } } prepareConsumable(gear: Gear = this.data.data): void { diff --git a/src/module/handlebars.ts b/src/module/handlebars.ts index dfedd73c3..3238316b3 100644 --- a/src/module/handlebars.ts +++ b/src/module/handlebars.ts @@ -179,20 +179,20 @@ export default function registerHandlebarsHelpers(): void { Handlebars.registerHelper("concat", (...args) => args.slice(0, args.length - 1).join('')); - Handlebars.registerHelper('each_sort_by_name', (array, options) => { + Handlebars.registerHelper('each_sort_by_property', (property:string, array:TwodsixItem[], options) => { let sortedArray: TwodsixItem[] = []; - const slice: TwodsixItem[] = array?.slice(0); + const slice = array?.slice(0); if (slice) { sortedArray = slice.sort((a, b) => { - if (a.name == null) { + if (a[property] == null) { return 1; } else { - if (b.name == null) { + if (b[property] == null) { return -1; - } else if (a.name === b.name) { + } else if (a[property] === b[property]) { return 0; } else { - return a.name?.toLocaleLowerCase().localeCompare(b.name?.toLocaleLowerCase()); + return a[property]?.toLocaleLowerCase().localeCompare(b[property]?.toLocaleLowerCase()); } } }); diff --git a/src/module/sheets/AbstractTwodsixActorSheet.ts b/src/module/sheets/AbstractTwodsixActorSheet.ts index 813d3440c..36127f97b 100644 --- a/src/module/sheets/AbstractTwodsixActorSheet.ts +++ b/src/module/sheets/AbstractTwodsixActorSheet.ts @@ -2,6 +2,7 @@ import TwodsixItem from "../entities/TwodsixItem"; import { getDataFromDropEvent, getItemDataFromDropData } from "../utils/sheetUtils"; import Armor = dataTwodsix.Armor; import Skills = dataTwodsix.Skills; +import Trait = dataTwodsix.Trait; import UsesConsumables = dataTwodsix.UsesConsumables; import TwodsixActor from "../entities/TwodsixActor"; @@ -45,7 +46,7 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet { title: title, content: template, yes: async () => { - // somehow on hooks isn't wokring when a consumable is deleted - force the issue + // somehow on hooks isn't working when a consumable is deleted - force the issue if (ownedItem.type === "consumable") { this.actor.items.filter(i => i.type !== "skills").forEach(i => { const usesConsumables:UsesConsumables = i.data.data; @@ -140,13 +141,19 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet { type, data }; - // Remove the type from the dataset since it's in the itemData.type prop. // delete itemData.data.type; this.updateWithItemSpecificValues(itemData, type); - // Finally, create the item! - await this.actor.createEmbeddedDocuments("Item", [itemData]); + const items = await this.actor.createEmbeddedDocuments("Item", [itemData]); + + if (type === "trait") { + const effects = await this.actor.createEmbeddedDocuments("ActiveEffect", [{ + origin: items[0].uuid, + icon: "systems/twodsix/assets/icons/science.svg" + }]); + items[0].update({"data.effectId": effects[0].id}); + } } @@ -243,10 +250,18 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet { itemData.data.skill = actor.items.getName(itemData.data.associatedSkillName)?.data._id; } - // Create the owned item (TODO Add to type and remove the two lines below...) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this._onDropItemCreate(itemData); + const items = await this._onDropItemCreate(itemData); + + if (items[0].type === "trait") { + const effects = await actor.createEmbeddedDocuments("ActiveEffect", [{ + origin: items[0].uuid, + icon: "systems/twodsix/assets/icons/science.svg", + changes: (items[0].data.data).changes + }]); + await items[0].update({"data.effectId": effects[0].id}); + actor.render(); + } + return items; } private static _getWeight(item): number{ diff --git a/src/module/sheets/TwodsixActorSheet.ts b/src/module/sheets/TwodsixActorSheet.ts index b469ac3db..c091cf30c 100644 --- a/src/module/sheets/TwodsixActorSheet.ts +++ b/src/module/sheets/TwodsixActorSheet.ts @@ -46,6 +46,12 @@ export class TwodsixActorSheet extends AbstractTwodsixActorSheet { showLifebloodStamina: game.settings.get("twodsix", "showLifebloodStamina"), showHeroPoints: game.settings.get("twodsix", "showHeroPoints") }; + + data.ACTIVE_EFFECT_MODES = Object.entries(CONST.ACTIVE_EFFECT_MODES).reduce((ret, entry) => { + const [ key, value ] = entry; + ret[ value ] = key; + return ret; + }, {}); data.config = TWODSIX; return data; @@ -95,8 +101,14 @@ export class TwodsixActorSheet extends AbstractTwodsixActorSheet { html.find(".item-value-edit").on("click", (event) => { $(event.currentTarget).trigger("select"); }); + + html.find(".effect-edit").on("click", this._onTraitEdit.bind(this)); } + private _onTraitEdit(event) { + const effect = this.actor.effects.get($(event.currentTarget).data('effectId')); + effect?.sheet.render(true); + } private getItem(event): TwodsixItem { const itemId = $(event.currentTarget).parents('.item').data('item-id'); diff --git a/src/module/sheets/TwodsixItemSheet.ts b/src/module/sheets/TwodsixItemSheet.ts index 86c295649..fcf44aa63 100644 --- a/src/module/sheets/TwodsixItemSheet.ts +++ b/src/module/sheets/TwodsixItemSheet.ts @@ -2,6 +2,7 @@ import { AbstractTwodsixItemSheet } from "./AbstractTwodsixItemSheet"; import { TWODSIX } from "../config"; import TwodsixItem from "../entities/TwodsixItem"; import { getDataFromDropEvent, getItemDataFromDropData } from "../utils/sheetUtils"; +import Trait = dataTwodsix.Trait; /** * Extend the basic ItemSheet with some very simple modifications @@ -31,7 +32,7 @@ export class TwodsixItemSheet extends AbstractTwodsixItemSheet { /** @override */ getData(): ItemSheet { const data = super.getData(); - data.actor = data.data; + data.actor = this.item.actor; (this.item).prepareConsumable(); // Add relevant data from system settings @@ -46,6 +47,12 @@ export class TwodsixItemSheet extends AbstractTwodsixItemSheet { }; data.data.config = TWODSIX; data.data.isOwned = this.item.isOwned; + + data.ACTIVE_EFFECT_MODES = Object.entries(CONST.ACTIVE_EFFECT_MODES).reduce((ret, entry) => { + const [ key, value ] = entry; + ret[ value ] = key; + return ret; + }, {}); return data; } @@ -75,19 +82,64 @@ export class TwodsixItemSheet extends AbstractTwodsixItemSheet { html.find('.consumable-edit').on('click', this._onEditConsumable.bind(this)); html.find('.consumable-delete').on('click', this._onDeleteConsumable.bind(this)); html.find('.consumable-use-consumable-for-attack').on('change', this._onChangeUseConsumableForAttack.bind(this)); + + //change-row + html.find('.change-row input, .change-row select').on('change', this._onEditChange.bind(this)); + html.find('.change-create').on('click', this._onCreateChange.bind(this)); + html.find('.change-delete').on('click', this._onDeleteChange.bind(this)); + + html.find(".effect-edit").on("click", this._onEditEffect.bind(this)); + this.handleContentEditable(html); } - private getConsumable(event) { - const li = $(event.currentTarget).parents(".consumable"); - return this.item.actor?.items.get(li.data("consumableId")); + private _onEditEffect(event:Event): void { + if (event.currentTarget) { + const effect = (this.actor).effects.get($(event.currentTarget).data('effectId')); + effect?.sheet.render(true); + } + } + + private _onDeleteChange(event:Event): void { + if (event.currentTarget) { + const idx = parseInt($(event.currentTarget).data("index"), 10); + const changes = (this.item.data.data).changes.filter((_, i) => i !== idx); + this.item.update({"data.changes": changes}); + } + } + + private _onEditChange(event:Event) : void{ + if (event.currentTarget) { + const idx = parseInt($(event.currentTarget).parents(".change-row").data("change-index"), 10); + const changes = (this.item.data.data).changes; + const type:string = $(event.currentTarget).data("type"); + if (!isNaN(idx) && type) { + const val = $(event.currentTarget).val() as string; + changes[idx][type] = type === "mode" ? parseInt(val, 10) : val; + this.item.update({"data.changes": changes}); + } + } + } + + private _onCreateChange(): void { + const changes = (this.item.data.data).changes ?? []; + this.item.update({"data.changes": changes.concat({key: "", value: "", mode: 0})}); + } + + private getConsumable(event:Event):TwodsixItem | undefined { + if (event.currentTarget) { + const li = $(event.currentTarget).parents(".consumable"); + return (this.item).actor?.items.get(li.data("consumableId")); + } else { + return undefined; + } } - private _onEditConsumable(event): void { + private _onEditConsumable(event:Event): void { this.getConsumable(event)?.sheet?.render(true); } - private async _onDeleteConsumable(event): Promise { + private async _onDeleteConsumable(event:Event): Promise { const consumable = this.getConsumable(event); if (!consumable) { (this.item).removeConsumable(""); //TODO Should have await? diff --git a/src/scripts/create-migration.mjs b/src/scripts/create-migration.mjs index a74e49fa9..10a331af2 100644 --- a/src/scripts/create-migration.mjs +++ b/src/scripts/create-migration.mjs @@ -15,4 +15,4 @@ const date = (new Date(temporaryDate.toUTCString())).toISOString().replace(/[:_t const templateString = 'export async function migrate():Promise {\n\n}'; fs.writeFileSync(`src/migrations/${date}-${migrationName}.ts`, templateString); -console.log(`Succesfully created migration: src/migrations/${date}-${migrationName}.ts`); +console.log(`Successfully created migration: src/migrations/${date}-${migrationName}.ts`); diff --git a/src/types/template.d.ts b/src/types/template.d.ts index 429b45939..a3057c76c 100644 --- a/src/types/template.d.ts +++ b/src/types/template.d.ts @@ -387,6 +387,12 @@ declare namespace dataTwodsix { weight: number; } + export interface TraitChange { + mode: number; + key: string; + value: string; + } + export interface Trait { templates: string[]; value: number; @@ -397,6 +403,8 @@ declare namespace dataTwodsix { subtype: string; reference: string; key: string; + changes: TraitChange[]; + effectId: string; } export interface Weapon extends GearTemplate { diff --git a/static/lang/en.json b/static/lang/en.json index cca1b67c3..a5666b200 100644 --- a/static/lang/en.json +++ b/static/lang/en.json @@ -232,7 +232,17 @@ }, "Traits": { "Prerequisite": "Prerequisite", - "TraitName": "Trait Name" + "TraitName": "Trait Name", + "AttributeKey": "Attribute Key", + "ChangeMode": "Change Mode", + "EffectValue": "Effect Value", + "CreateEffect": "Create Effect", + "DeleteEffect": "Delete Effect", + "EditEffects": "Edit Effects", + "EditEffect": "Edit Effect", + "Effects": "Effects", + "CreateTrait": "Create Trait", + "DeleteTrait": "Delete Trait" }, "Tool": { "Bonus": "Bonus" diff --git a/static/styles/twodsix.css b/static/styles/twodsix.css index 1c92e6f26..b6ae15e5d 100644 --- a/static/styles/twodsix.css +++ b/static/styles/twodsix.css @@ -765,9 +765,18 @@ a.notes-tab.active { .items-traits { display: grid; - grid-template-columns: 14em 2em 14em 3em; + grid-template-columns: 7em 22em 4em; gap: 1px 1px; - grid-template-areas: '. . . .'; + grid-template-areas: '. . .'; +} + +.trait-name { + display: inline-flex; + align-items: center; +} + +.effect-edit { + width: 150%; } .items-consumable { diff --git a/static/styles/twodsix_basic.css b/static/styles/twodsix_basic.css index 3c2fd8c79..4ef88d82d 100644 --- a/static/styles/twodsix_basic.css +++ b/static/styles/twodsix_basic.css @@ -516,9 +516,18 @@ a.skill-tab.active, a.item-tab.active, a.finances-tab.active, a.info-tab.active, .items-traits { display: grid; - grid-template-columns: 14em 2em 14em 3em; + grid-template-columns: 7em 22em 4em; gap: 1px 1px; - grid-template-areas: '. . . .'; + grid-template-areas: '. . .'; +} + +.trait-name { + display: inline-flex; + align-items: center; +} + +.effect-edit { + width: 150%; } .items-consumable { @@ -1458,6 +1467,7 @@ button.flexrow.flex-group-center.toggle-skills { text-shadow: 0 0 5px /*var(--default-color); }*/ + .item-type { position: relative; float: right; diff --git a/static/template.json b/static/template.json index da05f4464..6ba4abecf 100644 --- a/static/template.json +++ b/static/template.json @@ -411,7 +411,8 @@ "shortdescr": "", "subtype": "", "reference": "", - "key": "key" + "effectId": "", + "changes": [] }, "consumable": { "templates": ["gearTemplate"], diff --git a/static/templates/actors/parts/actor/actor-info.html b/static/templates/actors/parts/actor/actor-info.html index de8ce408e..dc1e56f33 100644 --- a/static/templates/actors/parts/actor/actor-info.html +++ b/static/templates/actors/parts/actor/actor-info.html @@ -2,32 +2,43 @@
{{localize "TWODSIX.Actor.Items.TRAITS"}}
- {{localize "TWODSIX.Actor.Items.Name"}} - {{localize "TWODSIX.Actor.Skills.Level"}} - {{localize "TWODSIX.Actor.Items.ShortDescr"}} - + {{localize "TWODSIX.Items.Traits.TraitName"}} + {{localize "TWODSIX.Items.Traits.Effects"}} + + + + +
- - {{#each_sort_by_name data.traits as |item id|}} + {{#each_sort_by_property "name" data.traits as |item|}}
  1. - {{item.name}} - {{item.data.data.value}} - {{item.data.data.shortdescr}} + {{item.name}} + +
      + {{#each item.data.data.effect.data.changes as |change|}} +
    • {{change.key}} {{lookup @root.ACTIVE_EFFECT_MODES change.mode}} {{change.value}}
    • + {{/each}} +
    +
    - - + + + + + + + + +
- {{/each_sort_by_name}} + {{/each_sort_by_property}}
diff --git a/static/templates/actors/parts/actor/actor-items.html b/static/templates/actors/parts/actor/actor-items.html index 24bb97c94..ba3b98cde 100644 --- a/static/templates/actors/parts/actor/actor-items.html +++ b/static/templates/actors/parts/actor/actor-items.html @@ -15,7 +15,7 @@
- {{#each_sort_by_name data.weapon as |item id|}} + {{#each_sort_by_property "name" data.weapon as |item id|}}
  1. @@ -60,7 +60,7 @@ {{> "systems/twodsix/templates/actors/parts/actor/actor-consumable.html" consumableData}} {{/each}}
- {{/each_sort_by_name}} + {{/each_sort_by_property}} @@ -78,7 +78,7 @@ - {{#each_sort_by_name data.armor as |item id|}} + {{#each_sort_by_property "name" data.armor as |item id|}}
  1. @@ -101,7 +101,7 @@ {{> "systems/twodsix/templates/actors/parts/actor/actor-consumable.html" consumableData}} {{/each}}
- {{/each_sort_by_name}} + {{/each_sort_by_property}} @@ -118,7 +118,7 @@ class="fas fa-plus"> - {{#each_sort_by_name data.augment as |item id|}} + {{#each_sort_by_property "name" data.augment as |item id|}}
  1. @@ -140,7 +140,7 @@ {{> "systems/twodsix/templates/actors/parts/actor/actor-consumable.html" consumableData}} {{/each}}
- {{/each_sort_by_name}} + {{/each_sort_by_property}} @@ -156,7 +156,7 @@ class="fas fa-plus"> - {{#each_sort_by_name data.equipment as |item id|}} + {{#each_sort_by_property "name" data.equipment as |item id|}}
  1. @@ -178,7 +178,7 @@ {{> "systems/twodsix/templates/actors/parts/actor/actor-consumable.html" consumableData}} {{/each}}
- {{/each_sort_by_name}} + {{/each_sort_by_property}} diff --git a/static/templates/actors/parts/actor/actor-skills.html b/static/templates/actors/parts/actor/actor-skills.html index 77f4bdfc3..54a333171 100644 --- a/static/templates/actors/parts/actor/actor-skills.html +++ b/static/templates/actors/parts/actor/actor-skills.html @@ -29,7 +29,7 @@ -{{#each_sort_by_name data.skills as |item id|}} +{{#each_sort_by_property "name" data.skills as |item id|}} {{#if (twodsix_hideUntrainedSkills item.data.data.value)}} {{else}} @@ -57,7 +57,7 @@ {{/if}} {{/if}} -{{/each_sort_by_name}} +{{/each_sort_by_property}} {{#if (twodsix_hideUntrainedSkills -1)}}
diff --git a/static/templates/actors/parts/ship/ship-components-double.html b/static/templates/actors/parts/ship/ship-components-double.html index 9c0f11924..192037b6e 100644 --- a/static/templates/actors/parts/ship/ship-components-double.html +++ b/static/templates/actors/parts/ship/ship-components-double.html @@ -21,7 +21,7 @@
- {{#each_sort_by_name data.component as |item id|}} + {{#each_sort_by_property "name" data.component as |item id|}}
  1. @@ -50,6 +50,6 @@
- {{/each_sort_by_name}} + {{/each_sort_by_property}}
diff --git a/static/templates/actors/parts/ship/ship-components-single.html b/static/templates/actors/parts/ship/ship-components-single.html index b2a953d62..2116b9c2e 100644 --- a/static/templates/actors/parts/ship/ship-components-single.html +++ b/static/templates/actors/parts/ship/ship-components-single.html @@ -17,7 +17,7 @@
- {{#each_sort_by_name data.component as |item id|}} + {{#each_sort_by_property "name" data.component as |item id|}}
  1. @@ -51,6 +51,6 @@
- {{/each_sort_by_name}} + {{/each_sort_by_property}}
diff --git a/static/templates/actors/parts/ship/ship-storage.html b/static/templates/actors/parts/ship/ship-storage.html index a12d5c5e0..77759030f 100644 --- a/static/templates/actors/parts/ship/ship-storage.html +++ b/static/templates/actors/parts/ship/ship-storage.html @@ -21,7 +21,7 @@
- {{#each_sort_by_name data.storage as |item id|}} + {{#each_sort_by_property "name" data.storage as |item id|}}
  1. @@ -39,6 +39,6 @@
- {{/each_sort_by_name}} + {{/each_sort_by_property}}
diff --git a/static/templates/items/trait-sheet.html b/static/templates/items/trait-sheet.html index 2fe59424f..9d062ed1b 100644 --- a/static/templates/items/trait-sheet.html +++ b/static/templates/items/trait-sheet.html @@ -17,6 +17,54 @@ + + + + + + + + + + + {{#each data.changes as |change|}} + + {{#if ../actor}} + + + + + {{else}} + + + + + {{/if}} + + {{/each}} + +
{{localize "TWODSIX.Items.Traits.AttributeKey"}}{{localize "TWODSIX.Items.Traits.Mode"}}{{localize "TWODSIX.Items.Traits.EffectValue"}} + {{#unless actor}} + + + {{/unless}} +
{{change.key}}{{lookup @root.ACTIVE_EFFECT_MODES change.mode}}{{change.value}} + + + + +
+ {{#if actor}} + + {{/if}}