diff --git a/less/original/chat.less b/less/original/chat.less index 7bf83c2a..6aca1df4 100644 --- a/less/original/chat.less +++ b/less/original/chat.less @@ -23,7 +23,7 @@ flex: 1; margin: 0; line-height: 36px; - .bungeeInline(); + .engli-Besh(); color: @colorOlive; &:hover { color: #111; diff --git a/module/actor/entity.js b/module/actor/entity.js index a25fd738..127a0d05 100644 --- a/module/actor/entity.js +++ b/module/actor/entity.js @@ -458,8 +458,8 @@ export default class Actor5e extends Actor { return weight + (q * w); }, 0); - // [Optional] add Currency Weight - if ( game.settings.get("sw5e", "currencyWeight") ) { + // [Optional] add Currency Weight (for non-transformed actors) + if ( game.settings.get("sw5e", "currencyWeight") && actorData.data.currency ) { const currency = actorData.data.currency; const numCoins = Object.values(currency).reduce((val, denom) => val += Math.max(denom, 0), 0); weight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight; @@ -553,43 +553,56 @@ export default class Actor5e extends Actor { const isNPC = this.data.type === 'npc'; let initial = {}; switch ( itemData.type ) { + case "weapon": - initial["data.equipped"] = isNPC; // NPCs automatically equip weapons - let hasWeaponProf = isNPC; // NPCs automatically have weapon proficiency - if ( !isNPC ) { - const weaponProf = { - "natural": true, - "simpleVW": "sim", - "simpleB": "sim", - "simpleLW": "sim", - "martialVW": "mar", - "martialB": "mar", - "martialLW": "mar" - }[itemData.data?.weaponType]; - const actorWeaponProfs = this.data.data.traits?.weaponProf?.value || []; - hasWeaponProf = (weaponProf === true) || actorWeaponProfs.includes(weaponProf); + if ( getProperty(itemData, "data.equipped") === undefined ) { + initial["data.equipped"] = isNPC; // NPCs automatically equip weapons + } + if ( getProperty(itemData, "data.proficient") === undefined ) { + if ( isNPC ) { + initial["data.proficient"] = true; // NPCs automatically have equipment proficiency + } else { + const weaponProf = { + "natural": true, + "simpleVW": "sim", + "simpleB": "sim", + "simpleLW": "sim", + "martialVW": "mar", + "martialB": "mar", + "martialLW": "mar" + }[itemData.data?.weaponType]; // Player characters check proficiency + const actorWeaponProfs = this.data.data.traits?.weaponProf?.value || []; + const hasWeaponProf = (weaponProf === true) || actorWeaponProfs.includes(weaponProf); + initial["data.proficient"] = hasWeaponProf; + } } - initial["data.proficient"] = hasWeaponProf; break; + case "equipment": - initial["data.equipped"] = isNPC; // NPCs automatically equip equipment - let hasEquipmentProf = isNPC; // NPCs automatically have equipment proficiency - if ( !isNPC ) { - const armorProf = { - "natural": true, - "clothing": true, - "light": "lgt", - "medium": "med", - "heavy": "hvy", - "shield": "shl" - }[itemData.data?.armor?.type]; - const actorArmorProfs = this.data.data.traits?.armorProf?.value || []; - hasEquipmentProf = (armorProf === true) || actorArmorProfs.includes(armorProf); + if ( getProperty(itemData, "data.equipped") === undefined ) { + initial["data.equipped"] = isNPC; // NPCs automatically equip equipment + } + if ( getProperty(itemData, "data.proficient") === undefined ) { + if ( isNPC ) { + initial["data.proficient"] = true; // NPCs automatically have equipment proficiency + } else { + const armorProf = { + "natural": true, + "clothing": true, + "light": "lgt", + "medium": "med", + "heavy": "hvy", + "shield": "shl" + }[itemData.data?.armor?.type]; // Player characters check proficiency + const actorArmorProfs = this.data.data.traits?.armorProf?.value || []; + const hasEquipmentProf = (armorProf === true) || actorArmorProfs.includes(armorProf); + initial["data.proficient"] = hasEquipmentProf; + } } - initial["data.proficient"] = hasEquipmentProf; break; + case "power": - initial["data.prepared"] = true; // NPCs automatically prepare powers + initial["data.prepared"] = true; // automatically prepare powers for everyone break; } mergeObject(itemData, initial); @@ -1103,7 +1116,7 @@ export default class Actor5e extends Actor { // Recover power slots for ( let [k, v] of Object.entries(data.powers) ) { - updateData[`data.powers.${k}.value`] = !Number.isNaN(v.override) ? v.override : (v.max ?? 0); + updateData[`data.powers.${k}.value`] = Number.isNumeric(v.override) ? v.override : (v.max ?? 0); } // Recover pact slots. @@ -1210,10 +1223,10 @@ export default class Actor5e extends Actor { } // Get the original Actor data and the new source data - const o = this.toJSON(); + const o = duplicate(this.toJSON()); o.flags.sw5e = o.flags.sw5e || {}; o.flags.sw5e.transformOptions = {mergeSkills, mergeSaves}; - const source = target.toJSON(); + const source = duplicate(target.toJSON()); // Prepare new data to merge from the source const d = { @@ -1244,7 +1257,7 @@ export default class Actor5e extends Actor { // Handle wildcard if ( source.token.randomImg ) { const images = await target.getTokenImages(); - d.token.img = images[0]; + d.token.img = images[Math.floor(Math.random() * images.length)]; } // Keep Token configurations @@ -1328,7 +1341,7 @@ export default class Actor5e extends Actor { newTokenData.actorId = newActor.id; return newTokenData; }); - return canvas.scene.updateEmbeddedEntity("Token", updates); + return canvas.scene?.updateEmbeddedEntity("Token", updates); } /* -------------------------------------------- */ diff --git a/module/actor/sheets/newSheet/base.js b/module/actor/sheets/newSheet/base.js index 1851c0a0..475f5a6d 100644 --- a/module/actor/sheets/newSheet/base.js +++ b/module/actor/sheets/newSheet/base.js @@ -619,6 +619,11 @@ export default class ActorSheet5e extends ActorSheet { itemData = scroll.data; } + // Ignore certain statuses + if ( itemData.data ) { + ["attunement", "equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]); + } + // Create the owned item as normal return super._onDropItemCreate(itemData); } diff --git a/module/actor/sheets/oldSheets/base.js b/module/actor/sheets/oldSheets/base.js index 485a78b7..c97dd95a 100644 --- a/module/actor/sheets/oldSheets/base.js +++ b/module/actor/sheets/oldSheets/base.js @@ -619,6 +619,11 @@ export default class ActorSheet5e extends ActorSheet { itemData = scroll.data; } + // Ignore certain statuses + if ( itemData.data ) { + ["attunement", "equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]); + } + // Create the owned item as normal return super._onDropItemCreate(itemData); } diff --git a/module/apps/ability-use-dialog.js b/module/apps/ability-use-dialog.js index a0f8b716..cc277f9b 100644 --- a/module/apps/ability-use-dialog.js +++ b/module/apps/ability-use-dialog.js @@ -168,6 +168,8 @@ export default class AbilityUseDialog extends Dialog { type: item.data.consumableType, value: uses.value, quantity: item.data.quantity, + max: uses.max, + per: CONFIG.SW5E.limitedUsePeriods[uses.per] }); } diff --git a/module/chat.js b/module/chat.js index f5b72e8d..f8ab035f 100644 --- a/module/chat.js +++ b/module/chat.js @@ -66,7 +66,7 @@ export const displayChatActionButtons = function(message, html, data) { export const addChatMessageContextOptions = function(html, options) { let canApply = li => { const message = game.messages.get(li.data("messageId")); - return message.isRoll && message.isContentVisible && canvas.tokens.controlled.length; + return message?.isRoll && message?.isContentVisible && canvas?.tokens.controlled.length; }; options.push( { @@ -103,15 +103,16 @@ export const addChatMessageContextOptions = function(html, options) { * Apply rolled dice damage to the token or tokens which are currently controlled. * This allows for damage to be scaled by a multiplier to account for healing, critical hits, or resistance * - * @param {HTMLElement} roll The chat entry which contains the roll data + * @param {HTMLElement} li The chat entry which contains the roll data * @param {Number} multiplier A damage multiplier to apply to the rolled damage. * @return {Promise} */ -function applyChatCardDamage(roll, multiplier) { - const amount = roll.find('.dice-total').text(); +function applyChatCardDamage(li, multiplier) { + const message = game.messages.get(li.data("messageId")); + const roll = message.roll; return Promise.all(canvas.tokens.controlled.map(t => { const a = t.actor; - return a.applyDamage(amount, multiplier); + return a.applyDamage(roll.total, multiplier); })); } diff --git a/module/combat.js b/module/combat.js index b407c33a..8132a5ac 100644 --- a/module/combat.js +++ b/module/combat.js @@ -12,7 +12,7 @@ export const _getInitiativeFormula = function(combatant) { let nd = 1; let mods = ""; - + if (actor.getFlag("sw5e", "halflingLucky")) mods += "r1=1"; if (actor.getFlag("sw5e", "initiativeAdv")) { nd = 2; @@ -26,15 +26,3 @@ export const _getInitiativeFormula = function(combatant) { if ( tiebreaker ) parts.push(actor.data.data.abilities.dex.value / 100); return parts.filter(p => p !== null).join(" + "); }; - -/** - * When the Combat encounter updates - re-render open Actor sheets for combatants in the encounter. - */ -Hooks.on("updateCombat", (combat, data, options, userId) => { - const updateTurn = ("turn" in data) || ("round" in data); - if ( !updateTurn ) return; - for ( let t of combat.turns ) { - const a = t.actor; - if ( t.actor ) t.actor.sheet.render(false); - } -}); diff --git a/module/dice.js b/module/dice.js index c05dd3df..cea097fd 100644 --- a/module/dice.js +++ b/module/dice.js @@ -42,7 +42,7 @@ export function simplifyRollFormula(formula, data, {constantFirst = false} = {}) const parts = constantFirst ? // Order the rollable and constant terms, either constant first or second depending on the optional argumen [constantPart, rollableFormula] : [rollableFormula, constantPart]; - + // Join the parts with a + sign, pass them to `Roll` once again to clean up the formula return new Roll(parts.filterJoin(" + ")).formula; } @@ -52,7 +52,7 @@ export function simplifyRollFormula(formula, data, {constantFirst = false} = {}) /** * Only some terms are supported by simplifyRollFormula, this method returns true when the term is not supported. * @param {*} term - A single Dice term to check support on - * @return {Boolean} True when unsupported, false if supported + * @return {Boolean} True when unsupported, false if supported */ function _isUnsupportedTerm(term) { const diceTerm = term instanceof DiceTerm; @@ -110,8 +110,8 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ let adv = 0; fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)); if (fastForward) { - if ( advantage || event.altKey ) adv = 1; - else if ( disadvantage || event.ctrlKey || event.metaKey ) adv = -1; + if ( advantage ?? event.altKey ) adv = 1; + else if ( disadvantage ?? (event.ctrlKey || event.metaKey) ) adv = -1; } // Define the inner roll function diff --git a/module/item/entity.js b/module/item/entity.js index a1b64f0c..8b35bcc5 100644 --- a/module/item/entity.js +++ b/module/item/entity.js @@ -251,12 +251,14 @@ export default class Item5e extends Item { // Item Actions if ( data.hasOwnProperty("actionType") ) { + // if this item is owned, we populate the label and saving throw during actor init + if (!this.isOwned) { + // Saving throws + this.getSaveDC(); - // Saving throws - this.getSaveDC(); - - // To Hit - this.getAttackToHit(); + // To Hit + this.getAttackToHit(); + } // Damage let dam = data.damage || {}; @@ -312,7 +314,7 @@ export default class Item5e extends Item { * - item's actor's proficiency bonus if applicable * - item's actor's global bonuses to the given item type * - item's ammunition if applicable - * + * * @returns {Object} returns `rollData` and `parts` to be used in the item's Attack roll */ getAttackToHit() { @@ -915,7 +917,8 @@ export default class Item5e extends Item { if ( powerLevel ) rollData.item.level = powerLevel; // Configure the damage roll - const title = `${this.name} - ${game.i18n.localize("SW5E.DamageRoll")}`; + const actionFlavor = game.i18n.localize(itemData.actionType === "heal" ? "SW5E.Healing" : "SW5E.DamageRoll"); + const title = `${this.name} - ${actionFlavor}`; const rollConfig = { actor: this.actor, critical: critical ?? event?.altKey ?? false, diff --git a/module/templates.js b/module/templates.js index f3de3dc4..af337996 100644 --- a/module/templates.js +++ b/module/templates.js @@ -14,11 +14,11 @@ export const preloadHandlebarsTemplates = async function() { "systems/sw5e/templates/actors/oldActor/parts/actor-inventory.html", "systems/sw5e/templates/actors/oldActor/parts/actor-features.html", "systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html", - "systems/sw5e/templates/actors/oldActor/parts/actor-notes.html", + "systems/sw5e/templates/actors/oldActor/parts/actor-notes.html", "systems/sw5e/templates/actors/newActor/parts/swalt-biography.html", "systems/sw5e/templates/actors/newActor/parts/swalt-core.html", - "systems/sw5e/templates/actors/newActor/parts/swalt-active-effects.html", + "systems/sw5e/templates/actors/newActor/parts/swalt-active-effects.html", "systems/sw5e/templates/actors/newActor/parts/swalt-features.html", "systems/sw5e/templates/actors/newActor/parts/swalt-inventory.html", "systems/sw5e/templates/actors/newActor/parts/swalt-powerbook.html", diff --git a/sw5e copy.css b/sw5e copy.css index 58d9546a..237bb2cc 100644 --- a/sw5e copy.css +++ b/sw5e copy.css @@ -10,12 +10,16 @@ font-weight: 400; src: url('./fonts/RussoOne.ttf'); } -/* bungee-inline-regular - latin */ @font-face { - font-family: 'Bungee Inline'; + font-family: 'Engli-Besh'; font-style: normal; font-weight: 400; - src: url('./fonts/BungeeInline.ttf'); + src: url('./fonts/EngliBesh-KG3W.ttf'); +} +.engli-Besh { + font-family: 'Engli-Besh'; + font-size: 20px; + font-weight: 400; } /* open-sans-regular - latin */ @font-face { diff --git a/sw5e-global.css b/sw5e-global.css index a54ef3d5..a9023252 100644 --- a/sw5e-global.css +++ b/sw5e-global.css @@ -54,11 +54,6 @@ font-weight: 400; src: url('./fonts/EngliBesh-KG3W.ttf'); } -.engli-Besh { - font-family: 'Engli-Besh'; - font-size: 20px; - font-weight: 400; -} /* ----------------------------------------- */ /* Fonts */ /* ----------------------------------------- */ @@ -768,7 +763,7 @@ input[type="reset"]:disabled { grid-template-rows: 1fr 26px auto; grid-template-columns: 128px 1fr; column-gap: 8px; - row-gap: 8px; + grid-row-gap: 8px; } .sw5e.sheet.actor .swalt-sheet header img { grid-column-start: 1; @@ -1390,7 +1385,7 @@ input[type="reset"]:disabled { display: grid; grid-template-columns: 1fr 1fr; grid-gap: 4px; - row-gap: 4px; + grid-row-gap: 4px; } .sw5e.sheet.actor .swalt-sheet .tab.attributes .traits-resources section.traits ul.passives strong { font-size: 13px; @@ -1601,7 +1596,7 @@ input[type="reset"]:disabled { } .sw5e.sheet.actor .swalt-sheet.limited { grid-template-rows: 144px auto; - row-gap: 8px; + grid-row-gap: 8px; } .sw5e.sheet.actor .swalt-sheet.limited header { grid-template-rows: 1fr; diff --git a/sw5e.css b/sw5e.css index 98dde4b5..11a330c5 100644 --- a/sw5e.css +++ b/sw5e.css @@ -508,6 +508,41 @@ height: 24px; margin: 2px; } +/* ----------------------------------------- */ +/* HUD +/* ----------------------------------------- */ +.placeable-hud .control-icon { + box-sizing: content-box; + width: 40px; + flex: 0 0 40px; + margin: 8px 0; + font-size: 28px; + line-height: 40px; + text-align: center; + color: #FBF4F4; + background: rgba(0, 0, 0, 0.6); + box-shadow: 0 0 15px #000; + border: 1px solid #333; + border-radius: 8px; + pointer-events: all; +} +#token-hud .status-effects { + visibility: hidden; + position: absolute; + left: 50px; + top: 0; + display: grid; + padding: 3px; + box-sizing: content-box; + width: 100px; + color: #FBF4F4; + grid-template-columns: 25px 25px 25px 25px; + background: rgba(0, 0, 0, 0.6); + box-shadow: 0 0 15px #000; + border: 1px solid #333; + border-radius: 4px; + pointer-events: all; +} .sw5e.sheet.actor { /* ----------------------------------------- */ /* Sheet Header */ @@ -1557,7 +1592,7 @@ flex: 1; margin: 0; line-height: 36px; - font-family: 'Bungee Inline'; + font-family: 'Engli-Besh'; font-size: 20px; font-weight: 400; color: #4b4a44; @@ -1837,35 +1872,3 @@ max-width: 40px; text-align: right; } -.placeable-hud .control-icon { - box-sizing: content-box; - width: 40px; - flex: 0 0 40px; - margin: 8px 0; - font-size: 28px; - line-height: 40px; - text-align: center; - color: #FBF4F4; - background: rgba(0, 0, 0, 0.6); - box-shadow: 0 0 15px #000; - border: 1px solid #333; - border-radius: 8px; - pointer-events: all; -} -#token-hud .status-effects { - visibility: hidden; - position: absolute; - left: 50px; - top: 0; - display: grid; - padding: 3px; - box-sizing: content-box; - width: 100px; - color: #FBF4F4; - grid-template-columns: 25px 25px 25px 25px; - background: rgba(0, 0, 0, 0.6); - box-shadow: 0 0 15px #000; - border: 1px solid #333; - border-radius: 4px; - pointer-events: all; -} diff --git a/sw5e.js b/sw5e.js index bdbfb497..7c110d31 100644 --- a/sw5e.js +++ b/sw5e.js @@ -26,6 +26,7 @@ import ActorSheet5eCharacter from "./module/actor/sheets/oldSheets/character.js" import ActorSheet5eNPC from "./module/actor/sheets/oldSheets/npc.js"; import ActorSheet5eVehicle from "./module/actor/sheets/oldSheets/vehicle.js"; import ActorSheet5eCharacterNew from "./module/actor/sheets/newSheet/character.js"; +import ActorSheet5eNPCNew from "./module/actor/sheets/newSheet/npc.js"; import ItemSheet5e from "./module/item/sheet.js"; import ShortRestDialog from "./module/apps/short-rest.js"; import TraitSelector from "./module/apps/trait-selector.js"; @@ -53,6 +54,7 @@ Hooks.once("init", function() { ActorSheet5eCharacter, ActorSheet5eCharacterNew, ActorSheet5eNPC, + ActorSheet5eNPCNew, ActorSheet5eVehicle, ItemSheet5e, ShortRestDialog, @@ -86,7 +88,7 @@ Hooks.once("init", function() { // 5e cone RAW should be 53.13 degrees CONFIG.MeasuredTemplate.defaults.angle = 53.13; - + // Add DND5e namespace for module compatability game.dnd5e = game.sw5e; CONFIG.DND5E = CONFIG.SW5E; @@ -110,11 +112,16 @@ Hooks.once("init", function() { makeDefault: false, label: "SW5E.SheetClassCharacterOld" }); - Actors.registerSheet("sw5e", ActorSheet5eNPC, { + Actors.registerSheet("sw5e", ActorSheet5eNPCNew, { types: ["npc"], makeDefault: true, label: "SW5E.SheetClassNPC" }); + Actors.registerSheet("sw5e", ActorSheet5eNPC, { + types: ["npc"], + makeDefault: false, + label: "SW5E.SheetClassNPCOld" + }); Actors.registerSheet('sw5e', ActorSheet5eVehicle, { types: ['vehicle'], makeDefault: true, diff --git a/system.json b/system.json index daa334a3..dd9d6803 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "name": "sw5e", "title": "SW 5th Edition", "description": "A comprehensive game system for running games of SW 5th Edition in the Foundry VTT environment.", - "version": "1.2.2", + "version": "1.2.3", "author": "Dev Team", "scripts": [], "esmodules": ["sw5e.js"], diff --git a/templates/actors/newActor/character-sheet.html b/templates/actors/newActor/character-sheet.html index e96d7d3c..58de5c48 100644 --- a/templates/actors/newActor/character-sheet.html +++ b/templates/actors/newActor/character-sheet.html @@ -115,7 +115,6 @@ - {{!-- PC Sheet Body --}}
@@ -143,4 +142,4 @@
{{> "systems/sw5e/templates/actors/newActor/parts/swalt-biography.html"}}
- + \ No newline at end of file