diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 191b0e340d9..5b930003730 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,14 @@ version: 2 updates: - package-ecosystem: "github-actions" + target-branch: "dev/patch" directory: "/" schedule: interval: "weekly" labels: - "dependencies" - package-ecosystem: "gradle" + target-branch: "dev/patch" directory: "/" schedule: interval: "weekly" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ce87d77a996..698a65a0554 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,6 +2,6 @@ --- -**Target Minecraft Versions:** -**Requirements:** -**Related Issues:** +**Target Minecraft Versions:** any +**Requirements:** none +**Related Issues:** none diff --git a/.github/workflows/cleanup-docs.yml b/.github/workflows/cleanup-docs.yml new file mode 100644 index 00000000000..750bab83213 --- /dev/null +++ b/.github/workflows/cleanup-docs.yml @@ -0,0 +1,39 @@ +name: Cleanup nightly documentation +on: delete +jobs: + cleanup-nightly-docs: + if: github.event.ref_type == 'branch' + runs-on: ubuntu-latest + steps: + - name: Configure workflow + id: configuration + env: + DELETED_BRANCH: ${{ github.event.ref }} + run: | + BRANCH_NAME="${DELETED_BRANCH#refs/*/}" + echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT + - name: Checkout Skript + uses: actions/checkout@v3 + with: + ref: ${{ github.event.repository.default_branch }} + submodules: recursive + path: skript + - name: Setup documentation environment + uses: ./skript/.github/workflows/docs/setup-docs + with: + docs_deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} + docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + - name: Cleanup nightly documentation + env: + DOCS_OUTPUT_DIR: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + run: | + rm -rf ${DOCS_OUTPUT_DIR} || true + - name: Push nightly documentation cleanup + uses: ./skript/.github/workflows/docs/push-docs + with: + docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + git_name: Nightly Docs Bot + git_email: nightlydocs@skriptlang.org + git_commit_message: "Delete ${{ steps.configuration.outputs.BRANCH_NAME }} branch nightly docs" diff --git a/.github/workflows/docs/generate-docs/action.yml b/.github/workflows/docs/generate-docs/action.yml index f84023a60df..e033996868e 100644 --- a/.github/workflows/docs/generate-docs/action.yml +++ b/.github/workflows/docs/generate-docs/action.yml @@ -18,24 +18,92 @@ inputs: required: false default: false type: boolean + cleanup_pattern: + description: "A pattern designating which files to delete when cleaning the documentation output directory" + required: false + default: "*" + type: string + generate_javadocs: + description: "Designates whether to generate javadocs for this nightly documentation" + required: false + default: false + type: boolean + +outputs: + DOCS_CHANGED: + description: "Whether or not the documentation has changed since the last push" + value: ${{ steps.generate.outputs.DOCS_CHANGED }} runs: using: 'composite' steps: - name: generate-docs + id: generate shell: bash env: DOCS_OUTPUT_DIR: ${{ inputs.docs_output_dir }} DOCS_REPO_DIR: ${{ inputs.docs_repo_dir }} SKRIPT_REPO_DIR: ${{ inputs.skript_repo_dir }} IS_RELEASE: ${{ inputs.is_release }} + CLEANUP_PATTERN: ${{ inputs.cleanup_pattern }} + GENERATE_JAVADOCS: ${{ inputs.generate_javadocs }} run: | - export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/doc-templates - export SKRIPT_DOCS_OUTPUT_DIR=${DOCS_OUTPUT_DIR}/ + replace_in_directory() { + find $1 -type f -exec sed -i -e "s/$2/$3/g" {} \; + } + + # this should be replaced with a more reliable jq command, + # but it can't be right now because docs.json is actually not valid json. + get_skript_version_of_directory() { + grep skriptVersion "$1/docs.json" | cut -d\" -f 4 + } + + if [ -d "${DOCS_REPO_DIR}/docs/templates" ] + then + export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/docs/templates + else # compatibility for older versions + export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/doc-templates + fi + + export SKRIPT_DOCS_OUTPUT_DIR=/tmp/generated-docs + cd $SKRIPT_REPO_DIR if [[ "${IS_RELEASE}" == "true" ]]; then ./gradlew genReleaseDocs releaseJavadoc - else + elif [[ "${GENERATE_JAVADOCS}" == "true" ]]; then ./gradlew genNightlyDocs javadoc + else + ./gradlew genNightlyDocs fi - cp -a "./build/docs/javadoc/." "${DOCS_OUTPUT_DIR}/javadocs" + + if [ -d "${DOCS_OUTPUT_DIR}" ]; then + if [[ "${GENERATE_JAVADOCS}" == "true" ]]; then + mkdir -p "${SKRIPT_DOCS_OUTPUT_DIR}/javadocs" && cp -a "./build/docs/javadoc/." "$_" + fi + + mkdir -p "/tmp/normalized-output-docs" && cp -a "${DOCS_OUTPUT_DIR}/." "$_" + mkdir -p "/tmp/normalized-generated-docs" && cp -a "${SKRIPT_DOCS_OUTPUT_DIR}/." "$_" + + output_skript_version=$(get_skript_version_of_directory "/tmp/normalized-output-docs") + generated_skript_version=$(get_skript_version_of_directory "/tmp/normalized-generated-docs") + + replace_in_directory "/tmp/normalized-output-docs" "${output_skript_version}" "Skript" + replace_in_directory "/tmp/normalized-generated-docs" "${generated_skript_version}" "Skript" + + diff -qbr /tmp/normalized-output-docs /tmp/normalized-generated-docs || diff_exit_code=$? + # If diff exits with exit code 1, that means there were some differences + if [[ ${diff_exit_code} -eq 1 ]]; then + echo "DOCS_CHANGED=true" >> $GITHUB_OUTPUT + echo "Documentation has changed since last push" + else + echo "Documentation hasn't changed since last push" + fi + else + echo "DOCS_CHANGED=true" >> $GITHUB_OUTPUT + echo "No existing documentation found" + fi + + rm -rf ${DOCS_OUTPUT_DIR}/${CLEANUP_PATTERN} || true + mkdir -p "${DOCS_OUTPUT_DIR}/" && cp -a "${SKRIPT_DOCS_OUTPUT_DIR}/." "$_" + + diff --git a/.github/workflows/docs/push-docs/action.yml b/.github/workflows/docs/push-docs/action.yml index bcc296a85c3..477a4c46aa4 100644 --- a/.github/workflows/docs/push-docs/action.yml +++ b/.github/workflows/docs/push-docs/action.yml @@ -1,10 +1,6 @@ -name: Generate documentation +name: Push documentation inputs: - docs_output_dir: - description: "The directory to generate the documentation into" - required: true - type: string docs_repo_dir: description: "The skript-docs repository directory" required: true @@ -38,4 +34,10 @@ runs: git config user.email "${GIT_EMAIL}" git add -A git commit -m "${GIT_COMMIT_MESSAGE}" || (echo "Nothing to push!" && exit 0) - git push origin main + # Attempt rebasing and pushing 5 times in case another job pushes before us + for i in 1 2 3 4 5 + do + git pull --rebase -X theirs origin main + git push origin main && break + sleep 5 + done diff --git a/.github/workflows/docs/setup-docs/action.yml b/.github/workflows/docs/setup-docs/action.yml index 1feedcd8f10..cd6c6a05216 100644 --- a/.github/workflows/docs/setup-docs/action.yml +++ b/.github/workflows/docs/setup-docs/action.yml @@ -38,7 +38,6 @@ runs: CLEANUP_PATTERN: ${{ inputs.cleanup_pattern }} run: | eval `ssh-agent` - rm -rf ${DOCS_OUTPUT_DIR}/${CLEANUP_PATTERN} || true echo "$DOCS_DEPLOY_KEY" | tr -d '\r' | ssh-add - > /dev/null mkdir ~/.ssh ssh-keyscan www.github.com >> ~/.ssh/known_hosts diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 67007d76d90..fa432f7f17d 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index adcd71fb09b..4a36e74a44b 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index eca1ebb9706..69c5b73f27a 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/junit-8-builds.yml b/.github/workflows/junit-8-builds.yml index 93971c79624..3aa98f554a7 100644 --- a/.github/workflows/junit-8-builds.yml +++ b/.github/workflows/junit-8-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml index 2a6841c3771..0d5cc824ad0 100644 --- a/.github/workflows/nightly-docs.yml +++ b/.github/workflows/nightly-docs.yml @@ -3,43 +3,58 @@ name: Nightly documentation on: push: branches: - - '**' + - 'dev/feature' + - 'dev/patch' + - 'enhancement/**' + - 'feature/**' + - 'fix/**' tags-ignore: - '**' jobs: nightly-docs: - if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + if: "!contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - name: Configure workflow id: configuration + env: + DOCS_DEPLOY_KEY: ${{ secrets.DOCS_DEPLOY_KEY }} run: | + if [ -n "$DOCS_DEPLOY_KEY" ] + then + echo "DOCS_DEPLOY_KEY_PRESENT=true" >> $GITHUB_OUTPUT + else + echo "Secret 'DOCS_DEPLOY_KEY' not present. Exiting job." + fi BRANCH_NAME="${GITHUB_REF#refs/*/}" echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITHUB_OUTPUT echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${BRANCH_NAME}" >> $GITHUB_OUTPUT echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT echo "SKRIPT_REPO_DIR=${GITHUB_WORKSPACE}/skript" >> $GITHUB_OUTPUT - name: Checkout Skript - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive path: skript - name: Setup documentation environment + if: steps.configuration.outputs.DOCS_DEPLOY_KEY_PRESENT == 'true' uses: ./skript/.github/workflows/docs/setup-docs with: docs_deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} - name: Generate documentation + id: generate + if: steps.configuration.outputs.DOCS_DEPLOY_KEY_PRESENT == 'true' uses: ./skript/.github/workflows/docs/generate-docs with: docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} - name: Push nightly documentation + if: steps.generate.outputs.DOCS_CHANGED == 'true' uses: ./skript/.github/workflows/docs/push-docs with: - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} git_name: Nightly Docs Bot git_email: nightlydocs@skriptlang.org diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index 9ec5a5ad257..06cb5569076 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -17,7 +17,7 @@ jobs: echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT echo "SKRIPT_REPO_DIR=${GITHUB_WORKSPACE}/skript" >> $GITHUB_OUTPUT - name: Checkout Skript - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive path: skript @@ -26,7 +26,6 @@ jobs: with: docs_deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} - cleanup_pattern: "!(nightly|archives)" - name: Generate documentation uses: ./skript/.github/workflows/docs/generate-docs with: @@ -34,6 +33,7 @@ jobs: docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} is_release: true + cleanup_pattern: "!(nightly|archives|templates)" - name: Push release documentation uses: ./skript/.github/workflows/docs/push-docs with: @@ -56,7 +56,7 @@ jobs: echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT echo "SKRIPT_REPO_DIR=${GITHUB_WORKSPACE}/skript" >> $GITHUB_OUTPUT - name: Checkout Skript - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive path: skript @@ -68,14 +68,12 @@ jobs: - name: Generate documentation uses: ./skript/.github/workflows/docs/generate-docs with: - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} is_release: true - name: Push archive documentation uses: ./skript/.github/workflows/docs/push-docs with: - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} git_name: Archive Docs Bot git_email: archivedocs@skriptlang.org diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index d7639b98a94..3b6640cc765 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -8,7 +8,7 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/CLOCKWORK_RELEASE_MODEL.md b/CLOCKWORK_RELEASE_MODEL.md new file mode 100644 index 00000000000..e5e086f62eb --- /dev/null +++ b/CLOCKWORK_RELEASE_MODEL.md @@ -0,0 +1,294 @@ +# Clockwork Release Model + +## Table of Contents +1. [Introduction](#introduction) + - [Preamble](#preamble) + - [Motivations](#motivations) + - [Goals](#goals) + - [Non-Goals](#non-goals) +2. [Release Types](#release-types) + - [Feature Releases](#feature-releases) + - [Patch Releases](#patch-releases) + - [Pre-Releases](#pre-releases) + - [Emergency Patch Releases](#emergency-patch-releases) +3. [Timetable](#timetable) + - [Major Version Schedule](#major-version-schedule) + - [Pre-Release Schedule](#pre-release-schedule) + - [Patch Schedule](#patch-schedule) +4. [Content Curation](#content-curation) + - [Labels](#labels) + - [Branches](#branches) +5. [Conclusion](#conclusion) + - [Paradigm Versions](#addendum-1-paradigm-versions) + - [Failure Standards](#addendum-2-failure-standards) + +## Introduction + +### Preamble + +This document defines the structure of future Skript releases, the kinds of material included in releases and the outline of the dates on which releases will be published. + +A 'release' is the publication of a verified and signed build artefact on the GitHub releases tab, made available for all users to download and install. + +This document does *not* cover the distribution or publication of artifacts built in other ways (e.g. privately, from a nightly action) or those not published from our GitHub (e.g. test builds shared in our public testing group). + +Plans for a new release model began in March 2023 and several models were discussed, with this being the final version agreed upon by the organisation's administrative group and approved by the core contributors. + +### Motivations + +The release cycle for the `2.7.0` version was significant in that it took an unusually long time and included an unusually-large number of additions and changes. + +While it was not the first version to have taken a long time to finalise and produce, it was distinct in that a lot of time had passed since the previous public build of Skript having been marked stable. + +Members of the organisation and the wider community identified several problems that resulted from this, some of which are (not exhaustively) detailed below: +- 291 days had passed since the previous release + - Users were unable to benefit from bug fixes or new features produced during that time + - Although beta versions were released, these were marked unstable and were not fully tested +- When the release arrived it contained a very large number of changes and additions + - Some users were unaware of changes that could not be extensively documented in the changelog or were buried in a large list + - Users who obtained a build elsewhere (e.g. direct download, automatic installer) may have been unaware of the scale of the changes +- Several additions were made at short notice and without sufficient testing + - Some of these introduced problems that required fixes in a following `2.7.1` patch +- Several features could not be completed in time and had to be dropped to a future `2.8.0` version + - One result of this was that any corrections or improvements made as part of these were not present in `2.7.0` + - Aspects of some of these larger-scale changes had to be re-created or cherry-picked for the `2.7.0` version +- The release lacked a clear timetable or vision for what additions to include + - The initial timetable was not adhered to; plans were made for pre-releases to begin at the end of November 2022, which was delayed to early December in order to accommodate a large feature PR (which was eventually dropped to `2.8.0`) + - Delays persisted, and the final release took place 7 months later in September 2023 + - There was no clear cut-off point for new features; feature pull requests were being included even up to 3 days before release + +Of these, the principle complaint is that the `2.7.0` version took a significant amount of time to finish and this had an adverse effect on the community and the wider ecosystem. + +### Goals + +Our release model has been designed to achieve the following goals: +1. To reduce the delay between finishing new features and releasing them to the public. +2. To significantly reduce the time between an issue being fixed and that fix being made public in a stable build. +3. To reduce the risk of untested changes going into a release. +4. To make the release timetable clear and accessible. +5. To prevent a version being indefinitely delayed to accommodate additional changes. + +### Non-Goals + +This release model is not intended to change any of the following: +- The content or feature-theme of a particular version. +- The process for reviewing or approving changes. + +## Release Types + +This section details the different categories of version for release. + +The versioning will follow the form `A.B.C`, where `B` is a [feature version](#Feature-Releases) containing changes and additions, `C` is a [patch version](#Patch-Releases) containing only issue fixes, and `A` is reserved for major paradigmatic changes. + +### Feature Releases +A 'feature' version (labelled `0.X.0`) may contain: +- Additions to the language (syntax, grammar, structures) +- Bug fixes +- Developer API additions and changes +- Breaking changes1 + +All content added to a feature version must pass through the typical review process. +Content must also have been included in a prior [pre-release](#Pre-Releases) for public testing. + +> 1 Breaking changes are to be avoided where possible but may be necessary, such as in a case where a significant improvement could be made to an existing feature but only by changing its structure somehow. +> Such changes should be clearly labelled and well documented, preferably giving users ample notice. + +### Patch Releases +A 'patch' version (labelled `0.0.X`) may contain: +- Bug fixes +- Non-impactful2 improvements to existing features +- Changes to meta content (e.g. documentation) + +There may be **very rare** occasions when a breaking change is necessary in a patch release. These may occur if and only if: either a breaking change is required in order to fix an issue, and the issue is significant enough to need fixing in a patch rather than waiting for a major release, or an issue occurred with an inclusion in the version immediately-prior to this, which must be changed or reverted in some way. + +All content added to a patch version must pass through the typical review process. + +> 2 A non-impactful change is one in which there is no apparent difference to the user in how a feature is employed or what it does but that may have a material difference in areas such as performance, efficiency or machine resource usage. + +### Pre-Releases + +A 'pre-release' version (labelled `0.X.0-preY`) will contain all of the content expected to be in the feature release immediately following this. + +Pre-release versions are a final opportunity for testing and getting public feedback on changes before a major release, allowing time to identify and fix any issues before the proper release, rather than needing an immediate patch. + +The content of a pre-release should be identical to the content of the upcoming release -- barring any bug fixes -- and new content should never be included after a pre-release. + +### Emergency Patch Releases + +An 'emergency patch' version will be released if a critical security vulnerability is reported that the organisation feels prevents an immediate risk to the user base, such that it cannot wait for the subsequent patch. + +An emergency patch will be labelled as another patch version (`0.0.X`). It should be noted that an emergency patch will *not* disrupt the typical timetable detailed below. + +These kinds of releases may be published immediately and do not have to go through the typical reviewing and testing process. \ +They must never include content, additions or unnecessary changes. + +The only content permitted in an emergency patch is the material needed to fix the security risk. + +The exact nature of the security vulnerability (such as the means to reproduce it) should not be included in the notes surrounding the release. + +## Timetable + +The 'clockwork' release model follows a strict monthly cycle, with versions being released on exact dates. + +A table of (expected) dates is displayed below. + +| Date | Release Type | Example Version
Name | +|----------|-----------------|-------------------------| +| 1st Jan | Pre-release | 0.1.0-pre1 | +| 15th Jan | Feature release | 0.1.0 | +| 1st Feb | Patch | 0.1.1 | +| 1st Mar | Patch | 0.1.2 | +| 1st Apr | Patch | 0.1.3 | +| 1st May | Patch | 0.1.4 | +| 1st Jun | Patch | 0.1.5 | +| 1st Jul | Pre-release | 0.2.0-pre1 | +| 15th Jul | Feature release | 0.2.0 | +| 1st Aug | Patch | 0.2.1 | +| 1st Sep | Patch | 0.2.2 | +| 1st Oct | Patch | 0.2.3 | +| 1st Nov | Patch | 0.2.4 | +| 1st Dec | Patch | 0.2.5 | + +An estimated 14 releases are expected per year, with 10 patches, 2 pre-releases and 2 feature-releases that immediately follow them. + +Please note that the actual number may differ from this in cases such as: +- A version requiring multiple pre-releases to correct mistakes (`0.3.0-pre1`, `0.3.0-pre2`) +- An emergency patch having to be released +- No bug fixes being prepared in a month, meaning no patch is needed + +There is no fixed timetable for the circulation of unpublished builds to the public testing group or the addon developers group. + +### Major Version Schedule + +A [feature version](#feature-releases) will be released on the **15th of January** and the **15th of July**. + +This will include all finished content from the previous 6 months that was tested in the pre-release. + +Any features, additions or changes that were *not* ready or approved at the time of the pre-release may **not** be included in the feature release [according to goal 3](#goals). \ +The feature release must **not** be delayed to accomodate content that was not ready by the deadline [according to goal 5](#goals). + +If there is no content ready at the scheduled date of a feature release, the release will be skipped and a notice published explaining this. + +### Pre-Release Schedule + +A [pre-release](#pre-releases) will be released on the **1st of January** and the **1st of July**, leaving two weeks before the following release for public testing to occur. + +This pre-release may include all finished content from the previous 6 months. + +Any features, additions or changes that have *not* passed the review/approval process by the day of the pre-release may **not** be included in the pre-release [according to goal 3](#goals). \ +The pre-release must **not** be delayed to accomodate content that was not ready by the deadline [according to goal 5](#goals). + +If there is no content ready at the scheduled date of a pre-release, the entire feature-release will be skipped and a notice published explaining this. + +If issues are found requiring a new build be produced (e.g. the build fails to load, a core feature is non-functional, a fix was made but needs additional testing) then another version of the pre-release may be published. +There is no limit on the number of pre-releases that can be published if required. + +### Patch Schedule + +A [patch](#patch-releases) will be released on the **1st** of every month (except January and July) containing any fixes prepared during the previous month(s). + +On the 1st of January and July the patch will be replaced by the pre-release. + +A patch should include all bug fixes from the previous month that have passed the review/approval process. + +Ideally, a patch build should be circulated in the public testing group prior to its release, but this is not a strict requirement. + +If there are no applicable bug fixes ready by the scheduled date of the patch then the month will be skipped and the patch will not take place. A public notice is not required to explain this. + +## Content Curation + +To help curate content on our GitHub repository we have designed a new branch model and accompanying labels for categorising contributions. + +### Labels + +We shall provide issue and pull request labels to help categorise changes to prevent contributions missing a release (or slipping into the incorrect kind of release). + +1. `patch-ready` \ + to denote a pull request that has: + - passed the review/approval process + - is of the sufficient kind to be included in a monthly patch version +2. `feature-ready` \ + to denote a pull request that has: + - passed the review/approval process + - should wait for a biannual feature release + - is not suitable to be included in a patch + +### Branches + +We shall maintain three core branches: `dev/patch`, `dev/feature` and `master`, which function vertically3. + +We may also create legacy branches where necessary. \ +As an example, if a previous release, say `2.6.4` requires an emergency security update, a branch can be made from its release tag and the patch may directly target that branch (and be released). + +We may also maintain other assorted branches for individual features, for the purpose of group work or for experimentation. These are not detailed below. + +> 3 Changes are always made to the 'top' (working) branch and then this is merged downwards into the more stable branch below when required. +> +> Branches are never merged upwards. + +#### Patch + +Pull requests that only address issues or are otherwise suitable for a patch release should target the **`dev/patch` branch**. These may be merged whenever appropriate (i.e. all review and testing requirements have passed). + +At the time of the patch release, the **patch branch** will be merged downwards into the **master branch**, and a release will be created from the **master branch**. + +When a feature release occurs and all branches are merged into the master branch, the patch branch will be rebased off the current master commit, effectively bringing it up to speed with the new changes. \ +As an example, when feature version 0.5.0 releases, the patch branch will be at 0.4.5 and missing the new features, so must be rebased off the current release and catch up before changes for version 0.5.1 may be merged. + +#### Feature + +Pull requests that add features, make breaking changes or are otherwise unsuitable for a patch version should target the **`dev/feature` branch**. \ +These should be merged whenever appropriate (i.e. all review and testing requirements have passed), so that testing builds can be created and circulated in the public testing group. + +The **patch branch** may be merged downwards into the **feature branch** whenever appropriate (e.g. after changes have been made to it that may affect the state of contributions targeting the feature branch). + +The feature branch should __**never**__ be merged upwards into the patch branch4. + +The feature branch is only merged downwards into the master branch directly before a full feature release (i.e. after the pre-release and testing is complete.) + +Pre-releases are made directly from the feature branch5. At the end of the pre-release testing period the feature branch can be merged downwards into the master branch in order for the full release to be made. + +> 4 Merges only ever occur downwards. For the patch branch to see changes from the feature branch it must be rebased onto master branch after a feature release occurs. +> +> 5 Merging the branch down for the pre-release would introduce potentially-buggy, untested changes to the master branch. + +#### Master + +The **`master` branch** should reflect the most recent feature release. +Pull requests should **never** directly target the master branch. Changes are made to one of the other branches (as applicable) and then that branch is merged downwards into the **master branch** only when it is time for a release. + +This means that any user building from the master branch is guaranteed to receive a safe, stable build of the quality that we would release. + +The master branch should never be merged upwards into the feature or patch branches. If these branches need to see changes from the master branch they must be rebased onto the latest master branch commit. + +## Conclusion + +It is our aim that this release model will address all of our goals and satisfy our motivations. + +Setting a strict and regular schedule ought to prevent too much time passing without a release, while also helping to prevent a single release from becoming bloated and overbearing. + +By including testing requirements and mandating public pre-releases we hope to solve the persistent issue of untested changes slipping into supposedly-stable versions. + +Finally, by scheduling regular patches we aim to reduce the time between a bug being 'fixed' by a contributor and the userbase being able to benefit from that fix. Keeping these patches as small, controlled releases allows us to mark them as 'stable' therefore letting the version reach a wider audience. + +### Addendum 1: Paradigm Versions + +Paradigmatic `X.0.0` versions were deliberately excluded from this proposal. \ +The reasoning behind this choice was that over 10 years have passed since the inception of major version`2.0.0` in 2012, the previous paradigmatic change. + +As of writing this document there are proposals and roadmaps for a version `3.0.0` but no timetable or predicted date on the horizon. + +This kind of version, were it to be released, would likely take the place of a typical feature release in the model calendar, i.e. occurring on the 15th of January or July. However, due to its potentially-significant nature it may require exceptional changes to the pre-release cycle. + +As details of such a version are neither known nor easy to predict, it has been left to the discretion of the future team to be decided when required. + +### Addendum 2: Failure Standards + +No proposal is complete without failure standards; this model can be deemed to have failed if, in two years' time: +1. The delay between finishing new features and releasing them to the public has not been reduced. +2. The delay between an issue being fixed and that fix being made public in a stable build has not been reduced. +3. Untested features are being released in 'stable' builds. +4. The release timetable is unclear or inaccessible. +5. Versions are being indefinitely delayed to accommodate additional changes. + +Additionally, if this model is considered to have put an undue burden on the core development team, to the extent that it has hampered progress in a significant and measurable way, then it can be considered to have failed. diff --git a/README.md b/README.md index e23e1a288d2..36c82e5c00f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ versions will be supported as soon as possible. ## Download You can find the downloads for each version with their release notes in the [releases page](https://github.com/SkriptLang/Skript/releases). +Two major feature updates are expected each year in January and July, with monthly patches occurring in between. For full details, please review our [release model](CLOCKWORK_RELEASE_MODEL.md). + ## Documentation Documentation is available [here](https://docs.skriptlang.org/) for the latest version of Skript. diff --git a/build.gradle b/build.gradle index 5920cb95ac8..7ce7614c3b0 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.0' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.1-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.2-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -40,7 +40,7 @@ dependencies { implementation fileTree(dir: 'lib', include: '*.jar') testShadow group: 'junit', name: 'junit', version: '4.13.2' - testShadow group: 'org.easymock', name: 'easymock', version: '5.1.0' + testShadow group: 'org.easymock', name: 'easymock', version: '5.2.0' } task checkAliases { @@ -149,14 +149,6 @@ license { exclude('**/*.json') // JSON files do not have headers } -javadoc { - source = sourceSets.main.allJava - classpath = configurations.compileClasspath - options.encoding = 'UTF-8' - // currently our javadoc has a lot of errors, so we need to suppress the linter - options.addStringOption('Xdoclint:none', '-quiet') -} - task releaseJavadoc(type: Javadoc) { title = project.property('version') source = sourceSets.main.allJava @@ -254,7 +246,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi } } -def latestEnv = 'java17/paper-1.20.1.json' +def latestEnv = 'java17/paper-1.20.2.json' def latestJava = 17 def oldestJava = 8 @@ -394,3 +386,25 @@ task nightlyRelease(type: ShadowJar) { ) } } + +javadoc { + dependsOn nightlyResources + source = sourceSets.main.allJava + + exclude("ch/njol/skript/conditions/**") + exclude("ch/njol/skript/expressions/**") + exclude("ch/njol/skript/effects/**") + exclude("ch/njol/skript/events/**") + exclude("ch/njol/skript/sections/**") + exclude("ch/njol/skript/structures/**") + exclude("ch/njol/skript/lang/function/EffFunctionCall.java") + exclude("ch/njol/skript/lang/function/ExprFunctionCall.java") + exclude("ch/njol/skript/hooks/**") + exclude("ch/njol/skript/test/**") + + classpath = configurations.compileClasspath + sourceSets.main.output + options.encoding = 'UTF-8' + // currently our javadoc has a lot of errors, so we need to suppress the linter + options.addStringOption('Xdoclint:none', '-quiet') +} + diff --git a/gradle.properties b/gradle.properties index 62d7b588c56..1d5c2fb3e73 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupid=ch.njol name=skript -version=2.7.0 +version=2.7.1 jarName=Skript.jar -testEnv=java17/paper-1.20.1 +testEnv=java17/paper-1.20.2 testEnvJavaVersion=17 diff --git a/settings.gradle b/settings.gradle index 0a8d7b2d301..e5432e8214f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' } rootProject.name = 'Skript' diff --git a/skript-aliases b/skript-aliases index fb9c3044e55..1ee77d8573a 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit fb9c3044e555667b4dc5558467608bd55fa32df0 +Subproject commit 1ee77d8573aa37456f1b49fe12aec7bb410d1dd7 diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 45d076b7846..9740ee27efa 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -18,76 +18,9 @@ */ package ch.njol.skript; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.Thread.UncaughtExceptionHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.logging.Filter; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; - -import org.bstats.bukkit.Metrics; -import org.bstats.charts.SimplePie; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.ServerCommandEvent; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.java.JavaPlugin; -import org.eclipse.jdt.annotation.Nullable; -import org.junit.runner.JUnitCore; -import org.junit.runner.Result; -import org.skriptlang.skript.lang.entry.EntryValidator; -import org.skriptlang.skript.lang.script.Script; -import org.skriptlang.skript.lang.structure.Structure; -import org.skriptlang.skript.lang.structure.StructureInfo; - -import com.google.common.collect.Lists; -import com.google.gson.Gson; - import ch.njol.skript.aliases.Aliases; import ch.njol.skript.bukkitutil.BurgerHelper; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.comparator.Comparator; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.classes.data.BukkitClasses; import ch.njol.skript.classes.data.BukkitEventValues; import ch.njol.skript.classes.data.DefaultComparators; @@ -124,8 +57,6 @@ import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.log.Verbosity; import ch.njol.skript.registrations.Classes; -import org.skriptlang.skript.lang.comparator.Comparators; -import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.registrations.EventValues; import ch.njol.skript.test.runner.EffObjectives; import ch.njol.skript.test.runner.SkriptJUnitTest; @@ -153,6 +84,73 @@ import ch.njol.util.StringUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import org.bstats.bukkit.Metrics; +import org.bstats.charts.SimplePie; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.ServerCommandEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.skriptlang.skript.lang.comparator.Comparator; +import org.skriptlang.skript.lang.comparator.Comparators; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.Converters; +import org.skriptlang.skript.lang.entry.EntryValidator; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.structure.Structure; +import org.skriptlang.skript.lang.structure.StructureInfo; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Filter; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; // TODO meaningful error if someone uses an %expression with percent signs% outside of text or a variable @@ -770,7 +768,7 @@ protected void afterErrors() { SkriptConfig.defaultEventPriority.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ') )); metrics.addCustomChart(new SimplePie("logPlayerCommands", () -> - SkriptConfig.logPlayerCommands.value().toString() + String.valueOf((SkriptConfig.logEffectCommands.value() || SkriptConfig.logPlayerCommands.value())) )); metrics.addCustomChart(new SimplePie("maxTargetDistance", () -> SkriptConfig.maxTargetBlockDistance.value().toString() diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index 7f01e3ff331..62261d0e426 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -398,7 +398,7 @@ else if (args[0].equalsIgnoreCase("info")) { else if (args[0].equalsIgnoreCase("gen-docs")) { File templateDir = Documentation.getDocsTemplateDirectory(); if (!templateDir.exists()) { - Skript.error(sender, "Cannot generate docs! Documentation templates not found at 'plugins/Skript/doc-templates/'"); + Skript.error(sender, "Cannot generate docs! Documentation templates not found at '" + Documentation.getDocsTemplateDirectory().getPath() + "'"); TestMode.docsFailed = true; return true; } diff --git a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java index 19588312957..4cd9c445173 100644 --- a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java +++ b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java @@ -18,6 +18,7 @@ */ package ch.njol.skript; +import ch.njol.skript.doc.Documentation; import ch.njol.skript.test.runner.TestMode; import ch.njol.util.StringUtils; import org.bukkit.command.Command; @@ -116,7 +117,7 @@ public List onTabComplete(CommandSender sender, Command command, String options.add("disable"); options.add("update"); options.add("info"); - if (new File(Skript.getInstance().getDataFolder() + "/doc-templates").exists()) + if (Documentation.getDocsTemplateDirectory().exists()) options.add("gen-docs"); if (TestMode.DEV_MODE) options.add("test"); diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index 552debddd5c..d3803407f2c 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -126,7 +126,14 @@ public class SkriptConfig { public static final Option enableEffectCommands = new Option<>("enable effect commands", false); public static final Option effectCommandToken = new Option<>("effect command token", "!"); public static final Option allowOpsToUseEffectCommands = new Option<>("allow ops to use effect commands", false); - + + /* + * @deprecated Will be removed in 2.8.0. Use {@link #logEffectCommands} instead. + */ + @Deprecated + public static final Option logPlayerCommands = new Option<>("log player commands", false).optional(true); + public static final Option logEffectCommands = new Option<>("log effect commands", false); + // everything handled by Variables public static final OptionSection databases = new OptionSection("databases"); @@ -164,8 +171,7 @@ public static String formatDate(final long timestamp) { return null; } }); - - public static final Option logPlayerCommands = new Option("log player commands", false); + /** * Maximum number of digits to display after the period for floats and doubles diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java index b628c56a887..2b3fa65af5b 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -845,6 +845,30 @@ public Block get(PlayerMoveEvent event) { return event.getTo().clone().subtract(0, 0.5, 0).getBlock(); } }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter() { + @Override + public Location get(PlayerMoveEvent event) { + return event.getFrom(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter() { + @Override + public Location get(PlayerMoveEvent event) { + return event.getTo(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerMoveEvent.class, Chunk.class, new Getter() { + @Override + public Chunk get(PlayerMoveEvent event) { + return event.getFrom().getChunk(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerMoveEvent.class, Chunk.class, new Getter() { + @Override + public Chunk get(PlayerMoveEvent event) { + return event.getTo().getChunk(); + } + }, EventValues.TIME_NOW); // PlayerItemDamageEvent EventValues.registerEventValue(PlayerItemDamageEvent.class, ItemStack.class, new Getter() { @Override @@ -1367,7 +1391,7 @@ public ItemStack get(PlayerEditBookEvent event) { book.setItemMeta(event.getNewBookMeta()); return book; } - }, EventValues.TIME_FUTURE); + }, EventValues.TIME_NOW); EventValues.registerEventValue(PlayerEditBookEvent.class, String[].class, new Getter() { @Override public String[] get(PlayerEditBookEvent event) { @@ -1379,7 +1403,7 @@ public String[] get(PlayerEditBookEvent event) { public String[] get(PlayerEditBookEvent event) { return event.getNewBookMeta().getPages().toArray(new String[0]); } - }, EventValues.TIME_FUTURE); + }, EventValues.TIME_NOW); //ItemDespawnEvent EventValues.registerEventValue(ItemDespawnEvent.class, Item.class, new Getter() { @Override @@ -1425,21 +1449,6 @@ public TeleportCause get(final PlayerTeleportEvent e) { return e.getCause(); } }, 0); - //PlayerMoveEvent - EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter() { - @Override - @Nullable - public Location get(PlayerMoveEvent e) { - return e.getFrom(); - } - }, EventValues.TIME_PAST); - EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter() { - @Override - @Nullable - public Location get(PlayerMoveEvent e) { - return e.getTo(); - } - }, EventValues.TIME_NOW); //EntityMoveEvent if (Skript.classExists("io.papermc.paper.event.entity.EntityMoveEvent")) { EventValues.registerEventValue(EntityMoveEvent.class, Location.class, new Getter() { diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index 90a3f5d740a..35e12057bb1 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -21,9 +21,7 @@ import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; -import ch.njol.skript.config.validate.SectionValidator; import ch.njol.skript.lang.Effect; -import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.localization.ArgsMessage; @@ -39,17 +37,17 @@ import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.SimpleCommandMap; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.help.HelpMap; import org.bukkit.help.HelpTopic; import org.bukkit.plugin.SimplePluginManager; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.script.Script; import java.io.File; import java.lang.reflect.Field; @@ -59,7 +57,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.regex.Pattern; @@ -106,24 +103,24 @@ public static SimpleCommandMap getCommandMap(){ private static void init() { try { if (Bukkit.getPluginManager() instanceof SimplePluginManager) { - final Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap"); + Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap"); commandMapField.setAccessible(true); commandMap = (SimpleCommandMap) commandMapField.get(Bukkit.getPluginManager()); - final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); knownCommandsField.setAccessible(true); cmKnownCommands = (Map) knownCommandsField.get(commandMap); try { - final Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases"); + Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases"); aliasesField.setAccessible(true); cmAliases = (Set) aliasesField.get(commandMap); - } catch (final NoSuchFieldException e) {} + } catch (NoSuchFieldException ignored) {} } - } catch (final SecurityException e) { + } catch (SecurityException e) { Skript.error("Please disable the security manager"); commandMap = null; - } catch (final Exception e) { + } catch (Exception e) { Skript.outdatedError(e); commandMap = null; } @@ -137,71 +134,33 @@ private static void init() { @SuppressWarnings("null") private final static Pattern unescape = Pattern.compile("\\\\[" + Pattern.quote("(|)<>%\\") + "]"); - public static String escape(String s) { - return "" + escape.matcher(s).replaceAll("\\\\$0"); + public static String escape(String string) { + return "" + escape.matcher(string).replaceAll("\\\\$0"); } - public static String unescape(String s) { - return "" + unescape.matcher(s).replaceAll("$0"); + public static String unescape(String string) { + return "" + unescape.matcher(string).replaceAll("$0"); } private final static Listener commandListener = new Listener() { - @SuppressWarnings("null") - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onPlayerCommand(final PlayerCommandPreprocessEvent e) { - if (handleCommand(e.getPlayer(), e.getMessage().substring(1))) - e.setCancelled(true); - } - @SuppressWarnings("null") @EventHandler(priority = EventPriority.HIGHEST) - public void onServerCommand(final ServerCommandEvent e) { - if (e.getCommand() == null || e.getCommand().isEmpty() || e.isCancelled()) - return; - if ((Skript.testing() || SkriptConfig.enableEffectCommands.value()) && e.getCommand().startsWith(SkriptConfig.effectCommandToken.value())) { - if (handleEffectCommand(e.getSender(), e.getCommand())) { - e.setCancelled(true); - } + public void onServerCommand(ServerCommandEvent event) { + if (event.getCommand().isEmpty() || event.isCancelled()) return; + if ((Skript.testing() || SkriptConfig.enableEffectCommands.value()) && event.getCommand().startsWith(SkriptConfig.effectCommandToken.value())) { + if (handleEffectCommand(event.getSender(), event.getCommand())) + event.setCancelled(true); } } }; - - /** - * @param sender - * @param command full command string without the slash - * @return whether to cancel the event - */ - static boolean handleCommand(final CommandSender sender, final String command) { - final String[] cmd = command.split("\\s+", 2); - cmd[0] = cmd[0].toLowerCase(Locale.ENGLISH); - if (cmd[0].endsWith("?")) { - final ScriptCommand c = commands.get(cmd[0].substring(0, cmd[0].length() - 1)); - if (c != null) { - c.sendHelp(sender); - return true; - } - } - final ScriptCommand c = commands.get(cmd[0]); - if (c != null) { -// if (cmd.length == 2 && cmd[1].equals("?")) { -// c.sendHelp(sender); -// return true; -// } - if (SkriptConfig.logPlayerCommands.value() && sender instanceof Player) - SkriptLogger.LOGGER.info(sender.getName() + " [" + ((Player) sender).getUniqueId() + "]: /" + command); - c.execute(sender, "" + cmd[0], cmd.length == 1 ? "" : "" + cmd[1]); - return true; - } - return false; - } - static boolean handleEffectCommand(final CommandSender sender, String command) { + static boolean handleEffectCommand(CommandSender sender, String command) { if (!(sender instanceof ConsoleCommandSender || sender.hasPermission("skript.effectcommands") || SkriptConfig.allowOpsToUseEffectCommands.value() && sender.isOp())) return false; try { command = "" + command.substring(SkriptConfig.effectCommandToken.value().length()).trim(); - final RetainingLogHandler log = SkriptLogger.startRetainingLog(); + RetainingLogHandler log = SkriptLogger.startRetainingLog(); try { // Call the event on the Bukkit API for addon developers. EffectCommandEvent effectCommand = new EffectCommandEvent(sender, command); @@ -217,7 +176,8 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { log.printLog(); if (!effectCommand.isCancelled()) { sender.sendMessage(ChatColor.GRAY + "executing '" + SkriptColor.replaceColorChar(command) + "'"); - if (SkriptConfig.logPlayerCommands.value() && !(sender instanceof ConsoleCommandSender)) + // TODO: remove logPlayerCommands for 2.8.0 + if ((SkriptConfig.logEffectCommands.value() || SkriptConfig.logPlayerCommands.value()) && !(sender instanceof ConsoleCommandSender)) Skript.info(sender.getName() + " issued effect command: " + SkriptColor.replaceColorChar(command)); TriggerItem.walk(effect, effectCommand); Variables.removeLocals(effectCommand); @@ -235,7 +195,7 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { log.stop(); } return true; - } catch (final Exception e) { + } catch (Exception e) { Skript.exception(e, "Unexpected error while executing effect command '" + SkriptColor.replaceColorChar(command) + "' by '" + sender.getName() + "'"); sender.sendMessage(ChatColor.RED + "An internal error occurred while executing this effect. Please refer to the server log for details."); return true; @@ -246,15 +206,23 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { public static ScriptCommand getScriptCommand(String key) { return commands.get(key); } - - public static boolean skriptCommandExists(final String command) { - final ScriptCommand c = commands.get(command); - return c != null && c.getName().equals(command); + + /* + * @deprecated Use {@link #scriptCommandExists(String)} instead. + */ + @Deprecated + public static boolean skriptCommandExists(String command) { + return scriptCommandExists(command); + } + + public static boolean scriptCommandExists(String command) { + ScriptCommand scriptCommand = commands.get(command); + return scriptCommand != null && scriptCommand.getName().equals(command); } - public static void registerCommand(final ScriptCommand command) { + public static void registerCommand(ScriptCommand command) { // Validate that there are no duplicates - final ScriptCommand existingCommand = commands.get(command.getLabel()); + ScriptCommand existingCommand = commands.get(command.getLabel()); if (existingCommand != null && existingCommand.getLabel().equals(command.getLabel())) { Script script = existingCommand.getScript(); Skript.error("A command with the name /" + existingCommand.getName() + " is already defined" @@ -268,7 +236,7 @@ public static void registerCommand(final ScriptCommand command) { command.register(commandMap, cmKnownCommands, cmAliases); } commands.put(command.getLabel(), command); - for (final String alias : command.getActiveAliases()) { + for (String alias : command.getActiveAliases()) { commands.put(alias.toLowerCase(Locale.ENGLISH), command); } command.registerHelp(); @@ -303,30 +271,25 @@ public static void registerListeners() { Bukkit.getPluginManager().registerEvents(new Listener() { @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerChat(final AsyncPlayerChatEvent e) { - if (!SkriptConfig.enableEffectCommands.value() || !e.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) + public void onPlayerChat(AsyncPlayerChatEvent event) { + if (!SkriptConfig.enableEffectCommands.value() || !event.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) return; - if (!e.isAsynchronous()) { - if (handleEffectCommand(e.getPlayer(), e.getMessage())) - e.setCancelled(true); + if (!event.isAsynchronous()) { + if (handleEffectCommand(event.getPlayer(), event.getMessage())) + event.setCancelled(true); } else { - final Future f = Bukkit.getScheduler().callSyncMethod(Skript.getInstance(), new Callable() { - @Override - public Boolean call() throws Exception { - return handleEffectCommand(e.getPlayer(), e.getMessage()); - } - }); + Future f = Bukkit.getScheduler().callSyncMethod(Skript.getInstance(), () -> handleEffectCommand(event.getPlayer(), event.getMessage())); try { while (true) { try { if (f.get()) - e.setCancelled(true); + event.setCancelled(true); break; - } catch (final InterruptedException e1) { + } catch (InterruptedException ignored) { } } - } catch (final ExecutionException e1) { - Skript.exception(e1); + } catch (ExecutionException e) { + Skript.exception(e); } } } @@ -344,7 +307,7 @@ public static final class CommandAliasHelpTopic extends HelpTopic { private final String aliasFor; private final HelpMap helpMap; - public CommandAliasHelpTopic(final String alias, final String aliasFor, final HelpMap helpMap) { + public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { this.aliasFor = aliasFor.startsWith("/") ? aliasFor : "/" + aliasFor; this.helpMap = helpMap; name = alias.startsWith("/") ? alias : "/" + alias; @@ -353,29 +316,23 @@ public CommandAliasHelpTopic(final String alias, final String aliasFor, final He } @Override - public String getFullText(final CommandSender forWho) { - final StringBuilder sb = new StringBuilder(shortText); - final HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); + @NotNull + public String getFullText(CommandSender forWho) { + StringBuilder fullText = new StringBuilder(shortText); + HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); if (aliasForTopic != null) { - sb.append("\n"); - sb.append(aliasForTopic.getFullText(forWho)); + fullText.append("\n"); + fullText.append(aliasForTopic.getFullText(forWho)); } - return "" + sb.toString(); + return "" + fullText; } @Override - public boolean canSee(final CommandSender commandSender) { - if (amendedPermission == null) { - final HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); - if (aliasForTopic != null) { - return aliasForTopic.canSee(commandSender); - } else { - return false; - } - } else { - assert amendedPermission != null; + public boolean canSee(CommandSender commandSender) { + if (amendedPermission != null) return commandSender.hasPermission(amendedPermission); - } + HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); + return aliasForTopic != null && aliasForTopic.canSee(commandSender); } } diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 93e8cad25b0..2ce33ac7152 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -18,44 +18,11 @@ */ package ch.njol.skript.command; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; - import ch.njol.skript.ScriptLoader; -import ch.njol.skript.config.SectionNode; -import org.skriptlang.skript.lang.script.Script; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.OfflinePlayer; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.command.SimpleCommandMap; -import org.bukkit.command.TabExecutor; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.help.GenericCommandHelpTopic; -import org.bukkit.help.HelpMap; -import org.bukkit.help.HelpTopic; -import org.bukkit.help.HelpTopicComparator; -import org.bukkit.help.IndexHelpTopic; -import org.bukkit.plugin.Plugin; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; import ch.njol.skript.command.Commands.CommandAliasHelpTopic; +import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.Trigger; @@ -77,6 +44,39 @@ import ch.njol.skript.variables.Variables; import ch.njol.util.StringUtils; import ch.njol.util.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.help.GenericCommandHelpTopic; +import org.bukkit.help.HelpMap; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicComparator; +import org.bukkit.help.IndexHelpTopic; +import org.bukkit.plugin.Plugin; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; /** * This class is used for user-defined commands. @@ -274,9 +274,21 @@ public boolean execute(final CommandSender sender, final String commandLabel, fi } Runnable runnable = () -> { + // save previous last usage date to check if the execution has set the last usage date + Date previousLastUsage = null; + if (sender instanceof Player) + previousLastUsage = getLastUsage(((Player) sender).getUniqueId(), event); + + // execute the command - may modify the last usage date execute2(event, sender, commandLabel, rest); - if (sender instanceof Player && !event.isCooldownCancelled()) - setLastUsage(((Player) sender).getUniqueId(), event, new Date()); + + if (sender instanceof Player && !event.isCooldownCancelled()) { + Date lastUsage = getLastUsage(((Player) sender).getUniqueId(), event); + // check if the execution has set the last usage date + // if not, set it to the current date. if it has, we leave it alone so as not to affect the remaining/elapsed time (#5862) + if (Objects.equals(lastUsage, previousLastUsage)) + setLastUsage(((Player) sender).getUniqueId(), event, new Date()); + } }; if (Bukkit.isPrimaryThread()) { runnable.run(); @@ -474,7 +486,13 @@ public Date getLastUsage(UUID uuid, Event event) { } else { String name = getStorageVariableName(event); assert name != null; - return (Date) Variables.getVariable(name, null, false); + Object variable = Variables.getVariable(name, null, false); + if (!(variable instanceof Date)) { + Skript.warning("Variable {" + name + "} was not a date! You may be using this variable elsewhere. " + + "This warning is letting you know that this variable is now overridden for the command storage."); + return null; + } + return (Date) variable; } } @@ -510,7 +528,7 @@ public void setRemainingMilliseconds(UUID uuid, Event event, long milliseconds) assert cooldown != null; long cooldownMs = cooldown.getMilliSeconds(); if (milliseconds > cooldownMs) - throw new IllegalArgumentException("Remaining time may not be longer than the cooldown"); + milliseconds = cooldownMs; setElapsedMilliSeconds(uuid, event, cooldownMs - milliseconds); } diff --git a/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java b/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java index 98ed56c0570..725480bac1b 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java @@ -31,7 +31,7 @@ @Name("Is Infinite") @Description("Checks whether potion effects are infinite.") @Examples("all of the active potion effects of the player are infinite") -@Since("INSERT VERSION") +@Since("2.7") public class CondIsInfinite extends PropertyCondition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java b/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java index f8a6a2f7327..4c4f8097ba4 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java @@ -18,7 +18,7 @@ */ package ch.njol.skript.conditions; -import static ch.njol.skript.command.Commands.skriptCommandExists; +import static ch.njol.skript.command.Commands.scriptCommandExists; import ch.njol.skript.conditions.base.PropertyCondition; import ch.njol.skript.doc.Description; @@ -44,7 +44,7 @@ public class CondIsSkriptCommand extends PropertyCondition { @Override public boolean check(String cmd) { - return skriptCommandExists(cmd); + return scriptCommandExists(cmd); } @Override diff --git a/src/main/java/ch/njol/skript/config/Config.java b/src/main/java/ch/njol/skript/config/Config.java index 6ec44491749..bcabb8859f3 100644 --- a/src/main/java/ch/njol/skript/config/Config.java +++ b/src/main/java/ch/njol/skript/config/Config.java @@ -18,9 +18,11 @@ */ package ch.njol.skript.config; +import ch.njol.skript.Skript; +import ch.njol.skript.config.validate.SectionValidator; + import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; @@ -28,15 +30,13 @@ import java.lang.reflect.Modifier; import java.nio.channels.Channels; import java.nio.channels.FileChannel; +import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; -import ch.njol.skript.config.validate.SectionValidator; - /** * Represents a config file. * @@ -44,7 +44,7 @@ */ public class Config implements Comparable { - boolean simple = false; + boolean simple; /** * One level of the indentation, e.g. a tab or 4 spaces. @@ -58,8 +58,6 @@ public class Config implements Comparable { final String defaultSeparator; String separator; - String line = ""; - int level = 0; private final SectionNode main; @@ -91,11 +89,8 @@ public Config(final InputStream source, final String fileName, @Nullable final F if (Skript.logVeryHigh()) Skript.info("loading '" + fileName + "'"); - final ConfigReader r = new ConfigReader(source); - try { - main = SectionNode.load(this, r); - } finally { - r.close(); + try (ConfigReader reader = new ConfigReader(source)) { + main = SectionNode.load(this, reader); } } finally { source.close(); @@ -106,9 +101,8 @@ public Config(final InputStream source, final String fileName, final boolean sim this(source, fileName, null, simple, allowEmptySections, defaultSeparator); } - @SuppressWarnings("resource") public Config(final File file, final boolean simple, final boolean allowEmptySections, final String defaultSeparator) throws IOException { - this(new FileInputStream(file), "" + file.getName(), simple, allowEmptySections, defaultSeparator); + this(Files.newInputStream(file.toPath()), file.getName(), simple, allowEmptySections, defaultSeparator); this.file = file.toPath(); } @@ -120,7 +114,7 @@ public Config(final Path file, final boolean simple, final boolean allowEmptySec /** * For testing - * + * * @param s * @param fileName * @param simple @@ -133,7 +127,7 @@ public Config(final String s, final String fileName, final boolean simple, final } void setIndentation(final String indent) { - assert indent != null && indent.length() > 0 : indent; + assert indent != null && !indent.isEmpty() : indent; indentation = indent; indentationName = (indent.charAt(0) == ' ' ? "space" : "tab"); } @@ -156,7 +150,7 @@ public String getFileName() { /** * Saves the config to a file. - * + * * @param f The file to save to * @throws IOException If the file could not be written to. */ @@ -175,7 +169,7 @@ public void save(final File f) throws IOException { * Sets this config's values to those in the given config. *

