├── .editorconfig ├── .github └── workflows │ └── tutorial.yml ├── .gitignore ├── .poggit.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets └── icon.png ├── composer.json ├── composer.lock ├── src └── DiamondStrider1 │ └── Remark │ ├── Async │ ├── Thenable.php │ └── UnhandledAsyncException.php │ ├── Command │ ├── Arg │ │ ├── Arg.php │ │ ├── ArgumentStack.php │ │ ├── ExtractionFailed.php │ │ ├── SetParameterTrait.php │ │ ├── bool_arg.php │ │ ├── command_arg.php │ │ ├── enum.php │ │ ├── float_arg.php │ │ ├── int_arg.php │ │ ├── json_arg.php │ │ ├── player_arg.php │ │ ├── remaining.php │ │ ├── sender.php │ │ ├── text.php │ │ └── vector_arg.php │ ├── BoundCommand.php │ ├── Cmd.php │ ├── CmdConfig.php │ ├── CommandContext.php │ ├── CommandHintListener.php │ ├── Guard │ │ ├── Guard.php │ │ └── permission.php │ ├── HandlerMethod.php │ ├── HandlerMethodTree.php │ └── OverloadMap.php │ ├── Form │ ├── CustomFormElement │ │ ├── CustomFormElement.php │ │ ├── Dropdown.php │ │ ├── Input.php │ │ ├── Label.php │ │ ├── Slider.php │ │ ├── StepSlider.php │ │ └── Toggle.php │ ├── CustomFormResultTrait.php │ ├── Forms.php │ ├── InternalCustomForm.php │ ├── InternalMenuForm.php │ ├── InternalModalForm.php │ └── MenuFormElement │ │ ├── MenuFormButton.php │ │ └── MenuFormImage.php │ ├── Remark.php │ └── Types │ └── RelativeVector3.php ├── tools ├── php-cs-fixer │ ├── .php-cs-fixer.php │ ├── composer.json │ └── composer.lock └── phpstan │ ├── composer.json │ ├── composer.lock │ └── phpstan.neon.dist ├── tutorial ├── README.md ├── book.toml └── src │ ├── SUMMARY.md │ ├── in-depth │ ├── command_args.md │ ├── command_guards.md │ ├── commands.md │ ├── custom_form_elements.md │ ├── forms.md │ ├── index.md │ └── installing.md │ ├── introduction.md │ └── quick-guide │ ├── command_handling.md │ ├── forms.md │ └── index.md └── virion.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /.github/workflows/tutorial.yml: -------------------------------------------------------------------------------- 1 | name: The Remark Tutorial 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | - name: Install mdBook 14 | run: | 15 | mkdir mdbook 16 | curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.14/mdbook-v0.4.14-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook 17 | echo `pwd`/mdbook >> $GITHUB_PATH 18 | - name: Deploy GitHub Pages 19 | run: | 20 | git diff --exit-code HEAD~1...HEAD -- tutorial && exit 0 21 | cd tutorial 22 | mdbook build 23 | git worktree add gh-pages gh-pages 24 | git config user.name "tutorial-workflow[bot]" 25 | git config user.email "" 26 | cd gh-pages 27 | # Delete the ref to avoid keeping history. 28 | git update-ref -d refs/heads/gh-pages 29 | rm -rf * 30 | mv ../book/* . 31 | git add . 32 | git commit -m "Deploy $GITHUB_SHA to gh-pages" 33 | git push --force 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/vendor/ 2 | 3 | /.php-cs-fixer.cache 4 | 5 | /tools/phpstan/phpstan.neon 6 | 7 | /tutorial/book 8 | -------------------------------------------------------------------------------- /.poggit.yml: -------------------------------------------------------------------------------- 1 | build-by-default: true 2 | branches: 3 | - main 4 | projects: 5 | Remark: 6 | path: "" 7 | model: virion 8 | type: library 9 | lint: 10 | phpstan: false 11 | libs: 12 | - src: sof3/await-generator/await-generator 13 | version: ^3.3.0 14 | epitope: .random 15 | ... 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.2.2 2 | * Increased EventPriority of the AvailableCommandsPacket listener to HIGH. 3 | 4 | # v1.2.1 5 | * Fixed potentially crashing bug in SetParameterTrait.php 6 | * Fixed Composer settings so Arg's with underscores play nicely with static analyzers. 7 | 8 | # v1.2.0 9 | * Allow `Arg`'s to be optional by making the parameter's type accept null 10 | 11 | # v1.1.2 12 | * Fix bug that treated non-HandlerMethods as HandlerMethods in `Remark::command()`. 13 | 14 | # v1.1.1 15 | * `permission()` Guard errors if any passed permission does not exist. 16 | * Fixed bug that caused an error when a player closed out of a Custom Form. 17 | * Enum names assigned to subcommand parameters are prefixed to reduce the likelihood of conflicting with the `enum()` Arg. 18 | 19 | # v1.1.0 20 | * Added await-generator as a dependency. 21 | * Added args: bool_arg, command_arg, float_arg, int_arg, vector_arg. 22 | * `string[] CmdConfig->permissions` changed to `?string CmdConfig->permission` 23 | 24 | # v1.0.0 25 | * Command Handling and Forms implemented. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2022 DiamondStrider1 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Icon 3 |

