Compare commits

...

37 Commits

Author SHA1 Message Date
Andreas Troelsen 29c5d7f56d Release 0.108. 2024-01-01 20:15:55 +01:00
Andreas Troelsen e40fc6ef84 Add `publish-hangar` GitHub Actions workflow.
Introduces a new workflow that runs when a new build has been published
on GitHub Releases. It converts the release notes to Hangar Markdown and
sends it to Hangar along with the jar-file.

Note: The workflow currently relies on the version string being appended
to the filename of the jar-file. Without it, the file reference in the
`curl` request that uploads the build would need to change.

The workflow references a new secret, `HANGAR_TOKEN`, which is just an
API key for the Hangar API. The token was created in the Hangar profile
settings (API Keys), and its only permission is `create_version`.

In order to properly upload a new build to Hangar, we need to construct
a somewhat complex JSON object. This is because the Hangar API allows
for publishing releases on multiple platforms and for multiple versions,
which makes the simple use case for MobArena's single file upload look a
bit overcomplicated. Unlike the CurseForge API, the Hangar API supports
"normal" platform version strings, so we don't need to map anything. It
also supports patch version wildcards, so we can get away with `1.18.x`,
`1.19.x`, etc. for each version supported. The API only uses the API key
for authentication, which means we need to grab a JWT and use that for
the actual upload request. Note that the `pluginDependencies` property
is currently required, but it can be left empty.

The workflow can be invoked directly via the `workflow_dispatch` event,
which might come in handy if something in the pipeline breaks.

The Hangar base URL and project slug are both hardcoded, and things
would probably be cleaner if they were made into variables, but we don't
need this workflow anywhere else, so it's fine for now.
2024-01-01 17:05:41 +01:00
Andreas Troelsen be6fd85a6d Add `publish-curseforge` GitHub Actions workflow.
Introduces a new workflow that runs when a new build has been published
on GitHub Releases. It converts the release notes to CurseForge HTML and
sends it to CurseForge along with the jar-file.

Note: The workflow currently relies on the version string being appended
to the filename of the jar-file. Without it, the file reference in the
`curl` request that uploads the build would need to change.

The workflow references a new secret, `CURSEFORGE_TOKEN`, which is just
an API key for the CurseForge API. The token was created on CurseForge
under profile settings (My API Tokens).

In order to properly upload a new build to CurseForge, we need a list of
"game version IDs", which isn't completely trivial. The API gives us a
means of looking up _all_ Minecraft game version IDs, but we then have
to manually filter out the ones that don't apply to Bukkit plugins, as
there are duplicate entries for each Minecraft version, and only some of
them work for Bukkit plugins (which turns out to be the ones with game
version type ID 1). The structure of the `metadata` field combined with
how incredibly difficult bash can be to work with has resulted in some
gnarly text processing trying to filter the JSON response and turning it
into a list for use in the `jq` template, but it gets the job done.

The CurseForge base URL and project ID are both hardcoded, and things
would probably be cleaner if they were made into variables, but we don't
need this workflow anywhere else, so it's fine for now.

The workflow can be invoked directly via the `workflow_dispatch` event,
which might come in handy if something in the pipeline breaks.

Lots of inspiration was found in the probably really great GitHub Action
`curseforge-upload` [1]. We could have probably just used that, but it's
nice to have full control of the process. At any rate, thanks to itsmeow
and NotMyFault for publishing their work.

---

[1] https://github.com/itsmeow/curseforge-upload
2024-01-01 16:46:38 +01:00
Andreas Troelsen b881943656 Add `hangar` format to release note script.
Hangar uses Markdown, so this is a very easy release note "conversion"
just like GitHub Releases.
2023-12-31 15:14:25 +01:00
Andreas Troelsen 8e5d2f0d23 Make InventoryThingParser package-private.
We don't need the parser exposed outside of the `things` package, and
none of the other thing parsers are public anyway. Coincidentally, this
fixes a warning about exposing InventoryThing outside of its visibility
scope, so yay.
2024-01-01 18:34:20 +01:00
Andreas Troelsen 3b7b638b00 Make LexeMatcher package-private.
We don't need it outside of the `formula` package.

This fixes warnings about exposing Lexeme outside of its visibility
scope, so yay.
2024-01-01 18:26:44 +01:00
Andreas Troelsen eb51a31720 Remove unused ThingManager constructor.
This fixes a warning about exposing ItemStackThingParser outside of its
visibility scope, but really it's just a good little cleanup step, since
the constructor in question is never used for anything. We might want to
eventually expose the ItemStackThingParser and use it in more places in
the code base, but in that case, and in that case it would probably make
sense to re-introduce the constructor, but I'm calling YAGNI on this in
order to nuke a warning.
2024-01-01 18:23:54 +01:00
Andreas Troelsen 82f00c5535 Fix "unary operator" warnings in FormulaManagerIT.
Okay, the reason the code included the unary plus was to more directly
represent the resulting expression, but I'm guessing the compiler isn't
going to respect that intent even if it could somehow understand it, so
it will probably remove the symbols and just parse it all the same.

Unlike with the unary plus, the unary minus can be "fixed" by wrapping
it in parentheses. The end result is of course the exact same, but the
intent is perhaps a bit clearer this way. We want to try to coerce the
compiler into creating an expression with "add a negative value", just
for the sake of "correctness" at the runtime evaluation level, but even
if that isn't what will actually happen, the explicit code is still a
bit easier to read. While unary plus is easy to disregard, "fixing" an
unnecessary unary minus would mean having to change the binary operator
before it, which muddies the intent of the expression.
2024-01-01 17:49:21 +01:00
Andreas Troelsen d8fdbb80c0 Simplify formula operation interfaces.
This commit releases the BinaryOperation and UnaryOperation interfaces
of the `formula` package from their `java.util.function` supertypes and
redeclares the previously inherited functions directly in the operation
interfaces, but also reifies them by explicitly using primitive doubles
instead of generics and wrapper classes. Doing so does not change the
functionality or any other code at all, but it makes the interfaces much
"stronger", since they no longer need to consider `null` values, which
they didn't actually take into account anyway. This fixes a warning in
Visual Studio Code (not sure how to get the same warning in IntelliJ)
about the operator registrations in the default formula environment
factory method being unsafe.
2024-01-01 19:39:59 +01:00
Andreas Troelsen e5ffe169a1 Create release drafts from "Release ..." commits.
This commit adds a second job to the build workflow that runs after the
build job has completed. The job creates a GitHub Releases _draft_ that
needs to be manually published in order to be publicly available.

The job runs if, and only if, the following conditions are met:

- The build job has completed _successfully_, i.e. if the Gradle `build`
  task fails, the draft job doesn't run.

- The `push` event that triggered the workflow happened on the `master`
  branch, i.e. releases will never be created from temporary branches.

- The commit message of the most recent commit in the `push` event that
  triggered the workflow starts with `"Release "`, i.e. there must be a
  commit that explicitly tries to "release" a build.

- The version string does _not_ end with `-SNAPSHOT`, i.e. development
  builds will not be released.

All of these conditions act as safeguards, so we don't end up releasing
something we don't want to release, but they also prevent bloating the
Releases page with a bunch of useless drafts.

The job uses the `version` output variable from the build job that was
introduced in a recent commit to extract release notes using the script
that was also introduced recently, as well as for the name of the _tag_
to create when the release is published.

Note that the `GITHUB_TOKEN` environment variable is required to be set
when we want to use the GitHub CLI in a workflow [1]. The job also has
an explicit `contents: write` permission, which is required for creating
releases from GitHub Actions.
2023-12-31 04:43:53 +01:00
Andreas Troelsen 798ae0f578 Output version string in build workflow.
This commit makes the build workflow output the version string as found
in the `version` property in `build.gradle.kts`. The version string will
be necessary further down the pipeline when we need to extract release
notes and create tags.

There are many ways to extract the version string:

- Use `grep` to grab it from `build.gradle.kts` directly. This is pretty
  brittle, since we don't really know for sure if the structure of the
  file will change in the future.

- Create a Gradle task in `build.gradle.kts` that prints the version
  string. This is probably the most direct approach we could take, but
  such a hyper-specific task feels like a code smell. It also requires
  running Gradle again, which is a bit slow.

- Use the built-in `properties` task in Gradle to print the `version`
  property and `grep` it. We avoid changing `build.gradle.kts`, which is
  a plus, but we still have to actually run Gradle.

- Parse the filename of the resulting jar-file in `build/libs/`, since
  it now contains the version string. This is also brittle, because we
  don't know if we're gonna continue to append the version string to the
  jar-file, and depending so much on it being there is a little scary.

- Extract `plugin.yml` from the resulting jar-file and `grep` it. This
  is perhaps a little crude, but it is much faster than running Gradle,
  and as a bonus, we get a bit closer to "what's inside the jar-file",
  which should give us a bit more confidence that any given release is
  actually the version it claims to be.

It may seem like a small thing to invest so much text on, but from an
automation standpoint, it is much easier to be confident in automations
with predictable and robust mechanisms for deriving input variables.
2023-12-31 04:16:08 +01:00
Andreas Troelsen 84776990b9 Add release note extraction script.
Introduces a crude Python script that can extract release notes from the
changelog for a given version and convert it to one of three different
output formats:

