Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"useTabs": false,
"printWidth": 100
}
8 changes: 7 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,11 @@
"BASICFANTASYRPG.EffectCreate": "Create Effect",
"BASICFANTASYRPG.EffectToggle": "Toggle Effect",
"BASICFANTASYRPG.EffectEdit": "Edit Effect",
"BASICFANTASYRPG.EffectDelete": "Delete Effect"
"BASICFANTASYRPG.EffectDelete": "Delete Effect",

"BASICFANTASYRPG.Settings.SavesMenu.name": "Configure Saving Throw Names",
"BASICFANTASYRPG.Settings.SavesMenu.label": "Saving Throw Settings",
"BASICFANTASYRPG.Settings.SavesMenu.hint": "",
"BASICFANTASYRPG.SaveName.hint": "Custom name for this saving throw. The same name is used for all players."

}
7 changes: 6 additions & 1 deletion lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,10 @@
"BASICFANTASYRPG.EffectCreate": "Créer un Effet",
"BASICFANTASYRPG.EffectToggle": "Effet On/Off",
"BASICFANTASYRPG.EffectEdit": "Modifier l'Effet",
"BASICFANTASYRPG.EffectDelete": "Supprimer l'Effet"
"BASICFANTASYRPG.EffectDelete": "Supprimer l'Effet",

"BASICFANTASYRPG.AutoRollTokenHP.name": "Automatically Roll Token HP",
"BASICFANTASYRPG.AutoRollTokenHP.hint": "Based on HD value. If this setting is turned off, HP will be set to TODO WORK OUT WHAT",

"BASICFANTASYRPG.SaveName.hint": "Custom name for this saving throw. The same name is used for all players."
}
14 changes: 14 additions & 0 deletions module/basicfantasyrpg.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { BasicFantasyRPGItemSheet } from './sheets/item-sheet.mjs';
// Import helper/utility classes and constants.
import { preloadHandlebarsTemplates } from './helpers/templates.mjs';
import { BASICFANTASYRPG } from './helpers/config.mjs';
import { registerSettings } from './settings/settings.mjs';

