This commit reworks how MobArena interfaces with Vault economy provider
instances. Instead of affixing an Economy _instance_ to the main plugin
at "setup time", we create an instance of the new _Finance_ abstraction
at "(re)load time", and then use this new abstraction at every call site
(really just the MoneyThing) instead of Economy. This accomplishes two
important things:
1. Due to how the Bukkit load order works and how Vault providers are
registered with the plugin, the Finance abstraction gives us a means of
deferring the retrieval of the Economy provider needed to integrate with
the underlying economy plugin (or script). MobArena still uses Finance
as if it was a "complete" object and is completely unaware of what goes
on under the hodd. This is a much more elegant solution compared to the
terrible past proposals of fragmenting MobArena's bootstrapping phase,
especially considering the past proposals were server tick-centric...
2. It creates a simple and maintainable interface between MobArena and
Vault, effectively giving control back to MobArena as to how everything
should mesh together.
The biggest downside to this solution is that we no longer know if an
economy provider is available for when we need it at a convenient time
in the startup process. Instead, we need to react accordingly when an
economy operation is invoked. The implementation takes a somewhat mild
approach by simply _logging_ any issues encountered and defaulting to
"best effort" operations when an economy provider is not available. At
worst, this is a bunch of log spam and potentially a lot of work for
server owners to clean up after (compensating players for rewards and
stuff like that), but MobArena doesn't handle arbitrary exceptions in
its join/leave and start/end procedures very well, so it's probably a
better way to go about it than throwing exceptions.
If the server does not have Vault installed, an UnsupportedFinance is
created. This implementation just logs errors and "fails to work" on
every operation. If Vault is installed, a VaultFinance is created, and
this implementation should only log errors if a Vault economy provider
couldn't be found. We could have opted for a single implementation, but
this approach allows us to potentially support other economy registrars
in the future, so we (probably) won't have to make sweeping changes in
order to swap to something else.
Closes#797
This commit "fixes" the problem introduced in the "no sign edits" commit
by not invoking the sign actions for interact events where the player is
sneaking. This is not the ideal way to go about it, but since we need to
bump the Spigot API version to access the sign change event that would
fix the underlying issue, this will have to do for now.
Fixes#791
The previous change to this class introduced a _mutating_ method call,
which would be fine if it wasn't for the fact that the handler listened
on `MONITOR` priority prior to this commit. But no more! We're knocking
it down a notch to better comply with event handler best practices.
While this change doesn't _fix_ #787, it does allow @molor to implement
a hackaround that should keep things in check until we can bump up to a
later API version and use the new sign edit event to fix this particular
issue the right way.
Turns out this isn't necessary at all. I think the server used to throw
an exception if there were too many characters on a sign, but it doesn't
seem to be the case anymore (tested on 1.21 and 1.13.2).
This commit simply removes the truncation logic from the sign rendering
procedure, and it fixes issues where text that would otherwise fit on a
sign would be cut off, but more specifically one where color codes would
sort of "push" characters out of the sign despite not being visible.
Fixes#790
This commit changes the "source" of the explosions from `obsidian-bomb`
to _something_ that isn't the boss itself. Originally, these explosions
were block explosions, but without a "source" from the damage caused to
other mobs, the `monster-infight` flag was largely ignored. By switching
to "entity explosions" with the boss as the "source", the flag was now
respected, and mobs would no longer take damage from these explosions if
the `monster-infight` flag was set to `false`.
However, the handler for "entity explosions" now started reacting to the
explosions caused by the `obsidian-bomb` ability as if they were normal
entity explosions, e.g. those caused by creepers, and because entities
that explode usually "die" (although without an EntityDeathEvent), the
handler removes the entity from the monster manager, which effectively
"unregisters" the boss, leaving the entity itself just hanging around in
the arena as a normal mob (with a huge health pool).
The "obvious" solution is to change back to "block explosions", and that
could perhaps have worked if the API had consistent interplay between an
"explode event" and the resulting "damage event". It turns out that the
EntityExplodeEvent results in an EntityDamageByEntityEvent, and the same
can be said for BlockExplodeEvent and EntityDamageByBlockEvent, except
the latter will have a `null` block in it, so even if we were to attach
some metadata to the block, we wouldn't be able to retrieve it in the
damage handler. So we're sticking with "entity explosions"...
The hacky solution: We just spawn a new entity and pin the explosion on
it instead of the boss. That way the new entity is removed by the entity
explosion handler (which is a no-op, because the entity isn't an arena
monster) instead of the boss. It technically doesn't matter which entity
we spawn, but by spawning a primed TNT block, we can naturally stick a
source on it, and the damage handler will treat it as if the boss placed
a TNT block and detonated it, which is an apt analogy after all. Just to
make sure we don't forget the dual responsibility, we've added a little
comment in the damage handler.
An alternative solution would have been to spawn any other entity and
stick the boss entity ID on it, then look up the entity by that ID in
the damage handler if the damager had metadata on it. This is just a bit
more streamlined and likely a lot more performant.
Fixes#789
Introduces a means of "tracking" both the players and the boss itself
during the `shuffle-positions` ability for the sake of _not_ blocking
the teleport event(s) that could potentially be blocked otherwise.
Note that the change introduced in the PlayerTeleportEvent handler is
not strictly necessary. The final block of code _should_ always yield
the same result, as `shuffle-positions` _should_ be working solely on
positions that are all inside the arena region, but the out-of-bounds
check might be delayed, so we don't know for sure. It's just a safety
precaution, but it really shouldn't be necessary.
Fixes#792
This commit changes two things; it allows multiple single waves to have
the same wave number, and it makes the wave selection procedure pick
randomly between multiple valid matches.
Effectively, this means that MobArena now picks randomly when clashing
recurrent waves _also_ clash on priority (rather than deferring the
decision to the arbitrary but deterministic nature of sorted sets), and
it also picks randomly between multiple single waves with the same wave
number (which was not possible before).
This should allow server owners to add variety into their wave setups
while maintaining a great deal of control over the randomness involved.
For instance, it's now possible to have wave 10 be a boss wave, but the
_actual_ boss wave is randomly picked between three different waves all
with wave number 10. Upgrade waves could be randomized as well. As for
randomized recurrent waves, perhaps instead of having potentially four
different mobs in the same wave, a setup could instead have two _pairs_
of mobs that are randomly selected for a different kind of randomness.
Closes#795
This commit cleans up the old fake event show that was implemented when
the BlockExplodeEvent was introduced and caused all sorts of issues back
in the day. It should have been cleaned up years ago, but "if it ain't
broke"... Well, it is now! With this commit, however, we get rid of the
constructor invocation that throws up, but we obviously need to replace
it with something else to take care of the problem it used to fix.
The handler is close to a carbon copy of the EntityExplodeEvent handler,
simply because the code isn't very reusable due to direct method calls
on the event object itself. But because of the refactoring step of the
previous commit, we don't actually have to explicitly repeat the gnarly
"repairable" code that handles the block list, yay... We obviously don't
have an entity to check for (and remove from the monster manager), and
we need to dig a little deeper for the Location object needed for the
region check, but other than that, it's the same thing.
Fixes#796
Just a tiny little refactoring that makes reusing this logic a little
easier _without_ obfuscating the commit history for the code itself.
Just git things, really...
We'll want to reuse as much of the original method as possible for the
two explosion events, but because of the direct method calls, we can't
really reuse a good chunk of it. Still, this is better than nothing.
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.
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
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.
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.
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.
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.
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.
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.
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.
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
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.
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`.
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
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!
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
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.
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
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
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
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
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.
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.
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
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
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