- `github` for GitHub Releases. This is just the Markdown itself, but
  with the very first line (the version) removed, because the version is
also the title of the release itself.

- `spigot` for Spigot Resources. This is the BBCode format used on the
  forums and in the resource descriptions.

- `curse` for CurseForge File Uploads. Curse uses a so-called "WYSIWYG"
  format that's really just HTML underneath.

The formats for Spigot and CurseForge are straightforward to convert to
as long as we only use simple text formatting, bullet lists, and links,
but that is really all the changelog should consist of anyway.

While this script already makes the release process quite a bit easier
on its own, the end goal is to _automate_ releases as much as possible,
and to do that, we need to be able to extract release notes, and we need
to be able to do it from GitHub Actions, which is quite a bit simpler if
we don't use third-party libraries.

Publishing releases on GitHub is almost trivial, while CurseForge is
pretty easy, and Hangar should be very doable as well. Spigot, on the
other hand, is stuck in the dark ages, so we must continue to upload
files manually there.
2023-12-31 03:25:14 +01:00
Andreas Troelsen 590f877756 Build on push to branches, not tags.
According to the documentation, by not specifying anything in the `push`
event, GitHub Actions will run the workflow for both branches and tags,
but by specifying `branches` alone, it will _only_ run the workflow for
branches and _not_ tags [1].

We want to build on pushes to _all_ branches so we can give folks a jar
to try out for feature requests and bug fixes. We don't want to build on
tags, however, because they don't provide any value in the current build
pipeline. If the version was derived from Git tags, it could make sense
to build on tags, but that's not where we are.

As for the `**` glob pattern instead of just `*`, the documentation says
that the latter does not match forward slashes [2], and while our naming
convention for branches doesn't use slashes right now, there's no reason
that they shouldn't be viable later down the road.

---

[1] https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onpushbranchestagsbranches-ignoretags-ignore
[2] https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
2023-12-31 02:25:41 +01:00
Andreas Troelsen 932b9de8f2 Minor tweaks to build workflow.
Removes trailing whitespace and simplifies some step names.
2023-12-31 02:24:53 +01:00
Andreas Troelsen 614d95683e Update `upload-artifact` to v4.
Apparently there are numerous performance and behavioral improvements.
2023-12-30 23:49:03 +01:00
Andreas Troelsen 4c855e6705 Include version in artifact filename.
Removes the "archive version" override for the `shadowJar` task in the
build file, resulting in a jar-file that contains the current version
number in the filename. It's mostly a convenience tradeoff; either we
can see the version directly in the filename and avoid assumptions, or
we can easily overwrite an existing jar-file with a new one.

Also updates the upload step of the build workflow to a glob pattern so
we grab the file regardless of the version.
2023-12-30 23:36:07 +01:00
Andreas Troelsen 7942c33e67 Add `monster-teleporting` to resource file.
This should have been part of commit 511d16f, but apparently I forgot to
stage the file.

Whoops.
2023-12-17 21:20:25 +01:00
Andreas Troelsen f57f10ecd5 Convert to Gradle.
This commit converts the project from Maven to Gradle by removing the
old Maven-related files such as `pom.xml` and the Maven Wrapper, and
replacing them with their Gradle counterparts (more or less).

Comparing the artifacts from Maven and Gradle indicates no significant
differences in the resulting jar-files, and a quick test of the plugin
shows that things are still working as expected.

Bits of `build.gradle.kts` may need a bit of a tune-up later down the
road, e.g. the test sources "hack" put in place. It may be cleaner to
omit this hack and just suck up having to repeat dependencies, but in
that case, it might be better to embrace the "libs" file instead of
having to repeat dependencies in full.

Note that this commit changes the caching mechanism used in the GitHub
Actions build workflow, opting for the one built into `setup-java`.
2023-12-09 21:08:09 +01:00
Andreas Troelsen 511d16f45a Add per-arena setting `monster-teleporting`.
This setting allows server owners to allow arena monsters to teleport
around _while inside the region_. They still can't teleport out of the
region.

Taken at face value, this should just be the default behavior. However,
due to arena regions being boxes, any non-box shaped arena will need a
region that covers more than the physical arena structure, which means
mobs like Endermen will be able to teleport into possibly unreachable
areas of the physical structure. So we have to make do with a setting.

