Skip to content

Commit

Permalink
Initial Engagement Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Haxxer committed Aug 30, 2024
1 parent 0c66b85 commit f78ef3c
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 4 deletions.
6 changes: 6 additions & 0 deletions docs/api/home.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ In Foundry v10, if you have [Advanced Macros](https://foundryvtt.com/packages/ad

`.macro("New Macro", true, "fire-bolt", token.id)`

### Engagement

`.engagement()` or `.engagement("sounds/drums.wav")` or `.engagement("sounds/drums.wav", 5000)`

Causes the sequence to wait until every user has their browser focused on Foundry, with an optional sound to play for users whose browsers are not focused on Foundry, and an optional max wait time (in milliseconds).

### Wait

`.wait(1000)` or `.wait(500, 1000)`
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Changelog

## Version 3.2.18

- Added `.engagement()` section and `Sequencer.Helpers.waitForEngagement` - this allows you to curate your Sequences only to play when all users are focused on Foundry

## Version 3.2.17
- Fixed some issues with the vision masking shader relating to the batch batching optimizations (Thanks Codas on github!)
- Fixed above UI layer not working with recent render batching optimizations (Thanks Codas on github!)
Expand Down
9 changes: 9 additions & 0 deletions docs/helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Random Array Element](#random-array-element)
- [Random Object Element](#random-object-element)
- [Make Array Unique](#make-array-unique)
- [Wait For Engagement](#wait-for-engagement)

### Global Reference

Expand Down Expand Up @@ -98,3 +99,11 @@ Sequencer.Helpers.make_array_unique(array);
```

Turns an array containing multiples of the same string, objects, etc, and removes duplications, and returns a fresh array.

### Wait For Engagement

```js
Sequencer.Helpers.waitForEngagement(inSrc, inMaxWaitTime);
```

Returns a promise that resolves when every user is focused on Foundry, with an optional sound to play for users who do not have their browsers focused on Foundry, and an optional max wait time.
1 change: 1 addition & 0 deletions src/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ function initializeModule() {
random_array_element: lib.random_array_element,
random_object_element: lib.random_object_element,
make_array_unique: lib.make_array_unique,
waitForEngagement: SequencerFoundryReplicator._waitForEngagement.bind(SequencerFoundryReplicator)
},
};

Expand Down
48 changes: 48 additions & 0 deletions src/modules/sequencer-foundry-replicator.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import * as lib from "../lib/lib.js";
import * as canvaslib from "../lib/canvas-lib.js";
import { sequencerSocket, SOCKET_HANDLERS } from "../sockets.js";
import SequencerSoundManager from "./sequencer-sound-manager.js";

let lockedView = false;

export default class SequencerFoundryReplicator {

static #engagedUsers = new Map();

static registerHooks() {
Hooks.on("canvasPan", () => {
if (!lockedView) return;
Expand All @@ -14,8 +18,52 @@ export default class SequencerFoundryReplicator {
canvas.controls._onCanvasPan();
canvas.hud.align();
});
Hooks.on("renderPlayerList", () => {
game.users.forEach(user => {
if(!user.active){
return this.#engagedUsers.delete(user.id);
}
this.#engagedUsers.set(user.id, true)
});
});
$(window).on("focus", () => {
sequencerSocket.executeForEveryone(SOCKET_HANDLERS.RELAY_ENGAGEMENT, game.user.id, true);
});
$(window).on("blur", () => {
sequencerSocket.executeForEveryone(SOCKET_HANDLERS.RELAY_ENGAGEMENT, game.user.id, false);
});
sequencerSocket.executeForEveryone(SOCKET_HANDLERS.RELAY_ENGAGEMENT, game.user.id, document.hasFocus());
game.users.forEach(user => {
if(!user.active){
return this.#engagedUsers.delete(user.id);
}
this.#engagedUsers.set(user.id, true)
});
}

static _relayEngagement(userId, engaged){
this.#engagedUsers.set(userId, engaged);
}

static _pingEngagement(soundSrc = false) {
if(this.#engagedUsers.get(game.user.id) || !soundSrc) return;
SequencerSoundManager.AudioHelper.play({ src: soundSrc, volume: 1.0, loop: false }, false);
}

static async _waitForEngagement(inSrc = null, maxWaitTime = null) {
await sequencerSocket.executeForOthers(SOCKET_HANDLERS.PING_ENGAGEMENT, inSrc);
const startTime = Number(Date.now());
return new Promise(async (resolve) => {
while (true) {
const currentTime = Number(Date.now());
if(maxWaitTime && (currentTime - startTime) >= maxWaitTime) break;
await lib.wait(10);
if (Array.from(this.#engagedUsers.values()).every(Boolean)) break;
}
resolve();
});
}

static _validateObject(inObject, sceneId) {
if (lib.is_UUID(inObject) || !canvaslib.is_object_canvas_data(inObject)) {
inObject = lib.get_object_from_scene(inObject, sceneId);
Expand Down
25 changes: 21 additions & 4 deletions src/modules/sequencer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SequenceManager from "./sequence-manager.js";
import { get, writable } from "svelte/store";
import CONSTANTS from "../constants.js";
import WaitSection from "../sections/wait.js";
import EngagementSection from "../sections/engagement.js";

export default class Sequence {
constructor(
Expand Down Expand Up @@ -191,7 +192,7 @@ export default class Sequence {
* Creates an effect section. Until you call .then(), .effect(), .sound(), or .wait(), you'll be working on the Effect section.
*
* @param {string} [inFile] inFile
* @returns {Section}
* @returns {EffectSection}
*/
effect(inFile = "") {
const effect = lib.section_proxy_wrap(new EffectSection(this, inFile));
Expand All @@ -203,7 +204,7 @@ export default class Sequence {
* Creates a sound section. Until you call .then(), .effect(), .sound(), or .wait(), you'll be working on the Sound section.
*
* @param {string} [inFile] inFile
* @returns {Section}
* @returns {SoundSection}
*/
sound(inFile = "") {
const sound = lib.section_proxy_wrap(new SoundSection(this, inFile));
Expand Down Expand Up @@ -231,7 +232,7 @@ export default class Sequence {
* @param {Object|String|Boolean} [inTarget=false] inTarget
* @param {String|Boolean} [inText=false] inText
* @param {Object} [inTextOptions={}] inTextOptions
* @returns {AnimationSection}
* @returns {ScrollingTextSection}
*/
scrollingText(inTarget = false, inText = false, inTextOptions = {}) {
const scrolling = lib.section_proxy_wrap(
Expand All @@ -247,7 +248,7 @@ export default class Sequence {
* @param {Object|String|Boolean} [inTarget=false] inTarget
* @param {Boolean|Number} inDuration
* @param {Boolean|Number} inSpeed
* @returns {AnimationSection}
* @returns {CanvasPanSection}
*/
canvasPan(inTarget = false, inDuration = null, inSpeed = null) {
const panning = lib.section_proxy_wrap(
Expand All @@ -257,6 +258,22 @@ export default class Sequence {
return panning;
}

/**
* A section that will wait until every user has focused on Foundry, with an optional sound to play to users
* whose browsers are not focused on Foundry, and an optional max wait time (in milliseconds)
*
* @param {Boolean|String} [inSrc=false] inTarget
* @param {Boolean|Number} [inMaxWait=false] inMaxWait
* @returns {AnimationSection}
*/
engagement(inSrc = null, inMaxWait = null) {
const engagement = lib.section_proxy_wrap(
new EngagementSection(this, inSrc, inMaxWait)
);
this.sections.push(engagement);
return engagement;
}

/**
* Causes the sequence to wait after the last section for as many milliseconds as you pass to this method. If given
* a second number, a random wait time between the two given numbers will be generated.
Expand Down
33 changes: 33 additions & 0 deletions src/sections/engagement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as lib from "../lib/lib.js";
import Section from "./section.js";
import SequencerFoundryReplicator from "../modules/sequencer-foundry-replicator.js";
import SequencerSoundManager from "../modules/sequencer-sound-manager.js";

export default class EngagementSection extends Section {
constructor(inSequence, inSrc = null, inMaxWaitTime = null) {
super(inSequence);
this._waitUntilFinished = true;
this._sound = inSrc;
this._maxWaitTime = inMaxWaitTime;
}

static niceName = "Engagement";

/**
* @returns {Promise<void>}
*/
async run() {
lib.debug("Running engagement");
let soundFile = await SequencerSoundManager.AudioHelper.preloadSound(this._sound);
if(!soundFile) return;
await SequencerFoundryReplicator._waitForEngagement(this._sound, this._maxWaitTime);
}

/**
* @returns {Promise}
* @private
*/
async _execute() {
await this.run();
}
}
8 changes: 8 additions & 0 deletions src/sockets.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const SOCKET_HANDLERS = {
CREATE_LIGHT_SOURCE: "createLightSource",
PAN_CANVAS: "panCanvas",
RUN_SEQUENCE_LOCALLY: "runSequenceLocally",
RELAY_ENGAGEMENT: "relayEngagement",
PING_ENGAGEMENT: "pingEngagement"
};

export let sequencerSocket;
Expand Down Expand Up @@ -86,6 +88,12 @@ export function registerSocket() {
lib.debug("Playing remote Sequence");
new Sequence().fromJSON(data).play();
});
sequencerSocket.register(SOCKET_HANDLERS.RELAY_ENGAGEMENT, (...args) => {
SequencerFoundryReplicator._relayEngagement(...args);
});
sequencerSocket.register(SOCKET_HANDLERS.PING_ENGAGEMENT, (...args) => {
SequencerFoundryReplicator._pingEngagement(...args);
});
}

async function updateDocument(documentUuid, updates, animate) {
Expand Down

0 comments on commit f78ef3c

Please sign in to comment.