The Game class is the upper-most level of Foundry's data architecture and is responsible for initializing the application. It's usually referenced by the singleton game
instance.
Official documentation
The Game
class relies heavily on read-only properties which are not documented by the official JSDoc, you can see the class in more detail in your local install at foundryInstallPath\resources\app\client\game.js
.
Legend
Game.create // `.` indicates static method or property
Game#system // `#` indicates instance method or property
game.system // Lowercase game also indicates instance method or property
When a client connects to a Foundry server, after all relevant system and module code is imported, the Game class is initialized in the globally available instance game
. This process instantiates everything else available to Foundry developers, such as loading information from the databases and initializing the canvas.
More information on how data is processed is available in the From Load to Render guide.
The following lays out how the game
object acquires its properties.
The basics of how the game
object is instantiated and its properties are filled in are as follows
Game.create
is called after all code is importedGame.connect
establishes the underlying socket connection with the serverGame.getData
fetches the live data from the database.new Game
constructs the singleton instance which is publicly stashed as game
Game.initialize
is called, which progressively builds out the game
instance and calls a series of hooks.Note that the globally-available CONST
and CONFIG
objects are simply initialized as part of loading the relevant javascript module files (common\constants.mjs
and client\config.js
respectively). However, one should be hesitant to modify them before the init
hook, to avoid any issues with module load order.
Developers of all kinds almost always need to work with one or more of the following hooks. This section provides information on what is available at each stage. Each of these hooks is only called once, so there's no functional difference between Hooks.once("init", callback)
and Hooks.on("init", callback)
.
The first Hook
available to a Foundry developer. The following are typical actions performed in this hook.
CONFIG
object is modifiedThis is called before any of the other processes in Game#initialize
; as such, the only properties available are the ones added in the constructor. These are constructed as read-only properties and so do not currently show up in Foundry's documentation.
false
)The following properties are technically available but are not yet properly initialized with their data
canvas
)Technically called at the end of Localization#initialize
, this hook is called after the the following methods:
Game#registerSettings
— Registers various core settingsgame.i18n
is fully set up, so game.i18n.localize
and other similar methods are availableThis hook is less commonly used. It's called after the following are established
Game#registerTours
— initializes game.tours
Game#activateListeners
— initializes game.tooltip
, game.video
game.permissions
— loaded from world settingGame#initializePacks
— initalizes game.packs
Game#initializeDocuments
— initializes game.collections
and instantiates world collections like game.actors
for all primary document types.The main point of the setup
hook is it's after document data is loaded but before the canvas is initalized.
The final step of Foundry's initialization process, this hook is called after all of these other methods are called.
Game#initializeRTC
— initializes game.webrtc
Game#initializeMouse
— initializes game.mouse
Game#initializeGamepads
— initializes game.gamepad
Game#initializeKeyboard
— initializes game.keyboard
and game.keybindings
Game#initializeCanvas
— initializes game.canvas
Game#initializeUI
— Renders UI elements like the sidebarDocumentSheetConfig.initializeSheets
— processes registered sheet classesGame#activateSocketListeners
— Enables various pieces of interactivity and data-sharing that occur over socketsDocumentIndex#index
— initializes game.documentIndex
NewUserExperience#initialize
— initializes game.nue
game.ready = true
The game
object is where the in-world documents are stashed. Each of the primary document types has a WorldCollection, which is sourced from [Class].metadata.collection
, e.g. Actor.metadata.collection
returns "actors"
, and so game.actors
is a collection of actors.
Unlike Compendium Collections, all documents in world collections are stored in-memory on the client, so access can be performed synchronously. Updates are still asynchronous to allow synchronization between clients.
It's worth noting that game.collections
is itself a Collection of these world collections. However, it's basically never necessary to use that.
WorldCollection extends DocumentCollection, which provides the bulk of methods for interacting with the stored documents.
There's two main ways to get documents from a world collection, both of which run synchronously.
WorldCollection#get
, which fetches by document id
.WorldCollection#getName
, which fetches by the document's name
property.The global methods, fromUuid
and fromUuidSync
both work with documents in world collections; fromUuidSync
in particular is quite useful because it remains synchrous and has no problem fetching embedded documents.
Invalid Documents: Sometimes, a data model update will mark a document as invalid if it fails to cast data (a common example is Number('30 ft')
returns NaN
). In this cases, there's a few ways to access invalid documents
DocumentCollection#getInvalid(id)
is the primary canonical way.DocumentCollection#invalidDocumentIds
is a Set, which can be iterated through with a for
loop or accessed with a simple invalidDocumentIds.first()
.The upstream Collection class provides several ways of accessing the values stored inside: for
and forEach
iteration, map
, reduce
, filter
, and some
.
The DocumentCollection
class adds the search
function, which will look through all searchable fields based on the data model's metadata.
A third option is to use WorldCollection#folders
, but this is generally inferior to just using filter(d => d.folder.id === 'targetFolderID')
or some other construction with those basic methods inherited from Collection.
Here are some specific tips and tricks with the game
instance
Dumping functions into the global scope with globalThis.myFunc = myFunc
can be risky if there's a name collission with another package. One way to deal with this is to add them to your package's instance, at either game.system
or game.modules.get('my-module')
. By convention, many group them under the api
function myFunction() {}
// Doing it here requires assigning during
Hooks.once("init", () => {
// system example
game.system.api = {
myFunction,
}
// module example
game.modules.get('my-module').api = {
myFunction,
}
})
// a macro could then call the function with
game.system.api.myFunction()
game.modules.get('my-module').api.myFunction()
One important caveat to this method is they won't reliably be available until AFTER the init
hook, so this isn't as helpful for anything you expect end users to call either before hooks fire (classes to extend, for examples) or during the init
hook. In these cases, just scoping out a globally available object can be preferable, such as using our package name.
Stub
This section is a stub, you can help by contributing to it.