/* -------------------------------------------- */
/* Init Hook */
Expand Down Expand Up @@ -110,6 +111,19 @@ Handlebars.registerPartial('iconRanged', `<i class="fa-solid fa-crosshairs fa-2x
/* Ready Hook & Others */
/* -------------------------------------------- */

/**
* Since feature #69 plans to have customisable saving throw names
* with localised default values, Register settings cannot be
* called any earlier in the Foundry load process than i18Init.
* This event is early in the process, just after Init, but we
* need to be careful that any initialisation which requires a setting
* can either handle the settings not being available, or is done after
* this event.
*/
Hooks.once('i18nInit', () => {
registerSettings()
})

Hooks.once('ready', async function() {
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
Hooks.on('hotbarDrop', (bar, data, slot) => createItemMacro(data, slot));
Expand Down
17 changes: 17 additions & 0 deletions module/helpers/settings.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// not the greatest approach, but
export function objectsShallowEqual (obj1, obj2) {
const entries1 = Object.entries(obj1)
const entries2 = Object.entries(obj2)

if (entries1.length !== entries2.length) {
return false
}

for (let [key, value] of entries1) {
if (obj2[key] !== value) {
return false
}
}

return true
}
99 changes: 99 additions & 0 deletions module/settings/saves-settings.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { objectsShallowEqual } from '../helpers/settings.mjs'
import { SYSTEM_ID, SETTINGS } from './settings.mjs'

// Helper array of IDs we'll use in a few places
const saves = ['death', 'wands', 'paralysis', 'breath', 'spells']

export function registerSavesSettings () {
// The settings menu
game.settings.registerMenu(SYSTEM_ID, SETTINGS.SAVES_MENU, {
name: 'BASICFANTASYRPG.Settings.SavesMenu.name',
label: 'BASICFANTASYRPG.Settings.SavesMenu.label',
hint: 'BASICFANTASYRPG.Settings.SavesMenu.hint',
icon: 'fas fa-cog',
type: SavesSettings,
restricted: true, // GM-only
})

// the settings object
game.settings.register(SYSTEM_ID, SETTINGS.SAVES_SETTINGS, {
scope: 'world',
config: false,
type: Object,
default: SavesSettings.defaultSaves,
})
}

class SavesSettings extends FormApplication {
static #defaultSaves = null
static get defaultSaves () {
if (!SavesSettings.#defaultSaves) {
SavesSettings.#defaultSaves = {}
saves.forEach(s => {
SavesSettings.#defaultSaves[s] = game.i18n.localize(`BASICFANTASYRPG.Save${s.capitalize()}`)
})
}
return SavesSettings.#defaultSaves
}

static get defaultOptions () {
return foundry.utils.mergeObject(super.defaultOptions, {
popOut: true,
width: 400,
template: `systems/${SYSTEM_ID}/templates/settings/string-array-settings.hbs`,
id: SETTINGS.SAVES_MENU,
title: 'BASICFANTASYRPG.Settings.SavesMenu.name',
})
}

getData () {
const initialValues = game.settings.get(SYSTEM_ID, SETTINGS.SAVES_SETTINGS)
// repack the current saves names into id, label and values for the form
const data = {}
saves.forEach((v, i) => {
data[i] = {
id: v,
label: SavesSettings.defaultSaves[v],
value: initialValues[v],
required: true,
}
})
return data
}

_updateObject (event, formData) {
const data = foundry.utils.expandObject(formData)
const current = game.settings.get(SYSTEM_ID, SETTINGS.SAVES_SETTINGS)

for (let [k, v] of Object.entries(data)) {
// trim trailing and leading whitespace then strip out all HTML tags
// Has an unfortunate effect of deleting the entire string if someone types in '<Spells>'
// if we really want to strip HTML, then a library is the best bet.
// data[k] = v?.trim()?.replace(/<\/?[^>]+(>|$)/g, '')

// just do whitespace for now
data[k] = v.trim()
}

if (!objectsShallowEqual(data, current)) {
game.settings.set(SYSTEM_ID, SETTINGS.SAVES_SETTINGS, data)
SettingsConfig.reloadConfirm({ world: true })
}
}

activateListeners (html) {
super.activateListeners(html)
html.on('click', '[data-action=reset]', this._handleResetButtonClicked)
}

async _handleResetButtonClicked (event) {
console.log('BFRPG | Reset save names to default values')
saves.forEach(id => {
const element = $(event.delegateTarget).find(`[name=${id}]`)
if (element && element.length > 0) {
element[0].value = game.i18n.localize(`BASICFANTASYRPG.Save${id.capitalize()}`)
}
})
ui.notifications.notify(game.i18n.localize('SETTINGS.ResetInfo'))
}
}
42 changes: 42 additions & 0 deletions module/settings/settings.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { BASICFANTASYRPG } from '../helpers/config.mjs'
import { registerSavesSettings } from './saves-settings.mjs'

/**
* pseudo-enum to make setting ID references less error prone
*/
export const SETTINGS = {
SAVES_MENU: 'savesMenu',
SAVES_SETTINGS: 'savesSettings',
}

// Use this just for settings for now. Refactoring the whole system is too big a job!
export const SYSTEM_ID = 'basicfantasyrpg'

/**
* Array of setting IDs for the settings that should be hidden from
* non-GM users. We iterate the array in the renderSettingsConfig
* hook and remove those settings from the DOM when required.
* This workaround is necessary because in Foundry v12, a restricted
* flag to show a setting to GM users is only supported for a setting
* menu, not a setting itself.
*/
const GM_ONLY_SETTINGS = []

Hooks.on('renderSettingsConfig', (app, [html], context) => {
if (game.user.isGM) return

GM_ONLY_SETTINGS.forEach(id => {
html.querySelector(`.form-group[data-setting-id="${SYSTEM_ID}.${id}"]`)?.remove()
})
})

export function registerSettings () {
/**
* register settings menus
*/
registerSavesSettings()

/**
* register top-level settings
*/
}
4 changes: 3 additions & 1 deletion module/sheets/actor-sheet.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {successChatMessage} from '../helpers/chat.mjs';
import {onManageActiveEffect, prepareActiveEffectCategories} from '../helpers/effects.mjs';
import { SYSTEM_ID, SETTINGS } from '../settings/settings.mjs';

/**
* Extend the basic ActorSheet with some very simple modifications
Expand Down Expand Up @@ -84,8 +85,9 @@ export class BasicFantasyRPGActorSheet extends ActorSheet {
*/
_prepareActorData(context) {
// Handle saves.
const savesNames = game.settings.get(SYSTEM_ID, SETTINGS.SAVES_SETTINGS)
for (let [k, v] of Object.entries(context.data.saves)) {
v.label = game.i18n.localize(CONFIG.BASICFANTASYRPG.saves[k]) ?? k;
v.label = savesNames[k] ?? k
}
}

Expand Down
18 changes: 18 additions & 0 deletions styles/basicfantasyrpg.css
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,24 @@
width: 0;
}

/* Styles used in the settings forms */
.form-row {
display: flex;
align-items: center;
justify-content: center;
margin: 5px;

.label {
vertical-align: middle;
}
}

.button-container {
display: flex;
gap: 10px;
margin-top: 5px;
}

/* Styles limited to basicfantasyrpg sheets */

.basicfantasyrpg.sheet.actor {
Expand Down
25 changes: 25 additions & 0 deletions templates/settings/string-array-settings.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{{!-- fa
--}}

{{#*inline "stringFieldPartial"}}
<div class="form-row flexrow">
<label for="{{id}}">{{label}}:</label>
<input name="{{id}}" type="text" {{#if required}}required="true"{{/if}} value="{{value}}" />
</div>
{{/inline}}

{{log this}}
<form class="form-group flexcol">
{{#each this}}
{{> stringFieldPartial}}
{{/each}}

<footer class="sheet-footer flexrow button-container">
<button class="reset-all" type="button" name="reset" data-action="reset">
<i class="fa-solid fa-undo"></i> {{localize "SETTINGS.Reset"}}
</button>
<button type="submit" name="submit">
<i class="fa-solid fa-save"></i> {{localize "SETTINGS.Save"}}
</button>
</footer>
</form>