From c9f88d83886b9eec93605f367844eca376a80f71 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Wed, 30 Dec 2020 10:52:05 +1200 Subject: [PATCH 01/32] :zap: :bento: Update Electron used in desktop builds to v11.1.1 --- src/riotTags/main-menu/export-panel.tag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/riotTags/main-menu/export-panel.tag b/src/riotTags/main-menu/export-panel.tag index b7e1f26dc..72b1e6488 100644 --- a/src/riotTags/main-menu/export-panel.tag +++ b/src/riotTags/main-menu/export-panel.tag @@ -130,7 +130,7 @@ export-panel out: buildDir, asar: true, overwrite: true, - electronVersion: '8.2.2', + electronVersion: '11.1.1', icon: path.join(exportDir, 'icon'), // generic data From 33dd4f5a3d0861e1a79680c21733dba9345319e1 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Wed, 30 Dec 2020 10:52:51 +1200 Subject: [PATCH 02/32] :bug: Remove the old main-menu tag --- src/riotTags/main-menu.tag | 645 ------------------------------------- 1 file changed, 645 deletions(-) delete mode 100644 src/riotTags/main-menu.tag diff --git a/src/riotTags/main-menu.tag b/src/riotTags/main-menu.tag deleted file mode 100644 index 030ddae49..000000000 --- a/src/riotTags/main-menu.tag +++ /dev/null @@ -1,645 +0,0 @@ -main-menu.flexcol - nav.nogrow.flexrow(if="{global.currentProject}") - ul#app.nav.tabs - li.it30#ctlogo(onclick="{ctClick}" title="{voc.ctIDE}") - svg.feather.nmr - use(xlink:href="data/icons.svg#menu") - context-menu#theCatMenu(menu="{catMenu}" ref="catMenu") - li.it30(onclick="{changeTab('patrons')}" title="{voc.patrons}" class="{active: tab === 'patrons'}") - svg.feather - use(xlink:href="data/icons.svg#heart") - li.it30.nbr(onclick="{saveProject}" title="{voc.save} (Control+S)" data-hotkey="Control+s") - svg.feather - use(xlink:href="data/icons.svg#save") - - ul#mainnav.nav.tabs - li.nbl.it30(onclick="{runProject}" class="{active: tab === 'debug'}" title="{voc.launch} {voc.launchHotkeys}" data-hotkey="F5") - svg.feather.rotateccw(show="{exportingProject}") - use(xlink:href="data/icons.svg#refresh-ccw") - svg.feather(hide="{exportingProject}") - use(xlink:href="data/icons.svg#play") - span(if="{tab !== 'debug'}") {voc.launch} - span(if="{tab === 'debug'}") {voc.restart} - li(onclick="{changeTab('project')}" class="{active: tab === 'project'}" data-hotkey="Control+1" title="Control+1") - svg.feather - use(xlink:href="data/icons.svg#sliders") - span {voc.project} - li(onclick="{changeTab('textures')}" class="{active: tab === 'textures'}" data-hotkey="Control+2" title="Control+2") - svg.feather - use(xlink:href="data/icons.svg#texture") - span {voc.texture} - li(onclick="{changeTab('ui')}" class="{active: tab === 'ui'}" data-hotkey="Control+3" title="Control+3") - svg.feather - use(xlink:href="data/icons.svg#ui") - span {voc.ui} - li(onclick="{changeTab('fx')}" class="{active: tab === 'fx'}" data-hotkey="Control+4" title="Control+4") - svg.feather - use(xlink:href="data/icons.svg#sparkles") - span {voc.fx} - li(onclick="{changeTab('sounds')}" class="{active: tab === 'sounds'}" data-hotkey="Control+5" title="Control+5") - svg.feather - use(xlink:href="data/icons.svg#headphones") - span {voc.sounds} - li(onclick="{changeTab('types')}" class="{active: tab === 'types'}" data-hotkey="Control+6" title="Control+6") - svg.feather - use(xlink:href="data/icons.svg#type") - span {voc.types} - li(onclick="{changeTab('rooms')}" class="{active: tab === 'rooms'}" data-hotkey="Control+7" title="Control+7") - svg.feather - use(xlink:href="data/icons.svg#room") - span {voc.rooms} - div.flexitem.relative(if="{global.currentProject}") - debugger-screen-embedded(if="{tab === 'debug'}" params="{debugParams}" data-hotkey-scope="play" ref="debugger") - project-settings(show="{tab === 'project'}" data-hotkey-scope="project") - textures-panel(show="{tab === 'textures'}" data-hotkey-scope="textures") - ui-panel(show="{tab === 'ui'}" data-hotkey-scope="ui") - fx-panel(show="{tab === 'fx'}" data-hotkey-scope="fx") - sounds-panel(show="{tab === 'sounds'}" data-hotkey-scope="sounds") - types-panel(show="{tab === 'types'}" data-hotkey-scope="types") - rooms-panel(show="{tab === 'rooms'}" data-hotkey-scope="rooms") - license-panel(if="{showLicense}") - patreon-screen(if="{tab === 'patrons'}" data-hotkey-scope="patrons") - export-panel(show="{showExporter}") - new-project-onboarding(if="{sessionStorage.showOnboarding && localStorage.showOnboarding !== 'off'}") - script. - const fs = require('fs-extra'), - path = require('path'); - const archiver = require('archiver'); - const glob = require('./data/node_requires/glob'); - - this.namespace = 'menu'; - this.mixin(window.riotVoc); - - this.tab = 'project'; - this.changeTab = tab => () => { - this.tab = tab; - window.hotkeys.cleanScope(); - window.hotkeys.push(tab); - window.signals.trigger('globalTabChanged', tab); - window.signals.trigger(`${tab}Focus`); - }; - this.changeTab(this.tab)(); - - const assetListener = asset => { - const [assetType] = asset.split('/'); - this.changeTab(assetType)(); - this.update(); - }; - window.orders.on('openAsset', assetListener); - this.on('unmount', () => { - window.orders.off('openAsset', assetListener); - }); - - const languageSubmenu = { - items: [], - columns: 2 - }; - const recentProjectsSubmenu = { - items: [] - }; - this.refreshLatestProject = function refreshLatestProject() { - recentProjectsSubmenu.items.length = 0; - var lastProjects; - if (('lastProjects' in localStorage) && - (localStorage.lastProjects !== '')) { - lastProjects = localStorage.lastProjects.split(';'); - } else { - lastProjects = []; - } - for (const project of lastProjects) { - recentProjectsSubmenu.items.push({ - label: project, - click() { - alertify.confirm(window.languageJSON.common.reallyexit, e => { - if (e) { - window.signals.trigger('resetAll'); - window.loadProject(project); - } - }); - } - }); - } - }; - this.ctClick = (e) => { - this.refreshLatestProject(); - if (e) { - this.refs.catMenu.toggle(); - } - }; - this.saveProject = async () => { - try { - const YAML = require('js-yaml'); - const projectYAML = YAML.dump(global.currentProject); - await fs.outputFile(global.projdir + '.ict', projectYAML); - this.saveRecoveryDebounce(); - fs.remove(global.projdir + '.ict.recovery') - .catch(console.error); - glob.modified = false; - alertify.success(window.languageJSON.common.savedcomm, 'success', 3000); - } catch (e) { - alertify.error(e); - } - }; - this.saveRecovery = () => { - if (global.currentProject) { - const YAML = require('js-yaml'); - const recoveryYAML = YAML.dump(global.currentProject); - fs.outputFile(global.projdir + '.ict.recovery', recoveryYAML); - } - this.saveRecoveryDebounce(); - }; - this.saveRecoveryDebounce = window.debounce(this.saveRecovery, 1000 * 60 * 5); - window.signals.on('saveProject', this.saveProject); - this.on('unmount', () => { - window.signals.off('saveProject', this.saveProject); - }); - this.saveRecoveryDebounce(); - - const {getExportDir} = require('./data/node_requires/platformUtils'); - // Run a local server for ct.js games - let fileServer; - if (!this.debugServerStarted) { - getExportDir().then(dir => { - const fileServerSettings = { - public: dir, - cleanUrls: true - }; - const handler = require('serve-handler'); - fileServer = require('http').createServer((request, response) => - handler(request, response, fileServerSettings)); - fileServer.listen(0, () => { - // eslint-disable-next-line no-console - console.info(`[ct.debugger] Running dev server at http://localhost:${fileServer.address().port}`); - }); - this.debugServerStarted = true; - }); - } - - this.runProject = () => { - document.body.style.cursor = 'progress'; - this.exportingProject = true; - this.update(); - const runCtExport = require('./data/node_requires/exporter'); - runCtExport(global.currentProject, global.projdir) - .then(() => { - if (localStorage.disableBuiltInDebugger === 'yes') { - // Open in default browser - nw.Shell.openExternal(`http://localhost:${fileServer.address().port}/`); - } else if (this.tab === 'debug') { - // Restart the game as we already have the tab opened - this.refs.debugger.restartGame(); - } else { - // Open the debugger as usual - this.tab = 'debug'; - this.debugParams = { - title: global.currentProject.settings.authoring.title, - link: `http://localhost:${fileServer.address().port}/` - }; - } - }) - .catch(e => { - window.alertify.error(e); - console.error(e); - }) - .finally(() => { - document.body.style.cursor = ''; - this.exportingProject = false; - this.update(); - }); - }; - this.runProjectAlt = () => { - const runCtExport = require('./data/node_requires/exporter'); - runCtExport(global.currentProject, global.projdir) - .then(() => { - nw.Shell.openExternal(`http://localhost:${fileServer.address().port}/`); - }); - }; - window.hotkeys.on('Alt+F5', this.runProjectAlt); - this.on('unmount', () => { - window.hotkeys.off('Alt+F5', this.runProjectAlt); - }); - - this.toggleFullscreen = function toggleFullscreen() { - nw.Window.get().toggleFullscreen(); - }; - window.hotkeys.on('F11', this.toggleFullscreen); - this.on('unmount', () => { - window.hotkeys.off('F11', this.toggleFullscreen); - }); - - this.zipProject = async () => { - try { - const os = require('os'); - const path = require('path'); - const {getWritableDir} = require('./data/node_requires/platformUtils'); - - const writable = await getWritableDir(); - const inDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ctZipProject-')), - outName = path.join(writable, `/${sessionStorage.projname}.zip`); - - await this.saveProject(); - await fs.remove(outName); - await fs.remove(inDir); - await fs.copy(global.projdir + '.ict', path.join(inDir, sessionStorage.projname)); - await fs.copy(global.projdir, path.join(inDir, sessionStorage.projname.slice(0, -4))); - - const archive = archiver('zip'), - output = fs.createWriteStream(outName); - - output.on('close', () => { - nw.Shell.showItemInFolder(outName); - alertify.success(this.voc.successZipProject.replace('{0}', outName)); - fs.remove(inDir); - }); - - archive.pipe(output); - archive.directory(inDir, false); - archive.finalize(); - } catch (e) { - alertify.error(e); - } - }; - this.zipExport = async () => { - const {getBuildDir, getExportDir} = require('./data/node_requires/platformUtils'); - const buildFolder = await getBuildDir(); - const runCtExport = require('./data/node_requires/exporter'); - const exportFile = path.join( - buildFolder, - `${global.currentProject.settings.authoring.title || 'ct.js game'}.zip` - ); - const inDir = await getExportDir(); - await fs.remove(exportFile); - runCtExport(global.currentProject, global.projdir, true) - .then(() => { - const archive = archiver('zip'), - output = fs.createWriteStream(exportFile); - - output.on('close', () => { - nw.Shell.showItemInFolder(exportFile); - alertify.success(this.voc.successZipExport.replace('{0}', exportFile)); - }); - - archive.pipe(output); - archive.directory(inDir, false); - archive.finalize(); - }) - .catch(alertify.error); - }; - - const troubleshootingSubmenu = { - items: [{ - label: window.languageJSON.menu.toggleDevTools, - icon: 'terminal', - hotkeyLabel: 'Ctrl+Shift+C', - click: () => { - const win = nw.Window.get(); - win.showDevTools(); - } - }, { - label: window.languageJSON.menu.copySystemInfo, - icon: 'file-text', - click: () => { - const os = require('os'), - path = require('path'); - const packaged = path.basename(process.execPath, path.extname(process.execPath)) !== 'nw'; - const report = `Ct.js v${process.versions.ctjs} 😽 ${packaged ? '(packaged)' : '(runs from sources)'}\n\n` + - `NW.JS v${process.versions.nw}\n` + - `Chromium v${process.versions.chromium}\n` + - `Node.js v${process.versions.node}\n` + - `Pixi.js v${PIXI.VERSION}\n\n` + - `OS ${process.platform} ${process.arch} // ${os.type()} ${os.release()}`; - nw.Clipboard.get().set(report, 'text'); - } - }, { - label: window.languageJSON.menu.disableBuiltInDebugger, - type: 'checkbox', - checked: () => localStorage.disableBuiltInDebugger === 'yes', - click: () => { - if (localStorage.disableBuiltInDebugger === 'yes') { - localStorage.disableBuiltInDebugger = 'no'; - } else { - localStorage.disableBuiltInDebugger = 'yes'; - } - } - }, { - label: window.languageJSON.menu.forceProductionForDebug, - type: 'checkbox', - checked: () => localStorage.forceProductionForDebug === 'yes', - click: () => { - if (localStorage.forceProductionForDebug === 'yes') { - localStorage.forceProductionForDebug = 'no'; - } else { - localStorage.forceProductionForDebug = 'yes'; - } - } - }, { - type: 'separator' - }, { - icon: 'discord', - iconClass: 'icon', - label: window.languageJSON.menu.visitDiscordForGamedevSupport, - click: () => { - nw.Shell.openExternal('https://discord.gg/3f7TsRC'); - } - }, { - icon: 'github', - iconClass: 'icon', - label: window.languageJSON.menu.postAnIssue, - click: () => { - nw.Shell.openExternal('https://github.com/ct-js/ct-js/issues/new/choose'); - } - }] - }; - - const themeManager = require('./data/node_requires/themes'); - const themesSubmenu = { - items: themeManager.getThemeList().map(theme => ({ - label: theme.translated, - icon: () => localStorage.UItheme === theme.name && 'check', - click: async () => { - await themeManager.switchToTheme(theme.name); - this.update(); - } - })) - }; - const settingsSubmenu = { - items: [{ - label: window.languageJSON.common.language, - submenu: languageSubmenu - }, { - label: window.languageJSON.menu.theme, - submenu: themesSubmenu - }, { - label: window.languageJSON.menu.codeFont, - submenu: { - items: [{ - label: window.languageJSON.menu.codeFontDefault, - icon: () => !localStorage.fontFamily && 'check', - click: () => { - localStorage.fontFamily = ''; - window.signals.trigger('codeFontUpdated'); - } - }, { - label: 'Basis (Pooxel)', - icon: () => localStorage.fontFamily === 'Basis, monospace' && 'check', - click: () => { - localStorage.fontFamily = 'Basis, monospace'; - window.signals.trigger('codeFontUpdated'); - } - }, { - label: window.languageJSON.menu.codeFontOldSchool, - icon: () => localStorage.fontFamily === 'Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace' && 'check', - click: () => { - localStorage.fontFamily = 'Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace'; - window.signals.trigger('codeFontUpdated'); - } - }, { - label: window.languageJSON.menu.codeFontSystem, - icon: () => localStorage.fontFamily === 'monospace' && 'check', - click: () => { - localStorage.fontFamily = 'monospace'; - window.signals.trigger('codeFontUpdated'); - } - }, { - label: window.languageJSON.menu.codeFontCustom, - click: () => { - alertify - .defaultValue(localStorage.fontFamily || '') - .prompt(window.languageJSON.menu.newFont) - .then(e => { - if (e.inputValue && e.buttonClicked !== 'cancel') { - localStorage.fontFamily = `"${e.inputValue}", monospace`; - } - window.signals.trigger('codeFontUpdated'); - }); - } - }, { - type: 'separator' - }, { - label: window.languageJSON.menu.codeLigatures, - type: 'checkbox', - checked: () => localStorage.codeLigatures !== 'off', - click: () => { - localStorage.codeLigatures = localStorage.codeLigatures === 'off' ? 'on' : 'off'; - window.signals.trigger('codeFontUpdated'); - } - }, { - label: window.languageJSON.menu.codeDense, - type: 'checkbox', - checked: () => localStorage.codeDense === 'on', - click: () => { - localStorage.codeDense = localStorage.codeDense === 'off' ? 'on' : 'off'; - window.signals.trigger('codeFontUpdated'); - } - }] - } - }, { - label: window.languageJSON.menu.changeDataFolder, - click: () => { - require('./data/node_requires/platformUtils').requestWritableDir(); - } - }, { - label: window.languageJSON.menu.disableSounds, - type: 'checkbox', - checked: () => localStorage.disableSounds === 'on', - click: () => { - localStorage.disableSounds = (localStorage.disableSounds || 'off') === 'off' ? 'on' : 'off'; - } - }, { - type: 'separator' - }, { - label: window.languageJSON.common.zoomIn, - icon: 'zoom-in', - click: () => { - const win = nw.Window.get(); - let zoom = win.zoomLevel + 0.5; - if (Number.isNaN(zoom) || !zoom || !Number.isFinite(zoom)) { - zoom = 0; - } else if (zoom > 5) { - zoom = 5; - } - win.zoomLevel = zoom; - - localStorage.editorZooming = zoom; - }, - hotkey: 'Control+=', - hotkeyLabel: 'Ctrl+=' - }, { - label: window.languageJSON.common.zoomOut, - icon: 'zoom-out', - click: () => { - const win = nw.Window.get(); - let zoom = win.zoomLevel - 0.5; - if (Number.isNaN(zoom) || !zoom || !Number.isFinite(zoom)) { - zoom = 0; - } else if (zoom < -3) { - zoom = -3; - } - win.zoomLevel = zoom; - - localStorage.editorZooming = zoom; - }, - hotkey: 'Control+-', - hotkeyLabel: 'Ctrl+-' - }] - }; - - this.catMenu = { - items: [{ - label: window.languageJSON.common.save, - icon: 'save', - click: this.saveProject, - hotkey: 'Control+s', - hotkeyLabel: 'Ctrl+S' - }, { - label: this.voc.exportDesktop, - click: () => { - this.showExporter = true; - this.update(); - }, - icon: 'package' - }, { - label: this.voc.zipExport, - click: this.zipExport, - icon: 'upload-cloud' - }, { - label: this.voc.zipProject, - click: this.zipProject - }, { - label: this.voc.openIncludeFolder, - click: () => { - fs.ensureDir(path.join(global.projdir, '/include')) - .then(() => { - nw.Shell.openItem(path.join(global.projdir, '/include')); - }); - } - }, { - type: 'separator' - }, { - label: window.languageJSON.menu.openProject, - icon: 'folder', - click: () => { - alertify.confirm(window.languageJSON.common.reallyexit, () => { - window.showOpenDialog({ - defaultPath: require('./data/node_requires/resources/projects').getDefaultProjectDir(), - title: window.languageJSON.menu.openProject, - filter: '.ict' - }) - .then(projFile => { - if (!projFile) { - return; - } - window.signals.trigger('resetAll'); - window.loadProject(projFile); - }); - }); - } - }, { // The same as "Open project" item, but shows an examples' folder first - label: window.languageJSON.menu.openExample, - click: () => { - alertify.confirm(window.languageJSON.common.reallyexit, () => { - window.showOpenDialog({ - defaultPath: require('./data/node_requires/resources/projects').getExamplesDir(), - title: window.languageJSON.menu.openProject, - filter: '.ict' - }) - .then(projFile => { - if (!projFile) { - return; - } - window.signals.trigger('resetAll'); - window.loadProject(projFile); - }); - }); - } - }, { - label: window.languageJSON.intro.latest, - submenu: recentProjectsSubmenu - }, { - label: window.languageJSON.menu.startScreen, - click: () => { - alertify.confirm(window.languageJSON.common.reallyexit, e => { - if (e) { - window.signals.trigger('resetAll'); - } - }); - } - }, { - type: 'separator' - }, { - label: window.languageJSON.menu.settings, - submenu: settingsSubmenu, - icon: 'settings' - }, { - label: window.languageJSON.common.contribute, - click: () => { - nw.Shell.openExternal('https://github.com/ct-js/ct-js'); - }, - icon: 'code' - }, { - label: window.languageJSON.common.donate, - icon: 'heart', - click: () => { - nw.Shell.openExternal('https://www.patreon.com/comigo'); - } - }, { - label: window.languageJSON.menu.troubleshooting, - icon: 'alert-circle', - submenu: troubleshootingSubmenu - }, { - label: window.languageJSON.common.ctsite, - click: () => { - nw.Shell.openExternal('https://ctjs.rocks/'); - } - }, { - label: window.languageJSON.menu.license, - click: () => { - this.showLicense = true; - this.update(); - } - }] - }; - this.switchLanguage = filename => { - const i18n = require('./data/node_requires/i18n.js'); - try { - window.languageJSON = i18n.loadLanguage(filename); - localStorage.appLanguage = filename; - window.signals.trigger('updateLocales'); - window.riot.update(); - } catch (e) { - alertify.alert('Could not open a language file: ' + e); - } - }; - var {switchLanguage} = this; - - fs.readdir('./data/i18n/') - .then(files => { - files.forEach(filename => { - if (path.extname(filename) !== '.json') { - return; - } - var file = filename.slice(0, -5); - if (file === 'Comments') { - return; - } - languageSubmenu.items.push({ - label: file, - icon: () => localStorage.appLanguage === file && 'check', - click: () => { - switchLanguage(file); - } - }); - }); - languageSubmenu.items.push({ - type: 'separator' - }); - languageSubmenu.items.push({ - label: window.languageJSON.common.translateToYourLanguage, - click: () => { - nw.Shell.openExternal('https://github.com/ct-js/ct-js/tree/develop/app/data/i18n'); - } - }); - }) - .catch(e => { - alertify.alert('Could not get i18n files: ' + e); - }); From e513d43331ad75444f6d60775aab003483cfdf75 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Wed, 30 Dec 2020 11:13:51 +1200 Subject: [PATCH 03/32] :bug: Fix broken context menu entry for textures to create a type from them --- app/package-lock.json | 4 ++-- package-lock.json | 2 +- src/riotTags/app-view.tag | 1 + src/riotTags/textures/textures-panel.tag | 10 +--------- src/riotTags/type-editor.tag | 7 ++++++- src/riotTags/types-panel.tag | 17 +++++++++++++++++ 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index bf6c54c26..03c16c156 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,6 +1,6 @@ { "name": "ctjs", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3997,7 +3997,7 @@ }, "xmlbuilder": { "version": "9.0.7", - "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, "xmldom": { diff --git a/package-lock.json b/package-lock.json index 26f5483ff..33d7a516e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ctjsbuildenvironment", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/riotTags/app-view.tag b/src/riotTags/app-view.tag index 1a6542811..ef7840011 100644 --- a/src/riotTags/app-view.tag +++ b/src/riotTags/app-view.tag @@ -78,6 +78,7 @@ app-view.flexcol const assetListener = asset => { const [assetType] = asset.split('/'); this.changeTab(assetType)(); + console.log(assetType, asset); this.update(); }; window.orders.on('openAsset', assetListener); diff --git a/src/riotTags/textures/textures-panel.tag b/src/riotTags/textures/textures-panel.tag index 5f9700fd5..2f518d5e4 100644 --- a/src/riotTags/textures/textures-panel.tag +++ b/src/riotTags/textures/textures-panel.tag @@ -223,15 +223,7 @@ textures-panel.panel.view const typesAPI = require('./data/node_requires/resources/types/'); const type = typesAPI.createNewType(this.currentTexture.name); type.texture = this.currentTexture.uid; - // eslint-disable-next-line no-underscore-dangle - const mainMenu = document.getElementsByTagName('main-menu')[0]._tag; - mainMenu.changeTab('types')(); - mainMenu.update(); - // eslint-disable-next-line no-underscore-dangle - const typesPanel = document.getElementsByTagName('types-panel')[0]._tag; - typesPanel.refs.types.updateList(); - typesPanel.openType(type)(); - typesPanel.update(); + window.orders.trigger('openAsset', `types/${type.uid}`); } }, { label: window.languageJSON.common.open, diff --git a/src/riotTags/type-editor.tag b/src/riotTags/type-editor.tag index 994facfec..7aeb55f07 100644 --- a/src/riotTags/type-editor.tag +++ b/src/riotTags/type-editor.tag @@ -157,13 +157,16 @@ type-editor.panel.view.flexrow this.typeoncreate.focus(); }, 0); }); - this.on('update', () => { + this.checkNames = () => { if (global.currentProject.types.find(type => this.type.name === type.name && this.type !== type)) { this.nameTaken = true; } else { this.nameTaken = false; } + }; + this.on('update', () => { + this.checkNames(); }); this.on('unmount', () => { // Manually destroy code editors, to free memory @@ -194,7 +197,9 @@ type-editor.panel.view.flexrow this.update(); }; this.typeSave = () => { + this.checkNames(); if (this.nameTaken) { + this.update(); // animate the error notice require('./data/node_requires/jellify')(this.refs.errorNotice); if (localStorage.disableSounds !== 'on') { diff --git a/src/riotTags/types-panel.tag b/src/riotTags/types-panel.tag index d886d7129..d4aa0bd62 100644 --- a/src/riotTags/types-panel.tag +++ b/src/riotTags/types-panel.tag @@ -69,6 +69,23 @@ types-panel.panel.view this.editedType = type; }; + const assetListener = asset => { + const [assetType, uid] = asset.split('/'); + if (assetType !== 'types') { + return; + } + const type = global.currentProject.types.find(type => type.uid === uid); + this.openType(type)(); + this.refs.types.updateList(); + if (this.parent) { + this.parent.update(); + } + }; + window.orders.on('openAsset', assetListener); + this.on('unmount', () => { + window.orders.off('openAsset', assetListener); + }); + this.typeMenu = { items: [{ label: window.languageJSON.common.open, From 51b78d4ae1b0d3d09d42d16a96185edd53c139f4 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Wed, 30 Dec 2020 11:19:55 +1200 Subject: [PATCH 04/32] :zap: Renovate ct.desktop -> quit method --- app/data/ct.libs/desktop/index.js | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/app/data/ct.libs/desktop/index.js b/app/data/ct.libs/desktop/index.js index ab23de564..f42c68653 100644 --- a/app/data/ct.libs/desktop/index.js +++ b/app/data/ct.libs/desktop/index.js @@ -1,20 +1,27 @@ -/* global nw */ ct.desktop = { + isNw: window.nw && window.nw.App, + isElectron: null, quit() { - if (window.nw && window.nw.App) { + if (ct.desktop.isNw) { if (window.iAmInCtIdeDebugger) { - nw.Window.get().close(); + // eslint-disable-next-line no-console + console.warn('We don\'t quit because ct.js would quit as well. Let\'s imagine that the game has exited! :D'); } else { nw.App.quit(); } - return true; - } - try { + } else if (ct.desktop.isElectron) { require('electron').remote.getCurrentWindow().close(); - return true; - } catch (e) { - console.error('Could not exit the game :c Are we in a browser?'); - return false; + } else { + console.error('[ct.desktop/quit] Unknown environment :c Are we in a browser?'); } } }; + +try { + require('electron'); + ct.desktop.isElectron = true; +} catch (e) { + ct.desktop.isElectron = false; +} + +ct.desktop.isDesktop = ct.desktop.isNw || ct.desktop.isElectron; From 2fcb53bbfb4332fb56e2c81528e287d794580d4b Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Wed, 30 Dec 2020 11:21:17 +1200 Subject: [PATCH 05/32] :pencil: Add a memo about `ct.desktop.isNw` and `ct.desktop.isElectron` --- app/data/ct.libs/desktop/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/data/ct.libs/desktop/README.md b/app/data/ct.libs/desktop/README.md index c7176c51c..94ddd447b 100644 --- a/app/data/ct.libs/desktop/README.md +++ b/app/data/ct.libs/desktop/README.md @@ -1 +1,3 @@ -Currently just has a method `ct.desktop.quit()`, which exits the game when packaged into executables. \ No newline at end of file +Currently just has a method `ct.desktop.quit()`, which exits the game when packaged into executables. + +Also has variables `ct.desktop.isNw` and `ct.desktop.isElectron` to differentiate between these two platforms. \ No newline at end of file From 7b740f25532499b4229d87b5aabe9389532ff51d Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sat, 2 Jan 2021 13:20:11 +1200 Subject: [PATCH 06/32] :bug: Fix ct.place.meet returning duplicated references to copies if querying for multiple obstacles --- app/data/ct.libs/place/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/data/ct.libs/place/index.js b/app/data/ct.libs/place/index.js index ed163a98d..189ef3012 100644 --- a/app/data/ct.libs/place/index.js +++ b/app/data/ct.libs/place/index.js @@ -379,7 +379,9 @@ } return array[i]; } - results.push(array[i]); + if (!results.includes(array[i])) { + results.push(array[i]); + } } } } From f2b531fa30b35356c16dcaabc0c1b09ed17a5c64 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Tue, 19 Jan 2021 16:07:11 +1200 Subject: [PATCH 07/32] :bug: Fix "}" at the end of some texture files' names --- src/node_requires/resources/textures/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_requires/resources/textures/index.js b/src/node_requires/resources/textures/index.js index a8ef7bf74..1ac9f7cd6 100644 --- a/src/node_requires/resources/textures/index.js +++ b/src/node_requires/resources/textures/index.js @@ -208,7 +208,7 @@ const importImageToTexture = async (src, name) => { const id = generateGUID(); let dest; if (src instanceof Buffer) { - dest = path.join(global.projdir, 'img', `i${id}.png}`); + dest = path.join(global.projdir, 'img', `i${id}.png`); await fs.writeFile(dest, src); } else { dest = path.join(global.projdir, 'img', `i${id}${path.extname(src)}`); From 906369b80992e04ab85359483611ff33231eaa8e Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Tue, 16 Feb 2021 08:03:50 +1200 Subject: [PATCH 08/32] :bug: Allow resetting values in type and texture inputs at modded fields --- src/riotTags/shared/extensions-editor.tag | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/riotTags/shared/extensions-editor.tag b/src/riotTags/shared/extensions-editor.tag index 2ff612a9d..fc5037667 100644 --- a/src/riotTags/shared/extensions-editor.tag +++ b/src/riotTags/shared/extensions-editor.tag @@ -105,6 +105,7 @@ extensions-editor class="{compact: parent.opts.compact, wide: parent.opts.wide}" val="{parent.opts.entity[ext.key] || ext.default}" onselected="{writeUid(ext.key)}" + showempty="yep" ) .aPoint2DInput(if="{ext.type === 'point2D'}" class="{compact: parent.opts.compact, wide: parent.opts.wide}") label @@ -131,6 +132,7 @@ extensions-editor class="{compact: parent.opts.compact, wide: parent.opts.wide}" val="{parent.opts.entity[ext.key] || ext.default}" onselected="{writeUid(ext.key)}" + showempty="yep" ) color-input( if="{ext.type === 'color'}" From 15d46a27ebaa10d10165557a40c29e32d1f9d1f3 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Tue, 16 Feb 2021 08:04:45 +1200 Subject: [PATCH 09/32] :bug: Fix icons for nightly and regular releases --- gulpfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 7c1d7f97b..7ecf6af4a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -444,9 +444,9 @@ const bakePackages = async () => { const NwBuilder = require('nw-builder'); // Use the appropriate icon for each release channel if (nightly) { - await fs.copy('./buildAssets/icon.png', './app/ct_ide.png'); - } else { await fs.copy('./buildAssets/nightly.png', './app/ct_ide.png'); + } else { + await fs.copy('./buildAssets/icon.png', './app/ct_ide.png'); } await fs.remove(path.join('./build', `ctjs - v${pack.version}`)); var nw = new NwBuilder({ From 4773a003696f5734173824d0d5179bcf8e6ade4d Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Tue, 16 Feb 2021 08:05:51 +1200 Subject: [PATCH 10/32] :bento: Update nw.js to v0.51.1 --- gulpfile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 7ecf6af4a..7eccbebdd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -43,9 +43,9 @@ const path = require('path'), * * Also note that you may need to clear the `ct-js/cache` folder. */ -const nwSource = 'https://dl.nwjs.io/live-build/nw50/20201215-162000/25eea59e1/'; -const nwManifest = 'https://raw.githubusercontent.com/ct-js/ct-js/v1.x/customNwManifest.json'; -const nwVersion = void 0, +const nwSource = void 0; +const nwManifest = void 0; +const nwVersion = '0.51.1', platforms = ['osx64', 'win32', 'win64', 'linux32', 'linux64'], nwFiles = ['./app/**', '!./app/export/**', '!./app/projects/**', '!./app/exportDesktop/**', '!./app/cache/**', '!./app/.vscode/**', '!./app/JamGames/**']; From 3ffef584968ad5f07ecd3d74622dffa0b1106872 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Tue, 16 Feb 2021 08:06:40 +1200 Subject: [PATCH 11/32] :bug: Fix crashes of built-in debugger; disable nw and node in the devtools --- src/riotTags/debugger/debugger-screen-embedded.tag | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/riotTags/debugger/debugger-screen-embedded.tag b/src/riotTags/debugger/debugger-screen-embedded.tag index 892ad7091..3169ffc16 100644 --- a/src/riotTags/debugger/debugger-screen-embedded.tag +++ b/src/riotTags/debugger/debugger-screen-embedded.tag @@ -2,18 +2,21 @@ Exposes this.reloadGame debugger-screen-embedded(class="{opts.class} {flexrow: verticalLayout, flexcol: !verticalLayout}") webview.tall#thePreview( + ref="gameView" partition="persist:trusted" - ref="gameView" allownw nwfaketop + allownw nwfaketop ) .aResizer(ref="gutter" onmousedown="{gutterMouseDown}" class="{vertical: verticalLayout, horizontal: !verticalLayout}") .flexfix( style="flex: 0 0 auto; {verticalLayout? 'width:'+width+'px' : 'height:'+height+'px'}" ) webview.tall.flexfix-body( - partition="persist:trusted" src="empty.html" - ref="devtoolsView" allownw nwfaketop + ref="devtoolsView" + src="empty.html" + partition="persist:trusted" style="overflow: hidden;" ) + //allownw nwfaketop .flexfix-footer.aDebuggerToolbar.noshrink( class="{vertical: verticalLayout} {tight: (verticalLayout && width < 1000) || (!verticalLayout && window.innerWidth < 1000)}" ) From b67429f39be6db829838e332851465b07d23cf7f Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Tue, 16 Feb 2021 08:09:25 +1200 Subject: [PATCH 12/32] :zap: Allow Background class to accept a pixi.js texture --- app/data/ct.release/backgrounds.js | 4 +++- src/typedefs/ct.js/Background.d.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/data/ct.release/backgrounds.js b/app/data/ct.release/backgrounds.js index 4ce9579f0..7ceefba38 100644 --- a/app/data/ct.release/backgrounds.js +++ b/app/data/ct.release/backgrounds.js @@ -28,7 +28,9 @@ class Background extends PIXI.TilingSprite { constructor(texName, frame = 0, depth = 0, exts = {}) { var width = ct.camera.width, height = ct.camera.height; - const texture = ct.res.getTexture(texName, frame || 0); + const texture = texName instanceof PIXI.Texture ? + texName : + ct.res.getTexture(texName, frame || 0); if (exts.repeat === 'no-repeat' || exts.repeat === 'repeat-x') { height = texture.height * (exts.scaleY || 1); } diff --git a/src/typedefs/ct.js/Background.d.ts b/src/typedefs/ct.js/Background.d.ts index 28ef52e42..114a863e7 100644 --- a/src/typedefs/ct.js/Background.d.ts +++ b/src/typedefs/ct.js/Background.d.ts @@ -4,6 +4,7 @@ interface IBackgroundTemplate { declare class Background extends PIXI.TilingSprite { constructor(bgName: string, frame: number, depth: number, exts: object); + constructor(pixiTexture: PIXI.Texture, frame: number, depth: number, exts: object); depth: number; shiftX: number; shiftY: number; @@ -14,4 +15,4 @@ declare class Background extends PIXI.TilingSprite { scaleX: number; scaleY: number; repeat: string; -} \ No newline at end of file +} From 21caa7ce63d54be392ac62e4fc7cbc61d6d6ec71 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sat, 20 Feb 2021 21:42:45 +1200 Subject: [PATCH 13/32] :sparkles: Nano ID catmod of the same-named tiny library by Andrei Sitnik --- app/data/ct.libs/nanoid/README.md | 14 ++++++++++++++ app/data/ct.libs/nanoid/index.js | 26 ++++++++++++++++++++++++++ app/data/ct.libs/nanoid/module.json | 14 ++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 app/data/ct.libs/nanoid/README.md create mode 100644 app/data/ct.libs/nanoid/index.js create mode 100644 app/data/ct.libs/nanoid/module.json diff --git a/app/data/ct.libs/nanoid/README.md b/app/data/ct.libs/nanoid/README.md new file mode 100644 index 000000000..21d3b6124 --- /dev/null +++ b/app/data/ct.libs/nanoid/README.md @@ -0,0 +1,14 @@ +Nano ID for ct.js + +This catmod is a bit modified and minified `nanoid` [npm module](https://github.com/ai/nanoid). The original module is made by Andrey Sitnik and dozens of contributors, and is distributed under the MIT license. + +Nano ID is an URL-friendly, secure ID generator that is shorter but more collision-resilient than GUID. + +# Usage + +```js +// Regular ID +var id = ct.nanoid.generate() //=> "V1StGXR8_Z5jdHi6B-myT" +// An ID of a specific size +var shortId = ct.nanoid.generate(10) //=> "IRFa-VaY2b" +``` diff --git a/app/data/ct.libs/nanoid/index.js b/app/data/ct.libs/nanoid/index.js new file mode 100644 index 000000000..bc9eb3159 --- /dev/null +++ b/app/data/ct.libs/nanoid/index.js @@ -0,0 +1,26 @@ +/* +Nano ID (https://github.com/ai/nanoid) + +The MIT License (MIT) + +Copyright 2017 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +(function(exports){'use strict';let urlAlphabet='ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW';if(typeof crypto==='undefined'){throw new Error('Your browser does not have secure random generator. If you don’t need unpredictable IDs, you can use nanoid/non-secure.')}let random=bytes=>crypto.getRandomValues(new Uint8Array(bytes));let customRandom=(alphabet,size,getRandom)=>{let mask=(2<<(Math.log(alphabet.length-1)/Math.LN2))-1;let step= - ~((1.6*mask*size)/alphabet.length);return()=>{let id='';while(true){let bytes=getRandom(step);let j=step;while(j--){id+=alphabet[bytes[j]&mask]||'';if(id.length===size){return id}}}}};let customAlphabet=(alphabet,size)=>customRandom(alphabet,size,random);let nanoid=(size=21)=>{let id='';let bytes=crypto.getRandomValues(new Uint8Array(size));while(size--){let byte=bytes[size]&63;if(byte<36){id+=byte.toString(36)}else if(byte<62){id+=(byte-26).toString(36).toUpperCase()}else if(byte<63){id+='_'}else{id+='-'}}return id};exports.customAlphabet=customAlphabet;exports.customRandom=customRandom;exports.generate=nanoid;exports.random=random;exports.urlAlphabet=urlAlphabet;return exports}(ct.nanoid={})); diff --git a/app/data/ct.libs/nanoid/module.json b/app/data/ct.libs/nanoid/module.json new file mode 100644 index 000000000..d60e3bb8c --- /dev/null +++ b/app/data/ct.libs/nanoid/module.json @@ -0,0 +1,14 @@ +{ + "main": { + "name": "Nano ID", + "tagline": "A tiny, secure, URL-friendly, unique string ID generator for JavaScript, bundled for ct.js.", + "version": "3.1.20", + "packageName": "nanoid", + "authors": [{ + "name": "Andrey Sitnik and contributors" + }], + "categories": [ + "utilities" + ] + } +} From 1d3d09590a210ae8de083102d077dd7616bbf749 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Wed, 3 Mar 2021 10:15:59 +1200 Subject: [PATCH 14/32] :zap: Select only the needed Nw.js version for debugging --- gulpfile.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 7c1d7f97b..e04e1d2a9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -43,9 +43,9 @@ const path = require('path'), * * Also note that you may need to clear the `ct-js/cache` folder. */ -const nwSource = 'https://dl.nwjs.io/live-build/nw50/20201215-162000/25eea59e1/'; -const nwManifest = 'https://raw.githubusercontent.com/ct-js/ct-js/v1.x/customNwManifest.json'; -const nwVersion = void 0, +const nwSource = void 0; +const nwManifest = void 0; +const nwVersion = '0.51.2', platforms = ['osx64', 'win32', 'win64', 'linux32', 'linux64'], nwFiles = ['./app/**', '!./app/export/**', '!./app/projects/**', '!./app/exportDesktop/**', '!./app/cache/**', '!./app/.vscode/**', '!./app/JamGames/**']; @@ -299,14 +299,25 @@ const lintI18n = () => require('./node_requires/i18n')().then(console.log); const lint = gulp.series(lintJS, lintTags, lintStylus, lintI18n); +const processToPlatformMap = { + 'darwin-x64': 'darwin', + 'win32-x32': 'win32', + 'win32-x64': 'win64', + 'linux-x32': 'linux32', + 'linux-x64': 'linux64' +}; const launchApp = () => { const NwBuilder = require('nw-builder'); + const platformKey = `${process.platform}-${process.arch}`; + if (!(platformKey in processToPlatformMap)) { + throw new Error(`Combination of OS and architecture ${process.platform}-${process.arch} is not supported by NW.js.`); + } const nw = new NwBuilder({ files: nwFiles, version: nwVersion, downloadUrl: nwSource, manifestUrl: nwManifest, - platforms, + platforms: [processToPlatformMap[platformKey]], flavor: 'sdk' }); return nw.run() From 58eb26c22323cbcb16cf503da6de95a8af9c33f8 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Wed, 3 Mar 2021 17:54:11 +1200 Subject: [PATCH 15/32] :sparkles: Group modded fields into collapsible sections with a new field type --- src/riotTags/shared/collapsible-section.tag | 3 ++- src/riotTags/shared/extensions-editor.tag | 20 +++++++++++++++++-- src/styl/tags/shared/collapsible-section.styl | 14 +++++++++++-- src/styl/tags/shared/extensions-editor.styl | 7 ++++++- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/riotTags/shared/collapsible-section.tag b/src/riotTags/shared/collapsible-section.tag index 5d60bc075..20b58c0e3 100644 --- a/src/riotTags/shared/collapsible-section.tag +++ b/src/riotTags/shared/collapsible-section.tag @@ -1,5 +1,6 @@ // A small block that displays a section with an h3 header that can be collapsed. + Supports .panel modifier (CSS class), but also works by itself @attribute heading (string) The heading to display @@ -17,7 +18,7 @@ A callback that triggers when a user folds/unfolds the section. Passes the new state and this tag as two arguments. -collapsible-section +collapsible-section(class="{opts.class} {opened ? 'opened' : 'closed'}") .flexrow(onclick="{toggle}") h1(if="{opts.hlevel == 1}") {opts.heading} h2(if="{opts.hlevel == 2}") {opts.heading} diff --git a/src/riotTags/shared/extensions-editor.tag b/src/riotTags/shared/extensions-editor.tag index fc5037667..78587c6c4 100644 --- a/src/riotTags/shared/extensions-editor.tag +++ b/src/riotTags/shared/extensions-editor.tag @@ -24,7 +24,7 @@ // Below 'h1', 'h2', 'h3', 'h4' are purely decorational, for grouping fields. Others denote the type of an input field. type: 'h1' | 'h2' | 'h3' | 'h4' | 'text' | 'textfield' | 'code' | 'number' | 'slider' | 'sliderAndNumber' | 'point2D' | 'checkbox' | 'radio' | 'texture' | 'type' | - 'color', + 'color' | 'group', key?: string, // the name of a JSON key to write into the `opts.entity`. Not needed for hN types, but required otherwise // The key may have special suffixes that tell the exporter to unwrap foreign keys (resources' UIDs) into asset names. // These are supposed to always be used with `'type'` and `'texture'` input types. @@ -37,17 +37,33 @@ help?: string }>, array?: boolean, // Whether to display an editable list instead of just one field. + items?: Array, // For type === 'group', the grouped items. collect?: boolean, // Whether to collect values and suggest them later as an auto-completion results. (Not yet implemented) collectScope?: string // The name of a category under which to store suggestions from `collect`. } extensions-editor virtual(each="{ext in extensions}") + // ext="{ext}" is a workaround to lost loop variables in yields + collapsible-section.panel( + ext="{ext}" + if="{ext.type === 'group'}" + heading="{ext.name}" + hlevel="{parent.opts.compact? 4 : 3}" + defaultstate="{ext.openedByDefault? 'opened' : 'closed'}" + storestatekey="catmod-{ext.lsKey}" + ) + extensions-editor( + entity="{parent.opts.entity}" + customextends="{opts.ext.items}" + compact="{parent.opts.compact || void 0}" + wide="{parent.opts.wide || void 0}" + ) h1(if="{ext.type === 'h1'}") {ext.name} h2(if="{ext.type === 'h2'}") {ext.name} h3(if="{ext.type === 'h3'}") {ext.name} h4(if="{ext.type === 'h4'}") {ext.name} - dl(class="{compact: compact}" if="{['h1', 'h2', 'h3', 'h4'].indexOf(ext.type) === -1}") + dl(class="{compact: compact}" if="{['h1', 'h2', 'h3', 'h4', 'group'].indexOf(ext.type) === -1}") dt(if="{!parent.opts.intable}") label.block.checkbox(if="{ext.type === 'checkbox'}") input.nogrow( diff --git a/src/styl/tags/shared/collapsible-section.styl b/src/styl/tags/shared/collapsible-section.styl index 72055a149..2218b8693 100644 --- a/src/styl/tags/shared/collapsible-section.styl +++ b/src/styl/tags/shared/collapsible-section.styl @@ -8,7 +8,7 @@ collapsible-section align-items center cursor pointer border-bottom 1px solid borderBright - h3 + h1, h2, h3, h4, h5, h6 padding 0 margin 0 flex 1 1 auto @@ -23,4 +23,14 @@ collapsible-section &.rotated transform rotate(180deg) .&-aWrapper - padding 1rem 0 0.5rem \ No newline at end of file + padding 1rem 0 0.5rem + &.panel + & > .flexrow + h1, h2, h3, h4, h5, h6 + margin 0 1rem + svg + margin-right 0.5rem + &.closed > .flexrow + border-bottom 0 + .collapsible-section-aWrapper + padding 1rem diff --git a/src/styl/tags/shared/extensions-editor.styl b/src/styl/tags/shared/extensions-editor.styl index 6693e1c6b..66934d42e 100644 --- a/src/styl/tags/shared/extensions-editor.styl +++ b/src/styl/tags/shared/extensions-editor.styl @@ -9,4 +9,9 @@ extensions-editor box-sizing border-box max-width 100% .nicetable dl - margin 0 \ No newline at end of file + margin 0 + collapsible-section.panel > .collapsible-section-aWrapper > & + & > dl:first-child + margin-top 0 + & > dl:last-child + margin-bottom 0 \ No newline at end of file From 243cf2eb37173fc3b1d72b2b65ece4a641518c76 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Mon, 8 Mar 2021 21:18:53 +1200 Subject: [PATCH 16/32] :sparkles: Add ct.light module for adding ambient lighting and textured lights --- app/data/ct.libs/light/README.md | 91 ++++++++++++++ app/data/ct.libs/light/index.js | 113 ++++++++++++++++++ .../ct.libs/light/injects/beforeroomdraw.js | 4 + .../light/injects/beforeroomoncreate.js | 4 + .../ct.libs/light/injects/onbeforecreate.js | 9 ++ app/data/ct.libs/light/injects/ondestroy.js | 3 + app/data/ct.libs/light/injects/switch.js | 1 + app/data/ct.libs/light/module.json | 48 ++++++++ 8 files changed, 273 insertions(+) create mode 100644 app/data/ct.libs/light/README.md create mode 100644 app/data/ct.libs/light/index.js create mode 100644 app/data/ct.libs/light/injects/beforeroomdraw.js create mode 100644 app/data/ct.libs/light/injects/beforeroomoncreate.js create mode 100644 app/data/ct.libs/light/injects/onbeforecreate.js create mode 100644 app/data/ct.libs/light/injects/ondestroy.js create mode 100644 app/data/ct.libs/light/injects/switch.js create mode 100644 app/data/ct.libs/light/module.json diff --git a/app/data/ct.libs/light/README.md b/app/data/ct.libs/light/README.md new file mode 100644 index 000000000..f4c649cbf --- /dev/null +++ b/app/data/ct.libs/light/README.md @@ -0,0 +1,91 @@ +# Light system + +This module adds properties to your rooms and types that allow creating lights and day/night cycle: + +1. Firstly, set the ambient color inside your room's properties. The darker it is, the darker your room will be, and light effects will be more visible. +2. Then open the types you want to add lights to, and select a light texture. You can later remove it by clicking the texture selector again and picking "None" at the start of the texture list. +3. Adjust the size of the light, as well as its color. If you need to make the light less intensive, change its brightness. + +*Tip: you can use colored textures to produce interesting light effects! For example, bloom from a stained glass, or some magical effects.* + +> **If you don't see lights with big textures, make sure to mark them as backgrounds.** + +## Changing lights through code + +Each copy with a light will have a property `this.light`, which is a PIXI.Sprite. It has properties similar to copies, though you cannot transform them directly. For example, you can set light's opacity β€” its intensity β€” with `this.light.alpha = 0.35;`. + +To temporarily disable a light, either set `this.light.opacity` to `0` or set `this.light.visible` to `false`. + +Lights exist on a separate drawing layer with coordinates different from both UI and gameplay coordinates, and thus you cannot directly transform them β€” at least reliably. + +**To shift a light** relative to its owner, change its pivot. For example, this will shift the light a bit to the right and noticeably to the top: + +```js +this.light.pivot.x = -50; +this.light.pivot.y = 150; +``` + +Note that changing pivot pushes a light in the opposite direction. + +**To rotate a light**, set its `rotationFactor` in degrees. For example, this line will rotate a light by 90 degrees counter-clockwise: + +```js +this.light.rotationFactor = 90; +``` + +This works cumulatively with light owner's rotation. + +**To both shift and rotate a light**, consider creating a new invisible copy that is shifted relative to its owner. Otherwise you will get your light spinning on some radius instead around a point. + +**Do your lights align weirdly?** Make sure you've set up the axis of a used texture. + +**To change light's color,** set its tint value similar to copies. For example, this line will make the ligth red: + +```js +this.light.tint = 0xff0000; +``` + +**To scale a light**, change the property `this.light.scaleFactor`. The initial value matches the one you've set inside your type. A value of `1` means no scaling is applied relative to the owning copy. + +## Reading and changing ambient light color + +Ambient light can be read and set with a property `ct.light.ambientColor`. It is a pixi color, similar to copies' `tint` property. For example, this line will make the room pitch black: + +```js +ct.light.ambientColor = 0x000000; +``` + +You can also change the intensity of the whole light system by changing the property `ct.light.opacity`. + +## Adding lights without darkening a room + +If you want to add bloom or bright light effects in an already lit room, you don't need ct.light for it! You can create a copy with light's texture, and write `this.blendMode = PIXI.BLEND_MODES.ADD;` in its OnCreate code. + +## Adding and removing lights programmatically + +You can **add a light** with a method `ct.light.add(texture, x, y, options)`. + +* `texture` must be a Pixi.js texture. To get one from ct.js, use `ct.res.getTexture('TextureName', 0);` +* `x` and `y` are global position of a light. +* `options` is an object that can include: + * `tint` β€” a Pixi.js color that changes light's color. Use 0xFFFFFF for full white color. + * `scaleFactor` β€” scale of the light. `1` is the usual scale. + * `owner` β€” a copy that owns this light. It is used to follow the copy and to be properly positioned in the world. + * `copyOpacity` β€” used with `owner`. Tells to copy `this.opacity` from a copy, meaning that they two will fade together. + +For example: + +```js +ct.light.add( + ct.res.getTexture('AlarmGlowMask', 0), + 0, 0, { + owner: this, + tint: 0xFF0000 + } +); +``` + +This will create a ligth based on the first (zeroish) frame of a texture called `AlarmGlowMask`, and make it red. + +You can **remove a light** with a method `ct.light.remove(copyOrLight);`. +You can either pass an owning copy or the light itself. \ No newline at end of file diff --git a/app/data/ct.libs/light/index.js b/app/data/ct.libs/light/index.js new file mode 100644 index 000000000..1febd332a --- /dev/null +++ b/app/data/ct.libs/light/index.js @@ -0,0 +1,113 @@ +(function addCtLight() { + const lightLayer = new PIXI.Container(), + {renderer} = ct.pixiApp, + renderTexture = PIXI.RenderTexture.create({ + width: 1024, + height: 1024 + }), + lightSprite = new PIXI.Sprite(renderTexture); + let bg, ambientColor; + lightSprite.isUi = true; + lightSprite.blendMode = PIXI.BLEND_MODES.MULTIPLY; + ct.light = { + /** + * @param {PIXI.Texture} texture + * @param {number} x + * @param {number} y + * @returns {PIXI.Sprite} + */ + add(texture, x, y, options) { + const light = new PIXI.Sprite(texture); + light.blendMode = PIXI.BLEND_MODES.ADD; + light.x = x; + light.y = y; + if (options) { + Object.assign(light, options); + } + lightLayer.addChild(light); + ct.light.lights.push(light); + return light; + }, + /** + * @param {PIXI.Texture | PIXI.Sprite} copyOrLight + * @returns {void} + */ + remove(copyOrLight) { + copyOrLight = copyOrLight.light || copyOrLight; + if (copyOrLight.owner) { + delete copyOrLight.owner.light; + } + copyOrLight.destroy({ + children: true + }); + const arr = ct.light.lights; + arr.splice(arr.indexOf(copyOrLight), 1); + }, + lights: [], + render() { + const pixelScaleModifier = ct.highDensity ? (window.devicePixelRatio || 1) : 1; + if (renderTexture.resolution !== pixelScaleModifier) { + renderTexture.setResolution(pixelScaleModifier); + } + if (renderTexture.width !== ct.pixiApp.screen.width || + renderTexture.height !== ct.pixiApp.screen.height + ) { + renderTexture.resize(ct.pixiApp.screen.width, ct.pixiApp.screen.height); + bg.width = ct.pixiApp.screen.width; + bg.height = ct.pixiApp.screen.height; + lightSprite.width = ct.camera.width; + lightSprite.height = ct.camera.height; + } + renderer.render(lightLayer, renderTexture); + }, + update() { + for (const light of ct.light.lights) { + if (light.owner) { + if (!ct.types.exists(light.owner)) { + ct.light.remove(light); + continue; + } + light.transform.setFromMatrix(light.owner.worldTransform); + light.scale.x *= light.scaleFactor || 1; + light.scale.y *= light.scaleFactor || 1; + light.angle -= light.rotationFactor || 0; + if (light.copyOpacity) { + light.alpha = light.owner.alpha; + } + } + } + }, + clear() { + lightLayer.removeChildren(); + }, + get ambientColor() { + return ambientColor; + }, + set ambientColor(color) { + ambientColor = color; + if (bg) { + bg.tint = ambientColor; + } + return ambientColor; + }, + get opacity() { + return lightSprite.alpha; + }, + set opacity(opacity) { + lightSprite.alpha = opacity; + return opacity; + }, + install() { + bg = new PIXI.Sprite(PIXI.Texture.WHITE); + bg.width = ct.pixiApp.screen.width; + bg.height = ct.pixiApp.screen.height; + bg.tint = ambientColor; + lightLayer.addChildAt(bg, 0); + ct.pixiApp.stage.addChildAt( + lightSprite, + ct.pixiApp.stage.children.indexOf(ct.room) + 1 + ); + ct.light.render(); + } + }; +})(); diff --git a/app/data/ct.libs/light/injects/beforeroomdraw.js b/app/data/ct.libs/light/injects/beforeroomdraw.js new file mode 100644 index 000000000..87c290595 --- /dev/null +++ b/app/data/ct.libs/light/injects/beforeroomdraw.js @@ -0,0 +1,4 @@ +(function ctLightRender() { + ct.light.update(); + ct.light.render(); +})(); diff --git a/app/data/ct.libs/light/injects/beforeroomoncreate.js b/app/data/ct.libs/light/injects/beforeroomoncreate.js new file mode 100644 index 000000000..7585e642a --- /dev/null +++ b/app/data/ct.libs/light/injects/beforeroomoncreate.js @@ -0,0 +1,4 @@ +if (this === ct.room) { + ct.light.clear(); + ct.light.ambientColor = ct.u.hexToPixi(ct.room.lightAmbientColor || '#FFFFFF'); +} diff --git a/app/data/ct.libs/light/injects/onbeforecreate.js b/app/data/ct.libs/light/injects/onbeforecreate.js new file mode 100644 index 000000000..3e76c0191 --- /dev/null +++ b/app/data/ct.libs/light/injects/onbeforecreate.js @@ -0,0 +1,9 @@ +if ((this instanceof ct.types.Copy) && this.lightTexture) { + this.light = ct.light.add(ct.res.getTexture(this.lightTexture, 0), this.x, this.y, { + tint: ct.u.hexToPixi(this.lightColor || '#FFFFFF'), + scaleFactor: this.lightScale === void 0 ? true : this.lightScale, + copyOpacity: this.lightOpacity === void 0 ? true : this.lightOpacity, + owner: this + }); + this.light.scale.x = this.light.scale.y = this.lightScale || 1; +} diff --git a/app/data/ct.libs/light/injects/ondestroy.js b/app/data/ct.libs/light/injects/ondestroy.js new file mode 100644 index 000000000..eb3dd85e2 --- /dev/null +++ b/app/data/ct.libs/light/injects/ondestroy.js @@ -0,0 +1,3 @@ +if (this.light) { + ct.light.remove(this.light); +} diff --git a/app/data/ct.libs/light/injects/switch.js b/app/data/ct.libs/light/injects/switch.js new file mode 100644 index 000000000..461e77e14 --- /dev/null +++ b/app/data/ct.libs/light/injects/switch.js @@ -0,0 +1 @@ +ct.light.install(); diff --git a/app/data/ct.libs/light/module.json b/app/data/ct.libs/light/module.json new file mode 100644 index 000000000..999b3cf45 --- /dev/null +++ b/app/data/ct.libs/light/module.json @@ -0,0 +1,48 @@ +{ + "main": { + "name": "Light System", + "tagline": "Create dynamic lights with textures that follow your copies.", + "version": "1.0.0", + "authors": [{ + "name": "Cosmo Myzrail Gorynych", + "mail": "admin@nersta.ru" + }], + "categories": [ + "fx" + ] + }, + "typeExtends": [{ + "type": "group", + "name": "Light emitter", + "openedByDefault": false, + "lsKey": "light.general", + "items": [{ + "name": "Light", + "type": "texture", + "key": "lightTexture@@texture", + "default": -1 + }, { + "name": "Light color", + "type": "color", + "key": "lightColor", + "default": "#FFFFFF" + }, { + "name": "Light copies opacity", + "type": "checkbox", + "key": "lightOpacity", + "default": true + }, { + "name": "Light scale", + "type": "number", + "step": 0.1, + "key": "lightScale", + "default": 1 + }] + }], + "roomExtends": [{ + "name": "Ambient color", + "type": "color", + "key": "lightAmbientColor", + "default": "#FFFFFF" + }] +} From 8e57a040f65cb77baa6f0e1f70e9f8b7ab822601 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Mon, 8 Mar 2021 21:24:41 +1200 Subject: [PATCH 17/32] :sparkles: Add ct.filters module by SN frrom our Discord server. The module allows creating special visual effects with filters or custom shaders, applied to your copies or a whole viewport --- app/data/ct.libs/filters/CHANGELOG.md | 4 + app/data/ct.libs/filters/DOCS.md | 111 ++ .../ct.libs/filters/includes/pixi-filters.js | 9 + app/data/ct.libs/filters/index.js | 91 ++ .../ct.libs/filters/injects/htmlbottom.html | 1 + app/data/ct.libs/filters/module.json | 16 + app/data/ct.libs/filters/package.json | 14 + app/data/ct.libs/filters/types.d.ts | 1448 +++++++++++++++++ 8 files changed, 1694 insertions(+) create mode 100644 app/data/ct.libs/filters/CHANGELOG.md create mode 100644 app/data/ct.libs/filters/DOCS.md create mode 100644 app/data/ct.libs/filters/includes/pixi-filters.js create mode 100644 app/data/ct.libs/filters/index.js create mode 100644 app/data/ct.libs/filters/injects/htmlbottom.html create mode 100644 app/data/ct.libs/filters/module.json create mode 100644 app/data/ct.libs/filters/package.json create mode 100644 app/data/ct.libs/filters/types.d.ts diff --git a/app/data/ct.libs/filters/CHANGELOG.md b/app/data/ct.libs/filters/CHANGELOG.md new file mode 100644 index 000000000..11cce84cb --- /dev/null +++ b/app/data/ct.libs/filters/CHANGELOG.md @@ -0,0 +1,4 @@ +Version 1.0.0 + +- Initial release + diff --git a/app/data/ct.libs/filters/DOCS.md b/app/data/ct.libs/filters/DOCS.md new file mode 100644 index 000000000..ccc9de3dd --- /dev/null +++ b/app/data/ct.libs/filters/DOCS.md @@ -0,0 +1,111 @@ +# ct.filters + +This module is a collection of shader filters. +It includes [PixiJS built-in filters](https://pixijs.download/dev/docs/PIXI.filters.html) and [additional PixiJS community-authored filters](https://filters.pixijs.download/main/docs/PIXI.filters.html) (version 3.2.2, 30 december 2020). + +For each filter, you can check the PixiJS doc (links above) or use the ct.js autocomplete (to get all filters, all options of each filter, descriptions, types, and default values). + +You can also interactively play with those filters to see how they work [here](https://pixijs.io/pixi-filters/tools/demo/). + +## How it works? + +All PIXI.DisplayObject (stage, room, copy, container, etc.) have a `filters` property. +It's an `array`. +You can add/remove/enable/disable several filters on the same element (beware of performance of course). + +## How to add a filter with default options? + +```js +const fx = ct.filters.addCRT(this); +``` + +## How to add a filter with mandatory params? + +```js +// Replaces pure red with pure blue, and replaces pure green with pure white +const replacements = [ + [0xff0000, 0x0000ff], + [0x00ff00, 0xffffff], +]; +const fx = ct.filters.addMultiColorReplace(this, replacements); +``` + +## How to edit a filter? + +```js +const fx = ct.filters.addGlow(this); +fx.color = 0xff004d; +fx.innerStrength = 1; +``` + +If you type `fx.` the autocomplete will show all the available options (with description, type and default value) for that filter. + +Tip: You can `console.log` your `fx`, unfold the object and live tweak properties. + +## Example of a very interesting filter with presets/methods: + +```js +const fx = ct.filters.addColorMatrix(this); +// You can determine your own color matrix: +fx.matrix = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.5, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1]; +// Or use some nice predefined ones: +fx.night(0.1, true); +fx.polaroid(true); +fx.vintage(true); +``` + +For more (`browni`, `kodachrome`, `predator`, etc.) check the doc: +http://pixijs.download/release/docs/PIXI.filters.ColorMatrixFilter.html + +## How to enable/disable a filter? + +```js +const fx = ct.filters.addGodray(this); +fx.enabled = false; +``` + +If enabled is `true` the filter is applied, if `false` it will not. + +## How to remove a filter? + +```js +const fx = ct.filters.addCRT(this); +ct.filters.removeFilter(this, fx); +``` + +It removes a filter (`fx` in this example) from `this.filters`. + +## How to add a custom filter? + +```js +const fragment = ` +varying vec2 vTextureCoord; +uniform sampler2D uSampler; +uniform float red; +uniform float green; +uniform float blue; + +void main(void) +{ + vec4 c = texture2D(uSampler, vTextureCoord); + vec3 rgb = c.rgb; + rgb.r *= red; + rgb.g *= green; + rgb.b *= blue; + c.rgb = rgb; + gl_FragColor = c; +} +`; + +const uniforms = { +red: 1, +green: 0.8, +blue: 0.2} + +const fx = ct.filters.custom(this, undefined, fragment, uniforms); +``` +Mind, PIXI has a default vertex shader and a default fragment shader. +For more info, you can check these links: + +* https://pixijs.download/dev/docs/PIXI.Filter.html +* https://github.com/pixijs/pixi.js/wiki/v5-Creating-filters diff --git a/app/data/ct.libs/filters/includes/pixi-filters.js b/app/data/ct.libs/filters/includes/pixi-filters.js new file mode 100644 index 000000000..759f1caa8 --- /dev/null +++ b/app/data/ct.libs/filters/includes/pixi-filters.js @@ -0,0 +1,9 @@ +/*! + * pixi-filters - v3.2.2 + * Compiled Wed, 30 Dec 2020 19:07:56 UTC + * + * pixi-filters is licensed under the MIT License. + * http://www.opensource.org/licenses/mit-license + */ +var __filters=function(e,t,n,r,o,i,l,s){"use strict";var a="attribute vec2 aVertexPosition;\nattribute vec2 aTextureCoord;\n\nuniform mat3 projectionMatrix;\n\nvarying vec2 vTextureCoord;\n\nvoid main(void)\n{\n gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);\n vTextureCoord = aTextureCoord;\n}",u="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\n\nuniform float gamma;\nuniform float contrast;\nuniform float saturation;\nuniform float brightness;\nuniform float red;\nuniform float green;\nuniform float blue;\nuniform float alpha;\n\nvoid main(void)\n{\n vec4 c = texture2D(uSampler, vTextureCoord);\n\n if (c.a > 0.0) {\n c.rgb /= c.a;\n\n vec3 rgb = pow(c.rgb, vec3(1. / gamma));\n rgb = mix(vec3(.5), mix(vec3(dot(vec3(.2125, .7154, .0721), rgb)), rgb, saturation), contrast);\n rgb.r *= red;\n rgb.g *= green;\n rgb.b *= blue;\n c.rgb = rgb * brightness;\n\n c.rgb *= c.a;\n }\n\n gl_FragColor = c * alpha;\n}\n",c=function(e){function t(t){e.call(this,a,u),Object.assign(this,{gamma:1,saturation:1,contrast:1,brightness:1,red:1,green:1,blue:1,alpha:1},t)}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.apply=function(e,t,n,r){this.uniforms.gamma=Math.max(this.gamma,1e-4),this.uniforms.saturation=this.saturation,this.uniforms.contrast=this.contrast,this.uniforms.brightness=this.brightness,this.uniforms.red=this.red,this.uniforms.green=this.green,this.uniforms.blue=this.blue,this.uniforms.alpha=this.alpha,e.applyFilter(this,t,n,r)},t}(t.Filter),f=a,h="\nvarying vec2 vTextureCoord;\nuniform sampler2D uSampler;\n\nuniform vec2 uOffset;\n\nvoid main(void)\n{\n vec4 color = vec4(0.0);\n\n // Sample top left pixel\n color += texture2D(uSampler, vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y + uOffset.y));\n\n // Sample top right pixel\n color += texture2D(uSampler, vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y + uOffset.y));\n\n // Sample bottom right pixel\n color += texture2D(uSampler, vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y - uOffset.y));\n\n // Sample bottom left pixel\n color += texture2D(uSampler, vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y - uOffset.y));\n\n // Average\n color *= 0.25;\n\n gl_FragColor = color;\n}",p="\nvarying vec2 vTextureCoord;\nuniform sampler2D uSampler;\n\nuniform vec2 uOffset;\nuniform vec4 filterClamp;\n\nvoid main(void)\n{\n vec4 color = vec4(0.0);\n\n // Sample top left pixel\n color += texture2D(uSampler, clamp(vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y + uOffset.y), filterClamp.xy, filterClamp.zw));\n\n // Sample top right pixel\n color += texture2D(uSampler, clamp(vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y + uOffset.y), filterClamp.xy, filterClamp.zw));\n\n // Sample bottom right pixel\n color += texture2D(uSampler, clamp(vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y - uOffset.y), filterClamp.xy, filterClamp.zw));\n\n // Sample bottom left pixel\n color += texture2D(uSampler, clamp(vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y - uOffset.y), filterClamp.xy, filterClamp.zw));\n\n // Average\n color *= 0.25;\n\n gl_FragColor = color;\n}\n",d=function(e){function t(t,r,o){void 0===t&&(t=4),void 0===r&&(r=3),void 0===o&&(o=!1),e.call(this,f,o?p:h),this.uniforms.uOffset=new Float32Array(2),this._pixelSize=new n.Point,this.pixelSize=1,this._clamp=o,this._kernels=null,Array.isArray(t)?this.kernels=t:(this._blur=t,this.quality=r)}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var r={kernels:{configurable:!0},clamp:{configurable:!0},pixelSize:{configurable:!0},quality:{configurable:!0},blur:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){var o,i=this._pixelSize.x/t._frame.width,l=this._pixelSize.y/t._frame.height;if(1===this._quality||0===this._blur)o=this._kernels[0]+.5,this.uniforms.uOffset[0]=o*i,this.uniforms.uOffset[1]=o*l,e.applyFilter(this,t,n,r);else{for(var s,a=e.getFilterTexture(),u=t,c=a,f=this._quality-1,h=0;h0)for(var r=e,o=e/t,i=1;i0?(this._kernels=e,this._quality=e.length,this._blur=Math.max.apply(Math,e)):(this._kernels=[0],this._quality=1)},r.clamp.get=function(){return this._clamp},r.pixelSize.set=function(e){"number"==typeof e?(this._pixelSize.x=e,this._pixelSize.y=e):Array.isArray(e)?(this._pixelSize.x=e[0],this._pixelSize.y=e[1]):e instanceof n.Point?(this._pixelSize.x=e.x,this._pixelSize.y=e.y):(this._pixelSize.x=1,this._pixelSize.y=1)},r.pixelSize.get=function(){return this._pixelSize},r.quality.get=function(){return this._quality},r.quality.set=function(e){this._quality=Math.max(1,Math.round(e)),this._generateKernels()},r.blur.get=function(){return this._blur},r.blur.set=function(e){this._blur=e,this._generateKernels()},Object.defineProperties(t.prototype,r),t}(t.Filter),m=a,g="\nuniform sampler2D uSampler;\nvarying vec2 vTextureCoord;\n\nuniform float threshold;\n\nvoid main() {\n vec4 color = texture2D(uSampler, vTextureCoord);\n\n // A simple & fast algorithm for getting brightness.\n // It's inaccuracy , but good enought for this feature.\n float _max = max(max(color.r, color.g), color.b);\n float _min = min(min(color.r, color.g), color.b);\n float brightness = (_max + _min) * 0.5;\n\n if(brightness > threshold) {\n gl_FragColor = color;\n } else {\n gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);\n }\n}\n",v=function(e){function t(t){void 0===t&&(t=.5),e.call(this,m,g),this.threshold=t}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={threshold:{configurable:!0}};return n.threshold.get=function(){return this.uniforms.threshold},n.threshold.set=function(e){this.uniforms.threshold=e},Object.defineProperties(t.prototype,n),t}(t.Filter),x="uniform sampler2D uSampler;\nvarying vec2 vTextureCoord;\n\nuniform sampler2D bloomTexture;\nuniform float bloomScale;\nuniform float brightness;\n\nvoid main() {\n vec4 color = texture2D(uSampler, vTextureCoord);\n color.rgb *= brightness;\n vec4 bloomColor = vec4(texture2D(bloomTexture, vTextureCoord).rgb, 0.0);\n bloomColor.rgb *= bloomScale;\n gl_FragColor = color + bloomColor;\n}\n",y=function(e){function t(t){e.call(this,m,x),"number"==typeof t&&(t={threshold:t}),t=Object.assign({threshold:.5,bloomScale:1,brightness:1,kernels:null,blur:8,quality:4,pixelSize:1,resolution:r.settings.FILTER_RESOLUTION},t),this.bloomScale=t.bloomScale,this.brightness=t.brightness;var n=t.kernels,o=t.blur,i=t.quality,l=t.pixelSize,s=t.resolution;this._extractFilter=new v(t.threshold),this._extractFilter.resolution=s,this._blurFilter=n?new d(n):new d(o,i),this.pixelSize=l,this.resolution=s}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={resolution:{configurable:!0},threshold:{configurable:!0},kernels:{configurable:!0},blur:{configurable:!0},quality:{configurable:!0},pixelSize:{configurable:!0}};return t.prototype.apply=function(e,t,n,r,o){var i=e.getFilterTexture();this._extractFilter.apply(e,t,i,1,o);var l=e.getFilterTexture();this._blurFilter.apply(e,i,l,1,o),this.uniforms.bloomScale=this.bloomScale,this.uniforms.brightness=this.brightness,this.uniforms.bloomTexture=l,e.applyFilter(this,t,n,r),e.returnFilterTexture(l),e.returnFilterTexture(i)},n.resolution.get=function(){return this._resolution},n.resolution.set=function(e){this._resolution=e,this._extractFilter&&(this._extractFilter.resolution=e),this._blurFilter&&(this._blurFilter.resolution=e)},n.threshold.get=function(){return this._extractFilter.threshold},n.threshold.set=function(e){this._extractFilter.threshold=e},n.kernels.get=function(){return this._blurFilter.kernels},n.kernels.set=function(e){this._blurFilter.kernels=e},n.blur.get=function(){return this._blurFilter.blur},n.blur.set=function(e){this._blurFilter.blur=e},n.quality.get=function(){return this._blurFilter.quality},n.quality.set=function(e){this._blurFilter.quality=e},n.pixelSize.get=function(){return this._blurFilter.pixelSize},n.pixelSize.set=function(e){this._blurFilter.pixelSize=e},Object.defineProperties(t.prototype,n),t}(t.Filter),_=a,b="varying vec2 vTextureCoord;\n\nuniform vec4 filterArea;\nuniform float pixelSize;\nuniform sampler2D uSampler;\n\nvec2 mapCoord( vec2 coord )\n{\n coord *= filterArea.xy;\n coord += filterArea.zw;\n\n return coord;\n}\n\nvec2 unmapCoord( vec2 coord )\n{\n coord -= filterArea.zw;\n coord /= filterArea.xy;\n\n return coord;\n}\n\nvec2 pixelate(vec2 coord, vec2 size)\n{\n return floor( coord / size ) * size;\n}\n\nvec2 getMod(vec2 coord, vec2 size)\n{\n return mod( coord , size) / size;\n}\n\nfloat character(float n, vec2 p)\n{\n p = floor(p*vec2(4.0, -4.0) + 2.5);\n\n if (clamp(p.x, 0.0, 4.0) == p.x)\n {\n if (clamp(p.y, 0.0, 4.0) == p.y)\n {\n if (int(mod(n/exp2(p.x + 5.0*p.y), 2.0)) == 1) return 1.0;\n }\n }\n return 0.0;\n}\n\nvoid main()\n{\n vec2 coord = mapCoord(vTextureCoord);\n\n // get the rounded color..\n vec2 pixCoord = pixelate(coord, vec2(pixelSize));\n pixCoord = unmapCoord(pixCoord);\n\n vec4 color = texture2D(uSampler, pixCoord);\n\n // determine the character to use\n float gray = (color.r + color.g + color.b) / 3.0;\n\n float n = 65536.0; // .\n if (gray > 0.2) n = 65600.0; // :\n if (gray > 0.3) n = 332772.0; // *\n if (gray > 0.4) n = 15255086.0; // o\n if (gray > 0.5) n = 23385164.0; // &\n if (gray > 0.6) n = 15252014.0; // 8\n if (gray > 0.7) n = 13199452.0; // @\n if (gray > 0.8) n = 11512810.0; // #\n\n // get the mod..\n vec2 modd = getMod(coord, vec2(pixelSize));\n\n gl_FragColor = color * character( n, vec2(-1.0) + modd * 2.0);\n\n}\n",C=function(e){function t(t){void 0===t&&(t=8),e.call(this,_,b),this.size=t}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={size:{configurable:!0}};return n.size.get=function(){return this.uniforms.pixelSize},n.size.set=function(e){this.uniforms.pixelSize=e},Object.defineProperties(t.prototype,n),t}(t.Filter),S=a,F="precision mediump float;\n\nvarying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform vec4 filterArea;\n\nuniform float transformX;\nuniform float transformY;\nuniform vec3 lightColor;\nuniform float lightAlpha;\nuniform vec3 shadowColor;\nuniform float shadowAlpha;\n\nvoid main(void) {\n vec2 transform = vec2(1.0 / filterArea) * vec2(transformX, transformY);\n vec4 color = texture2D(uSampler, vTextureCoord);\n float light = texture2D(uSampler, vTextureCoord - transform).a;\n float shadow = texture2D(uSampler, vTextureCoord + transform).a;\n\n color.rgb = mix(color.rgb, lightColor, clamp((color.a - light) * lightAlpha, 0.0, 1.0));\n color.rgb = mix(color.rgb, shadowColor, clamp((color.a - shadow) * shadowAlpha, 0.0, 1.0));\n gl_FragColor = vec4(color.rgb * color.a, color.a);\n}\n",z=function(e){function t(t){void 0===t&&(t={}),e.call(this,S,F),this.uniforms.lightColor=new Float32Array(3),this.uniforms.shadowColor=new Float32Array(3),t=Object.assign({rotation:45,thickness:2,lightColor:16777215,lightAlpha:.7,shadowColor:0,shadowAlpha:.7},t),this.rotation=t.rotation,this.thickness=t.thickness,this.lightColor=t.lightColor,this.lightAlpha=t.lightAlpha,this.shadowColor=t.shadowColor,this.shadowAlpha=t.shadowAlpha,this.padding=1}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var r={rotation:{configurable:!0},thickness:{configurable:!0},lightColor:{configurable:!0},lightAlpha:{configurable:!0},shadowColor:{configurable:!0},shadowAlpha:{configurable:!0}};return t.prototype._updateTransform=function(){this.uniforms.transformX=this._thickness*Math.cos(this._angle),this.uniforms.transformY=this._thickness*Math.sin(this._angle)},r.rotation.get=function(){return this._angle/n.DEG_TO_RAD},r.rotation.set=function(e){this._angle=e*n.DEG_TO_RAD,this._updateTransform()},r.thickness.get=function(){return this._thickness},r.thickness.set=function(e){this._thickness=e,this._updateTransform()},r.lightColor.get=function(){return o.rgb2hex(this.uniforms.lightColor)},r.lightColor.set=function(e){o.hex2rgb(e,this.uniforms.lightColor)},r.lightAlpha.get=function(){return this.uniforms.lightAlpha},r.lightAlpha.set=function(e){this.uniforms.lightAlpha=e},r.shadowColor.get=function(){return o.rgb2hex(this.uniforms.shadowColor)},r.shadowColor.set=function(e){o.hex2rgb(e,this.uniforms.shadowColor)},r.shadowAlpha.get=function(){return this.uniforms.shadowAlpha},r.shadowAlpha.set=function(e){this.uniforms.shadowAlpha=e},Object.defineProperties(t.prototype,r),t}(t.Filter),A=function(e){function t(t,o,a,u){var c,f;void 0===t&&(t=2),void 0===o&&(o=4),void 0===a&&(a=r.settings.FILTER_RESOLUTION),void 0===u&&(u=5),e.call(this),"number"==typeof t?(c=t,f=t):t instanceof n.Point?(c=t.x,f=t.y):Array.isArray(t)&&(c=t[0],f=t[1]),this.blurXFilter=new s.BlurFilterPass(!0,c,o,a,u),this.blurYFilter=new s.BlurFilterPass(!1,f,o,a,u),this.blurYFilter.blendMode=i.BLEND_MODES.SCREEN,this.defaultFilter=new l.AlphaFilter}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var o={blur:{configurable:!0},blurX:{configurable:!0},blurY:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){var o=e.getFilterTexture();this.defaultFilter.apply(e,t,n,r),this.blurXFilter.apply(e,t,o),this.blurYFilter.apply(e,o,n,0),e.returnFilterTexture(o)},o.blur.get=function(){return this.blurXFilter.blur},o.blur.set=function(e){this.blurXFilter.blur=this.blurYFilter.blur=e},o.blurX.get=function(){return this.blurXFilter.blur},o.blurX.set=function(e){this.blurXFilter.blur=e},o.blurY.get=function(){return this.blurYFilter.blur},o.blurY.set=function(e){this.blurYFilter.blur=e},Object.defineProperties(t.prototype,o),t}(t.Filter),T=a,w="uniform float radius;\nuniform float strength;\nuniform vec2 center;\nuniform sampler2D uSampler;\nvarying vec2 vTextureCoord;\n\nuniform vec4 filterArea;\nuniform vec4 filterClamp;\nuniform vec2 dimensions;\n\nvoid main()\n{\n vec2 coord = vTextureCoord * filterArea.xy;\n coord -= center * dimensions.xy;\n float distance = length(coord);\n if (distance < radius) {\n float percent = distance / radius;\n if (strength > 0.0) {\n coord *= mix(1.0, smoothstep(0.0, radius / distance, percent), strength * 0.75);\n } else {\n coord *= mix(1.0, pow(percent, 1.0 + strength * 0.75) * radius / distance, 1.0 - percent);\n }\n }\n coord += center * dimensions.xy;\n coord /= filterArea.xy;\n vec2 clampedCoord = clamp(coord, filterClamp.xy, filterClamp.zw);\n vec4 color = texture2D(uSampler, clampedCoord);\n if (coord != clampedCoord) {\n color *= max(0.0, 1.0 - length(coord - clampedCoord));\n }\n\n gl_FragColor = color;\n}\n",O=function(e){function t(t){if(e.call(this,T,w),"object"!=typeof t){var n=arguments[0],r=arguments[1],o=arguments[2];t={},void 0!==n&&(t.center=n),void 0!==r&&(t.radius=r),void 0!==o&&(t.strength=o)}this.uniforms.dimensions=new Float32Array(2),Object.assign(this,{center:[.5,.5],radius:100,strength:1},t)}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={radius:{configurable:!0},strength:{configurable:!0},center:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){this.uniforms.dimensions[0]=t.filterFrame.width,this.uniforms.dimensions[1]=t.filterFrame.height,e.applyFilter(this,t,n,r)},n.radius.get=function(){return this.uniforms.radius},n.radius.set=function(e){this.uniforms.radius=e},n.strength.get=function(){return this.uniforms.strength},n.strength.set=function(e){this.uniforms.strength=e},n.center.get=function(){return this.uniforms.center},n.center.set=function(e){this.uniforms.center=e},Object.defineProperties(t.prototype,n),t}(t.Filter),D=a,P="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform sampler2D colorMap;\nuniform float _mix;\nuniform float _size;\nuniform float _sliceSize;\nuniform float _slicePixelSize;\nuniform float _sliceInnerSize;\nvoid main() {\n vec4 color = texture2D(uSampler, vTextureCoord.xy);\n\n vec4 adjusted;\n if (color.a > 0.0) {\n color.rgb /= color.a;\n float innerWidth = _size - 1.0;\n float zSlice0 = min(floor(color.b * innerWidth), innerWidth);\n float zSlice1 = min(zSlice0 + 1.0, innerWidth);\n float xOffset = _slicePixelSize * 0.5 + color.r * _sliceInnerSize;\n float s0 = xOffset + (zSlice0 * _sliceSize);\n float s1 = xOffset + (zSlice1 * _sliceSize);\n float yOffset = _sliceSize * 0.5 + color.g * (1.0 - _sliceSize);\n vec4 slice0Color = texture2D(colorMap, vec2(s0,yOffset));\n vec4 slice1Color = texture2D(colorMap, vec2(s1,yOffset));\n float zOffset = fract(color.b * innerWidth);\n adjusted = mix(slice0Color, slice1Color, zOffset);\n\n color.rgb *= color.a;\n }\n gl_FragColor = vec4(mix(color, adjusted, _mix).rgb, color.a);\n\n}",M=function(e){function n(t,n,r){void 0===n&&(n=!1),void 0===r&&(r=1),e.call(this,D,P),this._size=0,this._sliceSize=0,this._slicePixelSize=0,this._sliceInnerSize=0,this._scaleMode=null,this._nearest=!1,this.nearest=n,this.mix=r,this.colorMap=t}e&&(n.__proto__=e),n.prototype=Object.create(e&&e.prototype),n.prototype.constructor=n;var r={colorSize:{configurable:!0},colorMap:{configurable:!0},nearest:{configurable:!0}};return n.prototype.apply=function(e,t,n,r){this.uniforms._mix=this.mix,e.applyFilter(this,t,n,r)},r.colorSize.get=function(){return this._size},r.colorMap.get=function(){return this._colorMap},r.colorMap.set=function(e){e instanceof t.Texture||(e=t.Texture.from(e)),e&&e.baseTexture&&(e.baseTexture.scaleMode=this._scaleMode,e.baseTexture.mipmap=!1,this._size=e.height,this._sliceSize=1/this._size,this._slicePixelSize=this._sliceSize/this._size,this._sliceInnerSize=this._slicePixelSize*(this._size-1),this.uniforms._size=this._size,this.uniforms._sliceSize=this._sliceSize,this.uniforms._slicePixelSize=this._slicePixelSize,this.uniforms._sliceInnerSize=this._sliceInnerSize,this.uniforms.colorMap=e),this._colorMap=e},r.nearest.get=function(){return this._nearest},r.nearest.set=function(e){this._nearest=e,this._scaleMode=e?i.SCALE_MODES.NEAREST:i.SCALE_MODES.LINEAR;var t=this._colorMap;t&&t.baseTexture&&(t.baseTexture._glTextures={},t.baseTexture.scaleMode=this._scaleMode,t.baseTexture.mipmap=!1,t._updateID++,t.baseTexture.emit("update",t.baseTexture))},n.prototype.updateColorMap=function(){var e=this._colorMap;e&&e.baseTexture&&(e._updateID++,e.baseTexture.emit("update",e.baseTexture),this.colorMap=e)},n.prototype.destroy=function(t){this._colorMap&&this._colorMap.destroy(t),e.prototype.destroy.call(this)},Object.defineProperties(n.prototype,r),n}(t.Filter),R=a,k="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform vec3 color;\nvoid main(void) {\n vec4 currentColor = texture2D(uSampler, vTextureCoord);\n vec3 colorOverlay = color * currentColor.a;\n gl_FragColor = vec4(colorOverlay.r, colorOverlay.g, colorOverlay.b, currentColor.a);\n}\n",j=function(e){function t(t){void 0===t&&(t=0),e.call(this,R,k),this.uniforms.color=new Float32Array(3),this.color=t}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={color:{configurable:!0}};return n.color.set=function(e){var t=this.uniforms.color;"number"==typeof e?(o.hex2rgb(e,t),this._color=e):(t[0]=e[0],t[1]=e[1],t[2]=e[2],this._color=o.rgb2hex(t))},n.color.get=function(){return this._color},Object.defineProperties(t.prototype,n),t}(t.Filter),E=a,L="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform vec3 originalColor;\nuniform vec3 newColor;\nuniform float epsilon;\nvoid main(void) {\n vec4 currentColor = texture2D(uSampler, vTextureCoord);\n vec3 colorDiff = originalColor - (currentColor.rgb / max(currentColor.a, 0.0000000001));\n float colorDistance = length(colorDiff);\n float doReplace = step(colorDistance, epsilon);\n gl_FragColor = vec4(mix(currentColor.rgb, (newColor + colorDiff) * currentColor.a, doReplace), currentColor.a);\n}\n",I=function(e){function t(t,n,r){void 0===t&&(t=16711680),void 0===n&&(n=0),void 0===r&&(r=.4),e.call(this,E,L),this.uniforms.originalColor=new Float32Array(3),this.uniforms.newColor=new Float32Array(3),this.originalColor=t,this.newColor=n,this.epsilon=r}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={originalColor:{configurable:!0},newColor:{configurable:!0},epsilon:{configurable:!0}};return n.originalColor.set=function(e){var t=this.uniforms.originalColor;"number"==typeof e?(o.hex2rgb(e,t),this._originalColor=e):(t[0]=e[0],t[1]=e[1],t[2]=e[2],this._originalColor=o.rgb2hex(t))},n.originalColor.get=function(){return this._originalColor},n.newColor.set=function(e){var t=this.uniforms.newColor;"number"==typeof e?(o.hex2rgb(e,t),this._newColor=e):(t[0]=e[0],t[1]=e[1],t[2]=e[2],this._newColor=o.rgb2hex(t))},n.newColor.get=function(){return this._newColor},n.epsilon.set=function(e){this.uniforms.epsilon=e},n.epsilon.get=function(){return this.uniforms.epsilon},Object.defineProperties(t.prototype,n),t}(t.Filter),X=a,B="precision mediump float;\n\nvarying mediump vec2 vTextureCoord;\n\nuniform sampler2D uSampler;\nuniform vec2 texelSize;\nuniform float matrix[9];\n\nvoid main(void)\n{\n vec4 c11 = texture2D(uSampler, vTextureCoord - texelSize); // top left\n vec4 c12 = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - texelSize.y)); // top center\n vec4 c13 = texture2D(uSampler, vec2(vTextureCoord.x + texelSize.x, vTextureCoord.y - texelSize.y)); // top right\n\n vec4 c21 = texture2D(uSampler, vec2(vTextureCoord.x - texelSize.x, vTextureCoord.y)); // mid left\n vec4 c22 = texture2D(uSampler, vTextureCoord); // mid center\n vec4 c23 = texture2D(uSampler, vec2(vTextureCoord.x + texelSize.x, vTextureCoord.y)); // mid right\n\n vec4 c31 = texture2D(uSampler, vec2(vTextureCoord.x - texelSize.x, vTextureCoord.y + texelSize.y)); // bottom left\n vec4 c32 = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + texelSize.y)); // bottom center\n vec4 c33 = texture2D(uSampler, vTextureCoord + texelSize); // bottom right\n\n gl_FragColor =\n c11 * matrix[0] + c12 * matrix[1] + c13 * matrix[2] +\n c21 * matrix[3] + c22 * matrix[4] + c23 * matrix[5] +\n c31 * matrix[6] + c32 * matrix[7] + c33 * matrix[8];\n\n gl_FragColor.a = c22.a;\n}\n",N=function(e){function t(t,n,r){void 0===n&&(n=200),void 0===r&&(r=200),e.call(this,X,B),this.uniforms.texelSize=new Float32Array(2),this.uniforms.matrix=new Float32Array(9),void 0!==t&&(this.matrix=t),this.width=n,this.height=r}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={matrix:{configurable:!0},width:{configurable:!0},height:{configurable:!0}};return n.matrix.get=function(){return this.uniforms.matrix},n.matrix.set=function(e){var t=this;e.forEach(function(e,n){return t.uniforms.matrix[n]=e})},n.width.get=function(){return 1/this.uniforms.texelSize[0]},n.width.set=function(e){this.uniforms.texelSize[0]=1/e},n.height.get=function(){return 1/this.uniforms.texelSize[1]},n.height.set=function(e){this.uniforms.texelSize[1]=1/e},Object.defineProperties(t.prototype,n),t}(t.Filter),G=a,q="precision mediump float;\n\nvarying vec2 vTextureCoord;\n\nuniform sampler2D uSampler;\n\nvoid main(void)\n{\n float lum = length(texture2D(uSampler, vTextureCoord.xy).rgb);\n\n gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n\n if (lum < 1.00)\n {\n if (mod(gl_FragCoord.x + gl_FragCoord.y, 10.0) == 0.0)\n {\n gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n }\n }\n\n if (lum < 0.75)\n {\n if (mod(gl_FragCoord.x - gl_FragCoord.y, 10.0) == 0.0)\n {\n gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n }\n }\n\n if (lum < 0.50)\n {\n if (mod(gl_FragCoord.x + gl_FragCoord.y - 5.0, 10.0) == 0.0)\n {\n gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n }\n }\n\n if (lum < 0.3)\n {\n if (mod(gl_FragCoord.x - gl_FragCoord.y - 5.0, 10.0) == 0.0)\n {\n gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n }\n }\n}\n",K=function(e){function t(){e.call(this,G,q)}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t}(t.Filter),W=a,Y="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\n\nuniform vec4 filterArea;\nuniform vec2 dimensions;\n\nconst float SQRT_2 = 1.414213;\n\nconst float light = 1.0;\n\nuniform float curvature;\nuniform float lineWidth;\nuniform float lineContrast;\nuniform bool verticalLine;\nuniform float noise;\nuniform float noiseSize;\n\nuniform float vignetting;\nuniform float vignettingAlpha;\nuniform float vignettingBlur;\n\nuniform float seed;\nuniform float time;\n\nfloat rand(vec2 co) {\n return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);\n}\n\nvoid main(void)\n{\n vec2 pixelCoord = vTextureCoord.xy * filterArea.xy;\n vec2 dir = vec2(vTextureCoord.xy - vec2(0.5, 0.5)) * filterArea.xy / dimensions;\n\n gl_FragColor = texture2D(uSampler, vTextureCoord);\n vec3 rgb = gl_FragColor.rgb;\n\n if (noise > 0.0 && noiseSize > 0.0)\n {\n pixelCoord.x = floor(pixelCoord.x / noiseSize);\n pixelCoord.y = floor(pixelCoord.y / noiseSize);\n float _noise = rand(pixelCoord * noiseSize * seed) - 0.5;\n rgb += _noise * noise;\n }\n\n if (lineWidth > 0.0)\n {\n float _c = curvature > 0. ? curvature : 1.;\n float k = curvature > 0. ?(length(dir * dir) * 0.25 * _c * _c + 0.935 * _c) : 1.;\n vec2 uv = dir * k;\n\n float v = (verticalLine ? uv.x * dimensions.x : uv.y * dimensions.y) * min(1.0, 2.0 / lineWidth ) / _c;\n float j = 1. + cos(v * 1.2 - time) * 0.5 * lineContrast;\n rgb *= j;\n float segment = verticalLine ? mod((dir.x + .5) * dimensions.x, 4.) : mod((dir.y + .5) * dimensions.y, 4.);\n rgb *= 0.99 + ceil(segment) * 0.015;\n }\n\n if (vignetting > 0.0)\n {\n float outter = SQRT_2 - vignetting * SQRT_2;\n float darker = clamp((outter - length(dir) * SQRT_2) / ( 0.00001 + vignettingBlur * SQRT_2), 0.0, 1.0);\n rgb *= darker + (1.0 - darker) * (1.0 - vignettingAlpha);\n }\n\n gl_FragColor.rgb = rgb;\n}\n",Z=function(e){function t(t){e.call(this,W,Y),this.uniforms.dimensions=new Float32Array(2),this.time=0,this.seed=0,Object.assign(this,{curvature:1,lineWidth:1,lineContrast:.25,verticalLine:!1,noise:0,noiseSize:1,seed:0,vignetting:.3,vignettingAlpha:1,vignettingBlur:.3,time:0},t)}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={curvature:{configurable:!0},lineWidth:{configurable:!0},lineContrast:{configurable:!0},verticalLine:{configurable:!0},noise:{configurable:!0},noiseSize:{configurable:!0},vignetting:{configurable:!0},vignettingAlpha:{configurable:!0},vignettingBlur:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){this.uniforms.dimensions[0]=t.filterFrame.width,this.uniforms.dimensions[1]=t.filterFrame.height,this.uniforms.seed=this.seed,this.uniforms.time=this.time,e.applyFilter(this,t,n,r)},n.curvature.set=function(e){this.uniforms.curvature=e},n.curvature.get=function(){return this.uniforms.curvature},n.lineWidth.set=function(e){this.uniforms.lineWidth=e},n.lineWidth.get=function(){return this.uniforms.lineWidth},n.lineContrast.set=function(e){this.uniforms.lineContrast=e},n.lineContrast.get=function(){return this.uniforms.lineContrast},n.verticalLine.set=function(e){this.uniforms.verticalLine=e},n.verticalLine.get=function(){return this.uniforms.verticalLine},n.noise.set=function(e){this.uniforms.noise=e},n.noise.get=function(){return this.uniforms.noise},n.noiseSize.set=function(e){this.uniforms.noiseSize=e},n.noiseSize.get=function(){return this.uniforms.noiseSize},n.vignetting.set=function(e){this.uniforms.vignetting=e},n.vignetting.get=function(){return this.uniforms.vignetting},n.vignettingAlpha.set=function(e){this.uniforms.vignettingAlpha=e},n.vignettingAlpha.get=function(){return this.uniforms.vignettingAlpha},n.vignettingBlur.set=function(e){this.uniforms.vignettingBlur=e},n.vignettingBlur.get=function(){return this.uniforms.vignettingBlur},Object.defineProperties(t.prototype,n),t}(t.Filter),Q=a,U="precision mediump float;\n\nvarying vec2 vTextureCoord;\nvarying vec4 vColor;\n\nuniform vec4 filterArea;\nuniform sampler2D uSampler;\n\nuniform float angle;\nuniform float scale;\n\nfloat pattern()\n{\n float s = sin(angle), c = cos(angle);\n vec2 tex = vTextureCoord * filterArea.xy;\n vec2 point = vec2(\n c * tex.x - s * tex.y,\n s * tex.x + c * tex.y\n ) * scale;\n return (sin(point.x) * sin(point.y)) * 4.0;\n}\n\nvoid main()\n{\n vec4 color = texture2D(uSampler, vTextureCoord);\n float average = (color.r + color.g + color.b) / 3.0;\n gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\n}\n",V=function(e){function t(t,n){void 0===t&&(t=1),void 0===n&&(n=5),e.call(this,Q,U),this.scale=t,this.angle=n}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={scale:{configurable:!0},angle:{configurable:!0}};return n.scale.get=function(){return this.uniforms.scale},n.scale.set=function(e){this.uniforms.scale=e},n.angle.get=function(){return this.uniforms.angle},n.angle.set=function(e){this.uniforms.angle=e},Object.defineProperties(t.prototype,n),t}(t.Filter),H=a,$="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform float alpha;\nuniform vec3 color;\n\nuniform vec2 shift;\nuniform vec4 inputSize;\n\nvoid main(void){\n vec4 sample = texture2D(uSampler, vTextureCoord - shift * inputSize.zw);\n\n // Premultiply alpha\n sample.rgb = color.rgb * sample.a;\n\n // alpha user alpha\n sample *= alpha;\n\n gl_FragColor = sample;\n}",J=function(e){function t(t){t&&t.constructor!==Object&&(console.warn("DropShadowFilter now uses options instead of (rotation, distance, blur, color, alpha)"),t={rotation:t},void 0!==arguments[1]&&(t.distance=arguments[1]),void 0!==arguments[2]&&(t.blur=arguments[2]),void 0!==arguments[3]&&(t.color=arguments[3]),void 0!==arguments[4]&&(t.alpha=arguments[4])),t=Object.assign({rotation:45,distance:5,color:0,alpha:.5,shadowOnly:!1,kernels:null,blur:2,quality:3,pixelSize:1,resolution:r.settings.FILTER_RESOLUTION},t),e.call(this);var o=t.kernels,i=t.blur,l=t.quality,s=t.pixelSize,a=t.resolution;this._tintFilter=new e(H,$),this._tintFilter.uniforms.color=new Float32Array(4),this._tintFilter.uniforms.shift=new n.Point,this._tintFilter.resolution=a,this._blurFilter=o?new d(o):new d(i,l),this.pixelSize=s,this.resolution=a;var u=t.shadowOnly,c=t.rotation,f=t.distance,h=t.alpha,p=t.color;this.shadowOnly=u,this.rotation=c,this.distance=f,this.alpha=h,this.color=p,this._updatePadding()}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var i={resolution:{configurable:!0},distance:{configurable:!0},rotation:{configurable:!0},alpha:{configurable:!0},color:{configurable:!0},kernels:{configurable:!0},blur:{configurable:!0},quality:{configurable:!0},pixelSize:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){var o=e.getFilterTexture();this._tintFilter.apply(e,t,o,1),this._blurFilter.apply(e,o,n,r),!0!==this.shadowOnly&&e.applyFilter(this,t,n,0),e.returnFilterTexture(o)},t.prototype._updatePadding=function(){this.padding=this.distance+2*this.blur},t.prototype._updateShift=function(){this._tintFilter.uniforms.shift.set(this.distance*Math.cos(this.angle),this.distance*Math.sin(this.angle))},i.resolution.get=function(){return this._resolution},i.resolution.set=function(e){this._resolution=e,this._tintFilter&&(this._tintFilter.resolution=e),this._blurFilter&&(this._blurFilter.resolution=e)},i.distance.get=function(){return this._distance},i.distance.set=function(e){this._distance=e,this._updatePadding(),this._updateShift()},i.rotation.get=function(){return this.angle/n.DEG_TO_RAD},i.rotation.set=function(e){this.angle=e*n.DEG_TO_RAD,this._updateShift()},i.alpha.get=function(){return this._tintFilter.uniforms.alpha},i.alpha.set=function(e){this._tintFilter.uniforms.alpha=e},i.color.get=function(){return o.rgb2hex(this._tintFilter.uniforms.color)},i.color.set=function(e){o.hex2rgb(e,this._tintFilter.uniforms.color)},i.kernels.get=function(){return this._blurFilter.kernels},i.kernels.set=function(e){this._blurFilter.kernels=e},i.blur.get=function(){return this._blurFilter.blur},i.blur.set=function(e){this._blurFilter.blur=e,this._updatePadding()},i.quality.get=function(){return this._blurFilter.quality},i.quality.set=function(e){this._blurFilter.quality=e},i.pixelSize.get=function(){return this._blurFilter.pixelSize},i.pixelSize.set=function(e){this._blurFilter.pixelSize=e},Object.defineProperties(t.prototype,i),t}(t.Filter),ee=a,te="precision mediump float;\n\nvarying vec2 vTextureCoord;\n\nuniform sampler2D uSampler;\nuniform float strength;\nuniform vec4 filterArea;\n\n\nvoid main(void)\n{\n\tvec2 onePixel = vec2(1.0 / filterArea);\n\n\tvec4 color;\n\n\tcolor.rgb = vec3(0.5);\n\n\tcolor -= texture2D(uSampler, vTextureCoord - onePixel) * strength;\n\tcolor += texture2D(uSampler, vTextureCoord + onePixel) * strength;\n\n\tcolor.rgb = vec3((color.r + color.g + color.b) / 3.0);\n\n\tfloat alpha = texture2D(uSampler, vTextureCoord).a;\n\n\tgl_FragColor = vec4(color.rgb * alpha, alpha);\n}\n",ne=function(e){function t(t){void 0===t&&(t=5),e.call(this,ee,te),this.strength=t}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={strength:{configurable:!0}};return n.strength.get=function(){return this.uniforms.strength},n.strength.set=function(e){this.uniforms.strength=e},Object.defineProperties(t.prototype,n),t}(t.Filter),re=a,oe="// precision highp float;\n\nvarying vec2 vTextureCoord;\nuniform sampler2D uSampler;\n\nuniform vec4 filterArea;\nuniform vec4 filterClamp;\nuniform vec2 dimensions;\nuniform float aspect;\n\nuniform sampler2D displacementMap;\nuniform float offset;\nuniform float sinDir;\nuniform float cosDir;\nuniform int fillMode;\n\nuniform float seed;\nuniform vec2 red;\nuniform vec2 green;\nuniform vec2 blue;\n\nconst int TRANSPARENT = 0;\nconst int ORIGINAL = 1;\nconst int LOOP = 2;\nconst int CLAMP = 3;\nconst int MIRROR = 4;\n\nvoid main(void)\n{\n vec2 coord = (vTextureCoord * filterArea.xy) / dimensions;\n\n if (coord.x > 1.0 || coord.y > 1.0) {\n return;\n }\n\n float cx = coord.x - 0.5;\n float cy = (coord.y - 0.5) * aspect;\n float ny = (-sinDir * cx + cosDir * cy) / aspect + 0.5;\n\n // displacementMap: repeat\n // ny = ny > 1.0 ? ny - 1.0 : (ny < 0.0 ? 1.0 + ny : ny);\n\n // displacementMap: mirror\n ny = ny > 1.0 ? 2.0 - ny : (ny < 0.0 ? -ny : ny);\n\n vec4 dc = texture2D(displacementMap, vec2(0.5, ny));\n\n float displacement = (dc.r - dc.g) * (offset / filterArea.x);\n\n coord = vTextureCoord + vec2(cosDir * displacement, sinDir * displacement * aspect);\n\n if (fillMode == CLAMP) {\n coord = clamp(coord, filterClamp.xy, filterClamp.zw);\n } else {\n if( coord.x > filterClamp.z ) {\n if (fillMode == TRANSPARENT) {\n discard;\n } else if (fillMode == LOOP) {\n coord.x -= filterClamp.z;\n } else if (fillMode == MIRROR) {\n coord.x = filterClamp.z * 2.0 - coord.x;\n }\n } else if( coord.x < filterClamp.x ) {\n if (fillMode == TRANSPARENT) {\n discard;\n } else if (fillMode == LOOP) {\n coord.x += filterClamp.z;\n } else if (fillMode == MIRROR) {\n coord.x *= -filterClamp.z;\n }\n }\n\n if( coord.y > filterClamp.w ) {\n if (fillMode == TRANSPARENT) {\n discard;\n } else if (fillMode == LOOP) {\n coord.y -= filterClamp.w;\n } else if (fillMode == MIRROR) {\n coord.y = filterClamp.w * 2.0 - coord.y;\n }\n } else if( coord.y < filterClamp.y ) {\n if (fillMode == TRANSPARENT) {\n discard;\n } else if (fillMode == LOOP) {\n coord.y += filterClamp.w;\n } else if (fillMode == MIRROR) {\n coord.y *= -filterClamp.w;\n }\n }\n }\n\n gl_FragColor.r = texture2D(uSampler, coord + red * (1.0 - seed * 0.4) / filterArea.xy).r;\n gl_FragColor.g = texture2D(uSampler, coord + green * (1.0 - seed * 0.3) / filterArea.xy).g;\n gl_FragColor.b = texture2D(uSampler, coord + blue * (1.0 - seed * 0.2) / filterArea.xy).b;\n gl_FragColor.a = texture2D(uSampler, coord).a;\n}\n",ie=function(e){function r(n){void 0===n&&(n={}),e.call(this,re,oe),this.uniforms.dimensions=new Float32Array(2),n=Object.assign({slices:5,offset:100,direction:0,fillMode:0,average:!1,seed:0,red:[0,0],green:[0,0],blue:[0,0],minSize:8,sampleSize:512},n),this.direction=n.direction,this.red=n.red,this.green=n.green,this.blue=n.blue,this.offset=n.offset,this.fillMode=n.fillMode,this.average=n.average,this.seed=n.seed,this.minSize=n.minSize,this.sampleSize=n.sampleSize,this._canvas=document.createElement("canvas"),this._canvas.width=4,this._canvas.height=this.sampleSize,this.texture=t.Texture.from(this._canvas,{scaleMode:i.SCALE_MODES.NEAREST}),this._slices=0,this.slices=n.slices}e&&(r.__proto__=e),r.prototype=Object.create(e&&e.prototype),r.prototype.constructor=r;var o={sizes:{configurable:!0},offsets:{configurable:!0},slices:{configurable:!0},direction:{configurable:!0},red:{configurable:!0},green:{configurable:!0},blue:{configurable:!0}};return r.prototype.apply=function(e,t,n,r){var o=t.filterFrame.width,i=t.filterFrame.height;this.uniforms.dimensions[0]=o,this.uniforms.dimensions[1]=i,this.uniforms.aspect=i/o,this.uniforms.seed=this.seed,this.uniforms.offset=this.offset,this.uniforms.fillMode=this.fillMode,e.applyFilter(this,t,n,r)},r.prototype._randomizeSizes=function(){var e=this._sizes,t=this._slices-1,n=this.sampleSize,r=Math.min(this.minSize/n,.9/this._slices);if(this.average){for(var o=this._slices,i=1,l=0;l0;t--){var n=Math.random()*t>>0,r=e[t];e[t]=e[n],e[n]=r}},r.prototype._randomizeOffsets=function(){for(var e=0;e0?e:0,a=e<0?-e:0;r.fillStyle="rgba("+s+", "+a+", 0, 1)",r.fillRect(0,o>>0,t,l+1>>0),o+=l}n.baseTexture.update(),this.uniforms.displacementMap=n},o.sizes.set=function(e){for(var t=Math.min(this._slices,e.length),n=0;nthis._maxColors)throw"Length of replacements ("+r+") exceeds the maximum colors length ("+this._maxColors+")";t[3*r]=-1;for(var i=0;i 0.5) then: 1 - 2 * (1 - dst) * (1 - src)\n return vec3((dst.x <= 0.5) ? (2.0 * src.x * dst.x) : (1.0 - 2.0 * (1.0 - dst.x) * (1.0 - src.x)),\n (dst.y <= 0.5) ? (2.0 * src.y * dst.y) : (1.0 - 2.0 * (1.0 - dst.y) * (1.0 - src.y)),\n (dst.z <= 0.5) ? (2.0 * src.z * dst.z) : (1.0 - 2.0 * (1.0 - dst.z) * (1.0 - src.z)));\n}\n\n\nvoid main()\n{\n gl_FragColor = texture2D(uSampler, vTextureCoord);\n vec3 color = gl_FragColor.rgb;\n\n if (sepia > 0.0)\n {\n float gray = (color.x + color.y + color.z) / 3.0;\n vec3 grayscale = vec3(gray);\n\n color = Overlay(SEPIA_RGB, grayscale);\n\n color = grayscale + sepia * (color - grayscale);\n }\n\n vec2 coord = vTextureCoord * filterArea.xy / dimensions.xy;\n\n if (vignetting > 0.0)\n {\n float outter = SQRT_2 - vignetting * SQRT_2;\n vec2 dir = vec2(vec2(0.5, 0.5) - coord);\n dir.y *= dimensions.y / dimensions.x;\n float darker = clamp((outter - length(dir) * SQRT_2) / ( 0.00001 + vignettingBlur * SQRT_2), 0.0, 1.0);\n color.rgb *= darker + (1.0 - darker) * (1.0 - vignettingAlpha);\n }\n\n if (scratchDensity > seed && scratch != 0.0)\n {\n float phase = seed * 256.0;\n float s = mod(floor(phase), 2.0);\n float dist = 1.0 / scratchDensity;\n float d = distance(coord, vec2(seed * dist, abs(s - seed * dist)));\n if (d < seed * 0.6 + 0.4)\n {\n highp float period = scratchDensity * 10.0;\n\n float xx = coord.x * period + phase;\n float aa = abs(mod(xx, 0.5) * 4.0);\n float bb = mod(floor(xx / 0.5), 2.0);\n float yy = (1.0 - bb) * aa + bb * (2.0 - aa);\n\n float kk = 2.0 * period;\n float dw = scratchWidth / dimensions.x * (0.75 + seed);\n float dh = dw * kk;\n\n float tine = (yy - (2.0 - dh));\n\n if (tine > 0.0) {\n float _sign = sign(scratch);\n\n tine = s * tine / period + scratch + 0.1;\n tine = clamp(tine + 1.0, 0.5 + _sign * 0.5, 1.5 + _sign * 0.5);\n\n color.rgb *= tine;\n }\n }\n }\n\n if (noise > 0.0 && noiseSize > 0.0)\n {\n vec2 pixelCoord = vTextureCoord.xy * filterArea.xy;\n pixelCoord.x = floor(pixelCoord.x / noiseSize);\n pixelCoord.y = floor(pixelCoord.y / noiseSize);\n // vec2 d = pixelCoord * noiseSize * vec2(1024.0 + seed * 512.0, 1024.0 - seed * 512.0);\n // float _noise = snoise(d) * 0.5;\n float _noise = rand(pixelCoord * noiseSize * seed) - 0.5;\n color += _noise * noise;\n }\n\n gl_FragColor.rgb = color;\n}\n",be=function(e){function t(t,n){void 0===n&&(n=0),e.call(this,ye,_e),this.uniforms.dimensions=new Float32Array(2),"number"==typeof t?(this.seed=t,t=null):this.seed=n,Object.assign(this,{sepia:.3,noise:.3,noiseSize:1,scratch:.5,scratchDensity:.3,scratchWidth:1,vignetting:.3,vignettingAlpha:1,vignettingBlur:.3},t)}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={sepia:{configurable:!0},noise:{configurable:!0},noiseSize:{configurable:!0},scratch:{configurable:!0},scratchDensity:{configurable:!0},scratchWidth:{configurable:!0},vignetting:{configurable:!0},vignettingAlpha:{configurable:!0},vignettingBlur:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){this.uniforms.dimensions[0]=t.filterFrame.width,this.uniforms.dimensions[1]=t.filterFrame.height,this.uniforms.seed=this.seed,e.applyFilter(this,t,n,r)},n.sepia.set=function(e){this.uniforms.sepia=e},n.sepia.get=function(){return this.uniforms.sepia},n.noise.set=function(e){this.uniforms.noise=e},n.noise.get=function(){return this.uniforms.noise},n.noiseSize.set=function(e){this.uniforms.noiseSize=e},n.noiseSize.get=function(){return this.uniforms.noiseSize},n.scratch.set=function(e){this.uniforms.scratch=e},n.scratch.get=function(){return this.uniforms.scratch},n.scratchDensity.set=function(e){this.uniforms.scratchDensity=e},n.scratchDensity.get=function(){return this.uniforms.scratchDensity},n.scratchWidth.set=function(e){this.uniforms.scratchWidth=e},n.scratchWidth.get=function(){return this.uniforms.scratchWidth},n.vignetting.set=function(e){this.uniforms.vignetting=e},n.vignetting.get=function(){return this.uniforms.vignetting},n.vignettingAlpha.set=function(e){this.uniforms.vignettingAlpha=e},n.vignettingAlpha.get=function(){return this.uniforms.vignettingAlpha},n.vignettingBlur.set=function(e){this.uniforms.vignettingBlur=e},n.vignettingBlur.get=function(){return this.uniforms.vignettingBlur},Object.defineProperties(t.prototype,n),t}(t.Filter),Ce=a,Se="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\n\nuniform vec2 thickness;\nuniform vec4 outlineColor;\nuniform vec4 filterClamp;\n\nconst float DOUBLE_PI = 3.14159265358979323846264 * 2.;\n\nvoid main(void) {\n vec4 ownColor = texture2D(uSampler, vTextureCoord);\n vec4 curColor;\n float maxAlpha = 0.;\n vec2 displaced;\n for (float angle = 0.; angle <= DOUBLE_PI; angle += ${angleStep}) {\n displaced.x = vTextureCoord.x + thickness.x * cos(angle);\n displaced.y = vTextureCoord.y + thickness.y * sin(angle);\n curColor = texture2D(uSampler, clamp(displaced, filterClamp.xy, filterClamp.zw));\n maxAlpha = max(maxAlpha, curColor.a);\n }\n float resultAlpha = max(maxAlpha, ownColor.a);\n gl_FragColor = vec4((ownColor.rgb + outlineColor.rgb * (1. - ownColor.a)) * resultAlpha, resultAlpha);\n}\n",Fe=function(e){function t(n,r,o){void 0===n&&(n=1),void 0===r&&(r=0),void 0===o&&(o=.1);var i=Math.max(o*t.MAX_SAMPLES,t.MIN_SAMPLES),l=(2*Math.PI/i).toFixed(7);e.call(this,Ce,Se.replace(/\$\{angleStep\}/,l)),this.uniforms.thickness=new Float32Array([0,0]),this.uniforms.outlineColor=new Float32Array([0,0,0,1]),Object.assign(this,{thickness:n,color:r,quality:o})}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={color:{configurable:!0},thickness:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){this.uniforms.thickness[0]=this._thickness/t._frame.width,this.uniforms.thickness[1]=this._thickness/t._frame.height,e.applyFilter(this,t,n,r)},n.color.get=function(){return o.rgb2hex(this.uniforms.outlineColor)},n.color.set=function(e){o.hex2rgb(e,this.uniforms.outlineColor)},n.thickness.get=function(){return this._thickness},n.thickness.set=function(e){this._thickness=e,this.padding=e},Object.defineProperties(t.prototype,n),t}(t.Filter);Fe.MIN_SAMPLES=1,Fe.MAX_SAMPLES=100;var ze=a,Ae="precision mediump float;\n\nvarying vec2 vTextureCoord;\n\nuniform vec2 size;\nuniform sampler2D uSampler;\n\nuniform vec4 filterArea;\n\nvec2 mapCoord( vec2 coord )\n{\n coord *= filterArea.xy;\n coord += filterArea.zw;\n\n return coord;\n}\n\nvec2 unmapCoord( vec2 coord )\n{\n coord -= filterArea.zw;\n coord /= filterArea.xy;\n\n return coord;\n}\n\nvec2 pixelate(vec2 coord, vec2 size)\n{\n\treturn floor( coord / size ) * size;\n}\n\nvoid main(void)\n{\n vec2 coord = mapCoord(vTextureCoord);\n\n coord = pixelate(coord, size);\n\n coord = unmapCoord(coord);\n\n gl_FragColor = texture2D(uSampler, coord);\n}\n",Te=function(e){function t(t){void 0===t&&(t=10),e.call(this,ze,Ae),this.size=t}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={size:{configurable:!0}};return n.size.get=function(){return this.uniforms.size},n.size.set=function(e){"number"==typeof e&&(e=[e,e]),this.uniforms.size=e},Object.defineProperties(t.prototype,n),t}(t.Filter),we=a,Oe="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform vec4 filterArea;\n\nuniform float uRadian;\nuniform vec2 uCenter;\nuniform float uRadius;\nuniform int uKernelSize;\n\nconst int MAX_KERNEL_SIZE = 2048;\n\nvoid main(void)\n{\n vec4 color = texture2D(uSampler, vTextureCoord);\n\n if (uKernelSize == 0)\n {\n gl_FragColor = color;\n return;\n }\n\n float aspect = filterArea.y / filterArea.x;\n vec2 center = uCenter.xy / filterArea.xy;\n float gradient = uRadius / filterArea.x * 0.3;\n float radius = uRadius / filterArea.x - gradient * 0.5;\n int k = uKernelSize - 1;\n\n vec2 coord = vTextureCoord;\n vec2 dir = vec2(center - coord);\n float dist = length(vec2(dir.x, dir.y * aspect));\n\n float radianStep = uRadian;\n if (radius >= 0.0 && dist > radius) {\n float delta = dist - radius;\n float gap = gradient;\n float scale = 1.0 - abs(delta / gap);\n if (scale <= 0.0) {\n gl_FragColor = color;\n return;\n }\n radianStep *= scale;\n }\n radianStep /= float(k);\n\n float s = sin(radianStep);\n float c = cos(radianStep);\n mat2 rotationMatrix = mat2(vec2(c, -s), vec2(s, c));\n\n for(int i = 0; i < MAX_KERNEL_SIZE - 1; i++) {\n if (i == k) {\n break;\n }\n\n coord -= center;\n coord.y *= aspect;\n coord = rotationMatrix * coord;\n coord.y /= aspect;\n coord += center;\n\n vec4 sample = texture2D(uSampler, coord);\n\n // switch to pre-multiplied alpha to correctly blur transparent images\n // sample.rgb *= sample.a;\n\n color += sample;\n }\n\n gl_FragColor = color / float(uKernelSize);\n}\n",De=function(e){function t(t,n,r,o){void 0===t&&(t=0),void 0===n&&(n=[0,0]),void 0===r&&(r=5),void 0===o&&(o=-1),e.call(this,we,Oe),this._angle=0,this.angle=t,this.center=n,this.kernelSize=r,this.radius=o}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={angle:{configurable:!0},center:{configurable:!0},radius:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){this.uniforms.uKernelSize=0!==this._angle?this.kernelSize:0,e.applyFilter(this,t,n,r)},n.angle.set=function(e){this._angle=e,this.uniforms.uRadian=e*Math.PI/180},n.angle.get=function(){return this._angle},n.center.get=function(){return this.uniforms.uCenter},n.center.set=function(e){this.uniforms.uCenter=e},n.radius.get=function(){return this.uniforms.uRadius},n.radius.set=function(e){(e<0||e===1/0)&&(e=-1),this.uniforms.uRadius=e},Object.defineProperties(t.prototype,n),t}(t.Filter),Pe=a,Me="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\n\nuniform vec4 filterArea;\nuniform vec4 filterClamp;\nuniform vec2 dimensions;\n\nuniform bool mirror;\nuniform float boundary;\nuniform vec2 amplitude;\nuniform vec2 waveLength;\nuniform vec2 alpha;\nuniform float time;\n\nfloat rand(vec2 co) {\n return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);\n}\n\nvoid main(void)\n{\n vec2 pixelCoord = vTextureCoord.xy * filterArea.xy;\n vec2 coord = pixelCoord / dimensions;\n\n if (coord.y < boundary) {\n gl_FragColor = texture2D(uSampler, vTextureCoord);\n return;\n }\n\n float k = (coord.y - boundary) / (1. - boundary + 0.0001);\n float areaY = boundary * dimensions.y / filterArea.y;\n float v = areaY + areaY - vTextureCoord.y;\n float y = mirror ? v : vTextureCoord.y;\n\n float _amplitude = ((amplitude.y - amplitude.x) * k + amplitude.x ) / filterArea.x;\n float _waveLength = ((waveLength.y - waveLength.x) * k + waveLength.x) / filterArea.y;\n float _alpha = (alpha.y - alpha.x) * k + alpha.x;\n\n float x = vTextureCoord.x + cos(v * 6.28 / _waveLength - time) * _amplitude;\n x = clamp(x, filterClamp.x, filterClamp.z);\n\n vec4 color = texture2D(uSampler, vec2(x, y));\n\n gl_FragColor = color * _alpha;\n}\n",Re=function(e){function t(t){e.call(this,Pe,Me),this.uniforms.amplitude=new Float32Array(2),this.uniforms.waveLength=new Float32Array(2),this.uniforms.alpha=new Float32Array(2),this.uniforms.dimensions=new Float32Array(2),Object.assign(this,{mirror:!0,boundary:.5,amplitude:[0,20],waveLength:[30,100],alpha:[1,1],time:0},t)}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={mirror:{configurable:!0},boundary:{configurable:!0},amplitude:{configurable:!0},waveLength:{configurable:!0},alpha:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){this.uniforms.dimensions[0]=t.filterFrame.width,this.uniforms.dimensions[1]=t.filterFrame.height,this.uniforms.time=this.time,e.applyFilter(this,t,n,r)},n.mirror.set=function(e){this.uniforms.mirror=e},n.mirror.get=function(){return this.uniforms.mirror},n.boundary.set=function(e){this.uniforms.boundary=e},n.boundary.get=function(){return this.uniforms.boundary},n.amplitude.set=function(e){this.uniforms.amplitude[0]=e[0],this.uniforms.amplitude[1]=e[1]},n.amplitude.get=function(){return this.uniforms.amplitude},n.waveLength.set=function(e){this.uniforms.waveLength[0]=e[0],this.uniforms.waveLength[1]=e[1]},n.waveLength.get=function(){return this.uniforms.waveLength},n.alpha.set=function(e){this.uniforms.alpha[0]=e[0],this.uniforms.alpha[1]=e[1]},n.alpha.get=function(){return this.uniforms.alpha},Object.defineProperties(t.prototype,n),t}(t.Filter),ke=a,je="precision mediump float;\n\nvarying vec2 vTextureCoord;\n\nuniform sampler2D uSampler;\nuniform vec4 filterArea;\nuniform vec2 red;\nuniform vec2 green;\nuniform vec2 blue;\n\nvoid main(void)\n{\n gl_FragColor.r = texture2D(uSampler, vTextureCoord + red/filterArea.xy).r;\n gl_FragColor.g = texture2D(uSampler, vTextureCoord + green/filterArea.xy).g;\n gl_FragColor.b = texture2D(uSampler, vTextureCoord + blue/filterArea.xy).b;\n gl_FragColor.a = texture2D(uSampler, vTextureCoord).a;\n}\n",Ee=function(e){function t(t,n,r){void 0===t&&(t=[-10,0]),void 0===n&&(n=[0,10]),void 0===r&&(r=[0,0]),e.call(this,ke,je),this.red=t,this.green=n,this.blue=r}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={red:{configurable:!0},green:{configurable:!0},blue:{configurable:!0}};return n.red.get=function(){return this.uniforms.red},n.red.set=function(e){this.uniforms.red=e},n.green.get=function(){return this.uniforms.green},n.green.set=function(e){this.uniforms.green=e},n.blue.get=function(){return this.uniforms.blue},n.blue.set=function(e){this.uniforms.blue=e},Object.defineProperties(t.prototype,n),t}(t.Filter),Le=a,Ie="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform vec4 filterArea;\nuniform vec4 filterClamp;\n\nuniform vec2 center;\n\nuniform float amplitude;\nuniform float wavelength;\n// uniform float power;\nuniform float brightness;\nuniform float speed;\nuniform float radius;\n\nuniform float time;\n\nconst float PI = 3.14159;\n\nvoid main()\n{\n float halfWavelength = wavelength * 0.5 / filterArea.x;\n float maxRadius = radius / filterArea.x;\n float currentRadius = time * speed / filterArea.x;\n\n float fade = 1.0;\n\n if (maxRadius > 0.0) {\n if (currentRadius > maxRadius) {\n gl_FragColor = texture2D(uSampler, vTextureCoord);\n return;\n }\n fade = 1.0 - pow(currentRadius / maxRadius, 2.0);\n }\n\n vec2 dir = vec2(vTextureCoord - center / filterArea.xy);\n dir.y *= filterArea.y / filterArea.x;\n float dist = length(dir);\n\n if (dist <= 0.0 || dist < currentRadius - halfWavelength || dist > currentRadius + halfWavelength) {\n gl_FragColor = texture2D(uSampler, vTextureCoord);\n return;\n }\n\n vec2 diffUV = normalize(dir);\n\n float diff = (dist - currentRadius) / halfWavelength;\n\n float p = 1.0 - pow(abs(diff), 2.0);\n\n // float powDiff = diff * pow(p, 2.0) * ( amplitude * fade );\n float powDiff = 1.25 * sin(diff * PI) * p * ( amplitude * fade );\n\n vec2 offset = diffUV * powDiff / filterArea.xy;\n\n // Do clamp :\n vec2 coord = vTextureCoord + offset;\n vec2 clampedCoord = clamp(coord, filterClamp.xy, filterClamp.zw);\n vec4 color = texture2D(uSampler, clampedCoord);\n if (coord != clampedCoord) {\n color *= max(0.0, 1.0 - length(coord - clampedCoord));\n }\n\n // No clamp :\n // gl_FragColor = texture2D(uSampler, vTextureCoord + offset);\n\n color.rgb *= 1.0 + (brightness - 1.0) * p * fade;\n\n gl_FragColor = color;\n}\n",Xe=function(e){function t(t,n,r){void 0===t&&(t=[0,0]),void 0===n&&(n={}),void 0===r&&(r=0),e.call(this,Le,Ie),this.center=t,Array.isArray(n)&&(console.warn("Deprecated Warning: ShockwaveFilter params Array has been changed to options Object."),n={}),n=Object.assign({amplitude:30,wavelength:160,brightness:1,speed:500,radius:-1},n),this.amplitude=n.amplitude,this.wavelength=n.wavelength,this.brightness=n.brightness,this.speed=n.speed,this.radius=n.radius,this.time=r}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={center:{configurable:!0},amplitude:{configurable:!0},wavelength:{configurable:!0},brightness:{configurable:!0},speed:{configurable:!0},radius:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){this.uniforms.time=this.time,e.applyFilter(this,t,n,r)},n.center.get=function(){return this.uniforms.center},n.center.set=function(e){this.uniforms.center=e},n.amplitude.get=function(){return this.uniforms.amplitude},n.amplitude.set=function(e){this.uniforms.amplitude=e},n.wavelength.get=function(){return this.uniforms.wavelength},n.wavelength.set=function(e){this.uniforms.wavelength=e},n.brightness.get=function(){return this.uniforms.brightness},n.brightness.set=function(e){this.uniforms.brightness=e},n.speed.get=function(){return this.uniforms.speed},n.speed.set=function(e){this.uniforms.speed=e},n.radius.get=function(){return this.uniforms.radius},n.radius.set=function(e){this.uniforms.radius=e},Object.defineProperties(t.prototype,n),t}(t.Filter),Be=a,Ne="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform sampler2D uLightmap;\nuniform vec4 filterArea;\nuniform vec2 dimensions;\nuniform vec4 ambientColor;\nvoid main() {\n vec4 diffuseColor = texture2D(uSampler, vTextureCoord);\n vec2 lightCoord = (vTextureCoord * filterArea.xy) / dimensions;\n vec4 light = texture2D(uLightmap, lightCoord);\n vec3 ambient = ambientColor.rgb * ambientColor.a;\n vec3 intensity = ambient + light.rgb;\n vec3 finalColor = diffuseColor.rgb * intensity;\n gl_FragColor = vec4(finalColor, diffuseColor.a);\n}\n",Ge=function(e){function t(t,n,r){void 0===n&&(n=0),void 0===r&&(r=1),e.call(this,Be,Ne),this.uniforms.dimensions=new Float32Array(2),this.uniforms.ambientColor=new Float32Array([0,0,0,r]),this.texture=t,this.color=n}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={texture:{configurable:!0},color:{configurable:!0},alpha:{configurable:!0}};return t.prototype.apply=function(e,t,n,r){this.uniforms.dimensions[0]=t.filterFrame.width,this.uniforms.dimensions[1]=t.filterFrame.height,e.applyFilter(this,t,n,r)},n.texture.get=function(){return this.uniforms.uLightmap},n.texture.set=function(e){this.uniforms.uLightmap=e},n.color.set=function(e){var t=this.uniforms.ambientColor;"number"==typeof e?(o.hex2rgb(e,t),this._color=e):(t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],this._color=o.rgb2hex(t))},n.color.get=function(){return this._color},n.alpha.get=function(){return this.uniforms.ambientColor[3]},n.alpha.set=function(e){this.uniforms.ambientColor[3]=e},Object.defineProperties(t.prototype,n),t}(t.Filter),qe=a,Ke="varying vec2 vTextureCoord;\n\nuniform sampler2D uSampler;\nuniform float blur;\nuniform float gradientBlur;\nuniform vec2 start;\nuniform vec2 end;\nuniform vec2 delta;\nuniform vec2 texSize;\n\nfloat random(vec3 scale, float seed)\n{\n return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);\n}\n\nvoid main(void)\n{\n vec4 color = vec4(0.0);\n float total = 0.0;\n\n float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0);\n vec2 normal = normalize(vec2(start.y - end.y, end.x - start.x));\n float radius = smoothstep(0.0, 1.0, abs(dot(vTextureCoord * texSize - start, normal)) / gradientBlur) * blur;\n\n for (float t = -30.0; t <= 30.0; t++)\n {\n float percent = (t + offset - 0.5) / 30.0;\n float weight = 1.0 - abs(percent);\n vec4 sample = texture2D(uSampler, vTextureCoord + delta / texSize * percent * radius);\n sample.rgb *= sample.a;\n color += sample * weight;\n total += weight;\n }\n\n color /= total;\n color.rgb /= color.a + 0.00001;\n\n gl_FragColor = color;\n}\n",We=function(e){function t(t,r,o,i){void 0===t&&(t=100),void 0===r&&(r=600),void 0===o&&(o=null),void 0===i&&(i=null),e.call(this,qe,Ke),this.uniforms.blur=t,this.uniforms.gradientBlur=r,this.uniforms.start=o||new n.Point(0,window.innerHeight/2),this.uniforms.end=i||new n.Point(600,window.innerHeight/2),this.uniforms.delta=new n.Point(30,30),this.uniforms.texSize=new n.Point(window.innerWidth,window.innerHeight),this.updateDelta()}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var r={blur:{configurable:!0},gradientBlur:{configurable:!0},start:{configurable:!0},end:{configurable:!0}};return t.prototype.updateDelta=function(){this.uniforms.delta.x=0,this.uniforms.delta.y=0},r.blur.get=function(){return this.uniforms.blur},r.blur.set=function(e){this.uniforms.blur=e},r.gradientBlur.get=function(){return this.uniforms.gradientBlur},r.gradientBlur.set=function(e){this.uniforms.gradientBlur=e},r.start.get=function(){return this.uniforms.start},r.start.set=function(e){this.uniforms.start=e,this.updateDelta()},r.end.get=function(){return this.uniforms.end},r.end.set=function(e){this.uniforms.end=e,this.updateDelta()},Object.defineProperties(t.prototype,r),t}(t.Filter),Ye=function(e){function t(){e.apply(this,arguments)}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.updateDelta=function(){var e=this.uniforms.end.x-this.uniforms.start.x,t=this.uniforms.end.y-this.uniforms.start.y,n=Math.sqrt(e*e+t*t);this.uniforms.delta.x=e/n,this.uniforms.delta.y=t/n},t}(We),Ze=function(e){function t(){e.apply(this,arguments)}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.updateDelta=function(){var e=this.uniforms.end.x-this.uniforms.start.x,t=this.uniforms.end.y-this.uniforms.start.y,n=Math.sqrt(e*e+t*t);this.uniforms.delta.x=-t/n,this.uniforms.delta.y=e/n},t}(We),Qe=function(e){function t(t,n,r,o){void 0===t&&(t=100),void 0===n&&(n=600),void 0===r&&(r=null),void 0===o&&(o=null),e.call(this),this.tiltShiftXFilter=new Ye(t,n,r,o),this.tiltShiftYFilter=new Ze(t,n,r,o)}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={blur:{configurable:!0},gradientBlur:{configurable:!0},start:{configurable:!0},end:{configurable:!0}};return t.prototype.apply=function(e,t,n){var r=e.getFilterTexture();this.tiltShiftXFilter.apply(e,t,r,1),this.tiltShiftYFilter.apply(e,r,n),e.returnFilterTexture(r)},n.blur.get=function(){return this.tiltShiftXFilter.blur},n.blur.set=function(e){this.tiltShiftXFilter.blur=this.tiltShiftYFilter.blur=e},n.gradientBlur.get=function(){return this.tiltShiftXFilter.gradientBlur},n.gradientBlur.set=function(e){this.tiltShiftXFilter.gradientBlur=this.tiltShiftYFilter.gradientBlur=e},n.start.get=function(){return this.tiltShiftXFilter.start},n.start.set=function(e){this.tiltShiftXFilter.start=this.tiltShiftYFilter.start=e},n.end.get=function(){return this.tiltShiftXFilter.end},n.end.set=function(e){this.tiltShiftXFilter.end=this.tiltShiftYFilter.end=e},Object.defineProperties(t.prototype,n),t}(t.Filter),Ue=a,Ve="varying vec2 vTextureCoord;\n\nuniform sampler2D uSampler;\nuniform float radius;\nuniform float angle;\nuniform vec2 offset;\nuniform vec4 filterArea;\n\nvec2 mapCoord( vec2 coord )\n{\n coord *= filterArea.xy;\n coord += filterArea.zw;\n\n return coord;\n}\n\nvec2 unmapCoord( vec2 coord )\n{\n coord -= filterArea.zw;\n coord /= filterArea.xy;\n\n return coord;\n}\n\nvec2 twist(vec2 coord)\n{\n coord -= offset;\n\n float dist = length(coord);\n\n if (dist < radius)\n {\n float ratioDist = (radius - dist) / radius;\n float angleMod = ratioDist * ratioDist * angle;\n float s = sin(angleMod);\n float c = cos(angleMod);\n coord = vec2(coord.x * c - coord.y * s, coord.x * s + coord.y * c);\n }\n\n coord += offset;\n\n return coord;\n}\n\nvoid main(void)\n{\n\n vec2 coord = mapCoord(vTextureCoord);\n\n coord = twist(coord);\n\n coord = unmapCoord(coord);\n\n gl_FragColor = texture2D(uSampler, coord );\n\n}\n",He=function(e){function t(t){e.call(this,Ue,Ve),"number"==typeof t&&(t={radius:t},void 0!==arguments[1]&&(t.angle=arguments[1]),void 0!==arguments[2]&&(t.padding=arguments[2])),Object.assign(this,{radius:200,angle:4,padding:20,offset:new n.Point},t)}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var r={offset:{configurable:!0},radius:{configurable:!0},angle:{configurable:!0}};return r.offset.get=function(){return this.uniforms.offset},r.offset.set=function(e){this.uniforms.offset=e},r.radius.get=function(){return this.uniforms.radius},r.radius.set=function(e){this.uniforms.radius=e},r.angle.get=function(){return this.uniforms.angle},r.angle.set=function(e){this.uniforms.angle=e},Object.defineProperties(t.prototype,r),t}(t.Filter),$e=a,Je="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform vec4 filterArea;\n\nuniform vec2 uCenter;\nuniform float uStrength;\nuniform float uInnerRadius;\nuniform float uRadius;\n\nconst float MAX_KERNEL_SIZE = ${maxKernelSize};\n\n// author: http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/\nhighp float rand(vec2 co, float seed) {\n const highp float a = 12.9898, b = 78.233, c = 43758.5453;\n highp float dt = dot(co + seed, vec2(a, b)), sn = mod(dt, 3.14159);\n return fract(sin(sn) * c + seed);\n}\n\nvoid main() {\n\n float minGradient = uInnerRadius * 0.3;\n float innerRadius = (uInnerRadius + minGradient * 0.5) / filterArea.x;\n\n float gradient = uRadius * 0.3;\n float radius = (uRadius - gradient * 0.5) / filterArea.x;\n\n float countLimit = MAX_KERNEL_SIZE;\n\n vec2 dir = vec2(uCenter.xy / filterArea.xy - vTextureCoord);\n float dist = length(vec2(dir.x, dir.y * filterArea.y / filterArea.x));\n\n float strength = uStrength;\n\n float delta = 0.0;\n float gap;\n if (dist < innerRadius) {\n delta = innerRadius - dist;\n gap = minGradient;\n } else if (radius >= 0.0 && dist > radius) { // radius < 0 means it's infinity\n delta = dist - radius;\n gap = gradient;\n }\n\n if (delta > 0.0) {\n float normalCount = gap / filterArea.x;\n delta = (normalCount - delta) / normalCount;\n countLimit *= delta;\n strength *= delta;\n if (countLimit < 1.0)\n {\n gl_FragColor = texture2D(uSampler, vTextureCoord);\n return;\n }\n }\n\n // randomize the lookup values to hide the fixed number of samples\n float offset = rand(vTextureCoord, 0.0);\n\n float total = 0.0;\n vec4 color = vec4(0.0);\n\n dir *= strength;\n\n for (float t = 0.0; t < MAX_KERNEL_SIZE; t++) {\n float percent = (t + offset) / MAX_KERNEL_SIZE;\n float weight = 4.0 * (percent - percent * percent);\n vec2 p = vTextureCoord + dir * percent;\n vec4 sample = texture2D(uSampler, p);\n\n // switch to pre-multiplied alpha to correctly blur transparent images\n // sample.rgb *= sample.a;\n\n color += sample * weight;\n total += weight;\n\n if (t > countLimit){\n break;\n }\n }\n\n color /= total;\n // switch back from pre-multiplied alpha\n // color.rgb /= color.a + 0.00001;\n\n gl_FragColor = color;\n}\n",et=function(e){function t(t){if("object"!=typeof t){var n=arguments[0],r=arguments[1],o=arguments[2],i=arguments[3];t={},void 0!==n&&(t.strength=n),void 0!==r&&(t.center=r),void 0!==o&&(t.innerRadius=o),void 0!==i&&(t.radius=i)}t=Object.assign({strength:.1,center:[0,0],innerRadius:0,radius:-1,maxKernelSize:32},t),e.call(this,$e,Je.replace("${maxKernelSize}",t.maxKernelSize.toFixed(1))),this.strength=t.strength,this.center=t.center,this.innerRadius=t.innerRadius,this.radius=t.radius}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={center:{configurable:!0},strength:{configurable:!0},innerRadius:{configurable:!0},radius:{configurable:!0}};return n.center.get=function(){return this.uniforms.uCenter},n.center.set=function(e){this.uniforms.uCenter=e},n.strength.get=function(){return this.uniforms.uStrength},n.strength.set=function(e){this.uniforms.uStrength=e},n.innerRadius.get=function(){return this.uniforms.uInnerRadius},n.innerRadius.set=function(e){this.uniforms.uInnerRadius=e},n.radius.get=function(){return this.uniforms.uRadius},n.radius.set=function(e){(e<0||e===1/0)&&(e=-1),this.uniforms.uRadius=e},Object.defineProperties(t.prototype,n),t}(t.Filter);return e.AdjustmentFilter=c,e.AdvancedBloomFilter=y,e.AsciiFilter=C,e.BevelFilter=z,e.BloomFilter=A,e.BulgePinchFilter=O,e.CRTFilter=Z,e.ColorMapFilter=M,e.ColorOverlayFilter=j,e.ColorReplaceFilter=I,e.ConvolutionFilter=N,e.CrossHatchFilter=K,e.DotFilter=V,e.DropShadowFilter=J,e.EmbossFilter=ne,e.GlitchFilter=ie,e.GlowFilter=ae,e.GodrayFilter=he,e.KawaseBlurFilter=d,e.MotionBlurFilter=me,e.MultiColorReplaceFilter=xe,e.OldFilmFilter=be,e.OutlineFilter=Fe,e.PixelateFilter=Te,e.RGBSplitFilter=Ee,e.RadialBlurFilter=De,e.ReflectionFilter=Re,e.ShockwaveFilter=Xe,e.SimpleLightmapFilter=Ge,e.TiltShiftAxisFilter=We,e.TiltShiftFilter=Qe,e.TiltShiftXFilter=Ye,e.TiltShiftYFilter=Ze,e.TwistFilter=He,e.ZoomBlurFilter=et,e}({},PIXI,PIXI,PIXI,PIXI.utils,PIXI,PIXI.filters,PIXI.filters);Object.assign(PIXI.filters,__filters); +//# sourceMappingURL=pixi-filters.js.map diff --git a/app/data/ct.libs/filters/index.js b/app/data/ct.libs/filters/index.js new file mode 100644 index 000000000..ff455fa48 --- /dev/null +++ b/app/data/ct.libs/filters/index.js @@ -0,0 +1,91 @@ +/* Based on https://pixijs.io/pixi-filters/docs/PIXI.filters */ +/* Sandbox demo: https://pixijs.io/pixi-filters/tools/demo/ */ + +(() => { + const filters = [ + 'Adjustment', + 'AdvancedBloom', + 'Ascii', + 'Bevel', + 'Bloom', + 'BulgePinch', + 'ColorMap', + 'ColorOverlay', + 'ColorReplace', + 'Convolution', + 'CrossHatch', + 'CRT', + 'Dot', + 'DropShadow', + 'Emboss', + 'Glitch', + 'Glow', + 'Godray', + 'KawaseBlur', + 'MotionBlur', + 'MultiColorReplace', + 'OldFilm', + 'Outline', + 'Pixelate', + 'RadialBlur', + 'Reflection', + 'RGBSplit', + 'Shockwave', + 'SimpleLightmap', + 'TiltShift', + 'Twist', + 'ZoomBlur', + //Built-in filters + 'Alpha', + 'Blur', + 'BlurPass', + 'ColorMatrix', + 'Displacement', + 'FXAA', + 'Noise' + ]; + + const addFilter = (target, fx) => { + if (!target.filters) { + target.filters = [fx]; + } else { + target.filters.push(fx); + } + return fx; + }; + + const createFilter = (target, filter, ...args) => { + let fx; + let filterName = filter + 'Filter'; + if (filterName === 'BlurPassFilter') { + filterName = 'BlurFilterPass'; + } + if (args.length > 0) { + fx = new PIXI.filters[filterName](...args); + } else { + fx = new PIXI.filters[filterName](); + } + return addFilter(target, fx); + }; + + ct.filters = {}; + + for (const filter of filters) { + ct.filters['add' + filter] = (target, ...args) => + createFilter(target, filter, ...args); + } + + ct.filters.remove = (target, filter) => { + for (const f in target.filters) { + if (target.filters[f] === filter) { + target.filters.splice(f, 1); + } + } + }; + + ct.filters.custom = (target, vertex, fragment, uniforms) => { + const fx = new PIXI.Filter(vertex, fragment, uniforms); + return addFilter(target, fx); + } + +})(); diff --git a/app/data/ct.libs/filters/injects/htmlbottom.html b/app/data/ct.libs/filters/injects/htmlbottom.html new file mode 100644 index 000000000..df2abb875 --- /dev/null +++ b/app/data/ct.libs/filters/injects/htmlbottom.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/data/ct.libs/filters/module.json b/app/data/ct.libs/filters/module.json new file mode 100644 index 000000000..8935d46a9 --- /dev/null +++ b/app/data/ct.libs/filters/module.json @@ -0,0 +1,16 @@ +{ + "main": { + "name": "Filters", + "tagline": "Add filters and custom shaders to create special effects on your copies or whole viewport", + "version": "1.0.0", + "packageName": "filters", + "authors": [ + { + "name": "SN" + } + ], + "categories": [ + "FX" + ] + } +} diff --git a/app/data/ct.libs/filters/package.json b/app/data/ct.libs/filters/package.json new file mode 100644 index 000000000..7cce2c905 --- /dev/null +++ b/app/data/ct.libs/filters/package.json @@ -0,0 +1,14 @@ +{ + "name": "Filters", + "version": "1.0.0", + "description": "Add shaders filters", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "devDependencies": { + "eslint": "^6.5.1" + } +} diff --git a/app/data/ct.libs/filters/types.d.ts b/app/data/ct.libs/filters/types.d.ts new file mode 100644 index 000000000..a46a843d8 --- /dev/null +++ b/app/data/ct.libs/filters/types.d.ts @@ -0,0 +1,1448 @@ +declare namespace PIXI.filters { + class AdjustmentFilter extends PIXI.Filter { + constructor(options?: AdjustmentOptions); + /** + * The amount of luminance (default: 1) + */ + gamma?: number; + + /** + * The amount of color saturation (default: 1) + */ + saturation?: number; + + /** + * The amount of contrast (default: 1) + */ + contrast?: number; + + /** + * The overall brightness (default: 1) + */ + brightness?: number; + + /** + * The multipled red channel (default: 1) + */ + red?: number; + + /** + * The multipled green channel (default: 1) + */ + green?: number; + + /** + * The multipled blue channel (default: 1) + */ + blue?: number; + + /** + * The overall alpha amount (default: 1) + */ + alpha?: number; + } + interface AdjustmentOptions { + gamma?: number; + contrast?: number; + saturation?: number; + brightness?: number; + red?: number; + green?: number; + blue?: number; + alpha?: number; + } + + class AdvancedBloomFilter extends PIXI.Filter { + constructor(options?: AdvancedBloomOptions); + constructor(threshold?: number); + + /** + * Defines how bright a color needs to be to affect bloom (default 0.5). + */ + threshold: number; + + /** + * To adjust the strength of the bloom. Higher values is more intense brightness (default 1.0). + */ + bloomScale: number; + + /** + * The brightness, lower value is more subtle brightness, higher value is blown-out (default 1.0). + */ + brightness: number; + + /** + * Sets the kernels of the Blur Filter (default null). + */ + kernels: number[]; + + /** + * Sets the strength of the Blur properties simultaneously (default 8). + */ + blur: number; + + /** + * Sets the quality of the Blur Filter (default 4). + */ + quality: number; + + /** + * Sets the pixelSize of the Kawase Blur filter (default 1). + */ + pixelSize: number | PIXI.Point | number[]; + + /** + * The resolution of the filter. (default PIXI.settings.FILTER_RESOLUTION) + */ + resolution: number; + } + interface AdvancedBloomOptions { + threshold?: number; + bloomScale?: number; + brightness?: number; + kernels?: number[]; + blur?: number; + quality?: number; + pixelSize?: number | PIXI.Point | number[]; + resolution?: number; + } + + class AsciiFilter extends PIXI.Filter { + constructor(size?: number); + /** + * The pixel size used by the filter (default 8). + */ + size: number; + } + + class BevelFilter extends PIXI.Filter { + constructor(options?: BevelOptions); + /** + * The angle of the light in degrees (default 45). + */ + rotation: number; + + /** + * The tickness of the bevel (default 2). + */ + thickness: number; + + /** + * Color of the light (default 0xffffff). + */ + lightColor: number; + + /** + * Alpha of the light (default 0.7). + */ + lightAlpha: number; + + /** + * Color of the shadow (default 0x000000). + */ + shadowColor: number; + + /** + * Alpha of the shadow (default 0.7). + */ + shadowAlpha: number; + } + interface BevelOptions { + rotation: number; + thickness: number; + lightColor: number; + lightAlpha: number; + shadowColor: number; + shadowAlpha: number; + } + + class BloomFilter extends PIXI.Filter { + constructor(options?: BloomOptions); + /** + * Sets the strength of both the blurX and blurY properties simultaneously (default 2). + */ + blur?: number | PIXI.Point | number[]; + + /** + * The quality of the blurX & blurY filter (default 4). + */ + quality?: number; + + /** + * The resolution of the blurX & blurY filter (default PIXI.settings.RESOLUTION). + */ + resolution?: number; + + /** + * The kernelSize of the blurX & blurY filter.Options: 5, 7, 9, 11, 13, 15. (default 5). + */ + kernelSize?: number; + } + interface BloomOptions { + blur: number | PIXI.Point | number[]; + quality: number; + resolution: number; + kernelSize: number; + } + + class BulgePinchFilter extends PIXI.Filter { + constructor(options?: BulgePinchFilterOptions); + /** + * The x and y coordinates of the center of the circle of effect (default [0,0]). + */ + center?: PIXI.Point | [number, number]; + + /** + * The radius of the circle of effect (default: 100). + */ + radius?: number; + + /** + * The strength of the effect. -1 to 1: -1 is strong pinch, 0 is no effect, 1 is strong bulge (default: 1). + */ + strength?: number; + } + interface BulgePinchFilterOptions { + center?: PIXI.Point | [number, number]; + radius?: number; + strength?: number; + } + + class ColorMapFilter extends PIXI.Filter { + constructor( + colorMap?: + | HTMLImageElement + | HTMLCanvasElement + | PIXI.BaseTexture + | PIXI.Texture, + nearest?: boolean, + mix?: number, + readonly colorSize: number + ); + /** + * The colorMap texture of the filter. + */ + colorMap: + | HTMLImageElement + | HTMLCanvasElement + | PIXI.BaseTexture + | PIXI.Texture; + + /** + * Whether use NEAREST for colorMap texture (default false). + */ + nearest: boolean; + + /** + * The mix from 0 to 1, where 0 is the original image and 1 is the color mapped image (default 1). + */ + mix: number; + + /** + * The size of one color slice (readonly). + */ + readonly colorSize: number; + } + + class ColorOverlayFilter extends PIXI.Filter { + constructor(color?: number | [number, number, number]); + + /** + * The resulting color, as a 3 component RGB e.g. [1.0, 0.5, 1.0] (default 0x000000). + */ + color: number | [number, number, number]; + } + + class ColorReplaceFilter extends PIXI.Filter { + constructor( + originalColor?: number | number[], + newColor?: number | number[], + epsilon?: number + ); + /** + * The color that will be changed, as a 3 component RGB e.g. [1.0, 1.0, 1.0] (default 0xFF0000). + */ + originalColor: number | number[]; + + /** + * The resulting color, as a 3 component RGB e.g. [1.0, 0.5, 1.0] (default 0x000000). + */ + newColor: number | number[]; + + /** + * Tolerance/sensitivity of the floating-point comparison between colors: lower = more exact, higher = more inclusive (default 0.4). + */ + epsilon: number; + } + + class ConvolutionFilter extends PIXI.Filter { + constructor(matrix?: number[], width?: number, height?: number); + /** + * An array of values used for matrix transformation. Specified as a 9 point Array (default [0,0,0,0,0,0,0,0,0]). + */ + matrix?: number[]; + + /** + * Width of the object you are transforming (default 200). + */ + width?: number; + + /** + * Height of the object you are transforming (default 200). + */ + height?: number; + } + + class CrossHatchFilter extends PIXI.Filter { + constructor(); + } + + class CRTFilter extends PIXI.Filter { + constructor(options?: CRTFilterOptions); + /** + * Bent of interlaced lines, higher value means more bend (default 1). + */ + curvature: number; + + /** + * Width of interlaced lines (default 1). + */ + lineWidth: number; + + /** + * Contrast of interlaced lines (default 0.25). + */ + lineContrast: number; + + /** + * `true` for vertical lines, `false` for horizontal lines (default false). + */ + verticalLine: boolean; + + /** + * Opacity/intensity of the noise effect between `0` and `1` (default 0.3). + */ + noise: number; + + /** + * The size of the noise particles (default 1.0). + */ + noiseSize: number; + + /** + * A seed value to apply to the random noise generation (default 0). + */ + seed: number; + + /** + * The radius of the vignette effect, smaller values produces a smaller vignette (default 0.3). + */ + vignetting: number; + + /** + * Amount of opacity of vignette (default 1.0). + */ + vignettingAlpha: number; + + /** + * Blur intensity of the vignette (default 0.3). + */ + vignettingBlur: number; + + /** + * For animating interlaced lines (default 0). + */ + time: number; + } + interface CRTFilterOptions { + curvature?: number; + lineWidth?: number; + lineContrast?: number; + verticalLine?: boolean; + noise?: number; + noiseSize?: number; + seed?: number; + vignetting?: number; + vignettingAlpha?: number; + vignettingBlur?: number; + time?: number; + } + + class DotFilter extends PIXI.Filter { + constructor(scale?: number, angle?: number); + /** + * The scale of the effect (default 1). + */ + scale: number; + + /** + * The radius of the effect (default 5). + */ + angle: number; + } + + class DropShadowFilter extends PIXI.Filter { + constructor(options?: DropShadowFilterOptions); + /** + * The alpha of the shadow (default 0.5). + */ + alpha: number; + + /** + * The blur of the shadow (default 2). + */ + blur: number; + + /** + * The color of the shadow (default 0x000000). + */ + color: number; + + /** + * Distance offset of the shadow (default 5). + */ + distance: number; + + /** + * Sets the kernels of the Blur Filter (default null). + */ + kernels: number[]; + + /** + * Sets the pixelSize of the Kawase Blur filter (default 1). + */ + pixelSize: number | number[] | PIXI.Point; + + /** + * Sets the quality of the Blur Filter (default 3). + */ + quality: number; + + /** + * The resolution of the filter (default PIXI.settings.RESOLUTION). + */ + resolution: number; + + /** + * The angle of the shadow in degrees (default 45). + */ + rotation: number; + + /** + * Whether render shadow only (default false). + */ + shadowOnly: boolean; + } + interface DropShadowFilterOptions { + alpha?: number; + blur?: number; + color?: number; + distance?: number; + kernels?: number[]; + pixelSize?: number | number[] | PIXI.Point; + quality?: number; + resolution?: number; + rotation?: number; + shadowOnly?: boolean; + } + + class EmbossFilter extends PIXI.Filter { + constructor(strength?: number); + /** + * Strength of emboss (default 5). + */ + strength: number; + } + + class GlitchFilter extends PIXI.Filter { + constructor(options?: GlitchFilterOptions); + + /** + * The maximum number of slices (default 5). + */ + slices: number; + + /** + * The maximum offset value for each of the slices (default 100). + */ + offset: number; + + /** + * The angle in degree of the offset of slices (default 0). + */ + direction: number; + + /** + * The fill mode of the space after the offset (default 0). + * Acceptable values: + * 0 TRANSPARENT + * 1 ORIGINAL + * 2 LOOP + * 3 CLAMP + * 4 MIRROR + */ + fillMode: number; + + /** + * `true` will divide the bands roughly based on equal amounts + * where as setting to `false` will vary the band sizes dramatically (more random looking). + * (default false) + */ + average: boolean; + + /** + * A seed value for randomizing color offset. Animating + * this value to `Math.random()` produces a twitching effect. + * (default 0) + */ + seed: number; + + /** + * Red channel offset (default [0,0]). + */ + red: PIXI.Point; + + /** + * Green channel offset (default [0,0]). + */ + green: PIXI.Point; + + /** + * Blue channel offset (default [0,0]). + */ + blue: PIXI.Point; + + /** + * Minimum size of slices as a portion of the `sampleSize` (default 8). + */ + minSize: number; + + /** + * Height of the displacement map canvas (default 512). + */ + sampleSize: number; + + /** + * Regenerating random size, offsets for slices. + */ + refresh(): void; + + /** + * Shuffle the sizes of the slices, advanced usage. + */ + shuffle(): void; + + /** + * Redraw displacement bitmap texture, advanced usage. + */ + redraw(): void; + + /** + * The displacement map is used to generate the bands. + * If using your own texture, `slices` will be ignored. + */ + readonly texture: PIXI.Texture; + } + interface GlitchFilterOptions { + slices: number; + offset: number; + direction: number; + fillMode: number; + average: boolean; + seed: number; + red: PIXI.Point; + green: PIXI.Point; + blue: PIXI.Point; + minSize: number; + sampleSize: number; + } + + class GlowFilter extends PIXI.Filter { + constructor(options?: GlowFilterOptions); + /** + * The color of the glow (default 0xFFFFFF). + */ + color: number; + + /** + * The distance of the glow. Make it 2 times more for resolution=2. + * It can't be changed after filter creation. + * (default 10) + */ + distance: number; + + /** + * The strength of the glow inward from the edge of the sprite (default 0). + */ + innerStrength: number; + + /** + * The strength of the glow outward from the edge of the sprite (default 4). + */ + outerStrength: number; + + /** + * A number between 0 and 1 that describes the quality of the glow. + * The higher the number the less performant. + * (default 0.1) + */ + quality: number; + + /** + * Only draw the glow, not the texture itself (default false). + */ + knockout: boolean; + } + interface GlowFilterOptions { + color?: number; + distance?: number; + innerStrength?: number; + outerStrength?: number; + quality?: number; + knockout?: boolean; + } + + class GodrayFilter extends PIXI.Filter { + constructor(options?: GodrayFilterOptions); + /** + * The angle/light-source of the rays in degrees. For instance, a value of 0 is vertical rays, + * values of 90 or -90 produce horizontal rays (default 30). + */ + angle: number; + + /** + * The position of the emitting point for light rays + * only used if `parallel` is set to `false` (default [0, 0]). + */ + center: PIXI.Point | Array; + + /** + * `true` if light rays are parallel (uses angle), + * `false` to use the focal `center` point. + * (default true) + */ + parallel: boolean; + + /** + * General intensity of the effect. A value closer to 1 will produce a more intense effect, + * where a value closer to 0 will produce a subtler effect (default 0.5). + */ + gain: number; + + /** + * The density of the fractal noise. A higher amount produces more rays and a smaller amount + * produces fewer waves (default 2.5). + */ + lacunarity: number; + + /** + * The current time position (default 0). + */ + time: number; + } + interface GodrayFilterOptions { + angle: number; + center: PIXI.Point | Array; + parallel: boolean; + gain: number; + lacunarity: number; + time: number; + } + + class KawaseBlurFilter extends PIXI.Filter { + constructor(blur?: number | number[], quality?: number, clamp?: boolean); + + /** + * The blur of the filter. Should be greater than `0`. + * If value is an Array, setting kernels. (default 4). + */ + blur: number; + + /** + * The quality of the filter. Should be an integer greater than `1`. (default 3). + */ + quality: number; + + /** + * Clamp edges, useful for removing dark edges from fullscreen filters + * or bleeding to the edge of filterArea. (default false). + */ + clamp: boolean; + } + + class MotionBlurFilter extends PIXI.Filter { + constructor( + velocity?: PIXI.ObservablePoint | PIXI.Point | number[], + kernelSize?: number, + offset?: number + ); + /** + * Sets the velocity (x and y) of the motion for blur effect (default [0,0]). + */ + velocity: PIXI.ObservablePoint | PIXI.Point | number[]; + + /** + * The kernelSize of the blur, higher values are slower but look better. + * Use odd value greater than 5 (default 5). + */ + kernelSize: number; + + /** + * The offset of the blur filter (default 0). + */ + offset: number; + } + + class MultiColorReplaceFilter extends PIXI.Filter { + constructor( + replacements: Array, + epsilon?: number, + maxColors?: number + ); + /** + * The collection of replacement items. Each item is color-pair (an array length is 2). + * In the pair, the first value is original color , the second value is target color. + */ + replacements: Array; + + /** + * Tolerance of the floating-point comparison between colors (lower = more exact, higher = more inclusive). + * (default 0.05) + */ + epsilon: number; + + /** + * The maximum number of replacements filter is able to use. + * Because the fragment is only compiled once, this cannot be changed after construction. + * If omitted, the default value is the length of `replacements`. + */ + readonly maxColors: number; + + /** + * Should be called after changing any of the contents of the replacements. + * This is a convenience method for resetting the `replacements`. + */ + refresh(): void; + } + + class OldFilmFilter extends PIXI.Filter { + constructor(options?: OldFilmFilterOptions, seed?: number); + constructor(seed?: number); + /** + * The amount of saturation of sepia effect, + * a value of `1` is more saturation and closer to `0` is less, + * and a value of `0` produces no sepia effect (default 0.3). + */ + sepia: number; + + /** + * Opacity/intensity of the noise effect between `0` and `1` (default 0.3). + */ + noise: number; + + /** + * The size of the noise particles (default 1). + */ + noiseSize: number; + + /** + * How often scratches appear (default 0.5). + */ + scratch: number; + + /** + * The density of the number of scratches (default 0.3). + */ + scratchDensity: number; + + /** + * The width of the scratches (default 1.0). + */ + scratchWidth: number; + + /** + * The radius of the vignette effect, smaller values produces a smaller vignette (default 0.3). + */ + vignetting: number; + + /** + * Amount of opacity of vignette (default 1.0). + */ + vignettingAlpha: number; + + /** + * Blur intensity of the vignette (default 0.3). + */ + vignettingBlur: number; + + /** + * A seed value to apply to the random noise generation (default 0). + */ + seed: number; + } + interface OldFilmFilterOptions { + sepia?: number; + noise?: number; + noiseSize?: number; + scratch?: number; + scratchDensity?: number; + scratchWidth?: number; + vignetting?: number; + vignettingAlpha?: number; + vignettingBlur?: number; + } + + class OutlineFilter extends PIXI.Filter { + constructor(thickness?: number, color?: number); + /** + * The color of the outline (default 0x000000). + */ + color: number; + + /** + * The tickness of the outline. Make it 2 times more for resolution 2 (default 1). + */ + thickness: number; + + /** + * The quality of the outline from `0` to `1`, using a higher quality setting will result in slower performance and more accuracy + * (default 0.1). + */ + quality: number; + } + + class PixelateFilter extends PIXI.Filter { + constructor(size?: PIXI.Point | number[] | number); + /** + * Either the width/height of the size of the pixels, or square size (default 10). + */ + size: PIXI.Point | number[] | number; + } + + class RadialBlurFilter extends PIXI.Filter { + constructor( + angle?: number, + center?: number[] | PIXI.Point, + kernelSize?: number, + radius?: number + ); + /** + * Sets the angle in degrees of the motion for blur effect (default 0). + */ + angle: number; + + /** + * Center of the effect (default [0, 0]). + */ + center: number[] | PIXI.Point; + + /** + * The kernelSize of the blur filter. But be odd number >= 3 (default 5). + */ + kernelSize: number; + + /** + * Outer radius of the effect. The default value of `-1` is infinite (default -1). + */ + radius: number; + } + + class ReflectionFilter extends PIXI.Filter { + constructor(options?: ReflectionFilterOptions); + /** + * `true` to reflect the image, `false` for waves-only (default true). + */ + mirror: boolean; + + /** + * Vertical position of the reflection point, default is middle. + * Smaller numbers produce a larger reflection, larger numbers produce a smaller reflection. + * (default 0.5) + */ + boundary: number; + + /** + * Starting and ending amplitude of waves (default [0, 20]). + */ + amplitude: number[]; + + /** + * Starting and ending length of waves (default [30, 100]). + */ + waveLength: number[]; + + /** + * Starting and ending alpha values (default [1, 1]). + */ + alpha: number[]; + + /** + * Time for animating position of waves (default 0). + */ + time: number; + } + interface ReflectionFilterOptions { + mirror?: boolean; + boundary?: number; + amplitude?: number[]; + waveLength?: number[]; + alpha?: number[]; + time?: number; + } + + class RGBSplitFilter extends PIXI.Filter { + constructor(red?: PIXI.Point, green?: PIXI.Point, blue?: PIXI.Point); + /** + * Red channel offset (default [-10,0]). + */ + red: PIXI.Point; + + /** + * Green channel offset (default [0, 10]). + */ + green: PIXI.Point; + + /** + * Blue channel offset (default [0, 0]). + */ + blue: PIXI.Point; + } + + class ShockwaveFilter extends PIXI.Filter { + constructor( + center?: PIXI.Point | number[], + options?: ShockwaveFilterOptions, + time?: number + ); + /** + * Sets the center of the shockwave in normalized screen coords. + * That is (0,0) is the top-left and (1,1) is the bottom right. + * (default [0.5, 0.5]) + * NB: But in practice, it seems we need "world" coords. + */ + center: PIXI.Point | number[]; + + /** + * The amplitude of the shockwave (default 0.5). + */ + amplitude?: number; + + /** + * The wavelength of the shockwave (default 1.0). + */ + wavelength?: number; + + /** + * The brightness of the shockwave (default 8). + */ + brightness?: number; + + /** + * The speed about the shockwave ripples out (default 500.0). + * The unit is `pixel/second`. + */ + speed?: number; + + /** + * The maximum radius of shockwave. + * `< 0.0` means it's infinity. + * (default 4) + */ + radius?: number; + + /** + * Sets the elapsed time of the shockwave. + * It could control the current size of shockwave. + * (default 0) + */ + time: number; + } + interface ShockwaveFilterOptions { + amplitude?: number; + wavelength?: number; + brightness?: number; + speed?: number; + radius?: number; + } + + class SimpleLightmapFilter extends PIXI.Filter { + constructor(texture: PIXI.Texture, color?: number[] | number); + + /** + * Default alpha set independent of color (if it's a number, not array). + * When setting `color` as hex, this can be used to set alpha independently. + * (default 1) + */ + alpha: number; + + /** + * An RGBA array of the ambient color or a hex color without alpha (default 0x000000) + */ + color: number[] | number; + + /** + * A texture where your lightmap is rendered. + */ + texture: PIXI.Texture; + } + + class TiltShiftFilter extends PIXI.Filter { + constructor( + blur?: number, + gradientBlur?: number, + start?: PIXI.Point, + end?: PIXI.Point + ); + + /** + * The strength of the blur (default 100). + */ + blur: number; + + /** + * The strength of the gradient blur (default 600). + */ + gradientBlur: number; + + /** + * The Y value to start the effect at (default null). + */ + start: PIXI.Point; + + /** + * The Y value to end the effect at (default null). + */ + end: PIXI.Point; + } + + class TwistFilter extends PIXI.Filter { + constructor( + radius?: number, + angle?: number, + padding?: number, + offset?: PIXI.Point | [number, number] + ); + + /** + * The angle of the twist (default 4). + */ + angle: number; + + /** + * The radius of the twist (default 200). + */ + radius: number; + + /** + * Padding for filter area (default 20). + */ + padding: number; + + /** + * Center of twist, in local, pixel coordinates. + */ + offset: PIXI.Point | [number, number]; + } + + class ZoomBlurFilter extends PIXI.Filter { + constructor(options?: ZoomBlurFilterOptions); + constructor( + strength?: number, + center?: PIXI.Point | [number, number], + innerRadius?: number, + radius?: number + ); + + /** + * Strength of the zoom blur effect (default 0.1). + */ + strength: number; + + /** + * Center of the effect (default [0, 0]). + */ + center: PIXI.Point | [number, number]; + + /** + * The inner radius of zoom. The part in inner circle won't apply zoom blur effect + * (default 0). + */ + innerRadius: number; + + /** + * Outer radius of the effect. + * The default value is `-1`. + * `< 0.0` means it's infinity. + */ + radius: number; + } + interface ZoomBlurFilterOptions { + strength?: number; + center?: PIXI.Point | [number, number]; + innerRadius?: number; + radius?: number; + } +} + +declare namespace ct { + /** A collection of shader filters for ct.js */ + namespace filters { + /** + * The ability to adjust gamma, contrast, saturation, brightness, alpha or color-channel shift. This is a faster and much simpler to use than ColorMatrixFilter because it does not use a matrix. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.AdjustmentFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addAdjustment( + target: PIXI.DisplayObject + ): PIXI.filters.AdjustmentFilter; + + /** + * The AdvancedBloomFilter applies a Bloom Effect to an object. Unlike the normal BloomFilter this had some advanced controls for adjusting the look of the bloom. Note: this filter is slower than normal BloomFilter. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.AdvancedBloomFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addAdvancedBloom( + target: PIXI.DisplayObject + ): PIXI.filters.AdvancedBloomFilter; + + /** + * Turns everything in ASCII text. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.AsciiFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addAscii(target: PIXI.DisplayObject): PIXI.filters.AsciiFilter; + + /** + * Peforms an edge-beveling effect. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.BevelFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addBevel(target: PIXI.DisplayObject): PIXI.filters.BevelFilter; + + /** + * The BloomFilter applies a Gaussian blur to an object. The strength of the blur can be set for x- and y-axis separately. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.BloomFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addBloom(target: PIXI.DisplayObject): PIXI.filters.BloomFilter; + + /** + * Bulges or pinches the image in a circle. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.BulgePinchFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addBulgePinch( + target: PIXI.DisplayObject + ): PIXI.filters.BulgePinchFilter; + + /** + * The ColorMapFilter applies a color-map effect to an object. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @param {HTMLImageElement|HTMLCanvasElement|PIXI.BaseTexture|PIXI.Texture} colorMap - The colorMap texture of the filter. + * @return {PIXI.filters.ColorMapFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addColorMap( + target: PIXI.DisplayObject, + colorMap: HTMLImageElement|HTMLCanvasElement|PIXI.BaseTexture|PIXI.Texture, + ): PIXI.filters.ColorMapFilter; + + /** + * Replace all colors within a source graphic with a single color. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.ColorOverlayFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addColorOverlay( + target: PIXI.DisplayObject + ): PIXI.filters.ColorOverlayFilter; + + /** + * ColorReplaceFilter, originally by mishaa, updated by timetocode http://www.html5gamedevs.com/topic/10640-outline-a-sprite-change-certain-colors/?p=69966 + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.ColorReplaceFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addColorReplace( + target: PIXI.DisplayObject + ): PIXI.filters.ColorReplaceFilter; + + /** + * The ConvolutionFilter class applies a matrix convolution filter effect. A convolution combines pixels in the input image with neighboring pixels to produce a new image. A wide variety of image effects can be achieved through convolutions, including blurring, edge detection, sharpening, embossing, and beveling. The matrix should be specified as a 9 point Array. See http://docs.gimp.org/en/plug-in-convmatrix.html for more info. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.ConvolutionFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addConvolution( + target: PIXI.DisplayObject + ): PIXI.filters.ConvolutionFilter; + + /** + * A black and white cross-hatch effect filter. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.CrossHatchFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addCrossHatch( + target: PIXI.DisplayObject + ): PIXI.filters.CrossHatchFilter; + + /** + * Apply an effect resembling old CRT monitors. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.CRTFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addCRT(target: PIXI.DisplayObject): PIXI.filters.CRTFilter; + + /** + * This filter applies a dotscreen effect making display objects appear to be made out of black and white halftone dots like an old printer. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.DotFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addDot(target: PIXI.DisplayObject): PIXI.filters.DotFilter; + + /** + * Apply a drop shadow effect. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.DropShadowFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addDropShadow( + target: PIXI.DisplayObject + ): PIXI.filters.DropShadowFilter; + + /** + * Apply an emboss effect. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.EmbossFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addEmboss(target: PIXI.DisplayObject): PIXI.filters.EmbossFilter; + + /** + * Apply a glitch effect. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.GlitchFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addGlitch(target: PIXI.DisplayObject): PIXI.filters.GlitchFilter; + + /** + * GlowFilter, originally by mishaa codepen. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.GlowFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addGlow(target: PIXI.DisplayObject): PIXI.filters.GlowFilter; + + /** + * Apply and animate atmospheric light rays. Originally by Alain Galvan https://codepen.io/alaingalvan + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.GodrayFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addGodray(target: PIXI.DisplayObject): PIXI.filters.GodrayFilter; + + /** + * A much faster blur than Gaussian blur, but more complicated to use. https://software.intel.com/content/www/us/en/develop/blogs/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms.html + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.KawaseBlurFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addKawaseBlur( + target: PIXI.DisplayObject + ): PIXI.filters.KawaseBlurFilter; + + /** + * Apply a directional blur effect. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.MotionBlurFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addMotionBlur( + target: PIXI.DisplayObject + ): PIXI.filters.MotionBlurFilter; + + /** + * Filter for replacing a color with another color. Similar to ColorReplaceFilter, but support multiple colors. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @param {Array} replacements - The collection of replacement items. Each item is color-pair (an array length is 2). In the pair, the first value is original color, the second value is target color. + * @param {Array} epsilon - Tolerance of the floating-point comparison between colors. Lower = more exact, higher = more inclusive (default 0.05). + * @return {PIXI.filters.MultiColorReplaceFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addMultiColorReplace( + target: PIXI.DisplayObject, + replacements: Array, + epsilon: number + ): PIXI.filters.MultiColorReplaceFilter; + + /** + * Apply an old film effect with grain and scratches. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.OldFilmFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addOldFilm(target: PIXI.DisplayObject): PIXI.filters.OldFilmFilter; + + /** + * Apply an outline/stroke effect. Originally by mishaa http://www.html5gamedevs.com/topic/10640-outline-a-sprite-change-certain-colors/?p=69966 http://codepen.io/mishaa/pen/emGNRB + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.OutlineFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addOutline(target: PIXI.DisplayObject): PIXI.filters.OutlineFilter; + + /** + * Apply a pixelation effect. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.PixelateFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addPixelate( + target: PIXI.DisplayObject + ): PIXI.filters.PixelateFilter; + + /** + * Apply a radial blur effect. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.RadialBlurFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addRadialBlur( + target: PIXI.DisplayObject + ): PIXI.filters.RadialBlurFilter; + + /** + * Apply a reflection effect to simulate the reflection on water with waves. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.ReflectionFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addReflection( + target: PIXI.DisplayObject + ): PIXI.filters.ReflectionFilter; + + /** + * Filter to split and shift red, green or blue channels. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.RGBSplitFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addRGBSplit( + target: PIXI.DisplayObject + ): PIXI.filters.RGBSplitFilter; + + /** + * Apply a shockwave-type effect. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.ShockwaveFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addShockwave( + target: PIXI.DisplayObject + ): PIXI.filters.ShockwaveFilter; + + /** + * SimpleLightmap, originally by Oza94 http://www.html5gamedevs.com/topic/20027-pixijs-simple-lightmapping/ http://codepen.io/Oza94/pen/EPoRxj + * You have to specify filterArea, or suffer consequences. You may have to use it with filter.dontFit = true, until we rewrite this using same approach as for DisplacementFilter. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.SimpleLightmapFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addSimpleLightmap( + target: PIXI.DisplayObject + ): PIXI.filters.SimpleLightmapFilter; + + /** + * Apply a tilt-shift-like camera effect. Manages the pass of both a TiltShiftXFilter and TiltShiftYFilter. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.TiltShiftFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addTiltShift( + target: PIXI.DisplayObject + ): PIXI.filters.TiltShiftFilter; + + /** + * Apply a twist effect making display objects appear twisted in the given direction. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.TwistFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addTwist(target: PIXI.DisplayObject): PIXI.filters.TwistFilter; + + /** + * Apply a zoom blur effect. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.ZoomBlurFilter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addZoomBlur( + target: PIXI.DisplayObject + ): PIXI.filters.ZoomBlurFilter; + + /** + * Simplest filter - applies alpha. + * Use this instead of Container's alpha property to avoid visual layering of individual elements. AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. If elements are not opaque, they will blend with each other anyway. + * Very handy if you want to use common features of all filters: + * Assign a blendMode to this filter, blend all elements inside display object with background. + * To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.AlphaFilter } - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addAlpha(target: PIXI.DisplayObject): PIXI.filters.AlphaFilter; + + /** + * The BlurFilter applies a Gaussian blur to an object. + * The strength of the blur can be set for the x-axis and y-axis separately. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.BlurFilter } - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addBlur(target: PIXI.DisplayObject): PIXI.filters.BlurFilter; + + /** + * The BlurFilterPass applies a horizontal or vertical Gaussian blur to an object. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.BlurFilterPass } - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addBlurPass( + target: PIXI.DisplayObject + ): PIXI.filters.BlurFilterPass; + + /** + * The ColorMatrixFilter class lets you apply a 5x4 matrix transformation on the RGBA color and alpha values of every pixel on your displayObject to produce a result with a new set of RGBA color and alpha values. It's pretty powerful! + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.ColorMatrixFilter } - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addColorMatrix( + target: PIXI.DisplayObject + ): PIXI.filters.ColorMatrixFilter; + + /** + * The DisplacementFilter class uses the pixel values from the specified texture (called the displacement map) to perform a displacement of an object. + * You can use this filter to apply all manor of crazy warping effects. Currently the r property of the texture is used to offset the x and the g property of the texture is used to offset the y. + * The way it works is it uses the values of the displacement map to look up the correct pixels to output. + * This means it's not technically moving the original. + * Instead, it's starting at the output and asking "which pixel from the original goes here". + * For example, if a displacement map pixel has red = 1 and the filter scale is 20, this filter will output the pixel approximately 20 pixels to the right of the original. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.DisplacementFilter } - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addDisplacement( + target: PIXI.DisplayObject + ): PIXI.filters.DisplacementFilter; + + /** + * Basic FXAA (Fast Approximate Anti-Aliasing) implementation based on the code on geeks3d.com with the modification that the texture2DLod stuff was removed since it is unsupported by WebGL. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.FXAAFilter } - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addFXAA( + target: PIXI.DisplayObject + ): PIXI.filters.FXAAFilter; + + /** + * A Noise effect filter. + * Original filter: https://github.com/evanw/glfx.js/blob/master/src/filters/adjust/noise.js + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filters.NoiseFilter } - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function addNoise( + target: PIXI.DisplayObject + ): PIXI.filters.NoiseFilter; + + /** + * Add a custom filter. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @param {String} vertex - The vertex part of the shader: https://www.khronos.org/opengl/wiki/Vertex_Shader + * @param {String} fragment - The fragment part of the shader: https://www.khronos.org/opengl/wiki/Fragment_Shader + * @param {Object} uniforms - Custom uniforms to use to augment the built-in ones: https://www.khronos.org/opengl/wiki/Uniform_(GLSL) + * @return {PIXI.filter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function custom(target: PIXI.DisplayObject): PIXI.filter; + + /** + * Remove a filter. + * @param {PIXI.DisplayObject} target - Element (room, copy, container, etc.) to apply the filter. + * @return {PIXI.filter} - Filter is a special type of WebGL shader that is applied to the screen or a part of the screen. + */ + function remove(target: PIXI.DisplayObject): PIXI.filter; + } +} From 165853eb356e44df44a3c2a2a40c9b14128b0623 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Mon, 22 Mar 2021 10:13:06 +1200 Subject: [PATCH 18/32] :bug: Fix bitmap font's XML ("kerings" typo") --- src/node_requires/exporter/fonts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_requires/exporter/fonts.js b/src/node_requires/exporter/fonts.js index 8cf83facf..9391b73e4 100644 --- a/src/node_requires/exporter/fonts.js +++ b/src/node_requires/exporter/fonts.js @@ -21,7 +21,7 @@ const bundleFonts = async function (proj, projdir, writeDir) { var ttf = new Uint8Array(fontData); let woff; try { - woff = new Buffer(ttf2woff(ttf).buffer); + woff = Buffer.from(ttf2woff(ttf).buffer); } catch (e) { window.alertify.error(`Whoah! A buggy ttf file in the font ${font.typefaceName} ${font.weight} ${font.italic ? 'italic' : 'normal'}. You should either fix it or find a new one.`); throw e; @@ -78,7 +78,7 @@ const generateXML = function generateXML(fontData, ctFont, typefaceName) { XMLTemplate += ` - + `; return XMLTemplate; From ccbafdb17f7739878f1f37008416b7157e416e89 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Mon, 22 Mar 2021 10:21:27 +1200 Subject: [PATCH 19/32] :bug: In rooms' copy spawning code, check for scaling extensions separately --- app/data/ct.release/types.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/data/ct.release/types.js b/app/data/ct.release/types.js index 28678a1b0..2da2a8811 100644 --- a/app/data/ct.release/types.js +++ b/app/data/ct.release/types.js @@ -67,6 +67,8 @@ const Copy = (function Copy() { ct.u.ext(this, exts); if (exts.tx) { this.scale.x = exts.tx; + } + if (exts.ty) { this.scale.y = exts.ty; } if (exts.tr) { From 605731911dddf6af85c43caf1f671e2ef910f19e Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Mon, 22 Mar 2021 11:02:06 +1200 Subject: [PATCH 20/32] :bug: Fix flickering of just created lights --- app/data/ct.libs/light/index.js | 29 ++++++++++++---------- app/data/ct.libs/light/injects/oncreate.js | 4 +++ 2 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 app/data/ct.libs/light/injects/oncreate.js diff --git a/app/data/ct.libs/light/index.js b/app/data/ct.libs/light/index.js index 1febd332a..261205b9d 100644 --- a/app/data/ct.libs/light/index.js +++ b/app/data/ct.libs/light/index.js @@ -60,21 +60,24 @@ } renderer.render(lightLayer, renderTexture); }, + updateOne(light) { + if (light.owner) { + if (!ct.types.exists(light.owner)) { + ct.light.remove(light); + return; + } + light.transform.setFromMatrix(light.owner.worldTransform); + light.scale.x *= light.scaleFactor || 1; + light.scale.y *= light.scaleFactor || 1; + light.angle -= light.rotationFactor || 0; + if (light.copyOpacity) { + light.alpha = light.owner.alpha; + } + } + }, update() { for (const light of ct.light.lights) { - if (light.owner) { - if (!ct.types.exists(light.owner)) { - ct.light.remove(light); - continue; - } - light.transform.setFromMatrix(light.owner.worldTransform); - light.scale.x *= light.scaleFactor || 1; - light.scale.y *= light.scaleFactor || 1; - light.angle -= light.rotationFactor || 0; - if (light.copyOpacity) { - light.alpha = light.owner.alpha; - } - } + ct.light.updateOne(light); } }, clear() { diff --git a/app/data/ct.libs/light/injects/oncreate.js b/app/data/ct.libs/light/injects/oncreate.js new file mode 100644 index 000000000..92ab6d755 --- /dev/null +++ b/app/data/ct.libs/light/injects/oncreate.js @@ -0,0 +1,4 @@ +if ((this instanceof ct.types.Copy) && this.lightTexture) { + this.updateTransform(); + ct.light.updateOne(this.light); +} From ccb80efd2796890a6936c353973675b43835eac9 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Mon, 22 Mar 2021 12:46:42 +1200 Subject: [PATCH 21/32] :bug: Fix lights being shifted because of a dirty transform matrix --- app/data/ct.libs/light/index.js | 5 +++-- app/data/ct.libs/light/injects/afterroomdraw.js | 6 ++++++ app/data/ct.libs/light/injects/beforeroomdraw.js | 4 ---- 3 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 app/data/ct.libs/light/injects/afterroomdraw.js delete mode 100644 app/data/ct.libs/light/injects/beforeroomdraw.js diff --git a/app/data/ct.libs/light/index.js b/app/data/ct.libs/light/index.js index 261205b9d..fed156a18 100644 --- a/app/data/ct.libs/light/index.js +++ b/app/data/ct.libs/light/index.js @@ -55,8 +55,8 @@ renderTexture.resize(ct.pixiApp.screen.width, ct.pixiApp.screen.height); bg.width = ct.pixiApp.screen.width; bg.height = ct.pixiApp.screen.height; - lightSprite.width = ct.camera.width; - lightSprite.height = ct.camera.height; + lightSprite.width = Math.ceil(ct.camera.width); + lightSprite.height = Math.ceil(ct.camera.height); } renderer.render(lightLayer, renderTexture); }, @@ -76,6 +76,7 @@ } }, update() { + ct.room.updateTransform(); for (const light of ct.light.lights) { ct.light.updateOne(light); } diff --git a/app/data/ct.libs/light/injects/afterroomdraw.js b/app/data/ct.libs/light/injects/afterroomdraw.js new file mode 100644 index 000000000..cbfbafd57 --- /dev/null +++ b/app/data/ct.libs/light/injects/afterroomdraw.js @@ -0,0 +1,6 @@ +if (this === ct.room) { + (function ctLightRender() { + ct.light.update(); + ct.light.render(); + })(); +} diff --git a/app/data/ct.libs/light/injects/beforeroomdraw.js b/app/data/ct.libs/light/injects/beforeroomdraw.js deleted file mode 100644 index 87c290595..000000000 --- a/app/data/ct.libs/light/injects/beforeroomdraw.js +++ /dev/null @@ -1,4 +0,0 @@ -(function ctLightRender() { - ct.light.update(); - ct.light.render(); -})(); From 829d4a801d355529dd463b8ed682a9854970010e Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Mon, 22 Mar 2021 13:01:21 +1200 Subject: [PATCH 22/32] =?UTF-8?q?:bento:=20Bundle=20ct.nakama=20module=20b?= =?UTF-8?q?y=20@alexandargyurov=20=E2=80=94=20you=20can=20now=20create=20o?= =?UTF-8?q?nline=20games=20with=20ct.js!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/data/ct.libs/nakama/.babelrc | 9 + app/data/ct.libs/nakama/LICENSE | 202 +++++++++++++++++ app/data/ct.libs/nakama/README.md | 82 +++++++ app/data/ct.libs/nakama/index.js | 246 +++++++++++++++++++++ app/data/ct.libs/nakama/module.json | 52 +++++ app/data/ct.libs/nakama/package.json | 38 ++++ app/data/ct.libs/nakama/src/index.js | 6 + app/data/ct.libs/nakama/src/logger.js | 19 ++ app/data/ct.libs/nakama/src/nakama.js | 70 ++++++ app/data/ct.libs/nakama/src/nakama.test.js | 69 ++++++ app/data/ct.libs/nakama/types.d.ts | 62 ++++++ app/data/ct.libs/nakama/webpack.config.js | 28 +++ 12 files changed, 883 insertions(+) create mode 100644 app/data/ct.libs/nakama/.babelrc create mode 100644 app/data/ct.libs/nakama/LICENSE create mode 100644 app/data/ct.libs/nakama/README.md create mode 100644 app/data/ct.libs/nakama/index.js create mode 100644 app/data/ct.libs/nakama/module.json create mode 100644 app/data/ct.libs/nakama/package.json create mode 100644 app/data/ct.libs/nakama/src/index.js create mode 100644 app/data/ct.libs/nakama/src/logger.js create mode 100644 app/data/ct.libs/nakama/src/nakama.js create mode 100644 app/data/ct.libs/nakama/src/nakama.test.js create mode 100644 app/data/ct.libs/nakama/types.d.ts create mode 100644 app/data/ct.libs/nakama/webpack.config.js diff --git a/app/data/ct.libs/nakama/.babelrc b/app/data/ct.libs/nakama/.babelrc new file mode 100644 index 000000000..6d63250b4 --- /dev/null +++ b/app/data/ct.libs/nakama/.babelrc @@ -0,0 +1,9 @@ +{ + "presets": [ + "@babel/preset-env" + ], + "plugins": [ + "@babel/plugin-proposal-class-properties", + "@babel/transform-runtime" + ] +} \ No newline at end of file diff --git a/app/data/ct.libs/nakama/LICENSE b/app/data/ct.libs/nakama/LICENSE new file mode 100644 index 000000000..75b52484e --- /dev/null +++ b/app/data/ct.libs/nakama/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/app/data/ct.libs/nakama/README.md b/app/data/ct.libs/nakama/README.md new file mode 100644 index 000000000..a965b7e9c --- /dev/null +++ b/app/data/ct.libs/nakama/README.md @@ -0,0 +1,82 @@ + +# Multiplayer in ct.js! +This is a catmod to enable the use of the [Nakama JS Client library](https://heroiclabs.com/docs/javascript-client-guide/) into the [ct.js game engine](https://ctjs.rocks/). You can now build a fully scalable multiplayer game all with the amazing tools ct.js provides! + +Nakama makes it possible to create virtually any type of multiplayer experience you desire. Here are some of its features: + + - Low latency realtime engine + - Server-authoritative multiplayer + - Match listing and lobby rooms + - Leaderboards + - Social sign-in: Facebook, Google, and more + - Realtime, room-based, persisted chat channels + - [And a lot more!](https://heroiclabs.com/) + +## Video Tutorial Series! +[![image](https://user-images.githubusercontent.com/10382821/109438818-7af12480-7a23-11eb-8752-f67d6c42a44c.png)](https://www.youtube.com/watch?v=Glo9t3TV1vg&list=PLOoNs4RDYDKDtF5LO-LwuJiRD6m81rI8e +) +(image will take you to YouTube once clicked) + +## Notice: +This catmod is still in the very early stages of development. **It is not production ready!** I plan to create documentation, examples and a tutorial series to go through how to use this library all in the near future once the codebase gets stable. + +Any feedback is welcome and I am super excited to what we can build with this. + +## Getting Started: + +1. You'll need a [local instance of a Nakama server](https://heroiclabs.com/docs/nakama-download/) before you begin. +2. Download the [latest release](https://github.com/alexandargyurov/ct.nakama/releases) of this catmod. +3. Import the module into ct.js in the `Catmods` section. +4. You should be all set! πŸš€ + + +## Example Usage: +⚠️ Not all properties and functions have been documented! + +You can access the global variable called `Nakama` anywhere in ct.js. This is the class which provides you with all the functionality of Nakama. + +|Property|Description| +|--|--| +|`Nakama.client` |Returns the client| +|`Nakama.session`| Returns the current client session +|`Nakama.socket` |Returns the current socket connection to the server| +|`Nakama.state` |Returns the current state, defaults to an empty object. Useful for storing current players and other global objects.| + +### Create a match +From https://heroiclabs.com/docs/gameplay-multiplayer-realtime/#create-a-match +``` +let response = await Nakama.socket.createMatch(); +console.log("Created match with ID:", response.match.match_id); +``` + +### Join a match +From https://heroiclabs.com/docs/gameplay-multiplayer-realtime/#join-a-match +``` +let id = ""; +let match = await Nakama.socket.joinMatch(id); +Nakama.state.players = match.presences + +Nakama.state.players.forEach((opponent) => { + console.log("User id %o, username %o.", opponent.user_id, opponent.username); +}); +``` +## Useful Links + +https://github.com/heroiclabs/nakama-js + +https://heroiclabs.com/docs/ + +## Getting Help +Please open any issue such as bugs πŸ› or feature requests ✨ on GitHub. You can find me on the [ct.js Discord](https://discord.gg/Egwh9ETmJF) as well if you want to chat :) + +## Contributing +All contribution is welcome. Like I mentioned, this is still in the early days of a development, there's no roadmap, no planned features, just me working on it as I go along. + +If you'd like to build this yourself: + 1. Clone the repo + 2. `npm install` + 3. `npm build` + 5. A `dist` folder gets created with a zip for ct.js (you might need to create the dist folder in advance) + +## Licensing +As the [heroiclabs/nakama-js](https://github.com/heroiclabs/nakama-js) is under the Apache License 2.0, this library also falls under the Apache-2 License. diff --git a/app/data/ct.libs/nakama/index.js b/app/data/ct.libs/nakama/index.js new file mode 100644 index 000000000..c7f397a9b --- /dev/null +++ b/app/data/ct.libs/nakama/index.js @@ -0,0 +1,246 @@ +/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +var Nakama; +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ "./node_modules/@babel/runtime/helpers/asyncToGenerator.js": +/*!*****************************************************************!*\ + !*** ./node_modules/@babel/runtime/helpers/asyncToGenerator.js ***! + \*****************************************************************/ +/***/ ((module) => { + +eval("function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {\n try {\n var info = gen[key](arg);\n var value = info.value;\n } catch (error) {\n reject(error);\n return;\n }\n\n if (info.done) {\n resolve(value);\n } else {\n Promise.resolve(value).then(_next, _throw);\n }\n}\n\nfunction _asyncToGenerator(fn) {\n return function () {\n var self = this,\n args = arguments;\n return new Promise(function (resolve, reject) {\n var gen = fn.apply(self, args);\n\n function _next(value) {\n asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"next\", value);\n }\n\n function _throw(err) {\n asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"throw\", err);\n }\n\n _next(undefined);\n });\n };\n}\n\nmodule.exports = _asyncToGenerator;\n\n//# sourceURL=webpack://Nakama/./node_modules/@babel/runtime/helpers/asyncToGenerator.js?"); + +/***/ }), + +/***/ "./node_modules/@babel/runtime/helpers/classCallCheck.js": +/*!***************************************************************!*\ + !*** ./node_modules/@babel/runtime/helpers/classCallCheck.js ***! + \***************************************************************/ +/***/ ((module) => { + +eval("function _classCallCheck(instance, Constructor) {\n if (!(instance instanceof Constructor)) {\n throw new TypeError(\"Cannot call a class as a function\");\n }\n}\n\nmodule.exports = _classCallCheck;\n\n//# sourceURL=webpack://Nakama/./node_modules/@babel/runtime/helpers/classCallCheck.js?"); + +/***/ }), + +/***/ "./node_modules/@babel/runtime/helpers/createClass.js": +/*!************************************************************!*\ + !*** ./node_modules/@babel/runtime/helpers/createClass.js ***! + \************************************************************/ +/***/ ((module) => { + +eval("function _defineProperties(target, props) {\n for (var i = 0; i < props.length; i++) {\n var descriptor = props[i];\n descriptor.enumerable = descriptor.enumerable || false;\n descriptor.configurable = true;\n if (\"value\" in descriptor) descriptor.writable = true;\n Object.defineProperty(target, descriptor.key, descriptor);\n }\n}\n\nfunction _createClass(Constructor, protoProps, staticProps) {\n if (protoProps) _defineProperties(Constructor.prototype, protoProps);\n if (staticProps) _defineProperties(Constructor, staticProps);\n return Constructor;\n}\n\nmodule.exports = _createClass;\n\n//# sourceURL=webpack://Nakama/./node_modules/@babel/runtime/helpers/createClass.js?"); + +/***/ }), + +/***/ "./node_modules/@babel/runtime/helpers/defineProperty.js": +/*!***************************************************************!*\ + !*** ./node_modules/@babel/runtime/helpers/defineProperty.js ***! + \***************************************************************/ +/***/ ((module) => { + +eval("function _defineProperty(obj, key, value) {\n if (key in obj) {\n Object.defineProperty(obj, key, {\n value: value,\n enumerable: true,\n configurable: true,\n writable: true\n });\n } else {\n obj[key] = value;\n }\n\n return obj;\n}\n\nmodule.exports = _defineProperty;\n\n//# sourceURL=webpack://Nakama/./node_modules/@babel/runtime/helpers/defineProperty.js?"); + +/***/ }), + +/***/ "./node_modules/@babel/runtime/regenerator/index.js": +/*!**********************************************************!*\ + !*** ./node_modules/@babel/runtime/regenerator/index.js ***! + \**********************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("module.exports = __webpack_require__(/*! regenerator-runtime */ \"./node_modules/regenerator-runtime/runtime.js\");\n\n\n//# sourceURL=webpack://Nakama/./node_modules/@babel/runtime/regenerator/index.js?"); + +/***/ }), + +/***/ "./node_modules/@heroiclabs/nakama-js/dist/nakama-js.esm.js": +/*!******************************************************************!*\ + !*** ./node_modules/@heroiclabs/nakama-js/dist/nakama-js.esm.js ***! + \******************************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"Client\": () => (/* binding */ Client),\n/* harmony export */ \"DefaultSocket\": () => (/* binding */ DefaultSocket),\n/* harmony export */ \"Session\": () => (/* binding */ Session),\n/* harmony export */ \"WebSocketAdapterText\": () => (/* binding */ WebSocketAdapterText)\n/* harmony export */ });\nvar __create = Object.create;\nvar __defProp = Object.defineProperty;\nvar __getProtoOf = Object.getPrototypeOf;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __assign = Object.assign;\nvar __markAsModule = (target) => __defProp(target, \"__esModule\", {value: true});\nvar __commonJS = (callback, module) => () => {\n if (!module) {\n module = {exports: {}};\n callback(module.exports, module);\n }\n return module.exports;\n};\nvar __exportStar = (target, module, desc) => {\n __markAsModule(target);\n if (module && typeof module === \"object\" || typeof module === \"function\") {\n for (let key of __getOwnPropNames(module))\n if (!__hasOwnProp.call(target, key) && key !== \"default\")\n __defProp(target, key, {get: () => module[key], enumerable: !(desc = __getOwnPropDesc(module, key)) || desc.enumerable});\n }\n return target;\n};\nvar __toModule = (module) => {\n if (module && module.__esModule)\n return module;\n return __exportStar(__defProp(module != null ? __create(__getProtoOf(module)) : {}, \"default\", {value: module, enumerable: true}), module);\n};\nvar __async = (__this, __arguments, generator) => {\n return new Promise((resolve, reject) => {\n var fulfilled = (value) => {\n try {\n step(generator.next(value));\n } catch (e) {\n reject(e);\n }\n };\n var rejected = (value) => {\n try {\n step(generator.throw(value));\n } catch (e) {\n reject(e);\n }\n };\n var step = (result) => {\n return result.done ? resolve(result.value) : Promise.resolve(result.value).then(fulfilled, rejected);\n };\n step((generator = generator.apply(__this, __arguments)).next());\n });\n};\n\n// node_modules/whatwg-fetch/fetch.js\nvar require_fetch = __commonJS((exports) => {\n (function(self2) {\n \"use strict\";\n if (self2.fetch) {\n return;\n }\n var support = {\n searchParams: \"URLSearchParams\" in self2,\n iterable: \"Symbol\" in self2 && \"iterator\" in Symbol,\n blob: \"FileReader\" in self2 && \"Blob\" in self2 && function() {\n try {\n new Blob();\n return true;\n } catch (e) {\n return false;\n }\n }(),\n formData: \"FormData\" in self2,\n arrayBuffer: \"ArrayBuffer\" in self2\n };\n if (support.arrayBuffer) {\n var viewClasses = [\n \"[object Int8Array]\",\n \"[object Uint8Array]\",\n \"[object Uint8ClampedArray]\",\n \"[object Int16Array]\",\n \"[object Uint16Array]\",\n \"[object Int32Array]\",\n \"[object Uint32Array]\",\n \"[object Float32Array]\",\n \"[object Float64Array]\"\n ];\n var isDataView = function(obj) {\n return obj && DataView.prototype.isPrototypeOf(obj);\n };\n var isArrayBufferView = ArrayBuffer.isView || function(obj) {\n return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;\n };\n }\n function normalizeName(name) {\n if (typeof name !== \"string\") {\n name = String(name);\n }\n if (/[^a-z0-9\\-#$%&'*+.\\^_`|~]/i.test(name)) {\n throw new TypeError(\"Invalid character in header field name\");\n }\n return name.toLowerCase();\n }\n function normalizeValue(value) {\n if (typeof value !== \"string\") {\n value = String(value);\n }\n return value;\n }\n function iteratorFor(items) {\n var iterator = {\n next: function() {\n var value = items.shift();\n return {done: value === void 0, value};\n }\n };\n if (support.iterable) {\n iterator[Symbol.iterator] = function() {\n return iterator;\n };\n }\n return iterator;\n }\n function Headers(headers) {\n this.map = {};\n if (headers instanceof Headers) {\n headers.forEach(function(value, name) {\n this.append(name, value);\n }, this);\n } else if (Array.isArray(headers)) {\n headers.forEach(function(header) {\n this.append(header[0], header[1]);\n }, this);\n } else if (headers) {\n Object.getOwnPropertyNames(headers).forEach(function(name) {\n this.append(name, headers[name]);\n }, this);\n }\n }\n Headers.prototype.append = function(name, value) {\n name = normalizeName(name);\n value = normalizeValue(value);\n var oldValue = this.map[name];\n this.map[name] = oldValue ? oldValue + \",\" + value : value;\n };\n Headers.prototype[\"delete\"] = function(name) {\n delete this.map[normalizeName(name)];\n };\n Headers.prototype.get = function(name) {\n name = normalizeName(name);\n return this.has(name) ? this.map[name] : null;\n };\n Headers.prototype.has = function(name) {\n return this.map.hasOwnProperty(normalizeName(name));\n };\n Headers.prototype.set = function(name, value) {\n this.map[normalizeName(name)] = normalizeValue(value);\n };\n Headers.prototype.forEach = function(callback, thisArg) {\n for (var name in this.map) {\n if (this.map.hasOwnProperty(name)) {\n callback.call(thisArg, this.map[name], name, this);\n }\n }\n };\n Headers.prototype.keys = function() {\n var items = [];\n this.forEach(function(value, name) {\n items.push(name);\n });\n return iteratorFor(items);\n };\n Headers.prototype.values = function() {\n var items = [];\n this.forEach(function(value) {\n items.push(value);\n });\n return iteratorFor(items);\n };\n Headers.prototype.entries = function() {\n var items = [];\n this.forEach(function(value, name) {\n items.push([name, value]);\n });\n return iteratorFor(items);\n };\n if (support.iterable) {\n Headers.prototype[Symbol.iterator] = Headers.prototype.entries;\n }\n function consumed(body) {\n if (body.bodyUsed) {\n return Promise.reject(new TypeError(\"Already read\"));\n }\n body.bodyUsed = true;\n }\n function fileReaderReady(reader) {\n return new Promise(function(resolve, reject) {\n reader.onload = function() {\n resolve(reader.result);\n };\n reader.onerror = function() {\n reject(reader.error);\n };\n });\n }\n function readBlobAsArrayBuffer(blob) {\n var reader = new FileReader();\n var promise = fileReaderReady(reader);\n reader.readAsArrayBuffer(blob);\n return promise;\n }\n function readBlobAsText(blob) {\n var reader = new FileReader();\n var promise = fileReaderReady(reader);\n reader.readAsText(blob);\n return promise;\n }\n function readArrayBufferAsText(buf) {\n var view = new Uint8Array(buf);\n var chars = new Array(view.length);\n for (var i = 0; i < view.length; i++) {\n chars[i] = String.fromCharCode(view[i]);\n }\n return chars.join(\"\");\n }\n function bufferClone(buf) {\n if (buf.slice) {\n return buf.slice(0);\n } else {\n var view = new Uint8Array(buf.byteLength);\n view.set(new Uint8Array(buf));\n return view.buffer;\n }\n }\n function Body() {\n this.bodyUsed = false;\n this._initBody = function(body) {\n this._bodyInit = body;\n if (!body) {\n this._bodyText = \"\";\n } else if (typeof body === \"string\") {\n this._bodyText = body;\n } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {\n this._bodyBlob = body;\n } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {\n this._bodyFormData = body;\n } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {\n this._bodyText = body.toString();\n } else if (support.arrayBuffer && support.blob && isDataView(body)) {\n this._bodyArrayBuffer = bufferClone(body.buffer);\n this._bodyInit = new Blob([this._bodyArrayBuffer]);\n } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {\n this._bodyArrayBuffer = bufferClone(body);\n } else {\n throw new Error(\"unsupported BodyInit type\");\n }\n if (!this.headers.get(\"content-type\")) {\n if (typeof body === \"string\") {\n this.headers.set(\"content-type\", \"text/plain;charset=UTF-8\");\n } else if (this._bodyBlob && this._bodyBlob.type) {\n this.headers.set(\"content-type\", this._bodyBlob.type);\n } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {\n this.headers.set(\"content-type\", \"application/x-www-form-urlencoded;charset=UTF-8\");\n }\n }\n };\n if (support.blob) {\n this.blob = function() {\n var rejected = consumed(this);\n if (rejected) {\n return rejected;\n }\n if (this._bodyBlob) {\n return Promise.resolve(this._bodyBlob);\n } else if (this._bodyArrayBuffer) {\n return Promise.resolve(new Blob([this._bodyArrayBuffer]));\n } else if (this._bodyFormData) {\n throw new Error(\"could not read FormData body as blob\");\n } else {\n return Promise.resolve(new Blob([this._bodyText]));\n }\n };\n this.arrayBuffer = function() {\n if (this._bodyArrayBuffer) {\n return consumed(this) || Promise.resolve(this._bodyArrayBuffer);\n } else {\n return this.blob().then(readBlobAsArrayBuffer);\n }\n };\n }\n this.text = function() {\n var rejected = consumed(this);\n if (rejected) {\n return rejected;\n }\n if (this._bodyBlob) {\n return readBlobAsText(this._bodyBlob);\n } else if (this._bodyArrayBuffer) {\n return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));\n } else if (this._bodyFormData) {\n throw new Error(\"could not read FormData body as text\");\n } else {\n return Promise.resolve(this._bodyText);\n }\n };\n if (support.formData) {\n this.formData = function() {\n return this.text().then(decode2);\n };\n }\n this.json = function() {\n return this.text().then(JSON.parse);\n };\n return this;\n }\n var methods = [\"DELETE\", \"GET\", \"HEAD\", \"OPTIONS\", \"POST\", \"PUT\"];\n function normalizeMethod(method) {\n var upcased = method.toUpperCase();\n return methods.indexOf(upcased) > -1 ? upcased : method;\n }\n function Request(input, options) {\n options = options || {};\n var body = options.body;\n if (input instanceof Request) {\n if (input.bodyUsed) {\n throw new TypeError(\"Already read\");\n }\n this.url = input.url;\n this.credentials = input.credentials;\n if (!options.headers) {\n this.headers = new Headers(input.headers);\n }\n this.method = input.method;\n this.mode = input.mode;\n if (!body && input._bodyInit != null) {\n body = input._bodyInit;\n input.bodyUsed = true;\n }\n } else {\n this.url = String(input);\n }\n this.credentials = options.credentials || this.credentials || \"omit\";\n if (options.headers || !this.headers) {\n this.headers = new Headers(options.headers);\n }\n this.method = normalizeMethod(options.method || this.method || \"GET\");\n this.mode = options.mode || this.mode || null;\n this.referrer = null;\n if ((this.method === \"GET\" || this.method === \"HEAD\") && body) {\n throw new TypeError(\"Body not allowed for GET or HEAD requests\");\n }\n this._initBody(body);\n }\n Request.prototype.clone = function() {\n return new Request(this, {body: this._bodyInit});\n };\n function decode2(body) {\n var form = new FormData();\n body.trim().split(\"&\").forEach(function(bytes) {\n if (bytes) {\n var split = bytes.split(\"=\");\n var name = split.shift().replace(/\\+/g, \" \");\n var value = split.join(\"=\").replace(/\\+/g, \" \");\n form.append(decodeURIComponent(name), decodeURIComponent(value));\n }\n });\n return form;\n }\n function parseHeaders(rawHeaders) {\n var headers = new Headers();\n var preProcessedHeaders = rawHeaders.replace(/\\r?\\n[\\t ]+/g, \" \");\n preProcessedHeaders.split(/\\r?\\n/).forEach(function(line) {\n var parts = line.split(\":\");\n var key = parts.shift().trim();\n if (key) {\n var value = parts.join(\":\").trim();\n headers.append(key, value);\n }\n });\n return headers;\n }\n Body.call(Request.prototype);\n function Response(bodyInit, options) {\n if (!options) {\n options = {};\n }\n this.type = \"default\";\n this.status = options.status === void 0 ? 200 : options.status;\n this.ok = this.status >= 200 && this.status < 300;\n this.statusText = \"statusText\" in options ? options.statusText : \"OK\";\n this.headers = new Headers(options.headers);\n this.url = options.url || \"\";\n this._initBody(bodyInit);\n }\n Body.call(Response.prototype);\n Response.prototype.clone = function() {\n return new Response(this._bodyInit, {\n status: this.status,\n statusText: this.statusText,\n headers: new Headers(this.headers),\n url: this.url\n });\n };\n Response.error = function() {\n var response = new Response(null, {status: 0, statusText: \"\"});\n response.type = \"error\";\n return response;\n };\n var redirectStatuses = [301, 302, 303, 307, 308];\n Response.redirect = function(url, status) {\n if (redirectStatuses.indexOf(status) === -1) {\n throw new RangeError(\"Invalid status code\");\n }\n return new Response(null, {status, headers: {location: url}});\n };\n self2.Headers = Headers;\n self2.Request = Request;\n self2.Response = Response;\n self2.fetch = function(input, init) {\n return new Promise(function(resolve, reject) {\n var request = new Request(input, init);\n var xhr = new XMLHttpRequest();\n xhr.onload = function() {\n var options = {\n status: xhr.status,\n statusText: xhr.statusText,\n headers: parseHeaders(xhr.getAllResponseHeaders() || \"\")\n };\n options.url = \"responseURL\" in xhr ? xhr.responseURL : options.headers.get(\"X-Request-URL\");\n var body = \"response\" in xhr ? xhr.response : xhr.responseText;\n resolve(new Response(body, options));\n };\n xhr.onerror = function() {\n reject(new TypeError(\"Network request failed\"));\n };\n xhr.ontimeout = function() {\n reject(new TypeError(\"Network request failed\"));\n };\n xhr.open(request.method, request.url, true);\n if (request.credentials === \"include\") {\n xhr.withCredentials = true;\n } else if (request.credentials === \"omit\") {\n xhr.withCredentials = false;\n }\n if (\"responseType\" in xhr && support.blob) {\n xhr.responseType = \"blob\";\n }\n request.headers.forEach(function(value, name) {\n xhr.setRequestHeader(name, value);\n });\n xhr.send(typeof request._bodyInit === \"undefined\" ? null : request._bodyInit);\n });\n };\n self2.fetch.polyfill = true;\n })(typeof self !== \"undefined\" ? self : exports);\n});\n\n// index.ts\nvar import_whatwg_fetch = __toModule(require_fetch());\n\n// node_modules/js-base64/base64.mjs\nvar _hasatob = typeof atob === \"function\";\nvar _hasbtoa = typeof btoa === \"function\";\nvar _hasBuffer = typeof Buffer === \"function\";\nvar _TD = typeof TextDecoder === \"function\" ? new TextDecoder() : void 0;\nvar _TE = typeof TextEncoder === \"function\" ? new TextEncoder() : void 0;\nvar b64ch = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";\nvar b64chs = [...b64ch];\nvar b64tab = ((a) => {\n let tab = {};\n a.forEach((c, i) => tab[c] = i);\n return tab;\n})(b64chs);\nvar b64re = /^(?:[A-Za-z\\d+\\/]{4})*?(?:[A-Za-z\\d+\\/]{2}(?:==)?|[A-Za-z\\d+\\/]{3}=?)?$/;\nvar _fromCC = String.fromCharCode.bind(String);\nvar _U8Afrom = typeof Uint8Array.from === \"function\" ? Uint8Array.from.bind(Uint8Array) : (it, fn = (x) => x) => new Uint8Array(Array.prototype.slice.call(it, 0).map(fn));\nvar _mkUriSafe = (src) => src.replace(/[+\\/]/g, (m0) => m0 == \"+\" ? \"-\" : \"_\").replace(/=+$/m, \"\");\nvar _tidyB64 = (s) => s.replace(/[^A-Za-z0-9\\+\\/]/g, \"\");\nvar btoaPolyfill = (bin) => {\n let u32, c0, c1, c2, asc = \"\";\n const pad = bin.length % 3;\n for (let i = 0; i < bin.length; ) {\n if ((c0 = bin.charCodeAt(i++)) > 255 || (c1 = bin.charCodeAt(i++)) > 255 || (c2 = bin.charCodeAt(i++)) > 255)\n throw new TypeError(\"invalid character found\");\n u32 = c0 << 16 | c1 << 8 | c2;\n asc += b64chs[u32 >> 18 & 63] + b64chs[u32 >> 12 & 63] + b64chs[u32 >> 6 & 63] + b64chs[u32 & 63];\n }\n return pad ? asc.slice(0, pad - 3) + \"===\".substring(pad) : asc;\n};\nvar _btoa = _hasbtoa ? (bin) => btoa(bin) : _hasBuffer ? (bin) => Buffer.from(bin, \"binary\").toString(\"base64\") : btoaPolyfill;\nvar _fromUint8Array = _hasBuffer ? (u8a) => Buffer.from(u8a).toString(\"base64\") : (u8a) => {\n const maxargs = 4096;\n let strs = [];\n for (let i = 0, l = u8a.length; i < l; i += maxargs) {\n strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs)));\n }\n return _btoa(strs.join(\"\"));\n};\nvar cb_utob = (c) => {\n if (c.length < 2) {\n var cc = c.charCodeAt(0);\n return cc < 128 ? c : cc < 2048 ? _fromCC(192 | cc >>> 6) + _fromCC(128 | cc & 63) : _fromCC(224 | cc >>> 12 & 15) + _fromCC(128 | cc >>> 6 & 63) + _fromCC(128 | cc & 63);\n } else {\n var cc = 65536 + (c.charCodeAt(0) - 55296) * 1024 + (c.charCodeAt(1) - 56320);\n return _fromCC(240 | cc >>> 18 & 7) + _fromCC(128 | cc >>> 12 & 63) + _fromCC(128 | cc >>> 6 & 63) + _fromCC(128 | cc & 63);\n }\n};\nvar re_utob = /[\\uD800-\\uDBFF][\\uDC00-\\uDFFFF]|[^\\x00-\\x7F]/g;\nvar utob = (u) => u.replace(re_utob, cb_utob);\nvar _encode = _hasBuffer ? (s) => Buffer.from(s, \"utf8\").toString(\"base64\") : _TE ? (s) => _fromUint8Array(_TE.encode(s)) : (s) => _btoa(utob(s));\nvar encode = (src, urlsafe = false) => urlsafe ? _mkUriSafe(_encode(src)) : _encode(src);\nvar re_btou = /[\\xC0-\\xDF][\\x80-\\xBF]|[\\xE0-\\xEF][\\x80-\\xBF]{2}|[\\xF0-\\xF7][\\x80-\\xBF]{3}/g;\nvar cb_btou = (cccc) => {\n switch (cccc.length) {\n case 4:\n var cp = (7 & cccc.charCodeAt(0)) << 18 | (63 & cccc.charCodeAt(1)) << 12 | (63 & cccc.charCodeAt(2)) << 6 | 63 & cccc.charCodeAt(3), offset = cp - 65536;\n return _fromCC((offset >>> 10) + 55296) + _fromCC((offset & 1023) + 56320);\n case 3:\n return _fromCC((15 & cccc.charCodeAt(0)) << 12 | (63 & cccc.charCodeAt(1)) << 6 | 63 & cccc.charCodeAt(2));\n default:\n return _fromCC((31 & cccc.charCodeAt(0)) << 6 | 63 & cccc.charCodeAt(1));\n }\n};\nvar btou = (b) => b.replace(re_btou, cb_btou);\nvar atobPolyfill = (asc) => {\n asc = asc.replace(/\\s+/g, \"\");\n if (!b64re.test(asc))\n throw new TypeError(\"malformed base64.\");\n asc += \"==\".slice(2 - (asc.length & 3));\n let u24, bin = \"\", r1, r2;\n for (let i = 0; i < asc.length; ) {\n u24 = b64tab[asc.charAt(i++)] << 18 | b64tab[asc.charAt(i++)] << 12 | (r1 = b64tab[asc.charAt(i++)]) << 6 | (r2 = b64tab[asc.charAt(i++)]);\n bin += r1 === 64 ? _fromCC(u24 >> 16 & 255) : r2 === 64 ? _fromCC(u24 >> 16 & 255, u24 >> 8 & 255) : _fromCC(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255);\n }\n return bin;\n};\nvar _atob = _hasatob ? (asc) => atob(_tidyB64(asc)) : _hasBuffer ? (asc) => Buffer.from(asc, \"base64\").toString(\"binary\") : atobPolyfill;\nvar _toUint8Array = _hasBuffer ? (a) => _U8Afrom(Buffer.from(a, \"base64\")) : (a) => _U8Afrom(_atob(a), (c) => c.charCodeAt(0));\nvar _decode = _hasBuffer ? (a) => Buffer.from(a, \"base64\").toString(\"utf8\") : _TD ? (a) => _TD.decode(_toUint8Array(a)) : (a) => btou(_atob(a));\nvar _unURI = (a) => _tidyB64(a.replace(/[-_]/g, (m0) => m0 == \"-\" ? \"+\" : \"/\"));\nvar decode = (src) => _decode(_unURI(src));\n\n// api.gen.ts\nvar NakamaApi = class {\n constructor(configuration) {\n this.configuration = configuration;\n }\n doFetch(urlPath, method, queryParams, body, options) {\n const urlQuery = \"?\" + Object.keys(queryParams).map((k) => {\n if (queryParams[k] instanceof Array) {\n return queryParams[k].reduce((prev, curr) => {\n return prev + encodeURIComponent(k) + \"=\" + encodeURIComponent(curr) + \"&\";\n }, \"\");\n } else {\n if (queryParams[k] != null) {\n return encodeURIComponent(k) + \"=\" + encodeURIComponent(queryParams[k]) + \"&\";\n }\n }\n }).join(\"\");\n const fetchOptions = __assign(__assign({}, {method}), options);\n fetchOptions.headers = __assign({}, options.headers);\n const descriptor = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, \"withCredentials\");\n if (!(descriptor == null ? void 0 : descriptor.set)) {\n fetchOptions.credentials = \"cocos-ignore\";\n }\n if (this.configuration.bearerToken) {\n fetchOptions.headers[\"Authorization\"] = \"Bearer \" + this.configuration.bearerToken;\n } else if (this.configuration.username) {\n fetchOptions.headers[\"Authorization\"] = \"Basic \" + encode(this.configuration.username + \":\" + this.configuration.password);\n }\n if (!Object.keys(fetchOptions.headers).includes(\"Accept\")) {\n fetchOptions.headers[\"Accept\"] = \"application/json\";\n }\n if (!Object.keys(fetchOptions.headers).includes(\"Content-Type\")) {\n fetchOptions.headers[\"Content-Type\"] = \"application/json\";\n }\n Object.keys(fetchOptions.headers).forEach((key) => {\n if (!fetchOptions.headers[key]) {\n delete fetchOptions.headers[key];\n }\n });\n fetchOptions.body = body;\n return Promise.race([\n fetch(this.configuration.basePath + urlPath + urlQuery, fetchOptions).then((response) => {\n if (response.status == 204) {\n return response;\n } else if (response.status >= 200 && response.status < 300) {\n return response.json();\n } else {\n throw response;\n }\n }),\n new Promise((_, reject) => setTimeout(reject, this.configuration.timeoutMs, \"Request timed out.\"))\n ]);\n }\n healthcheck(options = {}) {\n const urlPath = \"/healthcheck\";\n const queryParams = {};\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n getAccount(options = {}) {\n const urlPath = \"/v2/account\";\n const queryParams = {};\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n updateAccount(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"PUT\", queryParams, _body, options);\n }\n authenticateApple(body, create, username, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/authenticate/apple\";\n const queryParams = {\n create,\n username\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n authenticateCustom(body, create, username, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/authenticate/custom\";\n const queryParams = {\n create,\n username\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n authenticateDevice(body, create, username, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/authenticate/device\";\n const queryParams = {\n create,\n username\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n authenticateEmail(body, create, username, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/authenticate/email\";\n const queryParams = {\n create,\n username\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n authenticateFacebook(body, create, username, sync, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/authenticate/facebook\";\n const queryParams = {\n create,\n username,\n sync\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n authenticateFacebookInstantGame(body, create, username, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/authenticate/facebookinstantgame\";\n const queryParams = {\n create,\n username\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n authenticateGameCenter(body, create, username, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/authenticate/gamecenter\";\n const queryParams = {\n create,\n username\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n authenticateGoogle(body, create, username, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/authenticate/google\";\n const queryParams = {\n create,\n username\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n authenticateSteam(body, create, username, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/authenticate/steam\";\n const queryParams = {\n create,\n username\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n linkApple(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/link/apple\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n linkCustom(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/link/custom\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n linkDevice(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/link/device\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n linkEmail(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/link/email\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n linkFacebook(body, sync, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/link/facebook\";\n const queryParams = {\n sync\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n linkFacebookInstantGame(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/link/facebookinstantgame\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n linkGameCenter(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/link/gamecenter\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n linkGoogle(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/link/google\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n linkSteam(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/link/steam\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n sessionRefresh(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/session/refresh\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n unlinkApple(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/unlink/apple\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n unlinkCustom(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/unlink/custom\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n unlinkDevice(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/unlink/device\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n unlinkEmail(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/unlink/email\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n unlinkFacebook(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/unlink/facebook\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n unlinkFacebookInstantGame(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/unlink/facebookinstantgame\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n unlinkGameCenter(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/unlink/gamecenter\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n unlinkGoogle(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/unlink/google\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n unlinkSteam(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/account/unlink/steam\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n listChannelMessages(channelId, limit, forward, cursor, options = {}) {\n if (channelId === null || channelId === void 0) {\n throw new Error(\"'channelId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/channel/{channelId}\".replace(\"{channelId}\", encodeURIComponent(String(channelId)));\n const queryParams = {\n limit,\n forward,\n cursor\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n event(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/event\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n deleteFriends(ids, usernames, options = {}) {\n const urlPath = \"/v2/friend\";\n const queryParams = {\n ids,\n usernames\n };\n let _body = null;\n return this.doFetch(urlPath, \"DELETE\", queryParams, _body, options);\n }\n listFriends(limit, state, cursor, options = {}) {\n const urlPath = \"/v2/friend\";\n const queryParams = {\n limit,\n state,\n cursor\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n addFriends(ids, usernames, options = {}) {\n const urlPath = \"/v2/friend\";\n const queryParams = {\n ids,\n usernames\n };\n let _body = null;\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n blockFriends(ids, usernames, options = {}) {\n const urlPath = \"/v2/friend/block\";\n const queryParams = {\n ids,\n usernames\n };\n let _body = null;\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n importFacebookFriends(body, reset, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/friend/facebook\";\n const queryParams = {\n reset\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n listGroups(name, cursor, limit, options = {}) {\n const urlPath = \"/v2/group\";\n const queryParams = {\n name,\n cursor,\n limit\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n createGroup(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n deleteGroup(groupId, options = {}) {\n if (groupId === null || groupId === void 0) {\n throw new Error(\"'groupId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group/{groupId}\".replace(\"{groupId}\", encodeURIComponent(String(groupId)));\n const queryParams = {};\n let _body = null;\n return this.doFetch(urlPath, \"DELETE\", queryParams, _body, options);\n }\n updateGroup(groupId, body, options = {}) {\n if (groupId === null || groupId === void 0) {\n throw new Error(\"'groupId' is a required parameter but is null or undefined.\");\n }\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group/{groupId}\".replace(\"{groupId}\", encodeURIComponent(String(groupId)));\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"PUT\", queryParams, _body, options);\n }\n addGroupUsers(groupId, userIds, options = {}) {\n if (groupId === null || groupId === void 0) {\n throw new Error(\"'groupId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group/{groupId}/add\".replace(\"{groupId}\", encodeURIComponent(String(groupId)));\n const queryParams = {\n user_ids: userIds\n };\n let _body = null;\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n banGroupUsers(groupId, userIds, options = {}) {\n if (groupId === null || groupId === void 0) {\n throw new Error(\"'groupId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group/{groupId}/ban\".replace(\"{groupId}\", encodeURIComponent(String(groupId)));\n const queryParams = {\n user_ids: userIds\n };\n let _body = null;\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n demoteGroupUsers(groupId, userIds, options = {}) {\n if (groupId === null || groupId === void 0) {\n throw new Error(\"'groupId' is a required parameter but is null or undefined.\");\n }\n if (userIds === null || userIds === void 0) {\n throw new Error(\"'userIds' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group/{groupId}/demote\".replace(\"{groupId}\", encodeURIComponent(String(groupId)));\n const queryParams = {\n user_ids: userIds\n };\n let _body = null;\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n joinGroup(groupId, options = {}) {\n if (groupId === null || groupId === void 0) {\n throw new Error(\"'groupId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group/{groupId}/join\".replace(\"{groupId}\", encodeURIComponent(String(groupId)));\n const queryParams = {};\n let _body = null;\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n kickGroupUsers(groupId, userIds, options = {}) {\n if (groupId === null || groupId === void 0) {\n throw new Error(\"'groupId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group/{groupId}/kick\".replace(\"{groupId}\", encodeURIComponent(String(groupId)));\n const queryParams = {\n user_ids: userIds\n };\n let _body = null;\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n leaveGroup(groupId, options = {}) {\n if (groupId === null || groupId === void 0) {\n throw new Error(\"'groupId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group/{groupId}/leave\".replace(\"{groupId}\", encodeURIComponent(String(groupId)));\n const queryParams = {};\n let _body = null;\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n promoteGroupUsers(groupId, userIds, options = {}) {\n if (groupId === null || groupId === void 0) {\n throw new Error(\"'groupId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group/{groupId}/promote\".replace(\"{groupId}\", encodeURIComponent(String(groupId)));\n const queryParams = {\n user_ids: userIds\n };\n let _body = null;\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n listGroupUsers(groupId, limit, state, cursor, options = {}) {\n if (groupId === null || groupId === void 0) {\n throw new Error(\"'groupId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/group/{groupId}/user\".replace(\"{groupId}\", encodeURIComponent(String(groupId)));\n const queryParams = {\n limit,\n state,\n cursor\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n deleteLeaderboardRecord(leaderboardId, options = {}) {\n if (leaderboardId === null || leaderboardId === void 0) {\n throw new Error(\"'leaderboardId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/leaderboard/{leaderboardId}\".replace(\"{leaderboardId}\", encodeURIComponent(String(leaderboardId)));\n const queryParams = {};\n let _body = null;\n return this.doFetch(urlPath, \"DELETE\", queryParams, _body, options);\n }\n listLeaderboardRecords(leaderboardId, ownerIds, limit, cursor, expiry, options = {}) {\n if (leaderboardId === null || leaderboardId === void 0) {\n throw new Error(\"'leaderboardId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/leaderboard/{leaderboardId}\".replace(\"{leaderboardId}\", encodeURIComponent(String(leaderboardId)));\n const queryParams = {\n ownerIds,\n limit,\n cursor,\n expiry\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n writeLeaderboardRecord(leaderboardId, body, options = {}) {\n if (leaderboardId === null || leaderboardId === void 0) {\n throw new Error(\"'leaderboardId' is a required parameter but is null or undefined.\");\n }\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/leaderboard/{leaderboardId}\".replace(\"{leaderboardId}\", encodeURIComponent(String(leaderboardId)));\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n listLeaderboardRecordsAroundOwner(leaderboardId, ownerId, limit, expiry, options = {}) {\n if (leaderboardId === null || leaderboardId === void 0) {\n throw new Error(\"'leaderboardId' is a required parameter but is null or undefined.\");\n }\n if (ownerId === null || ownerId === void 0) {\n throw new Error(\"'ownerId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/leaderboard/{leaderboardId}/owner/{ownerId}\".replace(\"{leaderboardId}\", encodeURIComponent(String(leaderboardId))).replace(\"{ownerId}\", encodeURIComponent(String(ownerId)));\n const queryParams = {\n limit,\n expiry\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n listMatches(limit, authoritative, label, minSize, maxSize, query, options = {}) {\n const urlPath = \"/v2/match\";\n const queryParams = {\n limit,\n authoritative,\n label,\n minSize,\n maxSize,\n query\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n deleteNotifications(ids, options = {}) {\n const urlPath = \"/v2/notification\";\n const queryParams = {\n ids\n };\n let _body = null;\n return this.doFetch(urlPath, \"DELETE\", queryParams, _body, options);\n }\n listNotifications(limit, cacheableCursor, options = {}) {\n const urlPath = \"/v2/notification\";\n const queryParams = {\n limit,\n cacheableCursor\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n rpcFunc2(id, payload, httpKey, options = {}) {\n if (id === null || id === void 0) {\n throw new Error(\"'id' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/rpc/{id}\".replace(\"{id}\", encodeURIComponent(String(id)));\n const queryParams = {\n payload,\n httpKey\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n rpcFunc(id, body, httpKey, options = {}) {\n if (id === null || id === void 0) {\n throw new Error(\"'id' is a required parameter but is null or undefined.\");\n }\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/rpc/{id}\".replace(\"{id}\", encodeURIComponent(String(id)));\n const queryParams = {\n httpKey\n };\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n readStorageObjects(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/storage\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n writeStorageObjects(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/storage\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"PUT\", queryParams, _body, options);\n }\n deleteStorageObjects(body, options = {}) {\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/storage/delete\";\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"PUT\", queryParams, _body, options);\n }\n listStorageObjects(collection, userId, limit, cursor, options = {}) {\n if (collection === null || collection === void 0) {\n throw new Error(\"'collection' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/storage/{collection}\".replace(\"{collection}\", encodeURIComponent(String(collection)));\n const queryParams = {\n userId,\n limit,\n cursor\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n listStorageObjects2(collection, userId, limit, cursor, options = {}) {\n if (collection === null || collection === void 0) {\n throw new Error(\"'collection' is a required parameter but is null or undefined.\");\n }\n if (userId === null || userId === void 0) {\n throw new Error(\"'userId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/storage/{collection}/{userId}\".replace(\"{collection}\", encodeURIComponent(String(collection))).replace(\"{userId}\", encodeURIComponent(String(userId)));\n const queryParams = {\n limit,\n cursor\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n listTournaments(categoryStart, categoryEnd, startTime, endTime, limit, cursor, options = {}) {\n const urlPath = \"/v2/tournament\";\n const queryParams = {\n categoryStart,\n categoryEnd,\n startTime,\n endTime,\n limit,\n cursor\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n listTournamentRecords(tournamentId, ownerIds, limit, cursor, expiry, options = {}) {\n if (tournamentId === null || tournamentId === void 0) {\n throw new Error(\"'tournamentId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/tournament/{tournamentId}\".replace(\"{tournamentId}\", encodeURIComponent(String(tournamentId)));\n const queryParams = {\n ownerIds,\n limit,\n cursor,\n expiry\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n writeTournamentRecord(tournamentId, body, options = {}) {\n if (tournamentId === null || tournamentId === void 0) {\n throw new Error(\"'tournamentId' is a required parameter but is null or undefined.\");\n }\n if (body === null || body === void 0) {\n throw new Error(\"'body' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/tournament/{tournamentId}\".replace(\"{tournamentId}\", encodeURIComponent(String(tournamentId)));\n const queryParams = {};\n let _body = null;\n _body = JSON.stringify(body || {});\n return this.doFetch(urlPath, \"PUT\", queryParams, _body, options);\n }\n joinTournament(tournamentId, options = {}) {\n if (tournamentId === null || tournamentId === void 0) {\n throw new Error(\"'tournamentId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/tournament/{tournamentId}/join\".replace(\"{tournamentId}\", encodeURIComponent(String(tournamentId)));\n const queryParams = {};\n let _body = null;\n return this.doFetch(urlPath, \"POST\", queryParams, _body, options);\n }\n listTournamentRecordsAroundOwner(tournamentId, ownerId, limit, expiry, options = {}) {\n if (tournamentId === null || tournamentId === void 0) {\n throw new Error(\"'tournamentId' is a required parameter but is null or undefined.\");\n }\n if (ownerId === null || ownerId === void 0) {\n throw new Error(\"'ownerId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/tournament/{tournamentId}/owner/{ownerId}\".replace(\"{tournamentId}\", encodeURIComponent(String(tournamentId))).replace(\"{ownerId}\", encodeURIComponent(String(ownerId)));\n const queryParams = {\n limit,\n expiry\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n getUsers(ids, usernames, facebookIds, options = {}) {\n const urlPath = \"/v2/user\";\n const queryParams = {\n ids,\n usernames,\n facebookIds\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n listUserGroups(userId, limit, state, cursor, options = {}) {\n if (userId === null || userId === void 0) {\n throw new Error(\"'userId' is a required parameter but is null or undefined.\");\n }\n const urlPath = \"/v2/user/{userId}/group\".replace(\"{userId}\", encodeURIComponent(String(userId)));\n const queryParams = {\n limit,\n state,\n cursor\n };\n let _body = null;\n return this.doFetch(urlPath, \"GET\", queryParams, _body, options);\n }\n};\n\n// session.ts\nvar Session = class {\n constructor(token, created_at, expires_at, username, user_id, vars) {\n this.token = token;\n this.created_at = created_at;\n this.expires_at = expires_at;\n this.username = username;\n this.user_id = user_id;\n this.vars = vars;\n }\n isexpired(currenttime) {\n return this.expires_at - currenttime < 0;\n }\n static restore(jwt) {\n const createdAt = Math.floor(new Date().getTime() / 1e3);\n const parts = jwt.split(\".\");\n if (parts.length != 3) {\n throw \"jwt is not valid.\";\n }\n const decoded = JSON.parse(atob(parts[1]));\n const expiresAt = Math.floor(parseInt(decoded[\"exp\"]));\n return new Session(jwt, createdAt, expiresAt, decoded[\"usn\"], decoded[\"uid\"], decoded[\"vrs\"]);\n }\n};\n\n// web_socket_adapter.ts\nvar WebSocketAdapterText = class {\n constructor() {\n this._isConnected = false;\n }\n get onClose() {\n return this._socket.onclose;\n }\n set onClose(value) {\n this._socket.onclose = value;\n }\n get onError() {\n return this._socket.onerror;\n }\n set onError(value) {\n this._socket.onerror = value;\n }\n get onMessage() {\n return this._socket.onmessage;\n }\n set onMessage(value) {\n if (value) {\n this._socket.onmessage = (evt) => {\n const message = JSON.parse(evt.data);\n value(message);\n };\n } else {\n value = null;\n }\n }\n get onOpen() {\n return this._socket.onopen;\n }\n set onOpen(value) {\n this._socket.onopen = value;\n }\n get isConnected() {\n return this._isConnected;\n }\n connect(scheme, host, port, createStatus, token) {\n const url = `${scheme}${host}:${port}/ws?lang=en&status=${encodeURIComponent(createStatus.toString())}&token=${encodeURIComponent(token)}`;\n this._socket = new WebSocket(url);\n this._isConnected = true;\n }\n close() {\n this._isConnected = false;\n this._socket.close();\n this._socket = void 0;\n }\n send(msg) {\n if (msg.match_data_send) {\n msg.match_data_send.op_code = msg.match_data_send.op_code.toString();\n }\n this._socket.send(JSON.stringify(msg));\n }\n};\n\n// utils.ts\nfunction b64EncodeUnicode(str) {\n return encode(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(_match, p1) {\n return String.fromCharCode(Number(\"0x\" + p1));\n }));\n}\nfunction b64DecodeUnicode(str) {\n return decodeURIComponent(decode(str).split(\"\").map(function(c) {\n return \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2);\n }).join(\"\"));\n}\n\n// socket.ts\nvar DefaultSocket = class {\n constructor(host, port, useSSL = false, verbose = false, adapter = new WebSocketAdapterText()) {\n this.host = host;\n this.port = port;\n this.useSSL = useSSL;\n this.verbose = verbose;\n this.adapter = adapter;\n this.cIds = {};\n this.nextCid = 1;\n }\n generatecid() {\n const cid = this.nextCid.toString();\n ++this.nextCid;\n return cid;\n }\n connect(session, createStatus = false) {\n if (this.adapter.isConnected) {\n return Promise.resolve(session);\n }\n const scheme = this.useSSL ? \"wss://\" : \"ws://\";\n this.adapter.connect(scheme, this.host, this.port, createStatus, session.token);\n this.adapter.onClose = (evt) => {\n this.ondisconnect(evt);\n };\n this.adapter.onError = (evt) => {\n this.onerror(evt);\n };\n this.adapter.onMessage = (message) => {\n if (this.verbose && window && window.console) {\n console.log(\"Response: %o\", message);\n }\n if (message.cid == void 0) {\n if (message.notifications) {\n message.notifications.notifications.forEach((n) => {\n n.content = n.content ? JSON.parse(n.content) : void 0;\n this.onnotification(n);\n });\n } else if (message.match_data) {\n message.match_data.data = message.match_data.data != null ? JSON.parse(b64DecodeUnicode(message.match_data.data)) : null;\n message.match_data.op_code = parseInt(message.match_data.op_code);\n this.onmatchdata(message.match_data);\n } else if (message.match_presence_event) {\n this.onmatchpresence(message.match_presence_event);\n } else if (message.matchmaker_matched) {\n this.onmatchmakermatched(message.matchmaker_matched);\n } else if (message.status_presence_event) {\n this.onstatuspresence(message.status_presence_event);\n } else if (message.stream_presence_event) {\n this.onstreampresence(message.stream_presence_event);\n } else if (message.stream_data) {\n this.onstreamdata(message.stream_data);\n } else if (message.channel_message) {\n message.channel_message.content = JSON.parse(message.channel_message.content);\n this.onchannelmessage(message.channel_message);\n } else if (message.channel_presence_event) {\n this.onchannelpresence(message.channel_presence_event);\n } else {\n if (this.verbose && window && window.console) {\n console.log(\"Unrecognized message received: %o\", message);\n }\n }\n } else {\n const executor = this.cIds[message.cid];\n if (!executor) {\n if (this.verbose && window && window.console) {\n console.error(\"No promise executor for message: %o\", message);\n }\n return;\n }\n delete this.cIds[message.cid];\n if (message.error) {\n executor.reject(message.error);\n } else {\n executor.resolve(message);\n }\n }\n };\n return new Promise((resolve, reject) => {\n this.adapter.onOpen = (evt) => {\n if (this.verbose && window && window.console) {\n console.log(evt);\n }\n resolve(session);\n };\n this.adapter.onError = (evt) => {\n reject(evt);\n this.adapter.close();\n };\n });\n }\n disconnect(fireDisconnectEvent = true) {\n if (this.adapter.isConnected) {\n this.adapter.close();\n }\n if (fireDisconnectEvent) {\n this.ondisconnect({});\n }\n }\n ondisconnect(evt) {\n if (this.verbose && window && window.console) {\n console.log(evt);\n }\n }\n onerror(evt) {\n if (this.verbose && window && window.console) {\n console.log(evt);\n }\n }\n onchannelmessage(channelMessage) {\n if (this.verbose && window && window.console) {\n console.log(channelMessage);\n }\n }\n onchannelpresence(channelPresence) {\n if (this.verbose && window && window.console) {\n console.log(channelPresence);\n }\n }\n onnotification(notification) {\n if (this.verbose && window && window.console) {\n console.log(notification);\n }\n }\n onmatchdata(matchData) {\n if (this.verbose && window && window.console) {\n console.log(matchData);\n }\n }\n onmatchpresence(matchPresence) {\n if (this.verbose && window && window.console) {\n console.log(matchPresence);\n }\n }\n onmatchmakermatched(matchmakerMatched) {\n if (this.verbose && window && window.console) {\n console.log(matchmakerMatched);\n }\n }\n onstatuspresence(statusPresence) {\n if (this.verbose && window && window.console) {\n console.log(statusPresence);\n }\n }\n onstreampresence(streamPresence) {\n if (this.verbose && window && window.console) {\n console.log(streamPresence);\n }\n }\n onstreamdata(streamData) {\n if (this.verbose && window && window.console) {\n console.log(streamData);\n }\n }\n send(message) {\n const untypedMessage = message;\n return new Promise((resolve, reject) => {\n if (!this.adapter.isConnected) {\n reject(\"Socket connection has not been established yet.\");\n } else {\n if (untypedMessage.match_data_send) {\n untypedMessage.match_data_send.data = b64EncodeUnicode(JSON.stringify(untypedMessage.match_data_send.data));\n this.adapter.send(untypedMessage);\n resolve();\n } else {\n if (untypedMessage.channel_message_send) {\n untypedMessage.channel_message_send.content = JSON.stringify(untypedMessage.channel_message_send.content);\n } else if (untypedMessage.channel_message_update) {\n untypedMessage.channel_message_update.content = JSON.stringify(untypedMessage.channel_message_update.content);\n }\n const cid = this.generatecid();\n this.cIds[cid] = {resolve, reject};\n untypedMessage.cid = cid;\n this.adapter.send(untypedMessage);\n }\n }\n if (this.verbose && window && window.console) {\n console.log(\"Sent message: %o\", untypedMessage);\n }\n });\n }\n addMatchmaker(query, minCount, maxCount, stringProperties, numericProperties) {\n return __async(this, null, function* () {\n const matchMakerAdd = {\n matchmaker_add: {\n min_count: minCount,\n max_count: maxCount,\n query,\n string_properties: stringProperties,\n numeric_properties: numericProperties\n }\n };\n const response = yield this.send(matchMakerAdd);\n return response.matchmaker_ticket;\n });\n }\n createMatch() {\n return __async(this, null, function* () {\n const response = yield this.send({match_create: {}});\n return response.match;\n });\n }\n followUsers(userIds) {\n return __async(this, null, function* () {\n const response = yield this.send({status_follow: {user_ids: userIds}});\n return response.status;\n });\n }\n joinChat(target, type, persistence, hidden) {\n return __async(this, null, function* () {\n const response = yield this.send({\n channel_join: {\n target,\n type,\n persistence,\n hidden\n }\n });\n return response.channel;\n });\n }\n joinMatch(match_id, token, metadata) {\n return __async(this, null, function* () {\n const join = {match_join: {metadata}};\n if (token) {\n join.match_join.token = token;\n } else {\n join.match_join.match_id = match_id;\n }\n const response = yield this.send(join);\n return response.match;\n });\n }\n leaveChat(channel_id) {\n return this.send({channel_leave: {channel_id}});\n }\n leaveMatch(matchId) {\n return this.send({match_leave: {match_id: matchId}});\n }\n removeChatMessage(channel_id, message_id) {\n return __async(this, null, function* () {\n const response = yield this.send({\n channel_message_remove: {\n channel_id,\n message_id\n }\n });\n return response.channel_message_ack;\n });\n }\n removeMatchmaker(ticket) {\n return this.send({matchmaker_remove: {ticket}});\n }\n rpc(id, payload, http_key) {\n return __async(this, null, function* () {\n const response = yield this.send({\n rpc: {\n id,\n payload,\n http_key\n }\n });\n return response.rpc;\n });\n }\n sendMatchState(matchId, opCode, data, presences) {\n return __async(this, null, function* () {\n return this.send({\n match_data_send: {\n match_id: matchId,\n op_code: opCode,\n data,\n presences: presences != null ? presences : []\n }\n });\n });\n }\n unfollowUsers(user_ids) {\n return this.send({status_unfollow: {user_ids}});\n }\n updateChatMessage(channel_id, message_id, content) {\n return __async(this, null, function* () {\n const response = yield this.send({channel_message_update: {channel_id, message_id, content}});\n return response.channel_message_ack;\n });\n }\n updateStatus(status) {\n return this.send({status_update: {status}});\n }\n writeChatMessage(channel_id, content) {\n return __async(this, null, function* () {\n const response = yield this.send({channel_message_send: {channel_id, content}});\n return response.channel_message_ack;\n });\n }\n};\n\n// client.ts\nvar DEFAULT_HOST = \"127.0.0.1\";\nvar DEFAULT_PORT = \"7350\";\nvar DEFAULT_SERVER_KEY = \"defaultkey\";\nvar DEFAULT_TIMEOUT_MS = 7e3;\nvar Client = class {\n constructor(serverkey = DEFAULT_SERVER_KEY, host = DEFAULT_HOST, port = DEFAULT_PORT, useSSL = false, timeout = DEFAULT_TIMEOUT_MS) {\n this.serverkey = serverkey;\n this.host = host;\n this.port = port;\n this.useSSL = useSSL;\n this.timeout = timeout;\n const scheme = useSSL ? \"https://\" : \"http://\";\n const basePath = `${scheme}${host}:${port}`;\n this.configuration = {\n basePath,\n username: serverkey,\n password: \"\",\n timeoutMs: timeout\n };\n this.apiClient = new NakamaApi(this.configuration);\n }\n addGroupUsers(session, groupId, ids) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.addGroupUsers(groupId, ids).then((response) => {\n return response !== void 0;\n });\n }\n addFriends(session, ids, usernames) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.addFriends(ids, usernames).then((response) => {\n return response !== void 0;\n });\n }\n authenticateApple(token, create, username, vars = new Map(), options = {}) {\n const request = {\n token,\n vars\n };\n return this.apiClient.authenticateApple(request, create, username, options).then((apiSession) => {\n return Session.restore(apiSession.token || \"\");\n });\n }\n authenticateCustom(id, create, username, vars = new Map(), options = {}) {\n const request = {\n id,\n vars\n };\n return this.apiClient.authenticateCustom(request, create, username, options).then((apiSession) => {\n return Session.restore(apiSession.token || \"\");\n });\n }\n authenticateDevice(id, create, username, vars) {\n const request = {\n id,\n vars\n };\n return this.apiClient.authenticateDevice(request, create, username).then((apiSession) => {\n return Session.restore(apiSession.token || \"\");\n });\n }\n authenticateEmail(email, password, create, username, vars) {\n const request = {\n email,\n password,\n vars\n };\n return this.apiClient.authenticateEmail(request, create, username).then((apiSession) => {\n return Session.restore(apiSession.token || \"\");\n });\n }\n authenticateFacebookInstantGame(signedPlayerInfo, create, username, vars, options = {}) {\n const request = {\n signed_player_info: signedPlayerInfo,\n vars\n };\n return this.apiClient.authenticateFacebookInstantGame({signed_player_info: request.signed_player_info, vars: request.vars}, create, username, options).then((apiSession) => {\n return Session.restore(apiSession.token || \"\");\n });\n }\n authenticateFacebook(token, create, username, sync, vars, options = {}) {\n const request = {\n token,\n vars\n };\n return this.apiClient.authenticateFacebook(request, create, username, sync, options).then((apiSession) => {\n return Session.restore(apiSession.token || \"\");\n });\n }\n authenticateGoogle(token, create, username, vars, options = {}) {\n const request = {\n token,\n vars\n };\n return this.apiClient.authenticateGoogle(request, create, username, options).then((apiSession) => {\n return Session.restore(apiSession.token || \"\");\n });\n }\n authenticateGameCenter(token, create, username, vars) {\n const request = {\n token,\n vars\n };\n return this.apiClient.authenticateGameCenter(request, create, username).then((apiSession) => {\n return Session.restore(apiSession.token || \"\");\n });\n }\n authenticateSteam(token, create, username, vars) {\n const request = {\n token,\n vars\n };\n return this.apiClient.authenticateSteam(request, create, username).then((apiSession) => {\n return Session.restore(apiSession.token || \"\");\n });\n }\n banGroupUsers(session, groupId, ids) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.banGroupUsers(groupId, ids).then((response) => {\n return response !== void 0;\n });\n }\n blockFriends(session, ids, usernames) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.blockFriends(ids, usernames).then((response) => {\n return Promise.resolve(response != void 0);\n });\n }\n createGroup(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.createGroup(request).then((response) => {\n return Promise.resolve({\n avatar_url: response.avatar_url,\n create_time: response.create_time,\n creator_id: response.creator_id,\n description: response.description,\n edge_count: response.edge_count ? Number(response.edge_count) : 0,\n id: response.id,\n lang_tag: response.lang_tag,\n max_count: response.max_count ? Number(response.max_count) : 0,\n metadata: response.metadata ? JSON.parse(response.metadata) : void 0,\n name: response.name,\n open: response.open,\n update_time: response.update_time\n });\n });\n }\n createSocket(useSSL = false, verbose = false, adapter = new WebSocketAdapterText()) {\n return new DefaultSocket(this.host, this.port, useSSL, verbose, adapter);\n }\n deleteFriends(session, ids, usernames) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.deleteFriends(ids, usernames).then((response) => {\n return response !== void 0;\n });\n }\n deleteGroup(session, groupId) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.deleteGroup(groupId).then((response) => {\n return response !== void 0;\n });\n }\n deleteNotifications(session, ids) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.deleteNotifications(ids).then((response) => {\n return Promise.resolve(response != void 0);\n });\n }\n deleteStorageObjects(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.deleteStorageObjects(request).then((response) => {\n return Promise.resolve(response != void 0);\n });\n }\n demoteGroupUsers(session, groupId, ids) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.demoteGroupUsers(groupId, ids);\n }\n emitEvent(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.event(request).then((response) => {\n return Promise.resolve(response != void 0);\n });\n }\n getAccount(session) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.getAccount();\n }\n importFacebookFriends(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.importFacebookFriends(request).then((response) => {\n return response !== void 0;\n });\n }\n getUsers(session, ids, usernames, facebookIds) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.getUsers(ids, usernames, facebookIds).then((response) => {\n var result = {\n users: []\n };\n if (response.users == null) {\n return Promise.resolve(result);\n }\n response.users.forEach((u) => {\n result.users.push({\n avatar_url: u.avatar_url,\n create_time: u.create_time,\n display_name: u.display_name,\n edge_count: u.edge_count ? Number(u.edge_count) : 0,\n facebook_id: u.facebook_id,\n gamecenter_id: u.gamecenter_id,\n google_id: u.google_id,\n id: u.id,\n lang_tag: u.lang_tag,\n location: u.location,\n online: u.online,\n steam_id: u.steam_id,\n timezone: u.timezone,\n update_time: u.update_time,\n username: u.username,\n metadata: u.metadata ? JSON.parse(u.metadata) : void 0\n });\n });\n return Promise.resolve(result);\n });\n }\n joinGroup(session, groupId) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.joinGroup(groupId, {}).then((response) => {\n return response !== void 0;\n });\n }\n joinTournament(session, tournamentId) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.joinTournament(tournamentId, {}).then((response) => {\n return response !== void 0;\n });\n }\n kickGroupUsers(session, groupId, ids) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.kickGroupUsers(groupId, ids).then((response) => {\n return Promise.resolve(response != void 0);\n });\n }\n leaveGroup(session, groupId) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.leaveGroup(groupId, {}).then((response) => {\n return response !== void 0;\n });\n }\n listChannelMessages(session, channelId, limit, forward, cursor) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listChannelMessages(channelId, limit, forward, cursor).then((response) => {\n var result = {\n messages: [],\n next_cursor: response.next_cursor,\n prev_cursor: response.prev_cursor\n };\n if (response.messages == null) {\n return Promise.resolve(result);\n }\n response.messages.forEach((m) => {\n result.messages.push({\n channel_id: m.channel_id,\n code: m.code ? Number(m.code) : 0,\n create_time: m.create_time,\n message_id: m.message_id,\n persistent: m.persistent,\n sender_id: m.sender_id,\n update_time: m.update_time,\n username: m.username,\n content: m.content ? JSON.parse(m.content) : void 0,\n group_id: m.group_id,\n room_name: m.room_name,\n user_id_one: m.user_id_one,\n user_id_two: m.user_id_two\n });\n });\n return Promise.resolve(result);\n });\n }\n listGroupUsers(session, groupId, state, limit, cursor) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listGroupUsers(groupId, limit, state, cursor).then((response) => {\n var result = {\n group_users: [],\n cursor: response.cursor\n };\n if (response.group_users == null) {\n return Promise.resolve(result);\n }\n response.group_users.forEach((gu) => {\n result.group_users.push({\n user: {\n avatar_url: gu.user.avatar_url,\n create_time: gu.user.create_time,\n display_name: gu.user.display_name,\n edge_count: gu.user.edge_count ? Number(gu.user.edge_count) : 0,\n facebook_id: gu.user.facebook_id,\n gamecenter_id: gu.user.gamecenter_id,\n google_id: gu.user.google_id,\n id: gu.user.id,\n lang_tag: gu.user.lang_tag,\n location: gu.user.location,\n online: gu.user.online,\n steam_id: gu.user.steam_id,\n timezone: gu.user.timezone,\n update_time: gu.user.update_time,\n username: gu.user.username,\n metadata: gu.user.metadata ? JSON.parse(gu.user.metadata) : void 0\n },\n state: gu.state ? Number(gu.state) : 0\n });\n });\n return Promise.resolve(result);\n });\n }\n listUserGroups(session, userId, state, limit, cursor) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listUserGroups(userId, state, limit, cursor).then((response) => {\n var result = {\n user_groups: [],\n cursor: response.cursor\n };\n if (response.user_groups == null) {\n return Promise.resolve(result);\n }\n response.user_groups.forEach((ug) => {\n result.user_groups.push({\n group: {\n avatar_url: ug.group.avatar_url,\n create_time: ug.group.create_time,\n creator_id: ug.group.creator_id,\n description: ug.group.description,\n edge_count: ug.group.edge_count ? Number(ug.group.edge_count) : 0,\n id: ug.group.id,\n lang_tag: ug.group.lang_tag,\n max_count: ug.group.max_count,\n metadata: ug.group.metadata ? JSON.parse(ug.group.metadata) : void 0,\n name: ug.group.name,\n open: ug.group.open,\n update_time: ug.group.update_time\n },\n state: ug.state ? Number(ug.state) : 0\n });\n });\n return Promise.resolve(result);\n });\n }\n listGroups(session, name, cursor, limit) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listGroups(name, cursor, limit).then((response) => {\n var result = {\n groups: []\n };\n if (response.groups == null) {\n return Promise.resolve(result);\n }\n result.cursor = response.cursor;\n response.groups.forEach((ug) => {\n result.groups.push({\n avatar_url: ug.avatar_url,\n create_time: ug.create_time,\n creator_id: ug.creator_id,\n description: ug.description,\n edge_count: ug.edge_count ? Number(ug.edge_count) : 0,\n id: ug.id,\n lang_tag: ug.lang_tag,\n max_count: ug.max_count,\n metadata: ug.metadata ? JSON.parse(ug.metadata) : void 0,\n name: ug.name,\n open: ug.open,\n update_time: ug.update_time\n });\n });\n return Promise.resolve(result);\n });\n }\n linkApple(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.linkApple(request).then((response) => {\n return response !== void 0;\n });\n }\n linkCustom(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.linkCustom(request).then((response) => {\n return response !== void 0;\n });\n }\n linkDevice(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.linkDevice(request).then((response) => {\n return response !== void 0;\n });\n }\n linkEmail(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.linkEmail(request).then((response) => {\n return response !== void 0;\n });\n }\n linkFacebook(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.linkFacebook(request).then((response) => {\n return response !== void 0;\n });\n }\n linkFacebookInstantGame(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.linkFacebookInstantGame(request).then((response) => {\n return response !== void 0;\n });\n }\n linkGoogle(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.linkGoogle(request).then((response) => {\n return response !== void 0;\n });\n }\n linkGameCenter(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.linkGameCenter(request).then((response) => {\n return response !== void 0;\n });\n }\n linkSteam(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.linkSteam(request).then((response) => {\n return response !== void 0;\n });\n }\n listFriends(session, state, limit, cursor) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listFriends(limit, state, cursor).then((response) => {\n var result = {\n friends: [],\n cursor: response.cursor\n };\n if (response.friends == null) {\n return Promise.resolve(result);\n }\n response.friends.forEach((f) => {\n result.friends.push({\n user: {\n avatar_url: f.user.avatar_url,\n create_time: f.user.create_time,\n display_name: f.user.display_name,\n edge_count: f.user.edge_count ? Number(f.user.edge_count) : 0,\n facebook_id: f.user.facebook_id,\n gamecenter_id: f.user.gamecenter_id,\n google_id: f.user.google_id,\n id: f.user.id,\n lang_tag: f.user.lang_tag,\n location: f.user.location,\n online: f.user.online,\n steam_id: f.user.steam_id,\n timezone: f.user.timezone,\n update_time: f.user.update_time,\n username: f.user.username,\n metadata: f.user.metadata ? JSON.parse(f.user.metadata) : void 0,\n facebook_instant_game_id: f.user.facebook_instant_game_id\n },\n state: f.state\n });\n });\n return Promise.resolve(result);\n });\n }\n listLeaderboardRecords(session, leaderboardId, ownerIds, limit, cursor, expiry) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listLeaderboardRecords(leaderboardId, ownerIds, limit, cursor, expiry).then((response) => {\n var list = {\n next_cursor: response.next_cursor,\n prev_cursor: response.prev_cursor,\n owner_records: [],\n records: []\n };\n if (response.owner_records != null) {\n response.owner_records.forEach((o) => {\n list.owner_records.push({\n expiry_time: o.expiry_time,\n leaderboard_id: o.leaderboard_id,\n metadata: o.metadata ? JSON.parse(o.metadata) : void 0,\n num_score: o.num_score ? Number(o.num_score) : 0,\n owner_id: o.owner_id,\n rank: o.rank ? Number(o.rank) : 0,\n score: o.score ? Number(o.score) : 0,\n subscore: o.subscore ? Number(o.subscore) : 0,\n update_time: o.update_time,\n username: o.username,\n max_num_score: o.max_num_score ? Number(o.max_num_score) : 0\n });\n });\n }\n if (response.records != null) {\n response.records.forEach((o) => {\n list.records.push({\n expiry_time: o.expiry_time,\n leaderboard_id: o.leaderboard_id,\n metadata: o.metadata ? JSON.parse(o.metadata) : void 0,\n num_score: o.num_score ? Number(o.num_score) : 0,\n owner_id: o.owner_id,\n rank: o.rank ? Number(o.rank) : 0,\n score: o.score ? Number(o.score) : 0,\n subscore: o.subscore ? Number(o.subscore) : 0,\n update_time: o.update_time,\n username: o.username,\n max_num_score: o.max_num_score ? Number(o.max_num_score) : 0\n });\n });\n }\n return Promise.resolve(list);\n });\n }\n listLeaderboardRecordsAroundOwner(session, leaderboardId, ownerId, limit, expiry) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listLeaderboardRecordsAroundOwner(leaderboardId, ownerId, limit, expiry).then((response) => {\n var list = {\n next_cursor: response.next_cursor,\n prev_cursor: response.prev_cursor,\n owner_records: [],\n records: []\n };\n if (response.owner_records != null) {\n response.owner_records.forEach((o) => {\n list.owner_records.push({\n expiry_time: o.expiry_time,\n leaderboard_id: o.leaderboard_id,\n metadata: o.metadata ? JSON.parse(o.metadata) : void 0,\n num_score: o.num_score ? Number(o.num_score) : 0,\n owner_id: o.owner_id,\n rank: o.rank ? Number(o.rank) : 0,\n score: o.score ? Number(o.score) : 0,\n subscore: o.subscore ? Number(o.subscore) : 0,\n update_time: o.update_time,\n username: o.username,\n max_num_score: o.max_num_score ? Number(o.max_num_score) : 0\n });\n });\n }\n if (response.records != null) {\n response.records.forEach((o) => {\n list.records.push({\n expiry_time: o.expiry_time,\n leaderboard_id: o.leaderboard_id,\n metadata: o.metadata ? JSON.parse(o.metadata) : void 0,\n num_score: o.num_score ? Number(o.num_score) : 0,\n owner_id: o.owner_id,\n rank: o.rank ? Number(o.rank) : 0,\n score: o.score ? Number(o.score) : 0,\n subscore: o.subscore ? Number(o.subscore) : 0,\n update_time: o.update_time,\n username: o.username,\n max_num_score: o.max_num_score ? Number(o.max_num_score) : 0\n });\n });\n }\n return Promise.resolve(list);\n });\n }\n listMatches(session, limit, authoritative, label, minSize, maxSize, query) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listMatches(limit, authoritative, label, minSize, maxSize, query);\n }\n listNotifications(session, limit, cacheableCursor) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listNotifications(limit, cacheableCursor).then((response) => {\n var result = {\n cacheable_cursor: response.cacheable_cursor,\n notifications: []\n };\n if (response.notifications == null) {\n return Promise.resolve(result);\n }\n response.notifications.forEach((n) => {\n result.notifications.push({\n code: n.code ? Number(n.code) : 0,\n create_time: n.create_time,\n id: n.id,\n persistent: n.persistent,\n sender_id: n.sender_id,\n subject: n.subject,\n content: n.content ? JSON.parse(n.content) : void 0\n });\n });\n return Promise.resolve(result);\n });\n }\n listStorageObjects(session, collection, userId, limit, cursor) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listStorageObjects(collection, userId, limit, cursor).then((response) => {\n var result = {\n objects: [],\n cursor: response.cursor\n };\n if (response.objects == null) {\n return Promise.resolve(result);\n }\n response.objects.forEach((o) => {\n result.objects.push({\n collection: o.collection,\n key: o.key,\n permission_read: o.permission_read ? Number(o.permission_read) : 0,\n permission_write: o.permission_write ? Number(o.permission_write) : 0,\n value: o.value ? JSON.parse(o.value) : void 0,\n version: o.version,\n user_id: o.user_id,\n create_time: o.create_time,\n update_time: o.update_time\n });\n });\n return Promise.resolve(result);\n });\n }\n listTournaments(session, categoryStart, categoryEnd, startTime, endTime, limit, cursor) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listTournaments(categoryStart, categoryEnd, startTime, endTime, limit, cursor).then((response) => {\n var list = {\n cursor: response.cursor,\n tournaments: []\n };\n if (response.tournaments != null) {\n response.tournaments.forEach((o) => {\n list.tournaments.push({\n id: o.id,\n title: o.title,\n description: o.description,\n duration: o.duration ? Number(o.duration) : 0,\n category: o.category ? Number(o.category) : 0,\n sort_order: o.sort_order ? Number(o.sort_order) : 0,\n size: o.size ? Number(o.size) : 0,\n max_size: o.max_size ? Number(o.max_size) : 0,\n max_num_score: o.max_num_score ? Number(o.max_num_score) : 0,\n can_enter: o.can_enter,\n end_active: o.end_active ? Number(o.end_active) : 0,\n next_reset: o.next_reset ? Number(o.next_reset) : 0,\n metadata: o.metadata ? JSON.parse(o.metadata) : void 0,\n create_time: o.create_time,\n start_time: o.start_time,\n end_time: o.end_time,\n start_active: o.start_active\n });\n });\n }\n return Promise.resolve(list);\n });\n }\n listTournamentRecords(session, tournamentId, ownerIds, limit, cursor, expiry) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listTournamentRecords(tournamentId, ownerIds, limit, cursor, expiry).then((response) => {\n var list = {\n next_cursor: response.next_cursor,\n prev_cursor: response.prev_cursor,\n owner_records: [],\n records: []\n };\n if (response.owner_records != null) {\n response.owner_records.forEach((o) => {\n list.owner_records.push({\n expiry_time: o.expiry_time,\n leaderboard_id: o.leaderboard_id,\n metadata: o.metadata ? JSON.parse(o.metadata) : void 0,\n num_score: o.num_score ? Number(o.num_score) : 0,\n owner_id: o.owner_id,\n rank: o.rank ? Number(o.rank) : 0,\n score: o.score ? Number(o.score) : 0,\n subscore: o.subscore ? Number(o.subscore) : 0,\n update_time: o.update_time,\n username: o.username,\n max_num_score: o.max_num_score ? Number(o.max_num_score) : 0\n });\n });\n }\n if (response.records != null) {\n response.records.forEach((o) => {\n list.records.push({\n expiry_time: o.expiry_time,\n leaderboard_id: o.leaderboard_id,\n metadata: o.metadata ? JSON.parse(o.metadata) : void 0,\n num_score: o.num_score ? Number(o.num_score) : 0,\n owner_id: o.owner_id,\n rank: o.rank ? Number(o.rank) : 0,\n score: o.score ? Number(o.score) : 0,\n subscore: o.subscore ? Number(o.subscore) : 0,\n update_time: o.update_time,\n username: o.username,\n max_num_score: o.max_num_score ? Number(o.max_num_score) : 0\n });\n });\n }\n return Promise.resolve(list);\n });\n }\n listTournamentRecordsAroundOwner(session, tournamentId, ownerId, limit, expiry) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.listTournamentRecordsAroundOwner(tournamentId, ownerId, limit, expiry).then((response) => {\n var list = {\n next_cursor: response.next_cursor,\n prev_cursor: response.prev_cursor,\n owner_records: [],\n records: []\n };\n if (response.owner_records != null) {\n response.owner_records.forEach((o) => {\n list.owner_records.push({\n expiry_time: o.expiry_time,\n leaderboard_id: o.leaderboard_id,\n metadata: o.metadata ? JSON.parse(o.metadata) : void 0,\n num_score: o.num_score ? Number(o.num_score) : 0,\n owner_id: o.owner_id,\n rank: o.rank ? Number(o.rank) : 0,\n score: o.score ? Number(o.score) : 0,\n subscore: o.subscore ? Number(o.subscore) : 0,\n update_time: o.update_time,\n username: o.username,\n max_num_score: o.max_num_score ? Number(o.max_num_score) : 0\n });\n });\n }\n if (response.records != null) {\n response.records.forEach((o) => {\n list.records.push({\n expiry_time: o.expiry_time,\n leaderboard_id: o.leaderboard_id,\n metadata: o.metadata ? JSON.parse(o.metadata) : void 0,\n num_score: o.num_score ? Number(o.num_score) : 0,\n owner_id: o.owner_id,\n rank: o.rank ? Number(o.rank) : 0,\n score: o.score ? Number(o.score) : 0,\n subscore: o.subscore ? Number(o.subscore) : 0,\n update_time: o.update_time,\n username: o.username,\n max_num_score: o.max_num_score ? Number(o.max_num_score) : 0\n });\n });\n }\n return Promise.resolve(list);\n });\n }\n promoteGroupUsers(session, groupId, ids) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.promoteGroupUsers(groupId, ids);\n }\n readStorageObjects(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.readStorageObjects(request).then((response) => {\n var result = {objects: []};\n if (response.objects == null) {\n return Promise.resolve(result);\n }\n response.objects.forEach((o) => {\n result.objects.push({\n collection: o.collection,\n key: o.key,\n permission_read: o.permission_read ? Number(o.permission_read) : 0,\n permission_write: o.permission_write ? Number(o.permission_write) : 0,\n value: o.value ? JSON.parse(o.value) : void 0,\n version: o.version,\n user_id: o.user_id,\n create_time: o.create_time,\n update_time: o.update_time\n });\n });\n return Promise.resolve(result);\n });\n }\n rpc(session, id, input) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.rpcFunc(id, JSON.stringify(input)).then((response) => {\n return Promise.resolve({\n id: response.id,\n payload: !response.payload ? void 0 : JSON.parse(response.payload)\n });\n });\n }\n rpcGet(id, session, httpKey, input) {\n if (!httpKey || httpKey == \"\") {\n this.configuration.bearerToken = session && session.token;\n } else {\n this.configuration.username = void 0;\n this.configuration.bearerToken = void 0;\n }\n return this.apiClient.rpcFunc2(id, input && JSON.stringify(input) || \"\", httpKey).then((response) => {\n this.configuration.username = this.serverkey;\n return Promise.resolve({\n id: response.id,\n payload: !response.payload ? void 0 : JSON.parse(response.payload)\n });\n }).catch((err) => {\n this.configuration.username = this.serverkey;\n throw err;\n });\n }\n unlinkApple(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.unlinkApple(request).then((response) => {\n return response !== void 0;\n });\n }\n unlinkCustom(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.unlinkCustom(request).then((response) => {\n return response !== void 0;\n });\n }\n unlinkDevice(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.unlinkDevice(request).then((response) => {\n return response !== void 0;\n });\n }\n unlinkEmail(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.unlinkEmail(request).then((response) => {\n return response !== void 0;\n });\n }\n unlinkFacebook(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.unlinkFacebook(request).then((response) => {\n return response !== void 0;\n });\n }\n unlinkFacebookInstantGame(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.unlinkFacebookInstantGame(request).then((response) => {\n return response !== void 0;\n });\n }\n unlinkGoogle(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.unlinkGoogle(request).then((response) => {\n return response !== void 0;\n });\n }\n unlinkGameCenter(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.unlinkGameCenter(request).then((response) => {\n return response !== void 0;\n });\n }\n unlinkSteam(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.unlinkSteam(request).then((response) => {\n return response !== void 0;\n });\n }\n updateAccount(session, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.updateAccount(request).then((response) => {\n return response !== void 0;\n });\n }\n updateGroup(session, groupId, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.updateGroup(groupId, request).then((response) => {\n return response !== void 0;\n });\n }\n writeLeaderboardRecord(session, leaderboardId, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.writeLeaderboardRecord(leaderboardId, {\n metadata: request.metadata ? JSON.stringify(request.metadata) : void 0,\n score: request.score,\n subscore: request.subscore\n }).then((response) => {\n return Promise.resolve({\n expiry_time: response.expiry_time,\n leaderboard_id: response.leaderboard_id,\n metadata: response.metadata ? JSON.parse(response.metadata) : void 0,\n num_score: response.num_score ? Number(response.num_score) : 0,\n owner_id: response.owner_id,\n score: response.score ? Number(response.score) : 0,\n subscore: response.subscore ? Number(response.subscore) : 0,\n update_time: response.update_time,\n username: response.username,\n max_num_score: response.max_num_score ? Number(response.max_num_score) : 0,\n rank: response.rank ? Number(response.rank) : 0\n });\n });\n }\n writeStorageObjects(session, objects) {\n this.configuration.bearerToken = session && session.token;\n var request = {objects: []};\n objects.forEach((o) => {\n request.objects.push({\n collection: o.collection,\n key: o.key,\n permission_read: o.permission_read,\n permission_write: o.permission_write,\n value: JSON.stringify(o.value),\n version: o.version\n });\n });\n return this.apiClient.writeStorageObjects(request);\n }\n writeTournamentRecord(session, tournamentId, request) {\n this.configuration.bearerToken = session && session.token;\n return this.apiClient.writeTournamentRecord(tournamentId, {\n metadata: request.metadata ? JSON.stringify(request.metadata) : void 0,\n score: request.score,\n subscore: request.subscore\n }).then((response) => {\n return Promise.resolve({\n expiry_time: response.expiry_time,\n leaderboard_id: response.leaderboard_id,\n metadata: response.metadata ? JSON.parse(response.metadata) : void 0,\n num_score: response.num_score ? Number(response.num_score) : 0,\n owner_id: response.owner_id,\n score: response.score ? Number(response.score) : 0,\n subscore: response.subscore ? Number(response.subscore) : 0,\n update_time: response.update_time,\n username: response.username,\n max_num_score: response.max_num_score ? Number(response.max_num_score) : 0,\n rank: response.rank ? Number(response.rank) : 0\n });\n });\n }\n};\n\n\n\n//# sourceURL=webpack://Nakama/./node_modules/@heroiclabs/nakama-js/dist/nakama-js.esm.js?"); + +/***/ }), + +/***/ "./src/index.js": +/*!**********************!*\ + !*** ./src/index.js ***! + \**********************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("var NakamaWrapper = __webpack_require__(/*! ./nakama */ \"./src/nakama.js\").default;\n\nvar Nakama = new NakamaWrapper(\"/*%clientHost%*/\", \"/*%clientPort%*/\", [\n /*%useSSL%*/\n][0]);\nNakama.initiate();\nmodule.exports = Nakama;\n\n//# sourceURL=webpack://Nakama/./src/index.js?"); + +/***/ }), + +/***/ "./src/logger.js": +/*!***********************!*\ + !*** ./src/logger.js ***! + \***********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ _default)\n/* harmony export */ });\n/* harmony import */ var _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/classCallCheck */ \"./node_modules/@babel/runtime/helpers/classCallCheck.js\");\n/* harmony import */ var _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \"./node_modules/@babel/runtime/helpers/createClass.js\");\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_1__);\n\n\n\nvar _default = /*#__PURE__*/function () {\n function _default() {\n _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0___default()(this, _default);\n }\n\n _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_1___default()(_default, null, [{\n key: \"log\",\n value: function log(msg) {\n var emoji = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';\n var colour = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'black';\n\n if ([\n /*%debugMode%*/\n ][0]) {\n console.log(\"%s %c\".concat(msg), emoji, \"color: \".concat(colour));\n }\n }\n }, {\n key: \"success\",\n value: function success(msg) {\n if ([\n /*%debugMode%*/\n ][0]) {\n console.log(\"%s \".concat(msg), 'βœ”οΈ');\n }\n }\n }, {\n key: \"warn\",\n value: function warn(msg) {\n if ([\n /*%debugMode%*/\n ][0]) {\n console.log(\"%s \".concat(msg), '⚠️');\n }\n }\n }]);\n\n return _default;\n}();\n\n\n\n//# sourceURL=webpack://Nakama/./src/logger.js?"); + +/***/ }), + +/***/ "./src/nakama.js": +/*!***********************!*\ + !*** ./src/nakama.js ***! + \***********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Nakama)\n/* harmony export */ });\n/* harmony import */ var _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/regenerator */ \"./node_modules/@babel/runtime/regenerator/index.js\");\n/* harmony import */ var _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\");\n/* harmony import */ var _babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @babel/runtime/helpers/classCallCheck */ \"./node_modules/@babel/runtime/helpers/classCallCheck.js\");\n/* harmony import */ var _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @babel/runtime/helpers/defineProperty */ \"./node_modules/@babel/runtime/helpers/defineProperty.js\");\n/* harmony import */ var _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _heroiclabs_nakama_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @heroiclabs/nakama-js */ \"./node_modules/@heroiclabs/nakama-js/dist/nakama-js.esm.js\");\n/* harmony import */ var uuid__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! uuid */ \"./node_modules/uuid/dist/esm-browser/v4.js\");\n/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./logger */ \"./src/logger.js\");\n\n\n\n\n\n\n\n\nvar Nakama = function Nakama(clientHost, clientPort, useSSL) {\n var _this = this;\n\n _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_2___default()(this, Nakama);\n\n _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_3___default()(this, \"initiate\", /*#__PURE__*/_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1___default()( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee() {\n return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n _context.next = 2;\n return _this.checkSessionAndAuthenticate();\n\n case 2:\n _context.next = 4;\n return _this.establishSocketConnection();\n\n case 4:\n _logger__WEBPACK_IMPORTED_MODULE_5__.default.log(\"ct.nakama has loaded!\", \"✨\");\n\n case 5:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee);\n })));\n\n _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_3___default()(this, \"checkSessionAndAuthenticate\", /*#__PURE__*/_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1___default()( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee2() {\n var nakamaAuthToken, session, currentTimeInSec;\n return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n // Checks browser for session and authenticates with server\n nakamaAuthToken = localStorage.getItem(\"nakamaAuthToken\");\n\n if (!(nakamaAuthToken && nakamaAuthToken != \"\")) {\n _context2.next = 15;\n break;\n }\n\n _logger__WEBPACK_IMPORTED_MODULE_5__.default.log(\"Session Found\");\n session = _heroiclabs_nakama_js__WEBPACK_IMPORTED_MODULE_4__.Session.restore(nakamaAuthToken);\n currentTimeInSec = new Date() / 1000;\n\n if (session.isexpired(currentTimeInSec)) {\n _context2.next = 10;\n break;\n }\n\n // Session valid so restore it\n _this.session = session;\n _logger__WEBPACK_IMPORTED_MODULE_5__.default.log(\"Session Restored\");\n _context2.next = 13;\n break;\n\n case 10:\n _logger__WEBPACK_IMPORTED_MODULE_5__.default.log(\"Session Expired\");\n _context2.next = 13;\n return _this.createSession();\n\n case 13:\n _context2.next = 17;\n break;\n\n case 15:\n _context2.next = 17;\n return _this.createSession();\n\n case 17:\n _logger__WEBPACK_IMPORTED_MODULE_5__.default.success(\"Authenticated Session\");\n\n case 18:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2);\n })));\n\n _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_3___default()(this, \"establishSocketConnection\", /*#__PURE__*/_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1___default()( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee3() {\n var trace;\n return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n // Create connection to the server via websockets\n trace = false; // TODO: understand what this does\n\n _this.socket = _this.client.createSocket(_this.useSSL, trace);\n _context3.next = 4;\n return _this.socket.connect(_this.session);\n\n case 4:\n _logger__WEBPACK_IMPORTED_MODULE_5__.default.success(\"Established Websocket Connection\");\n\n case 5:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3);\n })));\n\n _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_3___default()(this, \"createSession\", /*#__PURE__*/_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1___default()( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee4() {\n var newUserId, nakamaSession;\n return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default().wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n _logger__WEBPACK_IMPORTED_MODULE_5__.default.log(\"Creating New Session\");\n newUserId = (0,uuid__WEBPACK_IMPORTED_MODULE_6__.default)();\n _context4.next = 4;\n return _this.client.authenticateCustom(newUserId, true, newUserId);\n\n case 4:\n nakamaSession = _context4.sent;\n localStorage.setItem(\"nakamaAuthToken\", nakamaSession.token);\n _this.session = nakamaSession;\n return _context4.abrupt(\"return\", _this.session);\n\n case 8:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4);\n })));\n\n this.useSSL = useSSL;\n this.client = new _heroiclabs_nakama_js__WEBPACK_IMPORTED_MODULE_4__.Client(\"defaultkey\", clientHost, clientPort, this.useSSL);\n this.session;\n this.socket;\n this.state = {};\n};\n\n\n\n//# sourceURL=webpack://Nakama/./src/nakama.js?"); + +/***/ }), + +/***/ "./node_modules/regenerator-runtime/runtime.js": +/*!*****************************************************!*\ + !*** ./node_modules/regenerator-runtime/runtime.js ***! + \*****************************************************/ +/***/ ((module) => { + +eval("/**\n * Copyright (c) 2014-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nvar runtime = (function (exports) {\n \"use strict\";\n\n var Op = Object.prototype;\n var hasOwn = Op.hasOwnProperty;\n var undefined; // More compressible than void 0.\n var $Symbol = typeof Symbol === \"function\" ? Symbol : {};\n var iteratorSymbol = $Symbol.iterator || \"@@iterator\";\n var asyncIteratorSymbol = $Symbol.asyncIterator || \"@@asyncIterator\";\n var toStringTagSymbol = $Symbol.toStringTag || \"@@toStringTag\";\n\n function define(obj, key, value) {\n Object.defineProperty(obj, key, {\n value: value,\n enumerable: true,\n configurable: true,\n writable: true\n });\n return obj[key];\n }\n try {\n // IE 8 has a broken Object.defineProperty that only works on DOM objects.\n define({}, \"\");\n } catch (err) {\n define = function(obj, key, value) {\n return obj[key] = value;\n };\n }\n\n function wrap(innerFn, outerFn, self, tryLocsList) {\n // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.\n var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;\n var generator = Object.create(protoGenerator.prototype);\n var context = new Context(tryLocsList || []);\n\n // The ._invoke method unifies the implementations of the .next,\n // .throw, and .return methods.\n generator._invoke = makeInvokeMethod(innerFn, self, context);\n\n return generator;\n }\n exports.wrap = wrap;\n\n // Try/catch helper to minimize deoptimizations. Returns a completion\n // record like context.tryEntries[i].completion. This interface could\n // have been (and was previously) designed to take a closure to be\n // invoked without arguments, but in all the cases we care about we\n // already have an existing method we want to call, so there's no need\n // to create a new function object. We can even get away with assuming\n // the method takes exactly one argument, since that happens to be true\n // in every case, so we don't have to touch the arguments object. The\n // only additional allocation required is the completion record, which\n // has a stable shape and so hopefully should be cheap to allocate.\n function tryCatch(fn, obj, arg) {\n try {\n return { type: \"normal\", arg: fn.call(obj, arg) };\n } catch (err) {\n return { type: \"throw\", arg: err };\n }\n }\n\n var GenStateSuspendedStart = \"suspendedStart\";\n var GenStateSuspendedYield = \"suspendedYield\";\n var GenStateExecuting = \"executing\";\n var GenStateCompleted = \"completed\";\n\n // Returning this object from the innerFn has the same effect as\n // breaking out of the dispatch switch statement.\n var ContinueSentinel = {};\n\n // Dummy constructor functions that we use as the .constructor and\n // .constructor.prototype properties for functions that return Generator\n // objects. For full spec compliance, you may wish to configure your\n // minifier not to mangle the names of these two functions.\n function Generator() {}\n function GeneratorFunction() {}\n function GeneratorFunctionPrototype() {}\n\n // This is a polyfill for %IteratorPrototype% for environments that\n // don't natively support it.\n var IteratorPrototype = {};\n IteratorPrototype[iteratorSymbol] = function () {\n return this;\n };\n\n var getProto = Object.getPrototypeOf;\n var NativeIteratorPrototype = getProto && getProto(getProto(values([])));\n if (NativeIteratorPrototype &&\n NativeIteratorPrototype !== Op &&\n hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {\n // This environment has a native %IteratorPrototype%; use it instead\n // of the polyfill.\n IteratorPrototype = NativeIteratorPrototype;\n }\n\n var Gp = GeneratorFunctionPrototype.prototype =\n Generator.prototype = Object.create(IteratorPrototype);\n GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;\n GeneratorFunctionPrototype.constructor = GeneratorFunction;\n GeneratorFunction.displayName = define(\n GeneratorFunctionPrototype,\n toStringTagSymbol,\n \"GeneratorFunction\"\n );\n\n // Helper for defining the .next, .throw, and .return methods of the\n // Iterator interface in terms of a single ._invoke method.\n function defineIteratorMethods(prototype) {\n [\"next\", \"throw\", \"return\"].forEach(function(method) {\n define(prototype, method, function(arg) {\n return this._invoke(method, arg);\n });\n });\n }\n\n exports.isGeneratorFunction = function(genFun) {\n var ctor = typeof genFun === \"function\" && genFun.constructor;\n return ctor\n ? ctor === GeneratorFunction ||\n // For the native GeneratorFunction constructor, the best we can\n // do is to check its .name property.\n (ctor.displayName || ctor.name) === \"GeneratorFunction\"\n : false;\n };\n\n exports.mark = function(genFun) {\n if (Object.setPrototypeOf) {\n Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);\n } else {\n genFun.__proto__ = GeneratorFunctionPrototype;\n define(genFun, toStringTagSymbol, \"GeneratorFunction\");\n }\n genFun.prototype = Object.create(Gp);\n return genFun;\n };\n\n // Within the body of any async function, `await x` is transformed to\n // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test\n // `hasOwn.call(value, \"__await\")` to determine if the yielded value is\n // meant to be awaited.\n exports.awrap = function(arg) {\n return { __await: arg };\n };\n\n function AsyncIterator(generator, PromiseImpl) {\n function invoke(method, arg, resolve, reject) {\n var record = tryCatch(generator[method], generator, arg);\n if (record.type === \"throw\") {\n reject(record.arg);\n } else {\n var result = record.arg;\n var value = result.value;\n if (value &&\n typeof value === \"object\" &&\n hasOwn.call(value, \"__await\")) {\n return PromiseImpl.resolve(value.__await).then(function(value) {\n invoke(\"next\", value, resolve, reject);\n }, function(err) {\n invoke(\"throw\", err, resolve, reject);\n });\n }\n\n return PromiseImpl.resolve(value).then(function(unwrapped) {\n // When a yielded Promise is resolved, its final value becomes\n // the .value of the Promise<{value,done}> result for the\n // current iteration.\n result.value = unwrapped;\n resolve(result);\n }, function(error) {\n // If a rejected Promise was yielded, throw the rejection back\n // into the async generator function so it can be handled there.\n return invoke(\"throw\", error, resolve, reject);\n });\n }\n }\n\n var previousPromise;\n\n function enqueue(method, arg) {\n function callInvokeWithMethodAndArg() {\n return new PromiseImpl(function(resolve, reject) {\n invoke(method, arg, resolve, reject);\n });\n }\n\n return previousPromise =\n // If enqueue has been called before, then we want to wait until\n // all previous Promises have been resolved before calling invoke,\n // so that results are always delivered in the correct order. If\n // enqueue has not been called before, then it is important to\n // call invoke immediately, without waiting on a callback to fire,\n // so that the async generator function has the opportunity to do\n // any necessary setup in a predictable way. This predictability\n // is why the Promise constructor synchronously invokes its\n // executor callback, and why async functions synchronously\n // execute code before the first await. Since we implement simple\n // async functions in terms of async generators, it is especially\n // important to get this right, even though it requires care.\n previousPromise ? previousPromise.then(\n callInvokeWithMethodAndArg,\n // Avoid propagating failures to Promises returned by later\n // invocations of the iterator.\n callInvokeWithMethodAndArg\n ) : callInvokeWithMethodAndArg();\n }\n\n // Define the unified helper method that is used to implement .next,\n // .throw, and .return (see defineIteratorMethods).\n this._invoke = enqueue;\n }\n\n defineIteratorMethods(AsyncIterator.prototype);\n AsyncIterator.prototype[asyncIteratorSymbol] = function () {\n return this;\n };\n exports.AsyncIterator = AsyncIterator;\n\n // Note that simple async functions are implemented on top of\n // AsyncIterator objects; they just return a Promise for the value of\n // the final result produced by the iterator.\n exports.async = function(innerFn, outerFn, self, tryLocsList, PromiseImpl) {\n if (PromiseImpl === void 0) PromiseImpl = Promise;\n\n var iter = new AsyncIterator(\n wrap(innerFn, outerFn, self, tryLocsList),\n PromiseImpl\n );\n\n return exports.isGeneratorFunction(outerFn)\n ? iter // If outerFn is a generator, return the full iterator.\n : iter.next().then(function(result) {\n return result.done ? result.value : iter.next();\n });\n };\n\n function makeInvokeMethod(innerFn, self, context) {\n var state = GenStateSuspendedStart;\n\n return function invoke(method, arg) {\n if (state === GenStateExecuting) {\n throw new Error(\"Generator is already running\");\n }\n\n if (state === GenStateCompleted) {\n if (method === \"throw\") {\n throw arg;\n }\n\n // Be forgiving, per 25.3.3.3.3 of the spec:\n // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume\n return doneResult();\n }\n\n context.method = method;\n context.arg = arg;\n\n while (true) {\n var delegate = context.delegate;\n if (delegate) {\n var delegateResult = maybeInvokeDelegate(delegate, context);\n if (delegateResult) {\n if (delegateResult === ContinueSentinel) continue;\n return delegateResult;\n }\n }\n\n if (context.method === \"next\") {\n // Setting context._sent for legacy support of Babel's\n // function.sent implementation.\n context.sent = context._sent = context.arg;\n\n } else if (context.method === \"throw\") {\n if (state === GenStateSuspendedStart) {\n state = GenStateCompleted;\n throw context.arg;\n }\n\n context.dispatchException(context.arg);\n\n } else if (context.method === \"return\") {\n context.abrupt(\"return\", context.arg);\n }\n\n state = GenStateExecuting;\n\n var record = tryCatch(innerFn, self, context);\n if (record.type === \"normal\") {\n // If an exception is thrown from innerFn, we leave state ===\n // GenStateExecuting and loop back for another invocation.\n state = context.done\n ? GenStateCompleted\n : GenStateSuspendedYield;\n\n if (record.arg === ContinueSentinel) {\n continue;\n }\n\n return {\n value: record.arg,\n done: context.done\n };\n\n } else if (record.type === \"throw\") {\n state = GenStateCompleted;\n // Dispatch the exception by looping back around to the\n // context.dispatchException(context.arg) call above.\n context.method = \"throw\";\n context.arg = record.arg;\n }\n }\n };\n }\n\n // Call delegate.iterator[context.method](context.arg) and handle the\n // result, either by returning a { value, done } result from the\n // delegate iterator, or by modifying context.method and context.arg,\n // setting context.delegate to null, and returning the ContinueSentinel.\n function maybeInvokeDelegate(delegate, context) {\n var method = delegate.iterator[context.method];\n if (method === undefined) {\n // A .throw or .return when the delegate iterator has no .throw\n // method always terminates the yield* loop.\n context.delegate = null;\n\n if (context.method === \"throw\") {\n // Note: [\"return\"] must be used for ES3 parsing compatibility.\n if (delegate.iterator[\"return\"]) {\n // If the delegate iterator has a return method, give it a\n // chance to clean up.\n context.method = \"return\";\n context.arg = undefined;\n maybeInvokeDelegate(delegate, context);\n\n if (context.method === \"throw\") {\n // If maybeInvokeDelegate(context) changed context.method from\n // \"return\" to \"throw\", let that override the TypeError below.\n return ContinueSentinel;\n }\n }\n\n context.method = \"throw\";\n context.arg = new TypeError(\n \"The iterator does not provide a 'throw' method\");\n }\n\n return ContinueSentinel;\n }\n\n var record = tryCatch(method, delegate.iterator, context.arg);\n\n if (record.type === \"throw\") {\n context.method = \"throw\";\n context.arg = record.arg;\n context.delegate = null;\n return ContinueSentinel;\n }\n\n var info = record.arg;\n\n if (! info) {\n context.method = \"throw\";\n context.arg = new TypeError(\"iterator result is not an object\");\n context.delegate = null;\n return ContinueSentinel;\n }\n\n if (info.done) {\n // Assign the result of the finished delegate to the temporary\n // variable specified by delegate.resultName (see delegateYield).\n context[delegate.resultName] = info.value;\n\n // Resume execution at the desired location (see delegateYield).\n context.next = delegate.nextLoc;\n\n // If context.method was \"throw\" but the delegate handled the\n // exception, let the outer generator proceed normally. If\n // context.method was \"next\", forget context.arg since it has been\n // \"consumed\" by the delegate iterator. If context.method was\n // \"return\", allow the original .return call to continue in the\n // outer generator.\n if (context.method !== \"return\") {\n context.method = \"next\";\n context.arg = undefined;\n }\n\n } else {\n // Re-yield the result returned by the delegate method.\n return info;\n }\n\n // The delegate iterator is finished, so forget it and continue with\n // the outer generator.\n context.delegate = null;\n return ContinueSentinel;\n }\n\n // Define Generator.prototype.{next,throw,return} in terms of the\n // unified ._invoke helper method.\n defineIteratorMethods(Gp);\n\n define(Gp, toStringTagSymbol, \"Generator\");\n\n // A Generator should always return itself as the iterator object when the\n // @@iterator function is called on it. Some browsers' implementations of the\n // iterator prototype chain incorrectly implement this, causing the Generator\n // object to not be returned from this call. This ensures that doesn't happen.\n // See https://github.com/facebook/regenerator/issues/274 for more details.\n Gp[iteratorSymbol] = function() {\n return this;\n };\n\n Gp.toString = function() {\n return \"[object Generator]\";\n };\n\n function pushTryEntry(locs) {\n var entry = { tryLoc: locs[0] };\n\n if (1 in locs) {\n entry.catchLoc = locs[1];\n }\n\n if (2 in locs) {\n entry.finallyLoc = locs[2];\n entry.afterLoc = locs[3];\n }\n\n this.tryEntries.push(entry);\n }\n\n function resetTryEntry(entry) {\n var record = entry.completion || {};\n record.type = \"normal\";\n delete record.arg;\n entry.completion = record;\n }\n\n function Context(tryLocsList) {\n // The root entry object (effectively a try statement without a catch\n // or a finally block) gives us a place to store values thrown from\n // locations where there is no enclosing try statement.\n this.tryEntries = [{ tryLoc: \"root\" }];\n tryLocsList.forEach(pushTryEntry, this);\n this.reset(true);\n }\n\n exports.keys = function(object) {\n var keys = [];\n for (var key in object) {\n keys.push(key);\n }\n keys.reverse();\n\n // Rather than returning an object with a next method, we keep\n // things simple and return the next function itself.\n return function next() {\n while (keys.length) {\n var key = keys.pop();\n if (key in object) {\n next.value = key;\n next.done = false;\n return next;\n }\n }\n\n // To avoid creating an additional object, we just hang the .value\n // and .done properties off the next function object itself. This\n // also ensures that the minifier will not anonymize the function.\n next.done = true;\n return next;\n };\n };\n\n function values(iterable) {\n if (iterable) {\n var iteratorMethod = iterable[iteratorSymbol];\n if (iteratorMethod) {\n return iteratorMethod.call(iterable);\n }\n\n if (typeof iterable.next === \"function\") {\n return iterable;\n }\n\n if (!isNaN(iterable.length)) {\n var i = -1, next = function next() {\n while (++i < iterable.length) {\n if (hasOwn.call(iterable, i)) {\n next.value = iterable[i];\n next.done = false;\n return next;\n }\n }\n\n next.value = undefined;\n next.done = true;\n\n return next;\n };\n\n return next.next = next;\n }\n }\n\n // Return an iterator with no values.\n return { next: doneResult };\n }\n exports.values = values;\n\n function doneResult() {\n return { value: undefined, done: true };\n }\n\n Context.prototype = {\n constructor: Context,\n\n reset: function(skipTempReset) {\n this.prev = 0;\n this.next = 0;\n // Resetting context._sent for legacy support of Babel's\n // function.sent implementation.\n this.sent = this._sent = undefined;\n this.done = false;\n this.delegate = null;\n\n this.method = \"next\";\n this.arg = undefined;\n\n this.tryEntries.forEach(resetTryEntry);\n\n if (!skipTempReset) {\n for (var name in this) {\n // Not sure about the optimal order of these conditions:\n if (name.charAt(0) === \"t\" &&\n hasOwn.call(this, name) &&\n !isNaN(+name.slice(1))) {\n this[name] = undefined;\n }\n }\n }\n },\n\n stop: function() {\n this.done = true;\n\n var rootEntry = this.tryEntries[0];\n var rootRecord = rootEntry.completion;\n if (rootRecord.type === \"throw\") {\n throw rootRecord.arg;\n }\n\n return this.rval;\n },\n\n dispatchException: function(exception) {\n if (this.done) {\n throw exception;\n }\n\n var context = this;\n function handle(loc, caught) {\n record.type = \"throw\";\n record.arg = exception;\n context.next = loc;\n\n if (caught) {\n // If the dispatched exception was caught by a catch block,\n // then let that catch block handle the exception normally.\n context.method = \"next\";\n context.arg = undefined;\n }\n\n return !! caught;\n }\n\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n var record = entry.completion;\n\n if (entry.tryLoc === \"root\") {\n // Exception thrown outside of any try block that could handle\n // it, so set the completion value of the entire function to\n // throw the exception.\n return handle(\"end\");\n }\n\n if (entry.tryLoc <= this.prev) {\n var hasCatch = hasOwn.call(entry, \"catchLoc\");\n var hasFinally = hasOwn.call(entry, \"finallyLoc\");\n\n if (hasCatch && hasFinally) {\n if (this.prev < entry.catchLoc) {\n return handle(entry.catchLoc, true);\n } else if (this.prev < entry.finallyLoc) {\n return handle(entry.finallyLoc);\n }\n\n } else if (hasCatch) {\n if (this.prev < entry.catchLoc) {\n return handle(entry.catchLoc, true);\n }\n\n } else if (hasFinally) {\n if (this.prev < entry.finallyLoc) {\n return handle(entry.finallyLoc);\n }\n\n } else {\n throw new Error(\"try statement without catch or finally\");\n }\n }\n }\n },\n\n abrupt: function(type, arg) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.tryLoc <= this.prev &&\n hasOwn.call(entry, \"finallyLoc\") &&\n this.prev < entry.finallyLoc) {\n var finallyEntry = entry;\n break;\n }\n }\n\n if (finallyEntry &&\n (type === \"break\" ||\n type === \"continue\") &&\n finallyEntry.tryLoc <= arg &&\n arg <= finallyEntry.finallyLoc) {\n // Ignore the finally entry if control is not jumping to a\n // location outside the try/catch block.\n finallyEntry = null;\n }\n\n var record = finallyEntry ? finallyEntry.completion : {};\n record.type = type;\n record.arg = arg;\n\n if (finallyEntry) {\n this.method = \"next\";\n this.next = finallyEntry.finallyLoc;\n return ContinueSentinel;\n }\n\n return this.complete(record);\n },\n\n complete: function(record, afterLoc) {\n if (record.type === \"throw\") {\n throw record.arg;\n }\n\n if (record.type === \"break\" ||\n record.type === \"continue\") {\n this.next = record.arg;\n } else if (record.type === \"return\") {\n this.rval = this.arg = record.arg;\n this.method = \"return\";\n this.next = \"end\";\n } else if (record.type === \"normal\" && afterLoc) {\n this.next = afterLoc;\n }\n\n return ContinueSentinel;\n },\n\n finish: function(finallyLoc) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.finallyLoc === finallyLoc) {\n this.complete(entry.completion, entry.afterLoc);\n resetTryEntry(entry);\n return ContinueSentinel;\n }\n }\n },\n\n \"catch\": function(tryLoc) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.tryLoc === tryLoc) {\n var record = entry.completion;\n if (record.type === \"throw\") {\n var thrown = record.arg;\n resetTryEntry(entry);\n }\n return thrown;\n }\n }\n\n // The context.catch method must only be called with a location\n // argument that corresponds to a known catch block.\n throw new Error(\"illegal catch attempt\");\n },\n\n delegateYield: function(iterable, resultName, nextLoc) {\n this.delegate = {\n iterator: values(iterable),\n resultName: resultName,\n nextLoc: nextLoc\n };\n\n if (this.method === \"next\") {\n // Deliberately forget the last sent value so that we don't\n // accidentally pass it on to the delegate.\n this.arg = undefined;\n }\n\n return ContinueSentinel;\n }\n };\n\n // Regardless of whether this script is executing as a CommonJS module\n // or not, return the runtime object so that we can declare the variable\n // regeneratorRuntime in the outer scope, which allows this module to be\n // injected easily by `bin/regenerator --include-runtime script.js`.\n return exports;\n\n}(\n // If this script is executing as a CommonJS module, use module.exports\n // as the regeneratorRuntime namespace. Otherwise create a new empty\n // object. Either way, the resulting object will be used to initialize\n // the regeneratorRuntime variable at the top of this file.\n true ? module.exports : 0\n));\n\ntry {\n regeneratorRuntime = runtime;\n} catch (accidentalStrictMode) {\n // This module should not be running in strict mode, so the above\n // assignment should always work unless something is misconfigured. Just\n // in case runtime.js accidentally runs in strict mode, we can escape\n // strict mode using a global Function call. This could conceivably fail\n // if a Content Security Policy forbids using Function, but in that case\n // the proper solution is to fix the accidental strict mode problem. If\n // you've misconfigured your bundler to force strict mode and applied a\n // CSP to forbid Function, and you're not willing to fix either of those\n // problems, please detail your unique predicament in a GitHub issue.\n Function(\"r\", \"regeneratorRuntime = r\")(runtime);\n}\n\n\n//# sourceURL=webpack://Nakama/./node_modules/regenerator-runtime/runtime.js?"); + +/***/ }), + +/***/ "./node_modules/uuid/dist/esm-browser/regex.js": +/*!*****************************************************!*\ + !*** ./node_modules/uuid/dist/esm-browser/regex.js ***! + \*****************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i);\n\n//# sourceURL=webpack://Nakama/./node_modules/uuid/dist/esm-browser/regex.js?"); + +/***/ }), + +/***/ "./node_modules/uuid/dist/esm-browser/rng.js": +/*!***************************************************!*\ + !*** ./node_modules/uuid/dist/esm-browser/rng.js ***! + \***************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ rng)\n/* harmony export */ });\n// Unique ID creation requires a high quality random # generator. In the browser we therefore\n// require the crypto API and do not support built-in fallback to lower quality random number\n// generators (like Math.random()).\nvar getRandomValues;\nvar rnds8 = new Uint8Array(16);\nfunction rng() {\n // lazy load so that environments that need to polyfill have a chance to do so\n if (!getRandomValues) {\n // getRandomValues needs to be invoked in a context where \"this\" is a Crypto implementation. Also,\n // find the complete implementation of crypto (msCrypto) on IE11.\n getRandomValues = typeof crypto !== 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto) || typeof msCrypto !== 'undefined' && typeof msCrypto.getRandomValues === 'function' && msCrypto.getRandomValues.bind(msCrypto);\n\n if (!getRandomValues) {\n throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');\n }\n }\n\n return getRandomValues(rnds8);\n}\n\n//# sourceURL=webpack://Nakama/./node_modules/uuid/dist/esm-browser/rng.js?"); + +/***/ }), + +/***/ "./node_modules/uuid/dist/esm-browser/stringify.js": +/*!*********************************************************!*\ + !*** ./node_modules/uuid/dist/esm-browser/stringify.js ***! + \*********************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _validate_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./validate.js */ \"./node_modules/uuid/dist/esm-browser/validate.js\");\n\n/**\n * Convert array of 16 byte values to UUID string format of the form:\n * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\n */\n\nvar byteToHex = [];\n\nfor (var i = 0; i < 256; ++i) {\n byteToHex.push((i + 0x100).toString(16).substr(1));\n}\n\nfunction stringify(arr) {\n var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;\n // Note: Be careful editing this code! It's been tuned for performance\n // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434\n var uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); // Consistency check for valid UUID. If this throws, it's likely due to one\n // of the following:\n // - One or more input array values don't map to a hex octet (leading to\n // \"undefined\" in the uuid)\n // - Invalid input values for the RFC `version` or `variant` fields\n\n if (!(0,_validate_js__WEBPACK_IMPORTED_MODULE_0__.default)(uuid)) {\n throw TypeError('Stringified UUID is invalid');\n }\n\n return uuid;\n}\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (stringify);\n\n//# sourceURL=webpack://Nakama/./node_modules/uuid/dist/esm-browser/stringify.js?"); + +/***/ }), + +/***/ "./node_modules/uuid/dist/esm-browser/v4.js": +/*!**************************************************!*\ + !*** ./node_modules/uuid/dist/esm-browser/v4.js ***! + \**************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _rng_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./rng.js */ \"./node_modules/uuid/dist/esm-browser/rng.js\");\n/* harmony import */ var _stringify_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./stringify.js */ \"./node_modules/uuid/dist/esm-browser/stringify.js\");\n\n\n\nfunction v4(options, buf, offset) {\n options = options || {};\n var rnds = options.random || (options.rng || _rng_js__WEBPACK_IMPORTED_MODULE_0__.default)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`\n\n rnds[6] = rnds[6] & 0x0f | 0x40;\n rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided\n\n if (buf) {\n offset = offset || 0;\n\n for (var i = 0; i < 16; ++i) {\n buf[offset + i] = rnds[i];\n }\n\n return buf;\n }\n\n return (0,_stringify_js__WEBPACK_IMPORTED_MODULE_1__.default)(rnds);\n}\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (v4);\n\n//# sourceURL=webpack://Nakama/./node_modules/uuid/dist/esm-browser/v4.js?"); + +/***/ }), + +/***/ "./node_modules/uuid/dist/esm-browser/validate.js": +/*!********************************************************!*\ + !*** ./node_modules/uuid/dist/esm-browser/validate.js ***! + \********************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _regex_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./regex.js */ \"./node_modules/uuid/dist/esm-browser/regex.js\");\n\n\nfunction validate(uuid) {\n return typeof uuid === 'string' && _regex_js__WEBPACK_IMPORTED_MODULE_0__.default.test(uuid);\n}\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (validate);\n\n//# sourceURL=webpack://Nakama/./node_modules/uuid/dist/esm-browser/validate.js?"); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ if(__webpack_module_cache__[moduleId]) { +/******/ return __webpack_module_cache__[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ (() => { +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = (module) => { +/******/ var getter = module && module.__esModule ? +/******/ () => (module['default']) : +/******/ () => (module); +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ (() => { +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ var __webpack_exports__ = __webpack_require__("./src/index.js"); +/******/ Nakama = __webpack_exports__; +/******/ +/******/ })() +; \ No newline at end of file diff --git a/app/data/ct.libs/nakama/module.json b/app/data/ct.libs/nakama/module.json new file mode 100644 index 000000000..2c50119e7 --- /dev/null +++ b/app/data/ct.libs/nakama/module.json @@ -0,0 +1,52 @@ +{ + "main": { + "name": "ct.nakama", + "tagline": "Implementation of heroiclabs/nakama to enable multiplayer games", + "version": "0.0.2", + "authors": [ + { + "name": "Alexandar Gyurov", + "mail": "hello@alexandar.me" + } + ], + "categories": [ + "networking" + ] + }, + "fields": [ + { + "name": "Client", + "type": "h2" + }, + { + "name": "Host", + "key": "clientHost", + "default": "127.0.0.1", + "type": "code" + }, + { + "name": "Port", + "key": "clientPort", + "default": 7350, + "type": "number" + }, + { + "name": "Secure Mode (useSSL)", + "help": "Whether to use SSL to connect to the client.", + "key": "useSSL", + "default": false, + "type": "checkbox" + }, + { + "name": "Development", + "type": "h2" + }, + { + "name": "Debug Mode", + "help": "Logs useful information to the console", + "key": "debugMode", + "default": true, + "type": "checkbox" + } + ] +} \ No newline at end of file diff --git a/app/data/ct.libs/nakama/package.json b/app/data/ct.libs/nakama/package.json new file mode 100644 index 000000000..f7b5d77be --- /dev/null +++ b/app/data/ct.libs/nakama/package.json @@ -0,0 +1,38 @@ +{ + "name": "ct.nakama", + "version": "0.0.2", + "description": "", + "main": "index.js", + "scripts": { + "zip": "npm-build-zip --source=. --destination=./dist/", + "build": "webpack && npm-build-zip --source=. --destination=./dist/", + "test": "jest --watch --verbose false", + "test-coverage": "jest --coverage --collectCoverageFrom=src/**/*.{js,jsx}" + }, + "jest": { + "resetMocks": false, + "setupFiles": [ + "jest-localstorage-mock" + ] + }, + "author": "", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.12.13", + "@heroiclabs/nakama-js": "^2.1.4", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@babel/core": "^7.12.16", + "@babel/plugin-proposal-class-properties": "^7.12.13", + "@babel/plugin-transform-runtime": "^7.12.15", + "@babel/preset-env": "^7.12.16", + "babel-loader": "^8.2.2", + "eslint": "^7.20.0", + "jest": "^26.6.3", + "jest-localstorage-mock": "^2.4.7", + "npm-build-zip": "^1.0.3", + "webpack": "^5.22.0", + "webpack-cli": "^4.5.0" + } +} diff --git a/app/data/ct.libs/nakama/src/index.js b/app/data/ct.libs/nakama/src/index.js new file mode 100644 index 000000000..9c50c60c9 --- /dev/null +++ b/app/data/ct.libs/nakama/src/index.js @@ -0,0 +1,6 @@ +const NakamaWrapper = require("./nakama").default +const Nakama = new NakamaWrapper("/*%clientHost%*/", "/*%clientPort%*/", [/*%useSSL%*/][0]) + +Nakama.initiate() + +module.exports = Nakama \ No newline at end of file diff --git a/app/data/ct.libs/nakama/src/logger.js b/app/data/ct.libs/nakama/src/logger.js new file mode 100644 index 000000000..bc68126b3 --- /dev/null +++ b/app/data/ct.libs/nakama/src/logger.js @@ -0,0 +1,19 @@ +export default class { + static log(msg, emoji = '', colour = 'black') { + if ([/*%debugMode%*/][0]) { + console.log(`%s %c${msg}`, emoji, `color: ${colour}`); + } + } + + static success(msg) { + if ([/*%debugMode%*/][0]) { + console.log(`%s ${msg}`, 'βœ”οΈ'); + } + } + + static warn(msg) { + if ([/*%debugMode%*/][0]) { + console.log(`%s ${msg}`, '⚠️'); + } + } +} diff --git a/app/data/ct.libs/nakama/src/nakama.js b/app/data/ct.libs/nakama/src/nakama.js new file mode 100644 index 000000000..5b281b746 --- /dev/null +++ b/app/data/ct.libs/nakama/src/nakama.js @@ -0,0 +1,70 @@ +import { Client, Session } from "@heroiclabs/nakama-js"; +import { v4 as uuidv4 } from "uuid"; + +import Logger from "./logger" + +export default class Nakama { + constructor(clientHost, clientPort, useSSL) { + this.useSSL = useSSL; + this.client = new Client("defaultkey", clientHost, clientPort, this.useSSL); + + this.session; + this.socket; + + this.state = {} + } + + initiate = async () => { + await this.checkSessionAndAuthenticate() + await this.establishSocketConnection() + + Logger.log("ct.nakama has loaded!", "✨"); + } + + checkSessionAndAuthenticate = async () => { + // Checks browser for session and authenticates with server + + let nakamaAuthToken = localStorage.getItem("nakamaAuthToken"); + + if (nakamaAuthToken && nakamaAuthToken != "") { + Logger.log("Session Found"); + + let session = Session.restore(nakamaAuthToken); + let currentTimeInSec = new Date() / 1000; + + if (!session.isexpired(currentTimeInSec)) { + // Session valid so restore it + this.session = session + Logger.log("Session Restored"); + } else { + Logger.log("Session Expired"); + await this.createSession() + } + } else { + await this.createSession() + } + + Logger.success("Authenticated Session"); + }; + + establishSocketConnection = async () => { + // Create connection to the server via websockets + const trace = false; // TODO: understand what this does + this.socket = this.client.createSocket(this.useSSL, trace); + await this.socket.connect(this.session); + + Logger.success("Established Websocket Connection"); + }; + + createSession = async () => { + Logger.log("Creating New Session"); + + const newUserId = uuidv4(); + + let nakamaSession = await this.client.authenticateCustom(newUserId, true, newUserId); + localStorage.setItem("nakamaAuthToken", nakamaSession.token); + this.session = nakamaSession + + return this.session + } +} diff --git a/app/data/ct.libs/nakama/src/nakama.test.js b/app/data/ct.libs/nakama/src/nakama.test.js new file mode 100644 index 000000000..d6d2afb12 --- /dev/null +++ b/app/data/ct.libs/nakama/src/nakama.test.js @@ -0,0 +1,69 @@ +import { Session, DefaultSocket } from "@heroiclabs/nakama-js"; + +const NakamaWrapper = require('./nakama').default; + +let Nakama; + +beforeEach(() => { + localStorage.clear(); + Nakama = new NakamaWrapper("127.0.0.1", "7350", false) +}); + +describe('nakama', () => { + describe('initiate', () => { + test('session and socket exist', async () => { + await Nakama.initiate() + + expect(Nakama.session).not.toBeUndefined(); + expect(Nakama.socket).not.toBeUndefined(); + }); + }) + + describe('checkSessionAndAuthenticate', () => { + test('new session gets created if session does not exist', async () => { + expect(Nakama.session).toBeUndefined() + await Nakama.checkSessionAndAuthenticate(); + expect(Nakama.session).toBeInstanceOf(Session); + }); + + test('existing session is still valid so return it', async () => { + expect(Nakama.session).toBeUndefined() + + const existing_session = await Nakama.createSession() + + await Nakama.checkSessionAndAuthenticate(); + + expect(Nakama.session).toEqual(existing_session); + }); + + test('existing session is not valid so create a new session', async () => { + expect(Nakama.session).toBeUndefined() + + const expired_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTQ5NjIzNDEsImV4cCI6MTYxNDk2MjM0MiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSJ9.YXR26DYwENDtf6KjAMsNWE2UNm0PFDGhuMvI_vd3EMs" + localStorage.setItem("nakamaAuthToken", expired_token) + + await Nakama.checkSessionAndAuthenticate(); + + const new_token = localStorage.getItem("nakamaAuthToken") + expect(new_token).not.toEqual(expired_token); + }); + }) + + describe('establishSocketConnection', () => { + test('socket gets created', async () => { + expect(Nakama.socket).toBeUndefined() + await Nakama.checkSessionAndAuthenticate(); + await Nakama.establishSocketConnection(); + expect(Nakama.socket).toBeInstanceOf(DefaultSocket); + }); + }) + + describe('createSession', () => { + test('session gets created and stored in localstorage', async () => { + const session = await Nakama.createSession(); + + expect(localStorage.setItem).toHaveBeenLastCalledWith("nakamaAuthToken", session.token); + expect(Nakama.session).toBeInstanceOf(Session); + }); + }) +}) diff --git a/app/data/ct.libs/nakama/types.d.ts b/app/data/ct.libs/nakama/types.d.ts new file mode 100644 index 000000000..a203f931a --- /dev/null +++ b/app/data/ct.libs/nakama/types.d.ts @@ -0,0 +1,62 @@ +// THESE DO NOT GET UPDATED IF NAKAMA-JS CHANGES +// IN THE FUTURE THEY SHOULD AUTO UPDATE EVERY TIME YOU BUILD +// (FETCH THEM FROM NODE_MODULES AND IMPORT THEM HERE) + +interface Socket { + connect(session: Session, createStatus: boolean): Promise; + disconnect(fireDisconnectEvent: boolean): void; + send(message: ChannelJoin | ChannelLeave | ChannelMessageSend | ChannelMessageUpdate | ChannelMessageRemove | CreateMatch | JoinMatch | LeaveMatch | MatchDataSend | MatchmakerAdd | MatchmakerRemove | Rpc | StatusFollow | StatusUnfollow | StatusUpdate): Promise; + addMatchmaker(query: string, minCount: number, maxCount: number, stringProperties?: Record, numericProperties?: Record): Promise; + createMatch(): Promise; + followUsers(user_ids: string[]): Promise; + joinChat(target: string, type: number, persistence: boolean, hidden: boolean): Promise; + joinMatch(match_id?: string, token?: string, metadata?: {}): Promise; + leaveChat(channel_id: string): Promise; + leaveMatch(matchId: string): Promise; + removeChatMessage(channel_id: string, message_id: string): Promise; + removeMatchmaker(ticket: string): Promise; + rpc(id?: string, payload?: string, http_key?: string): Promise; + sendMatchState(matchId: string, opCode: number, data: any, presence?: Presence[]): Promise; + unfollowUsers(user_ids: string[]): Promise; + updateChatMessage(channel_id: string, message_id: string, content: any): Promise; + updateStatus(status?: string): Promise; + writeChatMessage(channel_id: string, content: any): Promise; + ondisconnect: (evt: Event) => void; + onerror: (evt: Event) => void; + onnotification: (notification: Notification) => void; + onmatchdata: (matchData: MatchData) => void; + onmatchpresence: (matchPresence: MatchPresenceEvent) => void; + onmatchmakermatched: (matchmakerMatched: MatchmakerMatched) => void; + onstatuspresence: (statusPresence: StatusPresenceEvent) => void; + onstreampresence: (streamPresence: StreamPresenceEvent) => void; + onstreamdata: (streamData: StreamData) => void; + onchannelmessage: (channelMessage: ChannelMessage) => void; + onchannelpresence: (channelPresence: ChannelPresenceEvent) => void; +} + +interface Session { + readonly token: string; + readonly created_at: number; + readonly expires_at: number; + readonly username: string; + readonly user_id: string; + readonly vars: object; + isexpired(currenttime: number): boolean; +} + +declare namespace Nakama { + /** + * Nakama socket for communicating with the server + */ + let socket: Socket; + + /** + * Current Nakama session of the player + */ + let session: Session; + + /** + * Current state of the session, defaults to an empty object. Useful for storing players etc. + */ + let state: object; +} diff --git a/app/data/ct.libs/nakama/webpack.config.js b/app/data/ct.libs/nakama/webpack.config.js new file mode 100644 index 000000000..c4af2d2a9 --- /dev/null +++ b/app/data/ct.libs/nakama/webpack.config.js @@ -0,0 +1,28 @@ +const path = require('path'); + +module.exports = { + entry: './src/index.js', + output: { + filename: 'index.js', + path: path.resolve(__dirname), + libraryTarget: "var", + library: "Nakama" + }, + mode: "development", + module: { + rules: [ + { + test: /\.m?js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: [ + ['@babel/preset-env', { targets: "defaults" }] + ] + } + } + } + ] + } +}; \ No newline at end of file From 188176b2f695a8150bdfff6b2f5a7dab14f8bc5b Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Mon, 22 Mar 2021 22:55:50 +1200 Subject: [PATCH 23/32] :bug: Fix Point2D initialization for modded fields --- src/node_requires/exporter/utils.js | 4 ++++ src/riotTags/shared/extensions-editor.tag | 15 +++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/node_requires/exporter/utils.js b/src/node_requires/exporter/utils.js index cb75424d2..f64fb1933 100644 --- a/src/node_requires/exporter/utils.js +++ b/src/node_requires/exporter/utils.js @@ -9,6 +9,10 @@ const {getTypeFromId} = require('./../resources/types'); const {getTextureFromId} = require('./../resources/textures'); const getUnwrappedExtends = function getUnwrappedExtends(exts) { + if (typeof exts !== 'object') { + // This is a primitive value + return exts; + } const out = {}; for (const i in exts) { if (Array.isArray(exts[i])) { diff --git a/src/riotTags/shared/extensions-editor.tag b/src/riotTags/shared/extensions-editor.tag index 78587c6c4..3fad7a73c 100644 --- a/src/riotTags/shared/extensions-editor.tag +++ b/src/riotTags/shared/extensions-editor.tag @@ -129,9 +129,9 @@ extensions-editor input( class="{compact: parent.opts.compact}" type="number" - step="8" + step="{ext.step || 8}" value="{parent.opts.entity[ext.key]? parent.opts.entity[ext.key][0] : ext.default[0]}" - onchange="{wire('this.opts.entity.'+ ext.key + '.0')}" + onchange="{ensurePoint2DAndWire(parent.opts.entity, ext.key, ext.default, 'this.opts.entity.'+ ext.key + '.0')}" ) .spacer label @@ -139,9 +139,9 @@ extensions-editor input( class="{compact: parent.opts.compact}" type="number" - step="8" + step="{ext.step || 8}" value="{parent.opts.entity[ext.key]? parent.opts.entity[ext.key][1] : ext.default[1]}" - onchange="{wire('this.opts.entity.'+ ext.key + '.1')}" + onchange="{ensurePoint2DAndWire(parent.opts.entity, ext.key, ext.default, 'this.opts.entity.'+ ext.key + '.1')}" ) type-input( if="{ext.type === 'type'}" @@ -287,6 +287,13 @@ extensions-editor this.update(); }; + this.ensurePoint2DAndWire = (obj, field, def, way) => e => { + if (!obj[field]) { + obj[field] = [...def]; + } + this.wire(way)(e); + } + this.addRow = e => { const {ext} = e.item; if (!this.opts.entity[ext.key]) { From c6d3a42ef24c2afbad6828befa814955b610fa1b Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Tue, 23 Mar 2021 18:03:33 +1200 Subject: [PATCH 24/32] :sparkles: Optionally make a camera stay inside a specific rectangle with new rooms' settings. --- app/data/ct.release/camera.js | 694 ++++++++++++---------- app/data/ct.release/rooms.js | 6 + app/data/i18n/English.json | 7 +- src/node_requires/exporter/rooms.js | 25 + src/riotTags/app-view.tag | 1 - src/riotTags/rooms/room-editor.tag | 91 ++- src/riotTags/shared/extensions-editor.tag | 2 +- 7 files changed, 482 insertions(+), 344 deletions(-) diff --git a/app/data/ct.release/camera.js b/app/data/ct.release/camera.js index 06b8b90ec..ffd30eef8 100644 --- a/app/data/ct.release/camera.js +++ b/app/data/ct.release/camera.js @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ /** * This class represents a camera that is used by ct.js' cameras. * Usually you won't create new instances of it, but if you need, you can substitute @@ -48,8 +47,10 @@ * relative to the screen's max side (100 is 100% of screen shake). * If set to 0 or less, it, disables the effect. * @property {number} shakePhase The current phase of screen shake oscillation. - * @property {number} shakeDecay The amount of `shake` units substracted in a second. Default is 5. - * @property {number} shakeFrequency The base frequency of the screen shake effect. Default is 50. + * @property {number} shakeDecay The amount of `shake` units substracted in a second. + * Default is 5. + * @property {number} shakeFrequency The base frequency of the screen shake effect. + * Default is 50. * @property {number} shakeX A multiplier applied to the horizontal screen shake effect. * Default is 1. * @property {number} shakeY A multiplier applied to the vertical screen shake effect. @@ -57,357 +58,398 @@ * @property {number} shakeMax The maximum possible value for the `shake` property * to protect players from losing their monitor, in `shake` units. Default is 10. */ -class Camera extends PIXI.DisplayObject { - constructor(x, y, w, h) { - super(); - this.follow = this.rotate = false; - this.followX = this.followY = true; - this.targetX = this.x = x; - this.targetY = this.y = y; - this.z = 500; - this.width = w || 1920; - this.height = h || 1080; - this.shiftX = this.shiftY = this.interpolatedShiftX = this.interpolatedShiftY = 0; - this.borderX = this.borderY = null; - this.drift = 0; - - this.shake = 0; - this.shakeDecay = 5; - this.shakeX = this.shakeY = 1; - this.shakeFrequency = 50; - this.shakePhase = this.shakePhaseX = this.shakePhaseY = 0; - this.shakeMax = 10; - - this.getBounds = this.getBoundingBox; - } - - get scale() { - return this.transform.scale; - } - set scale(value) { - if (typeof value === 'number') { - value = { - x: value, - y: value - }; +const Camera = (function Camera() { + const shakeCamera = function shakeCamera(camera, delta) { + const sec = delta / (PIXI.Ticker.shared.maxFPS || 60); + camera.shake -= sec * camera.shakeDecay; + camera.shake = Math.max(0, camera.shake); + if (camera.shakeMax) { + camera.shake = Math.min(camera.shake, camera.shakeMax); } - this.transform.scale.copyFrom(value); - } + const phaseDelta = sec * camera.shakeFrequency; + camera.shakePhase += phaseDelta; + // no logic in these constants + // They are used to desync fluctuations and remove repetitive circular movements + camera.shakePhaseX += phaseDelta * (1 + Math.sin(camera.shakePhase * 0.1489) * 0.25); + camera.shakePhaseY += phaseDelta * (1 + Math.sin(camera.shakePhase * 0.1734) * 0.25); + }; + const followCamera = function followCamera(camera) { + // eslint-disable-next-line max-len + const bx = camera.borderX === null ? camera.width / 2 : Math.min(camera.borderX, camera.width / 2), + // eslint-disable-next-line max-len + by = camera.borderY === null ? camera.height / 2 : Math.min(camera.borderY, camera.height / 2); + const tl = camera.uiToGameCoord(bx, by), + br = camera.uiToGameCoord(camera.width - bx, camera.height - by); - /** - * Moves the camera to a new position. It will have a smooth transition - * if a `drift` parameter is set. - * @param {number} x New x coordinate - * @param {number} y New y coordinate - * @returns {void} - */ - moveTo(x, y) { - this.targetX = x; - this.targetY = y; - } + if (camera.followX) { + if (camera.follow.x < tl[0] - camera.interpolatedShiftX) { + camera.targetX = camera.follow.x - bx + camera.width / 2; + } else if (camera.follow.x > br[0] - camera.interpolatedShiftX) { + camera.targetX = camera.follow.x + bx - camera.width / 2; + } + } + if (camera.followY) { + if (camera.follow.y < tl[1] - camera.interpolatedShiftY) { + camera.targetY = camera.follow.y - by + camera.height / 2; + } else if (camera.follow.y > br[1] - camera.interpolatedShiftY) { + camera.targetY = camera.follow.y + by - camera.height / 2; + } + } + }; + const restrictInRect = function restrictInRect(camera) { + if (camera.minX !== void 0) { + camera.x = Math.max(camera.minX + camera.width * camera.scale.x * 0.5, camera.x); + camera.targetX = Math.max(camera.minX, camera.targetX); + } + if (camera.maxX !== void 0) { + camera.x = Math.min(camera.maxX - camera.width * camera.scale.x * 0.5, camera.x); + camera.targetX = Math.min(camera.maxX, camera.targetX); + } + if (camera.minY !== void 0) { + camera.y = Math.max(camera.minY + camera.height * camera.scale.y * 0.5, camera.y); + camera.targetY = Math.max(camera.minY, camera.targetY); + } + if (camera.maxY !== void 0) { + camera.y = Math.min(camera.maxY - camera.height * camera.scale.y * 0.5, camera.y); + camera.targetY = Math.min(camera.maxY, camera.targetY); + } + }; + class Camera extends PIXI.DisplayObject { + constructor(x, y, w, h) { + super(); + this.follow = this.rotate = false; + this.followX = this.followY = true; + this.targetX = this.x = x; + this.targetY = this.y = y; + this.z = 500; + this.width = w || 1920; + this.height = h || 1080; + this.shiftX = this.shiftY = this.interpolatedShiftX = this.interpolatedShiftY = 0; + this.borderX = this.borderY = null; + this.drift = 0; - /** - * Moves the camera to a new position. Ignores the `drift` value. - * @param {number} x New x coordinate - * @param {number} y New y coordinate - * @returns {void} - */ - teleportTo(x, y) { - this.targetX = this.x = x; - this.targetY = this.y = y; - this.shakePhase = this.shakePhaseX = this.shakePhaseY = 0; - this.interpolatedShiftX = this.shiftX; - this.interpolatedShiftY = this.shiftY; - } + this.shake = 0; + this.shakeDecay = 5; + this.shakeX = this.shakeY = 1; + this.shakeFrequency = 50; + this.shakePhase = this.shakePhaseX = this.shakePhaseY = 0; + this.shakeMax = 10; - /** - * Updates the position of the camera - * @param {number} delta A delta value between the last two frames. This is usually ct.delta. - * @returns {void} - */ - update(delta) { - if (this.follow && this.follow.kill) { - this.follow = false; + this.getBounds = this.getBoundingBox; } - const sec = delta / (PIXI.Ticker.shared.maxFPS || 60); - this.shake -= sec * this.shakeDecay; - this.shake = Math.max(0, this.shake); - if (this.shakeMax) { - this.shake = Math.min(this.shake, this.shakeMax); + get scale() { + return this.transform.scale; + } + set scale(value) { + if (typeof value === 'number') { + value = { + x: value, + y: value + }; + } + this.transform.scale.copyFrom(value); } - const phaseDelta = sec * this.shakeFrequency; - this.shakePhase += phaseDelta; - // no logic in these constants - // They are used to desync fluctuations and remove repetitive circular movements - this.shakePhaseX += phaseDelta * (1 + Math.sin(this.shakePhase * 0.1489) * 0.25); - this.shakePhaseY += phaseDelta * (1 + Math.sin(this.shakePhase * 0.1734) * 0.25); - // The speed of drift movement - const speed = this.drift ? Math.min(1, (1 - this.drift) * delta) : 1; + /** + * Moves the camera to a new position. It will have a smooth transition + * if a `drift` parameter is set. + * @param {number} x New x coordinate + * @param {number} y New y coordinate + * @returns {void} + */ + moveTo(x, y) { + this.targetX = x; + this.targetY = y; + } - if (this.follow && ('x' in this.follow) && ('y' in this.follow)) { - // eslint-disable-next-line max-len - const bx = this.borderX === null ? this.width / 2 : Math.min(this.borderX, this.width / 2), - // eslint-disable-next-line max-len - by = this.borderY === null ? this.height / 2 : Math.min(this.borderY, this.height / 2); - const tl = this.uiToGameCoord(bx, by), - br = this.uiToGameCoord(this.width - bx, this.height - by); + /** + * Moves the camera to a new position. Ignores the `drift` value. + * @param {number} x New x coordinate + * @param {number} y New y coordinate + * @returns {void} + */ + teleportTo(x, y) { + this.targetX = this.x = x; + this.targetY = this.y = y; + this.shakePhase = this.shakePhaseX = this.shakePhaseY = 0; + this.interpolatedShiftX = this.shiftX; + this.interpolatedShiftY = this.shiftY; + } - if (this.followX) { - if (this.follow.x < tl[0] - this.interpolatedShiftX) { - this.targetX = this.follow.x - bx + this.width / 2; - } else if (this.follow.x > br[0] - this.interpolatedShiftX) { - this.targetX = this.follow.x + bx - this.width / 2; - } + /** + * Updates the position of the camera + * @param {number} delta A delta value between the last two frames. + * This is usually ct.delta. + * @returns {void} + */ + update(delta) { + shakeCamera(this, delta); + // Check if we've been following a copy that is now killed + if (this.follow && this.follow.kill) { + this.follow = false; } - if (this.followY) { - if (this.follow.y < tl[1] - this.interpolatedShiftY) { - this.targetY = this.follow.y - by + this.height / 2; - } else if (this.follow.y > br[1] - this.interpolatedShiftY) { - this.targetY = this.follow.y + by - this.height / 2; - } + // Follow copies around + if (this.follow && ('x' in this.follow) && ('y' in this.follow)) { + followCamera(this); } - } - this.x = this.targetX * speed + this.x * (1 - speed); - this.y = this.targetY * speed + this.y * (1 - speed); - this.interpolatedShiftX = this.shiftX * speed + this.interpolatedShiftX * (1 - speed); - this.interpolatedShiftY = this.shiftY * speed + this.interpolatedShiftY * (1 - speed); + // The speed of drift movement + const speed = this.drift ? Math.min(1, (1 - this.drift) * delta) : 1; + // Perform drift motion + this.x = this.targetX * speed + this.x * (1 - speed); + this.y = this.targetY * speed + this.y * (1 - speed); - this.x = this.x || 0; - this.y = this.y || 0; - } + // Off-center shifts drift, too + this.interpolatedShiftX = this.shiftX * speed + this.interpolatedShiftX * (1 - speed); + this.interpolatedShiftY = this.shiftY * speed + this.interpolatedShiftY * (1 - speed); - /** - * Returns the current camera position plus the screen shake effect. - * @type {number} - */ - get computedX() { - const dx = (Math.sin(this.shakePhaseX) + Math.sin(this.shakePhaseX * 3.1846) * 0.25) / 1.25; - const x = this.x + dx * this.shake * Math.max(this.width, this.height) / 100 * this.shakeX; - return x + this.interpolatedShiftX; - } - /** - * Returns the current camera position plus the screen shake effect. - * @type {number} - */ - get computedY() { - const dy = (Math.sin(this.shakePhaseY) + Math.sin(this.shakePhaseY * 2.8948) * 0.25) / 1.25; - const y = this.y + dy * this.shake * Math.max(this.width, this.height) / 100 * this.shakeY; - return y + this.interpolatedShiftY; - } + restrictInRect(this); - /** - * Returns the position of the left edge where the visible rectangle ends, in game coordinates. - * This can be used for UI positioning in game coordinates. - * This does not count for rotations, though. - * For rotated and/or scaled viewports, see `getTopLeftCorner` - * and `getBottomLeftCorner` methods. - * @returns {number} The location of the left edge. - * @type {number} - * @readonly - */ - get left() { - return this.computedX - (this.width / 2) * this.scale.x; - } - /** - * Returns the position of the top edge where the visible rectangle ends, in game coordinates. - * This can be used for UI positioning in game coordinates. - * This does not count for rotations, though. - * For rotated and/or scaled viewports, see `getTopLeftCorner` and `getTopRightCorner` methods. - * @returns {number} The location of the top edge. - * @type {number} - * @readonly - */ - get top() { - return this.computedY - (this.height / 2) * this.scale.y; - } - /** - * Returns the position of the right edge where the visible rectangle ends, in game coordinates. - * This can be used for UI positioning in game coordinates. - * This does not count for rotations, though. - * For rotated and/or scaled viewports, see `getTopRightCorner` - * and `getBottomRightCorner` methods. - * @returns {number} The location of the right edge. - * @type {number} - * @readonly - */ - get right() { - return this.computedX + (this.width / 2) * this.scale.x; - } - /** - * Returns the position of the bottom edge where the visible rectangle ends, - * in game coordinates. This can be used for UI positioning in game coordinates. - * This does not count for rotations, though. - * For rotated and/or scaled viewports, see `getBottomLeftCorner` - * and `getBottomRightCorner` methods. - * @returns {number} The location of the bottom edge. - * @type {number} - * @readonly - */ - get bottom() { - return this.computedY + (this.height / 2) * this.scale.y; - } + // Recover from possible calculation errors + this.x = this.x || 0; + this.y = this.y || 0; + } - /** - * Translates a point from UI space to game space. - * @param {number} x The x coordinate in UI space. - * @param {number} y The y coordinate in UI space. - * @returns {Array} A pair of new `x` and `y` coordinates. - */ - uiToGameCoord(x, y) { - const modx = (x - this.width / 2) * this.scale.x, - mody = (y - this.height / 2) * this.scale.y; - const result = ct.u.rotate(modx, mody, this.rotation); - return [result[0] + this.computedX, result[1] + this.computedY]; - } + /** + * Returns the current camera position plus the screen shake effect. + * @type {number} + */ + get computedX() { + // eslint-disable-next-line max-len + const dx = (Math.sin(this.shakePhaseX) + Math.sin(this.shakePhaseX * 3.1846) * 0.25) / 1.25; + // eslint-disable-next-line max-len + const x = this.x + dx * this.shake * Math.max(this.width, this.height) / 100 * this.shakeX; + return x + this.interpolatedShiftX; + } + /** + * Returns the current camera position plus the screen shake effect. + * @type {number} + */ + get computedY() { + // eslint-disable-next-line max-len + const dy = (Math.sin(this.shakePhaseY) + Math.sin(this.shakePhaseY * 2.8948) * 0.25) / 1.25; + // eslint-disable-next-line max-len + const y = this.y + dy * this.shake * Math.max(this.width, this.height) / 100 * this.shakeY; + return y + this.interpolatedShiftY; + } - /** - * Translates a point from game space to UI space. - * @param {number} x The x coordinate in game space. - * @param {number} y The y coordinate in game space. - * @returns {Array} A pair of new `x` and `y` coordinates. - */ - gameToUiCoord(x, y) { - const relx = x - this.computedX, - rely = y - this.computedY; - const unrotated = ct.u.rotate(relx, rely, -this.rotation); - return [ - unrotated[0] / this.scale.x + this.width / 2, - unrotated[1] / this.scale.y + this.height / 2 - ]; - } - /** - * Gets the position of the top-left corner of the viewport in game coordinates. - * This is useful for positioning UI elements in game coordinates, - * especially with rotated viewports. - * @returns {Array} A pair of `x` and `y` coordinates. - */ - getTopLeftCorner() { - return this.uiToGameCoord(0, 0); - } + /** + * Returns the position of the left edge where the visible rectangle ends, + * in game coordinates. + * This can be used for UI positioning in game coordinates. + * This does not count for rotations, though. + * For rotated and/or scaled viewports, see `getTopLeftCorner` + * and `getBottomLeftCorner` methods. + * @returns {number} The location of the left edge. + * @type {number} + * @readonly + */ + get left() { + return this.computedX - (this.width / 2) * this.scale.x; + } + /** + * Returns the position of the top edge where the visible rectangle ends, + * in game coordinates. + * This can be used for UI positioning in game coordinates. + * This does not count for rotations, though. + * For rotated and/or scaled viewports, see `getTopLeftCorner` + * and `getTopRightCorner` methods. + * @returns {number} The location of the top edge. + * @type {number} + * @readonly + */ + get top() { + return this.computedY - (this.height / 2) * this.scale.y; + } + /** + * Returns the position of the right edge where the visible rectangle ends, + * in game coordinates. + * This can be used for UI positioning in game coordinates. + * This does not count for rotations, though. + * For rotated and/or scaled viewports, see `getTopRightCorner` + * and `getBottomRightCorner` methods. + * @returns {number} The location of the right edge. + * @type {number} + * @readonly + */ + get right() { + return this.computedX + (this.width / 2) * this.scale.x; + } + /** + * Returns the position of the bottom edge where the visible rectangle ends, + * in game coordinates. This can be used for UI positioning in game coordinates. + * This does not count for rotations, though. + * For rotated and/or scaled viewports, see `getBottomLeftCorner` + * and `getBottomRightCorner` methods. + * @returns {number} The location of the bottom edge. + * @type {number} + * @readonly + */ + get bottom() { + return this.computedY + (this.height / 2) * this.scale.y; + } - /** - * Gets the position of the top-right corner of the viewport in game coordinates. - * This is useful for positioning UI elements in game coordinates, - * especially with rotated viewports. - * @returns {Array} A pair of `x` and `y` coordinates. - */ - getTopRightCorner() { - return this.uiToGameCoord(this.width, 0); - } + /** + * Translates a point from UI space to game space. + * @param {number} x The x coordinate in UI space. + * @param {number} y The y coordinate in UI space. + * @returns {Array} A pair of new `x` and `y` coordinates. + */ + uiToGameCoord(x, y) { + const modx = (x - this.width / 2) * this.scale.x, + mody = (y - this.height / 2) * this.scale.y; + const result = ct.u.rotate(modx, mody, this.rotation); + return [result[0] + this.computedX, result[1] + this.computedY]; + } - /** - * Gets the position of the bottom-left corner of the viewport in game coordinates. - * This is useful for positioning UI elements in game coordinates, - * especially with rotated viewports. - * @returns {Array} A pair of `x` and `y` coordinates. - */ - getBottomLeftCorner() { - return this.uiToGameCoord(0, this.height); - } + /** + * Translates a point from game space to UI space. + * @param {number} x The x coordinate in game space. + * @param {number} y The y coordinate in game space. + * @returns {Array} A pair of new `x` and `y` coordinates. + */ + gameToUiCoord(x, y) { + const relx = x - this.computedX, + rely = y - this.computedY; + const unrotated = ct.u.rotate(relx, rely, -this.rotation); + return [ + unrotated[0] / this.scale.x + this.width / 2, + unrotated[1] / this.scale.y + this.height / 2 + ]; + } + /** + * Gets the position of the top-left corner of the viewport in game coordinates. + * This is useful for positioning UI elements in game coordinates, + * especially with rotated viewports. + * @returns {Array} A pair of `x` and `y` coordinates. + */ + getTopLeftCorner() { + return this.uiToGameCoord(0, 0); + } - /** - * Gets the position of the bottom-right corner of the viewport in game coordinates. - * This is useful for positioning UI elements in game coordinates, - * especially with rotated viewports. - * @returns {Array} A pair of `x` and `y` coordinates. - */ - getBottomRightCorner() { - return this.uiToGameCoord(this.width, this.height); - } + /** + * Gets the position of the top-right corner of the viewport in game coordinates. + * This is useful for positioning UI elements in game coordinates, + * especially with rotated viewports. + * @returns {Array} A pair of `x` and `y` coordinates. + */ + getTopRightCorner() { + return this.uiToGameCoord(this.width, 0); + } - /** - * Returns the bounding box of the camera. - * Useful for rotated viewports when something needs to be reliably covered by a rectangle. - * @returns {PIXI.Rectangle} The bounding box of the camera. - */ - getBoundingBox() { - const bb = new PIXI.Bounds(); - const tl = this.getTopLeftCorner(), - tr = this.getTopRightCorner(), - bl = this.getBottomLeftCorner(), - br = this.getBottomRightCorner(); - bb.addPoint(new PIXI.Point(tl[0], tl[1])); - bb.addPoint(new PIXI.Point(tr[0], tr[1])); - bb.addPoint(new PIXI.Point(bl[0], bl[1])); - bb.addPoint(new PIXI.Point(br[0], br[1])); - return bb.getRectangle(); - } + /** + * Gets the position of the bottom-left corner of the viewport in game coordinates. + * This is useful for positioning UI elements in game coordinates, + * especially with rotated viewports. + * @returns {Array} A pair of `x` and `y` coordinates. + */ + getBottomLeftCorner() { + return this.uiToGameCoord(0, this.height); + } - get rotation() { - return this.transform.rotation / Math.PI * -180; - } - /** - * The rotation angle of a camera. - * @param {number} value New rotation value - * @type {number} - */ - set rotation(value) { - this.transform.rotation = value * Math.PI / -180; - return value; - } + /** + * Gets the position of the bottom-right corner of the viewport in game coordinates. + * This is useful for positioning UI elements in game coordinates, + * especially with rotated viewports. + * @returns {Array} A pair of `x` and `y` coordinates. + */ + getBottomRightCorner() { + return this.uiToGameCoord(this.width, this.height); + } - /** - * Checks whether a given object (or any Pixi's DisplayObject) - * is potentially visible, meaning that its bounding box intersects - * the camera's bounding box. - * @param {PIXI.DisplayObject} copy An object to check for. - * @returns {boolean} `true` if an object is visible, `false` otherwise. - */ - contains(copy) { - // `true` skips transforms recalculations, boosting performance - const bounds = copy.getBounds(true); - return bounds.right > 0 && - bounds.left < this.width * this.scale.x && - bounds.bottom > 0 && - bounds.top < this.width * this.scale.y; - } + /** + * Returns the bounding box of the camera. + * Useful for rotated viewports when something needs to be reliably covered by a rectangle. + * @returns {PIXI.Rectangle} The bounding box of the camera. + */ + getBoundingBox() { + const bb = new PIXI.Bounds(); + const tl = this.getTopLeftCorner(), + tr = this.getTopRightCorner(), + bl = this.getBottomLeftCorner(), + br = this.getBottomRightCorner(); + bb.addPoint(new PIXI.Point(tl[0], tl[1])); + bb.addPoint(new PIXI.Point(tr[0], tr[1])); + bb.addPoint(new PIXI.Point(bl[0], bl[1])); + bb.addPoint(new PIXI.Point(br[0], br[1])); + return bb.getRectangle(); + } - /** - * Realigns all the copies in a room so that they distribute proportionally - * to a new camera size based on their `xstart` and `ystart` coordinates. - * Will throw an error if the given room is not in UI space (if `room.isUi` is not `true`). - * You can skip the realignment for some copies - * if you set their `skipRealign` parameter to `true`. - * @param {Room} room The room which copies will be realigned. - * @returns {void} - */ - realign(room) { - if (!room.isUi) { - throw new Error('[ct.camera] An attempt to realing a room that is not in UI space. The room in question is', room); + get rotation() { + return this.transform.rotation / Math.PI * -180; + } + /** + * The rotation angle of a camera. + * @param {number} value New rotation value + * @type {number} + */ + set rotation(value) { + this.transform.rotation = value * Math.PI / -180; + return value; } - const w = (ct.rooms.templates[room.name].width || 1), - h = (ct.rooms.templates[room.name].height || 1); - for (const copy of room.children) { - if (!('xstart' in copy) || copy.skipRealign) { - continue; + + /** + * Checks whether a given object (or any Pixi's DisplayObject) + * is potentially visible, meaning that its bounding box intersects + * the camera's bounding box. + * @param {PIXI.DisplayObject} copy An object to check for. + * @returns {boolean} `true` if an object is visible, `false` otherwise. + */ + contains(copy) { + // `true` skips transforms recalculations, boosting performance + const bounds = copy.getBounds(true); + return bounds.right > 0 && + bounds.left < this.width * this.scale.x && + bounds.bottom > 0 && + bounds.top < this.width * this.scale.y; + } + + /** + * Realigns all the copies in a room so that they distribute proportionally + * to a new camera size based on their `xstart` and `ystart` coordinates. + * Will throw an error if the given room is not in UI space (if `room.isUi` is not `true`). + * You can skip the realignment for some copies + * if you set their `skipRealign` parameter to `true`. + * @param {Room} room The room which copies will be realigned. + * @returns {void} + */ + realign(room) { + if (!room.isUi) { + throw new Error('[ct.camera] An attempt to realing a room that is not in UI space. The room in question is', room); + } + const w = (ct.rooms.templates[room.name].width || 1), + h = (ct.rooms.templates[room.name].height || 1); + for (const copy of room.children) { + if (!('xstart' in copy) || copy.skipRealign) { + continue; + } + copy.x = copy.xstart / w * this.width; + copy.y = copy.ystart / h * this.height; } - copy.x = copy.xstart / w * this.width; - copy.y = copy.ystart / h * this.height; } - } - /** - * This will align all non-UI layers in the game according to the camera's transforms. - * This is automatically called internally, and you will hardly ever use it. - * @returns {void} - */ - manageStage() { - const px = this.computedX, - py = this.computedY, - sx = 1 / (isNaN(this.scale.x) ? 1 : this.scale.x), - sy = 1 / (isNaN(this.scale.y) ? 1 : this.scale.y); - for (const item of ct.stage.children) { - if (!item.isUi && item.pivot) { - item.x = -this.width / 2; - item.y = -this.height / 2; - item.pivot.x = px; - item.pivot.y = py; - item.scale.x = sx; - item.scale.y = sy; - item.angle = -this.angle; + /** + * This will align all non-UI layers in the game according to the camera's transforms. + * This is automatically called internally, and you will hardly ever use it. + * @returns {void} + */ + manageStage() { + const px = this.computedX, + py = this.computedY, + sx = 1 / (isNaN(this.scale.x) ? 1 : this.scale.x), + sy = 1 / (isNaN(this.scale.y) ? 1 : this.scale.y); + for (const item of ct.stage.children) { + if (!item.isUi && item.pivot) { + item.x = -this.width / 2; + item.y = -this.height / 2; + item.pivot.x = px; + item.pivot.y = py; + item.scale.x = sx; + item.scale.y = sy; + item.angle = -this.angle; + } } } } -} + return Camera; +})(ct); diff --git a/app/data/ct.release/rooms.js b/app/data/ct.release/rooms.js index 04360b1ce..33c214b6c 100644 --- a/app/data/ct.release/rooms.js +++ b/app/data/ct.release/rooms.js @@ -296,6 +296,12 @@ Room.roomId = 0; ct.roomWidth, ct.roomHeight ); + if (template.cameraConstraints) { + ct.camera.minX = template.cameraConstraints.x1; + ct.camera.maxX = template.cameraConstraints.x2; + ct.camera.minY = template.cameraConstraints.y1; + ct.camera.maxY = template.cameraConstraints.y2; + } ct.pixiApp.renderer.resize(template.width, template.height); ct.rooms.current = ct.room = new Room(template); ct.stage.addChild(ct.room); diff --git a/app/data/i18n/English.json b/app/data/i18n/English.json index 8a029e148..6c0a14082 100644 --- a/app/data/i18n/English.json +++ b/app/data/i18n/English.json @@ -538,7 +538,12 @@ "position": "Position", "rotation": "Rotation", "scale": "Scale" - } + }, + "restrictCamera": "Keep camera in a rectangle", + "minimumX": "Min X", + "minimumY": "Min Y", + "maximumX": "Max X", + "maximumY": "Max Y" }, "notepad": { "local": "Project's notepad", diff --git a/src/node_requires/exporter/rooms.js b/src/node_requires/exporter/rooms.js index 504f9bb89..c44b61696 100644 --- a/src/node_requires/exporter/rooms.js +++ b/src/node_requires/exporter/rooms.js @@ -11,6 +11,28 @@ const getStartingRoom = proj => { } return startroom; }; +const getConstraints = r => { + if (r.restrictCamera) { + let x1 = r.restrictMinX || 0, + y1 = r.restrictMinY || 0, + x2 = r.restrictMaxX === void 0 ? r.width : r.restrictMaxX, + y2 = r.restrictMaxX === void 0 ? r.height : r.restrictMaxY; + if (x1 > x2) { + [x1, x2] = [x2, x1]; + } + if (y1 > y2) { + [y1, y2] = [y2, y1]; + } + return { + x1, + y1, + x2, + y2 + }; + } + return false; +}; + const stringifyRooms = proj => { let roomsCode = ''; for (const k in proj.rooms) { @@ -57,6 +79,8 @@ const stringifyRooms = proj => { } } + const constraints = getConstraints(r); + roomsCode += ` ct.rooms.templates['${r.name}'] = { name: '${r.name}', @@ -67,6 +91,7 @@ ct.rooms.templates['${r.name}'] = { bgs: JSON.parse('${JSON.stringify(bgsCopy)}'), tiles: JSON.parse('${JSON.stringify(tileLayers)}'), backgroundColor: '${r.backgroundColor || '#000000'}', + ${constraints ? 'cameraConstraints: ' + JSON.stringify(constraints) + ',' : ''} onStep() { ${proj.rooms[k].onstep} }, diff --git a/src/riotTags/app-view.tag b/src/riotTags/app-view.tag index ef7840011..1a6542811 100644 --- a/src/riotTags/app-view.tag +++ b/src/riotTags/app-view.tag @@ -78,7 +78,6 @@ app-view.flexcol const assetListener = asset => { const [assetType] = asset.split('/'); this.changeTab(assetType)(); - console.log(assetType, asset); this.update(); }; window.orders.on('openAsset', assetListener); diff --git a/src/riotTags/rooms/room-editor.tag b/src/riotTags/rooms/room-editor.tag index 107e8a233..3082ab3b7 100644 --- a/src/riotTags/rooms/room-editor.tag +++ b/src/riotTags/rooms/room-editor.tag @@ -33,22 +33,69 @@ room-editor.panel.view room-backgrounds-editor(show="{tab === 'roombackgrounds'}" room="{room}") room-tile-editor(show="{tab === 'roomtiles'}" room="{room}") .pad.panel(show="{tab === 'properties'}") - .fifty.npt.npb.npl - b {voc.width} - br - input.wide(type="number" value="{room.width}" onchange="{wire('this.room.width')}") - .fifty.npt.npb.npr - b {voc.height} + fieldset + .fifty.npt.npb.npl + b {voc.width} + br + input.wide(type="number" value="{room.width}" onchange="{wireAndRedraw('this.room.width')}") + .fifty.npt.npb.npr + b {voc.height} + br + input.wide(type="number" value="{room.height}" onchange="{wireAndRedraw('this.room.height')}") + .clear + fieldset + label.checkbox + input(type="checkbox" checked="{room.restrictCamera}" onchange="{wireAndRedraw('this.room.restrictCamera')}") + span {voc.restrictCamera} + .aPoint2DInput.compact.wide(if="{room.restrictCamera}") + label + span {voc.minimumX}: + | + input.compact( + step="{room.gridX}" type="number" + oninput="{wireAndRedraw('this.room.restrictMinX')}" + value="{room.restrictMinX === void 0 ? 0 : room.restrictMinX}" + ) + .spacer + label + span.nogrow {voc.minimumY}: + | + input.compact( + step="{room.gridY}" type="number" + oninput="{wireAndRedraw('this.room.restrictMinY')}" + value="{room.restrictMinY === void 0 ? 0 : room.restrictMinY}" + ) + .aPoint2DInput.compact.wide(if="{room.restrictCamera}") + label + span {voc.maximumX}: + | + input.compact( + step="{room.gridX}" type="number" + oninput="{wireAndRedraw('this.room.restrictMaxX')}" + value="{room.restrictMaxX === void 0 ? room.width : room.restrictMaxX}" + ) + .spacer + label + span.nogrow {voc.maximumY}: + | + input.compact( + step="{room.gridY}" type="number" + oninput="{wireAndRedraw('this.room.restrictMaxY')}" + value="{room.restrictMaxY === void 0 ? room.height : room.restrictMaxY}" + ) + + fieldset + b {voc.backgroundColor} br - input.wide(type="number" value="{room.height}" onchange="{wire('this.room.height')}") - .clear - b {voc.backgroundColor} - br - color-input.wide(onchange="{updateRoomBackground}" color="{room.backgroundColor || '#000000'}") - extensions-editor(entity="{room.extends}" type="room" wide="aye" compact="sure") - label.block.checkbox - input(type="checkbox" checked="{room.extends.isUi}" onchange="{wire('this.room.extends.isUi')}") - b {voc.isUi} + color-input.wide(onchange="{updateRoomBackground}" color="{room.backgroundColor || '#000000'}") + + fieldset + extensions-editor(entity="{room.extends}" type="room" wide="aye" compact="sure") + + fieldset + label.block.checkbox + input(type="checkbox" checked="{room.extends.isUi}" onchange="{wire('this.room.extends.isUi')}") + b {voc.isUi} .done.nogrow button.wide#roomviewdone(onclick="{roomSave}") @@ -157,6 +204,10 @@ room-editor.panel.view this.mixin(window.riotWired); this.mixin(window.roomCopyTools); this.mixin(window.roomTileTools); + this.wireAndRedraw = way => e => { + this.wire(way)(e); + this.refreshRoomCanvas(); + }; this.room = this.opts.room; if (!this.room.extends) { @@ -651,6 +702,16 @@ room-editor.panel.view // Outline the starting viewport frame this.drawSelection(-1.5, -1.5, this.room.width + 1.5, this.room.height + 1.5); + + // Outline room's limits + if (this.room.restrictCamera) { + this.drawSelection( + (this.room.restrictMinX || 0) - 1.5, + (this.room.restrictMinY || 0) - 1.5, + (this.room.restrictMaxX === void 0 ? this.room.width : this.room.restrictMaxX) + 1.5, + (this.room.restrictMaxY === void 0 ? this.room.height : this.room.restrictMaxY) + 1.5 + ); + } }; this.drawSelection = (x1, y1, x2, y2) => { diff --git a/src/riotTags/shared/extensions-editor.tag b/src/riotTags/shared/extensions-editor.tag index 3fad7a73c..e536a10a5 100644 --- a/src/riotTags/shared/extensions-editor.tag +++ b/src/riotTags/shared/extensions-editor.tag @@ -292,7 +292,7 @@ extensions-editor obj[field] = [...def]; } this.wire(way)(e); - } + }; this.addRow = e => { const {ext} = e.item; From b8a47f4273fb08e8377c5489ef8e30464bf41845 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sun, 28 Mar 2021 13:44:36 +1200 Subject: [PATCH 25/32] :zap: Modify emitter tandems to use ParticleContainer. Provides better performance, and also fixes issue with un-tintable emitters. --- app/data/ct.release/emitters.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/data/ct.release/emitters.js b/app/data/ct.release/emitters.js index 2d230d369..10fe2bbf2 100644 --- a/app/data/ct.release/emitters.js +++ b/app/data/ct.release/emitters.js @@ -32,7 +32,7 @@ * @property {Copy|DisplayObject} follow A copy to follow * @extends PIXI.Container */ -class EmitterTandem extends PIXI.Container { +class EmitterTandem extends PIXI.ParticleContainer { /** * Creates a new emitter tandem. This method should not be called directly; * better use the methods of `ct.emitters`. @@ -41,7 +41,19 @@ class EmitterTandem extends PIXI.Container { * @constructor */ constructor(tandemData, opts) { - super(); + let batchSize = 0; + for (const emt of tandemData) { + batchSize += Math.min( + emt.settings.maxParticles, + emt.settings.lifetime.max / emt.settings.frequency + ); + } + super(batchSize, { + vertices: true, + position: true, + rotation: true, + tint: true + }); this.emitters = []; this.delayed = []; @@ -84,6 +96,7 @@ class EmitterTandem extends PIXI.Container { this.deltaPosition = opts.position; this.depth = opts.depth; this.paused = this.frozen = false; + this.tint = opts.tint; if (this.isUi) { ct.emitters.uiTandems.push(this); @@ -216,7 +229,7 @@ class EmitterTandem extends PIXI.Container { x: 1, y: 1 }, - tint: 0xffffff, + tint: 0xff0fff, alpha: 1, position: { x: 0, From 6e0fd827643c99c56be6cb8ccfc30e18ef621f57 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sun, 28 Mar 2021 14:40:35 +1200 Subject: [PATCH 26/32] :sparkles: ct.matter module for 2D physics. See the new example! --- app/data/ct.libs/matter/.eslintrc.js | 15 + .../docs/Additional utility functions.md | 5 + .../ct.libs/matter/docs/Advanced usage.md | 12 + .../matter/docs/Creating constraints.md | 19 + .../docs/Listening to collision events.md | 38 + .../matter/docs/Manipulating copies.md | 73 + ...ad this or you will create a black hole.md | 32 + .../ct.libs/matter/includes/matter.min.js | 6 + app/data/ct.libs/matter/index.js | 174 + app/data/ct.libs/matter/injects/afterdraw.js | 9 + .../ct.libs/matter/injects/beforeroomdraw.js | 5 + .../matter/injects/beforeroomoncreate.js | 4 + .../ct.libs/matter/injects/htmlbottom.html | 1 + .../ct.libs/matter/injects/onbeforecreate.js | 3 + app/data/ct.libs/matter/injects/ondestroy.js | 3 + app/data/ct.libs/matter/module.json | 127 + app/data/ct.libs/matter/types.d.ts | 3963 +++++++++++++++++ src/examples/2DPhysics.ict | 1157 +++++ .../i03040255-0c4f-4605-aa7f-8a146a75675f.png | Bin 0 -> 3065 bytes ...5-0c4f-4605-aa7f-8a146a75675f.png_prev.png | Bin 0 -> 1294 bytes ...0c4f-4605-aa7f-8a146a75675f.png_prev@2.png | Bin 0 -> 3407 bytes .../i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png | Bin 0 -> 995 bytes ...3-05bd-47e1-88d2-7dbf7bc3596b.png_prev.png | Bin 0 -> 1733 bytes ...05bd-47e1-88d2-7dbf7bc3596b.png_prev@2.png | Bin 0 -> 4620 bytes .../i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png | Bin 0 -> 2606 bytes ...e-64e5-410f-aa6f-3bcfc9804519.png_prev.png | Bin 0 -> 3025 bytes ...64e5-410f-aa6f-3bcfc9804519.png_prev@2.png | Bin 0 -> 2670 bytes .../i37dfad62-8372-4804-8416-765039343b61.png | Bin 0 -> 8231 bytes ...2-8372-4804-8416-765039343b61.png_prev.png | Bin 0 -> 2432 bytes ...8372-4804-8416-765039343b61.png_prev@2.png | Bin 0 -> 6595 bytes .../i3a4eba8d-8b33-478e-95e5-3833099c1a03.png | Bin 0 -> 647 bytes ...d-8b33-478e-95e5-3833099c1a03.png_prev.png | Bin 0 -> 2063 bytes ...8b33-478e-95e5-3833099c1a03.png_prev@2.png | Bin 0 -> 1233 bytes .../i41f7c3b3-abc8-4940-8919-9114a884d7f2.png | Bin 0 -> 844 bytes ...3-abc8-4940-8919-9114a884d7f2.png_prev.png | Bin 0 -> 1445 bytes ...abc8-4940-8919-9114a884d7f2.png_prev@2.png | Bin 0 -> 1865 bytes .../i56ab43ae-1b27-4b5e-9e49-fbbe74d42597.png | Bin 0 -> 1591 bytes ...e-1b27-4b5e-9e49-fbbe74d42597.png_prev.png | Bin 0 -> 857 bytes ...1b27-4b5e-9e49-fbbe74d42597.png_prev@2.png | Bin 0 -> 2285 bytes .../i6c8a3edf-d5fe-4916-b63c-2201c099d28f.png | Bin 0 -> 2589 bytes ...f-d5fe-4916-b63c-2201c099d28f.png_prev.png | Bin 0 -> 2519 bytes ...d5fe-4916-b63c-2201c099d28f.png_prev@2.png | Bin 0 -> 2675 bytes .../i8a5f385a-d547-4d2d-9641-b1856738c62a.png | Bin 0 -> 3544 bytes ...a-d547-4d2d-9641-b1856738c62a.png_prev.png | Bin 0 -> 4325 bytes ...d547-4d2d-9641-b1856738c62a.png_prev@2.png | Bin 0 -> 3531 bytes .../i961b4fd2-4ed0-4864-890e-dfd79086129b.png | Bin 0 -> 2059 bytes ...2-4ed0-4864-890e-dfd79086129b.png_prev.png | Bin 0 -> 485 bytes ...4ed0-4864-890e-dfd79086129b.png_prev@2.png | Bin 0 -> 1497 bytes .../i9afd54d7-1265-4ba0-b5de-5226b3c7e2fb.png | Bin 0 -> 1044 bytes ...7-1265-4ba0-b5de-5226b3c7e2fb.png_prev.png | Bin 0 -> 551 bytes ...1265-4ba0-b5de-5226b3c7e2fb.png_prev@2.png | Bin 0 -> 1316 bytes .../i9e6feb47-e8bd-4832-a1f4-de20729e9fa2.png | Bin 0 -> 1030 bytes ...7-e8bd-4832-a1f4-de20729e9fa2.png_prev.png | Bin 0 -> 1969 bytes ...e8bd-4832-a1f4-de20729e9fa2.png_prev@2.png | Bin 0 -> 2074 bytes .../iabdeba09-af47-4b39-a276-fa2e59b164aa.png | Bin 0 -> 1754 bytes ...9-af47-4b39-a276-fa2e59b164aa.png_prev.png | Bin 0 -> 3598 bytes ...af47-4b39-a276-fa2e59b164aa.png_prev@2.png | Bin 0 -> 3098 bytes .../ic4d4030a-b640-44be-bded-26a5c030f542.png | Bin 0 -> 1991 bytes ...a-b640-44be-bded-26a5c030f542.png_prev.png | Bin 0 -> 4158 bytes ...b640-44be-bded-26a5c030f542.png_prev@2.png | Bin 0 -> 3701 bytes .../if6a40cf4-e9c0-47ff-afa8-6cdc259ef17e.png | Bin 0 -> 17747 bytes ...4-e9c0-47ff-afa8-6cdc259ef17e.png_prev.png | Bin 0 -> 678 bytes ...e9c0-47ff-afa8-6cdc259ef17e.png_prev@2.png | Bin 0 -> 1704 bytes .../if878c588-3c26-486b-a20f-b23ffab4d579.png | Bin 0 -> 98635 bytes ...8-3c26-486b-a20f-b23ffab4d579.png_prev.png | Bin 0 -> 1599 bytes ...3c26-486b-a20f-b23ffab4d579.png_prev@2.png | Bin 0 -> 4198 bytes .../ifc52b049-910a-4e96-adcf-378553c0818c.png | Bin 0 -> 1486 bytes ...9-910a-4e96-adcf-378553c0818c.png_prev.png | Bin 0 -> 1983 bytes ...910a-4e96-adcf-378553c0818c.png_prev@2.png | Bin 0 -> 5473 bytes src/examples/2DPhysics/img/r2d29a5c56244.png | Bin 0 -> 43284 bytes src/examples/2DPhysics/img/splash.png | Bin 0 -> 43284 bytes .../s3142b705-6de5-4f57-9b70-c9bc3fb4cdad.ogg | Bin 0 -> 8278 bytes .../scbca4dd5-7369-48d5-b3b7-e9b0055e2eda.ogg | Bin 0 -> 5634 bytes .../sccfe7f50-d4d3-4570-95e9-75d5d73bef0a.ogg | Bin 0 -> 12016 bytes 74 files changed, 5646 insertions(+) create mode 100644 app/data/ct.libs/matter/.eslintrc.js create mode 100644 app/data/ct.libs/matter/docs/Additional utility functions.md create mode 100644 app/data/ct.libs/matter/docs/Advanced usage.md create mode 100644 app/data/ct.libs/matter/docs/Creating constraints.md create mode 100644 app/data/ct.libs/matter/docs/Listening to collision events.md create mode 100644 app/data/ct.libs/matter/docs/Manipulating copies.md create mode 100644 app/data/ct.libs/matter/docs/Read this or you will create a black hole.md create mode 100644 app/data/ct.libs/matter/includes/matter.min.js create mode 100644 app/data/ct.libs/matter/index.js create mode 100644 app/data/ct.libs/matter/injects/afterdraw.js create mode 100644 app/data/ct.libs/matter/injects/beforeroomdraw.js create mode 100644 app/data/ct.libs/matter/injects/beforeroomoncreate.js create mode 100644 app/data/ct.libs/matter/injects/htmlbottom.html create mode 100644 app/data/ct.libs/matter/injects/onbeforecreate.js create mode 100644 app/data/ct.libs/matter/injects/ondestroy.js create mode 100644 app/data/ct.libs/matter/module.json create mode 100644 app/data/ct.libs/matter/types.d.ts create mode 100644 src/examples/2DPhysics.ict create mode 100644 src/examples/2DPhysics/img/i03040255-0c4f-4605-aa7f-8a146a75675f.png create mode 100644 src/examples/2DPhysics/img/i03040255-0c4f-4605-aa7f-8a146a75675f.png_prev.png create mode 100644 src/examples/2DPhysics/img/i03040255-0c4f-4605-aa7f-8a146a75675f.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png create mode 100644 src/examples/2DPhysics/img/i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png_prev.png create mode 100644 src/examples/2DPhysics/img/i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png create mode 100644 src/examples/2DPhysics/img/i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png_prev.png create mode 100644 src/examples/2DPhysics/img/i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i37dfad62-8372-4804-8416-765039343b61.png create mode 100644 src/examples/2DPhysics/img/i37dfad62-8372-4804-8416-765039343b61.png_prev.png create mode 100644 src/examples/2DPhysics/img/i37dfad62-8372-4804-8416-765039343b61.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i3a4eba8d-8b33-478e-95e5-3833099c1a03.png create mode 100644 src/examples/2DPhysics/img/i3a4eba8d-8b33-478e-95e5-3833099c1a03.png_prev.png create mode 100644 src/examples/2DPhysics/img/i3a4eba8d-8b33-478e-95e5-3833099c1a03.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i41f7c3b3-abc8-4940-8919-9114a884d7f2.png create mode 100644 src/examples/2DPhysics/img/i41f7c3b3-abc8-4940-8919-9114a884d7f2.png_prev.png create mode 100644 src/examples/2DPhysics/img/i41f7c3b3-abc8-4940-8919-9114a884d7f2.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i56ab43ae-1b27-4b5e-9e49-fbbe74d42597.png create mode 100644 src/examples/2DPhysics/img/i56ab43ae-1b27-4b5e-9e49-fbbe74d42597.png_prev.png create mode 100644 src/examples/2DPhysics/img/i56ab43ae-1b27-4b5e-9e49-fbbe74d42597.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i6c8a3edf-d5fe-4916-b63c-2201c099d28f.png create mode 100644 src/examples/2DPhysics/img/i6c8a3edf-d5fe-4916-b63c-2201c099d28f.png_prev.png create mode 100644 src/examples/2DPhysics/img/i6c8a3edf-d5fe-4916-b63c-2201c099d28f.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i8a5f385a-d547-4d2d-9641-b1856738c62a.png create mode 100644 src/examples/2DPhysics/img/i8a5f385a-d547-4d2d-9641-b1856738c62a.png_prev.png create mode 100644 src/examples/2DPhysics/img/i8a5f385a-d547-4d2d-9641-b1856738c62a.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i961b4fd2-4ed0-4864-890e-dfd79086129b.png create mode 100644 src/examples/2DPhysics/img/i961b4fd2-4ed0-4864-890e-dfd79086129b.png_prev.png create mode 100644 src/examples/2DPhysics/img/i961b4fd2-4ed0-4864-890e-dfd79086129b.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i9afd54d7-1265-4ba0-b5de-5226b3c7e2fb.png create mode 100644 src/examples/2DPhysics/img/i9afd54d7-1265-4ba0-b5de-5226b3c7e2fb.png_prev.png create mode 100644 src/examples/2DPhysics/img/i9afd54d7-1265-4ba0-b5de-5226b3c7e2fb.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/i9e6feb47-e8bd-4832-a1f4-de20729e9fa2.png create mode 100644 src/examples/2DPhysics/img/i9e6feb47-e8bd-4832-a1f4-de20729e9fa2.png_prev.png create mode 100644 src/examples/2DPhysics/img/i9e6feb47-e8bd-4832-a1f4-de20729e9fa2.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/iabdeba09-af47-4b39-a276-fa2e59b164aa.png create mode 100644 src/examples/2DPhysics/img/iabdeba09-af47-4b39-a276-fa2e59b164aa.png_prev.png create mode 100644 src/examples/2DPhysics/img/iabdeba09-af47-4b39-a276-fa2e59b164aa.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/ic4d4030a-b640-44be-bded-26a5c030f542.png create mode 100644 src/examples/2DPhysics/img/ic4d4030a-b640-44be-bded-26a5c030f542.png_prev.png create mode 100644 src/examples/2DPhysics/img/ic4d4030a-b640-44be-bded-26a5c030f542.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/if6a40cf4-e9c0-47ff-afa8-6cdc259ef17e.png create mode 100644 src/examples/2DPhysics/img/if6a40cf4-e9c0-47ff-afa8-6cdc259ef17e.png_prev.png create mode 100644 src/examples/2DPhysics/img/if6a40cf4-e9c0-47ff-afa8-6cdc259ef17e.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/if878c588-3c26-486b-a20f-b23ffab4d579.png create mode 100644 src/examples/2DPhysics/img/if878c588-3c26-486b-a20f-b23ffab4d579.png_prev.png create mode 100644 src/examples/2DPhysics/img/if878c588-3c26-486b-a20f-b23ffab4d579.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/ifc52b049-910a-4e96-adcf-378553c0818c.png create mode 100644 src/examples/2DPhysics/img/ifc52b049-910a-4e96-adcf-378553c0818c.png_prev.png create mode 100644 src/examples/2DPhysics/img/ifc52b049-910a-4e96-adcf-378553c0818c.png_prev@2.png create mode 100644 src/examples/2DPhysics/img/r2d29a5c56244.png create mode 100644 src/examples/2DPhysics/img/splash.png create mode 100644 src/examples/2DPhysics/snd/s3142b705-6de5-4f57-9b70-c9bc3fb4cdad.ogg create mode 100644 src/examples/2DPhysics/snd/scbca4dd5-7369-48d5-b3b7-e9b0055e2eda.ogg create mode 100644 src/examples/2DPhysics/snd/sccfe7f50-d4d3-4570-95e9-75d5d73bef0a.ogg diff --git a/app/data/ct.libs/matter/.eslintrc.js b/app/data/ct.libs/matter/.eslintrc.js new file mode 100644 index 000000000..c4a68dad9 --- /dev/null +++ b/app/data/ct.libs/matter/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + "env": { + "browser": true, + "es6": true + }, + "extends": "eslint:recommended", + "globals": { + "Matter": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2018 + }, + "rules": { + } +}; diff --git a/app/data/ct.libs/matter/docs/Additional utility functions.md b/app/data/ct.libs/matter/docs/Additional utility functions.md new file mode 100644 index 000000000..f36f7bd85 --- /dev/null +++ b/app/data/ct.libs/matter/docs/Additional utility functions.md @@ -0,0 +1,5 @@ +# Additional functions + +## `ct.matter.getImpact(pair)` + +Calculates a good approximation of an impact two colliding copies caused to each other. The impact is calculated based on copies' velocity relative to each other and their mass. It does not account for impulse transferred through several copies. diff --git a/app/data/ct.libs/matter/docs/Advanced usage.md b/app/data/ct.libs/matter/docs/Advanced usage.md new file mode 100644 index 000000000..fe5a9a378 --- /dev/null +++ b/app/data/ct.libs/matter/docs/Advanced usage.md @@ -0,0 +1,12 @@ +# Advanced usage + +This module bundles the whole Matter.js library. Though it is possible to make an entire game not using Matter.js methods, there are tons of advanced APIs that allow creating soft bodies, 2D cars, ragdolls, complex compound shapes, collision filtering, and other things. + +First of all, you can get the physical bodies that are used by copies with `this.matterBody`. Each of these bodies have a backwards reference to ct.js copies at `body.copy`, where `body` is a Matter.js physical body. + +All the Matter.js methods are available under the `Matter` object. For example, you can call `Matter.Body.setMass(this.matterBody, 100)` to dynamically change the mass of a copy. + +## Further reading + +* [Matter.js docs](https://brm.io/matter-js/docs/index.html) +* [Demos](https://brm.io/matter-js/demo/#wreckingBall). You can see their source code by clicking the `{}` icon at the top of each demo. diff --git a/app/data/ct.libs/matter/docs/Creating constraints.md b/app/data/ct.libs/matter/docs/Creating constraints.md new file mode 100644 index 000000000..d572113ea --- /dev/null +++ b/app/data/ct.libs/matter/docs/Creating constraints.md @@ -0,0 +1,19 @@ +# Creating physical constraints + +This module has a couple of methods that simplify the creation of constraints between two copies, or a copy and a position in space. These constraints create a spring, or a rope, that limits a copy's movement. + +## `ct.matter.pin(copy)` + +Pins a copy in place, making it spin around its center of mass but preventing any other movement. + +## `ct.matter.tie(copy, position, stiffness = 0.05, damping = 0.05)` + +Ties a copy to a specific position in space, making it swing around it. + +## `ct.matter.rope(copy, length, stiffness = 0.05, damping = 0.05)` + +Puts a copy on a rope. It is similar to `ct.matter.tie`, but the length of a rope is defined explicitly, and starts from the copy's current position. + +## `ct.matter.tieTogether(copy1, copy2, stiffness, damping)` + +Ties two copies together with a rope. \ No newline at end of file diff --git a/app/data/ct.libs/matter/docs/Listening to collision events.md b/app/data/ct.libs/matter/docs/Listening to collision events.md new file mode 100644 index 000000000..fba3a5acf --- /dev/null +++ b/app/data/ct.libs/matter/docs/Listening to collision events.md @@ -0,0 +1,38 @@ +# Listening to collision events + +You can listen to collision events with `ct.matter.on(eventName, callback)`. The callback is passed an object that has `pairs` property, which has all the collisions that happened in one frame. Long story short, see this example: + +```js +// Room's OnCreate code +// Listen for collisions in the world +ct.matter.on('collisionStart', e => { + // Loop over every collision in a frame + for (var pair of e.pairs) { + // Get how strong the impact was + // We will use it for damage calculation to aliens + var impact = ct.matter.getImpact(pair); + + // Each pair has bodyA and bodyB β€” two objects that has collided. + // This little loop applies checks for both bodies + var bodies = [pair.bodyA, pair.bodyB]; + for (var body of bodies) { + // Here, body.copy is the ct.js copy that owns this physical body. + // Does a body belong to a copy of type "Alien"? + if (body.copy.type === 'Alien') { + // If the impact was too strong, destroy the alien. + if (impact > 75) { + body.copy.kill = true; + } + } + } + } +}); +``` + +> **Warning**: DO NOT write `ct.matter.on` inside copies' code. This will apply large amounts of listeners that do the same thing, which will degrade performance and break your gameplay logic. Instead, write `ct.matter.on` once in room's On Create code. + +There are three collision events you can listen to: + +* `collisionStart` β€” objects has struck each other, or an object entered a sensor's area. +* `collisionActive` β€” called on each frame for all the current collisions. +* `collisionEnd` β€” objects stopped colliding, or an object has left sensor's area. diff --git a/app/data/ct.libs/matter/docs/Manipulating copies.md b/app/data/ct.libs/matter/docs/Manipulating copies.md new file mode 100644 index 000000000..9e00a74b9 --- /dev/null +++ b/app/data/ct.libs/matter/docs/Manipulating copies.md @@ -0,0 +1,73 @@ +# Manipulating copies. + +`ct.matter` requires you to use specific methods to move and transform your copies. Usual parameter-based manipulations won't work. + +## `ct.matter.teleport(copy, x, y);` + +Moves a copy to a new position without changing its velocity. + +| Argument | Type | Description | +| -------- | -------- | --------------------------- | +| `copy` | `Copy` | The ct.js copy to teleport. | +| `x` | `number` | The new X coordinate. | +| `y` | `number` | The new Y coordinate. | + +## `ct.matter.push(copy, forceX, forceY, fromX, fromY);` + +Applies a force onto a copy. The resulting velocity depends on object's mass and friction. +You can optionally define a point from which the force is applied to make the copy spin. + +| Argument | Type | Description | +| ------------------ | -------- | ------------------------------------------------------- | +| `copy` | `Copy` | The copy that should be pushed. | +| `forceX` | `number` | The force applied along the horizontal axis. | +| `forceY` | `number` | The force applied along the vertical axis. | +| `fromX` (optional) | `number` | An optional X coordinate from which to apply the force. | +| `fromY` (optional) | `number` | An optional Y coordinate from which to apply the force. | + +## `ct.matter.spin(copy, speed);` + +Sets copy's angular velocity. + +| Argument | Type | Description | +| -------- | -------- | -------------------------------- | +| `copy` | `Copy` | The copy that should be spinned. | +| `speed` | `number` | New angular velocity | + +## `ct.matter.rotate(copy, angle);` + +Rotates copy to the defined angle. + +| Argument | Type | Description | +| -------- | -------- | -------------------------------- | +| `copy` | `Copy` | The copy that should be rotated. | +| `angle` | `number` | New angle. | + +## `ct.matter.rotateBy(copy, angle);` + +Rotates copy by the defined angle. + +| Argument | Type | Description | +| -------- | -------- | -------------------------------- | +| `copy` | `Copy` | The copy that should be rotated. | +| `angle` | `number` | How much to turn the copy. | + +## `ct.matter.scale(copy, x, y);` + +Scales the copy and its physical object. + +| Argument | Type | Description | +| -------- | -------- | ------------------------------------ | +| `copy` | `Copy` | The copy that should be scaled | +| `x` | `number` | New scale value, by horizontal axis. | +| `y` | `number` | New scale value, by vertical axis. | + +## `ct.matter.launch(copy, hspeed, vspeed);` + +Sets the copy's velocity, instantly and magically. + +| Argument | Type | Description | +| -------- | -------- | --------------------------------------- | +| `copy` | `Copy` | The copy which speed should be changed. | +| `hspeed` | `number` | New horizontal speed. | +| `vspeed` | `number` | New vertical speed. | diff --git a/app/data/ct.libs/matter/docs/Read this or you will create a black hole.md b/app/data/ct.libs/matter/docs/Read this or you will create a black hole.md new file mode 100644 index 000000000..cc70ce4cb --- /dev/null +++ b/app/data/ct.libs/matter/docs/Read this or you will create a black hole.md @@ -0,0 +1,32 @@ +# Read this or you will create a black hole + +Working with copies when `ct.matter` is enabled is drastically different from the regular workflow. + +## `vspeed`, `hspeed`, `scale`, `x`, `y` and many others are read-only + +Changing any of these values will have no effect as Matter.js provides properties a bit more complex than these. Instead of that, use these methods: + +* `ct.matter.teleport(copy, x, y)` to move a copy in an arbitrary place; +* `ct.matter.push(copy, forceX, forceY)` to apply a force so that a copy gradually accelerates where you want; +* `ct.matter.launch(copy, hspeed, vspeed)` to set copy's velocity. Use it for actions like jumpts, or for explosion impacts. +* `ct.matter.spin(copy, speed)` to set angular velocity. +* `ct.matter.rotate(copy, amgle)` to set copy's orientation. +* `ct.matter.scale(copy, x, y)` to resize a copy. + +You can still read `x`, `y`, `hspeed`, `vspeed`, `speed`, `direction`, but these are to be considered read-only. + +See "Manipulating copies" page for more information. + +## Remove `this.move();` + +`this.move();` will conflict with the physics system and cause jittering and other unpleasanties. + +## Texture's axis is the center of mass + +This affects how a copy rotates when in contact with other copies. The axis must be inside the collision shape. + +## Collision logic is defined differently + +Instead of testing for collisions from behalf of a particular copy like it happens in `ct.place`, you will define a rulebook that will listen to all the collisions in a room, and you will filter out these collisions according to your gameplay logic. Due to that, **never** listen to these events in copies, as each your copy will have to loop over all the collision events, hindering performance badly. Instead, set up a listener once in your room's OnCreate code. + +See "Listening to collision events" for more information. diff --git a/app/data/ct.libs/matter/includes/matter.min.js b/app/data/ct.libs/matter/includes/matter.min.js new file mode 100644 index 000000000..996575636 --- /dev/null +++ b/app/data/ct.libs/matter/includes/matter.min.js @@ -0,0 +1,6 @@ +/*! + * matter-js 0.16.1 by @liabru 2021-01-31 + * http://brm.io/matter-js/ + * License MIT + */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(function(){try{return require("poly-decomp")}catch(e){}}()):"function"==typeof define&&define.amd?define("Matter",["poly-decomp"],t):"object"==typeof exports?exports.Matter=t(function(){try{return require("poly-decomp")}catch(e){}}()):e.Matter=t(e.decomp)}(this,(function(e){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=24)}([function(e,t){var n={};e.exports=n,function(){n._nextId=0,n._seed=0,n._nowStartTime=+new Date,n.extend=function(e,t){var i,o;"boolean"==typeof t?(i=2,o=t):(i=1,o=!0);for(var r=i;r0;t--){var i=Math.floor(n.random()*(t+1)),o=e[t];e[t]=e[i],e[i]=o}return e},n.choose=function(e){return e[Math.floor(n.random()*e.length)]},n.isElement=function(e){return"undefined"!=typeof HTMLElement?e instanceof HTMLElement:!!(e&&e.nodeType&&e.nodeName)},n.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)},n.isFunction=function(e){return"function"==typeof e},n.isPlainObject=function(e){return"object"==typeof e&&e.constructor===Object},n.isString=function(e){return"[object String]"===toString.call(e)},n.clamp=function(e,t,n){return en?n:e},n.sign=function(e){return e<0?-1:1},n.now=function(){if("undefined"!=typeof window&&window.performance){if(window.performance.now)return window.performance.now();if(window.performance.webkitNow)return window.performance.webkitNow()}return new Date-n._nowStartTime},n.random=function(t,n){return n=void 0!==n?n:1,(t=void 0!==t?t:0)+e()*(n-t)};var e=function(){return n._seed=(9301*n._seed+49297)%233280,n._seed/233280};n.colorToNumber=function(e){return 3==(e=e.replace("#","")).length&&(e=e.charAt(0)+e.charAt(0)+e.charAt(1)+e.charAt(1)+e.charAt(2)+e.charAt(2)),parseInt(e,16)},n.logLevel=1,n.log=function(){console&&n.logLevel>0&&n.logLevel<=3&&console.log.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.info=function(){console&&n.logLevel>0&&n.logLevel<=2&&console.info.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.warn=function(){console&&n.logLevel>0&&n.logLevel<=3&&console.warn.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.nextId=function(){return n._nextId++},n.indexOf=function(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0;ne.max.x&&(e.max.x=o.x),o.xe.max.y&&(e.max.y=o.y),o.y0?e.max.x+=n.x:e.min.x+=n.x,n.y>0?e.max.y+=n.y:e.min.y+=n.y)},n.contains=function(e,t){return t.x>=e.min.x&&t.x<=e.max.x&&t.y>=e.min.y&&t.y<=e.max.y},n.overlaps=function(e,t){return e.min.x<=t.max.x&&e.max.x>=t.min.x&&e.max.y>=t.min.y&&e.min.y<=t.max.y},n.translate=function(e,t){e.min.x+=t.x,e.max.x+=t.x,e.min.y+=t.y,e.max.y+=t.y},n.shift=function(e,t){var n=e.max.x-e.min.x,i=e.max.y-e.min.y;e.min.x=t.x,e.max.x=t.x+n,e.min.y=t.y,e.max.y=t.y+i}},function(e,t){var n={};e.exports=n,n.create=function(e,t){return{x:e||0,y:t||0}},n.clone=function(e){return{x:e.x,y:e.y}},n.magnitude=function(e){return Math.sqrt(e.x*e.x+e.y*e.y)},n.magnitudeSquared=function(e){return e.x*e.x+e.y*e.y},n.rotate=function(e,t,n){var i=Math.cos(t),o=Math.sin(t);n||(n={});var r=e.x*i-e.y*o;return n.y=e.x*o+e.y*i,n.x=r,n},n.rotateAbout=function(e,t,n,i){var o=Math.cos(t),r=Math.sin(t);i||(i={});var s=n.x+((e.x-n.x)*o-(e.y-n.y)*r);return i.y=n.y+((e.x-n.x)*r+(e.y-n.y)*o),i.x=s,i},n.normalise=function(e){var t=n.magnitude(e);return 0===t?{x:0,y:0}:{x:e.x/t,y:e.y/t}},n.dot=function(e,t){return e.x*t.x+e.y*t.y},n.cross=function(e,t){return e.x*t.y-e.y*t.x},n.cross3=function(e,t,n){return(t.x-e.x)*(n.y-e.y)-(t.y-e.y)*(n.x-e.x)},n.add=function(e,t,n){return n||(n={}),n.x=e.x+t.x,n.y=e.y+t.y,n},n.sub=function(e,t,n){return n||(n={}),n.x=e.x-t.x,n.y=e.y-t.y,n},n.mult=function(e,t){return{x:e.x*t,y:e.y*t}},n.div=function(e,t){return{x:e.x/t,y:e.y/t}},n.perp=function(e,t){return{x:(t=!0===t?-1:1)*-e.y,y:t*e.x}},n.neg=function(e){return{x:-e.x,y:-e.y}},n.angle=function(e,t){return Math.atan2(t.y-e.y,t.x-e.x)},n._temp=[n.create(),n.create(),n.create(),n.create(),n.create(),n.create()]},function(e,t,n){var i={};e.exports=i;var o=n(2),r=n(0);i.create=function(e,t){for(var n=[],i=0;i0)return!1}return!0},i.scale=function(e,t,n,r){if(1===t&&1===n)return e;var s,a;r=r||i.centre(e);for(var l=0;l=0?l-1:e.length-1],d=e[l],u=e[(l+1)%e.length],p=t[l0&&(r|=2),3===r)return!1;return 0!==r||null},i.hull=function(e){var t,n,i=[],r=[];for((e=e.slice(0)).sort((function(e,t){var n=e.x-t.x;return 0!==n?n:e.y-t.y})),n=0;n=2&&o.cross3(r[r.length-2],r[r.length-1],t)<=0;)r.pop();r.push(t)}for(n=e.length-1;n>=0;n-=1){for(t=e[n];i.length>=2&&o.cross3(i[i.length-2],i[i.length-1],t)<=0;)i.pop();i.push(t)}return i.pop(),r.pop(),i.concat(r)}},function(e,t,n){var i={};e.exports=i;var o=n(0);i.on=function(e,t,n){for(var i,o=t.split(" "),r=0;r0){n||(n={}),i=t.split(" ");for(var c=0;c0&&r.rotateAbout(s.position,n,e.position,s.position)}},i.setVelocity=function(e,t){e.positionPrev.x=e.position.x-t.x,e.positionPrev.y=e.position.y-t.y,e.velocity.x=t.x,e.velocity.y=t.y,e.speed=r.magnitude(e.velocity)},i.setAngularVelocity=function(e,t){e.anglePrev=e.angle-t,e.angularVelocity=t,e.angularSpeed=Math.abs(e.angularVelocity)},i.translate=function(e,t){i.setPosition(e,r.add(e.position,t))},i.rotate=function(e,t,n){if(n){var o=Math.cos(t),r=Math.sin(t),s=e.position.x-n.x,a=e.position.y-n.y;i.setPosition(e,{x:n.x+(s*o-a*r),y:n.y+(s*r+a*o)}),i.setAngle(e,e.angle+t)}else i.setAngle(e,e.angle+t)},i.scale=function(e,t,n,r){var s=0,a=0;r=r||e.position;for(var d=0;d0&&(s+=u.area,a+=u.inertia),u.position.x=r.x+(u.position.x-r.x)*t,u.position.y=r.y+(u.position.y-r.y)*n,l.update(u.bounds,u.vertices,e.velocity)}e.parts.length>1&&(e.area=s,e.isStatic||(i.setMass(e,e.density*s),i.setInertia(e,a))),e.circleRadius&&(t===n?e.circleRadius*=t:e.circleRadius=null)},i.update=function(e,t,n,i){var s=Math.pow(t*n*e.timeScale,2),a=1-e.frictionAir*n*e.timeScale,d=e.position.x-e.positionPrev.x,u=e.position.y-e.positionPrev.y;e.velocity.x=d*a*i+e.force.x/e.mass*s,e.velocity.y=u*a*i+e.force.y/e.mass*s,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.position.x+=e.velocity.x,e.position.y+=e.velocity.y,e.angularVelocity=(e.angle-e.anglePrev)*a*i+e.torque/e.inertia*s,e.anglePrev=e.angle,e.angle+=e.angularVelocity,e.speed=r.magnitude(e.velocity),e.angularSpeed=Math.abs(e.angularVelocity);for(var p=0;p0&&(f.position.x+=e.velocity.x,f.position.y+=e.velocity.y),0!==e.angularVelocity&&(o.rotate(f.vertices,e.angularVelocity,e.position),c.rotate(f.axes,e.angularVelocity),p>0&&r.rotateAbout(f.position,e.angularVelocity,e.position,f.position)),l.update(f.bounds,f.vertices,e.velocity)}},i.applyForce=function(e,t,n){e.force.x+=n.x,e.force.y+=n.y;var i=t.x-e.position.x,o=t.y-e.position.y;e.torque+=i*n.y-o*n.x},i._totalProperties=function(e){for(var t={mass:0,area:0,inertia:0,centre:{x:0,y:0}},n=1===e.parts.length?0:1;n0&&r.motion=r.sleepThreshold&&i.set(r,!0)):r.sleepCounter>0&&(r.sleepCounter-=1)}else i.set(r,!1)}},i.afterCollisions=function(e,t){for(var n=t*t*t,o=0;oi._motionWakeThreshold*n&&i.set(c,!1)}}}},i.set=function(e,t){var n=e.isSleeping;t?(e.isSleeping=!0,e.sleepCounter=e.sleepThreshold,e.positionImpulse.x=0,e.positionImpulse.y=0,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.anglePrev=e.angle,e.speed=0,e.angularSpeed=0,e.motion=0,n||o.trigger(e,"sleepStart")):(e.isSleeping=!1,e.sleepCounter=0,n&&o.trigger(e,"sleepEnd"))}},function(e,t,n){var i={};e.exports=i;var o=n(3),r=n(2),s=n(7),a=n(1),l=n(15),c=n(0);i._warming=.4,i._torqueDampen=1,i._minLength=1e-6,i.create=function(e){var t=e;t.bodyA&&!t.pointA&&(t.pointA={x:0,y:0}),t.bodyB&&!t.pointB&&(t.pointB={x:0,y:0});var n=t.bodyA?r.add(t.bodyA.position,t.pointA):t.pointA,i=t.bodyB?r.add(t.bodyB.position,t.pointB):t.pointB,o=r.magnitude(r.sub(n,i));t.length=void 0!==t.length?t.length:o,t.id=t.id||c.nextId(),t.label=t.label||"Constraint",t.type="constraint",t.stiffness=t.stiffness||(t.length>0?1:.7),t.damping=t.damping||0,t.angularStiffness=t.angularStiffness||0,t.angleA=t.bodyA?t.bodyA.angle:t.angleA,t.angleB=t.bodyB?t.bodyB.angle:t.angleB,t.plugin={};var s={visible:!0,lineWidth:2,strokeStyle:"#ffffff",type:"line",anchors:!0};return 0===t.length&&t.stiffness>.1?(s.type="pin",s.anchors=!1):t.stiffness<.9&&(s.type="spring"),t.render=c.extend(s,t.render),t},i.preSolveAll=function(e){for(var t=0;t0&&(u.position.x+=c.x,u.position.y+=c.y),0!==c.angle&&(o.rotate(u.vertices,c.angle,n.position),l.rotate(u.axes,c.angle),d>0&&r.rotateAbout(u.position,c.angle,n.position,u.position)),a.update(u.bounds,u.vertices,n.velocity)}c.angle*=i._warming,c.x*=i._warming,c.y*=i._warming}}},i.pointAWorld=function(e){return{x:(e.bodyA?e.bodyA.position.x:0)+e.pointA.x,y:(e.bodyA?e.bodyA.position.y:0)+e.pointA.y}},i.pointBWorld=function(e){return{x:(e.bodyB?e.bodyB.position.x:0)+e.pointB.x,y:(e.bodyB?e.bodyB.position.y:0)+e.pointB.y}}},function(e,t,n){var i={};e.exports=i;var o=n(18);i.create=function(e,t){var n=e.bodyA,o=e.bodyB,r=e.parentA,s=e.parentB,a={id:i.id(n,o),bodyA:n,bodyB:o,contacts:{},activeContacts:[],separation:0,isActive:!0,confirmedActive:!0,isSensor:n.isSensor||o.isSensor,timeCreated:t,timeUpdated:t,inverseMass:r.inverseMass+s.inverseMass,friction:Math.min(r.friction,s.friction),frictionStatic:Math.max(r.frictionStatic,s.frictionStatic),restitution:Math.max(r.restitution,s.restitution),slop:Math.max(r.slop,s.slop)};return i.update(a,e,t),a},i.update=function(e,t,n){var r=e.contacts,s=t.supports,a=e.activeContacts,l=t.parentA,c=t.parentB;if(e.collision=t,e.inverseMass=l.inverseMass+c.inverseMass,e.friction=Math.min(l.friction,c.friction),e.frictionStatic=Math.max(l.frictionStatic,c.frictionStatic),e.restitution=Math.max(l.restitution,c.restitution),e.slop=Math.max(l.slop,c.slop),a.length=0,t.collided){for(var d=0;dr.max.x&&(r.max.x=c.x),l.yr.max.y&&(r.max.y=c.y))}var u=r.max.x-r.min.x+2*n.x,p=r.max.y-r.min.y+2*n.y,f=e.canvas.height,v=e.canvas.width/f,m=u/p,y=1,g=1;m>v?g=m/v:y=v/m,e.options.hasBounds=!0,e.bounds.min.x=r.min.x,e.bounds.max.x=r.min.x+u*y,e.bounds.min.y=r.min.y,e.bounds.max.y=r.min.y+p*g,i&&(e.bounds.min.x+=.5*u-u*y*.5,e.bounds.max.x+=.5*u-u*y*.5,e.bounds.min.y+=.5*p-p*g*.5,e.bounds.max.y+=.5*p-p*g*.5),e.bounds.min.x-=n.x,e.bounds.max.x-=n.x,e.bounds.min.y-=n.y,e.bounds.max.y-=n.y,e.mouse&&(d.setScale(e.mouse,{x:(e.bounds.max.x-e.bounds.min.x)/e.canvas.width,y:(e.bounds.max.y-e.bounds.min.y)/e.canvas.height}),d.setOffset(e.mouse,e.bounds.min))},i.startViewTransform=function(e){var t=e.bounds.max.x-e.bounds.min.x,n=e.bounds.max.y-e.bounds.min.y,i=t/e.options.width,o=n/e.options.height;e.context.setTransform(e.options.pixelRatio/i,0,0,e.options.pixelRatio/o,0,0),e.context.translate(-e.bounds.min.x,-e.bounds.min.y)},i.endViewTransform=function(e){e.context.setTransform(e.options.pixelRatio,0,0,e.options.pixelRatio,0,0)},i.world=function(e){var t,n=e.engine,o=n.world,u=e.canvas,p=e.context,v=e.options,m=r.allBodies(o),y=r.allConstraints(o),g=v.wireframes?v.wireframeBackground:v.background,x=[],h=[],b={timestamp:n.timing.timestamp};if(a.trigger(e,"beforeRender",b),e.currentBackground!==g&&f(e,g),p.globalCompositeOperation="source-in",p.fillStyle="transparent",p.fillRect(0,0,u.width,u.height),p.globalCompositeOperation="source-over",v.hasBounds){for(t=0;t=500){var d="";s.timing&&(d+="fps: "+Math.round(s.timing.fps)+" "),s.extended&&(s.timing&&(d+="delta: "+s.timing.delta.toFixed(3)+" ",d+="correction: "+s.timing.correction.toFixed(3)+" "),d+="bodies: "+c.length+" ",i.broadphase.controller===l&&(d+="buckets: "+s.buckets+" "),d+="\n",d+="collisions: "+s.collisions+" ",d+="pairs: "+i.pairs.list.length+" ",d+="broad: "+s.broadEff+" ",d+="mid: "+s.midEff+" ",d+="narrow: "+s.narrowEff+" "),e.debugString=d,e.debugTimestamp=i.timing.timestamp}if(e.debugString){n.font="12px Arial",a.wireframes?n.fillStyle="rgba(255,255,255,0.5)":n.fillStyle="rgba(0,0,0,0.5)";for(var u=e.debugString.split("\n"),p=0;p1?1:0;s1?1:0;a1?1:0;r1?1:0;a1?1:0;r1?1:0;r1?1:0;o0)){var d=i.activeContacts[0].vertex.x,u=i.activeContacts[0].vertex.y;2===i.activeContacts.length&&(d=(i.activeContacts[0].vertex.x+i.activeContacts[1].vertex.x)/2,u=(i.activeContacts[0].vertex.y+i.activeContacts[1].vertex.y)/2),o.bodyB===o.supports[0].body||!0===o.bodyA.isStatic?a.moveTo(d-8*o.normal.x,u-8*o.normal.y):a.moveTo(d+8*o.normal.x,u+8*o.normal.y),a.lineTo(d,u)}l.wireframes?a.strokeStyle="rgba(255,165,0,0.7)":a.strokeStyle="orange",a.lineWidth=1,a.stroke()},i.separations=function(e,t,n){var i,o,r,s,a,l=n,c=e.options;for(l.beginPath(),a=0;ad.bounds.max.x||v.bounds.max.yd.bounds.max.y)){var m=i._getRegion(e,v);if(!v.region||m.id!==v.region.id||o){f.broadphaseTests+=1,v.region&&!o||(v.region=m);var y=i._regionUnion(m,v.region);for(s=y.startCol;s<=y.endCol;s++)for(a=y.startRow;a<=y.endRow;a++){l=u[c=i._getBucketId(s,a)];var g=s>=m.startCol&&s<=m.endCol&&a>=m.startRow&&a<=m.endRow,x=s>=v.region.startCol&&s<=v.region.endCol&&a>=v.region.startRow&&a<=v.region.endRow;!g&&x&&x&&l&&i._bucketRemoveBody(e,l,v),(v.region===m||g&&!x||o)&&(l||(l=i._createBucket(u,c)),i._bucketAddBody(e,l,v))}v.region=m,p=!0}}}p&&(e.pairsList=i._createActivePairsList(e))},i.clear=function(e){e.buckets={},e.pairs={},e.pairsList=[]},i._regionUnion=function(e,t){var n=Math.min(e.startCol,t.startCol),o=Math.max(e.endCol,t.endCol),r=Math.min(e.startRow,t.startRow),s=Math.max(e.endRow,t.endRow);return i._createRegion(n,o,r,s)},i._getRegion=function(e,t){var n=t.bounds,o=Math.floor(n.min.x/e.bucketWidth),r=Math.floor(n.max.x/e.bucketWidth),s=Math.floor(n.min.y/e.bucketHeight),a=Math.floor(n.max.y/e.bucketHeight);return i._createRegion(o,r,s,a)},i._createRegion=function(e,t,n,i){return{id:e+","+t+","+n+","+i,startCol:e,endCol:t,startRow:n,endRow:i}},i._getBucketId=function(e,t){return"C"+e+"R"+t},i._createBucket=function(e,t){return e[t]=[]},i._bucketAddBody=function(e,t,n){for(var i=0;i0?i.push(n):delete e.pairs[t[o]];return i}},function(e,t,n){var i={};e.exports=i;var o=n(13),r=n(9),s=n(1);i.collisions=function(e,t){for(var n=[],a=t.pairs.table,l=t.metrics,c=0;c1?1:0;p1?1:0;v0:0!=(e.mask&t.category)&&0!=(t.mask&e.category)}},function(e,t,n){var i={};e.exports=i;var o=n(3),r=n(2);i.collides=function(e,t,n){var s,a,l,c,d=!1;if(n){var u=e.parent,p=t.parent,f=u.speed*u.speed+u.angularSpeed*u.angularSpeed+p.speed*p.speed+p.angularSpeed*p.angularSpeed;d=n&&n.collided&&f<.2,c=n}else c={collided:!1,bodyA:e,bodyB:t};if(n&&d){var v=c.axisBody,m=v===e?t:e,y=[v.axes[n.axisNumber]];if(l=i._overlapAxes(v.vertices,m.vertices,y),c.reused=!0,l.overlap<=0)return c.collided=!1,c}else{if((s=i._overlapAxes(e.vertices,t.vertices,e.axes)).overlap<=0)return c.collided=!1,c;if((a=i._overlapAxes(t.vertices,e.vertices,t.axes)).overlap<=0)return c.collided=!1,c;s.overlapo?o=a:a=0?s.index-1:d.length-1],c.x=o.x-u.x,c.y=o.y-u.y,l=-r.dot(n,c),a=o,o=d[(s.index+1)%d.length],c.x=o.x-u.x,c.y=o.y-u.y,(i=-r.dot(n,c))0&&o.area(B)1?(g=s.create(r.extend({parts:x.slice(0)},d)),s.setPosition(g,{x:e,y:t}),g):x[0]}},function(e,t,n){var i={};e.exports=i;var o=n(0);i._registry={},i.register=function(e){if(i.isPlugin(e)||o.warn("Plugin.register:",i.toString(e),"does not implement all required fields."),e.name in i._registry){var t=i._registry[e.name],n=i.versionParse(e.version).number,r=i.versionParse(t.version).number;n>r?(o.warn("Plugin.register:",i.toString(t),"was upgraded to",i.toString(e)),i._registry[e.name]=e):n-1},i.isFor=function(e,t){var n=e.for&&i.dependencyParse(e.for);return!e.for||t.name===n.name&&i.versionSatisfies(t.version,n.range)},i.use=function(e,t){if(e.uses=(e.uses||[]).concat(t||[]),0!==e.uses.length){for(var n=i.dependencies(e),r=o.topologicalSort(n),s=[],a=0;a0&&o.info(s.join(" "))}else o.warn("Plugin.use:",i.toString(e),"does not specify any dependencies to install.")},i.dependencies=function(e,t){var n=i.dependencyParse(e),r=n.name;if(!(r in(t=t||{}))){e=i.resolve(e)||e,t[r]=o.map(e.uses||[],(function(t){i.isPlugin(t)&&i.register(t);var r=i.dependencyParse(t),s=i.resolve(t);return s&&!i.versionSatisfies(s.version,r.range)?(o.warn("Plugin.dependencies:",i.toString(s),"does not satisfy",i.toString(r),"used by",i.toString(n)+"."),s._warned=!0,e._warned=!0):s||(o.warn("Plugin.dependencies:",i.toString(t),"used by",i.toString(n),"could not be resolved."),e._warned=!0),r.name}));for(var s=0;s=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-]+)?$/;t.test(e)||o.warn("Plugin.versionParse:",e,"is not a valid version or range.");var n=t.exec(e),i=Number(n[4]),r=Number(n[5]),s=Number(n[6]);return{isRange:Boolean(n[1]||n[2]),version:n[3],range:e,operator:n[1]||n[2]||"",major:i,minor:r,patch:s,parts:[i,r,s],prerelease:n[7],number:1e8*i+1e4*r+s}},i.versionSatisfies=function(e,t){t=t||"*";var n=i.versionParse(t),o=i.versionParse(e);if(n.isRange){if("*"===n.operator||"*"===e)return!0;if(">"===n.operator)return o.number>n.number;if(">="===n.operator)return o.number>=n.number;if("~"===n.operator)return o.major===n.major&&o.minor===n.minor&&o.patch>=n.patch;if("^"===n.operator)return n.major>0?o.major===n.major&&o.number>=n.number:n.minor>0?o.minor===n.minor&&o.patch>=n.patch:o.patch===n.patch}return e===t||"*"===e}},function(e,t){var n={};e.exports=n,n.create=function(e){return{id:n.id(e),vertex:e,normalImpulse:0,tangentImpulse:0}},n.id=function(e){return e.body.id+"_"+e.index}},function(e,t,n){var i={};e.exports=i;var o=n(5),r=(n(8),n(0));i.create=function(e){var t=o.create(),n={label:"World",gravity:{x:0,y:1,scale:.001},bounds:{min:{x:-1/0,y:-1/0},max:{x:1/0,y:1/0}}};return r.extend(t,n,e)}},function(e,t,n){var i={};e.exports=i;var o=n(9),r=n(0);i._pairMaxIdleLife=1e3,i.create=function(e){return r.extend({table:{},list:[],collisionStart:[],collisionActive:[],collisionEnd:[]},e)},i.update=function(e,t,n){var i,r,s,a,l=e.list,c=e.table,d=e.collisionStart,u=e.collisionEnd,p=e.collisionActive;for(d.length=0,u.length=0,p.length=0,a=0;ai._pairMaxIdleLife&&c.push(s);for(s=0;sf.friction*f.frictionStatic*O*n&&(V=T,E=s.clamp(f.friction*R*n,-V,V));var F=r.cross(P,g),W=r.cross(C,g),q=b/(m.inverseMass+y.inverseMass+m.inverseInertia*F*F+y.inverseInertia*W*W);if(L*=q,E*=q,I<0&&I*I>i._restingThresh*n)S.normalImpulse=0;else{var j=S.normalImpulse;S.normalImpulse=Math.min(S.normalImpulse+L,0),L=S.normalImpulse-j}if(_*_>i._restingThreshTangent*n)S.tangentImpulse=0;else{var D=S.tangentImpulse;S.tangentImpulse=s.clamp(S.tangentImpulse+E,-V,V),E=S.tangentImpulse-D}o.x=g.x*L+x.x*E,o.y=g.y*L+x.y*E,m.isStatic||m.isSleeping||(m.positionPrev.x+=o.x*m.inverseMass,m.positionPrev.y+=o.y*m.inverseMass,m.anglePrev+=r.cross(P,o)*m.inverseInertia),y.isStatic||y.isSleeping||(y.positionPrev.x-=o.x*y.inverseMass,y.positionPrev.y-=o.y*y.inverseMass,y.anglePrev-=r.cross(C,o)*y.inverseInertia)}}}}},function(e,t,n){var i={};e.exports=i;var o=n(19),r=n(7),s=n(21),a=n(10),l=n(20),c=n(23),d=n(11),u=n(4),p=n(5),f=n(8),v=n(0),m=n(6);i.create=function(e,t){t=(t=v.isElement(e)?t:e)||{},((e=v.isElement(e)?e:null)||t.render)&&v.warn("Engine.create: engine.render is deprecated (see docs)");var n={positionIterations:6,velocityIterations:4,constraintIterations:2,enableSleeping:!1,events:[],plugin:{},timing:{timestamp:0,timeScale:1},broadphase:{controller:d}},i=v.extend(n,t);if(e||i.render){var r={element:e,controller:a};i.render=v.extend(r,i.render)}return i.render&&i.render.controller&&(i.render=i.render.controller.create(i.render)),i.render&&(i.render.engine=i),i.world=t.world||o.create(i.world),i.pairs=l.create(),i.broadphase=i.broadphase.controller.create(i.broadphase),i.metrics=i.metrics||{extended:!1},i.metrics=c.create(i.metrics),i},i.update=function(e,t,n){t=t||1e3/60,n=n||1;var o,a=e.world,d=e.timing,v=e.broadphase,m=[];d.timestamp+=t*d.timeScale;var y={timestamp:d.timestamp};u.trigger(e,"beforeUpdate",y);var g=p.allBodies(a),x=p.allConstraints(a);for(c.reset(e.metrics),e.enableSleeping&&r.update(g,d.timeScale),i._bodiesApplyGravity(g,a.gravity),i._bodiesUpdate(g,t,d.timeScale,n,a.bounds),f.preSolveAll(g),o=0;o0&&u.trigger(e,"collisionStart",{pairs:b.collisionStart}),s.preSolvePosition(b.list),o=0;o0&&u.trigger(e,"collisionActive",{pairs:b.collisionActive}),b.collisionEnd.length>0&&u.trigger(e,"collisionEnd",{pairs:b.collisionEnd}),c.update(e.metrics,e),i._bodiesClearForces(g),u.trigger(e,"afterUpdate",y),e},i.merge=function(e,t){if(v.extend(e,t),t.world){e.world=t.world,i.clear(e);for(var n=p.allBodies(e.world),o=0;o1?1:0;de.deltaMax?e.deltaMax:i)/e.delta,e.delta=i),0!==e.timeScalePrev&&(a*=s.timeScale/e.timeScalePrev),0===s.timeScale&&(a=0),e.timeScalePrev=s.timeScale,e.correction=a,e.frameCounter+=1,n-e.counterTimestamp>=1e3&&(e.fps=e.frameCounter*((n-e.counterTimestamp)/1e3),e.counterTimestamp=n,e.frameCounter=0),o.trigger(e,"tick",l),o.trigger(t,"tick",l),t.world.isModified&&t.render&&t.render.controller&&t.render.controller.clear&&t.render.controller.clear(t.render),o.trigger(e,"beforeUpdate",l),r.update(t,i,a),o.trigger(e,"afterUpdate",l),t.render&&t.render.controller&&(o.trigger(e,"beforeRender",l),o.trigger(t,"beforeRender",l),t.render.controller.world(t.render),o.trigger(e,"afterRender",l),o.trigger(t,"afterRender",l)),o.trigger(e,"afterTick",l),o.trigger(t,"afterTick",l)},i.stop=function(e){t(e.frameRequestId)},i.start=function(e,t){i.run(e,t)}}()},function(e,t,n){var i={};e.exports=i;var o=n(5),r=n(8),s=n(0),a=n(6),l=n(16);i.stack=function(e,t,n,i,r,s,l){for(var c,d=o.create({label:"Stack"}),u=e,p=t,f=0,v=0;vm&&(m=x),a.translate(g,{x:.5*h,y:.5*x}),u=g.bounds.max.x+r,o.addBody(d,g),c=g,f+=1}else u+=r}p+=m+s,u=e}return d},i.chain=function(e,t,n,i,a,l){for(var c=e.bodies,d=1;d0)for(c=0;c0&&(p=f[c-1+(l-1)*t],o.addConstraint(e,r.create(s.extend({bodyA:p,bodyB:u},a)))),i&&cp||s<(c=p-c)||s>n-1-c))return 1===u&&a.translate(d,{x:(s+(n%2==1?1:-1))*f,y:0}),l(e+(d?s*f:0)+s*r,i,s,c,d,u)}))},i.newtonsCradle=function(e,t,n,i,s){for(var a=o.create({label:"Newtons Cradle"}),c=0;c1;if(!p||e!=p.x||t!=p.y){p&&i?(f=p.x,v=p.y):(f=0,v=0);var o={x:f+e,y:v+t};!i&&p||(p=o),m.push(o),g=f+e,x=v+t}},b=function(e){var t=e.pathSegTypeAsLetter.toUpperCase();if("Z"!==t){switch(t){case"M":case"L":case"T":case"C":case"S":case"Q":g=e.x,x=e.y;break;case"H":g=e.x;break;case"V":x=e.y}h(g,x,e.pathSegType)}};for(i._svgPathToAbsolute(e),s=e.getTotalLength(),c=[],n=0;n1?1:0;p diff --git a/app/data/ct.libs/matter/injects/onbeforecreate.js b/app/data/ct.libs/matter/injects/onbeforecreate.js new file mode 100644 index 000000000..67cfe1e84 --- /dev/null +++ b/app/data/ct.libs/matter/injects/onbeforecreate.js @@ -0,0 +1,3 @@ +if (this.matterEnable) { + ct.matter.onCreate(this); +} diff --git a/app/data/ct.libs/matter/injects/ondestroy.js b/app/data/ct.libs/matter/injects/ondestroy.js new file mode 100644 index 000000000..6d50de952 --- /dev/null +++ b/app/data/ct.libs/matter/injects/ondestroy.js @@ -0,0 +1,3 @@ +if (this.matterEnable) { + Matter.World.remove(ct.room.matterWorld, this.matterBody); +} diff --git a/app/data/ct.libs/matter/module.json b/app/data/ct.libs/matter/module.json new file mode 100644 index 000000000..0384ed5ed --- /dev/null +++ b/app/data/ct.libs/matter/module.json @@ -0,0 +1,127 @@ +{ + "main": { + "name": "Matter.js physics library", + "tagline": "Add realtime 2D physics engine to your project.", + "version": "0.0.0", + "authors": [{ + "name": "Cosmo Myzrail Gorynych", + "mail": "admin@nersta.ru" + }], + "categories": [ + "motionPlanning" + ] + }, + + "fields": [{ + "name": "Use static delta time", + "type": "checkbox", + "default": true, + "help": "Uses a constant frame time instead of ct.delta to make world updates more deterministic and stable. Disabling this option may result in an unstable, jiggly physics behavior.", + "key": "matterUseStaticDeltaTime" + }], + + "typeExtends": [{ + "type": "group", + "name": "Physics", + "openedByDefault": false, + "lsKey": "physics.general", + "items": [{ + "name": "Enable", + "type": "checkbox", + "default": false, + "key": "matterEnable" + }, { + "name": "Is static", + "type": "checkbox", + "key": "matterStatic", + "default": false + }, { + "name": "Is a sensor", + "type": "checkbox", + "key": "matterSensor", + "default": false, + "help": "When turned on, the copy won't prevent others' movement but will be able to trigger collisions" + }, { + "name": "Density", + "type": "number", + "key": "matterDensity", + "hint": "How each pixel of this object contributes to its mass. The larger and more dense an object, the more heavy it is.", + "default": 0.001 + }, { + "name": "Bounciness", + "type": "number", + "key": "matterRestitution", + "default": 0.1, + "min": 0, + "max": 1, + "step": 0.1 + }, { + "name": "Friction", + "type": "number", + "key": "matterFriction", + "default": 1 + }, { + "name": "Static friction", + "type": "number", + "key": "matterFrictionStatic", + "default": 0.1 + }, { + "name": "Air friction", + "type": "number", + "key": "matterFrictionAir", + "default": 0.01 + }, { + "name": "Fix pivot", + "type": "point2D", + "key": "matterFixPivot", + "help": "Shifts the texture. Helps with polygonal shapes where the module cannot align center of mass and texture automatically.", + "step": 1, + "default": [0, 0] + }] + }, { + "type": "group", + "name": "Physics constraints", + "openedByDefault": false, + "lsKey": "physics.constraints", + "items": [{ + "name": "Type", + "type": "radio", + "options": [{ + "value": "none", + "name": "None" + }, { + "value": "pinpoint", + "name": "Pin in place", + "help": "Keeps position but allows rotation" + }, { + "value": "rope", + "name": "Rope", + "help": "Creates a rope with a defined length" + }], + "default": "none", + "key": "matterConstraint" + }, { + "name": "Rope length", + "type": "number", + "default": 64, + "key": "matterRopeLength" + }, { + "name": "Rope stiffness", + "type": "number", + "default": 0.05, + "key": "matterRopeStiffness" + }, { + "name": "Rope damping", + "type": "number", + "default": 0.05, + "key": "matterRopeDamping" + }] + }], + "roomExtends": [{ + "name": "Gravity", + "type": "point2D", + "key": "matterGravity", + "step": 0.1, + "default": [0, 9.8] + }] +} diff --git a/app/data/ct.libs/matter/types.d.ts b/app/data/ct.libs/matter/types.d.ts new file mode 100644 index 000000000..14bd69e1a --- /dev/null +++ b/app/data/ct.libs/matter/types.d.ts @@ -0,0 +1,3963 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable no-dupe-class-members */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable id-blacklist */ +/* eslint-disable no-use-before-define */ +/* eslint-disable max-len */ + +declare namespace Matter { + /** + * Installs the given plugins on the `Matter` namespace. + * This is a short-hand for `Plugin.use`, see it for more information. + * Call this function once at the start of your code, with all of the plugins you wish to install as arguments. + * Avoid calling this function multiple times unless you intend to manually control installation order. + * @method use + * @param ...plugin {Function} The plugin(s) to install on `base` (multi-argument). + */ + export function use(...plugins: (Plugin | string)[]): void; + + /** + * The `Matter.Axes` module contains methods for creating and manipulating sets of axes. + * + * @class Axes + */ + export class Axes { + /** + * Creates a new set of axes from the given vertices. + * @method fromVertices + * @param {vertices} vertices + * @return {axes} A new axes from the given vertices + */ + static fromVertices(vertices: Array): Array; + /** + * Rotates a set of axes by the given angle. + * @method rotate + * @param {axes} axes + * @param {number} angle + */ + static rotate(axes: Array, angle: number): void; + } + + interface IChamfer { + radius?: number | Array; + quality?: number; + qualityMin?: number; + qualityMax?: number; + } + + interface IChamferableBodyDefinition extends IBodyDefinition { + chamfer?: IChamfer; + } + + /** + * The `Matter.Bodies` module contains factory methods for creating rigid body models + * with commonly used body configurations (such as rectangles, circles and other polygons). + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class Bodies + */ + export class Bodies { + /** + * Creates a new rigid body model with a circle hull. + * The options parameter is an object that specifies any properties you wish to override the defaults. + * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object. + * @method circle + * @param {number} x + * @param {number} y + * @param {number} radius + * @param {object} [options] + * @param {number} [maxSides] + * @return {body} A new circle body + */ + static circle(x: number, y: number, radius: number, options?: IBodyDefinition, maxSides?: number): Body; + + /** + * Creates a new rigid body model with a regular polygon hull with the given number of sides. + * The options parameter is an object that specifies any properties you wish to override the defaults. + * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object. + * @method polygon + * @param {number} x + * @param {number} y + * @param {number} sides + * @param {number} radius + * @param {object} [options] + * @return {body} A new regular polygon body + */ + static polygon(x: number, y: number, sides: number, radius: number, options?: IChamferableBodyDefinition): Body; + + /** + * Creates a new rigid body model with a rectangle hull. + * The options parameter is an object that specifies any properties you wish to override the defaults. + * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object. + * @method rectangle + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {object} [options] + * @return {body} A new rectangle body + */ + static rectangle(x: number, y: number, width: number, height: number, options?: IChamferableBodyDefinition): Body; + + /** + * Creates a new rigid body model with a trapezoid hull. + * The options parameter is an object that specifies any properties you wish to override the defaults. + * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object. + * @method trapezoid + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {number} slope + * @param {object} [options] + * @return {body} A new trapezoid body + */ + static trapezoid(x: number, y: number, width: number, height: number, slope: number, options?: IChamferableBodyDefinition): Body; + /** + * Creates a body using the supplied vertices (or an array containing multiple sets of vertices). + * If the vertices are convex, they will pass through as supplied. + * Otherwise if the vertices are concave, they will be decomposed if [poly-decomp.js](https://github.com/schteppe/poly-decomp.js) is available. + * Note that this process is not guaranteed to support complex sets of vertices (e.g. those with holes may fail). + * By default the decomposition will discard collinear edges (to improve performance). + * It can also optionally discard any parts that have an area less than `minimumArea`. + * If the vertices can not be decomposed, the result will fall back to using the convex hull. + * The options parameter is an object that specifies any `Matter.Body` properties you wish to override the defaults. + * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object. + * @method fromVertices + * @param {number} x + * @param {number} y + * @param [[vector]] vertexSets + * @param {object} [options] + * @param {bool} [flagInternal=false] + * @param {number} [removeCollinear=0.01] + * @param {number} [minimumArea=10] + * @return {body} + */ + static fromVertices(x: number, y: number, vertexSets: Array>, options?: IBodyDefinition, flagInternal?: boolean, removeCollinear?: number, minimumArea?: number): Body; + } + + export interface IBodyDefinition { + /** + * A `Number` specifying the angle of the body, in radians. + * + * @property angle + * @type number + * @default 0 + */ + angle?: number; + /** + * A `Number` that _measures_ the current angular speed of the body after the last `Body.update`. It is read-only and always positive (it's the magnitude of `body.angularVelocity`). + * + * @readOnly + * @property angularSpeed + * @type number + * @default 0 + */ + angularSpeed?: number; + /** + * A `Number` that _measures_ the current angular velocity of the body after the last `Body.update`. It is read-only. + * If you need to modify a body's angular velocity directly, you should apply a torque or simply change the body's `angle` (as the engine uses position-Verlet integration). + * + * @readOnly + * @property angularVelocity + * @type number + * @default 0 + */ + angularVelocity?: number; + /** + * A `Number` that _measures_ the area of the body's convex hull, calculated at creation by `Body.create`. + * + * @property area + * @type string + * @default + */ + area?: number; + /** + * An array of unique axis vectors (edge normals) used for collision detection. + * These are automatically calculated from the given convex hull (`vertices` array) in `Body.create`. + * They are constantly updated by `Body.update` during the simulation. + * + * @property axes + * @type vector[] + */ + axes?: Array; + /** + * A `Bounds` object that defines the AABB region for the body. + * It is automatically calculated from the given convex hull (`vertices` array) in `Body.create` and constantly updated by `Body.update` during simulation. + * + * @property bounds + * @type bounds + */ + bounds?: Bounds; + /** + * A `Number` that defines the density of the body, that is its mass per unit area. + * If you pass the density via `Body.create` the `mass` property is automatically calculated for you based on the size (area) of the object. + * This is generally preferable to simply setting mass and allows for more intuitive definition of materials (e.g. rock has a higher density than wood). + * + * @property density + * @type number + * @default 0.001 + */ + density?: number; + /** + * A `Vector` that specifies the force to apply in the current step. It is zeroed after every `Body.update`. See also `Body.applyForce`. + * + * @property force + * @type vector + * @default { x: 0, y: 0 } + */ + force?: Vector; + /** + * A `Number` that defines the friction of the body. The value is always positive and is in the range `(0, 1)`. + * A value of `0` means that the body may slide indefinitely. + * A value of `1` means the body may come to a stop almost instantly after a force is applied. + * + * The effects of the value may be non-linear. + * High values may be unstable depending on the body. + * The engine uses a Coulomb friction model including static and kinetic friction. + * Note that collision response is based on _pairs_ of bodies, and that `friction` values are _combined_ with the following formula: + * + * Math.min(bodyA.friction, bodyB.friction) + * + * @property friction + * @type number + * @default 0.1 + */ + friction?: number; + /** + * A `Number` that defines the air friction of the body (air resistance). + * A value of `0` means the body will never slow as it moves through space. + * The higher the value, the faster a body slows when moving through space. + * The effects of the value are non-linear. + * + * @property frictionAir + * @type number + * @default 0.01 + */ + frictionAir?: number; + /** + * An integer `Number` uniquely identifying number generated in `Body.create` by `Common.nextId`. + * + * @property id + * @type number + */ + id?: number; + /** + * A `Number` that defines the moment of inertia (i.e. second moment of area) of the body. + * It is automatically calculated from the given convex hull (`vertices` array) and density in `Body.create`. + * If you modify this value, you must also modify the `body.inverseInertia` property (`1 / inertia`). + * + * @property inertia + * @type number + */ + inertia?: number; + /** + * A `Number` that defines the inverse moment of inertia of the body (`1 / inertia`). + * If you modify this value, you must also modify the `body.inertia` property. + * + * @property inverseInertia + * @type number + */ + inverseInertia?: number; + /** + * A `Number` that defines the inverse mass of the body (`1 / mass`). + * If you modify this value, you must also modify the `body.mass` property. + * + * @property inverseMass + * @type number + */ + inverseMass?: number; + /** + * A flag that indicates whether a body is a sensor. Sensor triggers collision events, but doesn't react with colliding body physically. + * + * @property isSensor + * @type boolean + * @default false + */ + isSensor?: boolean; + /** + * A flag that indicates whether the body is considered sleeping. A sleeping body acts similar to a static body, except it is only temporary and can be awoken. + * If you need to set a body as sleeping, you should use `Sleeping.set` as this requires more than just setting this flag. + * + * @property isSleeping + * @type boolean + * @default false + */ + isSleeping?: boolean; + /** + * A flag that indicates whether a body is considered static. A static body can never change position or angle and is completely fixed. + * If you need to set a body as static after its creation, you should use `Body.setStatic` as this requires more than just setting this flag. + * + * @property isStatic + * @type boolean + * @default false + */ + isStatic?: boolean; + /** + * An arbitrary `String` name to help the user identify and manage bodies. + * + * @property label + * @type string + * @default "Body" + */ + + label?: string; + /** + * A `Number` that defines the mass of the body, although it may be more appropriate to specify the `density` property instead. + * If you modify this value, you must also modify the `body.inverseMass` property (`1 / mass`). + * + * @property mass + * @type number + */ + mass?: number; + /** + * A `Number` that _measures_ the amount of movement a body currently has (a combination of `speed` and `angularSpeed`). It is read-only and always positive. + * It is used and updated by the `Matter.Sleeping` module during simulation to decide if a body has come to rest. + * + * @readOnly + * @property motion + * @type number + * @default 0 + */ + motion?: number; + /** + * An object reserved for storing plugin-specific properties. + * + * @property plugin + * @type {} + */ + plugin?: any; + /** + * A `Vector` that specifies the current world-space position of the body. + * + * @property position + * @type vector + * @default { x: 0, y: */ + position?: Vector; + /** + * An `Object` that defines the rendering properties to be consumed by the module `Matter.Render`. + * + * @property render + * @type object + */ + render?: IBodyRenderOptions; + /** + * A `Number` that defines the restitution (elasticity) of the body. The value is always positive and is in the range `(0, 1)`. + * A value of `0` means collisions may be perfectly inelastic and no bouncing may occur. + * A value of `0.8` means the body may bounce back with approximately 80% of its kinetic energy. + * Note that collision response is based on _pairs_ of bodies, and that `restitution` values are _combined_ with the following formula: + * + * Math.max(bodyA.restitution, bodyB.restitution) + * + * @property restitution + * @type number + * @default 0 + */ + restitution?: number; + /** + * A `Number` that defines the number of updates in which this body must have near-zero velocity before it is set as sleeping by the `Matter.Sleeping` module (if sleeping is enabled by the engine). + * + * @property sleepThreshold + * @type number + * @default 60 + */ + sleepThreshold?: number; + /** + * A `Number` that specifies a tolerance on how far a body is allowed to 'sink' or rotate into other bodies. + * Avoid changing this value unless you understand the purpose of `slop` in physics engines. + * The default should generally suffice, although very large bodies may require larger values for stable stacking. + * + * @property slop + * @type number + * @default 0.05 + */ + slop?: number; + /** + * A `Number` that _measures_ the current speed of the body after the last `Body.update`. It is read-only and always positive (it's the magnitude of `body.velocity`). + * + * @readOnly + * @property speed + * @type number + * @default 0 + */ + speed?: number; + /** + * A `Number` that allows per-body time scaling, e.g. a force-field where bodies inside are in slow-motion, while others are at full speed. + * + * @property timeScale + * @type number + * @default 1 + */ + timeScale?: number; + /** + * A `Number` that specifies the torque (turning force) to apply in the current step. It is zeroed after every `Body.update`. + * + * @property torque + * @type number + * @default 0 + */ + torque?: number; + /** + * A `String` denoting the type of object. + * + * @property type + * @type string + * @default "body" + */ + type?: string; + /** + * A `Vector` that _measures_ the current velocity of the body after the last `Body.update`. It is read-only. + * If you need to modify a body's velocity directly, you should either apply a force or simply change the body's `position` (as the engine uses position-Verlet integration). + * + * @readOnly + * @property velocity + * @type vector + * @default { x: 0, y: 0 } + */ + velocity?: Vector; + /** + * An array of `Vector` objects that specify the convex hull of the rigid body. + * These should be provided about the origin `(0, 0)`. E.g. + * + * [{ x: 0, y: 0 }, { x: 25, y: 50 }, { x: 50, y: 0 }] + * + * When passed via `Body.create`, the vertices are translated relative to `body.position` (i.e. world-space, and constantly updated by `Body.update` during simulation). + * The `Vector` objects are also augmented with additional properties required for efficient collision detection. + * + * Other properties such as `inertia` and `bounds` are automatically calculated from the passed vertices (unless provided via `options`). + * Concave hulls are not currently supported. The module `Matter.Vertices` contains useful methods for working with vertices. + * + * @property vertices + * @type vector[] + */ + vertices?: Array; + /** + * An array of bodies that make up this body. + * The first body in the array must always be a self reference to the current body instance. + * All bodies in the `parts` array together form a single rigid compound body. + * Parts are allowed to overlap, have gaps or holes or even form concave bodies. + * Parts themselves should never be added to a `World`, only the parent body should be. + * Use `Body.setParts` when setting parts to ensure correct updates of all properties. + * + * @property parts + * @type body[] + */ + parts?: Array; + /** + * A self reference if the body is _not_ a part of another body. + * Otherwise this is a reference to the body that this is a part of. + * See `body.parts`. + * + * @property parent + * @type body + */ + parent?: Body; + /** + * A `Number` that defines the static friction of the body (in the Coulomb friction model). + * A value of `0` means the body will never 'stick' when it is nearly stationary and only dynamic `friction` is used. + * The higher the value (e.g. `10`), the more force it will take to initially get the body moving when nearly stationary. + * This value is multiplied with the `friction` property to make it easier to change `friction` and maintain an appropriate amount of static friction. + * + * @property frictionStatic + * @type number + * @default 0.5 + */ + frictionStatic?: number; + /** + * An `Object` that specifies the collision filtering properties of this body. + * + * Collisions between two bodies will obey the following rules: + * - If the two bodies have the same non-zero value of `collisionFilter.group`, + * they will always collide if the value is positive, and they will never collide + * if the value is negative. + * - If the two bodies have different values of `collisionFilter.group` or if one + * (or both) of the bodies has a value of 0, then the category/mask rules apply as follows: + * + * Each body belongs to a collision category, given by `collisionFilter.category`. This + * value is used as a bit field and the category should have only one bit set, meaning that + * the value of this property is a power of two in the range [1, 2^31]. Thus, there are 32 + * different collision categories available. + * + * Each body also defines a collision bitmask, given by `collisionFilter.mask` which specifies + * the categories it collides with (the value is the bitwise AND value of all these categories). + * + * Using the category/mask rules, two bodies `A` and `B` collide if each includes the other's + * category in its mask, i.e. `(categoryA & maskB) !== 0` and `(categoryB & maskA) !== 0` + * are both true. + * + * @property collisionFilter + * @type object + */ + collisionFilter?: ICollisionFilter; + + } + + export interface IBodyRenderOptions { + + /** + * A flag that indicates if the body should be rendered. + * + * @property render.visible + * @type boolean + * @default true + */ + visible?: boolean; + + /** + * An `Object` that defines the sprite properties to use when rendering, if any. + * + * @property render.sprite + * @type object + */ + sprite?: IBodyRenderOptionsSprite; + + /** + * A String that defines the fill style to use when rendering the body (if a sprite is not defined). It is the same as when using a canvas, so it accepts CSS style property values. + Default: a random colour + */ + fillStyle?: string; + + /** + * A Number that defines the line width to use when rendering the body outline (if a sprite is not defined). A value of 0 means no outline will be rendered. + Default: 1.5 + */ + lineWidth?: number; + + /** + * A String that defines the stroke style to use when rendering the body outline (if a sprite is not defined). It is the same as when using a canvas, so it accepts CSS style property values. + Default: a random colour + */ + strokeStyle?: string; + + + /* + * Sets the opacity. 1.0 is fully opaque. 0.0 is fully translucent + */ + opacity?: number; + } + + export interface IBodyRenderOptionsSprite { + /** + * An `String` that defines the path to the image to use as the sprite texture, if any. + * + * @property render.sprite.texture + * @type string + */ + texture: string; + + /** + * A `Number` that defines the scaling in the x-axis for the sprite, if any. + * + * @property render.sprite.xScale + * @type number + * @default 1 + */ + xScale: number; + + /** + * A `Number` that defines the scaling in the y-axis for the sprite, if any. + * + * @property render.sprite.yScale + * @type number + * @default 1 + */ + yScale: number; + } + + /** + * The `Matter.Body` module contains methods for creating and manipulating body models. + * A `Matter.Body` is a rigid body that can be simulated by a `Matter.Engine`. + * Factories for commonly used body configurations (such as rectangles, circles and other polygons) can be found in the module `Matter.Bodies`. + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + + * @class Body + */ + export class Body { + /** + * Applies a force to a body from a given world-space position, including resulting torque. + * @method applyForce + * @param {body} body + * @param {vector} position + * @param {vector} force + */ + static applyForce(body: Body, position: Vector, force: Vector): void; + + /** + * Creates a new rigid body model. The options parameter is an object that specifies any properties you wish to override the defaults. + * All properties have default values, and many are pre-calculated automatically based on other properties. + * See the properties section below for detailed information on what you can pass via the `options` object. + * @method create + * @param {} options + * @return {body} body + */ + static create(options: IBodyDefinition): Body; + /** + * Rotates a body by a given angle relative to its current angle, without imparting any angular velocity. + * @method rotate + * @param {body} body + * @param {number} rotation + */ + static rotate(body: Body, rotation: number): void; + /** + * Returns the next unique group index for which bodies will collide. + * If `isNonColliding` is `true`, returns the next unique group index for which bodies will _not_ collide. + * See `body.collisionFilter` for more information. + * @method nextGroup + * @param {bool} [isNonColliding=false] + * @return {Number} Unique group index + */ + static nextGroup(isNonColliding: boolean): number; + /** + * Returns the next unique category bitfield (starting after the initial default category `0x0001`). + * There are 32 available. See `body.collisionFilter` for more information. + * @method nextCategory + * @return {Number} Unique category bitfield + */ + static nextCategory(): number; + /** + * Given a property and a value (or map of), sets the property(s) on the body, using the appropriate setter functions if they exist. + * Prefer to use the actual setter functions in performance critical situations. + * @method set + * @param {body} body + * @param {} settings A property name (or map of properties and values) to set on the body. + * @param {} value The value to set if `settings` is a single property name. + */ + static set(body: Body, settings: any, value?: any): void; + /** + * Sets the mass of the body. Inverse mass and density are automatically updated to reflect the change. + * @method setMass + * @param {body} body + * @param {number} mass + */ + static setMass(body: Body, mass: number): void; + /** + * Sets the density of the body. Mass is automatically updated to reflect the change. + * @method setDensity + * @param {body} body + * @param {number} density + */ + static setDensity(body: Body, density: number): void; + /** + * Sets the moment of inertia (i.e. second moment of area) of the body of the body. + * Inverse inertia is automatically updated to reflect the change. Mass is not changed. + * @method setInertia + * @param {body} body + * @param {number} inertia + */ + static setInertia(body: Body, interna: number): void; + /** + * Sets the body's vertices and updates body properties accordingly, including inertia, area and mass (with respect to `body.density`). + * Vertices will be automatically transformed to be orientated around their centre of mass as the origin. + * They are then automatically translated to world space based on `body.position`. + * + * The `vertices` argument should be passed as an array of `Matter.Vector` points (or a `Matter.Vertices` array). + * Vertices must form a convex hull, concave hulls are not supported. + * + * @method setVertices + * @param {body} body + * @param {vector[]} vertices + */ + static setVertices(body: Body, vertices: Array): void; + /** + * Sets the parts of the `body` and updates mass, inertia and centroid. + * Each part will have its parent set to `body`. + * By default the convex hull will be automatically computed and set on `body`, unless `autoHull` is set to `false.` + * Note that this method will ensure that the first part in `body.parts` will always be the `body`. + * @method setParts + * @param {body} body + * @param [body] parts + * @param {bool} [autoHull=true] + */ + static setParts(body: Body, parts: Body[], autoHull?: boolean): void; + /** + * Set the centre of mass of the body. + * The `centre` is a vector in world-space unless `relative` is set, in which case it is a translation. + * The centre of mass is the point the body rotates about and can be used to simulate non-uniform density. + * This is equal to moving `body.position` but not the `body.vertices`. + * Invalid if the `centre` falls outside the body's convex hull. + * @method setCentre + * @param body + * @param centre + * @param relative + */ + static setCentre(body: Body, centre: Vector, relative?: boolean): void; + /** + * Sets the position of the body instantly. Velocity, angle, force etc. are unchanged. + * @method setPosition + * @param {body} body + * @param {vector} position + */ + static setPosition(body: Body, position: Vector): void; + /** + * Sets the angle of the body instantly. Angular velocity, position, force etc. are unchanged. + * @method setAngle + * @param {body} body + * @param {number} angle + */ + static setAngle(body: Body, angle: number): void; + /** + * Sets the linear velocity of the body instantly. Position, angle, force etc. are unchanged. See also `Body.applyForce`. + * @method setVelocity + * @param {body} body + * @param {vector} velocity + */ + static setVelocity(body: Body, velocity: Vector): void; + /** + * Sets the angular velocity of the body instantly. Position, angle, force etc. are unchanged. See also `Body.applyForce`. + * @method setAngularVelocity + * @param {body} body + * @param {number} velocity + */ + static setAngularVelocity(body: Body, velocity: number): void; + + + /** + * Sets the body as static, including isStatic flag and setting mass and inertia to Infinity. + * @method setStatic + * @param {body} body + * @param {bool} isStatic + */ + static setStatic(body: Body, isStatic: boolean): void; + + /** + * Scales the body, including updating physical properties (mass, area, axes, inertia), from a world-space point (default is body centre). + * @method scale + * @param {body} body + * @param {number} scaleX + * @param {number} scaleY + * @param {vector} [point] + */ + static scale(body: Body, scaleX: number, scaleY: number, point?: Vector): void; + + /** + * Moves a body by a given vector relative to its current position, without imparting any velocity. + * @method translate + * @param {body} body + * @param {vector} translation + */ + static translate(body: Body, translation: Vector): void; + + /** + * Performs a simulation step for the given `body`, including updating position and angle using Verlet integration. + * @method update + * @param {body} body + * @param {number} deltaTime + * @param {number} timeScale + * @param {number} correction + */ + static update(body: Body, deltaTime: number, timeScale: number, correction: number): void; + + /** + * A `Number` specifying the angle of the body, in radians. + * + * @property angle + * @type number + * @default 0 + */ + angle: number; + /** + * A `Number` that _measures_ the current angular speed of the body after the last `Body.update`. It is read-only and always positive (it's the magnitude of `body.angularVelocity`). + * + * @readOnly + * @property angularSpeed + * @type number + * @default 0 + */ + angularSpeed: number; + /** + * A `Number` that _measures_ the current angular velocity of the body after the last `Body.update`. It is read-only. + * If you need to modify a body's angular velocity directly, you should apply a torque or simply change the body's `angle` (as the engine uses position-Verlet integration). + * + * @readOnly + * @property angularVelocity + * @type number + * @default 0 + */ + angularVelocity: number; + /** + * A `Number` that _measures_ the area of the body's convex hull, calculated at creation by `Body.create`. + * + * @property area + * @type string + * @default + */ + area: number; + /** + * An array of unique axis vectors (edge normals) used for collision detection. + * These are automatically calculated from the given convex hull (`vertices` array) in `Body.create`. + * They are constantly updated by `Body.update` during the simulation. + * + * @property axes + * @type vector[] + */ + axes: Array; + /** + * A `Bounds` object that defines the AABB region for the body. + * It is automatically calculated from the given convex hull (`vertices` array) in `Body.create` and constantly updated by `Body.update` during simulation. + * + * @property bounds + * @type bounds + */ + bounds: Bounds; + /** + * A `Number` that is set to the radius of the object if the body was constructed using `Bodies.circle`. + * May have a value of `null` if the body is no longer a circle (i.e. was scaled with a scaleX != scaleY). + * + * @property circleRadius + * @default 0 + */ + circleRadius?: number; + /** + * A `Number` that defines the density of the body, that is its mass per unit area. + * If you pass the density via `Body.create` the `mass` property is automatically calculated for you based on the size (area) of the object. + * This is generally preferable to simply setting mass and allows for more intuitive definition of materials (e.g. rock has a higher density than wood). + * + * @property density + * @type number + * @default 0.001 + */ + density: number; + /** + * A `Vector` that specifies the force to apply in the current step. It is zeroed after every `Body.update`. See also `Body.applyForce`. + * + * @property force + * @type vector + * @default { x: 0, y: 0 } + */ + force: Vector; + /** + * A `Number` that defines the friction of the body. The value is always positive and is in the range `(0, 1)`. + * A value of `0` means that the body may slide indefinitely. + * A value of `1` means the body may come to a stop almost instantly after a force is applied. + * + * The effects of the value may be non-linear. + * High values may be unstable depending on the body. + * The engine uses a Coulomb friction model including static and kinetic friction. + * Note that collision response is based on _pairs_ of bodies, and that `friction` values are _combined_ with the following formula: + * + * Math.min(bodyA.friction, bodyB.friction) + * + * @property friction + * @type number + * @default 0.1 + */ + friction: number; + /** + * A `Number` that defines the air friction of the body (air resistance). + * A value of `0` means the body will never slow as it moves through space. + * The higher the value, the faster a body slows when moving through space. + * The effects of the value are non-linear. + * + * @property frictionAir + * @type number + * @default 0.01 + */ + frictionAir: number; + /** + * An integer `Number` uniquely identifying number generated in `Body.create` by `Common.nextId`. + * + * @property id + * @type number + */ + id: number; + /** + * A `Number` that defines the moment of inertia (i.e. second moment of area) of the body. + * It is automatically calculated from the given convex hull (`vertices` array) and density in `Body.create`. + * If you modify this value, you must also modify the `body.inverseInertia` property (`1 / inertia`). + * + * @property inertia + * @type number + */ + inertia: number; + /** + * A `Number` that defines the inverse moment of inertia of the body (`1 / inertia`). + * If you modify this value, you must also modify the `body.inertia` property. + * + * @property inverseInertia + * @type number + */ + inverseInertia: number; + /** + * A `Number` that defines the inverse mass of the body (`1 / mass`). + * If you modify this value, you must also modify the `body.mass` property. + * + * @property inverseMass + * @type number + */ + inverseMass: number; + /** + * A flag that indicates whether the body is considered sleeping. A sleeping body acts similar to a static body, except it is only temporary and can be awoken. + * If you need to set a body as sleeping, you should use `Sleeping.set` as this requires more than just setting this flag. + * + * @property isSleeping + * @type boolean + * @default false + */ + isSleeping: boolean; + /** + * A flag that indicates whether a body is considered static. A static body can never change position or angle and is completely fixed. + * If you need to set a body as static after its creation, you should use `Body.setStatic` as this requires more than just setting this flag. + * + * @property isStatic + * @type boolean + * @default false + */ + isStatic: boolean; + /** + * A flag that indicates whether a body is a sensor. Sensor triggers collision events, but doesn't react with colliding body physically. + * + * @property isSensor + * @type boolean + * @default false + */ + isSensor: boolean; + /** + * An arbitrary `String` name to help the user identify and manage bodies. + * + * @property label + * @type string + * @default "Body" + */ + + label: string; + /** + * A `Number` that defines the mass of the body, although it may be more appropriate to specify the `density` property instead. + * If you modify this value, you must also modify the `body.inverseMass` property (`1 / mass`). + * + * @property mass + * @type number + */ + mass: number; + /** + * A `Number` that _measures_ the amount of movement a body currently has (a combination of `speed` and `angularSpeed`). It is read-only and always positive. + * It is used and updated by the `Matter.Sleeping` module during simulation to decide if a body has come to rest. + * + * @readOnly + * @property motion + * @type number + * @default 0 + */ + motion: number; + /** + * A `Vector` that specifies the current world-space position of the body. + * + * @property position + * @type vector + * @default { x: 0, y: */ + position: Vector; + /** + * An `Object` that defines the rendering properties to be consumed by the module `Matter.Render`. + * + * @property render + * @type object + */ + render: IBodyRenderOptions; + /** + * A `Number` that defines the restitution (elasticity) of the body. The value is always positive and is in the range `(0, 1)`. + * A value of `0` means collisions may be perfectly inelastic and no bouncing may occur. + * A value of `0.8` means the body may bounce back with approximately 80% of its kinetic energy. + * Note that collision response is based on _pairs_ of bodies, and that `restitution` values are _combined_ with the following formula: + * + * Math.max(bodyA.restitution, bodyB.restitution) + * + * @property restitution + * @type number + * @default 0 + */ + restitution: number; + /** + * A `Number` that defines the number of updates in which this body must have near-zero velocity before it is set as sleeping by the `Matter.Sleeping` module (if sleeping is enabled by the engine). + * + * @property sleepThreshold + * @type number + * @default 60 + */ + sleepThreshold: number; + /** + * A `Number` that specifies a tolerance on how far a body is allowed to 'sink' or rotate into other bodies. + * Avoid changing this value unless you understand the purpose of `slop` in physics engines. + * The default should generally suffice, although very large bodies may require larger values for stable stacking. + * + * @property slop + * @type number + * @default 0.05 + */ + slop: number; + /** + * A `Number` that _measures_ the current speed of the body after the last `Body.update`. It is read-only and always positive (it's the magnitude of `body.velocity`). + * + * @readOnly + * @property speed + * @type number + * @default 0 + */ + speed: number; + /** + * A `Number` that allows per-body time scaling, e.g. a force-field where bodies inside are in slow-motion, while others are at full speed. + * + * @property timeScale + * @type number + * @default 1 + */ + timeScale: number; + /** + * A `Number` that specifies the torque (turning force) to apply in the current step. It is zeroed after every `Body.update`. + * + * @property torque + * @type number + * @default 0 + */ + torque: number; + /** + * A `String` denoting the type of object. + * + * @property type + * @type string + * @default "body" + */ + type: string; + /** + * A `Vector` that _measures_ the current velocity of the body after the last `Body.update`. It is read-only. + * If you need to modify a body's velocity directly, you should either apply a force or simply change the body's `position` (as the engine uses position-Verlet integration). + * + * @readOnly + * @property velocity + * @type vector + * @default { x: 0, y: 0 } + */ + velocity: Vector; + /** + * An array of `Vector` objects that specify the convex hull of the rigid body. + * These should be provided about the origin `(0, 0)`. E.g. + * + * [{ x: 0, y: 0 }, { x: 25, y: 50 }, { x: 50, y: 0 }] + * + * When passed via `Body.create`, the vertices are translated relative to `body.position` (i.e. world-space, and constantly updated by `Body.update` during simulation). + * The `Vector` objects are also augmented with additional properties required for efficient collision detection. + * + * Other properties such as `inertia` and `bounds` are automatically calculated from the passed vertices (unless provided via `options`). + * Concave hulls are not currently supported. The module `Matter.Vertices` contains useful methods for working with vertices. + * + * @property vertices + * @type vector[] + */ + vertices: Array; + /** + * An array of bodies that make up this body. + * The first body in the array must always be a self reference to the current body instance. + * All bodies in the `parts` array together form a single rigid compound body. + * Parts are allowed to overlap, have gaps or holes or even form concave bodies. + * Parts themselves should never be added to a `World`, only the parent body should be. + * Use `Body.setParts` when setting parts to ensure correct updates of all properties. + * + * @property parts + * @type body[] + */ + parts: Array; + /** + * A self reference if the body is _not_ a part of another body. + * Otherwise this is a reference to the body that this is a part of. + * See `body.parts`. + * + * @property parent + * @type body + */ + parent: Body; + /** + * An object reserved for storing plugin-specific properties. + * + * @property plugin + */ + plugin: any; + /** + * A `Number` that defines the static friction of the body (in the Coulomb friction model). + * A value of `0` means the body will never 'stick' when it is nearly stationary and only dynamic `friction` is used. + * The higher the value (e.g. `10`), the more force it will take to initially get the body moving when nearly stationary. + * This value is multiplied with the `friction` property to make it easier to change `friction` and maintain an appropriate amount of static friction. + * + * @property frictionStatic + * @type number + * @default 0.5 + */ + frictionStatic: number; + /** + * An `Object` that specifies the collision filtering properties of this body. + * + * Collisions between two bodies will obey the following rules: + * - If the two bodies have the same non-zero value of `collisionFilter.group`, + * they will always collide if the value is positive, and they will never collide + * if the value is negative. + * - If the two bodies have different values of `collisionFilter.group` or if one + * (or both) of the bodies has a value of 0, then the category/mask rules apply as follows: + * + * Each body belongs to a collision category, given by `collisionFilter.category`. This + * value is used as a bit field and the category should have only one bit set, meaning that + * the value of this property is a power of two in the range [1, 2^31]. Thus, there are 32 + * different collision categories available. + * + * Each body also defines a collision bitmask, given by `collisionFilter.mask` which specifies + * the categories it collides with (the value is the bitwise AND value of all these categories). + * + * Using the category/mask rules, two bodies `A` and `B` collide if each includes the other's + * category in its mask, i.e. `(categoryA & maskB) !== 0` and `(categoryB & maskA) !== 0` + * are both true. + * + * @property collisionFilter + * @type object + */ + collisionFilter: ICollisionFilter; + + /** + * A ct.js copy that owns this Body object. + */ + copy: Copy + } + + /** + * The `Matter.Bounds` module contains methods for creating and manipulating axis-aligned bounding boxes (AABB). + * + * @class Bounds + */ + export class Bounds { + min: Vector; + max: Vector; + + /** + * Creates a new axis-aligned bounding box (AABB) for the given vertices. + * @method create + * @param {vertices} vertices + * @return {bounds} A new bounds object + */ + static create (vertices: Vertices): Bounds; + /** + * Updates bounds using the given vertices and extends the bounds given a velocity. + * @method update + * @param {bounds} bounds + * @param {vertices} vertices + * @param {vector} velocity + */ + static update(bounds: Bounds, vertices: Vertices, velocity: Vector): void; + /** + * Returns true if the bounds contains the given point. + * @method contains + * @param {bounds} bounds + * @param {vector} point + * @return {boolean} True if the bounds contain the point, otherwise false + */ + static contains(bounds: Bounds, point: Vector): boolean; + /** + * Returns true if the two bounds intersect. + * @method overlaps + * @param {bounds} boundsA + * @param {bounds} boundsB + * @return {boolean} True if the bounds overlap, otherwise false + */ + static overlaps(boundsA: Bounds, boundsB: Bounds): boolean; + /** + * Translates the bounds by the given vector. + * @method translate + * @param {bounds} bounds + * @param {vector} vector + */ + static translate(bounds: Bounds, vector: Vector): void; + /** + * Shifts the bounds to the given position. + * @method shift + * @param {bounds} bounds + * @param {vector} position + */ + static shift(bounds: Bounds, position: Vector): void; + } + + export interface ICompositeDefinition { + /** + * An array of `Body` that are _direct_ children of this composite. + * To add or remove bodies you should use `Composite.add` and `Composite.remove` methods rather than directly modifying this property. + * If you wish to recursively find all descendants, you should use the `Composite.allBodies` method. + * + * @property bodies + * @type body[] + * @default [] + */ + bodies?: Array; + + /** + * An array of `Composite` that are _direct_ children of this composite. + * To add or remove composites you should use `Composite.add` and `Composite.remove` methods rather than directly modifying this property. + * If you wish to recursively find all descendants, you should use the `Composite.allComposites` method. + * + * @property composites + * @type composite[] + * @default [] + */ + composites?: Array; + + /** + * An array of `Constraint` that are _direct_ children of this composite. + * To add or remove constraints you should use `Composite.add` and `Composite.remove` methods rather than directly modifying this property. + * If you wish to recursively find all descendants, you should use the `Composite.allConstraints` method. + * + * @property constraints + * @type constraint[] + * @default [] + */ + constraints?: Array; + + /** + * An integer `Number` uniquely identifying number generated in `Composite.create` by `Common.nextId`. + * + * @property id + * @type number + */ + id?: number; + + /** + * A flag that specifies whether the composite has been modified during the current step. + * Most `Matter.Composite` methods will automatically set this flag to `true` to inform the engine of changes to be handled. + * If you need to change it manually, you should use the `Composite.setModified` method. + * + * @property isModified + * @type boolean + * @default false + */ + isModified?: boolean; + + /** + * An arbitrary `String` name to help the user identify and manage composites. + * + * @property label + * @type string + * @default "Composite" + */ + label?: string; + + /** + * The `Composite` that is the parent of this composite. It is automatically managed by the `Matter.Composite` methods. + * + * @property parent + * @type composite + * @default null + */ + parent?: Composite; + + /** + * A `String` denoting the type of object. + * + * @property type + * @type string + * @default "composite" + */ + type?: string; + } + + /** + * The `Matter.Composite` module contains methods for creating and manipulating composite bodies. + * A composite body is a collection of `Matter.Body`, `Matter.Constraint` and other `Matter.Composite`, therefore composites form a tree structure. + * It is important to use the functions in this module to modify composites, rather than directly modifying their properties. + * Note that the `Matter.World` object is also a type of `Matter.Composite` and as such all composite methods here can also operate on a `Matter.World`. + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class Composite + */ + export class Composite { + /** + * Generic add function. Adds one or many body(s), constraint(s) or a composite(s) to the given composite. + * Triggers `beforeAdd` and `afterAdd` events on the `composite`. + * @method add + * @param {composite} composite + * @param {} object + * @return {composite} The original composite with the objects added + */ + static add(composite: Composite, object: Body | Composite | Constraint): Composite; + + /** + * Returns all bodies in the given composite, including all bodies in its children, recursively. + * @method allBodies + * @param {composite} composite + * @return {body[]} All the bodies + */ + static allBodies(composite: Composite): Array; + + /** + * Returns all composites in the given composite, including all composites in its children, recursively. + * @method allComposites + * @param {composite} composite + * @return {composite[]} All the composites + */ + static allComposites(composite: Composite): Array; + + /** + * Returns all constraints in the given composite, including all constraints in its children, recursively. + * @method allConstraints + * @param {composite} composite + * @return {constraint[]} All the constraints + */ + static allConstraints(composite: Composite): Array; + + /** + * Removes all bodies, constraints and composites from the given composite. + * Optionally clearing its children recursively. + * @method clear + * @param {composite} composite + * @param {boolean} keepStatic + * @param {boolean} [deep=false] + */ + static clear(composite: Composite, keepStatic: boolean, deep?: boolean): void; + + /** + * Creates a new composite. The options parameter is an object that specifies any properties you wish to override the defaults. + * See the properites section below for detailed information on what you can pass via the `options` object. + * @method create + * @param {} [options] + * @return {composite} A new composite + */ + static create(options?: ICompositeDefinition): Composite; + + /** + * Searches the composite recursively for an object matching the type and id supplied, null if not found. + * @method get + * @param {composite} composite + * @param {number} id + * @param {string} type + * @return {object} The requested object, if found + */ + static get(composite: Composite, id: number, type: string): Body | Composite | Constraint; + + /** + * Moves the given object(s) from compositeA to compositeB (equal to a remove followed by an add). + * @method move + * @param {compositeA} compositeA + * @param {object[]} objects + * @param {compositeB} compositeB + * @return {composite} Returns compositeA + */ + static move(compositeA: Composite, objects: Array, compositeB: Composite): Composite; + + /** + * Assigns new ids for all objects in the composite, recursively. + * @method rebase + * @param {composite} composite + * @return {composite} Returns composite + */ + static rebase(composite: Composite): Composite; + + /** + * Generic remove function. Removes one or many body(s), constraint(s) or a composite(s) to the given composite. + * Optionally searching its children recursively. + * Triggers `beforeRemove` and `afterRemove` events on the `composite`. + * @method remove + * @param {composite} composite + * @param {} object + * @param {boolean} [deep=false] + * @return {composite} The original composite with the objects removed + */ + static remove(composite: Composite, object: Body | Composite | Constraint, deep?: boolean): Composite; + + + /** + * Sets the composite's `isModified` flag. + * If `updateParents` is true, all parents will be set (default: false). + * If `updateChildren` is true, all children will be set (default: false). + * @method setModified + * @param {composite} composite + * @param {boolean} isModified + * @param {boolean} [updateParents=false] + * @param {boolean} [updateChildren=false] + */ + static setModified(composite: Composite, isModified: boolean, updateParents?: boolean, updateChildren?: boolean): void; + /** + * Translates all children in the composite by a given vector relative to their current positions, + * without imparting any velocity. + * @method translate + * @param {composite} composite + * @param {vector} translation + * @param {bool} [recursive=true] + */ + static translate(composite: Composite, translation: Vector, recursive?: boolean): void; + /** + * Rotates all children in the composite by a given angle about the given point, without imparting any angular velocity. + * @method rotate + * @param {composite} composite + * @param {number} rotation + * @param {vector} point + * @param {bool} [recursive=true] + */ + static rotate(composite: Composite, rotation: number, point: Vector, recursive?: boolean): void; + /** + * Scales all children in the composite, including updating physical properties (mass, area, axes, inertia), from a world-space point. + * @method scale + * @param {composite} composite + * @param {number} scaleX + * @param {number} scaleY + * @param {vector} point + * @param {bool} [recursive=true] + */ + static scale(composite: Composite, scaleX: number, scaleY: number, point: Vector, recursive?: boolean): void; + + + /** + * An array of `Body` that are _direct_ children of this composite. + * To add or remove bodies you should use `Composite.add` and `Composite.remove` methods rather than directly modifying this property. + * If you wish to recursively find all descendants, you should use the `Composite.allBodies` method. + * + * @property bodies + * @type body[] + * @default [] + */ + bodies: Array; + + /** + * An array of `Composite` that are _direct_ children of this composite. + * To add or remove composites you should use `Composite.add` and `Composite.remove` methods rather than directly modifying this property. + * If you wish to recursively find all descendants, you should use the `Composite.allComposites` method. + * + * @property composites + * @type composite[] + * @default [] + */ + composites: Array; + + /** + * An array of `Constraint` that are _direct_ children of this composite. + * To add or remove constraints you should use `Composite.add` and `Composite.remove` methods rather than directly modifying this property. + * If you wish to recursively find all descendants, you should use the `Composite.allConstraints` method. + * + * @property constraints + * @type constraint[] + * @default [] + */ + constraints: Array; + + /** + * An integer `Number` uniquely identifying number generated in `Composite.create` by `Common.nextId`. + * + * @property id + * @type number + */ + id: number; + + /** + * A flag that specifies whether the composite has been modified during the current step. + * Most `Matter.Composite` methods will automatically set this flag to `true` to inform the engine of changes to be handled. + * If you need to change it manually, you should use the `Composite.setModified` method. + * + * @property isModified + * @type boolean + * @default false + */ + isModified: boolean; + + /** + * An arbitrary `String` name to help the user identify and manage composites. + * + * @property label + * @type string + * @default "Composite" + */ + label: string; + + /** + * The `Composite` that is the parent of this composite. It is automatically managed by the `Matter.Composite` methods. + * + * @property parent + * @type composite + * @default null + */ + parent: Composite; + + /** + * A `String` denoting the type of object. + * + * @property type + * @type string + * @default "composite" + */ + type: string; + } + + /** + * The `Matter.Composites` module contains factory methods for creating composite bodies + * with commonly used configurations (such as stacks and chains). + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class Composites + */ + export class Composites { + /** + * Creates a composite with simple car setup of bodies and constraints. + * @method car + * @param {number} xx + * @param {number} yy + * @param {number} width + * @param {number} height + * @param {number} wheelSize + * @return {composite} A new composite car body + */ + static car(xx: number, yy: number, width: number, height: number, wheelSize: number): Composite; + + /** + * Chains all bodies in the given composite together using constraints. + * @method chain + * @param {composite} composite + * @param {number} xOffsetA + * @param {number} yOffsetA + * @param {number} xOffsetB + * @param {number} yOffsetB + * @param {object} options + * @return {composite} A new composite containing objects chained together with constraints + */ + static chain(composite: Composite, xOffsetA: number, yOffsetA: number, xOffsetB: number, yOffsetB: number, options: any): Composite; + + /** + * Connects bodies in the composite with constraints in a grid pattern, with optional cross braces. + * @method mesh + * @param {composite} composite + * @param {number} columns + * @param {number} rows + * @param {boolean} crossBrace + * @param {object} options + * @return {composite} The composite containing objects meshed together with constraints + */ + static mesh(composite: Composite, columns: number, rows: number, crossBrace: boolean, options: any): Composite; + + /** + * Creates a composite with a Newton's Cradle setup of bodies and constraints. + * @method newtonsCradle + * @param {number} xx + * @param {number} yy + * @param {number} number + * @param {number} size + * @param {number} length + * @return {composite} A new composite newtonsCradle body + */ + static newtonsCradle(xx: number, yy: number, _number: number, size: number, length: number): Composite; + + /** + * Create a new composite containing bodies created in the callback in a pyramid arrangement. + * This function uses the body's bounds to prevent overlaps. + * @method pyramid + * @param {number} xx + * @param {number} yy + * @param {number} columns + * @param {number} rows + * @param {number} columnGap + * @param {number} rowGap + * @param {function} callback + * @return {composite} A new composite containing objects created in the callback + */ + static pyramid(xx: number, yy: number, columns: number, rows: number, columnGap: number, rowGap: number, callback: Function): Composite; + + /** + * Creates a simple soft body like object. + * @method softBody + * @param {number} xx + * @param {number} yy + * @param {number} columns + * @param {number} rows + * @param {number} columnGap + * @param {number} rowGap + * @param {boolean} crossBrace + * @param {number} particleRadius + * @param {} particleOptions + * @param {} constraintOptions + * @return {composite} A new composite softBody + */ + static softBody(xx: number, yy: number, columns: number, rows: number, columnGap: number, rowGap: number, crossBrace: boolean, particleRadius: number, particleOptions: any, constraintOptions: any): Composite; + + /** + * Create a new composite containing bodies created in the callback in a grid arrangement. + * This function uses the body's bounds to prevent overlaps. + * @method stack + * @param {number} xx + * @param {number} yy + * @param {number} columns + * @param {number} rows + * @param {number} columnGap + * @param {number} rowGap + * @param {function} callback + * @return {composite} A new composite containing objects created in the callback + */ + static stack(xx: number, yy: number, columns: number, rows: number, columnGap: number, rowGap: number, callback: Function): Composite; + } + + export interface IConstraintDefinition { + /** + * The first possible `Body` that this constraint is attached to. + * + * @property bodyA + * @type body + * @default null + */ + bodyA?: Body; + + /** + * The second possible `Body` that this constraint is attached to. + * + * @property bodyB + * @type body + * @default null + */ + bodyB?: Body; + + /** + * An integer `Number` uniquely identifying number generated in `Composite.create` by `Common.nextId`. + * + * @property id + * @type number + */ + id?: number; + + /** + * An arbitrary `String` name to help the user identify and manage bodies. + * + * @property label + * @type string + * @default "Constraint" + */ + label?: string; + + /** + * A `Number` that specifies the target resting length of the constraint. + * It is calculated automatically in `Constraint.create` from initial positions of the `constraint.bodyA` and `constraint.bodyB`. + * + * @property length + * @type number + */ + length?: number; + + /** + * A `Vector` that specifies the offset of the constraint from center of the `constraint.bodyA` if defined, otherwise a world-space position. + * + * @property pointA + * @type vector + * @default { x: 0, y: 0 } + */ + pointA?: Vector; + + /** + * A `Vector` that specifies the offset of the constraint from center of the `constraint.bodyA` if defined, otherwise a world-space position. + * + * @property pointB + * @type vector + * @default { x: 0, y: 0 } + */ + pointB?: Vector; + + /** + * An `Object` that defines the rendering properties to be consumed by the module `Matter.Render`. + * + * @property render + * @type object + */ + render?: IConstraintRenderDefinition; + + /** + * A `Number` that specifies the stiffness of the constraint, i.e. the rate at which it returns to its resting `constraint.length`. + * A value of `1` means the constraint should be very stiff. + * A value of `0.2` means the constraint acts like a soft spring. + * + * @property stiffness + * @type number + * @default 1 + */ + stiffness?: number; + + /** + * A `Number` that specifies the damping of the constraint, + * i.e. the amount of resistance applied to each body based on their velocities to limit the amount of oscillation. + * Damping will only be apparent when the constraint also has a very low `stiffness`. + * A value of `0.1` means the constraint will apply heavy damping, resulting in little to no oscillation. + * A value of `0` means the constraint will apply no damping. + * + * @property damping + * @type number + * @default 0 + */ + damping?: number; + + /** + * A `String` denoting the type of object. + * + * @property type + * @type string + * @default "constraint" + */ + type?: string; + } + + export interface IConstraintRenderDefinition { + /** + * A `Number` that defines the line width to use when rendering the constraint outline. + * A value of `0` means no outline will be rendered. + * + * @property render.lineWidth + * @type number + * @default 2 + */ + lineWidth?: number; + + /** + * A `String` that defines the stroke style to use when rendering the constraint outline. + * It is the same as when using a canvas, so it accepts CSS style property values. + * + * @property render.strokeStyle + * @type string + * @default a random colour + */ + strokeStyle?: string; + + /** + * A flag that indicates if the constraint should be rendered. + * + * @property render.visible + * @type boolean + * @default true + */ + visible?: boolean; + + /** + * A `Boolean` that defines if the constraint's anchor points should be rendered. + * + * @property render.anchors + * @type boolean + * @default true + */ + anchors?: boolean; + + /** + * A String that defines the constraint rendering type. The possible values are + * 'line', 'pin', 'spring'. An appropriate render type will be automatically + * chosen unless one is given in options. + * + * @property render.type + * @type string + * @default 'line' + */ + type?: 'line' | 'pin' | 'spring'; + } + + + /** + * The `Matter.Constraint` module contains methods for creating and manipulating constraints. + * Constraints are used for specifying that a fixed distance must be maintained between two bodies (or a body and a fixed world-space position). + * The stiffness of constraints can be modified to create springs or elastic. + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class Constraint + */ + export class Constraint { + /** + * Creates a new constraint. + * All properties have default values, and many are pre-calculated automatically based on other properties. + * See the properties section below for detailed information on what you can pass via the `options` object. + * @method create + * @param {} options + * @return {constraint} constraint + */ + static create(options: IConstraintDefinition): Constraint; + + /** + * The first possible `Body` that this constraint is attached to. + * + * @property bodyA + * @type body + * @default null + */ + bodyA: Body; + + /** + * The second possible `Body` that this constraint is attached to. + * + * @property bodyB + * @type body + * @default null + */ + bodyB: Body; + + /** + * An integer `Number` uniquely identifying number generated in `Composite.create` by `Common.nextId`. + * + * @property id + * @type number + */ + id: number; + + /** + * An arbitrary `String` name to help the user identify and manage bodies. + * + * @property label + * @type string + * @default "Constraint" + */ + label: string; + + /** + * A `Number` that specifies the target resting length of the constraint. + * It is calculated automatically in `Constraint.create` from initial positions of the `constraint.bodyA` and `constraint.bodyB`. + * + * @property length + * @type number + */ + length: number; + + /** + * A `Vector` that specifies the offset of the constraint from center of the `constraint.bodyA` if defined, otherwise a world-space position. + * + * @property pointA + * @type vector + * @default { x: 0, y: 0 } + */ + pointA: Vector; + + /** + * A `Vector` that specifies the offset of the constraint from center of the `constraint.bodyA` if defined, otherwise a world-space position. + * + * @property pointB + * @type vector + * @default { x: 0, y: 0 } + */ + pointB: Vector; + + /** + * An `Object` that defines the rendering properties to be consumed by the module `Matter.Render`. + * + * @property render + * @type object + */ + render: IConstraintRenderDefinition; + + /** + * A `Number` that specifies the stiffness of the constraint, i.e. the rate at which it returns to its resting `constraint.length`. + * A value of `1` means the constraint should be very stiff. + * A value of `0.2` means the constraint acts like a soft spring. + * + * @property stiffness + * @type number + * @default 1 + */ + stiffness: number; + + /** + * A `Number` that specifies the damping of the constraint, + * i.e. the amount of resistance applied to each body based on their velocities to limit the amount of oscillation. + * Damping will only be apparent when the constraint also has a very low `stiffness`. + * A value of `0.1` means the constraint will apply heavy damping, resulting in little to no oscillation. + * A value of `0` means the constraint will apply no damping. + * + * @property damping + * @type number + * @default 0 + */ + damping: number; + + /** + * A `String` denoting the type of object. + * + * @property type + * @type string + * @default "constraint" + */ + type: string; + } + + + export interface IEngineDefinition { + /** + * An integer `Number` that specifies the number of position iterations to perform each update. + * The higher the value, the higher quality the simulation will be at the expense of performance. + * + * @property positionIterations + * @type number + * @default 6 + */ + positionIterations?: number; + /** + * An integer `Number` that specifies the number of velocity iterations to perform each update. + * The higher the value, the higher quality the simulation will be at the expense of performance. + * + * @property velocityIterations + * @type number + * @default 4 + */ + velocityIterations?: number; + /** + * An integer `Number` that specifies the number of constraint iterations to perform each update. + * The higher the value, the higher quality the simulation will be at the expense of performance. + * The default value of `2` is usually very adequate. + * + * @property constraintIterations + * @type number + * @default 2 + */ + constraintIterations?: number; + + /** + * A flag that specifies whether the engine should allow sleeping via the `Matter.Sleeping` module. + * Sleeping can improve stability and performance, but often at the expense of accuracy. + * + * @property enableSleeping + * @type boolean + * @default false + */ + enableSleeping?: boolean; + /** + * An `Object` containing properties regarding the timing systems of the engine. + * + * @property timing + * @type object + */ + timing?: IEngineTimingOptions; + /** + * An instance of a broadphase controller. The default value is a `Matter.Grid` instance created by `Engine.create`. + * + * @property broadphase + * @type grid + * @default a Matter.Grid instance + */ + grid?: Grid; + /** + * A `World` composite object that will contain all simulated bodies and constraints. + * + * @property world + * @type world + * @default a Matter.World instance + */ + world?: World; + + } + + export interface IEngineTimingOptions { + /** + * A `Number` that specifies the global scaling factor of time for all bodies. + * A value of `0` freezes the simulation. + * A value of `0.1` gives a slow-motion effect. + * A value of `1.2` gives a speed-up effect. + * + * @property timing.timeScale + * @type number + * @default 1 + */ + timeScale: number; + + /** + * A `Number` that specifies the current simulation-time in milliseconds starting from `0`. + * It is incremented on every `Engine.update` by the given `delta` argument. + * + * @property timing.timestamp + * @type number + * @default 0 + */ + timestamp: number; + } + + /** + * The `Matter.Engine` module contains methods for creating and manipulating engines. + * An engine is a controller that manages updating the simulation of the world. + * See `Matter.Runner` for an optional game loop utility. + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class Engine + */ + export class Engine { + /** + * Clears the engine including the world, pairs and broadphase. + * @method clear + * @param {engine} engine + */ + static clear(engine: Engine): void; + + /** + * Creates a new engine. The options parameter is an object that specifies any properties you wish to override the defaults. + * All properties have default values, and many are pre-calculated automatically based on other properties. + * See the properties section below for detailed information on what you can pass via the `options` object. + * @method create + * @param {HTMLElement} element + * @param {object} [options] + * @return {engine} engine + */ + static create(element?: HTMLElement | IEngineDefinition, options?: IEngineDefinition): Engine; + + /** + * Creates a new engine. The options parameter is an object that specifies any properties you wish to override the defaults. + * All properties have default values, and many are pre-calculated automatically based on other properties. + * See the properties section below for detailed information on what you can pass via the `options` object. + * @method create + * @param {object} [options] + * @return {engine} engine + * @deprecated + */ + static create(options?: IEngineDefinition): Engine; + + /** + * Merges two engines by keeping the configuration of `engineA` but replacing the world with the one from `engineB`. + * @method merge + * @param {engine} engineA + * @param {engine} engineB + */ + static merge(engineA: Engine, engineB: Engine): void; + + + /** + * Moves the simulation forward in time by `delta` ms. + * The `correction` argument is an optional `Number` that specifies the time correction factor to apply to the update. + * This can help improve the accuracy of the simulation in cases where `delta` is changing between updates. + * The value of `correction` is defined as `delta / lastDelta`, i.e. the percentage change of `delta` over the last step. + * Therefore the value is always `1` (no correction) when `delta` constant (or when no correction is desired, which is the default). + * See the paper on Time Corrected Verlet for more information. + * + * Triggers `beforeUpdate` and `afterUpdate` events. + * Triggers `collisionStart`, `collisionActive` and `collisionEnd` events. + * @method update + * @param {engine} engine + * @param {number} [delta=16.666] + * @param {number} [correction=1] + */ + static update(engine: Engine, delta?: number, correction?: number): Engine; + + /** + * An alias for `Runner.run`, see `Matter.Runner` for more information. + * @method run + * @param {engine} engine + */ + static run(enige: Engine): void; + + /** + * An instance of a broadphase controller. The default value is a `Matter.Grid` instance created by `Engine.create`. + * + * @property broadphase + * @type grid + * @default a Matter.Grid instance + */ + broadphase: Grid; + /** + * An integer `Number` that specifies the number of constraint iterations to perform each update. + * The higher the value, the higher quality the simulation will be at the expense of performance. + * The default value of `2` is usually very adequate. + * + * @property constraintIterations + * @type number + * @default 2 + */ + constraintIterations: number; + + /** + * A flag that specifies whether the engine is running or not. + */ + enabled: boolean; + + /** + * A flag that specifies whether the engine should allow sleeping via the `Matter.Sleeping` module. + * Sleeping can improve stability and performance, but often at the expense of accuracy. + * + * @property enableSleeping + * @type boolean + * @default false + */ + enableSleeping: boolean; + + /** + * Collision pair set for this `Engine`. + */ + pairs: any; + + /** + * An integer `Number` that specifies the number of position iterations to perform each update. + * The higher the value, the higher quality the simulation will be at the expense of performance. + * + * @property positionIterations + * @type number + * @default 6 + */ + positionIterations: number; + + /** + * An instance of a `Render` controller. The default value is a `Matter.Render` instance created by `Engine.create`. + * One may also develop a custom renderer module based on `Matter.Render` and pass an instance of it to `Engine.create` via `options.render`. + * + * A minimal custom renderer object must define at least three functions: `create`, `clear` and `world` (see `Matter.Render`). + * It is also possible to instead pass the _module_ reference via `options.render.controller` and `Engine.create` will instantiate one for you. + * + * @property render + * @type render + * @default a Matter.Render instance + */ + render: Render; + + /** + * An `Object` containing properties regarding the timing systems of the engine. + * + * @property timing + * @type object + */ + timing: IEngineTimingOptions; + + /** + * An integer `Number` that specifies the number of velocity iterations to perform each update. + * The higher the value, the higher quality the simulation will be at the expense of performance. + * + * @property velocityIterations + * @type number + * @default 4 + */ + velocityIterations: number; + + /** + * A `World` composite object that will contain all simulated bodies and constraints. + * + * @property world + * @type world + * @default a Matter.World instance + */ + world: World; + } + + + export interface IGridDefinition { + + } + + /** + * The `Matter.Grid` module contains methods for creating and manipulating collision broadphase grid structures. + * + * @class Grid + */ + export class Grid { + /** + * Creates a new grid. + * @method create + * @param {} options + * @return {grid} A new grid + */ + static create(options?: IGridDefinition): Grid; + + /** + * Updates the grid. + * @method update + * @param {grid} grid + * @param {body[]} bodies + * @param {engine} engine + * @param {boolean} forceUpdate + */ + static update(grid: Grid, bodies: Array, engine: Engine, forceUpdate: boolean): void; + + /** + * Clears the grid. + * @method clear + * @param {grid} grid + */ + static clear(grid: Grid): void; + + + /** + * The width of a single grid bucket. + * + * @property type + * @type number + */ + bucketWidth: number; + + /** + * The height of a single grid bucket. + * + * @property type + * @type number + */ + bucketHeight: number; + } + + export interface IMouseConstraintDefinition { + /** + * The `Constraint` object that is used to move the body during interaction. + * + * @property constraint + * @type constraint + */ + constraint?: Constraint; + + /** + * An `Object` that specifies the collision filter properties. + * The collision filter allows the user to define which types of body this mouse constraint can interact with. + * See `body.collisionFilter` for more information. + * + * @property collisionFilter + * @type object + */ + collisionFilter?: ICollisionFilter; + + /** + * The `Body` that is currently being moved by the user, or `null` if no body. + * + * @property body + * @type body + * @default null + */ + body?: Body; + + /** + * The `Mouse` instance in use. If not supplied in `MouseConstraint.create`, one will be created. + * + * @property mouse + * @type mouse + * @default mouse + */ + mouse?: Mouse; + + /** + * A `String` denoting the type of object. + * + * @property type + * @type string + * @default "constraint" + */ + + type?: string; + } + + /** + * The `Matter.MouseConstraint` module contains methods for creating mouse constraints. + * Mouse constraints are used for allowing user interaction, providing the ability to move bodies via the mouse or touch. + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class MouseConstraint + */ + export class MouseConstraint { + /** + * Creates a new mouse constraint. + * All properties have default values, and many are pre-calculated automatically based on other properties. + * See the properties section below for detailed information on what you can pass via the `options` object. + * @method create + * @param {engine} engine + * @param {} options + * @return {MouseConstraint} A new MouseConstraint + */ + static create(engine: Engine, options?: IMouseConstraintDefinition): MouseConstraint; + + /** + * The `Constraint` object that is used to move the body during interaction. + * + * @property constraint + * @type constraint + */ + constraint: Constraint; + + /** + * An `Object` that specifies the collision filter properties. + * The collision filter allows the user to define which types of body this mouse constraint can interact with. + * See `body.collisionFilter` for more information. + * + * @property collisionFilter + * @type object + */ + collisionFilter: ICollisionFilter; + + /** + * The `Body` that is currently being moved by the user, or `null` if no body. + * + * @property body + * @type body + * @default null + */ + body: Body; + + /** + * The `Mouse` instance in use. If not supplied in `MouseConstraint.create`, one will be created. + * + * @property mouse + * @type mouse + * @default mouse + */ + mouse: Mouse; + + /** + * A `String` denoting the type of object. + * + * @property type + * @type string + * @default "constraint" + */ + + type: string; + } + + /** + * The `Matter.Pairs` module contains methods for creating and manipulating collision pair sets. + * + * @class Pairs + */ + export class Pairs { + /** + * Clears the given pairs structure. + * @method clear + * @param {pairs} pairs + * @return {pairs} pairs + */ + static clear(pairs: any): any; + } + + export interface IPair { + id: number; + bodyA: Body; + bodyB: Body; + contacts: any; + activeContacts: any; + separation: number; + isActive: boolean; + timeCreated: number; + timeUpdated: number, + inverseMass: number; + friction: number; + frictionStatic: number; + restitution: number; + slop: number; + } + + /** + * The `Matter.Query` module contains methods for performing collision queries. + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class Query + */ + export class Query { + /** + * Finds a list of collisions between body and bodies. + * @method collides + * @param {body} body + * @param {body[]} bodies + * @return {object[]} Collisions + */ + static collides(body: Body, bodies: Array): Array; + + /** + * Casts a ray segment against a set of bodies and returns all collisions, ray width is optional. Intersection points are not provided. + * @method ray + * @param {body[]} bodies + * @param {vector} startPoint + * @param {vector} endPoint + * @param {number} [rayWidth] + * @return {object[]} Collisions + */ + static ray(bodies: Array, startPoint: Vector, endPoint: Vector, rayWidth?: number): Array; + + /** + * Returns all bodies whose bounds are inside (or outside if set) the given set of bounds, from the given set of bodies. + * @method region + * @param {body[]} bodies + * @param {bounds} bounds + * @param {bool} [outside=false] + * @return {body[]} The bodies matching the query + */ + static region(bodies: Array, bounds: Bounds, outside?: boolean): Array; + + /** + * Returns all bodies whose vertices contain the given point, from the given set of bodies. + * @method point + * @param {body[]} bodies + * @param {vector} point + * @return {body[]} The bodies matching the query + */ + static point(bodies: Array, point: Vector): Array; + } + + export interface IRenderDefinition { + /** + * A back-reference to the `Matter.Render` module. + * + * @property controller + * @type render + */ + controller?: any; + /** + * A reference to the `Matter.Engine` instance to be used. + * + * @property engine + * @type engine + */ + engine: Engine; + /** + * A reference to the element where the canvas is to be inserted (if `render.canvas` has not been specified) + * + * @property element + * @type HTMLElement + * @default null + * @deprecated + */ + element?: HTMLElement; + /** + * The canvas element to render to. If not specified, one will be created if `render.element` has been specified. + * + * @property canvas + * @type HTMLCanvasElement + * @default null + */ + canvas?: HTMLCanvasElement; + + /** + * The configuration options of the renderer. + * + * @property options + * @type {} + */ + options?: IRendererOptions; + + /** + * A `Bounds` object that specifies the drawing view region. + * Rendering will be automatically transformed and scaled to fit within the canvas size (`render.options.width` and `render.options.height`). + * This allows for creating views that can pan or zoom around the scene. + * You must also set `render.options.hasBounds` to `true` to enable bounded rendering. + * + * @property bounds + * @type bounds + */ + bounds?: Bounds; + + /** + * The 2d rendering context from the `render.canvas` element. + * + * @property context + * @type CanvasRenderingContext2D + */ + context?: CanvasRenderingContext2D; + + /** + * The sprite texture cache. + * + * @property textures + * @type {} + */ + textures?: any; + + + } + + export interface IRendererOptions { + /** + * The target width in pixels of the `render.canvas` to be created. + * + * @property options.width + * @type number + * @default 800 + */ + width?: number; + + /** + * The target height in pixels of the `render.canvas` to be created. + * + * @property options.height + * @type number + * @default 600 + */ + height?: number; + + /** + * A flag that specifies if `render.bounds` should be used when rendering. + * + * @property options.hasBounds + * @type boolean + * @default false + */ + hasBounds?: boolean; + + /** + * Render wireframes only + * @type boolean + * @default true + */ + wireframes?: boolean; + + /** + * Sets scene background + * @type string + * default undefined + */ + background?: string + + /** + * Sets wireframe background if `render.options.wireframes` is enabled + * @type string + * default undefined + */ + wireframeBackground?: string + + /** + * Sets opacity of sleeping body if `render.options.showSleeping` is enabled + * @type boolean + * default true + */ + showSleeping?: boolean; + } + + interface IRenderLookAtObject { + bounds?: Bounds; + position?: { + x: number; + y: number; + }; + min?: { + x: number; + y: number; + }; + max?: { + x: number; + y: number; + }; + } + + /** + * The `Matter.Render` module is a simple HTML5 canvas based renderer for visualising instances of `Matter.Engine`. + * It is intended for development and debugging purposes, but may also be suitable for simple games. + * It includes a number of drawing options including wireframe, vector with support for sprites and viewports. + * + * @class Render + */ + export class Render { + /** + * Creates a new renderer. The options parameter is an object that specifies any properties you wish to override the defaults. + * All properties have default values, and many are pre-calculated automatically based on other properties. + * See the properties section below for detailed information on what you can pass via the `options` object. + * @method create + * @param {object} [options] + * @return {render} A new renderer + */ + static create(options: IRenderDefinition): Render; + /** + * Continuously updates the render canvas on the `requestAnimationFrame` event. + * @method run + * @param {render} render + */ + static run(render: Render): void; + /** + * Ends execution of `Render.run` on the given `render`, by canceling the animation frame request event loop. + * @method stop + * @param {render} render + */ + static stop(render: Render): void; + /** + * Sets the pixel ratio of the renderer and updates the canvas. + * To automatically detect the correct ratio, pass the string `'auto'` for `pixelRatio`. + * @method setPixelRatio + * @param {render} render + * @param {number} pixelRatio + */ + static setPixelRatio(render: Render, pixelRatio: number): void; + /** + * Renders the given `engine`'s `Matter.World` object. + * This is the entry point for all rendering and should be called every time the scene changes. + * @method world + * @param {engine} engine + */ + static world(render: Render): void; + /** + * Positions and sizes the viewport around the given object bounds. + * @method lookAt + * @param {Render} render + * @param {IRenderLookAtObject | IRenderLookAtObject[]} objects + * @param {Vector} paddiing + * @param {boolean} center + */ + static lookAt(render: Render, objects: IRenderLookAtObject | IRenderLookAtObject[], paddiing?: Vector, center?: boolean): void; + + /** + * A back-reference to the `Matter.Render` module. + * + * @property controller + * @type render + */ + controller: any; + /** + * A reference to the element where the canvas is to be inserted (if `render.canvas` has not been specified) + * + * @property element + * @type HTMLElement + * @default null + */ + element: HTMLElement; + /** + * The canvas element to render to. If not specified, one will be created if `render.element` has been specified. + * + * @property canvas + * @type HTMLCanvasElement + * @default null + */ + canvas: HTMLCanvasElement; + + /** + * The configuration options of the renderer. + * + * @property options + * @type {} + */ + options: IRendererOptions; + + /** + * A `Bounds` object that specifies the drawing view region. + * Rendering will be automatically transformed and scaled to fit within the canvas size (`render.options.width` and `render.options.height`). + * This allows for creating views that can pan or zoom around the scene. + * You must also set `render.options.hasBounds` to `true` to enable bounded rendering. + * + * @property bounds + * @type bounds + */ + bounds: Bounds; + + /** + * The 2d rendering context from the `render.canvas` element. + * + * @property context + * @type CanvasRenderingContext2D + */ + context: CanvasRenderingContext2D; + + /** + * The sprite texture cache. + * + * @property textures + * @type {} + */ + textures: any; + } + + + export interface IRunnerOptions { + /** + * A `Boolean` that specifies if the runner should use a fixed timestep (otherwise it is variable). + * If timing is fixed, then the apparent simulation speed will change depending on the frame rate (but behaviour will be deterministic). + * If the timing is variable, then the apparent simulation speed will be constant (approximately, but at the cost of determininism). + * + * @property isFixed + * @type boolean + * @default false + */ + isFixed?: boolean; + + /** + * A `Number` that specifies the time step between updates in milliseconds. + * If `engine.timing.isFixed` is set to `true`, then `delta` is fixed. + * If it is `false`, then `delta` can dynamically change to maintain the correct apparent simulation speed. + * + * @property delta + * @type number + * @default 1000 / 60 + */ + delta?: number; + + /** + * A flag that specifies whether the runner is running or not. + * @property enabled + * @type boolean + * @default true + */ + enabled?: boolean; + } + + /** + * The `Matter.Runner` module is an optional utility which provides a game loop, + * that handles updating and rendering a `Matter.Engine` for you within a browser. + * It is intended for demo and testing purposes, but may be adequate for simple games. + * If you are using your own game loop instead, then you do not need the `Matter.Runner` module. + * Instead just call `Engine.update(engine, delta)` in your own loop. + * Note that the method `Engine.run` is an alias for `Runner.run`. + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class Runner + */ + export class Runner { + /** + * Creates a new Runner. The options parameter is an object that specifies any properties you wish to override the defaults. + * @method create + * @param {} options + */ + static create(options?: IRunnerOptions): Runner; + /** + * Continuously ticks a `Matter.Engine` by calling `Runner.tick` on the `requestAnimationFrame` event. + * @method run + * @param {engine} engine + */ + static run(runner: Runner, engine: Engine): Runner; + /** + * Continuously ticks a `Matter.Engine` by calling `Runner.tick` on the `requestAnimationFrame` event. + * @method run + * @param {engine} engine + */ + static run(engine: Engine): Runner; + /** + * A game loop utility that updates the engine and renderer by one step (a 'tick'). + * Features delta smoothing, time correction and fixed or dynamic timing. + * Triggers `beforeTick`, `tick` and `afterTick` events on the engine. + * Consider just `Engine.update(engine, delta)` if you're using your own loop. + * @method tick + * @param {runner} runner + * @param {engine} engine + * @param {number} time + */ + static tick(runner: Runner, engine: Engine, time: number): void; + /** + * Ends execution of `Runner.run` on the given `runner`, by canceling the animation frame request event loop. + * If you wish to only temporarily pause the engine, see `engine.enabled` instead. + * @method stop + * @param {runner} runner + */ + static stop(runner: Runner): void; + /** + * Alias for `Runner.run`. + * @method start + * @param {runner} runner + * @param {engine} engine + */ + static start(runner: Runner, engine: Engine): void; + + /** + * A flag that specifies whether the runner is running or not. + * + * @property enabled + * @type boolean + * @default true + */ + enabled: boolean; + + /** + * A `Boolean` that specifies if the runner should use a fixed timestep (otherwise it is variable). + * If timing is fixed, then the apparent simulation speed will change depending on the frame rate (but behaviour will be deterministic). + * If the timing is variable, then the apparent simulation speed will be constant (approximately, but at the cost of determininism). + * + * @property isFixed + * @type boolean + * @default false + */ + isFixed: boolean; + + /** + * A `Number` that specifies the time step between updates in milliseconds. + * If `engine.timing.isFixed` is set to `true`, then `delta` is fixed. + * If it is `false`, then `delta` can dynamically change to maintain the correct apparent simulation speed. + * + * @property delta + * @type number + * @default 1000 / 60 + */ + delta: number; + } + + /** + * The `Matter.Sleeping` module contains methods to manage the sleeping state of bodies. + * + * @class Sleeping + */ + export class Sleeping { + static set(body: Body, isSleeping: boolean): void; + } + + /** + * The `Matter.Svg` module contains methods for converting SVG images into an array of vector points. + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class Svg + */ + export class Svg { + /** + * Converts an SVG path into an array of vector points. + * If the input path forms a concave shape, you must decompose the result into convex parts before use. + * See `Bodies.fromVertices` which provides support for this. + * Note that this function is not guaranteed to support complex paths (such as those with holes). + * @method pathToVertices + * @param {SVGPathElement} path + * @param {Number} [sampleLength=15] + * @return {Vector[]} points + */ + static pathToVertices(path: SVGPathElement, sampleLength: number): Array; + } + + /** + * The `Matter.Vector` module contains methods for creating and manipulating vectors. + * Vectors are the basis of all the geometry related operations in the engine. + * A `Matter.Vector` object is of the form `{ x: 0, y: 0 }`. + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class Vector + */ + export class Vector { + x: number; + y: number; + + /** + * Creates a new vector. + * @method create + * @param {number} x + * @param {number} y + * @return {vector} A new vector + */ + static create(x?: number, y?: number): Vector; + + /** + * Returns a new vector with `x` and `y` copied from the given `vector`. + * @method clone + * @param {vector} vector + * @return {vector} A new cloned vector + */ + static clone(vector: Vector): Vector; + + + /** + * Returns the cross-product of three vectors. + * @method cross3 + * @param {vector} vectorA + * @param {vector} vectorB + * @param {vector} vectorC + * @return {number} The cross product of the three vectors + */ + static cross3(vectorA: Vector, vectorB: Vector, vectorC: Vector):number; + + /** + * Adds the two vectors. + * @method add + * @param {vector} vectorA + * @param {vector} vectorB + * @param {vector} [output] + * @return {vector} A new vector of vectorA and vectorB added + */ + static add(vectorA: Vector, vectorB: Vector, output?: Vector): Vector; + + /** + * Returns the angle in radians between the two vectors relative to the x-axis. + * @method angle + * @param {vector} vectorA + * @param {vector} vectorB + * @return {number} The angle in radians + */ + static angle(vectorA: Vector, vectorB: Vector): number; + + /** + * Returns the cross-product of two vectors. + * @method cross + * @param {vector} vectorA + * @param {vector} vectorB + * @return {number} The cross product of the two vectors + */ + static cross(vectorA: Vector, vectorB: Vector): number; + + /** + * Divides a vector and a scalar. + * @method div + * @param {vector} vector + * @param {number} scalar + * @return {vector} A new vector divided by scalar + */ + static div(vector: Vector, scalar: number): Vector; + + /** + * Returns the dot-product of two vectors. + * @method dot + * @param {vector} vectorA + * @param {vector} vectorB + * @return {number} The dot product of the two vectors + */ + static dot(vectorA: Vector, vectorB: Vector): number; + + /** + * Returns the magnitude (length) of a vector. + * @method magnitude + * @param {vector} vector + * @return {number} The magnitude of the vector + */ + static magnitude(vector: Vector): number; + + /** + * Returns the magnitude (length) of a vector (therefore saving a `sqrt` operation). + * @method magnitudeSquared + * @param {vector} vector + * @return {number} The squared magnitude of the vector + */ + static magnitudeSquared(vector: Vector): number; + + /** + * Multiplies a vector and a scalar. + * @method mult + * @param {vector} vector + * @param {number} scalar + * @return {vector} A new vector multiplied by scalar + */ + static mult(vector: Vector, scalar: number): Vector; + + /** + * Negates both components of a vector such that it points in the opposite direction. + * @method neg + * @param {vector} vector + * @return {vector} The negated vector + */ + static neg(vector: Vector): Vector; + + /** + * Normalises a vector (such that its magnitude is `1`). + * @method normalise + * @param {vector} vector + * @return {vector} A new vector normalised + */ + static normalise(vector: Vector): Vector; + + /** + * Returns the perpendicular vector. Set `negate` to true for the perpendicular in the opposite direction. + * @method perp + * @param {vector} vector + * @param {bool} [negate=false] + * @return {vector} The perpendicular vector + */ + static perp(vector: Vector, negate?: boolean): Vector; + + /** + * Rotates the vector about (0, 0) by specified angle. + * @method rotate + * @param {vector} vector + * @param {number} angle + * @return {vector} A new vector rotated about (0, 0) + */ + static rotate(vector: Vector, angle: number): Vector; + + /** + * Rotates the vector about a specified point by specified angle. + * @method rotateAbout + * @param {vector} vector + * @param {number} angle + * @param {vector} point + * @param {vector} [output] + * @return {vector} A new vector rotated about the point + */ + static rotateAbout(vector: Vector, angle: number, point: Vector, output?: Vector): Vector; + + /** + * Subtracts the two vectors. + * @method sub + * @param {vector} vectorA + * @param {vector} vectorB + * @param {vector} [output] + * @return {vector} A new vector of vectorA and vectorB subtracted + */ + static sub(vectorA: Vector, vectorB: Vector, optional?: Vector): Vector; + } + + /** + * The `Matter.Vertices` module contains methods for creating and manipulating sets of vertices. + * A set of vertices is an array of `Matter.Vector` with additional indexing properties inserted by `Vertices.create`. + * A `Matter.Body` maintains a set of vertices to represent the shape of the object (its convex hull). + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class Vertices + */ + export class Vertices { + /** + * Returns the average (mean) of the set of vertices. + * @method mean + * @param {vertices} vertices + * @return {vector} The average point + */ + static mean(vertices: Array): Array; + + /** + * Sorts the input vertices into clockwise order in place. + * @method clockwiseSort + * @param {vertices} vertices + * @return {vertices} vertices + */ + static clockwiseSort(vertices: Array): Array; + + /** + * Returns true if the vertices form a convex shape (vertices must be in clockwise order). + * @method isConvex + * @param {vertices} vertices + * @return {bool} `true` if the `vertices` are convex, `false` if not (or `null` if not computable). + */ + static isConvex(vertices: Array): boolean; + + /** + * Returns the convex hull of the input vertices as a new array of points. + * @method hull + * @param {vertices} vertices + * @return [vertex] vertices + */ + static hull(vertices: Array): Array; + + /** + * Returns the area of the set of vertices. + * @method area + * @param {vertices} vertices + * @param {bool} signed + * @return {number} The area + */ + static area(vertices: Array, signed: boolean): number; + + /** + * Returns the centre (centroid) of the set of vertices. + * @method centre + * @param {vertices} vertices + * @return {vector} The centre point + */ + static centre(vertices: Array): Vector; + + /** + * Chamfers a set of vertices by giving them rounded corners, returns a new set of vertices. + * The radius parameter is a single number or an array to specify the radius for each vertex. + * @method chamfer + * @param {vertices} vertices + * @param {number[]} radius + * @param {number} quality + * @param {number} qualityMin + * @param {number} qualityMax + * @return {vertices} vertices + */ + static chamfer(vertices: Array, radius: number | Array, quality: number, qualityMin: number, qualityMax: number): Array; + + + /** + * Returns `true` if the `point` is inside the set of `vertices`. + * @method contains + * @param {vertices} vertices + * @param {vector} point + * @return {boolean} True if the vertices contains point, otherwise false + */ + static contains(vertices: Array, point: Vector): boolean; + + /** + * Creates a new set of `Matter.Body` compatible vertices. + * The `points` argument accepts an array of `Matter.Vector` points orientated around the origin `(0, 0)`, for example: + * + * [{ x: 0, y: 0 }, { x: 25, y: 50 }, { x: 50, y: 0 }] + * + * The `Vertices.create` method returns a new array of vertices, which are similar to Matter.Vector objects, + * but with some additional references required for efficient collision detection routines. + * + * Note that the `body` argument is not optional, a `Matter.Body` reference must be provided. + * + * @method create + * @param {vector[]} points + * @param {body} body + * @return {vertices} vertices + */ + static create(points: Array, body: Body): Array; + + /** + * Parses a string containing ordered x y pairs separated by spaces (and optionally commas), + * into a `Matter.Vertices` object for the given `Matter.Body`. + * For parsing SVG paths, see `Svg.pathToVertices`. + * @method fromPath + * @param {string} path + * @param {body} body + * @return {vertices} vertices + */ + static fromPath(path: string, body: Body): Array; + + /** + * Returns the moment of inertia (second moment of area) of the set of vertices given the total mass. + * @method inertia + * @param {vertices} vertices + * @param {number} mass + * @return {number} The polygon's moment of inertia + */ + static inertia(vertices: Array, mass: number): number; + + /** + * Rotates the set of vertices in-place. + * @method rotate + * @param {vertices} vertices + * @param {number} angle + * @param {vector} point + * @return {vertices} vertices + */ + static rotate(vertices: Array, angle: number, point: Vector): Array; + + /** + * Scales the vertices from a point (default is centre) in-place. + * @method scale + * @param {vertices} vertices + * @param {number} scaleX + * @param {number} scaleY + * @param {vector} point + * @return {vertices} vertices + */ + static scale(vertices: Array, scaleX: number, scaleY: number, point: Vector): Array; + + /** + * Translates the set of vertices in-place. + * @method translate + * @param {vertices} vertices + * @param {vector} vector + * @param {number} scalar + * @return {vertices} vertices + */ + static translate(vertices: Array, vector: Vector, scalar: number): Array; + } + + interface IWorldDefinition extends ICompositeDefinition { + gravity?: Gravity; + bounds?: Bounds; + } + + interface Gravity extends Vector { + scale: number; + } + + /** + * The `Matter.World` module contains methods for creating and manipulating the world composite. + * A `Matter.World` is a `Matter.Composite` body, which is a collection of `Matter.Body`, `Matter.Constraint` and other `Matter.Composite`. + * A `Matter.World` has a few additional properties including `gravity` and `bounds`. + * It is important to use the functions in the `Matter.Composite` module to modify the world composite, rather than directly modifying its properties. + * There are also a few methods here that alias those in `Matter.Composite` for easier readability. + * + * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). + * + * @class World + * @extends Composite + */ + export class World extends Composite { + /** + * Add objects or arrays of objects of types: Body, Constraint, Composite + * @param world + * @param body + * @returns world + */ + static add(world: World, body: Body | Array | Composite | Array | Constraint | Array | MouseConstraint): World; + + /** + * An alias for Composite.addBody since World is also a Composite + * @method addBody + * @param {world} world + * @param {body} body + * @return {world} The original world with the body added + */ + static addBody(world: World, body: Body): World; + + /** + * An alias for Composite.add since World is also a Composite + * @method addComposite + * @param {world} world + * @param {composite} composite + * @return {world} The original world with the objects from composite added + */ + static addComposite(world: World, composite: Composite): World; + + /** + * An alias for Composite.addConstraint since World is also a Composite + * @method addConstraint + * @param {world} world + * @param {constraint} constraint + * @return {world} The original world with the constraint added + */ + static addConstraint(world: World, constraint: Constraint): World; + + /** + * An alias for Composite.clear since World is also a Composite + * @method clear + * @param {world} world + * @param {boolean} keepStatic + */ + static clear(world: World, keepStatic: boolean): void; + + /** + * Creates a new world composite. The options parameter is an object that specifies any properties you wish to override the defaults. + * See the properties section below for detailed information on what you can pass via the `options` object. + * @method create + * @constructor + * @param {} options + * @return {world} A new world + */ + static create(options: IWorldDefinition): World; + + gravity: Gravity; + bounds: Bounds; + } + + export interface ICollisionFilter { + category?: number; + mask?: number; + group?: number; + } + + export interface IMousePoint { + x: number; + y: number; + } + + export class Mouse { + static create(element: HTMLElement): Mouse; + static setElement(mouse: Mouse, element: HTMLElement): void; + static clearSourceEvents(mouse: Mouse): void; + static setOffset(mouse: Mouse, offset: Vector): void; + static setScale(mouse: Mouse, scale: Vector): void; + + element: HTMLElement; + absolute: IMousePoint; + position: IMousePoint; + mousedownPosition: IMousePoint; + mouseupPosition: IMousePoint; + offset: IMousePoint; + scale: IMousePoint; + wheelDelta: number; + button: number; + pixelRatio: number; + } + + export class Common { + /** + * Extends the object in the first argument using the object in the second argument. + * @method extend + * @param {} obj + * @param {boolean} deep + * @return {} obj extended + */ + static extend(obj: object, deep: boolean): object + + /** + * Creates a new clone of the object, if deep is true references will also be cloned. + * @method clone + * @param {} obj + * @param {bool} deep + * @return {} obj cloned + */ + static clone(obj: object, deep: boolean): object + + /** + * Returns the list of keys for the given object. + * @method keys + * @param {} obj + * @return {string[]} keys + */ + static keys(obj: object): Array + + /** + * Returns the list of values for the given object. + * @method values + * @param {} obj + * @return {array} Array of the objects property values + */ + static values(obj: object): Array + + /** + * Gets a value from `base` relative to the `path` string. + * @method get + * @param {} obj The base object + * @param {string} path The path relative to `base`, e.g. 'Foo.Bar.baz' + * @param {number} [begin] Path slice begin + * @param {number} [end] Path slice end + * @return {} The object at the given path + */ + static get(obj: object, path: string, begin: number, end: number): object + + /** + * Sets a value on `base` relative to the given `path` string. + * @method set + * @param {} obj The base object + * @param {string} path The path relative to `base`, e.g. 'Foo.Bar.baz' + * @param {} val The value to set + * @param {number} [begin] Path slice begin + * @param {number} [end] Path slice end + * @return {} Pass through `val` for chaining + */ + static set(obj: object, path: string, val: object, begin: number, end: number): Object + + /** + * Shuffles the given array in-place. + * The function uses a seeded random generator. + * @method shuffle + * @param {array} array + * @return {array} array shuffled randomly + */ + static shuffle(array: Array): Array + + /** + * Randomly chooses a value from a list with equal probability. + * The function uses a seeded random generator. + * @method choose + * @param {array} choices + * @return {object} A random choice object from the array + */ + static choose(choices: Array): any + + /** + * Returns true if the object is a HTMLElement, otherwise false. + * @method isElement + * @param {object} obj + * @return {boolean} True if the object is a HTMLElement, otherwise false + */ + static isElement(obj: object): boolean + + /** + * Returns true if the object is an array. + * @method isArray + * @param {object} obj + * @return {boolean} True if the object is an array, otherwise false + */ + static isArray(obj: object): boolean + + /** + * Returns true if the object is a function. + * @method isFunction + * @param {object} obj + * @return {boolean} True if the object is a function, otherwise false + */ + static isFunction(obj: object): boolean + + /** + * Returns true if the object is a plain object. + * @method isPlainObject + * @param {object} obj + * @return {boolean} True if the object is a plain object, otherwise false + */ + static isPlainObject(obj: object): boolean + + /** + * Returns true if the object is a string. + * @method isString + * @param {object} obj + * @return {boolean} True if the object is a string, otherwise false + */ + static isString(obj: object): boolean + + /** + * Returns the given value clamped between a minimum and maximum value. + * @method clamp + * @param {number} value + * @param {number} min + * @param {number} max + * @return {number} The value clamped between min and max inclusive + */ + static clamp(value: number, min: number, max: number): number + + /** + * Returns the sign of the given value. + * @method sign + * @param {number} value + * @return {number} -1 if negative, +1 if 0 or positive + */ + static sign(value: number): number + + /** + * Returns the current timestamp since the time origin (e.g. from page load). + * The result will be high-resolution including decimal places if available. + * @method now + * @return {number} the current timestamp + */ + static now(): number + + /** + * Returns a random value between a minimum and a maximum value inclusive. + * The function uses a seeded random generator. + * @method random + * @param {number} min + * @param {number} max + * @return {number} A random number between min and max inclusive + */ + static random(min?: number, max?: number): number + + /** + * Converts a CSS hex colour string into an integer. + * @method colorToNumber + * @param {string} colorString + * @return {number} An integer representing the CSS hex string + */ + static colorToNumber(colorString: string): number + + /** + * Shows a `console.log` message only if the current `Common.logLevel` allows it. + * The message will be prefixed with 'matter-js' to make it easily identifiable. + * @method log + * @param ...objs {} The objects to log. + */ + static log(): any + + /** + * Shows a `console.info` message only if the current `Common.logLevel` allows it. + * The message will be prefixed with 'matter-js' to make it easily identifiable. + * @method info + * @param ...objs {} The objects to log. + */ + static info(): any + + /** + * Shows a `console.warn` message only if the current `Common.logLevel` allows it. + * The message will be prefixed with 'matter-js' to make it easily identifiable. + * @method warn + * @param ...objs {} The objects to log. + */ + static warn(): any + + /** + * Returns the next unique sequential ID. + * @method nextId + * @return {number} Unique sequential ID + */ + static nextId(): number + + /** + * A cross browser compatible indexOf implementation. + * @method indexOf + * @param {array} haystack + * @param {object} needle + * @return {number} The position of needle in haystack, otherwise -1. + */ + static indexOf(haystack: Array, needle: object): number + + /** + * A cross browser compatible array map implementation. + * @method map + * @param {array} list + * @param {function} func + * @return {array} Values from list transformed by func. + */ + static map(list: Array, funct: Function): Array + + /** + * Takes a directed graph and returns the partially ordered set of vertices in topological order. + * Circular dependencies are allowed. + * @method topologicalSort + * @param {object} graph + * @return {array} Partially ordered set of vertices in topological order. + */ + static topologicalSort(graph: object): Array + + /** + * Takes _n_ functions as arguments and returns a new function that calls them in order. + * The arguments applied when calling the new function will also be applied to every function passed. + * The value of `this` refers to the last value returned in the chain that was not `undefined`. + * Therefore if a passed function does not return a value, the previously returned value is maintained. + * After all passed functions have been called the new function returns the last returned value (if any). + * If any of the passed functions are a chain, then the chain will be flattened. + * @method chain + * @param ...funcs {function} The functions to chain. + * @return {function} A new function that calls the passed functions in order. + */ + static chain(): Function + + /** + * Chains a function to excute before the original function on the given `path` relative to `base`. + * See also docs for `Common.chain`. + * @method chainPathBefore + * @param {} base The base object + * @param {string} path The path relative to `base` + * @param {function} func The function to chain before the original + * @return {function} The chained function that replaced the original + */ + static chainPathBefore(base: object, path: string, func: Function): Function + + /** + * Chains a function to excute after the original function on the given `path` relative to `base`. + * See also docs for `Common.chain`. + * @method chainPathAfter + * @param {} base The base object + * @param {string} path The path relative to `base` + * @param {function} func The function to chain after the original + * @return {function} The chained function that replaced the original + */ + static chainPathAfter(base: object, path: string, func: Function): Function + + /** + * Used to require external libraries outside of the bundle. + * It first looks for the `globalName` on the environment's global namespace. + * If the global is not found, it will fall back to using the standard `require` using the `moduleName`. + * @private + * @method _requireGlobal + * @param {string} globalName The global module name + * @param {string} moduleName The fallback CommonJS module name + * @return {} The loaded module + */ + static _requireGlobal(globalName: string, moduleName: string): any + } + + export interface IEvent { + /** + * The name of the event + */ + name: string; + /** + * The source object of the event + */ + source: T; + } + + export interface IEventComposite extends IEvent { + /** + * EventObjects (may be a single body, constraint, composite or a mixed array of these) + */ + object: any; + } + + export interface IEventTimestamped extends IEvent { + /** + * The engine.timing.timestamp of the event + */ + timestamp: number; + } + + export interface IEventCollision extends IEventTimestamped { + /** + * The collision pair + */ + pairs: Array; + } + + export interface IMouseEvent extends IEvent { + name: 'mousedown' | 'mousemove' | 'mouseup'; + } + + export class Events { + /** + * Fired when a body starts sleeping (where `this` is the body). + * + * @event sleepStart + * @this {body} The body that has started sleeping + * @param {} event An event object + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Body, name: 'sleepStart', callback: (e: IEvent) => void): void; + /** + * Fired when a body ends sleeping (where `this` is the body). + * + * @event sleepEnd + * @this {body} The body that has ended sleeping + * @param {} event An event object + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Body, name: 'sleepEnd', callback: (e: IEvent) => void): void; + + /** + * Fired when a call to `Composite.add` is made, before objects have been added. + * + * @event beforeAdd + * @param {} event An event object + * @param {} event.object The object(s) to be added (may be a single body, constraint, composite or a mixed array of these) + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'beforeAdd', callback: (e: IEventComposite) => void): void; + + /** + * Fired when a call to `Composite.add` is made, after objects have been added. + * + * @event afterAdd + * @param {} event An event object + * @param {} event.object The object(s) that have been added (may be a single body, constraint, composite or a mixed array of these) + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'afterAdd', callback: (e: IEventComposite) => void): void; + + /** + * Fired when a call to `Composite.remove` is made, before objects have been removed. + * + * @event beforeRemove + * @param {} event An event object + * @param {} event.object The object(s) to be removed (may be a single body, constraint, composite or a mixed array of these) + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'beforeRemove', callback: (e: IEventComposite) => void): void; + + /** + * Fired when a call to `Composite.remove` is made, after objects have been removed. + * + * @event afterRemove + * @param {} event An event object + * @param {} event.object The object(s) that have been removed (may be a single body, constraint, composite or a mixed array of these) + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'afterRemove', callback: (e: IEventComposite) => void): void; + + + /** + * Fired after engine update and all collision events + * + * @event afterUpdate + * @param {} event An event object + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'afterUpdate', callback: (e: IEventTimestamped) => void): void; + + /** + * Fired before rendering + * + * @event beforeRender + * @param {} event An event object + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'beforeRender', callback: (e: IEventTimestamped) => void): void; + /** + * Fired after rendering + * + * @event afterRender + * @param {} event An event object + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'afterRender', callback: (e: IEventTimestamped) => void): void; + + + /** + * Fired just before an update + * + * @event beforeUpdate + * @param {} event An event object + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'beforeUpdate', callback: (e: IEventTimestamped) => void): void; + + /** + * Fired after engine update, provides a list of all pairs that are colliding in the current tick (if any) + * + * @event collisionActive + * @param {} event An event object + * @param {} event.pairs List of affected pairs + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'collisionActive', callback: (e: IEventCollision) => void): void; + + + /** + * Fired after engine update, provides a list of all pairs that have ended collision in the current tick (if any) + * + * @event collisionEnd + * @param {} event An event object + * @param {} event.pairs List of affected pairs + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'collisionEnd', callback: (e: IEventCollision) => void): void; + + /** + * Fired after engine update, provides a list of all pairs that have started to collide in the current tick (if any) + * + * @event collisionStart + * @param {} event An event object + * @param {} event.pairs List of affected pairs + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'collisionStart', callback: (e: IEventCollision) => void): void; + + /** + * Fired at the start of a tick, before any updates to the engine or timing + * + * @event beforeTick + * @param {} event An event object + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'beforeTick', callback: (e: IEventTimestamped) => void): void; + + /** + * Fired after engine timing updated, but just before update + * + * @event tick + * @param {} event An event object + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'tick', callback: (e: IEventTimestamped) => void): void; + + /** + * Fired at the end of a tick, after engine update and after rendering + * + * @event afterTick + * @param {} event An event object + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'afterTick', callback: (e: IEventTimestamped) => void): void; + + /** + * Fired before rendering + * + * @event beforeRender + * @param {} event An event object + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'beforeRender', callback: (e: IEventTimestamped) => void): void; + + /** + * Fired after rendering + * + * @event afterRender + * @param {} event An event object + * @param {number} event.timestamp The engine.timing.timestamp of the event + * @param {} event.source The source object of the event + * @param {} event.name The name of the event + */ + static on(obj: Engine, name: 'afterRender', callback: (e: IEventTimestamped) => void): void; + + /** + * Fired when the mouse is down (or a touch has started) during the last step + * @param obj + * @param name + * @param callback + */ + static on(obj: MouseConstraint, name: 'mousedown', callback: (e: IMouseEvent) => void): void; + + /** + * Fired when the mouse has moved (or a touch moves) during the last step + * @param obj + * @param name + * @param callback + */ + static on(obj: MouseConstraint, name: 'mousemove', callback: (e: IMouseEvent) => void): void; + + /** + * Fired when the mouse is up (or a touch has ended) during the last step + * @param obj + * @param name + * @param callback + */ + static on(obj: MouseConstraint, name: 'mouseup', callback: (e: IMouseEvent) => void): void; + + + static on(obj: any, name: string, callback: (e: any) => void): void; + + /** + * Removes the given event callback. If no callback, clears all callbacks in eventNames. If no eventNames, clears all events. + * + * @param obj + * @param eventName + * @param callback + */ + static off(obj: any, eventName: string, callback: (e: any) => void): void; + + /** + * Fires all the callbacks subscribed to the given object's eventName, in the order they subscribed, if any. + * + * @param object + * @param eventNames + * @param event + */ + static trigger(object: any, eventNames: string, event?: any): void; + } + + type Dependency = {name: string, range: string} + | {name: string, version: string} + | string; + + export class Plugin { + name: string; + version: string; + install: () => void; + for?: string; + + /** + * Registers a plugin object so it can be resolved later by name. + * @method register + * @param plugin {} The plugin to register. + * @return {object} The plugin. + */ + static register(plugin: Plugin): Plugin; + + /** + * Resolves a dependency to a plugin object from the registry if it exists. + * The `dependency` may contain a version, but only the name matters when resolving. + * @method resolve + * @param dependency {string} The dependency. + * @return {object} The plugin if resolved, otherwise `undefined`. + */ + static resolve(dependency: string): Plugin | undefined; + + /** + * Returns `true` if the object meets the minimum standard to be considered a plugin. + * This means it must define the following properties: + * - `name` + * - `version` + * - `install` + * @method isPlugin + * @param obj {} The obj to test. + * @return {boolean} `true` if the object can be considered a plugin otherwise `false`. + */ + static isPlugin(obj: {}): boolean; + + /** + * Returns a pretty printed plugin name and version. + * @method toString + * @param plugin {} The plugin. + * @return {string} Pretty printed plugin name and version. + */ + static toString(plugin: string | Plugin): string; + + /** + * Returns `true` if `plugin.for` is applicable to `module` by comparing against `module.name` and `module.version`. + * If `plugin.for` is not specified then it is assumed to be applicable. + * The value of `plugin.for` is a string of the format `'module-name'` or `'module-name@version'`. + * @method isFor + * @param plugin {} The plugin. + * @param module {} The module. + * @return {boolean} `true` if `plugin.for` is applicable to `module`, otherwise `false`. + */ + static isFor(plugin: Plugin, module: {name?: string, [_: string]: any}): boolean; + + /** + * Installs the plugins by calling `plugin.install` on each plugin specified in `plugins` if passed, otherwise `module.uses`. + * For installing plugins on `Matter` see the convenience function `Matter.use`. + * Plugins may be specified either by their name or a reference to the plugin object. + * Plugins themselves may specify further dependencies, but each plugin is installed only once. + * Order is important, a topological sort is performed to find the best resulting order of installation. + * This sorting attempts to satisfy every dependency's requested ordering, but may not be exact in all cases. + * This function logs the resulting status of each dependency in the console, along with any warnings. + * - A green tick βœ… indicates a dependency was resolved and installed. + * - An orange diamond πŸ”Ά indicates a dependency was resolved but a warning was thrown for it or one if its dependencies. + * - A red cross ❌ indicates a dependency could not be resolved. + * Avoid calling this function multiple times on the same module unless you intend to manually control installation order. + * @method use + * @param module {} The module install plugins on. + * @param [plugins=module.uses] {} The plugins to install on module (optional, defaults to `module.uses`). + */ + static use( + module: {uses?: (Plugin | string)[]; [_: string]: any}, + plugins: (Plugin | string)[] + ): void; + + /** + * Recursively finds all of a module's dependencies and returns a flat dependency graph. + * @method dependencies + * @param module {} The module. + * @return {object} A dependency graph. + */ + static dependencies( + module: Dependency, + tracked?: {[_: string]: string[]} + ): {[_: string]: string[]} | string | undefined + + /** + * Parses a dependency string into its components. + * The `dependency` is a string of the format `'module-name'` or `'module-name@version'`. + * See documentation for `Plugin.versionParse` for a description of the format. + * This function can also handle dependencies that are already resolved (e.g. a module object). + * @method dependencyParse + * @param dependency {string} The dependency of the format `'module-name'` or `'module-name@version'`. + * @return {object} The dependency parsed into its components. + */ + static dependencyParse(dependency: Dependency) : {name: string, range: string}; + + /** + * Parses a version string into its components. + * Versions are strictly of the format `x.y.z` (as in [semver](http://semver.org/)). + * Versions may optionally have a prerelease tag in the format `x.y.z-alpha`. + * Ranges are a strict subset of [npm ranges](https://docs.npmjs.com/misc/semver#advanced-range-syntax). + * Only the following range types are supported: + * - Tilde ranges e.g. `~1.2.3` + * - Caret ranges e.g. `^1.2.3` + * - Exact version e.g. `1.2.3` + * - Any version `*` + * @method versionParse + * @param range {string} The version string. + * @return {object} The version range parsed into its components. + */ + static versionParse(range: string) : { + isRange: boolean, + version: string, + range: string, + operator: string + parts: number[], + prerelease: string, + number: number + }; + + /** + * Returns `true` if `version` satisfies the given `range`. + * See documentation for `Plugin.versionParse` for a description of the format. + * If a version or range is not specified, then any version (`*`) is assumed to satisfy. + * @method versionSatisfies + * @param version {string} The version string. + * @param range {string} The range string. + * @return {boolean} `true` if `version` satisfies `range`, otherwise `false`. + */ + static versionSatisfies(version: string, range: string): boolean; + } +} + +/*****************************\ +|* * * * Ct.js additions * * *| +\****************************/ + +type MatterEventCallback = (event: Matter.IEventCollision) => void; + +declare namespace ct { + namespace matter { + function on(event: string, callback: MatterEventCallback): void; + function off(event: string, callback: MatterEventCallback): void; + /** + * Moves a copy to a new position without changing its velocity. + */ + function teleport(copy: Copy, x: number, y: number): void; + /** + * Applies a force onto a copy. The resulting velocity depends on object's mass and friction. + * You can optionally define a point from which the force is applied to make the copy spin. + */ + function push(copy: Copy, forceX: number, forceY: number, fromX?: number, fromY?: number): void; + /** + * Sets copy's angular velocity. + */ + function spin(copy: Copy, speed: number): void; + /** + * Rotates a copy to the defined angle. + */ + function rotate(copy: Copy, angle: number): void; + /** + * Rotates a copy by the defined angle. + */ + function rotateBy(copy: Copy, angle: number): void; + /** + * Scales the copy and its physical object. + */ + function scale(copy: Copy, x: number, y: number): void; + /** + * Sets copy's velocity instantly. + */ + function launch(copy: Copy, hspeed: number, vspeed: number): void; + + /** + * Pins a copy in place, making it spin around its center of mass + * but preventing any other movement. + */ + function pin(copy: Copy): void; + /** + * Ties a copy to a specific position in space, making it swing around it. + */ + function tie(copy: Copy, position: ISimplePoint, stiffness?: number, damping?: number): void; + /** + * Puts a copy on a rope. It is similar to `ct.matter.tie`, but the length of a rope is defined + * explicitly, and starts from the copy's current position. + */ + function rope(copy: Copy, length: number, stiffness?: number, damping?: number): void; + /** + * Ties two copies together with a rope. + */ + function tieTogether(copy1: Copy, copy2: Copy, stiffness?: number, damping?: number): void; + + /** + * Calculates a good approximation of an impact two colliding copies caused to each other. + */ + function getImpact(pair: Matter.IPair): number; + } +} diff --git a/src/examples/2DPhysics.ict b/src/examples/2DPhysics.ict new file mode 100644 index 000000000..cf99e65be --- /dev/null +++ b/src/examples/2DPhysics.ict @@ -0,0 +1,1157 @@ +ctjsVersion: 1.5.1 +notes: /* empty */ +libs: + fittoscreen: + mode: scaleFit + mouse: {} + sound.howler: {} + matter: + matterUseStaticDeltaTime: true +textures: + - name: Star + untill: 0 + grid: + - 1 + - 1 + axis: + - 64.5 + - 67.5 + marginx: 0 + marginy: 0 + imgWidth: 128 + imgHeight: 128 + width: 128 + height: 128 + offx: 0 + offy: 0 + origname: ibfbb5de9-367a-438a-8cab-ea88479c51be.png + shape: strip + left: 0 + right: 787 + top: 0 + bottom: 692 + uid: bfbb5de9-367a-438a-8cab-ea88479c51be + padding: 1 + source: 'D:\JettyCat\img\ic9a62030-151c-429e-a2ce-3e829a79cb05.png_prev@2.png' + lastmod: 1615542695704 + stripPoints: + - x: -0.75 + 'y': -65.75 + - x: 11 + 'y': -58.25 + - x: 24 + 'y': -33.25 + - x: 60.75 + 'y': -24.5 + - x: 64.5 + 'y': -10 + - x: 39.75 + 'y': 15.25 + - x: 44.25 + 'y': 49.25 + - x: 29 + 'y': 59.75 + - x: -0.5 + 'y': 42.75 + - x: -29.921346167118823 + 'y': 59.885763470431215 + - x: -45.2195710804483 + 'y': 49.45615108543885 + - x: -40.87630005786883 + 'y': 15.43577488492599 + - x: -65.74239608827918 + 'y': -9.699902313160635 + - x: -62.05925582802869 + 'y': -24.21702936445152 + - x: -25.349968411049232 + 'y': -33.13629039536623 + - x: -12.465313208428675 + 'y': -58.19593245804509 + closedStrip: true + symmetryStrip: true + - name: WoodenPlank + untill: 0 + grid: + - 1 + - 1 + axis: + - 110 + - 35 + marginx: 0 + marginy: 0 + imgWidth: 220 + imgHeight: 70 + width: 220 + height: 70 + offx: 0 + offy: 0 + origname: i9afd54d7-1265-4ba0-b5de-5226b3c7e2fb.png + shape: rect + left: 110 + right: 110 + top: 35 + bottom: 35 + uid: 9afd54d7-1265-4ba0-b5de-5226b3c7e2fb + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Wood elements\elementWood012.png' + lastmod: 1616891166294 + - name: WoodenSquare + untill: 0 + grid: + - 1 + - 1 + axis: + - 35 + - 35 + marginx: 0 + marginy: 0 + imgWidth: 70 + imgHeight: 70 + width: 70 + height: 70 + offx: 0 + offy: 0 + origname: i41f7c3b3-abc8-4940-8919-9114a884d7f2.png + shape: rect + left: 35 + right: 35 + top: 35 + bottom: 35 + uid: 41f7c3b3-abc8-4940-8919-9114a884d7f2 + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Wood elements\elementWood010.png' + lastmod: 1616891205921 + tiled: false + - name: alienPink_square + untill: 0 + grid: + - 1 + - 1 + axis: + - 35 + - 35 + marginx: 0 + marginy: 0 + imgWidth: 70 + imgHeight: 70 + width: 70 + height: 70 + offx: 0 + offy: 0 + origname: i6c8a3edf-d5fe-4916-b63c-2201c099d28f.png + shape: rect + left: 35 + right: 35 + top: 35 + bottom: 35 + uid: 6c8a3edf-d5fe-4916-b63c-2201c099d28f + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Aliens\alienPink_square.png' + lastmod: 1616890309571 + - name: alienGreen_round + untill: 0 + grid: + - 1 + - 1 + axis: + - 35 + - 35 + marginx: 0 + marginy: 0 + imgWidth: 70 + imgHeight: 70 + width: 70 + height: 70 + offx: 0 + offy: 0 + origname: i8a5f385a-d547-4d2d-9641-b1856738c62a.png + shape: circle + left: 35 + right: 35 + top: 35 + bottom: 35 + uid: 8a5f385a-d547-4d2d-9641-b1856738c62a + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Aliens\alienGreen_round.png' + r: 35 + lastmod: 1616890345324 + - name: colored_grass + untill: 0 + grid: + - 1 + - 1 + axis: + - 0 + - 0 + marginx: 0 + marginy: 0 + imgWidth: 1024 + imgHeight: 1024 + width: 1024 + height: 1024 + offx: 0 + offy: 0 + origname: if6a40cf4-e9c0-47ff-afa8-6cdc259ef17e.png + shape: rect + left: 0 + right: 1024 + top: 0 + bottom: 1024 + uid: f6a40cf4-e9c0-47ff-afa8-6cdc259ef17e + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Backgrounds\blue_grass.png' + tiled: true + lastmod: 1616891207354 + - name: grass + untill: 0 + grid: + - 1 + - 1 + axis: + - 35 + - 35 + marginx: 0 + marginy: 0 + imgWidth: 70 + imgHeight: 70 + width: 70 + height: 70 + offx: 0 + offy: 0 + origname: i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png + shape: rect + left: 35 + right: 35 + top: 35 + bottom: 35 + uid: 375a4a5e-64e5-410f-aa6f-3bcfc9804519 + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Other\grass.png' + lastmod: 1615556407594 + - name: WoodenTriangle + untill: 0 + grid: + - 1 + - 1 + axis: + - 70 + - 43 + marginx: 0 + marginy: 0 + imgWidth: 140 + imgHeight: 70 + width: 140 + height: 70 + offx: 0 + offy: 0 + origname: i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png + shape: strip + left: 70 + right: 70 + top: 35 + bottom: 35 + uid: 0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Wood elements\elementWood054.png' + stripPoints: + - x: -0.0 + 'y': -43 + - x: 70 + 'y': 26 + - x: -70 + 'y': 26 + closedStrip: true + lastmod: 1616891300000 + - name: elementMetal013 + untill: 0 + grid: + - 1 + - 1 + axis: + - 110 + - 35 + marginx: 0 + marginy: 0 + imgWidth: 220 + imgHeight: 70 + width: 220 + height: 70 + offx: 0 + offy: 0 + origname: i56ab43ae-1b27-4b5e-9e49-fbbe74d42597.png + shape: rect + left: 110 + right: 110 + top: 35 + bottom: 35 + uid: 56ab43ae-1b27-4b5e-9e49-fbbe74d42597 + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Metal elements\elementMetal013.png' + lastmod: 1615557926300 + - name: Slingshot + untill: 0 + grid: + - 2 + - 1 + axis: + - 70 + - 60 + marginx: 0 + marginy: 0 + imgWidth: 304 + imgHeight: 221 + width: 152 + height: 221 + offx: 0 + offy: 0 + origname: i37dfad62-8372-4804-8416-765039343b61.png + shape: rect + left: 70 + right: 82 + top: 60 + bottom: 161 + uid: 37dfad62-8372-4804-8416-765039343b61 + padding: 1 + source: 'C:\Users\Master\Desktop\Slingshot.png' + lastmod: 1616892923665 + - name: elementMetal001 + untill: 0 + grid: + - 1 + - 1 + axis: + - 35 + - 35 + marginx: 0 + marginy: 0 + imgWidth: 70 + imgHeight: 70 + width: 70 + height: 70 + offx: 0 + offy: 0 + origname: ic4d4030a-b640-44be-bded-26a5c030f542.png + shape: circle + left: 35 + right: 35 + top: 35 + bottom: 35 + uid: c4d4030a-b640-44be-bded-26a5c030f542 + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Metal elements\elementMetal001.png' + r: 35 + lastmod: 1615704333828 + - name: elementMetal000 + untill: 0 + grid: + - 1 + - 1 + axis: + - 35 + - 35 + marginx: 0 + marginy: 0 + imgWidth: 70 + imgHeight: 70 + width: 70 + height: 70 + offx: 0 + offy: 0 + origname: i3a4eba8d-8b33-478e-95e5-3833099c1a03.png + shape: strip + left: 0 + right: 140 + top: 0 + bottom: 70 + uid: 3a4eba8d-8b33-478e-95e5-3833099c1a03 + padding: 3 + source: 'D:\Downloads\physicspack\PNG\Metal elements\elementMetal002.png' + lastmod: 1616892926979 + stripPoints: + - x: -35 + 'y': -35 + - x: 35 + 'y': 35 + - x: -35 + 'y': 35 + closedStrip: true + - name: Confetti + untill: 0 + grid: + - 2 + - 2 + axis: + - 0 + - 0 + marginx: 0 + marginy: 0 + imgWidth: 1024 + imgHeight: 1024 + width: 512 + height: 512 + offx: 0 + offy: 0 + origname: if878c588-3c26-486b-a20f-b23ffab4d579.png + shape: rect + left: 0 + right: 512 + top: 0 + bottom: 512 + uid: f878c588-3c26-486b-a20f-b23ffab4d579 + padding: 3 + source: data\particles\Confetti_2x2.png + lastmod: 1616895609998 + - name: WoodenSquare_Damaged + untill: 0 + grid: + - 1 + - 1 + axis: + - 35 + - 35 + marginx: 0 + marginy: 0 + imgWidth: 70 + imgHeight: 70 + width: 70 + height: 70 + offx: 0 + offy: 0 + origname: iabdeba09-af47-4b39-a276-fa2e59b164aa.png + shape: rect + left: 35 + right: 35 + top: 35 + bottom: 35 + uid: abdeba09-af47-4b39-a276-fa2e59b164aa + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Wood elements\elementWood045.png' + lastmod: 1616891224599 + - name: WoodenTriangle_Damaged + untill: 0 + grid: + - 1 + - 1 + axis: + - 70 + - 43 + marginx: 0 + marginy: 0 + imgWidth: 140 + imgHeight: 70 + width: 140 + height: 70 + offx: 0 + offy: 0 + origname: ifc52b049-910a-4e96-adcf-378553c0818c.png + shape: strip + left: 70 + right: 70 + top: 43 + bottom: 27 + uid: fc52b049-910a-4e96-adcf-378553c0818c + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Wood elements\elementWood043.png' + lastmod: 1616891305648 + stripPoints: + - x: 0 + 'y': -43 + - x: 70 + 'y': 26 + - x: -70 + 'y': 26 + closedStrip: true + - name: WoodenPlank_Damaged + untill: 0 + grid: + - 1 + - 1 + axis: + - 110 + - 35 + marginx: 0 + marginy: 0 + imgWidth: 220 + imgHeight: 70 + width: 220 + height: 70 + offx: 0 + offy: 0 + origname: i03040255-0c4f-4605-aa7f-8a146a75675f.png + shape: rect + left: 110 + right: 110 + top: 35 + bottom: 35 + uid: 03040255-0c4f-4605-aa7f-8a146a75675f + padding: 1 + source: 'D:\Downloads\physicspack\PNG\Wood elements\elementWood047.png' + lastmod: 1616891232841 +skeletons: [] +types: + - name: Alien_Launched + depth: 0 + oncreate: |- + this.tag = 'Alien'; + + this.timeout = ct.speed * 5; + onstep: |- + this.timeout--; + if (this.timeout <= 0) { + this.kill = true; + } + ondraw: '' + ondestroy: |- + ct.emitters.fire('FX_Burst', this.xprev, this.yprev, { + tint: 0x6EBA9D + }); + texture: 8a5f385a-d547-4d2d-9641-b1856738c62a + extends: + matterEnable: true + matterRestitution: 0.7 + matterMass: 5 + matterDensity: 0.003 + uid: 556178d8-6886-4718-a5dd-b7dbf74e4547 + lastmod: 1616894158784 + - name: AlienTarget + depth: 0 + oncreate: this.tag = 'Alien'; + onstep: this.move(); + ondraw: '' + ondestroy: |- + ct.emitters.fire('FX_Burst', this.xprev, this.yprev, { + tint: 0xFF85DA + }); + texture: 6c8a3edf-d5fe-4916-b63c-2201c099d28f + extends: + matterEnable: true + uid: beb36708-4cce-4b50-a3c3-f7e2edc1d994 + lastmod: 1616890301062 + - name: WoodenSquare + depth: 0 + oncreate: |- + this.tag = 'Wooden'; + this.damage = 0; + onstep: |- + if (this.damage > 150) { + this.kill = true; + } + ondraw: |- + if (this.damage > 75 && this.tex !== 'WoodenSquare_damaged') { + this.tex = 'WoodenSquare_Damaged'; + } + ondestroy: |- + ct.emitters.fire('FX_Burst', this.xprev, this.yprev, { + tint: 0xAB763E + }); + texture: 41f7c3b3-abc8-4940-8919-9114a884d7f2 + extends: + matterEnable: true + matterRestitution: 0.4 + matterFriction: 10 + matterFrictionStatic: 3.1 + matterFrictionAir: 0.05 + matterMass: 1 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + lastmod: 1616891394690 + - name: WoodenPlank + depth: 0 + oncreate: |- + this.tag = 'Wooden'; + this.damage = 0; + onstep: |- + if (this.damage > 300) { + this.kill = true; + } + ondraw: |- + if (this.damage > 100 && this.tex !== 'WoodenPlank_damaged') { + this.tex = 'WoodenPlank_Damaged'; + } + ondestroy: |- + ct.emitters.fire('FX_Burst', this.xprev, this.yprev, { + tint: 0xAB763E + }); + texture: 9afd54d7-1265-4ba0-b5de-5226b3c7e2fb + extends: + matterEnable: true + matterRestitution: 0.4 + matterMass: 1 + matterFriction: 10 + matterFrictionStatic: 3.1 + matterFrictionAir: 0.05 + uid: 256006c5-c853-40a5-a277-3bc1df64d226 + lastmod: 1616895635325 + - name: grass + depth: 0 + oncreate: '' + onstep: this.move(); + ondraw: '' + ondestroy: '' + texture: 375a4a5e-64e5-410f-aa6f-3bcfc9804519 + extends: + matterEnable: true + matterStatic: true + matterMass: 10000 + matterFriction: 15 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + lastmod: 1615557554543 + - name: WoodenTriangle + depth: 0 + oncreate: |- + this.tag = 'Wooden'; + this.damage = 0; + onstep: |- + if (this.damage > 250) { + this.kill = true; + } + ondraw: |- + if (this.damage > 100 && this.tex !== 'WoodenTriangle_damaged') { + this.tex = 'WoodenTriangle_Damaged'; + } + ondestroy: |- + ct.emitters.fire('FX_Burst', this.xprev, this.yprev, { + tint: 0xAB763E + }); + texture: 0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b + extends: + matterEnable: true + matterFriction: 10 + matterFrictionStatic: 3.1 + matterFrictionAir: 0.05 + matterMass: 1 + matterFixPivot: + - 0 + - 11 + uid: 7f79638a-a14f-4ec8-8826-981ff954c738 + lastmod: 1616895639247 + - name: Slingshot + depth: 0 + oncreate: '' + onstep: |- + if (ct.actions.Pointer.pressed && ct.mouse.hovers(this)) { + ct.types.copy('Alien_Dragging', this.x, this.y); + } + ondraw: '' + ondestroy: '' + texture: 37dfad62-8372-4804-8416-765039343b61 + extends: {} + uid: 070d18cf-978a-4874-ba4d-1aadd0d4f58a + lastmod: 1616892056278 + - name: Alien_Dragging + depth: 0 + oncreate: '' + onstep: |- + if (ct.actions.Pointer.released) { + const speedModifier = 0.3, + maxSpeed = 50; + this.kill = true; + const missile = ct.types.copy('Alien_Launched', this.x, this.y); + const slingshot = ct.types.list['Slingshot'][0]; + var hspeed = (slingshot.x - this.x) * speedModifier, + vspeed = (slingshot.y - this.y) * speedModifier, + l = ct.u.pdc(0, 0, hspeed, vspeed); + if (l > maxSpeed) { + hspeed /= l / maxSpeed; + vspeed /= l / maxSpeed; + } + ct.matter.launch(missile, hspeed, vspeed); + } + ondraw: | + const slingshot = ct.types.list['Slingshot'][0], + maxLength = 150; + let dx = ct.mouse.x - slingshot.x, + dy = ct.mouse.y - slingshot.y, + l = ct.u.pdc(0, 0, dx, dy); + + if (l > maxLength) { + dx /= l / maxLength, + dy /= l / maxLength; + } + + this.x = slingshot.x + dx; + this.y = slingshot.y + dy; + ondestroy: '' + texture: 8a5f385a-d547-4d2d-9641-b1856738c62a + extends: + matterEnable: false + matterRestitution: 0.7 + uid: 5b9b41e7-52db-44a2-b5d2-016407e259dc + lastmod: 1616891549758 + - name: elementMetal013 + depth: 0 + oncreate: this.tag = 'Metal'; + onstep: this.move(); + ondraw: '' + ondestroy: '' + texture: 56ab43ae-1b27-4b5e-9e49-fbbe74d42597 + extends: + matterEnable: true + matterConstraint: pinpoint + matterFrictionStatic: 0.1 + matterFrictionAir: 0.1 + matterDensity: 0.01 + matterRestitution: 0.6 + uid: 8e4e1fe0-54f6-4c9f-a468-99a94b05803f + lastmod: 1616892744865 + - name: elementMetal011 + depth: 0 + oncreate: this.tag = 'Metal'; + onstep: this.move(); + ondraw: '' + ondestroy: '' + texture: c4d4030a-b640-44be-bded-26a5c030f542 + extends: + matterMass: 10 + matterEnable: true + matterDensity: 0.005 + matterRestitution: 0.6 + uid: 54e149bb-f36a-48c0-98f5-181451bac20c + lastmod: 1616892752365 + - name: elementMetal000 + depth: 0 + oncreate: this.tag = 'Metal'; + onstep: this.move(); + ondraw: '' + ondestroy: '' + texture: 3a4eba8d-8b33-478e-95e5-3833099c1a03 + extends: + matterStatic: true + matterEnable: true + matterDensity: 0.005 + matterRestitution: 0.5 + matterFixPivot: + - 4 + - 4 + uid: 82c03796-7419-4f22-944c-78c1209c6874 + lastmod: 1616892936227 +sounds: + - name: Impact_Wooden + uid: ccfe7f50-d4d3-4570-95e9-75d5d73bef0a + origname: sccfe7f50-d4d3-4570-95e9-75d5d73bef0a.ogg + lastmod: 1616407436515 + - name: Impact_Metal + uid: cbca4dd5-7369-48d5-b3b7-e9b0055e2eda + origname: scbca4dd5-7369-48d5-b3b7-e9b0055e2eda.ogg + lastmod: 1616407498544 + - name: Impact_Alien + uid: 3142b705-6de5-4f57-9b70-c9bc3fb4cdad + origname: s3142b705-6de5-4f57-9b70-c9bc3fb4cdad.ogg + lastmod: 1616407549522 +styles: [] +rooms: + - name: HolericAliens + oncreate: | + // Listen for collisions in the world + ct.matter.on('collisionStart', e => { + // Loop over every collision in a frame + for (const pair of e.pairs) { + // Get how strong the impact was + // We will use it for damage calculation to aliens and wooden blocks + const impact = ct.matter.getImpact(pair); + + // Each pair has bodyA and bodyB β€” two objects that has collided. + // This little loop applies checks for both bodies + const bodies = [pair.bodyA, pair.bodyB]; + for (const body of bodies) { + // Does a body belong to a copy of type "AlienTarget"? + if (body.copy.type === 'AlienTarget') { + // If it was too strong, destroy the alien. + if (impact > 75) { + body.copy.kill = true; + } + } + + // Apply cumulative damage for wooden blocks + // Tags are set in types' On Create code + if (body.copy.tag === 'Wooden') { + // Ignore weak strikes + if (impact >= 25) { + // I apply a damage multiplier 0.35 bc initial values turned out to be too destructive, + // and I'm too lazy to walk over three blocks and change damage thresholds there. + body.copy.damage += impact * 0.35; + // Texture change and destruction are coded in types' On Step and Draw events. + } + } + + // Play sounds on considerable collisions + if (impact >= 25) { + if (body.copy.tag) { + ct.sound.spawn('Impact_' + body.copy.tag); + } + } + } + } + }); + onstep: '' + ondraw: '' + onleave: '' + width: 1640 + height: 900 + backgrounds: + - depth: -10 + texture: f6a40cf4-e9c0-47ff-afa8-6cdc259ef17e + extends: {} + copies: + - x: 905 + 'y': 770 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 905 + 'y': 700 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 905 + 'y': 630 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 980 + 'y': 560 + uid: 256006c5-c853-40a5-a277-3bc1df64d226 + exts: {} + - x: 1054 + 'y': 770 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 1054 + 'y': 700 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 1054 + 'y': 630 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 980 + 'y': 766 + uid: beb36708-4cce-4b50-a3c3-f7e2edc1d994 + exts: {} + - x: 1050 + 'y': 490 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 980 + 'y': 490 + uid: beb36708-4cce-4b50-a3c3-f7e2edc1d994 + exts: {} + - x: 1120 + 'y': 350 + uid: 256006c5-c853-40a5-a277-3bc1df64d226 + exts: {} + - x: 1190 + 'y': 420 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 1190 + 'y': 490 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 1190 + 'y': 700 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 1190 + 'y': 770 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 1260 + 'y': 630 + uid: 256006c5-c853-40a5-a277-3bc1df64d226 + exts: {} + - x: 1330 + 'y': 700 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 1330 + 'y': 770 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 1120 + 'y': 280 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 1190 + 'y': 560 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 1288 + 'y': 559 + uid: beb36708-4cce-4b50-a3c3-f7e2edc1d994 + exts: {} + - x: 910 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 980 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1050 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1120 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1190 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1260 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1330 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1400 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1470 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1540 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1610 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 840 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 770 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 700 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 560 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 630 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 490 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 420 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 350 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 280 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 210 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 140 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 70 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 0 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1680 + 'y': 840 + uid: 9150bbba-a1a8-4fc8-837d-68589f3d5074 + exts: {} + - x: 1120 + 'y': 210 + uid: 7f79638a-a14f-4ec8-8826-981ff954c738 + exts: {} + - x: 1024 + 'y': 278 + uid: beb36708-4cce-4b50-a3c3-f7e2edc1d994 + exts: {} + - x: 700 + 'y': 140 + uid: 8e4e1fe0-54f6-4c9f-a468-99a94b05803f + exts: {} + - x: 630 + 'y': 70 + uid: 54e149bb-f36a-48c0-98f5-181451bac20c + exts: {} + - x: 770 + 'y': 70 + uid: 54e149bb-f36a-48c0-98f5-181451bac20c + exts: {} + - x: 225 + 'y': 646 + uid: 070d18cf-978a-4874-ba4d-1aadd0d4f58a + exts: {} + - x: 568 + 'y': 770 + uid: 7f79638a-a14f-4ec8-8826-981ff954c738 + exts: {} + - x: 700 + 'y': 770 + uid: 7f79638a-a14f-4ec8-8826-981ff954c738 + exts: {} + - x: 1050 + 'y': 420 + uid: 6fd118b6-633f-4beb-93ec-2dd5c965fdd5 + exts: {} + - x: 630 + 'y': 700 + uid: 54e149bb-f36a-48c0-98f5-181451bac20c + exts: {} + tx: 2.2 + ty: 1 + tiles: + - depth: -10 + tiles: [] + extends: {} + uid: 125036f1-3a14-4fc3-ae95-2d29a5c56244 + thumbnail: 2d29a5c56244 + extends: + matterGravity: + - 0 + - 1 + gridX: 70 + gridY: 70 + lastmod: 1616896076747 +actions: + - name: Pointer + methods: + - code: mouse.Left +emitterTandems: + - name: FX_Burst + origname: pta85a19f1cde1 + emitters: + - texture: f878c588-3c26-486b-a20f-b23ffab4d579 + openedTabs: + - texture + - spawning + - gravity + - scaling + - colors + settings: + alpha: + list: + - value: 0 + time: 0 + - time: 0.15079365079365079 + value: 0.75 + - value: 1 + time: 0.5 + - value: 0 + time: 1 + isStepped: false + scale: + list: + - value: 0.18000000000000002 + time: 0 + - value: 0.039999999999999994 + time: 1 + isStepped: false + color: + list: + - value: FF85DA + time: 0 + - time: 0.15079365079365079 + value: FFFFFF + - value: FFFFFF + time: 0.5 + - value: FFFFFF + time: 1 + isStepped: false + blendMode: normal + speed: + list: + - value: 675 + time: 0 + - value: 100 + time: 1 + isStepped: false + startRotation: + min: 0 + max: 360 + rotationSpeed: + min: 0 + max: 0 + rotationAcceleration: 0 + lifetime: + min: 0.35 + max: 1 + frequency: 0.008 + spawnChance: 1 + particlesPerWave: 1 + angleStart: 270 + emitterLifetime: 0.1 + maxParticles: 1000 + maxSpeed: 0 + pos: + x: 0 + 'y': 0 + acceleration: + x: 0 + 'y': 1000 + addAtBack: false + spawnType: circle + spawnCircle: + x: 0 + 'y': 0 + r: 41 + delay: 0 + particleSpacing: 360 + minimumScaleMultiplier: 0.26 + minimumSpeedMultiplier: 0.15 + uid: 02de95c8-e4d6-41a2-aced-793d64657985 + showShapeVisualizer: false + previewTexture: 6c8a3edf-d5fe-4916-b63c-2201c099d28f +scripts: [] +starting: 0 +settings: + authoring: + author: '' + site: '' + title: '' + version: + - 0 + - 0 + - 0 + versionPostfix: '' + rendering: + usePixiLegacy: true + maxFPS: 60 + pixelatedrender: false + highDensity: true + desktopMode: maximized + export: + windows: true + linux: true + mac: true + functionWrap: false + codeModifier: none + branding: + icon: -1 + accent: '#446adb' + invertPreloaderScheme: true + fps: 30 +fonts: [] +palette: + - '#FF85DA' + - '#6EBA9D' + - '#9E703F' +startroom: 125036f1-3a14-4fc3-ae95-2d29a5c56244 diff --git a/src/examples/2DPhysics/img/i03040255-0c4f-4605-aa7f-8a146a75675f.png b/src/examples/2DPhysics/img/i03040255-0c4f-4605-aa7f-8a146a75675f.png new file mode 100644 index 0000000000000000000000000000000000000000..e10f7c7d91c8e21103453d8b36cb060b4ceb2b92 GIT binary patch literal 3065 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 Hu0mjfCPv3= literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i03040255-0c4f-4605-aa7f-8a146a75675f.png_prev.png b/src/examples/2DPhysics/img/i03040255-0c4f-4605-aa7f-8a146a75675f.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..43b87a41b78612ffbe3601d4c801ae802a8c29a0 GIT binary patch literal 1294 zcmV+p1@ZccP)z96O2ANN$@ZC8(*=7y|qTF3Lw6gw*~EB-G@V6p0^#5D397zW@+~ zO06I?q%91`30*Y0|Ey*Ynjmcf+5!VkfVRMZ3xM7Nv;}Aj3^)PW0s}4pdJE7Npe+Dkd1;}1el!$} zhI|lsw&zUJSq0nwDsO-O?b^@BgRguro17evZ%vE@d!F;OfM&IZqGDikzo7hO{}y3Z zmKM}^F30?aZoJY>Y-cM-EBd)FzPh(Z5-q=f^~!iG@L+P-_e%4g7F-Yn{J4?ZWInsK zXum!Y@MIIaIc;ONV7_Zwnwf}%w=Ty0j`Et84T0;OZLOy@Fn4b++OH+N0YFasPqwmM z)~)31gmc+*qJ%<8g)DK7z@0Yv&$N0HfNEIKj9SZl+Xk2(foB*7RhxxvRl&QcW~OW* zCUe-!>zEjl9E0$SECON!U>(4nn+xB~P0H{21QxnkgI=|t1+ADh>^!aDjp;ak**L_d zu|Urr4rK!f2sRE&28x;mmSGSJONjVhZo)oE!1_)RL$ZWnzjMQucm|TtSXT!?e%!f{ z`tn**{@AM7sF*hP3pyB<#r)I=o)%Q-MioAvh$n|C1hxi=XAzKiD7uA%qJawzdzs|q zI2PL(4f7XAP|;0j6%$siRekMBoK>nw4EbP~RmTcw3!v`GH&WlT9IjuRih*Yt+QeNsCpIYf`LRtJOQAYEIu5BqUug_x1BGFd|lF0qn$y}3=6Vf zgk@Q0<|pT94x(5xS^#`{eQxf2B6NQ&EI#u9vM{Sy7zsAaf+)auWE^K^bueKoqaisO zLQvwMYMlWkCi>_=MY}81v}zy^9Ws0Zhl=?e(y0JeZ!g-jN7rD}1PK6}Wx*WvWG?{_ zi`~gp>L!+q%g7ZJ2gn*zZI%a9Ywdq>cmkmgK;%#{IskC|9nb_&CJHE=bm5L?SdZn0Fh_Gb8Ke-jt6UJB;A}KQ!)?> zHO#e}kyA`WWf3^4@c#w?F|bY@|7C#6CLo`26ZS*_-BhXFTGM91q4Dx_J2nqgTu#Wa zjttL9oQ^!eNkFG_yH!9zts*%dM!u*zO&htzxATABRf?-$uSLlE<)zfaE9ZhUZex+h(!;b?y8F#q z*u4|E&E9A0D1bgIi09j(3h*3xpCzOU=(B=&z8$In&yn|8LaKm1D~RXYp$hOEd7mYu z3h1+fc)lH~0MC*4SwgCSJ}Zdl+o1~Z9C@E59C!u%2P{tmS1~cd&j0`b07*qoM6N<$ Eg4C5zI{*Lx literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i03040255-0c4f-4605-aa7f-8a146a75675f.png_prev@2.png b/src/examples/2DPhysics/img/i03040255-0c4f-4605-aa7f-8a146a75675f.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..c17f4a71e6bcf764cf46d040cc78b0dc23a52cb5 GIT binary patch literal 3407 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 M+J;)y8ul^&0c$x&-v9sr literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png b/src/examples/2DPhysics/img/i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png new file mode 100644 index 0000000000000000000000000000000000000000..dc5348d7f3450eee6a5b5af8adf0e9332496918c GIT binary patch literal 995 zcmV<9104K`P)ALz&-d?iZ&IkHc@LM6t$F+DuSS&Y}HmGMNMO*QYyFruP8NwiU?x7fQl}{ zCAb8aFy=in5GOOqna(?%+yB4=rA(O9nO|uxeV>sL)_BRn}CL8h2`hHSrgE(`k%AZ#_L`zU)N_$ylCWDu2_NBTCLRf z!}8(QF%v&D@))jM!LhXKQ+-&xXyh2sfW>*WIvt3`i$;zG4OpBncTNOiar3!V1r1nc z4TND>Km(T0SU>}o&{#kN7SOQ%t;Oo@UCo8kuQ;#_LL=vtb8`nWiskOP{m$E)Rdp<0 zG;$6(7icn$rPG>;4T~3zoC`D=$MR$IwDYMs9SXqfdc`EnV?M4OH|GLP#<9qa42$LA zr3n+zfW->S)60iUKm!&lEHAHBOh5w`-?UdI`>=S?$T6VF@M_HO+1sp*{f>%dYdIK; z7mXYn8BKT3vUl#_4r95!F!o1oz~=b_k+D3xaySr+o99<2BcqYM%ARHKph*DB{R;=3 zZyTo~VG#(!uxO!?y~>_JlOUGw8z-GkV=8tmQPD_bub@d7OW2zh1q*1v0vfQyj|DV2 z#`6Bg(O9tfp^BZtjBYTxSM@3V(VxwV2KDJmMElxO(yLqvaUMz0f-95@)WzQOD3NvMC zTjXPlrJVsRnrI4DY_#2OmONn5KvSrBZjGV%tYZNUSU>|7(0~Or#lZ6BdL`Fb{LskH zTqrbJMLxZwt9i!aH!v4=G&i%wMp{Kaesj)6NmMkMDK^sVW+^I`!ju5S*`NTLoS^{= zXutv*uz)5(G++UZ{Xae_7x~z9vk<+GnjI}R(k}9ea~cse2^Slsp__$dqy!+%4GN%1 z85%7tBwdLG4Ol<}7NU;^ETFMQqfz8T@`7eav5`iR56KUjfup$^bIv6Dv6|-w3$aEc zf2Y7gtV#gnIgR_NGg!zlG-74M{p6XDVG%2)r^S64O-NA8%j~X|zaDO=WiC)oM=~@;Zewp`Wpbzn Rf9?PP002ovPDHLkV1lj#y?6is literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png_prev.png b/src/examples/2DPhysics/img/i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..c5d23a2bf88358a8841311461af87537f2c7a676 GIT binary patch literal 1733 zcmV;$20HnPP)U%7E7&>+BxQG=z);IQVq6i?MUVHDyISPo@?h z7m6T6RSfz^CZkeVC_nb&Kt>^C6u=vgR@;k8xyhZCYe`I#LDv&)uWCk2H%TOhf#WzB z1P$j0@2HbwLeZzcWp0bi7yy)c+RokBRaHvriU@`=CpJDb-GDo@a_r;U0vZTBF%%5L z0H;{^^YWM~hlP!qazaJ`9D264mSX5XJe6yCP$iRiMbjY^R)D9;7=Wm(g40d`Mc@gL z5Ck9@2n^ndDw6#0(eL~E(%lo%4xq)mV=qO}zdls%Bne$H*2f+Cqy@lR`*u7|(bVZZH7-nuL`-psvKGtyeA}lI zfXV?mfu+q(;cPZI+dpb3vhu{or!M@mQV1&s(AwxeK+(kU-P_keBy2v=A@MV!meTgA z1Ry6AvP4n{HoG0pUAzs7tUce>6+E#L2rB~6>aBl)p~-JN6(t~xf+-F`)?juS;_oeo z0+s+Si4$m+gTT-@NV583o4@DVZiC-1je#g>Z+CPBk1Q6#q5-se>ksiP-Qlh%1~F=GcqsFtk~#5l zK?nky!U-(TL2zgSqN3c~=I=SW7zm32fVDK%e^O{;Ua#Hk09h1G|2q{`KsNwLJn;=~ zF##;+8CBN-ot+#!WtvX2Ec9KQf|-c)S$kK{n*fk_3Y$0Me*rXmJzqL()XTMH4v_Ld zd_4YzATS8R$P_wreAVF(ys{AF1pzcSy8SkmY}{Gt1R;s-r=tp>BYn0qKVJxffUXkO zByj@Ab8vnz1QAi~>gWo3Q$a`y09{*4eeN%KmVBw9ssy5usA)cWuSaKka^4YkIhJ0@ z997d}cM0hHFn{ni9=b1&LsV41W@>}4CcAbw0eH>hp`2TFzq5dQrh4NV5CwC?8=sYd zz;V+h#!7^&WG+gCrt45rV7^snISvMHOhaf^_#t!!_kJD-%w0v_9SHzVG&K=DFzjFF z;PzFx>>x{$DK6CMb;AGxT~_4L&`=aJqehk_ss-U`FmROIJ&fA3Q!2ef$W&pQiQ&-M+4Ac^M8FL}j0 zoWQUQT)a60p&2pI?(f-&Lc~zRQ~EuFu3~QMmQo&MY3`!(ejPMJ!_~1Uj7|twsG2|} zcDTuX=7AF1o=r{;WcmIUA5SVx({Mc`z(3=Wvsi1BdkDj@^7}R83+XZpV>rKBc@DB* z0muRLyjs$7KwhOI2f{t$o literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png_prev@2.png b/src/examples/2DPhysics/img/i0f0e39f3-05bd-47e1-88d2-7dbf7bc3596b.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..10f2177c3c78eeb7c0fde3eac274bb95cdef5dad GIT binary patch literal 4620 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 Bk5~Wz literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png b/src/examples/2DPhysics/img/i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png new file mode 100644 index 0000000000000000000000000000000000000000..5f9178456e3c4a92c2316b4b6a1418cb7b4729b5 GIT binary patch literal 2606 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 literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png_prev.png b/src/examples/2DPhysics/img/i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..7f2c1af0452ddcad4eb5a975bde831387cf96119 GIT binary patch literal 3025 zcmV;?3oi7DP)>i*a?4B2f(44ihJRoQScE8`2nn)d$xi?& z!U*xqk_BuGgd~nh{21FabMNhUq3={zzpK0F&Xo|dYcv{rrtj(Vedjx;s!o;o?k}!M zdV+`e-Y=hjvI+Rf z7z6<@=tef}e>|h*`IuJc6UuF693;T_6e0i|_TVTSc4CY6?)mK3(WQt|FK0X2=9F}U z9c>f#txVATkxPq59vwcnro_)UVDJ%kbEH};-5LaFC&8pPI6HFanSbok-u-}`{Pc6j z^yt};l0X56aVWyXvUU~FV-Fxwbf98&CkPG{oh<1=b zXmH}v!3T4?aA(dc;30^@MM8I9T+;ESkP=@N@Or>tQ+)q-*Ja&F81zpZ+W&Y?`*&vK zL^glWay-hjlt|{x=*fkMqjs{d9IhLOVI3h|>2-uIeh5Mx<)fv3%3Kw|aFNo3=a;3z z^bDcN<wKSj4s}uk-u~~ zwE*7r7i2pgCE*EWX`}+<g=VDeE z$j}ZehKUt)>E8?5yBm~*YVeLXXOf`SBs!ri3Dw6qU@0r%@al@|83zIcxNDm}b8DCO z?gdPQ9t!+|EZZ%ACuL4)yl#*H2bbp~`sC$fo+a=yfWQ2&1?}INlec!t8d8DB8bGrp zDNEu83HVbyOX={+itb)tG6@I@s{FIJc4_gbki3!;`{{Ty5~A`y&l2`ilYmD7{OD3h z|9SbCl37OLXTSQQy!!WZ9CaNbj=U3)8#KXd7WzoQ8X*bPEOB%Y(Y3#wYow9#Q%AlI zzO>pUp(fvtzVMj#?)zNj-O%E>UWKq9ytc|DgiFd&r34Hp3~IRj%3~70{=rwI&h;|# zob$UZd(-Gk2R3d?uIg3_C#z4lWpA?EP6sXD+{t;?D64z}ypp|Xa5{v?Wlhk}p*Dzj zZ+x|}5VzeKd9&Syl#G^1vc!^T#f8x{FoPiH>#1b6Nrb4OFzQO)`z%L(&eCrb9)n@1 zHeMf7maH33)CdqP#1dNlAvJd8*7bF~)No56O8Rcn2gCxkD#2>%iB8SHpEvklanhg{&*s16I z*jfO#@&q=Uy~hRt?5Gz^Tm`CEG6_jUGV5Vl2ob>#F9Is!}ngkA1FL>3e2chs;57_hlfPFS8LHyVfDT*OR;+lDQ^``?jgEZJqBfquIXz| z;=OlX?<|Cdz-t3TB=qN=B;-84>rtvdi%6j!;CpC)Ur##|c>T;62^|7*93sR~Z{*v+ zESwzq{Yt1m%WZRj=V@qorN<<=9xFjNR!xL5_$Va!JT$oN@3`dpey4~x2tWme?6WkT zblQWLj*$SsaI!CzOit^lf!zN5l{6K6^$w7LmR!&vp(FrpqbLOU@dL$<61XPJ$Z_lI zd#n+1&dt++Z%Y>rH#cjTK~jptbtmTsWl;PPO7MfA+^jJHC?1$Tpny#Wik?54l~W`N z9-Q0+e3t}tdsVUJscyXSwF?)8jKJoz7f z<|zSF6@VJF<*Pike$aL$a2j7HDF8P{Y+GHD%JS`G44ia|HG0bD|Jxw}zB;x=X_l)S zAtPgy0Bz8+&}x=!_^c#o@E{c<%W~QV34FE3QTqjePt$P{1_`Kx*F>I`gf@6>_pBr! zK!`ki)+5g)6N{gwVI-gr{8>pbfj=t=ec-2(&`ZD&_)SR|1HUN=L*N@E7-~X?00!?g zrDT#1gazqlBSmZ~_$dz8eX$GKPuV(~YC?m6A@KD}Qf)%GFlKr0SwayfCSt}Y<_wOJZ!=?NM&DJgEwhuMJy9w2CohN%D)*&B##0<@c5)uRx_-|+1j)C>*w>bfGzsGOZ@nhF z;BkQIDuaf*H)umi!|5yXw__70IUu?Rd^NLZ60kdMMh!&_A?kw1!HZWkIQnjj=;JmV z!8hA+d{eY@I;;;cA*F$fN32{Z0Y_NelN(xg7T|Hr-qWJ4#~XD9_X{{3#+I^tgvj?*YaGdhD@%3XVNQgrc z@4WtHUI%Pz;NBqTEpB$7;tt=~`&A>fx-CBT{FJ}*Eb#WXzck)8m}2e!^qKzxkgz8z T?Q-w|00000NkvXXu0mjfb4{p( literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png_prev@2.png b/src/examples/2DPhysics/img/i375a4a5e-64e5-410f-aa6f-3bcfc9804519.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..6019f64dddb7adbaf2c721b45049bd6e36f72a55 GIT binary patch literal 2670 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 yXb+$dM2ybThhIAW%-ty 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 BV~+p; literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i3a4eba8d-8b33-478e-95e5-3833099c1a03.png b/src/examples/2DPhysics/img/i3a4eba8d-8b33-478e-95e5-3833099c1a03.png new file mode 100644 index 0000000000000000000000000000000000000000..cd0f0603eef6a04441942ab4b22d0efe5e6c5d88 GIT binary patch literal 647 zcmV;20(kw2P)11Z`tMiir542r5*qUzHXSkwPhA@uA+JM~F@(V0D{j zH@lnJnO*q9vNUw~k!A_aq*4uQOFn1*o{#|Dg(IJRJ%!?6kD6pn2eXK>1ZaRR3-81y)0 z!l1?}8wM>71{jn$SYXiMV1hw~Q{;ao(DO= hWiC)oM=~@;Zewp`Wpbznf9?PP002ovPDHLkV1koC8pQwr literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i3a4eba8d-8b33-478e-95e5-3833099c1a03.png_prev.png b/src/examples/2DPhysics/img/i3a4eba8d-8b33-478e-95e5-3833099c1a03.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..c8e03f4aee834893ebb15ff6afead7940f230240 GIT binary patch literal 2063 zcmV+q2=MobP)141rS0bn|U!pm<4e69k&4?WF7>p3EZ-NlS;h#{!3fF0RUBO z5at2E&AAYSkkH%T4@1Kvcsg_lDPKCUZS!|U4K&XxVzS zm>?uYK;q^M-2g%fbaq{Ufx#jC(SR-ZGsOhqssM1uhG78egp+4FoOD14f?Jyoyz*R8 zq{Dv#z|dDtxl>paz%&dnOao4xIqRGbLWuK1+S^LU|7r_|!*(I3L=pfgB}`7w!1PQM z5CmlumX?=6d081yN};o>3xLFDnhglJc@yAajb*hRDERJQ6JHS3*?S9Q)&FAr;0X0GQ+04`2;&i=d(Dz!;sE z7FR~X(8vgIZvSoz{@MpETTwO_#FbJ9Tx}P#vRO*#?Tf%5%4XX>FKzyOq3DDK0Z6O~ z_;puL4-5{CVErf<#kgPqZiL_wi8`U<+}o!&tz*&&6!eJse@7UNLG7XZ`-80M)iOb0BG=PqqIV}M1z1X}V z91hdzrFD-mqTbV(c2m=u)ex$#@~jh>rooxBofru?fE*M`Zpud_qz%BWeA_?#;&Ds| zM%lXcO--b#YMEyUcubz^IP25|1*%)wYHQEWZSA=%j0*;UAa;E8#Z!c`T^gh7)?MF3 zt17)iz)0xmJQr7tdGIG>1OP(V*?RD4AoMMSa6{8tT2+aP(VJ{`TIuedy!aC`1|S{@ zho2%8c4->Bv1#=xrzWI29q{)s6`a!{5B`LV0>I6@+IsK_M(G}1*VnIVSm94G=E0wk zaRBj1IQST)WUpzOH#gMBbwWC_IT!wftN>6-k)5rF9wChGDKSm|it(~ZBNzUJtN_3$ zc=pgCeaVb`Py@Cn5HN3DQCH_H5_08F$Ql5I@cgHrngOZb&j{NaFbvsJN6S58fAzZErk5WeWGsf26uyzepS5^8_jM?%h zr7j}X4Ohh^8{BmXVRaLHvqU9Y^61g?lyGerj_Z+<^}o_@(RG|9X>w% zC?$Y8VM9Z`E0(+tS{Lq5NCN;|BtX7O`DVo92b5Aw5x9>4G|o+%{S8{t;GM2NAsql7 zx0c?=5P};CsS0Bz{0Y7Q%`8HX$(S@%b+wE zOtAH)_9r9(h{obZNsG@JC4(gas9si)nU!bgE-}k;{EX;obFW~)gxZ=An5GF{{Rv3` zQW+&PwEhL(2K|hGk6lcbqKsfz2wlVMgi zr|>8IXA!h4j{i00*k#cvif41Gp=Pe>;KM-(_`qbI=9Q;=GeYHIhidz0+!2Af66riW zQ~hcFMme^)Sg?LQniAiW8+2_ror}|?>rZJFzkV(Ud%UU!TQs|AuwnAlOU2kCM@ok) zB=oV9*;OT)qg3sjdT};RX5XoINS0;s3hgaId2mHNFe?~=gPyG_L&7OZW#8Py!~`MH zvnn2^t_y#Xt!NHYceUL3y8WE<*6_OlR*I>s8HsjWSnFJA5D!FB-{fcGAYf}sQWo6& zU)g9^CvJIc8lx(_^E_M)<*%cSj;!0Zg}%V!cRcJHA5TK3XZ2axrRC)-g%63L0Q5IV z^Rs681+>mDbko#dAc%=+i$%^P`3B@&-X3$-d`wiN|u}{iD`MH>1reDdRoT4y3@#QTlNO!#W}LlBu$(9Bygrej?0c` z$K=vTEUP0*RucmkBQhiUTzG|>bdhq!TwOdp(bzgSc9LMWD-B8d_i(#&*b3iY+)xQ} zjyP_$XK6QZhyHw)>(x!iQ5`GCio~9}grLApa|-+nn1Pv?wTaw8biv$x4{mm89V42n(lhii$Cf3G9Z10HL*_>;mp+Aeh#%I%6DZ zwBO(2z%djk*Sv;=Uxq@|0LMj(;Cm_+Z<{7HVOSAvqX=S6-H+B!&m_6A@tVmXTw_5V z<14<4nBPQ>#dhdbQFVkUV7FnT{L->`{(-FYxdoZaSKPVPR?SdmPrd2gP<8fq`9gh$ zt+LDc=gJX*#JSlF_qqEsnN+*{d*B@Vs>F-H+u1W|tkq2kkPQFYi(^0brC!$OW z%3B#z`ArH8Q+qrA!DNytFg^XF%jgGu<7F%O8O%GHBFN0WA>O~Zlh>GO~*HNAMB)2;2S7%$i1D-!B$Ekbec5}>0rs-27TetDzP@rXvFw~ltvf|~ z!;93<%2``QG4R`iJakUTtu&e(vchgoA#uSeB!5K}pldLlD{^j+q9x@E%VYhjIzFgs z(?`_n@~|80l@ayh&Z$TSe!JxHY$m7fPiEX4CEs!(Rh!7~-LoA<4>39D@l;lQT{#h1 zgs_1ek!vt|^uYMDo)qk986^a&Ew8B0#xmb}1D(_s+XV2DDP-GN!XI1tGoOs`;`OdX135QZ>> zAbeMbN1AZ%mMAbs85WHDA*Ka(`(wOZpNZ)_jU#;)0qP&nqJ7LL9AUFG!6wh2kla@O22W#4*3vs&4jZQa+HxGJ!G0T*1-pI5NZyl zR(_CtcK%}O$MQQ9!WmGFB^segEib*!~ltE>NVqB5jreqqVY5OI;{RTs^M$uCC z-)H~;04;PySaefwW^{L9a%BKwc`jmXZ*OE|c`jped2n=ZE@^FHXJsx>PDe5{MQ&qn WWMy)w27m4V0000$$v=z literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i41f7c3b3-abc8-4940-8919-9114a884d7f2.png_prev.png b/src/examples/2DPhysics/img/i41f7c3b3-abc8-4940-8919-9114a884d7f2.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..d8acf2b6023a9fd56f924238ebf423df586fea12 GIT binary patch literal 1445 zcmV;W1zP%vP)`2@h7b1j}E;KQ0fJlgF0+A@?!$N82`+eMd&3)6UmDs-5=~(Br?(M)X%b2&4`Z(JP!$jxhpb0HIFCXo0+4c@NelRh>Wmt)zlb z3PPx&QtGrIalBe8|8(NqHAm9~98HexvK*OynYcP;Ft#(DGLTD~$fr$YZ4(*@&ItPV zIE<>}Lr4_>QF8_6$`0l#9`x_NP#2|?$I6xB84UtWPJ%gChMr?Yr!150=;=(OuRDWG zibE-d0L*zrImGPsbG4p3fC7`eMRHx-4}kO!}|yF*^c}dDT5vC z@6KY|mJArQ$_PjyA*Bp}h|?e<=udzJX)|WvoNI@GJ2Mqb-LJs$x981JkJYkXFUxKgkc(x;P~a{ED9BGO8D+RIx;%=p=laNxA%6SCvU=Y zz1T@Ss-Ws1nE3r(1$Sm^@O|$MIx;qNCS~#iPi@P?V)0Y=sLU^fJtCA`;QHMXTvvWg z6Qjf5+ZNmVL|+F|e0{0?Qs4+MT%9VS=7`%gF*RLBnyj`3Fsn4l;ElZn{DxhGPh>=U8iiCnP+-xdh5Kc~ETKumGgz9a;iFoSoomOd!#K#02V_ z8pjjCDNfkXCsQdiJjv1Xtl2)G6(+E7`V@kIKIdp0c7%ZyD*(cnY3Ba~fVJ8Qw!#FO zk#N{Mi3TJZ5FRaBQWDdNkj@3dgV@6f5ukH{s9rrxU3OhuQvbiI%3uNIBA0zfnG`eI2l39j(yZSH)5 zq!k#n9RPa2zueHyP-lm&8VLYvwGW6QF>L~g1|%ks_<)V=1eXhg8kv<;d!qQAwu%Ca zH$qD-zHu2l3Ug6t+6sUu3vM+{0zfUg(d-D;g;Cg zaKSQJZf9QxQLdQAMZ(LiQ{WX|lJvvj(LIR- literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i41f7c3b3-abc8-4940-8919-9114a884d7f2.png_prev@2.png b/src/examples/2DPhysics/img/i41f7c3b3-abc8-4940-8919-9114a884d7f2.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..bd7614b5e272d2bd7e34ec727ed29836f97473d4 GIT binary patch literal 1865 zcmd5+`#%$UAO8+xv7tpRM)N$@=$z9Wqug^VZOd8>EteA|w~Q0VC1dT>6pz9|46*g# zaHN??@whA$+KW+99oJJX$FaFJcXHPAH#|Q)KYVVl*XQ-QzCWK-D#Z<fTpSAwZ3c)LS@7yvbGqN|ff{139Cu}B?HolePcei1`le~Jiwen=P54<1lIJ>)K^ z)tN3;wk#ZSK!qm?9YTy8=ohZlH2uSU);4u5=oIN)N&y#k9Ij+4TTu&-i(_$ZhmO3K zcNWFO_DVjc;53tjPFNcOS{d5@;3 z&lZQ{1jxnDz1uLjJZx~Dd&A%Uq= zG6nJ^;lLLq-4vOJdWlB9g8I_2h(10i~faCaPa*8vM|m8&SK)gox&w2yF!%DA zdMr7bjBSjGD^BD^-0>|;uwDH5q?z@dS45s!PmOQd@ksP2`9=AQ1;M+StbEL+;F->` z175X`m&;aPcT^w487Lcq-W3+M%WlW}sSBl$Pin}j8~aTd9L#9^{HLis7oRoFY~NQs zd5oOkZwIO z#~n0)83NVQ!@m?dxLpV@ugb3z&pgJRQ$4nQe&pM&t-m}zCKyLhZ8o|;wET#h=sAa8 zvcvoR%j`~2*sqN^z5z(Gm%;y_@Q5JO!5wL^8@x9fUY$ON)HObQNzKM5r%_1(%xJa@ zMgR85lxt0iCnNPHTiOEJUlsn-RT@%_v$@0wK=e_s4^j#_AngMK+Sq ze?Pt5ocbg-Qkrn-y9{roOh^uQwB-ysTU61;K=*rpm+`w62tk5Djtcl5ruQVxLUAlq z&qo1AZ~S|-LEi`p0OpRPN2ZxtO2Pcm&6$E^WwauiR{X>k zLS3|r68*ZVe8)~BMT2d%^`TNrsxo*Lrwux83RWJnk6B~y8E==HUNf4A|IA_ApZZ{!+d4E#4wT1wwI0=^yu@O4ac6cc~(28JE~V095m?x<0Ipkh&c zloz8}k(=+TNpdtGv$K?9!Z>h6JkDNoVD5;5H0`c~!qzOWl2R3mMAYn|X9pwX*c`Sh z@}Z&2z+Uzz{AAl3=a|+yWq(;mU%TY#S!UWswcnK!)Lbv$jZaQzYF$daGVTt|zyC?p zeWXP2%3-9?=YdcDap;7$;fS*Nwyt@6_~hjIo+uSBFu%B zzES&+cf~j5?;tm7#k!5QKKAOjXWvpo3lSi6>l2}wSI~ye<*mjBhO}lgHavf}!>#pS zv~K$Bve@h|{kYb%YCXq_1yxtoj`42xW;3=#3rHDwe<}dG-8pK4SB5%^Xtqkfb$=MVU{w8fO2KZAPDlT=l@gR Z(P)xB78rT16BG~v#N!lKfwTXOzX3{hH$MOX literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i56ab43ae-1b27-4b5e-9e49-fbbe74d42597.png b/src/examples/2DPhysics/img/i56ab43ae-1b27-4b5e-9e49-fbbe74d42597.png new file mode 100644 index 0000000000000000000000000000000000000000..ef1edf87d14c35bc34f161480f9d14757ad65074 GIT binary patch literal 1591 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 jVJQa)QzdC(=^gL~!*9C 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 MW9493anv{ZKZ$o9?EnA( literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i8a5f385a-d547-4d2d-9641-b1856738c62a.png_prev.png b/src/examples/2DPhysics/img/i8a5f385a-d547-4d2d-9641-b1856738c62a.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..525b2040710f19d78d84e5ecf2efaa07a5c30837 GIT binary patch literal 4325 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 TF1{Q700000NkvXXu0mjf)t+4_ literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i8a5f385a-d547-4d2d-9641-b1856738c62a.png_prev@2.png b/src/examples/2DPhysics/img/i8a5f385a-d547-4d2d-9641-b1856738c62a.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..8416504cb4901ca2bc79476deca84e4c193a0e0f GIT binary patch literal 3531 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 SuAeOf2WD$UvaGZ4P5v+46-tBv literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i961b4fd2-4ed0-4864-890e-dfd79086129b.png b/src/examples/2DPhysics/img/i961b4fd2-4ed0-4864-890e-dfd79086129b.png new file mode 100644 index 0000000000000000000000000000000000000000..f8a8168d9843c50f80b31624c57fe844b75d7e90 GIT binary patch literal 2059 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 p5DgFvSo-p4fb=U8+@SIX`~WxBRS literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i9afd54d7-1265-4ba0-b5de-5226b3c7e2fb.png_prev@2.png b/src/examples/2DPhysics/img/i9afd54d7-1265-4ba0-b5de-5226b3c7e2fb.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..615212731bc6e4f6df5ce3e5e727509bca0a1133 GIT binary patch literal 1316 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV0rE7;uum9_jb$`$+g=>yIX_ z6)vj`U#7h4xwBE%Ol{>=pIOi5Xs_!rw!QziD(%@hpD6_n!T}dvO2%n0>|haTU}_L% z=s(Z4fZ+klfybK}H5e=yKm5pH;9~G&u(6XKSm{4X24yMt6^~yupAxipW5+Dh=BAQ1 zg+iUA{bFW)k9Q>al>GVh*1bGuOG=ZvTiZIVwSM_;PglSGB>D2woFfd^FU}Q|w{gu- z+Q`PTaN_yPsg-{}MhhIhc=hCL{^M7Fym;W~{(5)yuj?mY^-S9!)Ma>V6;t2Z;z!y` zkNmqD)%|+cr`lsDE}OGSS)$12p)#q5; z+rQR*k%p3(0Tchdn#nE=5C7RjlpNd2A|{%!DnM-|pTdTf=E25C7cu!v_hCPh-tumZ zGvfr)jFk*!S2ayG-mu@#zjn%xb$((h7E#^{mNK0Q>6x+Oo7SS(q%srMjJIF9OEBD9$*Rqpxx%F2wls*3{r}{wj z!6lzf`~^CD76m2EzxXlM{Z>@N(xXq7zZT3eIVr+$K$?re;AF3onb3iC+g(DBM}=#D zyfej*$=fmF`;9#38;`56Jr7nrTT-TJdOu^o?@q3aG=sd??>VmgTDqBgrR4poQPUgC zRu}*O_ggMV_<&hJsp!qww(}PM@c4QE>__|hRTod?d|jH*#dIWy>t9XUfqni7@Bb7o zabK{O^@EJA9IHqEH`iC|m~YJ0s|I>A*5e%v%}8fB*8Mta;2! zG6U|eZjdr>39{z&F;>`q*M>=CL8VUklW7_Uy5?+q_V>bnorc<7)03YH7tFnNKJnas z38NjV^K}%95(GV1Hmq;w&pGq%-LuyR12)^oay@-|{8F%~ol(hB>*w<%Uz~Pibm0G8 zvr5DM)YR;(Hy<9voxk3{E$@U^e&6xrsThu_vric-0j=kk}bu#kEQO% z@4xl9cMjjy1N%0ATpBTPrIWx6551UOUlWS{U3vfJ;#_N+xx3Z9uC2P6&;L4r@5IJ8 z<@L1&$L2(RFfBaca$)o5NBnmfxEvS{a5fxIXAWQ}V0!TJCW8pW9EOS-GX_=$bq2Zl eyaO-&pYxWQj{L+;<*$LIAA_f>pUXO@geCxnY)gp% literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i9e6feb47-e8bd-4832-a1f4-de20729e9fa2.png b/src/examples/2DPhysics/img/i9e6feb47-e8bd-4832-a1f4-de20729e9fa2.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d8ae0276f1dbbd104d0a4c97bcb9912f0072eb GIT binary patch literal 1030 zcmV+h1o``kP)#}*;#Z6%p6Eg0>kcR|G%7>b9Of8Xj+fY>kprwPWnRA9$!#5Eq`O7+S^Za zOh&`K-{}bI&^Fr!7FICg#wcNgem_5`xw>P;&y?>52}_W zk58C!`BzHAy>lJka|ZJ-Z&(`J0EJ)9%slxYlsrEuoXE1Bv)|G}aB9#9t!;4h4k#1e z;4AJWAc~WU7gyGanR=6@Yv*jcawfGw1GI3DN5JghUW16NdAtE1eqce$om^T6c3*&-%wR;CFtxBCg9SqQ?V_s}%Z@Li zA{f|G;qY!caWKo~AU|L+wR`UZ7?T-{6HSSuMhi^fTI0#wYMaNbAJjT7gzp%W8H^K6 zSYLVQ*7z3;${v#4uzO&}P{v>e117e2ETxUX3=2%5(Hv>Z*UyJDl|~&*x_UMS(``8y zb^(+GlNx4J8dWIS+Rx#J>rsTVBUo^g<}(mUFqx{ox2zf!dSFD}k|;H3<}sr1yizRF z10zc2)(>kmGhaJz*(#sXd?d&T!ZTipkOPASHzjygLh>O}l*}PKlLO-=xDp7b|>ESI(gXt%kBL$}4@Js@XD4AQ^uhIh}49^a{MM*FZN#>LX z^U!2Yc`%}6&MrJF3q~BC>ong)_OsVQ28`&%nJAf47R+OlIVHge!}ETH9vC;nvr=H( zO6HUT^O(IBrNKCTaYhQvWA|E=1><%yrvw-`_F9w#Gl*nPdN6|w&(eY!WHKi;7&rG? zlmRp7WNu(!dKFfxSS;EX7;43$wqO#b`%;S`SB7fuz@?gN=u2{5HUL4Xl$rp?I zm)38TB@XsBZj@INuv+M^T&PvJdedNtMw^MkPFU}~)yW#wSR3MES$Gzy`#_okxN ziYZhRwqoKR^pwHM42mj3LhlA>vjg%EP%ND*nDiI#0000jbVXQnQ*UN;cVTj60AhJA zVr*}3WMp|RV{&KQKGBibQV{c?-a;OG>?f?J)07*qoM6N<$g6luh AEC2ui literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i9e6feb47-e8bd-4832-a1f4-de20729e9fa2.png_prev.png b/src/examples/2DPhysics/img/i9e6feb47-e8bd-4832-a1f4-de20729e9fa2.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..aa69d2ab3c75d58a0cdd7788826d6ba4c2552c56 GIT binary patch literal 1969 zcmV;i2Tu5jP)6A%-U#s}krN-R++K9Tt3 zgNdT?Ngp&(j8HDoplBnP(gH2*nfv9OeYRP*Q;umscCmdejJ>uV8+a{)w>0US&WL$DK?j&Cz44fgHX zQ(}yX^4VGWMSp+4c`=WzLWn(J>;+xdW4f+G*EE394}(Up6`&vpARs_Lfe65T4xjTh zg!nQLqAzcFXAke)TO?K_0KL7RS-Hf-a~i|jreSOdN5V+N<7iJL5Q{}6;{--D5>bGC zAd}64a}FT{vbh{4CR~iWE^^r%e4h{aoWET#y{`xa*4x{wW#h}AFm&UXZQH9mS1d=* zs#SV0LwI?Re5ML15zw`Qg8%@Wtp%o3uGFXt_2eC*5hM`ZJ#81DRL_S{#*nz%J0w-j% z#~Vm67329AdGa zF`txuK>%cO`NcZ(9epm@d?Q*A0G>DFSsQ)9fAxMKYo6)iPJV63vKoHVl*c>qZI9;dp0VWczw#5AcsU!e2}GNl0^#=VMS ztx^XlR4-Z>Yz8MtJsM)3OrRPSiY0{X7LEc`fiut^T<12Zd zY=M>EZaDzzB|X%%DS9)~C-2Zw0MrY6(kwiGRs*!<0H~Mr_?)9ewm-)-T1Ep%!sPX< zB|VeUC)=NC-j;)7%K%W$_p9Z6zR#uYSG%@r2>|LzpEQD0pG=?{wFCep{aUp?g}lnX z|6CB12cTZ3hiZXBsdP&VzY+wc0U*^U$=6PLDCMo+n^YPAne^04d5fi*rLgi*fWo=- z{54Wuvir(g-&)NuWi^139s;0R*rSv;UzB4#4M|A=Cc|F!7B{_@C)Kaf2x*BZ&?x1l zPS1QvISn9_mz|y}9R6GBD_IQHOL{2X^}LyJm-%RcQW`)KF7laqq`56_QvF&T;FgF2 zb)`@FK6QHLBle2VD5C+gsHq|STDAUsuuuX3lB&mf)tg;u79L5zdbQUG1SJ5d*WxC% zFHW_%8!@Y7Ib1L4kvBgi{Tj9Yd;n>B4QN#BmoGTx!>n=|P`}m>WTS)3<94X2HK5i7 zhwKAP_4#gx`KHbU>s5R5dTRkmk1{4uuhy4Naq4pOcBOyY8bF7kfoYlvq}RHxp*-(QtUyCP~c^nL@FMiDXJ%IhBdV=$Cc0tHM9VCR}7IhB|5A zp)DSl>i}h{1Rxa(*|A3++yc`ykg-IB@%K> zo)#u5Z(L~TsJnB8G8!;6a(z}PB{QIf3Hl|Pa9y1L<49q97(g$dooVjUF#FYWSO{J&z9f*0`G9~jN+m<{G{(TX{BiG<@{~$ZicVwdo zv=eQKSZw{;wFrf5dF@obb2oHd{>SpNfeI+luoN{=DX%C4V3`&)jUkuIf2Xb3k($EeHnwUe}7WPo6=zuUjTG_=E} zX*|7jNe8+*?~+@h)fNy{1%@PlWq25)V>jV@{(v9&uSHTfewbRF?KMj6o!f{bcoN&ZyMeLlt!92bE&;zqR7#5|pQ?oJeYrT4=~I3YG`Fm^JA zXEkUKfT8j1X%nI#Xnj5i1_L3EGZB2AGx@2x>+}Br&q3*W0iQaO00000NkvXXu0mjf DmUEz1 literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/i9e6feb47-e8bd-4832-a1f4-de20729e9fa2.png_prev@2.png b/src/examples/2DPhysics/img/i9e6feb47-e8bd-4832-a1f4-de20729e9fa2.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..da49e4674033c3a2aae5079b0de879f42ea8de75 GIT binary patch literal 2074 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 literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/iabdeba09-af47-4b39-a276-fa2e59b164aa.png_prev.png b/src/examples/2DPhysics/img/iabdeba09-af47-4b39-a276-fa2e59b164aa.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..2c1e937bdba84b8d1a9e29b1d895a2b71458f1d0 GIT binary patch literal 3598 zcmV+p4)O7cP)$ z1z__4B$zs6{0ADR=vk`ldGl7_op0O zF`9-efG7;x%8G!nYzv`cc;jpdm3nkm#^FcU{)s)0+P3xZj?pwmvNobH>Pd;Wa#fEM zriy`)ykUNXm~TjaIc;r%a4J{F`J#_Fjy}isPu}!`Yl~Yyuq_LRudlk36^^BbW3}wb zHSQ(4-??IdLN#tMPm*6y`;}}}0-+i)ym`KiFjUX8eG}t9^BjKj`?oYuTraAI66cE{ zf>^<|Inu5`HetcDIN79C5dnI3E#`vzfijld69wPxA|ub!zO zh!n^O&IpT-Y|1*=+~brlnel9Dz~t5tEzM z6qjoAn}MX&0K;q(X4XP#f2I&1SB{W$E$rSg2!{i{@2`sa=7L^3Q#IR@Jv1?Xh;w|( z=4^z#00BW!UppcL)Jn9tFxHqUH+-=rIEFGVc5cp?#f3p+T&nTImVjkh5P~=U<_yJ3 zU;>!AN`Np{kS68;5)MPgqitegt{NG&C+5el7zAe;etmi7EzwzuivS$lyVvrE%bye+ z`)$E?j$~|%WE`}+$+@&(C?hQjowtvqka8_l{Sd`c9TBNR3(>M1$3oJx zk@Otcf&vt7>_fR8qF4!0qL4L=7h-#5(w1z}F&ZV6#7v=LG-o;5I^cD*D2o8lm0z0t zaKeK>!5Keb3w8~GFr1+UUWnvOsbO$vuM^`WHP5jy(x1R^)-@q>xfY=2M;#YH7kIXS z=i2aG3ziT@gGk3JeuQ!@GOrYnT``(5lAoQcc4VIJJDR<4>$O|V1pv?m2PenUfpYHR zf;pq-+bm+f79)r>Mh6lY z$$Ah>8>z?f`bLiz*o=jUX(bU}>+jYC0Nv=p+po!3PVx@M`6Gg}tLQUwO|%!uT~Xu} zQ?|Gc36pV!#@1bW>rion3Nxe`E1@+5Ko5E7&g-t^!g>abe{?A03JN=YpHzK)7;iE{ zMT(VJjYi-kjK}FjYX;!{JFXwKIsY2x{Nw#;H@R~(0Re5c!Fp3ISou9E7FY?b34jIW zAK{#TCY!XJ_YHZA*&JE_^`@QyU~&@TN{nq2+W8{m{PWp_!Sq)JV|P=gwspWY{=Jo!0zi=-Rby8}6a-Pi^sWOyy?_HB zyM8yM^{^1^?o2{pOP`Ar)t8pgdIFGGFlg^O01w=Ci0PLR}&p8$w+O??>(=SKwNdOMqJ+57gG6-ftMWsog}}S?=LG2SPg@ne)InTbW;#{w{1Wt zX|Cq`p40#`^sPAqS@hP-mXcg5j;^Gl+X<9T08rWP^|LkV3&hkIS+yIaM2K>M&Q1)V zhGr#b)DnpX+X(`GQ%TwFo9RYyi{pE-NprI_! zb>PPuC#K7AEQaB($7n!4@Y6;TVFr&hGCxW+cN-AfrR>m z3D3dIypO3u9fN7h7{qSp6A1t^w)6D}r}6Ub+HxDo&tatM!iDK zI8Mw|%v|5L0T-05c0&XJb+Z02Tc>t?Nhr2fvE=Vl&w_J1#yd=rD71b`MtF$2vX zoS3edVNDvaBF1STqcMF(0~Ijy!DJ&Syb!!GfJM=%l)4y8d2c9kKWwM^?DYL213gL&>Y5Ix!pM_bb(R+z57Kz1v+&T98S(<|`Vzqag{CgDiBUXnPRj zjhv(bLdrFx^0nFdFgGJcwpJaX`R4Yzn%13+g5Xg-m~_h>Mx+1FT-3~8mcE>t6az8ES$R425GzqDi6 zKKj{jAB!$q07&w=a%c)IeMuXGDQmHi(k>k)Bb={VZIh93}-#;*p!CF zyBnIOc}E)Sjbr_P?7-xB$+3Cn+AYa-DM)Ul77+8N3qGo$!cfKo9rCbOF)`j!6fK(r z6OP4i-8tf!+1y^GhS~>a5}oE0cWusq+WBD^c7433(e%Pkv%lA=PR})4sFWX z*xKI+IC_n$fy9})0A)X>NbYq@@Eb=mu04{WG5hW-$Yw!xHbZ|ZZpsuz&=cx$#!v(9D6XfJCV zn)JS5+x%W~CEea|Bf%>Bv9Z|1yjJl)IV}F>7{=ybQg2+(nb>Dp>~FgPfzAg|VU*4r z$ymMUqze<~SmD0OJ=X&4XrQ$BmA~!@h*545aKR;qYP3n7v_~wz+XD^GfRPGNm7V?75duwqAPQExX)QD)r}r zi@y_`e~3UJ8`j!dYcftuQ;CcUu2Slkp!ELS5c{8RQzY2D={@(1+e3L=#hCgu82hx~ zJVoRCoH23@-CLS9AZ7|4jqL8t#eV4zbkdniR-y3l6# zN@?Vj(nlnd&lCr&mx+0k3oKbD`~2RqRB}LEX9?>z2KLvSVK*4-d;(z&3}5t9t&2+O z|I|8u;?Q%izSS&Yw|fN5I9{#y>}8xk4dB`~*Gy}rf1seARbl8yo3!vWc((05~rh=$QUh!hc{v{N*++r(OWq z=P&8t%4sO*!#$f8vE(_|WB3)%-@Tw%U7eBJ zfRNNNR@Ah!s48o8ZzzN3#JY2`+_-YB6$YW5XJP^n8U>*$n62^uGZ!qy=UP)Ynq&-m z+;H6V76HGhy6$M{z72hKYk_hoHhR>Ke5KB_BCXKpXm{M}NCy3*9ZHD3Lk~kp-rzau zi$=!?yJ(_@ZD3@1Ik_slR?qHzS#>{v{{3^t{gq@!XHU;7Krw{+IiJ2|%2q~2MXOVT z|2fqY%rP7>`iDQKRM;?CqB(#xO8qpLTewnivo`h2i*p!My2Y^4`haq8!FJgGRFj`M zBc6{COBeW!jv+s39XQ_C>Vc1w^|)Z4*_j=t`I-O5yNyO|&ao^H1qHb&d+Yt)4~iz; zV!}YMAANWud5*2_#anuzRqtC*QH~kVhM~Pd?Nm#kJw67FuMN6dmoMdQnU%6!t)Q5N z;<&NTPmuV9bxfj=9S>J_+IU3iLjPt^--RqBm~@G-0*R)e5b5l6D_$Li-OhR6l+12y z^<$bU$iHokRRSJdzm1xeSxYeu$bWOqJa(iPH)cV)- zor!J1S2@&&Vc~9T%8li5&E!mpYpu~T=V=dA5Du4X1^7;XLW|Cud#dGO2Ik;I zuTK597yA79iyk}RZRgnu4q56J?Xq+JdPGXdX@j;&MqJ})UaWo_9chmm4~O8Eqi{)Z z1Qhzr(!u{q9;R8Q{b_BW@e@(5&F;ydKd4Ks%%^^0xPRVd2wiPYeN^a1kmHy^A)NSJ zb_sdLUKsA_dF%2-7%Y&NQUlFX(rQjn2ga^Qo@IE1e#cCuX!higVX^Ymv(|j+*Omz% z+Z)Criqb7ySHH4n>GCmk@c1XN!BhwBz@Z6T$5bCW9liy&2}k7OR_yd zWvxR^lCS^qaplo%r;rd82&M}vE>*1bsp6Nl#p4e)>aL4kjHFV;=tIR#gA7WjF40^& z{Fdn?19#cIc7d-Hh0C>q&)ol5{gdaO#*$}DTLrTZpGp_6hq=F1l~S%tO_7aJ*?0hN zkFAg{`)=Md=_@3o(wx$>#W76DE(gk5V~JpM4uZhViO)M@>Fv)h8I zG5i{clB9ot7FZVJb)*%#SfSD$3+`;|-#Kes;$~bHyg?7Olp2|0C4g1`Am+XL;Z-;} zwUJB2cg-Rx+3BX3xZv%d6>|NOJ{eD(AytBqISaGO10Sd26D8vc#{RBn;of5Xt+y%j z?wd>WT1Fw&>qNn2RNdT?GUHp0KKv<%86+swpZmdRUhMs=_sAFuue&ZAf_F(VajXSlO z&C}lJN8nK`1i^&_Ht?Rrgnfcwn7z2Zzzgs-tAAz6I8)dN{QeL+00J9P z>T%0Kvcum+Bf=GafC3_a zGkP#JHpl<61z?fv0|s=u7UbAl}aw!(kydf_2%fOf3KVh{{_AM z=B^`h4j~!ei3DoEF|s2H>b%0V#)jdYC`*j4WR-bmJP)}4Yt0VOF3c0Qd|$UbSG{RZ za8_)2lOhRxm9-#)Ffh#>W7qP*TiFUv$axK!*8SSIotLr7*tV2ELebVF$@eHhfhbl1 z7V^Ul;o;9W@ID^ER^7gzIOccp4lb)+BwGW^#Wi>M;Pz?u-LW&ML}VwTuudi_K3p`u zh4K3rt4%|#P|-!{Y4LCkTAgx(=N_$GDv@_3Go(2ZffrKo=~tnJDX{VfN~JIjDcQ!F zEJDT2>3>6$|ID#p!RUQjq&s&Wxoa0`tuqN-<+D*ayYCbz3?QL+@36391*d;69LCmiuntctc7OMA*DtpggS*h(o#whf~A>%(QBt#b|X>6^jKNtOVx}6hbjFzNAT$ zaA4sZ?&tt8`A_wB>Ya5$AmU;7e9pp0D@y6R9S|s#R!s@~JmGeD(agP3ICc@Aa5C_a93EZHzA*W zZ?CUJq9o@2@oQgI&!BG##rw58g?#TyIs~xy@)1IJo7pTrvEz>WE$yep;pd z;xj;C1Q;Dl&%8`)*_vDXP9)82=gsJ?Mf!<8l%4L5ihtiNFZWsP>ryItBbo!eXV1Yd z>upP|ce2{zjtcL#VU3GdeW-(4Y{*ixi(50#AO!h8i_?cyCruinsMiuM6-=&}qvIlv zw?)uq=(~O&@rqgV+QUUjWBrpv6HnHA{(Q2413J!nQ+lExwcIAHr<7#bK_PPp=Zn4F zk{dpBbbG+qwqLs6{a``~6}ZSLB5@Eh_Cw|q*K)FqA0VGyzN~9v*D|aVDdhfQfa7fQ z_V@mK$M#2A$5|pg?N^vbPr1Y#2UfWWv+7z3smE>kbe3ws&dm|b9F2q4y!hTAQj+Wu z3`e;ySySHdbm$Fw8F#e8zVWaB%o03@1wKY_$X~96^mib+{-1_+9{gcWuuxqM>hJyU P1%OMsMmm++juHO@&5NdD literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/ic4d4030a-b640-44be-bded-26a5c030f542.png b/src/examples/2DPhysics/img/ic4d4030a-b640-44be-bded-26a5c030f542.png new file mode 100644 index 0000000000000000000000000000000000000000..39154f84a7c3b1f25f31bdb468e7882ebdb69339 GIT binary patch literal 1991 zcmV;&2RQhNP)^)l^D`QUw@L15HRps=!PV7(ND=VIWXQ zm=6*@l77)0umsnShvT{9VeETtk8OfbLXRLAIc9vFfs3>!#i`H~$!+KKLv0nVq=(3&n|5=+yf4P6pZ@qFp`_pXE`fl>kB|uFy z@xV-b6L+v3BID;W@Y|zP*5tDlXM#+aiAObnx;yjK1JL+6*Ul6`45sZN9YF95Ji-gc z*2zE4vFh7Dt&PKX)_Uu-%U0`0J;y3<-aDVK9sk{Rt~L9AGpn^OPa@P9OC~sk!N|aH zc?k*Z<-0RyQZnhNCO`qO3KG}kW~d>?*i!KlTvzoF!bSBAJR%M-`dPVg?0~*~`mcLk z#vmSq2Pf)1fWjRSP>6XM815dIcbFJAK>#9D@mwXmrU%YFYUto45;k~98i05W4zXR-56JAB%nLLQPOFX?BHw5V4H zoESYv@b1{b5m4Y1E`UWD?6U3NrTs%|VXtXD+o|`r-MaIcrB{d6 z%E8-6p#?D$Rx0O21SLPMfcJm&;a#@e0+PI>`qlzGpg?wdRQvR-9X;oH-g6fn!7RWlj_Z z6eg@rrey!U(Kxbdho{zB!~eSl4bQ^+05J?WjDScXs?9f&>I<)!NHWAKJhEVWTBUjH0wPup05uH%a80gl zk_bW%!Z(2-gjwxA{vp|SHeYwNa9R* zg*(k%BXopEytWw-LMpIfjWL)=J2W9xIg!%WTU5d|2_XiAsa#^}E#xjyfe~;_ice7~ zyvCPXqz=+OIVbk>6di9tUE%;mjC8;xq=bksZBs}sL{CH4TgYJN13H zj7`^TI$YyXD5_5WK|P_%EyOYtTD7^s50qeeZp99Ym@ePIHBw7;gX$|t0KzZJ+Cjv}CQ@$bLjyjDE zxiUSqxUQYy26;=;@*HGK8W}O+Bs}BDwq(1H%Ujx2b5waQ4nUsc2&YFY)lRQQ)|lsN zdwRgwgc-$KyzPi2Ot%6rY`X+pbrp$&T8b2qx1=f0QOg9J!%g&riM4AZA7J9{wZv(< zy&6}Ux!CCm7A`Pf=klMLhEvw$*<_Ke*UMYh z?fVZLV0tz%%3{85K9eP%$wZyv+NqG1Jd6GK>y!Qq`|6^;DG8_D zYl*UXQL^1GHHyn?(#%_;Y*}C4d6p{Lt&5B{Kwnhd zBtBtt8wMYDl}%Mmvzu%8d`JFTDVuBs{1WkoH?a zGWVJk>F0>y))b;~tUjnp!#O2n1Q2*H(=Xd`5<-s$2}pPnJrM46P%gy(`5fxP_oV&k zeQylcx^k~+qPNq27gaY%F}#wg$hkk5_f_!#11_(=O~fC7iKg=sFWW2NjwA{7f5xtf zRA~e!p#T5?Ep$a#bW?9;ba!ELWdLG%E@EtNZ)9Y7E@N_eaCC1jX>DO=WiC)oM=~@; ZZewp`Wpbznf9?PP002ovPDHLkV1kvzu`mDt literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/ic4d4030a-b640-44be-bded-26a5c030f542.png_prev.png b/src/examples/2DPhysics/img/ic4d4030a-b640-44be-bded-26a5c030f542.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..35ea710c1fe7636a7109e22ef9219c793e2025d2 GIT binary patch literal 4158 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

`LwSNwb5v$wM2=4?!gaAoUw*8_;&UF||_V zY}f5Q-#1g8Z#7>DXLsJia0WD4zC{qa!=%~$ZJDQUTg@*|$V&p+7D&vh0F;A7J$t%r zMkx?3*qK%-8fYmq>gZR4|Km+Al@VE!5OACpvvpz#j$J(Va-lp`>(F>ZoJO!J=XalChg_ZvA{K zk3jU?urN@tf``9rpvkUrFs@c>B7ujXfV6ifJ8YU|$UOJo^?U?700SBW`hFU6wc>Wx zpPeuJ-maaR99P~5?|_WBz#yHM>LJ?SN@{7pJ);f4HJBaHn#@T;wMDTt1IW(e<0K)l zE19thBx%_LNZYLy6EV}H`aD>x&ah`vpn2% zM0=?_jpN1mfoKdf z(&x&%l-Zy3X}i4RQvT~gt0iCPG~s>}E%esoHnu4|H-GIG{Oi|;*RCm7E>)B-R>_Rc zyC`>_h`D-P!dVy6SK&>4oi(}mb{)0qV)sms+Ft`MLPnfpDEC9qK8<9C<78%2UYA`e zD_`TSt2hI7fQ;~~sdJ3-%l)9x=_ishpb%H5yD;~7(?H9U|hVaQDfd Pvg5hamZ$J1-2VDsee#d7 literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/if6a40cf4-e9c0-47ff-afa8-6cdc259ef17e.png_prev.png b/src/examples/2DPhysics/img/if6a40cf4-e9c0-47ff-afa8-6cdc259ef17e.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..61e9bfa1fbe40b8ee0a284c48b8b1f5f4b41593c GIT binary patch literal 678 zcmV;X0$KfuP)J-`z<-4YCPn_^1uxr`s&i$VUyfE4x|DA;(<-*p zEP+6GO50FQ9)Jo+78bo$bbenK-=!^1q66r_+>r(MzOTYU+V&WLG|ONaMKE)mN}sJC z0XDEYRgs}k1wbW+vjgxEt{~W!FLk0qR05cX;qK=5a4`j&dfTh2kP3a7N{ehn9l(1| zeAEI!JCki?1i+{>0hdFOg(%Y*op%5m)O2jajpvnOWdpnOC-4@8(r;`s$rGE_06cvz z)V?OjM!2^lpqA8^rnb7^-vD5F^#y>ulWgMhh0oC40lbE}op|8e#cz#@jir19ME-jL zP?hYJ>%g_^a!uB83}2fKx00#=bNiD6P&bjf>kvR68c?@dI@VzaU63>v};OaK4? M07*qoM6N<$f_v8+)&Kwi literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/if6a40cf4-e9c0-47ff-afa8-6cdc259ef17e.png_prev@2.png b/src/examples/2DPhysics/img/if6a40cf4-e9c0-47ff-afa8-6cdc259ef17e.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..84cd9e046449587aac08728dd870921bd4321dc3 GIT binary patch literal 1704 zcmd6n|3A|S9LL|EjrqDxs!OI?=&o?(5FN^tiq82GD^hOV`EtfgCt-?3QM7ZW(pan% zlC~t8iqxF7EixF6o{_v7(;ydLk@^QYGpFONf5Z9{DU zz`D7*98q2RKck6J?Pq&WH3ML%ZZ6Kgae;F708`?kcPW4w95DOYsx5?d&#{VSYCT#3 z7cQC~iLfopsT^@?@~!@Zb6sg|&6H5u+KJ@Ykf&p8&xfq6$Mw9niTtSe2Lyl`<+9~z z7#6I6D0&3}5E+ZZF^@2czC;Vu9>Hc+G#mY)6<$H&VkU(%wv1L0MFVex$WRo%SdN|P zPgGf=Rt?*s{m>uePW%h`i=U{J7f4MAgac3BXe*9h{O9ji^dVgmRu$BPp}libS$1nE za*;BmhQC=tXp?KGlrc!OrthK;Eq{+2uUb(F!$zWiVIt7~1>>VgtVnn-NUk{NIT0Cp zuQ^Vy9k1)T@aGi!XmtzO!sdn#$^NP*vMqHeXxmbJi%j$rrW((nUeWeB;&gm+<&I zZbm+$l@j69QvQvA>PDWH`YAo}-J;_-G)e#WB8I1)y9`l};dWo?yYNDm;I&12h2l!V{uYEaeiSg#QF&bYBjS6aO)`L+}9A(|@lP@R3zLZBrdd;>5l^q5@u1#}E zL3BxuRIs(OC9XbZv3qG+`i47lWJUI8OmUO7TAE4d{-4Rnu*E{D8YQ@d{F^TFEGnbB zI4K`jtennc^*q5L*sKCa{@ii z%n=#vDCFigy!(Lf&lzMZW4qmo_&0flzU-}!BryFDz{D1`}_p0eU+3}7*R_-m? z!Qiff>?ZCs`$N`qYK!}h9Qm3|$&-~$X|EBsRQaS=ykxb@?Ftawik$NEmlMjZaoCBI zOara$?IrT|Gc5@0e)?V_t9e}9dAQe;J8VO97}Iyc~pq|6#-mDzwi5J;zXt Qs=6554tTh5_mPtS2H4OGaR2}S literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/if878c588-3c26-486b-a20f-b23ffab4d579.png b/src/examples/2DPhysics/img/if878c588-3c26-486b-a20f-b23ffab4d579.png new file mode 100644 index 0000000000000000000000000000000000000000..8d85509b9d6b062a4d9207d36a50a1290a3e6146 GIT binary patch literal 98635 zcmZU*c_5VC|35r427_eATGla&vWt*)(%ARP5{V*|eJMLLQAW13A-jZkI<{cw9^LCM%+wOR z@|Xb?lKsc^4hWPIvKVKm_ zW(l)L{jdLlC(6Q!$^Z8U|NA=+U~nVYu~A_W?f>^<{_}rDVT*_V`*Z*IHSAa-6A`KA zl!yBNyczmI3KBN_zy2c;k3h=89}uJCIEDZ3Q~u|>Q(zR;|MefydPJBgjD7Jz67K)M zK0J&A7RdPjTQqnz6L>csA$aQi|6aKR0=DqKw*w1Dq`>qj=XCD>ckBM|N{wKn0{?q! zk3sKd3T)BC{GZ)^ERMw~VLB6ITsJJjYg6Af_U!ox9enad6Y#@N%A555&(kU5aEv~O zIS=M^BKyCjZ=#H~GMgjYDM2l-K4T=o?~qcM9T3=jHtri||9eU#UwXo%CvL0g)b>%) z+)TIESmhS#`Kco{0ta89hH^kq=-8!6#2AEIUrBLMNr~qXUf!DyQ!lBnBNWLkoVaH? z1XhBA1)+)E{8?)EmT|(~%l+-;M$z1x&Vm8@B|L;loIKl=^=WO)QKu>0y`Jk?1%%0C z@|w#9du#f;`_a+Srj`~K8LwZyJQMUys+#|FC%fFi=TRK#2~JEH4_xl62Vv6a&f%hB z?2*j{{TAWu5GasZq4PMEDgU;@Xq3b#Xnj$V+all zK5O+dzBR@}fm$dy0$rv9_PSS19LdW=iWUqmtZHnGct@_7WemkVGbU_J8{uvWY))xN z&pdtBd?SOuW|dFadH9lrMP+nqKxJiRhD4>~Amc&UK4&4fsFG=p&+Tt~=87)0On*RL zf4)b=w4=^~HQj^u`vD_KcKqan_UT)jd3$OJUZWJEt4)QQc zDsGQMxXg2?Rut3ewYEmLClVojbxJMa~4i_|D?u;v>SOK4F!B3Bt*{ zb*d})(fi*estRWpA)DfkuB4@THC&42%>B;+A4lMp#c^BAW_wugS>7T>e_P(0y`$Gj zFyw1?3X*7%$nwF`1|N(4y|T%*bv7$COOLRX6CsBg%gQj&j3YL4BuP8(-6g9%Hh2-< zIXv8uni>mdi=2lx!&(H3Q%N1tCOmr0q_Tx)vva%yTVu?P&>(}zuQ7rNU0XE3kFt~2 z=K4$pd3l3hNr!PJH7ADB`W#m~#$~ea(nq}1#4WQp@13bS{CPh|RF<)fri3i_)XKE2HIqz}cg6m@BaQUv_ zS5@UT2?R|cd1oq{*Sp_OpLY`Om_y|%N&<_k1}XkYfWF>dG<$%t#4;i~E!Dh-yYvte zi>?8M3L&N)ozDWcc(3-uhYv2YE5(E!6T&0o#G^|`Mas+x%Y?^<1>JnVe*LNlV3NrKa}_E~HHaY@hZ1h`g>28f6C}I)}rNSV%wrexX5b#((Pu zp+|1HTwyjvHe3?9%2UdC>s0ouSK`&dYqR??+Ln3<1X?ehNDh5U9!{zG{MW>q>{_u! z7mUO~FC^(Qfhs6Nkffh)+}}I-j9j>ZU zEIvtO0Qo?1g%<~$BQz?i=pn7{6MKbzM(7Wbbc=BgQ=eY=-wy!`*`}9;JM7C77(@^v zhTB=q)OL;}+mv_A%4o6{p1_cDMg})hTwGkwqkC9qiBnb-iIsguBV8I{Q95|SeL|k@q3~pK!<_Q>28UoB8K_|IFp*o>aliBkKlbK7 z|3G7lHN6)Nboml?BGjpITXccuScgJz@Q1==QFEP$xxP}bBmDfKr{x=?$d(jFgbx~> zl*Q56*A&mU@i)!0cE+9GSL-mENE84$rHZTlkl>1WaUD^+Y%Eq2V1I)PF7J3e{W=@x zSd$@G|59+~o3A>Az@ge(0ytkW%#aD8)(XRl@Zszz&u$dM+27)yhCBCqSsfIXC>$0S zfaw&|n&HpTa`1xVPmj@F2|-eNa#?%1uI{T~Dc zvDZEwGDss%bkZ($XKi+NwiFb@zY20XpOJ9XPZ0Lebfd?2jT#mkmfVccf6ng04YR~QhCc>TvZxg$U3T@;y&F8zmjmT7 z^`z8-yBb&qBK|G%drV6sx>KyY-`(o%dEF}TsXTDZC1b)|-Q`#}5WGdqmu(`lx0yNU z8B>m~F)kvG$`F}g6GBP#@Trx}-y$RHZl@y@K=W{dV_ILFe`)G+DplyN8fPu&n35QD z(i;)O{WM9YqgS;kmLM|9Hf~CwQZ@^VV}$1eK}En`X_sGiCq%K_{pcaGy={J7d0FfZ z?BNM4uF>?x`G72CpK;Cknu&z5W4u@wP+}th91$U`N(X{wn8cDUlB+JlflRz~mO|ay z=)|IH!D6+k1a0 zF=N0Npo#bgh~^UW2nU&2Hl%*Wp9X*y|4a+%t-rfGe}=Fc?%bg9;BY4ssJ4YfGXIZa zIBxl5vyTbmuQMv3vd3|ullHN>(|mkb1o+oFW>*i=@HbJEw4HYL8e@eHoENFXML|*u z2{ofP5hA*qVMMYZ+i_4kLgAPh$0?cnEh8@Fmv*%gtOzvuC6TN~B8PKzuRh>LVi#RW zvCn&iI)2c*qP;=L6L~&V<2AA(9+C&!sMY@(jw*qj$VY{qeqvBx4dRP<5-xlg9EUO- zvnhZXLSIg#F#&vHjh|!r)9^9z{e5EUw&Zu#oNZ3fK~2k!*=M#-3+WPUj0#0s54Q*) ze88E(aa*A(8G8=4wtNi&5-=va;@e@~9rVEgC)}EP^_wiznRFeTpzO zH0f>OM4(aT_&GM(%@0M!=BLz zBuuIjR_h4IVvh;KNHr+_cTLC-8a+a&Jvs0_ffpn~BsrNcthvhKx_<8k%DfJ}kK#p) zfn~L8Pkv8GNccMzN4x@l)0f`+kNvwlH9`oqtQpv$)ys}|vXfK4P-@~2GYPH6g{zG* z7JS?7E4JOZckRu;v(6=K5rjAh-iT-0!Zqw{ZMPdh@z+mY$pJCXz%=?(Gg6R23jiqb zqpF&$X*-tj4O5pxti+ZVANL%UAh>Hf-ksXs{; zZgy$meuCn1aU(ZVS=Y+)NRlph7F{NLds?Jso&u#wf2{$1P$KH&T+T;|Gl3@1mIYw^D&V ziWTk%jv-4f5kUf_={5^(;h2oe)UDeEW17M4@I^1@K>>&=8ooGh^K$uk6MZxh0^~cT zlpDC`*2%Lq}*wyI}!eLegkdD!9VKh#l(5685~%110twmuR9Elc&@Sp)zWwjd0~SV78R z%^niboy{?{A*$NN^K5eOnK)oi)jL8pG^5GkctT>iq<<6(Sn8+&qVaV}$zC_8O6sHF z7fYmxj{A$Z!5*L&rMw@Kue4BuMgp4tF&M$DSG}R+7Xx`@ z>bC_0C75y@6iBvGC=%c!h}yQrGt|(QjSRU)I0uYdZS=HT@5LW|eG%Syvoe{f^zDa) zK~nF5=;H&JKifcYzuI|l>H0w437nf5S3UFvr|HBRcD)#%6QR`Zb@ zSDBvxXjHK9Nbe9+z2X$v<3NeTXX6m>U%h&j*#M}*_xo}8&SD*EKp~*V+!zu9*a4zk zRTMc~FkF5gX=cR(I@1TQ=Gt3xdxh4LvvOSV4AC(MND~Rfjxg#DbF)vy_!(X#y9Ci| zpJSVrqzrnJ{peX-u%NuqQiR*vdGLgscGax*0CLMlN>F-^FisMBO%Ai{%Fy4xe;2M! zT`Q!^@k1Q}4}_rWAyzgB+M#97;v;nb!7%kgjWU5D{3)=-sf(RO%bPoH+LJ5P^j9>> zkwf61iB~~@;KiTRiacuNSCUkSq62Vr@oz+79gQD!@MvQ1puEz&lY$>@T;PttTzWd$}IsP)?^?8*n;wcr90SZ zE6$FINU!V8J%^Sz?*IWn%-TBHtN-nb{Hm&TE5L1()K2NRxQP87Y1Diz+$jT{vl|F$ zEPw>KN3Ed)&$2NmkOlx{%MtvEq4dUP(({GyS`}*kd+tJ{T6x_ z)K5`|Gu8WBeU0I>AMdiTJQ6j2wcgd<5Vq~woFH&T`papELS2)>;%vKS{PT;8|C+t# z(Dsetpj7Y2giPukL7Si^3!E&M+#KC#ZTXgD_a$%W!5R`uRE zO|d>~?BiXbqL(jEwl-Qzc74xK)we=sLqVojL%HF+_Xsqc*eF%(?h#O|2gtMuIX(K5 zP%5Cbz?a=It9zaJP=k06t$VF@u+pt>4n)+^L3<#0oOmrlxE`GXLouxz40--68~aYr!aXP-wCp6P zdfOc6Oi7Kw!zV z`@!!AMgbZleM3sC)0KAWRxFYi83$ClYL3nk zKA{r`NQgyeQqY+C$El41!)V$ih%+vS$o`7nH;gWQ`7$SHWy<`*g$tL=XY}EH6YQl-ool?j7~ZfE^RLIwqvflN!KrVIVm+Sy$OPc>^8 zvj|yS8QbqNsnEdt`N_9ss=t3tTYU_(W0h6N4wr zb>zsA?2-~`6@QwUUTUr_RN8b{c4`_L_RbVp7V|2o7$B4NpwDe-PRIkOYM(>dgkb78 z)rNnW4l$%M!Og`8Q!_i$ERCTdUNb8zE8pY-lMLU)Lx1MxB;K(NLb^Id2Qa!sA=l1ix*@-$6!fM(+C5@Xo zT{S@ZiC@&{{Lp*-i2GlzjV|Q?l?<>I@RWN1cCUg4=a=6^u6cMI)bU4abxBfy@0=iI z5PF)Z>fSHwz^5VE5?}3-Y0?%Q=^SAO;^S^7t;8knrse2 zb?)Sa{EA|m%Go+|l5h$Eyht^M_B_T&Q2!hZq0jwO;q4zGAp$O#1?(((2XvTBSv9pC zdFK+Q!f)p9J7b6-Hy&gPni;72PNHtzzWuP5=|H-BfF@;8)6bMU_P@Q(!|_n2OTi3J zXu@=A;GW%d2t)yfMZ{YpG=)z7i~bRKl_`YA!vPa~p!0t6fDVCI7~-C({i-~>zY7Gj zn>TLwSRwPypBx#QV}h#&%;#kazNRe2NArR5X-gmOzdYIMGC{`M3T5s3#J0RA(?r%? z?i_+@Yan+=yMfIj+lX4QD9f}KD6TU$CpdYvC#wkx<S8&gqTo}l_(8hU@M$nxXQjh*<3G1ehpc93C zl?_|`z$lik8L=nq+|Ur`iKxG|^KVHZVlVWjtLurE_MA+TF|0};0&X*h4*R4l{W_|W zTLt>qurWcX>U8*LRw#T5D2ljXcKNKOAR%cseNZlre=M?JI*&=cBNbKn*T$dT5Qwe}TZg z%_2Kr;??*Fo(nkhC>EnmN;SVeSY%CK`U#NlKY&UqVm52q2jXfjYwpYuD1Z9P^-b9p zyZvn`y~VaOpPBgkXPR02K-awungmQU)M^x%1{e`#7!h`Fs_T1WzBmI{8T( z5NP}v1S2HA*VuZ#(~bx_8;Q>b-=09WWc+m!k~gppPbo7=cOnMbm{~e6iv#&5ap7E! znYB2Mjl;aS**326H7NbulGga{0@DqyB2W6Cm2NzLhu-(~h1GyoOC$oVYk3YRbhe)F z|0+^_pY4@N-Zv$1b0k@Q)Hy&RRwO?W-QAfbnkxef063pMZT7c^TaJv3>}yih20s1n zwL5k7BhscWP|o<-zpQ3bq{eUmrA6kY+Bv>a@V^AvJTC!#6qaoI8$x2fV%UzrtxeoJKhRO`wG)EPGsqE zq^mT69f;P-VB1y8`~NhLKVb1CTjB|`F?uZ5;$!{?*J3&0#q7f$sCUhRbqh$~%N;no zz`kocH^_Sjol|uJ<%-zbYiTdWK&yhOOW`BDZHPH+JP_0jM;?1(pk5CYo5r2NC^up6 zcSIB5xabN)kPn-W@VYu>+N4lzoqv~!=%&^So*L|IGTGlTP=&c$E; zToiyTW;%rVH)nnv>H`mMVopDvE2feVSwGOEfh(uu!0fWoH28O2*8Q~n!C|)|vK*&0 zB{n?=kHio+Om8Pe@eF0ZyKIuYGuIUnG5uSc$M=Lt-;W>f1`xcwyrF9mTdOx_|NO}V zZ~Rxw#slgoT2>!VxNVYp>iX|j7DfIMBD}~@u;nad*Z9&jZ#f8lsv?(x?)N3Z8}nNP zG=`dw@5^rg{nbx>t-bF*BFLL$iv@{v^8;R!bq&7bI%We>$BcA3VWO_1<0twl1Uj&o zFAet|d`N^r%EcIp_O-@>8rQny9eVCB7Y8hPNn^F+TxD2Tn9T>@pu**+w}*fJq=>CBaV$-HI&!=2^RbiP#KGU_jNaYMg068z{STgyTc=PH3zkC2~pc_rMV*{ zt8pF~s15v?%S{j4ryKEv(ofsIx8TK09fN~LQc_Y4%=Cv39jYmy&(?qqOM}nVBC$|+ znp2IOi|)P=nM47_$DU04jX4xAd|=re?3w!Qe5^I;`F;Z0{1#RqmjmjToh-B~`!1(e z5}&itV;+lu_{G!%U&>xdS-FO5dvCYVWTJW3e&-bIe`Wy^3jj3idVKOi=+NN6z+4Fm zI0LJVj5WsVQ=q=Xxg) z)m|eBj&$m=V_F~s!2Z6xc+)2&WP2FpNa+Ate2ZZW&7Spth}xpWA`65(%v%Ra({uX7 z!V%c4cFhEGczjs!hlU)W#IW5(6unyghW26|K-8TX69><<0h;K$3FR#o%y-R%b@L-i zwd9=yVCPE9B7xt4r!-Q0a5i75Hr+@mQAA88%=-Opj||HAF^IezNmW}v8W+vuq zTdOk_4j&M&ii_9M*FHot4spTh=;$&RKHPb;pHr@TB zJLzap_eS;cvCpf6YT0=%i9KjxB5p44TQV~uhAXgiLRrYjeO+%5$m497&JYSjl_ykS zY*7(hB!UTS()XR0+)C`CB|+kWNx{p?${!hr7JqGRZ@(-micR1@E5%;wTyq!@BRdD+ z#oYV>yqJBl)g)PB60lIt5g21o_L4_7qtKk-%gr6g*8=z>sH-uDXJ3x($=!1m$A4WfCC#t^@&9y}AD|Gcv?Nv)Y{3E*`BwG6S23W9vNP zg6>yPMe30A*QQLrNE~S0P{5=#D)*gRnVaW`&I7Q~G|vEU}02=lcK|u2^l;jZqvF z4QGL9&sS&EF&Ys7?jWLn>TNsw+gY` zl6pITQqvpHVxOCe%NAo$P*Qp?#&FTXB8YkJa|{Or)l?$(>I&w3Fi>fM(MP40wmY4w zJ;pCJg}`;;3DGgMSB>_c(_u59Ev&QboRQxz-@SpQD@DYlz?5(QvF_`6d0{%_Z0GE< zZMEl%9AT+DG0yk##>VmQd9z=>Z1vOI-dHFA6>RKMF!v{r3V1ZYI>Kpf8mGw(9nt-u z^H}1xbpOwhh%a(Ddq$eRMPAc{cMJ{fP6N3mYkkF#+tio}CBEF(zSF)T>rACxeU5^|GLpYt!-+FJIyT}Hc0O0L6 ziWa`VNewMbH}NhFL%hj?(dOpf%X$tfd!@C=goJOe|Mjb;&rTKV=;#>DJObF-^hxF7 zm23`pa47cR`H)mR3XtyfP~b%T?$yM*d(VN3qfLlXFYjE0N=0@ksqd70f{fHiXRY8h zb$G|Xz;+}kziLJ41zml8)7+>Y%}y$NhZd>!xWoM5yF6QDUByIm;(HGG^!JPv-!ml# z`eg#;y+HG(>luO3D}LaWkPqi4LU1~WU2)=zeWvg;*HqyhKYmmKetXOBK&-frx)l)c zNbqQ3M7;A;A~$1?7HMnZg;}=7r*qMV->t#Dmm@E=vv=aL&WM7T!hRqAT@EQ{1TNPi z(xpjKDi5Hc&-SB-Fj618_Wx}4aNf1=8Ey~Hc>iJ+PLnG{&qSvrd@F4%Q zAVl!x(Vfkep$WQr#i6M;Ase2&p!Wr{90=K?x}%W*V)V7M*7O47Er=IWtfYCK0C8O) zSpkhY;;x}D{k1djvVeu*J^=HlGi3$c-dN{-|8Bz1bA-VYA9mA@2=!JFW9n;#Vj47N zwv0mur5c?_B(DPdbIM56Ppu`q)2aeQ(sf+$tqOAoe<=4F=zCAC6gMnP@5b-g`Nkxl^=kf;w!Gs%tnBgOL1D;+RmNC+x6$H!BqCQ8*nq9}ZE zq)rOAtcClCNt6qGss~3faRhS9@4I~T)v2{YlYb!bR#ts=Q&UqV{wj8zb*phfyOcu1 z)=nyC#|1=%28EhBJa>9+eI1g&-hBN;)xBScb$9~!D9E^m53jN@f4D#(MH5yJErLBl zm+3jM%Fj(6=S3Fg3O)X`uHie`(sZLcUy3axU1F{D$E1?SFM`bpblYVT6NhL=u6C>| z)Hd(%3ku$jMb0vz4^)<~n8K2h%%R@i-U}u|HA(bKDeXNJxtT=SjUm!GKCplq&8-x#R!$Qz(LP@o^5 zSlmdM*s+M&7+0hNJ->~xdXi4-EDFkjI_A2R)W+Xy*Dmu!kbWA$($z!OJ3w0U83#Jq z5KP?Y91Mv}rW3#9Dyph#*Wf@9SEDa#w-gKV=FAh5hDxiN&8T}=*zPSL;;674Q3YFL zMOa0uKG*F5g$h9*{++6HT-GZul$B&{1mg$tPi1_3d=CG%)$G7?A+(a$aGVtU1;TQ% z!C%FAWvcD@rE=#WL5pf6trs?g`_Dj4G$jq>a9rY?&r4xYQMCx~S(zBGLYsfY7$2c; z*F?>DZbxp+l7wCXDFD!@0kT^#!;`6lEoSRENaHF>jn9NDP@*o{+b^8#Ad%pJ1&c~? zlc(om4)K7xOc(;VSVMk7llf^qD{dLs$$Z~F>ePTW_|j{%Q6rKDSCck>AYW&}l>KQP zA-sHinoi3iy&s7-5G}|}m65$Qd(o!M0Uj@;IeeiEp#1q8aNWe4@mvR4(8rBVa=Bi9 ziT{gJe=H8iNN(9SKlSu?v^T(E)=adczl*7%(w3KmQ}G9{h}hfN1a0vbUP0>*A^HBo z&o_~W`8!60I^5B&H*fk@Gte-1t~@hYrO7wxqpwtWvm)807CD-Iez8kATw2EEhPXyJ zU;2sS0m5wwC4;*2don+fT6)}J4T<;iT4;8mrYtF@3BKV@>b|)g85yZ~H{+*urE)em zQ_nThl{5`UJ5071-6q7I0G}Pj;_fu{gh>g%)rT&- zjldJmGqMS)X*f0=@?xyzTjjl67uYF<4jhjXJRav9N%{H4amWK6A3x0Y>}O*{qpb++ zk-DPV)z2u>eU`xqq$g$P1}^y&d)`;B5^UQzS=HCQ>)Yl#kuv|A+za z0rN<^C*}8T&z+-_vZ)|T{-j?_0rNl$S2py{D1;y@@DO%+SyJ--LdVvaeJp-{pt`+# ztp)}8ZA5aqenB_n51lyd_)ml@*9{$Nt%T@tpxTUgEUoO`Y`bU6Ew`Uy_uTCNfz{yZ{e z^99g<()3&VdeyWiw+XB6wiCL8AdapfzbA0+sb;3cCAsP&ECGgG*(~7bOq6a9?8?)h zm-D<*RGoZ`yUTc>+N&TlGxI^PZuM1EUiXavTc{?*YRy%#NpS|w^WT4aZ^@`b3@OQB zJ*{lg^frZqtAuRCducDrzkp_jVv~&40=Z60aYB-|-g~Y(rU{=~ibedZhmT_TP`?>kvag8^SS3m-^kSl%Do~ z!Jfnf(P6@*8D3)P0WBFu48*t|mFJnr5Pi#{MX818LG}IA{g8^uO6#=wAH7~TJZ=K) zKL3;m@aiv4#cMxi%^rz$1Lxz$_~)2RwN=*^P-<#UBS48FIRVhO{kH$l_M_DL%}cQ@ z?J+c+6%p+yuxlUbJ+DtyLQy72|2ho@CfqjIJO5r;?q=Hu;&<&_$%^kg^FA8mIdy}> zZ5r^5&}W(v4bvYVgQ+2JD>{C)z>Wqd4B+=>Pl@C|xA|QJCXbXX$Z&TP_mzo|5;zgQ2zKfGInW(o!y2 zN#vZRJR(|fzyPgu1E!#9s@?4*ZidTmuF=QyDV01P5P9$7>PiN_n@%*FP=X}Gq06Dypjb8}ZNk#j-g}ZG^IO zaz0#G{BkX6ePLvwm$5@13u+g(0W??Hw6O-`aXc{1wD(fA-73V2{O_bI;4 z!-oV=|1f1Y5ThK_(B!PilH0|-Nb6evm$z&YtCE|*b#{meM9Z8dgc+rt`ZAP|E5;aj z1C$-l97=u2t$7w`f~W}p#ya%8u5NSb__HjJpqQ4q7}`qJm-s+XX$VY%->B^-7n}4H z(QpY-m|*z7F&{CLbmgy9Qt?P>aq+Ecq*02jFuKLycJ2tXTc@%@2>oh`!4U80+GA#r zpW4D~0xC{kU1Z>N*1Uqrk7Tqu4b$V~K{M2hp`|(#B2Pd{^<{GhjQj$T6}iJ~Ee8j~ z$|FXEtmU-ys?uATUEcQI3NRA(P#IcG&2YzUMMgDaM0{T$9I>6LSgUIa7_QB zHfLL5&Uzx5w)vree4vSm8D9eMq>d4?3$tQVf=soRwV%ORj%b2!Zi=^MUMORG)?eio=FE^)v%L-!a7tFH~r+p;vX z0MQmdec54x7QJ6=Tiw<9T$gT+LqJC2pxaj?S?;6Um85S@nU_h zv~hTavCN=J@s~BA&)EX1w?}Izo!N)>w*jV~CtURNE1&Kb_dnhOl1onv2_d6xBq1-w zq(BUsW-pi73M5G4SYit01^clqAnxxWvaU}leUX*PyLcAv21zhH^esw<>v`?=Lb1#aBOaN))hEJmfR?{pyj-f%cB$u zk?Aox#|Uo6lr4eqzfMffFF^kJRT=B=h62>hp#e-mh^DoboYL(2r+orfN$`J4el?yf za0;B?HEh|PRHZNfc~2_`tnr%j^ZUYWX-bJUwttIQz>wV}H;Rx{L*Bo^;vHDbmaTau zu|zFmPwnsEJB|fBu^nc27Ar{t^Wgem?v8KW`!0RxlW^@xFwa=e%oaNaWoaPBx}927 zf0^tCO+_bhz`$gVUAn=Gs(9{D&Io%Jq_llQIn2nU6lm?eHqx-u_@3v0B~Ta^0RMD6m(Xbv-Tf9rGL4VPNZ=%JC!ASA@1>5o z-zJm4>%hB3Ve?|ShxvqM;xCypbx6U%q{v(8Cm%kX{Z)HQLO}i1mj|8WqS<0rG~^Q# zRN#$T++-3EC_Ev!{sd?!_Acf9VwTPoPKh|&)|bd|I0-BhPvIotFl~-g>iNhh<#7=Q zDw6WwF;%pD!8oPpTd}RJ?T}EZVX5)gZ9u~3s~b-+yL4S(66FpPx@AZieCMYxR#d03 zyST3x26N|F!hzOcRz-0TeRT92b;}TILk1GaH@ClOZ`eAKc3@RWETPe<_UIr^6!s~i z$q3R+-=}PH_+036`|ke{{axzvdG09@_%tb;+}D`&M!`yKB+d4J8FmL~&CxmB zCG$vKrX#(jw^U9jfBEN1#|H~QFm-TSXUI?~I3n87MAej&6piBG0NVr$|4Oa?{CUm% z1`R_pOqPBDg3UH{81VQT?SilIZ#6O{yx!PfoeNX7RQ zxleOaI(7xyjca+Zi+7b~-ynJ8JZ)2zyaZ=nacO!i4A*n7H&ywn0>usF-sW`bGx=M5Wv<)oVT;H>+bEncl`A6CA)1=%%&7ZT{^0y z6To>G={$1Pw(=-7E@C=4H#$U(B}Ep#sO}t|Fimz0%mMXaoLIZ@tEqQn#57IObiIfK0b{Ehr7TFJg2OYtwm=cD+<5_P9u0n|m zMk|F}rohDy#8no83VgBS9NUlewKV~;++SrJl{pb<)yTq9Zc?{UMIei781d;-?u};C z0l=`g!?SNk37x*o0#oExgL6@s}3X^Ztc+@&b_rd`r>JVXd%ppV@OrpV%2HgMX>o3x{;O_XOwGk)o4HeNyZ0>ie^uoREdx$6^n46lSd#_z`Nl;S%}16Hk-Q00KYuW0R+ zanB1-NPVs707mQmT9?RwM$5L!JqLKM5)--|dq|+@+yJUeo&;T;q7-K#u)p6!{Xq@v zn#Pkh=Y>1QWuU%&smf;B}@X`q*A1Fl0W&!1vzy=pF*6u>l-KmmiG(+_fjOVrcD%KkRk~(PlAv40Bpm zUR)$uoV*>%LO2C4b8dU~s`Zgrmh+U#2u5s&VRy0g5lrQ%gCijKDon%|!-^X9U5>pQ z7A*7$KvnY-14=nGn3c#z$icU}*T@3Vy$cX7muAUgy>3j>2s=j90vE;%?m?lCVE*Cc z8xQlM3-j>D4j{=%>W;&4j`(it;8R4wMn#wJ{BGS{j-$1;wTmCV{}Qvd83k=94@}o} zrpP&}1$gi61OescjiXIDAgHb2{y=QMd+)ZUZ0q<^MlreY1@|w5&Hr3wL8Go@N2dCH z2#6IF_P#~Z6NQ24+ify!f9%8K0|x|n6}!EPRpl=2we*NJIzby>Y`v3`dZjc7!z$RKHSEoJa4NX-5C+(u0-QP18Gt{~~;6Dw4XMQW_F}Fji%oYoHi2anl^HE?)NL zP3N!G%2{9n4(J1>{qQ6tWK?SqM_MHhIHh-w)2WfP8mBg@%vbQIg#Rq3Q6!fymsbd~ z7oqG`at>!wz@nevwZ))xDvF_fGe4d-=~F36>}4pcoMEg11ItOCta!r`{K+-=#UcQP zGEg-DMEEW}cvlHP&ntJu?bB`?vRH4-(Q}09m=o9W%FxAky()@d>gIN;KGfuF9)Hu*2Z0-ROp@wUu^Pz;<%`lkyaR&f)=GhX`jOMo;Z%mbX z^D~C@eG3f4b?FcapPSti_5>$^z;ujTa=h8mL>u~_&&9tGgDJ|dyk&R%;sHki&$x4~ z&)&2)?^o|_;Jk0t;6usfYfKCr)xC_?Z=IOHa96)obS<+3m+&y9Zm`jm?;pE@G(F8&LU#voh;G|Ix3>n(22Z4v&a zmA$C4H#2X%RG7S%C-wP0=~CebW?fjao10tM+N%KrklWfoZCr=OZSXJX&5&iGKA6s% z1MMdo#a2O2g<6D?TsikK?MS>6f-4OM?wux(Cvo36=`?j5K1MOkKZP|4r5oiAHb!Uy zYcNTALC3HP@8%{^SyAzz#CNHCH4ipqpd2v0cG?|$3v3q#x(9_JfJ&To=E zod=rH&*NneQs4h!KHAx2oPF(}YJ;1@zXR^op9QvGh5yX?E-!Nrk1gX8fJc(Nsx&@~ zj+cOuXfXHQ_4-nIXuvDbw(k?cq}|8{0Mbz>DO4rkVcSQY(MjLtPNFANh7kv6I@ODKk`A-}M3o^|=6`7g&F)%PJr4d_0vO^f^WZ0HDcjp~ ztMmXXiUWf-GW8a>V4E3S-O;WZ83+1V2TGUC&4w|N2IJ_rK{|3HWE`dzKI1r!U9H4x z|5q9{&HP+zV|ayYmy)NA@bCl!4S8$u4s14}mfZnN_%|d{dMsc-o0^XSS~A%HT)L>N zsIFd!zZp8v%!N_cTDo2}P5K7}z9Rb7pE<46xH~jU&89%4ebNE+IEMSUryVXBZUV~4|x>oka6r`rz@7zk6ovKJiw%UbmAd`*Q`lusAS(FnlB<)*nAEw^nBM=g6 z&bXf~M1jBTgj`LzkQ#KZ?gHU<73$(-@_m4>!Hv{@ZqCn})+9*x1+~Cy?J3c3`ujV2 zgb$T(c+rp^sHS=mt9nL_#-121Z3ec_mQkH`(Eqfj(sn0Fqe zdL?>?9%2Q5DMAzg1mOPqCKxEQ^G=K=0=n}T25eRjw{8TfmKUa5*T04Q6k}UWN4o(B z3us_e1_Lsx81H|wmVDbypIH0Yt$}$_j1>nX!5TV*g%f{02kAkf!JHi0O^-+s7xOj*AsN@wBx~0d9XhKOkPnkI8p2f!S6aqFs5JOww zkP$cs+g)sQ??2VG1yfYo?X}$SeSUV-mz19`3QVPN{YshD&qU}7gCB$L-guk19r*b4 zHs}4?Rl$d01!Q-zTlsgsC5AxT;Km^#tdW2jI|JBN)$rMl5H?iv}h$uh%7CZjO@%v8B3NnYYdV$TNKGM zrD&N#B+8m%L=no)?|Pi?=Xd@*uXAS1^W67!U)OuPlZf~Lj57?#M`5w*lJs5Fv*ik& zzHI333yMZ&kh4f*9Y3tsNi_ZS@`a3P-UB#evRTS;3FZv|n zrQ6rjp|m!l+fI!2^l8^ptAs|xSJ*ozhuUEb2LQmAj zS=@hsva`4M`ZxAbU`Y-L%bp8)Dxln$V1jKQCbkRX_u?k};$9zMa_(Pi-B;^}tb7Q8yg$1 zCIhjvEF?T2*mo5*7}y5lqHfvxiY?btn6ex5Mz?55nA>T6M^4rOl9b z)tV8SKG(&fL!wmfDzDE&-QM={CGl_t1{7(9H&krw!Y%0n*}8r&47W}|*WDU*k~WB| zux54erRkMdL@NG_6EoE82M)e*I1Y7@xVzv``IoO>|JLVw8o^AxM4dj7Q#o*cwXxu@ ziAgsWJ>tRTgC-+d8%-iRD2vnbX`ZfFen#CEbynZypEv0vZNIjjFOpvl5Tz)=VgjaQ ze+1~4_gvd_<-&->N?n~t^*q+z+VWfZ&?e%@JNNRzBL2=&$}Xy)PmhbOEuIhhg8|UHjM+IWEB7NLC@Nad|MK|lh=FUs&cxU;Q&GB=A!|`UYr-qvso(g~7bcCW* zTcHgGHvc_;(bxBZs49-mU@)S$G;04U*_k-?v(sJb{7G_NeNYmA@LHM(GW%I#GSh-_ zo+Rh{6i+cEdE*#yPW`Z$g~Jd(H%6gH)cWVTpLg7DTVQ{l_|@Bc;;^Zy>}3RAgHH3% z;lpB@3oD-=)%l0c{0@lpr4|f`d<_j6_mv%%uW?FLkp~dIi&`Na;{s>fko~q6sBwzkN-R-}+$~_sqv!Le1$E{}E0Yg2yO+;&a9EWqNobd%!2;(fQS3ueqM!GBU2)L z4d~b-4;;zo-)r?dP>Ixs^b)M=ZPwpqr-~F$@WuIoayEkHsKP74OK9A8xLk89I z#q=C`A0sy#{{Qt*2S#Ysq79d{FGt8c^BXk(=*rWI#C@D`^-Q6tN#3>am_N4@UGP^K z7{8j(h`;1#%Y2E021<`w)#PodMkdt2`Ow=wZ#+3t`d z1;x~L($ZV*iF6-X2f|Go?abO|+wx!ei2tXDU)*M94xK0tQ|~sUWKU2s-CIgY1Hx1> zuSu~}@?H#y1=@+P)?dFuq3t&OGM_6X4E~#nt(PU=WNr7ln)7wpw`jlVuio0Ppyt#% z3@yCjw|F0Co)*&PGovu`aKD1cSGbN3d318{VFJTU%<+)f%^3|)ZYjcoP-<=!G^KJ0h4TK>qTSF`LpNyBr)7(@nUKNC4?#~tpwvCq1?y1qek_b;D+T--P!!QvQ?EU`94GZZVn1 z-cnQIu|}b{h0tbLca13pvy&!k2~~L%VU~_e^H#gutggDGgl@;!tq+HxpjAk&|L(ln*wFjTJ*lVi;TM6ch^6FS0rPx^ zfbwI+CkNWed>$)_>3}}JCO_%09!9)8IE9-x+Otw82bIPhbBe;=TU~W#_F6g1DbAZSP&e z1cb%`U^%Gbcajn`evP>ZVx(TVUi_c&2T)r@*CVphj~iGl);w3SuUvg zigUt@k`#l22FOw^KKg&J)4`+g+I6!IoJHtBQPicbw>Mrc?c984#liDA`;g3jA?2gL zzs!I*MeRgw$m496q+@xY8mjd1`ConrOO^pa2NkOn{uTv72bR5_I{nLXBV7|Y&Lak& zy1fw{t;Bj}hyEJ_F5#g3*$R)udgE$#g;$#=9Kg46+pW&QhF)lhqaRvehp#|5nIT}< z%Ub(3M6k${`^X`FFI~V2Od$nhcsK^y!Co6&WpJjeOF~^V#it7&l{UDUC4U|YA2D7H zjc^tKy5;T+HE{YA=TYz&4TlM`9=uvDp?d){M8O5d0`s?@?C@W@@&U@IGND!L?I(!zAL*1yV0r=mEz;cI}hsf^dUn?hv0Fgg`{o2te#pqOF zt#&Y#@2=a)Q_77)rtiWO3BY`)COOW5nw|OHy{(z&z}E6cJ89U zqL$Xm)4oh!Lu?)ae+oGe>$8~4igD2p-dBU?ZbpOn5|FYJTXp6$5F5BXW`B;+Ia(I} zgB<8=G{8|Ot>wP=jmhXei4a;pghE*U-e97$%<;>z7rpM|c`uGBxhGxf3yO<)Q`t9g zVBlOq$>KXn$U+B(Q&B7O6EEsz+F5kbWuwpw20y(=V$_zv7E8du3Tp@5Jk;1z0iXN& z#wCl}KW*`}k(iZ%$r7703H>FWxKHK(us(Jwdn$eIS>12;D*|&j37+16VF{L0Jr`U2 ztSGY$25=nf0h)FcXr(B^TKx;){-QudJ>KK>$FfL-p06tZ&Q?{@CV)m;yrb&eJqD(v z<##311uDKsxPf&Bdy9KxeUnvrgQJ8bJS(I}F{joY8F0{$g=W2_;Dd@yvFe#||2G%; z!t%G)c2t8vj4S2hi3AT;zT?KF1GkX+X#f$fNT5TaNf|1>U;MHrtRg=NV#3&laa+7=A$+L*Egk z8isu%fE#bfUY8UwpmCF*o!V82-&hmg27dHS-K6B{T|l9)7rd>7L{CuI&)6ufq`wh* z#nF@s@QpG%qH>|DQ$oi2AQ@HgAm$m;g*#|jAv zU2nVccxKy_;I?#5;KO}v78{!IV=O|PNi#N=LQ+6F)m^6NcV%cID+_sArqnt~9{JnJ z){Kk{>6z>F^Qroo?83e$Kd+9~hR$RV)|_Of(B%s5A!Hg$#nbZKtCOvUJAIq84fp8p zEUkyU+n-l-dedf45en+`Q+t)jDzV%dlWB<8gZXuqFsq=|_j@qJK>%!$mMd3&NxMi1 zT#|}?T}gf~bkPj{8$*G8k>6TCe>qUhivaGjA5GTq@*|MuEqu!_^pEX5#d%^!cM@6G zF#Mh?L%JWVM3X&s9THE;jP*3z#oIta_RR8u{==LwDGJFs9ywN#&^pedp*?}1ge_LI z29-XbN=MO8DxuM;r1XP&@Z1619~ouVCCuMWisAA6tUK3^RI9NH3k$Qn6Y48%ReO7Z zf-xK9dAyNA3#M%RO`zbBXH;rV8NX6m?;dLV%;x-S7HlX81>ZPwCEq@6+2_HPZ?dhADYIunIw8?v}C# zAQ)cWuWK7@P zdjpPq&RNt_oFK#tcs}2&(++^|h2Hlw)6;GO=!o$8xZNx8i#@tS$7@GwG+Wq$@D#xv zw{ti0WxC2Fj;tRzk#LL}9DLWy!9Slkiw?c5xgaZaa0cICMbMvb6jlBf$Ik(SJ8ltK z7JT2LAb%8;18>JfSea*r{6|77^7A(w#!n2qkz5gtf3>Wfv-oE$9;)as>h#f0DnCD8 zr*2&!g=B#=bnzYq1qFIfCf+qw`0ltsW65pc#>4|fWuBk3pm5Ci6>znrypq|??k_>9 zID3M1Bi&}sNN^)z3-WET)RVbP1Nkj`Hp-kv8v>JoyLWxD)X{$1k;QIdv2Pb~ej0Nx&`U z8@XK1R3CAIGFDam?!$+-FJHb~6Pf4bvO9Q3flSfkCr>_t;c4nLpNLt%ImQm((uS#6 zn>?O?3h(>5jH-5FH5Jd5*>a}Z#0}iFi+!ozYpK)eemx19$cnJtFmc>Hf}L@vMF5*c zEGIH%Ghn62UHsPmTe?B@|sDLKXP z=KG`R-h{har^zY1dwU!q0%K~~FyN>7NN86qy!+4H-4D;S`*lGeqWq@3q_6D;8LJ%j z4o2#>scS5k^NVZ#rv+fZS{qYE88mSoEUkWDB|V$KQo><>AnI!fUH&L?1QuE)8v^XK zE^S{r{VD(mE{-{)y5iOE2c@OHeuodYDLh-?_g^4SYLe%7rwJ$PgSw%LZR*~5z)aS| zB4!TXc?_i?)IcI<#iuuO`@1Lt*Kgc7rHxnJ%-<-m4n!%tP-?vWQS=_nP993UXUHM? z(bTOTt@%8^LPX+UAAbM-{piCusb3Dj2P|hWjRfQo9A=GxxTco2`FVn@;}3Obdo)Qd zY4mBD&ue391&Yzn&pkd2T~39YW4R3t%-rT+otn1PWp3!lRA-sb@V1EQJgf%1;ug{Q z@3JI$G7;>Gk(FEaSNOlju$>64ea``(qtneW!|7cao4wZH61;)kdm@1nYM3=3*2-|8 zNqmfpda}{k*_l*s(mzr98x1A%pMbc8#HmEVtQ}`=7OejZHS-_7WyZfQmFWN_BCQOV zV8OX3mxfE!>8_|hYXAwBmDGK~5nTUUtOqG&`huvmxYU^m7DEH-^0okNkb&^OD9|q& zhZwi==h9p8x(d!e{1~ZC4=%yquCaalb$@B{q&z&L`W%0rAm47FF^p*~@<#n27Kc^B z*5cH3#3E4nL-EogRPyeP%huF7&r_wIf*2DC@M9vXte@@o7<s}(e`2DBu4@hPm*bx24m<+?)$}I_b4rY3b}6sm!c| zS0|^^yJ%#aX_K96N)+=}E<>~j3LS%8zLFj=LfndBn*sK|pAU?AdUgB*;C4>XC7VB!?%r@8OCNJ1LyUkws9i%HhGScvuyB zPQ$CoJM0HBq^moHKMagxFLUc!gu&(ks{mZShpt){_)4aoGP`v7#TM)1$M*n>c(cUo zLfhd$o6jQq^t|s6xpTQ+3o(c+0LI|2%d#LmIOUGzwlYGd`(P6F=!sbPbo;o-OWu(oKZ1hy(dj@@CBS)YRd= zGz&Y>R8^Ze`}mYtkWJT`NVU|W;USn;H(z#4{eaaC_}P9yMq2vBDI0munV;~Jp7~Wm zjQQ%mly@GAyl_{Y#)E!`J*$RVd$}x!dDB)>O*)Fg#L@Opz=AkbP`~DFLFT;?;+bj# zloU3L07(&Vv&30Aw0<$**L-*O-5jA*$y`FwT86Xrv5o?|gD;{&i& z8mp&=^quIGGaAUlfUZkq+mtc(qQG$qI}`&jesh*M;kF{W+JyCi7pRY39t$c%Tv=6> z!{KeYf|+^>s2_+W0LTZRqFvDo)~druWc! z=rMnX7dhGw-p-;}b-WD4k&p3=HUrDeX77 z@%Lg7TNFYS#=S~2?b90#b-BewMPai3{CH4_jrvsD+vkM&1&Dj`K`j>U?D@&SO(*!fg3SaS6Bb+%JqE;k>u6aloRDI_zAV_ zz?yET#BNnQ4`xuZv=&83Y6{%uKUlTl#T9@Cdoeg1E*+Yxeg4VCfS_K>7Kad( zi`OO3|EZ!&w$ZfNkCzRQw{htB@u$xRy`sqmL@%X0iQNAzAl`y|ormJR`54Oz$wl;E zI8=j1#BOTI8*Pnl;C8;Nvy7j%+awpmv*v6@cak)A=l@J{05)%1AJNUi9mrIqL;Ngc#y2w zoX((jv?tA_h%Sbs&k$ON9}3G9!G`O?gImE4+p>Y@B28FG$jzO{p^9aH1sV>2O!c1r zj|Eoh=H!&8hwtotJ|zC3)u~d6IJbsa`+UOyNeG}jHs+zsEBKE^aAzY2H;Kmf!6gdw zExZb5BwtK*;!_YW_y+&_TA8#z+hAQ)O>%N2{9O#F#LXf4Z+&;cy$IyIg@zkD9zh4@ zK+s5k%Dxl<3inA#-#k?5w~u!Wg8AZkLCLs?1GVmru9m$z0zw}@fi?{yX|M0ywEEJD zDb28ShI%f1J=f4HtQnRP`x?v)IhWK|gEDnhlTQwjV-~1^gib>)qm-w8-mm#iF5ws z`P-krnHs<%>c^U=>*)@w@p%U&xBSJPRY!Ra{|O3H*HUk=hN93*Z1H~|U@=A7%8v-4 z*Bb;8WUwK_za%SmPqalQCEjJ$#50#)}!SL<>P~>9|;-k{EsW#eBJc9q`X#TBexJ=4)yy!>a?23<;%(N ztep9?Jciypx8(1N-o;Kq>d~wqaqd|ui+lh-yN`6&3bVKEHV@w#=Zlp3T7}y6<7Las z%hfQk>iqhp(Xj3o^Cj(@i*C6^2Q0{on2Q{TA?HZ00Olt0r!kubGt=fG)#9avVeLG@ zbFU(t$gyLGz+EpPfji@G-$O#e$!Nd3^*&!A z(tU7GnogG4hs%WHF;Xcb)b{mJN08g5`im*@S~k@(n2;EXH}av|D}oGq-*F_KUWzS* z4WNXBi9>4_Juc39DMf;~^ZfVSaQrpm7^=9AmnX}HABFK&>Z_8-n}~Y|$U_-Pd&~<{2o)bZ|&|ADsFx$2y%P5^p~!ML&C7Q!1@-Ad6lYI}LwIAnt?4lxmV)r*OmUcA{aj zeOizJjP6#5$fyG5^N&|B7io&u_MAR*Ccn40msD`c;VjTyG!qk(9&k=2cC{E}RM>*0 zVpHB@NuBs1WScu|ZF$ZqH^HEd1$2)gugb@DsAP`FuW3da!(6%q!!rgj^mV#%a0aw2 zFD>w-N{K!?(l1zMJ7#$E3A6DmkmNapR+V$3C}9LMC+&R2N%M*hj8ezRsz4LrCq^_J zwNk`FR;3$uIvPpuIlNJn|N3j_^R%omTOFzhcWaXOE`wt`x1xgfVSQkCYfj`D&*%JL zE&#hlLu?(i+9ZM<*ba-=oP(IF&zM9B6!g{c2X5`$!Qnig|K2}3U@<{>eS-Dyp=$X? z*6rIH7h((-qrEa9_CjGN2=8L@sh;~Gx5-Hx(tU5WE#=jBLrHO^PN#I+v#%jIfwyu? z();&5p3yP}Tl#I$l&Peq!E+pk@q>cVpz=yMOi9H)1S9hFYc|Uf_Idl4FP7C7sA69@ zasy!weFo_%t-e;XDxt(+DdJLmeV%y*?1X0QbWDa=xTMyLOB8eV2!^ebKBra+yls?% zwY|L+pz1>1o6#XVDWReI+IXSHuiJ-Jso#r`Hk$JkZ2-DZX3b@01lMvHhGYW%3X1O+ z^L9&utY|<5%6V-g==BdfBcHax`rttm=r@lzILKz*-g@dh$Yyu?_M=87Q4Rwj{5S`n zs4i&J@%;5M>IqBg0qh1DH%JK!vUSF24H~$Jh!9MlRnzlvbg;da#8b&%HP=G9D#cDW z)c+O)e7V9|(VSXsf;btpocu1CQXG=gz(A-IX|EXse zbG$UP#8V&$XOrA>*%NL!v^qb>ZOB_47i~+@kV9JtqNI~u)~ZoyGeJ=ehVX?eSRh9l z8_f}DY9*@PuHiaKZ66(3#3?`q?R0>&?lv(rBCkN8+mBlV9{POo^ z8$6wxVbbjKUNPWnEdnAVBaiiYa77i>uzPRq{1nQs0D*Gscua}M(8|A)&P;yHXEEOb z6bt=o@XrQL4XOc{@95Q~I6X7t0X-IgQZ+ndOPs@;?zLCUXM&86P}7Ro8F`XL&nblzC~T!`odOs)NiL znudKpUUDjT<+mz@N$FzbD%t+chBHSF9B8&`Tw)7*x7X*>wCP(#(sVV4V{ykWUAzD% z;ta0tHAnXoXUUI(JuXmn4Ar7L_-B^iJ^R`z4ssnMzLf6$vOosQ`zPV|ku5j$wv+<4 z&fdUXYNvjWhdMiZ=lz1&4V!3Cp3QqhY*%>jj`LSy0wPg84Y%|1UL+2+0>jfU!tl|W z%3S)t;^Tmf+kb*V?duX`0Ze`UChJ3oOaW~Z8uRW(AXZST!SMQFVfl!Q%fKUG=@kO2 zElT$DgIprKDnEa4@WMZx;K}ctkEk&*=fZ7YF4H}5a_&Q^p{wZa8GMx;s`JsQcklMK zh2isIS%I1iearh9g5nZM;P>y}q&vf1S=5bRy1Q#$m7~cT@38-%=>LJq zL&UzLKS289IxFb=Poi$}=_(`|k^1==O6hY2*-Weng(Qf!5k!d?PPatAc=5uYW1tY9 z=BENjHyM2k1hHt^hGNo*YFSDujtj@q6TvWj$#|FQJHI%+Tnbe{G&XuLvXrKExMw?D zPWM)Zz=V75O0D`)TI!D<_*Dp^pJCxCc^-o0tFSkx>`YMWRyajj7TD87XNUxOzMC7T z>g&K-YS=;!zvH#FZa6#CcT@gSclL-2#0E)$FwgOx+|NFHJ3QozJ|9762V0N`aDh+D zdybacIpP%)h|T;g2($-R4pl9M+KNo+pT%1g(a^4?udlioG(uzmcPhzUlHM^xwY+L7c0N=Zrc|jsiw&21}`=v~Wm5Z?Y=(yj07* z#}IQSFa~t~M#Q5-gFL0~lnFO;3YB+no5Kaz5dCAX2z5#j-BtGy=*TtdcZzW+H7s|C zVWQ?4(z;&Lgng0$!HM19v|DAdXC)>q(fx<1(?<4My9S|4U}2*oEKxVHw*M|H-GaGC z_??}ZS)FJn#LRw0M(v{|d_`6~6>C{mI0Wvg6SaNMc@`$286{jxS3>qNc?;Q`Yr%tp z&+34=ilT#WcdddosfuzG)!4R8^yT!}2QPnB$!AT!_qWsq;?}I+xqa$&$_9@bnSm0E zJCR9O)QHL|DpOXSOfig$r02;Yiv}Tj_mWb7EQWni#K;yQ$PL%{UR}W>sY?A%=v(>0 zeQ}!=(eTg$E2ryA2VKK2?Z-azQYp)TBNPPo276TcM47Xm;0@-v`S;wmjXb+jZE0oY zlqR9!C0b|r=*N48xk7S1`%L;A-<-}xNm73n@>?>BUDg_t^KA4}cZR^x8np>wTQcC~ zwZIK%Ilq5+@xBxkYFbNjp_Qiqo%I_tti$NT%8FGXsRGIae;x(Czi zqU^ZSQ?sOh)`E*hS}PWJamh8~ScAhzk3Q};q`t79fQBw6TNJFHyl}A$--SIuV08BO z?uUau`}~ZphH)ZH6Mt;?;+qs19y#F`JbZk7@~wL8d(Z9Jv&TIh;%tORRM8G_3Orb) zda9+-J7Xv}G1;3@8%1Nc5L+EH48;!Lyopr32_~f_6{|c2qG>KfZn|aMv1qR)US`r# zp}(qyTX^PciNri#dt{0A89^4JOiyA%;xfSP-g34_k6yZl9YdLC5~|ABQt5L&I(5Q< z6t+Gld%kpiQd6bIRLJ-QNfZvl5)5nyN5}PQwx#Tn&D|Qm5$@EF0m)cZ1GNg(1KGB! zPxPpB+v_@ z)&1`+YmjpU4kW->Obk8rZ|!e`fu_u)U%l^VA6%T^^Zc0{+zB%VhxPHu$dz7=d)#ek z*a&~3LJkQ)L11e$)^ip`zuH+e5K2whhk@#okE{s`3aZLX53jT@5q7<}38uVwENu^6 z5)CeW_k;1hXP_WxRY2C;M=_okK_l%eN47jhB29T*IP!Wqsa++-;6@_)fey2IW zJ>9{xWbmg=NOxaZ7aFvRV2cS{=b!g zA%kHZ!Q0p#IliJvQ+omE$&ARFHN2IWv`Y`SO0-1LC3ygw&3JXK3IeyP;^-Rw*lmP~ zYxH4c2Y!1Gw`zeJ=^-qINbIzX(=B0ZTU%}ptDJd+%AFW!pn<{Tqf^ZH`SZ}U+Uy2l zhaTQwrrTdXG$aJ$FG^X1wF#%L$hFst1>C#7b)E9otxxWIRqz0e%5fbMbFp0ebl#rr zkE27V(*AXFavf7eaf|!nx$v+`1fmD7)1hxYgy4gTi6H@NAI6>W*_K`d6V*P(f%Yw> z8i)xwd&K~->+xvKnX~6R-t#mx_nJKNi7%E|oX&2Zd7 z6F3A-pz{S6$vh;D59Rs}2bltCuiD_z<%$LwYOMs`-Ddu%PIAHx&DKcxEJ|D^&CM5G z>R0H8U|!=;z-EtF6S`|HU_RXixlSZQP10^D$QY2m@~tn9Bz%A0Q|4|Lc-3Fp!;~{1 zwKqI>E$b44v~p`*9D3K)V*&;CKUjx;|z|YklyiNWMutTp0tx5lb$jNS}(9S zV|y$RO`BgE4^;aOn7Z#_L-nxcT|c^faog23Kgt3|I{-`^5RIu8r0G=OyYIc}?mHY5 zVWo%8PbsVz**A_({W)oK2N+m%JwE6WiWRil7bZ+)Elut>%_2-Zr7v%X=vReu;xTLW z9`T8};haL(ro_4Mi|n2sKYkzqM)5gZ-oNipo1M_VFs9qt*H{1g@}1U=2k#IxjtR9u z6uBA7C!TBgi7?543j_aVW)9oiXFBQN zW#g!1VHxeSx;Gs=s`SS&NF$X~TwL5I@a*E@xzWo}JGCfIc7zs1Y!&iifg35N4_&Un zDED{$|9W5H?Y>onrGE&juz@P8&F=4Ac3xh;oOzxM7TTFqxOZuAASKAp^Nv^D>A}V; z#($5t;5)=H$emSs;q%kO2E@Jii!B>?n*=cP0f4UDw&M3SA)zoa>3W^UdU!TT;gS=7 z(7exy28}u@b;gyAKqFWD%SvGl)cZ#;^q(+q{NmJnn7WDw@iqP-Jwoh$ zIXY^wZF3K40*XCtan?^gaRYq5-;uDjZPyMM1)q0zW;Mp-#%R0s?Dc&}V!c9#kHo6k z8WFx!M%#!vLG)eF4&9eH#ygyW69P>ZQKtihWDtr@2REJB0=%O2(W5sqT{?RM;UD*l zY}(Fqp-zLpAQ$`r$OU2sYvpH5`t2~yMhjyNaGuF1eD^^Et*ymVL0~yGotO{JGU^{M z`|CID<2E!tuNx69M@BbGq2xlix%_}yIdU8%^ZZQi!&b4tK`6E-s1oF^e8?z;JIB4? zh(c_i`z{7okjX3Epz2^4WU%uIK_)D?!s_Ds_3KwqSNjo5+Q6<|Xf*;ivI&Ql!5O{a zr==1i#gUWq$?>DBzC52MXcC+Y*w`_IK*dI5{m-Hss+#0xdsFT!wGV| z#=1CHQ9a5nZ>*@*RJR{+JMpuuF{yBzQ~#eP`Fh=L+oGBJpSM2|2K(=vej7c)+5QtuH#a*vT@ zZi`9rl}JQQA+TlN`yTAz5cS9QC7ffx4mj|$1z<>8T($V@57vJ?iV7bWSc(j zvWSqF*ax>FkYlN)k&D~km2@(pgyM_o5gRH2*tq}jp^*1gD(%m=U8j9~3VTm{P1IjD zfn)9*i*&KcU_{B zd@s>`1W?kIE^Fl#^XeOw;fy9s+-F0u(jF(1dPvjG=-SxwO&F5{Y?sqxM7m%yV;1q7 zUsmtD+m~FknE9S~l%W~hj-eumv9Zt4_}4d`M(t@Bnk=z>L~+Y-cYkXCq~u+Win{tr z5X9XW71}QRGe4t^pS_PT(A!kf_hz3QVhT%oiXw-EA_hc3fD7Mem+*WN#svQQa_Y@h z6!9sEtm6a0LOF^T>EO!yYKMMV)Jz=_}UVE^0Cwq^aNSH|D2+}x+YNY`31 zKP}^0zJB{v7Kdxj12-5DO2Pr zc#gu}k9LwkiNDZDkQZ$|Ge$M}ih!+?5-qL;G$%TUVu!z8FUlsjYUBt0Fb5~ zCGthC1@1$J#8{CPZ5{Obh+m@E3S%S||EhO*j<+SX^!C;*14?DD>M5`fKA!LzFD@?rdj9Wd+1_>Ie9eNhk5Lom z?#oYx1Bj&munkb_<-rxQn!yk`Y`gSQwv`#(TY*)6WVx)caF_>hjTTr8Bg=37W+iR5 zfl#`KU5P{P-n+3zSa>9b{zJQqbsYKlCx}$w3Stw6tLjRNL`t@YAwYM~g!7PRq1ER? zs1FOZqVnJHaCPEC#k|758wJCYem=SZ5*go?~yHAdX!aFr3*{~;58i)H7&FK{Uoq0+3VoDrLUNepLA~%^i#Mz z0V44T5n49WvPpbe*Gi3-D=G=Y6bk!DBF!N9d|y#zuU+~tf4Eh6zrbuYYGQUUDHU!3 z1X8)|nkRSl#*!Nt=ma)~!D+bjDyfOD< zphaXJcw~|W8~ZB;NZp~Hz&^+R3$k!+?Psxx05nA`zG=|OdC2$$G+t14$|?Ap*xev; zc!o$E)Fb4SUdpKg(5??zsH3L>kTc>T{lkY3&WcHh=hz+#_Z>M^y1lvD@ML?Uxq>r; zKEA3LC_Xi0#!2wkQ42C-@xvoDWaQT{L&e#^u8I#cAdvo{32NoD}L zA_*vepT4}KWY5)%f|KO-E@To$Vcj`tWgdTje$rdIg)syH3gA$5({-0qNO{8hS-B#} zGTaXgCPb^S9(~M_5r8+U1OYdapz2v4_(Cw8LV}Ck=RnY=pX#9=$=k#~q{19lAP6xY zJ$jT2UHQlu8HA#@Db50j9OHz1EcHpyp>-z6?|*HEFe687Y%Dm-b1M2bEYl${*wYTV zl{@%$qJ@IKBBv%d~`g)Ou0X2d{dLA zZW>Nw&Bks}{LdZ&YAoSc$%%chUiBLPJwp8sDUXq*@O6yV;~eW8k|JySn-bQsCH^L+p9$Y4pJj)}H*pYRzvK(=qX|1(UVoNgS!vh1;E%#(Gf*30IZcWjNM;lWMq7Vj~#oxNg2aXL7nZbd8#cM zJEoME#}X2qL`siu0kE+~Wrd9w> z>Z`v+`v2|F(9j5)=C=NXC#6ce5USA<*?}Q%i)HeTb@DmaJmu0j_iOu1Oj& zf~|HDVPA&e*bp>WD{UHu!aYLdhFkWv8GPV~X*MFuHA)qzlj&%b;bwNxnV_KAZ*YO< zO}LUlEO`c`t+ zV06UsZfHt*V$&c=QQpW?A|RlN0J7yJBqeud?Aw;!KDd0s&c6+7M^m zPPp^bo%efDEHBBq1UWwB&zwD5^RD+q%TDYgDg0S9WwTI25FasIF;voE^**GWRVEAS z1>`l>{|g3zW)l2w!(z+VP1^~x0dRQ*g=$9qKDIqWmhT&2x#_OB$0WdN&!5F5!OOhB z50uU0;hXl}=`J=4o>G{n0@!L4iJU?um&Gt|4hcjLBkjlRL1zf$14aJHucm<5F3-bv z^ts#fd9KB51;WFXN{bP69PTb)S|2!Y;Lci>liyX!hwDKE%uNAtDXGlBo&YIrOvMq2 zE5{WGTnrtv?((10!jG*(vHC%JqM}vLIWx;# zRRH#2VCaCmCJw-Of0G7!nesCrRehl5C3JK-I z?+N5L=Quucd&Bc#CIRx~&M~b2SB&?Fb6iFK@B$A?@-;*oZm~|{cL0~^NQr+fWQQF9 z)nc=U`U6-|pD(KfJT5a|8dqO#W{P@syI=>jLq{)~0hD<*mEQUlPL-v0p>d>1lI7359 zJSyZW1x)N-eh77zTo2OCU;0vNcG0eHrV6QolKj#4Vd-Y=-bhoYAC;WTUCqm>lK{^E zkg&A(voV)19|GgRnV>gTCq%17%<9%3!FtESO%$X&y_?&I+RQ;zq!r4`g@V&rc${<= zU!{(0^K#hcHr4X{WI9_NXL=OX5)D_S)NB=vJ=-J$r5t{Rvj0vtII5j!NBTj}HV@cB zrH;1tw{feQgQiL(vJ)xKSitey=iNb`p4>}fIBOyHGYlw^VU^^ZfJ@@`T{tk{jqg+< zRAcCe-~p9rPJJD7-SrOn=*11z4i0+}4aC8NDf}UBZd6DOoQ;F(wv-e3h;o}yiiAi) zYf@hPs=tB;E0$A-1|&&v6+j#n1eJMuU|)D;wnmg7CL3&PXjVbNsgNx-F}wqkjHsH1lpiZKDNA>WT)~$RMlBe;fAVArX5EA%Wk-$7yk_TZAi$WMIkv9@uT z$u$hV_ss=qisj(m`$~{kMYdhc1v^2|F;o*eN!9p^L^ka*vCKuV!A=dS>WABuT!97*hhPL5UF!|dd|rqd@p&mJV1{! zvU}sCWZ3M?lrs4OA}N(;axZ!}F_!(MT9z<05s2_N`Phb=E~vLSND4e`ke&%lRL>)j z+x%|+kmPmr&%G`=QJP^!;Jc1LK0dh zQokAFI$J_zw-oKFtJ#q8@$cb@+G;r?&;g=w z^wHN)a)Z}tjh`GkS;>oELdMzGCZt`|5|DGOWHyus{dWTjpx`<8?%uV98yohe;E&-i zkL=6a#XzW}vy696z_+p7wyoPF@clG{4bFsX>Y-ilCc0i=thT3Qq6APnzA$T!pBifZ z-WZQ)CGooI&KW}ww7t7$;Z{KNB&55CZ&+D|R~>l_&PWh)ysw%5T=hbYg`=y)W9Aot zobYWREsP3)JbOYBPE36^7Oew|VQowZJYjyV`t!2?mrLj^urYm3qSdqAR|`Ka%peIM z4Bcs9=(^tdw{@X_9lA3)IZ3*ckTogZLS}>Y&ix;|b9A!L^&cghZ4?m(aQ{j*)ar%s zER5+)8T^33wKbqjd-KL^QqTRw_QbD#tN2b6bj#aCV{1g^m)93s#3+b%f~)-=oVQ&z zcN{>F0d@a24!yKvD?Y)75|2%l;;W#rN&suGo3JdP9hE93@q-Xm&aT}3{WsE&XotlO zuo!il-fATLAV0d8u9P@M>|PDF-;BO>rg}5DetsT_flMS9@&5r*0gMlsBKgYhF zfp=WF3S-ziE5qU2UFy=H{Y>9<0roqB%-&yESopi0?&pXIIgmS)ngWcyqv#Tt5!%kf zLRGC+9+KSJJ6_9QY?geg#-w2Qdh-CVfuyC7u|jhZ8$5i~4|_oLn#Ee5m4^vcBK=k= zjCs63X>FIjVstKe&=YtBxU?R)v`}ajRLTc7BgP0k!c-qUa>Uu4q8(KAc?WQ-4kt)! z7anKFv36a2SO2rbXL_N`+$*8(uJFAX}1@y;Hl zFEJn@Q%1SXWv{YhPELJrjgSA$q;C)~iNYKcph=30Q$LqD^?9Gl2VWMUst)`Iqk)jq z-Ix5}TExWvWADr3q3+v%#~6$)Gxo|dhLC-$gcyuS){uQ`u~b5oof%mMMOrYjmnF&) z6|ziJR8vS&QDiAgMQO1+*SGt5p7T4ubN)Q%zw^BAKknzQ*LS|l=W~6o>-~OT@2mGH zT1Sz@jfOqfv@;P&-Q&3R9x-QE^U)=LuE7G?eeT^q%|pW`Wal*uA-Q@Uiy(DXoL^Lx zl+Nwku_GS?L*GYlBG>%^Fv^~TzbZjC!43<+$A^PV#8>6+)rE8I9$RQ+r-jeA?`Lvo zDt>U!6WS$7-owg7SC~I>;IkjC3$B`nDIk<&gG@PO_nz~~@E=;(VC2hAu#H&S9l5whC+_a-M~|jW~A-_6$yQBS6A()E0RKZ?UV|bc+x7 z5MAmCiH=sdlRIc=BUyh71)_&{K$YrA%R#x>o0Pi2hbh{$?vo)6d@RXqZ9U9CI>U0! z+)c<;+gQfFFZ%eLd{Mty59Yy>pjWjJWG{2RK2c}L&9-xk8_=`IjRuahaRI)AL0Ik) zSeBqodBKyTag+f#GTfxu2PoSESKBp>EqnYS zPd;2zx0!W$1d-|#2rSC^tlJ?tK3b6y$EmdC9q0$E{BxC=dJjru`*@%bKE#5b26=2d z{`}t4{RW6~q6-7MnVn}sL-oGY)mCp!e<3KU$Td}SM~$)#6T8Lm;T9X1!O%i1YLZgs zgH7u7u?PST7vgf8@QCsWVN3Ykj{@4lDde3e-qe0vnlDX1Cp9WbMOui%Lqi)L8sOWt zU)F8IxQGIUe^RUB(Oq$uKiJC0TCuki29zuil&Ix&Mruv-@JwAJKKS@j3J*&g-MkPC;+XH>i2j~yNu_*L1v zt-!bgn#fcIF^_+50buL3?@%?)L7_&c_bIVUq`OK37S5h-8`bPq@~$Y zSGN^-KkTBTR|4Hr^8DWKd~I=EFyzIp2JO0e?beOibKdJF6!l#mE@Hi&$G=1M zg;trDWUj8Tz>wky61<1291s4U2Kn@7C%$n#hjqWsKc#NDPymBw^v7#z@yZA)#em)a z0w@`YjTb|UuY1pP+`$#C@SPLlP}#V%zUzyD*Ep)kUklliE)3-qdd95Pl zmOe!M;3CCP*aC|r5_yxbkkAgOdc}VkHX;tMja1}qv<(dmoR%ya77@LwuD0_kDK2g) zF2ulyjH1qRX=&E8NMqk@r=ThM&DnSI&15_d=YERQ)I7ro?iPEG2v&(d;y@wMGWUBZ zQ1a)YXb`R<;(M4y8v&e3!I#2^0#@ATgjiKnd5TD*Kjn+sXR*mc`pB?Sg5HZZa$t-`h0y|0TRBK+}{Z>QUqu)xq|SLFh9SFj&}$9axbBqnWImS zvN?V;rCj346U(&uop=;gEOy5C(q0yZb0Cm4?a!H>a>O^fCo;YfMqzs1l)+T{0PwpZ z_YRTTVYjxTfoJmk`sGXX{npl^)A!gKP(cwucg1l23}to0b=!HdY8qVh-5M?i+CN6J zjmQVFB=459w6-y$!71R>f;>i>Ac=em@`SuoZO4DYY%}d`wV;LA4bH?}CYTY|-HG4& zVna1M6n**KmZI_l0O}mXEox}CbL)Vb5f!)cW?R(RGrXPQ6q^3m1AnGq^kLB6(GiGy zlG^=3kw9_{3y1%BBBu*x@s-2rtZPx8q>MeUN*HhUXY6iyH&-{o03&bXgczoqzwv$G z`8GrRbBmUSr~>mpO+}1JgYcZZ04zRbwnH~fTY&Kb!UoP)kX&7dL;&en??oDjB)Q1M z%qn5d&@8Gj?_=Q8l@&(0&XEK(4YuT*xIin1L@X#m8(Oo$xVhq)cTp644;|__`RCR; zr<()KvVHt;3UN?@KRO7^+3haAI^tt5t|_H>#WYqInZ|cc+Q@sR9bk$^Aqk@+A4+P*>A_;lqi;(y{VP*dqXKT) z{^iS;)vJ`!#83jsMGYpTy%6iIIb8EBE^e~s4kc)E?VXiqHDr7*r>5km2a$Yvzrn;T zg1jbSV1__s6hJL;ALjRhVUH4&3+G(#{AWf$H$U^m5OnT{5Id?1&k(N#fCEeKGiD&v z`pf-sf60fbK2u3cxl2!iqMtqpHzsgUtAyAeb8#(iG?*17vc|Zt&mLdfrI^OG+#eBB z+CR;l{o(03+diEfwN_;C`z7D{k_X_qx`5~Yx)?q5U;)>7076bis~iv`BWc}*5lTu4 zaJag;7r|}>1$hq&MvPJzISkDny#+x2B-?_!Tmk3kc1HNG7_b-f_U+1J`#yRHmc#sL zR44Mm=1uZmjzXzqhFdHprP2*!wA{&gvTAP8X6suSbr0)8GM~ENY zY$fL(SEAoB>i>01ZE^t5-l0Wj(CcPXO}nt(iFPEi6|FlEh6;SA2XP@}otGXO9B20! z=;`SN>-`z~+j#9LkfR^7`~sNLY1t_f!yFd>%~uVLx4==?`gr{?1Y-`PY;Ar3Ae024HTg1_>HYxzGbC$A zYLbyM?7@trjRzc(O7fs5(80I9HT9OZ$1Va+woG5TcepO++XE^>QT5yN zw`r_NhZ#hoS_d!ivbzNqGQ4-s#6O1HB_Zv51u#VU`Aw2s%7mBziG`tl?6|H5&p6^w zbKKSBI1YPMC)MC_{R`Ie}rH>bb-cBG|!@1a642>mz1Of5^xLg%F!(e@J3NYHb zD#M}>2zP^M=wey<0D6~^(ODT>iyX2E9;!`9JOm8z^IjtUz1V!&uFiZ0jSzF#o#od9z96N) z4Ba9Y%&`Lx@ABJ%HWYYcYhyDnEH)?7;BBIhn=e4hjCW9Gl(&j(CL+X&4ERmO^#D<^ zYXvNIX7<2NXxSyZ+Y}n4J^3?F;%LFoo$-9^VDO;=k)AIQ(o4<78*#m+TbB$$d26lr zojljuYm*p8Gy+>|$_NHl+MSrmr&sPRE1!HgtIQH@^YeB1gt(=i#K=SD2(iRJVpkG7 z0jCD29~T$bhdS8yhgtUXP9xZl2vWaVzu)!9iR!@qydW)(G zWpdCXzVQY==EY$zP-qQl-E#pDm!6=87A-aZ zrM~Ab5VWsXh)x(ND8dgE!Tx*;$h^0mt#x|(y|YFvjG^(v$3sDhyo30okgPTEI=xpL zqXZrjvBc#e2w6S1-0$!74_@$tPJIgH78>j$o919Mjoi5-parEV#-*sD!t5J3^&aq% z;0-M?dvOXjgp%AZ$FsoOgaB^?Q@{t-Q^op{9KZtK(ZyxCPxo7+aoRtsp~VA#b(w#A zoPHkt$8{qCVNHC2HTg-Ai#Noz;USdlGCmqul;nLvFjG}i^M%lz>@BxG%N7|-Zn((s zFD*igt<~gfOu=99PMmu8?%lw!(9o7XUAz>cZ4X4`SZljRqC*l##`d;+gU>oz-CiCU zH&vW03+!K<4S5|c#}tnLkg=Nztjm3H#EtvrmNE5Y4$hsfIcY!RRd{Z0c<$^JziYhDukAxDSjZN{$l8jE zsZP)eB%fUThusC3(HC}y7hotv$)xlPa5QFM5lowoVA{XTw6UEW$ma06FwE7bcFqx8 z0BC;2uIfc2y_{J5+6B6S81$kgs6@%YG~WbsuaUNxKZUS&_ZORQc>)z#79ym4;09*c z4Vz({ugg2NE!;ODDWi(J9Ov&p$^ufBI4kn9LS30d1DaaJwK`WSz2@=}X;LC6AsK+U zxP*ktru}W*0ALI}7lFVm-9i2zkLmLF+Z1xt?Vv6KhP?PdmzT zW-6)rxup8SM%vzM)(I*h$de(nm)2JP48p!KA>LNYx9ZWf47p2Vf&}wQng79@GUqJw zQrYqYH$WrjaM`?+NNhgD^|x`&lMCGW!u!FwY@JU6*O&>g#!ug-Z|IaOv0j4DEhf@HaFp zLx7)B7%VZ=$u9`K;a9YA($ot|5{x6_F%$s2q{rrAo=o~b$;0N3Gk=?=3-#B)ta?AO zl1qx^fCQXe4W`7*9w9-&=xaA__#es3__z}RkZ`qTpqYdjJJp(&^#{qvU<|7po}O=2 z?n^Jbp~?HSfBNTTXzbmdZ)I&Jdr7AsH6rISk>LZ!9?9Fg7-V2YCBWd{d>>OB6URNO zdL}NTP_AU;Zh-4_Z*OlDZK@}ysYAd}3OmU5Q{KP_R2xsW@`gqj4U|rD_P?BNc%hY(1FsuA0E`5AZxbIs$JN>wm6d(Es(A2b zcvb27k~#(_M9oI2)2hY)0(!%8mNHWOQlc`8e6!hJq;@-Ub8+e4LryW3uJ~`T>S2o- zvgC_v19Y;Dcp%%V{EnZRDY+7wGgPcAEXw;`v~o_}@UzLYkmc^5)@qYH9dxeOA&3oZ z7qy~?0`6JzASxCvmes`LiNtr|nNZH6NA{C#=M(mNWvn;Efwp0!@$228B;!jv_z~|g zK>0A~V!IDqw2;l7X7AMLC%~fGVQH5w%Ye{1kB+g@ z#>b)K?8NE&eyZB=a~NNxJ^A|p8fgGu{dU?aUbE+WXf5_z62Jn2nj2Bjpcn>yok{EqZQfc^&g)-uf|q5!rEo-?8S9p?%= zsXC-!`tB?Uh z{a&>HELDBPGxYsZ;wP(B6I^cbZ_g4)vcs1E<*TAE4Un(T=9sT#Cx%H2XL_nFY^X0- zU+bGXIkz{p`(F=<>vn`Fz$dAHvs_k*Si)B3|H87ozaD?Cqhm$@ICM{bqwG0n7^?HB5shwD6}m_ z9qN)TcO;elaF=q~fg6YN{k?L_dg{-}uEHU%*Bbe2#x-x6w{;QOE(YiL4>9+V{@_H# z#ao@pC;5>vEi)8NV@E1G2ZtR1A+`0a+Zyu4B9$HAwFFUR0cfhF zpUC}`8>$l{pgF6=qKswAluSbsc{}}wpBdRoW5E_5Q9%Er9^$H#uip?n{x~K=E6w!Z zZ=FJ_Kw3(#k1R|Fr#0(&7tp3;X*&I0%_kG!h=JYfUQlGhx(6l(lcNIGBoY&(p1T zT(QAZ+$Y~__7Yx7}WHzM;w_8gUf726ZpunYx7+UbaR zVc1@w=lydX%KWVc_rP+$4D|c2v0VD*U!=*~;8#@cb~Kg!Oh3%Q__ZFu}jcty`fD6*xT>$lb@g6)2<|{}7tze*oXv_YIiT}Q;Ht?<#%@3<5frS0M{K?~712`1(tBbiZ_p}e zsod&z1DDwOuUU_=It$$Py&b5z&qrf_*kHs+fB?^hBazKjRmYb`=B@_vH~z`nSbA@+ zaeDbf2nmJx=buV*6ep!}EI9;+V)^g?%qOAvQ6GYmME?69z+bIUV39#prb@mr{P&gq zb4kPz%M2CXx!L1?{-^)^rJ_pA1yqGXtTxC0_H#hjVhJpxyuRTq|NEa&EBQ(2qRPdO zV*k4F|9b5fDGG+NEAJ!Q|KZR6Z#-bhUZe`Iq%nmvorxM9TF>W9&$EoN{g=C}*o3@} zPH_)-9Y*HR1QT$%%)dTmYO2OLIj*K{P)xj;rk7(TQw@W;qKr~8-c%Hpg2M6`jTR?M zj58oCUuQ|14f6$zG!8J)11^%iX!Vu*Z5pD-3S5KQ#@+r91 zme%dh)N4WaeI`$gm-)xuK;QM||NDuG;xS6kmia9Et-{1U^AL5;(ygUSLKLQME0mcW zVb!dWEpZ^;&ZRp}E-Vc|U%?v>A;TapG(qhMJSETO&BAc!3FnbJ&+7R+@T_$?7hcf{ zJvqZYa!WOBb$9kN_xJDKMKbKVg4oe;YlI*pG~J@?jF%T_ZL4kg6NA-~I9YtY;OS7} z%T8v7K;IVP0nT_iKrOBnAN=~PE`9>>`(YH`*R4BM*g)uR!LZ~WvWVK zy|eI3#;u(GmpoPDS=scmB|raXbzJ{En;Uw6wo3k`p3kZ>CE55swq(!cXFIk&`Zb%b zuU_Q;?h=4BiX~XI`dNx3vfh*3DeSPp968_Iz2!00qd&v8(Ji-9I@}ZH;9B>Xe1t*| z)Z!U;eN_EG@9#dOzQlqn*{KO=kyP!~`f7x?Rb3NN3yJZ6U3*LoT3mXVd;)zxq&<_o zzet`CUpK%~NK3Hi07V@d6vF>d9V2MpF{Xq$1o%EYg#whg57!Q(5d$KtKf+jo*eP{ zw6LT|o`AZ*V^uvGChppMKj0YPq=xIYMS~J%<7b_l87q~6Bf-~qWh*A&ED;Jo^Od-T z&p}g-0lC%BY9UJTT9GMJ?~}Y&wd3ogk05VJJ_UNoW^~?6l0$cckH)^ewcVYAN2ZrR4DMOPxj zv(Q3zCFwRc1POQq1z5k$G&+p7Rajq^rh zNJXLO8G+cYV3!m>7W{L$l6Nm}nfOfhgA^Z-Ah)e_8eSgRi+L2Vgj;!(KJFH?SQ^Lg zWs(7{KWAyN`^oL#VtPmVAv~8hJKXBL6a4LPb1yI*kcKQaG-X^{)z|0O%ecyNw9(j? zpq|NDAY?4fjEqE)#jhrVPOhQ3gI>}1le4kQxhLLo^Md&IbJ{Newz{MBCB0}uV0^OF7ux`>KWs1MBcG&b4Q5?cnSuBxxufqP+4w9Ngyv7t4oD>w;A8Q zer9;>by0!S$X$djdDFNwtu*yZUU> zsL=3c^h~OFli#`OX^LC%Mbeb?Rcw=OxRG$>!4EenME(hF2cJtl7h&2NK0J>ecd_U593YSPNgS9Ch)tB*ukvFoR4!ci z5smSXraXn_P+m<>Cl(R4X$=&*)n&oA#suK^GuB($6 z)|mB&%&@8E*up}xAvgB2FsYwD!QX@)?`8#K>hS18aJxBy(lCvVbc8}QjUSSQ0gW3@2{{jS3Sx0pKpX{v%-7B;nQd*0e5-8m6N;=S0M^t zNg+u%$&fLEIWZ)mNDP!K`J)zcseQmGe?R6F@rP}{R{PAwJvm5rLKWbL*Zv)54(-G@ z(P^@*B5)v46k47!?`=*Bj(c;+Sns0@1w*=g88h&2UjXetLbg;!Fy|2s`lXPjVGi@oST!4_uIa8mS%Br zFF&69+nE??`OwI;kzm6^6(|01hIjAIJi=t5nhQW4_%gv;5~C;MHS{};X7+wAPFa(0 zp?8NHTORZP<1-U)*dKFX7*nI+;WLoKQlk%e`%ARpgt!pB_)WVEWoy%ECxK}$AaJmJ zeIN(lY5ormPeFmaLKEirX=qkOWs4GQ6sR8frVm=rJue?a&B9JEfdK`<8-@73&3!ac z+jz=x%)w*1XN&U4`wsU8U_5R8fmUe2$LeAe~xmYYmSSTqJX`cGK) ze<+?^j+A_s&_IFDOc>pYUHT}CoX9YC=@#*nrlvJ#SOe_^^l?qhkwcsObB9)M3cQ1d z9+pD7-If6AnmEcw2=q0Y%ZaDa>o!v;8 z%#IXB)VB)Q^IUM6g`85!whMTZdyQXnj7Ja%T2;W@-?u>jY;@6OVUT{+IIDzGv=agCSg1{Jf*9-b9c8UuOWc~=^$!-%vt>fq>D{%tYtB0V{Io61AH z6`FY4sJ}rm)O}?03oKp;OHTWn{63qEwIqv>6;F}5(NschGOZdGHI-D6Xyyr*V+;kJ+2e{8)%$qL&#!f%G_-u73~gEviss!K|7UL9ww5 zph|;fTQRMrtMFCUKS)+^OHsjW<{TpYLE~FFOtOp`AM|bVSJd>Zo?Y(jdrX=7C&ok4 z_WoPo6&pAn)kI;8{ClnETRE;KZ}6c_;q{?N;c*;`KNcM+WOG` z@_AS%YT8s_Ol3hA5NAOi+@!x%6Bc7UXfW5U$k_mFDPtpx>z0v{cU^*Hh{4imuRnZ< zy-KT4*bpV)*wArc>^3NS>B%>FOq!@6V-Az0q{fl&zz(Gm!z z!f9=*t(w+pT38Gp`lDP>Js7d_#^umz#UQp^Hs(8S=!HXH-5KDb1$L76qoSe?gHi+o z?0x)ZEcrY*W{(ujF7uTLYeUIvfU8xiN~F(8m$(>AQK4|5rV99NRX_C50opzGI0ah= z;mqKwm{UUp9V`YWF0bDL$B*_E+QF`1KQQrl)z^o|EcIg1Cj}b+omCI-2ZdBk;*dmLCOF))N`i=o=O0xwHnmHH+W$@Xl>i67; zwKlJfo;8z4h*}O7Jsn3hns(aTN{Qm;H?uf|$>v*a&}bhfH2p8ILR7&6t>gV^a7$8f zyQvD4t#D;*_g+IEHSA5-d*3d@+>?#NFuePk^>wFw_BSwgKk;u1=%L43IT0*WdP7iZLiTm#?}UFT9D*6T}S(n2%@k*Rv^$Nfh2L=OFqdQH*Y}w{rmT&A4{v? zH2iksSTPPmVkkm3bRU5J$VtA~RS3MOxH-O8YzYi7{Fy_9a#wSjt{d2aFzxz4m194H zK_U^8EO0@IjPc|e%!$xPTOc1#_?v_PCKQz)s|O<{ zJPRyjzwfi-oR4q8a8d|!5)DySUZBE~26vVwZ*b`;5O#)k;c{?}=(`)R==EyBGP&~@ z&Zbq}pnP^&nfb-u7!?iE%@8Np=f0mFG4+Ofbc%UlZHyq*1T64jpB%Aie!J<{%240TR~>j%N+J#W&8Vn>0R5>NZF!J+~9( zbDfpcn5UwCmEzscER&m_V&&dEhG?q0in{G!#6LG_t zuPz7VSk*{`3CtO_1)=8ZjU5xgL_LV%l%vkv^rc;7LCH`JStLCtWx_rC8yk1VgMQ;g z=6*G02C^ULKN&Xf!n~^6u(R4-itI+-58+BL(keM?p}WOsh}899`}A0a_dy&d&8&cK z0PUM1@fDJ}h74Y4X<9ZP=y=BKS_>H^gcZX4R#?{<`aE*Pu11~1J|ongntb{Y+O6(| z$9#^Hzvz%nfzH=nknSbl<_R$*1YzuFOWah#PJ=(;HwNb9A3~!TT=pF<^U>l&yI3kx zru{uJI<=nPIU_*&EO&tUmzuf~VNtH~IhoTh+T0@l>mRn;?ao zm!dvrMweF4@~zlxm7!8*0ZBOK^XVwre!MwY6oaf15&p%S-lDr$Ey9t~=|ii@k)!-h zpp^8q(qEe3ex@}(eG0!ll*>k(m7Tr!-GJR<&?gw(y&1ru_E7hYBwwqKSWG1eK*+C3 zmi@1u#kmU&cW#3>9m?7A988(^?ph5pO~;TL|A_QmG4mRzbnrCz^~!FfON?niY;vo% z*-9F~+U#S2$`!grU<%H0+a~Sn_qTqp1u&@L#Lg8ypWh8%Pdu!XMiOk|ulmCUG!~&Jz2A#I zbPz`ah@)Kv`tm|J2m33p6=`_r>gsa;VrjnLH+DxO!TZirOeB4&74j0>qvAn)=%WI) z0Z~f$J!JCd%K<#>9Tr}y_yB>-_sn5MVlr(CrzxqkJUH_weOz#T#kO7llenSJ!LQro zcBg9=(B)`mA9e9JRLt&>wNh!oR-66Grw`1jcUX>Y?+W&ST0gQw+^3<$7)@|lIqzo; zDL@iHGkzn$?i#P~y@MV+u3s$BA^ly6LK5X11`E_0YuQUN-aMPs6AKxsLW+A~sC0us z-?I)$Wul%iyc`qq(7>}jnpJZ{CKFR5ftoyr>piEGoo^rE(zG7b3Sh@u8m#UxCYNb0 z#4Vc0g1sJK z&x^i7m=FD@d>A~~p?xBrw2jj#>lcSlti!e##$5x5-v;!E9r!)Znbv^*9{Y>PCF}gL z2dL#O@e~(6e;>JApfA(Y`cx0MQUq!AFtiapU^NK($O0ikkHRCeE{FKFCc>tTdAMQz z-Y%!-bD^@}ly-Vlk~b%&60-itoFyHYjAM6nJA5J&_(Ut^rzOKOk=3+iN=5l&YnOx5cnXaHCvCEnw?d2<9^fTp0D;0MYX{%S{*(-&H-K1XA5$Q z-@Tq^JM8@WJahHRyoqoVt$Mqf=<1U`rMPl8@|XW!^6iqcfvFe$P=KUdmyNtpC2J4#3%4XQ=YW57P_G?AXwS zvCIDN+T+^g)<-c=lg!h`wX;F%wJBru0WX#(1-h~eK5M^VWr|gf)f>m-7*CR|x2)yU z4$`{0eAd&vMHe|5jG~%g5a>rps-uayRc+)6BcJM;3%WUbHbK13_s<1W)LJBld|5lY zticJ)?RX)Wjv19sUeo>tLz6%PxcPuGMx8{E!Ti=-7-+6m<=m%-m6kzZ-w8LBKic^7 zeEZD2Hyy5_IWboqyMhmb`YJAA_Zz~)EUWS!d}dm@4BI9V0#E!;(s9Aeu-vkxRuTt3 z_`4a3yvjm5wFp}@d=5q&wBe7k%caIV%TjKLU5nfz%9!@olP;E;zXBCPh6)U5lgy>z z%!&{UJU#W=Si5zYEqSNLLQHPiT6Jlu$sM1E^XdR%Y@s{n;~Ia?b5cWMly-H$T+Uf? za~~|^JcYA;4H4~fm_bKfnpwckt6wc-_$xmvKMuxLj8UL3Tt$$ZIeBUngQm(ArO!as z4m5}^t0WH^s|A;Bd&HdE6pE7)R3%#D`sQ+$!hqu7@4!h5yaX=33%+Uu^Jev1rgVvq zkA(1Mln{?Sc^!_NC8m;4C~LnE5mNF*M#s%|xc@ck$Ij#t{lkxWj)C8FxL5>5;A_q? zk&26`${2zU`{K&mq~-?@)J_tcA8T0-1d6j_^yFYo?O;aV`<(u9J{+^Ym)t&}_4OX` z1hbUwEf`P%93Ttx_c@TYcC=(3)Yrf_J<@vKWB)-wuMx@r?xl1T%pu_CK4M8^!k|K3 zozpqUGtEKpsdeZfc4W4ui~(LK9d{6WwM^{Awt->pMHK*-wVBbds0+P)em+p*lRL;q zMNl2*+_D>E!HXQe*kJ{=)4TESAyUOieo`LO&?o=SAMWYkSWt^zU!Xt^&s=eC=V5 zSRbxGR>=lB_z1O3GdH@0?4JY!7qU|Ty7Es1%n83tv#PwCMOz+GB8JhrdqC5gMAIO> z=^}u;_RHOnBnvTHD71?ddodQg^U>T-n;$)YtkjMqfU3GI;ssEo%<}uhda3!sI4VP; zmzo**6{Ops|GWl{Fhc5E?VvpsQj_{h?s>-%g@jrg9K;=K^3PNoj6fd2{&*Xpk5-*( zugU?@1@Z{MoNGBviF8jDB6m)N>;-WsqLJQ{t_Skk$jSf0!_c4ppW4{{5wkvZ#{ekp z8@H&%u7)3w!>FZOBF>qKWCqYSR4gXmoaE5d@ND#s4g)bCv^Lw5)Gx(k2Zdm2w1a&+4uWJp(<8K!Es!T1c*I&`cjqw8&uxd+;;`@Q;Jp5z%EzDYhhEle z>BIQe6g60&?*_!B(oSxFWC2d!mWBG_Y^)sAdoTyj<^CDv(`U|P#cw`NDw=^(qX*vN z43rwI4kD~9#Zs^YO_=Ab3l-b1SyxG~=a#)sw357%EN;S=b@T>0KG6GHmZhQ$%c~oxSDf1VPvGe+TQ_XYxRY(!xaRp86%8k4h|y=I&Rz}8nWkIQC6 zJ>8OYn%m|PdS!W;g2kXA<7Wrs2jC!eZ91f0hav1EOu_sR)?q|2BMU^l=+gQx51L$h`gbL~kc`ody7D6pUf4K7R6~zF=K1zM31V z0}nEJk<4VvGQEn+yq~kQAL7op+x|JM)Z2*Hd8>JU!-hZ2#|IjeCI&;`uS9o5x*BeC)Y2cA-@*{^k^?x7+?|y z!#2;mApYQKmgPel4u^92;C z^4!d(Uk!CPb?}o6)Zs_ze-JZMETlBA6h51LEs*KkvkW;7_YSoEEsB%U!C0JBJ-D8c zkSqgaI~QmPN;(MwhtRDv9J~9q;_zdl_}nyj|B)Bys%QF+*jwJ%89Q?HQ_W~8u`+`c zH2&g<#*Viqe*a>wdJuKw8Z!lJ+{kUmaz7=fdQ#~g0`p%=yJs1rd>BC_Zogp8Rk5EZ z6AkUkt(0Y0llWAtetYeha2!T#?~C;jGXlkL+?z-Y6Z8h~<|B!B52^6;7H@xc8;pHD zvEF_Ki892Qch3g_j~D5^(y93+$lmauO#kX|^*#r~C#AWMw^I&0iD=lwiOL{#p8o?! zqeXy9_K*o#y5-NhTgHqQMM1Qpn$t@!n*{z|#fK3S3@SS&-Uz(5^kPjAhEMQ8@3c7L zM*iZ-=Y`6%K;vd&jAFXx==UJB?*#0X}n;n~w3h`N_f{cRctj`20J&Xi1Yp#|p z#j3`1IDUs9BVQoo!+s9p?|P9=(uCaG%EO2eNJVk+1MAz-#YbU!4m6H!0c-6ai4rs* zC}6!w__V!HJ2yfR1iqy9(4$m5?8J>DpX^9Sryu}iLlz@|95P95TNFtIC!~D8O4GL@ z*YIZj7<&c8>|+=#Gn(TY+^CnIh@q4>eq;jp&@mm_e3n)gIT<1!yZ8JS6X^T94iHaO z+YMvqEz3>8SRBH*dnn!S>I7h;)vIq<6aHpD$>KMNcgffB0JV z>tCN;wlnrP$40;f{RPbdg{9Lz;BH?s_SEDVKg%*wVDq%=s;T@vA;Y(K*R~Lw2j^t$ z15l@TAdW}!qKhrh-!+B!7Vo7r6DcfceYt<-QQNPCKBUQ0JO4!8q9u7ActiY=0fQ~82u{6Sycn$Rf3_lEng}{PRkoIrrcX(f~i2Q((&Dzo`Ql3 z-@{vMlx$+42Utn|wqmv2X-*pxv;(oI#`-j+1I04J|K=Z_@!>r#shcJ6CJ%PK_H}%< z&+J@Ux5BTK^6J?(>sQH`-5ppQb=&?di$1Nds3-bm_C9U<27mQ%>`AYkHboOo z=%$8G&F5Ek#nS-tHTsNlkOPssEKSPZqW;*{3VxQ#^Ux^0c$uQMkd64(e>X`Jo{Xu) z-&R0ye@I_$38{%ITq0_Kp?pnE&4yb@NWH@EWGID$#L&sE&Q&LgY4I*fHsmuqw6rWb z3&Y~}e1c-(xIxilHh3c&c{GpD@i!`S!OX$@VLGqF`UVFEgsd>3kSosXj&rEu5 zL+=cDM_$s?_v3vf`I1Hx%6tzi*nBu((?-(J-CH__ZuoVx{pmLEN0zUHGn~p>G+63j zZFKTuOKH)GG%IfYO?cQVoaGVe}@INaYhJAB4W;_1qaGkKZ;xpRR zFX$55bTkyph0*&r2#s&*B-pT1LoF?z6YY&u&KB`yS4uvFcy&y0 zUH8+cic8^@w?@SEhjq-*R0b^As!TeVu~yDrnM*$Q%RkqrYA$gvnZS?2zn!W`@E`hp zlDI`OF`Q0^3Bop0V$e`1nRtQYeBHf!_by<;&x7N}CO9)jrO~eRrBm*MlD{)$&9hq(V6~E!Ii-u_!aSsdV)-O-T#2igwM5-eZD~bOT zfZ_j8g|3^ibt-9MJC50ZjHD=tRp@Y%W1!;g1xdOe%oVUTr7o_S5W1L>eW%yX#65}6 zE#|jL+&I-VyoG3VKP#@&u5h{Cc<`4e?-jXEXHnS8rr?(_kVrl){e5BaHh6FUu4h#E zLG`VmD^Ar87-~zT=?hV9GPag9OB7YJSJ<(1CMA|_I`qUf5}+6(qE}DBo^3-6BU}ZF zL2VR zb+!Bc8C}@DR0{2c&6RPO%=C&cvZy#5349AXERU;-F4E< zEUbO87REU3hdz$tZzrSYUG#P^Zvs0t4XidCE3M{B6!$+!y&{Ou|JXLkaJIj)=6PK7 z@d&zHEZz`+J4E>dU{8q8&KTjDH9!_#`sNblI5;WY0TJ4)2j5!Zmu$7Kg~%y5$dR_8 z?YBOt$bgwe!qw~m^vKW1>{}toLPqj%T1dQGgw$oo^LKqn(s42~Qm6d2*9BA&^2AuV(OaUY)&^i3$?m&T|kU zBimsfieqP3Z*=8rYNc1vv2CJhkK$!VcC=RRN6vii6x6_894n@BAB>FtM|D)0t1}uE z1Ocv!7?#FfxVddJ@#QuDcf5XcB5kcE}S^fZltsqVed24ej^xUZAaB0=~}D?z8~|7y~R0N`Nl@KI_$aZ#qqB zC(k%9vqhDJHQpw~NwEp=)jI2c{CL!jGXKW2PGi5T>`)4vMHg9Mu%K$6`F!^7bYs`M zCyK#Ogowwje&vb87%+Y6=U`Pm>Jyawc75i)GBJy<)ZxI(K~M&HZ4+ngguOcnSkGxz z7;Q4SC;v?+3`*Guc}T+>+`5}yu?#0srLJU2W57_XB7TiPmU7r8XxVRedXr5G%0ZP- z$(ll`_|`Hv7ON+bp$ekLtuf_FWAQ|%(BA;&d^@*z7r;R*FFcbfp(HBB@7SGqosHFn zet@6%-dQz$ugZ_IjYAvZ+5?702qk}#2!bOCYS8;@^ng)!cp#6A6_fZ1=f=}-+#NzF3kX!kfdoPgVn23(*&8b}dLP#4yE_)9CL{{5fTj3+{>~cA4Kk$?4`Jd+y>rbl z=q>d2lgMoaW-|IPIyi(e#s}*LI)bv7j4e&<^(M7n#M>(j*y3l^K$AGijR4L*_@#4$ zhrp=yb=2~7dE8a{#x?EoiO_E?`5^WA@Aw(g`K21F#%%9!zB&xXt5 zE+`e|L1k8O%d&l7@3U z!Ed9wQ5k~tXCKk!_%Cr^;$pHQ&L%=`sRa=4@ol#vt&I^`=d($p5Iqm_5Asjpel3Tq zY6z!{<^MOQ3=#|>kA#tzFNw@(7|rvBJ8u4aAV8j@{#Y$)#qYzvVT=$s_Xh3pg7qAG zagv?|qhvZ#C^0L&FJ5a-1zo?5;yaf6+r@n_3~hQELU!v_+GEGA`&Aweb^A_F#NB{4 zWgbxZT|lyfD>Qvu|L-F1p_$9LX(VUPHD8He`wZwJ%9>n+z@?f#u`xwwu_@rM-6f*K zQ4!^CD+VBkcPIP1`(c5Cou*7xnO;+JqAGrSwC|@6o$Zo@k}W++ThHI^(OfFXy_(hb}#tkhnMI0XA@#$;3WT0;l*j z(<awvyGh2QFC-#o(^!R`3xKA(6U__U3{daXYqVdpNc;&*q?E1b}OKLv~+ zd@3-^hUr}T4$ixsVH$^$4BAGz=+rsIqt30O>I;{MB_2Nq0g$%pZ{US4dQbQ5g_Ccd zAJLE#=qos)@zx<-{HFL82tFeCzM7=FPj}T9KK;C~0E3t*_xHEG<;SwHY~Z0!%Q3b4 zJ^6Bw>wBo&r>=DYyL?eyAPPbiq%#mD-U(u)QY{Oa98S!X7v#40oEfy; z3WXFxa=okXy2F=vEvdHTdn&E+%(}Lw>fuk5OVRq5%u8+F)|I^K^mg0k0ZB}5k%=^M z!%T)>hkvi((C#TV>I}{%08MXt9x!)F&bqArg-#kfz&)GwqZf}OHvk`TGgb;}+-h&1 zD_0(Z0K&504~7yY?u-X0jf-+9Y}=VLXI{<>RPEb_QLD5X#S;%0FqI^Mi8ubs#Gf|7 z0TZ8f^q1N~cM;7|bpz~!Eyn14Pty(Mu!~4Wmt7IJiyqPFnhG^zMN<d1ffv<1b3zJV#Kpch0FGM0V=$n% zB7R=w0-P%M@#QL zC-25y)$}v^W)maDy<3y`6pe~B{^j<)A!uOuV>V158_y&5ccd3>0MN~w(A(8Vtub#!!GDKSa=VeFK4 zgJ2^}MG0P8ivh!cL!u%R6?Q1VI3SDG=(ODeVGZzm88rLbP<%u@ zt*rngNUz_$OC1-mT{a%8cM5>QcP8eK$Q9b~@!U@i`kidMfr6Ei0%ZpXTCbk!uyse- z??U0Hp)>HxJM`pK%2OBMl5h7*ovVkM2OLYhJ3%?_u{hBHzI6${$DJSe|imB@0z56Y?xXL@<13>{6V;SFQ#MX%PbZnh&x0 z+N*A>-IJF)lJ_KcO*31X?OmN5^`PQR^-ZMfOGEF)|Dfr*hdkz_{$6&X$BWXniV3YiJn{H{;m=l93=dA*+3^L%~I=l;z8!Tj-I zY9{QE*J2rI*V18lxM@Y{6(#j^E#LqzgEIA(Co{46@j$Fl%1ic(5j21l{*XQdYnU;( zNR=OwOfxsr2#&Yw9S5Q0G82}GJk^J};HTVpPxmwu(%+PGlb4piHpdlvK^J{B%~sbh zsSQ?yRu~OYQFrJN@q1g@&>hB9IX+CFC{EygfXnB6V#&!uR-CSbC1Y_mU z5U^3CB(e=E$2k8{z`?TBjOAV>-JrruoL{UCKX39B-ZvzP&I+~hW!sx5`HC5oqnLrgDw&W$A0nXieK0G04KEvf{8bg2P{~xph5OT* zQ|cgjXFbafSpxm38d(Ji^feo;-?I z6EFLk@kR>&*7W2qcl#DzG-He@rJPA?1Yx#>Km+LFbmh7vTbcvzD%t+{^*v9`2>wZ@ z{}RQ;#XVseIkqY@wlJ%QC^)BAE2~hpo9t(6*Fu31)y*JQrCS7JQ=gz7jt}6l`Et)n zd3eaWLsj|V_il^f`VWFtcq9^5ioF$B24Ete+QdoS7*lR+lD)-)498$qo`bP0vi&%* z`HMNP?j4>3#-0C6PvJq&y_0V)BNQRuOr(`E;K#kb{6ewf%&fD!m#3#f*w>`2tv+>* z^BU)n0-BDG-8u<5_&H?;*?)X1_AOmbt@mH=CVRlVw$TW57!AvFfm-~u0+R`-z}qv& zfFOLmI-0fMlka(bIbR$e;e!|J6G!7p|wa5wQZa$PN8o zHvlOI0>3-}H{|2O=qM9&^VPo4)#<<+%fB@7rZ`9-fp9+I`2GYg)NsHt+HJe&V3|mB)Yl(6D&u$6AK@N>*@iV@LS*dSr}PGT9#g)gx!R=j9rW6T}^UlM`@* z-j!L^y7n=8CCMowfW9n)v|C$A;A9hkp543N*SF6{5v9u|7-GU@38?X zuuC)A7f6A?*~3BZt1{%5Crm6~!qb7H-Q^t6GEEby9A5F=O_mfK)GSEGwB1T`iQ%IL zkCLq+Xx6n>-Cj9YO5x=Tq`Y&<_*! zDTcwk-*@4n?2%j*;SGrdU&<1ys7V|e;Yl5XR<|dxwH%?_Vs4%Pj#mgLz!+`aDzR%D zDYcq*P#{mqyG}k}gc~dM95ZV7Yk+J@6CVU@k41n&fIoo!Zu&oL>OGaOE9VCPK9Wvu@8=|UvK#;densY!T*FC(lSXn28*baU|1 zW1S3YlXWtS^IcU&obh%$UmQWo${euC4+|FvYR8c@n$EqY@%5_;j*6%0ovx-=e8s9M zrX!8uwlr*hR0sn{23S)c8*|2JADHweDLth>twJbMT>TGzemx26Ttoy!(kQ>ac zz|ivcg#I>R(V5u>gwbHt_aETba9D+OVmGJ0YLF`aB(7;txD^yMp|{#_H+8mgKbfyi zT{9GEgXClztKMZ>|JABU;XAk~F4J12lxh=+L$dfSYQY#lob6?RInY#fv<2W?Uz{`C z4C)nij`RjG>lKL@$O}Lc31p=yPN$r|UZuzZphUq8Z65V=ncR;p&+6;f)ue1CsW7od z0AC%!owpQaaF2RcGe1#rCvt?I0clDUz%&l6#1?+d%sr~pd87Io%NPHQL|sO#@(0pE zSxZ$3!G%+vm-i8U{goXpe97P66q;WStOHRgTnAkw4$O(SYMoke)IL)@)}e;<%>K-k zvAU*8J$z(hWervN)S##c0V|HK6n@yq)jK$duS`rx+B+vdzQuJB_|!iqS3 z8XsS9!*=Vpv+M8Nb&I^6t-7~6X>qa?bjX%x50W(qY*A?7-%2`X)gFpjpC4{@0s3gL z)%)$~-|Tr=Bk`ll+$G@Fr?&X(2>4V>4};(-Xko*pw^_Ou-kV*JwS zYBt2t_WLN*BnL+L9?#V>FRA7qLUT-A{_nF%yLF73)NydAG#=mVqdO0q^I&vvaP2~? zR#b@*qTRZCj6UpxV9^}9d;lfmPT~(VHd=Mws*G*-fZ78a{Z0jDWqIf(T%S%=Eq%WW-x!WrBl%-qu z8jB?~!Lo?sLHNgt4xybwnM$ez37OS+_#YnK!I3dl@5PS2!9cTM?Gw;e589U1QU+;E zItgDL^S-X_MA2*U$)t;z@{Pc;z&2w_Pl0EA`CT??4+*7Z3zjPN)wuceC^3F`$ z=b(f-={h~pgqidQZqsWaJ4tgbGzcZ|fo;?!UX}hj!{)M z4bA1Cw4YN$&!Yf@`Lw`!m)_dBC&?YuwUA;{(RY&I6_-6MyXfUGyi6b1OU;0}q4sYw zc}m`!Tfxzr^Zm=C&)-`V!XB+M-Q$7J%tqRcUHzz%end;*GyVqyb_=NCb3`0i&Ey%C zrQ@Eg5h;N`BXN;WW=hc@?lcVzfi@;?^aFFq_XOdZJYW;o>ZI~IoYP@@R6|QqxibWO zo^nE9R`szMz#KGY2uRD>T>D)lpHfKgIY^+3ky9tf7EB2=Vo6xKMYri2(yn}ivWpAQ zc{6kyY3mn72G-J7w|3|8KSXgFN1looltok`3}5<$SjyKePTPZgIO`kkkT zAyHbr&3?hu5|O)ZRVwUhO~@WfQq}gQDgW^$HEXL$x6Lw&1(Z#tg+R$)6JHN!^*#`O zR3|(WvmbsCODLd%a^~60J>tld>a$r}F#@m3tM7%4nspLP?`u}-ki(26%r@O{nJlrR2(`Pr?&7{*Jcss8e*J;(y!-A;Q?h+s zlm$xEz^VXZLngE7v`qmQ7Z}5A@B2a^mdNU3ZHf1=KsZUeNjCo>)0{8Am^)d1s;GjgdagWNJl3%_!?Q0$U zu9O2PMCoEXRd>OD5Dz8hISb7RO)D=G*DT>ra^Q+}jR2&?G zDvTa`_^Jz2;I1vtJ1eJzw{-gjD<{vd{{f)=^c?guGg#ZG8Oeq^&jTH@mGI50-)_Bud?+vsYf&8%2&C@LCp;=n8*h zPr$4>0G63>!^rtvDcfxZqtJOyz_02q*eE7t7oM0PKf#Ug)07xvU{WtAk$CrxfFm2d zMFMJF@A9qp1CAm31PfLL#Uy#CplBV(Hc(XIl~^j zU-g3KH;)Z$_SQ6Flr0{$45L*|TBJ93X#b79X8rU1Yq=Ne?X36~8sTTKk=?Q%ed6Uw ziE-2N-C^zpfrqG^&bw#adE=V~d?E4I*F~10>KXb#+GzS^TJLEt;UIe0H(5i_yYcGK zCm7SX=Uk+ZDKa8dc~p5B)IZHTb%`Cu7di*L*;A%;t*wVIwck9lF<*%$_%l&U{y&+- z_z?4J<~Nd#ypxieN54EdvEF~6=k84nBXz}<@T&x{k-(WPw6N4|%bQuxjw^h3>u(Sz%#^*;|hZ6Az3_p)x@tvVkgdboEY)#y~s z>xacs8PxJuac$RZ<(~k};|O>>ZGIHJacG#Ii57v~Vo$a!Yj=}`e>JwKu)d;{r2gDc z%U>*UzcLynC&77}Z$Nf2A94|~*rU%HMOU{XYD+!aw|B1c$xv0r1_zq5|Ky<|6vIfx z1gum2t?XzgPlrmA$+Y4a^*Oi#cY?-X6sO`6n)4ff(e_j&3)x(y{sV15x;B7T_0?2l z=MfzmYR=DpClY3Nr{3L|`=?Cgu?S3jRonN5? zwNO*?7>mAdEN$6uNlmC8Wk|iBw=>^W5`@#_bQ0IP+Us^Nt_XI{SVQP^ZfG!$S96EBknv40y!qcWEnAjS_&$*(s<*Cc7$b|C)m<{u6qAty8#xq>uJmodG z;CoA*m4A{E!RN2w{_M?ExlDHjI<)riiQd2W-FJSHkEL(W1D||+4^$ub_3#ItFf$xA zXnoQUJVT;}pqp{vi$36UbssuM@kR()6%4ccO9bhuWlgK(5HT-&4Mh??{i|}G5-;-d ze*mS4WbbF4hVx(Fr|_ID%e$)Ixp)Ftj0_C$s}r&}tPKPYT~os#1iMu&yaTy>nCqc8 zS$yi;mNcPY)_$LnM{(_xdrONbdXg36sLNkAMmc@;h4Hh7hGJ)yG{uVTpFq=L%%61G zl9FjX%$WZFOo~usOiv5HCdTMc(3`O>YbAtS8G3;%zjr!jR(seFEzHlaHrdaL#S#>0 zikQhXD=eY;<`1ynA$Lc+>vRS%O2mnSjI_|d)^eaO9gL|y(f1uH&HOh+@qiQozU5*r z3U>)=TEN`p0;^IG3#cf+kK&wo8R}mOiqd#%CNPp%nXAC?YaHm|(QVP;;MdE9^ca#l zBoedcez6WoP3nrhg1bFruF2veBvQwLBI!G+R&nf6w+;rrE-g^#$hux>gM*{W`M1Qs zucp5pAWR2sc)51s?B#k}yoyiGwBdtuuPg5{nLOOf+G>q|Lg}ww#0C5LFU+5s;tnEQ-6i4Rnkz@^W8U;#6LGU>EKm2{Ig)I9$S|#Wk#!e2#pL4 zS?XdI#^N-pqM~XN`$~=Ol?xKW_EW4M{eig^xs<~)(|^w|xG+G|*b{-qMX=3jLlZ2|n^>YMM*4{JSkD{3rV1uM7(HLR*|A&Dff z{ImZ;B8G65-{rj>vN>&sY~R_{J_t3JxS(T8Q%SoN#Rm=>03CdKXxHh--P#q`!op-? zZ9Z6!8hY`4_A#K8;q!BIHE1P(l0GoQA}_O~)yk||!lZ}YIiJ3Uuh7dkQz<-PktyW8 z!c0^thxIv9>iLz)V(-DMV{6pjLzt%7#$zwlI=dS;MYG)#d1MZl)50e$yAYsT7~!Ls zWoavIfL8#*pqPng%+O4)bL(P<3$zGwnWB3)zTT%U*N7?058|r0BmxaJ8=0Vm5p&R> zR4e)egzYsYh%Rn9?q`2CYYks>JNJ;ZdltvV<3LMPIt0I^&&F=V_}l+;0Zs(DS$M@= z{@k!w0eEYk>D#YgUXuz%&hwVCs>570j^8Em0KpQE;kNk>M8u5xV1BSrnXM>lapPVi zW%&}Bva)hiCT*lJKa%BMggFL(UYQF5D1i$z_$lBEYZF3NGfTNeIg^{34 z8{g}t{BjcKBV#xaTwgPOEDC5b5m&A}w7TwK*~=o#k%Y1&u>web@4CXjy3sCcE53!r zDwf}}*&&~nT0`s=sMoXAP=OkG*>&B=B{^{2SOZDv9EVfH%97U{!RH^!nFPs1u*~Wy zq`ZS+%$PHfButDV6aN<*?bB9g@283@ZjAdUEzZ^_W+y3#OeQuwI--u|UpT|vzX=No zRm+YGt?>|*e82vqEZU`^H+^PFYlwf3`yx>hfo$JItIY(ovbbCH5ck8Pmsl+Yru@SQ99gruuxc1UL?MI6SM0vT+iBx3?x-`$64WwiU|Q8l4`Y`f2pX>gcPHk*z%9L77a*(An=V$T&9`$MVq+gK2W@7a^epzErsNUF^7RJ+qRTY;WyDjO7dh zX9qH>;fl^PzM%qAlkL0eHeQ2u!H8-U-pi^y z#abxkY}VnEH_u)Vb_Lw$zdZwI!wM(*bJHXc6TYynE2f@th*QQrx&3RBe;Ke-wF%oA zxFUJ?cO*0AYxHmAGqWvw?18=k6emIW04*IPK8BJIA|)9k4Y&B_4kfUK{`Owcs{w1o zKbc^u>uA&eJ^ExAhfMUdG@NWWIWQ^ydF9}K2s=^Rp(}<&^xJNHH_;LaH z?COg=J|RlIbdQ`*PI-VlTL=UEdqZXWa|6)`fdoe*xk}$y6MetqSAnq| z=o5@Q?NJGzfCiiX{w55y@Vz_#_*s~^iYo)$mZLYcg}6UAv>M<&D5S5`tM==g2$2t3 zlC|E9P;mm*JiMwSN?Q-p0-U)fecJ>rCeZ(e-1r9%!4$$b?{ZiZX#x`_T<%nUN>;zQasbA}w|_)UUW`jwp0S`|U_FG}4i zI}7CX_4OEStO21T8+O06Xn7A{_`$#8*mX? zBj+m08Ur=_6lH=pg&O$h+SmyEOI)()Ky*DnJsNgDo!AP>NW`K77DGOIkKfS3UlvPV9EyuHcy6k<%%(r+mwb#Q@`(&&u^%|GpdOt8s9+qPj?#)}|UI_6T{J znQShHP`Wh}P%6qCB8Uas^0D+cul^=<=#_eUC!POIJPZt{O{7XiW4!GD&@Z7<8Y<*NszP^WDl(4sZBrdBSD&8b)m-NEWv;(g4%CylK9X z9HpXM<&Y`j+n`8^ykGktdATJre3pd_ch&hvN7?`=BPOxaw1lFMVlZmX#}PpBdNv>D z2QCsEo8$Q~WZ^ixUYO~9Z!?33hvy&<-c{jU?eQpg9i8@zWyXb3{wrdVs~$)jCiGw6 z;&q))A{S9{EgsS%2t33L(U7SoLzf_HOWR@CXM^0w10ozM#tcc+B(bM5pRiwo4Xls5 zb-^9Ta|U`s%#a z<37y4{t!0IvCyRiOyU!VFOU$Rfx(eTabv-&?erVT(rvUf9`-9ijRs!Owu96v;Ua=4 zJKj-;xmwWBI*YWNT@SmbG_8RfPXb6&71h*uy9zCcAt(UQfB!fYGe^m^S?X{ zp3p~Cg$p)b_GMfAsj#(K;7P-EQ_@Id5ng^iMu(Yf=+pPx2aH@dR!(JJeg*t*xJQg& z(^}%M^GzxXGsg3q@IM;x%Ud1rWT%)z3?Pe*xPJafY7Y{$^dp?HH?$ z)66I~G~Lq-A;36gW;yK7z4x|BZJ!z(8L@#E=05ir@7rhgrdP!TuLvyQ3lR0h6nzbl z33)O;K)C0xy6-ol7t;;>ZU@e} zH+Pn9wD>*I%bR08-z>-f*_p_bOm=i27jLOaFv;!9JD5p{Bj@bBv*G?Vck4?}Jfq)p z$bwsZlGm4B2203H+|@ml90wLoSZWUtTW8i&w(|YsY7O+349k@o_KPs=^Au>LFC-58 zKlsdDu_vPRMb^}~A7e1Ea11wdS+%h4!xvRkZ|^1lT*~PmKT7KO*S!der`oi_F^58% z9oHA5qxHW@Dqui!TAsqz*0u&lmCj6Yr?`M7@;i`&D1=+`MBgK5HSX-gkkQvW9(f+^ zNRos^*fV{9by}mToykyzl-9Iar4=bEVgYV{bhj1A>U9WV%0+c`vJXVPjyl7y92^?T z=vO+H12bF|^V?0w=HgWm>E=lbGU!Vy{P6Mfit3<49~S+6)o>u{h|eOF#n0Uk>5j#y zwS(8YFg5>o5jqXQZ*%>RrM^L{9dnB081~L_zeRj0E>2eA#6P*Z1tU;Lf96saChUB!xr&GUw$U)izc0i&F8iZ z$)N_-JxNXg4&Zaf72V{kh`lP& z8b|>XQZnq)QxtFIsYfJ!7nOR}5`%KSGzGUJh9Q^C{C@Zw8}~F+R#s~DO(#YjFtwpk zD)cohHcdfS9OSuGzBj&2^R-7;e3dm(rtk&b5XGPc&WbW}v%#?4sOVENWhY`-hi-a; zrI#!g*jt)?y}h^G+HXFfq5D7}OhJTqfW72j{1);%vr`~&(j6NcJD#vTwlV{5U{qZn zK9qjOp80a&JQ@F&T3@bDAd=Il0KzLx{f}ht1hA2%wzGtC@muCnRz{K`;|u@{=(QS= zB3dVw4|My5-~Ur6Kf&BKb-=D(s$!5zhSxt@ch!`Y&FF$NjYFlVy#KU8u%4ct7cYyU zWgq*;Bnng#KlWHC8mg0K6>bLAjF*R{(g!fKbaWLhEd|Gn3k^9>{GG)jL>}m+ zv!wi&sTF%oJe}4L5I=Nlw&uo=;U z3okAUwm5+**;qI1nu9qYeQfB7lS!JUQZ#uJ@ZH!<=a)myMQ_w~2m(zwRB>@Gj$bYU>y8VKBL&g^~;H438v7|tY-Dw=O{A3jVo z{qyHfL1V#4cm2hTvwS)hxA8G*{Euw$`#En#q%!C*gQYQEM~j|3xqW+`PpDqO`QpWk zuJ_xVP4W#qBU#eqfGxo?FCc(J&hQUJbl-kvC1mWj%n|n&7T{W!wWWs(i_H$M^x4>p zItY5EclgG=0@~*{vxmBu=d3^Qss`seyxfvr<%w!-UEke=jbEFk7&v!9K|w*JHU3bX z#y4<(tPZ)<^}|o3H6%MZb!>`7(s0!c*ovFl7c3g-e*FBY#f~1NL644(>WRp)(rTTi zU6_6JQ)JH!^oxZbFd3j>{n{uYqQQ)SQ^QeN4M*rH4c^>8hBd7{ix7ss23&{n>%~LZ z7s}iW(6sY%7eCvsXl`$=6BV@|2=+DdO>iB5fI!Dk*i-h`UpJJF^4hn&Jnj>yw|+v8 zP)t-@H%hG9WIxI=_c5C1Qn&{<62^`oV}VaNP0Y-0-w7DfjzPMw`}Q=$P80E{-o%%y@UNuhU%;Hwjs+Wxd}dBf)eAxFaQJU>&&V`6#F=q2f)w$jGd zhn{0+ws&@n@7%dFI3lY`|sPLd|@t9fyK4uD+px9b#Nl!fYVEq<}%~tcf!;&aZ&r>nh8k3%`E%_4PS>dyD@? z5jEN9P<}-&x?U8Js0w1gWR9JXY&t?QC`L9ngj%;Cgju$Ampam!v{Ujbhsl2R6qJyt zJr4p)W#8!ueoO=u#_S{%eK(3>T1uv(`_0c)Amqnul?a_V6Q0j<`j>yw%o|2kB?U3D zv-v73K{zkpvtNxq_=6ObDx5s)B#{&^0Re&gp`oEKQuIFvb;6PeanO41YqrT6uJhgt zD`cWa`_YseqKDjJXRBS|^|*K@aE`V9*)wzQGKxA-pNP+Yar887XtE{>m^E>3ISt_p2ux>*)Tt>teq_Mbj*<*s8kb{AoBCSM z2l1f$)gHGBWOu9B|JbWX)y=n;2y!u4={!Xb8 zory7dIScuDkL6FYi$|o^bgU6`gcR8M=8>!0C)&QBX7jD>?1gh@owqF+ z?C&>QTnQPBG7Ax%6t%7cN4JZK5F^#KS|Zw6>?rWj zJb~ur;+m5FRGHO37No^G1+5=6?)#u`zOxQ83GsP%`TiUQ zhUkkoQ!C{_2bS);56j;}$07bQpGceUcmN$l9aB@&`va>1=c8;;zTYj?jjY)~Gk{;l zn1Tg!TL{L{aXQqM_c23hu~w(nMG3YT5W;CXE-2GFxV&-jsxp*`3Oq5?Dj|zh(!d8+ z(7@(^bN>y;OMQ!6l0>wQ(d@otF?*rW7<(OCfQ>bv)0 zaKn$jW;s0kbl|BoMA8`mJCh(0bR7VgQcLg^_-!H()*x?LKrUh1j8ZJj`yFMcaM zo#h<036d+-fkm;-Y3f7qZ=!)va|1u+qJ*4BVQR={c>{a=HN^EkxX555zrxZKWUs91 z-|k(^E3L0TCBaG?X@ggJOQHzs&PW`A`NnC0z=tjzd}^8Z@Rx%3-!jwF>!i9|aO~up z3uOE!B`$5Vvr%Kb@IUa|R>TkE$BItVC>?~U`T3m}{s}nS4WRe_&9L)nUf=WfS8GAS zleq9L^`yV4lB;`Btp_d<%>Cw*-{ScS@-i~ko$zao0TsTGx}5}~zWKy%(^7%2ukY`V zDuv5bzpkFVeJ*}pJV@G4GIJHaW8PVOkGzD8D~7E2_HAY_504^<9?y*#5u43O2nYMJ zO8HI;6-XkB zi(@G)mywl~H8U}}rMLW{aoQGV3VBWt88*8SM6q~qw@>Gy50-weLn9 zFn&pwmNvW#F>)37JNN+qC)3G+1&|Z6fD5D2_w}&M_llo9_jI(i!{E>Ne_PJD(+i2R z&_b51BcvN3!fE{e4DS^GEw7l}-CcP6l`AJho4+RYD?m5O7(VsaTXWn}s>sF`10;E2 zrJc_QdgYZ*agL(ri5EQJyTNd! z0zMW~e=q6hA)ktii`#u(uDq|n{}_vVewIh`Uhl=HPoJuO!#K%J6DUw}j`fGSN09<& z^{h-5>OomHAuP@?pGy)-xySG?bGtK?ol7_lQLV!nu}Z$e$H{4us?$`Op!oO0g>}g z6WFw8A_ffQ0%1S6b!JJ7`d@$Z?H+8cixc_yq_c1<`aO0rsf0I|ZzpYLs#tQMR>wqs zYlC3mnQI=%bNaR65;m53dVgZb(%9MU;>Fe$q|rSF-G+9==xqGkN=TFzJ|jrT+R}3U z$Llz{p`HDqppR*D&tEzFo;5N8dx4p6(_4>Glhak1E-$K{TE)l5??_i)zLR?t&6_&& z9t0nSWlHStfIqA&5lIw2N3XLI1agTl&knEqC`u~`W(1+9YB5T9{9V3x!CwN0l@&q5 z(qQE9!73@jXDm|wwha?I>NfFa1v3k%{LtZD?arG@a-q9f;TBju>ChO^n>~6|3`uQP zkYQK}6*w1nH?==2&PmF&_AYOzUYI{Y%@A`I17qmT_e^HH@N42?zE`haRm_xd`Z~WJ z#&HhYLm{=~JZ;7C3iqMG@EfrsLQh<~XXK5_V= zU9ccG1BZf^CY}VfF?<;{{$nv^AXRq}vXD7bhjc6*tR0yAKnP5gLQ_FH2>3o49x{1dZ6+94pt=bSVKEFI24Y8U8p|CZ>=AoMt6YQuI^(& zcd~%h^hYR(RNbeo9X+kwmzwvt=lR6prdPoJJSK5Q=VRTi>}O|01vrjf11CqvIs-!p z3qSO{}RYUzeo`eucV(*dPH0cQ{w%cViFLtb+5|Ry8 zLzJj)OR|*`)XXUGKZrH+>w5XD>HkvYVgj-J-Oe|7M4bfZA*;{I^^IE>-CIjNqo0P0 zzl|phA?>hLQBhG_@Y|fv@~b+%^!cHYzO(z|u`?`F-^?=-tiQyaI&!0rL?YuTO}TSa zGOaDlaNzc~%>th0TMv?(*qr3YHNgOL*k$OMJhS83kzTQy%0Cc1eu1`&0@2-ef}ta0 zsLh}2G}&y3k#?iHUdhPvtJoh7`}W1E!86C_Sn=|UlRiJ_Vvb{?Dm}T3o)OWs4sa!I z&?XlKMy-MXNyIl_`JNj6tSu+Fln=((`23DK$F~9as4_k>vRU!^^=pGKU!rlJ+(<7& zi;Xr{XE9Kd=d*-8uz|9ohXvcMXS@>Fw$?zsn_23~S1%+T3osv(FOfiuh#{(ktj+f6 zEQHaj;gA0ShoKJ8oMXyIy1WtcIVPVa6^yi@0k$ydGYi zoM6NB=qw@+P7gWnIzTe9m~+hGdsJ+;YOeS@`Fji}Q1ahI`QvGjx+alWQ}{?g zQLQZ!EdeaO;H9%i4*$as!2@*AQ8={{Yo$uTk28i_pw`37iz`J%VC}lv9|Ss%xOwyD zM6R^khpI>wr#~#dGsy#4Ter@b2fKfQDr7oP-71rs^M=%Gk^&({t&t`~slW~@Ik{3h zhjOIcXp2MRq!WlKsRz-K0N*3fUmW}Kb@;z&0WPL}gV~VF(X?YAhnX|ajr)ff(zk0${mCa~FCx;8{ zq#0=7ox>%LuQ4((c!R3<(q-r+nW4`5uq1bICI9(){*U;S`obPAt3jHu#8i#x0b>^-v51* zDq2XSqgm1p$XGD_h5&K}IMnJsYc%GQ?>-c$}EfVdXH) z(XPm_chvb=8*ppAPl||a`MJ6M3>e=OF4;TnZgnKS_}mFdo08PA-C=MMwe*mDo~TIU zH2CX**6-_p|3sj9p@%WIhXFzd5IqE~k4@oQd2wiPxgU9MBd5)lBJo)mHR<&+cy+6cU^ zg##Q~J1e%1N+8kEik8^X1xgl~?~(BMJ|!ikoP!MiLk2>Be8!8T?3nLtBe3j8AcCma z0SbA<=TLHZLE9c_7yEof0Ny3s9y_D~?_wn5|2Xa_mKL1?K1HV*Yg#+7o%}S&BThpA zM&rhUvOP6m?Q@L_loY*;SuCZA~2R-lkiK@!VrIQj8satI9 zBaW8*26%c>a=P;6?IOT*THa`K2Bv8?RjQ|$@ft|ZRL#tU z4cr~=VEaz*11*1RK-PMcn|NcesOhy82%4+SY61O&Z91G=++>sDxK4tc}q zATmB4n7QMR9W9?%bfhsD9qJlScy@WT-v$it*8$h(U}a|yld*pN_d|Sp>)vuhWa*&- z$o~#Utb8$P1S^0G?%XR=H>9Oo_OXFrmZ zjxr^oWDb&`M=)XYzDNRjh#c&LGsM|k(?Q7wAP^ct58eLW_Ki2+wm=(w4Es(#AV$p| zv7UFZhjoj4^5n_q#H6H^tZ4g08hW6k=&S~7YNR_$V%br@MSQE_edczJ7f^E}V%OiE zU<%0w5rU`u3>yCyE{;HyWijO+a}5s*;|&Et7~@}!QNeW=dKuAuv@q`ZqUh11-(TSo za+VergRX9FT4z}Is@Y7!uaTVH1JS6Qj}X-!9p?Nt#R18j1U7A=osUf5=;UP(JAE+U z_-)}t8nP3}=E3MMKZAGR`~K+Di@|v?uG0XligltS7lxS~%$}!oIgWOpzt(t-kltf%SO5HoBX->J{e9vN=5?Gzgz@gM{tcHn z(!AP9l>Ob=L+o1U#(~mRGSRA0Cft=iCV}F&(fnKOS*V&yKu2{qpLqAZ`8hZ$pTL^h zU5TD4)p7c!C%7>Ks`k7V{yG~Rl~W_VJ${8TSX|?c>>)9fhaPhqTJ3g`l;LGiB#cp0 zTSVt@eAZpN0YY;x9hd6>8sF9FQO-P+4S|^*p>8D19=to^wd+aP0NuL?v9Fw*nP1Vs z+}y;2d3o!dj10q#u*Zdk*J}T^XK(p4bW5Bxhkm22qtylnNWMJ*uIuHXXrXd2;bwQ9 z)^DAdpc6pCOEwtCY%rU?R$bdzTU+3PDmhS7lRV0?qlB@S&Fc$H)bB*9OymN*Rnyto z`6_&``@mq~{Pc@%W0^haw`jF(Fm){Nko_2p?He8_=42&{5u=8VPAnL!F_KXuY4I^Q z?qhui&#Z>V;b4Ga(%J_Yy}nqTm>|Ezgha<3*r!8=2jeeaDvhr-O+Cr)`D5koxLk8* zK)?=qLhXSDO`=eyD`1-y7il%snZUwgKz5fW*%$*bn;%@isl*_gbDJY0oM8E}e+9rK zOMsT%R9RYmS3Ld+C_#xZ&#z)*VX;>E{rmUqt*x!i1!&H4hOdm5Y#;q1fFQZ_#lB)dVQ1M4vSxIJ!FoL(B7+HZsH)A@E;xaa#nf8P1g z+uQpYF4FBf9)EuKs}u<3Bpg5#J9sB#(T;t~-~X{~r)($5SptN_+r#rG9bxGDT-B7o z*SBy;vPP(tZ=ZnvY7JZ3UqR!fU;MW&#|-3@W*iGQBT*rF5)IsKsYLgYj3is9+2!SB zXzh{8KpuHpplMp&ex3sQl27WMKi_m(U0q%Ny%jN#l$iK(qk!%zvia4`2K55Bwp zP=ZnJUYMb}4Gj{&_86+ zv&Qf146idb3T*^@V4*|(3OF@T0zyc9xZFo`SvYm!YfL=?bdTI4`K7kf-`V`k@TH3pqA6wjp*_R@Ya@ z_9#GmW;Q{%z(j6;CTXv!)Kjh``e%0)6CJ9%>Db}IG#5^C(n41VHL9c@x{>{XJna)D z>z3NkOKHo1`u8S&4LD438b^OEFHa~dDODF7+SI80qq_l9|9KO=y}zG~kB@(b1)gE! z;IN+l>9Ug>(7Kdast|q zFJ8P@g#-BG`}gzQA7>iqJT+Soax2J=cWHU|%PwM%l;S(85HbTFqYhMlD}1C~5gnm8 zSAOSf1fmj<7H*!tuC0q4^%M>!?0U%rfrSO?Ir>7-zv+P8I{`Sp1hRv}g{QMC^I*2v z+}_D)xu>hENkC36SPxLNHRwisc>P*~@$~%JAxpodm(avsTN|)ex-~TgDSh{g(Xxx~ zkT3Pj&y7_A6qS?B@uC4G$PjZJ>McKqVQc&-&llv?OQ08=2ij3(9PAA7KS8kV+Wq_Y z+W~Q^F*&|wAIQAJ1-q2mRQDpu{=5Vw>PGiqYuthd_1Tb#Fv~M4s4e|E8dY` zs{1VwG#-6j!QJFs25D+8@vHj`cSZ6;Me|m@QvhZ*t-ClRd9@p1&Fio~(wiN9>f5KX z;SR7YfPa3jgnUrqH3sa&$^|r*YQ`c}#psUww)>XK*~0-Gv?I_uFxI`VQVZ3E&anj| z!(AH44yE-m`WBGvBB(>LpCm6h{%j_ddg>{Ve-`4^WdYnlk_;Qlh>bk;=0nTn7jvJ@ z)+=L(*Lx1caTh8RbfH)09>$AZk@Wu&jO{!>UA?eyS@D4GE|YI8%%oB2?j@EcbbM*7 zxlnl1rqO^oAY@=a)i>7u_}nLiR`k)j>}|H~4}NA6C7_bRoZp>sfh?A29OB_-~GJoqBQ z-p%WuI;H3}X0Ah}eT#{Y((WjfceY%ID8qkmwzB0Azu{u2fVG&P^hc`_NxV;RnCKKU z_g(EG7l4%VmMmEyX#yzzzu=5W^(X|+S%A?(^gCdv?wsdMCi8<2#xH*Q&=WIA=U-~! zTQ0d7yp1Icz;P3X+Tqo&P@F1q;)5Wr1`}r=26e}bz)D7XdBr0!YX%VK%603df5wvOjrqF0tG5U+)VWM$c0qP zo5{&}g188F^DrUF&?gj-dNrie@@@o_LFWuW_z|ku$j+1zA`O`^WXW=Y4$+zE{&;U> zVHen$($KiXy>~86ESeE;{43FjwG=WvR2|UH6Kryvl?(dMAC2WX(RieODbMQd6o-lo ze1DXhz6*$B@0Ne9fI^iNP@e@;*L z1F^TKJ)1-lW@bo4vCuk`Icwko9-W2##D=f?TC+){Ba5et&>E0k#~k893Zi6olNka_ z-(0e`+6uDOKrmqzB+2i(+L@ZFCvixmyyb!`;tFsrdQQ?L^INka6eoTW1S}_WkGk@K zx$P~J?mfsvQ)}k=3|wflxR7%r02iA-KA=|MwmLK1b8Hj{xhy|_E{5k)jw0F@#%$7@ zksDUs&rj;KPQwj$I+UL8TUIGrpay*&!Hc`8>S@v~HHE1F0!RSewM+Csi1f-~_s0!D%BIIlp$zmO>P{ydq zYiY~~I_NWZr1y?Nir&#l6#U(nHS?SAyU2#oBJg)B*qE@4*b2?Jkrf{?NF|?1Tb_IH z_ivD*-|Ow+Ri76>*+29|2Q9Zki+l+#rHj_&y_aD%2oGOUM+w2Eg)EetTAlwIg-Vs@ zn6UKos5=Zu$X9>|@nD--1z}nIF@qlu02-V9p%-}mS^dYUm0OBrld(#5-zJrq6L$A9ZF1SL-hN?kT6)_;Ey4Y7~~8_9PZ{-O*9FIX2iZv~R= zwI^wyitGavS^TDQeD=nO^wC(Ae+Pi`wzzYS`x&f5?plJ>Q4BYdcK>SJ_oRAo-QwTX z2u?C{#EzVY@64?zj@?ijqYm(aqjVPJL`0iJb5393`UomD{l9~yR|^+&So{kAZ2}rS z=+na};`Qjq>6Iy++@inzpaIf7{I3DRuZG7x$tO;elS8a)XLK|huJB=$0%nR$=6OuhlU*&_P5PgmfE$UO$UrpYe~n{){z6X ze-wT>OJBS*u!c$uoLR8V#B5_A=HAY}GI}R98`KjM231fT4e*I3fO@p&|Dm$yRsulv zM>)o5+`K`ns0IeQ;`46NT_q66G5b(INGS3%mOxbf2MS&#$0~m#y57QIq>U%#NZc>B zynTFnkcJ?1I3eY$3t3rhW#s1J`2sLpk_7EyEMF?n+j3*D`+R7iJ*rAcOPf6nUUqy0 z8w$S+&2cdT{A@U8(NIP);Bu%~peIm)b~-=Jm{;frD90+4BZI9;4Ruh2nnQ88dLH_m z>AWJ`pQWS#Gs;`x65gU>IYAD-)a zU(a=AW?svAopYc2zK>_GmVt@MpN5|pdVtd9ygR~cx*$#d>t>^`M|-BG>k%iH9c4bOjo9-tc% zdqGAjnjAWP(_Io0PAA*ITFy!s ztsM`HUcajD)#RZUgS0;i-n*QHcGC;tnTh}y8nimol{sc{@nZDN+CEU!&KBlpWLt{Q zRX(z3FuBx97rhYJTEb#p&D zi7JKjkk6(sN(JO(ZuA|jIIA;cQG;*A$u2|Bp_rN`qIropQtUPIj|tk%rXb#blQ_3S zq#T|HH;gd(uU(TYP`Ph=v&sb%ptk(4quj1O%{2z6sX3j>-uzJda=gm97{5r+-NT2_T_du0bMZ*PE^XF>V$ z94#{>ep?mLFXq$xC0e_WXKQP>}T|oJHr87ZiM@zyS$i@W$P*A&35i4ClcsYFL-;Y7j&A0`T|%_>c} zs6y<4vEDa0XlEKpL593f@w89M<&LuZx&KJNl0&$AaB$tz)N}~b${+fggltvyWNr=w z=`?&N)tld;-eg_+BmA&_CdG+lwR$mxla1{=G}4R1iX{IB&d$1{Xcf2J`~VG?#2Ms_ zt|lu6odr(4M%lmoAU)x=5}HC51Q-e6gD^x{%YBISqqUhq>rF_zckf<0;Mjj^``@!r z)#j>OCWS8YW{XL9$VT&lGdB#?Rl%G*!CAaW4k@tFiZB0ZMiB`qR+X+Y``F3=ttE(#?Sa>n=tGy4rr{2*4T_=(0G5P>Xh=t;Q%c?OD!g;5| zxFNK{6jC&a`4a_r&gl4UgJ=v{jL=Uqx<>w#$(?MqHmMFHOx>B3rk>x)@?OU~(%5zw zcA#*|0fzjX3OP*h;xijF&JFJCN06&R!Xijnqcw@N%z zQ=svUm^|@0fG%1A5^{rJFuYleiHQmGq0?|QN}@pt(XnE(bFc%BANZU$;aBO>-3Gm>{sIRNklK9uBfPd{@^$AMMEEp?iDzP&@ z&JD2f!#@6C`0!!^=tmYH(~qh@6*P62^r8%*S|d4A%?u5zTISa-tc%>JV1R5HnjG>2 zOC9YEU2WeX1!kk$dE1r*Xpa<7xRR8CW@B5CbP(zU{u7=}23b{AUh3kkpI@~z1imTF zK#}N^`6zmqIZ8bE`_C;r-0+OW<-hf%Fu{mROB@qHcp2ivl7b!6Y z=>d0le}7edQIVrJjdC{=>N+zkZ?-E#59rz=E{8xI$xu(THVjysD)mRnb{c(X_f?nB zZV0<~6={j#+1_9x^#(&j7azo{J|jB;nJL`=Yn=W=yU?hAZq<5(>ORBppS~D#G#TL9 z_9^$8gL2*|{jHo>C_+^i-@Q}SxqiLmR6UI{3#yaJHzsuSb-Rmq9_R%zwt>S%a(%r` zb0b7D?E|SV+Ydc$J9Og&Gl4ei5AHRIT-DgVU0ZXIA$oXZa@eUf7jzM=>=W5HV0Rva z6YIy>*@9!s@WIm5G!7|7ma80KYXf`jLXNY(B*-|tu;xGM-z4jDRoA4BqQIC;Q5`pC zXIoxhfD0;VmQt*mZyw%J43>^c?(dnfmj?WYaInvl)`9h{M)HP_Q6JlU54UkD1RmK6 z3PA8I&pU=9;oT;o6KnsC@MA4Ww}}nLKg;?m1JmWLh_EzE8$v8@mMZuhX7f1Rj*rAx zundhAn7m#Kc{IFCOfb8yX5Uq6Deh~ftsUvViR?It6hVB#r*!gw+r%QIF>^CxPKFZ7 zala9JT!3`Q)}+Ao40n{l|3rLwdHFj^f5dN4n(^yqH|WI#njLo*An zXwX^#XKOe2_!ZyX3)7B)Rs@$UF_wL_c zYJdOUk3!{s?)0?QdUoHzAV*^&(Q+nkd;GtDf|oK>-IFJGAuCF7D*#W1L<1wEqQF*jJOwa`w6?%UN7HFc@KBJ6(Eq7~ap{*B zsjrf*5EdW$7vNO3#1g)ug7~s^F4fKlHmsGHR_4hnD8R@8`cciw^73e~Y-4gg&hl^` z#*QZf_l{V_$9ns;4ry_9%?+V?l>r<-SV}nAV9pZ{FJuf;>94|ABxt&|fw%fz1nF^Z z`~mz%jmv}m)rS8n5KE{^8{$HbKA-eDPu+_vXlE1;Q%%pV8WNg@86GDvoBr7wQ&2Jk zLl|!2(kl)StdEfhxs$lnSARYL7K6x)5C)CbhUuEUUQuY@O&lb3|zjO?07DdCan_kwz+ z2L>vN`emy2yRS^C4THa~zovM9?$uoyz!`{rUT18=o!B&^K0-1{12U=exIT4UE}Nc2 zn`Z{$?Q+M?fV|i#OpCbf$<+*;2Q+IR1YEVV0UegVla|&)d8YkaH8=BUQYCmhBeZX> zmN!Ly)CaWvaQJN1&H@M+`GXowb5JzwJ~uj4d3;PtYs;NuQk)EaG=eOf5gjcz8IFj} z;kbPrt!e-rV8``MFc;j!-5HV_6^|fZoe9#gK2jB2?f=VS=emtT;P#&pCnu+7D}azT z??b@;>ENpkiOFXXOf!GBe13UB2ePz&QNF0Ku$-^z>({Hp&{P1#?h-xiIc{_~?C-fj zueG?t{{r?WL2itG=&UjB`RBI|=>Jf5tuk5L$0fPk58Nj;RpcIj``ypt2{m#;;!O~5 zR>SaE@GxS@Fv`lxgW%cNCRFSM(9pxsz}M{ugg#*2hV92H zsoOPX@CaD9TvvciY#lW*HomWgSb#DIuT^w{YUJZb2|xx4Rux(5k1eRh@}JK*SXQ(E zBS+=s<)2dBmqjP(68`m(pN<~Jk-*%4j3q7HFAa>N=Et}s{#)cI+%HaBBSq!~?1R4-a-_ zAjDf+AiRErGA)4U9D++^2l5#K7ePs$4?)@OB}wh>6O52F-rNanxH)m#DTEOY z`!6_mFmjR|XAqyklzp6*d5VA!o=FV*ZS1%bbd{gX*PT)@z0Lgh;?XlhbHyuf_}#*J%0tQs8R~+Z z$9-1cXwlK{+vil9pF2)kkOiK~$Oazt>|1u%=iBd^KTKDE*6bKq`3%$RWb9{4?q0OL zQs2Tuhu3~z&tLkPDfnFeo;B!*5?oZ>f6ZPb&5Fx*fPi;`>C+R3{~~i}Eut%on3ccDdavhY`&#)xL6fEa(hds?Fm0qtcA?rx zm2(;p@p^?yIZSHVBl{_N4!4+q#~WQwc={uq3U@(4K~jMhJDlG4Anyt}i4qT>8@FXN z!S$5Z&D!(jz&IoY9ubIt)c^~H+84cfOxG!g0{qx`?rmmrl)QlGmINgks+1)<^R?t1 z`9&Sht;DAMVMcaloN+oY`}&<5zMr$F@L6eTugrc~SEd7Pk15I#YmlVASb+h7kUH^uLMlU++Fs_U9QE?$ zOBo0)B+sAsX(NRmg(~qSjlVB_fWyzu9?ro4UQ^S3G0}+{u;9F4*t8Vm8O{F}#btpJ zn4z;dI8XhzRyEXFsi6ZAW4{#B7NgcJ7CyogCV|=1dYY)iS^wgpo*@17=kV0IWBf(w8iPZbbG5E6ida|IHFFZH zB9jk&HobStG&QgA3U#1t0$X2KyUb^|15C`!ZzbAen&4*ILBBetawO{*TfG?QZOq*| zXj%$vW{e9`18t33|9q``dS z1-XZRUR)!k21{W2(VrJ$FHLF@@_({#flH&!$mg5tG|EM%@sa-`%gI6WBd5%hOtiDrM1c%&sVPuFF`+qc-g{&_|>=7>hnM5#JiQF z_pY|rji>L1B{Mt5)^b6J7H>LDR z^4r~4s~Z75o|$D)!z|!;oDMo7{vY8-l0kL61eDt1WMjGG_x|P(VNjyQgaQTK8Kc*A z)k1Q&OFPi(2Or(#Kl2nIoo@a**3y2Tzlc5PdLsZRLEPCv5BMvF1#f=;m>XHMVZ7lD z{^f(8K7Bf>wK&U=3EA9)NBu;>DW`#{nW?FQ)_NnjR&=JF?Vp$iuGtB<6!v#Q1T?7| z`Tp|GA>J)i*12)|;Qo}nDlGZm4f~tm6`Uc^H)d89(aj}zWBMC4~ z=r9X2^NiVp+m>Ab=3^tc9$>k}FXKALeiXNEewzqwVx{B!e2Zih5~*13cpwh`MGsi_ zIoR93duhm9Wet4;P)H0kIK@QVJ$dLaFah_ggdTQ#2_5aK9p?$$gsIe^wffjgFzmS# zPzFjCv9!2Kc8`}Rf=$WrU(vaTM=4#j>D=I-nc=Hk&f{G8O(su2TMbDfp1ACYTq;>vJk+d zbH!|~d%nbSqH9tJ;NLTw;#5-3=m53!Cl-RF;Rn_1JaFhT#Zk4P0D{%42|!xbVL5 zRv9wuW+|rx$)yE$K~~E$Aje~El=$nzbY9>ELs))J2N$0m(7#(jU+hZ#w{zQeZ7~-B zL zTm9C>={!Koqjd&hD8UV}12X*+_sm6iJb`$h82+yGi>|EuUA!R*D(&cI=V#^)Z<-KB zmpN5lZ=?b_P65Nf_b*F<%E5^oVqLYHv$Hb;-L%D}yPOv4#||9lIPw7yOKLy{`cE#~ ztD(z0k=SXliVKpE^mfyH^`?cflTLT(QsFJ+)6X`B3)G}44LsT1wI9FZI~pwJm~yXI zZz8&db$^jCC{RblgRo9PQl}{nm#&wqUF>cJ)$e>kL4m!e)#CMzp%roj`PGyeWt?ZC z*~LdPWD|6{^-qh=BfRwRhN6sAJ#Z5WOG~?o9)-qQ|LXx6=jZ;FsQz84;3QIv1R0z? zFP&(;TbX}f5cD<;45vq3K1CiqvzZI-qygNvj5MKUgamRtKo8(oQWKWU@+O__pb`i< zjz$Tr$m9Ei=(uFv@O@%ZgOqnko@Y>uvU^>g**~!eqzVW^mcfDR_bcnZBSgo_=86gl zl_^$NddLW-K~9oC!5MX1k!&i=n6VGhcFuZE0)F&$7(BVR)b~65bRI<<_vZgXS_FmeP_tc&F#9G`?BLy zEsxI#Gs0k5mW^r~`k_5zwRWg{!?S4aEI4MZ=fR++0fUar!qwqAM3(Li32a{AbKPosK@pLN zr`=$XV0utOTKWPo+Mj4sVLe{#1wPZp%KN1D_rHjg%`8r1#Z2d7JM&FCG3D$n`9cea zk5gc*Rt1bng=qe@nmn2j7DU`#pzrIA-L1Vq9;QT4Las$73}@vwG<<$lfP=Ms4qbeD zI*mz8(kT?iy#(f~O@Rv-TO~gxd+&3&A5nt9Qcv-H`&!WzufFG-rWM)Q7?(Chl7bdW z?Yiee@6Lzt*&wlg)z zhVa`x`)`hQ4B^j>AogU_Gf1>wmm=rrdh@UUAjxWIE-zmePWE(3*K_*FjBt~{>z|NY zQO(6_X&r_+<57JMNTVuCaaE9P$R6)Lt{Xl97A8=(m5NZ^7vKNmPihI9Yx5rfiRKc> zt}QZHYib_WQ9q-Vq75Wwx7XiRP1ggR<-hVhPIw}UD7f^N%WmjZ`=^DIlq@Xzvieyy zHA$d+(}YwYf!`P0Z{J-E_B}?>8}PkD4vfGX`ue?Y4M=ekO7b_gY^O7+wAH%d?fu&y zb15r>!*FR+CskTh6iG6vT7{G}Jo1$02PMu+270hFFM;jA3`9g1Om@Ki6mC(9PbVH_ zW^(zZeSoheKL|#b7=QxuQQdlw#5NCl?JV}$;FI?sKDTgExQDeIMpI5ECtFX!e3Umb-%-c&o9g>FGNZbYBb%3>Y_ty4!kl z(%ue!`Lg5b<+Zk^{XsZ(MQ!MCjYA5T046OeT`g0(&)+l{fNZ_X>1(N3ML8rBtRrhv z)CH}?52-0BWvJMIo9#e3B%L+4hor2E#B!**7rG68h>w9RVpgXUcYtRK9Hy&vz%c0K zp_zn}Lb=XJEG7HB<)HTMqKKXgkD*(Ph2J_Qc5AIOF@6EOkDt4aorU-0*xc*#-2#VU zMXk?=&-_K|;d9;@1RvrG5HEiI{8to6j6)pn%mVtDd*%%s)p`vLjpE)W-TN%|3tuio zym?Vw{fz@AOCF#T4Zk5+r_8-TrmmsE8+FUHQO?rB!UB2%x`J`uw%Q`9xJ5YFgWBzf z<@PhmNB+UUN1q|FfTI8l2&_8kdG+emxmb0jgTOB!f(sU2fDmhWt4c@@!T>$H;=Eou zjc(>OYS^1NYQTj1G5CHW56J4Xp?7)J03|h0V9K&}8l8}4vyL|BSO)lWh zy7z$dLhoW|v+(6dfU#W7?7FWt&iOM;YXgOsAeoJLZ7^ydbR!6$gr1lKw)$w3R>{yX zKDRdF;lm!uuG7i_IvLNUPC!xola#Mg0*hxa`*AD8a-)+{uW84p zv)}tQ3O_-3kLcXcV`(Y7Xqf5%! z)PQMh3FuRMCJ5vs^Dm(9)1Q`Al=)Ec#lAO!h(r4*HLnLZM%?su>fJmGa8~k$$xv*prd8J; zv9>Zb;0V{*fOW`GgO>8rde0cEoIs3Q<2{L=9yj@t_sNxmQAecDAEs7L#lW=TkrC0T zR3YT~DHdR=%InXaPu~xR=BAeL)RG@Nc8on=EA>V9+@S22sZq#p00r-v%}bNyA^l~+ zKg+R5P${;{vPv?Xbt&kfN7hRpIt@LrsrbJ=u$A^F+>n0_e!0=gX>#uUlkmt(dtoV& z?h|n5i-1w32)cnOYuQII?(6gLr%yFJEu0+2O;!;1;;ypF)9u73pNWiBRwA(~Q6zZ!1XUE3i05R8z-}<1>Ob2zD`2Yxg;yw+Jw(FqVfVj#jXL!B4^YGR zlx7@WZb>TU+l~GKzhn?}o|HN&^1n7~uOWd&(9ceDkn8G<8OL1cbM8kH$g0|izlX)r zOMeRm9`Fz0HG6g>{lxMpgtn;OT-Lt*L=*dq#9Ue`DJgbKnb<5`tCq)v+1c5-B3xxJ98)G3 zW}5u%!7-=NJc@`qIOCRs_3}DZn1gzD*8Rc)^xV+{j3j zvAK=}-1y|lln%74Web1A2={&Y(wJLU=O4x^#u4MMO)#WJ9Cu2i!Rv^Tm7K_u zK4jkDdvbcpBxqC9$47<)r2^+rVpAgY-8rpZTVT)R@aX>a?}65|GTC1W;89S!2h_IS z5!e-L`~eT(V+H|j189XER#B7N9-|x-MMQ|98WNM6C)~`Q90E}#sgDZiS7A-frxgz1 z=>UTXn(8J;M?~SWk<18QiEdzY_M_$m3+XOQfU`pt6i_zHDr!qmucy1Zf=F50-#Gbb zf2}*xGJtM;t9?7~R}D=83N=7o;PbI>9udp0RdKq;AGL>Mi9{{j#)E0IsN1;=Rl5BU zL`E)uAKg^YjWmBCS&W2)j`e?mk343sCbOb$vycAjnvhfHy&0W-ql zv^ukJ%xJefcs9>;WSmL;@##rkWo0Y~i?g!%eo8s~q`&`STnk;48FlwW^0pZgqg?bsygiW`}lZnQmp zDq=h)d`y+D|0jrlb-;u^@OV+@>=XrZFsPta=b-O{&<7km5BRvpbxEFHfJi#VVvAGd zkCy@73#IeZBMZ-cWk5_SlYcBEVE>w{nr8tHTnKhGwB+RE1FqR|zW$Nn-6JFQ)$owM zn50)$GG=nyfjP7=vuETyAEDi}W)wipc=EoIsyQ`$oBDjFt$r!;$4GS)#OAb;#PO|^ zyvAdf!<8@Z1#wL#Fwfc|E_|VOGaK|9U-rP>c71f){-T5$BHLR1id;fE?)##Af))pv$oHkL#&Ch}6iO zcVUCkyk-^gI5GNw+k99=0KNgy**V>Pd8kSg(ZI4EtY|BBG^%T(bA6|?KVs>A_)YmZ zJsn`tDzyK3|AhTA0N{f#Zmk3w<3(Spl@vU!F83h17hqD;7xh9{c~34?_TVC_U+e4P zx`NhiDZRbD6aB^(OmDJ_wcnYcm3{!c3+YC@vzB3p@ z#!+KmZejrctkwVKO%=|hz>fSMDF^XQvC*niSB0kY9RrP#*eo*Sjwc?5(NIcuR={lb ziG0PKLK6!MF&>63+GxGeQ=oES`P~&qLx)8m$cK7tPDDh|IK`YN9P zP&R-;jf?a*5y;EqA9FQ$GP~AsFepg$^rFS?sgoefdI5G6Gb?ryEhkVVj)>WU&G(4n z;$krh9$%i2%Ow&kWmQ-!tH}!tpvY^9JKdG3D8|p9<|h%M=h!j7HYzJF?)HX}<2D8s z2UZAcNEWB}Fbp?Qp03~%nAOxY>a~vT$C|?{s>MgIu(i4A-ft3hs-4bl`i-K5#7BYL z?Xb9yu1DA}(zzw#cO~%$4jl;{9lfGYm71JPSK6@hfMJaKX9#q$ZNMISe-{ktk|007 z2L9CFAQZC~Rb)}KfJDpj+O>%uteR>k zA9*2YWupNorB9PGGiBtT_bj`=xwhQ>^XE^SoC6HB|MrEfohh_+(q{>b5}csOQ)s_j zD4pHa(a{KmwY8Grf`J{wx;kW*tA*5mw^S*XQy0h7kK+ik!4Q}q^22bq`Wxp;A4r`& zdm?amH8YidBr7Wu@JL#A#&Ge!XO@CnmpnKuEKCP%e-vj|H%f|Mxt}V>@yDt`+P{>b zH{+B&{-5B-wZn+qxQI8`#u$`n1_^pMZQmZN?g3QA*3t%?S+jBopi^uf>kihpRa?C4SBpJ=apB@+-WY1{QQ#Y@1I|uau+tK>Zc)d1@qZ}ODiqoA|r}sX_ zKm{9ACzGr>Is(~H2vB#lwsJ{#Z@K`gm zkuL!EJR1pr`@@M+F!$Hw?(QvMPaITVWGpCzcoG5=nkFG))p{rtQXyB#k8sNK0mDhl zR8{e~Zjcxr8YY3i#&+(vd|g);3hy^htRh``xcB4F$ZhTH)>^lSU~QQB)4%bp;)8bw z8?-I`{Wo92Z1JvF6IX&$oDj2SY!5GRp_SaO*00y)NyL)-`}@C!3^8Ok8=IrJ$$$`R z-gfVgq%u&y3?l^P0=5>a?H-nd`0gLEWGt+Vj)ukuWvD=^!R)$!>Wqesdwf?)axz;j zm=r}B9}P~{Mi;zq)=Z-rzpD|;v2Y@bi_Z6hXm#k@w-@yFz=EQp#&&&7k?&fS%BGagpoCSf=3z=fC)h;m2G%6?|c4^xIwp0ETamHL|G*89ps8W|sw7 zNaejl0^ECh6DN`tH)jgc3M+`1Vt_~b8XLcNe@oMDEuprKU6b!M4)wyhO5;R8!oF6EkM`bQ^H5YXnMm&4qZ z70Q+RCTCl3fthFE43Vk4)|UP-zW9a2e-F>{ z+^y+7Rj?KcBUMPXRG;f^BgUejm&cdVr8OiWGR zpOTST4HQlN(dy*zv!l5~`gOLaJ)!{_2sI?cKQT2SIW|Iec#O@LV_D7pKHwdXVWdjd z`vfTD+nY$pW=5r&^yY34&hLEtyq)?=NB7?*>_%fHk{hPhx3;p%qhMuz4^bCOK1TlJ zR{-R~$RgqC(uKddf)v&&N6vy``-hz(!?N$Tm#2E6FaSiEz{KmU>xoPG7dXmt2)};) zVgNP7SGy|dp)_ad`Z(=;WsWtw*u&+rhyOzienMy;Ec?@D?Pp8{-jbm$_i3r&#j_27 z==WNLpS-LyhdPr-$^CSIvbxE zW4yMPd;Cm5v4~xvSsbZ$rQvrXmq>0`;*XUh$Pfn>?|d=JN0z;b1Z?%o3DcDu;aK4Q z)hxed*Afy)*CuWPY}w?X?zW%AK$w9H=#sv^{!YRHHp>Ut#d~IhlrzE^#-B&Mw|0NE zw7zcgJU~lKK}Kf%PDx3Lg5!U|6Qx4f16g;&tyZn^y?Sl)8mygYGPIyFlth<5`_Dg9 z3Q{C^pH0bg7;^p{2?afui@*_&y*$x!3k3%*8jQT}S966?*igX4r$QBQT3I|{bwt@o z6MqmJ#=iTjS7%HQG9U&esYKb3tB?+4!^RLJeHxS@gvQHGuC>6USP6* z?7uT0Ze$q{u-$~^c55!b80IY+e675IUqGOlmF8gcU3!%3G5F>Y^H$c@Q;BDecE#Fg zUKK=9#0h2*X~xr+0`j{vDFna4-L{3&@Q9j_{&c&Q$X;|!5&&(0w;-rFBwfjY9r~#2 zTR5yYQ64O1shP-L&}Sjb;=9>*Z;CM2>j<5fsnnFY$E*7QKS~Bv$54nexil=$P!f}o zmfqDDns!uUV7cXl~Kv+q>2 zYQrt)b1HfSo-)uCMgE6iQU(08KB{FxNg<9KfUR|-cJK2< zS+*=CY#vWp2!8h(c1I0goJxAZoZx@0x6X1!)!rcg_oP^Q1j9hqeyEANhE8j>=E}fl z`~nxONVyX?=wE~&4&z;%4+tD^J`hz%5yzb+gZw3c1oJZ7iNyLon6!RE$fn|MA) zfea@Hc;PxN7!H=n9v&3Q?E1E5wR;xyMNJba*@92z;48jRYx0vIFep)oR6D%)BpcPt zDlfk=x8NGIH3qu6pm6lwp37=rd!Ju}N}QP4(Bbl7+I_gay&WyKHNwx=TkCLY;=j-f zInBIxvl#5i_)nsJ8wq2elP?DAuy=?%dwbi-0Jr`Os-jaTO+4De`!J%EOKxyo`o$55^h0i~8= z_~5IQAy`q$fNB$wQ4q*Am}{KZ7yh?I*O_YEr`xua9(Rw`SDlrdYnyi8fNbX_SXxxu zz80``q3%YA`x0|{W)G zTPl=WFciAi!^7kL`qG%U!#7@U?beaL41+>vd{DEc9Gtnsix6OR8dY zkNvMorO-o~R5~VP9Aa5N}eS`^C`kmwQ_IIj!1^49*#1a(sgQ&UsCfX-!#xOloe zRL|AxrKYBC?i(9Zyl57$|ICo|N8$!`4t7f|unA4qYnk8c2G+;83PibBfWW?`$4S-h z*R0bhusGeF9c{eHReJjNmMt?C`AN7m&5!{H_TTEi7@baTk4S1zfa9PbrPZr*a8}Y! z=oPP}UGQ`Cgb%3Gn|X2#ytU&#@1@)X6J&0jicox_p8FEdUCYOIk~irKGA=?#>(6t^UIC|nhYS6wga97l?hx!g zcu&gS+J)S0$Wwl25kzDWy-qwqcJLY%d~5=cHa^Srtu{(bFgMV{-hTGJxB3I~y)Trt zi6S}9fS`)jD9GmK)1-gVcKyE6W5qI+3)UBlVY!NGRv{=#>a~>`-%v|X%9?{*+XM0f zYRFi}o^4Ku?B`1_7?aY{0vhV;w{l)+c1}I17;=_R*B+w-p^F4*UCMGH#)yVRaH8QN z=Etp!v9ib8Q<%V6sG9&`;Ur`Oa6e6-pXHVKSE7LTKM7W;zj^a!vqNG)QS|-Y>}G&N z1ptZL5!P!9I5l8>c<)Vk^pbcvh*BL&;e=Qu&Anr3@hCWMV&Ky1$|2{DQ=EvR-3vF| zKjk41HdO+3;tIt@hIOtXXKx{h7DFQ=3T?S~d^(xqgjiNOVa*&~^6LspXPKyGU4I7= zzzWwB*bwnlR^o^E0<|N~(#Gc4AT0gtjQg7F4nx>$hCz#)wQPU?pa3$!DSG(~z@t zta`5YT_v6?c=MZy)mF#zkd|MK&gb;33+7(aYm=w$wR9j;r`J_aXHhy`1B^1o$&198 zcmI3vj43sBbQH^>v!w9lUd6^UB5`zkAak9&*5%G+Bih4=a2YB3umE-Sjj}fmWv5Yt zjJoPOy6PK*@$vCkJ#})X{q@ofBmDXErxA?EX@!%W2IT_gT?f;7N}VXX5oIr{UcN_2 zeh>DEif7+e@7g*sjEO+Rw|#Gf z2AtOuhH>ZT#_dw&NsD2SQWt{LwM|vc$F6!%?@iKd?>kDUAlW=V69t7+Yx!aOL(>ZXt z5-s;1Te6~K;%CnWT5zLlnlH_}_@tYkNYvq@w}8pRZUH|>Igg5EA}#BiZ{50&z&NtLa@28(Z}N@{WA2_-(D+UqU5=!b>+d& z6AW;GqxE*H^yb6>I12n)u_AbAZy`BOl-1P8j`Z~O*i=gZnPvr^Dq3<>@YoBp>r;2| z7vT>^#7OaUf{L=Zby5xjCjWSy$dg^I=D=P6V~BqU1OiiHyo%i3`!bFR1}cLcxF19g z=kmc;))1q>!idxWY-Nb`9%5Y)KaW3%7kc`ZeA*f@DRP-vE$9w$byKSuJy8m8Rn3RF z9FD#C3oT(NJsp9J`R#}973tK==70$Rb~_fzxx#aZteqL2y%%N3<+WJkgEteBN))BY zuM}U+kvn4c{_z{^hz|(Q!JVX&R4Vf<_Xa_E2 z4U;0V=#{Vjdw7qbY`Vh{NIbr~&MW$XwOxd=29L|$SHq5Tkx6L@L-KCCbLEMyV5qf( zpG55Y?Y6@*pC4IjpbJg~7ZG!dPg(NLKfI7bOTly2$y)Q;kluI<8pD6rw@6p;btIQ! z#&eqlzL32Hi^4zBrwSZ^G)8u+tWRy7Nd5G9ZXkDVgx~3m{L!t^^(Mhw1H>LofK4| zl$)IH9~$!81oHPKQ6*i;{h*gcZ4T2C{ZU$6tl$OX98Qkds449oKGN8@5T(c9*+x^( zCBz(l4NMh@d3uGk9H)KVK2EHd97UQ@vHoW1Oz5vYEd(WW;GK9qHKA#b(pxQK00T{q zT(5WO-)w)kmwJ(*%c`;bvF`y@&ZIMiKLm5RcirFsMm-Ewp&|Qx9D9K9Re}BCg=Wm% zKb-dyzsCJq8~ff|6jVtZp4|SM>%^S)+sk#p$cbK+kKPX4$h>?N+2m5Tisq7spMSTd6?%@m&G^|mldb3Dr8}#p3bC{7O$5f${a36OG_&@!mmdE zzre*IA}%Mlq3YwaE+8y))z42ZQInr*f;=dG_GEqnWKT7g>!6=aUD?>yfjVZp!$95U zN!+))Xt8GR9H=R}I1~d=_B=|!Q#=M`(1W;l- z0rK=t^S2m09xa71cV}{wAV8pq0Zas~L|mm7nkE;T7K6_UnQV>Hsh^_HqS?POgo`M3 zp(wrMV!5AJGWZfdI6&4I1%L80Z*IFnk#5X+!UdGsu1)2|k|z}*_&^Cfdg4i3Mft(< zn)jtIweqd^gTjmdC1q7b5DPzm^MGQW!?3H{lg!HXlphN`uqj8kEKQi<8Y=^A%g)?= zCAj8&^f z^ahhd29VVz(<_OSA<5d{0_z@3h`o0qr>B`tDWLmR+P4t&X85ld^}>fibAWGmZTHJ; z>9rLCwZ@cs)!Jcy?GVUtGAITE!B}y(Fwep9>Q$cXxVqx{dNU$X^(+Oq>5mHA>I%yn3OcV=mEjfOwgroG(|i*;^7epyu(|_BW-+x~eXt;{q*T z-ReG{VvE|3L)?%fE&B%K1IVQf=lu^L(E?67_y=?aC6Hat`5SvxLH7KNcwN0YdaNPL zV!-u>jO$_|b;zp+t&>BrTQC=Hc_Tm=h{a!Jj10dVV^;p(eIjL@MZADr$ag-y{l5>~ zLuyE`m&ml;jsO2w|NF21_YeM;l>wghe{q$bXb|9_3iqwW9z literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/if878c588-3c26-486b-a20f-b23ffab4d579.png_prev.png b/src/examples/2DPhysics/img/if878c588-3c26-486b-a20f-b23ffab4d579.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..13394ac894dd4219a1637e2c84bd73058c4de871 GIT binary patch literal 1599 zcmV-F2Eh4=P)USGcj@F zMic!5Na-9XZw!Hj2kB<-de7*@0&Z;^kNpj6^*A&UJyi5pb7YSUe+voGWGB`N6?Zl>I0x-r80pN2d zPB@iKCX@F@Mn*2|=y5~<#+cYKDoVyqYS}7~KZpY2L~k${tmy6S{ex8pHv!3H z@}Jq+*&_A~U44B$)YaAHWm*`ef++!n5VEtg z1KZntm#v09}2p*R!*;kV>Vv!B8SrR#xET7 zw_jytWzgQPyrfUilV^O)RZlGCqkJ~kVOE-I1QlRD$`2|r?)|>b8l}CHa9n& zVB(UsBEYo7?e6Y|qN40tXl`x}lF1~OBkE*&WYVK^ad8pO&(Ce?;1O~}05wF5AzBn^ zhusZQ)$`!N1E{U76}~?h5`Zy&3;-XovMC}ZsO|p#e(?MK!iX>gtN zxslhRDo$4kP?3v@2A|Id0|NuD-YA6-pdM{{=|f9pS~5EsH8nNR(9qyYBSshj6kv06 zGgMYqW?{X#xq(C?;e>-*S+&rbpPz@z%S&7Ogc2Y+CA9kO+(>gmfLTgxZEeBs?yjxX z(+cr!ln9~EB9X|as-91%9Ng+q(}MOVX`fOZgpjYI(dfIGhJ>ep z78O0p=#i#3tiqRvq2l6V=<4da6HKa5${n`|y+i3Zkj039729bn#3~On^>nk_Y?$bD7%W@p$|x zLg=0?3l4>hqT%A=0u~k)9BDUu9(yJNW@cvYr&1};o_0i}1VU&s5{YCtFYYMSYl(4} z#EzCnBW!j`EH5v^>FFt#Iqt{NT%H81ySH{8>v3rXp6bntfOtIq6GG^}1JvW;FeQGM zBq?MCfvGKSr9|ZCEiEk&3hCpZFHQ8tNpG;MzHS82f96XX*u68bf;S`}7K`=x{eJf@ z=PreaSxTr4l7fPQ`~Cg>f3li1d>qZkD4?J^J3FDIq(m2=q9|L1g@p~Q;0+_-E)6~a zfG>EIx>uk{{?cdOJBsiOKAWA002ovPDHLkV1m-v;gtXY literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/if878c588-3c26-486b-a20f-b23ffab4d579.png_prev@2.png b/src/examples/2DPhysics/img/if878c588-3c26-486b-a20f-b23ffab4d579.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..a0b2e46bda818c239e13cc00e81f4651b5863cb8 GIT binary patch literal 4198 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 literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/ifc52b049-910a-4e96-adcf-378553c0818c.png b/src/examples/2DPhysics/img/ifc52b049-910a-4e96-adcf-378553c0818c.png new file mode 100644 index 0000000000000000000000000000000000000000..0b71aa819db8b1b861e4a371cd11a428922f221d GIT binary patch literal 1486 zcmV;<1u^=GP)#c2Z@GQ4oC>Kun-Ng#KuB2#1b0|(GUyKaHNkV3{7#Y?VqW^mtJ7@i6s=x z4`&9&%AxH<e$xazWlOZfPaF5Q`hvlo8E*V6VAuKp>^XI z+&B@8#g7J`O^jx=^=V<`mDhL#9mcY8d}kmQ71w9;iP4-p+%D0a9c%rUXdJ}?2(_^2 zLi5||8{)!v2ho@sNN}UDBt?VxeRY^t% zem>oo3YKbUuq~o#(D-Qh+Y1Bud2z3}rveTV z=n@>FF@yyPMxv4Mn{$2Q!Q#G}aMFC}NHmUM@g_bBoKMHwt%O4~zEP{KX;LgfaIlbG zpY1Ita;bDRb@^x~&oowJiP+XOEN*|$6A6nq)@Sk+XBWT$q}w0%JF%w0cg`Fd9lcq+ zrK?e}V9LRoMQs)8k4(+fmPhY<>;X1vJL$xf|J= z1#1=c<#44$FqWh7zpM=`*kD{N^#-=1o{^wN$;39Z5UHTi(QZT6ms5+iTQQ&JG7O=y z1&eyHrrvM){AdRcwryx^!cs=FG*m8TyA!h@jFBQ58?hiBFYPOrmhdcN3Qdj1M)-h@ zSP-a)^cHdvP5Nj;OLL=EBQ`1kM9EjEiAH}!s^`}6X1^&cm@>W7Ceav1gK5YmEY;Rp zL}LbxT+^uA{WfC>noEhsEE-s8!BfkyWKQKGBibQV{c?-a;OG>?f?J)07*qoM6N<$f(OgJ^Z)<= literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/ifc52b049-910a-4e96-adcf-378553c0818c.png_prev.png b/src/examples/2DPhysics/img/ifc52b049-910a-4e96-adcf-378553c0818c.png_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..30de9f33c4ffdf1c7c401857f0c808ed5e3b616b GIT binary patch literal 1983 zcmV;w2SE6VP)2U0Habm}j;&W-g ze$Mm%o&WQkb6!)}<%8Pg`cVZ?b-*qsP%VL7Zh&eNtd>Bv1a>)rY6RMntl3gY~_P(9NP+D;Q8)4n`3T0zP}-$@_=pIS*aYy zL9>7lZ_Y1sw!3fO{H4@(G1xW${U>@`D8|3k)!Go0IS-zj>vo!SmeTG-#TJ&+xc7dCysxHR^x&R{6HbvH^Jc8=cL(D&79Vp&$#_$`kK;KJG1BSpa(i zJQzAR6^f!jb0XfmHtSfL@%eH&p)3HR$B!IhICiYJeUA*sgzvAc^y;u(4@u32%(K~# zbjv}b%7J6(oaYDxumWO}_jKE|zVzzrmoJyPCzKq(kB|3s@ErACcUz;%!hz@Ic6-zD zkV@N-J`ukv$gmvLDI5eg7Xm2+6FfAG}ja2qgv3ccS|!!!mDnw>2tU zA@R0{MZ3xUgb0>$!(PbD48cD_7x?$U))K`0r3=<&x6F}!l+i;o6r z*D?#P{Gz_T_*Fq!2r`@J1X2zRpX~{rYo$8||9bUWDIk;tzz?78Xi;kAkB)UUQ${+S zmsiGevn#*W_Uj2C;Vqo<}J7op7l(RbhVJ(W2z! zVpuMgHzEH2%OM~#kP0|~7bV1|=3rXRx6h1>{bjQdHVq(p{E4SHUU<8uQO@pV^UEe^ z%13LX2$7?qn=aC(3t^pxx6y=6+l3-9kcHfGAU%QQ1;nS5FwM<7VN(F2krd6l8_oup&2&fQ*#hl3VR#!`$DlvlILxQxQT`; z=0YIf$!fMS!m+(y44oVMSuyd80uYUa!W_d4AM0v{ww(V4D{VS3EDtq`i252CDb2*y zJE<&)U=0tNob0wREXRe!(@=zb2#TuUKUZgwG2NdGjmA%Jbeauk)E^H0falrY9BtnN z)6nzcYZ(U_+XK&jTC1|i!*qek|1P2_z-PPqRtJGVdQ2fFh_ZzE)I9Wz)purO?A49T zwIKk}NN=AivV%ts)WgbT^5Uj+8=eoa^rt5{0wb|#qN!HGf#v|Tv z?o=mpd7*ew#O0eQETzrg4~~v~A3*L!WX&%=6F^@$^ixITUJ11|Y)HK4`$#NWAUEB^ z`y1hTE|#=R-i4@~5Cj5YCPnTXe;OKzz4V~D9tfcCWX}lC(I=mJEC@r-xBa4SXN9h= zJaIx&bI{dVk6M|>y@h3X#2RH9#0lh8LTv#jNV0^BlS!mA&gjr+JiJ~C>j9wp!o7b~ z1m?w~hnk?L3yF6??)2;@SJJf28{qMKQ0C!q{@`G6nE`fBG{|f@PX}$Aql{*fC2{p+u?xJ{{zpq-UDZ3 RXqo^3002ovPDHLkV1j&(#z_DG literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/ifc52b049-910a-4e96-adcf-378553c0818c.png_prev@2.png b/src/examples/2DPhysics/img/ifc52b049-910a-4e96-adcf-378553c0818c.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..0344a9190d56fd00a70cef61e505d8034f1f96c4 GIT binary patch literal 5473 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 literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/img/r2d29a5c56244.png b/src/examples/2DPhysics/img/r2d29a5c56244.png new file mode 100644 index 0000000000000000000000000000000000000000..e7749ae9cec91001b6d2df36845bc6696ea5e0e1 GIT binary patch literal 43284 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 HU8VM49)n>w literal 0 HcmV?d00001 diff --git a/src/examples/2DPhysics/snd/scbca4dd5-7369-48d5-b3b7-e9b0055e2eda.ogg b/src/examples/2DPhysics/snd/scbca4dd5-7369-48d5-b3b7-e9b0055e2eda.ogg new file mode 100644 index 0000000000000000000000000000000000000000..c7571219ca1de805962b755672b39b80c6a08cc5 GIT binary patch literal 5634 zcmeG=d05lOwiDI_5F-Q)h%`YE!lDF%B^Kvs5on0O#TXC) z5h4MCKmh>}pI1a#1OWkA)T_7wTD4NKRr}Pw`6cM}dHdbI@9X#eyYo#ZGv}O{GjoyFju_PTYp^LT7#`=u8Y8HErjn zY90CglX6H=zLYtn@Q;Y&uJ(%GL5hwD-7I53Vv?+_sMc22RyHJ4ub9|~xOh$iCpM9S zvRVXxj%;71kFSFpCpMOovJnEDygWQyJbir}e0Cu+8)0%hUA-&v?GFKx_kHT)rn}=G9fW3JYwhSu$?g!jIw-N6o%7g48X#LsqUmM z-qk!m0Pp|=8&Nb5Z)Z{@qH@!eLXj)l>$1u!6lHZ0!?LN}?;8>=Tn7Lpz%#|=o|s`q z4$^lJO>*PQ=nQ}^Osd&v6ao0QL%#*W9*^;WhhgCxRNsnY_tjGlQW61 z`CdQ1mt0j6uVz+VmZoOiOFH0g{ix!w=ekGLCvw;QXxe<0p>Ekc!g8mMZ4RqHrxLU` zVURvsve4jU=ODqoFjREI@eC}wJfBio7!Q|{ABzNd@R(rZAp*0J(BG&(Fl^-Y+`?Pi zcG$+n$JfITmZYF9$()Jgq>1FzfWnNeZK(lm8Cxeaf?j5X5HlC*tJJBNXq6qEgaKtM z=>_*GVnaRgJ-S#iJ+oE;grg!M;e>`1;R$Bt_Q?8(;#Tg-)|mb#i~c4J840u)C3H8S zETX9LU+VUN0?waTgxhNd5Gc!@UHUz{j9l18J@FQ*G70wrs8fV((G^eIm_X}Eu!nVH zqVr{u`@jqL#qX7XsvQ7?s^7Is|1y*Y+j2DCc1wc8M1scz6a^}ge_!^?>;)c#d~AC` zBoAwo7a|i24=zhA$2w`qIY9z``2$kX)uQn)gt^sAD=3-r5iM%Inus57`A*n1sb^3wngg?(y12-I81B(eKg8Ywkb zC6qv}Bp)_N{F~WX#01=`eJ99;Lf;UeBig9@$K!WLR_PRmSbLOtCzO42eOXf6@4Srm!fqo7hr;dzcK zx-uupER64?R6(!!SaEYkLrYc+e^iHl*7w#jhIQE5(#OWZ&)08Da!kOHq?@NRf+mxu zCQ_#+a=u=c`Ke)j@Eic0CSB%}qR?z=PsssK22QTv_nxzh&~r%t@*yL)CL@m#i-G4f zuX)Zk%(>pm*?KxPpmo7vvt9{}*TtQW#po%Y;<>#7a6+~ne z)tnJW_tigc|I_-xb2i2s!3sUcCEn;G&*`A*J3()1vhbLfpV46$3N+DW*~bb1pd(Mi zO?HoXvn|KiwqtBdKVOG`?=cWNM)Me@LBZw&pbNl*ipp!>XljM#(X$-6Smq`Du5Ifc zm7Lg?{e9lb4a}!xpDTE!mcpyd48pa0fsrc`d=KW-g}XXhwsy10)77f*+QWi!U7*>6 z(f3f)cTOUEz`(RM(zUF5^R)jV>ID!DWjZhuE4XyfbZ`k zLK1V1#tRFWv)`lVu6@UUsr}+2)zSCf0R!xF+8zV@#g(``+Tv2xp#u>gl1@NMfrwH_ z?2000FTA&FxRE(`?MmEmdZdE=XnI5^Eu`j;H^&2jhfxJQpxuXj*wYDDEdcC<;Q=te za20YefX4|aRRuuENiq22W&ZEbPZ(l&rT}~|_Fo#O->;Ax7JvvX)PnBE09Epdv{s5jXEFVz$7TkkOsR;N@r6laYOU) z+@=C?gaQmNQ0&U6*w=Dd7Mh#5y(~a70v&hl3Z_%UrUIBh)%5MM=%07?CV2Sx1s%EX zUoe{`HZ7{GXI~! zBteTTA;R=VN(e)99FdR;^JGufHdb=oHX+nNG!0V9upNuD~-~DH(bH+x#&+IERN{c%iJ#4q52!Qv3MT^3V6n|7UM9 zFsiO)GawFTC?WyrIX1OM$8>U4mgzNV0W1z`-H}gyXe1J{SUQYsx~Xw~L7LELKIfpGHxhhTXz$DV6Q0Ms{17|td=?( zcv!3Rm4S~XB#wf%urfSH+8qcD2gk|Iu^ba63@QV}!44qCL`M@{zoj83aLWvMGR2&M zZ%Al*-Kd43cEzqBCO|VIrK2yg3akv_2-8cbGu^QFi1KtHyARGl5s;m+F61c7o8$_Ne@){`+3vCZT|c>OA(x8F)L z%S)a%_4W!I3D)k2%xB>X4X@hRJRW#5JHC&}taI9+H&a$I$y6n`_`7jPj(tqlu{112 zMVgUqCMi5z!00bzYlNCp6SJus>C?W99wNN5(*RIhVy%rOAp7@g06WArPXVZ@Ypl5F zf*`BOCpyaF-0e(`t%aWv_*d{*{4Nr;T+h_3QjyoCoVgeDUY&G3+s0e1xb13JkSYVq zz^D1^BN&{W-Q7%++jHs5OUitFe?%Z301Ut#czs<5hKAy!_aDx^0=Ol*WD1q;xI~8j z0=xpimCfVxxifa_?AId<=MAnbch61mdc(*fAp$U1to*@=jcRc5`>=OO`THtHvY%jWo%Mw2wmy?m3 zyf>}MS6|6(VeC5`+kO0tnDMGM^4oW(O8R`ji_ECzMMG^Rc1eQUaXGDbb&qIQdG2~F z%^@6(!CfGex=Ch}p>@H_qxb7om+LIja~YEc9~c*OW7Wit=ERAI2eWl9jH_+16?2i5 z!P`FD%X-wHd+4(z9qeQ4C5e;Sy??_7OFd7QMs5%k*$)Kgh4IW({FG>EVkh%50ky6y z@)Yr4Or1&tzj{&G=8UYJ=Y~e}&!s-o_;LqVU4Mf^{l@<`b@SZ!xi-D+Vs+0i+|M|d zA3fV{sJi=cfs4SfilV*>k8j+7Z{Ww|Y;FlPS6NX5b)tN&;qTK&YR0P9(G;+T<9lu? zHHRL|nG}z`$!f>FU>=NItTg!bla#OL-mQ=De!9Qo^+AVc>bHN#Xg$DL6N$~3tU3SA z{9tKgOQ>I!W*uo&?9TqZx7w3i1U18EOCzV-2k1MeAJ6YL`L2KYjoI`gzlYiQXPn3X zF!C#Z{HNj_zm?2A-xsZNa&Fl@zZ(X_H&W-Xd=8Maw_;D(hP-c_DDqq)0M?!O>zfDHhFt{Qy00Juf5J4Xt&*3 zQ!W;Z`?(tDgg8V@46}tUKrxN1^|+L!!Ufb z8e({Svoyn+4Ye87fY%imh5^?Mg9!j(uzCG>7Vct`#>J+onKx&H1pQ6-ocVG7c)(N2 z0|u|&yj}MXUgkIFouX$vZ{7+pd5GQn?zr}J{)#43`KYZxwLtaU(2bME#3g1uXR_8LAe+gkQwsmG<}6awQVExSm!Y{ zn91&6G@!^e1o*G2-?U71vO84N^3VUe)n9G0<8#pQ6;fPff^5Qq{b zAXbnBkzNeVE+{IFb(9e-_X{}loA-Nn-TT(M>-~4XbvT@|&)Mhfa(2nihzM^$1o-*g zA+0qM9^KjsSqIs-Cy^SJBped4Z}|z5um*e?vQxP7?@zcA0`}a;>uVKG{rI=9P;_l8 z0}z57wKsC(&cp~zY!sEWiU8w;F)_xO7@HWIV)S;##YgQ;3`+`&Pu3Trk^}#2ynLLz zeYUxT#m9#o*Z~gM?R0Y^xchi-^WKvZABu?xiw{c-P7VvjP!C{s1jmJWABazm#DpjA ziNp9Lr9?zw^ioq(Hzo_4MoGyjp;3D_hU|&chlsBoOW)Rx1OcGn#yD3DpEi1e2mmku z2-4P<<$5v9eKIw+UH?%dx#1HH1K;?YJL7D=1M~y6mm$0JJHL}rQbxbX> zPvcx=qBOR#CSBU350l|)^0Mxv`<9oDwFO&V>9vj8${4kcJG%I1B?1m74O=Z# z{;Z@{?|}i&Vk@B>3bTa@x2NkjlqG^&tz8QP(7m_9K--eZD>q;=LbViMe{LJ zg13(w5&TjDcI^wB+Lto5?}%?%mfz(gzL&H7rn3T;vVzgsKg*v-9xVyW>eVq2phnB8 zbWWeCslt3}#T3oVZV>@Og(Sg96=~`h)jBnVN1u;6-5%N89yf5&aNwfsDhMbsV#3n^ zHBl7}|5pk3Ee-qc3qG~@kh zSau}a-zjz-`or~?znCCoI{@HuYWxJXJ0LZ@j3yGzcO`9`N^+Y5Ng<@jfBmJc$_scA zjjHg{Xd2Y?MDQwEXyCT!vrs$PHB2A?{p>uZVzlDpkd=JJvzjKv>51+fpVJ#Uo61hF zaxivSc@E^Iet$W?rhK#_Y1m1&qJFJ)K;g6`yJ;L8Q(mZ)?^O;4;iASXB%5Z);9B3& zisy};2(Xn?e0l@7(`RkJpJLGzSUKEn^#ojzGkZFwQz`&#eF?36GbAM(`*-odgM15~ zM)y{wV~!L^P!Spp8YkB!AD2H_gX`0;TbEzp;M|nq;ibHxJ~@5$<`@9Tgsn>PU)8E8 zuSs!T5d+(=cWKaMz))DV^bBuVZ0&*B$>KmMHZ%vNIG_8xI_bQXV@y4Fl44L_?B%Rq zzotg87QQK|YaOi{C&W~neiUdzHH*aLX7&2u773OUbFkv1xK@1b@3!7;V^XoX`+x}Bw z*3{CF{S@ab0Qh% z${6S9j0JjuUQTILP6exz8QXt;@ydT&{??owiP~TRHHVO>{SVFQ!l~JT+H}#-?c>@V zw~c`WO(rP)lK}wGbwbu<^&Ro_G7@;13%rboKHL6ljR9u`W^NN^AYn@YKp6n$>l*GJ zlvkjhu*$KCggWum`2Jg7R@VCG9Xp}r;PktO_aID+TQ=&Hg}D2a60M%(lbg>Db+j>R z@AcAn)+h-+dr;u4G9Z5&qUI*5<{}Dep73*;0NfCeAw{8K0>7>?GrZz9ydq((`#-Be zL}s31VEMTXh-l0QGG_J@7l@_Hlz=w_Ey@4<`%iFrnnQN}0~g-R4e#UjKgaaHJNz#N z{#y!wA`X!U|4_DPH4H>(mJs0B9KHzS^v;YH;``2ezly8kV=~J6mkP;=zqA81upi9! zue1K8LF$CrFV&JG8Bu>bodD(lI8hA(I|?Tjy8n`E@F4G86&s|+GNVPTCo-dMnFX`{ z^TW{qfCiBSXnR7w)Ev2VA&03~(ZIU05kxeyYN9~rF0$h9 zVKX-b_zHusaez|A^Bmz0EY^7$5OTuI4TPK$XGT^uG_gQSf?ov`qy#9@g<~wxx`+sY zhyhb2VYDc4-Ieo(qCRVV4I*#(|8f8 z>UQ}ZfKa)p3D7}V$P0GH)ysw`*h-%bcC~fX2=(&9>eox#Vs%QqY#i(ByiA;p>Px-E zHA;${n=o}=Ui#tyP2CdwBSW*YTf~45ptl!NOdYgZ(6)8PJ+Dks(5Od)?oAUSMCjub z7vVrZ+1>e08926~J`{wjJn6Ls9Fqqfj${y|uGq`=MP(A`e^h}W5CFeMZAPrSv4YPk zxsgEEVDxp-~63b=1!~@qdaxga)pm!G1<47J0!xTM5$tj9UH`zx@AfO;cjnToQmD z&JxA@X6Bo=XjdT%B$QMxDgaO^kn1*d+zajEB4=l$ZJw2$PDv?+!M3I2p!IckDX}=z zh?V5ox?&1Lzx^r5AaD%43dLVWqrRAuYt)WD9jgIyEe0G5F?HsGW8hU{44pf{F%BOc zlTXk)?I)a#VVmneOLE}|C?PFe-P*h&;aCfIQnT30q!-~0wkk4>dza85Ew}~BNrhW~ zuJbkmfnz{f&ZO2!BNdjHZzL#A8Wsoe+bjXR4%%jeG7%MAE2XrKw#t}JFopz0 zuU9BxLb>v)Lnnb^hRGC0Vs+5f2^-M81bL<$^4G-K%p$LTaD{LJM5j}KcWs59&=YoU z`g@|jxY%oTEp*jI218Myr>X$TZm}1*E(^r5<~xG~kp^+BPB?=hE5cT4ZKAjcgscBK z0U`qT|2Y970=HRP0XBp#c!>~J*jTNbge|bURuR~7$^uLy&<9~TbUp@mRz(lnAWGwlXCDUoMyDOGUZ!mn z^&jO2NZLYeVd-QwJVeUU@=5lFhwm~~D{H)cUg1GJ0I&{t3_f2ofTkw1eD1}&KLIJY zvW7m+$_BnVy%qQq035w&bb91-%PrR1@wWC3j?S*`9^mr^S+#_C00M=s1)Rb@6uOo~ z>9Sj04+xVeLbDhl%^gy5-m$~+2l5AGW$E>Yhp+D6yZ7S3@ci#jc9`|1B3=6)Ny}wQ#Vbkz|)C zb|Ag75&=vyQTfp}wR%j5ry0YU|K{zFmF<^z#SsP3#2#OmliFs5%`DE!LjZBeO&qD_ zxWo_iwh@;c|Hfu}+iT)%&{AHsAmUweawNgWfQ1w=+;!GJ6NAV2@bplG=EM~q*@tj1 zJU~+qU>S~vb3~>T@p3L;Y@|! zNg!IU$rmO?%}0CI_L70HtQ_c*Gy^9M3|5!qdUw3lv&D~$Gjz2(=Ri2dh9hJ7EGjm; z-W(_H+7zyi%x$jtQ_%pmr;RT?iNQ)^Hi}>C%V-`f*Gn3nK~XkHPgkATng@+FiM|p1)pYB_I>IPkx zpB7(G;9a!aycAx${dnd24C3Pb2ia`p)M2wiD>Lo>J6A4t9)G5CwU|CQcQU zg$LccV#j-{Pkl7VK7PZx`j5&_8zDzkulM4fKt(x!t-P$fTp5T)qg1!%Rtb#n_S(m~PLSeaJzzaPF&3&=5}$<>Rb@B2vFr$W z(FPcn$MeTmPDYFK&@#-dPZJOQx~Sr<4$b84Q1z{qn~ATQw~%5s7h219PF`*U#oA%7 z>D1DL!V4z9l*SLFF2n~A%RiP5e%gA~e3{3FAN!KH`H^b7%3VQP%s1`E-Caugi};Io zI$Nshl4=K==f7sMXS6;hl~jJg`iGmSTy@)jA-8&>++u0YH70_+oBj0Fl`{cKA(O`T zNv2APknO*mY5ExLs2F+Ui5^h>vhok}F~-*1tZ%nMO{z}b(4{0qa9Vel4f?VCG7%SR zx=h-A?ELFx5)CR;E-|;hk&1+K+sq8( zf{|m=lTCsepjV=msYpa*o~qAw{ge=ca?=uDy%SfAb3S^O=1YB}T3h(XIk!NJtmY|>-X zmgcVQQ(+3VM@{}*_Ft^R?=4@N$r{=Dz~p4YjqRebLA#N(rHE6{4=$3f-P$s<_sp*{ z^Gtqh{Dc06UjkXz+}@()C|);q){1D~ukXM2N5NQ)&a)LWEdJIJ-{#vJH>Sniv(Z*` zxD^^6PNjzHf^KHCr|N52KW+zldfJc3?PjHL+Ymhx%_s}XW>-Hb3Etg`SS*#mmZGVR zawH?ytZiCs;@wssB<~6m(U;Xg63n6iz()+;>R}LFo5hkR0J3NRX6`D+?W(9JvOx@Z z@fdO=C=>K1iySJ2jAp(QFC)da-xg@gyiY!n$xxd+$-GE~AjCOnn~>)q-NOcjXD zjKum}RG;kES^4VUaO7aB2=s&bQ0*k5KJ3P;%vpwI>>~csbrEL~$o8npugZSMG;TAW zNJ|(CoaW|7rnksiq2zWM)0!Sm=7ow@>KHFn=jIwKh{qOX78+ZY(8|eqp!fqoD+hjv zL!G`?7L99ZsEXovdPD9$jE*VOjOL=tqGPxaPtL4XBVAz3Ve63$S@J|NKQ2~;9%%$b ziruRT0`dV18X-0&Uz#S@m<~y&&IY;hDjSe9XTg~2XXB0X2z4aUFSEw^m;PtG?bXNFPO|4qcIJqqyVj}XQ6=0=n*xQ>DL-10KH6kP!n`I-A1{`4E-5_I4-fdgJ76ACQ_JVceY@~+f zB$yNiXcAzziy{ZmK+%B!_RT$De{ZqIujI#36cyh@UBOX_y?#ZXJ{^wnM;gzX6Ry9# z``eH2=eGg&hu}pR^>-m};()+N!{uEptB8{C>t9cZKS8PFelLk0_O1URxsq&iS;=Pl zL3g)?5=^T|JCCQm)mYKg6cjlcH3p5fpk}ZI#&O1S97gaZBiA0Y)-eZzhI%3ajx&^9 zOgEQlMG)k=Sy>-0W-Qld8!SnoeC?j;r$Jx!eA>y%B+39mT1=aB)FCq;aKj&9o+&aV z66vQe2f*q)Y>+ZNSF)O-$z*dMC}w<@c+l{{MSRgt$eXaQcGHX#m6`Y7JXb&Ch|*+h z&86O^V6R?-+B10#w;wqv>3<~N2S1ZdY7rxm5;ETaelV9^-hjLkb1Xr>2qC$~>tG&tq za5#Y*U5j)vB)EARl#&HxSQW#ec`OoQh839>VMx;&MVOutHD%~cP1Vpecy1$Aj=}U! z1MnU#O!B4pU}?2e5CB0qC*oiT3|ph~rm;*L0of*(DInu%oeP zvsHT3^`VXEMn%`x9b2#N(6(ND%w2_{g$ge}RrDPUd&J6(C|bF$2Ye5= zexRkB$Jwjmetu8y#5U?Jw9L6D^9L2R(bHZzIaaPzoFo;83&(})hUtFde zu>K6+;@PiW2i0khWdKpkv_mro4RB~M8X+_CX4oU2JD+baW9QA2vmiuN*LCD<2g6k@xuApyjCuis>J$m&)BERjN#J)lwrO>m^3Fi^?11$@2yW z2erX>rzg6Getp)DjY4=6<6>h8M*A=rbVD#7w#a}sNW|t-ZMtY|B#+F6#{Vvl3i9A; zQX#EQGD}jifu) zysZ=AGXMER#i!2;pNw|iTN+Uu&<4_PahpYEEw_#jKY^Rd@#@Z_>8aoLM=dYEYGzLP z9r^RgrbsD>%4iNC4IB-(UO40-dZ#8GS*bkoW!+u8Nn?=sg*pq=Z>^8ak_6F z=u-IRm6dB_z>{Hm(eRLN6;FGC?r9@Kfz@!o3U@UwwPi04=X@WhITU@+C>}-L%F-#P2H-LG3%4OG;h9_V8;D+QZIMDUOOA zhX(gwjT`Xix1G?9Un=RLy68MM&Qa&7@pxsvdFA{vFj8qXjpb9Pn}Q>1sT{hw!DI`z z!CX<*Rkg){#8$~RGJ-8e(={51F$hBx04E@1NB}`n0@ZaSU7uG25r-i}dGTINVk$N8 zZZKf0g~L^~fY$4jPjI7O?3Z;ji@KaNWy#`Br$;Sc$w#WagNVF_*OlN)j%?l*e(LR@ z)y)LSmA11Z$DUm>bWc}OjbYSYvi~SJpLXUzb82eU59N#Jt(7vh(=_!TI%v9IqCo<| z%WWz|f6Bn^_5N2gDQx|;nwphmE1Hvm=7?Sr&BcN5H?VKI95vA<-Xm{F9fBET2UED& z!3^>2Ec3g!P}#`E#;CNYrZGCC#T+%(-nl#pU^*^r)`p0p zdFZTQpmnRIrGle|uI@gbVejA|)fKd_)6M*cK8IrEdL}1(@wgT;hZ-C_)Jjbwchnoj zwfP_oP!?pIk()Q%j;$=gVlgp?{*YO)MLB6CJVZ#Hm2!IY=>C1}bC4he$i{q4S{e-k zX{DoDUW`HkP3WiKM*~~i!(6vroSZxS`E~*JE-UKHx)por&cr}NuTRGv;2v>#G_x?f zDB8&miQDV;?>nAwc`7SFv-i=}MEOsbE$AmdvM2WYy*RIRpEEuBuN*lw$*cZl-!!EOl9?o-e}#y&;pqC#-gaFk+*&_9JCXfFFT$GaA! z>F`oGgy`B@FC9(KpNzewz!SO4${Dh#XpBS{uzFb-Ivso!1(67XSn&8Hw^1W;VN)&* z8pHuz;uaNfD*%YiA5C3y7didI@@qN<)u!Te&zWP>HC}E#j6oWa@|kqvm>xsZ#VYV~ zTIKPnn^BQx&c$~!3GUyzzs_z>HOGcWsP628MMiC=&#&v5s9mHS%Iy@ z&*|JU7j#u~&J)kBs@L!Q$8^y0@3w zG+a?JHwOoLEhmLmf$uKxFiej!msM4bZ3QDzgbjf`9m^k=;GQRwadPHyT`W4>Q$<&#zb~)2$2}F>aIyHs^X)PAiiq~JwQ%d2&ysqpNewZRY$0`l)ue_{(QMu)aN&0! zj0@%;OihdqcV4~Ie#XmSUBuzRQ+GTqR;nKtm~MYQFt2y|$j-gHEu_GJ3^a`L^72&q zL7{;JchJoaj-dz7QbU@vgHJ{Umo?-}N0J+Zo1!lTYgjZ=7?5-%n?Y=*>rvY#HOP2k z7ZY3j+a`f+QTy`r0SRKnXo=Ns7uiR30_Nqo9ifINLI>2Zf9fR~eBT^X(sNe#;Vt%2 z+l@KTzF2$TQoAU7jqMzGu)zF@@%ZyQc-8fJ&$o|5lEn^x45)re6D#3~R@PWfMU#vo zcE^=t5>D)&y526h!c-Dbmj&(P{Y}S3gCDmAyDM0lzBzR}&3Nyu@dhdXee=fZVEO^J z)tH0Mtyc&M4=)0*)wQl?xXnE7b!#}G;A>-I|eTMs36Vp3`Pbaj% ziRLvu)RH&OCnBEK;ylIkvFUO_nJ;2H5x`JgrQIuHZZ=PLzPa+|djrf$f$kC%rWN+! zWyr@^@tu=X)4vD*`5|>>sl0qkfX7h_QpcE<+X=yj&qpftG%0yTs18pNc$`2E-N_Ge1Yq7xDFp^j1vw8v%7+&Q2+EcRLTL*$^PEaSxdkV$*u&bQLx zY7zv;4}to1)eme2)A(v$bZ=ogp}bs57~?Rb_v~p@XJr#(1>+7xa{+?W8eYRhWe=h0 ztcucXdVN|HIy;4EP|7H;Kv~GaBYPyjLB^I3y*KH~!s##lh(*3gOx+dTk&W7`z6k(O zhbf{M5p<)vq?!%>s|Cf=2ny^+A3IPbW9zuvy&L8rt6vv*Grr`)<*Ba?>C5-N*MvX} zhc2w_7CpQSiwQGRBQvlugKw|LzVSZZmk*Hu)`k0@iyP}7ok_`DcR{9l>)>Vgd(4uv zmlmPh0+pEnP2F93ufIG^^0o?TObdb9^6IAEb@|Xj!+Q zOkx)X(3|U9HpaZOJ0=b8jGlewdj+8W!ZOa$ZXU8*;6PDG$!KTmXtb9-Tw6aDY zu}CeN4StjFY|6v2M;lM|+*lYf8A9M>HswpmFG#?t z72Zr$XCaA(Ix}a+w*;I}EFH)$HuS7oe<4vEsuVX7apTMU)LW<7Sr%ub{Rh^WEJvei zv(DSMPVP6beHN1OoAR!-%j=$hnzBwcJ@?k#Aj>=LN_?JnBvt6d1YS5NnZeAGUMhQk z@Ot$+Sq2Jw{}>S4!~+dIJ%=M|%0ZjrOr1c(7_=v353ak7>~=^tH=mhp4(G%NUvjhX zCMr_6V=zuzI+h8gQQ0IyEkoSKfYnHzJrMKZjdy5U8-G(o1}v+EYNXfh zfD|kAPuj;!F1@-}ZC^f2{Tjm_`pstORNP1bW{HM#grEqhZ!d zE_ab;qL)poS9D%US3vx&f;wJeN;NSKgH#?Bd>pBRLa%?qxYe5&8!Jof8J6G}8{y)N zVKFEYLKe|X?vXIau3%cA?Q8A0>GI+pv0`in!QO-0$g&ZG$9g^`1sjDGq&E`eE^;^< znBBbq%0RK>+AAXm0R`VCLM`iSiSgODKk!G|{sR70onIe(pFgdwucxx8wa|Zmzxq6; z(XhnD|JadRdv)(e(;ADX-|3|lOb2$Z9NaMd=a0E7g71n_YP0W_M^$(2&QKuG0E`51 zBkWq-NGPj@8?4PMec!(AWK;TNb|GvRHQ%TN;Mg%lIT@n4RxprhLYtxaIXj;fyoc<{ zRq|_PQOTyVixz(9QWVLak1()kVm4r=w-EV_bWA$A*&I-@fuGGaW*sWqj|6hfMM#E| zMbC7b+SI!Z6z61)$%sMPm{^JKPjXj+z5qWyBHw9eTz#A%qno1e!#nwUYvTt5x0n~x z$bIe7<2QMpQX@M1A;9Hn2D(Y1`(b2160l6X_s0Kb7aMP2FExGpd2J|dUfeIE3w%Eg zuV2hIfV#g3hML|up3;L9+wh06{~g`WK0MHV@m_Zc?U&Fw11uV^2gl4p8z{W4@m6ef zl!iqxnXY0%zGNH=rq4Y22-Ox}^H`sWaR#-5WatX7A`x0VVfk_Ssk=-cTZ~YCY$fe* zA2?P=e`;MG?L|0z?VA6YzDVfIT@NR(XCa@zmqD5|HFtoi&>wb3PZ|#&X#QlhEAi`r z*NL-AFX2U6qmGox+zvzWQsuFwrbi!--D@q~#d!F3X-j7PW}W>9UltPHNdV%3Vb&w2 z#pywQb|vqvM?9p{0&T0C>Q>K(ISE3QaFm@Tbuclz3X~f zxr)4D_ZAXS@WP{oF0bCsM5=E|iS+bL!D1(CCiH5$>5V~1B(3>}!4+3`DzzyzYjcGJ z9HJ`5j&$kwIq*682mHEDm3JRk%pE%Eu8~b?KK8B-`!Mm-ZB&JwZ(ky+y>Eu8|LemJ zAC6wZ5JC1~=b8QAzbUSy|0#ZFQ^vqDBlot&tCC4OL+9<4!}jTDC{^-d*kS(;nP~~t z7be@!-xH6`kW<4?55W~pb8(8f!e0P{a@O8JiEWW+0CRrr(bMC$Y`+?dpt90v>JX1* zZf9>qkdyIjb`X%0(`hx138&O|0s<@mW%PdVdp7vB?98v(FTJc-v~Lj`@B5!y8Ah6r@l_Yxs=c_WgEg z^oZhe)XrHkDcSv)vl3+;Gypt!;P9)~C_X7|o{Ibk%Rfx44ru>RTg#;;izhtJZ;n|*=76;H(RQOwt>lGRDvvyYzuj$M%L;fEiL zRC#pC%k<`5Sm{)ho(}H5v6XJsYKNt+njBP!1bFaRp7f+Wdq%-UW-*&`iOR@o%nDYa zQ97JliOA-L;HVro6siG@qO*_&Or{0qNHk?f$hj!955X|)l$c*;V=x7t>o_T=yqr7d zz8oR=C@(I;?2XCCka?OX$&VswAK@cel?$~m<4(Z?TVom96^NmyoUUrYRAbIx-R87> zb3h~Dx!26(u{Q_&M)Yoc^UQb%gKQ$fbwYwmCAuv}lfkGz>cJIq87ex8 z!lDveh&(2949j3LiT2I7NaW+$DXaFln+4C%C9K-Kn;XNgE?cC=wSSjVF5RW0@?0jO ze=N>TD)R^7+LP=Xmpu9p7tg#;yzXatyCV$qHecF>NfG zno8zUXJdl}ZIH2=XdjLogN$RoxEq<@D2;l;Jy>e~=B>MS2jNQpYpIul(^H8_mh#72 zYJV4tPc+#qZ#?&Iu~ zI(kH2R15Mw?CV1NQGJN(C;r+E S{Es5m5@k`jVa*?nfd2r=`D}y$ literal 0 HcmV?d00001 From ce2ba7a7d0a03326b667ecf482a65bdde5e4d752 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sun, 28 Mar 2021 15:10:17 +1200 Subject: [PATCH 27/32] :bento: :pencil: Check out the latest docs --- docs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs b/docs index b6c1f84b6..495c12ee4 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit b6c1f84b6d168f29cb2ccc017e016870c89bd189 +Subproject commit 495c12ee477c4659a1b5abd5e0731f0f4ade8041 From 846a2b8386025ad16be1d2e4b4755b152963b0ff Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sun, 28 Mar 2021 15:12:10 +1200 Subject: [PATCH 28/32] :bookmark: Bump version numbers --- app/package.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/package.json b/app/package.json index 2ce3d999b..08804838c 100644 --- a/app/package.json +++ b/app/package.json @@ -2,7 +2,7 @@ "main": "index.html", "name": "ctjs", "description": "ct.js β€” a free 2D game engine", - "version": "1.5.1", + "version": "1.6.0", "homepage": "https://ctjs.rocks/", "author": { "name": "Cosmo Myzrail Gorynych", diff --git a/package.json b/package.json index 2a77d89e1..0e6ecc62a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ctjsbuildenvironment", - "version": "1.5.1", + "version": "1.6.0", "description": "", "directories": { "doc": "docs" From 17356f1309b5d11b628d969577a36207fc03d59a Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sun, 28 Mar 2021 15:12:39 +1200 Subject: [PATCH 29/32] :bug: Bump ct.matter's version number --- app/data/ct.libs/matter/module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/data/ct.libs/matter/module.json b/app/data/ct.libs/matter/module.json index 0384ed5ed..254b87e40 100644 --- a/app/data/ct.libs/matter/module.json +++ b/app/data/ct.libs/matter/module.json @@ -2,7 +2,7 @@ "main": { "name": "Matter.js physics library", "tagline": "Add realtime 2D physics engine to your project.", - "version": "0.0.0", + "version": "1.0.0", "authors": [{ "name": "Cosmo Myzrail Gorynych", "mail": "admin@nersta.ru" From 4f53a48212ef71ac4fabc64a541376ada3280936 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sun, 28 Mar 2021 15:20:53 +1200 Subject: [PATCH 30/32] :pencil: Update changelog --- app/Changelog.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/app/Changelog.md b/app/Changelog.md index a069b78e3..3faa3f8f2 100644 --- a/app/Changelog.md +++ b/app/Changelog.md @@ -1,3 +1,54 @@ +## v1.6.0 + +*Sun Mar 28 2021* + +### ✨ New Features + +* Add `ct.filters` module by SN frrom our Discord server. The module allows creating special visual effects with filters or custom shaders, applied to your copies or a whole viewport +* Add `ct.light` module for adding ambient lighting and textured lights +* Add `ct.matter` module for 2D physics. See the new example! +* Bundle `ct.nakama` module by @alexandargyurov β€” you can now create online games with ct.js! +* Group modded fields into collapsible sections with a new field type +* Nano ID catmod of the same-named tiny library by Andrei Sitnik +* Optionally make a camera stay inside a specific rectangle with new rooms' settings. + +### ⚑️ General Improvements + +* :bento: Update Electron used in desktop builds to v11.1.1 +* Allow Background class to accept a pixi.js texture +* Modify emitter tandems to use `PIXI.ParticleContainer`. Provides better performance, and also fixes issue with un-tintable emitters. +* Renovate `ct.desktop` -> quit method +* Select only the needed Nw.js version for debugging + +### πŸ› Bug Fixes + +* Allow resetting values in type and texture inputs at modded fields +* Fix "}" at the end of some texture files' names +* Fix bitmap font's XML ("kerings" typo") +* Fix broken context menu entry for textures to create a type from them +* Fix crashes of built-in debugger; disable nw and node in the devtools +* Fix `ct.place.meet` returning duplicated references to copies if querying for multiple obstacles +* Fix icons for nightly and regular releases +* Fix Point2D initialization for modded fields +* In rooms' copy spawning code, check for scaling extensions separately +* Remove the old main-menu tag + +### 🍱 Demos, Dependencies and Stuff + +* Update nw.js to v0.51.1 + +### πŸ“ Docs + +* Add "Dragging Copies Around" tutorial by @qewer33 +* :bug: Add missing methods `ct.types.isCopy`, `ct.u.hexToPixi`, `ct.u.pixiToHex` +* :bug: Add `moveTo` and `teleportTo` methods in `ct.camera` (#49 by @firecakes) +* :sparkles: Add a list of gamedev resources +* :zap: Add categories to ct.u methods list +* :zap: Minor edits for JS intro, pt. 1 +* :zap: Refurbish the home page. Move most old content to "Basic concepts". Add links to tutorials and the cheatsheet. +* πŸ› remove duplicate instruction to draw `scoreLabel`. +* Add a memo about `ct.desktop.isNw` and `ct.desktop.isElectron` +* Fixed typos in the Space Shooter tutorial by @sarturodev ## v1.5.1 From 7275ffb588cf6c73fa278e625f530da5aae50a20 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sun, 28 Mar 2021 15:22:36 +1200 Subject: [PATCH 31/32] :doughnut: O_o --- app/package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 03c16c156..db5d5a70a 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,6 +1,6 @@ { "name": "ctjs", - "version": "1.5.1", + "version": "1.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3997,7 +3997,7 @@ }, "xmlbuilder": { "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, "xmldom": { From 0cd3bb6672baa96ab1a9a08608d740b3fcb4331d Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sun, 28 Mar 2021 15:27:13 +1200 Subject: [PATCH 32/32] fdsfsd; --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 33d7a516e..51319549c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ctjsbuildenvironment", - "version": "1.5.1", + "version": "1.6.0", "lockfileVersion": 1, "requires": true, "dependencies": {