* Used by Skript to import old settings into the updated config. The return value is used to not modify the config if no new options were added. - * + * * @param other * @return Whether the configs' keys differ, i.e. false == configs only differ in values, not keys. */ @@ -203,7 +197,7 @@ public File getFile() { if (file != null) { try { return file.toFile(); - } catch(Exception e) { + } catch (Exception e) { return null; // ZipPath, for example, throws undocumented exception } } @@ -235,7 +229,7 @@ public String getSaveSeparator() { /** * Splits the given path at the dot character and passes the result to {@link #get(String...)}. - * + * * @param path * @return get(path.split("\\.")) */ @@ -247,7 +241,7 @@ public String getByPath(final String path) { /** * Gets an entry node's value at the designated path - * + * * @param path * @return The entry node's value at the location defined by path or null if it either doesn't exist or is not an entry. */ @@ -284,22 +278,19 @@ public boolean validate(final SectionValidator validator) { return validator.validate(getMainNode()); } - private void load(final Class c, final @Nullable Object o, final String path) { - for (final Field f : c.getDeclaredFields()) { - f.setAccessible(true); - if (o != null || Modifier.isStatic(f.getModifiers())) { + private void load(final Class cls, final @Nullable Object object, final String path) { + for (final Field field : cls.getDeclaredFields()) { + field.setAccessible(true); + if (object != null || Modifier.isStatic(field.getModifiers())) { try { - if (OptionSection.class.isAssignableFrom(f.getType())) { - final Object p = f.get(o); - @NonNull - final Class pc = p.getClass(); - load(pc, p, path + ((OptionSection) p).key + "."); - } else if (Option.class.isAssignableFrom(f.getType())) { - ((Option) f.get(o)).set(this, path); + if (OptionSection.class.isAssignableFrom(field.getType())) { + final OptionSection section = (OptionSection) field.get(object); + @NonNull final Class pc = section.getClass(); + load(pc, section, path + section.key + "."); + } else if (Option.class.isAssignableFrom(field.getType())) { + ((Option) field.get(object)).set(this, path); } - } catch (final IllegalArgumentException e) { - assert false; - } catch (final IllegalAccessException e) { + } catch (final IllegalArgumentException | IllegalAccessException e) { assert false; } } diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index 5da07aefb09..c7b9ebebbb1 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -60,7 +60,7 @@ public class Documentation { private static final Pattern CP_EMPTY_PARSE_MARKS_PATTERN = Pattern.compile("\\(\\)"); private static final Pattern CP_PARSE_TAGS_PATTERN = Pattern.compile("(?<=[(|\\[ ])[-a-zA-Z0-9!$#%^&*_+~=\"'<>?,.]*?:"); private static final Pattern CP_EXTRA_OPTIONAL_PATTERN = Pattern.compile("\\[\\(((\\w+? ?)+)\\)]"); - private static final File DOCS_TEMPLATE_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "doc-templates"); + private static final File DOCS_TEMPLATE_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "docs/templates"); private static final File DOCS_OUTPUT_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "docs"); /** diff --git a/src/main/java/ch/njol/skript/effects/EffKeepInventory.java b/src/main/java/ch/njol/skript/effects/EffKeepInventory.java index 4c86bac1bcf..34cf29e85c6 100644 --- a/src/main/java/ch/njol/skript/effects/EffKeepInventory.java +++ b/src/main/java/ch/njol/skript/effects/EffKeepInventory.java @@ -19,6 +19,7 @@ package ch.njol.skript.effects; import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.eclipse.jdt.annotation.Nullable; @@ -35,17 +36,19 @@ @Name("Keep Inventory / Experience") @Description("Keeps the inventory or/and experiences of the dead player in a death event.") -@Examples({"on death of a player:", +@Examples({ + "on death of a player:", "\tif the victim is an op:", - "\t\tkeep the inventory and experiences"}) + "\t\tkeep the inventory and experiences" +}) @Since("2.4") @Events("death") public class EffKeepInventory extends Effect { static { Skript.registerEffect(EffKeepInventory.class, - "keep [the] (inventory|items) [(1¦and [e]xp[erience][s] [point[s]])]", - "keep [the] [e]xp[erience][s] [point[s]] [(1¦and (inventory|items))]"); + "keep [the] (inventory|items) [(1:and [e]xp[erience][s] [point[s]])]", + "keep [the] [e]xp[erience][s] [point[s]] [(1:and (inventory|items))]"); } private boolean keepItems, keepExp; @@ -54,7 +57,7 @@ public class EffKeepInventory extends Effect { public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { keepItems = matchedPattern == 0 || parseResult.mark == 1; keepExp = matchedPattern == 1 || parseResult.mark == 1; - if (!getParser().isCurrentEvent(PlayerDeathEvent.class)) { + if (!getParser().isCurrentEvent(EntityDeathEvent.class)) { Skript.error("The keep inventory/experience effect can't be used outside of a death event"); return false; } @@ -66,18 +69,18 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected void execute(Event e) { - if (e instanceof PlayerDeathEvent) { - PlayerDeathEvent event = (PlayerDeathEvent) e; + protected void execute(Event event) { + if (event instanceof PlayerDeathEvent) { + PlayerDeathEvent deathEvent = (PlayerDeathEvent) event; if (keepItems) - event.setKeepInventory(true); + deathEvent.setKeepInventory(true); if (keepExp) - event.setKeepLevel(true); + deathEvent.setKeepLevel(true); } } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { if (keepItems && !keepExp) return "keep the inventory"; else diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index 64b07bb0be4..4ebc19e8d9f 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -52,7 +52,7 @@ @Since( "2.0, 2.2-dev27 (ambient and particle-less potion effects), " + "2.5 (replacing existing effect), 2.5.2 (potion effects), " + - "INSERT VERSION (icon and infinite)" + "2.7 (icon and infinite)" ) public class EffPotion extends Effect { @@ -101,7 +101,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye potions = (Expression) exprs[0]; tier = (Expression) exprs[1]; entities = (Expression) exprs[2]; - if (infinite) + if (!infinite) duration = (Expression) exprs[3]; } return true; @@ -125,7 +125,7 @@ protected void execute(Event event) { Timespan timespan = this.duration.getSingle(event); if (timespan == null) return; - duration = (int) Math.max(timespan.getTicks_i(), Integer.MAX_VALUE); + duration = (int) Math.min(timespan.getTicks_i(), Integer.MAX_VALUE); } for (LivingEntity entity : entities.getArray(event)) { for (PotionEffectType potionEffectType : potionEffectTypes) { diff --git a/src/main/java/ch/njol/skript/effects/EffRespawn.java b/src/main/java/ch/njol/skript/effects/EffRespawn.java index eb21064e4a8..de67c2b07ad 100644 --- a/src/main/java/ch/njol/skript/effects/EffRespawn.java +++ b/src/main/java/ch/njol/skript/effects/EffRespawn.java @@ -20,7 +20,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; -import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.scheduler.BukkitRunnable; import org.eclipse.jdt.annotation.Nullable; @@ -62,7 +62,7 @@ public boolean init(final Expression[] exprs, final int matchedPattern, final players = (Expression) exprs[0]; // Force a delay before respawning the player if we're in the death event and there isn't already a delay // Unexpected behavior may occur if we don't do this - forceDelay = getParser().isCurrentEvent(PlayerDeathEvent.class) && isDelayed.isFalse(); + forceDelay = getParser().isCurrentEvent(EntityDeathEvent.class) && isDelayed.isFalse(); return true; } diff --git a/src/main/java/ch/njol/skript/effects/EffStopSound.java b/src/main/java/ch/njol/skript/effects/EffStopSound.java index fe0af9ef26c..dc39449a06a 100644 --- a/src/main/java/ch/njol/skript/effects/EffStopSound.java +++ b/src/main/java/ch/njol/skript/effects/EffStopSound.java @@ -80,14 +80,9 @@ public class EffStopSound extends Effect { @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { allSounds = parseResult.hasTag("all"); - if (allSounds) { - category = (Expression) exprs[0]; - players = (Expression) exprs[1]; - } else { - sounds = (Expression) exprs[0]; - category = (Expression) exprs[1]; - players = (Expression) exprs[2]; - } + sounds = (Expression) exprs[0]; + category = (Expression) exprs[1]; + players = (Expression) exprs[2]; return true; } diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java index ad94cfa9ad2..10b83986f4d 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java @@ -33,12 +33,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.VectorMath; -/** - * @author bi0qaw - */ @Name("Vectors - Rotate Around Vector") -@Description("Rotates a vector around another vector") -@Examples({"rotate {_v} around vector 1, 0, 0 by 90"}) +@Description("Rotates one or more vectors around another vector") +@Examples("rotate {_v} around vector 1, 0, 0 by 90") @Since("2.2-dev28") public class EffVectorRotateAroundAnother extends Effect { @@ -47,7 +44,7 @@ public class EffVectorRotateAroundAnother extends Effect { } @SuppressWarnings("null") - private Expression first, second; + private Expression vectors, axis; @SuppressWarnings("null") private Expression degree; @@ -55,26 +52,26 @@ public class EffVectorRotateAroundAnother extends Effect { @SuppressWarnings({"unchecked", "null"}) @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean kleenean, ParseResult parseResult) { - first = (Expression) exprs[0]; - second = (Expression) exprs[1]; + vectors = (Expression) exprs[0]; + axis = (Expression) exprs[1]; degree = (Expression) exprs[2]; return true; } @SuppressWarnings("null") @Override - protected void execute(Event e) { - Vector v2 = second.getSingle(e); - Number d = degree.getSingle(e); - if (v2 == null || d == null) + protected void execute(Event event) { + Vector axis = this.axis.getSingle(event); + Number angle = degree.getSingle(event); + if (axis == null || angle == null) return; - for (Vector v1 : first.getArray(e)) - VectorMath.rot(v1, v2, d.doubleValue()); + for (Vector vector : vectors.getArray(event)) + VectorMath.rot(vector, axis, angle.doubleValue()); } @Override - public String toString(@Nullable Event e, boolean debug) { - return "rotate " + first.toString(e, debug) + " around " + second.toString(e, debug) + " by " + degree + "degrees"; + public String toString(@Nullable Event event, boolean debug) { + return "rotate " + vectors.toString(event, debug) + " around " + axis.toString(event, debug) + " by " + degree.toString(event, debug) + "degrees"; } } diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java index 2c3cd5d3d94..6e24d2882bb 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java @@ -33,19 +33,18 @@ import ch.njol.util.Kleenean; import ch.njol.util.VectorMath; -/** - * @author bi0qaw - */ @Name("Vectors - Rotate around XYZ") -@Description("Rotates a vector around x, y, or z axis by some degrees") -@Examples({"rotate {_v} around x-axis by 90", - "rotate {_v} around y-axis by 90", - "rotate {_v} around z-axis by 90 degrees"}) +@Description("Rotates one or more vectors around the x, y, or z axis by some amount of degrees") +@Examples({ + "rotate {_v} around x-axis by 90", + "rotate {_v} around y-axis by 90", + "rotate {_v} around z-axis by 90 degrees" +}) @Since("2.2-dev28") public class EffVectorRotateXYZ extends Effect { static { - Skript.registerEffect(EffVectorRotateXYZ.class, "rotate %vectors% around (1¦x|2¦y|3¦z)(-| )axis by %number% [degrees]"); + Skript.registerEffect(EffVectorRotateXYZ.class, "rotate %vectors% around (0¦x|1¦y|2¦z)(-| )axis by %number% [degrees]"); } private final static Character[] axes = new Character[] {'x', 'y', 'z'}; @@ -68,28 +67,28 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is @Override @SuppressWarnings("null") - protected void execute(Event e) { - Number d = degree.getSingle(e); - if (d == null) + protected void execute(Event event) { + Number angle = degree.getSingle(event); + if (angle == null) return; switch (axis) { + case 0: + for (Vector vector : vectors.getArray(event)) + VectorMath.rotX(vector, angle.doubleValue()); + break; case 1: - for (Vector v : vectors.getArray(e)) - VectorMath.rotX(v, d.doubleValue()); + for (Vector vector : vectors.getArray(event)) + VectorMath.rotY(vector, angle.doubleValue()); break; case 2: - for (Vector v : vectors.getArray(e)) - VectorMath.rotY(v, d.doubleValue()); - break; - case 3: - for (Vector v : vectors.getArray(e)) - VectorMath.rotZ(v, d.doubleValue()); + for (Vector vector : vectors.getArray(event)) + VectorMath.rotZ(vector, angle.doubleValue()); } } @Override - public String toString(@Nullable Event e, boolean debug) { - return "rotate " + vectors.toString(e, debug) + " around " + axes[axis] + "-axis" + " by " + degree + "degrees"; + public String toString(@Nullable Event event, boolean debug) { + return "rotate " + vectors.toString(event, debug) + " around " + axes[axis] + "-axis" + " by " + degree.toString(event, debug) + "degrees"; } } diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index d49668a8af3..ae02901b769 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -18,6 +18,28 @@ */ package ch.njol.skript.entity; +import java.io.NotSerializableException; +import java.io.StreamCorruptedException; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.RegionAccessor; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.eclipse.jdt.annotation.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.bukkitutil.EntityUtils; @@ -44,37 +66,40 @@ import ch.njol.util.coll.iterator.SingleItemIterator; import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.util.Consumer; -import org.eclipse.jdt.annotation.Nullable; -import java.io.NotSerializableException; -import java.io.StreamCorruptedException; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * @author Peter Güttinger - */ @SuppressWarnings("rawtypes") public abstract class EntityData implements SyntaxElement, YggdrasilExtendedSerializable {// TODO extended horse support, zombie villagers // REMIND unit + /* + * In 1.20.2 Spigot deprecated org.bukkit.util.Consumer. + * From the class header: "API methods which use this consumer will be remapped to Java's consumer at runtime, resulting in an error." + * But in 1.13-1.16 the only way to use a consumer was World#spawn(Location, Class, org.bukkit.util.Consumer). + */ + @Nullable + protected static Method WORLD_1_13_CONSUMER_METHOD; + protected static final boolean WORLD_1_13_CONSUMER = Skript.methodExists(World.class, "spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); + + @Nullable + protected static Method WORLD_1_17_CONSUMER_METHOD; + protected static boolean WORLD_1_17_CONSUMER; + + static { + try { + if (WORLD_1_13_CONSUMER) { + WORLD_1_13_CONSUMER_METHOD = World.class.getDeclaredMethod("spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); + } else if (Skript.classExists("org.bukkit.RegionAccessor")) { + if (WORLD_1_17_CONSUMER = Skript.methodExists(RegionAccessor.class, "spawn", Location.class, Class.class, org.bukkit.util.Consumer.class)) + WORLD_1_17_CONSUMER_METHOD = RegionAccessor.class.getDeclaredMethod("spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); + } + } catch (NoSuchMethodException | SecurityException ignored) { /* We already checked if the method exists */ } + } + public final static String LANGUAGE_NODE = "entities"; - + public final static Message m_age_pattern = new Message(LANGUAGE_NODE + ".age pattern"); public final static Adjective m_baby = new Adjective(LANGUAGE_NODE + ".age adjectives.baby"), m_adult = new Adjective(LANGUAGE_NODE + ".age adjectives.adult"); - + // must be here to be initialised before 'new SimpleLiteral' is called in the register block below private final static List>> infos = new ArrayList<>(); @@ -312,7 +337,7 @@ public final boolean init(final Expression[] exprs, final int matchedPattern, /** * Returns the super type of this entity data, e.g. 'wolf' for 'angry wolf'. - * + * * @return The supertype of this entity data. Must not be null. */ public abstract EntityData getSuperType(); @@ -401,7 +426,7 @@ public static EntityDataInfo getInfo(final String codeName) { /** * Prints errors. - * + * * @param s String with optional indefinite article at the beginning * @return The parsed entity data */ @@ -416,11 +441,10 @@ public static EntityData parse(String s) { /** * Prints errors. - * + * * @param s * @return The parsed entity data */ - @SuppressWarnings("null") @Nullable public static EntityData parseWithoutIndefiniteArticle(String s) { if (!REGEX_PATTERN.matcher(s).matches()) @@ -428,11 +452,6 @@ public static EntityData parseWithoutIndefiniteArticle(String s) { Iterator>> it = infos.iterator(); return SkriptParser.parseStatic(s, it, null); } - - @Nullable - public final E spawn(Location loc) { - return spawn(loc, null); - } private E apply(E entity) { if (baby.isTrue()) { @@ -444,24 +463,54 @@ private E apply(E entity) { return entity; } + /** + * Spawn this entity data at a location. + * + * @param location The {@link Location} to spawn the entity at. + * @return The Entity object that is spawned. + */ + @Nullable + public final E spawn(Location location) { + return spawn(location, (Consumer) null); + } + + /** + * Spawn this entity data at a location. + * The consumer allows for modiciation to the entity before it actually gets spawned. + *

+ * Bukkit's own {@link org.bukkit.util.Consumer} is deprecated. + * Use {@link #spawn(Location, Consumer)} + * + * @param location The {@link Location} to spawn the entity at. + * @param consumer A {@link Consumer} to apply the entity changes to. + * @return The Entity object that is spawned. + */ + @Nullable + @Deprecated + @SuppressWarnings("deprecation") + public E spawn(Location location, org.bukkit.util.@Nullable Consumer consumer) { + return spawn(location, (Consumer) e -> consumer.accept(e)); + } + + /** + * Spawn this entity data at a location. + * The consumer allows for modiciation to the entity before it actually gets spawned. + * + * @param location The {@link Location} to spawn the entity at. + * @param consumer A {@link Consumer} to apply the entity changes to. + * @return The Entity object that is spawned. + */ @Nullable - @SuppressWarnings("unchecked") public E spawn(Location location, @Nullable Consumer consumer) { assert location != null; - try { - if (consumer != null) { - return location.getWorld().spawn(location, (Class) getType(), e -> consumer.accept(apply(e))); - } else { - return apply(location.getWorld().spawn(location, getType())); - } - } catch (IllegalArgumentException e) { - if (Skript.testing()) - Skript.error("Can't spawn " + getType().getName()); - return null; + if (consumer != null) { + return EntityData.spawn(location, getType(), e -> consumer.accept(this.apply(e))); + } else { + return apply(location.getWorld().spawn(location, getType())); } } - - @SuppressWarnings({"null", "unchecked"}) + + @SuppressWarnings("unchecked") public E[] getAll(final World... worlds) { assert worlds != null && worlds.length > 0 : Arrays.toString(worlds); final List list = new ArrayList<>(); @@ -598,4 +647,22 @@ protected boolean deserialize(final String s) { return false; } + @SuppressWarnings({"unchecked", "deprecation"}) + protected static @Nullable E spawn(Location location, Class type, Consumer consumer) { + try { + if (WORLD_1_17_CONSUMER) { + return (@Nullable E) WORLD_1_17_CONSUMER_METHOD.invoke(location.getWorld(), location, type, + (org.bukkit.util.Consumer) consumer::accept); + } else if (WORLD_1_13_CONSUMER) { + return (@Nullable E) WORLD_1_13_CONSUMER_METHOD.invoke(location.getWorld(), location, type, + (org.bukkit.util.Consumer) consumer::accept); + } + } catch (InvocationTargetException | IllegalAccessException e) { + if (Skript.testing()) + Skript.exception(e, "Can't spawn " + type.getName()); + return null; + } + return location.getWorld().spawn(location, type, consumer); + } + } diff --git a/src/main/java/ch/njol/skript/entity/FallingBlockData.java b/src/main/java/ch/njol/skript/entity/FallingBlockData.java index b3dcfbbcadf..2f937148fe9 100644 --- a/src/main/java/ch/njol/skript/entity/FallingBlockData.java +++ b/src/main/java/ch/njol/skript/entity/FallingBlockData.java @@ -19,12 +19,13 @@ package ch.njol.skript.entity; import java.util.Arrays; + import java.util.Iterator; +import java.util.function.Consumer; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.FallingBlock; -import org.bukkit.util.Consumer; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -42,9 +43,6 @@ import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.coll.CollectionUtils; -/** - * @author Peter Güttinger - */ public class FallingBlockData extends EntityData { static { EntityData.register(FallingBlockData.class, "falling block", FallingBlock.class, "falling block"); @@ -117,11 +115,11 @@ public FallingBlock spawn(Location loc, @Nullable Consumer consume ItemType t = types == null ? new ItemType(Material.STONE) : CollectionUtils.getRandom(types); assert t != null; Material material = t.getMaterial(); - if (!material.isBlock()) { assert false : t; return null; } + FallingBlock fallingBlock = loc.getWorld().spawnFallingBlock(loc, material.createBlockData()); if (consumer != null) consumer.accept(fallingBlock); diff --git a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java index 2f60014a310..407460d56c5 100644 --- a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java +++ b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java @@ -19,13 +19,13 @@ package ch.njol.skript.entity; import java.util.Arrays; +import java.util.function.Consumer; import org.bukkit.Location; import org.bukkit.entity.LingeringPotion; import org.bukkit.entity.ThrownPotion; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.util.Consumer; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -40,9 +40,6 @@ import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.coll.CollectionUtils; -/** - * @author Peter Güttinger - */ public class ThrownPotionData extends EntityData { static { EntityData.register(ThrownPotionData.class, "thrown potion", ThrownPotion.class, "thrown potion"); @@ -103,9 +100,9 @@ protected boolean match(ThrownPotion entity) { return true; } - @SuppressWarnings({"unchecked", "rawtypes"}) @Override - public @Nullable ThrownPotion spawn(Location loc, @Nullable Consumer consumer) { + @SuppressWarnings({"unchecked", "rawtypes"}) + public @Nullable ThrownPotion spawn(Location location, @Nullable Consumer consumer) { ItemType t = CollectionUtils.getRandom(types); assert t != null; ItemStack i = t.getRandom(); @@ -114,11 +111,14 @@ protected boolean match(ThrownPotion entity) { Class thrownPotionClass = (Class) (LINGER_POTION.isOfType(i) ? LINGERING_POTION_ENTITY_CLASS : ThrownPotion.class); ThrownPotion potion; - if (consumer != null) - potion = loc.getWorld().spawn(loc, thrownPotionClass, consumer); - else - potion = loc.getWorld().spawn(loc, thrownPotionClass); + if (consumer != null) { + potion = EntityData.spawn(location, thrownPotionClass, consumer); + } else { + potion = location.getWorld().spawn(location, thrownPotionClass); + } + if (potion == null) + return null; potion.setItem(i); return potion; } diff --git a/src/main/java/ch/njol/skript/entity/XpOrbData.java b/src/main/java/ch/njol/skript/entity/XpOrbData.java index 8582d513c33..69aa7d8c9f2 100644 --- a/src/main/java/ch/njol/skript/entity/XpOrbData.java +++ b/src/main/java/ch/njol/skript/entity/XpOrbData.java @@ -18,18 +18,16 @@ */ package ch.njol.skript.entity; +import java.util.function.Consumer; + import org.bukkit.Location; import org.bukkit.entity.ExperienceOrb; -import org.bukkit.util.Consumer; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.localization.ArgsMessage; -/** - * @author Peter Güttinger - */ public class XpOrbData extends EntityData { static { EntityData.register(XpOrbData.class, "xporb", ExperienceOrb.class, "xp-orb"); diff --git a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java index e79030c1553..d3caf6d325b 100644 --- a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java +++ b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java @@ -22,11 +22,9 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; - import org.bukkit.event.Event; import org.bukkit.event.player.PlayerMoveEvent; - -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; public class EvtPlayerChunkEnter extends SkriptEvent { @@ -46,7 +44,8 @@ public boolean init(Literal[] args, int matchedPattern, ParseResult parseResu @Override public boolean check(Event event) { - return ((PlayerMoveEvent) event).getFrom().getChunk() != ((PlayerMoveEvent) event).getTo().getChunk(); + PlayerMoveEvent moveEvent = ((PlayerMoveEvent) event); + return !moveEvent.getFrom().getChunk().equals(moveEvent.getTo().getChunk()); } @Override diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index bcb301489e1..376acae9d13 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -718,7 +718,7 @@ public class SimpleEvents { "\t\tsend \"You can't drag your items here!\" to player", "\t\tcancel event" ) - .since("INSERT VERSION"); + .since("2.7"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java b/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java index c7c81de0d5c..ddb4b3b27ad 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java @@ -18,16 +18,6 @@ */ package ch.njol.skript.expressions; -import org.bukkit.attribute.Attribute; - -import java.util.stream.Stream; - -import org.bukkit.attribute.Attributable; -import org.bukkit.attribute.AttributeInstance; -import org.bukkit.entity.Entity; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -40,21 +30,34 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.attribute.Attributable; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Objects; +import java.util.stream.Stream; @Name("Entity Attribute") -@Description({"The numerical value of an entity's particular attribute.", - "Note that the movement speed attribute cannot be reliably used for players. For that purpose, use the speed expression instead.", - "Resetting an entity's attribute is only available in Minecraft 1.11 and above."}) -@Examples({"on damage of player:", - " send \"You are wounded!\"", - " set victim's attack speed attribute to 2"}) +@Description({ + "The numerical value of an entity's particular attribute.", + "Note that the movement speed attribute cannot be reliably used for players. For that purpose, use the speed expression instead.", + "Resetting an entity's attribute is only available in Minecraft 1.11 and above." +}) +@Examples({ + "on damage of player:", + "\tsend \"You are wounded!\" to victim", + "\tset victim's attack speed attribute to 2" +}) @Since("2.5, 2.6.1 (final attribute value)") public class ExprEntityAttribute extends PropertyExpression { static { Skript.registerExpression(ExprEntityAttribute.class, Number.class, ExpressionType.COMBINED, - "[the] %attributetype% [(1¦(total|final|modified))] attribute [value] of %entities%", - "%entities%'[s] %attributetype% [(1¦(total|final|modified))] attribute [value]"); + "[the] %attributetype% [(1:(total|final|modified))] attribute [value] of %entities%", + "%entities%'[s] %attributetype% [(1:(total|final|modified))] attribute [value]"); } @Nullable @@ -72,10 +75,11 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Number[] get(Event e, Entity[] entities) { - Attribute a = attributes.getSingle(e); + protected Number[] get(Event event, Entity[] entities) { + Attribute attribute = attributes.getSingle(event); return Stream.of(entities) - .map(ent -> getAttribute(ent, a)) + .map(ent -> getAttribute(ent, attribute)) + .filter(Objects::nonNull) .map(att -> withModifiers ? att.getValue() : att.getBaseValue()) .toArray(Number[]::new); } @@ -90,27 +94,27 @@ public Class[] acceptChange(ChangeMode mode) { @Override @SuppressWarnings("null") - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - Attribute a = attributes.getSingle(e); - double d = delta == null ? 0 : ((Number) delta[0]).doubleValue(); - for (Entity entity : getExpr().getArray(e)) { - AttributeInstance ai = getAttribute(entity, a); - if(ai != null) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + Attribute attribute = attributes.getSingle(event); + double deltaValue = delta == null ? 0 : ((Number) delta[0]).doubleValue(); + for (Entity entity : getExpr().getArray(event)) { + AttributeInstance instance = getAttribute(entity, attribute); + if (instance != null) { switch(mode) { case ADD: - ai.setBaseValue(ai.getBaseValue() + d); + instance.setBaseValue(instance.getBaseValue() + deltaValue); break; case SET: - ai.setBaseValue(d); + instance.setBaseValue(deltaValue); break; case DELETE: - ai.setBaseValue(0); + instance.setBaseValue(0); break; case RESET: - ai.setBaseValue(ai.getDefaultValue()); + instance.setBaseValue(instance.getDefaultValue()); break; case REMOVE: - ai.setBaseValue(ai.getBaseValue() - d); + instance.setBaseValue(instance.getBaseValue() - deltaValue); break; case REMOVE_ALL: assert false; @@ -126,14 +130,14 @@ public Class getReturnType() { @Override @SuppressWarnings("null") - public String toString(@Nullable Event e, boolean debug) { - return "entity " + getExpr().toString(e, debug) + "'s " + (attributes == null ? "" : attributes.toString(e, debug)) + "attribute"; + public String toString(@Nullable Event event, boolean debug) { + return "entity " + getExpr().toString(event, debug) + "'s " + (attributes == null ? "" : attributes.toString(event, debug)) + "attribute"; } @Nullable - private static AttributeInstance getAttribute(Entity e, @Nullable Attribute a) { - if (a != null && e instanceof Attributable) { - return ((Attributable) e).getAttribute(a); + private static AttributeInstance getAttribute(Entity entity, @Nullable Attribute attribute) { + if (attribute != null && entity instanceof Attributable) { + return ((Attributable) entity).getAttribute(attribute); } return null; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLevel.java b/src/main/java/ch/njol/skript/expressions/ExprLevel.java index d55550616e2..74795570377 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLevel.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLevel.java @@ -20,6 +20,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerLevelChangeEvent; import org.bukkit.event.player.PlayerRespawnEvent; @@ -80,11 +81,11 @@ public Class[] acceptChange(final ChangeMode mode) { Skript.error("Cannot change a player's level in a respawn event. Add a delay of 1 tick or change the 'new level' in a death event."); return null; } - if (getParser().isCurrentEvent(PlayerDeathEvent.class) && getTime() == 0 && getExpr().isDefault() && !getParser().getHasDelayBefore().isTrue()) { + if (getParser().isCurrentEvent(EntityDeathEvent.class) && getTime() == 0 && getExpr().isDefault() && !getParser().getHasDelayBefore().isTrue()) { Skript.warning("Changing the player's level in a death event will change the player's level before he dies. " + "Use either 'past level of player' or 'new level of player' to clearly state whether to change the level before or after he dies."); } - if (getTime() == -1 && !getParser().isCurrentEvent(PlayerDeathEvent.class)) + if (getTime() == -1 && !getParser().isCurrentEvent(EntityDeathEvent.class)) return null; return new Class[] {Number.class}; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java index d774cbd9848..ba3853ad311 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java @@ -88,7 +88,7 @@ protected Number[] get(Event event) { return new Long[] {sup}; return new Long[0]; } - return new Long[] {random.nextLong(inf, sup + 1)}; + return new Long[] {inf + Math2.mod(random.nextLong(), sup - inf + 1)}; } return new Double[] {min + random.nextDouble() * (max - min)}; diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java index 07d3cb2867d..1cdf3626c8e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java @@ -35,12 +35,9 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Angle Between") @Description("Gets the angle between two vectors.") -@Examples({"send \"%the angle between vector 1, 0, 0 and vector 0, 1, 1%\""}) +@Examples("send \"%the angle between vector 1, 0, 0 and vector 0, 1, 1%\"") @Since("2.2-dev28") public class ExprVectorAngleBetween extends SimpleExpression { @@ -62,12 +59,12 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Number[] get(Event e) { - Vector v1 = first.getSingle(e); - Vector v2 = second.getSingle(e); - if (v1 == null || v2 == null) + protected Number[] get(Event event) { + Vector first = this.first.getSingle(event); + Vector second = this.second.getSingle(event); + if (first == null || second == null) return null; - return CollectionUtils.array(v1.angle(v2) * (float) VectorMath.RAD_TO_DEG); + return CollectionUtils.array(first.angle(second) * (float) VectorMath.RAD_TO_DEG); } @Override @@ -81,8 +78,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "the angle between " + first.toString(e, debug) + " and " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "the angle between " + first.toString(event, debug) + " and " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java index 1dc75efd574..41c81c2d8d4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java @@ -35,46 +35,41 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Arithmetic") @Description("Arithmetic expressions for vectors.") -@Examples({"set {_v} to vector 1, 2, 3 // 5", - "set {_v} to {_v} ++ {_v}", - "set {_v} to {_v} ++ 5", - "set {_v} to {_v} -- {_v}", - "set {_v} to {_v} -- 5", - "set {_v} to {_v} ** {_v}", - "set {_v} to {_v} ** 5", - "set {_v} to {_v} // {_v}", - "set {_v} to {_v} // 5"}) -@Since("2.2-dev28") +@Examples({ + "set {_v} to vector 1, 2, 3 // vector 5, 5, 5", + "set {_v} to {_v} ++ {_v}", + "set {_v} to {_v} -- {_v}", + "set {_v} to {_v} ** {_v}", + "set {_v} to {_v} // {_v}" +}) +@Since("2.2-desecond8") public class ExprVectorArithmetic extends SimpleExpression { private enum Operator { PLUS("++") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().add(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().add(second); } }, MINUS("--") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().subtract(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().subtract(second); } }, MULT("**") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().multiply(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().multiply(second); } }, DIV("//") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().divide(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().divide(second); } }; @@ -84,7 +79,7 @@ public Vector calculate(final Vector v1, final Vector v2) { this.sign = sign; } - public abstract Vector calculate(Vector v1, Vector v2); + public abstract Vector calculate(Vector first, Vector second); @Override public String toString() { @@ -107,25 +102,22 @@ public String toString() { private Expression first, second; @SuppressWarnings("null") - private Operator op; + private Operator operator; @Override @SuppressWarnings({"unchecked", "null"}) public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { first = (Expression) exprs[0]; second = (Expression) exprs[1]; - op = patterns.getInfo(matchedPattern); + operator = patterns.getInfo(matchedPattern); return true; } @Override - protected Vector[] get(Event e) { - Vector v1 = first.getSingle(e), v2 = second.getSingle(e); - if (v1 == null) - v1 = new Vector(); - if (v2 == null) - v2 = new Vector(); - return CollectionUtils.array(op.calculate(v1, v2)); + protected Vector[] get(Event event) { + Vector first = this.first.getOptionalSingle(event).orElse(new Vector()); + Vector second = this.second.getOptionalSingle(event).orElse(new Vector()); + return CollectionUtils.array(operator.calculate(first, second)); } @Override @@ -139,8 +131,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return first.toString(e, debug) + " " + op + " " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return first.toString(event, debug) + " " + operator + " " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java index 8b407c38dd2..75412c017b2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java @@ -35,12 +35,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Vector Between Locations") @Description("Creates a vector between two locations.") -@Examples({"set {_v} to vector between {_loc1} and {_loc2}"}) +@Examples("set {_v} to vector between {_loc1} and {_loc2}") @Since("2.2-dev28") public class ExprVectorBetweenLocations extends SimpleExpression { @@ -62,12 +59,12 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Location l1 = from.getSingle(e); - Location l2 = to.getSingle(e); - if (l1 == null || l2 == null) + protected Vector[] get(Event event) { + Location from = this.from.getSingle(event); + Location to = this.to.getSingle(event); + if (from == null || to == null) return null; - return CollectionUtils.array(new Vector(l2.getX() - l1.getX(), l2.getY() - l1.getY(), l2.getZ() - l1.getZ())); + return CollectionUtils.array(new Vector(to.getX() - from.getX(), to.getY() - from.getY(), to.getZ() - from.getZ())); } @Override @@ -80,8 +77,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from " + from.toString(e, debug) + " to " + to.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from " + from.toString(event, debug) + " to " + to.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java index 11d040f2855..ae8ebe990e1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java @@ -34,12 +34,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Cross Product") @Description("Gets the cross product between two vectors.") -@Examples({"send \"%vector 1, 0, 0 cross vector 0, 1, 0%\""}) +@Examples("send \"%vector 1, 0, 0 cross vector 0, 1, 0%\"") @Since("2.2-dev28") public class ExprVectorCrossProduct extends SimpleExpression { @@ -60,12 +57,12 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Vector v1 = first.getSingle(e); - Vector v2 = second.getSingle(e); - if (v1 == null || v2 == null) + protected Vector[] get(Event event) { + Vector first = this.first.getSingle(event); + Vector second = this.second.getSingle(event); + if (first == null || second == null) return null; - return CollectionUtils.array(v1.clone().crossProduct(v2)); + return CollectionUtils.array(first.clone().crossProduct(second)); } @Override @@ -79,8 +76,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return first.toString(e, debug) + " cross " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return first.toString(event, debug) + " cross " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java index 65d4fc5134c..40cd9edd963 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java @@ -35,14 +35,13 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Cylindrical Shape") @Description("Forms a 'cylindrical shaped' vector using yaw to manipulate the current point.") -@Examples({"loop 360 times:", - " set {_v} to cylindrical vector radius 1, yaw loop-value, height 2", - "set {_v} to cylindrical vector radius 1, yaw 90, height 2"}) +@Examples({ + "loop 360 times:", + "\tset {_v} to cylindrical vector radius 1, yaw loop-value, height 2", + "set {_v} to cylindrical vector radius 1, yaw 90, height 2" +}) @Since("2.2-dev28") public class ExprVectorCylindrical extends SimpleExpression { @@ -65,13 +64,13 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number r = radius.getSingle(e); - Number y = yaw.getSingle(e); - Number h = height.getSingle(e); - if (r == null || y == null || h == null) + protected Vector[] get(Event event) { + Number radius = this.radius.getSingle(event); + Number yaw = this.yaw.getSingle(event); + Number height = this.height.getSingle(event); + if (radius == null || yaw == null || height == null) return null; - return CollectionUtils.array(VectorMath.fromCylindricalCoordinates(r.doubleValue(), VectorMath.fromSkriptYaw(y.floatValue()), h.doubleValue())); + return CollectionUtils.array(VectorMath.fromCylindricalCoordinates(radius.doubleValue(), VectorMath.fromSkriptYaw(yaw.floatValue()), height.doubleValue())); } @Override @@ -85,9 +84,9 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "cylindrical vector with radius " + radius.toString(e, debug) + ", yaw " + - yaw.toString(e, debug) + " and height " + height.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "cylindrical vector with radius " + radius.toString(event, debug) + ", yaw " + + yaw.toString(event, debug) + " and height " + height.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java index d5eb0a5c911..50ef5e9ea15 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java @@ -34,20 +34,10 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Dot Product") @Description("Gets the dot product between two vectors.") -@Examples({"set {_v} to {_v2} dot {_v3}"}) +@Examples("set {_dot} to {_v1} dot {_v2}") @Since("2.2-dev28") -/** - * NOTE vector 1, 2, 3 dot vector 1, 2, 3 does NOT work! - * it returns a new vector: 1, 2, 18. This should not happen - * and I have no idea why it does. I have also no idea why - * "z" takes the value 18. There must be some black magic - * going on. - */ public class ExprVectorDotProduct extends SimpleExpression { static { @@ -67,12 +57,12 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Number[] get(Event e) { - Vector v1 = first.getSingle(e); - Vector v2 = second.getSingle(e); - if (v1 == null || v2 == null) + protected Number[] get(Event event) { + Vector first = this.first.getSingle(event); + Vector second = this.second.getSingle(event); + if (first == null || second == null) return null; - return CollectionUtils.array(v1.getX() * v2.getX() + v1.getY() * v2.getY() + v1.getZ() * v2.getZ()); + return CollectionUtils.array(first.getX() * second.getX() + first.getY() * second.getY() + first.getZ() * second.getZ()); } @Override @@ -86,8 +76,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return first.toString(e, debug) + " dot " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return first.toString(event, debug) + " dot " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java index 71896818d71..6b553e6f50b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java @@ -34,12 +34,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Create from XYZ") @Description("Creates a vector from x, y and z values.") -@Examples({"set {_v} to vector 0, 1, 0"}) +@Examples("set {_v} to vector 0, 1, 0") @Since("2.2-dev28") public class ExprVectorFromXYZ extends SimpleExpression { @@ -62,10 +59,10 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number x = this.x.getSingle(e); - Number y = this.y.getSingle(e); - Number z = this.z.getSingle(e); + protected Vector[] get(Event event) { + Number x = this.x.getSingle(event); + Number y = this.y.getSingle(event); + Number z = this.z.getSingle(event); if (x == null || y == null || z == null) return null; return CollectionUtils.array(new Vector(x.doubleValue(), y.doubleValue(), z.doubleValue())); @@ -82,8 +79,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from x " + x.toString(e, debug) + ", y " + y.toString(e, debug) + ", z " + z.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from x " + x.toString(event, debug) + ", y " + y.toString(event, debug) + ", z " + z.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java index 75a65274d0d..8f7179e7d26 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java @@ -35,12 +35,9 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Vector from Pitch and Yaw") @Description("Creates a vector from a yaw and pitch value.") -@Examples({"set {_v} to vector from yaw 45 and pitch 45"}) +@Examples("set {_v} to vector from yaw 45 and pitch 45") @Since("2.2-dev28") public class ExprVectorFromYawAndPitch extends SimpleExpression { @@ -62,13 +59,13 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number y = yaw.getSingle(e); - Number p = pitch.getSingle(e); - if (y == null || p == null) + protected Vector[] get(Event event) { + Number skriptYaw = yaw.getSingle(event); + Number skriptPitch = pitch.getSingle(event); + if (skriptYaw == null || skriptPitch == null) return null; - float yaw = VectorMath.fromSkriptYaw(VectorMath.wrapAngleDeg(y.floatValue())); - float pitch = VectorMath.fromSkriptPitch(VectorMath.wrapAngleDeg(p.floatValue())); + float yaw = VectorMath.fromSkriptYaw(VectorMath.wrapAngleDeg(skriptYaw.floatValue())); + float pitch = VectorMath.fromSkriptPitch(VectorMath.wrapAngleDeg(skriptPitch.floatValue())); return CollectionUtils.array(VectorMath.fromYawAndPitch(yaw, pitch)); } @@ -83,8 +80,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from yaw " + yaw.toString(e, debug) + " and pitch " + pitch.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from yaw " + yaw.toString(event, debug) + " and pitch " + pitch.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java index 8c2d1094141..62f2a695e85 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java @@ -30,15 +30,14 @@ import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Length") @Description("Gets or sets the length of a vector.") -@Examples({"send \"%standard length of vector 1, 2, 3%\"", - "set {_v} to vector 1, 2, 3", - "set standard length of {_v} to 2", - "send \"%standard length of {_v}%\""}) +@Examples({ + "send \"%standard length of vector 1, 2, 3%\"", + "set {_v} to vector 1, 2, 3", + "set standard length of {_v} to 2", + "send \"%standard length of {_v}%\"" +}) @Since("2.2-dev28") public class ExprVectorLength extends SimplePropertyExpression { @@ -61,43 +60,49 @@ public Class[] acceptChange(ChangeMode mode) { } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; - final Vector v = getExpr().getSingle(e); - if (v == null) - return; - double n = ((Number) delta[0]).doubleValue(); + final Vector[] vectors = getExpr().getArray(event); + double deltaLength = ((Number) delta[0]).doubleValue(); switch (mode) { + case REMOVE: + deltaLength = -deltaLength; + //$FALL-THROUGH$ case ADD: - if (n < 0 && v.lengthSquared() < n * n) { - v.zero(); - } else { - double l = n + v.length(); - v.normalize().multiply(l); + for (Vector vector : vectors) { + if (deltaLength < 0 && vector.lengthSquared() < deltaLength * deltaLength) { + vector.zero(); + } else { + double newLength = deltaLength + vector.length(); + if (!vector.isNormalized()) + vector.normalize(); + vector.multiply(newLength); + } } - getExpr().change(e, new Vector[]{v}, ChangeMode.SET); break; - case REMOVE: - n = -n; - //$FALL-THROUGH$ case SET: - if (n < 0) - v.zero(); - else - v.normalize().multiply(n); - getExpr().change(e, new Vector[]{v}, ChangeMode.SET); + for (Vector vector : vectors) { + if (deltaLength < 0) { + vector.zero(); + } else { + if (!vector.isNormalized()) + vector.normalize(); + vector.multiply(deltaLength); + } + } break; } + getExpr().change(event, vectors, ChangeMode.SET); } @Override - protected String getPropertyName() { - return "vector length"; + public Class getReturnType() { + return Number.class; } @Override - public Class getReturnType() { - return Number.class; + protected String getPropertyName() { + return "vector length"; } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java index c450c074ddd..00b80f7d109 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java @@ -34,12 +34,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Normalized") @Description("Returns the same vector but with length 1.") -@Examples({"set {_v} to normalized {_v}"}) +@Examples("set {_v} to normalized {_v}") @Since("2.2-dev28") public class ExprVectorNormalize extends SimpleExpression { @@ -61,11 +58,14 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Vector v = vector.getSingle(e); - if (v == null) + protected Vector[] get(Event event) { + Vector vector = this.vector.getSingle(event); + if (vector == null) return null; - return CollectionUtils.array(v.clone().normalize()); + vector = vector.clone(); + if (!vector.isNormalized()) + vector.normalize(); + return CollectionUtils.array(vector); } @Override @@ -79,8 +79,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "normalized " + vector.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "normalized " + vector.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java index 21f71a3bef8..e15974b78a7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java @@ -35,12 +35,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Vector from Location") @Description("Creates a vector from a location.") -@Examples({"set {_v} to vector of {_loc}"}) +@Examples("set {_v} to vector of {_loc}") @Since("2.2-dev28") public class ExprVectorOfLocation extends SimpleExpression { @@ -62,11 +59,11 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Location l = location.getSingle(e); - if (l == null) + protected Vector[] get(Event event) { + Location location = this.location.getSingle(event); + if (location == null) return null; - return CollectionUtils.array(l.toVector()); + return CollectionUtils.array(location.toVector()); } @Override @@ -80,8 +77,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from " + location.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from " + location.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java b/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java index 493c023eb95..3507f8ab25a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java @@ -35,14 +35,13 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Spherical Shape") @Description("Forms a 'spherical shaped' vector using yaw and pitch to manipulate the current point.") -@Examples({"loop 360 times:", - " set {_v} to spherical vector radius 1, yaw loop-value, pitch loop-value", - "set {_v} to spherical vector radius 1, yaw 45, pitch 90"}) +@Examples({ + "loop 360 times:", + "\tset {_v} to spherical vector radius 1, yaw loop-value, pitch loop-value", + "set {_v} to spherical vector radius 1, yaw 45, pitch 90" +}) @Since("2.2-dev28") public class ExprVectorSpherical extends SimpleExpression { @@ -65,13 +64,13 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number r = radius.getSingle(e); - Number y = yaw.getSingle(e); - Number p = pitch.getSingle(e); - if (r == null || y == null || p == null) + protected Vector[] get(Event event) { + Number radius = this.radius.getSingle(event); + Number yaw = this.yaw.getSingle(event); + Number pitch = this.pitch.getSingle(event); + if (radius == null || yaw == null || pitch == null) return null; - return CollectionUtils.array(VectorMath.fromSphericalCoordinates(r.doubleValue(), VectorMath.fromSkriptYaw(y.floatValue()), p.floatValue() + 90)); + return CollectionUtils.array(VectorMath.fromSphericalCoordinates(radius.doubleValue(), VectorMath.fromSkriptYaw(yaw.floatValue()), pitch.floatValue() + 90)); } @Override @@ -85,9 +84,9 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "spherical vector with radius " + radius.toString(e, debug) + ", yaw " + yaw.toString(e, debug) + - " and pitch" + pitch.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "spherical vector with radius " + radius.toString(event, debug) + ", yaw " + yaw.toString(event, debug) + + " and pitch" + pitch.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java index ef5444347ca..bdfeeb33b74 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java @@ -26,12 +26,9 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; -/** - * @author bi0qaw - */ @Name("Vectors - Squared Length") @Description("Gets the squared length of a vector.") -@Examples({"send \"%squared length of vector 1, 2, 3%\""}) +@Examples("send \"%squared length of vector 1, 2, 3%\"") @Since("2.2-dev28") public class ExprVectorSquaredLength extends SimplePropertyExpression { @@ -46,13 +43,14 @@ public Number convert(Vector vector) { } @Override - protected String getPropertyName() { - return "squared length of vector"; + public Class getReturnType() { + return Number.class; } @Override - public Class getReturnType() { - return Number.class; + protected String getPropertyName() { + return "squared length of vector"; } + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java index 76b5ab59fe2..2955fef5b97 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java @@ -34,21 +34,20 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - XYZ Component") @Description("Gets or changes the x, y or z component of a vector.") -@Examples({"set {_v} to vector 1, 2, 3", - "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", - "add 1 to x of {_v}", - "add 2 to y of {_v}", - "add 3 to z of {_v}", - "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", - "set x component of {_v} to 1", - "set y component of {_v} to 2", - "set z component of {_v} to 3", - "send \"%x component of {_v}%, %y component of {_v}%, %z component of {_v}%\"",}) +@Examples({ + "set {_v} to vector 1, 2, 3", + "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", + "add 1 to x of {_v}", + "add 2 to y of {_v}", + "add 3 to z of {_v}", + "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", + "set x component of {_v::*} to 1", + "set y component of {_v::*} to 2", + "set z component of {_v::*} to 3", + "send \"%x component of {_v::*}%, %y component of {_v::*}%, %z component of {_v::*}%\"" +}) @Since("2.2-dev28") public class ExprVectorXYZ extends SimplePropertyExpression { @@ -68,58 +67,62 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } @Override - public Number convert(Vector v) { - return axis == 0 ? v.getX() : (axis == 1 ? v.getY() : v.getZ()); + public Number convert(Vector vector) { + return axis == 0 ? vector.getX() : (axis == 1 ? vector.getY() : vector.getZ()); } @Override @SuppressWarnings("null") public Class[] acceptChange(ChangeMode mode) { if ((mode == ChangeMode.ADD || mode == ChangeMode.REMOVE || mode == ChangeMode.SET) - && getExpr().isSingle() && Changer.ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class)) + && Changer.ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class)) return CollectionUtils.array(Number.class); return null; } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; - final Vector v = getExpr().getSingle(e); - if (v == null) - return; - double n = ((Number) delta[0]).doubleValue(); + Vector[] vectors = getExpr().getArray(event); + double deltaValue = ((Number) delta[0]).doubleValue(); switch (mode) { case REMOVE: - n = -n; + deltaValue = -deltaValue; //$FALL-THROUGH$ case ADD: - if (axis == 0) - v.setX(v.getX() + n); - else if (axis == 1) - v.setY(v.getY() + n); - else - v.setZ(v.getZ() + n); - getExpr().change(e, new Vector[] {v}, ChangeMode.SET); + for (Vector vector : vectors) { + if (axis == 0) + vector.setX(vector.getX() + deltaValue); + else if (axis == 1) + vector.setY(vector.getY() + deltaValue); + else + vector.setZ(vector.getZ() + deltaValue); + } break; case SET: - if (axis == 0) - v.setX(n); - else if (axis == 1) - v.setY(n); - else - v.setZ(n); - getExpr().change(e, new Vector[] {v}, ChangeMode.SET); + for (Vector vector : vectors) { + if (axis == 0) + vector.setX(deltaValue); + else if (axis == 1) + vector.setY(deltaValue); + else + vector.setZ(deltaValue); + } + break; + default: + assert false; + return; } + getExpr().change(event, vectors, ChangeMode.SET); } - - @Override - protected String getPropertyName() { - return axes[axis] + " component"; - } - + @Override public Class getReturnType() { return Number.class; } - + + @Override + protected String getPropertyName() { + return axes[axis] + " component"; + } } diff --git a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java index 878fe70d1db..320b4d63c39 100644 --- a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java +++ b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java @@ -18,6 +18,17 @@ */ package ch.njol.skript.sections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.config.SectionNode; import ch.njol.skript.doc.Description; @@ -35,16 +46,6 @@ import ch.njol.skript.util.Getter; import ch.njol.skript.variables.Variables; import ch.njol.util.Kleenean; -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.bukkit.util.Consumer; -import org.eclipse.jdt.annotation.Nullable; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; @Name("Spawn") @Description({ @@ -93,16 +94,18 @@ public Entity get(SpawnEvent spawnEvent) { }, EventValues.TIME_NOW); } - @Nullable - public static Entity lastSpawned = null; - @SuppressWarnings("NotNullFieldNotInitialized") private Expression locations; + @SuppressWarnings("NotNullFieldNotInitialized") private Expression types; + @Nullable private Expression amount; + @Nullable + public static Entity lastSpawned; + @Nullable private Trigger trigger; @@ -137,16 +140,15 @@ public boolean init(Expression[] exprs, protected TriggerItem walk(Event event) { lastSpawned = null; - Object localVars = Variables.copyLocalVariables(event); - Consumer consumer; if (trigger != null) { consumer = o -> { lastSpawned = o; SpawnEvent spawnEvent = new SpawnEvent(o); // Copy the local variables from the calling code to this section - Variables.setLocalVariables(spawnEvent, localVars); + Variables.setLocalVariables(spawnEvent, Variables.copyLocalVariables(event)); TriggerItem.walk(trigger, spawnEvent); + // And copy our (possibly modified) local variables back to the calling code Variables.setLocalVariables(event, Variables.copyLocalVariables(spawnEvent)); // Clear spawnEvent's local variables as it won't be done automatically Variables.removeLocals(spawnEvent); diff --git a/src/main/java/ch/njol/skript/structures/StructAliases.java b/src/main/java/ch/njol/skript/structures/StructAliases.java index 8d09fcf802d..cf473c63eb3 100644 --- a/src/main/java/ch/njol/skript/structures/StructAliases.java +++ b/src/main/java/ch/njol/skript/structures/StructAliases.java @@ -20,6 +20,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.aliases.ScriptAliases; import ch.njol.skript.config.SectionNode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -30,6 +31,7 @@ import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.structure.Structure; @Name("Aliases") @@ -54,7 +56,11 @@ public boolean init(Literal[] args, int matchedPattern, ParseResult parseResu node.convertToEntries(0, "="); // Initialize and load script aliases - Aliases.createScriptAliases(getParser().getCurrentScript()).parser.load(node); + Script script = getParser().getCurrentScript(); + ScriptAliases scriptAliases = Aliases.getScriptAliases(script); + if (scriptAliases == null) + scriptAliases = Aliases.createScriptAliases(script); + scriptAliases.parser.load(node); return true; } diff --git a/src/main/java/ch/njol/skript/structures/StructVariables.java b/src/main/java/ch/njol/skript/structures/StructVariables.java index b6810c4812e..f3e32e19dcf 100644 --- a/src/main/java/ch/njol/skript/structures/StructVariables.java +++ b/src/main/java/ch/njol/skript/structures/StructVariables.java @@ -87,14 +87,14 @@ public static class DefaultVariables implements ScriptData { private final Deque[]>> hints = new ArrayDeque<>(); private final List> variables; + private boolean loaded; public DefaultVariables(Collection> variables) { this.variables = ImmutableList.copyOf(variables); } - @SuppressWarnings("unchecked") public void add(String variable, Class... hints) { - if (hints == null || hints.length <= 0) + if (hints == null || hints.length == 0) return; if (CollectionUtils.containsAll(hints, Object.class)) // Ignore useless type hint. return; @@ -115,7 +115,7 @@ public void exitScope() { /** * Returns the type hints of a variable. * Can be null if no type hint was saved. - * + * * @param variable The variable string of a variable. * @return type hints of a variable if found otherwise null. */ @@ -140,14 +140,24 @@ public boolean hasDefaultVariables() { public List> getVariables() { return variables; } + + private boolean isLoaded() { + return loaded; + } } @Override public boolean init(Literal[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { SectionNode node = entryContainer.getSource(); node.convertToEntries(0, "="); - - List> variables = new ArrayList<>(); + List> variables; + Script script = getParser().getCurrentScript(); + DefaultVariables existing = script.getData(DefaultVariables.class); // if the user has TWO variables: sections + if (existing != null && existing.hasDefaultVariables()) { + variables = new ArrayList<>(existing.variables); + } else { + variables = new ArrayList<>(); + } for (Node n : node) { if (!(n instanceof EntryNode)) { Skript.error("Invalid line in variables structure"); @@ -227,20 +237,26 @@ public boolean init(Literal[] args, int matchedPattern, ParseResult parseResu } variables.add(new NonNullPair<>(name, o)); } - getParser().getCurrentScript().addData(new DefaultVariables(variables)); + script.addData(new DefaultVariables(variables)); // we replace the previous entry return true; } @Override public boolean load() { DefaultVariables data = getParser().getCurrentScript().getData(DefaultVariables.class); + if (data == null) { // this shouldn't happen + Skript.error("Default variables data missing"); + return false; + } else if (data.isLoaded()) { + return true; + } for (NonNullPair pair : data.getVariables()) { String name = pair.getKey(); if (Variables.getVariable(name, null, false) != null) continue; - Variables.setVariable(name, pair.getValue(), null, false); } + data.loaded = true; return true; } @@ -248,8 +264,13 @@ public boolean load() { public void postUnload() { Script script = getParser().getCurrentScript(); DefaultVariables data = script.getData(DefaultVariables.class); - for (NonNullPair pair : data.getVariables()) - Variables.setVariable(pair.getKey(), null, null, false); + if (data == null) // band-aid fix for this section's behaviour being handled by a previous section + return; // see https://github.com/SkriptLang/Skript/issues/6013 + for (NonNullPair pair : data.getVariables()) { + String name = pair.getKey(); + if (name.contains("<") && name.contains(">")) // probably a template made by us + Variables.setVariable(pair.getKey(), null, null, false); + } script.removeData(DefaultVariables.class); } diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java index e707d3337d3..42e964918f9 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java @@ -69,7 +69,7 @@ public ExpressionEntryData( @SuppressWarnings("unchecked") protected Expression getValue(String value) { Expression expression; - try (ParseLogHandler log = new ParseLogHandler()) { + try (ParseLogHandler log = new ParseLogHandler().start()) { expression = new SkriptParser(value, flags, ParseContext.DEFAULT) .parseExpression(returnType); if (expression == null) // print an error if it couldn't parse diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index a95cfe461b1..54a9bb7a070 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -76,6 +76,10 @@ allow ops to use effect commands: false # Whether server operators which do not have the permission "skript.effectcommands" should have access to effect commands. # This setting is mainly useful for servers that do not run any permissions plugin. +log effect commands: false +# Whether Skript should log the usage of effect commands. +# They will be logged as [INFORMATION] in this format: ' issued effect command: ' + player variable fix: true # Whether to enable the player variable fix if a player has rejoined and was reciding inside a variable. # Player objects inside a variable(list or normal) are not updated to the new player object @@ -119,11 +123,6 @@ plugin priority: high # Skript removes drops it shouldn't => decrease priority or specify which item types to remove -log player commands: false -# Whether Skript should log the usage of custom commands. -# They will be logged as [INFORMATION] in this format: ': / ' - - number accuracy: 2 # How many digits should be displayed after the dot at maximum when displaying numbers. # Zeroes will never be displayed at all, so this setting only applies to numbers that actually have a decimal part with one or more non-zero digits. diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 00236cd6d6b..89130449b8c 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1910,6 +1910,7 @@ attribute types: generic_follow_range: generic follow range, follow range generic_knockback_resistance: generic knockback resistance, knockback resistance generic_luck: generic luck, luck + generic_max_absorption: generic max absorption, max absorption generic_max_health: generic max health, max health generic_movement_speed: generic movement speed, movement speed horse_jump_strength: horse jump strength diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index 17225590e3b..40db6ddb7a0 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -45,7 +45,7 @@ skript command: changes: Lists all changes since the current version download: Download the newest version info: Prints a message with links to Skript's aliases and documentation - gen-docs: Generates documentation using doc-templates in plugin folder + gen-docs: Generates documentation using docs/templates in plugin folder test: Used for running internal Skript tests invalid script: Can't find the script '%s' in the scripts folder! diff --git a/src/main/resources/lang/french.lang b/src/main/resources/lang/french.lang index 9a548ba8587..6aa7e26342d 100644 --- a/src/main/resources/lang/french.lang +++ b/src/main/resources/lang/french.lang @@ -45,7 +45,7 @@ skript command: changes: Liste toutes les modifications apportées depuis la version actuelle download: Télécharge la dernière version info: Affiche un message contenant les liens vers les alias et la documentation de Skript - gen-docs: Génère la documentation en utilisant doc-templates dans le dossier du plugin + gen-docs: Génère la documentation en utilisant docs/templates dans le dossier du plugin test: Utilisé pour exécuter les tests Skript invalid script: Impossible de trouver le script '%s' dans le dossier des scripts ! diff --git a/src/main/resources/lang/german.lang b/src/main/resources/lang/german.lang index cb9637e9dd7..0a8255f44e8 100644 --- a/src/main/resources/lang/german.lang +++ b/src/main/resources/lang/german.lang @@ -45,7 +45,7 @@ skript command: changes: Listet alle Änderungen seit der aktuellen Version auf (auf englisch) download: Lädt die neueste Version herunter info: Druckt eine Nachricht mit Links zu den Aliases und der Dokumentation von Skript. - gen-docs: Generiert Dokumentation mithilfe von doc-templates im Plugin-Ordner + gen-docs: Generiert Dokumentation mithilfe von docs/templates im Plugin-Ordner test: Wird zum Ausführen von Skript-Tests verwendet invalid script: Das Skript '%s' konnte nicht gefunden werden. diff --git a/src/main/resources/lang/simplifiedchinese.lang b/src/main/resources/lang/simplifiedchinese.lang index e1229106eba..c64b007caba 100644 --- a/src/main/resources/lang/simplifiedchinese.lang +++ b/src/main/resources/lang/simplifiedchinese.lang @@ -45,7 +45,7 @@ skript command: changes: 列出自当前版本以来的所有变化 download: 下载最新的版本 info: 打印一个带有Skript的别名和文档链接的信息 - gen-docs: 使用插件文件夹中的doc-templates生成文档 + gen-docs: 使用插件文件夹中的docs/templates生成文档 test: 用于运行内部的Skript测试 invalid script: 无法在scripts文件夹中找到脚本%s! diff --git a/src/test/skript/environments/java17/paper-1.20.1.json b/src/test/skript/environments/java17/paper-1.20.2.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.20.1.json rename to src/test/skript/environments/java17/paper-1.20.2.json index 3a117b97397..0512ae142b0 100644 --- a/src/test/skript/environments/java17/paper-1.20.1.json +++ b/src/test/skript/environments/java17/paper-1.20.2.json @@ -1,11 +1,11 @@ { - "name": "paper-1.20.1", + "name": "paper-1.20.2", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.20.1", + "version": "1.20.2", "target": "paperclip.jar" } ], diff --git a/src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk b/src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk new file mode 100644 index 00000000000..23033b01db0 --- /dev/null +++ b/src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk @@ -0,0 +1,5 @@ +test "local vars created in EffSecSpawn": + set {_spawn} to spawn of world "world" + spawn 4 zombies at {_spawn}: + add 1 to {_test} + assert {_test} is 4 with "local var created in EffSecSpawn was not properly incremented" diff --git a/src/test/skript/tests/syntaxes/structures/StructAliases.sk b/src/test/skript/tests/syntaxes/structures/StructAliases.sk new file mode 100644 index 00000000000..88b1024793b --- /dev/null +++ b/src/test/skript/tests/syntaxes/structures/StructAliases.sk @@ -0,0 +1,9 @@ +aliases: + cool_dirt = dirt + +aliases: + cool_stone = stone + +test "script aliases": + assert cool_dirt is dirt with "custom dirt alias failed" + assert cool_stone is stone with "custom stone alias failed" diff --git a/src/test/skript/tests/syntaxes/structures/StructVariables.sk b/src/test/skript/tests/syntaxes/structures/StructVariables.sk new file mode 100644 index 00000000000..5de1d5c7be3 --- /dev/null +++ b/src/test/skript/tests/syntaxes/structures/StructVariables.sk @@ -0,0 +1,10 @@ +variables: + {variables_test1} = true + +variables: + {variables_test1} = false # we hope it doesn't overwrite! + {variables_test2} = true + +test "default variables": + assert {variables_test1} is true with "{variables_test1} was not true" + assert {variables_test2} is true with "{variables_test2} was not true"