Closes #762
2023-11-13 16:29:51 +01:00
Andreas Troelsen 457bf2ffff Clean up PvP activation logic.
The idea behind the previous implementation worked, but it was a tad bit
confusing. This commit refactors the activation logic by simply removing
it entirely. The "activation" part of the logic is now derived from the
wave number (if 0, it means we haven't _really_ started yet), instead of
relying on the spawn thread to toggle the flag on and off. This kind of
dependency inversion (spawn thread -> listener, listener -> "phase") is
a pretty decent (albeit super tiny) step towards cleaning up the whole
session system, so I call that a victory in and of itself!
2023-11-13 16:19:01 +01:00
Andreas Troelsen a662157cbf Reformat ArenaListener fields.
What a mess, sheesh...
2023-11-13 15:37:51 +01:00
Andreas Troelsen 9e083a91de Report Spigot health error to admins.
Using the new `mobarena.admin.errors` permission, this commit provides
server owners with a way to make the infamous Spigot health error much
more visible by sending the error message to any "admins" online when
the error occurs.

Closes #764
2023-11-13 14:39:05 +01:00
Andreas Troelsen 12314f476c Add `mobarena.admin.errors` permission.
Introduces a new permission for "admins" that can be used to increase
visibility of errors caught by the plugin. Server owners may not want
_all_ online players to see these types of messages, so the permission
gives error handlers a way to filter the list of online players before
sending the error message.
2023-11-13 14:37:34 +01:00
Andreas Troelsen feb257213c Add support for saved items.
Introduces the concept of a _saved item_; an in-game item that has been
captured in a YAML file via Bukkit's item serialization mechanism. These
items can be referenced in the config-file in all places that any other
normal item can be used, assuming the ThingManager is in charge of the
parsing. This should help bridge the gap between class chests and the
config-file by allowing any Bukkit serializable item stack to be stored
and referenced as if MobArena's item syntax directly supported it.

Three new setup commands are introduced to help manage the items, such
that they can be created, deleted, and loaded (for "editing" purposes).
The commands are somewhat rough around the edges and may need a little
bit of polish going forward.

Together with the new inventory referencing Things, this functionality
should help provide most of the flexibility people have been missing
from the item syntax for about a decade... Hell, it's about time.

Closes #212
2023-11-13 14:33:31 +01:00
Andreas Troelsen d7336526e1 Add InventoryThing collection.
Adds three new Thing types that can be used to reference items in chests
(or any block-based InventoryHolder):

- InventoryIndexThing looks up an item by index/slot in an inventory.
- InventoryGroupThing groups all non-null/non-air items in an inventory
  into a ThingGroup.
- InventoryRangeThing groups all non-null/non-air items in a given range
  of an inventory into a ThingGroup.

The new Thing types aim to bridge a gap between the class chests and the
rest of the Thing-based parts of the config-file. The goal is two-fold:
allow for more in-game configuration so access to the config-file isn't
_quite_ as crucial, and propagate the item-wise feature completeness of
class chests to other parts of the plugin.

While class chests are low configuration and a bit "all or nothing", the
inventory Thing types require manually punching in the coords for chests
and possibly indices/ranges for items. This means that the initial setup
could be a bit unwieldy, and highly volatile wave setups are definitely
not a good fit. If the wave setup is mostly pre-defined, it is fairly
easy to tweak upgrade waves and rewards in the same way class chests are
tweaked.

As for item-wise feature completeness, the inventory Thing types share
the same "if Bukkit can copy it, it will work" rule of thumb as class
chests do, which means items with metadata such as custom names, lore,
or even NBTs, should just work. This could remove the need to employ
other plugins.

By no means can this solution be considered "optimal", but it it _does_
enable some long-requested features.

Closes #456
2023-11-13 14:32:35 +01:00
Andreas Troelsen 1a7109a1d4 Add Hangar link to README.
We'll get around to the Gradle plugin and auto-publishing at some point,
but for now, let's at least just link to the project page.
2023-11-13 12:21:40 +01:00
Andreas Troelsen af513b03b0 Prevent unauthorized sign edits.
Since Minecraft 1.20, players can edit signs by right-clicking on them,
and that poses a problem for the sign-centric portions of the plugin,
such as class selection signs and the various types of arena signs.

This commit refactors the PlayerInteractEvent handler in ArenaListener
in order to break open the possibility of handling non-lobby players as
well. We're a little more strict with lobby players, and we still want
to handle class sign clicks and iron block clicks here. For players who
aren't in the lobby, we're really just blocking the event according to
the regular protection rules (block is inside region, protect is on, and
arena is not in edit mode).

It also blanket cancels events in the HandlesSignClicks event handler,
because there is no meaningful way to edit an arena sign, since their
contents come from the template file and not from what is written on
them by the sign renderer.

Ideally, we'd refactor some of this event handler logic, because it's a
lot easier to take care of the individual responsibilities in separate
event handlers.

Fixes #765
2023-11-04 00:20:39 +01:00
Andreas Troelsen 6579c4bf0e Call `update()` when reparing signs.
It boggles the mind that this tiny little class has worked as intended
since 2011 (!!), and all of a sudden, signs no longer retain their text
in the repair procedure...

By calling the somewhat arbitrary `update()` method on the sign after
setting the contents, the sign appears to correctly update again.

Fixes #772
2023-10-23 18:55:08 +02:00
Andreas Troelsen a79678430d Add `-Xlint:all` to compiler args.
Nice little flag to get all those juicy warnings out into the build
output for better visibility.
2023-10-23 18:35:54 +02:00
Andreas Troelsen 61550161a7 Remove unused class.
I had no idea this class existed, but it seems like it's actually never
been used for anything, since the commit that introduced it didn't even
use it either.
2023-10-23 00:30:55 +02:00
Andreas Troelsen bbe7ed491d Bulk up the test suite for FormulaManager.
Introduces a couple of tests for the FormulaManager test suite in order
to cover all the methods the class exposes. This means it is no longer
necessary to suppress the "unused" warnings.
2023-10-23 00:30:55 +02:00
Tad Hunt 58f1423a6e
Upgrade versions in GitHub Actions
Fixes a bunch of deprecation warnings.
2023-10-22 01:27:53 +02:00
Tad Hunt a8b2cf90e9
Fix scanner resource leak.
Wraps the Scanner in try-with-resources to ensure resource cleanup after
reading the config-file from the plugin jar.
2023-10-22 01:24:53 +02:00
Andreas Troelsen 15a79bdd7e Remove discrepancy in auto-ready behavior.
It's not clear why this variation in the auto-ready logic exists, and
the commit history doesn't seem to have any clues either. Perhaps the
actual readying up logic was incompatible with auto-ready at some point,
but at this point in time it doesn't seem like this is necessary at all,
and it appears to be causing a bug with the MobArenaStats extension.

By simply calling the player ready procedure regardless of the status of
the auto start timer, MobArena fires the arena player ready event that
MobArenaStats depends on for some of its pre-session bookkeeping. It
could be argued that MobArenaStats should be more robust, but we would
much rather fix the root problem than slack on the otherwise fairly
sound strictness of the MobArenaStats data model.

Fixes #746
2023-05-07 11:52:31 +02:00
Andreas Troelsen 4f78936716 Use boss entity as source for obsidian bomb.
Makes the boss entity the source of the obsidian bomb explosion, which
then makes the damage event listener handle the explosion damage as if
the boss is the damager, which means the `monster-infight` flag should
be respected.

Fixes #759
2023-05-07 11:29:36 +02:00
Andreas Troelsen 2eb1761e76 Use exploding sheep as source for explosion.
Makes the sheep entity the source of the explosion that's created when
it triggers close to a player. This, in turn, makes the damage event
listener handle the explosion damage as if the sheep is the damager,
which means it will respect the `monster-infight` flag.

Fixes #758
2023-05-07 11:28:13 +02:00
Andreas Troelsen 9871bb85d8 Bump version to 0.107.1. 2023-05-07 11:21:21 +02:00
48 changed files with 1913 additions and 934 deletions

View File

@ -1,35 +1,79 @@
name: build
on:
on:
workflow_dispatch:
push:
branches:
- '**'
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: 'Checkout source code'
uses: actions/checkout@v2
- name: 'Checkout'
uses: actions/checkout@v4
- name: 'Set up JDK'
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'adopt'
cache: 'gradle'
- name: 'Cache dependencies'
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: 'Build, test, and package'
run: mvn -B package --file pom.xml
- name: 'Build'
run: ./gradlew build --no-daemon
- name: 'Upload artifact'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: MobArena.jar
path: target/MobArena.jar
path: build/libs/MobArena-*.jar
- name: 'Output version'
id: version
run: |
version=$(
unzip -p build/libs/MobArena-*.jar plugin.yml \
| grep '^version: ' \
| awk '{printf $2}' \
| tr -d "'" \
)
echo "version=${version}" >> "${GITHUB_OUTPUT}"
draft:
needs: build
if: |
needs.build.result == 'success' &&
github.ref_name == 'master' &&
startsWith(github.event.head_commit.message, 'Release ') &&
!endsWith(needs.build.outputs.version, '-SNAPSHOT')
runs-on: ubuntu-latest
permissions:
contents: write
env:
VERSION: ${{ needs.build.outputs.version }}
steps:
- name: 'Checkout'
uses: actions/checkout@v4
- name: 'Download artifact'
uses: actions/download-artifact@v4
with:
name: MobArena.jar
- name: 'Extract release notes'
run: scripts/extract-release-notes -f github "${VERSION}" > release-notes.md
- name: 'Create release draft'
run: gh release create "${VERSION}" --draft --notes-file release-notes.md MobArena-*.jar
env:
GITHUB_TOKEN: ${{ github.token }}

View File

@ -0,0 +1,77 @@
name: publish-curseforge
on:
release:
types:
- 'released'
workflow_dispatch:
inputs:
tag_name:
description: 'The tag name of the release to publish'
required: true
type: string
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
env:
TAG_NAME: ${{ github.event.release.tag_name || inputs.tag_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download release assets
run: gh release download "${TAG_NAME}"
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Publish to CurseForge
run: |
echo 'Extract release notes'
changelog=$(scripts/extract-release-notes -f curse "${TAG_NAME}")
echo 'Look up game version IDs'
game_version_type_id=1
game_version_names='"1.20","1.19","1.18","1.17","1.16","1.15","1.14","1.13"'
type_condition="(.gameVersionTypeID == ${game_version_type_id})"
name_condition="(.name | startswith(${game_version_names}))"
game_version_ids=$(
curl -s -X GET 'https://minecraft.curseforge.com/api/game/versions' \
-H "X-Api-Token: ${{ secrets.CURSEFORGE_TOKEN }}" \
| jq -c ".[] | select(${type_condition} and ${name_condition}) | .id" \
| paste -sd, - \
)
echo 'Create metadata file'
cat << EOF > metadata.jq
{
changelog: \$changelog,
changelogType: "html",
displayName: \$displayName,
gameVersions: \$gameVersions,
releaseType: "beta"
}
EOF
jq -c -n \
--arg changelog "${changelog}" \
--arg displayName "MobArena v${TAG_NAME}" \
--argjson gameVersions "[${game_version_ids}]" \
-f metadata.jq \
> metadata.json
echo 'Publish build to CurseForge'
base_url='https://minecraft.curseforge.com'
project_id=31265
curl -s -X POST "${base_url}/api/projects/${project_id}/upload-file" \
-H "X-Api-Token: ${{ secrets.CURSEFORGE_TOKEN }}" \
-F 'metadata=<metadata.json' \
-F "file=@MobArena-${TAG_NAME}.jar"

79
.github/workflows/publish-hangar.yml vendored Normal file
View File

@ -0,0 +1,79 @@
name: publish-hangar
on:
release:
types:
- 'released'
workflow_dispatch:
inputs:
tag_name:
description: 'The tag name of the release to publish'
required: true
type: string
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
env:
TAG_NAME: ${{ github.event.release.tag_name || inputs.tag_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download release assets
run: gh release download "${TAG_NAME}"
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Publish to Hangar
run: |
echo 'Extract release notes'
changelog=$(scripts/extract-release-notes -f hangar "${TAG_NAME}")
echo 'Create version upload file'
cat << EOF > version-upload.jq
{
version: \$version,
channel: "Release",
description: \$changelog,
platformDependencies: {
"PAPER": [
"1.13.x",
"1.14.x",
"1.15.x",
"1.16.x",
"1.17.x",
"1.18.x",
"1.19.x",
"1.20.x"
]
},
pluginDependencies: {},
files: [
{ platforms: ["PAPER"] }
]
}
EOF
jq -c -n \
--arg version "${TAG_NAME}" \
--arg changelog "${changelog}" \
-f version-upload.jq \
> version-upload.json
echo 'Authenticate with Hangar'
base_url='https://hangar.papermc.io/api/v1'
key=${{ secrets.HANGAR_TOKEN }}
jwt=$(curl -s -X POST "${base_url}/authenticate?apiKey=${key}" | jq -r '.token')
echo 'Publish build to Hangar'
project_slug='MobArena'
curl -s -X POST "${base_url}/projects/${project_slug}/upload" \
-H "Authorization: ${jwt}" \
-F 'versionUpload=<version-upload.json;type=application/json' \
-F "files=@MobArena-${TAG_NAME}.jar"

9
.gitignore vendored
View File

@ -1,12 +1,9 @@
# Java
*.class
# Maven
target/
# Artifacts
*.jar
*.zip
# Gradle
.gradle/
build/
# IntelliJ
.idea/

View File

@ -1,117 +0,0 @@
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

Binary file not shown.

View File

@ -1,2 +0,0 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

View File

@ -11,6 +11,7 @@ one of these two sites:
- [Bukkit](https://dev.bukkit.org/projects/mobarena)
- [Spigot](https://www.spigotmc.org/resources/34110/)
- [Hangar](https://hangar.papermc.io/garbagemule/MobArena)
The wiki here on Github should have all the information you need to get
started using the plugin.

68
build.gradle.kts Normal file
View File

@ -0,0 +1,68 @@
plugins {
id("java-library")
id("com.github.johnrengelman.shadow") version "8.1.1"
}
group = "com.garbagemule"
version = "0.108"
repositories {
mavenLocal()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://jitpack.io")
maven("https://repo.maven.apache.org/maven2/")
}
dependencies {
compileOnly("org.spigotmc:spigot-api:1.19-R0.1-SNAPSHOT")
compileOnly("com.github.MilkBowl:VaultAPI:1.7.1")
api("org.bstats:bstats-bukkit:2.2.1")
testImplementation("junit:junit:4.13.2")
testImplementation("org.hamcrest:hamcrest-all:1.3")
testImplementation("org.mockito:mockito-core:3.12.4")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(8))
}
}
sourceSets {
test {
// This is a bit of a hack. It ensures that the dependencies for the
// "main" source set are available to the test suite. Without it, the
// dependencies would have to be listed twice, and even that doesn't
// seem to work with the currently used version of Mockito.
configurations.testImplementation.configure {
extendsFrom(configurations.compileOnly.get())
}
}
}
tasks {
processResources {
filesMatching("plugin.yml") {
expand("project" to mapOf(
"name" to "MobArena",
"version" to version,
))
}
}
shadowJar {
minimize()
relocate("org.bstats", "com.garbagemule.MobArena.metrics")
archiveBaseName = "MobArena"
archiveClassifier = ""
}
// We're using shadowJar, so we can skip the regular jar task.
jar { enabled = false }
// Let the build task produce the final artifact.
build { dependsOn(shadowJar) }
}

View File

@ -12,6 +12,20 @@ These changes will (most likely) be included in the next version.
## [Unreleased]
## [0.108] - 2024-01-01
### Added
- Support for chest references in item syntax. The new `inv` syntax allows for referencing container indices in the config-file. This should help bridge the gap between class chests and various other parts of the config-file, such as rewards and upgrade waves.
- Support for saved items. The new `/ma save-item` command can be used to save the currently held item to disk, which allows it to be used in various places in the config-file. This should help bridge the gap between the config-file and class chests for config-file centric setups.
- New per-arena setting `monster-teleporting` allows monsters to teleport _inside_ the arena region. This should allow for stuff like `/tp` commands and for Endermen to "do their thing".
- New permission `mobarena.admin.errors` for better error visibility. Players with this permission will get a message if an arena encounters an error. Currently, the only such error is the one resulting from Spigot's "max max health" setting throwing an exception when monster health is set "too high".
### Fixed
- Explosion damage caused by Exploding Sheep now correctly counts as monster damage. This means that the explosions only affect other mobs if the per-arena setting `monster-infight` is set to `true`.
- Explosion damage caused by the boss ability `obsidian-bomb` now correctly counts as monster damage. This means that the explosions only affect other mobs if the per-arena setting `monster-infight` is set to `true`.
- An old discrepancy with auto start timers in the auto-ready logic has been removed. This fixes an issue in MobArenaStats where the extension would throw errors in arenas with `auto-ready: true` and a non-zero auto start timer. Note that the combination of `auto-ready: true` and a `default-class` now _requires_ the use of a `start-delay-timer` to prevent the arena from starting immediately when the first player joins.
- Signs in arena regions, as well as Arena Signs anywhere, can no longer be edited by right-clicking.
- Signs now correctly restore themselves again in arenas with `soft-restore: true`.
## [0.107] - 2022-07-30
### Added
- New monster variant `angry-bees` can be used to spawn angry bees.
@ -247,7 +261,8 @@ Thanks to:
- Swatacular for help with testing bug fixes
- Haileykins for contributions to the code base
[Unreleased]: https://github.com/garbagemule/MobArena/compare/0.107...HEAD
[Unreleased]: https://github.com/garbagemule/MobArena/compare/0.108...HEAD
[0.108]: https://github.com/garbagemule/MobArena/compare/0.107...0.108
[0.107]: https://github.com/garbagemule/MobArena/compare/0.106...0.107
[0.106]: https://github.com/garbagemule/MobArena/compare/0.105...0.106
[0.105]: https://github.com/garbagemule/MobArena/compare/0.104.2...0.105

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
gradlew vendored Executable file
View File

@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
gradlew.bat vendored Normal file
View File

@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

310
mvnw vendored
View File

@ -1,310 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

182
mvnw.cmd vendored
View File

@ -1,182 +0,0 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

136
pom.xml
View File

@ -1,136 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.garbagemule</groupId>
<artifactId>mobarena</artifactId>
<name>MobArena</name>
<version>0.107</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<!-- Use Java 8 for compilation -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- Rename the final jar-file to MobArena.jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<finalName>MobArena</finalName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<minimizeJar>true</minimizeJar>
<relocations>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>com.garbagemule.MobArena.metrics</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<!-- Resource filtering; use Maven version in plugin.yml -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<!-- Spigot repo -->
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<!-- JitPack for Vault -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.19-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Vault -->
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7.1</version>
<scope>provided</scope>
</dependency>
<!-- bStats -->
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>2.2.1</version>
<scope>compile</scope>
</dependency>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- Hamcrest matchers -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

235
scripts/extract-release-notes Executable file
View File

@ -0,0 +1,235 @@
#!/usr/bin/env python3
import argparse
import os
import re
import sys
VERSION_PREFIX = '## '
SECTION_PREFIX = '### '
LIST_ITEM_PREFIX = '- '
def main():
args = parse_args()
lines = extract(args.version)
output(lines, args.format)
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
'version',
help='the version to extract release notes from the changelog for',
)
parser.add_argument(
'--format',
'-f',
choices=['github', 'hangar', 'spigot', 'curse'],
help='the format to output the release notes in',
)
return parser.parse_args()
def extract(target):
filename = 'changelog.md'
if not os.path.isfile(filename):
filename = os.path.join('..', filename)
if not os.path.isfile(filename):
print('error: changelog.md not found!')
sys.exit(1)
lines = []
with open(filename) as changelog:
found = False
for entry in changelog:
if entry.startswith(VERSION_PREFIX):
if found:
break
i = entry.find('[') + 1
j = entry.find(']')
version = entry[i:j]
if version == target:
if version[0].isdigit():
version = f'v{version}'
lines.append(f'{VERSION_PREFIX}{version}')
lines.append('')
found = True
continue
if not found:
continue
lines.append(entry.strip())
if not found:
print(f'error: version {target} not found!')
sys.exit(1)
return lines
def output(lines, fmt):
if fmt == 'github':
output_as_github_markdown(lines)
elif fmt == 'hangar':
output_as_hangar_markdown(lines)
elif fmt == 'spigot':
output_as_spigot_bbcode(lines)
elif fmt == 'curse':
output_as_curseforge_html(lines)
else:
output_raw(lines)
def output_as_github_markdown(lines):
"""
GitHub Releases Markdown is printed as the raw output from the changelog
except for the version header (the first line), because the version number
is already used as the release title, so we don't want it to appear twice.
"""
output_raw(lines[1:])
def output_as_hangar_markdown(lines):
"""
Hangar Versions use Markdown in the same format as GitHub Releases, so we
don't actually need to do anything else here either. Just strip the first
line so we don't get a duplicate header.
"""
output_raw(lines[1:])
def output_as_spigot_bbcode(lines):
"""
Spigot uses BBCode for resource update descriptions. It's very similar to
regular HTML, which makes it fairly easy to convert from Markdown. We just
need to use a [FONT] tag with Courier New for code bits.
"""
listing = False
for line in lines:
line = line.strip()
if line.startswith(VERSION_PREFIX):
i = len(VERSION_PREFIX)
version = line[i:]
print(f'[B]{version}[/B]')
continue
if line.startswith(SECTION_PREFIX):
if listing:
print('[/LIST]')
listing = False
i = len(SECTION_PREFIX)
section = line[i:]
print(f'[B]{section}:[/B]')
continue
if line.startswith(LIST_ITEM_PREFIX):
if not listing:
print('[LIST]')
listing = True
i = len(LIST_ITEM_PREFIX)
item = line[i:]
# Replace **bold** text
item = re.sub(r'\*\*(.*?)\*\*', r'[B]\1[/B]', item)
# Replace _italic_ text
item = re.sub(r'_(.*?)_', r'[I]\1[/I]', item)
# Replace `code` text
item = re.sub(r'`(.*?)`', r'[FONT=Courier New]\1[/FONT]', item)
# Replace [links](url)
item = re.sub(r'\[([^\]]+)]\(([^)]+)\)', r'[URL=\2]\1[/URL]', item)
print(f'[*]{item}')
continue
if len(line) > 0:
print(line)
if listing:
print('[/LIST]')
def output_as_curseforge_html(lines):
"""
CurseForge uses regular HTML for file update descriptions, which makes it
fairly easy to convert from Markdown. Angled brackets need to be replaced
with their HTML entity equivalents, but other than that it's very similar
to the Spigot BBCode conversion.
"""
listing = False
for line in lines:
line = line.strip()
if line.startswith(VERSION_PREFIX):
i = len(VERSION_PREFIX)
version = line[i:]
print(f'<p><strong>{version}</strong></p>')
continue
if line.startswith(SECTION_PREFIX):
if listing:
print('</ul>')
listing = False
i = len(SECTION_PREFIX)
section = line[i:]
print(f'<p><strong>{section}:</strong></p>')
continue
if line.startswith(LIST_ITEM_PREFIX):
if not listing:
print('<ul>')
listing = True
i = len(LIST_ITEM_PREFIX)
item = line[i:]
# Replace angled brackets
item = item.replace('<', '&lt;')
item = item.replace('>', '&gt;')
# Replace **bold** text
item = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', item)
# Replace _italic_ text
item = re.sub(r'_(.*?)_', r'<emph>\1</emph>', item)
# Replace `code` text
item = re.sub(r'`(.*?)`', r'<code>\1</code>', item)
# Replace [links](url)
item = re.sub(r'\[([^\]]+)]\(([^)]+)\)', r'<a href="\2">\1</a>', item)
print(f'<li>{item}</li>')
continue
if len(line) > 0:
print(line)
if listing:
print('</ul>')
def output_raw(lines):
[print(line.strip()) for line in lines]
if __name__ == '__main__':
main()

1
settings.gradle.kts Normal file
View File

@ -0,0 +1 @@
rootProject.name = "mobarena"

View File

@ -1310,11 +1310,7 @@ public class ArenaImpl implements Arena
private void autoReady(Player p) {
if (settings.getBoolean("auto-ready", false)) {
if (autoStartTimer.getRemaining() <= 0) {
playerReady(p);
} else {
readyPlayers.add(p);
}
playerReady(p);
}
}

View File

@ -44,6 +44,7 @@ import org.bukkit.entity.TNTPrimed;
import org.bukkit.entity.ThrownPotion;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.Event.Result;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockBurnEvent;
import org.bukkit.event.block.BlockEvent;
@ -110,19 +111,19 @@ public class ArenaListener
private MonsterManager monsters;
private ClassLimitManager classLimits;
private boolean softRestore,
softRestoreDrops,
protect;
private boolean monsterExp,
monsterInfight,
pvpOn, // pvp-enabled in config
pvpEnabled = false, // activated on first wave
foodRegen,
lockFoodLevel,
useClassChests;
private boolean allowTeleport,
canShare,
autoIgniteTNT;
private boolean softRestore;
private boolean softRestoreDrops;
private boolean protect;
private boolean monsterExp;
private boolean monsterInfight;
private boolean pvpEnabled;
private boolean foodRegen;
private boolean lockFoodLevel;
private boolean useClassChests;
private boolean allowTeleport;
private boolean monsterTeleport;
private boolean canShare;
private boolean autoIgniteTNT;
private int autoIgniteFuse;
private Set<Player> banned;
@ -141,10 +142,11 @@ public class ArenaListener
this.protect = s.getBoolean("protect", true);
this.monsterExp = s.getBoolean("monster-exp", false);
this.monsterInfight = s.getBoolean("monster-infight", false);
this.pvpOn = s.getBoolean("pvp-enabled", false);
this.pvpEnabled = s.getBoolean("pvp-enabled", false);
this.foodRegen = s.getBoolean("food-regen", false);
this.lockFoodLevel = s.getBoolean("lock-food-level", true);
this.allowTeleport = s.getBoolean("allow-teleporting", false);
this.monsterTeleport = s.getBoolean("monster-teleporting", false);
this.canShare = s.getBoolean("share-items-in-arena", true);
this.autoIgniteTNT = s.getBoolean("auto-ignite-tnt", false);
this.autoIgniteFuse = s.getInt("auto-ignite-fuse", 80);
@ -160,16 +162,6 @@ public class ArenaListener
);
}
void pvpActivate() {
if (arena.isRunning() && !arena.getPlayersInArena().isEmpty()) {
pvpEnabled = pvpOn;
}
}
void pvpDeactivate() {
if (pvpOn) pvpEnabled = false;
}
public void onBlockBreak(BlockBreakEvent event) {
// Check if the block is a sign, it might be a leaderboard
if (event.getBlock() instanceof Sign) {
@ -721,11 +713,15 @@ public class ArenaListener
return;
}
// Cancel PvP damage if disabled
if (!pvpEnabled && damager instanceof Player && !damager.equals(player)) {
event.setCancelled(true);
return;
// If this is player damage (and not self-inflicted), handle PvP
if (damager instanceof Player && !damager.equals(player)) {
// PvP must be enabled, and the first wave must have spawned
if (!pvpEnabled || arena.getWaveManager().getWaveNumber() == 0) {
event.setCancelled(true);
return;
}
}
event.setCancelled(false);
arena.getArenaPlayer(player).getStats().add("dmgTaken", event.getDamage());
@ -801,7 +797,7 @@ public class ArenaListener
return;
}
if (!pvpEnabled) {
if (!pvpEnabled || arena.getWaveManager().getWaveNumber() == 0) {
event.setCancelled(true);
}
}
@ -899,6 +895,14 @@ public class ArenaListener
if (monsters.hasPet(event.getEntity()) && region.contains(event.getTo())) {
return;
}
if (isArenaMonster(event.getEntity())) {
if (!monsterTeleport || !region.contains(event.getTo())) {
event.setCancelled(true);
}
return;
}
if (region.contains(event.getFrom()) || region.contains(event.getTo())) {
event.setCancelled(true);
}
@ -912,7 +916,7 @@ public class ArenaListener
if (potion.getShooter() instanceof Player) {
// Check for PvP stuff if the shooter is a player
if (!pvpEnabled) {
if (!pvpEnabled || arena.getWaveManager().getWaveNumber() == 0) {
// If a potion has harmful effects, remove all players.
for (PotionEffect effect : potion.getEffects()) {
PotionEffectType type = effect.getType();
@ -1089,26 +1093,58 @@ public class ArenaListener
}
public void onPlayerInteract(PlayerInteractEvent event) {
Player p = event.getPlayer();
if (!arena.inLobby(p)) return;
if (arena.inLobby(event.getPlayer())) {
onLobbyPlayerInteract(event);
} else {
onNonLobbyPlayerInteract(event);
}
}
// Prevent placing blocks and using held items
private void onLobbyPlayerInteract(PlayerInteractEvent event) {
if (event.hasItem()) {
event.setUseItemInHand(Result.DENY);
}
// Bail if off-hand or if there's no block involved.
if (event.getHand() == EquipmentSlot.OFF_HAND || !event.hasBlock())
if (event.getHand() == EquipmentSlot.OFF_HAND) {
return;
// Iron block
if (event.getClickedBlock().getType() == Material.IRON_BLOCK) {
handleReadyBlock(p);
}
// Sign
else if (event.getClickedBlock().getState() instanceof Sign) {
Block block = event.getClickedBlock();
if (block == null) {
return;
}
if (block.getType() == Material.IRON_BLOCK) {
handleReadyBlock(event.getPlayer());
} else if (block.getState() instanceof Sign) {
if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
event.setCancelled(true);
}
Sign sign = (Sign) event.getClickedBlock().getState();
handleSign(sign, p);
handleSign(sign, event.getPlayer());
}
}
private void onNonLobbyPlayerInteract(PlayerInteractEvent event) {
if (!protect) {
return;
}
Block block = event.getClickedBlock();
if (block == null) {
return;
}
if (!region.contains(block.getLocation())) {
return;
}
if (arena.inEditMode()) {
return;
}
if (block.getState() instanceof Sign) {
if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
event.setCancelled(true);
}
}
}

View File

@ -91,10 +91,7 @@ public class MASpawnThread implements Runnable
}
int delay = arena.getSettings().getInt("first-wave-delay", 5) * 20;
task = Bukkit.getScheduler().runTaskLater(plugin, () -> {
arena.getEventListener().pvpActivate();
this.run();
}, delay);
task = Bukkit.getScheduler().runTaskLater(plugin, this, delay);
}
public void stop() {
@ -103,8 +100,6 @@ public class MASpawnThread implements Runnable
return;
}
arena.getEventListener().pvpDeactivate();
task.cancel();
task = null;
}
@ -244,6 +239,11 @@ public class MASpawnThread implements Runnable
} else {
e.setCustomName("SPIGOT ERROR");
}
for (Player p : plugin.getServer().getOnlinePlayers()) {
if (p.hasPermission("mobarena.admin.errors")) {
arena.getMessenger().tell(p, "Failed to set boss health (" + health + ") in arena " + arena.configName() + " (wave " + wave + ") because Spigot 'maxHealth' is too low. See console for details.");
}
}
}
// Switch on the type.

View File

@ -8,6 +8,7 @@ import com.garbagemule.MobArena.formula.FormulaMacros;
import com.garbagemule.MobArena.formula.FormulaManager;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
import com.garbagemule.MobArena.items.SavedItemsManager;
import com.garbagemule.MobArena.listeners.MAGlobalListener;
import com.garbagemule.MobArena.metrics.ArenaCountChart;
import com.garbagemule.MobArena.metrics.ClassChestsChart;
@ -67,6 +68,8 @@ public class MobArena extends JavaPlugin
private FormulaManager formman;
private FormulaMacros macros;
private SavedItemsManager itemman;
private SignListeners signListeners;
@Override
@ -106,6 +109,7 @@ public class MobArena extends JavaPlugin
try {
createDataFolder();
setupFormulaMacros();
setupSavedItemsManager();
setupArenaMaster();
setupCommandHandler();
@ -134,6 +138,10 @@ public class MobArena extends JavaPlugin
macros = FormulaMacros.create(this);
}
private void setupSavedItemsManager() {
itemman = new SavedItemsManager(this);
}
private void setupArenaMaster() {
arenaMaster = new ArenaMasterImpl(this);
}
@ -190,6 +198,7 @@ public class MobArena extends JavaPlugin
reloadConfig();
reloadGlobalMessenger();
reloadFormulaMacros();
reloadSavedItemsManager();
reloadArenaMaster();
reloadAnnouncementsFile();
reloadSigns();
@ -226,6 +235,10 @@ public class MobArena extends JavaPlugin
}
}
private void reloadSavedItemsManager() {
itemman.reload();
}
private void reloadArenaMaster() {
arenaMaster.getArenas().forEach(Arena::forceEnd);
arenaMaster.initialize();
@ -317,4 +330,8 @@ public class MobArena extends JavaPlugin
public FormulaMacros getFormulaMacros() {
return macros;
}
public SavedItemsManager getSavedItemsManager() {
return itemman;
}
}

