Compare commits
169 Commits
Author | SHA1 | Date |
---|---|---|
Andreas Troelsen | 29c5d7f56d | |
Andreas Troelsen | e40fc6ef84 | |
Andreas Troelsen | be6fd85a6d | |
Andreas Troelsen | b881943656 | |
Andreas Troelsen | 8e5d2f0d23 | |
Andreas Troelsen | 3b7b638b00 | |
Andreas Troelsen | eb51a31720 | |
Andreas Troelsen | 82f00c5535 | |
Andreas Troelsen | d8fdbb80c0 | |
Andreas Troelsen | e5ffe169a1 | |
Andreas Troelsen | 798ae0f578 | |
Andreas Troelsen | 84776990b9 | |
Andreas Troelsen | 590f877756 | |
Andreas Troelsen | 932b9de8f2 | |
Andreas Troelsen | 614d95683e | |
Andreas Troelsen | 4c855e6705 | |
Andreas Troelsen | 7942c33e67 | |
Andreas Troelsen | f57f10ecd5 | |
Andreas Troelsen | 511d16f45a | |
Andreas Troelsen | 457bf2ffff | |
Andreas Troelsen | a662157cbf | |
Andreas Troelsen | 9e083a91de | |
Andreas Troelsen | 12314f476c | |
Andreas Troelsen | feb257213c | |
Andreas Troelsen | d7336526e1 | |
Andreas Troelsen | 1a7109a1d4 | |
Andreas Troelsen | af513b03b0 | |
Andreas Troelsen | 6579c4bf0e | |
Andreas Troelsen | a79678430d | |
Andreas Troelsen | 61550161a7 | |
Andreas Troelsen | bbe7ed491d | |
Tad Hunt | 58f1423a6e | |
Tad Hunt | a8b2cf90e9 | |
Andreas Troelsen | 15a79bdd7e | |
Andreas Troelsen | 4f78936716 | |
Andreas Troelsen | 2eb1761e76 | |
Andreas Troelsen | 9871bb85d8 | |
Andreas Troelsen | 00605b54f9 | |
Andreas Troelsen | ec644df05b | |
Andreas Troelsen | 2c0d3e292c | |
Andreas Troelsen | d963d90cb9 | |
Andreas Troelsen | b76b0e6719 | |
Andreas Troelsen | 6cec72ebfb | |
Andreas Troelsen | 8c5ae13bef | |
Andreas Troelsen | 36908cbe85 | |
Nesseley | 3e2c614c18 | |
Andreas Troelsen | c88f20c46f | |
Andreas Troelsen | b9b4d0d204 | |
Andreas Troelsen | 4470b60aaf | |
Andreas Troelsen | d3e5d44cb2 | |
Andreas Troelsen | d4dcc8dc90 | |
Andreas Troelsen | 77b2525707 | |
Ghmmy | d5ea15fa08 | |
sepulzera | be6be4bb98 | |
Andreas Troelsen | 9164b125bd | |
Andreas Troelsen | c80f0375ee | |
Andreas Troelsen | 2c12112fd0 | |
Andreas Troelsen | fe9d160edd | |
Andreas Troelsen | 4348a0497e | |
Maroon28 | 3693c039a6 | |
Griffin | caee8be6ca | |
Nesseley | c9766dee97 | |
Andreas Troelsen | c143cc81c9 | |
Andreas Troelsen | 52226fa1c9 | |
Maroon28 | 1c225d93f9 | |
Bobcat00 | 6be130daf1 | |
Maroon28 | a21f47e193 | |
Andreas Troelsen | 7b9a9505b9 | |
Andreas Troelsen | cad2eef8ba | |
Andreas Troelsen | 9fec49cc58 | |
Andreas Troelsen | 823be96b4e | |
Andreas Troelsen | 286071871f | |
Andreas Troelsen | c1716709f3 | |
Andreas Troelsen | 15698d3eee | |
Andreas Troelsen | b4cd509eff | |
Andreas Troelsen | b49920fc38 | |
Andreas Troelsen | 36237ebe2d | |
Andreas Troelsen | 4de0ce258b | |
Andreas Troelsen | 72f4d16e6f | |
Andreas Troelsen | 490df61375 | |
Andreas Troelsen | 95b65371f1 | |
Andreas Troelsen | f1cfd4136e | |
Andreas Troelsen | b99713f1ec | |
Andreas Troelsen | 903752d23a | |
Andreas Troelsen | 614da20df8 | |
Andreas Troelsen | 2c2c36b880 | |
Andreas Troelsen | 498cef9ec7 | |
Andreas Troelsen | 30c059f51b | |
Andreas Troelsen | b08c74afbd | |
Andreas Troelsen | c7e740040c | |
Andreas Troelsen | 9081ec8055 | |
Andreas Troelsen | 0da90f3963 | |
Andreas Troelsen | cf296704b0 | |
Andreas Troelsen | 9bc23c61a0 | |
Andreas Troelsen | 3bef4948a6 | |
Andreas Troelsen | 5f62b8013b | |
Andreas Troelsen | 532a2ce0da | |
Andreas Troelsen | 07f0ce5d08 | |
Andreas Troelsen | d960aebbb0 | |
Andreas Troelsen | 4ceb5b7bec | |
Andreas Troelsen | 6c693294bd | |
Andreas Troelsen | 70e51d24fb | |
Andreas Troelsen | 7288fc566b | |
Andreas Troelsen | 31282014b3 | |
Andreas Troelsen | 2a87aef9f3 | |
Andreas Troelsen | 06cedde031 | |
Andreas Troelsen | 0f93d8ac05 | |
Andreas Troelsen | fdb84dfaf4 | |
Andreas Troelsen | f10c7e464c | |
Andreas Troelsen | 037c2ffa43 | |
Andreas Troelsen | 5bcab8fa46 | |
Andreas Troelsen | 519886cf3e | |
Andreas Troelsen | 1b46e17e38 | |
Andreas Troelsen | aed57f5cb6 | |
dependabot[bot] | 5ebdf45f6a | |
Andreas Troelsen | efc66820c6 | |
Andreas Troelsen | dafae0cf07 | |
Andreas Troelsen | 3b132d28dd | |
Andreas Troelsen | d30bd96a2a | |
Andreas Troelsen | 994ebaff81 | |
Andreas Troelsen | bff1ab5694 | |
Andreas Troelsen | dd54f70682 | |
Andreas Troelsen | 2d0aad19d6 | |
Andreas Troelsen | 5566d8fd86 | |
Andreas Troelsen | e8bb8a9e4d | |
Andreas Troelsen | e1784552de | |
Andreas Troelsen | 4f11889549 | |
Andreas Troelsen | 31010c1576 | |
Andreas Troelsen | 514c03dad0 | |
Andreas Troelsen | 043d970593 | |
Andreas Troelsen | 64d55673a4 | |
Andreas Troelsen | b343976dc8 | |
Andreas Troelsen | 7f7fb1631c | |
Andreas Troelsen | 84a7a2ed8a | |
Andreas Troelsen | 7fc0473a72 | |
Andreas Troelsen | b0969c655c | |
Andreas Troelsen | 6de0a2fa83 | |
Andreas Troelsen | c682e45714 | |
Andreas Troelsen | 962eb7aaba | |
Andreas Troelsen | 684116918a | |
Andreas Troelsen | 62496127d7 | |
Andreas Troelsen | 58cb29ae97 | |
Nesseley | 7e37d93a0e | |
Nesseley | e8cce8e620 | |
Andreas Troelsen | ce9f07e6b5 | |
Andreas Troelsen | 787f1120d1 | |
Nesseley | 5e7485682a | |
Andreas Troelsen | 8426be46fb | |
Andreas Troelsen | 9245e53509 | |
Andreas Troelsen | c9b0f85bee | |
Andreas Troelsen | 299a16ca8c | |
Chew | c9b36f4993 | |
Andreas Troelsen | 5369b1fbfa | |
Andreas Troelsen | 0237dcdbd7 | |
Andreas Troelsen | c2cd5ae219 | |
Andreas Troelsen | a9d39fe345 | |
Andreas Troelsen | 9fcfad3748 | |
Andreas Troelsen | 7547e74278 | |
Andreas Troelsen | 15736fcaa0 | |
Andreas Troelsen | 86be8bdfbd | |
Bobcat00 | 90d1f211fa | |
Andreas Troelsen | 5f7be69f0f | |
Andreas Troelsen | 01b253d304 | |
Bobcat00 | 6e8a3e1626 | |
Andreas Troelsen | 8f9664670f | |
Andreas Troelsen | 5c30164ba7 | |
Andreas Troelsen | 14ad15b15d | |
Andreas Troelsen | 3a017b179d | |
Andreas Troelsen | b9e1e07a15 |
|
@ -0,0 +1,79 @@
|
|||
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@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: 'Build'
|
||||
run: ./gradlew build --no-daemon
|
||||
|
||||
- name: 'Upload artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
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 }}
|
|
@ -0,0 +1,77 @@
|
|||
name: publish-curseforge
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- 'released'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'The tag name of the release to publish'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.release.tag_name || inputs.tag_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download release assets
|
||||
run: gh release download "${TAG_NAME}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Publish to CurseForge
|
||||
run: |
|
||||
echo 'Extract release notes'
|
||||
changelog=$(scripts/extract-release-notes -f curse "${TAG_NAME}")
|
||||
|
||||
echo 'Look up game version IDs'
|
||||
game_version_type_id=1
|
||||
game_version_names='"1.20","1.19","1.18","1.17","1.16","1.15","1.14","1.13"'
|
||||
|
||||
type_condition="(.gameVersionTypeID == ${game_version_type_id})"
|
||||
name_condition="(.name | startswith(${game_version_names}))"
|
||||
|
||||
game_version_ids=$(
|
||||
curl -s -X GET 'https://minecraft.curseforge.com/api/game/versions' \
|
||||
-H "X-Api-Token: ${{ secrets.CURSEFORGE_TOKEN }}" \
|
||||
| jq -c ".[] | select(${type_condition} and ${name_condition}) | .id" \
|
||||
| paste -sd, - \
|
||||
)
|
||||
|
||||
echo 'Create metadata file'
|
||||
cat << EOF > metadata.jq
|
||||
{
|
||||
changelog: \$changelog,
|
||||
changelogType: "html",
|
||||
displayName: \$displayName,
|
||||
gameVersions: \$gameVersions,
|
||||
releaseType: "beta"
|
||||
}
|
||||
EOF
|
||||
|
||||
jq -c -n \
|
||||
--arg changelog "${changelog}" \
|
||||
--arg displayName "MobArena v${TAG_NAME}" \
|
||||
--argjson gameVersions "[${game_version_ids}]" \
|
||||
-f metadata.jq \
|
||||
> metadata.json
|
||||
|
||||
echo 'Publish build to CurseForge'
|
||||
base_url='https://minecraft.curseforge.com'
|
||||
project_id=31265
|
||||
|
||||
curl -s -X POST "${base_url}/api/projects/${project_id}/upload-file" \
|
||||
-H "X-Api-Token: ${{ secrets.CURSEFORGE_TOKEN }}" \
|
||||
-F 'metadata=<metadata.json' \
|
||||
-F "file=@MobArena-${TAG_NAME}.jar"
|
|
@ -0,0 +1,79 @@
|
|||
name: publish-hangar
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- 'released'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'The tag name of the release to publish'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.release.tag_name || inputs.tag_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download release assets
|
||||
run: gh release download "${TAG_NAME}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Publish to Hangar
|
||||
run: |
|
||||
echo 'Extract release notes'
|
||||
changelog=$(scripts/extract-release-notes -f hangar "${TAG_NAME}")
|
||||
|
||||
echo 'Create version upload file'
|
||||
cat << EOF > version-upload.jq
|
||||
{
|
||||
version: \$version,
|
||||
channel: "Release",
|
||||
description: \$changelog,
|
||||
platformDependencies: {
|
||||
"PAPER": [
|
||||
"1.13.x",
|
||||
"1.14.x",
|
||||
"1.15.x",
|
||||
"1.16.x",
|
||||
"1.17.x",
|
||||
"1.18.x",
|
||||
"1.19.x",
|
||||
"1.20.x"
|
||||
]
|
||||
},
|
||||
pluginDependencies: {},
|
||||
files: [
|
||||
{ platforms: ["PAPER"] }
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
jq -c -n \
|
||||
--arg version "${TAG_NAME}" \
|
||||
--arg changelog "${changelog}" \
|
||||
-f version-upload.jq \
|
||||
> version-upload.json
|
||||
|
||||
echo 'Authenticate with Hangar'
|
||||
base_url='https://hangar.papermc.io/api/v1'
|
||||
key=${{ secrets.HANGAR_TOKEN }}
|
||||
jwt=$(curl -s -X POST "${base_url}/authenticate?apiKey=${key}" | jq -r '.token')
|
||||
|
||||
echo 'Publish build to Hangar'
|
||||
project_slug='MobArena'
|
||||
curl -s -X POST "${base_url}/projects/${project_slug}/upload" \
|
||||
-H "Authorization: ${jwt}" \
|
||||
-F 'versionUpload=<version-upload.json;type=application/json' \
|
||||
-F "files=@MobArena-${TAG_NAME}.jar"
|
|
@ -1,40 +1,11 @@
|
|||
# IDE files and folders
|
||||
.project
|
||||
.classpath
|
||||
*.iml
|
||||
.idea/
|
||||
.settings/
|
||||
|
||||
# Compiled files
|
||||
# Java
|
||||
*.class
|
||||
|
||||
# OS files.
|
||||
.DS_Store*
|
||||
Thumbs.db
|
||||
|
||||
# Misc folders
|
||||
bin/
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
misc/
|
||||
target/
|
||||
|
||||
# Misc files
|
||||
MobArena.jar
|
||||
build.properties
|
||||
|
||||
# Virtualenv (for docs)
|
||||
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
|
||||
.Python
|
||||
[Bb]in
|
||||
[Ii]nclude
|
||||
[Ll]ib/python*/
|
||||
[Ll]ib64
|
||||
[Ll]ocal
|
||||
[Mm]an
|
||||
[Ss]cripts
|
||||
pyvenv.cfg
|
||||
.venv
|
||||
pip-selfcheck.json
|
||||
|
||||
# ReadTheDocs generated documentation
|
||||
docs/_build
|
||||
# IntelliJ
|
||||
.idea/
|
||||
*.iml
|
||||
out/
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
language: java
|
||||
jdk:
|
||||
- openjdk8
|
|
@ -1,4 +1,4 @@
|
|||
MobArena [![Build Status](https://travis-ci.org/garbagemule/MobArena.svg?branch=master)](https://travis-ci.org/garbagemule/MobArena) [![Documentation Status](https://readthedocs.org/projects/mobarena/badge/?version=latest)](http://mobarena.readthedocs.io/en/latest/?badge=latest)
|
||||
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 is an arena-style minigame for Spigot-based Minecraft servers
|
||||
|
@ -11,6 +11,7 @@ one of these two sites:
|
|||
|
||||
- [Bukkit](https://dev.bukkit.org/projects/mobarena)
|
||||
- [Spigot](https://www.spigotmc.org/resources/34110/)
|
||||
- [Hangar](https://hangar.papermc.io/garbagemule/MobArena)
|
||||
|
||||
The wiki here on Github should have all the information you need to get
|
||||
started using the plugin.
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
plugins {
|
||||
id("java-library")
|
||||
id("com.github.johnrengelman.shadow") version "8.1.1"
|
||||
}
|
||||
|
||||
group = "com.garbagemule"
|
||||
version = "0.108"
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
|
||||
maven("https://jitpack.io")
|
||||
maven("https://repo.maven.apache.org/maven2/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.spigotmc:spigot-api:1.19-R0.1-SNAPSHOT")
|
||||
compileOnly("com.github.MilkBowl:VaultAPI:1.7.1")
|
||||
api("org.bstats:bstats-bukkit:2.2.1")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.hamcrest:hamcrest-all:1.3")
|
||||
testImplementation("org.mockito:mockito-core:3.12.4")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(8))
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
test {
|
||||
// This is a bit of a hack. It ensures that the dependencies for the
|
||||
// "main" source set are available to the test suite. Without it, the
|
||||
// dependencies would have to be listed twice, and even that doesn't
|
||||
// seem to work with the currently used version of Mockito.
|
||||
configurations.testImplementation.configure {
|
||||
extendsFrom(configurations.compileOnly.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
filesMatching("plugin.yml") {
|
||||
expand("project" to mapOf(
|
||||
"name" to "MobArena",
|
||||
"version" to version,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
minimize()
|
||||
|
||||
relocate("org.bstats", "com.garbagemule.MobArena.metrics")
|
||||
|
||||
archiveBaseName = "MobArena"
|
||||
archiveClassifier = ""
|
||||
}
|
||||
|
||||
// We're using shadowJar, so we can skip the regular jar task.
|
||||
jar { enabled = false }
|
||||
|
||||
// Let the build task produce the final artifact.
|
||||
build { dependsOn(shadowJar) }
|
||||
}
|
121
changelog.md
121
changelog.md
|
@ -12,6 +12,120 @@ These changes will (most likely) be included in the next version.
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.108] - 2024-01-01
|
||||
### Added
|
||||
- Support for chest references in item syntax. The new `inv` syntax allows for referencing container indices in the config-file. This should help bridge the gap between class chests and various other parts of the config-file, such as rewards and upgrade waves.
|
||||
- Support for saved items. The new `/ma save-item` command can be used to save the currently held item to disk, which allows it to be used in various places in the config-file. This should help bridge the gap between the config-file and class chests for config-file centric setups.
|
||||
- New per-arena setting `monster-teleporting` allows monsters to teleport _inside_ the arena region. This should allow for stuff like `/tp` commands and for Endermen to "do their thing".
|
||||
- New permission `mobarena.admin.errors` for better error visibility. Players with this permission will get a message if an arena encounters an error. Currently, the only such error is the one resulting from Spigot's "max max health" setting throwing an exception when monster health is set "too high".
|
||||
|
||||
### Fixed
|
||||
- Explosion damage caused by Exploding Sheep now correctly counts as monster damage. This means that the explosions only affect other mobs if the per-arena setting `monster-infight` is set to `true`.
|
||||
- Explosion damage caused by the boss ability `obsidian-bomb` now correctly counts as monster damage. This means that the explosions only affect other mobs if the per-arena setting `monster-infight` is set to `true`.
|
||||
- An old discrepancy with auto start timers in the auto-ready logic has been removed. This fixes an issue in MobArenaStats where the extension would throw errors in arenas with `auto-ready: true` and a non-zero auto start timer. Note that the combination of `auto-ready: true` and a `default-class` now _requires_ the use of a `start-delay-timer` to prevent the arena from starting immediately when the first player joins.
|
||||
- Signs in arena regions, as well as Arena Signs anywhere, can no longer be edited by right-clicking.
|
||||
- Signs now correctly restore themselves again in arenas with `soft-restore: true`.
|
||||
|
||||
## [0.107] - 2022-07-30
|
||||
### Added
|
||||
- New monster variant `angry-bees` can be used to spawn angry bees.
|
||||
- Husks, drowned, piglins, hoglins, and zoglins can now be spawned in their baby versions using the `baby` prefix seen on other monster types (e.g. `baby-zombie`).
|
||||
- Pet names are now per-class configurable via the optional `pet-name` property, which defaults to `<display-name>'s pet` (the `<player-name>` variable is also supported).
|
||||
- New per-arena setting `auto-leave-on-end` can be used to automatically "kick" spectators when the current session ends.
|
||||
- New per-arena setting `clear-wave-leeway` allows for tweaking the number of mobs allowed to be alive before the next wave spawns. The setting affects `clear-wave-before-next`, `clear-wave-before-boss`, and the final wave check, and it defaults to 0.
|
||||
- New per-arena setting `auto-ignite-fuse` makes the fuse time for auto-ignited TNT configurable. The unit is ticks and defaults to 80.
|
||||
- Added boss abilities `disorient-all`, `fetch-all`, `pull-all`, and `throw-all`. These abilities work like their target-specific and distance-based counterparts, but affect all players in the arena.
|
||||
- (API) MobArena's internal command handler now supports registering pre-instantiated subcommand instances. This should make it easier for extensions to avoid the Singleton anti-pattern for command dependencies.
|
||||
- (API) MobArena now fires MobArenaPreReloadEvent and MobArenaReloadEvent before and after, respectively, reloading its config-file. This should allow extensions and other plugins to better respond to configuration changes.
|
||||
|
||||
### Changed
|
||||
- MobArena now targets the Minecraft 1.19 version of the Spigot API (but still works on 1.13-1.18). This should make it easier to tackle feature requests and bug reports related to modern Minecraft.
|
||||
- Monsters are no longer stripped of the _weapons_ they spawn with naturally, only their _armor_. This should improve forwards compatibility with new weapon-reliant monsters.
|
||||
- The regex pattern for the player list command is now less greedy, so it will only match on `/ma players`, `/ma playerlist`, and `/ma player-list`. The previous pattern matched on anything that starts with `player`, which rendered the `/ma player-stats` command in MobArenaStats impossible to invoke.
|
||||
|
||||
### Fixed
|
||||
- Pillagers and vindicators no longer spawn without their much-needed weapons.
|
||||
- Piglins, piglin brutes, and hoglins no longer zombify. This fixes a bug where the mobs would despawn due to the zombification process.
|
||||
- Zombies, husks, drowned, zombie villagers, piglins, hoglins, and zoglins without the `baby` prefix are now forced into adulthood to prevent them from occasionally spawning as babies.
|
||||
- Evokers are once again capable of spawning vexes on 1.18.1+.
|
||||
- Reward groups with `nothing` in them no longer cause errors when earned/granted.
|
||||
- The title-based announcer and the title-based boss health bar have been fixed to work with the breaking change to the Title API in Spigot 1.17.
|
||||
- Arena Signs now correctly update for arenas that don't have `kebab-case` names in the config-file.
|
||||
- Block explosion events cancelled by other plugins now remain cancelled unless MobArena specifically uncancels them for an arena.
|
||||
- Flaming arrows now ignite TNT blocks in the arena.
|
||||
- Players no longer take fall damage when they leave (or get removed from) an arena while falling.
|
||||
- Players no longer take damage from projectiles shot by pets of other players.
|
||||
- Normal shulker boxes are now properly removed from inventories of players using the My Items class.
|
||||
- Class pets are now correctly removed from the arena when their owner dies, rather than when they leave.
|
||||
- MobArena no longer nags players with the `mobarena.admin.teleport` permission when they engage in a teleport that would have otherwise been blocked.
|
||||
- MobArena now correctly sets the source property on auto-ignited TNT.
|
||||
|
||||
## [0.106] - 2021-05-09
|
||||
### Added
|
||||
- It is now possible to write custom formulas for wave growth in Default Wave, swarm amounts in Swarm Waves, and boss health in Boss Waves, allowing for much more control and fine-tuning. The formulas support various session-related variables as well as various mathematical operators and functions. Formulas can be predefined as macros in the new `formulas.yml` file. Check the wiki for details.
|
||||
|
||||
### Changed
|
||||
- bStats Metrics client updated to 2.2.1.
|
||||
- MobArena now uses Github Actions instead of Travis CI. This should make it easier to get development builds directly from Github for those interested.
|
||||
|
||||
### Fixed
|
||||
- Arena signs in unloaded or missing worlds no longer break the startup procedure. Sign data is stored in a new format that MobArena will automatically migrate to on a per-world basis during startup.
|
||||
|
||||
## [0.105] - 2020-11-08
|
||||
### Minor breaking changes
|
||||
MobArena 0.105 includes a rework of how arenas and classes are referenced, both internally in the plugin, but also in permissions, commands, etc.
|
||||
Instead of the ambiguous and arbitrary "config names", MobArena now uses "slugs", which just means a `kebab-case` version of a name.
|
||||
For example, the slug for an arena named "Castle of Doom" will be `castle-of-doom`, and the slug for the implicit "My Items" class is `my-items`.
|
||||
All commands that take arena or class names as arguments will tab complete slugs, so they should be fairly easy to figure out.
|
||||
|
||||
The goal of this change is to make the plugin more consistent about arena and class references in commands, permissions, etc., as well as to allow for multi-word names.
|
||||
Backwards compatibility has taken a backseat for the sake of maintainability and clarity, so certain adjustments may be necessary in some setups:
|
||||
|
||||
- **Permissions:** All permissions for arenas and classes must be changed to the new slug-based permission keys. For example, the permission for the "My Items" class is now `mobarena.classes.my-items`.
|
||||
- **Class signs:** It may be necessary to recreate the signs for classes with multi-word names. The name on the sign should match the name in the config-file (or the slug). For example, `My Items` (or `my-items`) instead of `Myitems`.
|
||||
- **Default classes:** The per-arena setting `default-class` may need to be adjusted. The class name should match the name in the config-file (or the slug). For example, if the default class is "My Items", the value should be `My Items` (or `my-items`).
|
||||
- **Custom integrations:** Custom commands or integrations may need to be adjusted accordingly. If you're running a setup like that, you probably already know what you're doing and what you need to do. If not, hop on Discord.
|
||||
|
||||
### Added
|
||||
- A new `ready` state is now available for arena sign templates. Signs are in this state when all players in the lobby have readied up, but the arena has not yet started due to a start delay timer. Check the wiki for details.
|
||||
- Arena signs now support dynamic list entry variables for 4 different player lists. As an example, `<notready-1>` results in the name of a player in the lobby who hasn't readied up yet. This is useful for visualizing who is holding up the lobby. Check the wiki for details.
|
||||
- Elytra are now supported chest pieces in class chests.
|
||||
- Boss names now support color codes.
|
||||
- New per-arena setting `arena-warp-offset` can be used to spread out players randomly by an offset from the arena warp. This should help prevent players taking suffocation damage.
|
||||
- New per-arena setting `announcer-type` determines where to display per-arena announcements such as wave spawns, auto start timers, boss abilities, and death messages. Options are `title` (default) or `chat`.
|
||||
- It is now possible to group rewards. For example, `all(stick, bone)` results a stick and a bone, while `random(all(stick, bone), all(dirt, stone))` results in getting _either_ a stick and a bone _or_ a dirt block and a stone block.
|
||||
- The new `nothing` keyword can be used to _not_ grant a reward. This can be used in a crude way to create "loot table"-style reward systems where there is a _chance_ that something is reward, but it might also just be nothing.
|
||||
- Boss rewards also support the `all()` and `random()` functions as well as the `nothing` keyword.
|
||||
- New command `/ma addreward <player> <thing>` can be used to add a reward to an arena player's reward list. This can be useful for hooking into the rewards system from scripts or other plugins.
|
||||
- The `/ma addarena` and `/ma autogenerate` commands now supports multi-word arena names.
|
||||
|
||||
### Changed
|
||||
- The Root Target ability now uses potion effects (slowness, slow falling, and negative jump boost) instead of repeated teleports. This should make for a smoother root experience.
|
||||
- Permissions for arenas and classes are now based on "slugs". It is now possible to configure permissions for arenas and classes with multi-word names (including "My Items"). Check the Permissions page on the wiki for details.
|
||||
- Commands that resolve arena and/or class names now consistently resolve and tab complete "slugs" instead of arbitrarily "squashed" names. This greatly improves support for multi-word names.
|
||||
- The class signs generated by the `/ma autogenerate` command now use class names from the config-file instead of arbitrarily "squashed" names.
|
||||
- Leaderboards now use arena and class names from the config-file instead of arbitrarily "prettified" names.
|
||||
- Using `spectate-on-death: true` no longer forces players out to their join location/exit warp before moving them to the spectator area. This should prevent "jumpy" behavior in multi-world setups.
|
||||
- Config-file errors imposed by incorrect usage of `/ma setting` no longer cause "internal errors". Instead, the errors are properly communicated in the command output similar to how the `/ma reload` command works.
|
||||
- Guardians and elder guardians no longer instantly retarget players when they break line of sight. This should make their behavior work a bit closer to vanilla.
|
||||
- (API) MobArenaHandler now returns class name slugs in the `getPlayerClass()` methods.
|
||||
|
||||
### Fixed
|
||||
- Elytra and Netherite armor pieces now correctly auto-equip if specified in the generic `armor` node in classes in the config-file.
|
||||
- Players should now properly respawn at the spectator area rather than at world spawn on servers with plugins that override respawn locations.
|
||||
- Config-files with missing `pet-items` nodes no longer errors. A missing `pet-items` node in `global-settings` is treated as empty, i.e. no pet items will be registered.
|
||||
- The `player-time-in-arena` setting has been fixed.
|
||||
- The `soft-restore` setting has been fixed for blocks broken by players. Note that the functionality is still unreliable for non-trivial blocks.
|
||||
- Items in class chests are now cloned before they are made unbreakable and given to players. This fixes an issue where setting `unbreakable-weapons: false` had no effect on the items. Note that any affected items in existing class chests will need to be replaced.
|
||||
- (1.8) Potions no longer turn into water bottles.
|
||||
|
||||
### Removed
|
||||
- The MagicSpells integration has been removed. This means that the extra `magicspells.yml` config-file (if it exists) no longer does anything and can be removed.
|
||||
|
||||
## [0.104.2] - 2020-01-03
|
||||
- The region overlap check now works across both arena and lobby regions, i.e. all four combinations of intersections between two regions (arena-arena, arena-lobby, lobby-arena, and lobby-lobby) are evaluated.
|
||||
- Arenas with missing regions no longer cause errors in the region overlap check.
|
||||
|
||||
## [0.104.1] - 2019-12-31
|
||||
- It is no longer necessary to have recurrent waves for an arena to work. MobArena automatically creates a "catch all" recurrent wave in case the arena session reaches a wave number that isn't covered by any other wave definitions.
|
||||
- Entities outside of the arena can no longer target players, pets, or monsters inside of the arena.
|
||||
|
@ -147,7 +261,12 @@ Thanks to:
|
|||
- Swatacular for help with testing bug fixes
|
||||
- Haileykins for contributions to the code base
|
||||
|
||||
[Unreleased]: https://github.com/garbagemule/MobArena/compare/0.104.1...HEAD
|
||||
[Unreleased]: https://github.com/garbagemule/MobArena/compare/0.108...HEAD
|
||||
[0.108]: https://github.com/garbagemule/MobArena/compare/0.107...0.108
|
||||
[0.107]: https://github.com/garbagemule/MobArena/compare/0.106...0.107
|
||||
[0.106]: https://github.com/garbagemule/MobArena/compare/0.105...0.106
|
||||
[0.105]: https://github.com/garbagemule/MobArena/compare/0.104.2...0.105
|
||||
[0.104.2]: https://github.com/garbagemule/MobArena/compare/0.104.1...0.104.2
|
||||
[0.104.1]: https://github.com/garbagemule/MobArena/compare/0.104...0.104.1
|
||||
[0.104]: https://github.com/garbagemule/MobArena/compare/0.103.2...0.104
|
||||
[0.103.2]: https://github.com/garbagemule/MobArena/compare/0.103.1...0.103.2
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = MobArena
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
180
docs/conf.py
180
docs/conf.py
|
@ -1,180 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# MobArena documentation build configuration file, created by
|
||||
# sphinx-quickstart on Wed Nov 29 13:24:13 2017.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ['sphinx.ext.todo']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'MobArena'
|
||||
copyright = '2017, garbagemule, Justin W. Flory (jflory7), MobArena contributors'
|
||||
author = 'garbagemule, Justin W. Flory (jflory7), MobArena contributors'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.99'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.99.1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
# html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# This is required for the alabaster theme
|
||||
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
|
||||
# html_sidebars = {
|
||||
# '**': [
|
||||
# 'relations.html', # needs 'show_related': True theme option to display
|
||||
# 'searchbox.html',
|
||||
# ]
|
||||
# }
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'MobArenadoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'MobArena.tex', 'MobArena Documentation',
|
||||
'garbagemule, Justin W. Flory (jflory7), MobArena contributors', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'mobarena', 'MobArena Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'MobArena', 'MobArena Documentation',
|
||||
author, 'MobArena', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
# -- Use the RTD Sphinx Theme -------------------------------------------
|
||||
|
||||
import sphinx_rtd_theme
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# RTD theme options (see theme.conf for more information)
|
||||
html_theme_options = {
|
||||
'canonical_url': "https://mobarena.readthedocs.io/en/latest/",
|
||||
'collapse_navigation': False,
|
||||
'navigation_depth': 2,
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
.. MobArena documentation master file, created by
|
||||
sphinx-quickstart on Wed Nov 29 13:24:13 2017.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to MobArena's documentation!
|
||||
====================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:name: dev
|
||||
:caption: Developer resources
|
||||
:glob:
|
||||
|
||||
dev/*
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:name: user
|
||||
:caption: User documentation
|
||||
:glob:
|
||||
|
||||
user/*
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
|
@ -1,36 +0,0 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
set SPHINXPROJ=MobArena
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
|
@ -1,3 +0,0 @@
|
|||
## Theme for Sphinx documentation
|
||||
Sphinx
|
||||
sphinx_rtd_theme
|
|
@ -1,11 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
#
|
||||
# Script to automatically build and test the Sphinx documentation currently in
|
||||
# the repo. This script should always be run before submitting a new pull
|
||||
# request.
|
||||
#
|
||||
# If you're on Windows, please use the `make.bat` script in `docs/` directory.
|
||||
#
|
||||
|
||||
make clean html
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
####################
|
||||
Announcement strings
|
||||
####################
|
||||
|
||||
MobArena supports custom strings for announcement and messages. This lets you
|
||||
create custom messages unique to your server based on certain actions, like a
|
||||
game ending. The ``announcements.yml`` file stores MobArena announcement and
|
||||
message strings. [#]_
|
||||
|
||||
.. [#] Default strings are found in the `Msg.java`_ class
|
||||
|
||||
|
||||
*************
|
||||
Color support
|
||||
*************
|
||||
|
||||
Color codes are supported. **To add color to a message**, use an ampersand
|
||||
(``&``) followed by a valid color code. `Color codes`_ are available in the
|
||||
Gamepedia Minecraft wiki. Examples are in the default file.
|
||||
|
||||
|
||||
*********
|
||||
Variables
|
||||
*********
|
||||
|
||||
Some announcements use a variable. In the string, variables are represented by a
|
||||
percent sign (``%``). Remove it if you do not want a variable in the message.
|
||||
|
||||
There are some limitations to variables:
|
||||
|
||||
- Not possible to add variables to announcements that don't take them by default
|
||||
- Announcements that take one variable cannot take more than that variable
|
||||
|
||||
|
||||
***********************
|
||||
Disable an announcement
|
||||
***********************
|
||||
|
||||
If you want to **disable a specific announcement**, set its value to two single
|
||||
quotes (``''``). MobArena ignores announcements set to an empty value. To
|
||||
disable one, you must override it. Deleting the option does not disable an
|
||||
announcement since MobArena adds them in by default.
|
||||
|
||||
|
||||
********
|
||||
Examples
|
||||
********
|
||||
|
||||
Three examples below show all of the features explained above.
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 2, 5, 8
|
||||
|
||||
# Use a red message for the start of a new game
|
||||
arena-start: 'Let the games begin! &cMay the odds be ever in your favor!'
|
||||
|
||||
# Use a variable for the number of seconds until the game begins
|
||||
arena-auto-start: 'Arena will auto-start in &c%&r seconds.'
|
||||
|
||||
# Turn off the golem-died message
|
||||
golem-died: ''
|
||||
|
||||
.. _`Msg.java`: https://github.com/garbagemule/MobArena/blob/master/src/main/java/com/garbagemule/MobArena/Msg.java
|
||||
.. _`color codes`: https://minecraft.gamepedia.com/Formatting_codes#Color_codes
|
|
@ -1,277 +0,0 @@
|
|||
###########
|
||||
Arena setup
|
||||
###########
|
||||
|
||||
This page explains how to set up an arena, from defining it to configuring it.
|
||||
|
||||
|
||||
*****************
|
||||
Building an arena
|
||||
*****************
|
||||
|
||||
There are four key parts to an arena:
|
||||
|
||||
#. `Lobby`_
|
||||
#. `Arena floor`_
|
||||
#. `Spectator area`_
|
||||
#. `Exit point`_
|
||||
|
||||
Lobby
|
||||
=====
|
||||
|
||||
A lobby is where players choose a class before joining an arena. It's also the
|
||||
"waiting area" before a new match begins.
|
||||
|
||||
Lobbies have two requirements:
|
||||
|
||||
- **Class selection signs**: Clicked to select a class
|
||||
- **Iron block**: Clicked to mark self as "ready"
|
||||
|
||||
Class selection signs must have the name of the class on the first line
|
||||
(case-sensitive). The last three lines are not checked and they can have any
|
||||
text.
|
||||
|
||||
You will not receive a confirmation message after making a new sign since
|
||||
MobArena does not register the creation of class selection signs. If you get a
|
||||
confirmation message, another plugin is interferring.
|
||||
|
||||
Arena floor
|
||||
===========
|
||||
|
||||
An arena floor is where the action happens. Players fight through mob waves on
|
||||
the arena floor. How the arena floor looks is up to you, but we recommend these
|
||||
minimum requirements:
|
||||
|
||||
- Closed in by walls on all sides
|
||||
- Have a ceiling / roof
|
||||
|
||||
This prevents players and mobs from escaping and also blocks players from
|
||||
wandering out of the arena with class items.
|
||||
|
||||
Spectator area
|
||||
==============
|
||||
|
||||
A spectator area lets non-players watch an on-going match. The ``/ma spec``
|
||||
command teleports a player into the spectator area. If configured, [#]_ players
|
||||
warp into the spectator arena when they die.
|
||||
|
||||
Design the area so spectators cannot escape the spectator area, since they are
|
||||
invincible. Spectators should *not* enter the arena floor or exit the spectator
|
||||
area on foot.
|
||||
|
||||
.. [#] Set ``spectate-after-death`` to ``true`` in the config file to force
|
||||
players to the spectator area after dying
|
||||
|
||||
Exit point
|
||||
==========
|
||||
|
||||
When players leave the arena or when the last player standing dies, arena
|
||||
players and spectators teleport to the location they joined from. Optionally, an
|
||||
arena can have an exit warp. This controls where players go after leaving a
|
||||
match.
|
||||
|
||||
|
||||
**************
|
||||
Defining areas
|
||||
**************
|
||||
|
||||
By now, you have a full arena map and you are ready to configure it in MobArena.
|
||||
When an area is built, it must be **defined** by MobArena. This tells MobArena
|
||||
where the arena boundaries are, such as whether an area is a lobby or the arena
|
||||
floor.
|
||||
|
||||
MobArena has four types of areas:
|
||||
|
||||
#. `Regions`_
|
||||
#. `Warps`_
|
||||
#. `Spawnpoints`_
|
||||
#. `Containers`_
|
||||
|
||||
Regions
|
||||
=======
|
||||
|
||||
Arenas **must** have an arena region and optionally a lobby region. Regions are
|
||||
set using the Regions tools. MobArena uses the arena region to…
|
||||
|
||||
- Stop cheating by kicking players if they leave the arena region
|
||||
- Only spawn MobArena mobs inside arena region
|
||||
|
||||
Warps
|
||||
=====
|
||||
|
||||
Players teleport to different warps for different events. There are four warps
|
||||
used in MobArena:
|
||||
|
||||
- **Lobby warp**: Warp location for players joining a new match; leave when all
|
||||
players "ready up" or match countdown timer ends
|
||||
- **Arena warp**: Warp location for players to spawn in the arena floor
|
||||
- **Spectator warp**: Warp location for spectators to watch an on-going match
|
||||
- **Exit warp**: Optional warp location for players to teleport to after a match
|
||||
finishs or they leave the arena
|
||||
|
||||
Spawnpoints
|
||||
===========
|
||||
|
||||
Mobs spawn at the spawnpoint(s) of an area. MobArena only uses spawnpoints in a
|
||||
*15-block radius* from any player. An arena can have multiple spawnpoints.
|
||||
Spawnpoints are added using the Spawnpoints tool.
|
||||
|
||||
The number of mobs spawned is not determined by the number of spawnpoints, but
|
||||
actual formulas. See :doc:`wave-formulas` for more information.
|
||||
|
||||
Containers
|
||||
==========
|
||||
|
||||
Containers are locations of chests, dispensers, or other containers with
|
||||
renewable contents. Any containers added to an arena must be registered using
|
||||
the Chests tool.
|
||||
|
||||
|
||||
**********
|
||||
Setup Mode
|
||||
**********
|
||||
|
||||
Configure a new arena with *Setup Mode*. Setup Mode is a special mode that
|
||||
temporarily stores inventory and gives an administrator a set of golden tools.
|
||||
The golden tools are called the `Toolbox`_.
|
||||
|
||||
Flying is enabled to simplify arena setup. Talking in server chat is also
|
||||
disabled because Setup Mode starts an isolated conversation with the
|
||||
administrator (explained below).
|
||||
|
||||
- **Create a new arena**: ``/ma addarena <arena name>``
|
||||
- **Enter Setup Mode**: ``/ma setup <arena name>`` [#]_
|
||||
- **Leave Setup Mode**: ``done`` (no slash)
|
||||
- **Delete an arena**: ``/ma delarena <arena name>`` [#]_
|
||||
|
||||
.. [#] If you only have one arena, you don't have to specify the arena name
|
||||
.. [#] An arena named ``default`` is created on first use. You can remove this
|
||||
arena if you want to use an arena with a different name.
|
||||
|
||||
Setup Mode commands
|
||||
===================
|
||||
|
||||
Setup Mode is an *isolated conversation*, which means Setup Mode intercepts
|
||||
everything an administrator types. This makes commands in Setup Mode shorter and
|
||||
prevents accidental use of other plugins.
|
||||
|
||||
Below is a list of all commands in Setup Mode:
|
||||
|
||||
+-------------------+-------------------------------------+------------+
|
||||
| Command | Description | Aliases |
|
||||
+===================+=====================================+============+
|
||||
| done | Leave Setup Mode | end, stop, |
|
||||
| | | done, quit |
|
||||
+-------------------+-------------------------------------+------------+
|
||||
| help | Display help screen | ?, h |
|
||||
+-------------------+-------------------------------------+------------+
|
||||
| missing | Display list of missing (mandatory) | miss |
|
||||
| | regions, warps, spawnpoints. Useful | |
|
||||
| | to check what is left to set up. | |
|
||||
+-------------------+-------------------------------------+------------+
|
||||
| expand | Expand region by some amount in a | exp |
|
||||
| ``<region>`` | given direction. *Example*: | |
|
||||
| ``<amount>`` | ``expand ar 5 up`` | |
|
||||
| ``<direction>`` | | |
|
||||
+-------------------+-------------------------------------+------------+
|
||||
| show | Show a region, warp, spawnpoint(s), | N/A |
|
||||
| ``[<region>|`` | or container as red wool blocks. | |
|
||||
| ``<warp>|`` | *Example*: ``show sp`` | |
|
||||
| ``<spawnpoint>|`` | | |
|
||||
| ``<container>]`` | | |
|
||||
+-------------------+-------------------------------------+------------+
|
||||
|
||||
- **Valid regions**: ``ar`` (arena region), ``lr`` (lobby region)
|
||||
- **Valid amounts**: Any positive integer (i.e. whole number)
|
||||
- **Valid directions**: ``up``, ``down``, ``out``
|
||||
- **Valid warps**: ``arena``, ``lobby``, ``spec``, ``exit``
|
||||
- **Valid spawnpoints**: ``spawns`` (or ``sp``)
|
||||
- **Valid containers**: ``chests`` (or ``c``)
|
||||
|
||||
Toolbox
|
||||
=======
|
||||
|
||||
The Toolbox is a set of golden tools. Each tool has a specific function. We use
|
||||
them to set up regions, warps, spawnpoints, and containers. Toolbox tools are
|
||||
used with either a left- or right-click.
|
||||
|
||||
Tool functions are also described in the *item tooltip* in your inventory.
|
||||
|
||||
Region tools
|
||||
------------
|
||||
|
||||
|r-icon|
|
||||
|
||||
Arena and lobby regions are defined with Region tools (golden axes). There are
|
||||
two golden axes in the Toolbox. One is for *arena setup* and the other is for
|
||||
*lobby setup*. The tools are named accordingly.
|
||||
|
||||
Region tools behave similarly to the WorldEdit wand (wooden axe). If you are
|
||||
familiar with regions in WorldEdit, Region tools should feel familiar.
|
||||
|
||||
- **Left-click**: Sets first point on clicked block
|
||||
- **Right-click**: Sets second point on clicked block
|
||||
|
||||
When both points are set, the region is defined. ``show ar`` (or ``show lr``)
|
||||
lets you check the region spans the desired area. If the region is too small,
|
||||
use the ``expand`` command (see above) to make it bigger.
|
||||
|
||||
The region must be three-dimensional (like a box) and not two-dimensional (flat
|
||||
rectangle). Make sure your arena floor is contained in the region selection
|
||||
(expanding a block or two below the floor is recommended).
|
||||
|
||||
Warp tool
|
||||
---------
|
||||
|
||||
|w-icon|
|
||||
|
||||
All warps are defined using the Warp tool (golden hoe). The tool defines any of
|
||||
the four types of warps depending which one is selected.
|
||||
|
||||
- **Left-click:** Set selected warp type on top of clicked block
|
||||
- **Right-click:** Cycle between warp types
|
||||
|
||||
A selected warp is placed on top of the clicked block. The direction you are
|
||||
looking is also taken into account.
|
||||
|
||||
Arena, lobby, and spectator warps are required. An exit warp is optional.
|
||||
|
||||
Spawnpoint tool
|
||||
---------------
|
||||
|
||||
|s-icon|
|
||||
|
||||
Spawnpoints are set up with the Spawnpoint tool (golden sword). The tool allows
|
||||
an administrator to set or remove spawnpoints for mobs.
|
||||
|
||||
- **Left-click:** Add spawnpoint on top of clicked block
|
||||
- **Right-click:** Remove spawnpoint on top of clicked block (if one exists)
|
||||
|
||||
A **high number of spawnpoints** is recommended. Mobs only spawn at spawnpoints
|
||||
within 15 blocks of a player. Every area in the arena should have one or more
|
||||
spawnpoints in a 15 block radius from each other.
|
||||
|
||||
If a player is not within 15 blocks of a spawnpoint, MobArena prints a warning
|
||||
to the console with coordinates. If no players are within 15 blocks of a
|
||||
spawnpoint, MobArena uses a random spawnpoint. This means mobs may spawn far
|
||||
away from players.
|
||||
|
||||
Container tool
|
||||
--------------
|
||||
|
||||
|c-icon|
|
||||
|
||||
Containers are set up with the Container tool (golden shovel). It works like the
|
||||
Spawnpoint tool, but checks that the clicked block is a valid container.
|
||||
|
||||
- **Left-click:** Register clicked container (if not registered)
|
||||
- **Right-click:** Unregister clicked container (if registered)
|
||||
|
||||
At the end of a match, a container is restored to its contents from the
|
||||
beginning of the match.
|
||||
|
||||
.. |r-icon| image:: http://puu.sh/4wwCH.png
|
||||
.. |w-icon| image:: http://puu.sh/4wwIB.png
|
||||
.. |s-icon| image:: http://puu.sh/4wwCJ.png
|
||||
.. |c-icon| image:: http://puu.sh/4wwIF.png
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
############
|
||||
Class chests
|
||||
############
|
||||
|
||||
**On this page:** \* `About Class Chests <#about-class-chests>`__ \*
|
||||
`Linked Class Chests <#linked-class-chests>`__
|
||||
|
||||
About Class Chests
|
||||
------------------
|
||||
|
||||
If some of your favorite items aren't supported by MobArena's internal
|
||||
[[item parser\|Item and Reward Syntax]], or if you just want to be able
|
||||
to configure your class items from in-game, the **class chests** may be
|
||||
what you're looking for!
|
||||
|
||||
--------------
|
||||
|
||||
**Note: The Class Chests will only work for arenas with
|
||||
``use-class-chests: true``, and the classes *MUST* exist in the
|
||||
config-file for MobArena to recognize them, however the items and armor
|
||||
lists can be empty.** \* \* \*
|
||||
|
||||
The idea behind the class chests is to simply place some **chests below
|
||||
the corresponding class signs** in the lobby, and fill them with
|
||||
whatever items you want the given class to have. When the players
|
||||
activate the class signs, the **contents of the chests are copied to the
|
||||
player inventory**. This suggests a type of "control room" setup, where
|
||||
an admin-only access room below the lobby contains the chests, allowing
|
||||
admins to warp down there and change the contents of the chests.
|
||||
|
||||
.. figure:: img/1.png
|
||||
:alt: Lobby and Control Room
|
||||
|
||||
Lobby and Control Room
|
||||
|
||||
For easier access and modification of the class chests, omitting the
|
||||
control room from the arena or lobby region may prove useful. Otherwise,
|
||||
arenas may have to be temporarily disabled or put into edit mode to
|
||||
allow warping to and changing the contents of the chests.
|
||||
|
||||
The class chests can be located **up to 6 blocks below the sign** itself
|
||||
or below the block right behind the sign (for wall signs, this would be
|
||||
the block the sign is attached to). The chest may also be in the block
|
||||
directly behind the sign itself - this is safe, because MobArena
|
||||
prevents players in the lobby from opening inventories, so if your lobby
|
||||
is in a tight spot, this might be the better option.
|
||||
|
||||
**Multiple sign rows:** It is possible to have two rows of class signs
|
||||
in the lobby and still use this feature. Simply place the class chest
|
||||
for the sign of the bottom row exactly at the 6-block limit, and the
|
||||
class chest for the sign of the top row one block up and behind the
|
||||
other chest (in a stair-like fashion). The blocks are searched in a
|
||||
vertical/pillar-like fashion, which is the reason this works.
|
||||
|
||||
.. figure:: img/2.png
|
||||
:alt: Chests Below
|
||||
|
||||
Chests Below
|
||||
|
||||
To get **auto-equipped armor** from the class chests, place the armor
|
||||
pieces in the **last four slots of the third row** in the chest.
|
||||
MobArena will check these four slots, and if any of them are armor
|
||||
pieces, they will be equipped. Note that the item placed in the very
|
||||
last slot (bottom right), will always be equipped as a helmet (this
|
||||
allows wool blocks, pumpkins, etc. to be used as helmets). The order of
|
||||
the other three slots doesn't matter.
|
||||
|
||||
The **fifth last slot**, right next to the armor slots, will be equipped
|
||||
as an **off-hand** item.
|
||||
|
||||
.. figure:: img/3.png
|
||||
:alt: Armor Slots
|
||||
|
||||
Armor Slots
|
||||
|
||||
The class chests are the best way to add items that are not currently
|
||||
supported by the MobArena [[item parser\|Item Syntax]]. This is because
|
||||
the class chests **simply copy the contents of the chests** to the
|
||||
player inventories, thus making any items supported by Bukkit supported
|
||||
by MobArena.
|
||||
|
||||
.. figure:: img/4.png
|
||||
:alt: Dyed Armor
|
||||
|
||||
Dyed Armor
|
||||
|
||||
Linked Class Chests
|
||||
-------------------
|
||||
|
||||
If per-arena class chest setups is too troublesome (e.g. if you have
|
||||
many arenas), if you don't need per-arena setups, or if you simply want
|
||||
a single, global class chest for each class, *linked class chests* are
|
||||
what you're looking for.
|
||||
|
||||
When you link a chest to a class, MobArena will always copy the contents
|
||||
of that chest to the player's inventory, when they pick the given class,
|
||||
regardless of any local class chests (note that the arena must still
|
||||
have ``use-class-chests: true``).
|
||||
|
||||
To link a chest to a class, simply look at the chest and type
|
||||
``/ma classchest <class>``, and you're done! The linked class chests may
|
||||
exist in any world, but remember that there can only be one class chest
|
||||
per class, and that local class chests will be ignored!
|
||||
|
||||
To unlink a class chest, you will have to open the config-file and
|
||||
remove the ``classchest`` node from the given class.
|
|
@ -1,119 +0,0 @@
|
|||
########
|
||||
Commands
|
||||
########
|
||||
|
||||
This page documents the commands available in MobArena, both for players and
|
||||
administrators.
|
||||
|
||||
*Note*: Parentheses (``()``) around an parameter means it's optional (i.e. not
|
||||
required to work).
|
||||
|
||||
|
||||
***************
|
||||
Player commands
|
||||
***************
|
||||
|
||||
Any player on the server can use these commands by default.
|
||||
|
||||
+---------------------------+----------------------------+---------------------+
|
||||
| Command | Description | Alias |
|
||||
+===========================+============================+=====================+
|
||||
| ``/ma join (<arena>)`` | Join arena with given name | ``/ma j (<arena>)`` |
|
||||
+---------------------------+----------------------------+---------------------+
|
||||
| ``/ma leave`` | Leave current arena or | ``/ma l`` |
|
||||
| | spectator area | |
|
||||
+---------------------------+----------------------------+---------------------+
|
||||
| ``/ma notready`` | List players who are not | |
|
||||
| | ready in arena lobby | |
|
||||
+---------------------------+----------------------------+---------------------+
|
||||
| ``/ma spec (<arena>)`` | Enter an arena's spectator | ``/ma s (<arena>)`` |
|
||||
| | arena | |
|
||||
+---------------------------+----------------------------+---------------------+
|
||||
| ``/ma arenas`` | List all arenas. Green | |
|
||||
| | names are enabled, gray | |
|
||||
| | names are disabled | |
|
||||
+---------------------------+----------------------------+---------------------+
|
||||
| ``/ma players (<arena>)`` | List all players in an | |
|
||||
| | area | |
|
||||
+---------------------------+----------------------------+---------------------+
|
||||
| ``/ma class <class>`` | Manually choose a class in | |
|
||||
| | an arena lobby (instead of | |
|
||||
| | punching sign) | |
|
||||
+---------------------------+----------------------------+---------------------+
|
||||
|
||||
|
||||
**************
|
||||
Admin commands
|
||||
**************
|
||||
|
||||
Players with OP privileges or assigned permissions can use these commands.
|
||||
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| Command | Description |
|
||||
+=============================+================================================+
|
||||
| ``/ma enable (<arena>)`` | Enable MobArena (optionally a specific arena) |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma disable`` | Disable MobArena (optionally a specific arena) |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma force end (<arena>)`` | Forcefully end all arenas or a specific arena |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma force start <arena>`` | Forcefully start an arena (players that aren't |
|
||||
| | ready are removed from arena) |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma notready <arena>`` | List all players in an arena that aren't ready |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma restore <player>`` | Restore a player's inventory (if possible) |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma config reload`` | Reload config file into memory |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
|
||||
|
||||
**************
|
||||
Setup commands
|
||||
**************
|
||||
|
||||
Players with OP privileges or assigned permissions can use these commands.
|
||||
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| Command | Description |
|
||||
+=============================+================================================+
|
||||
| ``/ma setup <arena>`` | Enter Setup Mode for an arena (see |
|
||||
| | :doc:`arena-setup` for more info) |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma addarena <arena>`` | Create new arena node in current world |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma delarena <arena>`` | Delete arena with given name |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma editarena <arena>`` | Toggle Edit Mode for an arena |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma editarena <arena>`` | Turn Edit Mode on or off for an arena |
|
||||
| ``[true|false]`` | |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma setting <arena>`` | List per-arena settings for an arena |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma setting <arena>`` | Check current value of a setting for an arena |
|
||||
| ``<setting>`` | |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma setting <arena>`` | Change a setting for an arena to given value |
|
||||
| ``<setting> <value>`` | |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma checkspawns`` | Show all spawnpoints in arena you are standing |
|
||||
| | in as red wool blocks (helpful to check |
|
||||
| | spawnpoint coverage) |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma classchest <class>`` | Create a linked class chest for a class (see |
|
||||
| | :doc:`class-chests` for more info) |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma auto-generate`` | Auto-generate new arena with given name |
|
||||
| ``<arena>`` | (generated directly below player) |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
| ``/ma auto-degenerate`` | Degenerate an auto-generated arena with given |
|
||||
| ``<arena>`` | name |
|
||||
+-----------------------------+------------------------------------------------+
|
||||
|
||||
|
||||
***********
|
||||
Permissions
|
||||
***********
|
||||
|
||||
See :doc:`permissions`.
|
|
@ -1,60 +0,0 @@
|
|||
###############
|
||||
Getting started
|
||||
###############
|
||||
|
||||
To get MobArena up and running on your server, you'll first have to
|
||||
download it and stick it in your server's plugins-folder.
|
||||
|
||||
When the plugin has been loaded up, you'll have to build an arena if you
|
||||
haven't already. Then it's time to set it up. Follow the instructions on
|
||||
the [[Arena Setup]] page, which has all the information you'll need to
|
||||
get your arena up and running in no time at all. After setup, you're
|
||||
ready to play! Check out the [[Using MobArena]] page for a brief
|
||||
description of the usage commands.
|
||||
|
||||
The default arena setup is only meant as a starting point, so you may
|
||||
want to configure the waves, the rewards, the classes, or perhaps some
|
||||
of the arena-specific settings. The various pages on this wiki will help
|
||||
you configure MobArena to your liking. Here are a couple of links to get
|
||||
you started:
|
||||
|
||||
- [[Setting up the config-file]]
|
||||
- [[Setting up the waves]]
|
||||
- [[MobArena Commands]]
|
||||
|
||||
Also make sure to have a look at the [[Item and Reward Syntax]] page, if
|
||||
you're planning to change the classes or the rewards. MobArena uses its
|
||||
own semi-compact item syntax in the config-file, so you'll need to get
|
||||
familiar with it to get things like enchantments and sub-types to work.
|
||||
|
||||
****************
|
||||
More information
|
||||
****************
|
||||
|
||||
Always make sure to check the pages on the wiki when you're in doubt -
|
||||
99% of the information you'll ever need is right here. Check out the
|
||||
[[list of wiki
|
||||
pages\|https://github.com/garbagemule/MobArena/wiki/\_pages]] to see if
|
||||
maybe you'd be able to find your answer there. You can also check the
|
||||
project pages on
|
||||
[[Spigot\|https://www.spigotmc.org/resources/mobarena.34110/]] and
|
||||
[[Bukkit\|http://dev.bukkit.org/bukkit-plugins/mobarena/]].
|
||||
|
||||
***********************
|
||||
Suggestions, bugs, etc.
|
||||
***********************
|
||||
|
||||
If you think you have found a bug, or if you have a suggestion for the
|
||||
plugin, you can create a ticket here on Github. Make sure to be as
|
||||
descriptive as possible, because the more information you can provide,
|
||||
the easier it is to do something about your bug report or feature
|
||||
request.
|
||||
|
||||
If you'd rather have a little chat about the plugin, you can visit the
|
||||
[[IRC channel\|http://webchat.esper.net/?channels=#mobarena]] (#mobarena
|
||||
@ EsperNet) - please note that IRC is *idle chat*, which means people
|
||||
will appear to be in the channel, but they might not be at their
|
||||
computers when you join. Hop on, ask your question, and then hang around
|
||||
until you get an answer; it may be seconds, minutes or hours, but there
|
||||
is no harm in hanging around the channel indefinitely (as many people
|
||||
do).
|
|
@ -1,151 +0,0 @@
|
|||
######################
|
||||
Item and reward syntax
|
||||
######################
|
||||
|
||||
This page explains the syntax for items and rewards for completing MobArena
|
||||
waves.
|
||||
|
||||
|
||||
**********
|
||||
Item types
|
||||
**********
|
||||
|
||||
MobArena items must use a specific syntax to work as expected. The plugin allows
|
||||
three different ways of defining items:
|
||||
|
||||
- **Single**: ``[<id>|<name>]``
|
||||
- **Multiple**: ``[<id>|<name>]:<amount>``
|
||||
- **Sub-types**: ``[<id>|<name>]:<data>:<amount>``
|
||||
|
||||
Single items
|
||||
============
|
||||
|
||||
``[<id>|<name>]``
|
||||
|
||||
*Either* an item ID number [#]_ (``<id>``) or item name (``<name>``) can be used.
|
||||
Item names are defined in the `Material enum`_ of the Bukkit API. Item names are
|
||||
case-insensitive.
|
||||
|
||||
The Material enum changes when items are added, changed, or reworked. The
|
||||
Minecraft server version determines what items are available. Check the
|
||||
documentation for what item names are available.
|
||||
|
||||
This example gives the ``barbarian`` class a bow and leather leggings.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
classes:
|
||||
barbarian:
|
||||
items: bow
|
||||
armor: leather_leggings
|
||||
|
||||
.. [#] Each item ID number must be wrapped in apostrophes (``''``) to work
|
||||
correctly.
|
||||
|
||||
Multiple items
|
||||
==============
|
||||
|
||||
``[<id>|<name>]:<amount>``
|
||||
|
||||
This method lets you add an amount to an item. This is useful for items like
|
||||
arrows or potions.
|
||||
|
||||
Now, our example also gives 64 arrows and four pieces of cooked porkchops.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
classes:
|
||||
barbarian:
|
||||
items: bow, arrow:64, grilled_pork:4
|
||||
armor: leather_leggings
|
||||
|
||||
Item sub-types
|
||||
==============
|
||||
|
||||
``[<id>|<name>]:<data>:<amount>``
|
||||
|
||||
To add an item sub-type, both the sub-type name and an amount must be specified.
|
||||
This is helpful for items like potions, wool, and/or dyes.
|
||||
|
||||
- `Potion effect types`_
|
||||
- `Color names`_
|
||||
|
||||
Now, our example gives one Potion of Strength I and one piece of purple wool.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
classes:
|
||||
barbarian:
|
||||
items: bow, arrow:64, grilled_pork:4, potion:increase_damage:1,
|
||||
wool:purple:1
|
||||
armor: leather_leggings
|
||||
|
||||
|
||||
************
|
||||
Enchantments
|
||||
************
|
||||
|
||||
``<item> <eid>:<level>;<eid>:<level>;...``
|
||||
|
||||
Add enchantments to an item by adding a space with a semi-colon separated list
|
||||
with the enchantment name and the strength. Find valid enchantment names in the
|
||||
`Enchantment class`_ in the Spigot API.
|
||||
|
||||
Now, our example adds Power II and Flame I to the bow and a diamond sword with
|
||||
Sharpness I and Knockback II.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
classes:
|
||||
barbarian:
|
||||
items: bow arrow_damage:2;arrow_fire:1, arrow:64, grilled_pork:4,
|
||||
potion:increase_damage:1, wool:purple:1,
|
||||
diamond_sword damage_all:1;knockback:2
|
||||
armor: leather_leggings
|
||||
|
||||
|
||||
*************
|
||||
Economy Money
|
||||
*************
|
||||
|
||||
``$<amount>``
|
||||
|
||||
MobArena supports entry fees and rewards with economy plugins. You must use
|
||||
`Vault`_ for this to work.
|
||||
|
||||
Since v0.96, floating point numbers are also supported.
|
||||
|
||||
Examples:
|
||||
|
||||
- ``$1``
|
||||
- ``$5``
|
||||
- ``$3.14``
|
||||
- ``$0.99``
|
||||
|
||||
|
||||
***************
|
||||
Command Rewards
|
||||
***************
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
cmd:/give <player> dirt
|
||||
cmd(description of reward):/give <player> dirt
|
||||
|
||||
You can run a command to give a special reward to a player. Command rewards are
|
||||
supported since v0.99. Useful examples might be to give a permission (maybe
|
||||
unlocking a new arena) or integrating with other plugins.
|
||||
|
||||
It is possible to customize the message a player receives when they receive a
|
||||
command reward. By default, the plugin prints the command. You can specify a
|
||||
more user-friendly message in the second format listed above.
|
||||
|
||||
For example, ``cmd(New arena to play!)/perm add <player> <permission>``
|
||||
appears to the player as ``You just earned a reward: New arena to play!``.
|
||||
|
||||
|
||||
.. _`Material enum`: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html
|
||||
.. _`Potion effect types`: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/potion/PotionEffectType.html
|
||||
.. _`Color names`: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/DyeColor.html
|
||||
.. _`Enchantment class`: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/enchantments/Enchantment.html
|
||||
.. _`Vault`: https://dev.bukkit.org/projects/vault
|
|
@ -1,68 +0,0 @@
|
|||
############
|
||||
Leaderboards
|
||||
############
|
||||
|
||||
MobArena supports so-called *leaderboards* (although technically they
|
||||
are more like scoreboards or session signs). By arranging signs in a
|
||||
two-dimensional grid on a wall, spectators can see which classes each
|
||||
player has chosen, which wave they died on, how many kills and how much
|
||||
damage they have done, etc.
|
||||
|
||||
.. figure:: img/leaderboards/1.png
|
||||
:alt: Sign Grid
|
||||
|
||||
Sign Grid
|
||||
|
||||
The requirements for leaderboards are at the very least two rows of
|
||||
signs on a wall as seen in the screenshot above. The top row should not
|
||||
be empty, however, as it should contain the leaderboard *headers*, which
|
||||
denote what kind of information the signs below them display. How many
|
||||
headers (and which) you want is entirely up to you.
|
||||
|
||||
.. figure:: img/leaderboards/2.png
|
||||
:alt: Top Left Sign
|
||||
|
||||
Top Left Sign
|
||||
|
||||
To get started, replace the top left empty sign (or place it if you
|
||||
haven't already), and write ``[MA]<name>``, where ``<name>`` is the name
|
||||
of your arena, on the first line. In the screenshot above, I have set up
|
||||
the top left sign for the arena named Jail by writing ``[MA]jail`` on
|
||||
it. MobArena automatically fills in the rest of the text and the colors
|
||||
for you.
|
||||
|
||||
.. figure:: img/leaderboards/3.png
|
||||
:alt: Sign Text Screen
|
||||
|
||||
Sign Text Screen
|
||||
|
||||
MobArena will then tell you that the sign has been created, and that you
|
||||
should set up the rest of the signs. The rest of the headers follow the
|
||||
same kind of format as the top left sign, so you simply write
|
||||
``[MA]<stat>``, where ``<stat>`` is one of the following:
|
||||
|
||||
- ``class`` - The class name of the player
|
||||
- ``lastWave`` - The last wave the player was part of (current wave if
|
||||
still alive)
|
||||
- ``kills`` - The number of monsters the player has killed
|
||||
- ``dmgDone`` - The amount of damage the player has dealt
|
||||
- ``dmgTaken`` - The amount of damage the player has taken
|
||||
- ``swings`` - The number of times the player has swung their weapon
|
||||
- ``hits`` - The number of times the player has swung their weapon and
|
||||
successfully hit a monster
|
||||
|
||||
In the screenshot above, I have already set up a couple of signs, and
|
||||
I'm about to set up the sign for damage done. As with the top left sign,
|
||||
your only job is to tell MobArena which stat you want - it'll take care
|
||||
of colors and formatting automatically. Note that MobArena's sign
|
||||
handling is case sensitive, so make sure you get it right.
|
||||
|
||||
.. figure:: img/leaderboards/4.png
|
||||
:alt: Final Setup
|
||||
|
||||
Final Setup
|
||||
|
||||
When you're done setting up the leaderboards, they should look something
|
||||
like the screenshot above, and you should be good to go! Leaderboards
|
||||
can be set up anywhere (even outside of the world the arena is in), but
|
||||
you can only have a single leaderboard per arena.
|
|
@ -1,38 +0,0 @@
|
|||
#############
|
||||
Monster types
|
||||
#############
|
||||
|
||||
MobArena supports any monster available in the `EntityType
|
||||
enum <https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/EntityType.html>`__.
|
||||
You don't have to write the names in all caps, and you can omit or
|
||||
include underscores (``_``), hyphens (``-``), and periods (``.``) as you
|
||||
please.
|
||||
|
||||
Some monsters are a little special. Creepers, for instance, can be
|
||||
charged or powered, meaning their explosions become much more powerful,
|
||||
and their appearance changes. Another example is slimes and magma cubes,
|
||||
which have different sizes.
|
||||
|
||||
MobArena supports some of the variations of these different monster
|
||||
types, and they are listed here:
|
||||
|
||||
- ``explodingsheep`` is a MobArena-specific type of sheep that bounces
|
||||
around and explodes when in the proximity of a player
|
||||
- ``poweredcreeper`` is a charged/powered creeper
|
||||
- ``angrywolf`` is an aggressive wolf with red eyes
|
||||
- ``babyzombie`` is a baby-version of a zombie
|
||||
- ``babypigman`` is a baby-version of a pigman
|
||||
- ``babyzombievillager`` is a baby-version of a zombie villager
|
||||
- ``killerbunny`` is a killer bunny version of a rabbit
|
||||
|
||||
As for slimes and magma cubes, both monster types are assigned a random
|
||||
size when they spawn. However, they also both support size suffixes that
|
||||
force them to be a specific size. They are:
|
||||
|
||||
- ``tiny`` size 1
|
||||
- ``small`` size 2
|
||||
- ``big`` size 3
|
||||
- ``huge`` size 4
|
||||
|
||||
As an example, ``slimehuge`` will spawn a size 4 slime, while
|
||||
``magmacubetiny`` will spawn a size 1 magma cube.
|
|
@ -1,179 +0,0 @@
|
|||
###########
|
||||
Permissions
|
||||
###########
|
||||
|
||||
A permissions plugin **is NOT required** for MobArena to work, but if
|
||||
you want that extra bit of control, here's a rundown of the different
|
||||
types of permission nodes you can use with MobArena.
|
||||
|
||||
**NOTE: MobArena uses sane defaults. This means that by default, all
|
||||
players can use all arenas and all classes, and ops can use all admin
|
||||
and setup commands. Unless you want to prevent some groups from
|
||||
accessing certain arenas or classes, or you want to give non-ops admin
|
||||
and setup permissions, there is no need to mess with any permissions at
|
||||
all, so go away from this page and remove all occurrences of
|
||||
``mobarena`` in your permissions-file!**
|
||||
|
||||
Arenas
|
||||
~~~~~~
|
||||
|
||||
Did you read the note at the top? If not, read it before you continue.
|
||||
|
||||
So, you want to remove permissions for certain arenas from certain
|
||||
users? Alright, that means you will have to *negate* or *revoke* the
|
||||
permissions in your permissions plugin. In bPermissions, the negation
|
||||
modifier is a caret, ``^``, in GroupManager and PermissionsEx it is a
|
||||
minus, ``-``, and in zPermissions it is by setting the permission to
|
||||
``false``. The examples below revoke the permission for the default
|
||||
arena.
|
||||
|
||||
| bPermissions: ``^mobarena.arenas.default``
|
||||
| GroupManager: ``-mobarena.arenas.default``
|
||||
| zPermissions: ``mobarena.arenas.default: false``
|
||||
|
||||
I recommend letting everyone enjoy all your arenas, but this could be
|
||||
used in combination with "leveling" plugins to allow players to use
|
||||
"harder" arenas at higher levels. It could also be used for
|
||||
sponsors-only arenas.
|
||||
|
||||
Still confused? Check the `sample setup <#sample-setup>`__ at the bottom
|
||||
of the page!
|
||||
|
||||
Classes
|
||||
~~~~~~~
|
||||
|
||||
Did you read the note at the top? If not, read it before you continue.
|
||||
|
||||
Alright, if you're reading this, you want to remove permissions for
|
||||
certain classes from certain users. As with the arena permissions, you
|
||||
need to *negate* or *revoke* the permissions in your permissions plugin.
|
||||
In bPermissions, the negation modifier is a caret, ``^``, in
|
||||
GroupManager and PermissionsEx it is a minus, ``-``, and in zPermissions
|
||||
it is by setting the permission to ``false``. The examples below revoke
|
||||
the permission for the Knight class.
|
||||
|
||||
| bPermissions: ``^mobarena.classes.knight``
|
||||
| GroupManager: ``-mobarena.classes.knight``
|
||||
| zPermissions: ``mobarena.classes.knight: false``
|
||||
|
||||
**Note how the class name is lowercase.** This is important. Even if the
|
||||
Knight class is called ``KnIGhT`` in your config-file, it MUST be all
|
||||
lowercase in your permissions-file.
|
||||
|
||||
As with arenas, I recommend letting everyone enjoy all the classes,
|
||||
unless you have a special reason not to.
|
||||
|
||||
Still confused? Check the `sample setup <#sample-setup>`__ at the bottom
|
||||
of the page!
|
||||
|
||||
Commands
|
||||
~~~~~~~~
|
||||
|
||||
Did you read the note at the top? If not, read it before you continue.
|
||||
|
||||
If you're reading this, you want to either give certain users access to
|
||||
some of the admin and/or setup commands, or you want to remove some of
|
||||
the user commands from some groups. If this is not the case, stop
|
||||
reading and leave this page!
|
||||
|
||||
The first group of commands are the user commands. They are accessible
|
||||
by all players by default, so don't put ``mobarena.use.*`` or something
|
||||
stupid like that in your permissions-file! If you want a group to not
|
||||
have access to the user commands, *negate* the permission
|
||||
``mobarena.use``, which is the *parent permission node* for all the user
|
||||
commands. See the classes and arenas sections for information on how to
|
||||
negate permissions. If that doesn't work, negate the
|
||||
``mobarena.use.join`` and ``mobarena.use.spec`` permissions. That should
|
||||
be enough.
|
||||
|
||||
::
|
||||
|
||||
mobarena.use.join
|
||||
mobarena.use.leave
|
||||
mobarena.use.spec
|
||||
mobarena.use.arenalist
|
||||
mobarena.use.playerlist
|
||||
mobarena.use.notready
|
||||
mobarena.use.class
|
||||
|
||||
The admin commands are simple. They allow disabling/enabling MobArena
|
||||
and individual arenas, kicking players from the arenas, restoring player
|
||||
inventories if they got lost somehow, forcing arenas to start or end,
|
||||
and teleporting in and out of arenas regardless of what the arena state
|
||||
is. If you want to grant all of these permissions, use the *parent
|
||||
permission node* ``mobarena.admin``. Don't mess around with ``*`` or
|
||||
something stupid like that.
|
||||
|
||||
::
|
||||
|
||||
mobarena.admin.enable
|
||||
mobarena.admin.kick
|
||||
mobarena.admin.restore
|
||||
mobarena.admin.force
|
||||
mobarena.admin.teleport
|
||||
|
||||
Setup commands are only for ops, just like admin commands. **Do not**
|
||||
give these permissions to random people, because they can remove your
|
||||
arenas and destroy your config-files, if they do something stupid. The
|
||||
setup commands allow you to manage arenas, regions, spawnpoints, chests,
|
||||
leaderboards, etc. They also allow you to set up new classes in-game. If
|
||||
you want to grant all of these permissions, use the *parent permission
|
||||
node* ``mobarena.setup``. Don't mess around with ``*`` or something
|
||||
stupid like that.
|
||||
|
||||
::
|
||||
|
||||
mobarena.setup.config
|
||||
mobarena.setup.setup
|
||||
mobarena.setup.setting
|
||||
mobarena.setup.addarena
|
||||
mobarena.setup.removearena
|
||||
mobarena.setup.editarena
|
||||
mobarena.setup.spawnpoints
|
||||
mobarena.setup.containers
|
||||
mobarena.setup.checkdata
|
||||
mobarena.setup.checkspawns
|
||||
mobarena.setup.classchest
|
||||
mobarena.setup.classes
|
||||
mobarena.setup.leaderboards
|
||||
mobarena.setup.autogenerate
|
||||
mobarena.setup.autodegenerate
|
||||
|
||||
Sample setup
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Assume you have a class called DiamondKnight that you only want your
|
||||
donors to be able to use (very common use case). How do you set up your
|
||||
permissions plugin when you have to revoke the class permission from the
|
||||
default group, but the donor group inherits from the default group? It's
|
||||
very simple: You're doing it wrong...
|
||||
|
||||
What you have to do instead is make an *auxiliary default*-group that
|
||||
contains all your default permissions, and have your default group
|
||||
inherit from that group, and furthermore revoke the DiamondKnight class
|
||||
permission in MobArena. Your donor group then also inherits from the
|
||||
auxiliary group, and everything is wonderful. Confusing? Here's a
|
||||
pseudo-code example:
|
||||
|
||||
::
|
||||
|
||||
default-aux: <-- This is the auxiliary group that is to
|
||||
permissions: be inherited by the default group and
|
||||
- essentials.balance the donor group. It is not used for
|
||||
- essentials.pay anything else.
|
||||
- essentials.sell
|
||||
|
||||
default: <-- This is the default group. It inherits
|
||||
inherits: default-aux from default-aux, but also revokes the
|
||||
permissions: permission for the special class.
|
||||
- -mobarena.classes.diamondknight
|
||||
|
||||
donor: <-- This is the donor group, which also
|
||||
inherits: default-aux inherits from default-aux, but it
|
||||
permissions: does not revoke any class permissions,
|
||||
- essentials.balance.others which means it has access to all of
|
||||
- essentials.kit them by default.
|
||||
|
||||
This sample setup is **pseudo code** and cannot be simply copy/pasted
|
||||
into your own permissions file. It's your job to figure out how your
|
||||
permissions plugin works, and what its syntax is.
|
|
@ -1,530 +0,0 @@
|
|||
##########################
|
||||
Setting up the config file
|
||||
##########################
|
||||
|
||||
**On this page:** \*
|
||||
`Overview <./Setting-up-the-config-file#wiki-an-overview>`__ \*
|
||||
```global-settings`` <./Setting-up-the-config-file#wiki-global-settings>`__
|
||||
\* ```classes`` <./Setting-up-the-config-file#wiki-classes>`__ \* `Bring
|
||||
your own items <./Setting-up-the-config-file#bring-your-own-items>`__ \*
|
||||
`Slot-specific armor
|
||||
nodes <./Setting-up-the-config-file#slot-specific-armor-nodes>`__ \*
|
||||
`Price <./Setting-up-the-config-file#price>`__ \* `Unbreakable
|
||||
items <./Setting-up-the-config-file#wiki-unbreakable-weaponsarmor>`__ \*
|
||||
`Per-class
|
||||
permissions <./Setting-up-the-config-file#wiki-per-class-permissions>`__
|
||||
\* `Pet classes <./Setting-up-the-config-file#wiki-pet-classes>`__ \*
|
||||
`Mounts <./Setting-up-the-config-file#wiki-mounts>`__ \*
|
||||
```arenas`` <./Setting-up-the-config-file#wiki-arenas>`__ \*
|
||||
```settings`` <./Setting-up-the-config-file#wiki-settings>`__ \*
|
||||
```waves`` <./Setting-up-the-config-file#wiki-waves>`__ \*
|
||||
```rewards`` <./Setting-up-the-config-file#wiki-rewards>`__ \*
|
||||
```coords`` <./Setting-up-the-config-file#wiki-coords>`__
|
||||
|
||||
An Overview
|
||||
~~~~~~~~~~~
|
||||
|
||||
*Note: When editing the config-file, you **MUST use spaces for
|
||||
indentation**! Using tabs instead of spaces will give you errors!*
|
||||
|
||||
The config-file, ``plugins/MobArena/config.yml``, consists of 3
|
||||
sections: ``global-settings``, ``classes``, and ``arenas``. The default
|
||||
config-file that is generated when MobArena is first loaded looks
|
||||
something like this:
|
||||
|
||||
::
|
||||
|
||||
[...]
|
||||
global-settings:
|
||||
update-notification: true
|
||||
enabled: true
|
||||
allowed-commands: /list
|
||||
prefix: '&a[MobArena] '
|
||||
classes:
|
||||
Knight:
|
||||
items: diamond_sword, grilled_pork:2
|
||||
armor: 306,307,308,309
|
||||
Archer:
|
||||
items: wood_sword, bow, arrow:128, grilled_pork
|
||||
armor: 298,299,300,301
|
||||
[...]
|
||||
arenas:
|
||||
default:
|
||||
settings:
|
||||
prefix: ''
|
||||
world: Tundra
|
||||
enabled: true
|
||||
protect: true
|
||||
clear-wave-before-next: false
|
||||
[...]
|
||||
waves:
|
||||
[...]
|
||||
rewards:
|
||||
[...]
|
||||
|
||||
Note about notation: ``[true|false]`` means the setting must be "true or
|
||||
false", either or. ``<time>`` means the setting must be an amount of
|
||||
time (in seconds or server ticks), always a whole number, and always
|
||||
``0`` or greater. ``<amount>`` is similar to time.
|
||||
|
||||
global-settings
|
||||
---------------
|
||||
|
||||
The ``global-settings`` are few, but important. Note that if
|
||||
``enabled: false``, no arenas can be joined, regardless of their
|
||||
individual ``enabled`` status.
|
||||
|
||||
- ``enabled: [true|false]`` - This determines if MobArena is enabled or
|
||||
not. If set to ``false``, players will not be able to join any arenas
|
||||
at all, regardless of what the arenas' individual statuses are.
|
||||
- ``update-notification: [true|false]`` - If true, MobArena will send a
|
||||
message to ops when they log on if a new version of MobArena is
|
||||
available.
|
||||
- ``allowed-commands: <com1>, <com2>, ...`` - A comma-separated list of
|
||||
the commands that players are allowed to use while in the lobby
|
||||
and/or arena. This is useful if you don't want players to use
|
||||
teleport-commands, flying carpets, kill commands, etc. If you write
|
||||
the command WITH its forward-slash, the entire command and all
|
||||
"sub-commands" will be allowed. For instance, writing ``/kill`` will
|
||||
allow both ``/kill``, ``/kill Sausageman22`` and ``/kill Notch``.
|
||||
Writing the command WITHOUT its forward-slash will allow only that
|
||||
specific command or "sub-command". Writing ``kill`` will thus ONLY
|
||||
allow ``/kill``, but not ``/kill Sausageman22``.
|
||||
- ``prefix: <prefix>`` - The prefix MobArena uses for all of its
|
||||
messages. The default is the classic green ``[MobArena]``, but you
|
||||
can change it to whatever you want. You can override the prefix for
|
||||
specific arenas by using the arena-specific setting with the same
|
||||
name.
|
||||
|
||||
I recommended leaving the update notifications on, and disabling
|
||||
commands like ``/kill`` and ``/tp``.
|
||||
|
||||
classes
|
||||
-------
|
||||
|
||||
The ``classes``-section is slightly more complicated. It is divided into
|
||||
*class-branches*, where each branch denotes the *name of the class*, and
|
||||
each branch has mandatory nodes ``items`` and ``armor``, as well as
|
||||
optional slot-specific armor nodes and optional nodes ``price``,
|
||||
``permissions``, ``lobby-permissions``, ``unbreakable-weapons``, and
|
||||
``unbreakable-armor``.
|
||||
|
||||
**Note:** YAML is picky about how you type your items. Make sure you
|
||||
read the short [[Item and Reward Syntax]]-page and fully understand it
|
||||
before you attempt to modify the config file!
|
||||
|
||||
::
|
||||
|
||||
classes:
|
||||
Archer:
|
||||
items: wood_sword, bow, arrow:128, grilled_pork
|
||||
armor: 298,299,300,301
|
||||
permissions:
|
||||
- EffectiveArrows.use.*
|
||||
- -mobarena.use.leave
|
||||
Tank:
|
||||
items: iron_sword
|
||||
armor: 310,311,312,313
|
||||
offhand: shield
|
||||
Knight:
|
||||
items: '276'
|
||||
armor: iron_helmet, iron_chestplate, iron_leggings, iron_boots
|
||||
Wolf Master:
|
||||
items: stone_sword, grilled_pork, bone:2
|
||||
armor: 298,299,300,301
|
||||
Crusader:
|
||||
items: iron_sword, hay_block:17
|
||||
armor: 302,303,304,305
|
||||
price: $5
|
||||
|
||||
Bring your own items
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
MobArena allows you to just bring your own items into the arena via the
|
||||
implicit 'My Items' class. What this means is that if you just place a
|
||||
sign in the lobby with the text My Class, you'll get the items that you
|
||||
had before joining the arena. Items are still restored on dying in or
|
||||
leaving the arena.
|
||||
|
||||
For a smooth, own-items-only experience, ditch the signs and set the
|
||||
per-arena setting ``default-class`` to ``myitems``.
|
||||
|
||||
Slot-specific armor nodes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to use off-hand items, or if you just want to be explicit
|
||||
about which items go where in the armor slots, use the optional
|
||||
slot-specific armor nodes: ``offhand``, ``helmet``, ``chestplate``,
|
||||
``leggings``, ``boots``. In the example above, the **Tank** class gets a
|
||||
shield in its off-hand slot.
|
||||
|
||||
Price
|
||||
~~~~~
|
||||
|
||||
The optional ``price`` node can be used to give classes a per-session
|
||||
price. When a player tries to pick a class that has a price, they will
|
||||
only be able to if they can afford it. The money is withdrawn when the
|
||||
arena starts, i.e. picking different priced classes in succession will
|
||||
not (necessarily) result in empty player wallets. In the example above,
|
||||
the **Crusader** class costs ``$5``.
|
||||
|
||||
Unbreakable weapons/armor
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The optional ``unbreakable-weapons`` and ``unbreakable-armor`` nodes can
|
||||
be used to toggle on or off the unbreakability of class items and armor.
|
||||
The nodes *default to true*, so they are really only necessary if you
|
||||
want to toggle OFF the feature, i.e. if you want items to deteriorate
|
||||
and break! If that's what you want, set the nodes to false.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<pre>
|
||||
classes:
|
||||
FrailTank:
|
||||
items: diamond_sword
|
||||
armor: 310,311,312,313
|
||||
<font color="blue">unbreakable-weapons: false</font>
|
||||
<font color="blue">unbreakable-armor: false</font>
|
||||
</pre>
|
||||
|
||||
Per-class permissions
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using the optional ``permissions``-node, you can give classes special
|
||||
permissions to customize them even more. Each permission must be listed
|
||||
with a dash (-) in front of it. If you want a class to *not* have a
|
||||
permission, put a dash/minus at the very beginning of the permission
|
||||
node. In the example above, the **Archer** class will be able to use the
|
||||
EffectiveArrows plugin, but won't be able to use ``/ma leave`` (meaning
|
||||
it's impossible to leave the arena without dying).
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<pre>
|
||||
classes:
|
||||
Archer:
|
||||
items: wood_sword, bow, arrow:128, grilled_pork
|
||||
armor: 298,299,300,301
|
||||
<font color="blue">permissions:</font>
|
||||
<font color="blue">- EffectiveArrows.use.*</font>
|
||||
<font color="blue">- -mobarena.use.leave</font>
|
||||
</pre>
|
||||
|
||||
The optional ``lobby-permissions``-node gives players special
|
||||
permissions while they are in the lobby *after they have picked a
|
||||
class*. This feature can be used e.g. in combination with a shop plugin
|
||||
and a base class that perhaps has nothing (maybe except for a few
|
||||
potions).
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<pre>
|
||||
classes:
|
||||
Basic:
|
||||
items: ''
|
||||
armor: ''
|
||||
<font color="blue">lobby-permissions:</font>
|
||||
<font color="blue">- shop.buy</font>
|
||||
</pre>
|
||||
|
||||
Pet classes
|
||||
~~~~~~~~~~~
|
||||
|
||||
For every bone (Material name: ``bone``, data value: ``352``) in a
|
||||
class' items-list (or class chest), one wolf pet will spawn upon arena
|
||||
start. In the example above, every player that picks the **Wolf Master**
|
||||
class will have 2 wolves spawn upon arena start. The wolves are
|
||||
invincible, but deal less damage than normal wolves.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<pre>
|
||||
classes:
|
||||
Wolf Master:
|
||||
items: stone_sword, grilled_pork, <font color="blue">bone:2</font>
|
||||
armor: 298,299,300,301
|
||||
</pre>
|
||||
|
||||
Mounts
|
||||
~~~~~~
|
||||
|
||||
To give a class a horse mount, give it a hay block in the items-list (or
|
||||
place a hay block in the class chest). The item stack amount (in the
|
||||
first encountered stack) determines the variant and barding of the
|
||||
horse. You can use this table to figure out which hay block amount you
|
||||
need for your desired variant and barding:
|
||||
|
||||
+----------------+------------+------------+------------+---------------+
|
||||
| | **None** | **Iron** | **Gold** | **Diamond** |
|
||||
+================+============+============+============+===============+
|
||||
| **Horse** | 1 | 9 | 17 | 25 |
|
||||
+----------------+------------+------------+------------+---------------+
|
||||
| **Donkey** | 2 | - | - | - |
|
||||
+----------------+------------+------------+------------+---------------+
|
||||
| **Mule** | 3 | - | - | - |
|
||||
+----------------+------------+------------+------------+---------------+
|
||||
| **Skeleton** | 4 | - | - | - |
|
||||
+----------------+------------+------------+------------+---------------+
|
||||
| **Zombie** | 5 | - | - | - |
|
||||
+----------------+------------+------------+------------+---------------+
|
||||
|
||||
Note that only normal horses can have barding.
|
||||
|
||||
In the example above, every player that picks the **Crusader** class
|
||||
will have a white horse with gold barding upon arena start. The mounts
|
||||
are invincible.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<pre>
|
||||
classes:
|
||||
Crusader:
|
||||
items: iron_sword, <font color="blue">hay_block:17</font>
|
||||
armor: 302,303,304,305
|
||||
</pre>
|
||||
|
||||
arenas
|
||||
------
|
||||
|
||||
This section is by far the largest, and it is divided into several
|
||||
smaller branches. In the above example, ``default`` denotes the *name*
|
||||
of the default arena. This name can be altered, but it must contain no
|
||||
spaces (use underscores instead). The arena name is significant when a
|
||||
server has multiple arenas and no Master Lobby (will be featured later).
|
||||
Let's go over the different branches:
|
||||
|
||||
settings
|
||||
~~~~~~~~
|
||||
|
||||
The settings-branch is quite extensive, and besides the ``world``-node,
|
||||
it is basically just a bunch of toggles (on/off, true/false), though a
|
||||
few are number-based.
|
||||
|
||||
- ``prefix: <prefix>`` - An arena-specific prefix to use for
|
||||
messages/announcements in this arena only. The default is the empty
|
||||
string (``''``), which means the ``global-settings`` prefix will be
|
||||
used.
|
||||
- ``world: <name>`` - The name of the world the arena resides in.
|
||||
- ``enabled: [true|false]`` - If false, players cannot join the arena.
|
||||
- ``protect: [true|false]`` - If false, the arena will not be protected
|
||||
from explosions and players breaking the blocks.
|
||||
- ``entry-fee: [$<amount>|<item>:<amount>]`` - Follows the exact same
|
||||
notation as the class items and rewards (read the [[Item and Reward
|
||||
Syntax]]-page). ``$20`` will subtract 20 of whatever currency you use
|
||||
from the players upon joining. ``$5, stick:2`` will require the
|
||||
player to have 5 currency units and 2 sticks to join the arena. The
|
||||
entry-fee will be refunded if the player leaves before the arena
|
||||
starts.
|
||||
- ``default-class: <class>`` - If non-empty, this class is
|
||||
automatically assigned to players when they join the arena. The class
|
||||
name must be all lowercase and with no spaces.
|
||||
- ``clear-wave-before-next: [true|false]`` - If true, no monsters will
|
||||
spawn before all monsters of the previous wave have been killed.
|
||||
- ``clear-boss-before-next: [true|false]`` - If true, no new waves will
|
||||
spawn before the current boss (if any) is dead.
|
||||
- ``clear-wave-before-boss: [true|false]`` - If true, a boss wave will
|
||||
not spawn until all previous monsters have been killed.
|
||||
- ``auto-equip-armor: [true|false]`` - If true, armor pieces will
|
||||
automatically be equipped upon class selection. Note that this does
|
||||
not work if a class has more than 1 of an armor piece type.
|
||||
- ``soft-restore: [true|false]`` - If true, all destroyed blocks will
|
||||
be saved in a "repair list", which will be used to restore blocks at
|
||||
arena end. No data is saved to the harddrive. Note that this setting,
|
||||
if true, ignores the ``protect`` flag.
|
||||
- ``soft-restore-drops: [true|false]`` - If true, blocks destroyed by
|
||||
players will drop as items like they normally do (using pickaxes,
|
||||
spades, etc.). Note that this makes it very easy for classes with
|
||||
pickaxes to "mine the arena" and build forts.
|
||||
- ``require-empty-inv-join: [true|false]`` - If false, players'
|
||||
inventories will be saved upon joining, and restored upon
|
||||
death/leaving.
|
||||
- ``require-empty-inv-spec: [true|false]`` - If false, players can
|
||||
spectate the arena without having to empty their inventories.
|
||||
- ``hellhounds: [true|false]`` - If true, all pet wolves in the arena
|
||||
will be in flames! This has no actual function, and is purely for the
|
||||
cool-factor. Also useful for distinguishing enemy wolves and pet
|
||||
wolves.
|
||||
- ``pvp-enabled: [true|false]`` - If true, players can damage each
|
||||
other in the arena.
|
||||
- ``monster-infight: [true|false]`` - If false, monsters will no longer
|
||||
damage each other.
|
||||
- ``allow-teleporting: [true|false]`` - If false, all warping to and
|
||||
from the arena region is blocked. Useful for preventing players from
|
||||
summoning other players into the arena for help.
|
||||
- ``spectate-on-death: [true|false]`` - If false, players will not get
|
||||
warped to the spectator area, but instead be "kicked" from the arena
|
||||
(essentially a forced /ma leave).
|
||||
- ``auto-respawn: [true|false]`` - If false, players will be greeted
|
||||
with the typical death screen upon dying in the arena, and will have
|
||||
to click the respawn button to respawn. With this setting at false,
|
||||
players will actually die in the arena, meaning plugins like Heroes
|
||||
and mcMMO will properly trigger their resetting of internal data upon
|
||||
respawn.
|
||||
- ``share-items-in-arena: [true|false]`` - If false, players will not
|
||||
be able to drop items in the arena.
|
||||
- ``min-players: <amount>`` - Gives a lower limit on how many players
|
||||
are required to start the arena. The default of ``0`` is the same as
|
||||
``1``, which means 1 or more players may start the arena. Note that
|
||||
this feature is incompatible with ``auto-start-timer`` and
|
||||
``start-delay-timer``!
|
||||
- ``max-players: <amount>`` - Gives an upper limit on how many players
|
||||
may join the arena. The default of ``0`` means no limit.
|
||||
- ``max-join-distance: <distance>`` - The maximum distance (in blocks)
|
||||
from which players can join or spectate the arena. If 0 (default),
|
||||
there is no limit, and players can join from any world. Note that the
|
||||
distance is calculated from every corner of the arena region, and
|
||||
that players not in the arena world won't be able to join or
|
||||
spectate.
|
||||
- ``first-wave-delay: <time>`` - The time (in seconds) before the first
|
||||
wave of monsters upon arena start.
|
||||
- ``wave-interval: <time>`` - The time (in seconds) between each new
|
||||
wave of monsters. If clear-wave-before-next: true, this setting will
|
||||
be ignored.
|
||||
- ``final-wave: <number>`` - The number of the final wave before the
|
||||
arena is force ended. This is useful if you want to set a cap on how
|
||||
many waves an arena will have.
|
||||
- ``monster-limit: <amount>`` - The maximum amount of monsters MobArena
|
||||
is allowed to spawn for this arena. The next wave, if any, will not
|
||||
spawn until there is room for more monsters.
|
||||
- ``monster-exp: [true|false]`` - If true, monsters will drop
|
||||
experience orbs. This is useful if you wish to give players the
|
||||
ability to spend the gathered experience on enchants or something
|
||||
else (using different plugins) during the session.
|
||||
- ``keep-exp: [true|false]`` - If true, players will keep the
|
||||
experience they gather in the arenas after death. This is useful if
|
||||
you want to allow players to level up or gather experience in the
|
||||
arenas. NOTE: If using ``display-waves-as-level`` or
|
||||
``display-timer-as-level``, set ``keep-exp`` to false.
|
||||
- ``food-regen: [true|false]`` - If true, a full food bar will cause
|
||||
players to regenerate health while in the arena. Note that this
|
||||
potentially makes tank-like classes extremely overpowered, since
|
||||
diamond armor (by default) coupled with a full food bar will make a
|
||||
player very hard to kill.
|
||||
- ``lock-food-level: [true|false]`` - If true, the food bar will be
|
||||
locked for all players in the arena, meaning they will not end up
|
||||
starving, and they will be able to sprint around as they please.
|
||||
- ``player-time-in-arena: <time of day>`` - When set to anything but
|
||||
world, this setting will freeze the apparent world time for players
|
||||
in the arena to whatever value you set. This is useful for making
|
||||
time-of-day themed arenas (e.g. constant night time for a cemetery,
|
||||
broad daylight for a pirate ship). Valid values are: dawn, sunrise,
|
||||
morning, midday, noon, day, afternoon, evening, sunset, dusk, night,
|
||||
midnight.
|
||||
- ``auto-ignite-tnt: [true|false]`` - If true, TNT will be
|
||||
automatically ignited when placed. This is useful for preventing
|
||||
Oddjob-like classes from forting.
|
||||
- ``auto-start-timer: <time>`` - The time (in seconds) before the arena
|
||||
will be force started after the first player has joined the lobby
|
||||
(the default of 0 means deactivated or infinite time). Non-ready
|
||||
players will be removed from the lobby. This setting is useful to
|
||||
prevent ill-minded players from delaying or preventing other players
|
||||
from starting the arena. Note that this feature is incompatible with
|
||||
``min-players``!
|
||||
- ``start-delay-timer: <time>`` - The time (in seconds) before the
|
||||
arena can be started after the first player has joined the lobby.
|
||||
This setting is useful if you want to give your players a fixed
|
||||
window of time to join the arena after the first player has joined,
|
||||
so they can't just start it off right away. Note that this feature is
|
||||
incompatible with ``min-players``!
|
||||
- ``display-waves-as-level: [true|false]`` - When set to true, the
|
||||
players' level counter (above the experience bar) will be used to
|
||||
display the current wave number. If the wave announcements in the
|
||||
announcements-file are silenced, this can be used to make a much less
|
||||
"spammy" MobArena experience. NOTE: Do not use this if ``keep-exp``
|
||||
is set to true!
|
||||
- ``display-timer-as-level: [true|false]`` - When set to true, the
|
||||
players' level counter (above the experience bar) will be used to
|
||||
display the auto-start timer in the lobby. NOTE: Do not use this if
|
||||
``keep-exp`` is set to true!
|
||||
- ``auto-ready: [true|false]`` - When set to true, players are
|
||||
automatically flagged as ready when they pick a class. Useful for
|
||||
arenas with many players where hitting an iron block becomes
|
||||
difficult.
|
||||
- ``use-scoreboards: [true|false]`` - Whether to use scoreboards in
|
||||
MobArena or not.
|
||||
- ``isolated-chat: [true|false]`` - When set to true, all chat messages
|
||||
sent by arena players will be seen only by other arena players in the
|
||||
same arena. The arena players will still be able to see chat messages
|
||||
from other players on the server who aren't in an arena.
|
||||
- ``global-end-announce: [true|false]`` - When set to true, MobArena
|
||||
will announce the ``arena-end-global`` message (see
|
||||
[[Announcements]]) to all players on the server when an arena ends.
|
||||
- ``global-join-announce: [true|false]`` - When set to true, MobArena
|
||||
will announce the ``arena-join-global`` message (see
|
||||
[[Announcements]]) to all players on the server when the first player
|
||||
joins an arena.
|
||||
|
||||
waves
|
||||
~~~~~
|
||||
|
||||
Please go to [[setting up the waves]] for more information.
|
||||
|
||||
rewards
|
||||
~~~~~~~
|
||||
|
||||
The rewards-section denotes which rewards the arena players can win in
|
||||
the arena. It uses the exact same item system as the classes-section
|
||||
does, so nothing new there. You can also specify monetary rewards if you
|
||||
use a major economy plugin (iConomy, BOSEconomy, Essentials Eco) in the
|
||||
notation ``$<amount>``.
|
||||
|
||||
**Note:** YAML is picky about how you type your items. Make sure you
|
||||
read the short [[Item and Reward Syntax]]-page and fully understand it
|
||||
before you attempt to modify the config file!
|
||||
|
||||
The waves-branch is broken into ``every``- and ``after``-branches. The
|
||||
``every``-branch denotes rewards that the players can receive *every* x
|
||||
waves (repeated). The ``after``-branch denotes rewards that the player
|
||||
can receive *after* wave x (only once) has started. Note that **only one
|
||||
reward** is picked at random from the list.
|
||||
|
||||
In the following example, players will receive either four arrows or a
|
||||
gold bar every 3 waves (3, 6, 9, 12, etc.), and a diamond every 10 waves
|
||||
(10, 20, 30, etc.), as well as an iron tool on wave 7 (only on wave 7),
|
||||
a diamond sword on wave 19 (only on wave 19), and 200 currency units on
|
||||
wave 20:
|
||||
|
||||
::
|
||||
|
||||
rewards:
|
||||
waves:
|
||||
every:
|
||||
'3': arrow:4, gold_ingot
|
||||
'10': diamond
|
||||
after:
|
||||
'7': iron_spade, iron_hoe, iron_axe, iron_pickaxe
|
||||
'19': diamond_sword
|
||||
'20': $200
|
||||
|
||||
**Note:** The wave numbers **must be enclosed by apostrophes** (e.g.
|
||||
``'7':``, not ``7:``), or YAML will throw errors. If you aren't sure how
|
||||
to do it, just copy one of the other lines and change the wave number
|
||||
and the items.
|
||||
|
||||
coords
|
||||
~~~~~~
|
||||
|
||||
The coords-section does not exist when MobArena first generates the
|
||||
config-file. This is because the coordinates need to be set by the user
|
||||
*in-game*. See the in-game section for more details on how to set
|
||||
everything up. The coords-section consists of five key points, and an
|
||||
arbitrary amount of spawnpoints:
|
||||
|
||||
- ``p1`` and ``p2`` - These two points should span the entire arena
|
||||
region (including spectator areas and the lobby associated with the
|
||||
arena, if possible).
|
||||
- ``l1`` and ``l2`` - [OPTIONAL] If the lobby can't properly reside
|
||||
within the arena region for some reason, these two points should span
|
||||
the lobby region.
|
||||
- ``arena`` - This warp is where the players will be teleported upon
|
||||
arena start.
|
||||
- ``lobby`` - Where the players will be teleported upon joining the
|
||||
arena.
|
||||
- ``spectator`` - Where the players will be teleported upon death or
|
||||
spectating.
|
||||
- ``spawnpoints`` - A list of points where monsters can spawn from.
|
||||
|
||||
Note that editing these points manually can have some very unhappy
|
||||
consequences. Always edit these points from within Minecraft to ensure
|
||||
that they are generated properly.
|
|
@ -1,493 +0,0 @@
|
|||
################
|
||||
Setting up waves
|
||||
################
|
||||
|
||||
**On this page:** \* `About
|
||||
Modules <./Setting-up-the-waves#wiki-about-modules>`__ \* `Wave
|
||||
Branches <./Setting-up-the-waves#wiki-wave-branches>`__ \* `Common
|
||||
Nodes <./Setting-up-the-waves#wiki-common-nodes>`__ \*
|
||||
```recurrent`` <./Setting-up-the-waves#wiki-recurrent-waves>`__ \*
|
||||
```single`` <./Setting-up-the-waves#wiki-single-waves>`__ \* `Wave
|
||||
Types <./Setting-up-the-waves#wiki-wave-types>`__ \*
|
||||
```default`` <./Setting-up-the-waves#wiki-default-waves>`__ \*
|
||||
```special`` <./Setting-up-the-waves#wiki-special-waves>`__ \*
|
||||
```swarm`` <./Setting-up-the-waves#wiki-swarm-waves>`__ \*
|
||||
```supply`` <./Setting-up-the-waves#wiki-supply-waves>`__ \*
|
||||
```upgrade`` <./Setting-up-the-waves#wiki-upgrade-waves>`__ \*
|
||||
```boss`` <./Setting-up-the-waves#wiki-boss-waves>`__ \* `A Sample
|
||||
Setup <./Setting-up-the-waves#wiki-sample-config-file-setup>`__
|
||||
|
||||
**Note: If you are impatient, go to the bottom of this page for an
|
||||
example config-file setup to see what the waves could look like. Modify
|
||||
them as you please, but make sure to read this page before asking any
|
||||
questions!**
|
||||
|
||||
Make sure to check out `Agnate's MobArena Bosses and Waves
|
||||
Thread <http://forums.bukkit.org/threads/mobarena-boss-and-wave-thread.31797/>`__
|
||||
if you need inspiration for adding some cool bosses to your arenas!
|
||||
|
||||
About Modules
|
||||
-------------
|
||||
|
||||
The new MobArena waves-system is extremely modular, meaning every time
|
||||
you plug in a new wave, you only have to provide the nodes required by
|
||||
the specific modules you are using. The modules can be broken into *wave
|
||||
branches* and *wave types*. The structure of the waves-section in
|
||||
config-file is the following:
|
||||
|
||||
::
|
||||
|
||||
waves:
|
||||
recurrent: <-- Wave branch
|
||||
<wave name>:
|
||||
type: <wave type> <-- Wave type
|
||||
frequency: #
|
||||
priority: #
|
||||
single: <-- Wave branch
|
||||
<wave name>:
|
||||
type: <wave type> <-- Wave type
|
||||
wave: #
|
||||
|
||||
Wave Branches
|
||||
-------------
|
||||
|
||||
The waves are split into two branches, ``recurrent`` and ``single``.
|
||||
*Recurrent waves* (may) occur more than once (as in, they repeat), given
|
||||
a frequency (how often they occur) and a priority (how "important" they
|
||||
are, i.e. which wave should spawn if two recurrent waves clash). *Single
|
||||
waves* occur just once, on the given wave (and always occur over
|
||||
recurrent waves, should they clash).
|
||||
|
||||
Common Nodes
|
||||
~~~~~~~~~~~~
|
||||
|
||||
As you can see, the two branches have one thing in common, the
|
||||
``type``-node. Other than that, their other nodes differ. However, there
|
||||
are two additional nodes that can be used regardless of branch and type
|
||||
(doesn't work for boss waves, though):
|
||||
|
||||
``amount-multiplier: <decimal value>`` (optional) minimum value of 0.1,
|
||||
this multiplier helps determine how many monsters spawn per wave
|
||||
(minimum 1). If 8 mobs are supposed to spawn, and the value is ``0.5``,
|
||||
only 4 mobs will spawn. If the value is ``3``, 24 will spawn.
|
||||
|
||||
``health-multiplier: <decimal value>`` (optional) minimum value of 0.1,
|
||||
this multiplier helps determine the health for each monster in a wave.
|
||||
If a zombie spawns with the default of 20 health points and the value is
|
||||
``0.5``, the zombie will have 10 health points. If the value is ``4``,
|
||||
it will be 80 health points.
|
||||
|
||||
These two common nodes can be used to greatly customize the difficulty
|
||||
of the monsters in each wave. If you want more monsters, just set the
|
||||
amount-multiplier higher than 1, and maybe adjust the health with the
|
||||
health-multiplier accordingly. If you want the monsters to be tougher to
|
||||
kill, just up the health-multiplier.
|
||||
|
||||
An additional node can be used to help determine where enemies will
|
||||
spawn:
|
||||
|
||||
``spawnpoints: <semi-colon separated list of spawnpoints>``
|
||||
|
||||
For example, we can make a swarm wave spawn monsters only on spawns
|
||||
``5,53,198``, ``-16,54,185``, and ``-7,53,179`` if players are in range:
|
||||
|
||||
::
|
||||
|
||||
swarm3:
|
||||
type: swarm
|
||||
wave: 11
|
||||
monster: zombie_pigman
|
||||
spawnpoints: 5,53,198; -16,54,185; -7,53,179
|
||||
|
||||
Note that these spawnpoints must exist in the ``spawnpoints``-list of
|
||||
the ``coords``-section to work.
|
||||
|
||||
Recurrent Waves
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
``type: [default|special|swarm|supply|upgrade|boss]`` (required)
|
||||
determines the wave type. Read the **Wave types** section further down
|
||||
for more details.
|
||||
|
||||
``frequency: #`` (required) determines how often the wave will/can
|
||||
spawn. With a frequency of 1, the wave can potentially spawn on every
|
||||
single wave number. The implicit default waves in MobArena have a
|
||||
frequency of 1, and the implicit special waves have a frequency of 4,
|
||||
which means the default waves (can) spawn every wave, and the special
|
||||
waves (can) spawn every 4th wave.
|
||||
|
||||
``priority: #`` (required) determines how "important" the wave is. If
|
||||
two recurrent waves clash, the wave with the highest priority will
|
||||
spawn. The implicit default waves in MobArena have a priority of 1, and
|
||||
the implicit special waves have a priority of 2, which means if the
|
||||
default and special waves clash, the special waves will spawn because
|
||||
their priority is higher.
|
||||
|
||||
``wave: #`` (optional) determines the first wave number on which this
|
||||
wave can/will spawn. This is useful for offsetting waves with similar
|
||||
frequencies. Note that if no wave is specified, it will default to the
|
||||
value of the (required) frequency-node. The implicit default waves in
|
||||
MobArena have wave value of 1, and the implicit special waves have a
|
||||
wave value of 4 (same as the frequency), which means the default waves
|
||||
may begin spawning from wave 1, and the special waves may begin spawning
|
||||
from wave 4.
|
||||
|
||||
Single Waves
|
||||
~~~~~~~~~~~~
|
||||
|
||||
``type: [default|special|swarm|supply|upgrade|boss]`` (required)
|
||||
determines the wave type. Read the **Wave types** section further down
|
||||
for more details.
|
||||
|
||||
``wave: #`` (required) determines the wave on which this wave *will*
|
||||
spawn. No matter what priority a recurrent wave have, if it clashes with
|
||||
a single wave, the single wave will always spawn instead of the
|
||||
recurrent waves. Single waves are good for extraordinary waves like
|
||||
"swarm" waves, "boss" waves or even normal waves with specific monster
|
||||
types, for instance.
|
||||
|
||||
Wave Types
|
||||
----------
|
||||
|
||||
All MobArena waves must specify a *wave type*, which must be either
|
||||
``default``, ``special``, ``swarm``, ``supply``, ``upgrade`` or
|
||||
``boss``. These different wave type modules introduce some new required
|
||||
and optional nodes. Note that no matter what the wave type is, any wave
|
||||
*must* adhere to the requirements of the wave branch (read above).
|
||||
|
||||
Default Waves
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Default waves are waves that spawn an amount of monsters picked
|
||||
semi-randomly from an optional list of monsters. The amount of monsters
|
||||
grow at a configurable (but optional) rate. If no growth or monster-list
|
||||
is specified, default waves will consist of 5 different monster types
|
||||
(zombie, skeleton, spider, creeper, wolf), all equally likely to spawn,
|
||||
spawned at the "old" growth rate (player count + wave number). Nodes:
|
||||
|
||||
``growth: [old|slow|medium|fast|psycho]`` (optional) determines how fast
|
||||
the monster count grows with every wave. ``old`` means (player count +
|
||||
wave number), but the other four use a mathematical function to
|
||||
determine the monster count, also based on player count and wave number.
|
||||
See [[Formulas]] for more info.
|
||||
|
||||
``monsters: <list of <monster>: <probability>>`` (optional) determines
|
||||
[[monster types]], and their individual probabilities of spawning on
|
||||
each wave. Note that the probabilities are just that, probabilities.
|
||||
They do not specify exact amounts, but only chance of spawning. The
|
||||
following sample will statistically spawn twice as many zombies as
|
||||
skeletons:
|
||||
|
||||
::
|
||||
|
||||
monsters:
|
||||
zombies: 10
|
||||
skeletons: 5
|
||||
|
||||
``fixed: [true|false]`` (optional) the probability values in the
|
||||
monsters list becomes amount values instead, such that the above wave
|
||||
will spawn exactly 10 zombies and 5 skeletons, regardless of player
|
||||
count and wave number.
|
||||
|
||||
Special Waves
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Special waves are waves that spawn *one type* of monster, and always a
|
||||
fixed amount. Unlike with *default waves*, the (optional) monster list
|
||||
with probabilities determines which monster out of the entire list
|
||||
should spawn. The monsters-node's notation is identical to that of
|
||||
*default waves*.
|
||||
|
||||
``monsters: <list of <monster>: <probability>>`` (optional) determines
|
||||
[[monster types]], and their probabilities of spawning on each wave. The
|
||||
following sample will statistically spawn powered-creepers twice as
|
||||
often as slimes:
|
||||
|
||||
::
|
||||
|
||||
monsters:
|
||||
powered-creepers: 4
|
||||
slimes: 2
|
||||
|
||||
Swarm Waves
|
||||
~~~~~~~~~~~
|
||||
|
||||
Like *special waves*, swarm waves spawn just *one type* of monster, but
|
||||
in a configurable (but optional) amount. The swarm wave monsters only
|
||||
have *1 health point*, meaning they will die with just one blow from
|
||||
anything. Their numbers are vast compared to default and special waves,
|
||||
however, so they may be a bit hard on some servers. Use with caution!
|
||||
|
||||
``monster: <monster>`` (required) which [[monster types]] the swarm
|
||||
consists of. Note that this is different from special waves, in that
|
||||
only one type is specified, and no probability value.
|
||||
|
||||
``amount: [low|medium|high|psycho]`` (optional) how many monsters should
|
||||
spawn. Defaults to low (which is still a lot). See [[Formulas]] for more
|
||||
info.
|
||||
|
||||
Supply Waves
|
||||
~~~~~~~~~~~~
|
||||
|
||||
These waves spawn one monster per player, and will drop a random item
|
||||
from a customized drop list (same notation as the class items). The
|
||||
monster list notation is identical to that of default and special waves.
|
||||
|
||||
::
|
||||
|
||||
drops: grilled_pork, cooked_chicken, cooked_beef, cooked_fish:2
|
||||
|
||||
Upgrade Waves
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
These waves don't spawn any monsters, but will give or upgrade items.
|
||||
The class names are optional (you don't have to give items to all
|
||||
classes), and it is possible to use the ``all`` identifier to specify
|
||||
items that will be given to all players regardless of class. The
|
||||
``give-all-items`` flag determines if all items in the list should be
|
||||
given, or just a random item off the list (like with rewards and supply
|
||||
waves).
|
||||
|
||||
**Legacy setup**: In the following example, all players get a healing
|
||||
potion, and on top of that, all Archers get 64 arrows, and all Oddjobs
|
||||
get either 2 TNT or a Netherrack:
|
||||
|
||||
::
|
||||
|
||||
upgrades:
|
||||
all: potion:8197:1
|
||||
Archer: arrow:64
|
||||
Oddjob: tnt:2, netherrack
|
||||
give-all-items: false
|
||||
|
||||
**Advanced setup**: Since MobArena v0.95, the Upgrade Waves can be set
|
||||
up to upgrade/replace certain weapons and armor, as well as add/remove
|
||||
permissions. The setup follows the setup of the classes-section. In the
|
||||
following example, the Knight class gets its diamond sword enchanted and
|
||||
its iron chestplate replaced with a diamond chestplate. The Archer just
|
||||
gets some more arrows (legacy setup) while the Wizard class gets the
|
||||
permission to cast the Forcepush spell from MagicSpells:
|
||||
|
||||
::
|
||||
|
||||
classes:
|
||||
Knight:
|
||||
armor: iron_helmet, iron_chestplate, iron_leggings, iron_boots
|
||||
items: diamond_sword
|
||||
|
||||
...
|
||||
|
||||
arenas:
|
||||
...
|
||||
waves:
|
||||
...
|
||||
upgrades:
|
||||
Archer: arrow:64
|
||||
Knight:
|
||||
armor: diamond_chestplate
|
||||
items: diamond_sword 16:2
|
||||
Wizard:
|
||||
permissions:
|
||||
- magicspells.cast.forcepush
|
||||
give-all-items: true
|
||||
|
||||
Explanation: Items listed in the ``armor`` node will be considered
|
||||
armor, and (if valid) will replace any item currently in the armor slots
|
||||
of the players. Items in the ``items`` node will be checked if they are
|
||||
weapons or not; if they are weapons, then MobArena will search through
|
||||
the players' inventories for weapons with the same ID, and then replace
|
||||
the first weapon that matches it (automatic upgrades). If no weapon is
|
||||
found, it will default to a generic item, which will just be added to
|
||||
the inventory.
|
||||
|
||||
Boss Waves
|
||||
~~~~~~~~~~
|
||||
|
||||
Boss waves consist of *one monster* with a configurable (but optional)
|
||||
amount of health, and a configurable (but optional) list of special
|
||||
abilities. The health of a boss monster is significantly higher than
|
||||
that of normal monsters, and thus take much longer to kill. The special
|
||||
abilities help increase the difficulty (and fun!) of a boss wave.
|
||||
|
||||
``monster: <monster>`` (required) the boss [[monster types]]. Note that
|
||||
only one monster will spawn.
|
||||
|
||||
``name: <name>`` (optional) the name of the boss. Shows the given name
|
||||
in a name tag above the boss' head.
|
||||
|
||||
``health: <amount>|[verylow|low|medium|high|veryhigh|psycho]``
|
||||
(optional) how much health the boss has. Can be either a flat value,
|
||||
e.g. 40 or 800, or one of the scaling values. Defaults to the scaling
|
||||
value medium. See [[Formulas]] for more info about the scaling values.
|
||||
|
||||
``reward: <item>`` (optional) a reward for getting the killing blow on
|
||||
the boss. This reward will only be given to one player (the killer, if
|
||||
any).
|
||||
|
||||
``drops: <item list>`` (optional) a comma-separated list of items
|
||||
dropped by the boss when killed. The boss will drop exactly the items
|
||||
listed. This could be used to have the boss drop a "key" to advance in
|
||||
the arena, or to gain access to a shed full of weapon chests or
|
||||
something wonderful like that. The item syntax is the same as the one
|
||||
for Supply Waves.
|
||||
|
||||
``potions: <potion list>`` (optional) a comma-separated list of potion
|
||||
effects that will be applied to the boss when it spawns. Use this to
|
||||
slow down or speed up bosses that don't move at quite the speed you
|
||||
want, or perhaps to give a boss the wither effect to limit the amount of
|
||||
time it will stay alive. The potion syntax is
|
||||
``<effect>:<amplifier>:<seconds>``. The amplifier and duration are
|
||||
optional, and will default to 0 (level 1) and pseudo-infinity,
|
||||
respectively. Note that ``slow``, ``slow:0``, and ``slow:0:600`` are
|
||||
identical, except the last one will only last 10 minutes (600 seconds).
|
||||
Check the sample config-file at the bottom for more examples.
|
||||
|
||||
``abilities: <comma-separated list of boss abilities>`` (optional)
|
||||
determines which (if any) boss abilities this boss has. The boss can
|
||||
have several abilities; just separate each ability with a comma (e.g.
|
||||
``arrows, fire-aura, throw-target``). Note that the abilities happen in
|
||||
a cycle every few seconds, so the more abilities, the longer it takes
|
||||
before each ability is used again. Here is an overview of the different
|
||||
abilities bosses can have:
|
||||
|
||||
::
|
||||
|
||||
NAME DESCRIPTION
|
||||
arrows Shoots arrows
|
||||
fireballs Hurls fireballs
|
||||
fire-aura Burns all nearby (5 blocks radius) players
|
||||
lightning-aura Strikes lightning 4 places around itself (3-block radius)
|
||||
living-bomb A random player is set on fire, and explodes after 3 seconds
|
||||
obsidian-bomb Spawns an Obsidian block which explodes after 4 seconds
|
||||
chain-lightning Lightning strikes the target and jumps to a nearby player
|
||||
disorient-target Spins the target around 45-315 degrees
|
||||
disorient-nearby Spins all nearby (5 blocks radius) players
|
||||
disorient-distant Spins all distant (8+ blocks) players
|
||||
root-target Locks the target in place for a couple of seconds
|
||||
warp-to-player Picks a random player in the arena to warp to
|
||||
shuffle-positions Swaps everyone's (including the boss) positions around
|
||||
flood Places a water block on a random player's location
|
||||
throw-target Throws the target backwards (if in distance)
|
||||
throw-nearby Throws all nearby (5 blocks radius) players
|
||||
throw-distant Throws all distant (8+ blocks) players
|
||||
pull-target Pulls the target towards the boss' location
|
||||
pull-nearby Pulls all nearby (5 blocks radius) players towards the boss' location
|
||||
pull-distant Pulls all distant (8+ blocks) players towards the boss' location
|
||||
fetch-target Warps the target to the boss' location
|
||||
fetch-nearby Warps all nearby (5 blocks radius) players to the boss' location
|
||||
fetch-distant Warps all distant (8+ blocks) players to the boss' location
|
||||
|
||||
``ability-announce: [true|false]`` (optional) should boss abilities be
|
||||
announced to arena players? Defaults to true.
|
||||
|
||||
``ability-interval: <seconds>`` (optional) time between each ability.
|
||||
Defaults to 3.
|
||||
|
||||
Sample config-file setup
|
||||
------------------------
|
||||
|
||||
If you want to try a sample setup, here's one that you can use. Simply
|
||||
copy this block of text, and paste it into your own config-file,
|
||||
replacing the waves-section.
|
||||
|
||||
::
|
||||
|
||||
waves:
|
||||
recurrent:
|
||||
def1:
|
||||
type: default
|
||||
priority: 1
|
||||
frequency: 1
|
||||
monsters:
|
||||
zombies: 10
|
||||
skeletons: 4
|
||||
exploding_sheep: 5
|
||||
def2:
|
||||
type: default
|
||||
priority: 2
|
||||
frequency: 1
|
||||
wave: 5
|
||||
monsters:
|
||||
zombies: 10
|
||||
skeletons: 6
|
||||
creepers: 4
|
||||
spec1:
|
||||
type: special
|
||||
priority: 5
|
||||
frequency: 4
|
||||
wave: 4
|
||||
monsters:
|
||||
powered_creepers: 10
|
||||
angry_wolves: 10
|
||||
zombie_pigmen: 10
|
||||
upgrade1:
|
||||
type: upgrade
|
||||
priority: 7
|
||||
frequency: 10
|
||||
wave: 10
|
||||
upgrades:
|
||||
all: potion:8197:2
|
||||
Archer: arrow:64
|
||||
Oddjob: tnt:2, netherrack
|
||||
give-all-items: true
|
||||
single:
|
||||
swarm1:
|
||||
type: swarm
|
||||
wave: 7
|
||||
monster: slimes
|
||||
amount: medium
|
||||
boss1:
|
||||
type: boss
|
||||
wave: 9
|
||||
monster: spider
|
||||
health: medium
|
||||
abilities: fire-aura, disorient-target, fireballs, throw-nearby
|
||||
potions: speed:3:20, wither, increase_damage:1
|
||||
ability-interval: 5
|
||||
boss2:
|
||||
type: boss
|
||||
wave: 13
|
||||
monster: zombie_pigman
|
||||
health: high
|
||||
abilities: root-target, arrows, fetch-distant, fire-aura
|
||||
drops: lever, stone_button
|
||||
upgrade2:
|
||||
type: upgrade
|
||||
wave: 14
|
||||
upgrades:
|
||||
all: potion:8197:2
|
||||
Knight:
|
||||
armor: diamond_helmet
|
||||
items: diamond_sword 16:2;19:1
|
||||
Tank:
|
||||
items: iron_sword 19:3
|
||||
Oddjob:
|
||||
armor: iron_chestplate, iron_leggings
|
||||
Wizard:
|
||||
permissions:
|
||||
- magicspells.cast.ChainLightning
|
||||
give-all-items: true
|
||||
boss3:
|
||||
type: boss
|
||||
wave: 16
|
||||
monster: wolf
|
||||
health: psycho
|
||||
abilities: warp-to-player, fire-aura, throw-nearby, fireballs, fetch-target, arrows
|
||||
potions: slow:1
|
||||
ability-interval: 1
|
||||
reward: diamond_chestplate
|
||||
supply1:
|
||||
type: supply
|
||||
wave: 19
|
||||
monsters:
|
||||
cows: 10
|
||||
pigs: 5
|
||||
drops: grilled_pork, cooked_chicken, cooked_beef, cooked_fish:2
|
||||
boss4:
|
||||
type: boss
|
||||
wave: 20
|
||||
monster: blaze
|
||||
health: low
|
||||
abilities: fire-aura, throw-nearby
|
||||
potions: speed
|
||||
reward: diamond_helmet
|
|
@ -1,137 +0,0 @@
|
|||
##############
|
||||
Using MobArena
|
||||
##############
|
||||
|
||||
**On this page:** \* `Overview <#overview>`__ \* `Joining <#joining>`__
|
||||
\* `Getting a list of arenas <#getting-a-list-of-arenas>`__ \* `Picking
|
||||
a class <#the-class-command>`__ \* `Finding out who isn't
|
||||
ready <#finding-out-who-isnt-ready>`__ \* `Leaving <#leaving>`__ \*
|
||||
`Spectating <#spectating>`__ \* `A note on
|
||||
inventories <#a-note-on-inventories>`__
|
||||
|
||||
Overview
|
||||
~~~~~~~~
|
||||
|
||||
This page briefly describes the usage-commands of MobArena. Make sure to
|
||||
check out the [[MobArena Commands]] page for a list of all the commands.
|
||||
Remember that typing ``/ma help`` will get you a list of all available
|
||||
commands in-game, along with a (very) brief description of what the
|
||||
command does.
|
||||
|
||||
The commands covered on this page pertain to the usage-commands only,
|
||||
i.e. the commands that the players will use to interact with MobArena.
|
||||
The most basic commands are:
|
||||
|
||||
- ``/ma join`` - for `joining <#joining>`__ arenas to start playing
|
||||
- ``/ma leave`` - for `leaving <#leaving>`__ lobbies or arenas
|
||||
- ``/ma spec`` - for `spectating <#spectating>`__ arenas in progress
|
||||
- ``/ma arenas`` - for getting a `list of
|
||||
arenas <#getting-a-list-of-arenas>`__
|
||||
|
||||
Additionally, there are a few commands that might prove useful:
|
||||
|
||||
- ``/ma class`` - for `picking classes <#the-class-command>`__ in the
|
||||
lobby instead of punching signs
|
||||
- ``/ma notready`` - for finding out `who isn't ready
|
||||
yet <#finding-out-who-isnt-ready>`__
|
||||
|
||||
Joining
|
||||
~~~~~~~
|
||||
|
||||
To join an arena, use the ``/ma join`` command. If you have more than
|
||||
one arena, you will also need to specify an arena name. Let's say we
|
||||
have arenas ``cave`` and ``ship``. To join the Ship arena, simply type
|
||||
``/ma join ship``.
|
||||
|
||||
Upon joining, you will be taken to the lobby of the given arena. In the
|
||||
lobby, you will have to pick a class, which is traditionally done by
|
||||
punching a sign with a class name on it. You can also `use a
|
||||
command <#the-class-command>`__ directly to manually pick a class if you
|
||||
know its name, or indirectly via buttons powering command blocks, or
|
||||
perhaps something more complex (like NPCs).
|
||||
|
||||
Once you've picked a class, you will need to ready up, which is
|
||||
traditionally done by punching an iron block. However, there is also a
|
||||
per-arena config-file setting that automatically flags you as ready once
|
||||
you've picked a class (check the [[Setting up the config-file]] page).
|
||||
|
||||
Getting a list of arenas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To get a list of available arenas, you simply type ``/ma arenas``.
|
||||
Arenas that have been set up and are ready for use will be green, and
|
||||
unavailable arenas (disabled or not yet set up) will be gray. Note that
|
||||
you won't see arenas for which you don't have permission, just like you
|
||||
won't be able to actually join an arena for which you don't have
|
||||
permission. Check the [[Permissions]] page for more information.
|
||||
|
||||
The class command
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Normally, you would punch class signs to pick classes, but MobArena also
|
||||
supports picking a class with the ``/ma class`` command. If you want to
|
||||
pick the Knight class, for example, simply type ``/ma class knight``.
|
||||
Forcing manual class selection like this is not recommended, because
|
||||
it's unintuitive and there is no way of listing available classes.
|
||||
Instead, this command is useful if you want to set up command blocks or
|
||||
something more advanced like NPCs wearing the class items and armor, and
|
||||
allowing players to pick the class by interacting with the NPCs.
|
||||
|
||||
Finding out who isn't ready
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Sometimes it's hard to keep track of who hit the iron block and who
|
||||
didn't. To figure out which of the players in the lobby have not yet
|
||||
readied up, you can use the ``/ma notready`` command. This is useful if
|
||||
everyone thinks they've readied up and are waiting for everyone else,
|
||||
but in fact one or more people haven't readied up yet.
|
||||
|
||||
Leaving
|
||||
~~~~~~~
|
||||
|
||||
When you're done playing in the arena, you have to leave it with the
|
||||
``/ma leave`` command (unless the ``spectate-after-death`` setting in
|
||||
the config-file is set to ``false``, in which case the command will be
|
||||
executed for you). You may also leave the lobby of an arena you didn't
|
||||
actually want to join, or if you just don't want to play anymore, and
|
||||
you don't want to wait for death.
|
||||
|
||||
Leaving an arena in progress has no consequences other than missing out
|
||||
on the fun! You'll still be given the rewards you earned up to this
|
||||
point. If you don't want your players to be able to leave arenas in
|
||||
progress, you can revoke the permission on a per-class basis. See the
|
||||
[[Setting up the config-file]] page for more information.
|
||||
|
||||
Spectating
|
||||
~~~~~~~~~~
|
||||
|
||||
If you want to spectate an arena already in progress, use the
|
||||
``/ma spec`` command, just like you would use the ``/ma join`` command.
|
||||
Spectating an arena means you will be taken to the spectator area of the
|
||||
arena so you have a great overlook of the arena.
|
||||
|
||||
Spectators cannot interact with the arena players or the monsters in the
|
||||
arena, nor with each other, so there should be no hooligan fights
|
||||
whatsoever.
|
||||
|
||||
Note that when you die in an arena, you will automatically become a
|
||||
spectator, unless ``spectate-after-death`` is set to ``false``.
|
||||
|
||||
A note on inventories
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Once you have joined an arena, either for playing or for spectating, you
|
||||
are considered as *in the arena*. What this means is that MobArena will
|
||||
hold on to your inventory until you leave. You leave by typing
|
||||
``/ma leave``, either from the lobby or the arena, or after you've died.
|
||||
If the ``spectate-after-death`` option in the config-file is ``false``,
|
||||
you are automatically kicked out of the arena when you die, so in that
|
||||
case, you won't have to do anything.
|
||||
|
||||
Inventories are stored on a per-player basis until they leave. Joining a
|
||||
new arena (or the same arena again) after finishing a session will not
|
||||
cause your inventory to be overwritten, even though it seems like you
|
||||
have no items. When you leave with the ``/ma leave`` command, all of
|
||||
your earned rewards from all sessions since you first joined will be
|
||||
granted. Of course, with ``spectate-after-death`` set to ``false``, you
|
||||
will automatically leave after every session.
|
|
@ -1,92 +0,0 @@
|
|||
#############
|
||||
Wave formulas
|
||||
#############
|
||||
|
||||
This page holds an overview of all the formulas used in the MobArena
|
||||
waves system. Customizing the different properties of the waves should
|
||||
be somewhat easier if they can be calculated, so here they all are!
|
||||
|
||||
About notation: Each variable used in the formulas will have its own
|
||||
name. A variable that starts with a ``#`` denotes "number (of)", so
|
||||
``#players`` means "number of players", and ``#wave`` means "wave
|
||||
number". The function ``min(a,b)`` returns the lowest of the values
|
||||
``a`` and ``b``, and ``max(a,b)`` returns the highest.
|
||||
|
||||
Wave growth
|
||||
~~~~~~~~~~~
|
||||
|
||||
The wave growth node ``growth``, used in default waves, denotes how fast
|
||||
monster amounts grow over time. The base is calculated by half of the
|
||||
number of players, but at most 13 (i.e. there is no difference between
|
||||
25 and 50 players). The amounts can be altered further using the
|
||||
``amount-multiplier`` (see the [[wave setup page\|Setting up the
|
||||
waves]]).
|
||||
|
||||
::
|
||||
|
||||
#monsters = base * #wave^exp
|
||||
base = min(#players/2 + 1 , 13)
|
||||
|
||||
The ``exp`` variable is defined by the growth node, and has the
|
||||
following values:
|
||||
|
||||
::
|
||||
|
||||
slow = 0.5
|
||||
medium = 0.65
|
||||
fast = 0.8
|
||||
psycho = 1.2
|
||||
|
||||
Note that with the node value ``old`` (which is the default), the
|
||||
monster count is ``#wave + #players``.
|
||||
|
||||
Swarm Amount
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The swarm amount node ``amount``, used in swarm waves, denotes how many
|
||||
monsters should spawn in the swarm waves. There will always be at least
|
||||
10 monsters due to the max function and the lowest multiplier value
|
||||
being 10, however this can be further customized with the
|
||||
``amount-multiplier`` (see the [[wave setup page\|Setting up the
|
||||
waves]]).
|
||||
|
||||
::
|
||||
|
||||
#monsters = max(1, #players/2) * multiplier
|
||||
|
||||
The ``multiplier`` variable is defined by the amount node, and has the
|
||||
following values:
|
||||
|
||||
::
|
||||
|
||||
low = 10
|
||||
medium = 20
|
||||
high = 30
|
||||
psycho = 60
|
||||
|
||||
Boss Health
|
||||
~~~~~~~~~~~
|
||||
|
||||
The boss health node ``health``, used in boss waves, denotes how much
|
||||
health the boss has. Note that the ``health-multiplier`` node (see the
|
||||
[[wave setup page\|Setting up the waves]]) **does NOT** affect boss
|
||||
waves at all, so these are the only values that can be used. The minimum
|
||||
health a boss can have is 320 health points (~160 hearts), which is with
|
||||
``low`` health and only 1 player fighting. With 10 players and ``high``
|
||||
health, the boss will have 5500 health points (~2750 hearts).
|
||||
|
||||
::
|
||||
|
||||
health = (#players + 1) * 20 * multiplier
|
||||
|
||||
The ``multiplier`` variable is defined by the health node, and has the
|
||||
following values:
|
||||
|
||||
::
|
||||
|
||||
verylow = 4
|
||||
low = 8
|
||||
medium = 15
|
||||
high = 25
|
||||
veryhigh = 40
|
||||
psycho = 60
|
Binary file not shown.
|
@ -0,0 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
|
@ -0,0 +1,249 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,92 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
154
pom.xml
154
pom.xml
|
@ -1,154 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.garbagemule</groupId>
|
||||
<artifactId>mobarena</artifactId>
|
||||
<name>MobArena</name>
|
||||
<version>0.104.1</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Use Java 8 for compilation -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.3</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Rename the final jar-file to MobArena.jar -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.5</version>
|
||||
<configuration>
|
||||
<finalName>MobArena</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>com.garbagemule.MobArena.metrics</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
<resources>
|
||||
<!-- Resource filtering; use Maven version in plugin.yml -->
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<!-- Spigot repo -->
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
|
||||
<!-- Vault repo -->
|
||||
<repository>
|
||||
<id>vault-repo</id>
|
||||
<url>http://nexus.hc.to/content/repositories/pub_releases</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>bstats-repo</id>
|
||||
<url>http://repo.codemc.org/repository/maven-public/</url>
|
||||
</repository>
|
||||
|
||||
<!-- JitPack for MagicSpells -->
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spigot-Bukkit API -->
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>bukkit</artifactId>
|
||||
<version>1.13-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Vault -->
|
||||
<dependency>
|
||||
<groupId>net.milkbowl.vault</groupId>
|
||||
<artifactId>VaultAPI</artifactId>
|
||||
<version>1.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bstats</groupId>
|
||||
<artifactId>bstats-bukkit</artifactId>
|
||||
<version>1.5</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- MagicSpells -->
|
||||
<dependency>
|
||||
<groupId>com.github.TheComputerGeek2</groupId>
|
||||
<artifactId>MagicSpells</artifactId>
|
||||
<version>0ad7c1f1a1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JUnit -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Hamcrest matchers -->
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<version>1.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Mockito -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>2.18.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,235 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
VERSION_PREFIX = '## '
|
||||
SECTION_PREFIX = '### '
|
||||
LIST_ITEM_PREFIX = '- '
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
lines = extract(args.version)
|
||||
output(lines, args.format)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'version',
|
||||
help='the version to extract release notes from the changelog for',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--format',
|
||||
'-f',
|
||||
choices=['github', 'hangar', 'spigot', 'curse'],
|
||||
help='the format to output the release notes in',
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def extract(target):
|
||||
filename = 'changelog.md'
|
||||
if not os.path.isfile(filename):
|
||||
filename = os.path.join('..', filename)
|
||||
if not os.path.isfile(filename):
|
||||
print('error: changelog.md not found!')
|
||||
sys.exit(1)
|
||||
|
||||
lines = []
|
||||
|
||||
with open(filename) as changelog:
|
||||
found = False
|
||||
|
||||
for entry in changelog:
|
||||
if entry.startswith(VERSION_PREFIX):
|
||||
if found:
|
||||
break
|
||||
|
||||
i = entry.find('[') + 1
|
||||
j = entry.find(']')
|
||||
version = entry[i:j]
|
||||
|
||||
if version == target:
|
||||
if version[0].isdigit():
|
||||
version = f'v{version}'
|
||||
|
||||
lines.append(f'{VERSION_PREFIX}{version}')
|
||||
lines.append('')
|
||||
found = True
|
||||
|
||||
continue
|
||||
|
||||
if not found:
|
||||
continue
|
||||
|
||||
lines.append(entry.strip())
|
||||
|
||||
if not found:
|
||||
print(f'error: version {target} not found!')
|
||||
sys.exit(1)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def output(lines, fmt):
|
||||
if fmt == 'github':
|
||||
output_as_github_markdown(lines)
|
||||
elif fmt == 'hangar':
|
||||
output_as_hangar_markdown(lines)
|
||||
elif fmt == 'spigot':
|
||||
output_as_spigot_bbcode(lines)
|
||||
elif fmt == 'curse':
|
||||
output_as_curseforge_html(lines)
|
||||
else:
|
||||
output_raw(lines)
|
||||
|
||||
|
||||
def output_as_github_markdown(lines):
|
||||
"""
|
||||
GitHub Releases Markdown is printed as the raw output from the changelog
|
||||
except for the version header (the first line), because the version number
|
||||
is already used as the release title, so we don't want it to appear twice.
|
||||
"""
|
||||
output_raw(lines[1:])
|
||||
|
||||
|
||||
def output_as_hangar_markdown(lines):
|
||||
"""
|
||||
Hangar Versions use Markdown in the same format as GitHub Releases, so we
|
||||
don't actually need to do anything else here either. Just strip the first
|
||||
line so we don't get a duplicate header.
|
||||
"""
|
||||
output_raw(lines[1:])
|
||||
|
||||
|
||||
def output_as_spigot_bbcode(lines):
|
||||
"""
|
||||
Spigot uses BBCode for resource update descriptions. It's very similar to
|
||||
regular HTML, which makes it fairly easy to convert from Markdown. We just
|
||||
need to use a [FONT] tag with Courier New for code bits.
|
||||
"""
|
||||
listing = False
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith(VERSION_PREFIX):
|
||||
i = len(VERSION_PREFIX)
|
||||
version = line[i:]
|
||||
print(f'[B]{version}[/B]')
|
||||
continue
|
||||
|
||||
if line.startswith(SECTION_PREFIX):
|
||||
if listing:
|
||||
print('[/LIST]')
|
||||
listing = False
|
||||
|
||||
i = len(SECTION_PREFIX)
|
||||
section = line[i:]
|
||||
print(f'[B]{section}:[/B]')
|
||||
continue
|
||||
|
||||
if line.startswith(LIST_ITEM_PREFIX):
|
||||
if not listing:
|
||||
print('[LIST]')
|
||||
listing = True
|
||||
|
||||
i = len(LIST_ITEM_PREFIX)
|
||||
item = line[i:]
|
||||
|
||||
# Replace **bold** text
|
||||
item = re.sub(r'\*\*(.*?)\*\*', r'[B]\1[/B]', item)
|
||||
|
||||
# Replace _italic_ text
|
||||
item = re.sub(r'_(.*?)_', r'[I]\1[/I]', item)
|
||||
|
||||
# Replace `code` text
|
||||
item = re.sub(r'`(.*?)`', r'[FONT=Courier New]\1[/FONT]', item)
|
||||
|
||||
# Replace [links](url)
|
||||
item = re.sub(r'\[([^\]]+)]\(([^)]+)\)', r'[URL=\2]\1[/URL]', item)
|
||||
|
||||
print(f'[*]{item}')
|
||||
continue
|
||||
|
||||
if len(line) > 0:
|
||||
print(line)
|
||||
|
||||
if listing:
|
||||
print('[/LIST]')
|
||||
|
||||
|
||||
def output_as_curseforge_html(lines):
|
||||
"""
|
||||
CurseForge uses regular HTML for file update descriptions, which makes it
|
||||
fairly easy to convert from Markdown. Angled brackets need to be replaced
|
||||
with their HTML entity equivalents, but other than that it's very similar
|
||||
to the Spigot BBCode conversion.
|
||||
"""
|
||||
listing = False
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith(VERSION_PREFIX):
|
||||
i = len(VERSION_PREFIX)
|
||||
version = line[i:]
|
||||
print(f'<p><strong>{version}</strong></p>')
|
||||
continue
|
||||
|
||||
if line.startswith(SECTION_PREFIX):
|
||||
if listing:
|
||||
print('</ul>')
|
||||
listing = False
|
||||
|
||||
i = len(SECTION_PREFIX)
|
||||
section = line[i:]
|
||||
print(f'<p><strong>{section}:</strong></p>')
|
||||
continue
|
||||
|
||||
if line.startswith(LIST_ITEM_PREFIX):
|
||||
if not listing:
|
||||
print('<ul>')
|
||||
listing = True
|
||||
|
||||
i = len(LIST_ITEM_PREFIX)
|
||||
item = line[i:]
|
||||
|
||||
# Replace angled brackets
|
||||
item = item.replace('<', '<')
|
||||
item = item.replace('>', '>')
|
||||
|
||||
# Replace **bold** text
|
||||
item = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', item)
|
||||
|
||||
# Replace _italic_ text
|
||||
item = re.sub(r'_(.*?)_', r'<emph>\1</emph>', item)
|
||||
|
||||
# Replace `code` text
|
||||
item = re.sub(r'`(.*?)`', r'<code>\1</code>', item)
|
||||
|
||||
# Replace [links](url)
|
||||
item = re.sub(r'\[([^\]]+)]\(([^)]+)\)', r'<a href="\2">\1</a>', item)
|
||||
|
||||
print(f'<li>{item}</li>')
|
||||
continue
|
||||
|
||||
if len(line) > 0:
|
||||
print(line)
|
||||
|
||||
if listing:
|
||||
print('</ul>')
|
||||
|
||||
|
||||
def output_raw(lines):
|
||||
[print(line.strip()) for line in lines]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1 @@
|
|||
rootProject.name = "mobarena"
|
|
@ -3,6 +3,7 @@ package com.garbagemule.MobArena;
|
|||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.things.Thing;
|
||||
import com.garbagemule.MobArena.util.Slugs;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
|
@ -15,7 +16,8 @@ import java.util.stream.IntStream;
|
|||
|
||||
public class ArenaClass
|
||||
{
|
||||
private String configName, lowercaseName;
|
||||
private String configName;
|
||||
private String slug;
|
||||
private Thing helmet, chestplate, leggings, boots, offhand;
|
||||
private List<Thing> armor;
|
||||
private List<Thing> items;
|
||||
|
@ -25,6 +27,7 @@ public class ArenaClass
|
|||
private boolean unbreakableWeapons, unbreakableArmor;
|
||||
private Thing price;
|
||||
private Location classchest;
|
||||
private String petName;
|
||||
|
||||
/**
|
||||
* Create a new, empty arena class with the given name.
|
||||
|
@ -32,8 +35,8 @@ public class ArenaClass
|
|||
*/
|
||||
public ArenaClass(String name, Thing price, boolean unbreakableWeapons, boolean unbreakableArmor) {
|
||||
this.configName = name;
|
||||
this.lowercaseName = name.toLowerCase().replace(" ", "");
|
||||
|
||||
this.slug = Slugs.create(name);
|
||||
|
||||
this.items = new ArrayList<>();
|
||||
this.armor = new ArrayList<>(4);
|
||||
this.effects = new ArrayList<>();
|
||||
|
@ -45,7 +48,7 @@ public class ArenaClass
|
|||
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the arena class as it appears in the config-file.
|
||||
* @return the class name as it appears in the config-file
|
||||
|
@ -53,15 +56,25 @@ public class ArenaClass
|
|||
public String getConfigName() {
|
||||
return configName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the slug version of the arena class name.
|
||||
* @return the slugified class name
|
||||
*/
|
||||
public String getSlug() {
|
||||
return slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lowercase class name.
|
||||
* @return the lowercase class name
|
||||
* @deprecated use {@link #getSlug()} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public String getLowercaseName() {
|
||||
return lowercaseName;
|
||||
return slug;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the helmet slot for the class.
|
||||
* @param helmet a Thing
|
||||
|
@ -69,7 +82,7 @@ public class ArenaClass
|
|||
public void setHelmet(Thing helmet) {
|
||||
this.helmet = helmet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the chestplate slot for the class.
|
||||
* @param chestplate a Thing
|
||||
|
@ -77,7 +90,7 @@ public class ArenaClass
|
|||
public void setChestplate(Thing chestplate) {
|
||||
this.chestplate = chestplate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the leggings slot for the class.
|
||||
* @param leggings a Thing
|
||||
|
@ -85,7 +98,7 @@ public class ArenaClass
|
|||
public void setLeggings(Thing leggings) {
|
||||
this.leggings = leggings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the boots slot for the class.
|
||||
* @param boots a Thing
|
||||
|
@ -93,7 +106,7 @@ public class ArenaClass
|
|||
public void setBoots(Thing boots) {
|
||||
this.boots = boots;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the off-hand slot for the class.
|
||||
* @param offhand a Thing
|
||||
|
@ -111,7 +124,7 @@ public class ArenaClass
|
|||
items.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace the current items list with a new list of all the items in the given list.
|
||||
* This method uses the addItem() method for each item to ensure consistency.
|
||||
|
@ -121,7 +134,7 @@ public class ArenaClass
|
|||
this.items = new ArrayList<>(items.size());
|
||||
items.forEach(this::addItem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace the current armor list with the given list.
|
||||
* @param armor a list of Things
|
||||
|
@ -133,10 +146,23 @@ public class ArenaClass
|
|||
public void setEffects(List<Thing> effects) {
|
||||
this.effects = effects;
|
||||
}
|
||||
|
||||
public String getPetName() {
|
||||
return this.petName;
|
||||
}
|
||||
|
||||
public void setPetName(String petName) {
|
||||
this.petName = petName;
|
||||
}
|
||||
|
||||
public boolean hasPermission(Player p) {
|
||||
String perm = "mobarena.classes." + configName;
|
||||
return !p.isPermissionSet(perm) || p.hasPermission(perm);
|
||||
String key = "mobarena.classes." + slug;
|
||||
if (p.isPermissionSet(key)) {
|
||||
return p.hasPermission(key);
|
||||
}
|
||||
|
||||
// Permissive by default.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,7 +170,7 @@ public class ArenaClass
|
|||
* The normal items will be added to the inventory normally, while the
|
||||
* armor items will be verified as armor items and placed in their
|
||||
* appropriate slots. If any specific armor slots are specified, they
|
||||
* will overwrite any items in the armor list.
|
||||
* will overwrite any items in the armor list.
|
||||
* @param p a player
|
||||
*/
|
||||
public void grantItems(Player p) {
|
||||
|
@ -152,7 +178,7 @@ public class ArenaClass
|
|||
|
||||
// Fork over the items.
|
||||
items.forEach(item -> item.giveTo(p));
|
||||
|
||||
|
||||
// Check for legacy armor-node items
|
||||
armor.forEach(thing -> thing.giveTo(p));
|
||||
|
||||
|
@ -167,7 +193,7 @@ public class ArenaClass
|
|||
public void grantPotionEffects(Player p) {
|
||||
effects.forEach(thing -> thing.giveTo(p));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a permission value to the class.
|
||||
*/
|
||||
|
@ -212,20 +238,20 @@ public class ArenaClass
|
|||
public Thing getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) return false;
|
||||
if (this == o) return true;
|
||||
if (!this.getClass().equals(o.getClass())) return false;
|
||||
|
||||
|
||||
ArenaClass other = (ArenaClass) o;
|
||||
return other.lowercaseName.equals(this.lowercaseName);
|
||||
return other.slug.equals(this.slug);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return lowercaseName.hashCode();
|
||||
return slug.hashCode();
|
||||
}
|
||||
|
||||
public static class MyItems extends ArenaClass {
|
||||
|
@ -269,7 +295,7 @@ public class ArenaClass
|
|||
case SHULKER_SHELL:
|
||||
return true;
|
||||
}
|
||||
return type.name().endsWith("_SHULKER_BOX");
|
||||
return type.name().endsWith("SHULKER_BOX");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@ package com.garbagemule.MobArena;
|
|||
import static com.garbagemule.MobArena.util.config.ConfigUtils.makeSection;
|
||||
|
||||
import com.garbagemule.MobArena.ScoreboardManager.NullScoreboardManager;
|
||||
import com.garbagemule.MobArena.announce.Announcer;
|
||||
import com.garbagemule.MobArena.announce.MessengerAnnouncer;
|
||||
import com.garbagemule.MobArena.announce.TitleAnnouncer;
|
||||
import com.garbagemule.MobArena.steps.Step;
|
||||
import com.garbagemule.MobArena.steps.StepFactory;
|
||||
import com.garbagemule.MobArena.steps.PlayerJoinArena;
|
||||
|
@ -21,7 +24,9 @@ import com.garbagemule.MobArena.repairable.RepairableComparator;
|
|||
import com.garbagemule.MobArena.repairable.RepairableContainer;
|
||||
import com.garbagemule.MobArena.things.InvalidThingInputString;
|
||||
import com.garbagemule.MobArena.things.Thing;
|
||||
import com.garbagemule.MobArena.things.ThingPicker;
|
||||
import com.garbagemule.MobArena.util.ClassChests;
|
||||
import com.garbagemule.MobArena.util.Slugs;
|
||||
import com.garbagemule.MobArena.util.inventory.InventoryManager;
|
||||
import com.garbagemule.MobArena.util.timer.AutoStartTimer;
|
||||
import com.garbagemule.MobArena.util.timer.StartDelayTimer;
|
||||
|
@ -73,65 +78,70 @@ public class ArenaImpl implements Arena
|
|||
// General stuff
|
||||
private MobArena plugin;
|
||||
private String name;
|
||||
private String slug;
|
||||
private World world;
|
||||
private Messenger messenger;
|
||||
|
||||
private Announcer announcer;
|
||||
|
||||
// Settings section of the config-file for this arena.
|
||||
private ConfigurationSection settings;
|
||||
|
||||
|
||||
// Run-time settings and critical config settings
|
||||
private boolean enabled, protect, running, edit;
|
||||
|
||||
|
||||
// World stuff
|
||||
private boolean allowMonsters, allowAnimals;
|
||||
//private Difficulty spawnMonsters;
|
||||
|
||||
|
||||
// Warps, points and locations
|
||||
private ArenaRegion region;
|
||||
private Leaderboard leaderboard;
|
||||
|
||||
|
||||
// Player stuff
|
||||
private InventoryManager inventoryManager;
|
||||
private RewardManager rewardManager;
|
||||
private ClassLimitManager limitManager;
|
||||
private Map<Player,ArenaPlayer> arenaPlayerMap;
|
||||
|
||||
|
||||
private Set<Player> arenaPlayers, lobbyPlayers, readyPlayers, specPlayers, deadPlayers;
|
||||
private Set<Player> movingPlayers;
|
||||
private Set<Player> leavingPlayers;
|
||||
private Set<Player> randoms;
|
||||
|
||||
|
||||
// Classes stuff
|
||||
private ArenaClass defaultClass;
|
||||
private Map<String,ArenaClass> classes;
|
||||
|
||||
|
||||
// Blocks and pets
|
||||
private PriorityBlockingQueue<Repairable> repairQueue;
|
||||
private Set<Block> blocks;
|
||||
private LinkedList<Repairable> repairables, containables;
|
||||
|
||||
|
||||
// Monster stuff
|
||||
private MonsterManager monsterManager;
|
||||
|
||||
|
||||
// Wave stuff
|
||||
private WaveManager waveManager;
|
||||
private MASpawnThread spawnThread;
|
||||
private SheepBouncer sheepBouncer;
|
||||
private Map<Integer,List<Thing>> everyWaveMap, afterWaveMap;
|
||||
|
||||
private Map<Integer, ThingPicker> everyWaveMap, afterWaveMap;
|
||||
|
||||
// Misc
|
||||
private ArenaListener eventListener;
|
||||
private List<Thing> entryFee;
|
||||
private AutoStartTimer autoStartTimer;
|
||||
private StartDelayTimer startDelayTimer;
|
||||
private boolean isolatedChat;
|
||||
|
||||
|
||||
// Warp offsets
|
||||
private double arenaWarpOffset;
|
||||
|
||||
// Scoreboards
|
||||
private ScoreboardManager scoreboard;
|
||||
|
||||
// Last player standing
|
||||
private Player lastStanding;
|
||||
|
||||
|
||||
// Actions
|
||||
private Map<Player, Step> histories;
|
||||
private StepFactory playerJoinArena;
|
||||
|
@ -145,13 +155,14 @@ public class ArenaImpl implements Arena
|
|||
public ArenaImpl(MobArena plugin, ConfigurationSection section, String name, World world) {
|
||||
if (world == null)
|
||||
throw new NullPointerException("[MobArena] ERROR! World for arena '" + name + "' does not exist!");
|
||||
|
||||
|
||||
this.name = name;
|
||||
this.slug = Slugs.create(name);
|
||||
this.world = world;
|
||||
this.plugin = plugin;
|
||||
this.settings = makeSection(section, "settings");
|
||||
this.region = new ArenaRegion(section, this);
|
||||
|
||||
|
||||
this.enabled = settings.getBoolean("enabled", false);
|
||||
this.protect = settings.getBoolean("protect", true);
|
||||
this.running = false;
|
||||
|
@ -180,23 +191,24 @@ public class ArenaImpl implements Arena
|
|||
|
||||
String defaultClassName = settings.getString("default-class", null);
|
||||
if (defaultClassName != null) {
|
||||
this.defaultClass = classes.get(defaultClassName);
|
||||
String slug = Slugs.create(defaultClassName);
|
||||
this.defaultClass = classes.get(slug);
|
||||
}
|
||||
|
||||
|
||||
// Blocks and pets
|
||||
this.repairQueue = new PriorityBlockingQueue<>(100, new RepairableComparator());
|
||||
this.blocks = new HashSet<>();
|
||||
this.repairables = new LinkedList<>();
|
||||
this.containables = new LinkedList<>();
|
||||
|
||||
|
||||
// Monster stuff
|
||||
this.monsterManager = new MonsterManager();
|
||||
|
||||
|
||||
// Wave stuff
|
||||
this.waveManager = new WaveManager(this, section.getConfigurationSection("waves"));
|
||||
this.everyWaveMap = MAUtils.getArenaRewardMap(plugin, section, name, "every");
|
||||
this.afterWaveMap = MAUtils.getArenaRewardMap(plugin, section, name, "after");
|
||||
|
||||
|
||||
// Misc
|
||||
this.eventListener = new ArenaListener(this, plugin);
|
||||
this.allowMonsters = world.getAllowMonsters();
|
||||
|
@ -219,7 +231,9 @@ public class ArenaImpl implements Arena
|
|||
this.startDelayTimer = new StartDelayTimer(this, autoStartTimer);
|
||||
|
||||
this.isolatedChat = settings.getBoolean("isolated-chat", false);
|
||||
|
||||
|
||||
this.arenaWarpOffset = settings.getDouble("arena-warp-offset", 0.0);
|
||||
|
||||
// Scoreboards
|
||||
this.scoreboard = (settings.getBoolean("use-scoreboards", true) ? new ScoreboardManager(this) : new NullScoreboardManager(this));
|
||||
|
||||
|
@ -227,6 +241,16 @@ public class ArenaImpl implements Arena
|
|||
String prefix = settings.getString("prefix", "");
|
||||
this.messenger = !prefix.isEmpty() ? new Messenger(prefix) : plugin.getGlobalMessenger();
|
||||
|
||||
// Announcer
|
||||
String announcerType = settings.getString("announcer-type", "chat");
|
||||
if (announcerType.equals("chat")) {
|
||||
announcer = new MessengerAnnouncer(this.messenger);
|
||||
} else if (announcerType.equals("title")) {
|
||||
announcer = new TitleAnnouncer(5, 60, 10);
|
||||
} else {
|
||||
throw new ConfigError("Unsupported announcer type: " + announcerType);
|
||||
}
|
||||
|
||||
// Actions
|
||||
this.histories = new HashMap<>();
|
||||
this.playerJoinArena = PlayerJoinArena.create(this);
|
||||
|
@ -234,15 +258,15 @@ public class ArenaImpl implements Arena
|
|||
|
||||
this.spawnsPets = plugin.getArenaMaster().getSpawnsPets();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*/////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// NEW METHODS IN REFACTORING
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
@Override
|
||||
public ConfigurationSection getSettings() {
|
||||
return settings;
|
||||
|
@ -307,7 +331,7 @@ public class ArenaImpl implements Arena
|
|||
public int getMaxPlayers() {
|
||||
return settings.getInt("max-players");
|
||||
}
|
||||
|
||||
|
||||
private int getJoinDistance() {
|
||||
return settings.getInt("max-join-distance");
|
||||
}
|
||||
|
@ -318,12 +342,12 @@ public class ArenaImpl implements Arena
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<Integer,List<Thing>>> getEveryWaveEntrySet() {
|
||||
public Set<Map.Entry<Integer, ThingPicker>> getEveryWaveEntrySet() {
|
||||
return everyWaveMap.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Thing> getAfterWaveReward(int wave) {
|
||||
public ThingPicker getAfterWaveReward(int wave) {
|
||||
return afterWaveMap.get(wave);
|
||||
}
|
||||
|
||||
|
@ -416,25 +440,25 @@ public class ArenaImpl implements Arena
|
|||
public MonsterManager getMonsterManager() {
|
||||
return monsterManager;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ClassLimitManager getClassLimitManager() {
|
||||
return limitManager;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ScoreboardManager getScoreboard() {
|
||||
return scoreboard;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Messenger getMessenger() {
|
||||
|
@ -449,7 +473,7 @@ public class ArenaImpl implements Arena
|
|||
@Override
|
||||
public void announce(String msg) {
|
||||
for (Player p : getAllPlayers()) {
|
||||
messenger.tell(p, msg);
|
||||
announcer.announce(p, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -487,38 +511,46 @@ public class ArenaImpl implements Arena
|
|||
|
||||
// Store all chest contents.
|
||||
storeContainerContents();
|
||||
|
||||
|
||||
// Populate arenaPlayers and clear the lobby.
|
||||
arenaPlayers.addAll(lobbyPlayers);
|
||||
lobbyPlayers.clear();
|
||||
readyPlayers.clear();
|
||||
|
||||
|
||||
// Assign random classes.
|
||||
for (Player p : randoms) {
|
||||
assignRandomClass(p);
|
||||
}
|
||||
randoms.clear();
|
||||
|
||||
|
||||
// Then check if there are still players left.
|
||||
if (arenaPlayers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Initialize scoreboards
|
||||
scoreboard.initialize();
|
||||
|
||||
|
||||
// Teleport players, give full health, initialize map
|
||||
for (Player p : arenaPlayers) {
|
||||
// TODO figure out how people die in lobby and get sent to spectator area early
|
||||
// Remove player from spec list to avoid invincibility issues
|
||||
if (inSpec(p)) {
|
||||
specPlayers.remove(p);
|
||||
System.out.println("[MobArena] Player " + p.getName() + " joined the arena from the spec area!");
|
||||
System.out.println("[MobArena] Invincibility glitch attempt stopped!");
|
||||
}
|
||||
|
||||
|
||||
movingPlayers.add(p);
|
||||
p.teleport(region.getArenaWarp());
|
||||
if (arenaWarpOffset > 0.01) {
|
||||
Location warp = region.getArenaWarp();
|
||||
double x = warp.getX() + (arenaWarpOffset * 2 * (Math.random() - 0.5));
|
||||
double y = warp.getY();
|
||||
double z = warp.getZ() + (arenaWarpOffset * 2 * (Math.random() - 0.5));
|
||||
Location offset = new Location(warp.getWorld(), x, y, z);
|
||||
p.teleport(offset);
|
||||
} else {
|
||||
p.teleport(region.getArenaWarp());
|
||||
}
|
||||
movingPlayers.remove(p);
|
||||
|
||||
addClassPermissions(p);
|
||||
|
@ -528,35 +560,35 @@ public class ArenaImpl implements Arena
|
|||
if (price != null) {
|
||||
price.takeFrom(p);
|
||||
}
|
||||
|
||||
|
||||
scoreboard.addPlayer(p);
|
||||
}
|
||||
|
||||
|
||||
// Start spawning monsters (must happen before 'running = true;')
|
||||
startSpawner();
|
||||
startBouncingSheep();
|
||||
|
||||
|
||||
// Set the boolean.
|
||||
running = true;
|
||||
|
||||
|
||||
// Spawn pets (must happen after 'running = true;')
|
||||
spawnsPets.spawn(this);
|
||||
|
||||
|
||||
// Spawn mounts
|
||||
spawnMounts();
|
||||
|
||||
|
||||
// Clear the classes in use map, as they're no longer needed
|
||||
limitManager.clearClassesInUse();
|
||||
|
||||
|
||||
// Reset rewards
|
||||
rewardManager.reset();
|
||||
|
||||
|
||||
// Initialize leaderboards and start displaying info.
|
||||
leaderboard.initialize();
|
||||
leaderboard.startTracking();
|
||||
|
||||
|
||||
announce(Msg.ARENA_START);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -576,16 +608,16 @@ public class ArenaImpl implements Arena
|
|||
|
||||
// Reset last standing
|
||||
lastStanding = null;
|
||||
|
||||
|
||||
// Set the running boolean and disable arena if not disabled.
|
||||
boolean en = enabled;
|
||||
enabled = false;
|
||||
running = false;
|
||||
|
||||
|
||||
// Stop tracking leaderboards
|
||||
leaderboard.stopTracking();
|
||||
leaderboard.update();
|
||||
|
||||
|
||||
// Stop spawning.
|
||||
stopSpawner();
|
||||
stopBouncingSheep();
|
||||
|
@ -599,18 +631,23 @@ public class ArenaImpl implements Arena
|
|||
announce(Msg.ARENA_END);
|
||||
}
|
||||
cleanup();
|
||||
|
||||
|
||||
// Restore region.
|
||||
if (settings.getBoolean("soft-restore", false)) {
|
||||
restoreRegion();
|
||||
}
|
||||
|
||||
|
||||
// Restore chests
|
||||
restoreContainerContents();
|
||||
|
||||
|
||||
// Restore enabled status.
|
||||
enabled = en;
|
||||
|
||||
|
||||
// Auto-leave
|
||||
if (settings.getBoolean("auto-leave-on-end", false)) {
|
||||
specPlayers.forEach(this::playerLeave);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -619,12 +656,12 @@ public class ArenaImpl implements Arena
|
|||
{
|
||||
if (running)
|
||||
return;
|
||||
|
||||
|
||||
// Set operations.
|
||||
Set<Player> tmp = new HashSet<>();
|
||||
tmp.addAll(lobbyPlayers);
|
||||
tmp.removeAll(readyPlayers);
|
||||
|
||||
|
||||
// Force leave.
|
||||
for (Player p : tmp) {
|
||||
playerLeave(p);
|
||||
|
@ -642,15 +679,20 @@ public class ArenaImpl implements Arena
|
|||
if (players.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
players.forEach(this::playerLeave);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(Player p) {
|
||||
String perm = "mobarena.arenas." + name;
|
||||
return !p.isPermissionSet(perm) || p.hasPermission(perm);
|
||||
String key = "mobarena.arenas." + slug;
|
||||
if (p.isPermissionSet(key)) {
|
||||
return p.hasPermission(key);
|
||||
}
|
||||
|
||||
// Permissive by default.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -700,17 +742,17 @@ public class ArenaImpl implements Arena
|
|||
|
||||
lobbyPlayers.add(p);
|
||||
plugin.getArenaMaster().addPlayer(p, this);
|
||||
|
||||
|
||||
arenaPlayerMap.put(p, new ArenaPlayer(p, this, plugin));
|
||||
|
||||
// Start the start-delay-timer if applicable
|
||||
if (!autoStartTimer.isRunning()) {
|
||||
startDelayTimer.start();
|
||||
}
|
||||
|
||||
|
||||
// Notify player of joining
|
||||
messenger.tell(p, Msg.JOIN_PLAYER_JOINED);
|
||||
|
||||
|
||||
// Notify player of time left
|
||||
if (startDelayTimer.isRunning()) {
|
||||
messenger.tell(p, Msg.ARENA_START_DELAY, "" + startDelayTimer.getRemaining() / 20l);
|
||||
|
@ -721,11 +763,12 @@ public class ArenaImpl implements Arena
|
|||
if (defaultClass != null) {
|
||||
// Assign default class if applicable
|
||||
if (!ClassChests.assignClassFromStoredClassChest(this, p, defaultClass)) {
|
||||
assignClass(p, defaultClass.getLowercaseName());
|
||||
String slug = defaultClass.getSlug();
|
||||
assignClass(p, slug);
|
||||
messenger.tell(p, Msg.LOBBY_CLASS_PICKED, defaultClass.getConfigName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
movingPlayers.remove(p);
|
||||
return true;
|
||||
}
|
||||
|
@ -740,14 +783,14 @@ public class ArenaImpl implements Arena
|
|||
}
|
||||
|
||||
readyPlayers.add(p);
|
||||
|
||||
|
||||
int minPlayers = getMinPlayers();
|
||||
if (minPlayers > 0 && lobbyPlayers.size() < minPlayers)
|
||||
{
|
||||
messenger.tell(p, Msg.LOBBY_NOT_ENOUGH_PLAYERS, "" + minPlayers);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
startArena();
|
||||
}
|
||||
|
||||
|
@ -767,15 +810,18 @@ public class ArenaImpl implements Arena
|
|||
}
|
||||
leavingPlayers.add(p);
|
||||
|
||||
// Remove pets.
|
||||
monsterManager.removePets(p);
|
||||
|
||||
// Clear inventory if player is an arena player, and unmount
|
||||
if (arenaPlayers.contains(p)) {
|
||||
unmount(p);
|
||||
clearInv(p);
|
||||
}
|
||||
|
||||
|
||||
removePermissionAttachments(p);
|
||||
removePotionEffects(p);
|
||||
|
||||
|
||||
boolean refund = inLobby(p);
|
||||
|
||||
if (inLobby(p)) {
|
||||
|
@ -789,13 +835,13 @@ public class ArenaImpl implements Arena
|
|||
startDelayTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
discardPlayer(p);
|
||||
|
||||
if (refund) {
|
||||
refund(p);
|
||||
}
|
||||
|
||||
|
||||
endArena();
|
||||
|
||||
leavingPlayers.remove(p);
|
||||
|
@ -823,12 +869,15 @@ public class ArenaImpl implements Arena
|
|||
ArenaPlayerDeathEvent event = new ArenaPlayerDeathEvent(p, this, last);
|
||||
plugin.getServer().getPluginManager().callEvent(event);
|
||||
|
||||
// Remove pets.
|
||||
monsterManager.removePets(p);
|
||||
|
||||
// Clear the player's inventory, and unmount
|
||||
if (arenaPlayers.remove(p)) {
|
||||
unmount(p);
|
||||
clearInv(p);
|
||||
}
|
||||
|
||||
|
||||
deadPlayers.add(p);
|
||||
endArena();
|
||||
}
|
||||
|
@ -854,20 +903,30 @@ public class ArenaImpl implements Arena
|
|||
@Override
|
||||
public void playerRespawn(Player p) {
|
||||
deadPlayers.remove(p);
|
||||
plugin.getServer().getScheduler()
|
||||
.scheduleSyncDelayedTask(plugin, () -> revivePlayer(p));
|
||||
revivePlayer(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void revivePlayer(Player p) {
|
||||
removePermissionAttachments(p);
|
||||
removePotionEffects(p);
|
||||
|
||||
discardPlayer(p);
|
||||
|
||||
specPlayers.add(p);
|
||||
|
||||
if (settings.getBoolean("spectate-on-death", true)) {
|
||||
playerSpec(p, null);
|
||||
// At this point, we know that we want players to become
|
||||
// spectators when they respawn, but if we also want them
|
||||
// to auto-leave on arena end, we first need to make sure
|
||||
// the arena is running. If not, the session has probably
|
||||
// ended, so we fall through to schedule an auto-leave.
|
||||
if (!settings.getBoolean("auto-leave-on-end", false) || running) {
|
||||
messenger.tell(p, Msg.SPEC_PLAYER_SPECTATE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
plugin.getServer().getScheduler()
|
||||
.scheduleSyncDelayedTask(plugin, () -> playerLeave(p));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -882,7 +941,7 @@ public class ArenaImpl implements Arena
|
|||
}
|
||||
movingPlayers.add(p);
|
||||
|
||||
|
||||
|
||||
rollback(p);
|
||||
|
||||
Step step = playerSpecArena.create(p);
|
||||
|
@ -896,7 +955,7 @@ public class ArenaImpl implements Arena
|
|||
|
||||
specPlayers.add(p);
|
||||
plugin.getArenaMaster().addPlayer(p, this);
|
||||
|
||||
|
||||
messenger.tell(p, Msg.SPEC_PLAYER_SPECTATE);
|
||||
movingPlayers.remove(p);
|
||||
}
|
||||
|
@ -985,7 +1044,7 @@ public class ArenaImpl implements Arena
|
|||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void startSpawner() {
|
||||
if (spawnThread != null) {
|
||||
spawnThread.stop();
|
||||
|
@ -997,7 +1056,7 @@ public class ArenaImpl implements Arena
|
|||
spawnThread = new MASpawnThread(plugin, this);
|
||||
spawnThread.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Schedule a Runnable to be executed after the given delay in
|
||||
* server ticks. The method is used by the MASpawnThread to
|
||||
|
@ -1008,7 +1067,7 @@ public class ArenaImpl implements Arena
|
|||
public void scheduleTask(Runnable r, int delay) {
|
||||
Bukkit.getScheduler().runTaskLater(plugin, r, delay);
|
||||
}
|
||||
|
||||
|
||||
private void stopSpawner() {
|
||||
if (spawnThread == null) {
|
||||
plugin.getLogger().warning("Can't stop non-existent spawner in arena " + configName() + ". This should never happen.");
|
||||
|
@ -1020,7 +1079,7 @@ public class ArenaImpl implements Arena
|
|||
|
||||
world.setSpawnFlags(allowMonsters, allowAnimals);
|
||||
}
|
||||
|
||||
|
||||
private void startBouncingSheep() {
|
||||
if (sheepBouncer != null) {
|
||||
sheepBouncer.stop();
|
||||
|
@ -1067,7 +1126,7 @@ public class ArenaImpl implements Arena
|
|||
plugin.getArenaMaster().removePlayer(p);
|
||||
clearPlayer(p);
|
||||
}
|
||||
|
||||
|
||||
private void clearPlayer(Player p)
|
||||
{
|
||||
// Remove from boss health bar
|
||||
|
@ -1077,20 +1136,17 @@ public class ArenaImpl implements Arena
|
|||
boss.getHealthBar().removePlayer(p);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove pets.
|
||||
monsterManager.removePets(p);
|
||||
|
||||
|
||||
// readyPlayers before lobbyPlayers because of startArena sanity-checks
|
||||
readyPlayers.remove(p);
|
||||
specPlayers.remove(p);
|
||||
arenaPlayers.remove(p);
|
||||
lobbyPlayers.remove(p);
|
||||
arenaPlayerMap.remove(p);
|
||||
|
||||
|
||||
scoreboard.removePlayer(p);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void repairBlocks()
|
||||
{
|
||||
|
@ -1103,9 +1159,9 @@ public class ArenaImpl implements Arena
|
|||
{
|
||||
repairQueue.add(r);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Items & Cleanup
|
||||
|
@ -1116,11 +1172,11 @@ public class ArenaImpl implements Arena
|
|||
public void assignClass(Player p, String className) {
|
||||
ArenaPlayer arenaPlayer = arenaPlayerMap.get(p);
|
||||
ArenaClass arenaClass = classes.get(className);
|
||||
|
||||
|
||||
if (arenaPlayer == null || arenaClass == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
InventoryManager.clearInventory(p);
|
||||
removePotionEffects(p);
|
||||
arenaPlayer.setArenaClass(arenaClass);
|
||||
|
@ -1143,34 +1199,42 @@ public class ArenaImpl implements Arena
|
|||
|
||||
autoReady(p);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void assignClassGiveInv(Player p, String className, ItemStack[] contents) {
|
||||
public void assignClassGiveInv(Player p, String className, ItemStack[] source) {
|
||||
ArenaPlayer arenaPlayer = arenaPlayerMap.get(p);
|
||||
ArenaClass arenaClass = classes.get(className);
|
||||
|
||||
|
||||
if (arenaPlayer == null || arenaClass == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
InventoryManager.clearInventory(p);
|
||||
removePermissionAttachments(p);
|
||||
removePotionEffects(p);
|
||||
arenaPlayer.setArenaClass(arenaClass);
|
||||
|
||||
|
||||
PlayerInventory inv = p.getInventory();
|
||||
|
||||
// Clone the source array to make sure we don't modify its contents
|
||||
ItemStack[] contents = new ItemStack[source.length];
|
||||
for (int i = 0; i < source.length; i++) {
|
||||
if (source[i] != null) {
|
||||
contents[i] = source[i].clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Collect armor items, because setContents() now overwrites everyhing
|
||||
ItemStack helmet = null;
|
||||
ItemStack chestplate = null;
|
||||
ItemStack leggings = null;
|
||||
ItemStack boots = null;
|
||||
ItemStack offhand = null;
|
||||
|
||||
|
||||
// Check the very last slot to see if it'll work as a helmet
|
||||
int last = contents.length-1;
|
||||
if (contents[last] != null) {
|
||||
helmet = contents[last].clone();
|
||||
helmet = contents[last];
|
||||
if (arenaClass.hasUnbreakableArmor()) {
|
||||
makeUnbreakable(helmet);
|
||||
}
|
||||
|
@ -1185,11 +1249,12 @@ public class ArenaImpl implements Arena
|
|||
String type = parts[parts.length - 1];
|
||||
if (type.equals("HELMET")) continue;
|
||||
|
||||
ItemStack stack = contents[i].clone();
|
||||
ItemStack stack = contents[i];
|
||||
if (arenaClass.hasUnbreakableArmor()) {
|
||||
makeUnbreakable(stack);
|
||||
}
|
||||
switch (type) {
|
||||
case "ELYTRA":
|
||||
case "CHESTPLATE": chestplate = stack; break;
|
||||
case "LEGGINGS": leggings = stack; break;
|
||||
case "BOOTS": boots = stack; break;
|
||||
|
@ -1197,11 +1262,10 @@ public class ArenaImpl implements Arena
|
|||
}
|
||||
contents[i] = null;
|
||||
}
|
||||
|
||||
|
||||
// Equip the fifth last slot as the off-hand
|
||||
ItemStack fifth = contents[contents.length - 5];
|
||||
if (fifth != null) {
|
||||
offhand = fifth.clone();
|
||||
offhand = contents[contents.length - 5];
|
||||
if (offhand != null) {
|
||||
if (arenaClass.hasUnbreakableWeapons()) {
|
||||
makeUnbreakable(offhand);
|
||||
}
|
||||
|
@ -1246,14 +1310,10 @@ public class ArenaImpl implements Arena
|
|||
|
||||
private void autoReady(Player p) {
|
||||
if (settings.getBoolean("auto-ready", false)) {
|
||||
if (autoStartTimer.getRemaining() <= 0) {
|
||||
playerReady(p);
|
||||
} else {
|
||||
readyPlayers.add(p);
|
||||
}
|
||||
playerReady(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addRandomPlayer(Player p) {
|
||||
randoms.add(p);
|
||||
|
@ -1271,12 +1331,12 @@ public class ArenaImpl implements Arena
|
|||
playerLeave(p);
|
||||
return;
|
||||
}
|
||||
|
||||
int index = MobArena.random.nextInt(classes.size());
|
||||
String className = classes.get(index).getConfigName();
|
||||
|
||||
assignClass(p, className);
|
||||
messenger.tell(p, Msg.LOBBY_CLASS_PICKED, this.classes.get(className).getConfigName());
|
||||
int index = MobArena.random.nextInt(classes.size());
|
||||
String slug = classes.get(index).getSlug();
|
||||
|
||||
assignClass(p, slug);
|
||||
messenger.tell(p, Msg.LOBBY_CLASS_PICKED, this.classes.get(slug).getConfigName());
|
||||
}
|
||||
|
||||
private void addClassPermissions(Player player) {
|
||||
|
@ -1299,7 +1359,7 @@ public class ArenaImpl implements Arena
|
|||
.map(PermissionAttachmentInfo::getAttachment)
|
||||
.forEach(PermissionAttachment::remove);
|
||||
}
|
||||
|
||||
|
||||
private void removePotionEffects(Player p) {
|
||||
p.getActivePotionEffects().stream()
|
||||
.map(PotionEffect::getType)
|
||||
|
@ -1312,21 +1372,21 @@ public class ArenaImpl implements Arena
|
|||
removeEntities();
|
||||
clearPlayers();
|
||||
}
|
||||
|
||||
|
||||
private void removeMonsters() {
|
||||
monsterManager.clear();
|
||||
}
|
||||
|
||||
|
||||
private void removeBlocks() {
|
||||
for (Block b : blocks) {
|
||||
b.setType(Material.AIR);
|
||||
}
|
||||
blocks.clear();
|
||||
}
|
||||
|
||||
|
||||
private void removeEntities() {
|
||||
List<Chunk> chunks = region.getChunks();
|
||||
|
||||
|
||||
for (Chunk c : chunks) {
|
||||
for (Entity e : c.getEntities()) {
|
||||
if (e == null) {
|
||||
|
@ -1339,22 +1399,23 @@ public class ArenaImpl implements Arena
|
|||
case ARROW:
|
||||
case MINECART:
|
||||
case BOAT:
|
||||
case PRIMED_TNT:
|
||||
case SHULKER_BULLET:
|
||||
e.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void clearPlayers() {
|
||||
arenaPlayers.clear();
|
||||
arenaPlayerMap.clear();
|
||||
lobbyPlayers.clear();
|
||||
readyPlayers.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Initialization & Checks
|
||||
|
@ -1365,13 +1426,13 @@ public class ArenaImpl implements Arena
|
|||
public void restoreRegion()
|
||||
{
|
||||
Collections.sort(repairables, new RepairableComparator());
|
||||
|
||||
|
||||
for (Repairable r : repairables)
|
||||
r.repair();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Getters & Misc
|
||||
|
@ -1407,7 +1468,12 @@ public class ArenaImpl implements Arena
|
|||
@Override
|
||||
public String arenaName()
|
||||
{
|
||||
return MAUtils.nameConfigToArena(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSlug() {
|
||||
return slug;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1435,7 +1501,7 @@ public class ArenaImpl implements Arena
|
|||
result.addAll(arenaPlayers);
|
||||
result.addAll(lobbyPlayers);
|
||||
result.addAll(specPlayers);
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1454,10 +1520,10 @@ public class ArenaImpl implements Arena
|
|||
public List<ArenaPlayerStatistics> getArenaPlayerStatistics(Comparator<ArenaPlayerStatistics> comparator)
|
||||
{
|
||||
List<ArenaPlayerStatistics> list = new ArrayList<ArenaPlayerStatistics>();
|
||||
|
||||
|
||||
for (ArenaPlayer ap : arenaPlayerMap.values())
|
||||
list.add(ap.getStats());
|
||||
|
||||
|
||||
Collections.sort(list, comparator);
|
||||
return list;
|
||||
}*/
|
||||
|
@ -1496,7 +1562,7 @@ public class ArenaImpl implements Arena
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean refund(Player p) {
|
||||
entryFee.forEach(fee -> fee.giveTo(p));
|
||||
|
@ -1526,7 +1592,7 @@ public class ArenaImpl implements Arena
|
|||
else if (!canAfford(p))
|
||||
messenger.tell(p, Msg.JOIN_FEE_REQUIRED, MAUtils.listToString(entryFee, plugin));
|
||||
else return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1545,7 +1611,7 @@ public class ArenaImpl implements Arena
|
|||
else if (getJoinDistance() > 0 && !region.contains(p.getLocation(), getJoinDistance()))
|
||||
messenger.tell(p, Msg.JOIN_TOO_FAR);
|
||||
else return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1558,7 +1624,7 @@ public class ArenaImpl implements Arena
|
|||
public Player getLastPlayerStanding() {
|
||||
return lastStanding;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The "perfect equals method" cf. "Object-Oriented Design and Patterns"
|
||||
* by Cay S. Horstmann.
|
||||
|
@ -1568,11 +1634,11 @@ public class ArenaImpl implements Arena
|
|||
if (this == other) return true;
|
||||
if (other == null) return false;
|
||||
if (getClass() != other.getClass()) return false;
|
||||
|
||||
|
||||
// Arenas must have different names.
|
||||
if (other instanceof ArenaImpl && ((ArenaImpl)other).name.equals(name))
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,9 @@ import com.garbagemule.MobArena.repairable.RepairableDoor;
|
|||
import com.garbagemule.MobArena.repairable.RepairableSign;
|
||||
import com.garbagemule.MobArena.things.ExperienceThing;
|
||||
import com.garbagemule.MobArena.things.Thing;
|
||||
import com.garbagemule.MobArena.things.ThingPicker;
|
||||
import com.garbagemule.MobArena.util.ClassChests;
|
||||
import com.garbagemule.MobArena.util.Slugs;
|
||||
import com.garbagemule.MobArena.waves.MABoss;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
|
@ -28,6 +30,7 @@ import org.bukkit.configuration.ConfigurationSection;
|
|||
import org.bukkit.entity.AbstractHorse;
|
||||
import org.bukkit.entity.AnimalTamer;
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
import org.bukkit.entity.Arrow;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Horse;
|
||||
|
@ -41,6 +44,7 @@ import org.bukkit.entity.TNTPrimed;
|
|||
import org.bukkit.entity.ThrownPotion;
|
||||
import org.bukkit.entity.Vehicle;
|
||||
import org.bukkit.event.Event.Result;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockBurnEvent;
|
||||
import org.bukkit.event.block.BlockEvent;
|
||||
|
@ -88,13 +92,11 @@ import org.bukkit.material.Attachable;
|
|||
import org.bukkit.material.Bed;
|
||||
import org.bukkit.material.Door;
|
||||
import org.bukkit.material.Redstone;
|
||||
import org.bukkit.metadata.FixedMetadataValue;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
import org.bukkit.metadata.Metadatable;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.projectiles.ProjectileSource;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -109,61 +111,55 @@ public class ArenaListener
|
|||
private MonsterManager monsters;
|
||||
private ClassLimitManager classLimits;
|
||||
|
||||
private boolean softRestore,
|
||||
softRestoreDrops,
|
||||
protect;
|
||||
private boolean monsterExp,
|
||||
monsterInfight,
|
||||
pvpOn, // pvp-enabled in config
|
||||
pvpEnabled = false, // activated on first wave
|
||||
foodRegen,
|
||||
lockFoodLevel,
|
||||
useClassChests;
|
||||
private boolean allowTeleport,
|
||||
canShare,
|
||||
autoIgniteTNT;
|
||||
private boolean softRestore;
|
||||
private boolean softRestoreDrops;
|
||||
private boolean protect;
|
||||
private boolean monsterExp;
|
||||
private boolean monsterInfight;
|
||||
private boolean pvpEnabled;
|
||||
private boolean foodRegen;
|
||||
private boolean lockFoodLevel;
|
||||
private boolean useClassChests;
|
||||
private boolean allowTeleport;
|
||||
private boolean monsterTeleport;
|
||||
private boolean canShare;
|
||||
private boolean autoIgniteTNT;
|
||||
private int autoIgniteFuse;
|
||||
|
||||
private Set<Player> banned;
|
||||
|
||||
private EnumSet<EntityType> excludeFromRetargeting;
|
||||
|
||||
public ArenaListener(Arena arena, MobArena plugin) {
|
||||
this.plugin = plugin;
|
||||
this.arena = arena;
|
||||
this.region = arena.getRegion();
|
||||
this.monsters = arena.getMonsterManager();
|
||||
|
||||
/*
|
||||
* TODO: Figure out if this is really a good idea + It saves needing all
|
||||
* those methods in Arena.java + It is relatively simple + It would be
|
||||
* fairly easy to implement an observer pattern - More private fields -
|
||||
* Uglier code
|
||||
*/
|
||||
ConfigurationSection s = arena.getSettings();
|
||||
this.softRestore = s.getBoolean("soft-restore", false);
|
||||
this.softRestoreDrops = s.getBoolean("soft-restore-drops", false);
|
||||
this.protect = s.getBoolean("protect", true);
|
||||
this.monsterExp = s.getBoolean("monster-exp", false);
|
||||
this.monsterInfight = s.getBoolean("monster-infight", false);
|
||||
this.pvpOn = s.getBoolean("pvp-enabled", false);
|
||||
this.pvpEnabled = s.getBoolean("pvp-enabled", false);
|
||||
this.foodRegen = s.getBoolean("food-regen", false);
|
||||
this.lockFoodLevel = s.getBoolean("lock-food-level", true);
|
||||
this.allowTeleport = s.getBoolean("allow-teleporting", false);
|
||||
this.monsterTeleport = s.getBoolean("monster-teleporting", false);
|
||||
this.canShare = s.getBoolean("share-items-in-arena", true);
|
||||
this.autoIgniteTNT = s.getBoolean("auto-ignite-tnt", false);
|
||||
this.autoIgniteFuse = s.getInt("auto-ignite-fuse", 80);
|
||||
this.useClassChests = s.getBoolean("use-class-chests", false);
|
||||
|
||||
|
||||
this.classLimits = arena.getClassLimitManager();
|
||||
|
||||
this.banned = new HashSet<>();
|
||||
}
|
||||
|
||||
void pvpActivate() {
|
||||
if (arena.isRunning() && !arena.getPlayersInArena().isEmpty()) {
|
||||
pvpEnabled = pvpOn;
|
||||
}
|
||||
}
|
||||
|
||||
void pvpDeactivate() {
|
||||
if (pvpOn) pvpEnabled = false;
|
||||
|
||||
this.excludeFromRetargeting = EnumSet.of(
|
||||
EntityType.ELDER_GUARDIAN,
|
||||
EntityType.GUARDIAN
|
||||
);
|
||||
}
|
||||
|
||||
public void onBlockBreak(BlockBreakEvent event) {
|
||||
|
@ -177,17 +173,17 @@ public class ArenaListener
|
|||
|
||||
// If the arena isn't protected, care
|
||||
if (!protect) return;
|
||||
|
||||
|
||||
if (!arena.getRegion().contains(event.getBlock().getLocation()))
|
||||
return;
|
||||
|
||||
|
||||
if (!arena.inArena(event.getPlayer())) {
|
||||
if (arena.inEditMode())
|
||||
return;
|
||||
else
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
|
||||
if (onBlockDestroy(event))
|
||||
return;
|
||||
|
||||
|
@ -221,21 +217,18 @@ public class ArenaListener
|
|||
private boolean onBlockDestroy(BlockEvent event) {
|
||||
if (arena.inEditMode())
|
||||
return true;
|
||||
|
||||
|
||||
if (!arena.isRunning())
|
||||
return false;
|
||||
|
||||
Block b = event.getBlock();
|
||||
if (arena.removeBlock(b) || b.getType() == Material.TNT)
|
||||
return true;
|
||||
|
||||
|
||||
if (softRestore) {
|
||||
if (arena.isProtected())
|
||||
return false;
|
||||
|
||||
BlockState state = b.getState();
|
||||
Repairable r = null;
|
||||
|
||||
|
||||
if (state instanceof InventoryHolder)
|
||||
r = new RepairableContainer(state);
|
||||
else if (state instanceof Sign)
|
||||
|
@ -246,7 +239,7 @@ public class ArenaListener
|
|||
r = new RepairableBlock(state);
|
||||
|
||||
arena.addRepairable(r);
|
||||
|
||||
|
||||
if (!softRestoreDrops)
|
||||
b.setType(Material.AIR);
|
||||
return true;
|
||||
|
@ -284,10 +277,10 @@ public class ArenaListener
|
|||
}
|
||||
stack.setAmount(stack.getAmount() - 1);
|
||||
TNTPrimed tnt = b.getWorld().spawn(b.getRelative(BlockFace.UP).getLocation(), TNTPrimed.class);
|
||||
setPlanter(tnt, event.getPlayer());
|
||||
tnt.setSource(event.getPlayer());
|
||||
tnt.setFuseTicks(Math.max(0, autoIgniteFuse));
|
||||
return;
|
||||
}
|
||||
setPlanter(b, event.getPlayer());
|
||||
}
|
||||
|
||||
// Any other block we don't care about if we're not protecting
|
||||
|
@ -303,20 +296,6 @@ public class ArenaListener
|
|||
arena.addBlock(b.getRelative(0, 1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private void setPlanter(Metadatable tnt, Player planter) {
|
||||
tnt.setMetadata("mobarena-planter", new FixedMetadataValue(plugin, planter));
|
||||
}
|
||||
|
||||
private Player getPlanter(Metadatable tnt) {
|
||||
List<MetadataValue> values = tnt.getMetadata("mobarena-planter");
|
||||
for (MetadataValue value : values) {
|
||||
if (value.getOwningPlugin().equals(plugin)) {
|
||||
return (Player) value.value();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onBlockForm(BlockFormEvent event) {
|
||||
// If the arena isn't protected, care
|
||||
|
@ -357,16 +336,7 @@ public class ArenaListener
|
|||
case FLINT_AND_STEEL:
|
||||
if (arena.inEditMode()) return;
|
||||
if (arena.isRunning()) {
|
||||
if (b.getType() == Material.TNT) {
|
||||
Player planter = getPlanter(b);
|
||||
if (planter != null) {
|
||||
b.setType(Material.AIR);
|
||||
TNTPrimed tnt = b.getWorld().spawn(b.getLocation(), TNTPrimed.class);
|
||||
setPlanter(tnt, planter);
|
||||
}
|
||||
} else {
|
||||
arena.addBlock(event.getBlock().getRelative(BlockFace.UP));
|
||||
}
|
||||
arena.addBlock(b.getRelative(BlockFace.UP));
|
||||
break;
|
||||
}
|
||||
case LIGHTNING:
|
||||
|
@ -415,15 +385,23 @@ public class ArenaListener
|
|||
* reason means MobArena didn't trigger the event. However, we
|
||||
* make an exception for certain mobs that spawn as results of
|
||||
* other entities spawning them, e.g. when Evokers summon Vexes.
|
||||
* Note that the "spell" reason was introduced somewhere between
|
||||
* 1.18 and 1.18.1, so "default" is kept only for compatibility
|
||||
* with older server versions. Also note the use of `switch` as
|
||||
* a workaround for the NoSuchFieldError that would occur if we
|
||||
* used a simple equality check.
|
||||
*/
|
||||
if (reason == SpawnReason.DEFAULT) {
|
||||
if (event.getEntityType() == EntityType.VEX) {
|
||||
event.setCancelled(false);
|
||||
monsters.addMonster(event.getEntity());
|
||||
} else {
|
||||
event.setCancelled(true);
|
||||
switch (reason) {
|
||||
case DEFAULT:
|
||||
case SPELL: {
|
||||
if (event.getEntityType() == EntityType.VEX) {
|
||||
event.setCancelled(false);
|
||||
monsters.addMonster(event.getEntity());
|
||||
} else {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If not custom, we probably don't want it, so get rid of it
|
||||
|
@ -512,9 +490,9 @@ public class ArenaListener
|
|||
}
|
||||
|
||||
/******************************************************
|
||||
*
|
||||
*
|
||||
* DEATH LISTENERS
|
||||
*
|
||||
*
|
||||
******************************************************/
|
||||
|
||||
public void onEntityDeath(EntityDeathEvent event) {
|
||||
|
@ -572,11 +550,11 @@ public class ArenaListener
|
|||
arena.playerRespawn(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void onMountDeath(EntityDeathEvent event) {
|
||||
// Shouldn't ever happen
|
||||
}
|
||||
|
||||
|
||||
private void onMonsterDeath(EntityDeathEvent event) {
|
||||
EntityDamageEvent e1 = event.getEntity().getLastDamageCause();
|
||||
EntityDamageByEntityEvent e2 = (e1 instanceof EntityDamageByEntityEvent) ? (EntityDamageByEntityEvent) e1 : null;
|
||||
|
@ -611,10 +589,13 @@ public class ArenaListener
|
|||
for (Player q : arena.getPlayersInArena()) {
|
||||
arena.getMessenger().tell(q, Msg.WAVE_BOSS_KILLED, p.getName());
|
||||
}
|
||||
Thing reward = boss.getReward();
|
||||
if (reward != null) {
|
||||
arena.getRewardManager().addReward(p, reward);
|
||||
arena.getMessenger().tell(damager, Msg.WAVE_BOSS_REWARD_EARNED, reward.toString());
|
||||
ThingPicker picker = boss.getReward();
|
||||
if (picker != null) {
|
||||
Thing reward = picker.pick();
|
||||
if (reward != null) {
|
||||
arena.getRewardManager().addReward(p, reward);
|
||||
arena.getMessenger().tell(damager, Msg.WAVE_BOSS_REWARD_EARNED, reward.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -653,9 +634,9 @@ public class ArenaListener
|
|||
}
|
||||
|
||||
/******************************************************
|
||||
*
|
||||
*
|
||||
* DAMAGE LISTENERS
|
||||
*
|
||||
*
|
||||
******************************************************/
|
||||
|
||||
public void onEntityDamage(EntityDamageEvent event) {
|
||||
|
@ -675,7 +656,7 @@ public class ArenaListener
|
|||
}
|
||||
|
||||
if (damager instanceof TNTPrimed) {
|
||||
damager = getPlanter(damager);
|
||||
damager = ((TNTPrimed) damager).getSource();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -726,11 +707,21 @@ public class ArenaListener
|
|||
}
|
||||
|
||||
if (arena.inArena(player)) {
|
||||
// Cancel PvP damage if disabled
|
||||
if (!pvpEnabled && damager instanceof Player && !damager.equals(player)) {
|
||||
// Cancel damage from pets (and their projectiles)
|
||||
if (monsters.hasPet(damager)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is player damage (and not self-inflicted), handle PvP
|
||||
if (damager instanceof Player && !damager.equals(player)) {
|
||||
// PvP must be enabled, and the first wave must have spawned
|
||||
if (!pvpEnabled || arena.getWaveManager().getWaveNumber() == 0) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event.setCancelled(false);
|
||||
arena.getArenaPlayer(player).getStats().add("dmgTaken", event.getDamage());
|
||||
|
||||
|
@ -772,7 +763,7 @@ public class ArenaListener
|
|||
double progress = boss.getHealth() / boss.getMaxHealth();
|
||||
boss.getHealthBar().setProgress(progress);
|
||||
}
|
||||
|
||||
|
||||
private void onMonsterDamage(EntityDamageEvent event, Entity monster, Entity damager) {
|
||||
if (damager instanceof Player) {
|
||||
Player p = (Player) damager;
|
||||
|
@ -797,7 +788,7 @@ public class ArenaListener
|
|||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void onGolemDamage(EntityDamageEvent event, Entity golem, Entity damager) {
|
||||
if (damager instanceof Player) {
|
||||
Player p = (Player) damager;
|
||||
|
@ -805,8 +796,8 @@ public class ArenaListener
|
|||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pvpEnabled) {
|
||||
|
||||
if (!pvpEnabled || arena.getWaveManager().getWaveNumber() == 0) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
@ -852,6 +843,10 @@ public class ArenaListener
|
|||
private void onMonsterTarget(EntityTargetEvent event, Entity monster, Entity target) {
|
||||
// Null means we lost our target or the target died, so find a new one
|
||||
if (target == null) {
|
||||
// ... unless the monster is excluded from retargeting
|
||||
if (excludeFromRetargeting.contains(monster.getType())) {
|
||||
return;
|
||||
}
|
||||
event.setTarget(MAUtils.getClosestPlayer(plugin, monster, arena));
|
||||
return;
|
||||
}
|
||||
|
@ -895,16 +890,24 @@ public class ArenaListener
|
|||
private boolean isArenaPet(Entity entity) {
|
||||
return arena.hasPet(entity);
|
||||
}
|
||||
|
||||
|
||||
public void onEntityTeleport(EntityTeleportEvent event) {
|
||||
if (monsters.hasPet(event.getEntity()) && region.contains(event.getTo())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isArenaMonster(event.getEntity())) {
|
||||
if (!monsterTeleport || !region.contains(event.getTo())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (region.contains(event.getFrom()) || region.contains(event.getTo())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void onPotionSplash(PotionSplashEvent event) {
|
||||
ThrownPotion potion = event.getPotion();
|
||||
if (!region.contains(potion.getLocation())) {
|
||||
|
@ -913,7 +916,7 @@ public class ArenaListener
|
|||
|
||||
if (potion.getShooter() instanceof Player) {
|
||||
// Check for PvP stuff if the shooter is a player
|
||||
if (!pvpEnabled) {
|
||||
if (!pvpEnabled || arena.getWaveManager().getWaveNumber() == 0) {
|
||||
// If a potion has harmful effects, remove all players.
|
||||
for (PotionEffect effect : potion.getEffects()) {
|
||||
PotionEffectType type = effect.getType();
|
||||
|
@ -944,8 +947,32 @@ public class ArenaListener
|
|||
}
|
||||
|
||||
public void onEntityChangeBlock(EntityChangeBlockEvent event) {
|
||||
if (arena.getRegion().contains(event.getBlock().getLocation()))
|
||||
event.setCancelled(true);
|
||||
if (!protect) {
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = event.getBlock();
|
||||
if (!arena.getRegion().contains(block.getLocation())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (arena.isRunning()) {
|
||||
if (block.getType() == Material.TNT) {
|
||||
Entity entity = event.getEntity();
|
||||
if (entity instanceof Arrow) {
|
||||
Arrow arrow = (Arrow) entity;
|
||||
ProjectileSource shooter = arrow.getShooter();
|
||||
if (shooter instanceof Player) {
|
||||
Player player = (Player) shooter;
|
||||
if (arena.inArena(player)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
public void onEntityRegainHealth(EntityRegainHealthEvent event) {
|
||||
|
@ -1011,18 +1038,18 @@ public class ArenaListener
|
|||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If the player is in the lobby, just cancel
|
||||
else if (arena.inLobby(p)) {
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_DROP_ITEM);
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
|
||||
// Same if it's a spectator, but...
|
||||
else if (arena.inSpec(p)) {
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_DROP_ITEM);
|
||||
event.setCancelled(true);
|
||||
|
||||
|
||||
// If the spectator isn't in the region, force them to leave
|
||||
if (!region.contains(p.getLocation())) {
|
||||
arena.getMessenger().tell(p, Msg.MISC_MA_LEAVE_REMINDER);
|
||||
|
@ -1066,26 +1093,58 @@ public class ArenaListener
|
|||
}
|
||||
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
Player p = event.getPlayer();
|
||||
if (!arena.inLobby(p)) return;
|
||||
if (arena.inLobby(event.getPlayer())) {
|
||||
onLobbyPlayerInteract(event);
|
||||
} else {
|
||||
onNonLobbyPlayerInteract(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent placing blocks and using held items
|
||||
private void onLobbyPlayerInteract(PlayerInteractEvent event) {
|
||||
if (event.hasItem()) {
|
||||
event.setUseItemInHand(Result.DENY);
|
||||
}
|
||||
|
||||
// Bail if off-hand or if there's no block involved.
|
||||
if (event.getHand() == EquipmentSlot.OFF_HAND || !event.hasBlock())
|
||||
if (event.getHand() == EquipmentSlot.OFF_HAND) {
|
||||
return;
|
||||
|
||||
// Iron block
|
||||
if (event.getClickedBlock().getType() == Material.IRON_BLOCK) {
|
||||
handleReadyBlock(p);
|
||||
}
|
||||
// Sign
|
||||
else if (event.getClickedBlock().getState() instanceof Sign) {
|
||||
|
||||
Block block = event.getClickedBlock();
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (block.getType() == Material.IRON_BLOCK) {
|
||||
handleReadyBlock(event.getPlayer());
|
||||
} else if (block.getState() instanceof Sign) {
|
||||
if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
Sign sign = (Sign) event.getClickedBlock().getState();
|
||||
handleSign(sign, p);
|
||||
handleSign(sign, event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
private void onNonLobbyPlayerInteract(PlayerInteractEvent event) {
|
||||
if (!protect) {
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = event.getClickedBlock();
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
if (!region.contains(block.getLocation())) {
|
||||
return;
|
||||
}
|
||||
if (arena.inEditMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (block.getState() instanceof Sign) {
|
||||
if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1107,26 +1166,27 @@ public class ArenaListener
|
|||
|
||||
private void handleSign(Sign sign, Player p) {
|
||||
// Check if the first line is a class name.
|
||||
String className = ChatColor.stripColor(sign.getLine(0)).toLowerCase().replace(" ", "");
|
||||
String className = ChatColor.stripColor(sign.getLine(0));
|
||||
String slug = Slugs.create(className);
|
||||
|
||||
if (!arena.getClasses().containsKey(className) && !className.equals("random"))
|
||||
if (!arena.getClasses().containsKey(slug) && !slug.equals("random"))
|
||||
return;
|
||||
|
||||
ArenaClass newAC = arena.getClasses().get(className);
|
||||
ArenaClass newAC = arena.getClasses().get(slug);
|
||||
|
||||
// Check for permission.
|
||||
if (!newAC.hasPermission(p) && !className.equals("random")) {
|
||||
if (!newAC.hasPermission(p) && !slug.equals("random")) {
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PERMISSION);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ArenaClass oldAC = arena.getArenaPlayer(p).getArenaClass();
|
||||
|
||||
|
||||
// Same class, do nothing.
|
||||
if (newAC.equals(oldAC)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// If the new class is full, inform the player.
|
||||
if (!classLimits.canPlayerJoinClass(newAC)) {
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_FULL);
|
||||
|
@ -1141,33 +1201,33 @@ public class ArenaListener
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Otherwise, leave the old class, and pick the new!
|
||||
classLimits.playerLeftClass(oldAC, p);
|
||||
classLimits.playerPickedClass(newAC, p);
|
||||
|
||||
// Delay the inventory stuff to ensure that right-clicking works.
|
||||
delayAssignClass(p, className, price, sign);
|
||||
delayAssignClass(p, slug, price, sign);
|
||||
}
|
||||
|
||||
|
||||
/*private boolean cansPlayerJoinClass(ArenaClass ac, Player p) {
|
||||
// If they can not join the class, deny them
|
||||
if (!classLimits.canPlayerJoinClass(ac)) {
|
||||
Messenger.tell(p, Msg.LOBBY_CLASS_FULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Increment the "in use" in the Class Limit Manager
|
||||
classLimits.playerPickedClass(ac);
|
||||
return true;
|
||||
}*/
|
||||
|
||||
private void delayAssignClass(final Player p, final String className, final Thing price, final Sign sign) {
|
||||
private void delayAssignClass(final Player p, final String slug, final Thing price, final Sign sign) {
|
||||
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin,new Runnable() {
|
||||
public void run() {
|
||||
if (!className.equalsIgnoreCase("random")) {
|
||||
if (!slug.equalsIgnoreCase("random")) {
|
||||
if (useClassChests) {
|
||||
ArenaClass ac = plugin.getArenaMaster().getClasses().get(className.toLowerCase().replace(" ", ""));
|
||||
ArenaClass ac = plugin.getArenaMaster().getClasses().get(slug);
|
||||
if (ClassChests.assignClassFromStoredClassChest(arena, p, ac)) {
|
||||
return;
|
||||
}
|
||||
|
@ -1176,8 +1236,8 @@ public class ArenaListener
|
|||
}
|
||||
// Otherwise just fall through and use the items from the config-file
|
||||
}
|
||||
arena.assignClass(p, className);
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PICKED, arena.getClasses().get(className).getConfigName());
|
||||
arena.assignClass(p, slug);
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PICKED, arena.getClasses().get(slug).getConfigName());
|
||||
if (price != null) {
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PRICE, price.toString());
|
||||
}
|
||||
|
@ -1219,7 +1279,7 @@ public class ArenaListener
|
|||
}
|
||||
}, ticks);
|
||||
}
|
||||
|
||||
|
||||
public TeleportResponse onPlayerTeleport(PlayerTeleportEvent event) {
|
||||
if (!arena.isEnabled() || !region.isSetup() || arena.inEditMode() || allowTeleport) {
|
||||
return TeleportResponse.IDGAF;
|
||||
|
@ -1251,23 +1311,20 @@ public class ArenaListener
|
|||
if (region.contains(to)) {
|
||||
// Inside -> inside
|
||||
if (!(arena.inArena(p) || arena.inLobby(p))) {
|
||||
arena.getMessenger().tell(p, Msg.WARP_TO_ARENA);
|
||||
return TeleportResponse.REJECT;
|
||||
return reject(p, Msg.WARP_TO_ARENA);
|
||||
}
|
||||
return TeleportResponse.ALLOW;
|
||||
} else {
|
||||
// Inside -> outside
|
||||
if (arena.getAllPlayers().contains(p)) {
|
||||
arena.getMessenger().tell(p, Msg.WARP_FROM_ARENA);
|
||||
return TeleportResponse.REJECT;
|
||||
return reject(p, Msg.WARP_FROM_ARENA);
|
||||
}
|
||||
return TeleportResponse.IDGAF;
|
||||
}
|
||||
} else {
|
||||
if (region.contains(to)) {
|
||||
// Outside -> inside
|
||||
arena.getMessenger().tell(p, Msg.WARP_TO_ARENA);
|
||||
return TeleportResponse.REJECT;
|
||||
return reject(p, Msg.WARP_TO_ARENA);
|
||||
} else {
|
||||
// Outside -> outside
|
||||
return TeleportResponse.IDGAF;
|
||||
|
@ -1275,6 +1332,15 @@ public class ArenaListener
|
|||
}
|
||||
}
|
||||
|
||||
private TeleportResponse reject(Player p, Msg message) {
|
||||
if (p.hasPermission("mobarena.admin.teleport")) {
|
||||
return TeleportResponse.IDGAF;
|
||||
}
|
||||
|
||||
arena.getMessenger().tell(p, message);
|
||||
return TeleportResponse.REJECT;
|
||||
}
|
||||
|
||||
public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
|
||||
Player p = event.getPlayer();
|
||||
|
||||
|
@ -1307,10 +1373,10 @@ public class ArenaListener
|
|||
public void onPlayerPreLogin(PlayerLoginEvent event) {
|
||||
Player p = event.getPlayer();
|
||||
if (p == null || !p.isOnline()) return;
|
||||
|
||||
|
||||
Arena arena = plugin.getArenaMaster().getArenaWithPlayer(p);
|
||||
if (arena == null) return;
|
||||
|
||||
|
||||
arena.playerLeave(p);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.garbagemule.MobArena.framework.ArenaMaster;
|
|||
import com.garbagemule.MobArena.things.InvalidThingInputString;
|
||||
import com.garbagemule.MobArena.things.Thing;
|
||||
import com.garbagemule.MobArena.util.JoinInterruptTimer;
|
||||
import com.garbagemule.MobArena.util.Slugs;
|
||||
import com.garbagemule.MobArena.util.config.ConfigUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
|
@ -137,7 +138,7 @@ public class ArenaMasterImpl implements ArenaMaster
|
|||
public List<Arena> getEnabledArenas(List<Arena> arenas) {
|
||||
List<Arena> result = new ArrayList<>(arenas.size());
|
||||
for (Arena arena : arenas)
|
||||
if (arena.isEnabled())
|
||||
if (arena.isEnabled())
|
||||
result.add(arena);
|
||||
return result;
|
||||
}
|
||||
|
@ -232,8 +233,9 @@ public class ArenaMasterImpl implements ArenaMaster
|
|||
}
|
||||
|
||||
public Arena getArenaWithName(Collection<Arena> arenas, String configName) {
|
||||
String slug = Slugs.create(configName);
|
||||
for (Arena arena : arenas)
|
||||
if (arena.configName().equalsIgnoreCase(configName))
|
||||
if (arena.getSlug().equalsIgnoreCase(slug))
|
||||
return arena;
|
||||
return null;
|
||||
}
|
||||
|
@ -280,6 +282,9 @@ public class ArenaMasterImpl implements ArenaMaster
|
|||
spawnsPets.clear();
|
||||
|
||||
ConfigurationSection items = settings.getConfigurationSection("pet-items");
|
||||
if (items == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : items.getKeys(false)) {
|
||||
EntityType entity;
|
||||
|
@ -334,14 +339,13 @@ public class ArenaMasterImpl implements ArenaMaster
|
|||
private ArenaClass loadClass(String classname) {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
ConfigurationSection section = config.getConfigurationSection("classes." + classname);
|
||||
String lowercase = classname.toLowerCase().replace(" ", "");
|
||||
|
||||
// If the section doesn't exist, the class doesn't either.
|
||||
if (section == null) {
|
||||
// We may not have a class entry for My Items, but that's fine
|
||||
if (classname.equals("My Items")) {
|
||||
ArenaClass myItems = new ArenaClass.MyItems(null, false, false, this);
|
||||
classes.put(lowercase, myItems);
|
||||
classes.put(myItems.getSlug(), myItems);
|
||||
return myItems;
|
||||
}
|
||||
plugin.getLogger().severe("Failed to load class '" + classname + "'.");
|
||||
|
@ -389,8 +393,12 @@ public class ArenaMasterImpl implements ArenaMaster
|
|||
throw new ConfigError("Failed to parse classchest location for class " + classname + " because: " + e.getMessage());
|
||||
}
|
||||
|
||||
// Load pet name
|
||||
String petName = section.getString("pet-name", "<display-name>'s pet");
|
||||
arenaClass.setPetName(petName);
|
||||
|
||||
// Finally add the class to the classes map.
|
||||
classes.put(lowercase, arenaClass);
|
||||
classes.put(arenaClass.getSlug(), arenaClass);
|
||||
return arenaClass;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ public class ArenaPlayerStatistics
|
|||
public ArenaPlayerStatistics(ArenaPlayer player) {
|
||||
this.player = player;
|
||||
this.playerName = player.getPlayer().getName();
|
||||
this.className = player.getArenaClass().getLowercaseName();
|
||||
this.className = player.getArenaClass().getConfigName();
|
||||
reset();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ public class ClassLimitManager
|
|||
private HashMap<ArenaClass, HashSet<String>> classesInUse;
|
||||
private ConfigurationSection limits;
|
||||
private Map<String,ArenaClass> classes;
|
||||
|
||||
|
||||
public ClassLimitManager(Arena arena, Map<String,ArenaClass> classes, ConfigurationSection limits) {
|
||||
this.limits = limits;
|
||||
this.classes = classes;
|
||||
|
@ -26,7 +26,7 @@ public class ClassLimitManager
|
|||
loadLimitMap(arena.getPlugin());
|
||||
initInUseMap();
|
||||
}
|
||||
|
||||
|
||||
private void loadLimitMap(Plugin plugin) {
|
||||
// If the config-section is empty, create and populate it.
|
||||
if (limits.getKeys(false).isEmpty()) {
|
||||
|
@ -35,20 +35,20 @@ public class ClassLimitManager
|
|||
}
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
|
||||
// Populate the limits map using the values in the config-file.
|
||||
for (ArenaClass ac : classes.values()) {
|
||||
classLimits.put(ac, new MutableInt(limits.getInt(ac.getConfigName(), -1)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initInUseMap() {
|
||||
// Initialize the in-use map with zeros.
|
||||
for (ArenaClass ac : classes.values()) {
|
||||
classesInUse.put(ac, new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is the class a player is changing to
|
||||
* @param ac the new ArenaClass
|
||||
|
@ -56,7 +56,7 @@ public class ClassLimitManager
|
|||
public void playerPickedClass(ArenaClass ac, Player p) {
|
||||
classesInUse.get(ac).add(p.getName());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is the class a player left
|
||||
* @param ac the current/old ArenaClass
|
||||
|
@ -66,7 +66,7 @@ public class ClassLimitManager
|
|||
classesInUse.get(ac).remove(p.getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks to see if a player can pick a specific class
|
||||
* @param ac the ArenaClass to check
|
||||
|
@ -78,13 +78,13 @@ public class ClassLimitManager
|
|||
classLimits.put(ac, new MutableInt(-1));
|
||||
classesInUse.put(ac, new HashSet<>());
|
||||
}
|
||||
|
||||
|
||||
if (classLimits.get(ac).value() <= -1)
|
||||
return true;
|
||||
|
||||
|
||||
return classesInUse.get(ac).size() < classLimits.get(ac).value();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns a set of Player Names who have picked an ArenaClass
|
||||
* @param ac the ArenaClass in question
|
||||
|
@ -93,7 +93,7 @@ public class ClassLimitManager
|
|||
public HashSet<String> getPlayersWithClass(ArenaClass ac) {
|
||||
return classesInUse.get(ac);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear the classes in use map and reinitialize it for the next match
|
||||
*/
|
||||
|
@ -101,4 +101,4 @@ public class ClassLimitManager
|
|||
classesInUse.clear();
|
||||
initInUseMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.garbagemule.MobArena.healthbar.HealthBar;
|
|||
import com.garbagemule.MobArena.region.ArenaRegion;
|
||||
import com.garbagemule.MobArena.things.ExperienceThing;
|
||||
import com.garbagemule.MobArena.things.Thing;
|
||||
import com.garbagemule.MobArena.things.ThingPicker;
|
||||
import com.garbagemule.MobArena.waves.MABoss;
|
||||
import com.garbagemule.MobArena.waves.MACreature;
|
||||
import com.garbagemule.MobArena.waves.Wave;
|
||||
|
@ -41,6 +42,7 @@ public class MASpawnThread implements Runnable
|
|||
|
||||
private int playerCount, monsterLimit;
|
||||
private boolean waveClear, bossClear, preBossClear, wavesAsLevel;
|
||||
private int clearLeeway;
|
||||
private int waveInterval;
|
||||
private int nextWaveDelay;
|
||||
|
||||
|
@ -75,6 +77,7 @@ public class MASpawnThread implements Runnable
|
|||
waveClear = arena.getSettings().getBoolean("clear-wave-before-next", false);
|
||||
bossClear = arena.getSettings().getBoolean("clear-boss-before-next", false);
|
||||
preBossClear = arena.getSettings().getBoolean("clear-wave-before-boss", false);
|
||||
clearLeeway = arena.getSettings().getInt("clear-wave-leeway", 0);
|
||||
wavesAsLevel = arena.getSettings().getBoolean("display-waves-as-level", false);
|
||||
waveInterval = arena.getSettings().getInt("wave-interval", 3);
|
||||
nextWaveDelay = arena.getSettings().getInt("next-wave-delay", 0);
|
||||
|
@ -88,10 +91,7 @@ public class MASpawnThread implements Runnable
|
|||
}
|
||||
|
||||
int delay = arena.getSettings().getInt("first-wave-delay", 5) * 20;
|
||||
task = Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
arena.getEventListener().pvpActivate();
|
||||
this.run();
|
||||
}, delay);
|
||||
task = Bukkit.getScheduler().runTaskLater(plugin, this, delay);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
|
@ -100,8 +100,6 @@ public class MASpawnThread implements Runnable
|
|||
return;
|
||||
}
|
||||
|
||||
arena.getEventListener().pvpDeactivate();
|
||||
|
||||
task.cancel();
|
||||
task = null;
|
||||
}
|
||||
|
@ -184,9 +182,9 @@ public class MASpawnThread implements Runnable
|
|||
Wave w = waveManager.next();
|
||||
|
||||
w.announce(arena, wave);
|
||||
|
||||
|
||||
arena.getScoreboard().updateWave(wave);
|
||||
|
||||
|
||||
// Set the players' level to the wave number
|
||||
if (wavesAsLevel) {
|
||||
for (Player p : arena.getPlayersInArena()) {
|
||||
|
@ -241,13 +239,18 @@ public class MASpawnThread implements Runnable
|
|||
} else {
|
||||
e.setCustomName("SPIGOT ERROR");
|
||||
}
|
||||
for (Player p : plugin.getServer().getOnlinePlayers()) {
|
||||
if (p.hasPermission("mobarena.admin.errors")) {
|
||||
arena.getMessenger().tell(p, "Failed to set boss health (" + health + ") in arena " + arena.configName() + " (wave " + wave + ") because Spigot 'maxHealth' is too low. See console for details.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switch on the type.
|
||||
switch (w.getType()){
|
||||
case BOSS:
|
||||
BossWave bw = (BossWave) w;
|
||||
double maxHealth = bw.getMaxHealth(playerCount);
|
||||
double maxHealth = bw.getHealth().evaluate(arena);
|
||||
MABoss boss = monsterManager.addBoss(e, maxHealth);
|
||||
HealthBar healthbar = createsHealthBar.create(e, bw.getBossName());
|
||||
arena.getPlayersInArena().forEach(healthbar::addPlayer);
|
||||
|
@ -282,8 +285,8 @@ public class MASpawnThread implements Runnable
|
|||
UpgradeWave uw = (UpgradeWave) w;
|
||||
|
||||
for (Player p : arena.getPlayersInArena()) {
|
||||
String className = arena.getArenaPlayer(p).getArenaClass().getLowercaseName();
|
||||
uw.grantItems(p, className);
|
||||
String slug = arena.getArenaPlayer(p).getArenaClass().getSlug();
|
||||
uw.grantItems(p, slug);
|
||||
uw.grantItems(p, "all");
|
||||
}
|
||||
|
||||
|
@ -307,18 +310,18 @@ public class MASpawnThread implements Runnable
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check for wave and pre boss clear
|
||||
if (waveClear && !monsterManager.getMonsters().isEmpty()) {
|
||||
// Check for wave clear
|
||||
if (waveClear && monsterManager.getMonsters().size() > clearLeeway) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check for pre boss clear
|
||||
if (preBossClear && waveManager.getNext().getType() == WaveType.BOSS && !monsterManager.getMonsters().isEmpty()) {
|
||||
if (preBossClear && waveManager.getNext().getType() == WaveType.BOSS && monsterManager.getMonsters().size() > clearLeeway) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for final wave
|
||||
if (!monsterManager.getMonsters().isEmpty() && waveManager.getWaveNumber() == waveManager.getFinalWave()) {
|
||||
if (monsterManager.getMonsters().size() > clearLeeway && waveManager.getWaveNumber() == waveManager.getFinalWave()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -345,7 +348,7 @@ public class MASpawnThread implements Runnable
|
|||
if (region.contains(p.getLocation())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
arena.getMessenger().tell(p, "Leaving so soon?");
|
||||
p.getInventory().clear();
|
||||
arena.playerLeave(p);
|
||||
|
@ -353,13 +356,13 @@ public class MASpawnThread implements Runnable
|
|||
}
|
||||
|
||||
private void grantRewards(int wave) {
|
||||
for (Map.Entry<Integer, List<Thing>> entry : arena.getEveryWaveEntrySet()) {
|
||||
for (Map.Entry<Integer, ThingPicker> entry : arena.getEveryWaveEntrySet()) {
|
||||
if (wave > 0 && wave % entry.getKey() == 0) {
|
||||
addReward(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
List<Thing> after = arena.getAfterWaveReward(wave);
|
||||
ThingPicker after = arena.getAfterWaveReward(wave);
|
||||
if (after != null) {
|
||||
addReward(after);
|
||||
}
|
||||
|
@ -388,18 +391,13 @@ public class MASpawnThread implements Runnable
|
|||
/**
|
||||
* Rewards all players with an item from the input String.
|
||||
*/
|
||||
private void addReward(List<Thing> rewards) {
|
||||
private void addReward(ThingPicker picker) {
|
||||
for (Player p : arena.getPlayersInArena()) {
|
||||
Thing reward = rewards.get(MobArena.random.nextInt(rewards.size()));
|
||||
rewardManager.addReward(p, reward);
|
||||
|
||||
if (reward == null) {
|
||||
arena.getMessenger().tell(p, "ERROR! Problem with rewards. Notify server host!");
|
||||
plugin.getLogger().warning("Could not add null reward. Please check the config-file!");
|
||||
}
|
||||
else {
|
||||
Thing reward = picker.pick();
|
||||
if (reward != null) {
|
||||
rewardManager.addReward(p, reward);
|
||||
arena.getMessenger().tell(p, Msg.WAVE_REWARD, reward.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import com.garbagemule.MobArena.framework.Arena;
|
|||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.region.ArenaRegion;
|
||||
import com.garbagemule.MobArena.things.InvalidThingInputString;
|
||||
import com.garbagemule.MobArena.things.Thing;
|
||||
import com.garbagemule.MobArena.util.TextUtils;
|
||||
import com.garbagemule.MobArena.things.ThingPicker;
|
||||
import com.garbagemule.MobArena.util.Materials;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
|
@ -22,73 +22,69 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class MAUtils
|
||||
{
|
||||
{
|
||||
/* ///////////////////////////////////////////////////////////////////// //
|
||||
|
||||
|
||||
INITIALIZATION METHODS
|
||||
|
||||
|
||||
// ///////////////////////////////////////////////////////////////////// */
|
||||
|
||||
|
||||
/**
|
||||
* Generates a map of wave numbers and rewards based on the
|
||||
* type of wave ("after" or "every") and the config-file. If
|
||||
* no keys exist in the config-file, an empty map is returned.
|
||||
*/
|
||||
public static Map<Integer,List<Thing>> getArenaRewardMap(MobArena plugin, ConfigurationSection config, String arena, String type)
|
||||
*/
|
||||
public static Map<Integer, ThingPicker> getArenaRewardMap(MobArena plugin, ConfigurationSection config, String arena, String type)
|
||||
{
|
||||
//String arenaPath = "arenas." + arena + ".rewards.waves.";
|
||||
Map<Integer,List<Thing>> result = new HashMap<>();
|
||||
Map<Integer, ThingPicker> result = new HashMap<>();
|
||||
|
||||
String typePath = "rewards.waves." + type;
|
||||
if (!config.contains(typePath)) return result;
|
||||
|
||||
|
||||
//Set<String> waves = config.getKeys(arenaPath + type);
|
||||
Set<String> waves = config.getConfigurationSection(typePath).getKeys(false);
|
||||
if (waves == null) return result;
|
||||
|
||||
|
||||
for (String n : waves)
|
||||
{
|
||||
if (!n.matches("[0-9]+"))
|
||||
continue;
|
||||
|
||||
|
||||
int wave = Integer.parseInt(n);
|
||||
String path = typePath + "." + wave;
|
||||
String rewards = config.getString(path);
|
||||
|
||||
List<Thing> things = new ArrayList<>();
|
||||
for (String reward : rewards.split(",")) {
|
||||
try {
|
||||
Thing thing = plugin.getThingManager().parse(reward.trim());
|
||||
things.add(thing);
|
||||
} catch (InvalidThingInputString e) {
|
||||
throw new ConfigError("Failed to parse reward for wave " + wave + " in the '" + type + "' branch of arena " + arena + ": " + e.getInput());
|
||||
}
|
||||
try {
|
||||
String wrapped = "random(" + rewards + ")";
|
||||
ThingPicker picker = plugin.getThingPickerManager().parse(wrapped);
|
||||
result.put(wave, picker);
|
||||
} catch (InvalidThingInputString e) {
|
||||
throw new ConfigError("Failed to parse reward for wave " + wave + " in the '" + type + "' branch of arena " + arena + ": " + e.getInput());
|
||||
}
|
||||
result.put(wave, things);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ///////////////////////////////////////////////////////////////////// //
|
||||
|
||||
|
||||
MISC METHODS
|
||||
|
||||
|
||||
// ///////////////////////////////////////////////////////////////////// */
|
||||
|
||||
|
||||
|
||||
|
||||
public static Player getClosestPlayer(MobArena plugin, Entity e, Arena arena) {
|
||||
// Set up the comparison variable and the result.
|
||||
double current = Double.POSITIVE_INFINITY;
|
||||
Player result = null;
|
||||
|
||||
|
||||
/* Iterate through the ArrayList, and update current and result every
|
||||
* time a squared distance smaller than current is found. */
|
||||
List<Player> players = new ArrayList<>(arena.getPlayersInArena());
|
||||
|
@ -99,7 +95,7 @@ public class MAUtils
|
|||
arena.getMessenger().tell(p, "You warped out of the arena world.");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
double dist = distanceSquared(plugin, p, e.getLocation());
|
||||
if (dist < current && dist < 256D) {
|
||||
current = dist;
|
||||
|
@ -108,7 +104,7 @@ public class MAUtils
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static double distanceSquared(MobArena plugin, Player p, Location l) {
|
||||
try {
|
||||
return p.getLocation().distanceSquared(l);
|
||||
|
@ -121,50 +117,19 @@ public class MAUtils
|
|||
return Double.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a config-name to a proper spaced and capsed arena name.
|
||||
* The input String is split around all underscores, and every part
|
||||
* of the String array is properly capsed.
|
||||
*/
|
||||
public static String nameConfigToArena(String name)
|
||||
{
|
||||
String[] parts = name.split("_");
|
||||
if (parts.length == 1) {
|
||||
return toCamelCase(parts[0]);
|
||||
}
|
||||
|
||||
String separator = " ";
|
||||
StringBuffer buffy = new StringBuffer(name.length());
|
||||
for (String part : parts) {
|
||||
buffy.append(toCamelCase(part));
|
||||
buffy.append(separator);
|
||||
}
|
||||
buffy.replace(buffy.length()-1, buffy.length(), "");
|
||||
|
||||
return buffy.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input String with a capital first letter, and all the
|
||||
* other letters become lower case.
|
||||
*/
|
||||
public static String toCamelCase(String name) {
|
||||
return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Turn a list into a space-separated string-representation of the list.
|
||||
*/
|
||||
*/
|
||||
public static <E> String listToString(Collection<E> list, boolean none, MobArena plugin)
|
||||
{
|
||||
if (list == null || list.isEmpty()) {
|
||||
return (none ? Msg.MISC_NONE.toString() : "");
|
||||
}
|
||||
|
||||
|
||||
StringBuffer buffy = new StringBuffer();
|
||||
int trimLength = 0;
|
||||
|
||||
|
||||
E type = list.iterator().next();
|
||||
if (type instanceof Player) {
|
||||
for (E e : list) {
|
||||
|
@ -192,23 +157,7 @@ public class MAUtils
|
|||
return buffy.toString().substring(0, buffy.length() - trimLength);
|
||||
}
|
||||
public static <E> String listToString(Collection<E> list, JavaPlugin plugin) { return listToString(list, true, (MobArena) plugin); }
|
||||
|
||||
/**
|
||||
* Returns a String-list version of a comma-separated list.
|
||||
*/
|
||||
public static List<String> stringToList(String list)
|
||||
{
|
||||
List<String> result = new LinkedList<>();
|
||||
if (list == null) return result;
|
||||
|
||||
String[] parts = list.trim().split(",");
|
||||
|
||||
for (String part : parts)
|
||||
result.add(part.trim());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stand back, I'm going to try science!
|
||||
*/
|
||||
|
@ -216,11 +165,11 @@ public class MAUtils
|
|||
{
|
||||
// Grab the Configuration and ArenaMaster
|
||||
ArenaMaster am = plugin.getArenaMaster();
|
||||
|
||||
|
||||
// Create the arena node in the config-file.
|
||||
World world = loc.getWorld();
|
||||
Arena arena = am.createArenaNode(name, world);
|
||||
|
||||
|
||||
// Get the hippie bounds.
|
||||
int x1 = (int)loc.getX() - radius;
|
||||
int x2 = (int)loc.getX() + radius;
|
||||
|
@ -228,14 +177,14 @@ public class MAUtils
|
|||
int y2 = (int)loc.getY() - 1;
|
||||
int z1 = (int)loc.getZ() - radius;
|
||||
int z2 = (int)loc.getZ() + radius;
|
||||
|
||||
|
||||
int lx1 = x1;
|
||||
int lx2 = x1 + am.getClasses().size() + 3;
|
||||
int ly1 = y1-6;
|
||||
int ly2 = y1-2;
|
||||
int lz1 = z1;
|
||||
int lz2 = z1 + 6;
|
||||
|
||||
|
||||
// Build some monster walls.
|
||||
for (int i = x1; i <= x2; i++)
|
||||
{
|
||||
|
@ -253,7 +202,7 @@ public class MAUtils
|
|||
world.getBlockAt(x2,j,k).setType(Material.SANDSTONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add some hippie light.
|
||||
for (int i = x1; i <= x2; i++)
|
||||
{
|
||||
|
@ -265,7 +214,7 @@ public class MAUtils
|
|||
world.getBlockAt(x1,y1+2,k).setType(Material.GLOWSTONE);
|
||||
world.getBlockAt(x2,y1+2,k).setType(Material.GLOWSTONE);
|
||||
}
|
||||
|
||||
|
||||
// Build a monster floor, and some Obsidian foundation.
|
||||
for (int i = x1; i <= x2; i++)
|
||||
{
|
||||
|
@ -275,20 +224,20 @@ public class MAUtils
|
|||
world.getBlockAt(i,y1-1,k).setType(Material.OBSIDIAN);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Make a hippie roof.
|
||||
for (int i = x1; i <= x2; i++)
|
||||
{
|
||||
for (int k = z1; k <= z2; k++)
|
||||
world.getBlockAt(i,y2,k).setType(Material.GLASS);
|
||||
}
|
||||
|
||||
|
||||
// Monster bulldoze
|
||||
for (int i = x1+1; i < x2; i++)
|
||||
for (int j = y1+1; j < y2; j++)
|
||||
for (int k = z1+1; k < z2; k++)
|
||||
world.getBlockAt(i,j,k).setType(Material.AIR);
|
||||
|
||||
|
||||
// Build a hippie lobby
|
||||
for (int i = lx1; i <= lx2; i++) // Walls
|
||||
{
|
||||
|
@ -322,37 +271,37 @@ public class MAUtils
|
|||
for (int j = ly1+1; j <= ly2; j++)
|
||||
for (int k = lz1+1; k < lz2; k++)
|
||||
world.getBlockAt(i,j,k).setType(Material.AIR);
|
||||
|
||||
|
||||
// Place the hippie signs
|
||||
//Iterator<String> iterator = am.getClasses().iterator();
|
||||
Iterator<String> iterator = am.getClasses().keySet().iterator();
|
||||
Rotatable signData = (Rotatable) Material.SIGN.createBlockData();
|
||||
Iterator<ArenaClass> iterator = am.getClasses().values().iterator();
|
||||
Rotatable signData = (Rotatable) Materials.SIGN.createBlockData();
|
||||
signData.setRotation(BlockFace.NORTH);
|
||||
for (int i = lx1+2; i <= lx2-2; i++) // Signs
|
||||
{
|
||||
world.getBlockAt(i,ly1+1,lz2-1).setBlockData(signData);
|
||||
Sign sign = (Sign) world.getBlockAt(i,ly1+1,lz2-1).getState();
|
||||
sign.setLine(0, TextUtils.camelCase(iterator.next()));
|
||||
sign.setLine(0, iterator.next().getConfigName());
|
||||
sign.update();
|
||||
}
|
||||
world.getBlockAt(lx2-2,ly1+1,lz1+2).setType(Material.IRON_BLOCK);
|
||||
|
||||
// Set up the monster points.
|
||||
|
||||
// Set up the monster points.
|
||||
ArenaRegion region = arena.getRegion();
|
||||
region.set("p1", new Location(world, x1, ly1, z1));
|
||||
region.set("p2", new Location(world, x2, y2+1, z2));
|
||||
|
||||
|
||||
region.set("arena", new Location(world, loc.getX(), y1+1, loc.getZ()));
|
||||
region.set("lobby", new Location(world, x1+2, ly1+1, z1+2));
|
||||
region.set("spectator", new Location(world, loc.getX(), y2+1, loc.getZ()));
|
||||
|
||||
|
||||
region.addSpawn("s1", new Location(world, x1+3, y1+2, z1+3));
|
||||
region.addSpawn("s2", new Location(world, x1+3, y1+2, z2-3));
|
||||
region.addSpawn("s3", new Location(world, x2-3, y1+2, z1+3));
|
||||
region.addSpawn("s4", new Location(world, x2-3, y1+2, z2-3));
|
||||
region.save();
|
||||
|
||||
|
||||
am.reloadConfig();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,14 @@ package com.garbagemule.MobArena;
|
|||
|
||||
import com.garbagemule.MobArena.commands.CommandHandler;
|
||||
import com.garbagemule.MobArena.config.LoadsConfigFile;
|
||||
import com.garbagemule.MobArena.events.MobArenaPreReloadEvent;
|
||||
import com.garbagemule.MobArena.events.MobArenaReloadEvent;
|
||||
import com.garbagemule.MobArena.formula.FormulaMacros;
|
||||
import com.garbagemule.MobArena.formula.FormulaManager;
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.items.SavedItemsManager;
|
||||
import com.garbagemule.MobArena.listeners.MAGlobalListener;
|
||||
import com.garbagemule.MobArena.listeners.MagicSpellsListener;
|
||||
import com.garbagemule.MobArena.metrics.ArenaCountChart;
|
||||
import com.garbagemule.MobArena.metrics.ClassChestsChart;
|
||||
import com.garbagemule.MobArena.metrics.ClassCountChart;
|
||||
|
@ -14,10 +18,13 @@ import com.garbagemule.MobArena.metrics.IsolatedChatChart;
|
|||
import com.garbagemule.MobArena.metrics.MonsterInfightChart;
|
||||
import com.garbagemule.MobArena.metrics.PvpEnabledChart;
|
||||
import com.garbagemule.MobArena.metrics.VaultChart;
|
||||
import com.garbagemule.MobArena.signs.ArenaSign;
|
||||
import com.garbagemule.MobArena.signs.SignBootstrap;
|
||||
import com.garbagemule.MobArena.signs.SignListeners;
|
||||
import com.garbagemule.MobArena.things.NothingPickerParser;
|
||||
import com.garbagemule.MobArena.things.RandomThingPickerParser;
|
||||
import com.garbagemule.MobArena.things.ThingGroupPickerParser;
|
||||
import com.garbagemule.MobArena.things.ThingManager;
|
||||
import com.garbagemule.MobArena.things.ThingPickerManager;
|
||||
import com.garbagemule.MobArena.util.config.ConfigUtils;
|
||||
import com.garbagemule.MobArena.waves.ability.AbilityManager;
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
|
@ -26,7 +33,6 @@ import org.bukkit.ChatColor;
|
|||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
|
@ -58,12 +64,24 @@ public class MobArena extends JavaPlugin
|
|||
|
||||
private Messenger messenger;
|
||||
private ThingManager thingman;
|
||||
private ThingPickerManager pickman;
|
||||
private FormulaManager formman;
|
||||
private FormulaMacros macros;
|
||||
|
||||
private SavedItemsManager itemman;
|
||||
|
||||
private SignListeners signListeners;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
thingman = new ThingManager(this);
|
||||
|
||||
pickman = new ThingPickerManager(thingman);
|
||||
pickman.register(new ThingGroupPickerParser(pickman));
|
||||
pickman.register(new RandomThingPickerParser(pickman, random));
|
||||
pickman.register(new NothingPickerParser());
|
||||
|
||||
formman = FormulaManager.createDefault();
|
||||
}
|
||||
|
||||
public void onEnable() {
|
||||
|
@ -85,18 +103,17 @@ public class MobArena extends JavaPlugin
|
|||
arenaMaster = null;
|
||||
}
|
||||
loadsConfigFile = null;
|
||||
ConfigurationSerialization.unregisterClass(ArenaSign.class);
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
try {
|
||||
createDataFolder();
|
||||
setupFormulaMacros();
|
||||
setupSavedItemsManager();
|
||||
setupArenaMaster();
|
||||
setupCommandHandler();
|
||||
|
||||
registerConfigurationSerializers();
|
||||
setupVault();
|
||||
setupMagicSpells();
|
||||
setupBossAbilities();
|
||||
setupListeners();
|
||||
setupMetrics();
|
||||
|
@ -116,7 +133,15 @@ public class MobArena extends JavaPlugin
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setupFormulaMacros() {
|
||||
macros = FormulaMacros.create(this);
|
||||
}
|
||||
|
||||
private void setupSavedItemsManager() {
|
||||
itemman = new SavedItemsManager(this);
|
||||
}
|
||||
|
||||
private void setupArenaMaster() {
|
||||
arenaMaster = new ArenaMasterImpl(this);
|
||||
}
|
||||
|
@ -125,10 +150,6 @@ public class MobArena extends JavaPlugin
|
|||
getCommand("ma").setExecutor(new CommandHandler(this));
|
||||
}
|
||||
|
||||
private void registerConfigurationSerializers() {
|
||||
ConfigurationSerialization.registerClass(ArenaSign.class);
|
||||
}
|
||||
|
||||
private void setupVault() {
|
||||
Plugin vaultPlugin = this.getServer().getPluginManager().getPlugin("Vault");
|
||||
if (vaultPlugin == null) {
|
||||
|
@ -147,16 +168,6 @@ public class MobArena extends JavaPlugin
|
|||
}
|
||||
}
|
||||
|
||||
private void setupMagicSpells() {
|
||||
Plugin spells = this.getServer().getPluginManager().getPlugin("MagicSpells");
|
||||
if (spells == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
getLogger().info("MagicSpells found, loading config-file.");
|
||||
this.getServer().getPluginManager().registerEvents(new MagicSpellsListener(this), this);
|
||||
}
|
||||
|
||||
private void setupBossAbilities() {
|
||||
AbilityManager.loadCoreAbilities();
|
||||
AbilityManager.loadCustomAbilities(getDataFolder());
|
||||
|
@ -168,7 +179,7 @@ public class MobArena extends JavaPlugin
|
|||
}
|
||||
|
||||
private void setupMetrics() {
|
||||
Metrics metrics = new Metrics(this);
|
||||
Metrics metrics = new Metrics(this, 2572);
|
||||
metrics.addCustomChart(new VaultChart(this));
|
||||
metrics.addCustomChart(new ArenaCountChart(this));
|
||||
metrics.addCustomChart(new ClassCountChart(this));
|
||||
|
@ -180,9 +191,14 @@ public class MobArena extends JavaPlugin
|
|||
}
|
||||
|
||||
public void reload() {
|
||||
MobArenaPreReloadEvent pre = new MobArenaPreReloadEvent(this);
|
||||
getServer().getPluginManager().callEvent(pre);
|
||||
|
||||
try {
|
||||
reloadConfig();
|
||||
reloadGlobalMessenger();
|
||||
reloadFormulaMacros();
|
||||
reloadSavedItemsManager();
|
||||
reloadArenaMaster();
|
||||
reloadAnnouncementsFile();
|
||||
reloadSigns();
|
||||
|
@ -190,6 +206,9 @@ public class MobArena extends JavaPlugin
|
|||
setLastFailureCauseAndRethrow(e);
|
||||
}
|
||||
lastFailureCause = null;
|
||||
|
||||
MobArenaReloadEvent post = new MobArenaReloadEvent(this);
|
||||
getServer().getPluginManager().callEvent(post);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -208,6 +227,18 @@ public class MobArena extends JavaPlugin
|
|||
messenger = new Messenger(prefix);
|
||||
}
|
||||
|
||||
private void reloadFormulaMacros() {
|
||||
try {
|
||||
macros.reload();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("There was an error reloading the formulas-file:\n" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadSavedItemsManager() {
|
||||
itemman.reload();
|
||||
}
|
||||
|
||||
private void reloadArenaMaster() {
|
||||
arenaMaster.getArenas().forEach(Arena::forceEnd);
|
||||
arenaMaster.initialize();
|
||||
|
@ -264,10 +295,6 @@ public class MobArena extends JavaPlugin
|
|||
return lastFailureCause;
|
||||
}
|
||||
|
||||
public File getPluginFile() {
|
||||
return getFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileConfiguration getConfig() {
|
||||
if (config == null) {
|
||||
|
@ -291,4 +318,20 @@ public class MobArena extends JavaPlugin
|
|||
public ThingManager getThingManager() {
|
||||
return thingman;
|
||||
}
|
||||
|
||||
public ThingPickerManager getThingPickerManager() {
|
||||
return pickman;
|
||||
}
|
||||
|
||||
public FormulaManager getFormulaManager() {
|
||||
return formman;
|
||||
}
|
||||
|
||||
public FormulaMacros getFormulaMacros() {
|
||||
return macros;
|
||||
}
|
||||
|
||||
public SavedItemsManager getSavedItemsManager() {
|
||||
return itemman;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import org.bukkit.entity.Player;
|
|||
public class MobArenaHandler
|
||||
{
|
||||
private MobArena plugin;
|
||||
|
||||
|
||||
/**
|
||||
* Primary constructor.
|
||||
* The field 'plugin' is initalized, if the server is running MobArena.
|
||||
|
@ -18,15 +18,15 @@ public class MobArenaHandler
|
|||
public MobArenaHandler() {
|
||||
plugin = (MobArena) Bukkit.getServer().getPluginManager().getPlugin("MobArena");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
REGION/LOCATION METHODS
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
/**
|
||||
* Check if a Location is inside of any arena region.
|
||||
* @param loc A location.
|
||||
|
@ -41,7 +41,7 @@ public class MobArenaHandler
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a Location is inside of a specific arena region (by arena object).
|
||||
* @param arena An Arena object
|
||||
|
@ -51,7 +51,7 @@ public class MobArenaHandler
|
|||
public boolean inRegion(Arena arena, Location loc) {
|
||||
return (arena != null && arena.getRegion().contains(loc));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a Location is inside of a specific arena region (by arena name).
|
||||
* @param arenaName The name of an arena
|
||||
|
@ -65,7 +65,7 @@ public class MobArenaHandler
|
|||
|
||||
return arena.getRegion().contains(loc);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a Location is inside of the region of an arena that is currently running.
|
||||
* @param loc A location.
|
||||
|
@ -74,7 +74,7 @@ public class MobArenaHandler
|
|||
public boolean inRunningRegion(Location loc) {
|
||||
return inRegion(loc, false, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a Location is inside of the region of an arena that is currently enabled.
|
||||
* @param loc A location.
|
||||
|
@ -83,7 +83,7 @@ public class MobArenaHandler
|
|||
public boolean inEnabledRegion(Location loc) {
|
||||
return inRegion(loc, true, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private helper method for inRunningRegion and inEnabledRegion
|
||||
* @param loc A location
|
||||
|
@ -106,15 +106,15 @@ public class MobArenaHandler
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
PLAYER/MONSTER/PET METHODS
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
/**
|
||||
* Check if a player is in a MobArena arena (by Player).
|
||||
* @param player The player
|
||||
|
@ -123,7 +123,7 @@ public class MobArenaHandler
|
|||
public boolean isPlayerInArena(Player player) {
|
||||
return (plugin.getArenaMaster().getArenaWithPlayer(player) != null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a player is in a MobArena arena (by name).
|
||||
* @param playerName The name of the player
|
||||
|
@ -132,7 +132,7 @@ public class MobArenaHandler
|
|||
public boolean isPlayerInArena(String playerName) {
|
||||
return (plugin.getArenaMaster().getArenaWithPlayer(playerName) != null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the MobArena class of a given player.
|
||||
* @param player The player
|
||||
|
@ -144,7 +144,7 @@ public class MobArenaHandler
|
|||
|
||||
return getPlayerClass(arena, player);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the MobArena class of a given player in a given arena.
|
||||
* This method is faster than the above method, granted the Arena object is known.
|
||||
|
@ -155,13 +155,13 @@ public class MobArenaHandler
|
|||
public String getPlayerClass(Arena arena, Player player) {
|
||||
ArenaPlayer ap = arena.getArenaPlayer(player);
|
||||
if (ap == null) return null;
|
||||
|
||||
|
||||
ArenaClass ac = ap.getArenaClass();
|
||||
if (ac == null) return null;
|
||||
|
||||
return ac.getLowercaseName();
|
||||
|
||||
return ac.getSlug();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a monster is in a MobArena arena.
|
||||
* @param entity The monster entity
|
||||
|
@ -170,7 +170,7 @@ public class MobArenaHandler
|
|||
public boolean isMonsterInArena(LivingEntity entity) {
|
||||
return plugin.getArenaMaster().getArenaWithMonster(entity) != null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a pet is in a MobArena arena.
|
||||
* @param wolf The pet wolf
|
||||
|
@ -179,15 +179,15 @@ public class MobArenaHandler
|
|||
public boolean isPetInArena(LivingEntity wolf) {
|
||||
return plugin.getArenaMaster().getArenaWithPet(wolf) != null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
ARENA GETTERS
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
/**
|
||||
* Get an Arena object at the given location.
|
||||
* @param loc A location
|
||||
|
@ -196,7 +196,7 @@ public class MobArenaHandler
|
|||
public Arena getArenaAtLocation(Location loc) {
|
||||
return plugin.getArenaMaster().getArenaAtLocation(loc);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Arena object that the given player is currently in.
|
||||
* @param p A player
|
||||
|
@ -205,7 +205,7 @@ public class MobArenaHandler
|
|||
public Arena getArenaWithPlayer(Player p) {
|
||||
return plugin.getArenaMaster().getArenaWithPlayer(p);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Arena object that the given pet is currently in.
|
||||
* @param wolf A pet wolf
|
||||
|
@ -214,7 +214,7 @@ public class MobArenaHandler
|
|||
public Arena getArenaWithPet(Entity wolf) {
|
||||
return plugin.getArenaMaster().getArenaWithPet(wolf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Arena object that the given monster is currently in.
|
||||
* @param monster A monster
|
||||
|
|
|
@ -24,7 +24,7 @@ public class MonsterManager
|
|||
private Set<LivingEntity> mounts;
|
||||
private Map<Entity, Player> petToPlayer;
|
||||
private Map<Player, Set<Entity>> playerToPets;
|
||||
|
||||
|
||||
public MonsterManager() {
|
||||
this.monsters = new HashSet<>();
|
||||
this.sheep = new HashSet<>();
|
||||
|
@ -35,7 +35,7 @@ public class MonsterManager
|
|||
this.petToPlayer = new HashMap<>();
|
||||
this.playerToPets = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
public void reset() {
|
||||
monsters.clear();
|
||||
sheep.clear();
|
||||
|
@ -46,7 +46,7 @@ public class MonsterManager
|
|||
petToPlayer.clear();
|
||||
playerToPets.clear();
|
||||
}
|
||||
|
||||
|
||||
public void clear() {
|
||||
bosses.values().stream()
|
||||
.map(MABoss::getHealthBar)
|
||||
|
@ -60,10 +60,10 @@ public class MonsterManager
|
|||
removeAll(suppliers.keySet());
|
||||
removeAll(mounts);
|
||||
removeAll(petToPlayer.keySet());
|
||||
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
private void removeAll(Collection<? extends Entity> collection) {
|
||||
for (Entity e : collection) {
|
||||
if (e != null) {
|
||||
|
@ -71,7 +71,7 @@ public class MonsterManager
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void remove(Entity e) {
|
||||
if (monsters.remove(e)) {
|
||||
sheep.remove(e);
|
||||
|
@ -83,50 +83,50 @@ public class MonsterManager
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Set<LivingEntity> getMonsters() {
|
||||
return monsters;
|
||||
}
|
||||
|
||||
|
||||
public void addMonster(LivingEntity e) {
|
||||
monsters.add(e);
|
||||
}
|
||||
|
||||
|
||||
public boolean removeMonster(Entity e) {
|
||||
return monsters.remove(e);
|
||||
}
|
||||
|
||||
|
||||
public Set<LivingEntity> getExplodingSheep() {
|
||||
return sheep;
|
||||
}
|
||||
|
||||
|
||||
public void addExplodingSheep(LivingEntity e) {
|
||||
sheep.add(e);
|
||||
}
|
||||
|
||||
|
||||
public boolean removeExplodingSheep(LivingEntity e) {
|
||||
return sheep.remove(e);
|
||||
}
|
||||
|
||||
|
||||
public Set<LivingEntity> getGolems() {
|
||||
return golems;
|
||||
}
|
||||
|
||||
|
||||
public void addGolem(LivingEntity e) {
|
||||
golems.add(e);
|
||||
}
|
||||
|
||||
|
||||
public boolean removeGolem(LivingEntity e) {
|
||||
return golems.remove(e);
|
||||
}
|
||||
|
||||
|
||||
public void addPet(Player player, Entity pet) {
|
||||
petToPlayer.put(pet, player);
|
||||
playerToPets
|
||||
.computeIfAbsent(player, (key) -> new HashSet<>())
|
||||
.add(pet);
|
||||
}
|
||||
|
||||
|
||||
public boolean hasPet(Entity e) {
|
||||
return petToPlayer.containsKey(e);
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ public class MonsterManager
|
|||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
|
||||
public void removePets(Player p) {
|
||||
Set<Entity> pets = playerToPets.remove(p);
|
||||
if (pets != null) {
|
||||
|
@ -162,7 +162,7 @@ public class MonsterManager
|
|||
pets.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addMount(LivingEntity e) {
|
||||
mounts.add(e);
|
||||
}
|
||||
|
@ -180,29 +180,29 @@ public class MonsterManager
|
|||
e.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addSupplier(LivingEntity e, List<ItemStack> drops) {
|
||||
suppliers.put(e, drops);
|
||||
}
|
||||
|
||||
|
||||
public List<ItemStack> getLoot(Entity e) {
|
||||
return suppliers.get(e);
|
||||
}
|
||||
|
||||
|
||||
public MABoss addBoss(LivingEntity e, double maxHealth) {
|
||||
MABoss b = new MABoss(e, maxHealth);
|
||||
bosses.put(e, b);
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
public MABoss removeBoss(LivingEntity e) {
|
||||
return bosses.remove(e);
|
||||
}
|
||||
|
||||
|
||||
public MABoss getBoss(LivingEntity e) {
|
||||
return bosses.get(e);
|
||||
}
|
||||
|
||||
|
||||
public Set<LivingEntity> getBossMonsters() {
|
||||
return bosses.keySet();
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ public enum Msg {
|
|||
WAVE_BOSS_KILLED("&a%&r killed the boss!"),
|
||||
WAVE_BOSS_REWARD_EARNED("You earned: &e%"),
|
||||
WAVE_REWARD("You just earned a reward: &e%&r"),
|
||||
MISC_REWARD_ADDED("You were just given a reward: &e%&r"),
|
||||
MISC_LIST_PLAYERS("Live players: &a%&r"),
|
||||
MISC_LIST_ARENAS("Available arenas: %"),
|
||||
MISC_COMMAND_NOT_ALLOWED("You can't use that command in the arena!"),
|
||||
|
@ -124,4 +125,4 @@ public enum Msg {
|
|||
}
|
||||
return yaml;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import com.garbagemule.MobArena.things.Thing;
|
|||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -14,43 +13,32 @@ import java.util.Set;
|
|||
|
||||
public class RewardManager
|
||||
{
|
||||
@SuppressWarnings("unused")
|
||||
private MobArena plugin;
|
||||
@SuppressWarnings("unused")
|
||||
private Arena arena;
|
||||
private Map<Player,List<Thing>> players;
|
||||
private Set<Player> rewarded;
|
||||
|
||||
|
||||
public RewardManager(Arena arena) {
|
||||
this.plugin = arena.getPlugin();
|
||||
this.arena = arena;
|
||||
this.players = new HashMap<>();
|
||||
this.rewarded = new HashSet<>();
|
||||
}
|
||||
|
||||
|
||||
public void reset() {
|
||||
players.clear();
|
||||
rewarded.clear();
|
||||
}
|
||||
|
||||
|
||||
public void addReward(Player p, Thing thing) {
|
||||
if (!players.containsKey(p)) {
|
||||
players.put(p, new ArrayList<>());
|
||||
}
|
||||
players.get(p).add(thing);
|
||||
}
|
||||
|
||||
public List<Thing> getRewards(Player p) {
|
||||
List<Thing> rewards = players.get(p);
|
||||
return (rewards == null ? new ArrayList<>(1) : Collections.unmodifiableList(rewards));
|
||||
}
|
||||
|
||||
|
||||
public void grantRewards(Player p) {
|
||||
if (rewarded.contains(p)) return;
|
||||
|
||||
|
||||
List<Thing> rewards = players.get(p);
|
||||
if (rewards == null) return;
|
||||
|
||||
|
||||
for (Thing reward : rewards) {
|
||||
if (reward == null) {
|
||||
continue;
|
||||
|
@ -59,4 +47,4 @@ public class RewardManager
|
|||
}
|
||||
rewarded.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ public class ScoreboardManager {
|
|||
private Objective kills;
|
||||
|
||||
private Map<Player, Scoreboard> scoreboards;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new scoreboard for the given arena.
|
||||
* @param arena an arena
|
||||
|
@ -30,7 +30,7 @@ public class ScoreboardManager {
|
|||
scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
|
||||
scoreboards = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a player to the scoreboard by setting the player's scoreboard
|
||||
* and giving him an initial to-be-reset non-zero score.
|
||||
|
@ -44,7 +44,7 @@ public class ScoreboardManager {
|
|||
player.setScoreboard(scoreboard);
|
||||
kills.getScore(player.getName()).setScore(8);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a player from the scoreboard by setting the player's scoreboard
|
||||
* to the main server scoreboard.
|
||||
|
@ -108,7 +108,7 @@ public class ScoreboardManager {
|
|||
fake.setScore(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the scoreboard to display the given wave number.
|
||||
* @param wave a wave number
|
||||
|
@ -116,7 +116,7 @@ public class ScoreboardManager {
|
|||
void updateWave(int wave) {
|
||||
kills.setDisplayName(DISPLAY_NAME + wave);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the scoreboard by resetting the kills objective and
|
||||
* setting all player scores to 0.
|
||||
|
@ -129,7 +129,7 @@ public class ScoreboardManager {
|
|||
resetKills();
|
||||
arena.scheduleTask(this::resetPlayerScores, 1);
|
||||
}
|
||||
|
||||
|
||||
private void resetKills() {
|
||||
if (kills != null) {
|
||||
kills.unregister();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.garbagemule.MobArena;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
@ -42,11 +43,11 @@ public class SpawnsPets {
|
|||
}
|
||||
|
||||
for (Map.Entry<Material, EntityType> entry : materialToEntity.entrySet()) {
|
||||
spawnPetsFor(player, arena, entry.getKey(), entry.getValue());
|
||||
spawnPetsFor(player, arena, entry.getKey(), entry.getValue(), ac.getPetName());
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnPetsFor(Player player, Arena arena, Material material, EntityType entity) {
|
||||
private void spawnPetsFor(Player player, Arena arena, Material material, EntityType entity, String petName) {
|
||||
PlayerInventory inv = player.getInventory();
|
||||
|
||||
int index = inv.first(material);
|
||||
|
@ -57,8 +58,14 @@ public class SpawnsPets {
|
|||
int amount = inv.getItem(index).getAmount();
|
||||
for (int i = 0; i < amount; i++) {
|
||||
Entity pet = arena.getWorld().spawn(player.getLocation(), entity.getEntityClass());
|
||||
pet.setCustomName(player.getDisplayName() + "'s pet");
|
||||
pet.setCustomNameVisible(true);
|
||||
if (!petName.isEmpty()) {
|
||||
String resolved = petName
|
||||
.replace("<player-name>", player.getName())
|
||||
.replace("<display-name>", player.getDisplayName());
|
||||
String colorized = ChatColor.translateAlternateColorCodes('&', resolved);
|
||||
pet.setCustomName(colorized);
|
||||
pet.setCustomNameVisible(true);
|
||||
}
|
||||
if (pet instanceof Tameable) {
|
||||
Tameable tameable = (Tameable) pet;
|
||||
tameable.setTamed(true);
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.garbagemule.MobArena.announce;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface Announcer {
|
||||
|
||||
/**
|
||||
* Announce the given message to the given player.
|
||||
*
|
||||
* @param player a player to send a message to, non-null
|
||||
* @param message the message to send, non-null
|
||||
*/
|
||||
void announce(Player player, String message);
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.garbagemule.MobArena.announce;
|
||||
|
||||
import com.garbagemule.MobArena.Messenger;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class MessengerAnnouncer implements Announcer {
|
||||
|
||||
private final Messenger messenger;
|
||||
|
||||
public MessengerAnnouncer(Messenger messenger) {
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void announce(Player player, String message) {
|
||||
messenger.tell(player, message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.garbagemule.MobArena.announce;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class TitleAnnouncer implements Announcer {
|
||||
|
||||
private final int fadeIn;
|
||||
private final int stay;
|
||||
private final int fadeOut;
|
||||
|
||||
public TitleAnnouncer(int fadeIn, int stay, int fadeOut) {
|
||||
this.fadeIn = fadeIn;
|
||||
this.stay = stay;
|
||||
this.fadeOut = fadeOut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void announce(Player player, String message) {
|
||||
player.sendTitle(" ", message, fadeIn, stay, fadeOut);
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ import com.garbagemule.MobArena.ConfigError;
|
|||
import com.garbagemule.MobArena.Messenger;
|
||||
import com.garbagemule.MobArena.MobArena;
|
||||
import com.garbagemule.MobArena.Msg;
|
||||
import com.garbagemule.MobArena.commands.admin.AddRewardCommand;
|
||||
import com.garbagemule.MobArena.commands.admin.DisableCommand;
|
||||
import com.garbagemule.MobArena.commands.admin.EnableCommand;
|
||||
import com.garbagemule.MobArena.commands.admin.ForceCommand;
|
||||
|
@ -14,12 +15,15 @@ import com.garbagemule.MobArena.commands.setup.AutoGenerateCommand;
|
|||
import com.garbagemule.MobArena.commands.setup.CheckDataCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.CheckSpawnsCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.ClassChestCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.DeleteItemCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.EditArenaCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.ListClassesCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.LoadItemCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.RemoveArenaCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.RemoveContainerCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.RemoveLeaderboardCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.RemoveSpawnpointCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.SaveItemCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.SettingCommand;
|
||||
import com.garbagemule.MobArena.commands.setup.SetupCommand;
|
||||
import com.garbagemule.MobArena.commands.user.ArenaListCommand;
|
||||
|
@ -53,7 +57,7 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
private Messenger fallbackMessenger;
|
||||
|
||||
private Map<String,Command> commands;
|
||||
|
||||
|
||||
public CommandHandler(MobArena plugin) {
|
||||
this.plugin = plugin;
|
||||
this.fallbackMessenger = new Messenger("&a[MobArena] ");
|
||||
|
@ -98,7 +102,7 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
|
||||
// Get all commands that match the base.
|
||||
List<Command> matches = getMatchingCommands(base);
|
||||
|
||||
|
||||
// If there's more than one match, display them.
|
||||
if (matches.size() > 1) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.MISC_MULTIPLE_MATCHES);
|
||||
|
@ -107,29 +111,29 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// If there are no matches at all, notify.
|
||||
if (matches.size() == 0) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.MISC_NO_MATCHES);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Grab the only match.
|
||||
Command command = matches.get(0);
|
||||
CommandInfo info = command.getClass().getAnnotation(CommandInfo.class);
|
||||
|
||||
|
||||
// First check if the sender has permission.
|
||||
if (!sender.hasPermission(info.permission())) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.MISC_NO_ACCESS);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Check if the last argument is a ?, in which case, display usage and description
|
||||
if (last.equals("?") || last.equals("help")) {
|
||||
showUsage(command, sender, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Otherwise, execute the command!
|
||||
String[] params = trimFirstArg(args);
|
||||
if (!command.execute(am, sender, params)) {
|
||||
|
@ -162,7 +166,7 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all commands that match a given string.
|
||||
* @param arg the given string
|
||||
|
@ -170,17 +174,17 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
*/
|
||||
private List<Command> getMatchingCommands(String arg) {
|
||||
List<Command> result = new ArrayList<>();
|
||||
|
||||
|
||||
// Grab the commands that match the argument.
|
||||
for (Entry<String,Command> entry : commands.entrySet()) {
|
||||
if (arg.matches(entry.getKey())) {
|
||||
result.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the usage and description messages of a command to a player.
|
||||
* The usage will only be shown, if the player has permission for the command.
|
||||
|
@ -193,7 +197,7 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
|
||||
sender.sendMessage((prefix ? "Usage: " : "") + info.usage() + " " + ChatColor.YELLOW + info.desc());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the first argument of a string. This is because the very first
|
||||
* element of the arguments array will be the command itself.
|
||||
|
@ -203,7 +207,7 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
private String[] trimFirstArg(String[] args) {
|
||||
return Arrays.copyOfRange(args, 1, args.length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List all the available MobArena commands for the CommandSender.
|
||||
* @param sender a player or the console
|
||||
|
@ -240,7 +244,7 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
if (setup.length() > 0) am.getGlobalMessenger().tell(sender, "Setup commands: " + setup.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, org.bukkit.command.Command bcmd, String alias, String[] args) {
|
||||
// Only players can tab complete
|
||||
|
@ -302,7 +306,7 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
*/
|
||||
private void registerCommands() {
|
||||
commands = new LinkedHashMap<>();
|
||||
|
||||
|
||||
// mobarena.use
|
||||
register(JoinCommand.class);
|
||||
register(LeaveCommand.class);
|
||||
|
@ -319,7 +323,8 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
register(ForceCommand.class);
|
||||
register(KickCommand.class);
|
||||
register(RestoreCommand.class);
|
||||
|
||||
register(AddRewardCommand.class);
|
||||
|
||||
// mobarena.setup
|
||||
register(SetupCommand.class);
|
||||
register(SettingCommand.class);
|
||||
|
@ -338,8 +343,12 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
|
||||
register(RemoveLeaderboardCommand.class);
|
||||
register(AutoGenerateCommand.class);
|
||||
|
||||
register(SaveItemCommand.class);
|
||||
register(DeleteItemCommand.class);
|
||||
register(LoadItemCommand.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register a command.
|
||||
* The Command's CommandInfo annotation is queried to find its pattern
|
||||
|
@ -347,14 +356,34 @@ public class CommandHandler implements CommandExecutor, TabCompleter
|
|||
* @param c a Command
|
||||
*/
|
||||
public void register(Class<? extends Command> c) {
|
||||
CommandInfo info = c.getAnnotation(CommandInfo.class);
|
||||
if (info == null) return;
|
||||
|
||||
try {
|
||||
commands.put(info.pattern(), c.newInstance());
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Command command = c.newInstance();
|
||||
register(command);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalArgumentException("Failed to instantiate Command class: " + c.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a command instance.
|
||||
* <p>
|
||||
* Adds the given command to MobArena's internal command handler as a
|
||||
* subcommand, overwriting any existing subcommand mappings. This means
|
||||
* that the method is safe to call on reloads as long as a reload does
|
||||
* not change the pattern of a registered command.
|
||||
*
|
||||
* @param command the Command instance to register
|
||||
* @throws IllegalArgumentException if the CommandInfo annotation is
|
||||
* missing from the class of the Command instance
|
||||
*/
|
||||
public void register(Command command) {
|
||||
Class<?> cls = command.getClass();
|
||||
CommandInfo info = cls.getAnnotation(CommandInfo.class);
|
||||
if (info == null) {
|
||||
throw new IllegalArgumentException("Missing CommandInfo annotation on class " + cls.getName());
|
||||
}
|
||||
|
||||
commands.put(info.pattern(), command);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,24 +10,24 @@ public @interface CommandInfo
|
|||
* The actual name of the command. Not really used anywhere.
|
||||
*/
|
||||
String name();
|
||||
|
||||
|
||||
/**
|
||||
* A regex pattern that allows minor oddities and alternatives to the command name.
|
||||
*/
|
||||
String pattern();
|
||||
|
||||
|
||||
/**
|
||||
* The usage message, i.e. how the command should be used.
|
||||
*/
|
||||
String usage();
|
||||
|
||||
|
||||
/**
|
||||
* A description of what the command does.
|
||||
*/
|
||||
String desc();
|
||||
|
||||
|
||||
/**
|
||||
* The permission required to execute this command.
|
||||
*/
|
||||
String permission();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public class Commands
|
|||
public static boolean isPlayer(CommandSender sender) {
|
||||
return (sender instanceof Player);
|
||||
}
|
||||
|
||||
|
||||
public static Arena getArenaToJoinOrSpec(ArenaMaster am, Player p, String arg1) {
|
||||
// Check if MobArena is enabled first.
|
||||
if (!am.isEnabled()) {
|
||||
|
@ -48,17 +48,17 @@ public class Commands
|
|||
am.getGlobalMessenger().tell(p, Msg.JOIN_NO_PERMISSION);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Then check if we have any enabled arenas.
|
||||
arenas = am.getEnabledArenas(arenas);
|
||||
if (arenas.isEmpty()) {
|
||||
am.getGlobalMessenger().tell(p, Msg.JOIN_NOT_ENABLED);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// The arena to join.
|
||||
Arena arena = null;
|
||||
|
||||
|
||||
// Branch on whether there's an argument or not.
|
||||
if (arg1 != null) {
|
||||
arena = am.getArenaWithName(arg1);
|
||||
|
@ -66,7 +66,7 @@ public class Commands
|
|||
am.getGlobalMessenger().tell(p, Msg.ARENA_DOES_NOT_EXIST);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (!arenas.contains(arena)) {
|
||||
am.getGlobalMessenger().tell(p, Msg.JOIN_ARENA_NOT_ENABLED);
|
||||
return null;
|
||||
|
@ -80,18 +80,18 @@ public class Commands
|
|||
}
|
||||
arena = arenas.get(0);
|
||||
}
|
||||
|
||||
|
||||
// If player is in a boat/minecart, eject!
|
||||
if (p.isInsideVehicle()) {
|
||||
p.leaveVehicle();
|
||||
}
|
||||
|
||||
|
||||
// If player is in a bed, unbed!
|
||||
if (p.isSleeping()) {
|
||||
p.kickPlayer("Banned for life... Nah, just don't join from a bed ;)");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return arena;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
package com.garbagemule.MobArena.commands.admin;
|
||||
|
||||
import com.garbagemule.MobArena.Msg;
|
||||
import com.garbagemule.MobArena.commands.Command;
|
||||
import com.garbagemule.MobArena.commands.CommandInfo;
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.things.Thing;
|
||||
import com.garbagemule.MobArena.things.ThingPicker;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@CommandInfo(
|
||||
name = "addreward",
|
||||
pattern = "addreward",
|
||||
usage = "/ma addreward <player> <thing>",
|
||||
desc = "add a reward to an arena player's rewards list",
|
||||
permission = "mobarena.admin.addreward"
|
||||
)
|
||||
public class AddRewardCommand implements Command {
|
||||
|
||||
@Override
|
||||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
if (args.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player player = am.getPlugin().getServer().getPlayer(args[0]);
|
||||
if (player == null) {
|
||||
am.getGlobalMessenger().tell(sender, "Player not found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Arena arena = am.getArenaWithPlayer(player);
|
||||
if (arena == null) {
|
||||
am.getGlobalMessenger().tell(sender, "That player is not in an arena.");
|
||||
return true;
|
||||
}
|
||||
if (!arena.isRunning()) {
|
||||
am.getGlobalMessenger().tell(sender, "That arena is not running.");
|
||||
return true;
|
||||
}
|
||||
if (!arena.getPlayersInArena().contains(player)) {
|
||||
am.getGlobalMessenger().tell(sender, "That player is not an arena player.");
|
||||
return true;
|
||||
}
|
||||
|
||||
String[] rest = Arrays.copyOfRange(args, 1, args.length);
|
||||
String input = String.join(" ", rest);
|
||||
|
||||
Thing thing;
|
||||
try {
|
||||
ThingPicker picker = am.getPlugin().getThingPickerManager().parse(input);
|
||||
thing = picker.pick();
|
||||
} catch (Exception e) {
|
||||
am.getGlobalMessenger().tell(sender, e.getMessage());
|
||||
return true;
|
||||
}
|
||||
|
||||
arena.getRewardManager().addReward(player, thing);
|
||||
arena.getMessenger().tell(player, Msg.MISC_REWARD_ADDED, thing.toString());
|
||||
|
||||
String msg = "Added " + ChatColor.YELLOW + thing + ChatColor.RESET + " to " + ChatColor.YELLOW + player.getName() + "'s" + ChatColor.RESET + " rewards.";
|
||||
am.getGlobalMessenger().tell(sender, msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tab(ArenaMaster am, Player player, String... args) {
|
||||
if (args.length > 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String prefix = args[0].toLowerCase();
|
||||
|
||||
List<Player> players = am.getAllPlayers();
|
||||
|
||||
return players.stream()
|
||||
.filter(p -> p.getName().toLowerCase().startsWith(prefix))
|
||||
.map(Player::getName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
|
@ -26,14 +26,14 @@ public class DisableCommand implements Command
|
|||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
// Grab the argument, if any.
|
||||
String arg1 = (args.length > 0 ? args[0] : "");
|
||||
|
||||
|
||||
if (arg1.equals("all")) {
|
||||
for (Arena arena : am.getArenas()) {
|
||||
disable(arena, sender);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (!arg1.equals("")) {
|
||||
Arena arena = am.getArenaWithName(arg1);
|
||||
if (arena == null) {
|
||||
|
@ -43,13 +43,13 @@ public class DisableCommand implements Command
|
|||
disable(arena, sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
am.setEnabled(false);
|
||||
am.saveConfig();
|
||||
am.getGlobalMessenger().tell(sender, "MobArena " + ChatColor.RED + "disabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void disable(Arena arena, CommandSender sender) {
|
||||
arena.setEnabled(false);
|
||||
arena.getPlugin().saveConfig();
|
||||
|
@ -67,8 +67,8 @@ public class DisableCommand implements Command
|
|||
List<Arena> arenas = am.getArenas();
|
||||
|
||||
return arenas.stream()
|
||||
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
|
||||
.map(Arena::configName)
|
||||
.filter(arena -> arena.getSlug().startsWith(prefix))
|
||||
.map(Arena::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,14 +26,14 @@ public class EnableCommand implements Command
|
|||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
// Grab the argument, if any.
|
||||
String arg1 = (args.length > 0 ? args[0] : "");
|
||||
|
||||
|
||||
if (arg1.equals("all")) {
|
||||
for (Arena arena : am.getArenas()) {
|
||||
enable(arena, sender);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (!arg1.equals("")) {
|
||||
Arena arena = am.getArenaWithName(arg1);
|
||||
if (arena == null) {
|
||||
|
@ -43,13 +43,13 @@ public class EnableCommand implements Command
|
|||
enable(arena, sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
am.setEnabled(true);
|
||||
am.saveConfig();
|
||||
am.getGlobalMessenger().tell(sender, "MobArena " + ChatColor.GREEN + "enabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void enable(Arena arena, CommandSender sender) {
|
||||
arena.setEnabled(true);
|
||||
arena.getPlugin().saveConfig();
|
||||
|
@ -67,8 +67,8 @@ public class EnableCommand implements Command
|
|||
List<Arena> arenas = am.getArenas();
|
||||
|
||||
return arenas.stream()
|
||||
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
|
||||
.map(Arena::configName)
|
||||
.filter(arena -> arena.getSlug().startsWith(prefix))
|
||||
.map(Arena::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public class ForceCommand implements Command
|
|||
// Grab the argument, if any.
|
||||
String arg1 = (args.length > 0 ? args[0] : "");
|
||||
String arg2 = (args.length > 1 ? args[1] : "");
|
||||
|
||||
|
||||
if (arg1.equals("end")) {
|
||||
// With no arguments, end all.
|
||||
if (arg2.equals("")) {
|
||||
|
@ -41,46 +41,46 @@ public class ForceCommand implements Command
|
|||
am.resetArenaMap();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Otherwise, grab the arena in question.
|
||||
Arena arena = am.getArenaWithName(arg2);
|
||||
if (arena == null) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.ARENA_DOES_NOT_EXIST);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (arena.getAllPlayers().isEmpty()) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.FORCE_END_EMPTY);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// And end it!
|
||||
arena.forceEnd();
|
||||
am.getGlobalMessenger().tell(sender, Msg.FORCE_END_ENDED);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (arg1.equals("start")) {
|
||||
// Require argument.
|
||||
if (arg2.equals("")) return false;
|
||||
|
||||
|
||||
// Grab the arena.
|
||||
Arena arena = am.getArenaWithName(arg2);
|
||||
if (arena == null) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.ARENA_DOES_NOT_EXIST);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (arena.isRunning()) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.FORCE_START_RUNNING);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (arena.getReadyPlayersInLobby().isEmpty()) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.FORCE_START_NOT_READY);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// And start it!
|
||||
arena.forceStart();
|
||||
am.getGlobalMessenger().tell(sender, Msg.FORCE_START_STARTED);
|
||||
|
@ -117,9 +117,9 @@ public class ForceCommand implements Command
|
|||
List<Arena> arenas = am.getArenas();
|
||||
|
||||
return arenas.stream()
|
||||
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
|
||||
.filter(arena -> arena.getSlug().startsWith(prefix))
|
||||
.filter(arena -> start != arena.isRunning())
|
||||
.map(Arena::configName)
|
||||
.map(Arena::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,16 +24,16 @@ public class KickCommand implements Command
|
|||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
// Require a player name
|
||||
if (args.length != 1) return false;
|
||||
|
||||
|
||||
Arena arena = am.getArenaWithPlayer(args[0]);
|
||||
if (arena == null) {
|
||||
am.getGlobalMessenger().tell(sender, "That player is not in an arena.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Grab the Player object.
|
||||
Player bp = am.getPlugin().getServer().getPlayer(args[0]);
|
||||
|
||||
|
||||
// Force leave.
|
||||
arena.playerLeave(bp);
|
||||
am.getGlobalMessenger().tell(sender, "Player '" + args[0] + "' was kicked from arena '" + arena.configName() + "'.");
|
||||
|
|
|
@ -25,7 +25,7 @@ public class RestoreCommand implements Command
|
|||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
// Require a player name
|
||||
if (args.length != 1) return false;
|
||||
|
||||
|
||||
Player player = am.getPlugin().getServer().getPlayer(args[0]);
|
||||
if (player == null) {
|
||||
am.getGlobalMessenger().tell(sender, "Player not found.");
|
||||
|
@ -35,7 +35,7 @@ public class RestoreCommand implements Command
|
|||
am.getGlobalMessenger().tell(sender, "Player is currently in an arena.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (InventoryManager.restoreFromFile(am.getPlugin(), player)) {
|
||||
am.getGlobalMessenger().tell(sender, "Restored " + args[0] + "'s inventory!");
|
||||
} else {
|
||||
|
|
|
@ -6,9 +6,13 @@ import com.garbagemule.MobArena.commands.CommandInfo;
|
|||
import com.garbagemule.MobArena.commands.Commands;
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.util.Slugs;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@CommandInfo(
|
||||
name = "addarena",
|
||||
pattern = "(add|new)arena",
|
||||
|
@ -26,18 +30,20 @@ public class AddArenaCommand implements Command
|
|||
}
|
||||
|
||||
// Require an arena name
|
||||
if (args.length != 1) return false;
|
||||
|
||||
if (args.length < 1) return false;
|
||||
|
||||
// Unwrap the sender.
|
||||
Player p = Commands.unwrap(sender);
|
||||
|
||||
Arena arena = am.getArenaWithName(args[0]);
|
||||
|
||||
String name = String.join(" ", args);
|
||||
String slug = Slugs.create(name);
|
||||
Arena arena = am.getArenaWithName(slug);
|
||||
if (arena != null) {
|
||||
am.getGlobalMessenger().tell(sender, "An arena with that name already exists.");
|
||||
return true;
|
||||
}
|
||||
am.createArenaNode(args[0], p.getWorld());
|
||||
am.getGlobalMessenger().tell(sender, "New arena with name '" + args[0] + "' created!");
|
||||
am.createArenaNode(name, p.getWorld());
|
||||
am.getGlobalMessenger().tell(sender, "New arena with name '" + name + "' created!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.garbagemule.MobArena.commands.CommandInfo;
|
|||
import com.garbagemule.MobArena.commands.Commands;
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.util.Slugs;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
|
@ -27,24 +28,26 @@ public class AutoGenerateCommand implements Command
|
|||
}
|
||||
|
||||
// Require an arena name
|
||||
if (args.length != 1) return false;
|
||||
|
||||
if (args.length < 1) return false;
|
||||
|
||||
// Unwrap the sender.
|
||||
Player p = Commands.unwrap(sender);
|
||||
|
||||
|
||||
// Check if arena already exists.
|
||||
Arena arena = am.getArenaWithName(args[0]);
|
||||
String name = String.join(" ", args);
|
||||
String slug = Slugs.create(name);
|
||||
Arena arena = am.getArenaWithName(slug);
|
||||
if (arena != null) {
|
||||
am.getGlobalMessenger().tell(sender, "An arena with that name already exists.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!MAUtils.doooooItHippieMonster(p.getLocation(), 13, args[0], am.getPlugin())) {
|
||||
|
||||
if (!MAUtils.doooooItHippieMonster(p.getLocation(), 13, name, am.getPlugin())) {
|
||||
am.getGlobalMessenger().tell(sender, "Could not auto-generate arena.");
|
||||
return true;
|
||||
}
|
||||
|
||||
am.getGlobalMessenger().tell(sender, "Arena with name '" + args[0] + "' generated.");
|
||||
|
||||
am.getGlobalMessenger().tell(sender, "Arena with name '" + name + "' generated.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.garbagemule.MobArena.commands.Command;
|
|||
import com.garbagemule.MobArena.commands.CommandInfo;
|
||||
import com.garbagemule.MobArena.commands.Commands;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.util.Slugs;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
@ -37,7 +38,8 @@ public class ClassChestCommand implements Command {
|
|||
// Require a class name
|
||||
if (args.length != 1) return false;
|
||||
|
||||
ArenaClass ac = am.getClasses().get(args[0].toLowerCase());
|
||||
String slug = Slugs.create(args[0]);
|
||||
ArenaClass ac = am.getClasses().get(slug);
|
||||
if (ac == null) {
|
||||
am.getGlobalMessenger().tell(sender, "Class not found.");
|
||||
return true;
|
||||
|
@ -69,13 +71,13 @@ public class ClassChestCommand implements Command {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String prefix = args[0].toLowerCase();
|
||||
String prefix = Slugs.create(args[0]);
|
||||
|
||||
Collection<ArenaClass> classes = am.getClasses().values();
|
||||
|
||||
return classes.stream()
|
||||
.filter(cls -> cls.getConfigName().toLowerCase().startsWith(prefix))
|
||||
.map(ArenaClass::getConfigName)
|
||||
.filter(cls -> cls.getSlug().startsWith(prefix))
|
||||
.map(ArenaClass::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package com.garbagemule.MobArena.commands.setup;
|
||||
|
||||
import com.garbagemule.MobArena.commands.Command;
|
||||
import com.garbagemule.MobArena.commands.CommandInfo;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.items.SavedItemsManager;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@CommandInfo(
|
||||
name = "delete-item",
|
||||
pattern = "delete(-)?item",
|
||||
usage = "/ma delete-item <identifier>",
|
||||
desc = "delete the item with the given identifier",
|
||||
permission = "mobarena.setup.deleteitem"
|
||||
)
|
||||
public class DeleteItemCommand implements Command {
|
||||
|
||||
@Override
|
||||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
if (args.length != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String key = args[0];
|
||||
|
||||
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
|
||||
try {
|
||||
items.deleteItem(key);
|
||||
} catch (Exception e) {
|
||||
am.getGlobalMessenger().tell(sender, "Couldn't delete " + ChatColor.YELLOW + key + ChatColor.RESET + ", because: " + ChatColor.RED + e.getMessage());
|
||||
return true;
|
||||
}
|
||||
|
||||
am.getGlobalMessenger().tell(sender, "Saved item " + ChatColor.YELLOW + key + ChatColor.RESET + " deleted.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tab(ArenaMaster am, Player player, String... args) {
|
||||
if (args.length > 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String prefix = args[0].toLowerCase();
|
||||
|
||||
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
|
||||
List<String> keys = items.getKeys();
|
||||
|
||||
return keys.stream()
|
||||
.filter(key -> key.startsWith(prefix))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
|
@ -69,8 +69,8 @@ public class EditArenaCommand implements Command
|
|||
List<Arena> arenas = am.getArenas();
|
||||
|
||||
return arenas.stream()
|
||||
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
|
||||
.map(Arena::configName)
|
||||
.filter(arena -> arena.getSlug().startsWith(prefix))
|
||||
.map(Arena::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ public class ListClassesCommand implements Command
|
|||
am.getGlobalMessenger().tell(sender, "<none>");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
for (String c : classes) {
|
||||
am.getGlobalMessenger().tell(sender, "- " + c);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package com.garbagemule.MobArena.commands.setup;
|
||||
|
||||
import com.garbagemule.MobArena.Msg;
|
||||
import com.garbagemule.MobArena.commands.Command;
|
||||
import com.garbagemule.MobArena.commands.CommandInfo;
|
||||
import com.garbagemule.MobArena.commands.Commands;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.items.SavedItemsManager;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@CommandInfo(
|
||||
name = "load-item",
|
||||
pattern = "load(-)?item",
|
||||
usage = "/ma load-item <identifier>",
|
||||
desc = "load the item saved by the given identifier into your hand",
|
||||
permission = "mobarena.setup.loaditem"
|
||||
)
|
||||
public class LoadItemCommand implements Command {
|
||||
|
||||
@Override
|
||||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
if (!Commands.isPlayer(sender)) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.MISC_NOT_FROM_CONSOLE);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String key = args[0];
|
||||
|
||||
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
|
||||
ItemStack stack = items.getItem(key);
|
||||
if (stack == null) {
|
||||
am.getGlobalMessenger().tell(sender, "No saved item with identifier " + ChatColor.YELLOW + key + ChatColor.RESET + " found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Player player = Commands.unwrap(sender);
|
||||
player.getInventory().setItemInMainHand(stack);
|
||||
|
||||
am.getGlobalMessenger().tell(sender, "Saved item " + ChatColor.YELLOW + key + ChatColor.RESET + " loaded.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tab(ArenaMaster am, Player player, String... args) {
|
||||
if (args.length > 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String prefix = args[0].toLowerCase();
|
||||
|
||||
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
|
||||
List<String> keys = items.getKeys();
|
||||
|
||||
return keys.stream()
|
||||
.filter(key -> key.startsWith(prefix))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
|
@ -24,12 +24,12 @@ public class RemoveArenaCommand implements Command
|
|||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
// Require an arena name
|
||||
if (args.length != 1) return false;
|
||||
|
||||
|
||||
if (am.getArenas().size() == 1) {
|
||||
am.getGlobalMessenger().tell(sender, "At least one arena must exist.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Arena arena = am.getArenaWithName(args[0]);
|
||||
if (arena == null) {
|
||||
am.getGlobalMessenger().tell(sender, "There is no arena with that name.");
|
||||
|
@ -51,8 +51,8 @@ public class RemoveArenaCommand implements Command
|
|||
List<Arena> arenas = am.getArenas();
|
||||
|
||||
return arenas.stream()
|
||||
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
|
||||
.map(Arena::configName)
|
||||
.filter(arena -> arena.getSlug().startsWith(prefix))
|
||||
.map(Arena::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,4 +41,4 @@ public class RemoveLeaderboardCommand implements Command
|
|||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package com.garbagemule.MobArena.commands.setup;
|
||||
|
||||
import com.garbagemule.MobArena.Msg;
|
||||
import com.garbagemule.MobArena.commands.Command;
|
||||
import com.garbagemule.MobArena.commands.CommandInfo;
|
||||
import com.garbagemule.MobArena.commands.Commands;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.items.SavedItemsManager;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@CommandInfo(
|
||||
name = "save-item",
|
||||
pattern = "save(-)?item",
|
||||
usage = "/ma save-item <identifier>",
|
||||
desc = "save the currently held item for use in the config-file",
|
||||
permission = "mobarena.setup.saveitem"
|
||||
)
|
||||
public class SaveItemCommand implements Command {
|
||||
|
||||
@Override
|
||||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
if (!Commands.isPlayer(sender)) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.MISC_NOT_FROM_CONSOLE);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String key = args[0];
|
||||
if (!key.matches("^[\\p{IsAlphabetic}\\d_]+$")) {
|
||||
am.getGlobalMessenger().tell(sender, "The identifier must contain only letters, numbers, and underscores");
|
||||
return true;
|
||||
}
|
||||
|
||||
Player player = Commands.unwrap(sender);
|
||||
ItemStack stack = player.getInventory().getItemInMainHand();
|
||||
if (stack.getType() == Material.AIR) {
|
||||
am.getGlobalMessenger().tell(sender, "You must be holding an item.");
|
||||
return true;
|
||||
}
|
||||
|
||||
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
|
||||
try {
|
||||
items.saveItem(key, stack);
|
||||
} catch (Exception e) {
|
||||
am.getGlobalMessenger().tell(sender, "Couldn't save " + ChatColor.YELLOW + key + ChatColor.RESET + ", because: " + ChatColor.RED + e.getMessage());
|
||||
return true;
|
||||
}
|
||||
|
||||
am.getGlobalMessenger().tell(sender, "Item saved as " + ChatColor.YELLOW + key + ChatColor.RESET + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tab(ArenaMaster am, Player player, String... args) {
|
||||
if (args.length > 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String prefix = args[0].toLowerCase();
|
||||
|
||||
SavedItemsManager items = am.getPlugin().getSavedItemsManager();
|
||||
List<String> keys = items.getKeys();
|
||||
|
||||
return keys.stream()
|
||||
.filter(key -> key.startsWith(prefix))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.garbagemule.MobArena.commands.setup;
|
||||
|
||||
import com.garbagemule.MobArena.ConfigError;
|
||||
import com.garbagemule.MobArena.commands.Command;
|
||||
import com.garbagemule.MobArena.commands.CommandInfo;
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
|
@ -89,7 +90,13 @@ public class SettingCommand implements Command {
|
|||
|
||||
// Save config-file and reload arena
|
||||
am.saveConfig();
|
||||
am.reloadArena(args[0]);
|
||||
try {
|
||||
am.reloadArena(args[0]);
|
||||
} catch (ConfigError e) {
|
||||
am.getGlobalMessenger().tell(sender, "Failed to reload arena after changing setting, reason:\n" + ChatColor.RED + e.getMessage());
|
||||
am.getGlobalMessenger().tell(sender, "Fix the error in your config-file, then run " + ChatColor.YELLOW + "/ma reload");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Notify the sender
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
|
@ -113,9 +120,9 @@ public class SettingCommand implements Command {
|
|||
String prefix = args[0].toLowerCase();
|
||||
|
||||
return arenas.stream()
|
||||
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
|
||||
.filter(arena -> arena.getSlug().startsWith(prefix))
|
||||
.filter(arena -> !arena.isRunning())
|
||||
.map(Arena::configName)
|
||||
.map(Arena::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
|
|
@ -101,8 +101,8 @@ public class SetupCommand implements Command, Listener {
|
|||
List<Arena> arenas = am.getArenas();
|
||||
|
||||
return arenas.stream()
|
||||
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
|
||||
.map(Arena::configName)
|
||||
.filter(arena -> arena.getSlug().startsWith(prefix))
|
||||
.map(Arena::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
|
|
@ -24,14 +24,14 @@ public class ArenaListCommand implements Command
|
|||
@Override
|
||||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
List<Arena> arenas;
|
||||
|
||||
|
||||
if (Commands.isPlayer(sender)) {
|
||||
Player p = Commands.unwrap(sender);
|
||||
arenas = am.getPermittedArenas(p);
|
||||
arenas = am.getPermittedArenas(p);
|
||||
} else {
|
||||
arenas = am.getArenas();
|
||||
}
|
||||
|
||||
|
||||
String list = MAUtils.listToString(arenas, am.getPlugin());
|
||||
am.getGlobalMessenger().tell(sender, Msg.MISC_LIST_ARENAS.format(list));
|
||||
return true;
|
||||
|
|
|
@ -29,7 +29,7 @@ public class JoinCommand implements Command
|
|||
am.getGlobalMessenger().tell(sender, Msg.MISC_NOT_FROM_CONSOLE);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Unwrap the sender, grab the argument, if any.
|
||||
Player p = Commands.unwrap(sender);
|
||||
String arg1 = (args.length > 0 ? args[0] : null);
|
||||
|
@ -39,7 +39,7 @@ public class JoinCommand implements Command
|
|||
if (toArena == null || !canJoin(p, toArena)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Join the arena!
|
||||
int seconds = toArena.getSettings().getInt("join-interrupt-timer", 0);
|
||||
if (seconds > 0) {
|
||||
|
@ -94,9 +94,9 @@ public class JoinCommand implements Command
|
|||
|
||||
return arenas.stream()
|
||||
.filter(Arena::isEnabled)
|
||||
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
|
||||
.filter(arena -> arena.getSlug().startsWith(prefix))
|
||||
.filter(arena -> arena.getRegion().isSetup())
|
||||
.map(Arena::configName)
|
||||
.map(Arena::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,11 +24,11 @@ public class LeaveCommand implements Command
|
|||
am.getGlobalMessenger().tell(sender, Msg.MISC_NOT_FROM_CONSOLE);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Unwrap the sender.
|
||||
Player p = Commands.unwrap(sender);
|
||||
|
||||
Arena arena = am.getArenaWithPlayer(p);
|
||||
Arena arena = am.getArenaWithPlayer(p);
|
||||
if (arena == null) {
|
||||
arena = am.getArenaWithSpectator(p);
|
||||
if (arena == null) {
|
||||
|
@ -36,7 +36,7 @@ public class LeaveCommand implements Command
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (arena.playerLeave(p)) {
|
||||
arena.getMessenger().tell(p, Msg.LEAVE_PLAYER_LEFT);
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@ public class NotReadyCommand implements Command
|
|||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
// Grab the argument, if any
|
||||
String arg1 = (args.length > 0 ? args[0] : "");
|
||||
|
||||
|
||||
// The arena to query.
|
||||
Arena arena = null;
|
||||
|
||||
|
||||
if (!arg1.equals("")) {
|
||||
arena = am.getArenaWithName(arg1);
|
||||
if (arena == null) {
|
||||
|
@ -36,7 +36,7 @@ public class NotReadyCommand implements Command
|
|||
} else if (Commands.isPlayer(sender)) {
|
||||
Player p = Commands.unwrap(sender);
|
||||
arena = am.getArenaWithPlayer(p);
|
||||
|
||||
|
||||
if (arena == null) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.LEAVE_NOT_PLAYING);
|
||||
return true;
|
||||
|
@ -44,7 +44,7 @@ public class NotReadyCommand implements Command
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
String list = MAUtils.listToString(arena.getNonreadyPlayers(), am.getPlugin());
|
||||
arena.getMessenger().tell(sender, Msg.MISC_LIST_PLAYERS.format(list));
|
||||
return true;
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.garbagemule.MobArena.framework.Arena;
|
|||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import com.garbagemule.MobArena.things.Thing;
|
||||
import com.garbagemule.MobArena.util.ClassChests;
|
||||
import com.garbagemule.MobArena.util.Slugs;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
|
@ -36,7 +37,7 @@ public class PickClassCommand implements Command
|
|||
|
||||
// Require a class name
|
||||
if (args.length != 1) return false;
|
||||
|
||||
|
||||
// Unwrap the sender
|
||||
Player p = Commands.unwrap(sender);
|
||||
|
||||
|
@ -51,15 +52,15 @@ public class PickClassCommand implements Command
|
|||
}
|
||||
|
||||
// Grab the ArenaClass, if it exists
|
||||
String lowercase = args[0].toLowerCase();
|
||||
ArenaClass ac = am.getClasses().get(lowercase);
|
||||
String slug = Slugs.create(args[0]);
|
||||
ArenaClass ac = am.getClasses().get(slug);
|
||||
if (ac == null) {
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_NO_SUCH_CLASS, lowercase);
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_NO_SUCH_CLASS, slug);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for permission.
|
||||
if (!ac.hasPermission(p) && !lowercase.equals("random")) {
|
||||
if (!ac.hasPermission(p) && !slug.equals("random")) {
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PERMISSION);
|
||||
return true;
|
||||
}
|
||||
|
@ -88,15 +89,15 @@ public class PickClassCommand implements Command
|
|||
clm.playerLeftClass(oldAC, p);
|
||||
clm.playerPickedClass(ac, p);
|
||||
|
||||
if (!lowercase.equalsIgnoreCase("random")) {
|
||||
if (!slug.equalsIgnoreCase("random")) {
|
||||
if (arena.getSettings().getBoolean("use-class-chests", false)) {
|
||||
if (ClassChests.assignClassFromStoredClassChest(arena, p, ac)) {
|
||||
return true;
|
||||
}
|
||||
// No linked chest? Fall through to config-file
|
||||
}
|
||||
arena.assignClass(p, lowercase);
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PICKED, arena.getClasses().get(lowercase).getConfigName());
|
||||
arena.assignClass(p, slug);
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PICKED, arena.getClasses().get(slug).getConfigName());
|
||||
if (price != null) {
|
||||
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PRICE, price.toString());
|
||||
}
|
||||
|
@ -118,9 +119,9 @@ public class PickClassCommand implements Command
|
|||
Collection<ArenaClass> classes = am.getClasses().values();
|
||||
|
||||
return classes.stream()
|
||||
.filter(cls -> cls.getConfigName().toLowerCase().startsWith(prefix))
|
||||
.filter(cls -> cls.getSlug().startsWith(prefix))
|
||||
.filter(cls -> cls.hasPermission(player))
|
||||
.map(ArenaClass::getConfigName)
|
||||
.map(ArenaClass::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import java.util.List;
|
|||
|
||||
@CommandInfo(
|
||||
name = "playerlist",
|
||||
pattern = "player.*|listp.*",
|
||||
pattern = "players|playerlist|player-list",
|
||||
usage = "/ma players (<arena>)",
|
||||
desc = "lists players in an arena",
|
||||
permission = "mobarena.use.playerlist"
|
||||
|
@ -25,29 +25,29 @@ public class PlayerListCommand implements Command
|
|||
public boolean execute(ArenaMaster am, CommandSender sender, String... args) {
|
||||
// Grab the argument, if any.
|
||||
String arg1 = (args.length > 0 ? args[0] : "");
|
||||
|
||||
|
||||
String list = null;
|
||||
if (!arg1.equals("")) {
|
||||
Arena arena = am.getArenaWithName(arg1);
|
||||
|
||||
|
||||
if (arena == null) {
|
||||
am.getGlobalMessenger().tell(sender, Msg.ARENA_DOES_NOT_EXIST);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
list = MAUtils.listToString(arena.getPlayersInArena(), am.getPlugin());
|
||||
} else {
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
List<Player> players = new LinkedList<>();
|
||||
|
||||
|
||||
for (Arena arena : am.getArenas()) {
|
||||
players.addAll(arena.getPlayersInArena());
|
||||
}
|
||||
|
||||
|
||||
buffy.append(MAUtils.listToString(players, am.getPlugin()));
|
||||
list = buffy.toString();
|
||||
}
|
||||
|
||||
|
||||
am.getGlobalMessenger().tell(sender, Msg.MISC_LIST_PLAYERS.format(list));
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -29,11 +29,11 @@ public class SpecCommand implements Command
|
|||
am.getGlobalMessenger().tell(sender, Msg.MISC_NOT_FROM_CONSOLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Unwrap the sender, grab the argument, if any.
|
||||
Player p = Commands.unwrap(sender);
|
||||
String arg1 = (args.length > 0 ? args[0] : null);
|
||||
|
||||
|
||||
// Run some rough sanity checks, and grab the arena to spec.
|
||||
Arena toArena = Commands.getArenaToJoinOrSpec(am, p, arg1);
|
||||
if (toArena == null || !canSpec(p, toArena)) {
|
||||
|
@ -88,9 +88,9 @@ public class SpecCommand implements Command
|
|||
|
||||
return arenas.stream()
|
||||
.filter(Arena::isEnabled)
|
||||
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
|
||||
.filter(arena -> arena.getSlug().startsWith(prefix))
|
||||
.filter(arena -> arena.getRegion().isSetup())
|
||||
.map(Arena::configName)
|
||||
.map(Arena::getSlug)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,12 @@ public class ArenaEndEvent extends Event implements Cancellable
|
|||
private static final HandlerList handlers = new HandlerList();
|
||||
private Arena arena;
|
||||
private boolean cancelled;
|
||||
|
||||
|
||||
public ArenaEndEvent(Arena arena) {
|
||||
this.arena = arena;
|
||||
this.cancelled = false;
|
||||
}
|
||||
|
||||
|
||||
public Arena getArena() {
|
||||
return arena;
|
||||
}
|
||||
|
@ -29,12 +29,12 @@ public class ArenaEndEvent extends Event implements Cancellable
|
|||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,17 @@ public class ArenaPlayerDeathEvent extends Event
|
|||
private Player player;
|
||||
private Arena arena;
|
||||
private boolean last;
|
||||
|
||||
|
||||
public ArenaPlayerDeathEvent(Player player, Arena arena, boolean last) {
|
||||
this.player = player;
|
||||
this.arena = arena;
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
|
||||
public Arena getArena() {
|
||||
return arena;
|
||||
}
|
||||
|
@ -29,12 +29,12 @@ public class ArenaPlayerDeathEvent extends Event
|
|||
public boolean wasLastPlayerStanding() {
|
||||
return last;
|
||||
}
|
||||
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,17 +12,17 @@ public class ArenaPlayerJoinEvent extends Event implements Cancellable
|
|||
private Player player;
|
||||
private Arena arena;
|
||||
private boolean cancelled;
|
||||
|
||||
|
||||
public ArenaPlayerJoinEvent(Player player, Arena arena) {
|
||||
this.player = player;
|
||||
this.arena = arena;
|
||||
this.cancelled = false;
|
||||
}
|
||||
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
|
||||
public Arena getArena() {
|
||||
return arena;
|
||||
}
|
||||
|
@ -36,12 +36,12 @@ public class ArenaPlayerJoinEvent extends Event implements Cancellable
|
|||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,17 +12,17 @@ public class ArenaPlayerLeaveEvent extends Event implements Cancellable
|
|||
private Player player;
|
||||
private Arena arena;
|
||||
private boolean cancelled;
|
||||
|
||||
|
||||
public ArenaPlayerLeaveEvent(Player player, Arena arena) {
|
||||
this.player = player;
|
||||
this.arena = arena;
|
||||
this.cancelled = false;
|
||||
}
|
||||
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
|
||||
public Arena getArena() {
|
||||
return arena;
|
||||
}
|
||||
|
@ -36,12 +36,12 @@ public class ArenaPlayerLeaveEvent extends Event implements Cancellable
|
|||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,11 @@ public class ArenaPlayerReadyEvent extends Event implements Cancellable
|
|||
this.arena = arena;
|
||||
this.cancelled = false;
|
||||
}
|
||||
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
|
||||
public Arena getArena() {
|
||||
return arena;
|
||||
}
|
||||
|
@ -36,12 +36,12 @@ public class ArenaPlayerReadyEvent extends Event implements Cancellable
|
|||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,12 @@ public class ArenaStartEvent extends Event implements Cancellable
|
|||
private static final HandlerList handlers = new HandlerList();
|
||||
private Arena arena;
|
||||
private boolean cancelled;
|
||||
|
||||
|
||||
public ArenaStartEvent(Arena arena) {
|
||||
this.arena = arena;
|
||||
this.cancelled = false;
|
||||
}
|
||||
|
||||
|
||||
public Arena getArena() {
|
||||
return arena;
|
||||
}
|
||||
|
@ -29,12 +29,12 @@ public class ArenaStartEvent extends Event implements Cancellable
|
|||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package com.garbagemule.MobArena.events;
|
||||
|
||||
import com.garbagemule.MobArena.MobArena;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called right before MobArena begins reloading.
|
||||
* <p>
|
||||
* Use this event to re-register components that need to be set up and in
|
||||
* place <i>before</i> MobArena dips into its config-file. This is mostly
|
||||
* relevant for plugins that <code>loadbefore</code> MobArena to register
|
||||
* {@link com.garbagemule.MobArena.things.ThingParser}s and such.
|
||||
* <p>
|
||||
* This event is <i>not</i> suitable for working with the "current" state
|
||||
* of arenas, classes, etc., because MobArena's state at the time of this
|
||||
* event is about to become stale. To work with the "current" state after
|
||||
* a reload, use the {@link MobArenaReloadEvent} instead.
|
||||
*
|
||||
* @see MobArenaReloadEvent
|
||||
*/
|
||||
public class MobArenaPreReloadEvent extends Event {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private final MobArena plugin;
|
||||
|
||||
public MobArenaPreReloadEvent(MobArena plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current MobArena plugin instance.
|
||||
*
|
||||
* @return the MobArena plugin instance
|
||||
*/
|
||||
public MobArena getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.garbagemule.MobArena.events;
|
||||
|
||||
import com.garbagemule.MobArena.MobArena;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called right after MobArena has reloaded.
|
||||
* <p>
|
||||
* The primary goal of this event is to allow extension plugins to "ride
|
||||
* along" with the <code>/ma reload</code> command so they don't have to
|
||||
* implement their own commands. <i>One command to rule them all.</i>
|
||||
* <p>
|
||||
* This event is useful if you need to work with arenas, classes, etc. in
|
||||
* their "current" state. This is typical of plugins that (soft)depend on
|
||||
* MobArena and use its API after the initialization phase.
|
||||
* <p>
|
||||
* This event is <i>not</i> suitable for re-registering components that
|
||||
* need to be in place <i>before</i> MobArena reloads. For that, see the
|
||||
* {@link MobArenaPreReloadEvent}.
|
||||
*
|
||||
* @see MobArenaPreReloadEvent
|
||||
*/
|
||||
public class MobArenaReloadEvent extends Event {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private final MobArena plugin;
|
||||
|
||||
public MobArenaReloadEvent(MobArena plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current MobArena plugin instance.
|
||||
*
|
||||
* @return the MobArena plugin instance
|
||||
*/
|
||||
public MobArena getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
}
|
|
@ -11,10 +11,10 @@ public class NewWaveEvent extends Event implements Cancellable
|
|||
private static final HandlerList handlers = new HandlerList();
|
||||
private Arena arena;
|
||||
private boolean cancelled;
|
||||
|
||||
|
||||
private Wave wave;
|
||||
private int waveNo;
|
||||
|
||||
|
||||
public NewWaveEvent(Arena arena, Wave wave, int waveNo) {
|
||||
this.arena = arena;
|
||||
this.wave = wave;
|
||||
|
@ -24,11 +24,11 @@ public class NewWaveEvent extends Event implements Cancellable
|
|||
public Wave getWave() {
|
||||
return wave;
|
||||
}
|
||||
|
||||
|
||||
public int getWaveNumber() {
|
||||
return waveNo;
|
||||
}
|
||||
|
||||
|
||||
public Arena getArena() {
|
||||
return arena;
|
||||
}
|
||||
|
@ -42,11 +42,11 @@ public class NewWaveEvent extends Event implements Cancellable
|
|||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
class ArgumentMismatch extends FormulaError {
|
||||
|
||||
private static final String few = "Not enough arguments for %s(%s), expected %d, got %d";
|
||||
private static final String many = "Too many arguments for %s(%s), expected %d, got %d";
|
||||
|
||||
ArgumentMismatch(Lexeme lexeme, int expected, int counted, String input) {
|
||||
super(message(lexeme, expected, counted), input, lexeme.pos);
|
||||
}
|
||||
|
||||
private static String message(Lexeme lexeme, int expected, int counted) {
|
||||
String name = lexeme.value;
|
||||
String args = args(expected);
|
||||
String template = (counted < expected) ? few : many;
|
||||
return String.format(template, name, args, expected, counted);
|
||||
}
|
||||
|
||||
private static String args(int count) {
|
||||
if (count == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
char current = 'a';
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(current);
|
||||
|
||||
for (int i = 1; i < count; i++) {
|
||||
current++;
|
||||
result.append(",").append(current);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
|
||||
class BinaryFormula implements Formula {
|
||||
|
||||
private final BinaryOperation operation;
|
||||
private final Formula left;
|
||||
private final Formula right;
|
||||
|
||||
BinaryFormula(BinaryOperation operation, Formula left, Formula right) {
|
||||
this.operation = operation;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double evaluate(Arena arena) {
|
||||
double a = left.evaluate(arena);
|
||||
double b = right.evaluate(arena);
|
||||
return operation.apply(a, b);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
class BinaryFunction {
|
||||
|
||||
final String name;
|
||||
final BinaryOperation operation;
|
||||
|
||||
BinaryFunction(String name, BinaryOperation operation) {
|
||||
this.name = name;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
Formula create(Formula left, Formula right) {
|
||||
return new BinaryFormula(operation, left, right);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface BinaryOperation {
|
||||
|
||||
double apply(double left, double right);
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
class BinaryOperator {
|
||||
|
||||
final String symbol;
|
||||
final int precedence;
|
||||
final boolean left;
|
||||
final BinaryOperation operation;
|
||||
|
||||
BinaryOperator(String symbol, int precedence, boolean left, BinaryOperation operation) {
|
||||
this.symbol = symbol;
|
||||
this.precedence = precedence;
|
||||
this.left = left;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
Formula create(Formula left, Formula right) {
|
||||
return new BinaryFormula(operation, left, right);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class Environment {
|
||||
|
||||
final List<Token> unary;
|
||||
final List<Token> binary;
|
||||
final List<Token> symbols;
|
||||
|
||||
final Map<String, Double> constants;
|
||||
final Map<String, Formula> variables;
|
||||
final Map<String, UnaryOperator> unaryOperators;
|
||||
final Map<String, BinaryOperator> binaryOperators;
|
||||
final Map<String, UnaryFunction> unaryFunctions;
|
||||
final Map<String, BinaryFunction> binaryFunctions;
|
||||
|
||||
private Environment() {
|
||||
unary = new ArrayList<>();
|
||||
binary = new ArrayList<>();
|
||||
symbols = Arrays.asList(
|
||||
Token.LEFT_PAREN,
|
||||
Token.RIGHT_PAREN,
|
||||
Token.COMMA
|
||||
);
|
||||
|
||||
constants = new HashMap<>();
|
||||
variables = new HashMap<>();
|
||||
unaryOperators = new HashMap<>();
|
||||
binaryOperators = new HashMap<>();
|
||||
unaryFunctions = new HashMap<>();
|
||||
binaryFunctions = new HashMap<>();
|
||||
}
|
||||
|
||||
void registerConstant(String name, double value) {
|
||||
constants.put(name, value);
|
||||
}
|
||||
|
||||
void registerVariable(String name, Formula formula) {
|
||||
variables.put(name, formula);
|
||||
}
|
||||
|
||||
void registerUnaryOperator(String symbol, int precedence, UnaryOperation operation) {
|
||||
unaryOperators.put(symbol, new UnaryOperator(symbol, precedence, operation));
|
||||
registerOperatorToken(TokenType.UNARY_OPERATOR, symbol, unary);
|
||||
}
|
||||
|
||||
void registerBinaryOperator(String symbol, int precedence, boolean left, BinaryOperation operation) {
|
||||
binaryOperators.put(symbol, new BinaryOperator(symbol, precedence, left, operation));
|
||||
registerOperatorToken(TokenType.BINARY_OPERATOR, symbol, binary);
|
||||
}
|
||||
|
||||
void registerUnaryFunction(String name, UnaryOperation operation) {
|
||||
unaryFunctions.put(name, new UnaryFunction(name, operation));
|
||||
}
|
||||
|
||||
void registerBinaryFunction(String name, BinaryOperation operation) {
|
||||
binaryFunctions.put(name, new BinaryFunction(name, operation));
|
||||
}
|
||||
|
||||
boolean isConstant(String identifier) {
|
||||
return constants.containsKey(identifier);
|
||||
}
|
||||
|
||||
boolean isVariable(String identifier) {
|
||||
return variables.containsKey(identifier);
|
||||
}
|
||||
|
||||
boolean isUnaryOperator(String symbol) {
|
||||
return unaryOperators.containsKey(symbol);
|
||||
}
|
||||
|
||||
boolean isBinaryOperator(String symbol) {
|
||||
return binaryOperators.containsKey(symbol);
|
||||
}
|
||||
|
||||
boolean isUnaryFunction(String identifier) {
|
||||
return unaryFunctions.containsKey(identifier);
|
||||
}
|
||||
|
||||
boolean isBinaryFunction(String identifier) {
|
||||
return binaryFunctions.containsKey(identifier);
|
||||
}
|
||||
|
||||
boolean isFunction(String identifier) {
|
||||
return isUnaryFunction(identifier)
|
||||
|| isBinaryFunction(identifier);
|
||||
}
|
||||
|
||||
double getConstant(String identifier) {
|
||||
return constants.get(identifier);
|
||||
}
|
||||
|
||||
Formula getVariable(String identifier) {
|
||||
return variables.get(identifier);
|
||||
}
|
||||
|
||||
UnaryOperator getUnaryOperator(String symbol) {
|
||||
return unaryOperators.get(symbol);
|
||||
}
|
||||
|
||||
BinaryOperator getBinaryOperator(String symbol) {
|
||||
return binaryOperators.get(symbol);
|
||||
}
|
||||
|
||||
UnaryFunction getUnaryFunction(String identifier) {
|
||||
return unaryFunctions.get(identifier);
|
||||
}
|
||||
|
||||
BinaryFunction getBinaryFunction(String identifier) {
|
||||
return binaryFunctions.get(identifier);
|
||||
}
|
||||
|
||||
private void registerOperatorToken(TokenType type, String operator, List<Token> operators) {
|
||||
int i;
|
||||
for (i = 0; i < operators.size(); i++) {
|
||||
if (operators.get(i).symbol.length() < operator.length()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
String escaped = operator.replaceAll("", "\\\\");
|
||||
String trimmed = escaped.substring(0, escaped.length() - 1);
|
||||
Token token = new Token(type, trimmed, operator);
|
||||
operators.add(i, token);
|
||||
}
|
||||
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
static Environment createDefault() {
|
||||
Environment result = new Environment();
|
||||
|
||||
// Constants
|
||||
result.registerConstant("pi", Math.PI);
|
||||
result.registerConstant("e", Math.E);
|
||||
|
||||
// Unary operators
|
||||
result.registerUnaryOperator("+", 4, value -> +value);
|
||||
result.registerUnaryOperator("-", 4, value -> -value);
|
||||
|
||||
// Binary operators
|
||||
result.registerBinaryOperator("+", 2, true, (a, b) -> a + b);
|
||||
result.registerBinaryOperator("-", 2, true, (a, b) -> a - b);
|
||||
result.registerBinaryOperator("*", 3, true, (a, b) -> a * b);
|
||||
result.registerBinaryOperator("/", 3, true, (a, b) -> a / b);
|
||||
result.registerBinaryOperator("%", 3, true, (a, b) -> a % b);
|
||||
result.registerBinaryOperator("^", 4, false, (a, b) -> Math.pow(a, b));
|
||||
|
||||
// Unary functions
|
||||
result.registerUnaryFunction("sqrt", Math::sqrt);
|
||||
result.registerUnaryFunction("abs", Math::abs);
|
||||
|
||||
result.registerUnaryFunction("ceil", Math::ceil);
|
||||
result.registerUnaryFunction("floor", Math::floor);
|
||||
result.registerUnaryFunction("round", value -> (double) Math.round(value));
|
||||
|
||||
result.registerUnaryFunction("sin", Math::sin);
|
||||
result.registerUnaryFunction("cos", Math::cos);
|
||||
result.registerUnaryFunction("tan", Math::tan);
|
||||
|
||||
// Binary functions
|
||||
result.registerBinaryFunction("min", Math::min);
|
||||
result.registerBinaryFunction("max", Math::max);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
|
||||
public interface Formula {
|
||||
|
||||
double evaluate(Arena arena);
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
class FormulaError extends IllegalArgumentException {
|
||||
|
||||
private final String input;
|
||||
private final int pos;
|
||||
|
||||
FormulaError(String message, String input, int pos) {
|
||||
super(message);
|
||||
this.input = input;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String arrow = arrow(pos + 1);
|
||||
String template = "%s\n%s\n%s";
|
||||
return String.format(template, super.getMessage(), input, arrow);
|
||||
}
|
||||
|
||||
private String arrow(int length) {
|
||||
char[] value = new char[length];
|
||||
Arrays.fill(value, ' ');
|
||||
value[length - 1] = '^';
|
||||
return new String(value);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import com.garbagemule.MobArena.MobArena;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FormulaMacros {
|
||||
|
||||
private static final String FILENAME = "formulas.yml";
|
||||
|
||||
private final Path file;
|
||||
private final Map<String, Map<String, String>> macros;
|
||||
|
||||
private FormulaMacros(Path file) {
|
||||
this.file = file;
|
||||
this.macros = new HashMap<>();
|
||||
}
|
||||
|
||||
public void reload() throws IOException {
|
||||
byte[] bytes = Files.readAllBytes(file);
|
||||
String content = new String(bytes);
|
||||
|
||||
Yaml yaml = new Yaml();
|
||||
Map<?, ?> raw = yaml.load(content);
|
||||
Map<String, Map<String, String>> converted = convert(raw);
|
||||
|
||||
macros.clear();
|
||||
macros.putAll(converted);
|
||||
}
|
||||
|
||||
private Map<String, Map<String, String>> convert(Map<?, ?> raw) {
|
||||
Map<String, Map<String, String>> result = new HashMap<>();
|
||||
|
||||
for (Map.Entry<?, ?> entry : raw.entrySet()) {
|
||||
String section = String.valueOf(entry.getKey());
|
||||
Map<String, String> macros = convert(entry.getValue());
|
||||
if (macros != null) {
|
||||
result.put(section, macros);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, String> convert(Object raw) {
|
||||
if (raw instanceof Map) {
|
||||
Map<String, String> macros = new HashMap<>();
|
||||
for (Map.Entry<?, ?> entry : ((Map<?, ?>) raw).entrySet()) {
|
||||
String macro = String.valueOf(entry.getKey());
|
||||
String formula = String.valueOf(entry.getValue());
|
||||
macros.put(macro, formula);
|
||||
}
|
||||
return macros;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String get(String key) {
|
||||
return get(null, key);
|
||||
}
|
||||
|
||||
public String get(String section, String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
if (section != null) {
|
||||
String macro = lookup(section, key);
|
||||
if (macro != null) {
|
||||
return macro;
|
||||
}
|
||||
}
|
||||
return lookup("global", key);
|
||||
}
|
||||
|
||||
private String lookup(String section, String key) {
|
||||
Map<String, String> specific = macros.get(section);
|
||||
if (specific == null) {
|
||||
return null;
|
||||
}
|
||||
return specific.get(key);
|
||||
}
|
||||
|
||||
public static FormulaMacros create(MobArena plugin) {
|
||||
File file = new File(plugin.getDataFolder(), FILENAME);
|
||||
if (!file.exists()) {
|
||||
plugin.getLogger().info(FILENAME + " not found, creating default...");
|
||||
plugin.saveResource(FILENAME, false);
|
||||
}
|
||||
return new FormulaMacros(file.toPath());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FormulaManager {
|
||||
|
||||
private final Environment env;
|
||||
private final Lexer lexer;
|
||||
private final Parser parser;
|
||||
|
||||
FormulaManager(
|
||||
Environment env,
|
||||
Lexer lexer,
|
||||
Parser parser
|
||||
) {
|
||||
this.env = env;
|
||||
this.lexer = lexer;
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void registerConstant(String name, double value) {
|
||||
env.registerConstant(name, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void registerVariable(String name, Formula formula) {
|
||||
env.registerVariable(name, formula);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void registerUnaryOperator(String symbol, int precedence, UnaryOperation operation) {
|
||||
env.registerUnaryOperator(symbol, precedence, operation);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void registerBinaryOperator(String symbol, int precedence, boolean left, BinaryOperation operation) {
|
||||
env.registerBinaryOperator(symbol, precedence, left, operation);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void registerUnaryFunction(String name, UnaryOperation operation) {
|
||||
env.registerUnaryFunction(name, operation);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void registerBinaryFunction(String name, BinaryOperation operation) {
|
||||
env.registerBinaryFunction(name, operation);
|
||||
}
|
||||
|
||||
public Formula parse(String input) {
|
||||
List<Lexeme> infix = lexer.tokenize(input);
|
||||
return parser.parse(input, infix);
|
||||
}
|
||||
|
||||
public static FormulaManager createDefault() {
|
||||
Environment env = Environment.createDefault();
|
||||
|
||||
// Wave number variables
|
||||
env.registerVariable("current-wave", a -> a.getWaveManager().getWaveNumber());
|
||||
env.registerVariable("final-wave", a -> a.getWaveManager().getFinalWave());
|
||||
|
||||
// Player count variables
|
||||
env.registerVariable("initial-players", Arena::getPlayerCount);
|
||||
env.registerVariable("live-players", a -> a.getPlayersInArena().size());
|
||||
env.registerVariable("dead-players", a -> a.getPlayerCount() - a.getPlayersInArena().size());
|
||||
env.registerVariable("min-players", Arena::getMinPlayers);
|
||||
env.registerVariable("max-players", Arena::getMaxPlayers);
|
||||
|
||||
// Monster count variables
|
||||
env.registerVariable("live-monsters", a -> a.getMonsterManager().getMonsters().size());
|
||||
|
||||
Lexer lexer = new Lexer(env);
|
||||
Parser parser = new Parser(env);
|
||||
|
||||
return new FormulaManager(env, lexer, parser);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
public class Formulas {
|
||||
|
||||
/**
|
||||
* Default "old" style wave growth. Equivalent to the formula:
|
||||
* <pre>{@code <initial-players> + <current-wave>}</pre>
|
||||
*/
|
||||
public static final Formula DEFAULT_WAVE_GROWTH = (arena) -> {
|
||||
int players = arena.getPlayerCount();
|
||||
int wave = arena.getWaveManager().getWaveNumber();
|
||||
return players + wave;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default "low" swarm amount. Equivalent to the formula:
|
||||
* <pre>{@code max(1, <initial-players> / 2) * 10}</pre>
|
||||
*/
|
||||
public static final Formula DEFAULT_SWARM_AMOUNT = (arena) -> {
|
||||
int players = arena.getPlayerCount();
|
||||
return Math.max(1, players / 2) * 10;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default "medium" boss health. Equivalent to the formula:
|
||||
* <pre>{@code (<initial-players> + 1) 20 * 15}</pre>
|
||||
*/
|
||||
public static final Formula DEFAULT_BOSS_HEALTH = (arena) -> {
|
||||
int players = arena.getPlayerCount();
|
||||
return (players + 1) * 20 * 8;
|
||||
};
|
||||
|
||||
private Formulas() {
|
||||
// OK BOSS
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue