diff --git a/.github/workflows/prettier-ci.yml b/.github/workflows/prettier-ci.yml new file mode 100644 index 0000000..8e47972 --- /dev/null +++ b/.github/workflows/prettier-ci.yml @@ -0,0 +1,23 @@ +name: Prettier CI + +on: + pull_request: + +jobs: + prettier: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + persist-credentials: false + + - name: Prettify code + uses: creyD/prettier_action@v4.3 + with: + dry: true + prettier_options: --check . diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..340cdcf --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Ignore markdown while issue is open: https://github.com/prettier/prettier/issues/5019 +*.md diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..fa89313 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "tabWidth": 4, + "printWidth": 100, + "proseWrap": "never" +} diff --git a/LICENSE b/LICENSE index e1fd273..6a6f3d8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,9 @@ MIT License -Copyright (c) 2023 +Copyright (c) 2024 -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 5816289..ef5a885 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,79 @@ -# OpenRCT2 TypeScript Plugin Template +# Inventions Manager for OpenRCT2 -A simple and minimal template for OpenRCT2 plugins using TypeScript based on [Basssiiie's OpenRCT2-Simple-Typescript-Template](https://github.com/Basssiiie/OpenRCT2-Simple-Typescript-Template) with a few changes to file content and structure. +An alternative to the built-in inventions list found under the cheat menu. Adds the ability to filter inventions by research group, move around inventions while preserving other inventions' order, and more! Designed for ease of use when creating scenarios. -Also supports: -- Automatic plugin reload in OpenRCT2 (hot reload) -- Out of the box minification to improve file sizes -- Support for external NPM packages (like FlexUI) +![Invention Manager startup window](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/startup.png?raw=true) -## How to start +## Getting the plugin -1. Install latest version of [Node](https://nodejs.org/en/) and make sure to include NPM and enable the "Add to PATH" option during installation. -2. Use the green "Use this template" button in the top right corner of this page, or download the project to a location of your choice on your PC. -3. Open a terminal or command prompt. -4. Use `cd` to change your current directory to the root folder of this project. -5. Run `npm ci` to install the project's dependencies. -6. Find `openrct2.d.ts` TypeScript API declaration file in OpenRCT2 files. copy it to `./lib/` folder. - - This file can usually be found in the [OpenRCT2 installation directory](#openrct2-installation-directory). - - Alternatively you can download the file from Github [here](https://raw.githubusercontent.com/OpenRCT2/OpenRCT2/develop/distribution/openrct2.d.ts). - - Another option is to make a symbolic link instead of copying the file, which will keep the file up to date whenever you install new versions of OpenRCT2. -7. In `./src/info.js`, change plugin info, such as name and author, to your liking. +Download the `.js` file from the [latest release](https://github.com/KatieZeldaKat/openrct2-invention-manager/releases/latest) and place it in the "plugin" folder. This can be found by opening OpenRCT2 and selecting "Open custom content folder" under the toolbox in the main menu. --- -## Commands +## Selecting an invention -The template comes with several terminal commands to make developing plugins easier. +If you hover over any item in the "available inventions" or "to be invented" lists, you will see a preview image and information about it. Simply click one to select it. Once selected, hovering over other inventions will no longer update the preview. To deselect an invention, click on it again. You may also click on another invention to select that instead. -`npm run build` +![Selecting and deselecting inventions](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/select-invention.gif?raw=true) -Creates a release build of your plugin. This version is optimized for sharing with others, using Terser to make the file as small as possible. By default, the plugin will be outputted to `./dist/`. +## Research group tabs -`npm run build:dev` +There are seven different research groups items are invented in: -Creates a develop build of your plugin. This version is not optimized for sharing, but easier to read in case you want to see the outputted Javascript. By default, the plugin will be outputted in the plugin folder of the default [OpenRCT2 user directory](#openrct2-user-directory). +1. Transport Rides +2. Gentle Rides +3. Roller Coasters +4. Thrill Rides +5. Water Rides +6. Shops & Stalls +7. Scenery & Theming -`npm start` or `npm run start` +This plugin organizes these research groups into tabs you can click on to filter which inventions are shown. There is also an "All" tab (designated by the red conical flask), which displays all inventions at once, similarly to the built-in inventions list. -Will start a script that will automatically run `npm run build:dev` every time you make a change to any Typescript or Javascript file inside the `./src/` folder. +![Cycle through research group tabs](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/research-group-tabs.gif?raw=true) -### Output paths +## Moving inventions around -The output paths can be changed in `rollup.config.js`, specifically in the `getOutput()` method. +The built-in invention list allows for click-and-drag to move around inventions. Plugin windows cannot do this as of yet. As such, there are buttons you can use to move them around and between lists. ---- - -## Access game logs +### Make invented/uninvented -When your plugin is not loading properly, it may be useful to be able to read the logs of the game to see if there are any errors. Furthermore, if you use the `console.log` function, the resulting logs can be read here as well. +To make an invented item move to the "to be invented" list, simply select it and click the big red down arrow. -### Windows +To make an item that is yet to be invented move to the "available inventions" list, simply select it and click the big red up arrow. -1. Navigate to the folder where [OpenRCT2 is installed](#openrct2-installation-directory). -2. Launch the `openrct2.com` file located there (the MS-DOS application). - - If file extensions are hidden, make sure to [enable them](https://support.microsoft.com/en-us/windows/common-file-name-extensions-in-windows-da4a4430-8e76-89c5-59f7-1cdbbc75cb01). +![Moving the Chip Shop up and down with the red arrows](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/chips-invent-uninvent.gif?raw=true) -### MacOS +There are also buttons which let you invent all items or uninvent all items. This only affects whatever tab you are in. For example, clicking either button in the tab for Thrill Rides will only affect this research category. -1. Launch a terminal or another command-line prompt. -2. Using the `cd` command, navigate to the folder where [OpenRCT2 is installed](#openrct2-installation-directory). -3. Run `open OpenRCT2.app/Contents/MacOS/OpenRCT2` to launch OpenRCT2 with logging enabled. +![Inventing and uninventing all Thrill Rides](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/invent-uninvent-all.gif?raw=true) -### Linux +### Change invention order -1. Launch a terminal or another command-line prompt. -2. Using the `cd` command, navigate to the folder where [OpenRCT2 is installed](#openrct2-installation-directory). -3. Run `./openrct2` to launch OpenRCT2 with logging enabled. +For simplicity, the "available inventions" list's order is locked. Inventions are grouped by research group, then sorted alphabetically. This was done to simplify searching for a given invention, as the order of invented items has no impact on gameplay. ---- +As for inventions "to be invented", you have full control. After selecting an invention, use the small arrows to move the item up or down in the list. -## Hot reload +![Moving the Twist invention up and down](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/change-order.gif?raw=true) -This project supports the [OpenRCT2 hot reload feature](https://github.com/OpenRCT2/OpenRCT2/blob/master/distribution/scripting.md#writing-scripts) for development. +When in the "All" tab, moving inventions up and down will behave the same as the built-in inventions list. However, when in any of the other tabs, the inventions are modified differently. Say there are two "Shops & Stalls" to be invented: a Chip Shop and a Cash Machine. Note the positions in the overall list: -1. Navigate to your [OpenRCT2 user directory](#openrct2-user-directory) and open the `config.ini` file. -2. Enable hot reload by setting `enable_hot_reloading = true` in the file. -3. Run `npm start` in the directory of this project to start the hot reload server. -4. Start the OpenRCT2 and load a save or start a new game. -5. Each time you save any of the files in `./src/`, the server will compile `./src/registerPlugin.ts` and place compiled plugin file inside your local OpenRCT2 plugin directory. -6. OpenRCT2 will notice file changes and it will reload the plugin. - ---- +![The "to be invented" list with the Chip Shop and Cash Machine highlighted, with other inventions between and around them](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/shops-stalls-before.png?raw=true) -## Folders +Now, going to the "Shops & Stalls" tab, we can swap the order these are invented in: -### OpenRCT2 installation directory +![The Chip Shop is swapped to be invented after the Cash Machine](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/shops-stalls-swap.gif?raw=true) -This is the directory where the game is installed. +After we make this change, the Cash Machine is now set to be invented before the Chip Shop while preserving the rest of the invention order: -- **Windows:** usually `C:/Users//Documents/OpenRCT2/bin/` when using the launcher or `C:/Program Files/OpenRCT2/` when an installer was used. -- **MacOS:** the folder where the `OpenRCT2.app` application file was placed. -- **Linux:** depends on the distro, but likely either `/usr/share/openrct2` when installed through a package manager, or mounted in `/tmp` when using an AppImage. +![The "to be invented" list again with Chip Shop and Cash Machine highlighted, but swapped in order from before](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/shops-stalls-after.png?raw=true) -### OpenRCT2 user directory +## Shuffle -This is the directory where the game stores user data, like save games and plugins. - -- **Windows:** usually `Documents/OpenRCT2/` or `C:/Users//Documents/OpenRCT2/`. -- **MacOS:** usually `/Users//Library/Application Support/OpenRCT2/`. Note that `Library` is a hidden folder in your user directory, so by default it will not show up in Finder. -- **Linux:** usually `/home//.config`, `$HOME/.config`, or where the environment variable `$XDG_CONFIG_HOME` points to if it's set. - -You can also open this folder from inside OpenRCT2, by selecting "Open custom content folder" in the dropdown under the red toolbox in the main menu. - ---- +In every tab other than the "All" tab, shuffling behaves as expected. Upon clicking the button, all inventions "to be invented" will shuffle around into a random order. As mentioned before, the "available inventions" have a set order, and as such, they will not shuffle. -## Dependencies +![Shuffling the Roller Coasters tab](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/shuffle-rollercoasters.gif?raw=true) -The following libraries and tools are used in this template: +Upon clicking the button in the "All" tab, the inventions are shuffled such that the order of inventions within the research groups are preserved. This can be useful to make a random order of inventions while still ensuring certain inventions are researched before others (like a Virginia Reel always being researched before a Multi-Dimensional Coaster). -- **NodeJS** is the JavaScript engine used to develop and run code when the game is not running. -- **NPM** is a library and package manager for JavasScript and TypeScript and can be used to install new packages and update existing packages in the project. -- **TypeScript** is a expansion language to JavaScript that adds type checking when you are writing the code. It allows you to specify rules for how objects and values look like, so TypeScript can report back if your code follows these rules (instead of crashes or errors in-game). -- **Rollup** bundles all source code, runs it through some plugins like TypeScript, and then outputs a single JavaScript plugin file. -- **Nodemon** is the program that can watch a folder for changes and then trigger a specified action. It is used by `npm start` to watch the `./src/` folder and triggers `npm run build:dev` if any changes occur. +![Shuffling the "All" tab and showing the preserved order of Roller Coasters](https://github.com/KatieZeldaKat/openrct2-invention-manager/blob/v1.0.0/docs/images/shuffle-all.gif?raw=true) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..d0bc95f --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,116 @@ +# Contributing to this plugin + +Feel free to contribute through issues and/or pull requests. I will try my best to address them in a timely manner, though I do not promise any indefinite support. + +The instructions below are taken from [Basssiiie's Typescript Template](https://github.com/Basssiiie/OpenRCT2-Simple-Typescript-Template), though the template is slightly modified from the original. + +## How to start + +1. Install dependencies using `npm` +2. Clone this repository to your computer through git. +3. Open a terminal or command prompt. +4. Use `cd` to change your current directory to the root folder of this project. +5. Run `npm ci` to install the project's dependencies. +6. Place `openrct2.d.ts` in the project's files [as described here](./lib/put-openrct2.d.ts-here.md). + +--- + +## Dependencies + +The following libraries and tools are used in this template: + +- **NodeJS** is the JavaScript engine used to develop and run code when the game is not running. +- **NPM** is a library and package manager for JavasScript and TypeScript and can be used to install new packages and update existing packages in the project. +- **TypeScript** is a expansion language to JavaScript that adds type checking when you are writing the code. It allows you to specify rules for how objects and values look like, so TypeScript can report back if your code follows these rules (instead of crashes or errors in-game). +- **Rollup** bundles all source code, runs it through some plugins like TypeScript, and then outputs a single JavaScript plugin file. +- **Nodemon** is the program that can watch a folder for changes and then trigger a specified action. It is used by `npm start` to watch the `./src/` folder and triggers `npm run build:dev` if any changes occur. +- **Prettier** is the code formatter used to automatically clean up code to a specific format. This lets the entire repository be consistent and easier to maintain. Any code must be ran through this before it is approved to go into the repository. +- **FlexUI** allows for flexible windows that can be easily extended. Due to the complicated nature of the Invention Manager window, this project wouldn't be possible without this library by [Basssiiie](https://github.com/Basssiiie). + +## Commands + +Multiple commands are detailed below to help with development. They output files to different directories, which can be changed in `rollup.config.js`. Be sure to not commit any changes you should make to the output paths when collaborating with others. + +### Create release build + +`npm run build` + +This version is optimized for sharing with others, using Terser to make the file as small as possible. By default, the plugin will be outputted to `./dist/`. + +### Create dev build + +`npm run build:dev` + +This version is not optimized for sharing, but easier to read in case you want to see the outputted Javascript. By default, the plugin will be outputted in the plugin folder of the default [OpenRCT2 user directory](#openrct2-user-directory). + +### Run script to automatically create dev builds + +`npm start` or `npm run start` + +Will start a script that will automatically run `npm run build:dev` every time you make a change to any Typescript or Javascript file inside the `./src/` folder. + +### Format code using Prettier + +`npx prettier --write .` + +Formats all files in accordance with [Prettier](https://prettier.io/). Alternatively, you could use an extension compatable with your code editor. Just ensure that it is pointing to the configuration file (`.prettierrc.json`) and the ignore file (`.prettierignore`). + +--- + +## Access game logs + +When your plugin is not loading properly, it may be useful to be able to read the logs of the game to see if there are any errors. Furthermore, if you use the `console.log` function, the resulting logs can be read here as well. + +### Windows + +1. Navigate to the folder where [OpenRCT2 is installed](#openrct2-installation-directory). +2. Launch the `openrct2.com` file located there (the MS-DOS application). + + - If file extensions are hidden, make sure to [enable them](https://support.microsoft.com/en-us/windows/common-file-name-extensions-in-windows-da4a4430-8e76-89c5-59f7-1cdbbc75cb01). + +### MacOS + +1. Launch a terminal or another command-line prompt. +2. Using the `cd` command, navigate to the folder where [OpenRCT2 is installed](#openrct2-installation-directory). +3. Run `open OpenRCT2.app/Contents/MacOS/OpenRCT2` to launch OpenRCT2 with logging enabled. + +### Linux + +1. Launch a terminal or another command-line prompt. +2. Using the `cd` command, navigate to the folder where [OpenRCT2 is installed](#openrct2-installation-directory). +3. Run `./openrct2` to launch OpenRCT2 with logging enabled. + +--- + +## Hot reload + +This project supports the [OpenRCT2 hot reload feature](https://github.com/OpenRCT2/OpenRCT2/blob/master/distribution/scripting.md#writing-scripts) for development. + +1. Navigate to your [OpenRCT2 user directory](#openrct2-user-directory) and open the `config.ini` file. +2. Enable hot reload by setting `enable_hot_reloading = true` in the file. +3. Run `npm start` in the directory of this project to start the hot reload server. +4. Start the OpenRCT2 and load a save or start a new game. +5. Each time you save any of the files in `./src/`, the server will compile `./src/registerPlugin.ts` and place compiled plugin file inside your local OpenRCT2 plugin directory. +6. OpenRCT2 will notice file changes and it will reload the plugin. + +--- + +## Folders + +### OpenRCT2 installation directory + +This is the directory where the game is installed. + +- **Windows:** usually `C:/Users//Documents/OpenRCT2/bin/` when using the launcher or `C:/Program Files/OpenRCT2/` when an installer was used. +- **MacOS:** the folder where the `OpenRCT2.app` application file was placed. +- **Linux:** depends on the distro, but likely either `/usr/share/openrct2` when installed through a package manager, or mounted in `/tmp` when using an AppImage. + +### OpenRCT2 user directory + +This is the directory where the game stores user data, like save games and plugins. + +- **Windows:** usually `Documents/OpenRCT2/` or `C:/Users//Documents/OpenRCT2/`. +- **MacOS:** usually `/Users//Library/Application Support/OpenRCT2/`. Note that `Library` is a hidden folder in your user directory, so by default it will not show up in Finder. +- **Linux:** usually `/home//.config`, `$HOME/.config`, or where the environment variable `$XDG_CONFIG_HOME` points to if it's set. + +You can also open this folder from inside OpenRCT2, by selecting "Open custom content folder" in the dropdown under the red toolbox in the main menu. diff --git a/docs/images/change-order.gif b/docs/images/change-order.gif new file mode 100644 index 0000000..58d9a18 Binary files /dev/null and b/docs/images/change-order.gif differ diff --git a/docs/images/chips-invent-uninvent.gif b/docs/images/chips-invent-uninvent.gif new file mode 100644 index 0000000..0214276 Binary files /dev/null and b/docs/images/chips-invent-uninvent.gif differ diff --git a/docs/images/invent-uninvent-all.gif b/docs/images/invent-uninvent-all.gif new file mode 100644 index 0000000..5c67094 Binary files /dev/null and b/docs/images/invent-uninvent-all.gif differ diff --git a/docs/images/research-group-tabs.gif b/docs/images/research-group-tabs.gif new file mode 100644 index 0000000..9ef1f8e Binary files /dev/null and b/docs/images/research-group-tabs.gif differ diff --git a/docs/images/select-invention.gif b/docs/images/select-invention.gif new file mode 100644 index 0000000..c5cbf46 Binary files /dev/null and b/docs/images/select-invention.gif differ diff --git a/docs/images/shops-stalls-after.png b/docs/images/shops-stalls-after.png new file mode 100644 index 0000000..4ea836e Binary files /dev/null and b/docs/images/shops-stalls-after.png differ diff --git a/docs/images/shops-stalls-before.png b/docs/images/shops-stalls-before.png new file mode 100644 index 0000000..3ca6e7e Binary files /dev/null and b/docs/images/shops-stalls-before.png differ diff --git a/docs/images/shops-stalls-swap.gif b/docs/images/shops-stalls-swap.gif new file mode 100644 index 0000000..9f47cc6 Binary files /dev/null and b/docs/images/shops-stalls-swap.gif differ diff --git a/docs/images/shuffle-all.gif b/docs/images/shuffle-all.gif new file mode 100644 index 0000000..7552719 Binary files /dev/null and b/docs/images/shuffle-all.gif differ diff --git a/docs/images/shuffle-rollercoasters.gif b/docs/images/shuffle-rollercoasters.gif new file mode 100644 index 0000000..d20e938 Binary files /dev/null and b/docs/images/shuffle-rollercoasters.gif differ diff --git a/docs/images/startup.png b/docs/images/startup.png new file mode 100644 index 0000000..a1facf5 Binary files /dev/null and b/docs/images/startup.png differ diff --git a/lib/put-openrct2.d.ts-here.md b/lib/put-openrct2.d.ts-here.md index a62ba56..39c615a 100644 --- a/lib/put-openrct2.d.ts-here.md +++ b/lib/put-openrct2.d.ts-here.md @@ -1,7 +1,3 @@ -Put the `openrct2.d.ts` file in this folder. +Put the `openrct2.d.ts` file in this folder. Currently targeting API version 94 (v0.4.12): -You can take it from a OpenRCT2 installation or download it from here: - -https://raw.githubusercontent.com/OpenRCT2/OpenRCT2/master/distribution/openrct2.d.ts - -For more information, please read the [README.md](../README.md). +https://raw.githubusercontent.com/OpenRCT2/OpenRCT2/v0.4.12/distribution/openrct2.d.ts diff --git a/package-lock.json b/package-lock.json index ed2d664..42af3f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,823 +1,847 @@ { - "name": "openrct2-typescript-plugin", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "openrct2-typescript-plugin", - "version": "1.0.0", - "license": "MIT", - "devDependencies": { - "@rollup/plugin-node-resolve": "^15.0.1", - "@rollup/plugin-terser": "^0.4.0", - "@rollup/plugin-typescript": "^11.0.0", - "nodemon": "^3.0.1", - "rollup": "^3.15.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", - "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-terser": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", - "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", - "dev": true, - "dependencies": { - "serialize-javascript": "^6.0.1", - "smob": "^1.0.0", - "terser": "^5.17.4" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-typescript": { - "version": "11.1.5", - "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.5.tgz", - "integrity": "sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.14.0||^3.0.0||^4.0.0", - "tslib": "*", - "typescript": ">=3.7.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - }, - "tslib": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", - "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/nodemon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", - "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/smob": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz", - "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/terser": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", - "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } + "name": "openrct2-typescript-plugin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "openrct2-typescript-plugin", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "openrct2-flexui": "^0.1.0-prerelease.19" + }, + "devDependencies": { + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-terser": "^0.4.0", + "@rollup/plugin-typescript": "^11.0.0", + "nodemon": "^3.0.1", + "prettier": "3.2.5", + "rollup": "^3.15.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.5.tgz", + "integrity": "sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", + "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nodemon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", + "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/openrct2-flexui": { + "version": "0.1.0-prerelease.19", + "resolved": "https://registry.npmjs.org/openrct2-flexui/-/openrct2-flexui-0.1.0-prerelease.19.tgz", + "integrity": "sha512-XiSA1mR/4oWl5NLR1T20jSoiXiyv5tEl2S21jJ65/muKQgNLGROFw5KwZzxqAFCzzuRwG3mD7gwGlF+wptjvJQ==" + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smob": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz", + "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terser": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } } diff --git a/package.json b/package.json index 9fc81d5..72ba081 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,23 @@ { - "name": "openrct2-typescript-plugin", - "version": "1.0.0", - "type": "module", - "license": "MIT", - "scripts": { - "start": "nodemon --watch ./src --ext js,ts --exec \"npm run build:dev\"", - "build": "rollup --config rollup.config.js --environment BUILD:production", - "build:dev": "rollup --config rollup.config.js" - }, - "devDependencies": { - "@rollup/plugin-node-resolve": "^15.0.1", - "@rollup/plugin-terser": "^0.4.0", - "@rollup/plugin-typescript": "^11.0.0", - "nodemon": "^3.0.1", - "rollup": "^3.15.0", - "tslib": "^2.5.0" - } + "name": "openrct2-typescript-plugin", + "version": "1.0.0", + "type": "module", + "license": "MIT", + "scripts": { + "start": "nodemon --watch ./src --ext js,ts --exec \"npm run build:dev\"", + "build": "rollup --config rollup.config.js --environment BUILD:production", + "build:dev": "rollup --config rollup.config.js" + }, + "devDependencies": { + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-terser": "^0.4.0", + "@rollup/plugin-typescript": "^11.0.0", + "nodemon": "^3.0.1", + "prettier": "3.2.5", + "rollup": "^3.15.0", + "tslib": "^2.5.0" + }, + "dependencies": { + "openrct2-flexui": "^0.1.0-prerelease.19" + } } diff --git a/rollup.config.js b/rollup.config.js index 7ce7ba3..098871c 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,19 +6,17 @@ import { homedir } from "os"; import { promisify } from "util"; import { name, version } from "./src/info.js"; +const options = { + /** + * The filename of the output, taken from info.js + */ + filename: `${name}-v${version}.js`, -const options = -{ - /** - * The filename of the output, taken from info.js - */ - filename: `${name}-v${version}.js`, - - /** - * Determines in what build mode the plugin should be build. The default here takes - * from the environment (ex. CLI arguments) with "development" as fallback. - */ - build: process.env.BUILD || "development" + /** + * Determines in what build mode the plugin should be build. The default here takes + * from the environment (ex. CLI arguments) with "development" as fallback. + */ + build: process.env.BUILD || "development", }; /** @@ -32,60 +30,57 @@ const options = * > git update-index --no-skip-worktree rollup.config.js * ``` */ -async function getOutput() -{ - if (options.build !== "development") - { - return `./dist/${options.filename}`; - } +async function getOutput() { + if (options.build !== "development") { + return `./dist/${options.filename}`; + } - const platform = process.platform; - const pluginPath = `OpenRCT2/plugin/${options.filename}`; + const platform = process.platform; + const pluginPath = `OpenRCT2/plugin/${options.filename}`; - if (platform === "win32") // Windows - { - const { stdout } = await promisify(exec)("powershell -command \"[Environment]::GetFolderPath('MyDocuments')\""); - return `${stdout.trim()}/${pluginPath}`; - } - else if (platform === "darwin") // MacOS - { - return `${homedir()}/Library/Application Support/${pluginPath}`; - } - else // Linux - { - const configFolder = process.env.XDG_CONFIG_HOME || `${homedir()}/.config`; - return `${configFolder}/${pluginPath}`; - } + if (platform === "win32") { + // Windows + const { stdout } = await promisify(exec)( + "powershell -command \"[Environment]::GetFolderPath('MyDocuments')\"", + ); + return `${stdout.trim()}/${pluginPath}`; + } else if (platform === "darwin") { + // MacOS + return `${homedir()}/Library/Application Support/${pluginPath}`; + } // Linux + else { + const configFolder = process.env.XDG_CONFIG_HOME || `${homedir()}/.config`; + return `${configFolder}/${pluginPath}`; + } } - /** * @type {import("rollup").RollupOptions} */ const config = { - input: "./src/plugin.ts", - output: { - file: await getOutput(), - format: "iife", - compact: true - }, - treeshake: "smallest", - plugins: [ - resolve(), - typescript(), - terser({ - compress: { - passes: 5, - toplevel: true, - unsafe: true - }, - format: { - comments: false, - quote_style: 1, - wrap_iife: true, - beautify: (options.build === "development"), - } - }) - ] + input: "./src/plugin.ts", + output: { + file: await getOutput(), + format: "iife", + compact: true, + }, + treeshake: "smallest", + plugins: [ + resolve(), + typescript(), + terser({ + compress: { + passes: 5, + toplevel: true, + unsafe: true, + }, + format: { + comments: false, + quote_style: 1, + wrap_iife: true, + beautify: options.build === "development", + }, + }), + ], }; export default config; diff --git a/src/helpers/arrayExtensions.ts b/src/helpers/arrayExtensions.ts new file mode 100644 index 0000000..e8e6c6d --- /dev/null +++ b/src/helpers/arrayExtensions.ts @@ -0,0 +1,63 @@ +/** + * Meant to be passed as an argument to the built-in Array.sort() method. Doing so will sort + * the array of numbers from least to greatest, instead of treating the numbers like strings. + */ +export function sortLeastToGreatest(first: number, second: number) { + return first - second; +} + +/** + * Searches through an array until a specified condition is met (as an alternative + * to the built-in way of searching for a specific element in an array). + * @param array The array to search through. + * @param condition The condition to be met by the item for it to be found. + * @param startIndex The index the search should begin at. + * @returns The first index of a matching item, or -1 if not found. + */ +export function indexOf( + array: T[], + condition: (item: T) => boolean, + startIndex: number = 0, +): number { + for (let index = startIndex; index < array.length; index++) { + if (condition(array[index])) { + return index; + } + } + + return -1; +} + +/** + * Durstenfeld's version of the Fisher-Yates shuffle algorithm. Shuffles an array in O(n). + * @param array The array to shuffle (modified by reference). + * @param start The index of the first element to shuffle; by default the beginning of the array. + * @param end The index after the last element to shuffle; by default the length of the array. + * @returns A reference to the same array; the parameter array is shuffled in place. + */ +export function shuffle(array: T[], start?: number, end?: number): T[] { + start = start ?? 0; + end = end ?? array.length; + + for (let index = end - 1; index > start; index--) { + let chosenIndex = randomInteger(start, index + 1); + + // Swap the values between the current and chosen indicies + let tempItem = array[index]; + array[index] = array[chosenIndex]; + array[chosenIndex] = tempItem; + } + + return array; +} + +/** + * Generates a random integer in a given range. + * @param min The minimum value to generate (inclusive). + * @param max The maximum value to generate (exclusive). + * @returns An integer between the specified min and max values. + */ +function randomInteger(min: number, max: number): number { + let rangeSize = max - min; + return Math.floor(rangeSize * Math.random() + min); +} diff --git a/src/helpers/inventions.ts b/src/helpers/inventions.ts new file mode 100644 index 0000000..8b55aa9 --- /dev/null +++ b/src/helpers/inventions.ts @@ -0,0 +1,138 @@ +import { WritableStore, compute, store } from "openrct2-flexui"; +import { Invention } from "../objects/Invention"; +import { indexOf, sortLeastToGreatest } from "./arrayExtensions"; + +export const categories = [ + "transport", + "gentle", + "rollercoaster", + "thrill", + "water", + "shop", + "scenery", +]; + +const inventions: WritableStore = store([]); +const categorizedInventions: { + [category: string]: { [invented: string]: WritableStore }; +} = { + all: { true: store([]), false: store([]) }, + transport: { true: store([]), false: store([]) }, + gentle: { true: store([]), false: store([]) }, + rollercoaster: { true: store([]), false: store([]) }, + thrill: { true: store([]), false: store([]) }, + water: { true: store([]), false: store([]) }, + shop: { true: store([]), false: store([]) }, + scenery: { true: store([]), false: store([]) }, +}; + +const inventionObjects: { [identifier: string]: Invention } = {}; + +export function initialize() { + inventions.subscribe((inventions) => { + const invented = inventions + .filter((invention) => invention.invented) + .sort(Invention.compare); + const uninvented = inventions.filter((invention) => !invention.invented); + + categories.concat("all").forEach((category) => { + categorizedInventions[category].true.set(filterCategory(invented, category)); + categorizedInventions[category].false.set(filterCategory(uninvented, category)); + }); + + const inventedResearch = invented.map((invention) => invention.researchItem); + const uninventedResearch = uninvented.map((invention) => invention.researchItem); + + // Must be set twice due to a race condition. If an object is removed from the list + // before being added to the other, it creates a duplicate entry. Setting twice ensures + // this object is removed fully from one list and added to the other. + park.research.inventedItems = inventedResearch; + park.research.uninventedItems = uninventedResearch; + park.research.inventedItems = inventedResearch; + }); + + context.subscribe("interval.day", load); +} + +/** + * Takes the data from `park.research` and imports it into the locally tracked data. + * Should be called frequently enough to ensure data is not mismatched through regular play + * and/or adding new objects to the park. + */ +export function load() { + const invented = park.research.inventedItems + .map((item) => getInvention(item, true)) + .sort(Invention.compare); + const uninvented = park.research.uninventedItems.map((item) => { + return getInvention(item, false); + }); + + inventions.set(invented.concat(uninvented)); +} + +/** + * Gets a list of inventions that meet the given criteria. + * @param category The research category the items pertain to. + * @param invented Whether to get invented or uninvented items (true for invented). + * @returns A list of inventions. + */ +export function get(category: "all" | "scenery" | RideResearchCategory, invented: boolean) { + return categorizedInventions[category][String(invented)].get().concat(); +} + +/** + * Executes `compute` from FlexUI on the invention list. + * @param category The research category the items pertain to. + * @param invented Whether to get invented or uninvented items (true for invented). + * @param callback The callback to execute when the list updates. + * @returns A `Bindable` store to use with supporting FlexUI elements. + */ +export function computeInventions( + category: "all" | "scenery" | RideResearchCategory, + invented: boolean, + callback: (value: Invention[]) => T, +): WritableStore { + return compute(categorizedInventions[category][String(invented)], callback); +} + +/** + * Updates invention data to pass to UI elements. + * @param updated A list of inventions that have been updated and/or reordered. + */ +export function update(...updated: Invention[]) { + // Get where all the inventions already exist in the array + const inventionsCopy = inventions.get().concat(); + const indicies = updated + .map((updatedInvention) => + indexOf( + inventionsCopy, + (invention) => invention.identifier === updatedInvention.identifier, + ), + ) + .sort(sortLeastToGreatest); + + // Keep the order of the updated array by iterating through the inventionsCopy sequentially + updated.forEach((updatedInvention, index) => { + inventionsCopy[indicies[index]] = updatedInvention; + }); + + // Update the inventions + inventions.set(inventionsCopy); + load(); +} + +function getInvention(researchItem: ResearchItem, invented: boolean): Invention { + const type: ObjectType = researchItem.category === "scenery" ? "scenery_group" : "ride"; + const identifier = objectManager.getObject(type, researchItem.object).identifier; + + inventionObjects[identifier] ??= new Invention(researchItem); + inventionObjects[identifier].invented = invented; + + return inventionObjects[identifier]; +} + +function filterCategory(list: Invention[], category: string): Invention[] { + return list.filter( + category === "all" ? (_) => true : (invention) => invention.category === category, + ); +} diff --git a/src/info.js b/src/info.js index db84f1b..b6c5d08 100644 --- a/src/info.js +++ b/src/info.js @@ -1,14 +1,12 @@ -export const name = "name-of-your-plugin"; -export const authors = [ "Your name" ]; +export const name = "invention-manager"; +export const authors = ["Katherine Norton (KatieZeldaKat)"]; export const license = "MIT"; export const version = "1.0.0"; export const type = "remote"; /** - * The following field determines which OpenRCT2 API version to use. It's best to always target - * the latest release version, unless you want to use specific versions from a newer develop - * version. Version 78 equals the v0.4.6 release. - * @see https://github.com/OpenRCT2/OpenRCT2/blob/v0.4.6/src/openrct2/scripting/ScriptEngine.h#L50 + * Version 94 equals the v0.4.12 release. + * @see https://github.com/OpenRCT2/OpenRCT2/blob/v0.4.12/src/openrct2/scripting/ScriptEngine.h#L50 */ -export const targetApiVersion = 78; +export const targetApiVersion = 94; diff --git a/src/objects/Invention.ts b/src/objects/Invention.ts new file mode 100644 index 0000000..e5bd157 --- /dev/null +++ b/src/objects/Invention.ts @@ -0,0 +1,141 @@ +const rideTypes: { [rideType: number]: string } = { + 0: "Spiral Roller Coaster", + 1: "Stand-up Roller Coaster", + 2: "Suspended Swinging Coaster", + 3: "Inverted Roller Coaster", + 4: "Junior Roller Coaster", + 5: "Miniature Railway", + 6: "Monorail", + 7: "Mini Suspended Coaster", + 8: "Boat Hire", + 9: "Wooden Wild Mouse", + 10: "Steeplechase", + 11: "Car Ride", + 12: "Launched Freefall", + 13: "Bobsleigh Coaster", + 14: "Observation Tower", + 15: "Looping Roller Coaster", + 16: "Dinghy Slide", + 17: "Mine Train Coaster", + 18: "Chairlift", + 19: "Corkscrew Roller Coaster", + 20: "Maze", + 21: "Spiral Slide", + 22: "Go-Karts", + 23: "Log Flume", + 24: "River Rapids", + 25: "Dodgems", + 26: "Pirate Ship", + 27: "Swinging Inverter Ship", + 28: "Food Stall", + 30: "Drink Stall", + 32: "Shop", + 33: "Merry-Go-Round", + 35: "Information Kiosk", + 36: "Toilets", + 37: "Ferris Wheel", + 38: "Motion Simulator", + 39: "3D Cinema", + 40: "Top Spin", + 41: "Space Rings", + 42: "Reverse Freefall Coaster", + 43: "Lift", + 44: "Vertical Drop Roller Coaster", + 45: "Cash Machine", + 46: "Twist", + 47: "Haunted House", + 48: "First Aid Room", + 49: "Circus", + 50: "Ghost Train", + 51: "Twister Roller Coaster", + 52: "Wooden Roller Coaster", + 53: "Side-Friction Roller Coaster", + 54: "Steel Wild Mouse", + 55: "Multi-Dimension Roller Coaster", + 57: "Flying Roller Coaster", + 59: "Virginia Reel", + 60: "Splash Boats", + 61: "Mini Helicopters", + 62: "Lay-down Roller Coaster", + 63: "Suspended Monorail", + 65: "Reverser Roller Coaster", + 66: "Heartline Twister Coaster", + 67: "Mini Golf", + 68: "Giga Coaster", + 69: "Roto-Drop", + 70: "Flying Saucers", + 71: "Crooked House", + 72: "Monorail Cycles", + 73: "Compact Inverted Coaster", + 74: "Water Coaster", + 75: "Air Powered Vertical Coaster", + 76: "Inverted Hairpin Coaster", + 77: "Magic Carpet", + 78: "Submarine Ride", + 79: "River Rafts", + 81: "Enterprise", + 86: "Inverted Impulse Coaster", + 87: "Mini Roller Coaster", + 88: "Mine Ride", + 90: "LIM Launched Roller Coaster", + 91: "Hypercoaster", + 92: "Hyper-Twister", + 93: "Monster Trucks", + 94: "Spinning Wild Mouse", + 95: "Classic Mini Roller Coaster", + 96: "Hybrid Coaster", + 97: "Single Rail Roller Coaster", + 98: "Alpine Coaster", + 99: "Classic Wooden Roller Coaster", + 100: "Classic Stand-up Roller Coaster", +}; + +const categoryOrder: { [category: string]: number } = { + transport: 0, + gentle: 1, + rollercoaster: 2, + thrill: 3, + water: 4, + shop: 5, + scenery: 6, +}; + +export class Invention { + readonly identifier: string; + readonly type: string; + readonly name: string; + readonly category: "scenery" | RideResearchCategory; + readonly previewImage: number; + readonly researchItem: ResearchItem; + + invented: boolean = false; + + constructor(item: ResearchItem) { + let objectType: ObjectType = item.type === "ride" ? "ride" : "scenery_group"; + let itemObject = objectManager.getObject(objectType, item.object) as LoadedImageObject; + + this.researchItem = item; + this.identifier = itemObject.identifier; + this.name = itemObject.name; + this.category = item.category; + this.previewImage = itemObject.baseImageId; + this.type = + item.type === "ride" ? rideTypes[(item as RideResearchItem).rideType] : "Scenery Group"; + } + + /** + * A comparison method to use for sorting `Invention` items. + * @param first The first invention to compare. + * @param second The second invention to compare. + * @returns `> 0` if `first` < `second`, `> 0` if `first` > `second`, `0` if they are the same. + */ + public static compare(first: Invention, second: Invention) { + if (first.category !== second.category) { + return categoryOrder[first.category] - categoryOrder[second.category]; + } else if (first.category !== "scenery" && first.type !== second.type) { + return first.type.localeCompare(second.type); + } + + return first.name.localeCompare(second.name); + } +} diff --git a/src/plugin.ts b/src/plugin.ts index a7b3ba4..c41c6cb 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -3,11 +3,11 @@ import * as info from "./info.js"; import { startup } from "./startup"; registerPlugin({ - name: info.name, - version: info.version, - authors: info.authors, - type: info.type, - licence: info.license, - targetApiVersion: info.targetApiVersion, - main: startup, + name: info.name, + version: info.version, + authors: info.authors, + type: info.type, + licence: info.license, + targetApiVersion: info.targetApiVersion, + main: startup, }); diff --git a/src/startup.ts b/src/startup.ts index 4ac6bb3..68cc8b5 100644 --- a/src/startup.ts +++ b/src/startup.ts @@ -1,22 +1,15 @@ -// @ts-ignore -import * as info from "./info.js"; +import * as inventions from "./helpers/inventions.js"; +import * as window from "./window/window.js"; +export function startup() { + if (network.mode !== "none" || typeof ui === "undefined") { + return; + } -export function startup() -{ - // Write code here that should happen on startup of the plugin. - console.log("Hello world!"); + // Initialize invention tracking + inventions.initialize(); - // Register a menu item under the map icon: - if (typeof ui !== "undefined") - { - ui.registerMenuItem(info.name, () => onClickMenuItem()); - } -} - - -function onClickMenuItem() -{ - // Write code here that should happen when the player clicks the menu item under the map icon. - console.log("Clicked menu item"); + // Create window + window.initialize(); + ui.registerMenuItem("Invention Manager", () => window.openWindow()); } diff --git a/src/window/inventionTab.ts b/src/window/inventionTab.ts new file mode 100644 index 0000000..2b9fafe --- /dev/null +++ b/src/window/inventionTab.ts @@ -0,0 +1,323 @@ +import * as inventions from "../helpers/inventions"; +import { Invention } from "../objects/Invention"; +import { shuffle } from "../helpers/arrayExtensions"; +import { + LayoutDirection, + WritableStore, + store, + compute, + tab, + horizontal, + vertical, + listview, + label, + button, + twoway, +} from "openrct2-flexui"; + +/** + * The invention currently selected to be shown as a preview. + */ +const selected = store(); + +/** + * The invention that has last been clicked on to interact with (not just hovered). + */ +const locked = store(); + +const tabImageMap: { [category: string]: number | ImageAnimation } = { + all: { frameBase: 5327, frameCount: 8, frameDuration: 2 }, + transport: { frameBase: 5537, frameCount: 5, frameDuration: 4 }, + gentle: { frameBase: 5542, frameCount: 4, frameDuration: 8 }, + rollercoaster: { frameBase: 5546, frameCount: 5, frameDuration: 2 }, + thrill: 5562, // ImageAnimation doesn't work; unsure if possible to animate manually + water: { frameBase: 5551, frameCount: 6, frameDuration: 4 }, + shop: { frameBase: 5530, frameCount: 7, frameDuration: 4 }, + scenery: 5459, +}; + +const categoryMap: { [category: string]: string } = { + transport: "Transport Rides", + gentle: "Gentle Rides", + rollercoaster: "Roller Coasters", + thrill: "Thrill Rides", + water: "Water Rides", + shop: "Shops & Stalls", + scenery: "Scenery & Theming", +}; + +export function inventionTab(category: "all" | "scenery" | RideResearchCategory) { + return tab({ + image: tabImageMap[category], + direction: LayoutDirection.Horizontal, + content: tabContent(category), + onOpen: () => { + inventions.load(); + selected.set(undefined); + locked.set(undefined); + }, + }); +} + +function tabContent(category: "all" | "scenery" | RideResearchCategory) { + return [createLists(category), createSidebar(category)]; +} + +function createLists(category: "all" | "scenery" | RideResearchCategory) { + const invented = inventions.computeInventions(category, true, (inventions) => inventions); + const uninvented = inventions.computeInventions(category, false, (inventions) => inventions); + + return vertical({ + spacing: 1, + content: [ + label({ text: "{WHITE}Available inventions:", padding: 0 }), + createListView(invented), + label({ text: "{WHITE}To be invented:", padding: { top: 5 } }), + createListView(uninvented), + ], + }); +} + +function createSidebar(category: "all" | "scenery" | RideResearchCategory) { + const image = compute(selected, (invention) => invention?.previewImage ?? 0); + const visible = compute(selected, (invention) => (invention ? "visible" : "hidden")); + return vertical({ + spacing: 0, + content: [ + button({ + height: 110, + width: 110, + padding: { top: "20%", bottom: 5, left: 40 }, + image: image, + visibility: visible, + }), + label({ + text: compute(selected, (invention) => `{WHITE}${invention?.type ?? ""}`), + width: 190, + alignment: "centred", + padding: { left: 2, bottom: 0 }, + visibility: visible, + }), + label({ + text: compute(selected, (invention) => `{BLACK}${invention?.name ?? ""}`), + width: 190, + alignment: "centred", + padding: { left: 2, top: 0 }, + visibility: visible, + }), + label({ + text: compute( + selected, + (invention) => + `{WHITE}Research Group: {BLACK}${categoryMap[invention?.category ?? ""]}`, + ), + width: 190, + padding: [8, 2], + visibility: visible, + }), + horizontal({ + padding: { left: 55 }, + content: [ + createSwapButton("arrow_up", false), + vertical({ + spacing: 1, + padding: { bottom: 10 }, + content: [ + createShiftButton(category, true), + createShiftButton(category, false), + ], + }), + createSwapButton("arrow_down", true), + ], + }), + createListButton("Shuffle", category, false, () => { + if (category === "all") { + const shuffledCategories: ("scenery" | RideResearchCategory)[] = []; + const uninventedReversed: { [category: string]: Invention[] } = {}; + + // Cache the uninvented items in a reversed list + // and track how many exist in each category + inventions.categories.forEach((value) => { + const category = value as "scenery" | RideResearchCategory; + uninventedReversed[category] = inventions.get(category, false).reverse(); + const length = uninventedReversed[category].length; + for (let iterator = 0; iterator < length; iterator++) { + shuffledCategories.push(category); + } + }); + + shuffle(shuffledCategories); + + // Each category is shuffled, so the order of each individual + // category can be preserved while shuffling them around + const shuffledInventions: Invention[] = []; + shuffledCategories.forEach((category) => { + const invention = uninventedReversed[category].pop() as Invention; + shuffledInventions.push(invention); + }); + + inventions.update(...shuffledInventions); + } else { + const shuffled = shuffle(inventions.get(category, false)); + inventions.update(...shuffled); + } + }), + createListButton("Invent All", category, false, () => { + const uninvented = inventions.get(category, false); + uninvented.forEach((invention) => (invention.invented = true)); + inventions.update(...uninvented); + }), + createListButton("Uninvent All", category, true, () => { + const invented = inventions.get(category, true); + invented.forEach((invention) => (invention.invented = false)); + inventions.update(...invented); + }), + ], + }); +} + +/** + * A clickable list of inventions. + */ +function createListView(inventionList: WritableStore) { + const lockedSelection = twoway(store(null)); + locked.subscribe((invention) => { + if (invention === undefined) { + lockedSelection.twoway.set(null); + return; + } + + const row = inventionList.get().indexOf(invention); + if (row < 0) { + lockedSelection.twoway.set(null); + } else { + lockedSelection.twoway.set({ row: row, column: 0 }); + } + }); + + inventionList.subscribe((inventions) => { + if (locked.get() !== undefined) { + const index = inventions.indexOf(locked.get() as Invention); + if ( + lockedSelection.twoway.get() !== null && + index !== lockedSelection.twoway.get()?.row + ) { + // Update invention selection + const invention = locked.get(); + locked.set(undefined); + locked.set(invention); + } + } + }); + + return listview({ + canSelect: true, + height: "50%", + padding: 0, + columns: [{ header: "Type" }, { header: "Object" }], + items: compute(inventionList, (inventions) => { + return inventions.map((invention) => [invention.type, invention.name]); + }), + selectedCell: lockedSelection, + onHighlight: (item) => { + if (locked.get() === undefined) { + selected.set(inventionList.get()[item]); + } + }, + onClick: (item) => { + if (locked.get() === inventionList.get()[item]) { + locked.set(undefined); + return; + } + + const invention = inventionList.get()[item]; + selected.set(invention); + locked.set(invention); + }, + }); +} + +/** + * A button to swap inventions between being invented and uninvented. + */ +function createSwapButton(image: number | IconName, invented: boolean) { + return button({ + image: image, + height: 25, + width: 25, + disabled: compute( + locked, + (invention) => invention === undefined || invention.invented !== invented, + ), + onClick: () => { + const selectedInvention = locked.get() as Invention; + selectedInvention.invented = !selectedInvention.invented; + inventions.update(selectedInvention); + + // Update selection so arrows are correct + locked.set(undefined); + locked.set(selectedInvention); + }, + }); +} + +/** + * A button that shifts inventions up or down in their respective list. + */ +function createShiftButton(category: "all" | "scenery" | RideResearchCategory, top: boolean) { + return button({ + text: top ? "▲" : "▼", + height: 12, + width: 25, + padding: 0, + disabled: compute(locked, (invention) => { + if (invention === undefined || invention.invented === true) { + return true; + } + const inventionList = inventions.get(category, invention.invented); + const index = inventionList.indexOf(invention); + if (top) { + return index == 0; + } else { + return index == inventionList.length - 1; + } + }), + onClick: () => { + const invention = locked.get() as Invention; + const inventionList = inventions.get(category, invention.invented); + const index = inventionList.indexOf(invention); + const swapIndex = top ? index - 1 : index + 1; + + if (top) { + inventions.update(invention, inventionList[swapIndex]); + } else { + inventions.update(inventionList[swapIndex], invention); + } + + // Update selection so arrows are correct + locked.set(undefined); + locked.set(inventions.get(category, invention.invented)[swapIndex]); + }, + }); +} + +/** + * A misc. wide button with text to be displayed in a list. + */ +function createListButton( + text: string, + category: "all" | "scenery" | RideResearchCategory, + invented: boolean, + onClick: () => void, +) { + return button({ + text: text, + height: 15, + width: 170, + padding: [1, 10], + disabled: inventions.computeInventions(category, invented, (inventions) => { + return inventions.length == 0; + }), + onClick: onClick, + }); +} diff --git a/src/window/window.ts b/src/window/window.ts new file mode 100644 index 0000000..89eacae --- /dev/null +++ b/src/window/window.ts @@ -0,0 +1,32 @@ +import { categories } from "../helpers/inventions"; +import { inventionTab } from "./inventionTab"; +import * as flex from "openrct2-flexui"; + +let window: flex.WindowTemplate; +let isWindowOpen = false; + +export function initialize() { + window = flex.tabwindow({ + title: "Invention Manager", + width: { value: 600, min: 400, max: 1200 }, + height: { value: 400, min: 315, max: 800 }, + position: "center", + colours: [flex.Colour.LightPurple, flex.Colour.Grey, flex.Colour.Grey], + onOpen: () => (isWindowOpen = true), + onClose: () => (isWindowOpen = false), + tabs: ["all"] + .concat(categories) + .map((category) => inventionTab(category as "all" | "scenery" | RideResearchCategory)), + }); +} + +/** + * Opens the main window. If already open, the window will be focused. + */ +export function openWindow() { + if (isWindowOpen) { + window.focus(); + } else { + window.open(); + } +} diff --git a/tsconfig.json b/tsconfig.json index db85237..48a8432 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,24 +1,21 @@ { - "compilerOptions": { - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "module": "ES2020", - "moduleResolution": "node", - "noFallthroughCasesInSwitch": true, - "noImplicitAny": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "outDir": "out", - "strict": true, - "target": "es5", - "lib": [ "es5" ] - }, - "include": [ - "./lib/**/*.ts", - "./src/**/*.ts", - ], - "noEmit": true + "compilerOptions": { + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "module": "ES2020", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "out", + "strict": true, + "target": "es5", + "lib": ["es5"] + }, + "include": ["./lib/**/*.ts", "./src/**/*.ts"], + "noEmit": true }