From 782fa2e18bd89e1160849e06a276c5992b46d01f Mon Sep 17 00:00:00 2001 From: Perryvw Date: Thu, 1 Jul 2021 21:32:29 +0200 Subject: [PATCH 1/7] Update packages --- package-lock.json | 110 +++++++++++++++++++++++++++++++--------------- package.json | 6 +-- 2 files changed, 78 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index f9f1ac2..c420951 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,19 +6,19 @@ "": { "hasInstallScript": true, "devDependencies": { - "@moddota/dota-lua-types": "^4.10.0", - "@moddota/panorama-types": "^1.9.0", + "@moddota/dota-lua-types": "^4.11.0", + "@moddota/panorama-types": "^1.10.0", "find-steam-app": "^1.0.2", "fs-extra": "^9.0.0", "npm-run-all": "^4.1.5", "typescript": "^4.2.3", - "typescript-to-lua": "^0.39.3" + "typescript-to-lua": "^0.40.1" } }, "node_modules/@moddota/dota-lua-types": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@moddota/dota-lua-types/-/dota-lua-types-4.10.0.tgz", - "integrity": "sha512-rDBFRKAy3dcYniBtVA3Hru+ZWyBQkqJWBB/KG9BYFbnNiLxbwY/Z5Nj23QXtLkN3kixbehSQibntFSmrAiepeg==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@moddota/dota-lua-types/-/dota-lua-types-4.11.0.tgz", + "integrity": "sha512-5hiVqzQHEi9lliJSu+jpqMtx9l6bq1E3lgp+QokLd1rLseRE2TkWPhu162jmIQrUz+7YI+t+i2PpMT9UgZdN4w==", "dev": true, "dependencies": { "lua-types": "^2.8.0", @@ -35,9 +35,9 @@ "dev": true }, "node_modules/@moddota/panorama-types": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@moddota/panorama-types/-/panorama-types-1.9.0.tgz", - "integrity": "sha512-FlNPzXCuojUO/+WiXT30ugFH/4Na3hjAzMNM0ODFsttHWAFDXjbRfSu3S6pPcD0qkzvGk7ESFCpx0dTfLw405w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@moddota/panorama-types/-/panorama-types-1.10.0.tgz", + "integrity": "sha512-0XzWeAVqebR0fEfEHq4MToiqdHH4NRT426qvUEJPQD2T0VGQqeAocO75IbXiA4ehE8oKYUUOHm6RCEPfPT2ivA==", "dev": true, "dependencies": { "tslib": "^2.0.3" @@ -161,6 +161,19 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", + "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -313,9 +326,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", "dev": true }, "node_modules/has": { @@ -850,6 +863,15 @@ "node": ">=4" } }, + "node_modules/tapable": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", + "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -857,9 +879,9 @@ "dev": true }, "node_modules/typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -870,14 +892,15 @@ } }, "node_modules/typescript-to-lua": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/typescript-to-lua/-/typescript-to-lua-0.39.3.tgz", - "integrity": "sha512-a9unziEbOc+sDT6ZeKLQV538WhX+rOaOCVjA/pvcjUs0oIESjEGM0HJnz/B3e1sUk/EtgU+b6bZKBcqKX1q4VQ==", + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/typescript-to-lua/-/typescript-to-lua-0.40.1.tgz", + "integrity": "sha512-wbnXddF+kPWKDQMcuVZLRUeZsMde5Qni6FnYIc3SKOAhg2/ZAm9ctWQsyQLwH8piMUX+uZzFXaJ3pMe186crfw==", "dev": true, "dependencies": { + "enhanced-resolve": "^5.8.2", "resolve": "^1.15.1", "source-map": "^0.7.3", - "typescript": ">=4.0.2" + "typescript": "~4.3.2" }, "bin": { "tstl": "dist/tstl.js" @@ -944,9 +967,9 @@ }, "dependencies": { "@moddota/dota-lua-types": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@moddota/dota-lua-types/-/dota-lua-types-4.10.0.tgz", - "integrity": "sha512-rDBFRKAy3dcYniBtVA3Hru+ZWyBQkqJWBB/KG9BYFbnNiLxbwY/Z5Nj23QXtLkN3kixbehSQibntFSmrAiepeg==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@moddota/dota-lua-types/-/dota-lua-types-4.11.0.tgz", + "integrity": "sha512-5hiVqzQHEi9lliJSu+jpqMtx9l6bq1E3lgp+QokLd1rLseRE2TkWPhu162jmIQrUz+7YI+t+i2PpMT9UgZdN4w==", "dev": true, "requires": { "lua-types": "^2.8.0", @@ -962,9 +985,9 @@ } }, "@moddota/panorama-types": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@moddota/panorama-types/-/panorama-types-1.9.0.tgz", - "integrity": "sha512-FlNPzXCuojUO/+WiXT30ugFH/4Na3hjAzMNM0ODFsttHWAFDXjbRfSu3S6pPcD0qkzvGk7ESFCpx0dTfLw405w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@moddota/panorama-types/-/panorama-types-1.10.0.tgz", + "integrity": "sha512-0XzWeAVqebR0fEfEHq4MToiqdHH4NRT426qvUEJPQD2T0VGQqeAocO75IbXiA4ehE8oKYUUOHm6RCEPfPT2ivA==", "dev": true, "requires": { "tslib": "^2.0.3" @@ -1072,6 +1095,16 @@ "once": "^1.4.0" } }, + "enhanced-resolve": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", + "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1204,9 +1237,9 @@ } }, "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", "dev": true }, "has": { @@ -1631,6 +1664,12 @@ "has-flag": "^3.0.0" } }, + "tapable": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", + "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", + "dev": true + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -1638,20 +1677,21 @@ "dev": true }, "typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true }, "typescript-to-lua": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/typescript-to-lua/-/typescript-to-lua-0.39.3.tgz", - "integrity": "sha512-a9unziEbOc+sDT6ZeKLQV538WhX+rOaOCVjA/pvcjUs0oIESjEGM0HJnz/B3e1sUk/EtgU+b6bZKBcqKX1q4VQ==", + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/typescript-to-lua/-/typescript-to-lua-0.40.1.tgz", + "integrity": "sha512-wbnXddF+kPWKDQMcuVZLRUeZsMde5Qni6FnYIc3SKOAhg2/ZAm9ctWQsyQLwH8piMUX+uZzFXaJ3pMe186crfw==", "dev": true, "requires": { + "enhanced-resolve": "^5.8.2", "resolve": "^1.15.1", "source-map": "^0.7.3", - "typescript": ">=4.0.2" + "typescript": "~4.3.2" }, "dependencies": { "resolve": { diff --git a/package.json b/package.json index b2f1ec3..c5129dc 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,12 @@ "dev:vscripts": "tstl --project game/scripts/vscripts/tsconfig.json --watch" }, "devDependencies": { - "@moddota/dota-lua-types": "^4.10.0", - "@moddota/panorama-types": "^1.9.0", + "@moddota/dota-lua-types": "^4.11.0", + "@moddota/panorama-types": "^1.10.0", "find-steam-app": "^1.0.2", "fs-extra": "^9.0.0", "npm-run-all": "^4.1.5", "typescript": "^4.2.3", - "typescript-to-lua": "^0.39.3" + "typescript-to-lua": "^0.40.1" } } From bd3d23c1ca9e45dc1f23abe417c9480f920b6fbb Mon Sep 17 00:00:00 2001 From: Perryvw Date: Thu, 1 Jul 2021 22:39:25 +0200 Subject: [PATCH 2/7] Pull source files out of junctions --- .gitignore | 1 - README.md | 5 + content/panorama/scripts/custom_game/hud.ts | 7 -- package.json | 4 +- src/common/events.d.ts | 25 +++++ src/common/general.d.ts | 15 +++ src/panorama/hud.ts | 36 +++++++ .../custom_game => src/panorama}/manifest.ts | 0 .../panorama}/tsconfig.json | 4 +- {game/scripts => src}/vscripts/GameMode.ts | 18 +++- .../heroes/meepo/earthbind_ts_example.lua | 100 ++++++++++++++++++ .../heroes/meepo/earthbind_ts_example.ts | 0 .../vscripts/addon_game_mode.ts | 2 + .../vscripts/lib/dota_ts_adapter.ts | 0 .../scripts => src}/vscripts/lib/timers.d.ts | 0 {game/scripts => src}/vscripts/lib/timers.lua | 0 .../vscripts/lib/tstl-utils.ts | 0 .../vscripts/modifiers/modifier_panic.ts | 0 {game/scripts => src}/vscripts/tsconfig.json | 4 +- 19 files changed, 205 insertions(+), 16 deletions(-) delete mode 100644 content/panorama/scripts/custom_game/hud.ts create mode 100644 src/common/events.d.ts create mode 100644 src/common/general.d.ts create mode 100644 src/panorama/hud.ts rename {content/panorama/scripts/custom_game => src/panorama}/manifest.ts (100%) rename {content/panorama/scripts/custom_game => src/panorama}/tsconfig.json (64%) rename {game/scripts => src}/vscripts/GameMode.ts (78%) create mode 100644 src/vscripts/abilities/heroes/meepo/earthbind_ts_example.lua rename {game/scripts => src}/vscripts/abilities/heroes/meepo/earthbind_ts_example.ts (100%) rename {game/scripts => src}/vscripts/addon_game_mode.ts (61%) rename {game/scripts => src}/vscripts/lib/dota_ts_adapter.ts (100%) rename {game/scripts => src}/vscripts/lib/timers.d.ts (100%) rename {game/scripts => src}/vscripts/lib/timers.lua (100%) rename {game/scripts => src}/vscripts/lib/tstl-utils.ts (100%) rename {game/scripts => src}/vscripts/modifiers/modifier_panic.ts (100%) rename {game/scripts => src}/vscripts/tsconfig.json (79%) diff --git a/.gitignore b/.gitignore index 99fcae6..f199325 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ node_modules /content/panorama/scripts/custom_game/**/*.js /game/scripts/vscripts/**/*.lua -!/game/scripts/vscripts/lib/timers.lua # Dota 2 stuff *.bin diff --git a/README.md b/README.md index bc343d7..eba954a 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,11 @@ A template for Dota 2 Custom Games built with modern technologies. It includes After that you can press `Ctrl+Shift+B` in VSCode or run `npm run dev` command in terminal to compile your code and watch for changes. +## Contents: + +* **[src/vscripts]:** TypeScript code Dota addon (Lua) scripts. Compiles lua to game/scripts/vscripts. +* **[src/panorama]:** TypeScript code for panorama UI + ## Continuous Integration This template includes a [GitHub Actions](https://github.com/features/actions) [workflow](.github/workflows/ci.yml) that builds your custom game on every commit and fails when there are type errors. diff --git a/content/panorama/scripts/custom_game/hud.ts b/content/panorama/scripts/custom_game/hud.ts deleted file mode 100644 index 25c34da..0000000 --- a/content/panorama/scripts/custom_game/hud.ts +++ /dev/null @@ -1,7 +0,0 @@ -$.Msg("Hud panorama loaded"); - -GameEvents.Subscribe("my_custom_event", event => { - $.Msg("Received custom event", event); -}); - -GameEvents.SendCustomGameEventToServer<{}>("ui_loaded", {}); diff --git a/package.json b/package.json index c5129dc..27b0513 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "build:panorama": "tsc --project content/panorama/scripts/custom_game/tsconfig.json", "build:vscripts": "tstl --project game/scripts/vscripts/tsconfig.json", "dev": "run-p dev:*", - "dev:panorama": "tsc --project content/panorama/scripts/custom_game/tsconfig.json --watch", - "dev:vscripts": "tstl --project game/scripts/vscripts/tsconfig.json --watch" + "dev:panorama": "tsc --project src/panorama/tsconfig.json --watch", + "dev:vscripts": "tstl --project src/vscripts/tsconfig.json --watch" }, "devDependencies": { "@moddota/dota-lua-types": "^4.11.0", diff --git a/src/common/events.d.ts b/src/common/events.d.ts new file mode 100644 index 0000000..4fe9c56 --- /dev/null +++ b/src/common/events.d.ts @@ -0,0 +1,25 @@ +/** + * This file contains types for the events you want to send between the UI (Panorama) + * and the server (VScripts). + * + * IMPORTANT: + * + * The dota engine will change the type of event data slightly when it is sent, so on the + * Panorama side your event handlers will have to handle NetworkedData, changes are: + * - Booleans are turned to 0 | 1 + * - Arrays are automatically translated to objects when sending them as event. You have + * to change them back into arrays yourself! See 'toArray()' in src/panorama/hud.ts + */ + +// To declare an event for use, add it to this table with the type of its data +interface CustomGameEventDeclarations { + example_event: ExampleEventData +} + +// Define the type of data sent by the example_event event +interface ExampleEventData { + myNumber: number; + myBoolean: boolean; + myString: string[]; + myArrayOfNumbers: number[] +} \ No newline at end of file diff --git a/src/common/general.d.ts b/src/common/general.d.ts new file mode 100644 index 0000000..d90bc84 --- /dev/null +++ b/src/common/general.d.ts @@ -0,0 +1,15 @@ +/** + * This file contains some general types related to your game that can be shared between + * front-end (Panorama) and back-end (VScripts). Only put stuff in here you need to share. + */ + +interface Color { + r: number, + g: number, + b: number +} + +interface UnitData { + name: string, + level: number +} \ No newline at end of file diff --git a/src/panorama/hud.ts b/src/panorama/hud.ts new file mode 100644 index 0000000..e65f177 --- /dev/null +++ b/src/panorama/hud.ts @@ -0,0 +1,36 @@ +$.Msg("Hud panorama loaded"); + +GameEvents.Subscribe("my_custom_event", event => { + $.Msg("Received custom event", event); +}); + +GameEvents.SendCustomGameEventToServer<{}>("ui_loaded", {}); + +GameEvents.Subscribe("example_event", (data: NetworkedData) => { + const myNumber = data.myNumber; + const myString = data.myString; + + const myBoolean = data.myBoolean; // After sending to client this is now type 0 | 1! + + const myArrayObject = data.myArrayOfNumbers; // After sending this is now an object! + + const myArray = toArray(myArrayObject); // We can turn it back into an array ourselves. + +}); + +/** + * Turn a table object into an array. + * @param obj The object to transform to an array. + * @returns An array with items of the value type of the original object. + */ +function toArray(obj: Record): T[] { + const result = []; + + let key = 1; + while (obj[key]) { + result.push(obj[key]); + key++; + } + + return result; +} diff --git a/content/panorama/scripts/custom_game/manifest.ts b/src/panorama/manifest.ts similarity index 100% rename from content/panorama/scripts/custom_game/manifest.ts rename to src/panorama/manifest.ts diff --git a/content/panorama/scripts/custom_game/tsconfig.json b/src/panorama/tsconfig.json similarity index 64% rename from content/panorama/scripts/custom_game/tsconfig.json rename to src/panorama/tsconfig.json index 4d9983d..de749b7 100644 --- a/content/panorama/scripts/custom_game/tsconfig.json +++ b/src/panorama/tsconfig.json @@ -1,10 +1,12 @@ { "compilerOptions": { "rootDir": ".", + "outDir": "../../content/panorama/scripts/custom_game", "target": "es2017", "lib": ["es2017"], "types": ["@moddota/panorama-types"], "moduleResolution": "node", "strict": true - } + }, + "include": ["**/*.ts", "../common/**/*.ts"] } diff --git a/game/scripts/vscripts/GameMode.ts b/src/vscripts/GameMode.ts similarity index 78% rename from game/scripts/vscripts/GameMode.ts rename to src/vscripts/GameMode.ts index efa855e..14147d1 100644 --- a/game/scripts/vscripts/GameMode.ts +++ b/src/vscripts/GameMode.ts @@ -1,7 +1,7 @@ import { reloadable } from "./lib/tstl-utils"; -import "./modifiers/modifier_panic"; +import { modifier_panic } from "./modifiers/modifier_panic"; -const heroSelectionTime = 10; +const heroSelectionTime = 20; declare global { interface CDOTAGamerules { @@ -17,6 +17,7 @@ export class GameMode { } public static Activate(this: void) { + // When the addon activates, create a new instance of this GameMode class. GameRules.Addon = new GameMode(); } @@ -44,8 +45,17 @@ export class GameMode { } } + if (state === DOTA_GameState.DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP) { + // Automatically skip setup in tools + if (IsInToolsMode()) { + Timers.CreateTimer(3, () => { + GameRules.FinishCustomGameSetup(); + }); + } + } + // Start game once pregame hits - if (state == DOTA_GameState.DOTA_GAMERULES_STATE_PRE_GAME) { + if (state === DOTA_GameState.DOTA_GAMERULES_STATE_PRE_GAME) { Timers.CreateTimer(0.2, () => this.StartGame()); } } @@ -68,7 +78,7 @@ export class GameMode { const unit = EntIndexToHScript(event.entindex) as CDOTA_BaseNPC; // Cast to npc since this is the 'npc_spawned' event if (unit.IsRealHero()) { Timers.CreateTimer(1, () => { - unit.AddNewModifier(unit, undefined, "modifier_panic", { duration: 8 }); + unit.AddNewModifier(unit, undefined, modifier_panic.name, { duration: 8 }); }); if (!unit.HasAbility("meepo_earthbind_ts_example")) { diff --git a/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.lua b/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.lua new file mode 100644 index 0000000..e9bcdc5 --- /dev/null +++ b/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.lua @@ -0,0 +1,100 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +require("lualib_bundle"); +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["5"] = 1,["6"] = 1,["7"] = 1,["8"] = 3,["9"] = 4,["10"] = 3,["11"] = 4,["12"] = 7,["13"] = 8,["14"] = 9,["15"] = 10,["16"] = 11,["17"] = 12,["20"] = 16,["21"] = 7,["22"] = 19,["23"] = 20,["24"] = 21,["26"] = 24,["27"] = 19,["28"] = 27,["29"] = 28,["30"] = 27,["31"] = 31,["32"] = 32,["33"] = 33,["34"] = 34,["35"] = 36,["36"] = 37,["37"] = 38,["38"] = 40,["39"] = 41,["40"] = 47,["41"] = 47,["42"] = 47,["43"] = 47,["44"] = 47,["45"] = 48,["46"] = 49,["47"] = 49,["48"] = 49,["49"] = 49,["50"] = 49,["51"] = 51,["52"] = 51,["53"] = 51,["54"] = 51,["55"] = 51,["56"] = 51,["57"] = 51,["58"] = 51,["59"] = 51,["60"] = 51,["61"] = 51,["62"] = 51,["63"] = 51,["64"] = 51,["65"] = 51,["66"] = 51,["67"] = 51,["68"] = 51,["69"] = 51,["70"] = 31,["71"] = 70,["72"] = 71,["73"] = 72,["74"] = 73,["75"] = 75,["76"] = 75,["77"] = 75,["78"] = 75,["79"] = 75,["80"] = 75,["81"] = 75,["82"] = 75,["83"] = 75,["84"] = 75,["85"] = 75,["86"] = 87,["87"] = 88,["88"] = 89,["90"] = 92,["91"] = 93,["92"] = 95,["93"] = 70,["94"] = 4,["96"] = 3,["98"] = 4}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.meepo_earthbind_ts_example = __TS__Class() +local meepo_earthbind_ts_example = ____exports.meepo_earthbind_ts_example +meepo_earthbind_ts_example.name = "meepo_earthbind_ts_example" +__TS__ClassExtends(meepo_earthbind_ts_example, BaseAbility) +function meepo_earthbind_ts_example.prototype.GetCooldown(self) + local cooldown = self:GetSpecialValueFor("cooldown") + if IsServer() then + local talent = self:GetCaster():FindAbilityByName("special_bonus_unique_meepo_3") + if talent then + cooldown = cooldown - talent:GetSpecialValueFor("value") + end + end + return cooldown +end +function meepo_earthbind_ts_example.prototype.OnAbilityPhaseStart(self) + if IsServer() then + self:GetCaster():EmitSound("Hero_Meepo.Earthbind.Cast") + end + return true +end +function meepo_earthbind_ts_example.prototype.OnAbilityPhaseInterrupted(self) + self:GetCaster():StopSound("Hero_Meepo.Earthbind.Cast") +end +function meepo_earthbind_ts_example.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local projectileSpeed = self:GetSpecialValueFor("speed") + local direction = (point - caster:GetAbsOrigin()):Normalized() + direction.z = 0 + local distance = (point - caster:GetAbsOrigin()):Length() + local radius = self:GetSpecialValueFor("radius") + self.particle = ParticleManager:CreateParticle("particles/units/heroes/hero_meepo/meepo_earthbind_projectile_fx.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControl( + self.particle, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:SetParticleControl(self.particle, 1, point) + ParticleManager:SetParticleControl( + self.particle, + 2, + Vector(projectileSpeed, 0, 0) + ) + ProjectileManager:CreateLinearProjectile( + { + Ability = self, + EffectName = "", + vSpawnOrigin = caster:GetAbsOrigin(), + fDistance = distance, + fStartRadius = radius, + fEndRadius = radius, + Source = caster, + bHasFrontalCone = false, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_NONE, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = DOTA_UNIT_TARGET_NONE, + vVelocity = direction * projectileSpeed, + bProvidesVision = true, + iVisionRadius = radius, + iVisionTeamNumber = caster:GetTeamNumber() + } + ) +end +function meepo_earthbind_ts_example.prototype.OnProjectileHit(self, _target, location) + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + local radius = self:GetSpecialValueFor("radius") + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + location, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_HERO), + DOTA_UNIT_TARGET_FLAG_NONE, + 0, + false + ) + for ____, unit in ipairs(units) do + unit:AddNewModifier(caster, self, "modifier_meepo_earthbind", {duration = duration}) + unit:EmitSound("Hero_Meepo.Earthbind.Target") + end + ParticleManager:DestroyParticle(self.particle, false) + ParticleManager:ReleaseParticleIndex(self.particle) + return true +end +meepo_earthbind_ts_example = __TS__Decorate( + { + registerAbility(nil) + }, + meepo_earthbind_ts_example +) +return ____exports diff --git a/game/scripts/vscripts/abilities/heroes/meepo/earthbind_ts_example.ts b/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.ts similarity index 100% rename from game/scripts/vscripts/abilities/heroes/meepo/earthbind_ts_example.ts rename to src/vscripts/abilities/heroes/meepo/earthbind_ts_example.ts diff --git a/game/scripts/vscripts/addon_game_mode.ts b/src/vscripts/addon_game_mode.ts similarity index 61% rename from game/scripts/vscripts/addon_game_mode.ts rename to src/vscripts/addon_game_mode.ts index 7665d59..ed777fc 100644 --- a/game/scripts/vscripts/addon_game_mode.ts +++ b/src/vscripts/addon_game_mode.ts @@ -1,11 +1,13 @@ import "./lib/timers"; import { GameMode } from "./GameMode"; +// Connect GameMode.Activate and GameMode.Precache to the dota engine Object.assign(getfenv(), { Activate: GameMode.Activate, Precache: GameMode.Precache, }); if (GameRules.Addon) { + // This code is only run after script_reload, not at startup GameRules.Addon.Reload(); } diff --git a/game/scripts/vscripts/lib/dota_ts_adapter.ts b/src/vscripts/lib/dota_ts_adapter.ts similarity index 100% rename from game/scripts/vscripts/lib/dota_ts_adapter.ts rename to src/vscripts/lib/dota_ts_adapter.ts diff --git a/game/scripts/vscripts/lib/timers.d.ts b/src/vscripts/lib/timers.d.ts similarity index 100% rename from game/scripts/vscripts/lib/timers.d.ts rename to src/vscripts/lib/timers.d.ts diff --git a/game/scripts/vscripts/lib/timers.lua b/src/vscripts/lib/timers.lua similarity index 100% rename from game/scripts/vscripts/lib/timers.lua rename to src/vscripts/lib/timers.lua diff --git a/game/scripts/vscripts/lib/tstl-utils.ts b/src/vscripts/lib/tstl-utils.ts similarity index 100% rename from game/scripts/vscripts/lib/tstl-utils.ts rename to src/vscripts/lib/tstl-utils.ts diff --git a/game/scripts/vscripts/modifiers/modifier_panic.ts b/src/vscripts/modifiers/modifier_panic.ts similarity index 100% rename from game/scripts/vscripts/modifiers/modifier_panic.ts rename to src/vscripts/modifiers/modifier_panic.ts diff --git a/game/scripts/vscripts/tsconfig.json b/src/vscripts/tsconfig.json similarity index 79% rename from game/scripts/vscripts/tsconfig.json rename to src/vscripts/tsconfig.json index 863ea2b..29622cf 100644 --- a/game/scripts/vscripts/tsconfig.json +++ b/src/vscripts/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "rootDir": ".", + "outDir": "../../game/scripts/vscripts", "target": "esnext", "lib": ["esnext"], "types": ["@moddota/dota-lua-types"], @@ -12,5 +13,6 @@ "tstl": { "luaTarget": "JIT", "sourceMapTraceback": true - } + }, + "include": ["**/*.ts", "../common/**/*.ts"] } From b44b96fa63c1a85df8f5c903ecac08f25194d322 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Thu, 1 Jul 2021 22:44:34 +0200 Subject: [PATCH 3/7] improved readme --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eba954a..ae15171 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,18 @@ After that you can press `Ctrl+Shift+B` in VSCode or run `npm run dev` command i ## Contents: -* **[src/vscripts]:** TypeScript code Dota addon (Lua) scripts. Compiles lua to game/scripts/vscripts. -* **[src/panorama]:** TypeScript code for panorama UI +* **[src/common]:** TypeScript .d.ts type declaration files with types that can be shared between Panorama and VScripts +* **[src/vscripts]:** TypeScript code for Dota addon (Lua) vscripts. Compiles lua to game/scripts/vscripts. +* **[src/panorama]:** TypeScript code for panorama UI. Compiles js to content/panorama/scripts/custom_game + +-- + +* **[game/*]:** Dota game directory containing files such as npc kv files and compiled lua scripts. +* **[content/*]:** Dota content directory containing panorama sources other than scripts (xml, css, compiled js) + +-- + +* **[scripts/*]:** Repository installation scripts ## Continuous Integration From d674dc7a6ed4897e0ccbf335baf385272a836b04 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Thu, 1 Jul 2021 22:49:06 +0200 Subject: [PATCH 4/7] Removed earthbind lua from src --- .../heroes/meepo/earthbind_ts_example.lua | 100 ------------------ 1 file changed, 100 deletions(-) delete mode 100644 src/vscripts/abilities/heroes/meepo/earthbind_ts_example.lua diff --git a/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.lua b/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.lua deleted file mode 100644 index e9bcdc5..0000000 --- a/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.lua +++ /dev/null @@ -1,100 +0,0 @@ ---[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] -require("lualib_bundle"); -__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["5"] = 1,["6"] = 1,["7"] = 1,["8"] = 3,["9"] = 4,["10"] = 3,["11"] = 4,["12"] = 7,["13"] = 8,["14"] = 9,["15"] = 10,["16"] = 11,["17"] = 12,["20"] = 16,["21"] = 7,["22"] = 19,["23"] = 20,["24"] = 21,["26"] = 24,["27"] = 19,["28"] = 27,["29"] = 28,["30"] = 27,["31"] = 31,["32"] = 32,["33"] = 33,["34"] = 34,["35"] = 36,["36"] = 37,["37"] = 38,["38"] = 40,["39"] = 41,["40"] = 47,["41"] = 47,["42"] = 47,["43"] = 47,["44"] = 47,["45"] = 48,["46"] = 49,["47"] = 49,["48"] = 49,["49"] = 49,["50"] = 49,["51"] = 51,["52"] = 51,["53"] = 51,["54"] = 51,["55"] = 51,["56"] = 51,["57"] = 51,["58"] = 51,["59"] = 51,["60"] = 51,["61"] = 51,["62"] = 51,["63"] = 51,["64"] = 51,["65"] = 51,["66"] = 51,["67"] = 51,["68"] = 51,["69"] = 51,["70"] = 31,["71"] = 70,["72"] = 71,["73"] = 72,["74"] = 73,["75"] = 75,["76"] = 75,["77"] = 75,["78"] = 75,["79"] = 75,["80"] = 75,["81"] = 75,["82"] = 75,["83"] = 75,["84"] = 75,["85"] = 75,["86"] = 87,["87"] = 88,["88"] = 89,["90"] = 92,["91"] = 93,["92"] = 95,["93"] = 70,["94"] = 4,["96"] = 3,["98"] = 4}); -local ____exports = {} -local ____dota_ts_adapter = require("lib.dota_ts_adapter") -local BaseAbility = ____dota_ts_adapter.BaseAbility -local registerAbility = ____dota_ts_adapter.registerAbility -____exports.meepo_earthbind_ts_example = __TS__Class() -local meepo_earthbind_ts_example = ____exports.meepo_earthbind_ts_example -meepo_earthbind_ts_example.name = "meepo_earthbind_ts_example" -__TS__ClassExtends(meepo_earthbind_ts_example, BaseAbility) -function meepo_earthbind_ts_example.prototype.GetCooldown(self) - local cooldown = self:GetSpecialValueFor("cooldown") - if IsServer() then - local talent = self:GetCaster():FindAbilityByName("special_bonus_unique_meepo_3") - if talent then - cooldown = cooldown - talent:GetSpecialValueFor("value") - end - end - return cooldown -end -function meepo_earthbind_ts_example.prototype.OnAbilityPhaseStart(self) - if IsServer() then - self:GetCaster():EmitSound("Hero_Meepo.Earthbind.Cast") - end - return true -end -function meepo_earthbind_ts_example.prototype.OnAbilityPhaseInterrupted(self) - self:GetCaster():StopSound("Hero_Meepo.Earthbind.Cast") -end -function meepo_earthbind_ts_example.prototype.OnSpellStart(self) - local caster = self:GetCaster() - local point = self:GetCursorPosition() - local projectileSpeed = self:GetSpecialValueFor("speed") - local direction = (point - caster:GetAbsOrigin()):Normalized() - direction.z = 0 - local distance = (point - caster:GetAbsOrigin()):Length() - local radius = self:GetSpecialValueFor("radius") - self.particle = ParticleManager:CreateParticle("particles/units/heroes/hero_meepo/meepo_earthbind_projectile_fx.vpcf", PATTACH_CUSTOMORIGIN, caster) - ParticleManager:SetParticleControl( - self.particle, - 0, - caster:GetAbsOrigin() - ) - ParticleManager:SetParticleControl(self.particle, 1, point) - ParticleManager:SetParticleControl( - self.particle, - 2, - Vector(projectileSpeed, 0, 0) - ) - ProjectileManager:CreateLinearProjectile( - { - Ability = self, - EffectName = "", - vSpawnOrigin = caster:GetAbsOrigin(), - fDistance = distance, - fStartRadius = radius, - fEndRadius = radius, - Source = caster, - bHasFrontalCone = false, - iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_NONE, - iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, - iUnitTargetType = DOTA_UNIT_TARGET_NONE, - vVelocity = direction * projectileSpeed, - bProvidesVision = true, - iVisionRadius = radius, - iVisionTeamNumber = caster:GetTeamNumber() - } - ) -end -function meepo_earthbind_ts_example.prototype.OnProjectileHit(self, _target, location) - local caster = self:GetCaster() - local duration = self:GetSpecialValueFor("duration") - local radius = self:GetSpecialValueFor("radius") - local units = FindUnitsInRadius( - caster:GetTeamNumber(), - location, - nil, - radius, - DOTA_UNIT_TARGET_TEAM_ENEMY, - bit.bor(DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_HERO), - DOTA_UNIT_TARGET_FLAG_NONE, - 0, - false - ) - for ____, unit in ipairs(units) do - unit:AddNewModifier(caster, self, "modifier_meepo_earthbind", {duration = duration}) - unit:EmitSound("Hero_Meepo.Earthbind.Target") - end - ParticleManager:DestroyParticle(self.particle, false) - ParticleManager:ReleaseParticleIndex(self.particle) - return true -end -meepo_earthbind_ts_example = __TS__Decorate( - { - registerAbility(nil) - }, - meepo_earthbind_ts_example -) -return ____exports From 14e2eee02012cc4718c621dc838fc9f95ec54e1d Mon Sep 17 00:00:00 2001 From: Perryvw Date: Thu, 1 Jul 2021 22:59:17 +0200 Subject: [PATCH 5/7] Upgrade timers to binheap version --- src/vscripts/lib/timers.lua | 474 +++++++++++++++++++----------------- 1 file changed, 256 insertions(+), 218 deletions(-) diff --git a/src/vscripts/lib/timers.lua b/src/vscripts/lib/timers.lua index 992e759..442a4b8 100644 --- a/src/vscripts/lib/timers.lua +++ b/src/vscripts/lib/timers.lua @@ -1,248 +1,286 @@ -TIMERS_VERSION = "1.02" +TIMERS_VERSION = "1.06" --[[ - -- A timer running every second that starts immediately on the next frame, respects pauses - Timers:CreateTimer(function() - print ("Hello. I'm running immediately and then every second thereafter.") - return 1.0 - end - ) - - -- A timer running every second that starts 5 seconds in the future, respects pauses - Timers:CreateTimer(5, function() - print ("Hello. I'm running 5 seconds after you called me and then every second thereafter.") - return 1.0 - end - ) - - -- 10 second delayed, run once using gametime (respect pauses) - Timers:CreateTimer({ - endTime = 10, -- when this timer should first execute, you can omit this if you want it to run first on the next frame - callback = function() - print ("Hello. I'm running 10 seconds after when I was started.") - end - }) - - -- 10 second delayed, run once regardless of pauses - Timers:CreateTimer({ - useGameTime = false, - endTime = 10, -- when this timer should first execute, you can omit this if you want it to run first on the next frame - callback = function() - print ("Hello. I'm running 10 seconds after I was started even if someone paused the game.") - end - }) - - - -- A timer running every second that starts after 2 minutes regardless of pauses - Timers:CreateTimer("uniqueTimerString3", { - useGameTime = false, - endTime = 120, - callback = function() - print ("Hello. I'm running after 2 minutes and then every second thereafter.") - return 1 - end - }) - - - -- A timer using the old style to repeat every second starting 5 seconds ahead - Timers:CreateTimer("uniqueTimerString3", { - useOldStyle = true, - endTime = GameRules:GetGameTime() + 5, - callback = function() - print ("Hello. I'm running after 5 seconds and then every second thereafter.") - return GameRules:GetGameTime() + 1 - end - }) + 1.06 modified by Celireor (now uses binary heap priority queue instead of iteration to determine timer of shortest duration) + + DO NOT MODIFY A REALTIME TIMER TO USE GAMETIME OR VICE VERSA MIDWAY WITHOUT FIRST REMOVING AND RE-ADDING THE TIMER + + -- A timer running every second that starts immediately on the next frame, respects pauses + Timers:CreateTimer(function() + print ("Hello. I'm running immediately and then every second thereafter.") + return 1.0 + end + ) + + -- The same timer as above with a shorthand call + Timers(function() + print ("Hello. I'm running immediately and then every second thereafter.") + return 1.0 + end) + + + -- A timer which calls a function with a table context + Timers:CreateTimer(GameMode.someFunction, GameMode) + + -- A timer running every second that starts 5 seconds in the future, respects pauses + Timers:CreateTimer(5, function() + print ("Hello. I'm running 5 seconds after you called me and then every second thereafter.") + return 1.0 + end + ) + + -- 10 second delayed, run once using gametime (respect pauses) + Timers:CreateTimer({ + endTime = 10, -- when this timer should first execute, you can omit this if you want it to run first on the next frame + callback = function() + print ("Hello. I'm running 10 seconds after when I was started.") + end + }) + + -- 10 second delayed, run once regardless of pauses + Timers:CreateTimer({ + useGameTime = false, + endTime = 10, -- when this timer should first execute, you can omit this if you want it to run first on the next frame + callback = function() + print ("Hello. I'm running 10 seconds after I was started even if someone paused the game.") + end + }) + + + -- A timer running every second that starts after 2 minutes regardless of pauses + Timers:CreateTimer("uniqueTimerString3", { + useGameTime = false, + endTime = 120, + callback = function() + print ("Hello. I'm running after 2 minutes and then every second thereafter.") + return 1 + end + }) ]] -TIMERS_THINK = 0.01 - -if Timers == nil then - print ( '[Timers] creating Timers' ) - Timers = {} - Timers.__index = Timers +-- Binary Heap implementation copy-pasted from https://gist.github.com/starwing/1757443a1bd295653c39 +-- BinaryHeap[1] always points to the element with the lowest "key" variable +-- API +-- BinaryHeap(key) - Creates a new BinaryHeap with key. The key is the name of the integer variable used to sort objects. +-- BinaryHeap:Insert - Inserts an object into BinaryHeap +-- BinaryHeap:Remove - Removes an object from BinaryHeap + +BinaryHeap = BinaryHeap or {} +BinaryHeap.__index = BinaryHeap + +function BinaryHeap:Insert(item) + local index = #self + 1 + local key = self.key + item.index = index + self[index] = item + while index > 1 do + local parent = math.floor(index/2) + if self[parent][key] <= item[key] then + break + end + self[index], self[parent] = self[parent], self[index] + self[index].index = index + self[parent].index = parent + index = parent + end + return item end -function Timers:new( o ) - o = o or {} - setmetatable( o, Timers ) - return o +function BinaryHeap:Remove(item) + local index = item.index + if self[index] ~= item then return end + local key = self.key + local heap_size = #self + if index == heap_size then + self[heap_size] = nil + return + end + self[index] = self[heap_size] + self[index].index = index + self[heap_size] = nil + while true do + local left = index*2 + local right = left + 1 + if not self[left] then break end + local newindex = right + if self[index][key] >= self[left][key] then + if not self[right] or self[left][key] < self[right][key] then + newindex = left + end + elseif not self[right] or self[index][key] <= self[right][key] then + break + end + self[index], self[newindex] = self[newindex], self[index] + self[index].index = index + self[newindex].index = newindex + index = newindex + end end -function Timers:_xpcall (f, ...) - print(f) - print({...}) - PrintTable({...}) - local result = xpcall (function () return f(unpack(arg)) end, - function (msg) - -- build the error message - return msg..'\n'..debug.traceback()..'\n' - end) - - print(result) - PrintTable(result) - if not result[1] then - -- throw an error - end - -- remove status code - table.remove (result, 1) - return unpack (result) +setmetatable(BinaryHeap, {__call = function(self, key) return setmetatable({key=key}, self) end}) + + +TIMERS_THINK = 0.01 + +if Timers == nil then + print ( '[Timers] creating Timers' ) + Timers = {} + setmetatable(Timers, { + __call = function(t, ...) + return t:CreateTimer(...) + end + }) + --Timers.__index = Timers end function Timers:start() - Timers = self - self.timers = {} + self.started = true + Timers = self + self:InitializeTimers() + self.nextTickCallbacks = {} - local ent = Entities:CreateByClassname("info_target") -- Entities:FindByClassname(nil, 'CWorld') - ent:SetThink("Think", self, "timers", TIMERS_THINK) + local ent = SpawnEntityFromTableSynchronous("info_target", {targetname="timers_lua_thinker"}) + ent:SetThink("Think", self, "timers", TIMERS_THINK) end function Timers:Think() - if GameRules:State_Get() >= DOTA_GAMERULES_STATE_POST_GAME then - return - end - - -- Track game time, since the dt passed in to think is actually wall-clock time not simulation time. - local now = GameRules:GetGameTime() - - -- Process timers - for k,v in pairs(Timers.timers) do - local bUseGameTime = true - if v.useGameTime ~= nil and v.useGameTime == false then - bUseGameTime = false - end - local bOldStyle = false - if v.useOldStyle ~= nil and v.useOldStyle == true then - bOldStyle = true - end - - local now = GameRules:GetGameTime() - if not bUseGameTime then - now = Time() - end - - if v.endTime == nil then - v.endTime = now - end - -- Check if the timer has finished - if now >= v.endTime then - -- Remove from timers list - Timers.timers[k] = nil - - -- Run the callback - local status, nextCall - if v.context then - status, nextCall = xpcall(function() return v.callback(v.context, v) end, function (msg) - return msg..'\n'..debug.traceback()..'\n' - end) - else - status, nextCall = xpcall(function() return v.callback(v) end, function (msg) - return msg..'\n'..debug.traceback()..'\n' - end) - end - - -- Make sure it worked - if status then - -- Check if it needs to loop - if nextCall then - -- Change its end time - - if bOldStyle then - v.endTime = v.endTime + nextCall - now - else - v.endTime = v.endTime + nextCall - end - - Timers.timers[k] = v - end - - -- Update timer data - --self:UpdateTimerData() - else - -- Nope, handle the error - Timers:HandleEventError('Timer', k, nextCall) - end - end - end - - return TIMERS_THINK + local nextTickCallbacks = table.merge({}, Timers.nextTickCallbacks) + Timers.nextTickCallbacks = {} + for _, cb in ipairs(nextTickCallbacks) do + local status, result = xpcall(cb, debug.traceback) + if not status then + Timers:HandleEventError(result) + end + end + if GameRules:State_Get() >= DOTA_GAMERULES_STATE_POST_GAME then + return + end + + -- Track game time, since the dt passed in to think is actually wall-clock time not simulation time. + local now = GameRules:GetGameTime() + + -- Process timers + self:ExecuteTimers(self.realTimeHeap, Time()) + self:ExecuteTimers(self.gameTimeHeap, GameRules:GetGameTime()) + + return TIMERS_THINK end -function Timers:HandleEventError(name, event, err) - print(err) - - -- Ensure we have data - name = tostring(name or 'unknown') - event = tostring(event or 'unknown') - err = tostring(err or 'unknown') +function Timers:ExecuteTimers(timerList, now) + --Empty timer, ignore + if not timerList[1] then return end + + --Timers are alr. sorted by end time upon insertion + local currentTimer = timerList[1] + + currentTimer.endTime = currentTimer.endTime or now + --Check if timer has finished + if now >= currentTimer.endTime then + -- Remove from timers list + timerList:Remove(currentTimer) + Timers.runningTimer = k + Timers.removeSelf = false + + -- Run the callback + local status, timerResult + if currentTimer.context then + status, timerResult = xpcall(function() return currentTimer.callback(currentTimer.context, currentTimer) end, debug.traceback) + else + status, timerResult = xpcall(function() return currentTimer.callback(currentTimer) end, debug.traceback) + end + + Timers.runningTimer = nil + + -- Make sure it worked + if status then + -- Check if it needs to loop + if timerResult and not Timers.removeSelf then + -- Change its end time + + currentTimer.endTime = currentTimer.endTime + timerResult + + timerList:Insert(currentTimer) + end + + -- Update timer data + --self:UpdateTimerData() + else + -- Nope, handle the error + Timers:HandleEventError(timerResult) + end + --run again! + self:ExecuteTimers(timerList, now) + end +end - -- Tell everyone there was an error - --Say(nil, name .. ' threw an error on event '..event, false) - --Say(nil, err, false) +function Timers:HandleEventError(err) + if IsInToolsMode() then + print(err) + else + StatsClient:HandleError(err) + end +end - -- Prevent loop arounds - if not self.errorHandled then - -- Store that we handled an error - self.errorHandled = true - end +function Timers:CreateTimer(arg1, arg2, context) + local timer + if type(arg1) == "function" then + if arg2 ~= nil then + context = arg2 + end + timer = {callback = arg1} + elseif type(arg1) == "table" then + timer = arg1 + elseif type(arg1) == "number" then + timer = {endTime = arg1, callback = arg2} + end + if not timer.callback then + print("Invalid timer created") + return + end + + local now = GameRules:GetGameTime() + local timerHeap = self.gameTimeHeap + if timer.useGameTime ~= nil and timer.useGameTime == false then + now = Time() + timerHeap = self.realTimeHeap + end + + if timer.endTime == nil then + timer.endTime = now + else + timer.endTime = now + timer.endTime + end + + timer.context = context + + timerHeap:Insert(timer) + + return timer end -function Timers:CreateTimer(name, args, context) - if type(name) == "function" then - if args ~= nil then - context = args - end - args = {callback = name} - name = DoUniqueString("timer") - elseif type(name) == "table" then - args = name - name = DoUniqueString("timer") - elseif type(name) == "number" then - args = {endTime = name, callback = args} - name = DoUniqueString("timer") - end - if not args.callback then - print("Invalid timer created: "..name) - return - end - - - local now = GameRules:GetGameTime() - if args.useGameTime ~= nil and args.useGameTime == false then - now = Time() - end - - if args.endTime == nil then - args.endTime = now - elseif args.useOldStyle == nil or args.useOldStyle == false then - args.endTime = now + args.endTime - end - - args.context = context - - Timers.timers[name] = args - - return name +function Timers:NextTick(callback) + table.insert(Timers.nextTickCallbacks, callback) end function Timers:RemoveTimer(name) - Timers.timers[name] = nil + local timerHeap = self.gameTimeHeap + if name.useGameTime ~= nil and name.useGameTime == false then + timerHeap = self.realTimeHeap + end + + timerHeap:Remove(name) + if Timers.runningTimer == name then + Timers.removeSelf = true + end end -function Timers:RemoveTimers(killAll) - local timers = {} - - if not killAll then - for k,v in pairs(Timers.timers) do - if v.persist then - timers[k] = v - end - end - end - - Timers.timers = timers +function Timers:InitializeTimers() + self.realTimeHeap = BinaryHeap("endTime") + self.gameTimeHeap = BinaryHeap("endTime") end -if not Timers.timers then Timers:start() end +if not Timers.started then Timers:start() end + +GameRules.Timers = Timers \ No newline at end of file From 2701c9c672e19a8a3525dc55ede874ec7b34d689 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Fri, 2 Jul 2021 23:01:47 +0200 Subject: [PATCH 6/7] Updated examples, switched to normalized enums, fixed problem in timer --- .vscode/settings.json | 3 + README.md | 6 +- content/panorama/images/custom_game/tstl.png | Bin 0 -> 69910 bytes content/panorama/layout/custom_game/hud.xml | 18 +++++- content/panorama/styles/custom_game/hud.css | 54 ++++++++++++++++++ src/common/events.d.ts | 10 +++- src/panorama/hud.ts | 17 ++++-- src/panorama/manifest.ts | 11 +--- src/vscripts/GameMode.ts | 35 +++++++++--- .../heroes/meepo/earthbind_ts_example.ts | 14 ++--- src/vscripts/lib/dota_ts_adapter.ts | 19 ++++-- src/vscripts/lib/timers.lua | 7 +++ src/vscripts/modifiers/modifier_panic.ts | 6 +- src/vscripts/tsconfig.json | 2 +- 14 files changed, 159 insertions(+), 43 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 content/panorama/images/custom_game/tstl.png diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2b2aae8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "css.lint.unknownProperties": "ignore" +} \ No newline at end of file diff --git a/README.md b/README.md index ae15171..621bc75 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # ModDota template -A template for Dota 2 Custom Games built with modern technologies. It includes +A template for Dota 2 Custom Games built with modern technologies. + +[This tutorial](https://moddota.com/scripting/Typescript/typescript-introduction/) explains how to set up and use the template. + +The template includes: - [TypeScript for Panorama](https://moddota.com/panorama/introduction-to-panorama-ui-with-typescript) - [TypeScript for VScripts](https://typescripttolua.github.io/) diff --git a/content/panorama/images/custom_game/tstl.png b/content/panorama/images/custom_game/tstl.png new file mode 100644 index 0000000000000000000000000000000000000000..a60542a975936e88c8709cfc7b6ed64161650630 GIT binary patch literal 69910 zcmbTeXH=6x*EV`13IYNu0@6_wRC@0qO(_Bz=~a3s^bSE#P?2JxiGWHCE%e?5DM}Lw zU3w3l5IW>ceBS3h=f}6cAIG&^gm}-~Gkf;za$S4-`mxqSN^(YW2!bfpRFz>6M2rQ0 zcP^5EZv?)?5`HjvsTg=Yh1+`hT6ow%3f6E-8+J7p3p*Q_jfJ(p`%fDgFv8P8$H2=# z^O2Ml+=b7A@C=`yiyIgXK{E1wZWdNfHeT$OHg*oKvNt#Dnr^Z?Sj*nj7u6KdbW^gi zcTf%Ruz4DwrDGM~WF={RQ(lf;#!m`N;9}!t!S3hc?CL4yCwueXyi(vZ;cNbz?EfC| za+1CIpG6sHK4w>fd)Tmx@`>?U35p1?i%aqeiV28_3i7ZE2?&bw3yAUyi17*tN(o3v z2??_Q&!3y%Z64OPQZVHQ|MM>Jo9s<{FE2MKetutHUp`-9KDdV+zo4X~B)@zsf@uH&2$|@2MM1)j?l?4S=Bo!5f1QdkC#KiN~D=D$x z(X_B~a3%bHmvBb^*|M^Yhl7ue^#c#M3;RDIOF8@>79eCTAz~wFA;2qPBWT4dB4KOE zDz{y&=Y->-l=5?WNST96GDXFHcpyM~THbELRxO>#G?ZvNdtT$Tswp88Nn&PIA^&Jfb<;O(s zb3?1p=jDctuFLgzn3OMn{2E3cR#532*N{_C`Mcq)FO~$K`!;Rw?dN%HNCRv5@29^$ zn7^Oezs0rP+0``wL7{@y*4EedcXzwDx3|^4GC|O^QN05Z1gRt@CmZs}%F6Qa^75_) zbgiv9%ag6$*ug0n8X7(?y_A5bPMD&8Jf#$js`Xy&ty?Xs3#jR8I^CLSEz~P85Nzq| z;~$-x!Ux^I-+aiO@LK<{0h^YVHu{zkf-VA!G@`4e6$4jSS5J}k7ug`iTKa?*C2?!n;7~MR%;G}!LsJ(=hYqW>Mp#&qYA=mrJ^?S(l>V)0({@a z$J6R04S$wLG)CkxY3bEB41i^XfZK6kpsLz=rQ#!Aze$d#Nv`PZQ43y;nwnZp`r^Nt zpV2M#rbZdmdaVRr9pcVyF6ymJ;bpFSpMkp7+1tC9=xy+Hu%fYjU_e+S?39dlS4l~U z`sK@)>mSCXjyj*UIa5vH4!r}|POKH(k*ZJv))0FtT1EJRr1;7vhJ8mor_ z9;brjL1F`LtDoNRHo|;F-#~`LNUcw^`-5A6=Xh|@?ResLcfRn%_`@FoXeQe9B|dpn zUGxP_>5d0P&Gug#N_j9Eq8}aEb(N^MW_&t>65$v|+txl$b?Uw2%u02acVnD4@B&Uz z0@P6G2jb6xKvl6ieW{7&*|n{Gt6H&tuv=T5WMpKLKj?_AQQ9r6U;S&WDjNT$zcn!T zdq}ri!>6-#J8g7pa2Q$&Ie0#Zu&NjSsUelpgPI{j_kCCL1Kyg3i;G#8sz7cYeyDp< zdoY(Q*Mz+Jc04mKTgj<`4}TofIjfIcfOXya%Vu3jvq%Jylm#mnEUgUWa5U7{ml`2z z5s6GM4}&+*cBhj&<`#llJ}NJO02|LlqjrP_nYLlp84D zvT&uHz0^E4CEwrC!KHFo!)K5@-1ubn)4iZEsQ^X=qS9 z^^F!kkdXMo@~n>J;tmzr7DjV~57w{?u{qE9p&x|y56I~tk4n6EHxQMs1qh$^8hT@=eUhVcZ$kx`j_(S{m5Z`fD-;KZi599CYhVYu!?ou(x*Q{kh zN*DaRNe{=fj?}yp$-i*pzf8_AXXt<0T{AZ9eQ$L0yIlC*wX}z3W~`Y_Ir3n`Ge$4OF(O`L^ zV0vn#c1V4_V0~v&mVg@BEuSu^zSX_3nqX3sH1iY z{91+Ale09lhJXpr;b8~ii7bBMz=KI~#M#MD%rK_s3pwq*0+dk&#zS|ZD=`*@4^R}# zAnk22JZ$PN860>*qCxq4hwTdKII`nScig92R!$u2#VmGZeUd_U&C);`L3b_7{G|kKeB*WoDcnq*`%B zl4cq8zq`r(btQ3D13uifBaBRznQEMt5Qd+nQfWd3ER(Xj(+R&PjV|~iQw9yS1B3BJ zrgd^6({+Ezyci#p&a{zOB!)Mj7i@HKf#D>Sj!egsd%_z#aQ|=4EsIZcv_yG2hx52K z?&%lR!!87R8#cZ;j=s84cKu2*DR+8&;$eBM<@yuk4cKi4N-A$X$8YHC44w}iNG;L? zsGDJEJLmoN@nMV2$p#i`W(hYZM@PrI4^K@61qEr+VyO|Yiir2BR?MTrfXNLuTXZyg z>EBTM?i=~uV7|V&QO1MlEOGpP^QITLuPKZD7)+U6#Ytc5u>kpJ^@b}*xvMxfhCt*0 zdD%y?`Vn?7{Ml)hT7|y%*~uZM_o%KbfwyjZj;6oA;6n3RBh%w=t>q2_A;rV{6qhc^ zaLk<1lkImo4iy|KVX1xDzICy8ntPuW6h$!H_0F-4Ov(PHs$*cvE~?l*LpP?7#MMpg z=7jOwhZUG_I7Ob4^D4h{~t-|m!AvdUDixX{F+?0AXxaHvmpjZHyi(hsp9ekjyI{=pbk zIP$)I`}U-E60`%M;$p z(Molkr;_*d^4eYEzx9!&WsG=jX}BYAkVV+Gn~LLv2jd=c51kM5{kdc%dfuO=J9cn% zUjYVJkGImStDu3QRD}hKKMi!dlU)bAtSm(1|+sZDlWrl=h z`Cw*(E6BHY@J++O*LHn6$87hP7s(NFiW}DV@bsh%oNWVc}(C6wthvLYOV_$CX`f0bo3&Mq+2C5*6LStP?kmu!uEQ8%W7_b1Nl zpG*(}IKs_;T;s724=x8@?d8F8zj0rg9<(%m;-NGBnug1gidAMrFRzDin07Wc7Z|tg zbhRt5OL^HlkC#OHv5F{6zQWG9mOkG1K{kJ?IPXoY?VWIDT!vSw>1Ks4J(%!P??Rl9 zYE5@p-P}yS^F28J9&T-AYip|~kyYDU=gh6**VepWvo2#p#a&%GZc*O*c^gdQ4=-&= zc`iOmZH=U^`b!8CaelwhXf*7>fg$m^bG-?CrU7-?+sgsh@BN@pY~z9vuS8_!io4Kt z9;M&x>bQ;OO2M)HO7_QY{;)qc9TAZ@mE<=2QnB z8o|^U;tjiTC&5WadH3btUsJNiR4aNrJ1@Go^(C@b&3(&;A%R5Y8=Qs*rk@U!HYYaYi9dAP5T{>b|oMsCi8Vwa1`xen5V&czn zyzpZ)eETs#;a60QftfCHL5OcscTQ0;I3fAW9fX2-s%TEg)qeChc?k`d77v=lq{yV^ zJndgyWZ72qE`D?z@2^FI|#ga^Clh5 zyhSMclmeY_rurXBlkk8KcHwX118@Dt+eaWS6Yhjn2DlT9EWxwbXeMzvzvyDb%35Hd zQ3YLI*(UBm+tP1AsM28DelVcUcXOlGX}HK0qjf06#e<%!J;JRHulF*jQLNiCd+>I_)oH}M|0eXge3R_m{VDh1BGU<* zQAu9j!XKhb4Da4~K5#|_(3^5}*FB3dHl((qG;q2TRtPhlzmoa*SY!jr zlgam*@MnR2*rmSV>Yctou(+ze&*)LFHv*gUhc0h<8b93YU<_yZj3y2~XTeHHN}j3b zM?Tbf`aJ}A{a~$Bslnw@yL8H7csXfUec58Auj64~EVX6y_ZajC1RA^CbfY^Q1nSTu z-i?~hEl&KT`x~vxi>VSQmU~epjN-N7R=G@bvNy-SpZ7g{E^BB9b-S+S_cxm3sFsiC zragL=NuUu@1y(JpoxV;>83k6V%=RI_;DVY9z1=B@j)yeQ44dTi3OB|ooFd`lmFpY8 zwZy%8^-2Z45i=cA1wkj#@A`gq1^ImBF|*?R{@N_j#qvC1Bh9ZXV_)H5;tuMns@DSu zYNanHKHA#KvHz1Ry)EX z`t|-2s}9#xVY^=a^H%M{d&pVOw?Y;QVY}8lVWg8pB3$Rgx2y-!qKbgAHK5NS# zK75$dj~nLX;?f@jCZW*l3RJL=6M63eo3u}XgO5*TQ?0tw$-#Dt8M4CeE3>u~a*muu zCu_B>K2)2;T77$MdblFjGfI@u{#_b8GcWi(sOd`_5faxFA|Jca<8R;7U}m*tPdjD zLi{ASxH|R&%$Jsy60gzHiq_b4C2SfkT(`&sAX58OEIVp`aWMc0_)t#j5;PPS7bh_PK*8)ute|cOljH{T zsD1_uHN+G^X-!UYWOe=IE^Tr{kUlD^z6L-wazPNUnb=9ntt8nANPMDz*Vn z<&C_Z{_Z@49p5v0yB|Tx6fv^HNPB|o8-#~6oP9OyiqLzN^?{0VN7P2p_*bH9^ECQ; zYJRLCxS4mZ7RWB)C>yaWZ&HS}l7$v>fJOORB)K(lSZ3EZB;~dIm`REnTC4M3CGBC zOEdJVOCWE%-Re-`{>k1rypMh%+o$p(u;VN&ET>H=uC*&q<;1~TvK0@$hm($vtscjN z*zhTMeku>dC-ngtvz`+b{12*sm@OhPzv^sY;=-_*h>&?>_Wh z{5ZU+q1b({{TSHRaF)~k!{g)h#MeVF+M}7Y9Zy%zCJKn4;+Xz^F1f_Hp759x>9DXc zEm8R7iIR7Penyp8R;`lxB@exjP@Zf7b>RKgC4j3A*`+8yGGj%(e$Wz*;|FnQd)p z*%Nj6V{M|NgQgeUbd~M^XwB-OdB>XFP=O&94PdH!5=Wc&%<2!`fsp!4T1x5&8v%EH z#L;c;8^U-yYAW*_LzVy$j?;{Lv`FmgqC!&SU8NuslfN7QI#Y-_J0>``DX#%deBqOf zcIu52!v|1kaBg7P$=UIy>=4Qve=yS;X)JlP`P2?$TyDP|;_SRuom(YteybaA6gBg+R?uA`Iipg_?KIAIdLfoBy(8&$z-VVpxe% zc;SC}ai(%e_GwNF2yD&9LFU4a`)u~`%=E^M8<8pE@G}J7ZQQZ4oRY+tpMr_wgMmjmF0}?_Jg}6TCom{9RMo2wtOd`_JKx!Db-jx=OaqrkAg` zo&%J8)Sn1iQw%N&0g+ULKFaL0n-IV<1G$irvOTq18I0axtS`CIN^W7cn2}a$z&H4* zpM=8T?%;z;@yI@1GAwX2u9c09>&GiO=^G_Wqm4&% zf3I{IkFf{#SmdGH%eI#ajqiCXH7*F}J-dM^+QV7bNzZmR{rDh9T+D?t*$4Je5uv3z zaVOUwySOQTIR9bLT=kChM|Nt+?d4GV#?+$hV8oSz#_?<^B^x!?gTnC-xxcijW5pwlaZzk@*Fl^>qS0%V8?bC=f^V>;hieO6kX`s936 zt?3jAb41sgW=z-G3^@T+jAjtF?HDbybDCIo(n#Q`(FnFJ4mmw4`;1FgfFN?GIStAv z_*nVMnQp#bgWvXSTj}0WV?zM0cBu$8z`Qderf*`&@A-SSbmX>f!V_W&>ASkdmEWx& z26^^^Sgej<|A5MDr>_QU8W@b6fm4$|=;VkMS>D%NycQ+{HM59>*lFyXYRJfktd(?N z%AJNc7Mh!z2UJeeW@cuLF@yQyq#s6TpC zu(yAC3V`QTjG~Sr6=$nthF;{*bgx(wGi?%ZR640hZ8bGA{_g$irL@4>zk;&vB^)7g z^r`W&zjxpPORX1)L`0_w`NwBn0})nW-EJ3Y(c_ga(wH55;yqnQyL7qWjewKw4vr)K z&NSsXQYcA*UpdE#zP(qM=DWd9Jf#18@bUR+CIjLZm^7(h8;_h%Vl$dc9_* zg$3XgHfV)$H$1FO>82av0YCT2o}N_%*~&flM`vW?t~de z$zul#<2T*s`)0bk)@&wMZbY3&w!ej@*~HY_I>j8Spx$HCZgQgdNySU;qc6oz{c@)O9d0oOOgTjw~KmpD5& z7r6kywD$J)7zAHYcPnrisX=;Xski>-h5Q^NDQYlO7yalW+`&5aRv0iAP-ytXZu*qA zTtnsjhgjJ?gY|!&XCS+0mGhWukE@edJhSpjU2u7XFxHlh_uuP^Z`q|L5BL4r{P^a} zYBS|}d>;3>ozIh|D*)aDGhS1HhsivCCYp6LGRJh~KVP*yEr*JKQXCDi5oi)1D(!P> z#)cvpG@+V|YTg8L1MDJxrX0HS@uvAd*cOEBWgwudu<1|Lf?obnsQfBLh~pusSM1TR za53oRx60Iq7qOu#&cUK%@L8ASKKPM-#A9)dwne@x^uOH64`+cHcX=Tv}F~jrsNm9 z7?`uY&7ZBE-l%%P9~Ve)ry;C*ci#X-f$06?=pdi%`xfS^Nu-yY9mV^;{tMGIQ^Y9$ z6dgP=0#wg_)l|sw;og-JGcPg-l6VRX0H)MllMAY|zdf#_38KiW?tLl|v3b63y)7>( zjDM&h+LrKxPN@w$}J*8N67tM;|a z4e;Fx`P1D2>3atQqF^`5!45>v^nHQs#VdEVF%{|gr7jC6hghzhtgQGuolbuM3ZKYh zxI;Dpr<>}^|WtM?k@Z{#h_Q5z67bzkM zWnSY=$4$*XXvqlgJmCQnmXn~qG})^0)xe{}!`t-SdTW{43xA%0J>N04Cz^hIkL}t) zhyfu_YHZ}r?E?fvM;6#~`S5-L2m$~0rb^5A@1CZ1tOx*o`_fwu31}tl112WJhLcWG zD2DTTs1y@^y=rO<=Ntqmv#$Wkl0~~=R>B9V;zaYcU@#J6XLR&M{;y5(?ZgL+%mqe7M6j-nRpf(p%xN%H{jmmn9tSbzc6g+LhBcqu+#Iq0sh*-rLf z8k4&@yxR7q0q46C4lb8G2EfR^~;${ zQAn6%>m)h1BE^psfi3p==IS91oZwH8H8%CIPW1so&^R?34y?tU-gcl%^p1*gq@N$S zIEkzA0z_9!0G}4;I@cb3Y!1L_hrpmFSeE+fWX8kT<1uXsI#l6b)o>!{{`1jetv$xX z5#rvV_X5-sBMJJ9$M}P}SbVbNfSh;@ffoaD#%SdFSViE=vTdLu>_$K!e&fs*tamcg zS@W*ThMXKQ*!86qy2Nhk=jk9*eJA~wn#Va=%~6Gm&Hy?Pc-vkMx#cZ~1-;`6KIbf6 z6ScRTsrEr{^ls-;8!(k@Le{DuG!$3~a|88@|DYlX)KAwic)X4eY+USBAfHgsWWE0Y z9slIiBlymXCG)SN9240L1cKi6zCK1l}dH0hN5Kv2P<62cSJ z)Pz3-DoDA0s{3nrY)n(+Is{!e3^szQdAglm_p9DGLPf#Pay}M0;x`U!?fUNoU zuhsEsQTJro#}Lk3yVK~{YqN2#BX%k&AwjS73(5&ZEL;RvRx*>qG_ZU#^xKGF>FfA& z=ed~$A{-gH@R0sCy6U?r*v_j@!NsecVP~==y-9*|(ud};tHCaSYOf*4U`6zJR>o!h z!6(wor50b(c5I0rKhp&Q+;NE$pdM*u8K9wWnA*)2B>3MoBlD*vmTjUS4Q2klGuLpu zeEt5H7lB2_=j7egXo2{27FJJhSg;m90WosAl>4mm9LK|$^7@^90LPbse0A%uruf9J z8bIRxy~x>%TR&Tcf1#$h`;Y=kBnOJ^ql3r2SgX+9-`EE*l8dUQCT~^~RZ~&gSAv8{XeYt8g4N%G~hLeh4W_KSOP+Z$&D+0J@g-3p`7@ zBDvZUq^a@c3iOYnoiA%bpFud5oY&{F=0BjI-kxMabwD-eOFPr*nrc+K%oc^gRY@5 z?aLcP&E6nnXrBMfd5!mT;!u8p&Vg=7VS>$W=tf?O+M&HZqFz%?dVemAbqyaUb=%v> zJgxp0N}J}arvlGDk^)O>AN6mJ_aKBF=YnQhkmL)rxI7-+l{e=4{YrK8sYg$X&Em<0 z6yBZXJwvEsiqTJy9Qr3mxo-zPl%r4gxL*W3RU7gfl9^Vg2k;l9crw^pi{i;s8~n|! zn3sL#L_2i4{Ju3;T;bmG;4I@RmlE~yaps=-dOxBN6Z&@P(U|?(>80VJ6G3$U4&9iX2(Kew6AVyURhWzvrDj5k2f7Zemhv!IVb)Gb zkmrMI-i2=dJ*E3TS$vhCfOh{D>zlJ>W=hwyTdPLBdEQsz4Wn@)YRhl>O(;t7GyqwV zXB{}MozL7P&w*y@^#@9dW&;Yx{)0)hI=>?ciFoXZ%nk_U?=gxXIv4V^fpxe8;~+bN z8c1BVF|NO#&s>Gz7-5oEh1{w=3FDfXZrA7c303{s;rDdq7)-Ld^7lo+2V{zwdga@t z=+YBi$Cv1Pqgs+tE1{>XUjd1F8IVD49E8*hI5mf7gp8knuK&4md3HjqKJlH3lWcue zd>;S8&rdJOawT zsu1|IJpz@H=P*OvPkVWOg0r4}K&dW3#~_dw-|rSmn=6iF#CRY63Bsw?+rTuk61v2K z--(UdHL2$MGO{jRtR9+KJ^e}`|9OQ3eWgd*M5{3Y?FB172CObt^z6(oRoXYi^0#HIwqeGz9h+CD>MDj_{jJBmu-gWn zp*`p&c0_;p>CMr-*gsbl8;Dwi+@tM!h>O0-IWn`4dvIE$5vP1Tl_`Ls!BP`V>itUA zbV>6fAG!jXdUJ!|#tL)=Ry>YEluhLdLYiFzUd38Q6ZurBy$nFUG&)P&sWHFwb@Flt z?aI%qQ#m^bU`L`G#kje-ADc$tvBe55W6|=ZuQ#4whVMhDqPHt| z2bH>-j%Ct=Nlla*{KZsJk4D7?=QbBLf;o<(=gK?wPItl#KvE&dl9 zb|(urJ-N~F1@*JD^NyTV`&*U#NcrETZ4U#f{-|7@k7AzYUL|VGku#htaStuY6(1a1 z#l3be!(c>k@5=m3*(4tYUc_k?r_w>~fA=n8-Cn~tDOW`Zby1b)54-qGsvm~{Ab!fl zZ~H7I;~`vDTU_B)m1OhqM-~1Cbn3FKfpDntV|Zl+dX;!IVa`TUKYq=4?+>e@tOYB` zz^Fv`H!NlCdOnE?b8+STPTYzB1zy;_J*}J+_8;^T&HC@!WMRGxQQRt{9T-YOg=C*D+kbveT5z9*NnfKU?i0uHmx1yhr;8d7q|a0l=my4Lf-opY z^7gpfC{r-VF=s^Zt4XK3zEiws+E>o0m;odvtrqcmsbdV|$4CwgG?xj+_Pwta0_z zwmgh7-yd(1Y543Kb{W59v)hohk?2^sE$IhUa`WTz(A=fkhIhO8GaDC$f)r1^3ld@a zo_>P&pvbQNsv4$Q5`(EoJE9iSew=QnG z->a7YLV9RfZ~^kk4y&&{=lXtaPVWzuRZsp~w}SVGx}!^Qkqlsdqf1Ze?2Q2d%zSm8 zp4Ui(@5YUpuPq_51n_8oG7z3)EH7RAJf@|ENKUy6&wL_)3;=UdWlr8=&Q#x%m9~~N zb9}RQ{NBDtKK25g?9-+&kO#EwgoS_@&8NzFwA43>ah~+j_1`5E2OLZ*oIH%PR=W^t zLzV4^5j4Fv5r^i2ZFZj7XOr)h@;~~*n?Q||SCefUBmG=jcx7?M1fc#a;M@Ia#9r6vD7F&eT3L!gG(+bg;>U1KD!tpscjAo(M~@6kV;^OSCS1bF zYh$)2edn8s@S464(#>)ZH*09foIJwJ@S8plSE0m z&-(>474k)Ol%Ft-y)5N~uA6*IK(yB5e}#C;uek{B|c^ zLEK_!t4Mp}#M-@zb++d9Wyp6Cn6>z|akQ+f-1|?w$77d_tT2yJEhilX7R?q3BRg#- zws4OaxmUSMohVb|gQZUWLL={*fD;n}{#IQ1ta2-)&Bx$D7oO*&6=q{74L!nSq9u6o z5&rcGn?DYdgk!^Z;G=h=#tL+sH+;H$oCYe}lKroZ*;QpXonZUU`(K_!3C|$!MlEHM zs2`RjKuMni$~-=zo2>ETh6(tDV654gWvwu-EX&NQ@Ep}>rL$tv!>>V;H&$!1G!P*l z*r{8Nb(QJ^%KuWp8p=;Jo)9&wqf-(wKgBS72c9;Ee6f|sOC?>=;lSPrs3t851%B}S z@!Zmudg?g^9Z%9NtDMI-vVsnG+%dMk{TWr9r4iE`-%sJ=yJ6V4x8h8A7k2xE(X?n>9Qf%@As<-@Fp7G~yI>hZLvAX#o< z@K3r?ze>Z1%huDD{n1gn2TkXV<-ZTkmQunOp-o1Im;TUIHJnG#ft)0N^rin53)Tx_ zIoi_`po;eNEjx8SsbBD)ELN^zZ47NR6A{sU1!U}v;M)}%dl~Sb9@%U7G6#(__utXy z8jK?0bUs=ar}@!YJ7wfpMZPj|$v~ECi&Hdzy!X~dv7mS+_~K5nW9V?zT^#4KER0`c z(31Du{da5qN;8)f8-kbMF>>v{`l=P_#LFr}ZyMhocUogPsDXj1AT9m)_)gmYCVh^zp;La$pV}G(V;7!2XNMD^Vl)d6U zC%l~oyZ2)=;S&KS&dDuWNY5H8-1~l{`G6O}Bf8udpSNJHOY~(6&6N1EF(6qWb~PZt z<>$=5Tvb3Jzl`GT?e zbLA%~zC^{QX^oV$jN;A0l7Xh5M7Jxu=>e6t7o!(imk{yd1#>O_>=^EPHnY)MK;)z; z@~F6>{KL!I`Z5NmYtSW|RCLq40anQPKnrnc&DoOljv$nesD}9fp$VO9O|X7jcY6=KdSz@ z_tmvp#3(PPz08BC*);d(stl5T!N7V{vLkxYuUAH^tFQFIT#s8bopI(jYiVBCb*^rj zMVCrv`5a!1`z4t_eJCX-o95hn4-wDyN*F_9N))$&TB?0}!PedjQ(Qqp`7( z9zC}t6%=C0%fj`8DJbyq_fvPIz6fg;F=YFdb$vG*o#Rpe%eryty#PwCw(jY@u$ha0 z<7&U<7RjTGskpo)F1P($$j|8trG~>i|kqmoBwuHlYn3haRLlho+ z>QQFfg#olB$)uI=jw#ynI|t5$quxf>#Qn zTkiskR0l&># zQL$N$B@qO=n-`bKeB78rOC?X|o6u${MRd}-UP^6b*#}o8yy7xE!X2b=n;H{88A%|M zjv!wApX1Z=maiQPFH4sdxkBZ6-=Avf2O^%rkH|sVNFH|M~& zUOZlMY-acA1ks8l=o0fD(qpxDxXJ4aG_oqsT_f(l^8qA=>8+uq!T#p8k|6Qhm7 zsi7Bm-v$3%k2#^J?gp!3@;~>*)lpor3*XIh%ngNRh$fDGnPP`u6YngGvVe_ehB;i| znGYkdiNe@LY9*29&}NaoVby!|pez^_Wj$%u%;02%8a zIoDl(7=A^Qy}53{&xJpV3^sTj@DIe2?4rss{qp^96xrtTe_3XDMd*D zNZKOpGCba$@n#f4gHrGK_5)(5ITW2~7<{s7C~3xd+hfT5 z!9Ok8#0chXxaIe@>syO}4v^+2$gdtJW{TY8-hc5hI^vb{kL*j==t(58{PTcyx-qFs z>gla->|Hf|<1Gmk%Jc*-$Dc5l6u@!BAg2K@>&P;4Jlwwpbz7q3DpUW->yZe^zJ9nP z)9B?@@<=$&LDTJfY7<-?dxPaI+5Y#f3=7_qXEF&q*Fox)vW);bhOm>x?Pcz$cd$MU ze-QjaoN*kuG^xg)C2}8puK8&R-0wnIvAOvY+l>4EG~oPqH++W-3b>+>u;>*MsQCrj zu!hLv@01odxs3RMDxLNpvg#6T>lEM7>x*C$3X}b$j<|w@85mscP?#`()?`6U;@o6c z>51whR8u}5kv4GbBU~8DWQ2^hNSq-Y$Tdx%zaA&?!#jQLRT-c$iQ>?(OI`!x&JcftzyxYlQ5)knI(A8Tii+6s(7D1>AyfK+JSD3(KtFAIY2|;I>t}(;d(Lu(Fb@+Z?Kby|^7iwcTbd zXqG2G0eFlETUFUxp0A_IXFqvHiCb@zr-nYSzdc?s(|*ftapJy@k5BVk24T9a%*>Ck zqoM-6Ky~XXf!q7T5LQn_Rf)5?@({@DOY_E)4z1JcZ}R&p%;MWRyv~tqms|;#v6$2h z!k_N5rAc|`%`D*M{dcUb$adXHshF$dHzdS=(9Gkn&g+JnbL&RB0y>h$d(9+=O~c(= zTP;|fY1%R!W7Qv5-90%i2A>R?BG7A~dQ1kSjpG$g`|6-NeyTxh`rB(C@|^?EN65Ot zeBUm%Z{WzUjoQJqJ*tlLdnILdB|GN4i21hOp$AG`7{vr=7Ae>M)_95zF3k%I3B57j zT>X}jA>VYmH_nCyEmQ2>G_x#0j|(}YP??rMM1 zbL$7a=#PBy(+3WRo3F5c^#R?-bZaspKy#6!rhJag^Qx#gYmU3()tIl9H4kmZnKt?4 z1(?E!TqbHOnhF)n)BxZ4G(96H{Jg(3tB8{Ic3}eE?>dNHpqQ+f3QP|cjzf)BIrB?{wPo6w6Cotz3 zQ7vH+YN z6b&*4Zv3seXl$-uYW>rcfTV>{(eZW8@!yzK!iXL&1R-a=&O=ZltYh{9W~4wqn-VV{ zDa{k5=SZ$8{#Z3C%V^c=C-0R-O2yy@X2E)sL&JxF=G+IAMjZ;Zd7;+c_!UG8pDlkIkFBvb%;ly>zrN^t{Z$xD{8NxdGz){0nrnmSwCi3` zjvSY~Mav66oP?CI`8X&YZUp@UDZ#0s55iGb=eJ|szooIET)p(u3EFNjj)PjT%XrDe zl{--@u-hvnMI28YCy)>5Jt78j3#usz}*C}`%HK&{7NfC4N25|COx>&Yjl z!}M5CI{-a%#+9G+^^51w;**w6*`ux_{c4{(J+|T)<55TxfI|fGbN^s_PK>MDW8s&} z@>s>HfRw=&du%?*VPolmVcsQR2AX+ut@iqk1ep=3n=J2DD3DcI9?KWK9C_+9+9O7b z9A|_A>BkxZeTS2U?ea8ITx!3*PER-g1IT47jgNOqsvwJK7HNeu$2rC5ns+diql`S@ z`Ft}ux8ds1aWzylM~Gf;qb|!gI=ZzM4j9TbqSELBkvVx#q{l}HH%kfGq0TGz_+bKZ zNq%*nY}-T+5gne=#A9oJC?0hK>~lH|EojnkS6)fJat?*PPdmw|rkTZ*kTn#nR7T?8 zQXe%BkV+nt>a8+RzyGJQQH+z*RuS--O<7UmR6Es=_A4lb60$96Y^1W0Y_5evU0+k);ZegF*>nd zyLx-6iWj^mCns0JNvT~KSy>y~+M$PWk&L7mTY)LH_%Fb2B@Ia;H-y-ZqV-HXwK69U zt^1N4xhGpz=acx3` zd!3yVgVR_Tnfd_c>8*Z1R|2C}Oa02>s$JL`IVe?O6vx#qJ-V~d^^jnf-xZ?r^-+`h zB_$GDQsYY#-W~kr1f#2)&(ab*6vB4C$v!FRCNEN;onn6MtoLyK+4>_q|fe z*OBk-*v9FW-0*=JaC(bBKXBo1o>s0-G%< zJtru?;ig(cXU3|l1UwJ9Ais!;ZU3p2@${={YHol^d&NTfM%y#*u!4eu=FP@q*Dzqk z=0Rnyi@im7{pgi>l>hym!JYJcb%JRfOVjr}7wj79)OUAZ9hq}ZS*=Ic)2-H0!vCoD zHEi__dwMJoTd~_r%Qoo!q#ix0)hje=@LVmdI?I3cJ?1hTR2Mv=n^LXF0Vi-jXCrk$ zY4I_=gUa*8rx>e}j7;=Ej?D6_E~WZf;nRx@O(~#ZYZ3|a`~jQxw+s|OJqSi|5`jN0 zBmkOZ*zF{m+$1DQ;X1+JGS^lCVk}TTsaX2Ytt~&cPEbzJIsx1OA2H zXYBCud=DZN$rqIc8zi{6>gU9;b(@fibiEOoCGwALrZA;Uqu*ERU|&^a8=Ml}_UM52 zF<)e%an)YBIDAqB7++A5S>rq5tq+&p@VdCYD3_Znyp4GqBuOX^B3DgK-t1iHQu!1< z_^$0SGB8Vh;GN z&1dC2_RR1v-=Ky$Nq@Y2$EYQskUXMn>P^4ge&Me)29WjUL+ld5VRaR42XfB^ zmg1*3exz*P?^mP4icB7YY(XWb_v7BjnvwnTmLjYu!mzx5r*9~~##l3|;=U}YS@d0; z4vJQ&@n8F{k_OnzS9`fCpNH*7L(7I&bCZHWx=(#jGkfzUpZMeL+t`MCrMW25;|DO&%sj`UI^#13(CvMkDYi-!lJYiIsereo9vO7F zkjPC1W-H@G-F@=yN18=zgv#N;L8g>j&iz-uc$T_=`Jag}GlCE#3}Vpi$z_@uv=PY^ zBr9JVKYb)WvDt;)kk>&8@ZvE&UE&iI$KzF!DE`5oWg#418?EtjPktAsa2iU!Ekpua zma~|=O2Fp+71RQNY$rfVtCx~LTrY80o&fON(<`VR`pzV;FxlWcN&Rb9m|#lXJiZ`Q z7hUlgw2;N55WwQu}ATX z^a6)x(%I4Rl$&NYKb`_N@T;p-N06=1D=t<~sMDBxV>9dJL7>xDBC}7ii8*x3t6Ba5 z_K6{Q^`9$}Mn#Xrp9*`H7koD1736)Wq2AZHdu;c#v|`|{;aGB%;n;TOqjhD=h`mUj zvQiI+M!{U+-Tkg0*L#KFKUhfc0MXSaP*1nM1k8`4XB&Kpl2K$i;ht_@fg9}3tsiyf zaZMU@!i0hrJ2LK#d|m!^)F2i0@0+c}758qRRQ$4NG+MDN@(=yB5|unQgH0a$>bWu& z^AtI@A^)c$7?J6)^g-Cru!wB;)W%gjMGrLCYrH!Pa2_eP=!^wiNeJlYE&&|j+Xg~J z?rkXmnxFLJHg8jSlNxRqf%@WCmd_`R8r~=vJ<58cE2*T?EGZ2F(j9_=z|tLp2rLcC64LKkfA9NU*XJLK?DL#G zF>}t$Ju~;=s<8i=Te-0{78GoY42l#)6$!oGwXNmy!@#6bP z3gL?VO#F_PBY~2TAAmZmN9IZQzfvKM+<>~#4!kFVCe{DE6xXj@|5lCB4y>N+`Jx}G zah`X4xFr%>`i-e2vqZ>K%Aip~Pq8KGn}P?ao8D8VrmUoFtF!L5BjAAPxg+>3&MJgrqnL}<-UiFMhj&5^2pl7H(H7n=mLmPBtQaTEF$~LtJ|H z+3yo1{7)1@zy16b*Zojr8AFb|TkO%iY85^+x4BfpT-F9CS)5M)rm_gfu+ zE~Wbl)@kbrPN;$A#&^d}XNRpBBa{vj`EX&U*L(-hveUnhs$kx1{Ob)W82P5jXk3{< zk2CRs6cF#r+QOr%8$)$*<2pt5&f;m>)c0fgs`cmwtmoy@RM43$!3g7`NHlTn@YMhq zx!@ISkY{5_Vv1KxnIw2y@FRD&qqd0F29>71ownH0Fm_hmb$#iy=uCbZ9W*jsWc1cB z#Iey_18(70_iyQ_pP+9pZPlneSe0sF1177XYCuNf_}#oXfjT|C{&tZhVS|gK>DXyO zVymjE2zpu#C+~ep{|MZwQ^WqfD&GF8T)GGk_ilJYoS$T+Q;CVI4cGUH*pMgc3YMh; zhz=Y0`n0Cu8xto3@NU$RJ=(XOGE4@wjRl1s*+kD88gEP$zBn->p6Kfsny^dAt}+7tho}c* zIPanKmIhkZJ4YG0wY@r=Qbxrx_x%xKw`z(`#m-!Ze&PeSo301#M&h_wN zV&`dW3YPx8*V`8X6g#@~Vv;c)oBa!BdQEcofsa*Pz)-~HRn>Cv@MB! zq2|*~1)0+AXTH}iQ7jQ-nZGwK_h&V7is#;hpj>-mfB3Jq>FJfTJ5}$p{2qVpD|CBO z?)Wv=t!SLzDdb?w1!w1=h6oz2 z#vCYI;ck2^IJ_bklA~gKeYNEJufCdeV(iaPO=s>DiF3XQMe>4BTf>96-3d$iOuF%k zPn~xcOJqwB)!Sp#YUX%}h7Gpr84qcM2XC(T(Jl3bt)LBje%@Hx8n@y%I#DPR#Eo}1 z=B`xq-%$&~0v0L%Wgnj+XZy6oI~ie$AM{ux17y%`VNJ zPUCEPfNGfFqkgo3D|6+|3cF~co?nW>c+j|3wSA_*`F4u_GK^6$)J`NNDN9V)Dx+pa z_iEo@)rkeS?PR^C*&AkrU3+7;p`$&n@S9!V-=O=~eGU^~u#JzDtINY(Yl{qDMB{g( zyGNdLisx|2J%Jy+kgHBST2vZM|Dqy6k@t4n{zkoqqq^zN=@t z4b?M9dMl|c0_D-5J+_`mGWwiH=+p9&?m%(D=+=`Nr|>R8%F>-etYFMQ~8j0Q`~^w;z1s%Z;+Yr=Q-6HGSKF>8(0eDcXJ z96Im|8Mj?Sz4=0~>k4lf|L z%2Dmtu%3GV_l_~{jI37W&Yas9M;e;1xL@>HOHf;_07vCD&TRxmJ_9GK+0oay3~KCM zfp^7T%rrr~>sRqt{~hWx6!7eE$!(W4Y|kYmHOHVov$;m3GKmJ_v(RxWX+V4juIeru zq}p@lOP@IcY_R=)hEIhmS7l`-0X1RWyXc&k)&!8Q%+n)gxP3;-?B=zQ?9Loolx>8* zT7>w0zFYnMfv}yYpdQ`qp-!pKTF<{z3oawfx4LH*SPrKzS)2edg1sh(;aC zj@3(`v1FJ8T;t*jSHQ~mj?B?XEpx3%Z4%nNfj&r=7meeJsJptSb5aR-T;Ac(Li^b| zX9JJT$)Wo^JkhmFB@9bPhD-FX0D#}oaGV!Y1^NhZK+vjZ$Jms{+hZgC6-0^a@R9zTCP|ztm?=)~*lULmC>KB`uI*!) z4tOX`z2|Sbu(X7`!bXX~ZAGRnUr_B7B4okpvy#OzO0>H26q00_0Vl zdGQTO-LjerE5^h)oe z!^DPA*iOi(-cem$ovb}l8DLV;HP%DFBuM=gFC$I#&~d+n6`{`oF)ziZ>l*E>Ta0IS zD7Td(&G-|>l4_{=tx#D53jwW@xVqj^hjbZS($5))o1+do$R&I}A1$+w%#rt2nfc-9 zL?*pnwBQPpk$?lFoJO~&QDdkI4?=@fe;Myq9WpM_BvJ^WX08`$!(YSoyzj>xUk^xo zL{7|r0t0j7`JW>9m6BpI)3p-f;#}8ui{s+reuHwDAF4?ncX1+%ii!Ch17KuOB};wt zX2;!b&U>I(he}UW{yx-3dt7^ELSJjqauPzFn#5%37_Q>o8XSnlb8HYW~m@jf|0cdaIQ*MI#l7%FrEOP^~Z{fWV-4R6q$% z-$OVqe}BqL(-mNN1(myWf(|}|2@~KaGCG0OPD{{!nftk|FM`e-`V2`hsctPAkMT2! z-zSgNFgJ*Fme>&UZ;4A8Swo2E27!`}Z&W6yfSBmC8wb2`ez(JUd+3^pmr5Ri#3S%O z!D0`0Kq%x}o*7L??(`5L^f_cjM0>m_elPzzjGw=AOt&`E{dl&2`H9F5JuX#stU%qo zkgH4{)ZH}z_mrq_oJ2GD2&aWL#?tBlp7{|;%b(J2X+{R}_1C+9fub5{oI*3ZZk#8C zq}-Iu_ERyhqm<(YLR9xDt}?Z1-}x7AUc2ox(gd}WOhDQi0@Nb-)iga9d)E(m2D%;) z%=he2uSdg@AADP*0a6D^@NE6q9<#p0`79F2e*k1+E01=;*)28ABMQ){+yr!AY?Hew zIXO8!2DJ|SV~7>yn;0IDtSBxnK9?slSzNgbNG{U9VoYi7=FpDd#@scdJ=hcan_5^m zoEB@lX`p8ATS^qJ7Jd70f4|wZ*Dis_M0{Au3fIu$uV0MIpF~S>C0`_ZO_De%vemgV zyxqu=`n>JK=%mg}z~$!5fg{6rmDv94*zKB196=Hs8R>O?iYq|BgQk-M8}KA3S!AqT z=1+~U)r#R^BXKji!~?7Z%nD%bXdbFA!gQaNn2o_DJI{0BZC-S*Q#O=`R{Sn`kywuv zjfvm@F*vfXC;p+y9MFSXLX)>H!f@J(%S?eA{>c`4=t#pPHpGm9VnR!hU+r4{B z6a(?KA)qua&4M>b1q$FVpz{~=w|hp5ccq$3_qU~I4~6fwD30E~D z2e^+e?2&yqbm<)A7+JSBk>D`*Qb1Am%)oeYXUBhTToLv&LCW~Y7cH^8H#7{8?$fb~ z*ZPUyZ#>4YyIKt|fPZ~6YUuRw`nCgkm*#yq4lo6a3aVZ+SkaAbbIsl&#Uj_IcWH@t zGmbzSrfC6cZboAaH`K1*HOu_ubM2Q))2+pJYp;iEAliqX|CIu2`V)2m@(ckyoMts5 zJXM~3n38X+%>oc^+{vBFWm1+dQ7~6xmV`0@m4PoRJmbKUx;@ppGAukwoPE8`tpJ{r zVE>SEKMZ|+hGMm^47B9feWxtbBm;%os01aSfNDj>6(EYw0VGmk6KFqLFccRM5Ku|# ztVwhP=_w5>{f;AqB)^FHDk4*$Tdxetx2;xqOdOcn*kQE>oeXmtI4-c7CQDo<)nZ1O zP6}-29@v`O$YJy`hO$U z1;&ieKtkC-PtR3DoVW%d@5_NQwI5V$7G)CU(&*7J%Z={p>LS-1NUU98MW)6J z`o`!as`#=k%K$q-SmD;;Si|VzwSxdI;c>34cw%hjx`avJ?XK?Gp=Fw|N^Deax`<=% z1g-{hvIcHo(%wX10cb*_!^11rFqXI)8yo@V7O9P<=0L2Y6dly2PsmR`(Ii$MEM_{g zWFnC4jS4ZSziiw8BFE4%F#~sqaacZf#GO!+wVYsCV+ay$U*BveVPn z42&pE*=!G-k}2Xa0iHZkFn?Jlu4{@jF~N}uLOHz>j%`!Ou0p{FbC}{v0E!!U&$sw8 zT)|$8NlaX+_dD}K_4kB^hB_AF==MS?3w_zYVZ)%dmky3v8YH{R3#^i61@QcEdl^c~ zq5&4Omw*8%<2E2L>`a)bn~D5Ai(#(=X@rabYOh>VSq8KLe+4`KULlBmvm74Y{)4Lp0mjYi%h9BF?$jAJKX~Bv?ef!SQ;8K}_Lrw` zvB!mM+l&jySMF4v4~V3_Tob;Z+>I8(p3Qov3Z%aJu#y_v(u!?a@wM~MT0wxQ`ssVB z)+Zy%Nzvt?;@v3U>H^r#41o0Kf-b*Y$fnh2q$<%wjTn;q;!j}ts(Vv9A)JuyqnB-i zJC{Yi7tQv|EhNGKENK~B0HxEFue;Yj^gKx-qNb)MH!?Ev{>D+NIjDHf7bd5VSZ=L` zQ(8a)qu964f^Xr4yzIx8_0qA*){!-OLvZ4j3HkPQZxt4g8n>vhLd^R)LU5OBy+e4sSqbi16I$<1 zymH{gvG0_^7~i@3x6WsrchWifq{Em9Z2MZl%JEEKg+QobDPwqe*h|PwS*B*xy1L!o9ohn`V!wJwCvS*E-6aMZrCvZ`gU^}l_ZuasQzR&zW~ zYA&n!G)@7MwC$0r?B*Cl-og)KaZ|R)nc@f@QRy*N;pgX%#;mM3j$nm~z=6bc z_rX_OnLFd{b?v&cf&$M!k|zv+ltE&}mA|CSdd9E^f;eb}F_^a_mRS^%q(W78>v8q< zw_bYCVTPlH}7m>IvugAFr~ zaiOE4iR)3+kTG}e4Jf7|;l*-LC;>s3m>ay%j?^si`E)ug3n#AroRo^p(`T}>vRF{# zn$}B#B&8HV*baZ^_rx%#8NOk{G*{B<=(5FgD@{!|xBaVP z8Jape9$aRO)EOkzTb{T{o~;;98MQ*2QF=3{_b0{DwTx5bO{Y_-6C#RAO3rfA&t_(3 zqJ54lS>=;K#l4)qgKCMJo!8Qfivew3P; ziedL4FC-oZ`Uh$aSFz9F#UDwbm}wQ$FfkZM)JpwPbD_X$;>|yupo=`!N=9(_mQm=m zY_Ko?(6LVI@mR%7ZNJzFy3t_c3RW_1HobwPUBeZEl;KE$XAs+$q1%RbCw08TY{A`} zfVigCz0ub-gS^=I?+SPG-Z0joZXu+nINfc|867Q9#flv;TT#0sU&uT_@BxbXHOki{ ztMmC02aIMklFzL#SbFr1boDdG@tO_x;Cjwx#98SMqtMP5aCjNzji*nvpE*);>F+hh z`qjW=?YBk_bj`yu0@IQA7;5t!q7GcR;vp#RR*t+>NT4(no5|e4*n;_ur(*{nN$K{} z44Lkl=k21$jsVxn$OA`WXmqHsE!ZJ7XS$j!`=~*A#=0I4dbJRt^bS(m<*&`Qi<3S@ z*~a?m%!o^IpqD?*SCvqfe@eJN&$~GwIpFgS*7HLQd~xBXanv;kN>3!J!+iJ>{9fR} zmInpqwanl2n(`hFh}qtZLk!!NV~nsst2;vpa&)%=N<;MPU^MMxbLulz2zu9H7QZe% z-b~1*xj$O1&6L-y!A6z5^r3= zw#`+UM6B5Gbg2_VZH$#yZb`l=6|=082s5wRI_>t6Nk@T9%5KJ`F{CFQn4Zm&yf7H~ zy>@$t9EKi`yKo(V|5%F`!e!M!wjB|{<>o+W!g$zOcDfXxJRIjUgPK+o#-dsP=ql^s z*t>@RRxMo>?B51=;FI!S)VwJ$??06e);s_r{8y6To9z&OaJV!?toEha<4OFqJF5k* zm4_~}*yj)Mph+FS1Q~h(wn)MoEPZD2^joxFS$;f8ZpMO#x-l+r)H50gI(g9Jrg4e+ z^}BjPa@k1E=9d+75m^V!=({$956Ph?aUba4T#AviKmuvSZw3T+cUg_{;A3ESc4B@u zMf&IRjY3#>1np)1_PFFNsU+ETu(v`^ldu77gQWEBS1x4FZHB#8Xlwzq(T6X&(*mK7 zAm|$Hy+^CJFWvAW%R_+A-E-h16c{rHpkl%+=S*PI=pV_wx^XYyY0pYX3gZROaOdSK zSXeblurUD2&6aZczEa7p^2;Sn$W+56cxfEJ{+k!M`OHgDKu5Pqr8*c+9tCFqIB!PN zDhsS?M_iSOla(!xBG~wTI#r}TRu2d=4&-(ySgTT-Nl+)Gz+Bgqe@q_=+LzQCW_YX) zl}dFS?cB1P34}#^BD~i>EO@Jl?OM5jJkKGgN$Zc0VPFX2%{WFZ{SjEt$}BKu3jV}2 zS=HB1D-7xhyVhFS@j#c!YV%9=-3MNhLy~c3>1?ayv8fG@cH^bem6s=LG#_a zCaHEHQY-h~XJ5MqgseyhwhB>2&4%D0g`k)_;qy4mUS9U-US7398{kZJq@zEthFV@#NyJGnt7$hc8HI5Pnsh^~MM~%z7HL->R@h zmDJZC1>Ju5;&iD?;@r8{$m2)#c`%jnZ&x?BF(+zt*H)p3wzu9>(B(zm@7M-tBJ&T| zyQ;T~@8*t1Zy%-~fx3pBHw3;-m6aoBzruOuKGn3hw;zLup=cgF8ju*Zwwv)@_FBe+ z76o9mnP5iT$d1^hH>bbys1bgZG4#fCb#}VcIL`a6FB||GM1Ype;~031w>LH(u6(9O z&1mYBG^gUGY;jCPItBs~c?}c++o#gm(BbAh=qL%tk|p$7vhDWPIfyKK-xUel@D^L9cNjC?y~pTiC$dhMqwm{CZBAx>z~-k~o$?3F3FOXl5q(>Qnsz<@AqwE} zTbMO4;YHzgumrHIcL_SLVEtMpd_meI6EI|ey~>KF%g}#wHyS7j+eh;lvjfRHZdkMr zXJO5s+PpDcp!2skwtwL;j$-)A*{bY7Y9XW0Ja&FG=l2~FM_#2QM@L5q zg@GFgTf!4S#9oGZ4CAiNR}mv|B?kKcRr(@R^|`NWP7-+kc$ij$5AV zH|N(RYEaJ{qZ0K~P$*S3DWjSGvjy@DS0?*;oRwQP_xo*f*TpOBfv!K3^AVK`aPfNg zHYEVK&;`thu#xsb?ui;sApc?UY#~SZ#6y!E)l~PB-wPmc@_)_Q~YebxIA7 zBK!MQ4i^sQf#p-->BIErDwb)829xnK4kj8hNdUd1!!*ir;Kzj7m4({zL?v-fUs6z1 zbc=kT)3HN#9RnPgd;vj0CZ?@(m3%~IMa3>sg?Q1$dtGJN7^6uHNpixs@2Np=KL^|K zlq#>t&Cn3O(A30+eS1m#w9#Kiren?S;-nxc`ehU{X&%%*>t(5Zr`$RR$gs~B%Szsa zu1Rr4klN6$QY}%i!wwkR!rd<uL#7;ICzKIu_`2n?ES)IyQSti@jjDsKwRmba`P^l31*tKasVD9K|3Lwmg z^*V^&M__qLu;7-5XMkH#7R*0D$ZF^Ik77h;(}f!kT!e66HzKl!CNM-`_Pga!w1l|{ zy0!Wx=hlb7cRk_QaM0gWXn@TR4myP#ynO3^p;u`smTd(WzsJD;^cAfIcwG~*Qa~Cp zWC*bZHjJkvcl$jmtl682`%*{g?{DO&r#H1Rw7r8pdPGM@rwW>C1(p-}+LTE!@>**a z?^kfhcziLHdja3cr)ayUHlZJL`ySg&{2xC8U~1{y37zh$usU}YJ1J%%ruQ-jVpdU| zhyX5+>7%B(Cz98pQvOO|!Qq+^skN<~sO1}Oyu?@|BP0H^vopSrKm2>*I8b{^i_tlV zmQ7=(n@~WVL4ABWGQh5{>d_teM%_mbaX)a&$6OB@y&2|yfm4k2vcB-{#U9WOSno0Z zZ+_a^W0mv=-OC4~-*$VAo?ruubXtA(-?z=2ga z{@x0oQ{QqX!{YePGzZS7$rY0Xx0PSt*;-ZtCQK4%R?K$^!F`P@%u(s{vs5kPF%(wM zrzc?r>+7~+X2u`b&_sb!q3MpDw$;a;#&d(>S#ZuQ@SYYA?>&7{z52C#c~2xs>lvfQY%WP#(7$x zA0d*We*?3A^M?ezENFhsRloS76kvXCpal%dd$~bxjF=odQ|l<9Ja8T#AAhmrL@K>D7WdehUD_=W`$<7w+NQvD(jgOapB<qtm1)cHH%0E3( zuu8GIPa2qHZ-1{g{s0bnpzOFC=)ER0aD9#Q@_A-tAa|8Y6Mr!U9Gc-TipR?CqBF|RF}XL_ z+0)dL9PR83)(=lcvVXLG{P>GiOoI7C=!~YIfw#~*a9rK6@EqmgaWH$~&73gGpIgdx zFF5-S+<|)VJwK$M_4f9fU?}LugMnVgSo1h;#$Gpxk%o9P zOLG$iVAHH))3c(2OcJ&Q&FN?Ng^B0tI(%T%Rf2+ovSO9>z>majD!nWMjKIt?^wv`V z6aH3Uo$R~$UOxfvpRdQ;zxji6Djrw7xkXyg)O30q)rX06zsSqWi)GuLJ+13F1w2&= zp3QTgtPe8)zmx)40#5$fP`yn&NPgjUtvj$I3D1w&CNyqTe7{Fpo$nzo9@PNfib*G) zmwo>HD&XH-P6u10hrBTV5ku(2XW&pna1Qqp&AZh64XNw8Bzxw!a$FB){{|HB>iX5Ur{$%B(OauQA>z*!-IGInxQ-!I))xCAker~5Q2hBPS!5k-VHxTg=6vfSeu zJFqNhmIC6~T#Btf>fTD;SzXo5TtOZId}U0O$5EFl8<@(k_#RQloXl_LP_#WMu#+{C zk+WS<$2S+=ZFG#hMMc%huyyY1>wCNS9O1B*6MU4AkYK*IS>{oI_aPA2nhukx(qO{7 zR}ag!Z~4bKw+Aq;f2jA+773Q~%eRLAne}WXZpQw}vaFt{^w-oJAznmgI0qlyjTYT) zd05u8iyl!C*_y!}7mvU}wW4;yca&zvH6lO-_Q`wM+kTFoHeAq<_i?ks+A2*pMYn48 zmw`Oe5Hjq;{mn9~?!)G(F0A}I&0nCm{zdSCE0USBqo7?ds%jP6`YFC&QTD>gpKZCt6IK}HR6}^4?w(YA1T4Bnu-8mdg z7QT8>i)Ji#d?F&!s0;_1ic~uDwYTT21qN*$!0D483?k$rzX8)OTOa{Af|8HyCWSb8 z;9Ie=R?8ZsKVhTQ74`5iY3i;=V{B!Iqn&Ows{EtI-b_UdGKxEfts5yEmkk;l8;i52 zTAQ1Fzf50I9~1cr9xNdI(EJaJ=Tl zNkg91#rXKR`CbcfQHc=O!17UlyErXYmR7PSr;lk680E*8Nr_`!PZ|?H*z3103qHyw z#wNl3Q!kvU@Vwl2<+G^z0Q#WqwP>Lcovj}+_3#_H8G1rKi=Mbh5N~dD4MP6#(7J+s zLc!KT08#f}h|WI%+bHmxNBO^0?uK+NmJvR_NZ@A?D{cTJt5l^;Ee#3--jf`a^^u70 zXg#fKkm+NQhUd$N#na10i;C=o6j`OjZt%9jQ7>$34rwPJ;H?n^(N5RNu0({3i#~4i z1KVMKdYL2fp8%Qz;OI|PC91%C^8wp$wmd+mk~b*7$0-qglkC}`-R+*bXjFOLiGQnO z^0=zx^PJ`eDu`3E+I)4W7n}IQW8PPF`>Y&|M!WNm_#%-<3bzl!!^7wGTu(&Kh)!rj z*Hd$?7*F&8NoM>{4mtKPVyU52+cOKw77 zc*)X*jTN}}MBoUv_z$EA7=qg~e2^G(Q@fIr<6Vs~F(QD)=jt1X@yt24djaBG>WRYX z&Il<8()>H`Un6IbktofaTYxyvpROk#DStM-j%)8Lg*OynQ}DL6<&QN%u%v?eifHUy zGNlag`Madja0Z>_Zs)jP{Y9k8)z@*E{cdjk9aSp%mzdG!5)P_H?++eDgChlv>CZ>y zW&xn5ZFE$qwBIdhx=9Yjz>HobP#G5>0ji(Wmn6t6cxsONi;vzULujnj2zQOuN5xrJ z0w?!(I35&3D^N?;9ew^S#}szo&7l}UUo{1CSXu!~94yV-1fHlvN63CjX}NlfU;#nL zv3N@X_|$VN2cye&JS4#XZ$rQN663S=rDRyV&BYByrmB2g1E0MCiITUFzYheJ@j zQ@_>&kNk2ofp|Tp%-~xu77KggcC8qpmo_3-f9ihc-$OWci+LnaMcTuq%CB*SIz>2i zte$jwyZpsYVQxF3#`hV zl$i|M4}vLcQ$ghghy(BG`tZ0KIlGP(HGoVwf2x_n>HgSXAE|oGdEf9LzXGr6m32ey zl~2MR?}xQ!(#n4`qh+FSTe=qilg-3|*+%(>kff(TJkqjp&v9l7h1>{$MVoWrLCo=P zdlB9Kv$cB9SGS&i&{)Y6mL03VIv4`Bgmom+?X68Wk$+ zS9XiS#}P=E%SP7R2G|w!^aXr9woJTAS$YY1kJyd-e5HeI0daFuHVc?P$}|8oHQMVT zfIcT$O}O<&s@mTlyvyEn2%! zgI)~F(X&;k2#(YopdIu{>$lH(WM-0Xk;gtyvs+KS`~9v0$z8Y?0MK4W4GRzfQTzFG z6x%Z)`uSNC9=_Nw#WeE?@z3#AOIe|O!+BsfI4oJfj`#DoF|tgjw86hJqT-s^ucv7S z>-647&5ZBxruTr@ex?Mr4)js8HdmFq#VT-M1N9U%CU zdo@*L-CaASiOOkrZxQfj^|QLE6e376l>snztrr|5#nkjt;QI!xbTV|fPg)O`mf?FJ zU%oV+8fOwu92L5()OP)S4QNm9I-;5@4FCk0^S`YnOPd`lPYX)}Uz!?j8;r0_%+_-F zaO2&KW3<6RSGT@Y@`L*vB$+8b%w$-T^4t}`SiKZ|+LfOsIPX~{<>1}K>vIjS3g~dC zG1?0pW2B;I?ukC9wL9{f1JvKzSgQA;x}UXrt1@~csJ;cTOdss0>pws(UHj|v4bV37 zY;SJ50%m_f5Oju5)Pw-=`$hP-c3ozKDS*78mpNuDpA9`dnrHpam4qtUGTxX<3^=aG zJBrUT2X^>@89WGcOe!lYy9lZ=sygiY1fn(oUXc$zX_jd@Sb^h^Crt&JEZp)_)6)Lw z-S?lmL&eDzhZ5J~<`xNLVp))dIruinGwP3Uusk2bfoWH;Vq#+2Cw^da6t0RM&@cc_ z^a+#B>+|D{va*kHoj(so0?OZN;e(KA@@$L zOP>MZwmBvQDcuodWkzqT$Lp*n3({{-<3TWaf$D4B_qR(EMJ}f9m=Pmi9X#m(wiDol~Q`npy_76 zgDi}!UVJYX(ZY(}*h#iZYId#@bEDkK#Q{^WBX`%x9&P_g_K9=2qGz8oHA*;SUAM@* zD2gk_6mbXcn-gdm4^GS{D_b&%Zh;0?KGN)u-jzi-KdSflwLpUuE5a^3=WShZw!B!$K8Qdn( zQlZJHH^VxS{>X@1&yf8ZU7D@DFB+c(?44;Y6XEb-!PRVLx$zoMGZHY@;%pY;HS23? zYMRosIK8p=<;$1f85f8CCL|Zl4Gmu3KqVc|lUGR-EppGQ905$!+x?Q0w44ZHep{gC zd2%yy7p3DKBy;wRB>&wHg#uN!b5ptV7I&Q|Uw78A8Wi^`>!%NHS-_%ZqHK@Mfs5!2 zDz)|^0FolS)Jo;+%Xe@#>1Wo`+S#M|`O5*Ik25NKDGKzq@onFC|v#6^X7=OYkzSS@jzwfZJ@x()~=Bc3;q=SRQ z&CoWrBAkSED+YHuO3X4#cxNe>KB>{=HZWwT5!I9^3~81+!lKf4^CxC~$6ok= zKySZah({NysJy7UsJ=){ijnZK2%||{HD*z^ccjnjVBVwPjK~W^L`bNx?^6!$4Q+3d z0z#v*WkCo|t+pp2vS%*yJ!!j%6En*>DWdhS;vZ5z|JjA!m?2t2N%%|(yjQ7|e%2X^E(Pp$7-iqnN9umM&k zlgHW#b_ViMvA-fZN?9^a7q_^NPEcvX4dML1%xH^B%3iR|NOGX*Fo)+NX5wzR(-^v` z*3s&JR`%kkFC1Qx^FX#=?4x8tJ&A7FIc)U!_rtl31X*wzp~%km+B-g3UdQy&VXB+_ zzMCD4xP>4?rsy`>fjF{VCk@GQP&Dyu+<*IA$m>NECn}N0s;@86o+|K9JA9@*K(sMS zHr=aC_J!jXRR)iox~aSCPa3%#gy9dpi1h^yE_5Q}<9~t^BcyA`Kj%s-MN2RWf~`m#T2-2aZy zto^uq_pav0DSs!Y&r6wR=aM8tOc-wbi0pUZtTXj{&{S$if5oxYv-Fuu1-*;Cc2ufb z^4+c%mc0?8iG_Z1m^of#vzg=zfxX_1nd4ml!7lj#txjvh!0!J3xvaZrRT1l?2|kpV z*__iV;8th8Ty9b87yC##@GXZzDz`csW}gA2aWZgf4H5v*^tT zv+vZ9$_w@$;CW|T&6!qIRJgmly3${_NRn_GXqw+qrK6$Y7`%=_8qewW;3i>psC`Fq z-*y9eVgBIfzypOup=UO~xcg(?y?M+r;Gtml;-zJ;PGz5yrStHH0VDmu`jCN^_QBO)X>jn|IF(G&xsVz_RdhBvTT{7~AN=|V zrstl${T2(;IP0p|@;j;*=Vfa%S!c_Azdcc}$gjzPyo!r~}uNquu9CVb)BHjOi)K$y4QyNj&36d8y2uyn&!QP_tISi;d5flp>~e zH4=J#%%gl*0Lja29R?vcjLnR722Rs?AQ=!X2G2^)Fv&kx9q_q_Zj~TrG!y5KQwQb- z6i_2U%7jt8f-MYgM1cQ8aQ~#oXsR0+$Nen~uYJZNy=ttdRCp&Z) zAqDF8pv zZA*xXidsmUrL!D#E*y*&E)(G*X#lgik!M5G_M9d{h6HOgv@P=Jk#v$Oi)M9XI6$!= zIkFl#!T>3dg|I=4y-ai!t1iJ;WUlQrj-=kVC4snQ$cFd>3MSOG|{eGi=H5>AnA4w5GUvrbdN)90By(V zy?c@l%gA5p!)1|h)SC_Rfy=C*7`&LAJ@$mMt;cWM zPt#X0CGT5TX)RR;nzEd9Kx`ReCSXQ(G^Lvz?C_`xmZl@cDz{h`aJm6O-yyjh*)n~W6mK)rrWo2cxyJ>CnUjod4b8~V2;Hp3&g<@c!T-@n*3R#BY^(wP#r2$bpg2MV=l)$5kSE=olfcu zOZ|ZMtNCBMgs;-xRB!R0j&KGnx&L~5-_L(-IJZ^9|3&kje%;~ob5ZMReP5zudfbCJ z+Z_r357sP2c{ElhZu?3Y?-YuyuKWfJ^11(-VP>soe_anw3a+&O`t#Nb^bQ8|0<;AX zQ~?S!Vbmex;78~c9tdK?NK0_P*U)4G)?7y&{lCePlmBE02$paUf!a6R-;$zw?7;i! zjK_snPu=B>d zZ&YTH|E{3}Pk|lbsyHwffxmiB;@{{(aN9W)^C9HlSoojg#`52kB&h#;Fa}pOSsnqR z#o%?;Ki@$H4q@jYfMvj><09R88UKB!O~8#L)S2l83$WTjj5^`Ca(>6-$fqS@XlSSj z#GDy$)*^cv3#kS^6jn>Y3Y)n;cFl&J`l=%yyi)gRDmMxwoPCIQNGzPXmYlL2Bf2#( zHQj|6spe8X`@yz$t#}fi@EcP#_0{2ac@dAPI=ndMZ{QugEi#{JrYet^>yB#Aeeh^@ zLtahN#h96>d6V^I9<0onW$Z;C=Q=)N2HQS-Uh*%tXz*j(?+ zYA2|pfIyN`?lr9J^4@92`Ch(($Hn693IHQ6)34uD5njvLN%K2i6Vwov`6)yLg;fSf z9GK00yc=5Z+mfYLNf-8e=eNVe4NsK>BL$M-<*+rHN3~C6EUEH((fEl=R!_c~p=RF{ z^P$Up{j>E0cBuDyqWm&8SLtHY2*hpH7-F}KvWLg{*v>lqXlM?C4|VY7i+nSJyul3L z3`OGh1AO1AjGy~nd>kmV&o%dy*zO6S_-98f-sU8VmA%+_EfZsrL9R>wd!Fm$^oQBW zPjQt{H$z`hZ`#p*XMxkPm6>YC$%G(zQWxWfx`DpeXe}o-{1@YM0r> zRg(rwt51mK+|4Lnr0ZkRa0@%wAe6}}I6fJ(kr1Vg;pJMozS>3glVE*z^j%`wa!m5G zH5X~^C{E74xVw243hFg{94)GD)?;LsLi^aJY}Ovx*&h3yL>vVUX9gdWF?8%&j%0qa zp^d32<)s;JZ)TmfYZaL#02=nxY|gSWQm^Di2JH(bi==^%_90)uJ!A2eluGaIR8kMl zVK4Um-DkGj?8y&RY|Ex8Kll@QpNOx4fqHn7GceC~eEq_8>sy;ac#yYcc72BFP-R)o zk+x*?qrT8qhg&B_bYk;*L-!ueZQeo!+Bof9^4#~%c2Y>8*Gu5A8ei)DK%=Lj9%DI~ z(Q8o~B1=)Qb$VH|gK4JV@`+$d=u&WB^R7d0rt5x0u;Z;l)W=KzRwW3zF9EDV*z_Dw zEh<f)p+{jvI$<9vvy>SA}(=Ow8K}Q8v!uN1(z5TGAnMJ(WYiZ_r)}qDJlp$;b7}l^&vc0`&MCE#bZ->v-70znzrU9xr zo8YzGA=mtt-NTO07NcgpA8<*}}D_iNl z5f+dIzLR3Z(b%?re>}f`>|89`R(A^Aj&i=!gzY~u{3}?|pN?~Fo;K&ZD|yr8-GGtM znK4dEp!0QWuKtzzb$EF>2zlf(zcTC6D{FaUY4N+!2aeWe`>4~u!&8sowke)n_%~j1 zJ#D)ZQq2idpo#dhg0*-?w{&|9QD*FR_%HnGyy$aG}4ktJ&CmIry`h*+2G`qUX=8{+bJjw8p)@_q?p>bwpGYW_`+p ztXijRT&`lm?K3XK#$Ipcs)nBw97!bb`b<~zXM@_tx8k|Km6R&+p{4FOmu}{;_i{)y zm?oT>BCD5HggXRvpnD~0acyd5;vm;S-*3lP0$ohBPK?bPlP5N%ycRM=aKd|He*paQ z{6_oTE$qYP(r~L#DvqOjmlBdA9DaYjnANeRaP|wB(zLh57 zhYlLKm8SX1@IPLC7eB(coIZe#?X_ErRApN}ve`X(XuDLz*VBIY{wN_ZZ!ZNQn>#kE z1@EHih&+}}X+i4137>galfH`zS@Ff-$3mA2|2hjZ4#W=I-A4Ca-$gXdZgaS0u&io< z`Ym8*4m#S~D^2iWz1iC690x=@HgUEqj0_cDiZpG7IaWL_^YYX#J6-8#k2f(CHP{`2 z(=Xlg0QWuYZalPoW!s|YV~j5*Nf=-pdyH`63Do=L7Q)vx9ZL zjZdVZr<)Z*6=-Gu*Y69LH~jp+85TyOUCEivbknCLL00p-j~5>E>xg>83r5Q_UvOEw zFwvsh3Pk+PNGN6*?d%G$|n7&vU}fA>J9Eb{)G(lcOM{VGlYC|4?{%V&=Ux2b zQ#JbVg)9?LmJ|4A_8dIj6cS>4!14Qf2VJ{E1watfTaGWNuLkS>9ea|(frq}1Da8BC zQ~ZUSv^u0|o+)l1L!FI^UMQF#317h_)n$Tp>;r1?s;c{yHfp$kZGM!3%A492w^u44 zY`S&@dvhaMN6OFBGs*uoxYWXoQO-35iscecMG72@k0ItS5s%QueTEtFW{FI`O%FszgfvES z!^4+$V-=DX7=asmrCAJ1L~q&Z(H_04OpNXPa*rA^)p-7Wx6%8hj7!JVGJsZ=GIQ%3 zQ}wvJ>PfEPCM>MQVl6I-v@+jnPOVOa2x7AcN=LTxFz0i!$2_#to1(mX)m; zgEo~G4XE+&8FATvEOx?VkKdFgJRi5bM*4A1v)ExY0z`gc=1HpCJE~-586Q?Y7$j&r zeg56@(LVq5ce~y%g-Tu)Mhb(XzIotSWdpcSY2Y02%)n2Ak8s{LNXKB>qB2WrUVzOK zLQ3pYC(9ij8w2dIYxUgMD8(~tL={`S-0LYZfl6USUqAPm>=Z7)d-NhH+6zgQ*gLl3 z^dtmK7-cwXQhNH_?JcU8pe6e}knYnCb1V<4eU)QbR#ws-yR1lU98GYt$NR0q-sbwi z06}?W@D1v4;M;;C#}e<0GlJrgbsAd<7_1Gyyt5(_BqSW&+kknGjie-4#yY0)%W%@O z(YJdf^u;BQ@O23KLrRk`0Eh|PG*Ep_&WNebnZL0`wGMJB^Y=oe2SlNynDsc-&d;CN znngUEv*>vvr#76e>(>O1=8*9pFcrI}qA=$@G~M?G6lLQcr?SWQ^rP9?*#awZdOEsZ zb-K_tSe+sfgo(h)A2Jk5CL}J#*{P;>E=Xxbm3~i9%`klM8p7o;i@TAMk}8d0$&qL; zO#J!zISoBNw>>2JD=0BC}Q%!iiiZnKUYct?uV!|YbB~;%j+_&ZF1CF~AB_?+BwnE!{Q zs|<*$S;Gq`2ofqtmr6?s0@5f_(%p-+v@|TK2&jlOOLvDzHz-JVEZyC)OXr>auK(ri znKSdw8&B{eJ+)|;N5;gN=Dq6VKL1^TdEZGZ4)fU9-7WrlG4EA~NJY1!PCItIZrc_= z;^zW-Na&9r3g6c5^I)Vb+Gc>Q!|yrdqsCFT06$Z7IXN=(ODkw=Ms)1_Hvg?s4t zR;x^Uh}|_9@5c=76{f(eb=W@z1bIpw_sbBkAq+8cM+7^ca7Pn>+EamiEh-I%cY~E)+V}Xu3j=y(k3!| zY#M8krW4$0bUQIm+-CZxC>JvxX($8Sy_qr8O`JD%V?88bO5mV0Gl%CTWnIzq z3x;Yj1dD^+CRzRLQ?1~b3`oVXdW-(N5YOr5yr0v%ipxT3-wAHNMF2v6XQfyuHyu>L z5v2z4muvCnIqJrF6A5aQ^7S=G2{U(P=l$QxLfF^C!@_K$tr!%?cg_FN;m^A`P8Bbe z=`!?~6AWA!MJ`8Q;n$nh+nSW;^v{ORH#VvJo*0DSTUTZX(8m2vm({1k9-5v;1KT`3 zJ%jh-gFO*m){^$hv(md^A^F%4aINaM7@}Uwje20-xzsZxw!Hq;G09BpaLyB&}8mVNsC!CS%<&HKmvc0FS)-le2l&vSFSuB_sn z6r`#&$a3WCaJ>o=y*gH6b17G|-Rs+|D<7IlD7JA=}KXhdH*F4$P| zJ|pBL6XroIcl@&OBg{h4w;R#qHNhO#Fv6%@ydSG59AxhCkB)G!z}+zLs53eCAeHFl zXOn@5sWX0!78%m08QW1O& z#lFSJr_X{3w$h+*6;QhIZi2N!%&wI0^*-Fr+6b@ytrp78w0Y@Gud!ceXvuXk_j)Wt z$q6CftUc&pO&7Z>9JKNmLBmu3Qk~OKkDX4RTZ$*CeY+T4Nf#^gb@A%OX>Ww$d|1vE zf~~!v-Dbu3v8CQ@8963-c=_3Bc9M6cydySl&GO=0-VS-Hxx2K&)c-m^W#NF^UOppe zBG}B{N~;^c^maRu1{p#&l~wHgcE_!70ss#XiyDonD%s!j6E6X6n6Oi{WH-7{i6sK4 zw|-X@+M@HRmf!QJWA$DRnDd~wkGtxBe{|G4y?YV?I@`%#Jh|`S1Z4;PURv2D)@m`S}zwVU=?d}>$2UlPKT2HJj$Uc6ykN(J9G`9ZgP<%8R!DNa}U&)~UDVBSbBDvAR8GZfqsD|EUUu~xW-dN(NEfF4nk$BA&&6*? z;}Bz~9sJyKesVWoOVBa*=H^sRxv_ykkODwBN~(pgM|v`D=3G+Vp{GUSQGXmvihEk&~tckmC5`s>%-|6y)LTQG}^;f(tQR zH?H!pI+>Rr93HTrYlU3dttw3p?Z>1-b&>~hxo?}Q)P0cyu^$~8)D?$i97MQv$J8)Q z|x_7e2B$WvK`BH#^1#SABb%@>av>y1NiRKRJ#aEV(wJX8-%SXn*A+y_}R55 z_@JVX@i+xVR96z?l$|92T5<26j-rtKiJCKQ6ggMtzL&82qNzdCxc3E^KC?m4+6%5k z^N;M@R{LJJV4^xSdyG8NPd3pk-=DV91JDiyQs3nqrpl)Ad+7E5(q|u`8@T2LZOXS) z@V$Oxb7m13ktfk?peM}127g( zL2upKhbuM@3-ZH=g@MA#Ek4q?iTT90u}txz(!U@@PJogxav|H*aLKdah5f#xgJH12 zRpK%73XGlRJPJeC7{Vp?rKe70UyJaj){TBl(gPPWdDwdu2JQREO+sf;x?&4cqtajc z)P&2}8VLEDKZU~PP{$x|C##*i2Yw5V&P-%!OU~ZlOFU|Xt+keeL9FgOW>Qx z{J>f>9YT)S6pw1*ndkf52l&_4HT8JY?!P{OEk{vS!*27&NSR>r_PW6B{5Yg|c-moS z3c~cyK!^2*qbj1Lkw>Gj@7Dbxu};yUj;+nJI%=^0#o#?NSN|&-U*Tr z%wvDRCtI!(fiZ~C7FozQJsGwy;xz?^w7TPukJlT@ij87zp4`9(UWO>&KPF`ElcJX^ z4z1XX$ndZbt1KR?Vg!t?G&tqAy_b^-OWSVv^7IcLCC(aj$Xy!M?sn~#{eG#=hBr)D z=P(|w?29ZRJ(n@eJ-dF*RD%^cZxc;l=~x&88q$Ai;_Q(1G(M&|2G3`6XmV4;QfCgr zig{j=iY$l`H8sj}Hh4A8G&|*fd6Rzdzz0&X3V={ZJt@#RUN+B3IZq>f?|yRm&-%r> z^P+X$RUNnii#w|F1Mkg^5Av}Yo%L9pkIh$g{sf_9&Lbqoa`}@65z1bf2Ehjjn#`>V zof<2)yBm!ONefOmoVDUrjyuJXlJ}vIR&s9jH08 z3*h{RvTAQ*kAwI-;>NM`7Fw<*FUrloVRhW`;H2diVQR)Qrd_OuVSv3p`20hLOs}0@ z4Z%P^-BJyvEDWOsOi5G*`iKBh8dM(sd)y8g3pUld46Rnrda$@ZS6A?Ri=&Zg-60_V zN|QOGK!tnOts&A-8SKG#0{(eIDrN!F$FbopSZ-LTM_*;MzN@OVN0%r>)`?3U6M(x; zxxUbYcb4=^nF;edi&OPr}k(P+ev%50K`8pe`v#$>3(CpQeu)`6!RxY00J^-0kBo=f1Sa+ zhsJpBsi`k2aBejOk5P0M;KdaSd^~`kcqfBr7 zS^b)Bg~}E1a2 zqQ~`JmHrPid0BWmN@&ehg2o5G9OYdtzJ`w6c+;h5)2E7?2Az3hSXASLe<-oBI|kny zpuhqbg#mw2O?2$9gsfg&hr+e9?`pJK?b(+{hn~1ckPn#v;Iz`t-~rb4&gUmqd%W&( z+5#N*mmjj($1xj?jt6;LMIlogvX4%5d`p2k%Xc&#s<^ngQrK-rrwoXk-cM3mr#I)1 z7P8wOs`7q^0eO{_>;oOS{jn(Fr`?qN3|IA$1V4{W{N(0`T5=cXx*KknenDJ zo(oT51n!%JG91d|Q=&{hnZB%@AG7`qc+Wjl|7RHaL_oC3aEt%s`>g%r!cZs-;^>fw zbvfiEFIWM@fbM7DA|tr{L>T$Aa={dmK7m_YWGRYZ_ZjQ4v$nD#+)JEKyUDj-p-Z9q zoBNNhL&!fwR5XN`5G5rgwO?oOtLek3wy_uqo@4#gd+=_GQ$5q5qWcIiI@kC2kFm{N z)whac%|?9BxRSUP_b!X{`wd?4iM&Xe@A!NKF|jwyvLjSr@6~6IeGkYy1s_i7C@A8G z5eQAmc!EvUcm51N2py*&KkA@0~Lh&3`0ld-li`!-J%4oRn# z*b$M&eGtW6(hE$1HAOFZN9TU0q@nq(rIYLcS}t4V9-8Aq)1TJ`$0 zrk`Snr_=uVGW0j--+zexWtQ?M`HWja071H-;wCPz4~JJBWqWf_^M|k-8L$xf-rPHH z_l!}~2xk&QsPC_Tf;~RC8oa!~ z?)fF+?)fwjbS^Ce+>aPE7t8BPnO}f`q4&9Uo==4_)YQt(PBf>kE&&Q%R_(A=m^J`6 z$Hzgxjbc&?io>2~+iq6IO-Y}3o=uxaJrCoI4Iz8nO}t&dLR2_qZaVVbV^|__mJ<3H z0y$YkR9UhFyx`;KPiX)$YcN>B=t;luzCrrU&E!rI4cml`{;LIebaaCtn9Ls6q&Vm3k13aY#d`zh>PZ)rYhWKYczsbQPKcAq=5U0h!F zWXv{aWv%Q6&U<-%9BygIu>2kZd5oq5ip*Trw|4Um|d%j-L#Y zkrkUDCGSov_AXLbi)K6>d8I6OV^f!=)V21Fy;eXmgA z-hm`R)Lqd_ftN3n+SB>i_wX?xgz}0jYeaWeQxLl(rb$l2woaaQr6S1lXDe{+-iWAm zvahYi1i=^Y{Z)vvzWS+PE)QRRt(`MS4(BdeufBhR<^uJWj;iX5bI>y`{;wqn zSY;L!rSa&0$ho#VNmBU-q)GD z*+neRt`r(m*S9cu^@y#!TK%=I*Jm~Xd9vKqY|EVta0>^rf;|C7Lhb&%OuZjy@m5w< zjX8ZK2w4;Zx5)&JmTqOKwia^7Tj8;}@G6Q~Axfqsh0EQb%*#qRX3g^#E%#&CDnSuo zzjy#%W!K0H0@OQm^Hl&`a`^D!RkCC+7b(QQHs5j`>r>j*RU1F<10GW_Ym7pX?LepY z;M${|n=(oS;@Ty#Rm-)oKL;BUyuSewEN&8Kc7eR{t(?p5N}~%!I+2F(7eV5 zPA)D#Mq}fqnwqoZii(Q7jOq_MHvwd`YYn^b~;ux}PInTTEvyl^gGMxeg#nBXP6klL}$Tg@geY@Wq zz=9{VH8roDGxn{LH^>I--_DK|>kllcP^p8Ro_K;30z3V!yl9Wlyp3?S?>cu|+*{90 zxAPrGM&tq6&+vVNSMnAEosXJHx8gDAWc5m9rlw?*xo-_xTD`#sX+GNdk2C6=N3c79 zPG&el5KmiM#n2?MB}<+LIrwT>ceecZmA{3f_-EzjzEV|pFw;Ng;Yd(Xm7?o$Ez#sL zuq#31)RLHX4FN-;WAe~mGav;^p&^K`RzOQ)>XH=@67&dj702v1qvo5cqD&aR0@NIO zI7#H)&mmsxQbnyBPn^OKrb5JUxA~xau=48 zkMn4&R+fqiyYl}W+2*>si%p$|5x|{uK`KOpRqZv5OP=R(f#+LWTaRHU<6i5Sa_h!+rcBQu z{+w>Pid)skJsi&|S*6RUi8&B{3ipqRtkZ8!LJG6%*h41wKXvg@Tj{r^&7J#&3?~9& z`d2qU0KRw$9FJqN=F7>gwgS7(q3G7*Pef7i-*QF36EaJS3LMy-tpAVhCG^dB@A_WZ3V)`-E(cS znTs3&tCj{HpoC<3v;ziRL8mexK_Bch!5#Q`JKIs!(=&aMCljekylM#5r-tU<6w>8~ ze>MpIZ?wyZNe{hwi~Bu5_ALAwi&u$k-A-lyC*jOHp$-Bzz)ExZwDyxwkXV;)}OOxX{ zK&%E7J1)IqEu)G{0dP%HgAOmXFZ${$V0^g;)PMXBe+w23kF3XVUAva=Y@DivGiXLG zM>_b`yxF@=(pS>w=9`fmaB`FAoguSQZ%u< zvf;hIJ?#BwUuE1Mc0e?MZm9eg_(-Ls`1K^j%|5JbM+Ogp;|hGv`xi;Le}TdqwrkD@ zj_VJ2t5wX!E|ycDQI+iN2mj9uwY)aS)}Be9-ArD_=dDebo-iI!ZdzBsO;=;-1!8BH zT{=yE3at%*70Pcr@lh<-e{oh46ez@tE|V*}c0NvsQ~|jG?q^C-$vyTl=v4FOFz_6? zD5c&idc_5&;7gUV`k9PrTp*0>v#Lb z{Vf6%5b?fY$^E=|O$UPhKeoXVhYk4iy-5HwrZ_Vvxl7?zdz=}AEbp<-s$faB-25bY zX;v4Nfr#$FItMJ>xGGg2a}^7A*p0QVdNC7flj#hQws=o(S$Q&Xk0 zjuKKZy2EeM_`6?eeoC+4wz|Prj4Ak&%&S#U&We z?y_@oLKBaSp&Y6LXxethtWhyvvnOiJJn2!;sXRDqUmw?xFg0F7&ylGDJw7&5tY1G} z`JL=xi50NzE}!x8ZU)j891Ye#*nFRTnnABvDllp|ySDe;)dx{oobfed_-@ua?FU)z zpT5iq%lVV2G4V#7CYgOSyxXfV)!W#-zwUe@-Psqp-aq4f6bV2L5s(l#LaYf5c}iNZYx{XlQna*nOClk~TW!TlN)I zck#fXCLz&J)7;VJ^fbRw^>m$dG}d*#<*D(ER`5wne_5g#*>&h|04oyt#&agF>`-lOPYRc80aST?cW1zEi&yl%C1B~+?|;aO|E z^Z4EOZ&Fn-Z{0TrpIm>fVJw+P%fIq*+*CfFbZBYNDuFhhr>CcXv#wV$3M|nnT_OYo zCp|`NQUQJo{nk?bBa*lm8dwmO!6UsR%cYUkZ>%^gpY#hm)sxxe{Sj}Bul@}tW@f=Q zA9(I1N=ANURmZ${#0}@LE^3J3Icsk8Iwd?w;@ziQNQ?&RDpbw@yI7J$-uwWO`=Sa%@fb-`+w(sZ)PDTrB9vJ zSFWpIkx2`tP*5{ndD*>id$K=2KOVJz+xTq}7uBhr#4 zP^dyDz`qtg#h$eU@y|%JtRHB6w;vf6n;P!BeA>BG69Tr>1}^?Qg^E6`>gizXZ_>Js z_55+9OUYz0kvcdxk>3$52udW3J~7bX3scLHl}Rg{l|>31v)j9S zw#UH|w{Fc-PGr?H0WV&7>y*fSdybZnX-@`5c&uz}YP;QV9xmGFVdIC20@7JH?LN^Pd|2WlVu7`ok>PO94M% z<7%jMqQmbV-Q{9nob-PO4yeak7HAdMDP1ER){HrJ)>kVbgt&MSHw=+o2f+PkZ(VeOu!xr&8;UM4`Wsp3H-O25Dg zr9NwTG{>321;}V)tP%%$u-Hb4>>o0|=D&Xz`69a&(`-i6)og;yYqh2AM^YC7hPbPi z&d>grdW{rRCHzruGnnl>o0QN0{rd+Cg-+>hl$SaMy-oi{MU}a4MJ)qXzfDnGneKsE z)Ck$|>T0cw*`TE(kDvSJR|Z8Y`!)qJgs}oRqp|N@8_ER54vdy(OpTjWN9FaXG<)?O z$?6nEZcQVO?4qZWz8~~me6x<%poT2mKSCYV?E-58zXV5FY=y5e=k<@i^ls=+jeU)_ ztk)uj^Xiug@*ju`{qvKi05E1F^d_eltWE-q65%7%#2AETOMO+@h%WMPYFk%iO~?|1 z;Kh$ieeLi)j!cH0{x2Q}!cn8-XVmD9tn@*SHD@;ja=-|SY$#0kJuNl!wAjMfM0L@| zH|>5lGaIry{|twXhEvA0w*5Wyza8Wnb7#T2hhou?axL_knTV6LMiD&5ft=!FYW4Sw3ZDQ5)%<85-(K=^*L1o^kHL$TN#j9G|_Gy zqmG0oQeUMqmGlZf6&0W2peqvNSXa}@ZK|m#)O{1=n#ZDRkR0si5dyfvM3P|f%N6aK z&JhWvR^&=^r9Mo}tb*@?tx@NI0^WeOro!VUUf>rI-l=gEal%!1y); zaK$q_NmgNzj;5wZG3Ry~G?0aobO6unLed-}7vNsz3znrk@;=FtUAEg&=|07eoV3fG z&<{S2vJPPdtHsUps~-z;1!oi(-L~5)CGj*Kd$K7krX_Z9_#>$%`n3<wy78YEwAVKBpMxwfMIf@d5FtrIsTsDo^rt znciamd%HUG1mo<1XjSs+K(mDET=9Ky8jkv?1o)*-n@ZGNA^xfpm_?6BY@?oA5uRHt zds1BJ7Kw;l`+HaKXZ%jk`|FjaD8;JD6IX~jU@Xah!8tGumRmjU*VJpULAXcLx-GhJ zk1yi)H0z1c*Gbm?nJABseglIgcRv?P%)2}C^Q5c)HdnSpyjz|ia{gOxJg{Qa)+kDl zjFLyRC0rUbA}nICnAKO?1cS3XYG#oaNMa|3PuaV8EqVIg@@8+-%$&EezR%n2m2!Zs zZKV)1qs#*srxtn57Y2>ihh)6wO9dl5AH9o21h>O&UnMAfLZ<6&KBz5zO_7o;<0xD- zMdEkM=mAaPCYt+4ySOC+`J-TqQCq!WX(nVo`P0L5q0ooNdss}j?lj|7P2En6mwpU9 zjh?EfKl7ENCKz1B#ojQW(38fvHiZ6xD`d7|KT<^W*#9W;!Sj)-HhH1#T(7J)#fQUb z)?aFBgu<56JAOy~_lX(NpICuvizBkUC^-f9cHx_br&Yi4=5-hC`9TM-(~~x(Pe`7x zntVIcS=7(5(`p*}H34sZk@x$iXS`es!V}{EZF7CgRoGKZan8ue;NoXkj9-ZmV{Q}= zYyW;tBUFy{Vb@y^7AKimQ=Rr(usIrLQa}6zB?@FD_yS_n&v6Ei+(8~9qlRyzCXy%) zlnt3ijg(^||2jGM$vT#;iF_aHvBn45ih!-*WLY&Y30j$F`>^A#d|g4_h0|NJuq&H3 zT*1WN-P7^mLRE_I{uw09BF_O2+*{Rg#+n#xqyYmICISbtP@OKe`dw1Y$W!kkU**Geemjy1^K(Yn~$udJ{^_2BO=!N;?hjoovIVlT*WsO^&^1H4`-1*c<-(^$t ztGeW%m*u$n(b1fEO_gP>rXw%tFA7-XlIy%^{(D@GDw+jz7h2t-+JTX6{r%LqG9##g zx599FFW+luPeH_+SeYlCkJel#}nao}i@W1F4nD^%O zU2F+VwFipyxP+Nc8jGgP!SVLD`~}Uv-IR@-5q2!G6_2t0K3@+#=VqmN_z&m@Gl-J< zbbV+hsh%KS3()|!Yh6HHT;ygmH`P8>#1r`)i0l7V%H`F$odt^T!TBWtucF&wnH`~_ zavF)+f_UJ^$_#k;Et=$r@OU;CiALI<%?rHjWYg^V^7}0f-ms0eLV^VNt*0%(6m+@| zXgY@Y-CoXHl5#<>hH9PH6K*fOei_uN`R>m(VhKhE9i>~#fS3rG!@XDW&dEAfM07dL zR@mz5`H^dP@S`J#dZB+<<^9T`C_94BAS->ka`VJ$B& zQ>`9f9zVVU5K=_$Lmtk(kX{#slbUtv~Tfc3fRK1e}^)N8cRZmzxQC&lHIJIE|cvm%Zd3? z5ZiF^p7uU7I*LOHo|2EM)UYYXaV7XgBep&OX>$_glW(D+G)XBb&{#?9PKjn!yBr|j z2SIjNly~QX+_V+9-JpzO?^yCb&@=0P983YCbVlD?0$c$~g?%=l?DD_D#VhOHng}VI z^)H{+XMegX|J&q6!%2cyuhpv=6RxsrF2#4nz{(l{KDv_c>8!i*Rjw&=n<}MPKbhM| zRGd3mzC-Qn5!drs31jFutve&5FJ-Mh6DL(g}-13M*tzG!e96dn8W z8uZ(CAJ)&(rZMPxisFYv^Sn;<&uu{&{bagHfFnS|{$z+RT%6&*#C zP#33mq9=N7yzwD+q_s(A@M7!+M3dP)WrG$|)F`<#g*i8;&uLXt$ffjrR9u{nJFpnu zZ$kA8=iZd8)+%2=8NIx6Nktplu(7bPv_%a-fCSMI374+qWq>sW08jiF*!=j7w})|( zQ;UV;!(>6$v{DZF-YMH(A=H6Fke4kk)--UF<@LOt1A^`cpje)JNKL(iVwO!MLKS@N z0t)qU8(m^xR7;+mkf0X=CUpyRuj5p(`3)`}d8QMr2ZEV0LUHyI5>~^Q`Sn zQn6koWy$4ngD((o$DaeLlC4RJ_`3v%pFq`xTj#5GV`0pO2`e!x|b6per50b zaIFV?X8|Fhsmyg9^m0`+NqvSp{6(f0qO7b@cVzBBezZoccFUm*H9b;~WuKkv!>!f$ zMcjGHx6I$H)N*Pfj4aph_>#O$sFu~skyq2?eUyl3DV2hXZqlmr*bC%^`sHc6f`*!; z$Yd_)1E*Gbqt2;O#lTnE6NMPQ*8t=SXfTL&k#~z-D=74K+#oN{72`_4iG1S|I2YvuI(i4e;5O*rXg?{khJ#l5S!&@x z8=Q-n{dUJlzRB}<`iy4D5y7cvzKm2PhJ6k z(C5(M_QxPEa-R;%kCozs{1Mn(UphvdZLlp04z&64SNwWeymJlMa*c%vqlL6xDAYO0 z`*PXvzbP-21tzE=EGidmhk0H*A7l+;1}fe6=P;6nSmpDQuB3+ulqAKpiwV= z4frmCUN)DmJB>MYEAwNwyfFj(iU%QW7=`I2~n*te`!QvM?Zlt*-LN- zCOR~om$!2{pz7RRtj^@K*P7g6RS2067+Tm8>pZE|G zI-l&B#m?9`A;#6hA%YbnAM1CkkNyTCZ(U_(hmi_!>m`@6n}!ZuXeBb@Q;?U1SZFQD z$6fd(RuQWMA={~^i>4Tel$AhB{`RnA9>LY>9nWJv3t9G_q z42^nr?Eodi0^_vYb3Wu_3+_sEACF}{(1+sab_NWHn=W2o2SRu z-_dZlY0HFipm$lIy|&{kcg;Rs?dBRVMV{U25(67Ic?NlV!tiJ3j-#UdX6*KuDRZHAm3rkuZaZgO&7xtK+u{wd<1L#4 zjr_7MaJ@Oq->6tyTQfq}+Qq*819{qG`)VBjjLri=?N+I_leyyVo}yr?Zc`dIc^#EQ zd@ks|p}M`Do>m!8{Nl~&2I6ja3}FOQ%v1c+dPj5GW)a2wliyyp_cC$LbFC2w=WNe1 zWnkUajBdz9eK|KlqaUh8&(Vnn&?}DZ@XV)56|(Xx;U@-(7l|6V#*?|tk8-Ff#?=t; zL1#ZzW+&hUx3T`Qjv&I3l3&j;|CjPQt$y*)xh~7c1(%hE^nK0zu9Hv)t$1Qy`uoZ(&t_^CoM9(@N|Bld!a{yg>p^qJMgtEnBditssjugJw3!QZ1#T%cWKybh z%n`rt;POl;?B0DUtp7Q{)@h(mRr(6SAlBCZBFODsr8wm^{HV0ly>!Sq)92tX@)8_q zAt9J9aDR7i4=vuPZmu>miXZ2la^D)=)Kos{>g;5x1VgD@&n~bowT$_Ftso=7qi3ic z4zm0bZ??8wyDL&NK_cw6%WSsvP( z!q1iym3EAm(_q(!<85f?D&zx%t=efUv9H>PS&DCfXFAp z4IE@5SebII)9MMLSpQVNr6KIzy?dP>#(yQt@IGW>WYnB4HSM@=xT`?CF`nO-NKFB$ zdHr>upTHaDR1v(N$9A9Aj{TVln`~O&(rNQ_T)*Z@pH~JW`@c1)(tyE{H;!w8bZ7mw zHN}z#w7OZ`U5cG_u7~L)$Ez173bR1Q*uzW!ushhEObMp&Hg4J_j`$)C)cD<@1?aVSBV>6%H zeD6H7Ny8k^pHM#u^+vUtAN9FEqj?t8T$(FR4H9(ZP!^RVqGJJaS-`&u_a1dAN1=3Q5e`+SVPDUhh>XM0^xko4v8LdgCUR9}>uB5;NBc0bHqLd<$u<+l z-o$X)>Clg1$L}9)kC*t(*;mVWlqrJ(&uGl~*TXfRL5}Jfq_M1Qr@8XU5HS;Xd821u z)D>-|=O2 z_{xqn8B@eN8Hwg=O`QomrTw7{e{Q8~Qi+`7PbZr(6^VPRr>8dx>>E=LHfD^fb}*h9 z#t0b15fKt9*-w_O=Au&to(Z7BE!n_`7hGbP?CzypsR@G28X=3{gDJ0RlTvc)H@2pl^v3gYc*@5y4}yw(D6?|0 zfbpXXBu>-KtxAK9hP|)gZCo8;A6szG(IsW)?JNyP<>)w!Rg5O+`tV)jjlcA~`Cyt} zmUoVCuK2vJCZ>W*&v{*|oFJXaAB`g&`x-A>oT$NpZnT6yh4KljZrREq$jN-nEA+@Y zdxHZ5+dy7+aozC4l#<`})%K7Ko!wZo-)%$#N3T@!(Li1(w9j8E7$Yu{;}4rKA#+b% zW*3R%=6q37uUA$?gV-xQO0BRfPfsyFokZxoW|~7jm$r~>%C@egh1j6P)WxCN=efaw z9=fJ*r8ohy>ow|K+L6C+(N2!;^|O0Ja=!pTq-4UDzZJ}zJH581Dy&d*p}+dI3$^Ta z^HekU$VEI3W*@U@Otk4|9Goxho@}j@jKi<|GqL{sI7;rkh+VlYvY__X{OGTVUHLUq zkccl5_jDW%Zb*5L4-!gw?lm9>jwi{y;kcO3(vLwJBfU!~d2S=5k+PTX1e=2=uUv#Q zBO`ZADj%*Xu3+)S>I16{AYPN*4GJ9E6SLGujPH_c!O1auHr5)x9rYzJkTFlbLJ*Xj~St-f(Nl6e()iN28XNXY8H36T22seDTiy% ztw@t6H@PPIi98^fA{*f=2emKd1ZJm$Q&o&q4u{e zS&ul#W7ssOeo1;K3%QaWlCp?hYA@6+c>9^V!Nzq3kThzDH`zY|&U*S%s#*l>$Afv{(Dy%=-vygf8;12G$zX zx#W-aCkxAf9OT<0MkUbovW--=F`0l0zkK-dK;HlP08(ChS8vtUJHh++Ma7R@X!7S7iCw33!QH*Y%oV3P^`Axx@M5-^ZT$vi z!|rHawi)JZi&cfUxx}^4g-_Y)2v14fTW-x~dT#_9Zci_df-x_&9DT;$ZhocDhw{Ir z)FO=1FOYs0@ZDIg){GlxNxLdlA;uYZv%tPc&hnA+W+|09cPgS#yLrQ@788VY$zVbU z^!D{VFx?1S00zZ^DR5CF`9`zGryHf?Rqg`7(8SdInAEu0;9{eqH#N%g`ZY35BTw<2 z=C?CXJ_t=aSB`mCtyw+ZIfWBHn#(31N`Y%f)=1qFWIrUZIDYD>4KB7a+~@IHaITooH7=y&5?4sb36 zr)H_>=zrn@SneVR!rq5l4b!Fto91}i&e4##Z%uqNDXWpCAz?iNL@%Qc-oJ_H;H&r= zt!ZpfEgkc#`REW)cq~}G>a6Y2WcbdxGa&%p)~@NvJE*-(myN$x0Sab#S$VlAEOQ?C zyqOkj*~CU{x5N{*k#6`Xf<`P!B}JAVrOZA5$&KkcqGW}b)A6Jn@<73ZvK443b0^sk z^Gi-SZxihD393}uL;{yE#QQg)R02133h_KRsEcc0y)r$XB^R|Bw6E@A1-T{OFb!1p z!HA1)n)SW7a4t)!TwlaF+BxQ}?zk19!@%EFJtZetBS4L$0v)RhnAIUjXlc(H_nVi0 z3g6`P6DYNasK}z%KzMN5l_lN*!+&#QdY2bS1a`ETI!!j-p;{>9rZWBj7fVdf34ilft1OB3!@8Fz1D|++V+3#W$(H?>}K?xsEus+>PrD)kv z{r`P;!%`ccmT(Baez7ppG~vQ&xg^ZlOEu+&Xjj3$QueRZqk+}dGN*WCyas3i+HYUK zzPjvbZr-h$hQ3tf@DTKuxYggQl*cJ_`}XY4AKRm%h5J>|@bh|NR%~zHMX;2imt67^ z^ih12akK#hCZe7OVSN_m+ml-zKPZT2(L>&r)>f|3Jk{l3P`+^S$`i!((bo8;iBEyy zyXscYIkH(E3(G%!6}nl`X%iAAU(U9umZ@-0>XY)h$E}}$Jz~-3K~kUdqlCZknG^!6RxP`hie$bcVSZE?&Wd+y`JX4E>6@+GM$nd{@ESv zs?K`xB+^``YzMB)9PT{DKbBzFR>m)f1n%)Gq& zO=-EQMT551Q_RT0^!d)kQ-Y(5ze95clY(y`3Va^Fb^on)$NKe)KJ7U*%%Eo>p$g0H z^y*Rmj<7by_F5h?&7HGmcm1LUNHGsKLY@ygs3i`y;GY6c_r@;33;6=w$EvWHES29% zhW+8|&4LW^@%>5Tl=xC%@zV7jDnPu9evr&2EwzBZ#0jK8q0p!7l7YqRmb8(cVB6or zd#e_to|5FL)6Zyekks&^agA0O1F-WX_KBeyc9b$W{uEd~IimW|I3L*@dvnkp3u}$x z@JsS~Wl8ZiB}yP`v*0?4=f5B46XZoY7Ob%x`vSs*e?;n7_ZAxOr*YHx-F$n2M%%4Q zM122i(Ohy|IsQ|8nXm|2vo#p_SZCA-!ECv`FCNT*f;~dOVhfAp3GpslioWu^iAZ{0 zBmvE*=JGT2P$0Fh4KKfXf-mFOJw7d}1-4=Egi`*Tn{ zux?+$)JQqiaAv>jlRWS8MT7q0<6Z-YS8&bkMcQZJ1+kjsUA5{NPk|qy7#6otn)Yi2 zoHBw-&aPSFlkP1^I4j<-=OYmu1HFO@;R2kZq+4sWRPmp~`giXDsy`8Cqc5jzp0yIV zihj6okCaD<3m*G>*X^<2{p+xyyG@tkDAD9PucsU?KrM<*+5N}`eEAwCqnjgjSLprG zHP)YxAmzh7-vyHf?APX#&?U+`20^39G=OKv5a_`x0!*4t*sKzqed8zLvPBvi-J+hBiPLpMg&jpuZt)kdZ zJAtIppAzL1KXp4c89aY0tVSuPUri~>z2TNQG|^;aG*KZ|gm|WJ)T@%<9l1O3`((Vh z$aX%2=m`cmjHgUn z%Id{=C8Tx8GFG^ns1PW)VMaxB!c2`{p(+>cI*K81Iva^S5Ty5IJz`dO;INuaZ$b z;_uD09huodZYlCS$T@QJqbfO;T#r*1)pMoJVH4mFk^bVA9|tOEnWPl=SLn1hv# zx*91RR-GC?C94+~df7^b<-=1I)x^He_5j*O)<{}h;HtVd#J0FcF z-Y&tqelmNW?-u_I2U6?ABvr0s);br0{6pqt-diMzjKceJ;L}B+bndaMyLl^D)H~|} zO`2>$cr44S6^D2!w0aQJ_g>PWCqlO|PlXJ2r9cWJs zpsjat4Ku%olz4%gc4rO@QznY%fo~b}w{PE$?T>us{cf+f2`FF2QIV38DkBaGC7R`2 z@vleW_ciXPKZO9?SZZ=I@m6zKBSSJ(Y$Rx$XBqZABI(3=bA@aqTOFyiWZd)kE(9eV z3l#`cKwNCTz<*840yT&~eFMtV+H=&p-yuTVsQ9Xpz9bvmtcDdIhF-CCd^-{PbM(-7)?;MQGzMd@^!~DZTAZW#+2s07r3r+QmXC(buiTl<|1g(2&5vG}z2aBp%n@W!!{~Cd=viB4 zMFs;<%BhUk$~9xhs;!B%()DqS$LEIpjnXb|KCSqUxJTp8AYH@@A?3J;YmkgPJ^b{g z4N>pDFJsG}INM6*an2|o>n(BDF+f5*QGy8K2_!HcZyY0F^|P$o%bcq-C$ly)_UGFz znPrPQWoAMKB;EI!InBGzTrIP2zvxi#IhL@`P;yyq{!G%_PH2sO?OWsZ1JeNBF9=Mm ze2y~1#J*1hU}mGn7w-LXVD)4&U1@U~n|rxCS#G&;bA5TK_IieX(65-E=)ndCec0bQ z>+^H#!#0Nc-noxT(uEJE+`Wq$Dpul(#1q(Arp|L^>Mom1y@&dhtB z_p{vR-ursp*PV1yELR`)R#4Iy5%z0biUo#6ToAvj;fW*13JMG=%Yt?(?VgI(J*~cJ zI3?pcTJvHAsLR_Qv}LM|dVy}8t`RT3G?<#2YNwZRVn$wJw_HjMDR}72)J~hbFyK?)1U37#-k=orlRVy9L_p+pXO~aW`>`c$Y zwfvJny@*_t^Sjf1%|&kcN59bFBSg0Z>Rt)i)CS!o=J7VM!=&h{9Z!S|;P(1}gr}yW zN-cprcpFfXv5z2E?rFjz#8=Zo;}`EoCJZ;Kf4=@Cm>>JfKkOmoZ5_b{cRZ>8>$qg# zo9tU%d6<>$DWy9RamfpB)Yu9ZT3MXgM%QQ)c(0`Xt^duU;JN1Sgnxx5O?vUk-kMI- zjPr``P`m9J&@05n=U^WbVW7{GY91_-rB}P@8a|I9R&` z_!<}M<3Tx^o}}&dkvqK7sLFS1u#ax_bKTexGoS*8Du=O`VE{5>fC|xi?fgTF3#Fn- zXuwr=-H?2wS2-Sl?m-N~xV|aCV#h76*eI&wz{fOuG|IU>NPUIG&vzbHns~lVvc`^$K1I~$+C)xqm7--6cJ#Vug)-&VI18*I_Gjz z*6jHg)tFLO!^xp~^|EjK(t~?GCJsu8zrq+O))esHt{T5j=3|^}liF{^?Cp@rpP-Y~ zZl!Nt+oP7nzxfjj9FlIHbvo>sCPkRMb-s8#QNy>sa8Rxab7V~LcRi+J;u#w(ASv~l!V%l@N^ufC|O z$4y?M&$pFy9l2$gR~0|g@1eoIb!3&Nck_v)LgLB{QAJZzI7L+JshwT6at9gmydGnn z$+vcvpYyctyn`^^JxS6%yK&)J@zeg*9&@a>j3Q**X2y)@b>g#XCD%clA=pRB3D3CN z5|Ecv(Z~Fgn%an0K6v~`1WJI-E~6_&0pr_^cFNkoH z7vxDrB*`SLIVoyU_WlPQF4;M1qWW4Ltvcw~smMw7kPEgqU5&HpW}CCk)Qd3F5ywdG z>B9ogeF%$t+XQxwft>AJuO9z1N*~FARXUQbPYeH?ZZPT>vNG3nnW_Iy>gQ1ot1N`P z-6gMe8*P? z`E!YkZmjJp^#?vu(P}nR$2=@G?s@zUR+A-#$9}a;^ir-0+#R<2eibiNEpobY^&hB+ zz3-n3d1~e(tJEQd{uJ}T*zW-oDn0guECpRLn*k@tlBfOvI_D4loh1Gi+4tFIaa)b>Q`6A!!=cy{&?NU1%3XmwK~67%`8g*g z*8j^bIa{~jl%v3GiE7bXSl~1fxpF&E2ojci>+9vV|;+qXH6( zdb4+qj~^z&i{E6q4No$sU%7(2q74+8QEHi2v)>v{w$QFNVW3^(F<|3h8+-O1Jb2K6 z%ha}JVY$${Q<*5^Bb#iSO?InBIP_7Wr-hYt6*NY?j;){Voi@O=JHLc}JT&Wn#|*?` zjT~d0xfNs&+%)c&Iwd!j^eq%kRk+QqvLjET#`c;*HZ^g@4T_1rJ7FLBJ%29VNbPle zy`ncg$gVLH1k#%*WggAWa^CuQzKQ3+O}0znf4T9b`UlRW#s0OGfB@9A&-vD!@WaQ? z`1U{wG*3phf>Y}GV8i>fNy&j%&$Aw;J^F}TB{`K zN=zAOk}IZSO=Q2DW4G-D?|vjpc}Ips@~Uyd8xpH_2=Ac}=zre=m$^HWwBy^e9nwLp zV)V>9YjTuNtL5ei%?RO}8JbDYuuwPACp|0-T=b3H3c@xs@`}8E6@K7yaq3t+@+nAa znWF10$-l`Sb|TwoncstwX8U$g*)6d@1mJ5`r#2q;C~c&BM~bi&41d|4B!~X?o1Y?a zF~c#Bh{N=yh{s=!5@ed*1Ey1v!R(-#^e}O#%(cz%yV1%imdOi5tFM~+@r3Mq6L*12 zW>e02wzTocyOmMK80C?7;o*+6bN-Oy*#8RHl|8bxi0*UUQ(JT_f}YaNO-RDOw?$4% z!4o1+yOcPI$vST6B6TmE`c+xu;aBu4R9lLyx()TmyS;w(?70!jfUt9*NKL!;>Z_wL z+fAoY>`3c)>Eo_%;ldVGra^+Ic2)4BFJw)`_TF`~;=ZsugVfC(@}A~GegG%4NP19) zj;`*dIri~6e?emE!cg53>801LE@pXv^|~hB@Q8|vs?N>m8RdpWRmUao%h|b>_zO7y z0_H6B@C-hs0mUT}8+1dD@I%Px$7msxpSmOx5I#kmJ0bb)ORqX@t9{ZLF}qzjPTpnZ zu<;L~=xST!<&+%=dQ0bVAdueaW`T~I->JUp{Ti}riE!Tbm;Mzt3=0FasRB37K{y@L z^l_Dr`M+?j?m*pll+3UP@RAO0daute>E(|~QASa1yW90B6j`&oqfp0G$SWLrNshBB zjYn337Z65}|K?aoej?%YnKPgN!VaO)A8AfUh6lwRz4=n08{3gc$Z#y|E2(%0AwcTe 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 literal 0 HcmV?d00001 diff --git a/content/panorama/layout/custom_game/hud.xml b/content/panorama/layout/custom_game/hud.xml index 00a8794..5a128b4 100644 --- a/content/panorama/layout/custom_game/hud.xml +++ b/content/panorama/layout/custom_game/hud.xml @@ -1,15 +1,31 @@ + + + - + + + + + + + + diff --git a/content/panorama/styles/custom_game/hud.css b/content/panorama/styles/custom_game/hud.css index 70f6a22..671d30a 100644 --- a/content/panorama/styles/custom_game/hud.css +++ b/content/panorama/styles/custom_game/hud.css @@ -2,3 +2,57 @@ width: 100%; height: 100%; } + +#ExamplePanel { + width: 500px; + height: 500px; + /* Using a background image already in dota */ + background-image: url("s2r://panorama/images/compendium/international2020/compendium/dashboard_panel_bg_psd.vtex"); + + horizontal-align: center; + vertical-align: center; +} + +#ExamplePanel #BannerImage { + width: 200px; + height: 200px; + horizontal-align: center; + y: 40px; +} + +#ExamplePanel .ExampleTitle { + horizontal-align: center; + font-size: 30px; + y: 260px; +} + +#ExamplePanel .ExampleParagraph { + y: 320px; + margin-left: 30px; + margin-right: 30px; +} + +#ExamplePanel #CloseButton { + width: 100px; + height: 50px; + background-color: rgb(228, 228, 228); + horizontal-align: center; + vertical-align: bottom; + y: -30px; +} + +#ExamplePanel #CloseButton Label { + horizontal-align: center; + vertical-align: center; + color: black; +} + +#ExamplePanel #CloseButton:hover { + background-color: rgb(0, 135, 245); +} + +#ExamplePanel #CloseButton:hover Label { + color: white; +} + + diff --git a/src/common/events.d.ts b/src/common/events.d.ts index 4fe9c56..2ec3a82 100644 --- a/src/common/events.d.ts +++ b/src/common/events.d.ts @@ -13,13 +13,17 @@ // To declare an event for use, add it to this table with the type of its data interface CustomGameEventDeclarations { - example_event: ExampleEventData + example_event: ExampleEventData, + ui_panel_closed: UIPanelClosedEventData } // Define the type of data sent by the example_event event interface ExampleEventData { myNumber: number; myBoolean: boolean; - myString: string[]; + myString: string; myArrayOfNumbers: number[] -} \ No newline at end of file +} + +// This event has no data +interface UIPanelClosedEventData {} \ No newline at end of file diff --git a/src/panorama/hud.ts b/src/panorama/hud.ts index e65f177..b5eb3f5 100644 --- a/src/panorama/hud.ts +++ b/src/panorama/hud.ts @@ -1,10 +1,17 @@ $.Msg("Hud panorama loaded"); -GameEvents.Subscribe("my_custom_event", event => { - $.Msg("Received custom event", event); -}); +function OnCloseButtonClicked() { + $.Msg("Example close button clicked"); + + // Find panel by id + const examplePanel = $("#ExamplePanel"); -GameEvents.SendCustomGameEventToServer<{}>("ui_loaded", {}); + // Remove panel + examplePanel.DeleteAsync(0); + + // Send event to server + GameEvents.SendCustomGameEventToServer("ui_panel_closed", {}); +} GameEvents.Subscribe("example_event", (data: NetworkedData) => { const myNumber = data.myNumber; @@ -16,6 +23,8 @@ GameEvents.Subscribe("example_event", (data: NetworkedData) => const myArray = toArray(myArrayObject); // We can turn it back into an array ourselves. + $.Msg("Received example event", myNumber, myString, myBoolean, myArrayObject, myArray); + }); /** diff --git a/src/panorama/manifest.ts b/src/panorama/manifest.ts index fd64528..99113d3 100644 --- a/src/panorama/manifest.ts +++ b/src/panorama/manifest.ts @@ -1,10 +1 @@ -GameUI.SetDefaultUIEnabled(DotaDefaultUIElement_t.DOTA_DEFAULT_UI_ACTION_PANEL, false); -GameUI.SetDefaultUIEnabled(DotaDefaultUIElement_t.DOTA_DEFAULT_UI_ACTION_MINIMAP, false); -GameUI.SetDefaultUIEnabled(DotaDefaultUIElement_t.DOTA_DEFAULT_UI_INVENTORY_PANEL, false); -GameUI.SetDefaultUIEnabled(DotaDefaultUIElement_t.DOTA_DEFAULT_UI_INVENTORY_SHOP, false); -GameUI.SetDefaultUIEnabled(DotaDefaultUIElement_t.DOTA_DEFAULT_UI_FLYOUT_SCOREBOARD, false); -GameUI.SetDefaultUIEnabled(DotaDefaultUIElement_t.DOTA_DEFAULT_UI_TOP_BAR_BACKGROUND, false); -GameUI.SetDefaultUIEnabled(DotaDefaultUIElement_t.DOTA_DEFAULT_UI_TOP_HEROES, false); - -const hudRoot = $.GetContextPanel().GetParent()!.GetParent()!; -hudRoot.FindChildTraverse("quickstats")!.style.visibility = "collapse"; +$.Msg("ui manifest loaded"); \ No newline at end of file diff --git a/src/vscripts/GameMode.ts b/src/vscripts/GameMode.ts index 14147d1..ceff960 100644 --- a/src/vscripts/GameMode.ts +++ b/src/vscripts/GameMode.ts @@ -23,13 +23,33 @@ export class GameMode { constructor() { this.configure(); + + // Register event listeners for dota engine events ListenToGameEvent("game_rules_state_change", () => this.OnStateChange(), undefined); ListenToGameEvent("npc_spawned", event => this.OnNpcSpawned(event), undefined); + + // Register event listeners for events from the UI + CustomGameEventManager.RegisterListener("ui_panel_closed", (_, data) => { + print(`Player ${data.PlayerID} has closed their UI panel.`); + + // Respond by sending back an example event + const player = PlayerResource.GetPlayer(data.PlayerID)!; + CustomGameEventManager.Send_ServerToPlayer(player, "example_event", { + myNumber: 42, + myBoolean: true, + myString: "Hello!", + myArrayOfNumbers: [1.414, 2.718, 3.142] + }); + + // Also apply the panic modifier to the sending player's hero + const hero = player.GetAssignedHero(); + hero.AddNewModifier(hero, undefined, modifier_panic.name, { duration: 5 }); + }); } private configure(): void { - GameRules.SetCustomGameTeamMaxPlayers(DOTATeam_t.DOTA_TEAM_GOODGUYS, 3); - GameRules.SetCustomGameTeamMaxPlayers(DOTATeam_t.DOTA_TEAM_BADGUYS, 3); + GameRules.SetCustomGameTeamMaxPlayers(DotaTeam.GOODGUYS, 3); + GameRules.SetCustomGameTeamMaxPlayers(DotaTeam.BADGUYS, 3); GameRules.SetShowcaseTime(0); GameRules.SetHeroSelectionTime(heroSelectionTime); @@ -39,13 +59,13 @@ export class GameMode { const state = GameRules.State_Get(); // Add 4 bots to lobby in tools - if (IsInToolsMode() && state == DOTA_GameState.DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP) { + if (IsInToolsMode() && state == GameState.CUSTOM_GAME_SETUP) { for (let i = 0; i < 4; i++) { Tutorial.AddBot("npc_dota_hero_lina", "", "", false); } } - if (state === DOTA_GameState.DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP) { + if (state === GameState.CUSTOM_GAME_SETUP) { // Automatically skip setup in tools if (IsInToolsMode()) { Timers.CreateTimer(3, () => { @@ -55,7 +75,7 @@ export class GameMode { } // Start game once pregame hits - if (state === DOTA_GameState.DOTA_GAMERULES_STATE_PRE_GAME) { + if (state === GameState.PRE_GAME) { Timers.CreateTimer(0.2, () => this.StartGame()); } } @@ -76,11 +96,8 @@ export class GameMode { private OnNpcSpawned(event: NpcSpawnedEvent) { // After a hero unit spawns, apply modifier_panic for 8 seconds const unit = EntIndexToHScript(event.entindex) as CDOTA_BaseNPC; // Cast to npc since this is the 'npc_spawned' event + // Give all real heroes (not illusions) the meepo_earthbind_ts_example spell if (unit.IsRealHero()) { - Timers.CreateTimer(1, () => { - unit.AddNewModifier(unit, undefined, modifier_panic.name, { duration: 8 }); - }); - if (!unit.HasAbility("meepo_earthbind_ts_example")) { // Add lua ability to the unit unit.AddAbility("meepo_earthbind_ts_example"); diff --git a/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.ts b/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.ts index 6b9b7c9..1aa6cc2 100644 --- a/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.ts +++ b/src/vscripts/abilities/heroes/meepo/earthbind_ts_example.ts @@ -40,7 +40,7 @@ export class meepo_earthbind_ts_example extends BaseAbility { const radius = this.GetSpecialValueFor("radius"); this.particle = ParticleManager.CreateParticle( "particles/units/heroes/hero_meepo/meepo_earthbind_projectile_fx.vpcf", - ParticleAttachment_t.PATTACH_CUSTOMORIGIN, + ParticleAttachment.CUSTOMORIGIN, caster, ); @@ -57,9 +57,9 @@ export class meepo_earthbind_ts_example extends BaseAbility { fEndRadius: radius, Source: caster, bHasFrontalCone: false, - iUnitTargetTeam: DOTA_UNIT_TARGET_TEAM.DOTA_UNIT_TARGET_TEAM_NONE, - iUnitTargetFlags: DOTA_UNIT_TARGET_FLAGS.DOTA_UNIT_TARGET_FLAG_NONE, - iUnitTargetType: DOTA_UNIT_TARGET_TYPE.DOTA_UNIT_TARGET_NONE, + iUnitTargetTeam: UnitTargetTeam.NONE, + iUnitTargetFlags: UnitTargetFlags.NONE, + iUnitTargetType: UnitTargetType.NONE, vVelocity: (direction * projectileSpeed) as Vector, bProvidesVision: true, iVisionRadius: radius, @@ -77,9 +77,9 @@ export class meepo_earthbind_ts_example extends BaseAbility { location, undefined, radius, - DOTA_UNIT_TARGET_TEAM.DOTA_UNIT_TARGET_TEAM_ENEMY, - DOTA_UNIT_TARGET_TYPE.DOTA_UNIT_TARGET_BASIC | DOTA_UNIT_TARGET_TYPE.DOTA_UNIT_TARGET_HERO, - DOTA_UNIT_TARGET_FLAGS.DOTA_UNIT_TARGET_FLAG_NONE, + UnitTargetTeam.ENEMY, + UnitTargetType.BASIC | UnitTargetType.HERO, + UnitTargetFlags.NONE, 0, false, ); diff --git a/src/vscripts/lib/dota_ts_adapter.ts b/src/vscripts/lib/dota_ts_adapter.ts index b0d7c9a..30f9b23 100644 --- a/src/vscripts/lib/dota_ts_adapter.ts +++ b/src/vscripts/lib/dota_ts_adapter.ts @@ -85,17 +85,17 @@ export const registerModifier = (name?: string) => (modifier: new () => CDOTA_Mo } }; - let type = LuaModifierType.LUA_MODIFIER_MOTION_NONE; + let type = LuaModifierMotionType.NONE; let base = (modifier as any).____super; while (base) { if (base === BaseModifierMotionBoth) { - type = LuaModifierType.LUA_MODIFIER_MOTION_BOTH; + type = LuaModifierMotionType.BOTH; break; } else if (base === BaseModifierMotionHorizontal) { - type = LuaModifierType.LUA_MODIFIER_MOTION_HORIZONTAL; + type = LuaModifierMotionType.HORIZONTAL; break; } else if (base === BaseModifierMotionVertical) { - type = LuaModifierType.LUA_MODIFIER_MOTION_VERTICAL; + type = LuaModifierMotionType.VERTICAL; break; } @@ -105,6 +105,17 @@ export const registerModifier = (name?: string) => (modifier: new () => CDOTA_Mo LinkLuaModifier(name, fileName, type); }; +/** + * Use to expose top-level functions in entity scripts. + * Usage: registerEntityFunction("OnStartTouch", (trigger: TriggerStartTouchEvent) => { }); + */ +export function registerEntityFunction(name: string, f: (...args: any[]) => any) { + const [env] = getFileScope(); + env[name] = function (this: void, ...args: any[]) { + f(...args); + }; +} + function clearTable(table: object) { for (const key in table) { delete (table as any)[key]; diff --git a/src/vscripts/lib/timers.lua b/src/vscripts/lib/timers.lua index 442a4b8..14caf92 100644 --- a/src/vscripts/lib/timers.lua +++ b/src/vscripts/lib/timers.lua @@ -121,6 +121,13 @@ end setmetatable(BinaryHeap, {__call = function(self, key) return setmetatable({key=key}, self) end}) +function table.merge(input1, input2) + for i,v in pairs(input2) do + input1[i] = v + end + return input1 +end + TIMERS_THINK = 0.01 diff --git a/src/vscripts/modifiers/modifier_panic.ts b/src/vscripts/modifiers/modifier_panic.ts index 50845a2..67461f8 100644 --- a/src/vscripts/modifiers/modifier_panic.ts +++ b/src/vscripts/modifiers/modifier_panic.ts @@ -3,8 +3,8 @@ import { BaseModifier, registerModifier } from "../lib/dota_ts_adapter"; // Base speed modifier -- Could be moved to a separate file class ModifierSpeed extends BaseModifier { // Declare functions - DeclareFunctions(): modifierfunction[] { - return [modifierfunction.MODIFIER_PROPERTY_MOVESPEED_ABSOLUTE]; + DeclareFunctions(): ModifierFunction[] { + return [ModifierFunction.MOVESPEED_ABSOLUTE]; } GetModifierMoveSpeed_Absolute(): number { @@ -17,7 +17,7 @@ export class modifier_panic extends ModifierSpeed { // Set state CheckState(): Partial> { return { - [modifierstate.MODIFIER_STATE_COMMAND_RESTRICTED]: true, + [ModifierState.COMMAND_RESTRICTED]: true, }; } diff --git a/src/vscripts/tsconfig.json b/src/vscripts/tsconfig.json index 29622cf..11f40e1 100644 --- a/src/vscripts/tsconfig.json +++ b/src/vscripts/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "../../game/scripts/vscripts", "target": "esnext", "lib": ["esnext"], - "types": ["@moddota/dota-lua-types"], + "types": ["@moddota/dota-lua-types/normalized"], "plugins": [{ "transform": "@moddota/dota-lua-types/transformer" }], "moduleResolution": "node", "experimentalDecorators": true, From 8c6e6dc75feed84c5e2fd40e8c983c45c53a9748 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Fri, 2 Jul 2021 23:03:31 +0200 Subject: [PATCH 7/7] Fixed build script paths --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 27b0513..afecca0 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "postinstall": "node scripts/install.js", "launch": "node scripts/launch.js", "build": "run-p build:*", - "build:panorama": "tsc --project content/panorama/scripts/custom_game/tsconfig.json", - "build:vscripts": "tstl --project game/scripts/vscripts/tsconfig.json", + "build:panorama": "tsc --project src/panorama/tsconfig.json", + "build:vscripts": "tstl --project src/vscripts/tsconfig.json", "dev": "run-p dev:*", "dev:panorama": "tsc --project src/panorama/tsconfig.json --watch", "dev:vscripts": "tstl --project src/vscripts/tsconfig.json --watch"