diff --git a/lang/en-au.json b/lang/en-au.json index 1393762e..41fe8bb1 100644 --- a/lang/en-au.json +++ b/lang/en-au.json @@ -199,6 +199,7 @@ "DND4EBETA.ComponentMaterial": "Material", "DND4EBETA.ComponentSomatic": "Somatic", "DND4EBETA.ComponentVerbal": "Verbal", +"DND4EBETA.Condition": "Condition", "DND4EBETA.ConBlinded": "Blinded", "DND4EBETA.Concentration": "Concentration", "DND4EBETA.ConCharmed": "Charmed", @@ -1060,6 +1061,7 @@ "DND4EBETA.SpokenPrimordial": "Primordial", "DND4EBETA.SpokenSupernal": "Supernal", "DND4EBETA.Stance": "Stance", +"DND4EBETA.StatusConditions": "Status Conditions", "DND4EBETA.SubclassName": "Subclass Name", "DND4EBETA.SubName": "Class Name Attack 1", "DND4EBETA.Summoning": "Summoning", @@ -1421,12 +1423,15 @@ "EFFECTDESC.hidden": "The creature is hidden or disguised.", "EFFECTDESC.sneaking": "The creature is being stealthy. It is hidden (invisible and silent) from creatures that fail to detect it.", "EFFECTDESC.torch": "The creature is holding a light source.", -"DND4EUI.AttributeKey": "Attribute Key", +"ERROR.4eCopyStatusDetails": "Could not copy status details. This is expected if you have changed the standard list of status conditions since adding one to this effect.", "DND4EUI.AddNew": "Add New", +"DND4EUI.All": "All", +"DND4EUI.AttributeKey": "Attribute Key", "DND4EUI.Data Type": "Data Type", "DND4EUI.Delete": "Delete", "DND4EUI.GroupBy": "Group By", "DND4EUI.HowSelectMultiple": "Hold ctrl/cmd to select multiple", +"DND4EUI.Icon": "Icon", "DND4EUI.Import": "Import", "DND4EUI.ImportJSONInput": "Input JSON to import in the field below", "DND4EUI.ImportJSONUpload": "Upload JSON", @@ -1436,6 +1441,8 @@ "DND4EUI.ShowImage": "Show image", "DND4EUI.SortBy": "Sort By", "DND4EUI.StringEnterValues": "Enter values (separate entries with semicolons)", +"DND4EUI.UnknownCondition.Label": "Unknown value", +"DND4EUI.UnknownCondition.Tip": "No match was found for this id in the current status conditions configuration.", "DND4EUI.Yes": "Yes", "SETTINGS.4eAutoCollapseCardL": "Automatically collapse Item Card descriptions in the Chat Log", "SETTINGS.4eAutoCollapseCardN": "Collapse Item Cards in Chat", diff --git a/lang/en.json b/lang/en.json index 7370845d..ae41df0b 100644 --- a/lang/en.json +++ b/lang/en.json @@ -199,6 +199,7 @@ "DND4EBETA.ComponentMaterial": "Material", "DND4EBETA.ComponentSomatic": "Somatic", "DND4EBETA.ComponentVerbal": "Verbal", +"DND4EBETA.Condition": "Condition", "DND4EBETA.ConBlinded": "Blinded", "DND4EBETA.Concentration": "Concentration", "DND4EBETA.ConCharmed": "Charmed", @@ -1060,6 +1061,7 @@ "DND4EBETA.SpokenPrimordial": "Primordial", "DND4EBETA.SpokenSupernal": "Supernal", "DND4EBETA.Stance": "Stance", +"DND4EBETA.StatusConditions": "Status Conditions", "DND4EBETA.SubclassName": "Subclass Name", "DND4EBETA.SubName": "Class Name Attack 1", "DND4EBETA.Summoning": "Summoning", @@ -1078,6 +1080,8 @@ "DND4EBETA.Sustain": "Sustain", "DND4EBETA.Target": "Target", "DND4EBETA.TargetAll": "Affect All Targets", +"DND4EBETA.TargetAllies": "Affect All Allies", +"DND4EBETA.TargetEnemies": "Affect All Enemies", "DND4EBETA.TargetHit": "Affect Hit Targets", "DND4EBETA.TargetMiss": "Affect Miss Targets", "DND4EBETA.TargetSelf": "Affect Self", @@ -1419,12 +1423,15 @@ "EFFECTDESC.hidden": "The creature is hidden or disguised.", "EFFECTDESC.sneaking": "The creature is being stealthy. It is hidden (invisible and silent) from creatures that fail to detect it.", "EFFECTDESC.torch": "The creature is holding a light source.", -"DND4EUI.AttributeKey": "Attribute Key", +"ERROR.4eCopyStatusDetails": "Could not copy status details. This is expected if you have changed the standard list of status conditions since adding one to this effect.", "DND4EUI.AddNew": "Add New", +"DND4EUI.All": "All", +"DND4EUI.AttributeKey": "Attribute Key", "DND4EUI.Data Type": "Data Type", "DND4EUI.Delete": "Delete", "DND4EUI.GroupBy": "Group By", "DND4EUI.HowSelectMultiple": "Hold ctrl/cmd to select multiple", +"DND4EUI.Icon": "Icon", "DND4EUI.Import": "Import", "DND4EUI.ImportJSONInput": "Input JSON to import in the field below", "DND4EUI.ImportJSONUpload": "Upload JSON", @@ -1434,6 +1441,8 @@ "DND4EUI.ShowImage": "Show image", "DND4EUI.SortBy": "Sort By", "DND4EUI.StringEnterValues": "Enter values (separate entries with semicolons)", +"DND4EUI.Unknown": "Unknown value", +"DND4EUI.UnknownCondition.Tip": "No match was found for this id in the current status conditions configuration.", "DND4EUI.Yes": "Yes", "SETTINGS.4eAutoCollapseCardL": "Automatically collapse Item Card descriptions in the Chat Log", "SETTINGS.4eAutoCollapseCardN": "Collapse Item Cards in Chat", @@ -1496,6 +1505,9 @@ "MIGRATION.4eBegin": "Applying DnD4E System Migration for version {version}. Please be patient and do not close your game or shut down your server.", "MIGRATION.4eComplete": "DnD4E System Migration to version {version} completed!", "MIGRATION.4eVersionTooOldWarning": "Your DnD4e system data is from too old a Foundry version and cannot be reliably migrated to the latest version. The process will be attempted, but errors may occur.", +"SHEET.Character.Basic": "Basic Character Sheet", +"SHEET.NPC": "NPC Sheet", +"SHEET.Item": "Item Sheet", "TYPES.Actor.NPC": "NPC", "TYPES.Actor.Player Character": "Player Character", "TYPES.Item.backpack": "Container", diff --git a/module/effects/effects-config.js b/module/effects/effects-config.js index 964e5f48..9c6eabe5 100644 --- a/module/effects/effects-config.js +++ b/module/effects/effects-config.js @@ -19,6 +19,7 @@ export default class ActiveEffectConfig4e extends ActiveEffectConfig { data.config = CONFIG.DND4EBETA; data.powerParent = (["power", "consumable"].includes(this.object.parent.type)); + data.config.statusEffects = CONFIG.statusEffects; return data; } @@ -29,6 +30,7 @@ export default class ActiveEffectConfig4e extends ActiveEffectConfig { activateListeners(html) { super.activateListeners(html); html.find(".effect-dot-control").click(this._onEffectDotControl.bind(this)); + html.find(".effect-status-control").click(this._onEffectStatusControl.bind(this)); html.find(".refreshes").change(this._refresh.bind(this)); } @@ -53,6 +55,31 @@ export default class ActiveEffectConfig4e extends ActiveEffectConfig { /* ----------------------------------------- */ + /** + * Handling for mouse clicks on status control buttons - adapted from _onEffectControl + * Delegate responsibility out to action-specific handlers depending on the button action. + * @param {MouseEvent} event The originating click event + */ + _onEffectStatusControl(event) { + event.preventDefault(); + const button = event.currentTarget; + switch ( button.dataset.action ) { + case "copy-name": + case "copy-icon": + case "copy-desc": + case "copy-all": + const statusId = button.closest(".effect-status").getAttribute("data-status-id"); + return this._copyStatusDetails(statusId,button.dataset.action).then(() => this.render()); + case "add": + return this._addEffectStatus(); + case "delete": + button.closest(".effect-status").remove(); + return this.submit({preventClose: true}).then(() => this.render()); + } + } + + /* ----------------------------------------- */ + /** * Handle adding a new dot to the dots array - adapted from _addEffectChange */ @@ -64,6 +91,51 @@ export default class ActiveEffectConfig4e extends ActiveEffectConfig { }}); } + /* ----------------------------------------- */ + + /** + * Handle adding a new status to the statuses array - adapted from _addEffectChange + */ + async _addEffectStatus() { + const i = this.document.statuses.size; + return this.submit({preventClose: true, updateData: { + [`statuses.${i}`] : "none" + }}); + } + + /* ----------------------------------------- */ + + /** + * Copy fluff to effect from status condition config + */ + async _copyStatusDetails(statusId,scope="copy-all") { + if(!statusId) return; + + const statuses = CONFIG.statusEffects; + + try{ + //I remembered error handling this time! This should be expected to fail if the status id isn't found, such as if you have remapped your conditions since setting up the effect. + + const statusIndex = statuses.findIndex((x) => x.id == statusId); + let effectUpdates = {}; + + if(scope == "copy-name" || scope == "copy-all"){ + effectUpdates.name = game.i18n.localize(statuses[statusIndex].label); + } + if(scope == "copy-icon" || scope == "copy-all"){ + effectUpdates.icon = statuses[statusIndex].icon; + } + if(scope == "copy-desc" || scope == "copy-all"){ + effectUpdates.description = game.i18n.localize(statuses[statusIndex].description); + } + + return this.submit({preventClose: true, updateData: effectUpdates }); + + } catch(err) { + ui.notifications.error(game.i18n.localize('ERROR.4eCopyStatusDetails')); + } + } + /* ----------------------------------------- */ /* I'm really worried that I had to override this method from the core class. I'm afraid it might mess up module compatibility or something! @@ -74,18 +146,14 @@ export default class ActiveEffectConfig4e extends ActiveEffectConfig { let data = foundry.utils.expandObject(fd.object); if ( updateData ) foundry.utils.mergeObject(data, updateData); data.changes = Array.from(Object.values(data.changes || {})); + data.statuses = Array.from(Object.values(data.statuses || {})).filter(x => x); + //The form throws an error if it's updated while there is an unselected status condition row. I can't find a way to catch it, so instead I'm just trimming data.flags.dnd4e.dots = Array.from(Object.values(data.flags.dnd4e.dots || {})); if (data.flags.dnd4e.dots.length){ for (let [i, dot] of data.flags.dnd4e.dots.entries()){ data.flags.dnd4e.dots[i].amount = dot.amount; data.flags.dnd4e.dots[i].typesArray = dot.typesArray.sort(); - /*if(!dot.type) { - data.flags.dnd4e.dots[i].typesArray = ['physical']; - } else { - let type = dot.type.toLowerCase().replaceAll(/( and )|(;(?! ))|(; )|(, )|(,(?! ))|([^;,]) (?!and)/g,"$6|"); - data.flags.dnd4e.dots[i].typesArray = type.split("|").sort(); - }*/ } } diff --git a/module/helper.js b/module/helper.js index 032d9127..cb6c80d9 100644 --- a/module/helper.js +++ b/module/helper.js @@ -1252,6 +1252,26 @@ export async function handleAutoDoTs(data) { await actor.autoDoTsSocket(data.tokenID); } -Handlebars.registerHelper('contains', function(lunch, lunchbox) { - return lunchbox.includes(lunch); +/* "Contains" Handlebars Helper: checks if a value exists in an array. +* +* @param {String} lunch The value to find +* @param {Array} lunchbox The array to search +* @param {String} meal (optional) A key to pair with lunch +* @returns {boolean} true if lunch exists in lunchbox. +* If meal is provided, lunchbox is assumed to contain objects, +* and will search for one where meal = lunch. +* +* I don't know why, but meal is apparently the helper object, +* if not given? Not a null, which would have been useful :\ +* Anyway the type check shoudl take care of it. +/* */ +Handlebars.registerHelper('contains', function(lunch, lunchbox, meal) { + try{ + if(typeof meal != "string") return lunchbox.includes(lunch); + const lunchLocation = lunchbox.findIndex((x) => x[meal] == lunch); + if(lunchLocation > 0) return true; + return false; + } catch(err) { + return "Contains helper spat up. Did you give it the right parameter types?"; + } }); \ No newline at end of file diff --git a/styles/dnd4eBeta-DarkMode.css b/styles/dnd4eBeta-DarkMode.css index 5c25103e..6e5ce6aa 100644 --- a/styles/dnd4eBeta-DarkMode.css +++ b/styles/dnd4eBeta-DarkMode.css @@ -9,6 +9,8 @@ --color-text-light-primary:#c9c7b8; --color-text-light-heading:#e9e7d6; --color-text-light-highlight:#f7f5e8; + --color-text-good:#0fe75f; + --color-text-bad:#ff6060; } input::placeholder,.placeholder::after{ color:var(--color-text-light-primary); @@ -176,6 +178,11 @@ img[src$="svg"].rollable:hover{ .dnd4eBeta.sheet .npc .sheet-header .automath img{ filter:unset; } +.active-effect-sheet .effect-dot:not(:last-child), +.active-effect-sheet .effect-status:not(:last-child), +.active-effect-sheet .effect-change:not(:last-child){ + border-bottom:1px solid rgba(255,255,255,0.1); +} /** CHAT **/ @@ -186,18 +193,11 @@ a.content-link, a.inline-roll{ background:var(--color-bg-input); border-color:var(--color-border-light-tertiary); } -.dice-roll .hit-prediction.critical, -.dice-roll .hit-prediction.probable-hit, -.dot-report .damage-taken.resistant-full, -.dot-report .damage-taken.healing{ - color:#0fe75f; -} -.dice-roll .hit-prediction.fumble, -.dice-roll .hit-prediction.probable-miss, -.dot-report .damage-taken:not(.resistant-full,.healing){ - color:#ff6060; +.chat-message.whisper{ + background:rgba(219,219,251,0.23); } + /** JOURNALS **/ .sheet.journal-entry .journal-entry-content{ diff --git a/styles/dnd4eBeta.css b/styles/dnd4eBeta.css index 35fc653b..8e5714d5 100644 --- a/styles/dnd4eBeta.css +++ b/styles/dnd4eBeta.css @@ -85,6 +85,8 @@ textarea{ --color-bg-input:rgba(0,0,0,0.075); --color-bg-select:rgba(0,0,0,0.075); --color-label:var(--color-text-dark-secondary); + --color-text-good:green; + --color-text-bad:red; --gradient-4e:linear-gradient(90deg, #c3c2b7, #c3c2b718); --background-atwill:-o-linear-gradient(45deg, #125f15, #55aa2e); --background-atwill:linear-gradient(45deg, #125f15, #55aa2e); @@ -413,7 +415,7 @@ textarea{ margin-left:15px; border-right:var(--border-heavy); height:100%; - /* overflow-y:auto; */ + overflow-y:auto; } .dnd4eBeta .section--abilities{ margin-right:15px; @@ -464,8 +466,6 @@ textarea{ .dnd4eBeta .section--skills{ margin-right:15px; margin-top:10px; - overflow-y: auto; - height:calc(100% - 195px); } .dnd4eBeta .section--skills .skill--block{ min-height:27px; @@ -2184,18 +2184,38 @@ font-size:12px; .active-effect-sheet .effects-header>:first-child{ padding-left:4px; } -.active-effect-sheet .dots-list{ +.active-effect-sheet .dots-list, +.active-effect-sheet .statuses-list{ list-style:none; margin:0; padding:0; margin-bottom:2em; } -.active-effect-sheet .effect-dot{ +.active-effect-sheet .effect-dot, +.active-effect-sheet .effect-status{ align-items:center; padding:3px 1px; } -.active-effect-sheet .effect-dot:not(:last-child){ - border-bottom:1px solid var(--color-border-light-primary); +.active-effect-sheet .effect-dot:not(:last-child), +.active-effect-sheet .effect-status:not(:last-child), +.active-effect-sheet .effect-change:not(:last-child){ + border-bottom:1px solid rgba(0,0,0,0.1); +} +.active-effect-sheet .effect-status .status-select{ + flex-basis:8.5em; + margin-right:4px; +} +.active-effect-sheet .effect-status .status-controls{ + flex-basis:19.5em; +} +.active-effect-sheet .effect-status .status-select select{ + width:100%; +} +.active-effect-sheet .effect-status button{ + border-radius:3px; + font-size:80%; + line-height:1; + padding:0.5em; } @@ -2612,10 +2632,10 @@ span.flavor-text.probable-miss{ border:1px solid #6e0000; } .dice-roll .dice-total.critical{ - color:green; + color:var(--color-text-good); } .dice-roll .dice-total.fumble{ - color:red; + color:var(--color-text-bad); } .dice-roll .hit-prediction::before{ content:"\1F894"; @@ -2625,13 +2645,13 @@ span.flavor-text.probable-miss{ .dice-roll .hit-prediction.fumble, .dice-roll .hit-prediction.probable-miss, .dot-report .damage-taken:not(.resistant-full,.healing){ - color:red; + color:var(--color-text-bad); } .dice-roll .hit-prediction.critical, .dice-roll .hit-prediction.probable-hit, .dot-report .damage-taken.resistant-full, .dot-report .damage-taken.healing{ - color:green; + color:var(--color-text-good); } .dot-report .damage-taken{ font-weight:bold; diff --git a/templates/chat/roll-template-single.html b/templates/chat/roll-template-single.html index dcc19d94..c7149b69 100644 --- a/templates/chat/roll-template-single.html +++ b/templates/chat/roll-template-single.html @@ -5,7 +5,8 @@