4 | 5 | # Remark - Easy and Asynchronous Commands and Forms 6 | * [Quick Guide](https://swift-strider.github.io/Remark/quick-guide/index.html) - Learn Remark by building a plugin. 7 | * [Install](#install) - Add Remark as a library to your plugin. 8 | * [Example](https://github.com/Swift-Strider/ExampleRemarkPlugin) - View an example plugin using Remark. 9 | 10 | # Benefits 11 | * `TAB`-completion for commands 12 | * Command argument validation 13 | * Type-safe commands and forms 14 | * Asynchronous Forms API 15 | * Optional AwaitGenerator 16 | 17 | # Install 18 | ```sh 19 | composer require diamondstrider1/remark ^1.1.0 20 | ``` 21 | 22 | Add Remark as a library in `.poggit.yml`. 23 | ```yml 24 | projects: 25 | ExampleRemarkPlugin: 26 | path: "" 27 | libs: 28 | - src: Swift-Strider/Remark/Remark 29 | version: ^1.1.0 30 | epitope: .random 31 | ``` 32 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swift-Strider/Remark/fef59d259d67e2a5e10860226e9c9fd54db0283c/assets/icon.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://getcomposer.org/schema.json", 3 | "name": "diamondstrider1/remark", 4 | "description": "UI virion (library) for PocketMine-MP plugins", 5 | "version": "1.2.2", 6 | "type": "library", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "DiamondStrider1", 11 | "email": "62265561+Swift-Strider@users.noreply.github.com" 12 | } 13 | ], 14 | "minimum-stability": "stable", 15 | "require": { 16 | "pocketmine/pocketmine-mp": "^4.0.0", 17 | "sof3/await-generator": "^3.3.0" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "DiamondStrider1\\Remark\\": "src/DiamondStrider1/Remark/" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "4d05d3730805524c30549c5b6bd85ee5", 8 | "packages": [ 9 | { 10 | "name": "adhocore/json-comment", 11 | "version": "1.1.2", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/adhocore/php-json-comment.git", 15 | "reference": "fc2f76979f0a44a5f5bc2a2b600d0762fe0e78e7" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/adhocore/php-json-comment/zipball/fc2f76979f0a44a5f5bc2a2b600d0762fe0e78e7", 20 | "reference": "fc2f76979f0a44a5f5bc2a2b600d0762fe0e78e7", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-ctype": "*", 25 | "php": ">=7.0" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "^6.5 || ^7.5 || ^8.5" 29 | }, 30 | "type": "library", 31 | "autoload": { 32 | "psr-4": { 33 | "Ahc\\Json\\": "src/" 34 | } 35 | }, 36 | "notification-url": "https://packagist.org/downloads/", 37 | "license": [ 38 | "MIT" 39 | ], 40 | "authors": [ 41 | { 42 | "name": "Jitendra Adhikari", 43 | "email": "jiten.adhikary@gmail.com" 44 | } 45 | ], 46 | "description": "Lightweight JSON comment stripper library for PHP", 47 | "keywords": [ 48 | "comment", 49 | "json", 50 | "strip-comment" 51 | ], 52 | "support": { 53 | "issues": "https://github.com/adhocore/php-json-comment/issues", 54 | "source": "https://github.com/adhocore/php-json-comment/tree/1.1.2" 55 | }, 56 | "funding": [ 57 | { 58 | "url": "https://paypal.me/ji10", 59 | "type": "custom" 60 | } 61 | ], 62 | "time": "2021-04-09T03:06:06+00:00" 63 | }, 64 | { 65 | "name": "brick/math", 66 | "version": "0.9.3", 67 | "source": { 68 | "type": "git", 69 | "url": "https://github.com/brick/math.git", 70 | "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae" 71 | }, 72 | "dist": { 73 | "type": "zip", 74 | "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae", 75 | "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae", 76 | "shasum": "" 77 | }, 78 | "require": { 79 | "ext-json": "*", 80 | "php": "^7.1 || ^8.0" 81 | }, 82 | "require-dev": { 83 | "php-coveralls/php-coveralls": "^2.2", 84 | "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", 85 | "vimeo/psalm": "4.9.2" 86 | }, 87 | "type": "library", 88 | "autoload": { 89 | "psr-4": { 90 | "Brick\\Math\\": "src/" 91 | } 92 | }, 93 | "notification-url": "https://packagist.org/downloads/", 94 | "license": [ 95 | "MIT" 96 | ], 97 | "description": "Arbitrary-precision arithmetic library", 98 | "keywords": [ 99 | "Arbitrary-precision", 100 | "BigInteger", 101 | "BigRational", 102 | "arithmetic", 103 | "bigdecimal", 104 | "bignum", 105 | "brick", 106 | "math" 107 | ], 108 | "support": { 109 | "issues": "https://github.com/brick/math/issues", 110 | "source": "https://github.com/brick/math/tree/0.9.3" 111 | }, 112 | "funding": [ 113 | { 114 | "url": "https://github.com/BenMorel", 115 | "type": "github" 116 | }, 117 | { 118 | "url": "https://tidelift.com/funding/github/packagist/brick/math", 119 | "type": "tidelift" 120 | } 121 | ], 122 | "time": "2021-08-15T20:50:18+00:00" 123 | }, 124 | { 125 | "name": "fgrosse/phpasn1", 126 | "version": "v2.4.0", 127 | "source": { 128 | "type": "git", 129 | "url": "https://github.com/fgrosse/PHPASN1.git", 130 | "reference": "eef488991d53e58e60c9554b09b1201ca5ba9296" 131 | }, 132 | "dist": { 133 | "type": "zip", 134 | "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/eef488991d53e58e60c9554b09b1201ca5ba9296", 135 | "reference": "eef488991d53e58e60c9554b09b1201ca5ba9296", 136 | "shasum": "" 137 | }, 138 | "require": { 139 | "php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0" 140 | }, 141 | "require-dev": { 142 | "php-coveralls/php-coveralls": "~2.0", 143 | "phpunit/phpunit": "^6.3 || ^7.0 || ^8.0" 144 | }, 145 | "suggest": { 146 | "ext-bcmath": "BCmath is the fallback extension for big integer calculations", 147 | "ext-curl": "For loading OID information from the web if they have not bee defined statically", 148 | "ext-gmp": "GMP is the preferred extension for big integer calculations", 149 | "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available" 150 | }, 151 | "type": "library", 152 | "extra": { 153 | "branch-alias": { 154 | "dev-master": "2.0.x-dev" 155 | } 156 | }, 157 | "autoload": { 158 | "psr-4": { 159 | "FG\\": "lib/" 160 | } 161 | }, 162 | "notification-url": "https://packagist.org/downloads/", 163 | "license": [ 164 | "MIT" 165 | ], 166 | "authors": [ 167 | { 168 | "name": "Friedrich Große", 169 | "email": "friedrich.grosse@gmail.com", 170 | "homepage": "https://github.com/FGrosse", 171 | "role": "Author" 172 | }, 173 | { 174 | "name": "All contributors", 175 | "homepage": "https://github.com/FGrosse/PHPASN1/contributors" 176 | } 177 | ], 178 | "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", 179 | "homepage": "https://github.com/FGrosse/PHPASN1", 180 | "keywords": [ 181 | "DER", 182 | "asn.1", 183 | "asn1", 184 | "ber", 185 | "binary", 186 | "decoding", 187 | "encoding", 188 | "x.509", 189 | "x.690", 190 | "x509", 191 | "x690" 192 | ], 193 | "support": { 194 | "issues": "https://github.com/fgrosse/PHPASN1/issues", 195 | "source": "https://github.com/fgrosse/PHPASN1/tree/v2.4.0" 196 | }, 197 | "time": "2021-12-11T12:41:06+00:00" 198 | }, 199 | { 200 | "name": "netresearch/jsonmapper", 201 | "version": "v4.0.0", 202 | "source": { 203 | "type": "git", 204 | "url": "https://github.com/cweiske/jsonmapper.git", 205 | "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" 206 | }, 207 | "dist": { 208 | "type": "zip", 209 | "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", 210 | "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", 211 | "shasum": "" 212 | }, 213 | "require": { 214 | "ext-json": "*", 215 | "ext-pcre": "*", 216 | "ext-reflection": "*", 217 | "ext-spl": "*", 218 | "php": ">=7.1" 219 | }, 220 | "require-dev": { 221 | "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", 222 | "squizlabs/php_codesniffer": "~3.5" 223 | }, 224 | "type": "library", 225 | "autoload": { 226 | "psr-0": { 227 | "JsonMapper": "src/" 228 | } 229 | }, 230 | "notification-url": "https://packagist.org/downloads/", 231 | "license": [ 232 | "OSL-3.0" 233 | ], 234 | "authors": [ 235 | { 236 | "name": "Christian Weiske", 237 | "email": "cweiske@cweiske.de", 238 | "homepage": "http://github.com/cweiske/jsonmapper/", 239 | "role": "Developer" 240 | } 241 | ], 242 | "description": "Map nested JSON structures onto PHP classes", 243 | "support": { 244 | "email": "cweiske@cweiske.de", 245 | "issues": "https://github.com/cweiske/jsonmapper/issues", 246 | "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" 247 | }, 248 | "time": "2020-12-01T19:48:11+00:00" 249 | }, 250 | { 251 | "name": "pocketmine/bedrock-data", 252 | "version": "1.7.0+bedrock-1.18.30", 253 | "source": { 254 | "type": "git", 255 | "url": "https://github.com/pmmp/BedrockData.git", 256 | "reference": "c8f323ff0cbdb36a5d95e7e4a23969f562445be0" 257 | }, 258 | "dist": { 259 | "type": "zip", 260 | "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/c8f323ff0cbdb36a5d95e7e4a23969f562445be0", 261 | "reference": "c8f323ff0cbdb36a5d95e7e4a23969f562445be0", 262 | "shasum": "" 263 | }, 264 | "type": "library", 265 | "notification-url": "https://packagist.org/downloads/", 266 | "license": [ 267 | "CC0-1.0" 268 | ], 269 | "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", 270 | "support": { 271 | "issues": "https://github.com/pmmp/BedrockData/issues", 272 | "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.18.30" 273 | }, 274 | "time": "2022-04-20T12:40:59+00:00" 275 | }, 276 | { 277 | "name": "pocketmine/bedrock-protocol", 278 | "version": "7.3.1+bedrock-1.18.0", 279 | "source": { 280 | "type": "git", 281 | "url": "https://github.com/pmmp/BedrockProtocol.git", 282 | "reference": "c2667453b03ca08a8c54cd89a1fd45cb29196aeb" 283 | }, 284 | "dist": { 285 | "type": "zip", 286 | "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c2667453b03ca08a8c54cd89a1fd45cb29196aeb", 287 | "reference": "c2667453b03ca08a8c54cd89a1fd45cb29196aeb", 288 | "shasum": "" 289 | }, 290 | "require": { 291 | "ext-json": "*", 292 | "netresearch/jsonmapper": "^4.0", 293 | "php": "^8.0", 294 | "pocketmine/binaryutils": "^0.2.0", 295 | "pocketmine/color": "^0.2.0", 296 | "pocketmine/math": "^0.3.0 || ^0.4.0", 297 | "pocketmine/nbt": "^0.3.0", 298 | "ramsey/uuid": "^4.1" 299 | }, 300 | "require-dev": { 301 | "phpstan/phpstan": "1.4.2", 302 | "phpstan/phpstan-phpunit": "^1.0.0", 303 | "phpstan/phpstan-strict-rules": "^1.0.0", 304 | "phpunit/phpunit": "^9.5" 305 | }, 306 | "type": "library", 307 | "autoload": { 308 | "psr-4": { 309 | "pocketmine\\network\\mcpe\\protocol\\": "src/" 310 | } 311 | }, 312 | "notification-url": "https://packagist.org/downloads/", 313 | "license": [ 314 | "LGPL-3.0" 315 | ], 316 | "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP", 317 | "support": { 318 | "issues": "https://github.com/pmmp/BedrockProtocol/issues", 319 | "source": "https://github.com/pmmp/BedrockProtocol/tree/7.3.1+bedrock-1.18.0" 320 | }, 321 | "time": "2022-01-26T21:14:23+00:00" 322 | }, 323 | { 324 | "name": "pocketmine/binaryutils", 325 | "version": "0.2.4", 326 | "source": { 327 | "type": "git", 328 | "url": "https://github.com/pmmp/BinaryUtils.git", 329 | "reference": "5ac7eea91afbad8dc498f5ce34ce6297d5e6ea9a" 330 | }, 331 | "dist": { 332 | "type": "zip", 333 | "url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/5ac7eea91afbad8dc498f5ce34ce6297d5e6ea9a", 334 | "reference": "5ac7eea91afbad8dc498f5ce34ce6297d5e6ea9a", 335 | "shasum": "" 336 | }, 337 | "require": { 338 | "php": "^7.4 || ^8.0", 339 | "php-64bit": "*" 340 | }, 341 | "require-dev": { 342 | "phpstan/extension-installer": "^1.0", 343 | "phpstan/phpstan": "1.3.0", 344 | "phpstan/phpstan-phpunit": "^1.0", 345 | "phpstan/phpstan-strict-rules": "^1.0.0", 346 | "phpunit/phpunit": "^9.5" 347 | }, 348 | "type": "library", 349 | "autoload": { 350 | "psr-4": { 351 | "pocketmine\\utils\\": "src/" 352 | } 353 | }, 354 | "notification-url": "https://packagist.org/downloads/", 355 | "license": [ 356 | "LGPL-3.0" 357 | ], 358 | "description": "Classes and methods for conveniently handling binary data", 359 | "support": { 360 | "issues": "https://github.com/pmmp/BinaryUtils/issues", 361 | "source": "https://github.com/pmmp/BinaryUtils/tree/0.2.4" 362 | }, 363 | "time": "2022-01-12T18:06:33+00:00" 364 | }, 365 | { 366 | "name": "pocketmine/callback-validator", 367 | "version": "1.0.3", 368 | "source": { 369 | "type": "git", 370 | "url": "https://github.com/pmmp/CallbackValidator.git", 371 | "reference": "64787469766bcaa7e5885242e85c23c25e8c55a2" 372 | }, 373 | "dist": { 374 | "type": "zip", 375 | "url": "https://api.github.com/repos/pmmp/CallbackValidator/zipball/64787469766bcaa7e5885242e85c23c25e8c55a2", 376 | "reference": "64787469766bcaa7e5885242e85c23c25e8c55a2", 377 | "shasum": "" 378 | }, 379 | "require": { 380 | "ext-reflection": "*", 381 | "php": "^7.1 || ^8.0" 382 | }, 383 | "replace": { 384 | "daverandom/callback-validator": "*" 385 | }, 386 | "require-dev": { 387 | "phpstan/extension-installer": "^1.0", 388 | "phpstan/phpstan": "0.12.59", 389 | "phpstan/phpstan-strict-rules": "^0.12.4", 390 | "phpunit/phpunit": "^7.5 || ^8.5 || ^9.0" 391 | }, 392 | "type": "library", 393 | "autoload": { 394 | "psr-4": { 395 | "DaveRandom\\CallbackValidator\\": "src/" 396 | } 397 | }, 398 | "notification-url": "https://packagist.org/downloads/", 399 | "license": [ 400 | "MIT" 401 | ], 402 | "authors": [ 403 | { 404 | "name": "Chris Wright", 405 | "email": "cw@daverandom.com" 406 | } 407 | ], 408 | "description": "Fork of daverandom/callback-validator - Tools for validating callback signatures", 409 | "support": { 410 | "issues": "https://github.com/pmmp/CallbackValidator/issues", 411 | "source": "https://github.com/pmmp/CallbackValidator/tree/1.0.3" 412 | }, 413 | "time": "2020-12-11T01:45:37+00:00" 414 | }, 415 | { 416 | "name": "pocketmine/classloader", 417 | "version": "0.2.0", 418 | "source": { 419 | "type": "git", 420 | "url": "https://github.com/pmmp/ClassLoader.git", 421 | "reference": "49ea303993efdfb39cd302e2156d50aa78209e78" 422 | }, 423 | "dist": { 424 | "type": "zip", 425 | "url": "https://api.github.com/repos/pmmp/ClassLoader/zipball/49ea303993efdfb39cd302e2156d50aa78209e78", 426 | "reference": "49ea303993efdfb39cd302e2156d50aa78209e78", 427 | "shasum": "" 428 | }, 429 | "require": { 430 | "ext-pthreads": "~3.2.0 || ^4.0", 431 | "ext-reflection": "*", 432 | "php": "^8.0" 433 | }, 434 | "conflict": { 435 | "pocketmine/spl": "<0.4" 436 | }, 437 | "require-dev": { 438 | "phpstan/extension-installer": "^1.0", 439 | "phpstan/phpstan": "0.12.99", 440 | "phpstan/phpstan-strict-rules": "^0.12.4", 441 | "phpunit/phpunit": "^9.5" 442 | }, 443 | "type": "library", 444 | "autoload": { 445 | "classmap": [ 446 | "./src" 447 | ] 448 | }, 449 | "notification-url": "https://packagist.org/downloads/", 450 | "license": [ 451 | "LGPL-3.0" 452 | ], 453 | "description": "Ad-hoc autoloading components used by PocketMine-MP", 454 | "support": { 455 | "issues": "https://github.com/pmmp/ClassLoader/issues", 456 | "source": "https://github.com/pmmp/ClassLoader/tree/0.2.0" 457 | }, 458 | "time": "2021-11-01T20:17:27+00:00" 459 | }, 460 | { 461 | "name": "pocketmine/color", 462 | "version": "0.2.0", 463 | "source": { 464 | "type": "git", 465 | "url": "https://github.com/pmmp/Color.git", 466 | "reference": "09be6ea6d76f2e33d6813c39d29c22c46c17e1d2" 467 | }, 468 | "dist": { 469 | "type": "zip", 470 | "url": "https://api.github.com/repos/pmmp/Color/zipball/09be6ea6d76f2e33d6813c39d29c22c46c17e1d2", 471 | "reference": "09be6ea6d76f2e33d6813c39d29c22c46c17e1d2", 472 | "shasum": "" 473 | }, 474 | "require": { 475 | "php": "^7.2 || ^8.0" 476 | }, 477 | "require-dev": { 478 | "phpstan/phpstan": "0.12.59", 479 | "phpstan/phpstan-strict-rules": "^0.12.2" 480 | }, 481 | "type": "library", 482 | "autoload": { 483 | "psr-4": { 484 | "pocketmine\\color\\": "src/" 485 | } 486 | }, 487 | "notification-url": "https://packagist.org/downloads/", 488 | "license": [ 489 | "LGPL-3.0" 490 | ], 491 | "description": "Color handling library used by PocketMine-MP and related projects", 492 | "support": { 493 | "issues": "https://github.com/pmmp/Color/issues", 494 | "source": "https://github.com/pmmp/Color/tree/0.2.0" 495 | }, 496 | "time": "2020-12-11T01:24:32+00:00" 497 | }, 498 | { 499 | "name": "pocketmine/errorhandler", 500 | "version": "0.3.0", 501 | "source": { 502 | "type": "git", 503 | "url": "https://github.com/pmmp/ErrorHandler.git", 504 | "reference": "ec742b209e8056bbe855069c4eff94c9734ea19b" 505 | }, 506 | "dist": { 507 | "type": "zip", 508 | "url": "https://api.github.com/repos/pmmp/ErrorHandler/zipball/ec742b209e8056bbe855069c4eff94c9734ea19b", 509 | "reference": "ec742b209e8056bbe855069c4eff94c9734ea19b", 510 | "shasum": "" 511 | }, 512 | "require": { 513 | "php": "^7.2 || ^8.0" 514 | }, 515 | "require-dev": { 516 | "phpstan/phpstan": "0.12.75", 517 | "phpstan/phpstan-strict-rules": "^0.12.2" 518 | }, 519 | "type": "library", 520 | "autoload": { 521 | "psr-4": { 522 | "pocketmine\\errorhandler\\": "src/" 523 | } 524 | }, 525 | "notification-url": "https://packagist.org/downloads/", 526 | "license": [ 527 | "LGPL-3.0" 528 | ], 529 | "description": "Utilities to handle nasty PHP E_* errors in a usable way", 530 | "support": { 531 | "issues": "https://github.com/pmmp/ErrorHandler/issues", 532 | "source": "https://github.com/pmmp/ErrorHandler/tree/0.3.0" 533 | }, 534 | "time": "2021-02-12T18:56:22+00:00" 535 | }, 536 | { 537 | "name": "pocketmine/locale-data", 538 | "version": "2.8.3", 539 | "source": { 540 | "type": "git", 541 | "url": "https://github.com/pmmp/Language.git", 542 | "reference": "113c115a3b8976917eb22b74dccab464831b6483" 543 | }, 544 | "dist": { 545 | "type": "zip", 546 | "url": "https://api.github.com/repos/pmmp/Language/zipball/113c115a3b8976917eb22b74dccab464831b6483", 547 | "reference": "113c115a3b8976917eb22b74dccab464831b6483", 548 | "shasum": "" 549 | }, 550 | "type": "library", 551 | "notification-url": "https://packagist.org/downloads/", 552 | "description": "Language resources used by PocketMine-MP", 553 | "support": { 554 | "issues": "https://github.com/pmmp/Language/issues", 555 | "source": "https://github.com/pmmp/Language/tree/2.8.3" 556 | }, 557 | "time": "2022-05-11T13:51:37+00:00" 558 | }, 559 | { 560 | "name": "pocketmine/log", 561 | "version": "0.4.0", 562 | "source": { 563 | "type": "git", 564 | "url": "https://github.com/pmmp/Log.git", 565 | "reference": "e6c912c0f9055c81d23108ec2d179b96f404c043" 566 | }, 567 | "dist": { 568 | "type": "zip", 569 | "url": "https://api.github.com/repos/pmmp/Log/zipball/e6c912c0f9055c81d23108ec2d179b96f404c043", 570 | "reference": "e6c912c0f9055c81d23108ec2d179b96f404c043", 571 | "shasum": "" 572 | }, 573 | "require": { 574 | "php": "^7.4 || ^8.0" 575 | }, 576 | "conflict": { 577 | "pocketmine/spl": "<0.4" 578 | }, 579 | "require-dev": { 580 | "phpstan/phpstan": "0.12.88", 581 | "phpstan/phpstan-strict-rules": "^0.12.2" 582 | }, 583 | "type": "library", 584 | "autoload": { 585 | "classmap": [ 586 | "./src" 587 | ] 588 | }, 589 | "notification-url": "https://packagist.org/downloads/", 590 | "license": [ 591 | "LGPL-3.0" 592 | ], 593 | "description": "Logging components used by PocketMine-MP and related projects", 594 | "support": { 595 | "issues": "https://github.com/pmmp/Log/issues", 596 | "source": "https://github.com/pmmp/Log/tree/0.4.0" 597 | }, 598 | "time": "2021-06-18T19:08:09+00:00" 599 | }, 600 | { 601 | "name": "pocketmine/log-pthreads", 602 | "version": "0.4.0", 603 | "source": { 604 | "type": "git", 605 | "url": "https://github.com/pmmp/LogPthreads.git", 606 | "reference": "61f709e8cf36bcc24e4efe02acded680a1ce23cd" 607 | }, 608 | "dist": { 609 | "type": "zip", 610 | "url": "https://api.github.com/repos/pmmp/LogPthreads/zipball/61f709e8cf36bcc24e4efe02acded680a1ce23cd", 611 | "reference": "61f709e8cf36bcc24e4efe02acded680a1ce23cd", 612 | "shasum": "" 613 | }, 614 | "require": { 615 | "ext-pthreads": "~3.2.0 || ^4.0", 616 | "php": "^7.4 || ^8.0", 617 | "pocketmine/log": "^0.4.0" 618 | }, 619 | "conflict": { 620 | "pocketmine/spl": "<0.4" 621 | }, 622 | "require-dev": { 623 | "phpstan/extension-installer": "^1.0", 624 | "phpstan/phpstan": "0.12.88", 625 | "phpstan/phpstan-strict-rules": "^0.12.4" 626 | }, 627 | "type": "library", 628 | "autoload": { 629 | "classmap": [ 630 | "./src" 631 | ] 632 | }, 633 | "notification-url": "https://packagist.org/downloads/", 634 | "license": [ 635 | "LGPL-3.0" 636 | ], 637 | "description": "Logging components specialized for pthreads used by PocketMine-MP and related projects", 638 | "support": { 639 | "issues": "https://github.com/pmmp/LogPthreads/issues", 640 | "source": "https://github.com/pmmp/LogPthreads/tree/0.4.0" 641 | }, 642 | "time": "2021-11-01T21:42:09+00:00" 643 | }, 644 | { 645 | "name": "pocketmine/math", 646 | "version": "0.4.2", 647 | "source": { 648 | "type": "git", 649 | "url": "https://github.com/pmmp/Math.git", 650 | "reference": "aacc3759a508a69dfa5bc4dfa770ab733c5c94bf" 651 | }, 652 | "dist": { 653 | "type": "zip", 654 | "url": "https://api.github.com/repos/pmmp/Math/zipball/aacc3759a508a69dfa5bc4dfa770ab733c5c94bf", 655 | "reference": "aacc3759a508a69dfa5bc4dfa770ab733c5c94bf", 656 | "shasum": "" 657 | }, 658 | "require": { 659 | "php": "^8.0", 660 | "php-64bit": "*" 661 | }, 662 | "require-dev": { 663 | "phpstan/extension-installer": "^1.0", 664 | "phpstan/phpstan": "1.2.0", 665 | "phpstan/phpstan-strict-rules": "^1.0", 666 | "phpunit/phpunit": "^8.5 || ^9.5" 667 | }, 668 | "type": "library", 669 | "autoload": { 670 | "psr-4": { 671 | "pocketmine\\math\\": "src/" 672 | } 673 | }, 674 | "notification-url": "https://packagist.org/downloads/", 675 | "license": [ 676 | "LGPL-3.0" 677 | ], 678 | "description": "PHP library containing math related code used in PocketMine-MP", 679 | "support": { 680 | "issues": "https://github.com/pmmp/Math/issues", 681 | "source": "https://github.com/pmmp/Math/tree/0.4.2" 682 | }, 683 | "time": "2021-12-05T01:15:17+00:00" 684 | }, 685 | { 686 | "name": "pocketmine/nbt", 687 | "version": "0.3.2", 688 | "source": { 689 | "type": "git", 690 | "url": "https://github.com/pmmp/NBT.git", 691 | "reference": "3e0d9ef6b6c5fb45e3745a121296e75631b3eefe" 692 | }, 693 | "dist": { 694 | "type": "zip", 695 | "url": "https://api.github.com/repos/pmmp/NBT/zipball/3e0d9ef6b6c5fb45e3745a121296e75631b3eefe", 696 | "reference": "3e0d9ef6b6c5fb45e3745a121296e75631b3eefe", 697 | "shasum": "" 698 | }, 699 | "require": { 700 | "php": "^7.4 || ^8.0", 701 | "php-64bit": "*", 702 | "pocketmine/binaryutils": "^0.2.0" 703 | }, 704 | "require-dev": { 705 | "phpstan/extension-installer": "^1.0", 706 | "phpstan/phpstan": "1.2.0", 707 | "phpstan/phpstan-strict-rules": "^1.0", 708 | "phpunit/phpunit": "^9.5" 709 | }, 710 | "type": "library", 711 | "autoload": { 712 | "psr-4": { 713 | "pocketmine\\nbt\\": "src/" 714 | } 715 | }, 716 | "notification-url": "https://packagist.org/downloads/", 717 | "license": [ 718 | "LGPL-3.0" 719 | ], 720 | "description": "PHP library for working with Named Binary Tags", 721 | "support": { 722 | "issues": "https://github.com/pmmp/NBT/issues", 723 | "source": "https://github.com/pmmp/NBT/tree/0.3.2" 724 | }, 725 | "time": "2021-12-16T01:02:37+00:00" 726 | }, 727 | { 728 | "name": "pocketmine/pocketmine-mp", 729 | "version": "4.0.0", 730 | "source": { 731 | "type": "git", 732 | "url": "https://github.com/pmmp/PocketMine-MP.git", 733 | "reference": "468faa464b2bc5c97f23fafbb71ea61035f6f218" 734 | }, 735 | "dist": { 736 | "type": "zip", 737 | "url": "https://api.github.com/repos/pmmp/PocketMine-MP/zipball/468faa464b2bc5c97f23fafbb71ea61035f6f218", 738 | "reference": "468faa464b2bc5c97f23fafbb71ea61035f6f218", 739 | "shasum": "" 740 | }, 741 | "require": { 742 | "adhocore/json-comment": "^1.1", 743 | "composer-runtime-api": "^2.0", 744 | "ext-chunkutils2": "^0.3.1", 745 | "ext-crypto": "^0.3.1", 746 | "ext-ctype": "*", 747 | "ext-curl": "*", 748 | "ext-date": "*", 749 | "ext-gmp": "*", 750 | "ext-hash": "*", 751 | "ext-igbinary": "^3.0.1", 752 | "ext-json": "*", 753 | "ext-leveldb": "^0.2.1 || ^0.3.0", 754 | "ext-mbstring": "*", 755 | "ext-morton": "^0.1.0", 756 | "ext-openssl": "*", 757 | "ext-pcre": "*", 758 | "ext-phar": "*", 759 | "ext-pthreads": "^4.0", 760 | "ext-reflection": "*", 761 | "ext-simplexml": "*", 762 | "ext-sockets": "*", 763 | "ext-spl": "*", 764 | "ext-yaml": ">=2.0.0", 765 | "ext-zip": "*", 766 | "ext-zlib": ">=1.2.11", 767 | "fgrosse/phpasn1": "^2.3", 768 | "netresearch/jsonmapper": "^4.0", 769 | "php": "^8.0", 770 | "php-64bit": "*", 771 | "pocketmine/bedrock-data": "^1.5.0+bedrock-1.18.0", 772 | "pocketmine/bedrock-protocol": "^7.0.0+bedrock-1.18.0", 773 | "pocketmine/binaryutils": "^0.2.1", 774 | "pocketmine/callback-validator": "^1.0.2", 775 | "pocketmine/classloader": "^0.2.0", 776 | "pocketmine/color": "^0.2.0", 777 | "pocketmine/errorhandler": "^0.3.0", 778 | "pocketmine/locale-data": "^2.0.16", 779 | "pocketmine/log": "^0.4.0", 780 | "pocketmine/log-pthreads": "^0.4.0", 781 | "pocketmine/math": "^0.4.0", 782 | "pocketmine/nbt": "^0.3.0", 783 | "pocketmine/raklib": "^0.14.2", 784 | "pocketmine/raklib-ipc": "^0.1.0", 785 | "pocketmine/snooze": "^0.3.0", 786 | "ramsey/uuid": "^4.1", 787 | "webmozart/path-util": "^2.3" 788 | }, 789 | "require-dev": { 790 | "phpstan/phpstan": "1.2.0", 791 | "phpstan/phpstan-phpunit": "^1.0.0", 792 | "phpstan/phpstan-strict-rules": "^1.0.0", 793 | "phpunit/phpunit": "^9.2" 794 | }, 795 | "type": "project", 796 | "autoload": { 797 | "files": [ 798 | "src/CoreConstants.php" 799 | ], 800 | "psr-4": { 801 | "pocketmine\\": "src/" 802 | } 803 | }, 804 | "notification-url": "https://packagist.org/downloads/", 805 | "license": [ 806 | "LGPL-3.0" 807 | ], 808 | "description": "A server software for Minecraft: Bedrock Edition written in PHP", 809 | "homepage": "https://pmmp.io", 810 | "support": { 811 | "issues": "https://github.com/pmmp/PocketMine-MP/issues", 812 | "source": "https://github.com/pmmp/PocketMine-MP/tree/4.0.0" 813 | }, 814 | "funding": [ 815 | { 816 | "url": "https://github.com/pmmp/PocketMine-MP#donate", 817 | "type": "custom" 818 | }, 819 | { 820 | "url": "https://www.patreon.com/pocketminemp", 821 | "type": "patreon" 822 | } 823 | ], 824 | "time": "2021-12-01T22:33:52+00:00" 825 | }, 826 | { 827 | "name": "pocketmine/raklib", 828 | "version": "0.14.4", 829 | "source": { 830 | "type": "git", 831 | "url": "https://github.com/pmmp/RakLib.git", 832 | "reference": "1ea8e3b95a1b6bf785dc27d76578657be4185f42" 833 | }, 834 | "dist": { 835 | "type": "zip", 836 | "url": "https://api.github.com/repos/pmmp/RakLib/zipball/1ea8e3b95a1b6bf785dc27d76578657be4185f42", 837 | "reference": "1ea8e3b95a1b6bf785dc27d76578657be4185f42", 838 | "shasum": "" 839 | }, 840 | "require": { 841 | "ext-sockets": "*", 842 | "php": "^8.0", 843 | "php-64bit": "*", 844 | "php-ipv6": "*", 845 | "pocketmine/binaryutils": "^0.2.0", 846 | "pocketmine/log": "^0.3.0 || ^0.4.0" 847 | }, 848 | "require-dev": { 849 | "phpstan/phpstan": "1.5.4", 850 | "phpstan/phpstan-strict-rules": "^1.0" 851 | }, 852 | "type": "library", 853 | "autoload": { 854 | "psr-4": { 855 | "raklib\\": "src/" 856 | } 857 | }, 858 | "notification-url": "https://packagist.org/downloads/", 859 | "license": [ 860 | "GPL-3.0" 861 | ], 862 | "description": "A RakNet server implementation written in PHP", 863 | "support": { 864 | "issues": "https://github.com/pmmp/RakLib/issues", 865 | "source": "https://github.com/pmmp/RakLib/tree/0.14.4" 866 | }, 867 | "time": "2022-04-17T18:42:17+00:00" 868 | }, 869 | { 870 | "name": "pocketmine/raklib-ipc", 871 | "version": "0.1.1", 872 | "source": { 873 | "type": "git", 874 | "url": "https://github.com/pmmp/RakLibIpc.git", 875 | "reference": "922a6444b0c6c7daaa5aa5a832107e1ec4738aed" 876 | }, 877 | "dist": { 878 | "type": "zip", 879 | "url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/922a6444b0c6c7daaa5aa5a832107e1ec4738aed", 880 | "reference": "922a6444b0c6c7daaa5aa5a832107e1ec4738aed", 881 | "shasum": "" 882 | }, 883 | "require": { 884 | "php": "^7.4 || ^8.0", 885 | "php-64bit": "*", 886 | "pocketmine/binaryutils": "^0.2.0", 887 | "pocketmine/raklib": "^0.13.1 || ^0.14.0" 888 | }, 889 | "require-dev": { 890 | "phpstan/phpstan": "0.12.81", 891 | "phpstan/phpstan-strict-rules": "^0.12.2" 892 | }, 893 | "type": "library", 894 | "autoload": { 895 | "psr-4": { 896 | "raklib\\server\\ipc\\": "src/" 897 | } 898 | }, 899 | "notification-url": "https://packagist.org/downloads/", 900 | "license": [ 901 | "GPL-3.0" 902 | ], 903 | "description": "Channel-based protocols for inter-thread/inter-process communication with RakLib", 904 | "support": { 905 | "issues": "https://github.com/pmmp/RakLibIpc/issues", 906 | "source": "https://github.com/pmmp/RakLibIpc/tree/0.1.1" 907 | }, 908 | "time": "2021-09-22T17:01:12+00:00" 909 | }, 910 | { 911 | "name": "pocketmine/snooze", 912 | "version": "0.3.1", 913 | "source": { 914 | "type": "git", 915 | "url": "https://github.com/pmmp/Snooze.git", 916 | "reference": "0ac8fc2a781c419a1f64ebca4d5835028f59e29b" 917 | }, 918 | "dist": { 919 | "type": "zip", 920 | "url": "https://api.github.com/repos/pmmp/Snooze/zipball/0ac8fc2a781c419a1f64ebca4d5835028f59e29b", 921 | "reference": "0ac8fc2a781c419a1f64ebca4d5835028f59e29b", 922 | "shasum": "" 923 | }, 924 | "require": { 925 | "ext-pthreads": "~3.2.0 || ^4.0", 926 | "php-64bit": "^7.3 || ^8.0" 927 | }, 928 | "require-dev": { 929 | "phpstan/extension-installer": "^1.0", 930 | "phpstan/phpstan": "0.12.99", 931 | "phpstan/phpstan-strict-rules": "^0.12.4" 932 | }, 933 | "type": "library", 934 | "autoload": { 935 | "psr-4": { 936 | "pocketmine\\snooze\\": "src/" 937 | } 938 | }, 939 | "notification-url": "https://packagist.org/downloads/", 940 | "license": [ 941 | "LGPL-3.0" 942 | ], 943 | "description": "Thread notification management library for code using the pthreads extension", 944 | "support": { 945 | "issues": "https://github.com/pmmp/Snooze/issues", 946 | "source": "https://github.com/pmmp/Snooze/tree/0.3.1" 947 | }, 948 | "time": "2021-11-01T20:50:08+00:00" 949 | }, 950 | { 951 | "name": "ramsey/collection", 952 | "version": "1.2.2", 953 | "source": { 954 | "type": "git", 955 | "url": "https://github.com/ramsey/collection.git", 956 | "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" 957 | }, 958 | "dist": { 959 | "type": "zip", 960 | "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", 961 | "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", 962 | "shasum": "" 963 | }, 964 | "require": { 965 | "php": "^7.3 || ^8", 966 | "symfony/polyfill-php81": "^1.23" 967 | }, 968 | "require-dev": { 969 | "captainhook/captainhook": "^5.3", 970 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", 971 | "ergebnis/composer-normalize": "^2.6", 972 | "fakerphp/faker": "^1.5", 973 | "hamcrest/hamcrest-php": "^2", 974 | "jangregor/phpstan-prophecy": "^0.8", 975 | "mockery/mockery": "^1.3", 976 | "phpspec/prophecy-phpunit": "^2.0", 977 | "phpstan/extension-installer": "^1", 978 | "phpstan/phpstan": "^0.12.32", 979 | "phpstan/phpstan-mockery": "^0.12.5", 980 | "phpstan/phpstan-phpunit": "^0.12.11", 981 | "phpunit/phpunit": "^8.5 || ^9", 982 | "psy/psysh": "^0.10.4", 983 | "slevomat/coding-standard": "^6.3", 984 | "squizlabs/php_codesniffer": "^3.5", 985 | "vimeo/psalm": "^4.4" 986 | }, 987 | "type": "library", 988 | "autoload": { 989 | "psr-4": { 990 | "Ramsey\\Collection\\": "src/" 991 | } 992 | }, 993 | "notification-url": "https://packagist.org/downloads/", 994 | "license": [ 995 | "MIT" 996 | ], 997 | "authors": [ 998 | { 999 | "name": "Ben Ramsey", 1000 | "email": "ben@benramsey.com", 1001 | "homepage": "https://benramsey.com" 1002 | } 1003 | ], 1004 | "description": "A PHP library for representing and manipulating collections.", 1005 | "keywords": [ 1006 | "array", 1007 | "collection", 1008 | "hash", 1009 | "map", 1010 | "queue", 1011 | "set" 1012 | ], 1013 | "support": { 1014 | "issues": "https://github.com/ramsey/collection/issues", 1015 | "source": "https://github.com/ramsey/collection/tree/1.2.2" 1016 | }, 1017 | "funding": [ 1018 | { 1019 | "url": "https://github.com/ramsey", 1020 | "type": "github" 1021 | }, 1022 | { 1023 | "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", 1024 | "type": "tidelift" 1025 | } 1026 | ], 1027 | "time": "2021-10-10T03:01:02+00:00" 1028 | }, 1029 | { 1030 | "name": "ramsey/uuid", 1031 | "version": "4.3.1", 1032 | "source": { 1033 | "type": "git", 1034 | "url": "https://github.com/ramsey/uuid.git", 1035 | "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28" 1036 | }, 1037 | "dist": { 1038 | "type": "zip", 1039 | "url": "https://api.github.com/repos/ramsey/uuid/zipball/8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", 1040 | "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", 1041 | "shasum": "" 1042 | }, 1043 | "require": { 1044 | "brick/math": "^0.8 || ^0.9", 1045 | "ext-ctype": "*", 1046 | "ext-json": "*", 1047 | "php": "^8.0", 1048 | "ramsey/collection": "^1.0" 1049 | }, 1050 | "replace": { 1051 | "rhumsaa/uuid": "self.version" 1052 | }, 1053 | "require-dev": { 1054 | "captainhook/captainhook": "^5.10", 1055 | "captainhook/plugin-composer": "^5.3", 1056 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", 1057 | "doctrine/annotations": "^1.8", 1058 | "ergebnis/composer-normalize": "^2.15", 1059 | "mockery/mockery": "^1.3", 1060 | "moontoast/math": "^1.1", 1061 | "paragonie/random-lib": "^2", 1062 | "php-mock/php-mock": "^2.2", 1063 | "php-mock/php-mock-mockery": "^1.3", 1064 | "php-parallel-lint/php-parallel-lint": "^1.1", 1065 | "phpbench/phpbench": "^1.0", 1066 | "phpstan/extension-installer": "^1.0", 1067 | "phpstan/phpstan": "^0.12", 1068 | "phpstan/phpstan-mockery": "^0.12", 1069 | "phpstan/phpstan-phpunit": "^0.12", 1070 | "phpunit/phpunit": "^8.5 || ^9", 1071 | "slevomat/coding-standard": "^7.0", 1072 | "squizlabs/php_codesniffer": "^3.5", 1073 | "vimeo/psalm": "^4.9" 1074 | }, 1075 | "suggest": { 1076 | "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", 1077 | "ext-ctype": "Enables faster processing of character classification using ctype functions.", 1078 | "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", 1079 | "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", 1080 | "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", 1081 | "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." 1082 | }, 1083 | "type": "library", 1084 | "extra": { 1085 | "captainhook": { 1086 | "force-install": true 1087 | } 1088 | }, 1089 | "autoload": { 1090 | "files": [ 1091 | "src/functions.php" 1092 | ], 1093 | "psr-4": { 1094 | "Ramsey\\Uuid\\": "src/" 1095 | } 1096 | }, 1097 | "notification-url": "https://packagist.org/downloads/", 1098 | "license": [ 1099 | "MIT" 1100 | ], 1101 | "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", 1102 | "keywords": [ 1103 | "guid", 1104 | "identifier", 1105 | "uuid" 1106 | ], 1107 | "support": { 1108 | "issues": "https://github.com/ramsey/uuid/issues", 1109 | "source": "https://github.com/ramsey/uuid/tree/4.3.1" 1110 | }, 1111 | "funding": [ 1112 | { 1113 | "url": "https://github.com/ramsey", 1114 | "type": "github" 1115 | }, 1116 | { 1117 | "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", 1118 | "type": "tidelift" 1119 | } 1120 | ], 1121 | "time": "2022-03-27T21:42:02+00:00" 1122 | }, 1123 | { 1124 | "name": "sof3/await-generator", 1125 | "version": "3.3.0", 1126 | "source": { 1127 | "type": "git", 1128 | "url": "https://github.com/SOF3/await-generator.git", 1129 | "reference": "6cfe07c2b50dd069854ccb7719397f42ead9e946" 1130 | }, 1131 | "dist": { 1132 | "type": "zip", 1133 | "url": "https://api.github.com/repos/SOF3/await-generator/zipball/6cfe07c2b50dd069854ccb7719397f42ead9e946", 1134 | "reference": "6cfe07c2b50dd069854ccb7719397f42ead9e946", 1135 | "shasum": "" 1136 | }, 1137 | "require": { 1138 | "php": "^7.3 || ^8.0" 1139 | }, 1140 | "require-dev": { 1141 | "composer/package-versions-deprecated": "1.11.99.1", 1142 | "infection/infection": "^0.18.2 || ^0.20.2", 1143 | "phpstan/phpstan": "^0.12.84", 1144 | "phpunit/phpunit": "^9" 1145 | }, 1146 | "type": "library", 1147 | "autoload": { 1148 | "psr-0": { 1149 | "SOFe\\AwaitGenerator\\": "await-generator/src/" 1150 | } 1151 | }, 1152 | "notification-url": "https://packagist.org/downloads/", 1153 | "license": [ 1154 | "apache-2.0" 1155 | ], 1156 | "authors": [ 1157 | { 1158 | "name": "SOFe", 1159 | "email": "sofe2038@gmail.com" 1160 | } 1161 | ], 1162 | "description": "Use async/await in PHP using generators", 1163 | "support": { 1164 | "issues": "https://github.com/SOF3/await-generator/issues", 1165 | "source": "https://github.com/SOF3/await-generator/tree/3.3.0" 1166 | }, 1167 | "time": "2022-03-13T02:52:08+00:00" 1168 | }, 1169 | { 1170 | "name": "symfony/polyfill-ctype", 1171 | "version": "v1.25.0", 1172 | "source": { 1173 | "type": "git", 1174 | "url": "https://github.com/symfony/polyfill-ctype.git", 1175 | "reference": "30885182c981ab175d4d034db0f6f469898070ab" 1176 | }, 1177 | "dist": { 1178 | "type": "zip", 1179 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", 1180 | "reference": "30885182c981ab175d4d034db0f6f469898070ab", 1181 | "shasum": "" 1182 | }, 1183 | "require": { 1184 | "php": ">=7.1" 1185 | }, 1186 | "provide": { 1187 | "ext-ctype": "*" 1188 | }, 1189 | "suggest": { 1190 | "ext-ctype": "For best performance" 1191 | }, 1192 | "type": "library", 1193 | "extra": { 1194 | "branch-alias": { 1195 | "dev-main": "1.23-dev" 1196 | }, 1197 | "thanks": { 1198 | "name": "symfony/polyfill", 1199 | "url": "https://github.com/symfony/polyfill" 1200 | } 1201 | }, 1202 | "autoload": { 1203 | "files": [ 1204 | "bootstrap.php" 1205 | ], 1206 | "psr-4": { 1207 | "Symfony\\Polyfill\\Ctype\\": "" 1208 | } 1209 | }, 1210 | "notification-url": "https://packagist.org/downloads/", 1211 | "license": [ 1212 | "MIT" 1213 | ], 1214 | "authors": [ 1215 | { 1216 | "name": "Gert de Pagter", 1217 | "email": "BackEndTea@gmail.com" 1218 | }, 1219 | { 1220 | "name": "Symfony Community", 1221 | "homepage": "https://symfony.com/contributors" 1222 | } 1223 | ], 1224 | "description": "Symfony polyfill for ctype functions", 1225 | "homepage": "https://symfony.com", 1226 | "keywords": [ 1227 | "compatibility", 1228 | "ctype", 1229 | "polyfill", 1230 | "portable" 1231 | ], 1232 | "support": { 1233 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" 1234 | }, 1235 | "funding": [ 1236 | { 1237 | "url": "https://symfony.com/sponsor", 1238 | "type": "custom" 1239 | }, 1240 | { 1241 | "url": "https://github.com/fabpot", 1242 | "type": "github" 1243 | }, 1244 | { 1245 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1246 | "type": "tidelift" 1247 | } 1248 | ], 1249 | "time": "2021-10-20T20:35:02+00:00" 1250 | }, 1251 | { 1252 | "name": "symfony/polyfill-php81", 1253 | "version": "v1.25.0", 1254 | "source": { 1255 | "type": "git", 1256 | "url": "https://github.com/symfony/polyfill-php81.git", 1257 | "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" 1258 | }, 1259 | "dist": { 1260 | "type": "zip", 1261 | "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", 1262 | "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", 1263 | "shasum": "" 1264 | }, 1265 | "require": { 1266 | "php": ">=7.1" 1267 | }, 1268 | "type": "library", 1269 | "extra": { 1270 | "branch-alias": { 1271 | "dev-main": "1.23-dev" 1272 | }, 1273 | "thanks": { 1274 | "name": "symfony/polyfill", 1275 | "url": "https://github.com/symfony/polyfill" 1276 | } 1277 | }, 1278 | "autoload": { 1279 | "files": [ 1280 | "bootstrap.php" 1281 | ], 1282 | "psr-4": { 1283 | "Symfony\\Polyfill\\Php81\\": "" 1284 | }, 1285 | "classmap": [ 1286 | "Resources/stubs" 1287 | ] 1288 | }, 1289 | "notification-url": "https://packagist.org/downloads/", 1290 | "license": [ 1291 | "MIT" 1292 | ], 1293 | "authors": [ 1294 | { 1295 | "name": "Nicolas Grekas", 1296 | "email": "p@tchwork.com" 1297 | }, 1298 | { 1299 | "name": "Symfony Community", 1300 | "homepage": "https://symfony.com/contributors" 1301 | } 1302 | ], 1303 | "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", 1304 | "homepage": "https://symfony.com", 1305 | "keywords": [ 1306 | "compatibility", 1307 | "polyfill", 1308 | "portable", 1309 | "shim" 1310 | ], 1311 | "support": { 1312 | "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0" 1313 | }, 1314 | "funding": [ 1315 | { 1316 | "url": "https://symfony.com/sponsor", 1317 | "type": "custom" 1318 | }, 1319 | { 1320 | "url": "https://github.com/fabpot", 1321 | "type": "github" 1322 | }, 1323 | { 1324 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1325 | "type": "tidelift" 1326 | } 1327 | ], 1328 | "time": "2021-09-13T13:58:11+00:00" 1329 | }, 1330 | { 1331 | "name": "webmozart/assert", 1332 | "version": "1.10.0", 1333 | "source": { 1334 | "type": "git", 1335 | "url": "https://github.com/webmozarts/assert.git", 1336 | "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" 1337 | }, 1338 | "dist": { 1339 | "type": "zip", 1340 | "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", 1341 | "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", 1342 | "shasum": "" 1343 | }, 1344 | "require": { 1345 | "php": "^7.2 || ^8.0", 1346 | "symfony/polyfill-ctype": "^1.8" 1347 | }, 1348 | "conflict": { 1349 | "phpstan/phpstan": "<0.12.20", 1350 | "vimeo/psalm": "<4.6.1 || 4.6.2" 1351 | }, 1352 | "require-dev": { 1353 | "phpunit/phpunit": "^8.5.13" 1354 | }, 1355 | "type": "library", 1356 | "extra": { 1357 | "branch-alias": { 1358 | "dev-master": "1.10-dev" 1359 | } 1360 | }, 1361 | "autoload": { 1362 | "psr-4": { 1363 | "Webmozart\\Assert\\": "src/" 1364 | } 1365 | }, 1366 | "notification-url": "https://packagist.org/downloads/", 1367 | "license": [ 1368 | "MIT" 1369 | ], 1370 | "authors": [ 1371 | { 1372 | "name": "Bernhard Schussek", 1373 | "email": "bschussek@gmail.com" 1374 | } 1375 | ], 1376 | "description": "Assertions to validate method input/output with nice error messages.", 1377 | "keywords": [ 1378 | "assert", 1379 | "check", 1380 | "validate" 1381 | ], 1382 | "support": { 1383 | "issues": "https://github.com/webmozarts/assert/issues", 1384 | "source": "https://github.com/webmozarts/assert/tree/1.10.0" 1385 | }, 1386 | "time": "2021-03-09T10:59:23+00:00" 1387 | }, 1388 | { 1389 | "name": "webmozart/path-util", 1390 | "version": "2.3.0", 1391 | "source": { 1392 | "type": "git", 1393 | "url": "https://github.com/webmozart/path-util.git", 1394 | "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" 1395 | }, 1396 | "dist": { 1397 | "type": "zip", 1398 | "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", 1399 | "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", 1400 | "shasum": "" 1401 | }, 1402 | "require": { 1403 | "php": ">=5.3.3", 1404 | "webmozart/assert": "~1.0" 1405 | }, 1406 | "require-dev": { 1407 | "phpunit/phpunit": "^4.6", 1408 | "sebastian/version": "^1.0.1" 1409 | }, 1410 | "type": "library", 1411 | "extra": { 1412 | "branch-alias": { 1413 | "dev-master": "2.3-dev" 1414 | } 1415 | }, 1416 | "autoload": { 1417 | "psr-4": { 1418 | "Webmozart\\PathUtil\\": "src/" 1419 | } 1420 | }, 1421 | "notification-url": "https://packagist.org/downloads/", 1422 | "license": [ 1423 | "MIT" 1424 | ], 1425 | "authors": [ 1426 | { 1427 | "name": "Bernhard Schussek", 1428 | "email": "bschussek@gmail.com" 1429 | } 1430 | ], 1431 | "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", 1432 | "support": { 1433 | "issues": "https://github.com/webmozart/path-util/issues", 1434 | "source": "https://github.com/webmozart/path-util/tree/2.3.0" 1435 | }, 1436 | "abandoned": "symfony/filesystem", 1437 | "time": "2015-12-17T08:42:14+00:00" 1438 | } 1439 | ], 1440 | "packages-dev": [], 1441 | "aliases": [], 1442 | "minimum-stability": "stable", 1443 | "stability-flags": [], 1444 | "prefer-stable": false, 1445 | "prefer-lowest": false, 1446 | "platform": [], 1447 | "platform-dev": [], 1448 | "plugin-api-version": "2.3.0" 1449 | } 1450 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Async/Thenable.php: -------------------------------------------------------------------------------- 1 | state) { 54 | $this->onResolve[] = $onResolve; 55 | if (null !== $onReject) { 56 | $this->onReject[] = $onReject; 57 | } 58 | } elseif (self::RESOLVED === $this->state) { 59 | $onResolve($this->value); 60 | } elseif (null !== $onReject) { 61 | $onReject($this->exception); 62 | } 63 | } 64 | 65 | /** 66 | * Creates a new Thenable using a Closure like a JavaScript Promise. 67 | * 68 | * Like in JavaScript, the $closure will be called immediately, and 69 | * the returned Thenable will start in a PENDING state. 70 | * 71 | * @phpstan-template V 72 | * @phpstan-template T of Throwable 73 | * @phpstan-param Closure(Closure(V): void, Closure(T): void): void $closure 74 | * @phpstan-return Thenable 75 | */ 76 | public static function promise(Closure $closure): self 77 | { 78 | /** @phpstan-var Thenable $self */ 79 | $self = new self(); 80 | $closure( 81 | function (mixed $value) use ($self): void { 82 | if (self::PENDING !== $self->state) { 83 | $ending = match ($self->state) { 84 | self::RESOLVED => 'resolved!', 85 | self::REJECTED => 'rejected!', 86 | }; 87 | throw new LogicException('Attempt to resolve a promise that has already been '.$ending); 88 | } 89 | 90 | $self->value = $value; 91 | $self->state = self::RESOLVED; 92 | foreach ($self->onResolve as $onResolve) { 93 | $onResolve($value); 94 | } 95 | }, 96 | function (Throwable $exception) use ($self): void { 97 | if (self::PENDING !== $self->state) { 98 | $ending = match ($self->state) { 99 | self::RESOLVED => 'resolved!', 100 | self::REJECTED => 'rejected!', 101 | }; 102 | throw new LogicException('Attempt to resolve a promise that has already been '.$ending); 103 | } 104 | 105 | $self->exception = $exception; 106 | $self->state = self::REJECTED; 107 | if (0 === count($self->onReject)) { 108 | throw new UnhandledAsyncException("An asynchronous exception wasn't caught!", previous: $exception); 109 | } 110 | foreach ($self->onReject as $onReject) { 111 | $onReject($exception); 112 | } 113 | } 114 | ); 115 | 116 | return $self; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Async/UnhandledAsyncException.php: -------------------------------------------------------------------------------- 1 | . 36 | * 37 | * @param string $name the name of the parameter 38 | * 39 | * @return ?string null if not applicable 40 | */ 41 | public function toUsageComponent(string $name): ?string; 42 | 43 | /** 44 | * Create a CommandParameter, registering any command enums if needed. 45 | * 46 | * @param string $name the name of the parameter 47 | * 48 | * @return ?CommandParameter null if not applicable 49 | */ 50 | public function toCommandParameter(string $name): ?CommandParameter; 51 | } 52 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/ArgumentStack.php: -------------------------------------------------------------------------------- 1 | args[$this->cursor + $lookahead] ?? null; 39 | } 40 | 41 | /** 42 | * Peek at the current element or a future element 43 | * without modifying the stack. On failure, throws an 44 | * ExtractionFailed exception. 45 | * 46 | * @param int $lookahead a positive number (including zero) 47 | * 48 | * @throws ExtractionFailed 49 | */ 50 | public function peek(string|Translatable $failMessage, int $lookahead = 0): string 51 | { 52 | if ($lookahead < 0) { 53 | throw new InvalidArgumentException('The lookahead must be greater than one!'); 54 | } 55 | 56 | return $this->args[$this->cursor + $lookahead] ?? 57 | throw new ExtractionFailed($failMessage); 58 | } 59 | 60 | /** 61 | * Get the current element and 62 | * advance to the next element in the stack. 63 | * On failure, throws an ExtractionFailed exception. 64 | * 65 | * @throws ExtractionFailed 66 | */ 67 | public function tryPop(): ?string 68 | { 69 | return $this->args[$this->cursor++] ?? null; 70 | } 71 | 72 | /** 73 | * Get the current element and 74 | * advance to the next element in the stack. 75 | * On failure, throws an ExtractionFailed exception. 76 | * 77 | * @throws ExtractionFailed 78 | */ 79 | public function pop(string|Translatable $failMessage): string 80 | { 81 | return $this->args[$this->cursor++] ?? 82 | throw new ExtractionFailed($failMessage); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/ExtractionFailed.php: -------------------------------------------------------------------------------- 1 | getText()); 15 | } 16 | 17 | public function getTranslatable(): string|Translatable 18 | { 19 | return $this->translatable; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/SetParameterTrait.php: -------------------------------------------------------------------------------- 1 | optional = $parameter->getType()?->allowsNull() ?? false; 18 | $this->parameter = $parameter; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/bool_arg.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | #[Attribute( 22 | Attribute::IS_REPEATABLE | 23 | Attribute::TARGET_METHOD 24 | )] 25 | final class bool_arg implements Arg 26 | { 27 | use SetParameterTrait; 28 | 29 | public function extract(CommandContext $context, ArgumentStack $args): ?bool 30 | { 31 | $component = $this->toUsageComponent($this->parameter->getName()); 32 | if ($this->optional) { 33 | $value = $args->tryPop(); 34 | if (null === $value) { 35 | return null; 36 | } 37 | } else { 38 | $value = $args->pop("Required argument $component"); 39 | } 40 | 41 | return match ($value) { 42 | 'true', 'on', 'yes' => true, 43 | 'false', 'off', 'no' => false, 44 | default => throw new ExtractionFailed("$value does not satisfy $component"), 45 | }; 46 | } 47 | 48 | public function toUsageComponent(string $name): ?string 49 | { 50 | if ($this->optional) { 51 | return "[$name: true|false]"; 52 | } else { 53 | return "<$name: true|false>"; 54 | } 55 | } 56 | 57 | public function toCommandParameter(string $name): ?CommandParameter 58 | { 59 | return CommandParameter::enum($name, new CommandEnum('bool', ['true', 'false']), 0, $this->optional); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/command_arg.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | #[Attribute( 19 | Attribute::IS_REPEATABLE | 20 | Attribute::TARGET_METHOD 21 | )] 22 | final class command_arg implements Arg 23 | { 24 | use SetParameterTrait; 25 | 26 | public function extract(CommandContext $context, ArgumentStack $args): ?string 27 | { 28 | if ($this->optional) { 29 | return $args->tryPop(); 30 | } else { 31 | $component = $this->toUsageComponent($this->parameter->getName()); 32 | 33 | return $args->pop("Required argument $component"); 34 | } 35 | } 36 | 37 | public function toUsageComponent(string $name): ?string 38 | { 39 | if ($this->optional) { 40 | return "[$name: command]"; 41 | } else { 42 | return "<$name: command>"; 43 | } 44 | } 45 | 46 | public function toCommandParameter(string $name): ?CommandParameter 47 | { 48 | return CommandParameter::standard($name, ACP::ARG_TYPE_COMMAND, 0, $this->optional); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/enum.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | #[Attribute( 18 | Attribute::IS_REPEATABLE | 19 | Attribute::TARGET_METHOD 20 | )] 21 | final class enum implements Arg 22 | { 23 | use SetParameterTrait; 24 | /** @var string[] */ 25 | private array $choices; 26 | /** @var bool[] */ 27 | private array $choiceSet; 28 | 29 | public function __construct( 30 | private string $name, 31 | string $choice, 32 | string ...$otherChoices, 33 | ) { 34 | $this->choices = [$choice, ...$otherChoices]; 35 | $this->choiceSet = []; 36 | foreach ($this->choices as $c) { 37 | $this->choiceSet[$c] = true; 38 | } 39 | } 40 | 41 | public function extract(CommandContext $context, ArgumentStack $args): ?string 42 | { 43 | $component = $this->toUsageComponent($this->parameter->getName()); 44 | if ($this->optional) { 45 | $choice = $args->tryPop(); 46 | if (null === $choice) { 47 | return null; 48 | } 49 | } else { 50 | $choice = $args->pop("Required argument $component"); 51 | } 52 | if (isset($this->choiceSet[$choice])) { 53 | return $choice; 54 | } 55 | throw new ExtractionFailed("$choice does not satisfy $component"); 56 | } 57 | 58 | public function toUsageComponent(string $name): ?string 59 | { 60 | $choicesString = implode('|', $this->choices); 61 | 62 | if ($this->optional) { 63 | return "[$name: $choicesString]"; 64 | } else { 65 | return "<$name: $choicesString>"; 66 | } 67 | } 68 | 69 | public function toCommandParameter(string $name): ?CommandParameter 70 | { 71 | return CommandParameter::enum( 72 | $name, 73 | new CommandEnum($this->name, $this->choices), 74 | 0, 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/float_arg.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | #[Attribute( 18 | Attribute::IS_REPEATABLE | 19 | Attribute::TARGET_METHOD 20 | )] 21 | final class float_arg implements Arg 22 | { 23 | use SetParameterTrait; 24 | 25 | public function extract(CommandContext $context, ArgumentStack $args): ?float 26 | { 27 | $component = $this->toUsageComponent($this->parameter->getName()); 28 | if ($this->optional) { 29 | $value = $args->tryPop(); 30 | if (null === $value) { 31 | return null; 32 | } 33 | } else { 34 | $value = $args->pop("Required argument $component"); 35 | } 36 | if (is_numeric($value)) { 37 | return (float) $value; 38 | } 39 | throw new ExtractionFailed("$value does not satisfy $component"); 40 | } 41 | 42 | public function toUsageComponent(string $name): ?string 43 | { 44 | if ($this->optional) { 45 | return "[$name: float]"; 46 | } else { 47 | return "<$name: float>"; 48 | } 49 | } 50 | 51 | public function toCommandParameter(string $name): ?CommandParameter 52 | { 53 | return CommandParameter::standard($name, ACP::ARG_TYPE_FLOAT, 0, $this->optional); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/int_arg.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | #[Attribute( 18 | Attribute::IS_REPEATABLE | 19 | Attribute::TARGET_METHOD 20 | )] 21 | final class int_arg implements Arg 22 | { 23 | use SetParameterTrait; 24 | 25 | public function extract(CommandContext $context, ArgumentStack $args): ?int 26 | { 27 | $component = $this->toUsageComponent($this->parameter->getName()); 28 | if ($this->optional) { 29 | $value = $args->tryPop(); 30 | if (null === $value) { 31 | return null; 32 | } 33 | } else { 34 | $value = $args->pop("Required argument $component"); 35 | } 36 | if (is_numeric($value)) { 37 | return (int) $value; 38 | } 39 | throw new ExtractionFailed("$value does not satisfy $component"); 40 | } 41 | 42 | public function toUsageComponent(string $name): ?string 43 | { 44 | if ($this->optional) { 45 | return "[$name: int]"; 46 | } else { 47 | return "<$name: int>"; 48 | } 49 | } 50 | 51 | public function toCommandParameter(string $name): ?CommandParameter 52 | { 53 | return CommandParameter::standard($name, ACP::ARG_TYPE_INT, 0, $this->optional); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/json_arg.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | #[Attribute( 19 | Attribute::IS_REPEATABLE | 20 | Attribute::TARGET_METHOD 21 | )] 22 | final class json_arg implements Arg 23 | { 24 | use SetParameterTrait; 25 | 26 | public function extract(CommandContext $context, ArgumentStack $args): ?string 27 | { 28 | if ($this->optional) { 29 | return $args->tryPop(); 30 | } else { 31 | $component = $this->toUsageComponent($this->parameter->getName()); 32 | 33 | return $args->pop("Required argument $component"); 34 | } 35 | } 36 | 37 | public function toUsageComponent(string $name): ?string 38 | { 39 | if ($this->optional) { 40 | return "[$name: json]"; 41 | } else { 42 | return "<$name: json>"; 43 | } 44 | } 45 | 46 | public function toCommandParameter(string $name): ?CommandParameter 47 | { 48 | return CommandParameter::standard($name, ACP::ARG_TYPE_JSON, 0, $this->optional); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/player_arg.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | #[Attribute( 23 | Attribute::IS_REPEATABLE | 24 | Attribute::TARGET_METHOD 25 | )] 26 | final class player_arg implements Arg 27 | { 28 | use SetParameterTrait; 29 | 30 | /** 31 | * @param bool $exact whether to not match by prefix 32 | */ 33 | public function __construct( 34 | private bool $exact = false, 35 | ) { 36 | } 37 | 38 | public function extract(CommandContext $context, ArgumentStack $args): ?PmPlayer 39 | { 40 | $component = $this->toUsageComponent($this->parameter->getName()); 41 | if ($this->optional) { 42 | $name = $args->tryPop(); 43 | if (null === $name) { 44 | return null; 45 | } 46 | } else { 47 | $name = $args->pop("Required argument $component"); 48 | } 49 | if ($this->exact) { 50 | $player = Server::getInstance()->getPlayerExact($name); 51 | } else { 52 | $player = Server::getInstance()->getPlayerByPrefix($name); 53 | } 54 | 55 | return $player ?? throw new ExtractionFailed(KnownTranslationFactory::commands_generic_player_notFound()); 56 | } 57 | 58 | public function toUsageComponent(string $name): ?string 59 | { 60 | if ($this->optional) { 61 | return "[$name: target]"; 62 | } else { 63 | return "<$name: target>"; 64 | } 65 | } 66 | 67 | public function toCommandParameter(string $name): ?CommandParameter 68 | { 69 | return CommandParameter::standard($name, ACP::ARG_TYPE_TARGET, 0, $this->optional); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/remaining.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | #[Attribute( 18 | Attribute::IS_REPEATABLE | 19 | Attribute::TARGET_METHOD 20 | )] 21 | final class remaining implements Arg 22 | { 23 | use SetParameterTrait; 24 | 25 | /** 26 | * @return string[] 27 | */ 28 | public function extract(CommandContext $context, ArgumentStack $args): array 29 | { 30 | $collected = []; 31 | while (true) { 32 | $popped = $args->tryPop(); 33 | if (null === $popped) { 34 | break; 35 | } 36 | $collected[] = $popped; 37 | } 38 | 39 | return $collected; 40 | } 41 | 42 | public function toUsageComponent(string $name): ?string 43 | { 44 | return "[$name: text]"; 45 | } 46 | 47 | public function toCommandParameter(string $name): ?CommandParameter 48 | { 49 | return CommandParameter::standard($name, ACP::ARG_TYPE_RAWTEXT, 0, true); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/sender.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | #[Attribute( 23 | Attribute::IS_REPEATABLE | 24 | Attribute::TARGET_METHOD 25 | )] 26 | final class sender implements Arg 27 | { 28 | use SetParameterTrait; 29 | private bool $player; 30 | 31 | public function setParameter(ReflectionParameter $parameter): void 32 | { 33 | $type = $parameter->getType(); 34 | if ( 35 | !$type instanceof ReflectionNamedType || 36 | (CommandSender::class !== $type->getName() && 37 | Player::class !== $type->getName() 38 | ) 39 | ) { 40 | $name = $parameter->getName(); 41 | throw new InvalidArgumentException("The parameter ($name) corresponding to `sender()` Arg must have a type of either CommandSender or Player!"); 42 | } 43 | 44 | $this->player = Player::class === $type->getName(); 45 | $this->parameter = $parameter; 46 | } 47 | 48 | public function extract(CommandContext $context, ArgumentStack $args): CommandSender 49 | { 50 | $sender = $context->sender(); 51 | if ($this->player && !$sender instanceof Player) { 52 | throw new ExtractionFailed('You must be a player to run this command!'); 53 | } 54 | 55 | return $sender; 56 | } 57 | 58 | public function toUsageComponent(string $name): ?string 59 | { 60 | return null; 61 | } 62 | 63 | public function toCommandParameter(string $name): ?CommandParameter 64 | { 65 | return null; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/text.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | #[Attribute( 21 | Attribute::IS_REPEATABLE | 22 | Attribute::TARGET_METHOD 23 | )] 24 | final class text implements Arg 25 | { 26 | use SetParameterTrait; 27 | 28 | /** 29 | * @param int $count number of arguments to match 30 | * @param bool $require whether to fail when less 31 | * than $count arguments remain 32 | */ 33 | public function __construct( 34 | private int $count = 1, 35 | private bool $require = true, 36 | ) { 37 | if ($count <= 0) { 38 | throw new InvalidArgumentException('Count must greater than zero!'); 39 | } 40 | } 41 | 42 | public function setParameter(ReflectionParameter $parameter): void 43 | { 44 | $type = $parameter->getType(); 45 | if ($type instanceof ReflectionNamedType) { 46 | if ( 47 | 1 === $this->count && $this->require && 48 | ('string' !== $type->getName() || $type->allowsNull()) 49 | ) { 50 | $name = $parameter->getName(); 51 | throw new InvalidArgumentException("The corresponding parameter ($name) to `text()` must have the type `string` (hint: `?string` is invalid)!"); 52 | } elseif ( 53 | 1 === $this->count && !$this->require && 54 | ('string' !== $type->getName() || !$type->allowsNull()) 55 | ) { 56 | $name = $parameter->getName(); 57 | throw new InvalidArgumentException("The corresponding parameter ($name) to `text()` must have the type `?string` (hint: `string` is invalid)!"); 58 | } elseif ( 59 | 1 !== $this->count && 60 | ('array' !== $type->getName() || $type->allowsNull()) 61 | ) { 62 | $name = $parameter->getName(); 63 | throw new InvalidArgumentException("The corresponding parameter ($name) to `text()` must have the type `array` (hint: `?array` is invalid)!"); 64 | } 65 | } 66 | $this->parameter = $parameter; 67 | } 68 | 69 | /** 70 | * @return string|string[]|null 71 | */ 72 | public function extract(CommandContext $context, ArgumentStack $args): null|string|array 73 | { 74 | if (1 === $this->count && $this->require) { 75 | $component = $this->toUsageComponent($this->parameter->getName()); 76 | 77 | return $args->pop("Required argument $component"); 78 | } elseif (1 === $this->count) { 79 | return $args->tryPop(); 80 | } 81 | 82 | if ($this->require) { 83 | $collected = []; 84 | while (count($collected) < $this->count) { 85 | $component = $this->toUsageComponent($this->parameter->getName()); 86 | $collected[] = $args->pop("$component is not satisfied"); 87 | } 88 | 89 | return $collected; 90 | } else { 91 | $collected = []; 92 | while (count($collected) < $this->count) { 93 | $popped = $args->tryPop(); 94 | if (null === $popped) { 95 | break; 96 | } 97 | $collected[] = $popped; 98 | } 99 | 100 | return $collected; 101 | } 102 | } 103 | 104 | public function toUsageComponent(string $name): ?string 105 | { 106 | if ($this->require) { 107 | return "<$name: text>"; 108 | } else { 109 | return "[$name: text]"; 110 | } 111 | } 112 | 113 | public function toCommandParameter(string $name): ?CommandParameter 114 | { 115 | return CommandParameter::standard($name, ACP::ARG_TYPE_RAWTEXT, 0, !$this->require); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Arg/vector_arg.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | #[Attribute( 24 | Attribute::IS_REPEATABLE | 25 | Attribute::TARGET_METHOD 26 | )] 27 | final class vector_arg implements Arg 28 | { 29 | use SetParameterTrait; 30 | 31 | private bool $optional; 32 | private bool $relative; 33 | 34 | public function setParameter(ReflectionParameter $parameter): void 35 | { 36 | $type = $parameter->getType(); 37 | if ( 38 | !$type instanceof ReflectionNamedType || 39 | (Vector3::class !== $type->getName() && 40 | RelativeVector3::class !== $type->getName() 41 | ) 42 | ) { 43 | $name = $parameter->getName(); 44 | throw new InvalidArgumentException("The parameter ($name) corresponding to `sender()` Arg must have a type of either CommandSender or Player!"); 45 | } 46 | 47 | $this->optional = $type->allowsNull(); 48 | $this->relative = RelativeVector3::class === $type->getName(); 49 | $this->parameter = $parameter; 50 | } 51 | 52 | public function extract(CommandContext $context, ArgumentStack $args): Vector3|RelativeVector3|null 53 | { 54 | $component = $this->toUsageComponent($this->parameter->getName()); 55 | if ($this->optional) { 56 | $x = $args->tryPop(); 57 | $y = $args->tryPop(); 58 | $z = $args->tryPop(); 59 | if (null === $x || null === $y || null === $z) { 60 | return null; 61 | } 62 | } else { 63 | $x = $args->pop("Required argument $component"); 64 | $y = $args->pop("Required argument $component"); 65 | $z = $args->pop("Required argument $component"); 66 | } 67 | 68 | if ($this->relative) { 69 | [$vX, $oX] = $this->extractRelativeNumber($x); 70 | [$vY, $oY] = $this->extractRelativeNumber($y); 71 | [$vZ, $oZ] = $this->extractRelativeNumber($z); 72 | 73 | return new RelativeVector3($vX, $vY, $vZ, $oX, $oY, $oZ); 74 | } 75 | 76 | $vX = $this->extractNumber($x); 77 | $vY = $this->extractNumber($y); 78 | $vZ = $this->extractNumber($z); 79 | 80 | return new Vector3($vX, $vY, $vZ); 81 | } 82 | 83 | private function extractNumber(string $numeric, ?string $arg = null): float 84 | { 85 | if (!is_numeric($numeric)) { 86 | $arg ??= $numeric; 87 | $component = $this->toUsageComponent($this->parameter->getName()); 88 | throw new ExtractionFailed("$arg does not satisfy $component"); 89 | } 90 | 91 | return (float) $numeric; 92 | } 93 | 94 | /** 95 | * @phpstan-return array{float, bool} 96 | */ 97 | private function extractRelativeNumber(string $raw): array 98 | { 99 | $isOffset = false; 100 | $numeric = $raw; 101 | if (str_starts_with($raw, '~')) { 102 | $isOffset = true; 103 | if ('~' === $raw) { 104 | return [0, true]; 105 | } 106 | $numeric = substr($raw, 1, strlen($raw) - 1); 107 | } 108 | 109 | return [$this->extractNumber($numeric, $raw), $isOffset]; 110 | } 111 | 112 | public function toUsageComponent(string $name): ?string 113 | { 114 | if ($this->optional) { 115 | return "<$name: x y z>"; 116 | } else { 117 | return "[$name: x y z]"; 118 | } 119 | } 120 | 121 | public function toCommandParameter(string $name): ?CommandParameter 122 | { 123 | return CommandParameter::standard($name, ACP::ARG_TYPE_POSITION, 0, $this->optional); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/BoundCommand.php: -------------------------------------------------------------------------------- 1 | setPermission($permission); 38 | $this->map = new HandlerMethodTree(); 39 | $this->overloads = new OverloadMap($name); 40 | } 41 | 42 | /** 43 | * @param string[] $subNames 44 | */ 45 | public function attach(array $subNames, HandlerMethod $handlerMethod): void 46 | { 47 | $this->map->add($subNames, $handlerMethod); 48 | $this->overloads->add($subNames, $handlerMethod); 49 | $this->setUsage($this->overloads->getUsage()); 50 | } 51 | 52 | public function execute(CommandSender $sender, string $commandLabel, array $args): void 53 | { 54 | $newArgs = []; 55 | $handlerMethod = $this->map->getMostNested($args, $newArgs); 56 | if (null === $handlerMethod) { 57 | $message = $sender->getLanguage()->translate( 58 | KnownTranslationFactory::commands_generic_usage($this->getUsage()) 59 | ); 60 | $sender->sendMessage(TF::RED.$message); 61 | 62 | return; 63 | } 64 | $handlerMethod->invoke(new CommandContext($sender, $newArgs)); 65 | } 66 | 67 | /** 68 | * @return CommandParameter[][] 69 | */ 70 | public function getOverloads(): array 71 | { 72 | return $this->overloads->getOverloads(); 73 | } 74 | 75 | public function getOwningPlugin(): Plugin 76 | { 77 | return $this->plugin; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Cmd.php: -------------------------------------------------------------------------------- 1 | subNames = $subNames; 26 | } 27 | 28 | public function name(): string 29 | { 30 | return $this->name; 31 | } 32 | 33 | /** 34 | * @return string[] 35 | */ 36 | public function subNames(): array 37 | { 38 | return $this->subNames; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/CmdConfig.php: -------------------------------------------------------------------------------- 1 | name; 33 | } 34 | 35 | public function description(): string 36 | { 37 | return $this->description; 38 | } 39 | 40 | /** 41 | * @return string[] 42 | */ 43 | public function aliases(): array 44 | { 45 | return $this->aliases; 46 | } 47 | 48 | public function permission(): ?string 49 | { 50 | return $this->permission; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/CommandContext.php: -------------------------------------------------------------------------------- 1 | sender; 27 | } 28 | 29 | /** 30 | * @return string[] 31 | */ 32 | public function args(): array 33 | { 34 | return $this->args; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/CommandHintListener.php: -------------------------------------------------------------------------------- 1 | getPackets() as $pk) { 20 | if (!$pk instanceof AvailableCommandsPacket) { 21 | continue; 22 | } 23 | 24 | foreach (array_keys($pk->commandData) as $name) { 25 | $command = Server::getInstance()->getCommandMap()->getCommand($name); 26 | if (!$command instanceof BoundCommand) { 27 | continue; 28 | } 29 | 30 | $pk->commandData[$name]->overloads = $command->getOverloads(); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/Guard/Guard.php: -------------------------------------------------------------------------------- 1 | permissions = [$permission, ...$otherPermissions]; 25 | 26 | foreach ($this->permissions as $perm) { 27 | if (null === PermissionManager::getInstance()->getPermission($perm)) { 28 | throw new InvalidArgumentException("Cannot use non-existing permission \"$perm\""); 29 | } 30 | } 31 | } 32 | 33 | public function passes(CommandContext $context): null|string|Translatable 34 | { 35 | foreach ($this->permissions as $permission) { 36 | if (!$context->sender()->hasPermission($permission)) { 37 | return KnownTranslationFactory::commands_generic_permission(); 38 | } 39 | } 40 | 41 | return null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/HandlerMethod.php: -------------------------------------------------------------------------------- 1 | guards as $guard) { 39 | if (null !== ($message = $guard->passes($context))) { 40 | if ($message instanceof Translatable) { 41 | $message = $context->sender()->getLanguage()->translate($message); 42 | } 43 | $context->sender()->sendMessage(TF::RED.$message); 44 | 45 | return; 46 | } 47 | } 48 | 49 | $stack = new ArgumentStack($context->args()); 50 | $parameters = []; 51 | try { 52 | foreach ($this->args as $arg) { 53 | $parameters[] = $arg->extract($context, $stack); 54 | } 55 | } catch (ExtractionFailed $e) { 56 | $message = $e->getTranslatable(); 57 | if ($message instanceof Translatable) { 58 | $message = $context->sender()->getLanguage()->translate($message); 59 | } 60 | $context->sender()->sendMessage(TF::RED.$message); 61 | 62 | return; 63 | } 64 | 65 | $generator = $this->method->invokeArgs($this->handler, $parameters); 66 | if ($generator instanceof Generator) { 67 | Await::g2c($generator); 68 | } 69 | } 70 | 71 | /** 72 | * @return Arg[] parameter name => Arg 73 | * @phpstan-ignore-next-line generics 74 | */ 75 | public function getArgs(): array 76 | { 77 | $argCount = count($this->args); 78 | $params = $this->method->getParameters(); 79 | 80 | $args = []; 81 | for ($index = 0; $index < $argCount; ++$index) { 82 | $args[$params[$index]->getName()] = $this->args[$index]; 83 | } 84 | 85 | return $args; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/HandlerMethodTree.php: -------------------------------------------------------------------------------- 1 | root = $handlerMethod; 24 | 25 | return; 26 | } 27 | 28 | $name = array_shift($subNames); 29 | if (!isset($this->children[$name])) { 30 | $this->children[$name] = new self(); 31 | } 32 | $this->children[$name]->add($subNames, $handlerMethod); 33 | } 34 | 35 | /** 36 | * @param string[] $subNames 37 | * @param string[] $unusedNames 38 | */ 39 | public function getMostNested(array $subNames, array &$unusedNames): ?HandlerMethod 40 | { 41 | if (0 === count($subNames)) { 42 | $unusedNames = []; 43 | 44 | return $this->root; 45 | } 46 | 47 | $name = array_shift($subNames); 48 | $child = $this->children[$name] ?? null; 49 | $fetched = null === $child ? null : $child->getMostNested($subNames, $unusedNames); 50 | 51 | if (null === $fetched && null !== $this->root) { 52 | $unusedNames = [$name, ...$subNames]; 53 | 54 | return $this->root; 55 | } 56 | 57 | return $fetched; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Command/OverloadMap.php: -------------------------------------------------------------------------------- 1 | usage)) { 30 | $this->usage .= ' OR '; 31 | } 32 | $this->usage .= "/$this->name "; 33 | $subNamesString = implode(' ', $subNames); 34 | if (strlen($subNamesString) > 0) { 35 | $this->usage .= "$subNamesString "; 36 | } 37 | 38 | $overload = []; 39 | foreach ($subNames as $index => $subName) { 40 | $overload[] = CommandParameter::enum( 41 | $subName, 42 | new CommandEnum("param-$index-$subName", [$subName]), 43 | CommandParameter::FLAG_FORCE_COLLAPSE_ENUM, 44 | ); 45 | } 46 | 47 | foreach ($handlerMethod->getArgs() as $name => $arg) { 48 | $usageComponent = $arg->toUsageComponent($name); 49 | if (null !== $usageComponent) { 50 | $this->usage .= "$usageComponent "; 51 | } 52 | $parameter = $arg->toCommandParameter($name); 53 | if (null !== $parameter) { 54 | $overload[] = $parameter; 55 | } 56 | } 57 | $this->usage = rtrim($this->usage); 58 | $this->overloads[] = $overload; 59 | } 60 | 61 | /** 62 | * @return CommandParameter[][] 63 | */ 64 | public function getOverloads(): array 65 | { 66 | return $this->overloads; 67 | } 68 | 69 | public function getUsage(): string 70 | { 71 | return $this->usage; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/CustomFormElement/CustomFormElement.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Attribute(Attribute::TARGET_PROPERTY)] 14 | final class Dropdown implements CustomFormElement 15 | { 16 | /** 17 | * @param array $options 18 | * @param bool $allowDefault whether the player may skip filling 19 | * out a dropdown when its default 20 | * value is set out of range (ex: -1) 21 | */ 22 | public function __construct( 23 | private string $text, 24 | private array $options, 25 | private int $defaultOption = 0, 26 | private bool $allowDefault = true, 27 | ) { 28 | } 29 | 30 | public function extract(mixed $data): int 31 | { 32 | if ( 33 | (!is_int($data) || !isset($this->options[$data])) && 34 | !($data === $this->defaultOption && $this->allowDefault) 35 | ) { 36 | throw new FormValidationException('Invalid response to Dropdown element!'); 37 | } 38 | 39 | return $data; 40 | } 41 | 42 | public function jsonSerialize(): mixed 43 | { 44 | return [ 45 | 'type' => 'dropdown', 46 | 'text' => $this->text, 47 | 'options' => $this->options, 48 | 'default' => $this->defaultOption, 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/CustomFormElement/Input.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Attribute(Attribute::TARGET_PROPERTY)] 14 | final class Input implements CustomFormElement 15 | { 16 | public function __construct( 17 | private string $text, 18 | private string $placeholder = '', 19 | private string $default = '', 20 | ) { 21 | } 22 | 23 | public function extract(mixed $data): string 24 | { 25 | if (!is_string($data)) { 26 | throw new FormValidationException('Invalid response to Input element!'); 27 | } 28 | 29 | return $data; 30 | } 31 | 32 | public function jsonSerialize(): mixed 33 | { 34 | return [ 35 | 'type' => 'input', 36 | 'text' => $this->text, 37 | 'placeholder' => $this->placeholder, 38 | 'default' => $this->default, 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/CustomFormElement/Label.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] 14 | final class Label implements CustomFormElement 15 | { 16 | public function __construct( 17 | private string $text, 18 | ) { 19 | } 20 | 21 | public function extract(mixed $data): mixed 22 | { 23 | if (null !== $data) { 24 | throw new FormValidationException('Invalid response to Label element!'); 25 | } 26 | 27 | return null; 28 | } 29 | 30 | public function jsonSerialize(): mixed 31 | { 32 | return [ 33 | 'type' => 'label', 34 | 'text' => $this->text, 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/CustomFormElement/Slider.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Attribute(Attribute::TARGET_PROPERTY)] 14 | final class Slider implements CustomFormElement 15 | { 16 | public function __construct( 17 | private string $text, 18 | private float $min, 19 | private float $max, 20 | private float $step = 1.0, 21 | private ?float $default = null, 22 | ) { 23 | } 24 | 25 | public function extract(mixed $data): float 26 | { 27 | if ((!is_float($data) && !is_int($data)) || $data < $this->min || $data > $this->max) { 28 | throw new FormValidationException('Invalid response to Slider element!'); 29 | } 30 | 31 | return (float) $data; 32 | } 33 | 34 | public function jsonSerialize(): mixed 35 | { 36 | return [ 37 | 'type' => 'slider', 38 | 'text' => $this->text, 39 | 'min' => $this->min, 40 | 'max' => $this->max, 41 | 'step' => $this->step, 42 | 'default' => $this->default ?? $this->min, 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/CustomFormElement/StepSlider.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Attribute(Attribute::TARGET_PROPERTY)] 14 | final class StepSlider implements CustomFormElement 15 | { 16 | /** 17 | * @param array $steps 18 | */ 19 | public function __construct( 20 | private string $text, 21 | private array $steps, 22 | private int $defaultOption = 0, 23 | ) { 24 | } 25 | 26 | public function extract(mixed $data): int 27 | { 28 | if (!is_int($data) || !isset($this->steps[$data])) { 29 | throw new FormValidationException('Invalid response to StepSlider element!'); 30 | } 31 | 32 | return $data; 33 | } 34 | 35 | public function jsonSerialize(): mixed 36 | { 37 | return [ 38 | 'type' => 'step_slider', 39 | 'text' => $this->text, 40 | 'steps' => $this->steps, 41 | 'default' => $this->defaultOption, 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/CustomFormElement/Toggle.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Attribute(Attribute::TARGET_PROPERTY)] 14 | final class Toggle implements CustomFormElement 15 | { 16 | public function __construct( 17 | private string $text, 18 | private bool $default = false, 19 | ) { 20 | } 21 | 22 | public function extract(mixed $data): bool 23 | { 24 | if (!is_bool($data)) { 25 | throw new FormValidationException('Invalid response to Toggle element!'); 26 | } 27 | 28 | return $data; 29 | } 30 | 31 | public function jsonSerialize(): mixed 32 | { 33 | return [ 34 | 'type' => 'toggle', 35 | 'text' => $this->text, 36 | 'default' => $this->default, 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/CustomFormResultTrait.php: -------------------------------------------------------------------------------- 1 | 40 | */ 41 | public static function custom2then(Player $player, string $title): Thenable 42 | { 43 | $elements = []; 44 | $index2property = []; 45 | $reflection = new ReflectionClass(static::class); 46 | $properties = $reflection->getProperties(); 47 | foreach ($properties as $property) { 48 | $attrs = $property->getAttributes(CustomFormElement::class, ReflectionAttribute::IS_INSTANCEOF); 49 | $nonLabelFound = false; 50 | foreach ($attrs as $attr) { 51 | $element = $attr->newInstance(); 52 | if (!$element instanceof Label) { 53 | if ($nonLabelFound) { 54 | $name = $property->getName(); 55 | $class = static::class; 56 | throw new LogicException("Multiple non-Label element attributes on property \"$name\" of class \"$class\""); 57 | } 58 | $index2property[count($elements)] = $property; 59 | $nonLabelFound = true; 60 | } 61 | $elements[] = $element; 62 | } 63 | } 64 | 65 | // @phpstan-ignore-next-line 66 | return Thenable::promise(function ($resolve, $reject) use ($player, $title, $elements, $index2property, $reflection) { 67 | $player->sendForm(new InternalCustomForm( 68 | function (?array $response) use ($resolve, $index2property, $reflection) { 69 | if (null === $response) { 70 | return null; 71 | } 72 | $self = $reflection->newInstanceWithoutConstructor(); 73 | foreach ($index2property as $index => $property) { 74 | $property->setAccessible(true); 75 | $property->setValue($self, $response[$index]); 76 | } 77 | $resolve($self); 78 | }, 79 | $reject, $title, $elements 80 | )); 81 | }); 82 | } 83 | 84 | /** 85 | * @return Generator 86 | */ 87 | public static function custom2gen(Player $player, string $title): Generator 88 | { 89 | $elements = []; 90 | $index2property = []; 91 | $reflection = new ReflectionClass(static::class); 92 | $properties = $reflection->getProperties(); 93 | foreach ($properties as $property) { 94 | $attrs = $property->getAttributes(CustomFormElement::class, ReflectionAttribute::IS_INSTANCEOF); 95 | $nonLabelFound = false; 96 | foreach ($attrs as $attr) { 97 | $element = $attr->newInstance(); 98 | if (!$element instanceof Label) { 99 | if ($nonLabelFound) { 100 | $name = $property->getName(); 101 | $class = static::class; 102 | throw new LogicException("Multiple non-Label element attributes on property \"$name\" of class \"$class\""); 103 | } 104 | $index2property[count($elements)] = $property; 105 | $nonLabelFound = true; 106 | } 107 | $elements[] = $element; 108 | } 109 | } 110 | 111 | /** @var ?array $response */ 112 | $response = yield from Await::promise(function ($resolve, $reject) use ($player, $title, $elements) { 113 | $player->sendForm(new InternalCustomForm($resolve, $reject, $title, $elements 114 | )); 115 | }); 116 | 117 | if (null === $response) { 118 | return null; 119 | } 120 | 121 | $self = $reflection->newInstanceWithoutConstructor(); 122 | foreach ($index2property as $index => $property) { 123 | $property->setAccessible(true); 124 | $property->setValue($self, $response[$index]); 125 | } 126 | 127 | return $self; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/Forms.php: -------------------------------------------------------------------------------- 1 | 57 | */ 58 | public static function modal2then( 59 | Player $player, 60 | string $title, 61 | string $content, 62 | string $yesText = 'gui.yes', 63 | string $noText = 'gui.no', 64 | ): Thenable { 65 | /* @phpstan-ignore-next-line */ 66 | return Thenable::promise(function ($resolve, $reject) use ($player, $title, $content, $yesText, $noText) { 67 | $player->sendForm(new InternalModalForm($resolve, $reject, $title, $content, $yesText, $noText)); 68 | }); 69 | } 70 | 71 | /** 72 | * Sends a modal form that consists of a title, 73 | * content, and two buttons. The player must choose either 74 | * yes or no. 75 | * 76 | * This function returns a generator suitable with 77 | * `sof3/await-generator`, and the generator returns true 78 | * or false according to the player's choice. 79 | * 80 | * @return Generator 81 | */ 82 | public static function modal2gen( 83 | Player $player, 84 | string $title, 85 | string $content, 86 | string $yesText = 'gui.yes', 87 | string $noText = 'gui.no', 88 | ): Generator { 89 | /** @var bool $response */ 90 | $response = yield from Await::promise(function ($resolve, $reject) use ($player, $title, $content, $yesText, $noText) { 91 | $player->sendForm(new InternalModalForm($resolve, $reject, $title, $content, $yesText, $noText)); 92 | }); 93 | 94 | return $response; 95 | } 96 | 97 | /** 98 | * Sends a menu form that consists of a title, 99 | * content, and a number of buttons. The player 100 | * must choose a single button. The array of buttons 101 | * passed to this function must be a list, meaning its 102 | * first entry's key is zero, the next being one, etc. 103 | * 104 | * This function returns a Thenable that may be used when 105 | * `sof3/await-generator` is not present. Otherwise it's 106 | * recommended to use `Forms::menu2gen()` because of 107 | * AwaitGenerator's simpler syntax. 108 | * 109 | * @param array $buttons 110 | * @phpstan-return Thenable 111 | */ 112 | public static function menu2then( 113 | Player $player, 114 | string $title, 115 | string $content, 116 | array $buttons, 117 | ): Thenable { 118 | // @phpstan-ignore-next-line 119 | if ([] !== $buttons || $buttons !== array_values($buttons)) { 120 | $expected = 0; 121 | foreach (array_keys($buttons) as $index) { 122 | if ($index !== $expected++) { 123 | throw new InvalidArgumentException('The passed array of buttons is not a list!'); 124 | } 125 | } 126 | } 127 | /* @phpstan-ignore-next-line */ 128 | return Thenable::promise(function ($resolve, $reject) use ($player, $title, $content, $buttons) { 129 | $player->sendForm(new InternalMenuForm( 130 | $resolve, 131 | $reject, 132 | $title, 133 | $content, 134 | $buttons 135 | )); 136 | }); 137 | } 138 | 139 | /** 140 | * Sends a menu form that consists of a title, 141 | * content, and a number of buttons. The player 142 | * must choose a single button. The array of buttons 143 | * passed to this function must be a list, meaning its 144 | * first entry's key is zero, the next being one, etc. 145 | * 146 | * This function returns a generator suitable with 147 | * `sof3/await-generator`, and the generator returns the 148 | * index of the button chosen by the player. 149 | * 150 | * @param array $buttons 151 | * 152 | * @return Generator 153 | */ 154 | public static function menu2gen( 155 | Player $player, 156 | string $title, 157 | string $content, 158 | array $buttons, 159 | ): Generator { 160 | // @phpstan-ignore-next-line 161 | if ([] !== $buttons || $buttons !== array_values($buttons)) { 162 | $expected = 0; 163 | foreach (array_keys($buttons) as $index) { 164 | if ($index !== $expected++) { 165 | throw new InvalidArgumentException('The passed array of buttons is not a list!'); 166 | } 167 | } 168 | } 169 | /** @var ?int $response */ 170 | $response = yield from Await::promise(function ($resolve, $reject) use ($player, $title, $content, $buttons) { 171 | $player->sendForm(new InternalMenuForm( 172 | $resolve, 173 | $reject, 174 | $title, 175 | $content, 176 | $buttons 177 | )); 178 | }); 179 | 180 | return $response; 181 | } 182 | 183 | /** 184 | * Sends a custom form that consists of a title and 185 | * an array of elements. The player may fill in multiple 186 | * elements before pressing the submit button. 187 | * 188 | * This function returns a Thenable that may be used when 189 | * `sof3/await-generator` is not present. Otherwise it's 190 | * recommended to use `Forms::menu2gen()` because of 191 | * AwaitGenerator's simpler syntax. 192 | * 193 | * @param array $elements 194 | * 195 | * @phpstan-return Thenable, FormValidationException> 196 | */ 197 | public static function custom2then( 198 | Player $player, 199 | string $title, 200 | array $elements, 201 | ): Thenable { 202 | // @phpstan-ignore-next-line 203 | if ([] !== $elements || $elements !== array_values($elements)) { 204 | $expected = 0; 205 | foreach (array_keys($elements) as $index) { 206 | if ($index !== $expected++) { 207 | throw new InvalidArgumentException('The passed array of elements is not a list!'); 208 | } 209 | } 210 | } 211 | /* @phpstan-ignore-next-line */ 212 | return Thenable::promise(function ($resolve, $reject) use ($player, $title, $elements) { 213 | $player->sendForm(new InternalCustomForm( 214 | $resolve, 215 | $reject, 216 | $title, 217 | $elements 218 | )); 219 | }); 220 | } 221 | 222 | /** 223 | * Sends a custom form that consists of a title and 224 | * an array of elements. The player may fill in multiple 225 | * elements before pressing the submit button. 226 | * 227 | * This function returns a generator suitable with 228 | * `sof3/await-generator`, and the generator returns the 229 | * index of the button chosen by the player. 230 | * 231 | * @param array $elements 232 | * 233 | * @return Generator> 234 | */ 235 | public static function custom2gen( 236 | Player $player, 237 | string $title, 238 | array $elements, 239 | ): Generator { 240 | // @phpstan-ignore-next-line 241 | if ([] !== $elements || $elements !== array_values($elements)) { 242 | $expected = 0; 243 | foreach (array_keys($elements) as $index) { 244 | if ($index !== $expected++) { 245 | throw new InvalidArgumentException('The passed array of elements is not a list!'); 246 | } 247 | } 248 | } 249 | /** @var ?array $response */ 250 | $response = yield from Await::promise(function ($resolve, $reject) use ($player, $title, $elements) { 251 | $player->sendForm(new InternalCustomForm( 252 | $resolve, 253 | $reject, 254 | $title, 255 | $elements 256 | )); 257 | }); 258 | 259 | return $response; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/InternalCustomForm.php: -------------------------------------------------------------------------------- 1 | $elements 28 | */ 29 | public function __construct( 30 | private Closure $resolve, 31 | private Closure $reject, 32 | private string $title, 33 | private array $elements, 34 | ) { 35 | } 36 | 37 | public function handleResponse(Player $player, $data): void 38 | { 39 | if (null === $data) { 40 | ($this->resolve)(null); 41 | 42 | return; 43 | } 44 | 45 | if (!is_array($data)) { 46 | $exception = new FormValidationException('Expected a response of type null|array, got type '.gettype($data).' instead!'); 47 | ($this->reject)($exception); 48 | throw $exception; 49 | } 50 | 51 | $extracted = []; 52 | try { 53 | foreach ($this->elements as $index => $element) { 54 | if (!array_key_exists($index, $data)) { 55 | throw new FormValidationException("Expected response to have a value at index {$index}, but nothing was given!"); 56 | } 57 | $extracted[$index] = $element->extract($data[$index]); 58 | } 59 | } catch (FormValidationException $e) { 60 | ($this->reject)($e); 61 | throw $e; 62 | } 63 | 64 | ($this->resolve)($extracted); 65 | } 66 | 67 | public function jsonSerialize(): mixed 68 | { 69 | return [ 70 | 'type' => 'custom_form', 71 | 'title' => $this->title, 72 | 'content' => $this->elements, 73 | ]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/InternalMenuForm.php: -------------------------------------------------------------------------------- 1 | $buttons 22 | */ 23 | public function __construct( 24 | private Closure $resolve, 25 | private Closure $reject, 26 | private string $title, 27 | private string $content, 28 | private array $buttons, 29 | ) { 30 | } 31 | 32 | public function handleResponse(Player $player, $data): void 33 | { 34 | if ( 35 | null !== $data && 36 | (!is_int($data) || $data >= count($this->buttons)) 37 | ) { 38 | $exception = new FormValidationException('Expected a response of type null|int, got type '.gettype($data).' instead!'); 39 | ($this->reject)($exception); 40 | throw $exception; 41 | } 42 | 43 | ($this->resolve)($data); 44 | } 45 | 46 | public function jsonSerialize(): mixed 47 | { 48 | return [ 49 | 'type' => 'form', 50 | 'title' => $this->title, 51 | 'content' => $this->content, 52 | 'buttons' => $this->buttons, 53 | ]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/InternalModalForm.php: -------------------------------------------------------------------------------- 1 | reject)($exception); 34 | throw $exception; 35 | } 36 | 37 | ($this->resolve)($data); 38 | } 39 | 40 | public function jsonSerialize(): mixed 41 | { 42 | return [ 43 | 'type' => 'modal', 44 | 'title' => $this->title, 45 | 'content' => $this->content, 46 | 'button1' => $this->yesText, 47 | 'button2' => $this->noText, 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/MenuFormElement/MenuFormButton.php: -------------------------------------------------------------------------------- 1 | $this->text]; 20 | if (null !== $this->image) { 21 | $data['image'] = $this->image; 22 | } 23 | 24 | return $data; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Form/MenuFormElement/MenuFormImage.php: -------------------------------------------------------------------------------- 1 | $this->type, 52 | 'data' => $this->location, 53 | ]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Remark.php: -------------------------------------------------------------------------------- 1 | getCommandMap(); 40 | } 41 | 42 | $boundCommands = []; 43 | $reflection = new ReflectionClass($handler); 44 | 45 | $configs = $reflection->getAttributes(CmdConfig::class); 46 | foreach ($configs as $config) { 47 | $config = $config->newInstance(); 48 | $boundCommands[$config->name()] = new BoundCommand( 49 | $plugin, 50 | $config->name(), $config->description(), $config->aliases(), 51 | $config->permission(), 52 | ); 53 | } 54 | 55 | $methods = $reflection->getMethods(); 56 | foreach ($methods as $m) { 57 | $cmdAttrs = $m->getAttributes(Cmd::class); 58 | if (0 === count($cmdAttrs)) { 59 | // This method is not a HandlerMethod. 60 | continue; 61 | } 62 | 63 | $guards = $m->getAttributes(Guard::class, ReflectionAttribute::IS_INSTANCEOF); 64 | $guards = array_map(fn ($x) => $x->newInstance(), $guards); 65 | $args = $m->getAttributes(Arg::class, ReflectionAttribute::IS_INSTANCEOF); 66 | $args = array_map(fn ($x) => $x->newInstance(), $args); 67 | $parameters = $m->getParameters(); 68 | 69 | if (count($parameters) !== count($args)) { 70 | $name = $m->getName(); 71 | throw new InvalidArgumentException("There must be the same number of parameters as Arg's for method $name!"); 72 | } 73 | 74 | foreach ($args as $index => $arg) { 75 | /** @var Arg $arg @phpstan-ignore-next-line */ 76 | $arg->setParameter($parameters[$index]); 77 | } 78 | 79 | $handlerMethod = new HandlerMethod($handler, $m, $guards, $args); 80 | 81 | foreach ($cmdAttrs as $cmd) { 82 | $cmd = $cmd->newInstance(); 83 | if (!isset($boundCommands[$cmd->name()])) { 84 | $boundCommands[$cmd->name()] = new BoundCommand( 85 | $plugin, 86 | $cmd->name(), '', [], null, 87 | ); 88 | } 89 | $bound = $boundCommands[$cmd->name()]; 90 | $bound->attach($cmd->subNames(), $handlerMethod); 91 | } 92 | } 93 | 94 | $cm->registerAll(mb_strtolower($plugin->getName()), $boundCommands); 95 | } 96 | 97 | /** 98 | * Registers a packet listener to give command tab-completion to players. 99 | */ 100 | public static function activate(Plugin $plugin): void 101 | { 102 | $pm = Server::getInstance()->getPluginManager(); 103 | $pm->registerEvents(new CommandHintListener(), $plugin); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/DiamondStrider1/Remark/Types/RelativeVector3.php: -------------------------------------------------------------------------------- 1 | isOffsetX ? $vector->getX() + $this->valueX : $this->valueX; 24 | $y = $this->isOffsetY ? $vector->getY() + $this->valueY : $this->valueY; 25 | $z = $this->isOffsetZ ? $vector->getZ() + $this->valueZ : $this->valueZ; 26 | 27 | return new Vector3($x, $y, $z); 28 | } 29 | 30 | public function getValueX(): int|float 31 | { 32 | return $this->valueX; 33 | } 34 | 35 | public function getValueY(): int|float 36 | { 37 | return $this->valueY; 38 | } 39 | 40 | public function getValueZ(): int|float 41 | { 42 | return $this->valueZ; 43 | } 44 | 45 | public function isOffsetX(): bool 46 | { 47 | return $this->isOffsetX; 48 | } 49 | 50 | public function isOffsetY(): bool 51 | { 52 | return $this->isOffsetY; 53 | } 54 | 55 | public function isOffsetZ(): bool 56 | { 57 | return $this->isOffsetZ; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tools/php-cs-fixer/.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | in($root . $sep . 'src'); 8 | 9 | $config = new PhpCsFixer\Config(); 10 | 11 | return $config 12 | ->setRules([ 13 | '@Symfony' => true, 14 | 'phpdoc_to_comment' => [ 15 | 'ignored_tags' => ['var'] 16 | ] 17 | ]) 18 | ->setFinder($finder); 19 | -------------------------------------------------------------------------------- /tools/php-cs-fixer/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "friendsofphp/php-cs-fixer": "^3.8" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tools/phpstan/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "phpstan/phpstan": "^1.7", 4 | "phpstan/extension-installer": "^1.1", 5 | "phpstan/phpstan-strict-rules": "^1.2" 6 | }, 7 | "config": { 8 | "allow-plugins": { 9 | "phpstan/extension-installer": true 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tools/phpstan/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "5050285372ea6f613ae86d63e2044032", 8 | "packages": [ 9 | { 10 | "name": "phpstan/extension-installer", 11 | "version": "1.1.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/phpstan/extension-installer.git", 15 | "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", 20 | "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "composer-plugin-api": "^1.1 || ^2.0", 25 | "php": "^7.1 || ^8.0", 26 | "phpstan/phpstan": ">=0.11.6" 27 | }, 28 | "require-dev": { 29 | "composer/composer": "^1.8", 30 | "phing/phing": "^2.16.3", 31 | "php-parallel-lint/php-parallel-lint": "^1.2.0", 32 | "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" 33 | }, 34 | "type": "composer-plugin", 35 | "extra": { 36 | "class": "PHPStan\\ExtensionInstaller\\Plugin" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "PHPStan\\ExtensionInstaller\\": "src/" 41 | } 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "MIT" 46 | ], 47 | "description": "Composer plugin for automatic installation of PHPStan extensions", 48 | "support": { 49 | "issues": "https://github.com/phpstan/extension-installer/issues", 50 | "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" 51 | }, 52 | "time": "2020-12-13T13:06:13+00:00" 53 | }, 54 | { 55 | "name": "phpstan/phpstan", 56 | "version": "1.7.6", 57 | "source": { 58 | "type": "git", 59 | "url": "https://github.com/phpstan/phpstan.git", 60 | "reference": "1af9271ea529f995e57a1f493bebba6b830a97d0" 61 | }, 62 | "dist": { 63 | "type": "zip", 64 | "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1af9271ea529f995e57a1f493bebba6b830a97d0", 65 | "reference": "1af9271ea529f995e57a1f493bebba6b830a97d0", 66 | "shasum": "" 67 | }, 68 | "require": { 69 | "php": "^7.2|^8.0" 70 | }, 71 | "conflict": { 72 | "phpstan/phpstan-shim": "*" 73 | }, 74 | "bin": [ 75 | "phpstan", 76 | "phpstan.phar" 77 | ], 78 | "type": "library", 79 | "autoload": { 80 | "files": [ 81 | "bootstrap.php" 82 | ] 83 | }, 84 | "notification-url": "https://packagist.org/downloads/", 85 | "license": [ 86 | "MIT" 87 | ], 88 | "description": "PHPStan - PHP Static Analysis Tool", 89 | "support": { 90 | "issues": "https://github.com/phpstan/phpstan/issues", 91 | "source": "https://github.com/phpstan/phpstan/tree/1.7.6" 92 | }, 93 | "funding": [ 94 | { 95 | "url": "https://github.com/ondrejmirtes", 96 | "type": "github" 97 | }, 98 | { 99 | "url": "https://github.com/phpstan", 100 | "type": "github" 101 | }, 102 | { 103 | "url": "https://www.patreon.com/phpstan", 104 | "type": "patreon" 105 | }, 106 | { 107 | "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", 108 | "type": "tidelift" 109 | } 110 | ], 111 | "time": "2022-05-30T21:29:45+00:00" 112 | }, 113 | { 114 | "name": "phpstan/phpstan-strict-rules", 115 | "version": "1.2.3", 116 | "source": { 117 | "type": "git", 118 | "url": "https://github.com/phpstan/phpstan-strict-rules.git", 119 | "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" 120 | }, 121 | "dist": { 122 | "type": "zip", 123 | "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", 124 | "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", 125 | "shasum": "" 126 | }, 127 | "require": { 128 | "php": "^7.2 || ^8.0", 129 | "phpstan/phpstan": "^1.6.3" 130 | }, 131 | "require-dev": { 132 | "nikic/php-parser": "^4.13.0", 133 | "php-parallel-lint/php-parallel-lint": "^1.2", 134 | "phpstan/phpstan-phpunit": "^1.0", 135 | "phpunit/phpunit": "^9.5" 136 | }, 137 | "type": "phpstan-extension", 138 | "extra": { 139 | "phpstan": { 140 | "includes": [ 141 | "rules.neon" 142 | ] 143 | } 144 | }, 145 | "autoload": { 146 | "psr-4": { 147 | "PHPStan\\": "src/" 148 | } 149 | }, 150 | "notification-url": "https://packagist.org/downloads/", 151 | "license": [ 152 | "MIT" 153 | ], 154 | "description": "Extra strict and opinionated rules for PHPStan", 155 | "support": { 156 | "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", 157 | "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" 158 | }, 159 | "time": "2022-05-04T15:20:40+00:00" 160 | } 161 | ], 162 | "packages-dev": [], 163 | "aliases": [], 164 | "minimum-stability": "stable", 165 | "stability-flags": [], 166 | "prefer-stable": false, 167 | "prefer-lowest": false, 168 | "platform": [], 169 | "platform-dev": [], 170 | "plugin-api-version": "2.3.0" 171 | } 172 | -------------------------------------------------------------------------------- /tools/phpstan/phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: max 3 | paths: 4 | - ../../src 5 | -------------------------------------------------------------------------------- /tutorial/README.md: -------------------------------------------------------------------------------- 1 | # The Remark Tutorial 2 | 3 | The following are the source files for *The Remark Tutorial*. The book can be read [here](https://swift-strider.github.io/Remark/) 4 | -------------------------------------------------------------------------------- /tutorial/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["DiamondStrider1"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "The Remark Tutorial" 7 | -------------------------------------------------------------------------------- /tutorial/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](./introduction.md) 4 | 5 | - [Quick Guide](./quick-guide/index.md) 6 | - [Command Handling](./quick-guide/command_handling.md) 7 | - [Forms](./quick-guide/forms.md) 8 | - [In Depth](./in-depth/index.md) 9 | - [Installing](./in-depth/installing.md) 10 | - [Commands](./in-depth/commands.md) 11 | - [Forms](./in-depth/forms.md) 12 | - [Command Arg's](./in-depth/command_args.md) 13 | - [Command Guard's](./in-depth/command_guards.md) 14 | - [Custom Form Elements](./in-depth/custom_form_elements.md) 15 | -------------------------------------------------------------------------------- /tutorial/src/in-depth/command_args.md: -------------------------------------------------------------------------------- 1 | # Command Args 2 | An `Arg` provides a value to its corresponding parameter of a HandlerMethod. The number of `Args` in a HandlerMethod must be equal to its number of parameters. An `Arg` may extract its value from the arguments given when the command is run, or it may get its value from another source. 3 | 4 | ## sender 5 | Extracts the `CommandSender`. If its corresponding parameter has the type `Player` it will do an instance-of check automatically. Otherwise the type of the parameter must be CommandSender. 6 | 7 | *`sender()` does not take any arguments.* 8 | 9 | ## player_arg 10 | Extracts a `Player` using the name provided by a command argument. 11 | ```php 12 | bool $exact = false, 13 | ``` 14 | * exact - whether to not match by prefix 15 | 16 | ## text 17 | Extracts one or more strings from the command arguments. Depending on the parameters given to this Arg, the corresponding parameter must have a type of `string`, `?string`, or `array`. 18 | ```php 19 | int $count = 1, 20 | bool $require = true, 21 | ``` 22 | * count - number of arguments to take 23 | * require - wether to fail if the number 24 | of arguments remaining is less than count 25 | 26 | ## remaining 27 | Extracts the remaining strings from the command arguments. 28 | 29 | *`remaining()` does not take any arguments.* 30 | 31 | ## enum 32 | Extracts a string that must be in an immutable set of predefined strings. 33 | ```php 34 | string $name, 35 | string $choice, 36 | string ...$otherChoices, 37 | ``` 38 | * name - the enum's name, in-game it will show 39 | as a command hint (i. e. ``) 40 | * choice - A possible choice 41 | * otherChoices - Other possible choices 42 | 43 | ## bool_arg 44 | Extracts a true / false boolean. 45 | Valid command arguments for both choices are: 46 | - true: "true", "on", and "yes" 47 | - false: "false", "off", and "no" 48 | 49 | *`bool_arg()` does not take any arguments.* 50 | 51 | ## int_arg 52 | Extracts an integer. 53 | 54 | *`int_arg()` does not take any arguments.* 55 | 56 | ## float_arg 57 | Extracts a float. 58 | 59 | *`float_arg()` does not take any arguments.* 60 | 61 | ## vector_arg 62 | Extracts either a Vector3 or RelativeVector3 depending on the type of it's corresponding parameter. 63 | 64 | If your parameter has the type RelativeVector3, you can then call `$relativeVector->relativeTo($vector)` to get a real Vector3. 65 | 66 | *`vector_arg()` does not take any arguments.* 67 | 68 | ## json_arg 69 | Extracts a string **WITHOUT** validating that it's proper json. This is more of a marker, telling the player's client that JSON is needed. You **MUST** verify that the JSON is valid, yourself. 70 | 71 | *`json_arg()` does not take any arguments.* 72 | 73 | ## command_arg 74 | Extracts a string **WITHOUT** validating that it's the name of a command. This is more of a marker, telling the player's client that a command name is needed. You **MUST** verify that the command name is valid, yourself. 75 | 76 | *`command_arg()` does not take any arguments.* 77 | -------------------------------------------------------------------------------- /tutorial/src/in-depth/command_guards.md: -------------------------------------------------------------------------------- 1 | # Command Guards 2 | A `Guard` prevents a HandlerMethod from being ran when a requirement isn't satisfied. I. e. the command sender not having permission. 3 | 4 | ## permission 5 | Requires that the `CommandSender` has all of the permissions passed in. 6 | ```php 7 | string $permission, 8 | string ...$otherPermissions, 9 | ``` 10 | * permission - One required permission 11 | * otherPermissions - Other required permissions 12 | -------------------------------------------------------------------------------- /tutorial/src/in-depth/commands.md: -------------------------------------------------------------------------------- 1 | # In Depth | Commands 2 | 3 | `Remark::command()` is used to bind the HandlerMethods of an object to a CommandMap. By default, `Remark::command()` uses the CommandMap attached to the running PocketMine Server. Under-the-hood, a new `BoundCommand` is made for every `CmdConfig`, and they implement `PluginOwned` returning the plugin passed to `Remark::command()`. 4 | 5 | `Remark::activate()` registers a listener which will add `TAB`-completion for players. 6 | 7 | ## CmdConfig 8 | Configures the commands of a handler object. 9 | ```php 10 | string $name, 11 | string $description, 12 | array $aliases = [], 13 | ?string $permission = null, 14 | ``` 15 | * name - The name of the underlying command 16 | * description - The description of the command 17 | * aliases - The aliases of the command 18 | * permission - If set, one or more permissions separated by `;` 19 | 20 | ## Cmd 21 | Marks a method as a handler for a command. You may bind a HandlerMethod to multiple commands by repeating this attribute. 22 | ```php 23 | string $name, 24 | string ...$subNames, 25 | ``` 26 | * name - The name of the command to attach this HandlerMethod to 27 | * subNames - Zero or more subcommand names 28 | -------------------------------------------------------------------------------- /tutorial/src/in-depth/custom_form_elements.md: -------------------------------------------------------------------------------- 1 | # Custom Form Elements 2 | 3 | `Dropdown`, `Input`, `Label`, `Slider`, `StepSlider`, and `Toggle` are found in the `DiamondStrider1\Remark\Forms\CustomFormElement` namespace and all implement `CustomFormElement`. They may be used as normal classes (`new Label('Some Text')`) or as attributes (`#[Label('Some Text')]`). 4 | 5 | Information on creating custom forms can be found [here](forms.md#custom-form). 6 | 7 | ## Dropdown 8 | Returns an integer which is the index of the choice the player selected. 9 | ```php 10 | string $text, 11 | array $options, 12 | int $defaultOption = 0, 13 | bool $allowDefault = true, 14 | ``` 15 | * allowDefault - whether the player may skip filling out a dropdown when the Dropdown's default value is -1 16 | 17 | ## Input 18 | Returns a string that the player entered. 19 | ```php 20 | string $text, 21 | string $placeholder = '', 22 | string $default = '', 23 | ``` 24 | 25 | ## Label 26 | Does not return anything, but it does place text at its location. 27 | ```php 28 | string $text 29 | ``` 30 | 31 | ## Slider 32 | Returns a float in within the range [min, max]. It **DOES NOT** validate the step, however, so that responsibility is left to the developer. 33 | ```php 34 | string $text, 35 | float $min, 36 | float $max, 37 | float $step = 1.0, 38 | ?float $default = null, 39 | ``` 40 | 41 | ## StepSlider 42 | Returns an integer, the index of the step the player chose. Visually, looks like a Slider but the player chooses one of the steps. 43 | ```php 44 | string $text, 45 | array $steps, 46 | int $defaultOption = 0, 47 | ``` 48 | * steps - list of strings to choose from 49 | 50 | ## Toggle 51 | Returns a boolean. Creates a switch that the player can toggle. 52 | ```php 53 | string $text, 54 | bool $default = false, 55 | ``` 56 | -------------------------------------------------------------------------------- /tutorial/src/in-depth/forms.md: -------------------------------------------------------------------------------- 1 | # In Depth | Forms 2 | The `DiamondStrider1\Remark\Form\Forms` class holds static methods for creating forms. They are `modal2then()`, `modal2gen()`, `menu2then()`, `menu2gen()`, `custom2then()`, and `custom2gen()`. The `*2gen()` methods return generators to be used with await-generator, and the `*2then()` methods return `Thenable` a type defined by Remark. 3 | 4 | # Thenable 5 | A `Thenable` is much like a promise. One may call `$thenable->then($onResolve, $onReject)`. If you omit `$onReject` Remark will throw an `UnhandledAsyncException` if the `Thenable` is rejected. 6 | 7 | # Modal Form 8 | Use either `Forms::modal2then()` or `Forms::modal2gen()`. Resolves with a boolean, `true` if the yes button was hit, `false` if the no button was. 9 | ```php 10 | Player $player, 11 | string $title, 12 | string $content, 13 | string $yesText = 'gui.yes', 14 | string $noText = 'gui.no', 15 | ``` 16 | 17 | # Menu Form 18 | Use either `Forms::menu2then()` or `Forms::menu2gen()`. Resolves with an integer, the index of the button the player chose. 19 | ```php 20 | Player $player, 21 | string $title, 22 | string $content, 23 | array $buttons, 24 | ``` 25 | * buttons - A list of `MenuFormButton`s 26 | 27 | ## MenuFormButton 28 | A button for a menu form. 29 | ```php 30 | string $text, 31 | ?MenuFormImage $image = null, 32 | ``` 33 | * text - The text of the button 34 | * image - An optional image to display 35 | 36 | ## MenuFormImage 37 | An image that can be to present on a menu form. 38 | ```php 39 | string $type, 40 | string $location, 41 | ``` 42 | * type - Either 'url' or 'path'. 43 | * location - The location of the image 44 | 45 | If the image's type is `url`, Minecraft will 46 | fetch the image from online, and it may take 47 | some time to load. If the type is `path`, 48 | Minecraft will instantly load the image from 49 | the resource pack. 50 | 51 | On Minecraft Windows 10, `url` images may not 52 | show until ALT-TAB'ing out of then back into Minecraft. 53 | 54 | An example location of a `path` type image is 55 | "textures/block/dirt.png" without the leading 56 | slash. 57 | 58 | # Custom Form 59 | You may use `Forms::custom2then()`/`Forms::custom2gen()` or `CustomFormResultTrait`. 60 | 61 | ## Custom Form, static functions 62 | Returns an array with the indexes of elements mapped to the values of the player's response. 63 | ```php 64 | Player $player, 65 | string $title, 66 | array $elements, 67 | ``` 68 | * elements - A list of `CustomFormElement`s 69 | 70 | ## Custom Form, CustomFormResultTrait 71 | 72 | A class that uses this trait will have the static methods `custom2gen()` and `custom2then()` added which return a new instance of the class instead of an array. 73 | 74 | A class using CustomFormResultTrait must meet the following 75 | requirements: 76 | - Must not be abstract 77 | - Every property to be filled in... 78 | - May be marked with any number of Label attributes 79 | - Must be marked with at most one CustomFormElement that isn't Label 80 | 81 | Properties are filled in according to the non-Label attribute 82 | attached to them, or ignored if only Labels are attached to them. 83 | 84 | All properties without CustomFormElement attributes are ignored. 85 | -------------------------------------------------------------------------------- /tutorial/src/in-depth/index.md: -------------------------------------------------------------------------------- 1 | # In Depth 2 | 3 | For installing for development and production check [Installing]. 4 | 5 | Documentation can be found for [Command Args], [Command Guards], and [Custom Form Elements]. 6 | 7 | [Installing]: installing.md 8 | [Command Args]: command_args.md 9 | [Command Guards]: command_guards.md 10 | [Custom Form Elements]: custom_form_elements.md 11 | -------------------------------------------------------------------------------- /tutorial/src/in-depth/installing.md: -------------------------------------------------------------------------------- 1 | # Installing 2 | 3 | Setting up your plugin to use Remark is quite easy! 4 | 5 | It's advised that during development [DEVirion](#using-devirion) is used and when the plugin is ready production that this library is either installed [by Poggit](#installing-with-poggit) or installed [by hand](#manually-installing-into-a-plugin-phar). 6 | 7 | ## Using DEVirion 8 | 9 | DEVirion allows your plugin to use Remark. 10 | 11 | 1. Install `Remark.phar` from this project's [Github Releases](https://github.com/Swift-Strider/Remark/releases/). 12 | 1. Place `Remark.phar` into your server's `virions` folder, which is next to the `plugins` folder. 13 | 1. If not already downloaded, get DEVirion from [Poggit](https://poggit.pmmp.io/p/DEVirion/). 14 | 15 | ## Installing with Poggit 16 | 17 | Plugins that are built on poggit and use virions should declare there dependencies in `.poggit.yml`. To use Remark add an entry like this. 18 | ```yml 19 | projects: 20 | my-pugin-project: 21 | path: "" 22 | libs: 23 | - src: Swift-Strider/Remark/Remark 24 | version: ^3.3.0 25 | epitope: .random 26 | ``` 27 | 28 | ## Manually Installing into a Plugin Phar 29 | 30 | Install `Remark.phar` from this project's [Github Releases](https://github.com/Swift-Strider/Remark/releases/). 31 | 32 | If you haven't already, build your plugin into a phar file. This example script assumes you're in your plugin's directory and that the files/directories `plugin.yml`, `src`, and `resources` exist. The following works on both `Windows 10 (Powershell)` and `Ubuntu`. 33 | ```sh 34 | wget https://raw.githubusercontent.com/pmmp/DevTools/master/src/ConsoleScript.php -O ConsoleScript.php 35 | php -dphar.readonly=0 ConsoleScript.php --make src,resources,plugin.yml --relative . --out plugin.phar 36 | ``` 37 | 38 | Next you will "infect" your plugin's `.phar` file, embedding the Remark library inside of your plugin. 39 | ```sh 40 | php -dphar.readonly=0 Remark.phar plugin.phar 41 | ``` 42 | -------------------------------------------------------------------------------- /tutorial/src/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | For onlooking developers, Remark simplifies Command Handling and Forms through a declarative and asynchronous syntax. It is a virion that makes heavy uses of PHP 8's new attributes to provide simple, elegant solutions for usually boilerplate-heavy command handling. It tackles the async nature of PocketMine through await-generator, making complex forms easy. 4 | 5 | For getting started check out the [Quick Guide]! 6 | 7 | [Quick Guide]: quick-guide/index.md 8 | [Command Handling]: quick-guide/command_handling.md 9 | [Forms]: quick-guide/forms.md 10 | -------------------------------------------------------------------------------- /tutorial/src/quick-guide/command_handling.md: -------------------------------------------------------------------------------- 1 | # Command Handling 2 | 3 | Prerequisites: 4 | - Basic knowledge of PHP 5 | 6 | ## About Attributes 7 | 8 | Skip to [Your First Command](#your-first-command) if you already know PHP 8's attributes. 9 | 10 | PHP 8 added attributes, markers that can be placed on classes or methods and later found by Remark through reflection. 11 | ```php 12 | #[Test(debug: false)] 13 | public function myMethod(): void {} 14 | ``` 15 | PHP 8.1 attributes look a lot like comments. They start with `#[` and end with `]` and inside the brackets the name of attributes are placed. Attributes are really just classes, and inside you put parameters for their constructor. You have to make sure to import the attribute just like you would for a class. 16 | 17 | You can have multiple attributes inside the same `#[]` structure. 18 | ```php 19 | #[Test(debug: false), Logging(level: 2)] 20 | public function myMethod(): void {} 21 | ``` 22 | You can also omit the parameter names, to make the attributes more concise. 23 | ```php 24 | #[Test(false), Logging(2)] 25 | public function myMethod(): void {} 26 | ``` 27 | 28 | ## Your First Command 29 | 30 | In Remark, you mark methods using attributes to describe how you want to take arguments from the command line. 31 | ```php 32 | use DiamondStrider1\Remark\Command\Cmd; 33 | use DiamondStrider1\Remark\Command\CmdConfig; 34 | use DiamondStrider1\Remark\Command\Arg\sender; 35 | use DiamondStrider1\Remark\Command\Arg\text; 36 | use DiamondStrider1\Remark\Command\Guard\permission; 37 | use pocketmine\command\CommandSender; 38 | 39 | #[CmdConfig( 40 | name: 'helloworld', 41 | description: 'Run my hello world command!', 42 | aliases: ['hw'], 43 | permission: 'plugin.helloworld.use' 44 | )] 45 | class MyCommand { 46 | #[Cmd('helloworld'), permission('plugin.helloworld.use')] 47 | #[sender(), text()] 48 | public function myFirstCommand(CommandSender $sender, string $text): void 49 | { 50 | $sender->sendMessage("§aHello Command Handling! - $text"); 51 | } 52 | } 53 | ``` 54 | This is a lot of code to tackle, but before going over it let's register our command so it can be used. 55 | 56 | ## Registering Your First Command 57 | ```php 58 | public function onEnable(): void 59 | { 60 | Remark::command($this, new MyCommand()); 61 | Remark::activate($this); 62 | } 63 | ``` 64 | 65 | `Remark::command()` adds one or more `BoundCommand`s that implement `PluginOwned`. `Remark::activate()` registers a listener that will add `TAB`-completion for players. 66 | 67 | ## The Breakdown 68 | Let's go over everything in `MyCommand.php`. 69 | 70 | #### CmdConfig Attribute 71 | ```php 72 | #[CmdConfig( /* ... */ )] 73 | ``` 74 | CmdConfig customizes the underlying command that will ultimately be registered to PocketMine's CommandMap. Everything is pretty self-explanatory. `name` is the name of the command (i. e. `/command_name`).`permission` is set as the permission of the command, hiding the command from those who have insufficient permissions. **IT DOES NOT**, however, stop people from running the command by itself. We will get into that soon. 75 | 76 | #### Cmd and permission 77 | ```php 78 | #[Cmd('helloworld'), permission('plugin.helloworld.use')] 79 | ``` 80 | This line uses two attributes at once. The method these attributes are placed on are called a HandlerMethod. A method simply has to be marked by `Cmd` to become a HandlerMethod. 81 | 82 | #### Cmd Attribute 83 | The `Cmd` is passed the name of the command. You can change the command to a subcommand by adding a comma followed by another string. For example, `#[Cmd('cmd', 'subcmd')]` would bind the method to the subcommand at `/cmd subcmd`. The command or subcommand chosen must be unique, meaning there is one HandlerMethod per command/subcommand. 84 | 85 | #### The permission Guard 86 | The `permission` is something Remark calls a `Guard`. `Guard`s prevent unauthorized access to commands by ensuring that certain requirements are met. If the `permission` guard fails, it will send a generic not-enough-permissions error to the command sender. The HandlerMethod only runs if all `Guard`s attached to it succeed. Unlike the `permission` property of `CmdConfig`, this guard actually enforces that the command sender has permission to using the command. More permissions can be added by adding a comma and another string, ex: `permission('perm1', 'perm2')`. 87 | 88 | #### Args 89 | ```php 90 | #[sender(), text()] 91 | ``` 92 | 93 | Ranked's `Arg`s have many responsibilities. 94 | * Extracting data from command line arguments 95 | * Validation 96 | * Converting from string to a useful type (i. e. int) 97 | 98 | It's required that for every `Arg` of a HandlerMethod that there is a corresponding parameter of the correct type. That parameter will be given the value extracted by the Arg whenever the command is run. 99 | 100 | #### The sender() Arg 101 | The `sender()` Arg requires that it's parameter is of type `CommandSender` or `Player`. It doesn't actually take any arguments from the command line, but simply supplies the command sender performing an `instanceof` check if needed. 102 | 103 | #### The text() Arg 104 | The `text()` Arg requires that it's parameter is of type `string`, `?string` or `array` depending on the arguments passed to it. In this case the type of the parameter must be `string`. By default `text()` takes a single string from the command line arguments, and errors if none was given. 105 | 106 | # Asynchronous Commands 107 | A key aspect of Remark is it's asynchronous approach to UI. This is important because of how complex asynchronous programming will become if not given care. AwaitGenerator is a virion that allows async/await style programming through Generators and their pause/resume functions. It's not required you use AwaitGenerator, but it is recommended and supported by Remark. 108 | 109 | Here is the example extended to use Remark's first-class support of AwaitGenerator. 110 | ```php 111 | #[Cmd('helloworld'), permission('plugin.helloworld.use')] 112 | #[sender(), text()] 113 | public function myFirstCommand(CommandSender $sender, string $text): Generator 114 | { 115 | $sender->sendMessage("§aHello Command Handling! - $text"); 116 | $response = yield from AsyncApi::fetch($text); 117 | if ($sender instanceof Player && !$sender->isConnected()) { 118 | return; 119 | } 120 | $sender->sendMessage("Got Response: $response"); 121 | } 122 | ``` 123 | 124 | The return type of the function is now `Generator`, and `yield from` is used to call other AwaitGenerator functions. Remember to check that the player is still connected after doing any asynchronous logic. 125 | 126 | If you got all of that, let's move on to [forms](forms.md)! 127 | -------------------------------------------------------------------------------- /tutorial/src/quick-guide/forms.md: -------------------------------------------------------------------------------- 1 | # Forms 2 | 3 | Prerequisites: 4 | - Basic knowledge of PHP 5 | - Basic knowledge of PHP 8's attributes 6 | 7 | The main way to send forms to players is through Remark's `DiamondStrider1\Remark\Form\Forms` class. It contains the methods `modal2then()`, `modal2gen()`, `menu2then()`, `menu2gen()`, `custom2then()`, and `custom2gen()`. Methods ending in `gen` return an AwaitGenerator compatible generator that sends the form of its type and returns it's result. Methods ending in `then` exist in case AwaitGenerator isn't available, and return Thenable's that are resolved with the form's result. 8 | 9 | Let's continue the example from [Command Handling](command_handling.md). 10 | ```php 11 | #[Cmd('helloworld'), permission('plugin.helloworld.use')] 12 | #[sender(), text()] 13 | public function myFirstCommand(CommandSender $sender, string $text): Generator 14 | { 15 | $sender->sendMessage("§aHello Command Handling! - $text"); 16 | $response = yield from AsyncApi::fetch($text); 17 | if ($sender instanceof Player && !$sender->isConnected()) { 18 | return; 19 | } 20 | $sender->sendMessage("Got Response: $response"); 21 | } 22 | ``` 23 | For those not familiar with Remark's [command handling](command_handling.md), the method `myFirstCommand()` will be ran whenever a player (or the console) runs `/helloworld` and has the permission `plugin.helloworld.use`. 24 | 25 | ## ModalForm 26 | Starting off simple, we will send the player a yes/no modal form. Modal forms cannot be closed out of, if you try to hit `ESC` the game doesn't acts as if you had pressed the no button. 27 | ```php 28 | /** @var bool $choice */ 29 | $choice = yield from Forms::modal2gen( 30 | $sender, 31 | 'What is your favorite ice cream?', 32 | 'Pick from these ice cream flavors.', 33 | ); 34 | $choice = match ($choice) { 35 | true => '§aYes', 36 | false => '§cNo', 37 | }; 38 | $sender->sendMessage("You said {$choice}§r."); 39 | ``` 40 | Notice we don't have to check if the player's online because when a PocketMine guarantees the player is still connected when a form is submitted. 41 | 42 | ## MenuForm 43 | Let's now give the player a menu form with options to choose from. 44 | ```php 45 | /** @var ?int $choice */ 46 | $choice = yield from Forms::menu2gen( 47 | $sender, 48 | 'What is your favorite ice cream?', 49 | 'Pick from these ice cream flavors.', 50 | [ 51 | new MenuFormButton('Vanilla'), 52 | new MenuFormButton('Blueberry'), 53 | new MenuFormButton('Lime'), 54 | ] 55 | ); 56 | if (null !== $choice) { 57 | $choice = ['vanilla', 'blueberry', 'lime'][$choice]; 58 | $sender->sendMessage("You chose §g{$choice}§r."); 59 | } 60 | ``` 61 | A MenuFormButton can also have an attached MenuFormImage like so. 62 | ```php 63 | new MenuFormButton('My Button', new MenuFormImage(type: 'url', location: 'https://my.image.com/image')) 64 | new MenuFormButton('My Button', new MenuFormImage(type: 'path', location: 'textures/blocks/dirt.png')) 65 | ``` 66 | 67 | ## CustomForm 68 | Now for the most complex type of form. A custom form sends a list of elements for the player to fill out and a submit button to press when the player is finished. Remark gives you two ways to create a custom form. First we will start with the `custom2gen()`/`custom2then()` method. 69 | ```php 70 | /** @var array $response */ 71 | $response = yield from Forms::custom2gen( 72 | $sender, 73 | 'What is your favorite ice cream?', 74 | [ 75 | new Label('Type a valid ice cream flavor!'), 76 | new Input('Ice Cream Flavor', placeholder: 'Vanilla'), 77 | ] 78 | ); 79 | $sender->sendMessage("You said {$response[1]}§r."); 80 | ``` 81 | Using this method of sending custom forms, you have to index the response data with the index of the element. Remark already handles the validation for you. 82 | 83 | Another way to make custom forms is through attributes. 84 | ```php 85 | use DiamondStrider1\Remark\Form\CustomFormElement\Label; 86 | use DiamondStrider1\Remark\Form\CustomFormElement\Input; 87 | use DiamondStrider1\Remark\Form\CustomFormResultTrait; 88 | 89 | final class MySurveyForm 90 | { 91 | use CustomFormResultTrait; 92 | 93 | #[Label('Type a valid ice cream flavor!')] 94 | #[Input('Ice Cream Flavor', placeholder: 'Vanilla')] 95 | public string $name; 96 | } 97 | ``` 98 | ```php 99 | /** @var MySurveyForm $formResult */ 100 | $formResult = yield from MySurveyForm::custom2gen( 101 | $sender, 'What is your favorite ice cream?' 102 | ); 103 | $sender->sendMessage("You said {$formResult->name}§r."); 104 | ``` 105 | The CustomFormResultTrait adds the static functions `custom2gen()` and `custom2then()` that take a player and a title for the form. It's recommended that you mark form fields as public so they are easily accessible. 106 | -------------------------------------------------------------------------------- /tutorial/src/quick-guide/index.md: -------------------------------------------------------------------------------- 1 | # Quick Guide 2 | 3 | Get familiar with Remark, fast! 4 | 5 | Start with learning [Command Handling] by building a plugin step-by-step. 6 | 7 | Or skip straight to [Forms] if that's what you're looking for. 8 | 9 | [Command Handling]: command_handling.md 10 | [Forms]: forms.md 11 | -------------------------------------------------------------------------------- /virion.yml: -------------------------------------------------------------------------------- 1 | name: Remark 2 | antigen: DiamondStrider1\Remark 3 | version: 1.2.2 4 | api: 4.0.0 5 | author: DiamondStrider1 6 | --------------------------------------------------------------------------------