Compare commits

..
13 Commits
Author SHA1 Message Date
Michael Burgess 344c9ca6ca Add leave-buffer-distance to tolerate knockback at region edges
build / build (push) Successful in 2m6s
build / draft (push) Skipped
2026-08-06 23:23:24 -04:00
Michael Burgess d1eb4191e9 Add Maven publishing for MobArenaStats' local build dependency
build / build (push) Successful in 2m7s
build / draft (push) Skipped
2026-08-06 22:40:43 -04:00
Michael Burgess 23698ced3e Fix Gitea artifact upload
build / build (push) Successful in 2m28s
build / draft (push) Skipped
2026-08-06 17:46:06 -04:00
Michael Burgess 0087a0da7f Fix Paper 26.2 explosion restoration stall
build / build (push) Failing after 14m15s
build / draft (push) Skipped
2026-08-06 15:28:36 -04:00
Andreas Troelsen 53219f19df Release 0.109. 2024-10-13 16:10:11 +02:00
Andreas Troelsen bafc17e1df Fix auto-leave-on-end: true error.
The ConcurrentModificationException happens because the player leave
procedure alters the `specPlayers` set, and this is the set we iterate
during the automatic spectator kicking procedure. By making a copy of
the set and iterating the copy, we circumvent this problem. Java 101,
really, but when the code is stateful and complex, it's "fair enough"
that it slipped through the cracks. It's also impossible to reproduce
without at least two players, so go figure...

Fixes #802
2024-10-06 15:47:50 +02:00
Andreas Troelsen b4db44985b Use Mob interface for on-spawn targeting.
Since 1.13, we've had the Mob interface injected between Creature and
LivingEntity, and it contains the targeting methods we use. Slimes were
never Creatures, but they _are_ Mobs, so by swapping this interface out,
we effectively patch up functionality that's been broken since the very
beginning. Hopefully server owners don't actually depend on this piece
of functionality being broken...
2024-10-06 14:56:09 +02:00
Andreas Troelsen 00b59ed7c4 Add per-arena min/max spawnpoint distance settings.
Introduces two new settings for controlling how far away from or close
to a spawnpoint players must be in order for the spawnpoint to be valid
when spawning monsters.

This is a pretty big one. In ancient times, MobArena had no limit on how
close players had to be to spawnpoints for them to be valid. The result
was mobs spawning too far away from the players, frozen in place until
their target approached them or until another player attacked them. This
was clearly undesirable, so the 15-block max distance was introduced to
solve this problem. And it worked, hurray!

In the meantime, it also imposed a really cumbersome limitation on all
arena designs, since aesthetically (or strategically) placed spawnpoints
were seldom sufficient, and server owners have been needing to litter
their arena floors with spawnpoints to avoid warnings and to get their
expected monster behavior.

Modern versions of Minecraft no longer exhibit that "frozen in place"
behavior the hardcoded max distance set out to solve, and server owners
have been asking for configurability on this front for years. With this
commit, that distance is now per-arena configurable. The change has no
real impact on the performance of the plugin. It's worth noting that we
don't modify the pathfinding attribute `generic.followRange`. We might
want to revisit this in a future commit, but since it can _definitely_
affect performance, we should have some actual servers run a test build
with it before jumping on that wagon.

Having many spawnpoints might still be preferable to some, but it comes
with another problem of players standing right on top of a spawnpoint
when a monster spawns. To combat this, an additional setting to control
the _minimum_ distance that _all_ players must be from the spawnpoint
for it to be valid is introduced as well. This means it is possible to
have lots of spawnpoints for a less predictable session, but without the
risk of being instantly attacked when a new wave spawns. This change has
a theoretical performance impact on the plugin, because it's a lot more
brute force without the early returns of the old algorithm. However, if
the setting is left at 0, the old algorithm is used.

The only real downside to these changes is that it's more code and more
settings to maintain. It doesn't improve on the clusterfuck that is the
arena settings in the config-file - we're just making things worse. I do
think it's worth having, though, since the bigger revamps on the drawing
board are at best months away, and at worst, they'll never happen. When
or if the time comes, it's probably better to rethink certain aspects of
the plugin instead of trying to convert everything gracefully.

Closes #412
2024-10-06 14:42:02 +02:00
Andreas Troelsen 78f5dc0545 Add tiers wave reward section.
This commit introduces a new optional wave rewards section, `tiers`,
which allows server owners to configure rewards that "upgrade" over
time. This makes it possible to create reward configurations where
(some of) the rewards don't stack as the waves progress, but instead
change into different rewards. The most obvious use case is wanting to
grant an armor set for beating a specific wave, but a _better_ set for
beating a later wave, without granting both sets.

Turns out this was a lot easier than I thought it would be. We didn't
even need a rework of anything, and the Things API didn't even need to
adapt in any way. As an added bonus, we managed to get the associated
GitHub issue wrapped up before it turned 8 years old. Neat!

