diff --git a/src/ds4.scss b/src/ds4.scss index 212a180f..9ee0c00f 100644 --- a/src/ds4.scss +++ b/src/ds4.scss @@ -21,4 +21,5 @@ @include meta.load-css("scss/components/tabs"); @include meta.load-css("scss/components/talent_rank_equation"); @include meta.load-css("scss/components/currency"); + @include meta.load-css("scss/components/rollable_image"); } diff --git a/src/lang/de.json b/src/lang/de.json index 31800739..3e2c0ae9 100644 --- a/src/lang/de.json +++ b/src/lang/de.json @@ -5,6 +5,9 @@ "DS4.UserInteractionAddEffect": "Neuer Effekt", "DS4.UserInteractionEditEffect": "Effekt bearbeiten", "DS4.UserInteractionDeleteEffect": "Effekt löschen", + "DS4.EntityImageAltText": "Bild von {name}", + "DS4.RollableImageRollableTitle": "Für {name} würfeln", + "DS4.DiceOverlayImageAltText": "Bild eines W20", "DS4.NotOwned": "Nicht besessen", "DS4.HeadingBiography": "Biografie", "DS4.HeadingDetails": "Details", @@ -15,8 +18,9 @@ "DS4.HeadingSpells": "Zaubersprüche", "DS4.HeadingDescription": "Beschreibung", "DS4.HeadingSpecialCreatureAbilities": "Besondere Fähigkeiten", - "DS4.AttackType": "Angriffstyp", - "DS4.AttackTypeAbbr": "AT", + "DS4.AttackType": "Angriffsart", + "DS4.AttackTypeAbbr": "AA", + "DS4.AttackTypeSelection": "Welche Angriffsart?", "DS4.WeaponBonus": "Waffenbonus", "DS4.WeaponBonusAbbr": "WB", "DS4.OpponentDefense": "Gegnerabwehr", @@ -184,6 +188,12 @@ "DS4.ErrorDiceCritOverlap": "Es gibt eine Überlappung zwischen Patzern und Immersiegen.", "DS4.ErrorExplodingRecursionLimitExceeded": "Die maximale Rekursionstiefe für slayende Würfelwürfe wurde überschritten.", "DS4.ErrorDuringMigration": "Fehler während der Aktualisierung des DS4 Systems von Migrationsversion {currentVersion} auf {targetVersion}. Der Fehler trat während der Ausführung des Migrationsskripts mit der Version {migrationVersion} auf. Spätere Migrationsskripte wurden nicht ausgeführt. Mehr Details finden Sie in der Entwicklerkonsole (F12).", + "DS4.ErrorCannotRollUnownedItem": "Für das Item '{name}' ({id}) kann nicht gewürfelt werden, da es keinem Aktor gehört.", + "DS4.ErrorRollingForItemTypeNotPossible": "Würfeln ist für Items vom Typ '{type}' nicht möglich.", + "DS4.ErrorWrongItemType": "Ein Item vom Type '{expectedType}' wurde erwartet aber das Item '{name}' ({id}) ist vom Typ '{actualType}'.", + "DS4.ErrorUnexpectedAttackType": "Unerwartete Angriffsart '{actualType}', erwartete Angriffarten: {expectedTypes}", + "DS4.ErrorItemMustBeEquippedToBeRolled": "Um für das Item '{name}' ({id}) vom Typ '{type}' zu würfeln, muss es ausgerüstet sein.", + "DS4.InfoManuallyEnterSpellBonus": "Der korrekte Wert für den Zauberbonus '{spellBonus}' des Zaubers '{name}' musss manuell angegeben werden.", "DS4.InfoSystemUpdateStart": "Aktualisiere DS4 System von Migrationsversion {currentVersion} auf {targetVersion}. Bitte haben Sie etwas Geduld, schließen Sie nicht das Spiel und fahren Sie nicht den Server herunter.", "DS4.InfoSystemUpdateCompleted": "Aktualisierung des DS4 Systems von Migrationsversion {currentVersion} auf {targetVersion} erfolgreich!", "DS4.UnitRounds": "Runden", @@ -200,9 +210,9 @@ "DS4.UnitKilometersAbbr": "km", "DS4.UnitCustom": "individuell", "DS4.UnitCustomAbbr": " ", + "DS4.GenericOkButton": "OK", + "DS4.GenericCancelButton": "Abbrechen", "DS4.RollDialogDefaultTitle": "Proben-Optionen", - "DS4.RollDialogOkButton": "OK", - "DS4.RollDialogCancelButton": "Abbrechen", "DS4.ErrorUnexpectedHtmlType": "Typfehler: Erwartet wurde '{exType}', tatsächlich erhalten wurde '{realType}'.", "DS4.ErrorCouldNotFindForm": "Konnte HTML Element '{htmlElement}' nicht finden.", "DS4.ErrorActorDoesNotHaveItem": "Der Aktor '{actor}' hat kein Item mit der ID '{id}'.", diff --git a/src/lang/en.json b/src/lang/en.json index 0894350b..056091c6 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -5,6 +5,9 @@ "DS4.UserInteractionAddEffect": "Add Effect", "DS4.UserInteractionEditEffect": "Edit Effect", "DS4.UserInteractionDeleteEffect": "Delete Effect", + "DS4.EntityImageAltText": "Image of {name}", + "DS4.RollableImageRollableTitle": "Roll for {name}", + "DS4.DiceOverlayImageAltText": "Image of a d20", "DS4.NotOwned": "No owner", "DS4.HeadingBiography": "Biography", "DS4.HeadingDetails": "Details", @@ -17,6 +20,7 @@ "DS4.HeadingSpecialCreatureAbilities": "Special Abilities", "DS4.AttackType": "Attack Type", "DS4.AttackTypeAbbr": "AT", + "DS4.AttackTypeSelection": "Which Attack Type?", "DS4.WeaponBonus": "Weapon Bonus", "DS4.WeaponBonusAbbr": "WB", "DS4.OpponentDefense": "Opponent Defense", @@ -184,6 +188,12 @@ "DS4.ErrorDiceCritOverlap": "There's an overlap between Fumbles and Coups", "DS4.ErrorExplodingRecursionLimitExceeded": "Maximum recursion depth for exploding dice roll exceeded", "DS4.ErrorDuringMigration": "Error while migrating DS4 system from migration version {currentVersion} to {targetVersion}. The error occurred during execution of migration script with version {migrationVersion}. Later migrations have not been executed. For more details, please look at the development console (F12).", + "DS4.ErrorCannotRollUnownedItem": "Rolling for item '{name}' ({id})is not possible because it is not owned.", + "DS4.ErrorRollingForItemTypeNotPossible": "Rolling is not possible for items of type '{type}'.", + "DS4.ErrorWrongItemType": "Expected an item of type '{expectedType}' but item '{name}' ({id}) is of type '{actualType}'.", + "DS4.ErrorUnexpectedAttackType": "Unexpected attack type '{actualType}', expected it to be one of: {expectedTypes}", + "DS4.ErrorItemMustBeEquippedToBeRolled": "To roll for item '{name}' ({id}) of type '{type}', it needs to be equipped.", + "DS4.InfoManuallyEnterSpellBonus": "The correct value of the spell bons '{spellBonus}' of the spell '{name}' needs to be entered by manually.", "DS4.InfoSystemUpdateStart": "Migrating DS4 system from migration version {currentVersion} to {targetVersion}. Please be patient and do not close your game or shut down your server.", "DS4.InfoSystemUpdateCompleted": "Migration of DS4 system from migration version {currentVersion} to {targetVersion} successful!", "DS4.UnitRounds": "Rounds", @@ -200,9 +210,9 @@ "DS4.UnitKilometersAbbr": "km", "DS4.UnitCustom": "Custom Unit", "DS4.UnitCustomAbbr": " ", + "DS4.GenericOkButton": "Ok", + "DS4.GenericCancelButton": "Cancel", "DS4.RollDialogDefaultTitle": "Roll Options", - "DS4.RollDialogOkButton": "Ok", - "DS4.RollDialogCancelButton": "Cancel", "DS4.ErrorUnexpectedHtmlType": "Type Error: Expected '{exType}' but got '{realType}'.", "DS4.ErrorCouldNotFindForm": "Could not find HTML element '{htmlElement}'.", "DS4.ErrorActorDoesNotHaveItem": "The actor '{actor}' does not have any item with the id '{id}'.", diff --git a/src/module/actor/actor-data.ts b/src/module/actor/actor-data.ts index 9f12ef3e..7fcb8398 100644 --- a/src/module/actor/actor-data.ts +++ b/src/module/actor/actor-data.ts @@ -17,6 +17,7 @@ interface DS4ActorDataDataBase { attributes: DS4ActorDataDataAttributes; traits: DS4ActorDataDataTraits; combatValues: DS4ActorDataDataCombatValues; + rolling: DS4ActorDataDataRolling; } interface DS4ActorDataDataAttributes { @@ -45,6 +46,11 @@ interface DS4ActorDataDataCombatValues { targetedSpellcasting: ModifiableData; } +interface DS4ActorDataDataRolling { + maximumCoupResult?: number; + minimumFumbleResult?: number; +} + interface DS4CharacterDataData extends DS4ActorDataDataBase { baseInfo: DS4CharacterDataDataBaseInfo; progression: DS4CharacterDataDataProgression; diff --git a/src/module/actor/actor.ts b/src/module/actor/actor.ts index 305ca04e..0e4cbb0d 100644 --- a/src/module/actor/actor.ts +++ b/src/module/actor/actor.ts @@ -25,6 +25,11 @@ export class DS4Actor extends Actor { prepareBaseData(): void { const data = this.data; + data.data.rolling = { + minimumFumbleResult: 20, + maximumCoupResult: 1, + }; + const attributes = data.data.attributes; Object.values(attributes).forEach( (attribute: ModifiableData) => (attribute.total = attribute.base + attribute.mod), diff --git a/src/module/actor/sheets/actor-sheet.ts b/src/module/actor/sheets/actor-sheet.ts index aee139ff..e75f1be7 100644 --- a/src/module/actor/sheets/actor-sheet.ts +++ b/src/module/actor/sheets/actor-sheet.ts @@ -124,8 +124,7 @@ export class DS4ActorSheet extends ActorSheet> { html.find(".item-change").on("change", this._onItemChange.bind(this)); - // Rollable abilities. - html.find(".rollable").click(this._onRoll.bind(this)); + html.find(".rollable-item").on("click", this._onRoll.bind(this)); } /** @@ -239,17 +238,9 @@ export class DS4ActorSheet extends ActorSheet> { */ protected _onRoll(event: JQuery.ClickEvent): void { event.preventDefault(); - const element = event.currentTarget; - const dataset = element.dataset; - - if (dataset.roll) { - const roll = new Roll(dataset.roll, this.actor.data.data); - const label = dataset.label ? `Rolling ${dataset.label}` : ""; - roll.roll().toMessage({ - speaker: ChatMessage.getSpeaker({ actor: this.actor }), - flavor: label, - }); - } + const id = $(event.currentTarget).parents(".item").data("itemId"); + const item = this.actor.getOwnedItem(id); + item.roll(); } /** @override */ diff --git a/src/module/ds4.ts b/src/module/ds4.ts index c8243f4e..67d03724 100644 --- a/src/module/ds4.ts +++ b/src/module/ds4.ts @@ -8,7 +8,8 @@ import { DS4CreatureActorSheet } from "./actor/sheets/creature-sheet"; import { createCheckRoll } from "./rolls/check-factory"; import { registerSystemSettings } from "./settings"; import { migration } from "./migrations"; -import handlebarsHelpers from "./handlebars-helpers"; +import registerHandlebarsHelpers from "./handlebars/handlebars-helpers"; +import registerHandlebarsPartials from "./handlebars/handlebars-partials"; Hooks.once("init", async () => { console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`); @@ -44,37 +45,6 @@ Hooks.once("init", async () => { registerHandlebarsHelpers(); }); -async function registerHandlebarsPartials() { - const templatePaths = [ - "systems/ds4/templates/item/partials/sheet-header.hbs", - "systems/ds4/templates/item/partials/description.hbs", - "systems/ds4/templates/item/partials/details.hbs", - "systems/ds4/templates/item/partials/effects.hbs", - "systems/ds4/templates/item/partials/body.hbs", - "systems/ds4/templates/actor/partials/items-overview.hbs", - "systems/ds4/templates/actor/partials/talents-abilities-overview.hbs", - "systems/ds4/templates/actor/partials/spells-overview.hbs", - "systems/ds4/templates/actor/partials/overview-add-button.hbs", - "systems/ds4/templates/actor/partials/overview-control-buttons.hbs", - "systems/ds4/templates/actor/partials/attributes-traits.hbs", - "systems/ds4/templates/actor/partials/combat-values.hbs", - "systems/ds4/templates/actor/partials/profile.hbs", - "systems/ds4/templates/actor/partials/character-progression.hbs", - "systems/ds4/templates/actor/partials/special-creature-abilities-overview.hbs", - "systems/ds4/templates/actor/partials/character-inventory.hbs", - "systems/ds4/templates/actor/partials/creature-inventory.hbs", - "systems/ds4/templates/actor/partials/talent-rank-equation.hbs", - "systems/ds4/templates/actor/partials/item-list-header.hbs", - "systems/ds4/templates/actor/partials/item-list-entry.hbs", - "systems/ds4/templates/actor/partials/currency.hbs", - ]; - return loadTemplates(templatePaths); -} - -function registerHandlebarsHelpers() { - Object.entries(handlebarsHelpers).forEach(([key, helper]) => Handlebars.registerHelper(key, helper)); -} - /** * This function runs after game data has been requested and loaded from the servers, so entities exist */ diff --git a/src/module/handlebars-helpers.ts b/src/module/handlebars-helpers.ts deleted file mode 100644 index 95144da3..00000000 --- a/src/module/handlebars-helpers.ts +++ /dev/null @@ -1,10 +0,0 @@ -export default { htmlToPlainText, isEmpty }; - -function htmlToPlainText(input: string | null | undefined): string | null | undefined { - if (!input) return; - return $(input).text(); -} - -function isEmpty(input: Array | null | undefined): boolean { - return (input?.length ?? 0) === 0; -} diff --git a/src/module/handlebars/handlebars-helpers.ts b/src/module/handlebars/handlebars-helpers.ts new file mode 100644 index 00000000..fef92681 --- /dev/null +++ b/src/module/handlebars/handlebars-helpers.ts @@ -0,0 +1,12 @@ +export default function registerHandlebarsHelpers(): void { + Object.entries(helpers).forEach(([key, helper]) => Handlebars.registerHelper(key, helper)); +} + +const helpers = { + htmlToPlainText: (input: string | null | undefined): string | null | undefined => { + if (!input) return; + return $(input).text(); + }, + + isEmpty: (input: Array | null | undefined): boolean => (input?.length ?? 0) === 0, +}; diff --git a/src/module/handlebars/handlebars-partials.ts b/src/module/handlebars/handlebars-partials.ts new file mode 100644 index 00000000..81945ebc --- /dev/null +++ b/src/module/handlebars/handlebars-partials.ts @@ -0,0 +1,27 @@ +export default async function registerHandlebarsPartials(): Promise { + const templatePaths = [ + "systems/ds4/templates/item/partials/sheet-header.hbs", + "systems/ds4/templates/item/partials/description.hbs", + "systems/ds4/templates/item/partials/details.hbs", + "systems/ds4/templates/item/partials/effects.hbs", + "systems/ds4/templates/item/partials/body.hbs", + "systems/ds4/templates/actor/partials/items-overview.hbs", + "systems/ds4/templates/actor/partials/talents-abilities-overview.hbs", + "systems/ds4/templates/actor/partials/spells-overview.hbs", + "systems/ds4/templates/actor/partials/overview-add-button.hbs", + "systems/ds4/templates/actor/partials/overview-control-buttons.hbs", + "systems/ds4/templates/actor/partials/attributes-traits.hbs", + "systems/ds4/templates/actor/partials/combat-values.hbs", + "systems/ds4/templates/actor/partials/profile.hbs", + "systems/ds4/templates/actor/partials/character-progression.hbs", + "systems/ds4/templates/actor/partials/special-creature-abilities-overview.hbs", + "systems/ds4/templates/actor/partials/character-inventory.hbs", + "systems/ds4/templates/actor/partials/creature-inventory.hbs", + "systems/ds4/templates/actor/partials/talent-rank-equation.hbs", + "systems/ds4/templates/actor/partials/item-list-header.hbs", + "systems/ds4/templates/actor/partials/item-list-entry.hbs", + "systems/ds4/templates/actor/partials/currency.hbs", + "systems/ds4/templates/common/partials/rollable-image.hbs", + ]; + await loadTemplates(templatePaths); +} diff --git a/src/module/item/item-data.ts b/src/module/item/item-data.ts index 5afd83b3..c28fbe86 100644 --- a/src/module/item/item-data.ts +++ b/src/module/item/item-data.ts @@ -32,8 +32,14 @@ type DS4LanguageData = DS4ItemDataHelper; type DS4AlphabetData = DS4ItemDataHelper; type DS4SpecialCreatureAbilityData = DS4ItemDataHelper; -interface DS4WeaponDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical, DS4ItemDataDataEquipable { - attackType: "melee" | "ranged" | "meleeRanged"; +export type AttackType = keyof typeof DS4["i18n"]["attackTypes"]; + +interface DS4WeaponDataData + extends DS4ItemDataDataBase, + DS4ItemDataDataPhysical, + DS4ItemDataDataEquipable, + DS4ItemDataDataRollable { + attackType: AttackType; weaponBonus: number; opponentDefense: number; } @@ -55,7 +61,7 @@ interface DS4TalentRank extends ModifiableData { max: number; } -interface DS4SpellDataData extends DS4ItemDataDataBase, DS4ItemDataDataEquipable { +interface DS4SpellDataData extends DS4ItemDataDataBase, DS4ItemDataDataEquipable, DS4ItemDataDataRollable { spellType: "spellcasting" | "targetedSpellcasting"; bonus: string; spellCategory: @@ -109,6 +115,10 @@ interface DS4ItemDataDataEquipable { equipped: boolean; } +interface DS4ItemDataDataRollable { + rollable?: boolean; +} + interface DS4ItemDataDataProtective { armorValue: number; } diff --git a/src/module/item/item.ts b/src/module/item/item.ts index 5c0a9f3d..ac9b90cd 100644 --- a/src/module/item/item.ts +++ b/src/module/item/item.ts @@ -1,4 +1,8 @@ -import { DS4ItemData } from "./item-data"; +import { DS4Actor } from "../actor/actor"; +import { DS4 } from "../config"; +import { createCheckRoll } from "../rolls/check-factory"; +import notifications from "../ui/notifications"; +import { AttackType, DS4ItemData } from "./item-data"; /** * The Item class for DS4 @@ -17,6 +21,9 @@ export class DS4Item extends Item { const data = this.data.data; data.rank.total = data.rank.base + data.rank.mod; } + if (this.data.type === "weapon" || this.data.type === "spell") { + this.data.data.rollable = this.data.data.equipped; + } } isNonEquippedEuipable(): boolean { @@ -32,4 +39,138 @@ export class DS4Item extends Item { } return 1; } + + /** + * Roll a check for a action with this item. + */ + async roll(): Promise { + if (!this.isOwnedItem()) { + throw new Error(game.i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id })); + } + + switch (this.data.type) { + case "weapon": + return this.rollWeapon(); + case "spell": + return this.rollSpell(); + default: + throw new Error(game.i18n.format("DS4.ErrorRollingForItemTypeNotPossible", { type: this.data.type })); + } + } + + protected async rollWeapon(this: this & { readonly isOwned: true }): Promise { + if (!(this.data.type === "weapon")) { + throw new Error( + game.i18n.format("DS4.ErrorWrongItemType", { + actualType: this.data.type, + expectedType: "weapon", + id: this.id, + name: this.name, + }), + ); + } + + if (!this.data.data.equipped) { + throw new Error( + game.i18n.format("DS4.ErrorItemMustBeEquippedToBeRolled", { + name: this.name, + id: this.id, + type: this.data.type, + }), + ); + } + + const ownerDataData = ((this.actor as unknown) as DS4Actor).data.data; // TODO(types): Improve so that the concrete Actor type is known here + const weaponBonus = this.data.data.weaponBonus; + const combatValue = await this.getCombatValueKeyForAttackType(this.data.data.attackType); + const checkTargetValue = (ownerDataData.combatValues[combatValue].total as number) + weaponBonus; + await createCheckRoll(checkTargetValue, { + rollMode: game.settings.get("core", "rollMode"), + maxCritSuccess: ownerDataData.rolling.maximumCoupResult, + minCritFailure: ownerDataData.rolling.minimumFumbleResult, + }); + } + + protected async rollSpell(): Promise { + if (!(this.data.type === "spell")) { + throw new Error( + game.i18n.format("DS4.ErrorWrongItemType", { + actualType: this.data.type, + expectedType: "spell", + id: this.id, + name: this.name, + }), + ); + } + + if (!this.data.data.equipped) { + throw new Error( + game.i18n.format("DS4.ErrorItemMustBeEquippedToBeRolled", { + name: this.name, + id: this.id, + type: this.data.type, + }), + ); + } + + const ownerDataData = ((this.actor as unknown) as DS4Actor).data.data; // TODO(types): Improve so that the concrete Actor type is known here + const spellBonus = Number.isNumeric(this.data.data.bonus) ? parseInt(this.data.data.bonus) : undefined; + if (spellBonus === undefined) { + notifications.info( + game.i18n.format("DS4.InfoManuallyEnterSpellBonus", { + name: this.name, + spellBonus: this.data.data.bonus, + }), + ); + } + const spellType = this.data.data.spellType; + const checkTargetValue = (ownerDataData.combatValues[spellType].total as number) + (spellBonus ?? 0); + + await createCheckRoll(checkTargetValue, { + rollMode: game.settings.get("core", "rollMode"), + maxCritSuccess: ownerDataData.rolling.maximumCoupResult, + minCritFailure: ownerDataData.rolling.minimumFumbleResult, + }); + } + + protected async getCombatValueKeyForAttackType(attackType: AttackType): Promise<"meleeAttack" | "rangedAttack"> { + if (attackType === "meleeRanged") { + const { melee, ranged } = { ...DS4.i18n.attackTypes }; + const identifier = "attack-type-selection"; + const label = game.i18n.localize("DS4.AttackType"); + const answer = Dialog.prompt({ + title: game.i18n.localize("DS4.AttackTypeSelection"), + content: await renderTemplate("systems/ds4/templates/common/simple-select-form.hbs", { + label, + identifier, + options: { melee, ranged }, + }), + label: game.i18n.localize("DS4.GenericOkButton"), + callback: (html) => { + const selectedAttackType = html.find(`#${identifier}`).val(); + if (selectedAttackType !== "melee" && selectedAttackType !== "ranged") { + throw new Error( + game.i18n.format("DS4.ErrorUnexpectedAttackType", { + actualType: selectedAttackType, + expectedTypes: "'melee', 'ranged'", + }), + ); + } + return `${selectedAttackType}Attack` as const; + }, + render: () => undefined, // TODO(types): This is actually optional, remove when types are updated ) + options: { jQuery: true }, + }); + return answer; + } else { + return `${attackType}Attack` as const; + } + } + + /** + * Type-guarding variant to check if the item is owned. + */ + isOwnedItem(): this is this & { readonly isOwned: true } { + return this.isOwned; + } } diff --git a/src/module/rolls/check-factory.ts b/src/module/rolls/check-factory.ts index eb68789d..2b3556b7 100644 --- a/src/module/rolls/check-factory.ts +++ b/src/module/rolls/check-factory.ts @@ -138,7 +138,7 @@ async function askGmModifier( buttons: { ok: { icon: '', - label: game.i18n.localize("DS4.RollDialogOkButton"), + label: game.i18n.localize("DS4.GenericOkButton"), callback: (html) => { if (!("jquery" in html)) { throw new Error( @@ -160,7 +160,7 @@ async function askGmModifier( }, cancel: { icon: '', - label: game.i18n.localize("DS4.RollDialogCancelButton"), + label: game.i18n.localize("DS4.GenericCancelButton"), }, }, default: "ok", diff --git a/src/scss/components/_item_list.scss b/src/scss/components/_item_list.scss index afebd6cc..dfa69c5c 100644 --- a/src/scss/components/_item_list.scss +++ b/src/scss/components/_item_list.scss @@ -15,31 +15,31 @@ padding: 0; &--weapon { - grid-template-columns: $row-height $row-height 3ch 3fr $row-height 1fr 3ch 5fr 4ch; + grid-template-columns: $row-height $row-height 3ch 3fr $row-height 1fr 3ch 5fr 5ch; } &--armor { - grid-template-columns: $row-height $row-height 3ch 3fr 1fr 1fr 3ch 5fr 4ch; + grid-template-columns: $row-height $row-height 3ch 3fr 1fr 1fr 3ch 5fr 5ch; } &--shield { - grid-template-columns: $row-height $row-height 3ch 1fr 3ch 3fr 4ch; + grid-template-columns: $row-height $row-height 3ch 1fr 3ch 3fr 5ch; } &--equipment { - grid-template-columns: $row-height $row-height 3ch 1fr 10ch 3fr 4ch; + grid-template-columns: $row-height $row-height 3ch 1fr 10ch 3fr 5ch; } &--loot { - grid-template-columns: $row-height 3ch 1fr 10ch 3fr 4ch; + grid-template-columns: $row-height 3ch 1fr 10ch 3fr 5ch; } &--spell { - grid-template-columns: $row-height $row-height 2fr $row-height 1fr 1fr 1fr 1fr 4ch; + grid-template-columns: $row-height $row-height 2fr $row-height 1fr 1fr 1fr 1fr 5ch; } &--talent { - grid-template-columns: $row-height 1fr 1fr 3fr 4ch; + grid-template-columns: $row-height 1fr 21ch 3fr 5ch; } &--racial-ability, &--language, &--alphabet, &--special-creature-ability { - grid-template-columns: $row-height 1fr 3fr 4ch; + grid-template-columns: $row-height 1fr 3fr 5ch; } &__row { @@ -53,13 +53,12 @@ height: $row-height; line-height: $row-height; white-space: nowrap; + overflow-y: hidden; } } &__image { - background-position: center; - background-repeat: no-repeat; - background-size: 100%; + border: none; } &__editable { @@ -89,6 +88,14 @@ text-overflow: ellipsis; } } + + &__control-buttons { + display: grid; + grid-template-columns: 1fr 1fr; + text-align: center; + width: 100%; + padding: 0 calc(1em / 3); + } } .ds4-item-list-title { diff --git a/src/scss/components/_rollable_image.scss b/src/scss/components/_rollable_image.scss new file mode 100644 index 00000000..ba33bca7 --- /dev/null +++ b/src/scss/components/_rollable_image.scss @@ -0,0 +1,33 @@ +.ds4-rollable-image { + position: relative; + + &--rollable { + cursor: pointer; + + &:hover { + .ds4-rollable-image__image { + opacity: 0.25; + } + + .ds4-rollable-image__overlay { + opacity: 1; + } + } + } + + &__image { + border: none; + transition: 0.1s ease; + } + + &__overlay { + border: none; + bottom: 0; + left: 0; + opacity: 0; + position: absolute; + right: 0; + top: 0; + transition: 0.1s ease; + } +} diff --git a/src/templates/actor/partials/item-list-entry.hbs b/src/templates/actor/partials/item-list-entry.hbs index f74093b6..ca08d0fc 100644 --- a/src/templates/actor/partials/item-list-entry.hbs +++ b/src/templates/actor/partials/item-list-entry.hbs @@ -17,7 +17,9 @@ {{/if}} {{!-- image --}} -
+ {{> systems/ds4/templates/common/partials/rollable-image.hbs rollable=itemData.data.rollable src=itemData.img + alt=(localize "DS4.EntityImageAltText" name=itemData.name) title=itemData.name rollableTitle=(localize + "DS4.RollableImageRollableTitle" name=itemData.name) rollableClass="rollable-item"}} {{!-- amount --}} {{#if hasQuantity}} @@ -41,5 +43,5 @@ {{/unless}} {{!-- control buttons --}} - {{> systems/ds4/templates/actor/partials/overview-control-buttons.hbs }} + {{> systems/ds4/templates/actor/partials/overview-control-buttons.hbs class="ds4-item-list__control-buttons" }} diff --git a/src/templates/actor/partials/items-overview.hbs b/src/templates/actor/partials/items-overview.hbs index c3544805..094109b1 100644 --- a/src/templates/actor/partials/items-overview.hbs +++ b/src/templates/actor/partials/items-overview.hbs @@ -20,10 +20,8 @@ {{#each itemsByType.weapon as |itemData id|}} {{#> systems/ds4/templates/actor/partials/item-list-entry.hbs itemData=itemData isEquipable=true hasQuantity=true}} {{!-- attack type --}} -
-
+ {{!-- weapon bonus --}}
{{ itemData.data.weaponBonus}}
diff --git a/src/templates/actor/partials/overview-control-buttons.hbs b/src/templates/actor/partials/overview-control-buttons.hbs index 2c85899f..ac7177b2 100644 --- a/src/templates/actor/partials/overview-control-buttons.hbs +++ b/src/templates/actor/partials/overview-control-buttons.hbs @@ -1,8 +1,10 @@ {{!-- !-- Render a group of an "edit" and a "delete" button for the current item. !-- The current item is defined by the data-item-id HTML property of the parent li element. +!-- @param class: Additional CSS class(es) for the controls --}} -
+
- +
diff --git a/src/templates/actor/partials/spells-overview.hbs b/src/templates/actor/partials/spells-overview.hbs index e9861c77..e5f9b008 100644 --- a/src/templates/actor/partials/spells-overview.hbs +++ b/src/templates/actor/partials/spells-overview.hbs @@ -60,10 +60,8 @@ titleKey=titleKey}} {{#> systems/ds4/templates/actor/partials/item-list-entry.hbs itemData=itemData isEquipable=true hideDescription=true}} {{!-- spell type --}} -
-
+ {{!-- spell bonus --}} + {{alt}} + {{#if rollable}} + {{localize 'DS4.DiceOverlayImageAltText'}} + {{/if}} +
diff --git a/src/templates/common/simple-select-form.hbs b/src/templates/common/simple-select-form.hbs new file mode 100644 index 00000000..dc82f319 --- /dev/null +++ b/src/templates/common/simple-select-form.hbs @@ -0,0 +1,19 @@ +{{!-- +!-- Render a simple form with a single select element. It uses the default form classes of Foundry VTT. +!-- @param identifier: The identifier to use as id for the select element. Can be used to query the value later on. +!-- @param label: Text to display as the label for the select element. +!-- @param options: Key-value pairs that describe the options. The keys are used for the value attribute of the +options, the values are used as content. +--}} +
+
+ +
+ +
+
+