View File

@ -15,12 +15,15 @@ import com.garbagemule.MobArena.commands.setup.AutoGenerateCommand;
import com.garbagemule.MobArena.commands.setup.CheckDataCommand;
import com.garbagemule.MobArena.commands.setup.CheckSpawnsCommand;
import com.garbagemule.MobArena.commands.setup.ClassChestCommand;
import com.garbagemule.MobArena.commands.setup.DeleteItemCommand;
import com.garbagemule.MobArena.commands.setup.EditArenaCommand;
import com.garbagemule.MobArena.commands.setup.ListClassesCommand;
import com.garbagemule.MobArena.commands.setup.LoadItemCommand;
import com.garbagemule.MobArena.commands.setup.RemoveArenaCommand;
import com.garbagemule.MobArena.commands.setup.RemoveContainerCommand;
import com.garbagemule.MobArena.commands.setup.RemoveLeaderboardCommand;
import com.garbagemule.MobArena.commands.setup.RemoveSpawnpointCommand;
import com.garbagemule.MobArena.commands.setup.SaveItemCommand;
import com.garbagemule.MobArena.commands.setup.SettingCommand;
import com.garbagemule.MobArena.commands.setup.SetupCommand;
import com.garbagemule.MobArena.commands.user.ArenaListCommand;
@ -340,6 +343,10 @@ public class CommandHandler implements CommandExecutor, TabCompleter
register(RemoveLeaderboardCommand.class);
register(AutoGenerateCommand.class);
register(SaveItemCommand.class);
register(DeleteItemCommand.class);
register(LoadItemCommand.class);
}
/**

View File

@ -0,0 +1,60 @@
package com.garbagemule.MobArena.commands.setup;
import com.garbagemule.MobArena.commands.Command;
import com.garbagemule.MobArena.commands.CommandInfo;
import com.garbagemule.MobArena.framework.ArenaMaster;
import com.garbagemule.MobArena.items.SavedItemsManager;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@CommandInfo(
name = "delete-item",
pattern = "delete(-)?item",
usage = "/ma delete-item <identifier>",
desc = "delete the item with the given identifier",
permission = "mobarena.setup.deleteitem"
)
public class DeleteItemCommand implements Command {
@Override
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
if (args.length != 1) {
return false;
}
String key = args[0];
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
try {
items.deleteItem(key);
} catch (Exception e) {
am.getGlobalMessenger().tell(sender, "Couldn't delete " + ChatColor.YELLOW + key + ChatColor.RESET + ", because: " + ChatColor.RED + e.getMessage());
return true;
}
am.getGlobalMessenger().tell(sender, "Saved item " + ChatColor.YELLOW + key + ChatColor.RESET + " deleted.");
return true;
}
@Override
public List<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
List<String> keys = items.getKeys();
return keys.stream()
.filter(key -> key.startsWith(prefix))
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,70 @@
package com.garbagemule.MobArena.commands.setup;
import com.garbagemule.MobArena.Msg;
import com.garbagemule.MobArena.commands.Command;
import com.garbagemule.MobArena.commands.CommandInfo;
import com.garbagemule.MobArena.commands.Commands;
import com.garbagemule.MobArena.framework.ArenaMaster;
import com.garbagemule.MobArena.items.SavedItemsManager;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@CommandInfo(
name = "load-item",
pattern = "load(-)?item",
usage = "/ma load-item <identifier>",
desc = "load the item saved by the given identifier into your hand",
permission = "mobarena.setup.loaditem"
)
public class LoadItemCommand implements Command {
@Override
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
if (!Commands.isPlayer(sender)) {
am.getGlobalMessenger().tell(sender, Msg.MISC_NOT_FROM_CONSOLE);
return true;
}
if (args.length != 1) {
return false;
}
String key = args[0];
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
ItemStack stack = items.getItem(key);
if (stack == null) {
am.getGlobalMessenger().tell(sender, "No saved item with identifier " + ChatColor.YELLOW + key + ChatColor.RESET + " found.");
return true;
}
Player player = Commands.unwrap(sender);
player.getInventory().setItemInMainHand(stack);
am.getGlobalMessenger().tell(sender, "Saved item " + ChatColor.YELLOW + key + ChatColor.RESET + " loaded.");
return true;
}
@Override
public List<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
List<String> keys = items.getKeys();
return keys.stream()
.filter(key -> key.startsWith(prefix))
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,80 @@
package com.garbagemule.MobArena.commands.setup;
import com.garbagemule.MobArena.Msg;
import com.garbagemule.MobArena.commands.Command;
import com.garbagemule.MobArena.commands.CommandInfo;
import com.garbagemule.MobArena.commands.Commands;
import com.garbagemule.MobArena.framework.ArenaMaster;
import com.garbagemule.MobArena.items.SavedItemsManager;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@CommandInfo(
name = "save-item",
pattern = "save(-)?item",
usage = "/ma save-item <identifier>",
desc = "save the currently held item for use in the config-file",
permission = "mobarena.setup.saveitem"
)
public class SaveItemCommand implements Command {
@Override
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
if (!Commands.isPlayer(sender)) {
am.getGlobalMessenger().tell(sender, Msg.MISC_NOT_FROM_CONSOLE);
return true;
}
if (args.length != 1) {
return false;
}
String key = args[0];
if (!key.matches("^[\\p{IsAlphabetic}\\d_]+$")) {
am.getGlobalMessenger().tell(sender, "The identifier must contain only letters, numbers, and underscores");
return true;
}
Player player = Commands.unwrap(sender);
ItemStack stack = player.getInventory().getItemInMainHand();
if (stack.getType() == Material.AIR) {
am.getGlobalMessenger().tell(sender, "You must be holding an item.");
return true;
}
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
try {
items.saveItem(key, stack);
} catch (Exception e) {
am.getGlobalMessenger().tell(sender, "Couldn't save " + ChatColor.YELLOW + key + ChatColor.RESET + ", because: " + ChatColor.RED + e.getMessage());
return true;
}
am.getGlobalMessenger().tell(sender, "Item saved as " + ChatColor.YELLOW + key + ChatColor.RESET + ".");
return true;
}
@Override
public List<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
List<String> keys = items.getKeys();
return keys.stream()
.filter(key -> key.startsWith(prefix))
.collect(Collectors.toList());
}
}

View File

@ -1,7 +1,8 @@
package com.garbagemule.MobArena.formula;
import java.util.function.BiFunction;
@FunctionalInterface
public interface BinaryOperation extends BiFunction<Double, Double, Double> {
public interface BinaryOperation {
double apply(double left, double right);
}

View File

@ -1,7 +1,8 @@
package com.garbagemule.MobArena.formula;
import java.util.function.Function;
@FunctionalInterface
public interface UnaryOperation extends Function<Double, Double> {
public interface UnaryOperation {
double apply(double value);
}

View File

@ -0,0 +1,117 @@
package com.garbagemule.MobArena.items;
import com.garbagemule.MobArena.MobArena;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class SavedItemsManager {
private static final String FOLDER_NAME = "items";
private static final String FILE_EXT = ".yml";
private static final String YAML_KEY = "item";
private final MobArena plugin;
private final Path folder;
private final Map<String, ItemStack> items;
public SavedItemsManager(MobArena plugin) {
this.plugin = plugin;
this.folder = plugin.getDataFolder().toPath().resolve(FOLDER_NAME);
this.items = new HashMap<>();
}
public void reload() {
try {
Files.createDirectories(folder);
} catch (Exception e) {
throw new IllegalStateException("Failed to create items folder", e);
}
loadItems(folder);
}
private void loadItems(Path folder) {
items.clear();
try (Stream<Path> candidates = Files.list(folder)) {
candidates.forEach(this::loadItem);
} catch (IOException e) {
throw new IllegalStateException("Failed to load saved items", e);
}
if (!items.isEmpty()) {
plugin.getLogger().info("Loaded " + items.size() + " saved item(s).");
}
}
private void loadItem(Path path) {
if (!Files.isRegularFile(path)) {
return;
}
String filename = path.getFileName().toString();
if (!filename.endsWith(FILE_EXT)) {
return;
}
YamlConfiguration yaml = new YamlConfiguration();
try {
yaml.load(path.toFile());
} catch (Exception e) {
throw new IllegalStateException("Failed to load saved item from " + path, e);
}
ItemStack stack = yaml.getItemStack(YAML_KEY);
if (stack == null) {
throw new IllegalStateException("No item found in saved item file " + path);
}
String key = filename.substring(0, filename.length() - FILE_EXT.length());
items.put(key, stack);
}
public List<String> getKeys() {
return new ArrayList<>(items.keySet());
}
public ItemStack getItem(String key) {
ItemStack stack = items.get(key);
if (stack == null) {
return null;
}
return stack.clone();
}
public void saveItem(String key, ItemStack stack) throws IOException {
Path file = folder.resolve(key + FILE_EXT);
Files.deleteIfExists(file);
YamlConfiguration yaml = new YamlConfiguration();
yaml.set(YAML_KEY, stack);
yaml.save(file.toFile());
items.put(key, stack.clone());
}
public void deleteItem(String key) throws IOException {
items.remove(key);
Path file = folder.resolve(key + FILE_EXT);
try {
Files.delete(file);
} catch (NoSuchFileException e) {
throw new IllegalArgumentException("File " + file + " not found");
}
}
}

View File

@ -27,6 +27,7 @@ public class RepairableSign extends RepairableAttachable
s.setLine(1, lines[1]);
s.setLine(2, lines[2]);
s.setLine(3, lines[3]);
s.update();
}
}

View File

@ -41,6 +41,7 @@ class HandlesSignClicks implements Listener {
ArenaSign sign = signStore.findByLocation(block.getLocation());
if (sign != null) {
event.setCancelled(true);
purgeAndInvoke(sign, event.getPlayer());
}
}

View File

@ -0,0 +1,42 @@
package com.garbagemule.MobArena.things;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
class InventoryGroupThing extends InventoryThing {
InventoryGroupThing(Supplier<Location> location) {
super(location);
}
@Override
Thing load() {
Inventory inventory = super.getInventory();
if (inventory == null) {
return null;
}
List<Thing> things = new ArrayList<>();
for (ItemStack stack : inventory) {
if (stack != null && stack.getType() != Material.AIR) {
things.add(new ItemStackThing(stack));
}
}
if (things.isEmpty()) {
return null;
}
if (things.size() == 1) {
return things.get(0);
}
return new ThingGroup(things);
}
}

View File

@ -0,0 +1,40 @@
package com.garbagemule.MobArena.things;
import org.bukkit.Location;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import java.util.function.Supplier;
class InventoryIndexThing extends InventoryThing {
private final int index;
InventoryIndexThing(
Supplier<Location> location,
int index
) {
super(location);
this.index = index;
}
@Override
Thing load() {
Inventory inventory = super.getInventory();
if (inventory == null) {
return null;
}
if (inventory.getSize() <= index) {
return null;
}
ItemStack stack = inventory.getItem(index);
if (stack == null) {
return null;
}
ItemStack clone = stack.clone();
return new ItemStackThing(clone);
}
}

View File

@ -0,0 +1,56 @@
package com.garbagemule.MobArena.things;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
class InventoryRangeThing extends InventoryThing {
private final int first;
private final int last;
InventoryRangeThing(
Supplier<Location> location,
int first,
int last
) {
super(location);
this.first = first;
this.last = last;
}
@Override
Thing load() {
Inventory inventory = super.getInventory();
if (inventory == null) {
return null;
}
if (inventory.getSize() <= last) {
return null;
}
List<Thing> things = new ArrayList<>();
ItemStack[] content = inventory.getContents();
for (int i = first; i <= last; i++) {
ItemStack stack = content[i];
if (stack != null && stack.getType() != Material.AIR) {
things.add(new ItemStackThing(stack));
}
}
if (things.isEmpty()) {
return null;
}
if (things.size() == 1) {
return things.get(0);
}
return new ThingGroup(things);
}
}

View File

@ -0,0 +1,68 @@
package com.garbagemule.MobArena.things;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import java.util.function.Supplier;
abstract class InventoryThing implements Thing {
private final Supplier<Location> location;
protected InventoryThing(Supplier<Location> location) {
this.location = location;
}
@Override
public boolean giveTo(Player player) {
Thing thing = load();
if (thing == null) {
return false;
}
return thing.giveTo(player);
}
@Override
public boolean takeFrom(Player player) {
Thing thing = load();
if (thing == null) {
return false;
}
return thing.takeFrom(player);
}
@Override
public boolean heldBy(Player player) {
Thing thing = load();
if (thing == null) {
return false;
}
return thing.heldBy(player);
}
abstract Thing load();
Inventory getInventory() {
Location location = this.location.get();
if (location != null) {
Block block = location.getBlock();
BlockState state = block.getState();
if (state instanceof InventoryHolder) {
InventoryHolder holder = (InventoryHolder) state;
return holder.getInventory();
}
}
return null;
}
@Override
public String toString() {
Thing thing = load();
return (thing != null) ? thing.toString() : "nothing";
}
}

View File

@ -0,0 +1,80 @@
package com.garbagemule.MobArena.things;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import java.util.function.Supplier;
class InventoryThingParser implements ThingParser {
private static final String PREFIX = "inv(";
private static final String SUFFIX = ")";
private final Server server;
InventoryThingParser(Server server) {
this.server = server;
}
@Override
public InventoryThing parse(String s) {
if (!s.startsWith(PREFIX) || !s.endsWith(SUFFIX)) {
return null;
}
// Trim prefix and suffix
int start = PREFIX.length();
int end = s.length() - SUFFIX.length();
String inner = s.substring(start, end);
// Split by whitespace to get all the parts
String[] parts = inner.split("\\s+");
if (parts.length != 5) {
throw new IllegalArgumentException("Expected format " + PREFIX + "world x y z slot" + SUFFIX + ", got: " + s);
}
// Extract location
String name = parts[0];
int x = Integer.parseInt(parts[1]);
int y = Integer.parseInt(parts[2]);
int z = Integer.parseInt(parts[3]);
Supplier<Location> location = () -> {
World world = server.getWorld(name);
return new Location(world, x, y ,z);
};
// Determine type by slot value
String slot = parts[4];
if (slot.equals("all")) {
return group(location);
}
if (slot.contains("-")) {
return range(location, slot);
}
return index(location, slot);
}
private InventoryThing group(Supplier<Location> location) {
return new InventoryGroupThing(location);
}
private InventoryThing range(Supplier<Location> location, String slot) {
String[] indices = slot.split("-");
if (indices.length != 2) {
throw new IllegalArgumentException("Expected range format (e.g. 0-8), got: " + slot);
}
int first = Integer.parseInt(indices[0]);
int last = Integer.parseInt(indices[1]);
if (last < first) {
throw new IllegalArgumentException("Range end is less than range start: " + slot);
}
return new InventoryRangeThing(location, first, last);
}
private InventoryThing index(Supplier<Location> location, String slot) {
int index = Integer.parseInt(slot);
return new InventoryIndexThing(location, index);
}
}

View File

@ -0,0 +1,60 @@
package com.garbagemule.MobArena.things;
import com.garbagemule.MobArena.MobArena;
import com.garbagemule.MobArena.items.SavedItemsManager;
import org.bukkit.inventory.ItemStack;
class SavedItemParser implements ItemStackParser {
private final MobArena plugin;
SavedItemParser(MobArena plugin) {
this.plugin = plugin;
}
@Override
public ItemStack parse(String s) {
// Note that the saved items manager is not available during
// the load procedure, so we need to defer the call to the
// getter until we actually need it. We don't really have to
// check for null here, but without it, we would need to set
// up a dummy manager for the ThingManager test suite, which
// is a bit of a hassle.
SavedItemsManager items = plugin.getSavedItemsManager();
if (items == null) {
return null;
}
if (s.contains(":")) {
return parseWithAmount(s, items);
} else {
return parseSimple(s, items);
}
}
private ItemStack parseWithAmount(String s, SavedItemsManager items) {
String[] parts = s.split(":");
if (parts.length > 2) {
return null;
}
ItemStack stack = parseSimple(parts[0], items);
if (stack == null) {
return null;
}
try {
int amount = Integer.parseInt(parts[1]);
stack.setAmount(amount);
} catch (NumberFormatException e) {
return null;
}
return stack;
}
private ItemStack parseSimple(String s, SavedItemsManager items) {
return items.getItem(s);
}
}

View File

@ -10,17 +10,15 @@ public class ThingManager implements ThingParser {
private final List<ThingParser> parsers;
private final ItemStackThingParser items;
public ThingManager(MobArena plugin, ItemStackThingParser parser) {
public ThingManager(MobArena plugin) {
parsers = new ArrayList<>();
parsers.add(new CommandThingParser());
parsers.add(new MoneyThingParser(plugin));
parsers.add(new PermissionThingParser(plugin));
parsers.add(new PotionEffectThingParser());
items = parser;
}
public ThingManager(MobArena plugin) {
this(plugin, new ItemStackThingParser());
parsers.add(new InventoryThingParser(plugin.getServer()));
items = new ItemStackThingParser();
items.register(new SavedItemParser(plugin));
}
/**

View File

@ -1,90 +0,0 @@
package com.garbagemule.MobArena.util;
import org.bukkit.Location;
import org.bukkit.World;
import java.io.Serializable;
/**
* NOTE: I (garbagemule) DID NOT WRITE THIS CLASS (notice the author below)
* @author creadri
*/
@SuppressWarnings("serial")
public class EntityPosition implements Serializable{
private double x;
private double y;
private double z;
private String world;
private float yaw;
private float pitch;
public EntityPosition(double x, double y, double z, String world, float yaw, float pitch) {
this.x = x;
this.y = y;
this.z = z;
this.world = world;
this.yaw = yaw;
this.pitch = pitch;
}
public EntityPosition(Location location) {
this.x = location.getX();
this.y = location.getY();
this.z = location.getZ();
this.world = location.getWorld().getName();
this.yaw = location.getYaw();
this.pitch = location.getPitch();
}
public Location getLocation(World world) {
return new Location(world, x, y, z, yaw, pitch);
}
public float getPitch() {
return pitch;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public String getWorld() {
return world;
}
public void setWorld(String world) {
this.world = world;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public float getYaw() {
return yaw;
}
public void setYaw(float yaw) {
this.yaw = yaw;
}
public double getZ() {
return z;
}
public void setZ(double z) {
this.z = z;
}
}

View File

@ -16,6 +16,7 @@ import java.text.DecimalFormatSymbols;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.Set;
@ -46,11 +47,15 @@ public class ConfigUtils
if (is == null) {
throw new IllegalStateException("Couldn't read " + res + " from jar, please re-install MobArena");
}
Scanner scanner = new Scanner(is).useDelimiter("\\A");
if (!scanner.hasNext()) {
String contents;
try (Scanner scanner = new Scanner(is)) {
scanner.useDelimiter("\\A");
contents = scanner.next();
} catch (NoSuchElementException e) {
throw new IllegalStateException("No content in " + res + " in jar, please re-install MobArena");
}
String contents = scanner.next();
YamlConfiguration yaml = new YamlConfiguration();
try {
yaml.loadFromString(contents);

View File

@ -74,7 +74,7 @@ public class SheepBouncer implements Runnable
// Create an explosion if there's a player amongst the nearby entities.
for (Entity entity : e.getNearbyEntities(2D, 2D, 2D)) {
if (entity instanceof Player) {
e.getWorld().createExplosion(e.getLocation(), 2f);
e.getWorld().createExplosion(e.getLocation(), 2f, false, true, e);
e.remove();
break;

View File

@ -55,7 +55,7 @@ public class ObsidianBomb implements Ability
return;
world.getBlockAt(loc).setType(Material.AIR);
world.createExplosion(loc, 3F);
world.createExplosion(loc, 3F, false, true, boss.getEntity());
}
}, FUSE);
}

View File

@ -47,6 +47,7 @@ permissions:
mobarena.admin.force: true
mobarena.admin.teleport: true
mobarena.admin.addreward: true
mobarena.admin.errors: true
mobarena.admin.enable:
description: Enable and disable MobArena and/or arenas.
default: false
@ -65,6 +66,9 @@ permissions:
mobarena.admin.addreward:
description: Add rewards to an arena player's reward list.
default: false
mobarena.admin.errors:
description: Get notified when an arena encounters errors.
default: false
mobarena.setup:
description: Gives access to all setup commands
@ -85,6 +89,9 @@ permissions:
mobarena.setup.leaderboards: true
mobarena.setup.autogenerate: true
mobarena.setup.autodegenerate: true
mobarena.setup.saveitem: true
mobarena.setup.deleteitem: true
mobarena.setup.loaditem: true
mobarena.setup.config:
description: Save or reload the config-file
default: false
@ -130,3 +137,12 @@ permissions:
mobarena.setup.autodegenerate:
description: Auto-degenerate an arena.
default: false
mobarena.setup.saveitem:
description: Store the currently held item as a saved item.
default: false
mobarena.setup.deleteitem:
description: Delete a saved item.
default: false
mobarena.setup.loaditem:
description: Set a saved item as the currently held item.
default: false

View File

@ -15,6 +15,7 @@ require-empty-inv-spec: false
pvp-enabled: false
monster-infight: false
allow-teleporting: false
monster-teleporting: false
spectate-on-death: true
auto-leave-on-end: false
share-items-in-arena: true

View File

@ -129,6 +129,32 @@ public class FormulaManagerIT {
}
/**
* With custom constants, we are manipulating the internal
* state of the manager, so we need to use a local subject.
*/
public static class CustomConstants {
FormulaManager subject;
@Before
public void setup() {
subject = FormulaManager.createDefault();
}
@Test
public void resolveRegisteredCustomConstant() {
subject.registerConstant("pie", 3.14);
Formula formula = subject.parse("pie * 2");
double result = formula.evaluate(arena);
double expected = 6.28;
assertThat(result, equalTo(expected));
}
}
@RunWith(Parameterized.class)
public static class DefaultVariables {
@ -204,8 +230,8 @@ public class FormulaManagerIT {
@Parameters(name = "{0} = {1}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][]{
{"1 + +1.2", 1 + +1.2},
{"1 + -1.2", 1 + -1.2},
{"1 + +1.2", 1 + 1.2},
{"1 + -1.2", 1 + (-1.2)},
});
}
@ -233,8 +259,8 @@ public class FormulaManagerIT {
@Parameters(name = "{0} = {1}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][]{
{"1+-2", 1 + -2},
{"3-+4", 3 - +4},
{"1+-2", 1 + (-2)},
{"3-+4", 3 - 4},
{"3*7.5", 3 * 7.5},
{"10/2.5", 10 / 2.5},
{"9%4", 9 % 4},
@ -262,6 +288,54 @@ public class FormulaManagerIT {
}
/**
* With custom operators, we are manipulating the internal
* state of the manager, so we need to use a local subject.
*/
public static class CustomOperators {
FormulaManager subject;
@Before
public void setup() {
subject = FormulaManager.createDefault();
}
@Test
public void resolveRegisteredCustomUnaryOperator() {
subject.registerUnaryOperator("_", 1, a -> a - 1);
Formula formula = subject.parse("_(1 + 1)");
double result = formula.evaluate(arena);
double expected = 1;
assertThat(result, equalTo(expected));
}
@Test
public void resolveRegisteredCustomBinaryOperator1() {
subject.registerBinaryOperator("?", 1, true, (a, b) -> (a >= 0) ? a : b);
Formula formula = subject.parse("5 ? 6");
double result = formula.evaluate(arena);
double expected = 5;
assertThat(result, equalTo(expected));
}
@Test
public void resolveRegisteredCustomBinaryOperator2() {
subject.registerBinaryOperator("?", 1, true, (a, b) -> (a >= 0) ? a : b);
Formula formula = subject.parse("(5 - 10) ? (3 + 3)");
double result = formula.evaluate(arena);
double expected = 6;
assertThat(result, equalTo(expected));
}
}
@RunWith(Parameterized.class)
public static class DefaultUnaryFunctions {

View File

@ -6,7 +6,7 @@ import org.hamcrest.TypeSafeMatcher;
import java.util.Objects;
public class LexemeMatcher extends TypeSafeMatcher<Lexeme> {
class LexemeMatcher extends TypeSafeMatcher<Lexeme> {
private final TokenType type;
private final String value;

View File

@ -0,0 +1,124 @@
package com.garbagemule.MobArena.things;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
import static org.junit.Assert.assertThrows;
public class InventoryThingParserTest {
private InventoryThingParser subject;
@Before
public void setup() {
subject = new InventoryThingParser(null);
}
@Test
public void returnsNullOnWrongType() {
String input = "dirt";
Thing result = subject.parse(input);
assertThat(result, nullValue());
}
@Test
public void returnsNullOnMismatchedParenthesis() {
String input = "inv(world -10 20 130 15";
Thing result = subject.parse(input);
assertThat(result, nullValue());
}
@Test
public void throwsOnTooFewArguments() {
String input = "inv(world -10 20 130)";
assertThrows(
IllegalArgumentException.class,
() -> subject.parse(input)
);
}
@Test
public void throwsOnTooManyArguments() {
String input = "inv(world -10 20 130 15 16)";
assertThrows(
IllegalArgumentException.class,
() -> subject.parse(input)
);
}
@Test
public void throwsOnUnknownSlotType() {
String input = "inv(world -10 20 130 potato)";
assertThrows(
IllegalArgumentException.class,
() -> subject.parse(input)
);
}
@Test
public void parsesIndexThing() {
String input = "inv(world -10 20 130 15)";
Thing result = subject.parse(input);
assertThat(result, instanceOf(InventoryIndexThing.class));
}
@Test
public void throwsIfIndexIsNegative() {
String input = "inv(world -10 20 130 -1)";
assertThrows(
IllegalArgumentException.class,
() -> subject.parse(input)
);
}
@Test
public void parsesRangeThing() {
String input = "inv(world -10 20 130 0-8)";
Thing result = subject.parse(input);
assertThat(result, instanceOf(InventoryRangeThing.class));
}
@Test
public void throwsIfRangeStartIsLessThanZero() {
String input = "inv(world -10 20 130 -1-8)";
assertThrows(
IllegalArgumentException.class,
() -> subject.parse(input)
);
}
@Test
public void throwsIfRangeEndIsLessThanRangeStart() {
String input = "inv(world -10 20 130 5-2)";
assertThrows(
IllegalArgumentException.class,
() -> subject.parse(input)
);
}
@Test
public void parsesAllGroup() {
String input = "inv(world -10 20 130 all)";
Thing result = subject.parse(input);
assertThat(result, instanceOf(InventoryGroupThing.class));
}
}