Closes #346
2024-10-06 12:42:09 +02:00
Andreas Troelsen 0527f116e2 Refactor Vault economy integration.
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
2024-09-15 20:28:10 +02:00
Andreas Troelsen 9679f4b2f7 Remove Vault metric.
We might want to track which economy provider people are using, but
tracking just "uses Vault" is not particularly helpful.
2024-09-15 20:28:10 +02:00
Andreas Troelsen 7694872ebc Allow arena signs to be broken while sneaking.
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
2024-08-20 23:44:57 +02:00
Andreas Troelsen a33f0164b6 Fix arena sign event handler priority.
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.
2024-07-23 03:58:46 +02:00
44 changed files with 874 additions and 200 deletions
+78
View File
@@ -0,0 +1,78 @@
name: build
on:
workflow_dispatch:
push:
branches:
- '**'
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: 'Checkout'
uses: actions/checkout@v4
- name: 'Set up JDK'
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
- name: 'Build'
run: ./gradlew build --no-daemon
- name: 'Upload artifact'
uses: actions/upload-artifact@v3
with:
name: 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 }}
+3 -3
View File
@@ -18,10 +18,10 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: 'Set up JDK' - name: 'Set up JDK'
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '11' java-version: '25'
distribution: 'adopt' distribution: 'temurin'
cache: 'gradle' cache: 'gradle'
- name: 'Build' - name: 'Build'
+9 -3
View File
@@ -1,7 +1,13 @@
MobArena [![Build Status](https://github.com/garbagemule/MobArena/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/garbagemule/MobArena/actions/workflows/build.yml) MobArena — Paper 26.2 fork
======== ==========================
MobArena is an arena-style minigame for Spigot-based Minecraft servers This fork updates upstream MobArena 0.109 for **Paper 26.2** and **Java 25**. It removes the legacy Bukkit material access that could stall the server during arena explosions and updates APIs renamed in modern Paper.
Upstream project: https://github.com/garbagemule/MobArena
Build with `./gradlew build`. The deployable plugin is written to `build/libs/MobArena-0.109.1-tss3.jar`.
MobArena is an arena-style minigame for Paper-based Minecraft servers.
## Getting Started ## Getting Started
+46 -6
View File
@@ -1,31 +1,37 @@
plugins { plugins {
id("java-library") id("java-library")
id("com.github.johnrengelman.shadow") version "8.1.1" id("com.gradleup.shadow") version "9.6.1"
id("maven-publish")
} }
group = "com.garbagemule" group = "com.garbagemule"
version = "0.108.1-SNAPSHOT" version = "0.109.1-tss3"
repositories { repositories {
mavenLocal() mavenLocal()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") maven("https://repo.papermc.io/repository/maven-public/")
maven("https://jitpack.io") maven("https://jitpack.io")
maven("https://repo.maven.apache.org/maven2/") maven("https://repo.maven.apache.org/maven2/")
} }
dependencies { dependencies {
compileOnly("org.spigotmc:spigot-api:1.19-R0.1-SNAPSHOT") compileOnly("io.papermc.paper:paper-api:26.2.build.84-stable")
compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") compileOnly("com.github.MilkBowl:VaultAPI:1.7.1")
api("org.bstats:bstats-bukkit:2.2.1") api("org.bstats:bstats-bukkit:2.2.1")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
testImplementation("org.hamcrest:hamcrest-all:1.3") testImplementation("org.hamcrest:hamcrest-all:1.3")
testImplementation("org.mockito:mockito-core:3.12.4") testImplementation("org.mockito:mockito-core:5.23.0")
testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v26.1.2:4.113.2")
} }
// Tests use MockBukkit's exact Paper API build because 26.2 registries are not modeled yet.
configurations.matching { it.name.startsWith("test") }.configureEach {
resolutionStrategy.force("io.papermc.paper:paper-api:26.1.2.build.57-stable")
}
java { java {
toolchain { toolchain {
languageVersion.set(JavaLanguageVersion.of(8)) languageVersion.set(JavaLanguageVersion.of(25))
} }
} }
@@ -66,3 +72,37 @@ tasks {
// Let the build task produce the final artifact. // Let the build task produce the final artifact.
build { dependsOn(shadowJar) } build { dependsOn(shadowJar) }
} }
publishing {
publications {
create<MavenPublication>("shadow") {
groupId = "com.garbagemule"
artifactId = "MobArena"
version = project.version.toString()
artifact(tasks.shadowJar)
}
}
}
val checkNoLegacyMaterialApi = tasks.register("checkNoLegacyMaterialApi") {
group = "verification"
description = "Fails if legacy Bukkit material access returns to production sources."
val sources = fileTree("src/main/java") { include("**/*.java") }
inputs.files(sources)
doLast {
val violations = sources.files.flatMap { file ->
file.readLines().mapIndexedNotNull { index, line ->
if ("org.bukkit.material" in line || "state.getData()" in line) {
"${file.relativeTo(projectDir)}:${index + 1}"
} else null
}
}
check(violations.isEmpty()) {
"Legacy Bukkit material API usage found:\n${violations.joinToString("\n")}"
}
}
}
tasks.named("check") {
dependsOn(checkNoLegacyMaterialApi)
}
+24 -1
View File
@@ -11,15 +11,36 @@ These changes will (most likely) be included in the next version.
## [Unreleased] ## [Unreleased]
## [0.109.1-tss3] - 2026-08-06
### Changed
- Updated the build and runtime target to Paper 26.2 and Java 25.
- Updated renamed health attributes, potion effects, and entity types for the current Paper API.
- Updated Gradle, Shadow, Mockito, and the test harness for Java 25.
### Fixed
- Removed legacy Bukkit material access from explosion handling and arena block restoration. Creeper and other block explosions no longer initialize CraftLegacy and DataFixerUpper on the server thread.
- Restored support-sensitive blocks without physics after their supporting blocks, avoiding legacy attached-face data while preserving soft restoration.
## [0.109] - 2024-10-13
### Added
- MobArena now properly supports Vault economy providers registered after MobArena has started. This should make it possible to use custom economy providers that aren't built into Vault, such as those created with Denizen.
- New wave rewards section `tiers` allows for _non-stacking_ reward tiers for beating certain waves. This allows for configuring reward sets that get "upgraded" as the waves progress, e.g. by granting a full leather armor set for beating wave 15, but beating wave 20 _replaces_ that leather armor set with an iron armor set.
- New per-arena setting `spawnpoint-max-distance` can be used to tweak how close to any player a given spawnpoint must be to be considered valid when spawning monsters. This should help reduce the amount of spawnpoints required, especially for larger arenas.
- New per-arena setting `spawnpoint-min-distance` can be used to tweak how far away from all players a given spawnpoint must be to be considered valid when spawning monsters. This should help prevent monsters from spawning directly on top of players.
### Changed ### Changed
- Recurrent waves can now be randomized. If two or more recurrent waves clash on wave number, frequency, _and_ priority, MobArena will now randomly pick between them. This should make it easier to create more varied and interesting wave setups without having to resort to only massively randomized default waves. - Recurrent waves can now be randomized. If two or more recurrent waves clash on wave number, frequency, _and_ priority, MobArena will now randomly pick between them. This should make it easier to create more varied and interesting wave setups without having to resort to only massively randomized default waves.
- Single waves can now be randomized. If two or more single waves clash on wave number, MobArena will now randomly pick between them. This means it is now possible to make randomly selected bosses for boss waves, for instance. - Single waves can now be randomized. If two or more single waves clash on wave number, MobArena will now randomly pick between them. This means it is now possible to make randomly selected bosses for boss waves, for instance.
- Slimes, Magma Cubes, Bats, Ghasts, Phantoms, and Ender Dragons now all target players in the same way other monsters do.
### Fixed ### Fixed
- MobArena no longer throws errors when handling block explosions on Minecraft 1.21. - MobArena no longer throws errors when handling block explosions on Minecraft 1.21.
- MobArena no longer throws errors during the automatic removal of spectators when using `auto-leave-on-end: true`.
- The `shuffle-positions` ability now correctly shuffles the position of the boss as well if `monster-teleport` is set to `false`. - The `shuffle-positions` ability now correctly shuffles the position of the boss as well if `monster-teleport` is set to `false`.
- The `obsidian-bomb` ability no longer breaks boss waves. - The `obsidian-bomb` ability no longer breaks boss waves.
- Text on Arena Signs is no longer explicitly truncated. This fixes an issue where color codes would count towards the character limit, causing the text that would otherwise fit on the sign to be cut off. - Text on Arena Signs is no longer explicitly truncated. This fixes an issue where color codes would count towards the character limit, causing the text that would otherwise fit on the sign to be cut off.
- Arena Signs can once again be destroyed, but it is necessary to break them while sneaking.
## [0.108] - 2024-01-01 ## [0.108] - 2024-01-01
### Added ### Added
@@ -270,7 +291,9 @@ Thanks to:
- Swatacular for help with testing bug fixes - Swatacular for help with testing bug fixes
- Haileykins for contributions to the code base - Haileykins for contributions to the code base
[Unreleased]: https://github.com/garbagemule/MobArena/compare/0.108...HEAD [Unreleased]: https://git.tss3.us/skywalker3200/MobArena/compare/0.109.1-tss3...master
[0.109.1-tss3]: https://git.tss3.us/skywalker3200/MobArena/compare/0.109...0.109.1-tss3
[0.109]: https://github.com/garbagemule/MobArena/compare/0.108...0.109
[0.108]: https://github.com/garbagemule/MobArena/compare/0.107...0.108 [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.107]: https://github.com/garbagemule/MobArena/compare/0.106...0.107
[0.106]: https://github.com/garbagemule/MobArena/compare/0.105...0.106 [0.106]: https://github.com/garbagemule/MobArena/compare/0.105...0.106
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
@@ -43,10 +43,17 @@ import org.bukkit.attribute.Attribute;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.AbstractArrow;
import org.bukkit.entity.AbstractHorse; import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Boat;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.ExperienceOrb;
import org.bukkit.entity.Horse; import org.bukkit.entity.Horse;
import org.bukkit.entity.Item;
import org.bukkit.entity.Minecart;
import org.bukkit.entity.ShulkerBullet;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.InventoryView;
@@ -124,7 +131,10 @@ public class ArenaImpl implements Arena
private WaveManager waveManager; private WaveManager waveManager;
private MASpawnThread spawnThread; private MASpawnThread spawnThread;
private SheepBouncer sheepBouncer; private SheepBouncer sheepBouncer;
private Map<Integer, ThingPicker> everyWaveMap, afterWaveMap; private Map<Integer, ThingPicker> everyWaveMap, afterWaveMap, waveTiersMap;
private double spawnpointMinDistanceSquared;
private double spawnpointMaxDistanceSquared;
private int leaveBufferDistance;
// Misc // Misc
private ArenaListener eventListener; private ArenaListener eventListener;
@@ -208,6 +218,10 @@ public class ArenaImpl implements Arena
this.waveManager = new WaveManager(this, section.getConfigurationSection("waves")); this.waveManager = new WaveManager(this, section.getConfigurationSection("waves"));
this.everyWaveMap = MAUtils.getArenaRewardMap(plugin, section, name, "every"); this.everyWaveMap = MAUtils.getArenaRewardMap(plugin, section, name, "every");
this.afterWaveMap = MAUtils.getArenaRewardMap(plugin, section, name, "after"); this.afterWaveMap = MAUtils.getArenaRewardMap(plugin, section, name, "after");
this.waveTiersMap = MAUtils.getArenaRewardMap(plugin, section, name, "tiers");
this.spawnpointMinDistanceSquared = Math.pow(settings.getDouble("spawnpoint-min-distance", 0), 2);
this.spawnpointMaxDistanceSquared = Math.pow(settings.getDouble("spawnpoint-max-distance", 15), 2);
this.leaveBufferDistance = settings.getInt("leave-buffer-distance", 3);
// Misc // Misc
this.eventListener = new ArenaListener(this, plugin); this.eventListener = new ArenaListener(this, plugin);
@@ -351,6 +365,11 @@ public class ArenaImpl implements Arena
return afterWaveMap.get(wave); return afterWaveMap.get(wave);
} }
@Override
public ThingPicker getWaveTierReward(int wave) {
return waveTiersMap.get(wave);
}
@Override @Override
public Set<Player> getPlayersInArena() { public Set<Player> getPlayersInArena() {
return Collections.unmodifiableSet(arenaPlayers); return Collections.unmodifiableSet(arenaPlayers);
@@ -376,6 +395,21 @@ public class ArenaImpl implements Arena
return spawnThread; return spawnThread;
} }
@Override
public double getSpawnpointMinDistanceSquared() {
return spawnpointMinDistanceSquared;
}
@Override
public double getSpawnpointMaxDistanceSquared() {
return spawnpointMaxDistanceSquared;
}
@Override
public int getLeaveBufferDistance() {
return leaveBufferDistance;
}
@Override @Override
public WaveManager getWaveManager() { public WaveManager getWaveManager() {
return waveManager; return waveManager;
@@ -645,7 +679,8 @@ public class ArenaImpl implements Arena
// Auto-leave // Auto-leave
if (settings.getBoolean("auto-leave-on-end", false)) { if (settings.getBoolean("auto-leave-on-end", false)) {
specPlayers.forEach(this::playerLeave); List<Player> spectators = new ArrayList<>(specPlayers);
spectators.forEach(this::playerLeave);
} }
return true; return true;
@@ -1005,7 +1040,7 @@ public class ArenaImpl implements Arena
mount.setTamed(true); mount.setTamed(true);
mount.setOwner(p); mount.setOwner(p);
mount.addPassenger(p); mount.addPassenger(p);
mount.setHealth(mount.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); mount.setHealth(mount.getAttribute(Attribute.MAX_HEALTH).getValue());
// Add saddle // Add saddle
mount.getInventory().addItem(new ItemStack(Material.SADDLE)); mount.getInventory().addItem(new ItemStack(Material.SADDLE));
@@ -1393,15 +1428,14 @@ public class ArenaImpl implements Arena
continue; continue;
} }
switch (e.getType()) { if (e instanceof Item
case DROPPED_ITEM: || e instanceof ExperienceOrb
case EXPERIENCE_ORB: || e instanceof AbstractArrow
case ARROW: || e instanceof Minecart
case MINECART: || e instanceof Boat
case BOAT: || e instanceof TNTPrimed
case PRIMED_TNT: || e instanceof ShulkerBullet) {
case SHULKER_BULLET: e.remove();
e.remove();
} }
} }
} }
@@ -25,6 +25,7 @@ import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.block.PistonMoveReaction;
import org.bukkit.block.Sign; import org.bukkit.block.Sign;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.AbstractHorse; import org.bukkit.entity.AbstractHorse;
@@ -89,10 +90,12 @@ import org.bukkit.event.vehicle.VehicleExitEvent;
import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.material.Attachable;
import org.bukkit.material.Bed; import org.bukkit.block.data.Bisected;
import org.bukkit.material.Door; import org.bukkit.block.data.BlockData;
import org.bukkit.material.Redstone; import org.bukkit.block.data.type.Bed;
import org.bukkit.block.data.type.Door;
import org.bukkit.metadata.MetadataValue; import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
@@ -235,7 +238,7 @@ public class ArenaListener
r = new RepairableContainer(state); r = new RepairableContainer(state);
else if (state instanceof Sign) else if (state instanceof Sign)
r = new RepairableSign(state); r = new RepairableSign(state);
else if (state.getData() instanceof Attachable) else if (state.getBlockData().getPistonMoveReaction() == PistonMoveReaction.BREAK)
r = new RepairableAttachable(state); r = new RepairableAttachable(state);
else else
r = new RepairableBlock(state); r = new RepairableBlock(state);
@@ -485,12 +488,15 @@ public class ArenaListener
// Handle all the blocks in the block list. // Handle all the blocks in the block list.
for (Block b : blocks) { for (Block b : blocks) {
BlockState state = b.getState(); BlockState state = b.getState();
BlockData data = state.getBlockData();
if (state.getData() instanceof Door && ((Door) state.getData()).isTopHalf()) { if (data instanceof Door && ((Door) data).getHalf() == Bisected.Half.TOP) {
state = b.getRelative(BlockFace.DOWN).getState(); state = b.getRelative(BlockFace.DOWN).getState();
data = state.getBlockData();
} }
else if (state.getData() instanceof Bed && ((Bed) state.getData()).isHeadOfBed()) { else if (data instanceof Bed && ((Bed) data).getPart() == Bed.Part.HEAD) {
state = b.getRelative(((Bed) state.getData()).getFacing().getOppositeFace()).getState(); state = b.getRelative(((Bed) data).getFacing().getOppositeFace()).getState();
data = state.getBlockData();
} }
// Create a Repairable from the block. // Create a Repairable from the block.
@@ -499,11 +505,11 @@ public class ArenaListener
r = new RepairableContainer(state); r = new RepairableContainer(state);
else if (state instanceof Sign) else if (state instanceof Sign)
r = new RepairableSign(state); r = new RepairableSign(state);
else if (state.getData() instanceof Bed) else if (data instanceof Bed)
r = new RepairableBed(state); r = new RepairableBed(state);
else if (state.getData() instanceof Door) else if (data instanceof Door)
r = new RepairableDoor(state); r = new RepairableDoor(state);
else if (state.getData() instanceof Attachable || state.getData() instanceof Redstone) else if (data.getPistonMoveReaction() == PistonMoveReaction.BREAK)
r = new RepairableAttachable(state); r = new RepairableAttachable(state);
else else
r = new RepairableBlock(state); r = new RepairableBlock(state);
@@ -958,7 +964,7 @@ public class ArenaListener
// If a potion has harmful effects, remove all players. // If a potion has harmful effects, remove all players.
for (PotionEffect effect : potion.getEffects()) { for (PotionEffect effect : potion.getEffects()) {
PotionEffectType type = effect.getType(); PotionEffectType type = effect.getType();
if (type.equals(PotionEffectType.HARM) || type.equals(PotionEffectType.POISON)) { if (type.equals(PotionEffectType.INSTANT_DAMAGE) || type.equals(PotionEffectType.POISON)) {
for (LivingEntity le : event.getAffectedEntities()) { for (LivingEntity le : event.getAffectedEntities()) {
if (le instanceof Player) { if (le instanceof Player) {
event.setIntensity(le, 0.0); event.setIntensity(le, 0.0);
@@ -972,7 +978,7 @@ public class ArenaListener
// Otherwise, check for monster infighting // Otherwise, check for monster infighting
for (PotionEffect effect : potion.getEffects()) { for (PotionEffect effect : potion.getEffects()) {
PotionEffectType type = effect.getType(); PotionEffectType type = effect.getType();
if (type.equals(PotionEffectType.HARM) || type.equals(PotionEffectType.POISON)) { if (type.equals(PotionEffectType.INSTANT_DAMAGE) || type.equals(PotionEffectType.POISON)) {
for (LivingEntity le : event.getAffectedEntities()) { for (LivingEntity le : event.getAffectedEntities()) {
if (!(le instanceof Player)) { if (!(le instanceof Player)) {
event.setIntensity(le, 0.0); event.setIntensity(le, 0.0);
@@ -226,9 +226,9 @@ public class MASpawnThread implements Runnable
monsterManager.addMonster(e); monsterManager.addMonster(e);
// Set the health. // Set the health.
int health = (int) Math.max(1D, e.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue() * mul); int health = (int) Math.max(1D, e.getAttribute(Attribute.MAX_HEALTH).getValue() * mul);
try { try {
e.getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(health); e.getAttribute(Attribute.MAX_HEALTH).setBaseValue(health);
e.setHealth(health); e.setHealth(health);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
// Spigot... *facepalm* // Spigot... *facepalm*
@@ -266,9 +266,9 @@ public class MASpawnThread implements Runnable
} }
break; break;
case SWARM: case SWARM:
health = (int) (mul < 1D ? e.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue() * mul : 1); health = (int) (mul < 1D ? e.getAttribute(Attribute.MAX_HEALTH).getValue() * mul : 1);
health = Math.max(1, health); health = Math.max(1, health);
e.setHealth(Math.min(health, e.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue())); e.setHealth(Math.min(health, e.getAttribute(Attribute.MAX_HEALTH).getValue()));
break; break;
case SUPPLY: case SUPPLY:
SupplyWave sw = (SupplyWave) w; SupplyWave sw = (SupplyWave) w;
@@ -345,7 +345,7 @@ public class MASpawnThread implements Runnable
private void removeCheatingPlayers() { private void removeCheatingPlayers() {
List<Player> players = new ArrayList<>(arena.getPlayersInArena()); List<Player> players = new ArrayList<>(arena.getPlayersInArena());
for (Player p : players) { for (Player p : players) {
if (region.contains(p.getLocation())) { if (region.contains(p.getLocation(), arena.getLeaveBufferDistance())) {
continue; continue;
} }
@@ -366,6 +366,11 @@ public class MASpawnThread implements Runnable
if (after != null) { if (after != null) {
addReward(after); addReward(after);
} }
ThingPicker tier = arena.getWaveTierReward(wave);
if (tier != null) {
setRewardTier(tier);
}
} }
private void updateStats(int wave) { private void updateStats(int wave) {
@@ -400,4 +405,14 @@ public class MASpawnThread implements Runnable
} }
} }
} }
private void setRewardTier(ThingPicker picker) {
for (Player p : arena.getPlayersInArena()) {
Thing reward = picker.pick();
if (reward != null) {
rewardManager.setTieredReward(p, reward);
arena.getMessenger().tell(p, Msg.WAVE_TIER_REWARD, reward.toString());
}
}
}
} }
@@ -88,6 +88,7 @@ public class MAUtils
/* Iterate through the ArrayList, and update current and result every /* Iterate through the ArrayList, and update current and result every
* time a squared distance smaller than current is found. */ * time a squared distance smaller than current is found. */
List<Player> players = new ArrayList<>(arena.getPlayersInArena()); List<Player> players = new ArrayList<>(arena.getPlayersInArena());
double max = arena.getSpawnpointMaxDistanceSquared();
for (Player p : players) { for (Player p : players) {
if (!arena.getWorld().equals(p.getWorld())) { if (!arena.getWorld().equals(p.getWorld())) {
plugin.getLogger().info("Player '" + p.getName() + "' is not in the right world. Kicking..."); plugin.getLogger().info("Player '" + p.getName() + "' is not in the right world. Kicking...");
@@ -97,7 +98,7 @@ public class MAUtils
} }
double dist = distanceSquared(plugin, p, e.getLocation()); double dist = distanceSquared(plugin, p, e.getLocation());
if (dist < current && dist < 256D) { if (dist < current && dist < max) {
current = dist; current = dist;
result = p; result = p;
} }
@@ -2,6 +2,8 @@ package com.garbagemule.MobArena;
import com.garbagemule.MobArena.commands.CommandHandler; import com.garbagemule.MobArena.commands.CommandHandler;
import com.garbagemule.MobArena.config.LoadsConfigFile; import com.garbagemule.MobArena.config.LoadsConfigFile;
import com.garbagemule.MobArena.finance.Finance;
import com.garbagemule.MobArena.finance.FinanceFactory;
import com.garbagemule.MobArena.events.MobArenaPreReloadEvent; import com.garbagemule.MobArena.events.MobArenaPreReloadEvent;
import com.garbagemule.MobArena.events.MobArenaReloadEvent; import com.garbagemule.MobArena.events.MobArenaReloadEvent;
import com.garbagemule.MobArena.formula.FormulaMacros; import com.garbagemule.MobArena.formula.FormulaMacros;
@@ -17,7 +19,6 @@ import com.garbagemule.MobArena.metrics.FoodRegenChart;
import com.garbagemule.MobArena.metrics.IsolatedChatChart; import com.garbagemule.MobArena.metrics.IsolatedChatChart;
import com.garbagemule.MobArena.metrics.MonsterInfightChart; import com.garbagemule.MobArena.metrics.MonsterInfightChart;
import com.garbagemule.MobArena.metrics.PvpEnabledChart; import com.garbagemule.MobArena.metrics.PvpEnabledChart;
import com.garbagemule.MobArena.metrics.VaultChart;
import com.garbagemule.MobArena.signs.SignBootstrap; import com.garbagemule.MobArena.signs.SignBootstrap;
import com.garbagemule.MobArena.signs.SignListeners; import com.garbagemule.MobArena.signs.SignListeners;
import com.garbagemule.MobArena.things.NothingPickerParser; import com.garbagemule.MobArena.things.NothingPickerParser;
@@ -27,16 +28,12 @@ import com.garbagemule.MobArena.things.ThingManager;
import com.garbagemule.MobArena.things.ThingPickerManager; import com.garbagemule.MobArena.things.ThingPickerManager;
import com.garbagemule.MobArena.util.config.ConfigUtils; import com.garbagemule.MobArena.util.config.ConfigUtils;
import com.garbagemule.MobArena.waves.ability.AbilityManager; import com.garbagemule.MobArena.waves.ability.AbilityManager;
import net.milkbowl.vault.economy.Economy;
import org.bstats.bukkit.Metrics; import org.bstats.bukkit.Metrics;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicesManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import java.io.File; import java.io.File;
@@ -52,8 +49,7 @@ public class MobArena extends JavaPlugin
{ {
private ArenaMaster arenaMaster; private ArenaMaster arenaMaster;
// Vault private Finance finance;
private Economy economy;
private FileConfiguration config; private FileConfiguration config;
private LoadsConfigFile loadsConfigFile; private LoadsConfigFile loadsConfigFile;
@@ -113,7 +109,6 @@ public class MobArena extends JavaPlugin
setupArenaMaster(); setupArenaMaster();
setupCommandHandler(); setupCommandHandler();
setupVault();
setupBossAbilities(); setupBossAbilities();
setupListeners(); setupListeners();
setupMetrics(); setupMetrics();
@@ -150,24 +145,6 @@ public class MobArena extends JavaPlugin
getCommand("ma").setExecutor(new CommandHandler(this)); getCommand("ma").setExecutor(new CommandHandler(this));
} }
private void setupVault() {
Plugin vaultPlugin = this.getServer().getPluginManager().getPlugin("Vault");
if (vaultPlugin == null) {
getLogger().info("Vault was not found. Economy rewards will not work.");
return;
}
ServicesManager manager = this.getServer().getServicesManager();
RegisteredServiceProvider<Economy> e = manager.getRegistration(net.milkbowl.vault.economy.Economy.class);
if (e != null) {
economy = e.getProvider();
getLogger().info("Vault found; economy rewards enabled.");
} else {
getLogger().warning("Vault found, but no economy plugin detected. Economy rewards will not work!");
}
}
private void setupBossAbilities() { private void setupBossAbilities() {
AbilityManager.loadCoreAbilities(); AbilityManager.loadCoreAbilities();
AbilityManager.loadCustomAbilities(getDataFolder()); AbilityManager.loadCustomAbilities(getDataFolder());
@@ -180,7 +157,6 @@ public class MobArena extends JavaPlugin
private void setupMetrics() { private void setupMetrics() {
Metrics metrics = new Metrics(this, 2572); Metrics metrics = new Metrics(this, 2572);
metrics.addCustomChart(new VaultChart(this));
metrics.addCustomChart(new ArenaCountChart(this)); metrics.addCustomChart(new ArenaCountChart(this));
metrics.addCustomChart(new ClassCountChart(this)); metrics.addCustomChart(new ClassCountChart(this));
metrics.addCustomChart(new ClassChestsChart(this)); metrics.addCustomChart(new ClassChestsChart(this));
@@ -195,6 +171,7 @@ public class MobArena extends JavaPlugin
getServer().getPluginManager().callEvent(pre); getServer().getPluginManager().callEvent(pre);
try { try {
reloadFinance();
reloadConfig(); reloadConfig();
reloadGlobalMessenger(); reloadGlobalMessenger();
reloadFormulaMacros(); reloadFormulaMacros();
@@ -211,6 +188,10 @@ public class MobArena extends JavaPlugin
getServer().getPluginManager().callEvent(post); getServer().getPluginManager().callEvent(post);
} }
private void reloadFinance() {
finance = FinanceFactory.create(getServer(), getLogger());
}
@Override @Override
public void reloadConfig() { public void reloadConfig() {
if (loadsConfigFile == null) { if (loadsConfigFile == null) {
@@ -307,8 +288,8 @@ public class MobArena extends JavaPlugin
return arenaMaster; return arenaMaster;
} }
public Economy getEconomy() { public Finance getFinance() {
return economy; return finance;
} }
public Messenger getGlobalMessenger() { public Messenger getGlobalMessenger() {
@@ -78,6 +78,7 @@ public enum Msg {
WAVE_BOSS_KILLED("&a%&r killed the boss!"), WAVE_BOSS_KILLED("&a%&r killed the boss!"),
WAVE_BOSS_REWARD_EARNED("You earned: &e%"), WAVE_BOSS_REWARD_EARNED("You earned: &e%"),
WAVE_REWARD("You just earned a reward: &e%&r"), WAVE_REWARD("You just earned a reward: &e%&r"),
WAVE_TIER_REWARD("You just unlocked a new reward tier: &e%&r"),
MISC_REWARD_ADDED("You were just given a reward: &e%&r"), MISC_REWARD_ADDED("You were just given a reward: &e%&r"),
MISC_LIST_PLAYERS("Live players: &a%&r"), MISC_LIST_PLAYERS("Live players: &a%&r"),
MISC_LIST_ARENAS("Available arenas: %"), MISC_LIST_ARENAS("Available arenas: %"),
@@ -14,15 +14,18 @@ import java.util.Set;
public class RewardManager public class RewardManager
{ {
private Map<Player,List<Thing>> players; private Map<Player,List<Thing>> players;
private Map<Player,Thing> tiered;
private Set<Player> rewarded; private Set<Player> rewarded;
public RewardManager(Arena arena) { public RewardManager(Arena arena) {
this.players = new HashMap<>(); this.players = new HashMap<>();
this.tiered = new HashMap<>();
this.rewarded = new HashSet<>(); this.rewarded = new HashSet<>();
} }
public void reset() { public void reset() {
players.clear(); players.clear();
tiered.clear();
rewarded.clear(); rewarded.clear();
} }
@@ -33,7 +36,17 @@ public class RewardManager
players.get(p).add(thing); players.get(p).add(thing);
} }
public void setTieredReward(Player p, Thing thing) {
tiered.put(p, thing);
}
public void grantRewards(Player p) { public void grantRewards(Player p) {
grantNormalRewards(p);
grantTieredRewards(p);
rewarded.add(p);
}
private void grantNormalRewards(Player p) {
if (rewarded.contains(p)) return; if (rewarded.contains(p)) return;
List<Thing> rewards = players.get(p); List<Thing> rewards = players.get(p);
@@ -45,6 +58,14 @@ public class RewardManager
} }
reward.giveTo(p); reward.giveTo(p);
} }
rewarded.add(p); }
private void grantTieredRewards(Player p) {
if (rewarded.contains(p)) return;
Thing reward = tiered.get(p);
if (reward == null) return;
reward.giveTo(p);
} }
} }
@@ -0,0 +1,15 @@
package com.garbagemule.MobArena.finance;
import org.bukkit.entity.Player;
public interface Finance {
double getBalance(Player player);
boolean deposit(Player player, double amount);
boolean withdraw(Player player, double amount);
String format(double amount);
}
@@ -0,0 +1,25 @@
package com.garbagemule.MobArena.finance;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.ServicesManager;
import java.util.logging.Logger;
public class FinanceFactory {
FinanceFactory() {
// OK BOSS
}
public static Finance create(Server server, Logger log) {
Plugin plugin = server.getPluginManager().getPlugin("Vault");
if (plugin == null) {
return new UnsupportedFinance(log);
}
ServicesManager services = server.getServicesManager();
return new VaultFinance(services, log);
}
}
@@ -0,0 +1,39 @@
package com.garbagemule.MobArena.finance;
import org.bukkit.entity.Player;
import java.util.logging.Logger;
public class UnsupportedFinance implements Finance {
private final Logger log;
public UnsupportedFinance(Logger log) {
this.log = log;
}
@Override
public double getBalance(Player player) {
log.severe("Economy operations are only supported via Vault!");
return -1.0;
}
@Override
public boolean deposit(Player player, double amount) {
log.severe("Economy operations are only supported via Vault!");
return false;
}
@Override
public boolean withdraw(Player player, double amount) {
log.severe("Economy operations are only supported via Vault!");
return false;
}
@Override
public String format(double amount) {
log.severe("Economy operations are only supported via Vault!");
return "ERROR";
}
}
@@ -0,0 +1,78 @@
package com.garbagemule.MobArena.finance;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.economy.EconomyResponse;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicesManager;
import java.util.logging.Logger;
public class VaultFinance implements Finance {
private final ServicesManager services;
private final Logger log;
private Economy economy;
public VaultFinance(ServicesManager services, Logger log) {
this.services = services;
this.log = log;
}
@Override
public double getBalance(Player player) {
try {
return getEconomy().getBalance(player);
} catch (IllegalStateException e) {
log.severe("Failed to check balance of player " + player.getName() + " because: " + e.getMessage());
return 0;
}
}
@Override
public boolean deposit(Player player, double amount) {
try {
EconomyResponse res = getEconomy().depositPlayer(player, amount);
return res.type == EconomyResponse.ResponseType.SUCCESS;
} catch (IllegalStateException e) {
log.severe("Failed to give " + amount + " economy money to player " + player.getName() + " because: " + e.getMessage());
return false;
}
}
@Override
public boolean withdraw(Player player, double amount) {
try {
EconomyResponse res = getEconomy().withdrawPlayer(player, amount);
return res.type == EconomyResponse.ResponseType.SUCCESS;
} catch (IllegalStateException e) {
log.severe("Failed to take " + amount + " economy money from player " + player.getName() + " because: " + e.getMessage());
return false;
}
}
@Override
public String format(double amount) {
try {
return getEconomy().format(amount);
} catch (IllegalStateException e) {
log.severe("Failed to format " + amount + " as economy money because: " + e.getMessage());
return "ERROR";
}
}
private Economy getEconomy() {
if (economy != null) {
return economy;
}
RegisteredServiceProvider<Economy> provider = services.getRegistration(Economy.class);
if (provider == null) {
throw new IllegalStateException("No Vault economy provider found!");
}
return (economy = provider.getProvider());
}
}
@@ -70,6 +70,8 @@ public interface Arena
ThingPicker getAfterWaveReward(int wave); ThingPicker getAfterWaveReward(int wave);
ThingPicker getWaveTierReward(int wave);
Set<Player> getPlayersInArena(); Set<Player> getPlayersInArena();
Set<Player> getPlayersInLobby(); Set<Player> getPlayersInLobby();
@@ -80,6 +82,12 @@ public interface Arena
MASpawnThread getSpawnThread(); MASpawnThread getSpawnThread();
double getSpawnpointMinDistanceSquared();
double getSpawnpointMaxDistanceSquared();
int getLeaveBufferDistance();
WaveManager getWaveManager(); WaveManager getWaveManager();
ArenaListener getEventListener(); ArenaListener getEventListener();
@@ -1,12 +0,0 @@
package com.garbagemule.MobArena.metrics;
import com.garbagemule.MobArena.MobArena;
import org.bstats.charts.SimplePie;
public class VaultChart extends SimplePie {
public VaultChart(MobArena plugin) {
super("uses_vault", () -> plugin.getEconomy() != null ? "Yes" : "No");
}
}
@@ -648,9 +648,12 @@ public class ArenaRegion
} }
// Find all the spawnpoints that cover the location // Find all the spawnpoints that cover the location
double min = arena.getSpawnpointMinDistanceSquared();
double max = arena.getSpawnpointMaxDistanceSquared();
Map<String,Location> map = new HashMap<>(); Map<String,Location> map = new HashMap<>();
for (Map.Entry<String,Location> entry : spawnpoints.entrySet()) { for (Map.Entry<String,Location> entry : spawnpoints.entrySet()) {
if (p.getLocation().distanceSquared(entry.getValue()) < MobArena.MIN_PLAYER_DISTANCE_SQUARED) { double dist = p.getLocation().distanceSquared(entry.getValue());
if (min <= dist && dist <= max) {
map.put(entry.getKey(), entry.getValue()); map.put(entry.getKey(), entry.getValue());
} }
} }
@@ -2,37 +2,20 @@ package com.garbagemule.MobArena.repairable;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.material.Attachable;
public class RepairableAttachable extends RepairableBlock public class RepairableAttachable extends RepairableBlock
{ {
private int x, y, z;
public RepairableAttachable(BlockState state) public RepairableAttachable(BlockState state)
{ {
super(state); super(state);
state.getBlock().setType(Material.STONE, false);
BlockState attached;
if (state.getData() instanceof Attachable)
attached = state.getBlock().getRelative(((Attachable) state.getData()).getAttachedFace()).getState();
else
attached = state.getBlock().getRelative(BlockFace.DOWN).getState();
x = attached.getX();
y = attached.getY();
z = attached.getZ();
state.getBlock().setType(Material.STONE);
} }
@Override
public void repair() public void repair()
{ {
Block b = getWorld().getBlockAt(x,y,z); Block block = getWorld().getBlockAt(getX(), getY(), getZ());
if (b.getType() == Material.AIR) block.setBlockData(getData(), false);
b.setType(Material.STONE);
super.repair();
} }
} }
@@ -1,7 +1,7 @@
package com.garbagemule.MobArena.repairable; package com.garbagemule.MobArena.repairable;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.material.Bed; import org.bukkit.block.data.type.Bed;
public class RepairableBed extends RepairableBlock public class RepairableBed extends RepairableBlock
{ {
@@ -10,12 +10,12 @@ public class RepairableBed extends RepairableBlock
public RepairableBed(BlockState state) public RepairableBed(BlockState state)
{ {
super(state); super(state);
other = state.getBlock().getRelative(((Bed) state.getData()).getFacing()).getState(); other = state.getBlock().getRelative(((Bed) state.getBlockData()).getFacing()).getState();
} }
public void repair() public void repair()
{ {
if (getWorld().getBlockAt(getX(), getY(), getZ()).getState().getData() instanceof Bed) if (getWorld().getBlockAt(getX(), getY(), getZ()).getBlockData() instanceof Bed)
return; return;
super.repair(); super.repair();
@@ -1,11 +1,10 @@
package com.garbagemule.MobArena.repairable; package com.garbagemule.MobArena.repairable;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.data.Attachable; import org.bukkit.block.PistonMoveReaction;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Bed; import org.bukkit.block.data.type.Bed;
import org.bukkit.block.data.type.Door; import org.bukkit.block.data.type.Door;
import org.bukkit.block.data.type.RedstoneWire;
import java.util.Comparator; import java.util.Comparator;
@@ -30,6 +29,6 @@ public class RepairableComparator implements Comparator<Repairable>
Material t = r.getType(); Material t = r.getType();
BlockData data = r.getData(); BlockData data = r.getData();
return (data instanceof Attachable || data instanceof RedstoneWire || data instanceof Door || data instanceof Bed || t == Material.LAVA || t == Material.WATER || t == Material.FIRE); return (data.getPistonMoveReaction() == PistonMoveReaction.BREAK || data instanceof Door || data instanceof Bed || t == Material.LAVA || t == Material.WATER || t == Material.FIRE);
} }
} }
@@ -4,7 +4,7 @@ import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.material.Door; import org.bukkit.block.data.type.Door;
public class RepairableDoor extends RepairableAttachable//RepairableBlock public class RepairableDoor extends RepairableAttachable//RepairableBlock
{ {
@@ -24,7 +24,7 @@ public class RepairableDoor extends RepairableAttachable//RepairableBlock
public void repair() public void repair()
{ {
if (getWorld().getBlockAt(getX(), getY(), getZ()).getState().getData() instanceof Door) if (getWorld().getBlockAt(getX(), getY(), getZ()).getBlockData() instanceof Door)
return; return;
Block b = getWorld().getBlockAt(x,y,z); Block b = getWorld().getBlockAt(x,y,z);
@@ -6,6 +6,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import java.util.HashMap; import java.util.HashMap;
@@ -29,7 +30,7 @@ class HandlesSignClicks implements Listener {
this.cooldowns = new HashMap<>(); this.cooldowns = new HashMap<>();
} }
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.HIGHEST)
public void on(PlayerInteractEvent event) { public void on(PlayerInteractEvent event) {
Block block = event.getClickedBlock(); Block block = event.getClickedBlock();
if (block == null) { if (block == null) {
@@ -38,6 +39,9 @@ class HandlesSignClicks implements Listener {
if (!(block.getState() instanceof Sign)) { if (!(block.getState() instanceof Sign)) {
return; return;
} }
if (event.getPlayer().isSneaking() && event.getAction() == Action.LEFT_CLICK_BLOCK) {
return;
}
ArenaSign sign = signStore.findByLocation(block.getLocation()); ArenaSign sign = signStore.findByLocation(block.getLocation());
if (sign != null) { if (sign != null) {
@@ -24,13 +24,13 @@ class SetHealth extends PlayerStep {
player.setRemainingAir(NORMAL_AIR); player.setRemainingAir(NORMAL_AIR);
player.setFireTicks(NORMAL_FIRE); player.setFireTicks(NORMAL_FIRE);
double full = player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue(); double full = player.getAttribute(Attribute.MAX_HEALTH).getValue();
player.setHealth(full); player.setHealth(full);
} }
@Override @Override
public void undo() { public void undo() {
double max = player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue(); double max = player.getAttribute(Attribute.MAX_HEALTH).getValue();
double capped = Math.min(health, max); double capped = Math.min(health, max);
player.setHealth(capped); player.setHealth(capped);
@@ -1,52 +1,36 @@
package com.garbagemule.MobArena.things; package com.garbagemule.MobArena.things;
import net.milkbowl.vault.economy.Economy; import com.garbagemule.MobArena.finance.Finance;
import net.milkbowl.vault.economy.EconomyResponse;
import net.milkbowl.vault.economy.EconomyResponse.ResponseType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class MoneyThing implements Thing { public class MoneyThing implements Thing {
private final Economy economy; private final Finance finance;
private final double amount; private final double amount;
public MoneyThing(Economy economy, double amount) { public MoneyThing(Finance finance, double amount) {
this.economy = economy; this.finance = finance;
this.amount = amount; this.amount = amount;
} }
@Override @Override
public boolean giveTo(Player player) { public boolean giveTo(Player player) {
if (economy == null) { return finance.deposit(player, amount);
return false;
}
EconomyResponse result = economy.depositPlayer(player, amount);
return result.type == ResponseType.SUCCESS;
} }
@Override @Override
public boolean takeFrom(Player player) { public boolean takeFrom(Player player) {
if (economy == null) { return finance.withdraw(player, amount);
return false;
}
EconomyResponse result = economy.withdrawPlayer(player, amount);
return result.type == ResponseType.SUCCESS;
} }
@Override @Override
public boolean heldBy(Player player) { public boolean heldBy(Player player) {
if (economy == null) { return finance.getBalance(player) >= amount;
return false;
}
return economy.getBalance(player) >= amount;
} }
@Override @Override
public String toString() { public String toString() {
if (economy == null) { return finance.format(amount);
return "$" + amount;
}
return economy.format(amount);
} }
} }
@@ -1,7 +1,7 @@
package com.garbagemule.MobArena.things; package com.garbagemule.MobArena.things;
import com.garbagemule.MobArena.MobArena; import com.garbagemule.MobArena.MobArena;
import net.milkbowl.vault.economy.Economy; import com.garbagemule.MobArena.finance.Finance;
class MoneyThingParser implements ThingParser { class MoneyThingParser implements ThingParser {
@@ -20,11 +20,11 @@ class MoneyThingParser implements ThingParser {
if (money == null) { if (money == null) {
return null; return null;
} }
Economy economy = plugin.getEconomy();
if (economy == null) { Finance finance = plugin.getFinance();
plugin.getLogger().severe("Vault or economy plugin missing while parsing: " + s); double amount = Double.parseDouble(money);
}
return new MoneyThing(economy, Double.parseDouble(money)); return new MoneyThing(finance, amount);
} }
private String trimPrefix(String s) { private String trimPrefix(String s) {
@@ -25,7 +25,7 @@ public class MABoss
*/ */
public MABoss(LivingEntity entity, double maxHealth) { public MABoss(LivingEntity entity, double maxHealth) {
try { try {
entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(maxHealth); entity.getAttribute(Attribute.MAX_HEALTH).setBaseValue(maxHealth);
entity.setHealth(maxHealth); entity.setHealth(maxHealth);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
// Spigot... *facepalm* // Spigot... *facepalm*
@@ -57,7 +57,7 @@ public class MABoss
* @return the maximum health of the boss * @return the maximum health of the boss
*/ */
public double getMaxHealth() { public double getMaxHealth() {
return entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue(); return entity.getAttribute(Attribute.MAX_HEALTH).getValue();
} }
/** /**
@@ -7,11 +7,11 @@ import org.bukkit.DyeColor;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Bee; import org.bukkit.entity.Bee;
import org.bukkit.entity.Creature;
import org.bukkit.entity.Creeper; import org.bukkit.entity.Creeper;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Hoglin; import org.bukkit.entity.Hoglin;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Mob;
import org.bukkit.entity.PigZombie; import org.bukkit.entity.PigZombie;
import org.bukkit.entity.PiglinAbstract; import org.bukkit.entity.PiglinAbstract;
import org.bukkit.entity.Rabbit; import org.bukkit.entity.Rabbit;
@@ -181,9 +181,9 @@ public class MACreature {
break; break;
} }
if (e instanceof Creature) { if (e instanceof Mob) {
Creature c = (Creature) e; Mob m = (Mob) e;
c.setTarget(WaveUtils.getClosestPlayer(arena, e)); m.setTarget(WaveUtils.getClosestPlayer(arena, e));
} }
return e; return e;
@@ -27,14 +27,42 @@ public class WaveUtils
spawnpoints = arena.getRegion().getSpawnpointList(); spawnpoints = arena.getRegion().getSpawnpointList();
} }
// Loop through each one and check if any players are in range. double min = arena.getSpawnpointMinDistanceSquared();
for (Location l : spawnpoints) { double max = arena.getSpawnpointMaxDistanceSquared();
for (Player p : players) {
if (MAUtils.distanceSquared(plugin, p, l) >= MobArena.MIN_PLAYER_DISTANCE_SQUARED) { if (min > 0) {
continue; // If the min distance is greater than 0, we need to check the
// distance of every player, because one player within the max
// range is no longer enough to make a spawnpoint valid. If even
// a single player is too close, the spawnpoint is invalid.
for (Location l : spawnpoints) {
boolean valid = false;
for (Player p : players) {
double dist = MAUtils.distanceSquared(plugin, p, l);
if (dist < min) {
valid = false;
break;
}
if (dist <= max) {
valid = true;
}
}
if (valid) {
result.add(l);
}
}
} else {
// If the min distance is 0, the old "any player within range"
// approach is sufficient, because we can never invalidate a
// spawnpoint for being too close to a player.
for (Location l : spawnpoints) {
for (Player p : players) {
double dist = MAUtils.distanceSquared(plugin, p, l);
if (dist <= max) {
result.add(l);
break;
}
} }
result.add(l);
break;
} }
} }
@@ -70,7 +98,7 @@ public class WaveUtils
} }
dist = p.getLocation().distanceSquared(e.getLocation()); dist = p.getLocation().distanceSquared(e.getLocation());
if (dist < current && dist < MobArena.MIN_PLAYER_DISTANCE_SQUARED) if (dist < current && dist < arena.getSpawnpointMaxDistanceSquared())
{ {
current = dist; current = dist;
result = p; result = p;
@@ -33,8 +33,8 @@ public class RootTarget implements Ability
return; return;
Player player = (Player) target; Player player = (Player) target;
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, DURATION, AMPLIFIER)); player.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, DURATION, AMPLIFIER));
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_FALLING, DURATION, AMPLIFIER)); player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_FALLING, DURATION, AMPLIFIER));
player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP, DURATION, -AMPLIFIER)); player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP_BOOST, DURATION, -AMPLIFIER));
} }
} }
+1 -1
View File
@@ -2,7 +2,7 @@ name: ${project.name}
author: garbagemule author: garbagemule
main: com.garbagemule.MobArena.MobArena main: com.garbagemule.MobArena.MobArena
version: '${project.version}' version: '${project.version}'
api-version: 1.13 api-version: '26.2'
softdepend: [Multiverse-Core,Towny,Heroes,MagicSpells,Vault] softdepend: [Multiverse-Core,Towny,Heroes,MagicSpells,Vault]
commands: commands:
ma: ma:
+4
View File
@@ -7,3 +7,7 @@ waves:
'7': minecart, chest_minecart, furnace_minecart '7': minecart, chest_minecart, furnace_minecart
'13': iron_sword, iron_pickaxe, iron_shovel '13': iron_sword, iron_pickaxe, iron_shovel
'16': diamond_sword '16': diamond_sword
tiers:
'4': diamond
'11': diamond:10
'17': diamond:25
+3
View File
@@ -27,6 +27,9 @@ first-wave-delay: 5
next-wave-delay: 0 next-wave-delay: 0
wave-interval: 15 wave-interval: 15
final-wave: 0 final-wave: 0
spawnpoint-min-distance: 0
spawnpoint-max-distance: 15
leave-buffer-distance: 3
monster-limit: 100 monster-limit: 100
monster-exp: false monster-exp: false
keep-exp: false keep-exp: false
@@ -0,0 +1,58 @@
package com.garbagemule.MobArena.finance;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.junit.Test;
import java.util.logging.Logger;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class FinanceFactoryTest {
@Test
public void looksUpVaultPluginFromPluginManager() {
Server server = mock(Server.class);
Logger log = mock(Logger.class);
PluginManager plugins = mock(PluginManager.class);
when(server.getPluginManager()).thenReturn(plugins);
FinanceFactory.create(server, log);
verify(plugins).getPlugin("Vault");
}
@Test
public void createsUnsupportedFinanceIfVaultNotPresent() {
Server server = mock(Server.class);
Logger log = mock(Logger.class);
PluginManager plugins = mock(PluginManager.class);
when(server.getPluginManager()).thenReturn(plugins);
when(plugins.getPlugin(any())).thenReturn(null);
Finance result = FinanceFactory.create(server, log);
assertThat(result, instanceOf(UnsupportedFinance.class));
}
@Test
public void createsVaultFinanceIfVaultIsPresent() {
Server server = mock(Server.class);
Logger log = mock(Logger.class);
Plugin vault = mock(Plugin.class);
PluginManager plugins = mock(PluginManager.class);
when(server.getPluginManager()).thenReturn(plugins);
when(plugins.getPlugin(any())).thenReturn(vault);
Finance result = FinanceFactory.create(server, log);
assertThat(result, instanceOf(VaultFinance.class));
}
}
@@ -0,0 +1,82 @@
package com.garbagemule.MobArena.finance;
import org.junit.Before;
import org.junit.Test;
import java.util.logging.Logger;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class UnsupportedFinanceTest {
private Logger log;
private UnsupportedFinance subject;
@Before
public void setup() {
log = mock(Logger.class);
subject = new UnsupportedFinance(log);
}
@Test
public void logsErrorOnGetBalance() {
subject.getBalance(null);
verify(log).severe(anyString());
}
@Test
public void returnsNegativeOneOnGetBalance() {
double result = subject.getBalance(null);
assertThat(result, equalTo(-1.0));
}
@Test
public void logsErrorOnDeposit() {
subject.deposit(null, 1337);
verify(log).severe(anyString());
}
@Test
public void returnsFalseOnDeposit() {
boolean result = subject.deposit(null, 1337);
assertThat(result, equalTo(false));
}
@Test
public void logsErrorOnWithdraw() {
subject.withdraw(null, 1337);
verify(log).severe(anyString());
}
@Test
public void returnsFalseOnWithdraw() {
boolean result = subject.withdraw(null, 1337);
assertThat(result, equalTo(false));
}
@Test
public void logsErrorOnFormat() {
subject.format(1337);
verify(log).severe(anyString());
}
@Test
public void returnsErrorStringOnFormat() {
String result = subject.format(1337);
assertThat(result, equalTo("ERROR"));
}
}
@@ -0,0 +1,146 @@
package com.garbagemule.MobArena.finance;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.economy.EconomyResponse;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.ServicesManager;
import org.junit.Before;
import org.junit.Test;
import java.util.logging.Logger;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class VaultFinanceTest {
private ServicesManager services;
private Logger log;
private VaultFinance subject;
@Before
public void setup() {
services = mock(ServicesManager.class);
log = mock(Logger.class);
subject = new VaultFinance(services, log);
}
@Test
public void looksUpEconomyFromServicesManager() {
Player player = mock(Player.class);
when(player.getName()).thenReturn("Test Subject 287");
subject.getBalance(player);
verify(services).getRegistration(Economy.class);
}
@Test
public void logsErrorIfNoEconomyFound() {
Player player = mock(Player.class);
when(player.getName()).thenReturn("Test Subject 287");
when(services.getRegistration(Economy.class)).thenReturn(null);
subject.getBalance(player);
verify(log).severe(anyString());
}
@Test
public void initializationIsRepeatedIfEconomyIsNotFound() {
Player player = mock(Player.class);
when(player.getName()).thenReturn("Test Subject 287");
when(services.getRegistration(Economy.class)).thenReturn(null);
subject.getBalance(player);
subject.getBalance(player);
subject.getBalance(player);
verify(services, times(3)).getRegistration(Economy.class);
}
@Test
public void initializationIsIdempotentIfEconomyIsFound() {
Player player = mock(Player.class);
Economy economy = mock(Economy.class);
RegisteredServiceProvider<Economy> provider = createProvider(economy);
when(services.getRegistration(Economy.class)).thenReturn(provider);
subject.getBalance(player);
verify(services, times(1)).getRegistration(Economy.class);
}
@Test
public void delegatesGetBalanceToEconomyWithGivenPlayer() {
Player player = mock(Player.class);
Economy economy = mock(Economy.class);
RegisteredServiceProvider<Economy> provider = createProvider(economy);
when(services.getRegistration(Economy.class)).thenReturn(provider);
subject.getBalance(player);
verify(economy).getBalance(player);
}
@Test
public void delegatesDepositToEconomyWithGivenPlayerAndAmount() {
Player player = mock(Player.class);
double amount = 1337;
Economy economy = mock(Economy.class);
EconomyResponse res = new EconomyResponse(0, 0, EconomyResponse.ResponseType.SUCCESS, null);
RegisteredServiceProvider<Economy> provider = createProvider(economy);
when(services.getRegistration(Economy.class)).thenReturn(provider);
when(economy.depositPlayer(any(Player.class), anyDouble())).thenReturn(res);
subject.deposit(player, amount);
verify(economy).depositPlayer(player, amount);
}
@Test
public void delegatesWithdrawToEconomyWithGivenPlayerAndAmount() {
Player player = mock(Player.class);
double amount = 1337;
Economy economy = mock(Economy.class);
EconomyResponse res = new EconomyResponse(0, 0, EconomyResponse.ResponseType.SUCCESS, null);
RegisteredServiceProvider<Economy> provider = createProvider(economy);
when(services.getRegistration(Economy.class)).thenReturn(provider);
when(economy.withdrawPlayer(any(Player.class), anyDouble())).thenReturn(res);
subject.withdraw(player, amount);
verify(economy).withdrawPlayer(player, amount);
}
@Test
public void delegatesFormatToEconomyWithGivenAmount() {
double amount = 1337;
Economy economy = mock(Economy.class);
RegisteredServiceProvider<Economy> provider = createProvider(economy);
when(services.getRegistration(Economy.class)).thenReturn(provider);
subject.format(amount);
verify(economy).format(amount);
}
private RegisteredServiceProvider<Economy> createProvider(Economy economy) {
return new RegisteredServiceProvider<>(
Economy.class,
economy,
ServicePriority.Normal,
mock(Plugin.class)
);
}
}
@@ -5,6 +5,7 @@ import org.bukkit.block.Block;
import org.bukkit.block.Chest; import org.bukkit.block.Chest;
import org.bukkit.block.Sign; import org.bukkit.block.Sign;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -50,12 +51,27 @@ public class HandlesSignClicksTest {
verifyNoInteractions(signStore, invokesSignAction); verifyNoInteractions(signStore, invokesSignAction);
} }
@Test
public void sneakingPlayerNoFun() {
Block block = mock(Block.class);
when(block.getState()).thenReturn(mock(Sign.class));
Player player = mock(Player.class);
when(player.isSneaking()).thenReturn(true);
PlayerInteractEvent event = event(player, block);
subject.on(event);
verifyNoInteractions(signStore, invokesSignAction);
}
@Test @Test
public void nonArenaSignNoFun() { public void nonArenaSignNoFun() {
Block block = mock(Block.class); Block block = mock(Block.class);
when(block.getState()).thenReturn(mock(Sign.class)); when(block.getState()).thenReturn(mock(Sign.class));
Player player = mock(Player.class);
when(player.isSneaking()).thenReturn(false);
when(signStore.findByLocation(any())).thenReturn(null); when(signStore.findByLocation(any())).thenReturn(null);
PlayerInteractEvent event = event(null, block); PlayerInteractEvent event = event(player, block);
subject.on(event); subject.on(event);
@@ -68,9 +84,10 @@ public class HandlesSignClicksTest {
Block block = mock(Block.class); Block block = mock(Block.class);
when(block.getLocation()).thenReturn(location); when(block.getLocation()).thenReturn(location);
when(block.getState()).thenReturn(mock(Sign.class)); when(block.getState()).thenReturn(mock(Sign.class));
Player player = mock(Player.class);
when(player.isSneaking()).thenReturn(false);
ArenaSign sign = new ArenaSign(location, "", "", ""); ArenaSign sign = new ArenaSign(location, "", "", "");
when(signStore.findByLocation(location)).thenReturn(sign); when(signStore.findByLocation(location)).thenReturn(sign);
Player player = mock(Player.class);
PlayerInteractEvent event = event(player, block); PlayerInteractEvent event = event(player, block);
subject.on(event); subject.on(event);
@@ -79,7 +96,8 @@ public class HandlesSignClicksTest {
} }
private PlayerInteractEvent event(Player player, Block block) { private PlayerInteractEvent event(Player player, Block block) {
return new PlayerInteractEvent(player, null, null, block, null); Action action = Action.LEFT_CLICK_BLOCK;
return new PlayerInteractEvent(player, action, null, block, null);
} }
} }
@@ -50,7 +50,7 @@ public class HandlesSignCreationTest {
@Test @Test
public void noSignCreationNoAction() { public void noSignCreationNoAction() {
SignChangeEvent event = new SignChangeEvent(null, null, null); SignChangeEvent event = mock(SignChangeEvent.class);
when(creator.create(event)).thenReturn(null); when(creator.create(event)).thenReturn(null);
subject.on(event); subject.on(event);
@@ -61,7 +61,8 @@ public class HandlesSignCreationTest {
@Test @Test
public void passesSignFromCreator() throws Exception { public void passesSignFromCreator() throws Exception {
Player player = mock(Player.class); Player player = mock(Player.class);
SignChangeEvent event = new SignChangeEvent(null, player, null); SignChangeEvent event = mock(SignChangeEvent.class);
when(event.getPlayer()).thenReturn(player);
ArenaSign sign = new ArenaSign(location(), null, null, null); ArenaSign sign = new ArenaSign(location(), null, null, null);
when(creator.create(event)).thenReturn(sign); when(creator.create(event)).thenReturn(sign);
@@ -75,7 +76,8 @@ public class HandlesSignCreationTest {
@Test @Test
public void successMessageOnCreation() { public void successMessageOnCreation() {
Player player = mock(Player.class); Player player = mock(Player.class);
SignChangeEvent event = new SignChangeEvent(null, player, null); SignChangeEvent event = mock(SignChangeEvent.class);
when(event.getPlayer()).thenReturn(player);
ArenaSign sign = new ArenaSign(location(), null, "castle", "join"); ArenaSign sign = new ArenaSign(location(), null, "castle", "join");
when(creator.create(event)).thenReturn(sign); when(creator.create(event)).thenReturn(sign);
@@ -87,7 +89,7 @@ public class HandlesSignCreationTest {
@Test @Test
public void noWriteIfCreatorThrows() { public void noWriteIfCreatorThrows() {
SignChangeEvent event = new SignChangeEvent(null, null, null); SignChangeEvent event = mock(SignChangeEvent.class);
doThrow(IllegalArgumentException.class).when(creator).create(event); doThrow(IllegalArgumentException.class).when(creator).create(event);
subject.on(event); subject.on(event);
@@ -98,7 +100,8 @@ public class HandlesSignCreationTest {
@Test @Test
public void errorMessageIfCreatorThrows() { public void errorMessageIfCreatorThrows() {
Player player = mock(Player.class); Player player = mock(Player.class);
SignChangeEvent event = new SignChangeEvent(null, player, null); SignChangeEvent event = mock(SignChangeEvent.class);
when(event.getPlayer()).thenReturn(player);
String message = "it's bad"; String message = "it's bad";
doThrow(new IllegalArgumentException(message)).when(creator).create(event); doThrow(new IllegalArgumentException(message)).when(creator).create(event);
@@ -109,7 +112,7 @@ public class HandlesSignCreationTest {
@Test @Test
public void noStorageIfWriterThrows() throws Exception { public void noStorageIfWriterThrows() throws Exception {
SignChangeEvent event = new SignChangeEvent(null, null, null); SignChangeEvent event = mock(SignChangeEvent.class);
ArenaSign sign = new ArenaSign(null, null, null, null); ArenaSign sign = new ArenaSign(null, null, null, null);
when(creator.create(event)).thenReturn(sign); when(creator.create(event)).thenReturn(sign);
doThrow(IOException.class).when(writer).write(sign); doThrow(IOException.class).when(writer).write(sign);
@@ -122,7 +125,8 @@ public class HandlesSignCreationTest {
@Test @Test
public void errorMessageIfWriterThrows() throws Exception { public void errorMessageIfWriterThrows() throws Exception {
Player player = mock(Player.class); Player player = mock(Player.class);
SignChangeEvent event = new SignChangeEvent(null, player, null); SignChangeEvent event = mock(SignChangeEvent.class);
when(event.getPlayer()).thenReturn(player);
ArenaSign sign = new ArenaSign(null, null, null, null); ArenaSign sign = new ArenaSign(null, null, null, null);
when(creator.create(event)).thenReturn(sign); when(creator.create(event)).thenReturn(sign);
IOException exception = new IOException("it's bad"); IOException exception = new IOException("it's bad");
@@ -40,7 +40,7 @@ public class SignCreatorTest {
@Test @Test
public void noHeaderNoAction() { public void noHeaderNoAction() {
String[] lines = {"ma", "castle", "join", "cool-sign"}; String[] lines = {"ma", "castle", "join", "cool-sign"};
SignChangeEvent event = new SignChangeEvent(null, null, lines); SignChangeEvent event = event(lines, null);
ArenaSign result = subject.create(event); ArenaSign result = subject.create(event);
@@ -202,10 +202,12 @@ public class SignCreatorTest {
} }
private SignChangeEvent event(String[] lines, Location location) { private SignChangeEvent event(String[] lines, Location location) {
SignChangeEvent event = mock(SignChangeEvent.class);
when(event.getLine(anyInt())).thenAnswer(invocation -> lines[invocation.getArgument(0)]);
Block block = mock(Block.class); Block block = mock(Block.class);
when(block.getLocation()).thenReturn(location); when(block.getLocation()).thenReturn(location);
Player player = mock(Player.class); when(event.getBlock()).thenReturn(block);
return new SignChangeEvent(block, player, lines); return event;
} }
} }
@@ -2,9 +2,12 @@ package com.garbagemule.MobArena.things;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.mockito.InOrder; import org.mockito.InOrder;
import org.mockbukkit.mockbukkit.MockBukkit;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.MatcherAssert.*;
@@ -13,6 +16,15 @@ import static org.mockito.Mockito.*;
public class ItemStackThingParserTest { public class ItemStackThingParserTest {
private ItemStackThingParser subject; private ItemStackThingParser subject;
@BeforeClass
public static void startServer() {
MockBukkit.mock();
}
@AfterClass
public static void stopServer() {
MockBukkit.unmock();
}
@Before @Before
public void setup() { public void setup() {
@@ -107,7 +119,7 @@ public class ItemStackThingParserTest {
subject.parse(input); subject.parse(input);
verify(first).parse(input); verify(first).parse(input);
verifyZeroInteractions(third); verifyNoInteractions(third);
} }
} }
@@ -1,23 +1,20 @@
package com.garbagemule.MobArena.things; package com.garbagemule.MobArena.things;
import com.garbagemule.MobArena.MobArena; import com.garbagemule.MobArena.MobArena;
import net.milkbowl.vault.economy.Economy; import com.garbagemule.MobArena.finance.Finance;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import java.util.logging.Logger;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.MatcherAssert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
public class MoneyThingParserTest { public class MoneyThingParserTest {
private MoneyThingParser subject;
private MobArena plugin; private MobArena plugin;
private MoneyThingParser subject;
@Rule @Rule
public ExpectedException exception = ExpectedException.none(); public ExpectedException exception = ExpectedException.none();
@@ -25,8 +22,7 @@ public class MoneyThingParserTest {
@Before @Before
public void setup() { public void setup() {
plugin = mock(MobArena.class); plugin = mock(MobArena.class);
Economy economy = mock(Economy.class); when(plugin.getFinance()).thenReturn(mock(Finance.class));
when(plugin.getEconomy()).thenReturn(economy);
subject = new MoneyThingParser(plugin); subject = new MoneyThingParser(plugin);
} }
@@ -52,17 +48,6 @@ public class MoneyThingParserTest {
assertThat(result, not(nullValue())); assertThat(result, not(nullValue()));
} }
@Test
public void nullEconomyNullMoney() {
Logger logger = mock(Logger.class);
when(plugin.getEconomy()).thenReturn(null);
when(plugin.getLogger()).thenReturn(logger);
subject.parse("$500");
verify(logger).severe(anyString());
}
@Test @Test
public void numberFormatForNaughtyValues() { public void numberFormatForNaughtyValues() {
exception.expect(NumberFormatException.class); exception.expect(NumberFormatException.class);
@@ -70,7 +70,7 @@ public class ThingManagerTest {
verify(first).parse("thing"); verify(first).parse("thing");
verify(second).parse("thing"); verify(second).parse("thing");
verifyZeroInteractions(third); verifyNoInteractions(third);
} }
@Test @Test