Files

142 lines
4.0 KiB
JavaScript
Raw Permalink Normal View History

2025-01-04 00:34:03 +01:00
/**
* The singleton collection of User documents which exist within the active World.
* This Collection is accessible within the Game object as game.users.
* @extends {WorldCollection}
*
* @see {@link User} The User document
*/
class Users extends WorldCollection {
constructor(...args) {
super(...args);
/**
* The User document of the currently connected user
* @type {User|null}
*/
this.current = this.current || null;
}
/* -------------------------------------------- */
/**
* Initialize the Map object and all its contained documents
* @private
* @override
*/
_initialize() {
super._initialize();
// Flag the current user
this.current = this.get(game.data.userId) || null;
if ( this.current ) this.current.active = true;
// Set initial user activity state
for ( let activeId of game.data.activeUsers || [] ) {
this.get(activeId).active = true;
}
}
/* -------------------------------------------- */
/** @override */
static documentName = "User";
/* -------------------------------------------- */
/**
* Get the users with player roles
* @returns {User[]}
*/
get players() {
return this.filter(u => !u.isGM && u.hasRole("PLAYER"));
}
/* -------------------------------------------- */
/**
* Get one User who is an active Gamemaster (non-assistant if possible), or null if no active GM is available.
* This can be useful for workflows which occur on all clients, but where only one user should take action.
* @type {User|null}
*/
get activeGM() {
const activeGMs = game.users.filter(u => u.active && u.isGM);
activeGMs.sort((a, b) => (b.role - a.role) || a.id.compare(b.id)); // Alphanumeric sort IDs without using localeCompare
return activeGMs[0] || null;
}
/* -------------------------------------------- */
/* Socket Listeners and Handlers */
/* -------------------------------------------- */
static _activateSocketListeners(socket) {
socket.on("userActivity", this._handleUserActivity);
}
/* -------------------------------------------- */
/**
* Handle receipt of activity data from another User connected to the Game session
* @param {string} userId The User id who generated the activity data
* @param {ActivityData} activityData The object of activity data
* @private
*/
static _handleUserActivity(userId, activityData={}) {
const user = game.users.get(userId);
if ( !user || user.isSelf ) return;
// Update User active state
const active = "active" in activityData ? activityData.active : true;
if ( user.active !== active ) {
user.active = active;
game.users.render();
ui.nav.render();
Hooks.callAll("userConnected", user, active);
}
// Everything below here requires the game to be ready
if ( !game.ready ) return;
// Set viewed scene
const sceneChange = ("sceneId" in activityData) && (activityData.sceneId !== user.viewedScene);
if ( sceneChange ) {
user.viewedScene = activityData.sceneId;
ui.nav.render();
}
if ( "av" in activityData ) {
game.webrtc.settings.handleUserActivity(userId, activityData.av);
}
// Everything below requires an active canvas
if ( !canvas.ready ) return;
// User control deactivation
if ( (active === false) || (user.viewedScene !== canvas.id) ) {
canvas.controls.updateCursor(user, null);
canvas.controls.updateRuler(user, null);
user.updateTokenTargets([]);
return;
}
// Cursor position
if ( "cursor" in activityData ) {
canvas.controls.updateCursor(user, activityData.cursor);
}
// Was it a ping?
if ( "ping" in activityData ) {
canvas.controls.handlePing(user, activityData.cursor, activityData.ping);
}
// Ruler measurement
if ( "ruler" in activityData ) {
canvas.controls.updateRuler(user, activityData.ruler);
}
// Token targets
if ( "targets" in activityData ) {
user.updateTokenTargets(activityData.targets);
}
}
}