Skip to content

Commit

Permalink
Merge pull request #15 from EddieDover/only-online
Browse files Browse the repository at this point in the history
Adds ability to show any characters as well as only logged-in characters.
  • Loading branch information
EddieDover authored Oct 17, 2023
2 parents b8fa34e + a6cf3fe commit aab0bd3
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 57 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 1.4.0 (2023-10-17)

##### New Features

* added ability to show either online characters or a curated list of any non-npc ([5e209a14]('https://github.com/EddieDover/theater-of-the-mind'/commit/5e209a140e2919710f7e1119dbfc3a0d7f82631e))

## 1.3.0 (2023-10-15)

##### New Features
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@
The original intent of this module was to provide a 'party view' feature that I found missing when I first started using [FoundryVTT](https://www.foundryvtt.com) instead of [Fantasy Grounds](https://www.fantasygrounds.com). It will be updated with features that I personally want or need, or are requested of me by some poor soul.

## Features
- Adds a 'Party Sheet' similar to that provided by Fantasy Grounds. Just click the Party Sheet icon on the Tokens Controls sub-menu.
- Adds a 'Party Sheet' similar to that provided by Fantasy Grounds. Just click the Party Sheet icon on the Tokens Controls sub-menu.

- The Party Sheet can be configured via the options to either display only currently connected players, or all non-npc characters. When using the all-characters display, there is a separate dialog to allow hiding of any characters the DM doesn't need to worry about.

- Sound effects support if you have Syrinscape ([fvtt-syrin-control](https://github.com/frondeus/fvtt-syrin-control)) and Midi QOL ([midi-qol](https://gitlab.com/tposney/midi-qol/)) installed.

![Party Sheet Icon](images/psi.png)

![Preview of Plugin Party Sheet](images/preview1.png)
Only Connected Players:
![Preview of Plugin Party Sheet - Only Connected Players](images/preview1.png)

Specific Characters
![Preview of Plugin Party Sheet - Specific Actors](images/preview2.png)

Binary file added images/preview2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "theater-of-the-mind",
"version": "1.3.0",
"version": "1.4.0",
"description": "A FoundryVTT module for DMs",
"main": "index.js",
"author": {
Expand Down
4 changes: 4 additions & 0 deletions src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"classes": "Classes"
},
"settings": {
"enable-only-online": {
"name": "Show only online characters",
"hint": "Only show characters assigned to logged in players. Turning this off will show all non-NPC characters but allow you to 'hide' the ones you don't care about."
},
"enable-sounds": {
"name": "Enable Syrinscape Sounds",
"hint": "Sound support requires the Syrinscape Integration (fvtt-synrin-control) and Midi QOL (midi-qol) modules."
Expand Down
2 changes: 1 addition & 1 deletion src/module.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"url": "https://github.com/eddiedover/"
}
],
"version": "1.3.0",
"version": "1.4.0",
"compatibility": {
"minimum": "11",
"verified": "11"
Expand Down
57 changes: 57 additions & 0 deletions src/module/app/hidden-characters-settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export class HiddenCharactersSettings extends FormApplication {
constructor(overrides) {
super();
this.overrides = overrides || {};
}

getData(options) {
this.characterList = game.actors.filter(actor => actor.type !== "npc").map(actor => actor.name);
const hiddenCharacters = game.settings.get("theater-of-the-mind", "hiddenCharacters");
const enableOnlyOnline = game.settings.get("theater-of-the-mind", "enableOnlyOnline");

return mergeObject(super.getData(options), {
characters: this.characterList,
hiddenCharacters,
enableOnlyOnline,
overrides: this.overrides
});
}

static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
id: "totm-hidden-characters-settings",
classes: ["form"],
title: "Configure Hidden Characters",
template: "modules/theater-of-the-mind/templates/hidden-characters.html",
width: 'auto',
height: 300,
});
}

saveHiddenCharacters(event) {
const hiddenCharacters = [];
for (const character of this.characterList) {
const checkbox = document.getElementById(`hidden-character-${character}`);
if (checkbox.checked) {
hiddenCharacters.push(character);
}
}
game.settings.set("theater-of-the-mind", "hiddenCharacters", hiddenCharacters);
const closefunc = this.overrides?.onexit;
if (closefunc) {
closefunc();
}
super.close();
}

resetEffects() {
// this.effects = game.settings.settings.get('monks-little-details.additional-effects').default;
this.refresh();
}

activateListeners(html) {
super.activateListeners(html);
$('button[name="submit"]', html).click(this.saveHiddenCharacters.bind(this));
$('button[name="reset"]', html).click(this.resetEffects.bind(this));
}
}
49 changes: 49 additions & 0 deletions src/module/app/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { HiddenCharactersSettings } from "./hidden-characters-settings";

export const registerSettings = () => {
game.settings.register('theater-of-the-mind', 'hiddenCharacters', {
"scope":"world",
"config": false,
"default": [],
"type": Array
});

game.settings.register("theater-of-the-mind", "enableOnlyOnline", {
"name": "theater-of-the-mind.settings.enable-only-online.name",
"hint": "theater-of-the-mind.settings.enable-only-online.hint",
"scope": "world",
"config": true,
"default": true,
"type": Boolean
});

game.settings.registerMenu("theater-of-the-mind", "configureHiddenCharacters", {
"name": "",
"label": "Edit Displayed Characters",
"hint": "Edit which characters are displayed if not showing online characters.",
"icon": "fas fa-user",
"restricted": true,
"type": HiddenCharactersSettings
});

game.settings.register("theater-of-the-mind", "enableDarkMode", {
"name": "theater-of-the-mind.settings.enable-dark-mode.name",
"hint": "theater-of-the-mind.settings.enable-dark-mode.hint",
"scope": "world",
"config": true,
"default": false,
"type": Boolean,
"onChange": () => {
// Hooks.call("renderSceneControls");
},
});

game.settings.register("theater-of-the-mind", "enableSounds", {
"name": "theater-of-the-mind.settings.enable-sounds.name",
"hint": "theater-of-the-mind.settings.enable-sounds.hint",
"scope": "world",
"config": true,
"default": false,
"type": Boolean
});
}
118 changes: 65 additions & 53 deletions src/module/theater-of-the-mind.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { THEATER_SOUNDS } from "./sounds.js";
import { registerSettings } from "./app/settings.js";
import { HiddenCharactersSettings } from "./app/hidden-characters-settings.js";

let isSyrinscapeInstalled = false;
let isMidiQoLInstalled = false;
Expand All @@ -7,12 +9,21 @@ function log(...message) {
console.log("Theater of the Mind | ", message);
}

Handlebars.registerHelper('hccontains', function(needle, haystack, options) {
needle = Handlebars.escapeExpression(needle);
haystack = game.settings.get('theater-of-the-mind', 'hiddenCharacters') ?? [];
return (haystack.indexOf(needle) > -1) ? options.fn(this) : options.inverse(this);
});

const getPlayerData = () => {

const showOnlyOnlineUsers = game.settings.get("theater-of-the-mind", "enableOnlyOnline");
const actorList = showOnlyOnlineUsers ? game.users.filter( (user) => user.active && user.character).map(user => user.character) : game.actors.filter(actor => actor.type !== "npc");

try {
return game.users
.map((user) => {
if (user.active && user.character) {
const userChar = user.character;
return actorList
.map((character) => {
const userChar = character;
const userSys = userChar.system;
const stats = userSys.abilities;
const ac = userSys.attributes.ac.value;
Expand Down Expand Up @@ -70,7 +81,7 @@ const getPlayerData = () => {
ac,
passives,
};
}

})
.filter((player) => player);
} catch (ex) {
Expand All @@ -80,61 +91,67 @@ const getPlayerData = () => {
};

const convertPlayerDataToTable = () => {
const currentlyHiddenCharacters = game.settings.get('theater-of-the-mind', 'hiddenCharacters') ?? [];
try {
const players = getPlayerData();

if (players.length === 0) {
return "No players connected";
}

let table = `<table id='totm-ps-table'>`;

const localize = {
name: game.i18n.localize("theater-of-the-mind.party-sheet.name"),
race: game.i18n.localize("theater-of-the-mind.party-sheet.race"),
senses: game.i18n.localize("theater-of-the-mind.party-sheet.senses"),
classes: game.i18n.localize("theater-of-the-mind.party-sheet.classes"),
};

table += `<tr><th class="namerace"><div>${localize.name}</div><div>${localize.race}</div></th>`;
const showOnlyOnlineUsers = game.settings.get("theater-of-the-mind", "enableOnlyOnline");
const optionsButton = `<button class="totm-options"><i class="fas fa-cog"></i>Configure Shown Characters</button>`;
let table = `<table id='totm-ps-table'>`;
let thead = `<tr><th class="namerace"><div>${localize.name}</div><div>${localize.race}</div></th>`;
for (const stat in players[0].stats) {
table += `<th class="p-1">${stat.toUpperCase()}</th>`;
thead += `<th class="p-1">${stat.toUpperCase()}</th>`;
}
table += `<th>AC</th><th>Inv</th><th>${localize.senses}</th></tr>`;
thead += `<th>AC</th><th>Inv</th><th>${localize.senses}</th>`;
thead += `</tr>`;

table += `<tr><th>${localize.classes}</th>`;
thead += `<tr><th>${localize.classes}</th>`;
for (let i = 0; i < 6; i++) {
table += `<th></th>`;
thead += `<th></th>`;
}
table += ` <th class="p-1">Per</th><th class="p-1">Ins</th><th></th></tr>`;
thead += ` <th class="p-1">Per</th><th class="p-1">Ins</th><th></th></tr>`;

players.forEach((player) => {
table += `<tr><td rowspan="2"><div class="totm-ps-name-bar">${player.img}`;
table += `<div class='totm-ps-name-bar-namerace'>
table += thead;
let tbody = "";
players.filter(player => !currentlyHiddenCharacters.includes(player.name)).forEach((player) => {
let pbody = `<tr><td rowspan="2"><div class="totm-ps-name-bar">${player.img}`;
pbody += `<div class='totm-ps-name-bar-namerace'>
<div class='entry'>${player.name}</div>
<div class='entry'>${player.race}</div>
<div class='fullentry'>${player.classNames}</div>
</div>`;
table += `</div></td>`;
pbody += `</div></td>`;
for (const stat in player.stats) {
table += `<td>${player.stats[stat].value}</td>`;
pbody += `<td>${player.stats[stat].value}</td>`;
}
table += `<td>${player.ac}</td>`;
table += `<td>${player.passives.inv}</td>`;
table += `<td rowspan="2" style="white-space: nowrap;">${player.senses}</td>`;
table += `</tr>`;
pbody += `<td>${player.ac}</td>`;
pbody += `<td>${player.passives.inv}</td>`;
pbody += `<td rowspan="2" style="white-space: nowrap;">${player.senses}</td>`;
pbody += `</tr>`;

table += `<tr class="totm-ps-finrow">`;
pbody += `<tr class="totm-ps-finrow">`;
for (const stat in player.stats) {
table += `<td>${player.stats[stat].mod >= 0 ? "+" : ""}${
pbody += `<td>${player.stats[stat].mod >= 0 ? "+" : ""}${
player.stats[stat].mod
}</td>`;
}
table += `<td>${player.passives.prc}</td>`;
table += `<td>${player.passives.ins}</td>`;
table += `</tr>`;
pbody += `<td>${player.passives.prc}</td>`;
pbody += `<td>${player.passives.ins}</td>`;
pbody += `</tr>`;
tbody += pbody;
});
table += `</table>`;
return table;
table += tbody + `</table>`;
return showOnlyOnlineUsers ? table : optionsButton + table;
} catch (ex) {
log(ex);
return ex.message;
Expand All @@ -143,7 +160,7 @@ const convertPlayerDataToTable = () => {

const PartySheetDialog = new Dialog({
title: "Party Sheet",
content: convertPlayerDataToTable(),
content: null, //convertPlayerDataToTable(),
classNames: ["totm-ps-dialog"],
buttons: {
close: {
Expand All @@ -153,6 +170,22 @@ const PartySheetDialog = new Dialog({
},
},
render: (html) => {
// html.on('action', 'element', (event) => {
// });
html.on('click', 'button.totm-options', (event) => {
event.preventDefault();
const overrides = {
onexit: () => {
PartySheetDialog.close();
setTimeout( () => {
PartySheetDialog.data.content = convertPlayerDataToTable();
PartySheetDialog.render(true);
}, 350);
}
}
const hcs = new HiddenCharactersSettings(overrides);
hcs.render(true);
});
if (game.settings.get("theater-of-the-mind", "enableDarkMode")) {
var parentElement = html[0].parentElement;
parentElement.id = "totm-dialog-darkmode";
Expand Down Expand Up @@ -207,28 +240,7 @@ async function playSound(weapon, crit, hitmiss, override = null) {
Hooks.on("init", () => {
log("Initializing");

game.settings.register("theater-of-the-mind", "enableDarkMode", {
"name": "theater-of-the-mind.settings.enable-dark-mode.name",
"hint": "theater-of-the-mind.settings.enable-dark-mode.hint",
"scope": "world",
"config": true,
"default": false,
"type": Boolean,
"onChange": () => {
// Hooks.call("renderSceneControls");
},
});

game.settings.register("theater-of-the-mind", "enableSounds", {
"name": "theater-of-the-mind.settings.enable-sounds.name",
"hint": "theater-of-the-mind.settings.enable-sounds.hint",
"scope": "world",
"config": true,
"default": false,
"type": Boolean,
"onChange": () => {
},
});
registerSettings();
});

//
Expand Down
26 changes: 26 additions & 0 deletions src/styles/theater-of-the-mind.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@
}
}

.totm-hc-form {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;

.totm-hc-section {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
width: 100%;
height: 100%;
overflow-y: auto;

.totm-hc-item {
display: flex;
align-items: center;
width: fit-content;
border-radius: 5px;
padding: 2px;
margin: 2px;
border: 1px solid rgba(1,1,1,0.25);
}
}
}

#totm-ps-table {
text-align:center;

Expand Down
Loading

0 comments on commit aab0bd3

Please sign in to comment.