32 Commits
Author SHA1 Message Date
NotAlexNoyle 15d3be7bc0 Update canonical Gitea link 2026-10-30 14:18:49 -04:00
NotAlexNoyle 1412895b4e Exclude CODEOWNERS from language statistics 2026-10-29 16:19:00 -04:00
NotAlexNoyle 716b0b492d Add CODEOWNERS (one staff approval) 2026-10-29 16:19:00 -04:00
NotAlexNoyle aec512674a Merge pull request #1 from true-og/agent/fix-1.19.4-spawn-eggs
Fix spawn egg handling on Purpur 1.19.4
2026-07-23 18:55:19 -04:00
NotAlexNoyle 327aecbdc7 Use 1.19.4 spawn egg defaults 2026-07-23 16:51:47 -04:00
NotAlexNoyle fa5331119e Validate spawn eggs for Purpur 1.19.4 2026-07-23 16:51:01 -04:00
NotAlexNoyle 83d462d61d Remove spawn egg console dump 2026-07-23 16:50:29 -04:00
NotAlexNoyle 71ba86a9f8 Remove Template-OG artifact 2026-06-21 21:09:53 -04:00
NotAlexNoyle 7ca7f8a2bb Enhance entity limiting 2026-04-24 10:25:15 -04:00
NotAlexNoyle 5dda5178d5 Fix invalid config option 2026-04-23 12:08:46 -04:00
NotAlexNoyle c71aa1439b Use live TrueOG Network config file 2026-04-15 08:15:22 -04:00
NotAlexNoyle 55c3e740d0 Add gradle wrapper 2026-04-15 07:15:38 -04:00
NotAlexNoyle 869fd88b2a Initial release of MobLimiter-OG 2026-04-15 06:56:04 -04:00
Keelan c3806d51b4 Remove LogBlock functionality. 2025-08-20 11:37:50 -04:00
PPGOME ca92ef5db8 Update README.md 2024-09-01 03:35:30 -04:00
PPGOME 04baa77c91 Update README.md
Added new sections for the KeepBabyMobs and LimitSpawnEggs merger.
2024-09-01 03:31:53 -04:00
PPGOME 1a7788a69d Merge pull request #21 from PPGOME/master
KeepBabyMobs and LimitSpawnEggs merger
2024-09-01 03:16:02 -04:00
PPGOME 81afcce3f6 Integrated KeepBabyMobs and LimitSpawnEggs, minor performance tweaks 2024-09-01 03:13:29 -04:00
PPGOME 0dbe3e2f33 Added KeepBabyMobs logic 2024-08-29 01:50:08 -04:00
PPGOME 777bc6b549 Merge pull request #20 from PPGOME/master
Rebased plugin onto paper, fixed allay culling.
2024-08-08 00:23:36 -04:00
PPGOME 79072bac3e Minor tweaks 2024-08-08 00:23:06 -04:00
PPGOME b1d4a21179 Rebased plugin onto paper, fixed allay culling. 2024-08-08 00:18:44 -04:00
pez252 eee138b4a9 Merge pull request #19 from PPGOME/master
Fix for entity culling breaking in 1.17+
2024-07-03 21:17:36 -04:00
PPGOME 9982d87d7a Reverted from paper to spigot 2024-07-03 16:16:11 -04:00
PPGOME 92510048a9 Merge remote-tracking branch 'origin/master' 2024-07-03 06:48:46 -04:00
PPGOME 87c4f6d751 Fixed culling not working 1.17+, modified file names. 2024-07-03 06:48:39 -04:00
PPGOME e44fb8a417 Update README.md 2024-07-03 06:47:35 -04:00
PPGOME 2a73ea8b08 Fixed culling not working 1.17+ 2024-07-03 06:44:51 -04:00
bermudalocket 37ad369e5a Version 2.0.2; Use static hashset instead of creating a new list every time 2019-01-07 01:09:22 -05:00
redwallhp 0d26c2c62f Typo in tropical_fish 2018-11-29 21:29:45 -05:00
redwallhp e1e40706a7 Added Illusioner and Parrot 2018-11-29 20:52:25 -05:00
redwallhp 4728df0db1 Added 1.13 mobs to default config 2018-11-29 20:46:19 -05:00
34 changed files with 2961 additions and 1030 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+47 -5
View File
@@ -1,7 +1,49 @@
target
/classes
/bin
*~
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# macOS system files
.DS_Store
.idea
# gradle build output files
build/
.gradle/
# eclipse project files
.settings/
.project
.classpath
.checkstyle
bin/
# IntelliJ files
.idea/
.kotlin
*.iml
*.ipr
*.iws
out/
# Maven leftovers
target/
+53 -7
View File
@@ -1,5 +1,20 @@
MobLimiter
===========
MobLimiter-OG
=============
TrueOG Network fork of [MobLimiter](https://github.com/NerdNu/MobLimiter). Ported to Gradle 8.14.3 / Purpur API 1.19.4 with additional population controls and admin visibility.
Changes over upstream
---------------------
* **Gradle 8.14.3 build** targeting Purpur API `1.19.4-R0.1-SNAPSHOT`. Uses [Template-OG](https://git.trueog.net/trueog/Template-OG) tooling (Spotless, Checkstyle, Shadow, Eclipse integration).
* **Tamed mob limiting** — new `limit_tamed` config flag (default `false`). When `true`, tamed mobs are no longer exempt from age / spawn / chunk-unload limiters.
* **Elder guardian limiting** — new `limit_elder_guardian` config flag (default `false`). When `true`, elder guardians are no longer exempt.
* **Vanilla persistence respect** — new `respect_persistence` flag (default `true`). Honors vanilla `PersistenceRequired` so spawn-egg, `/summon`, and trader-llama mobs are exempt from limiters, matching vanilla "never despawn" semantics. Natural-spawn mobs (spawners, mob spawning) are not persistent and remain subject to limits.
* **Armed-mob culling** — new `limit_armed` flag (default `true`). Mobs carrying armor, a main-hand item, or an off-hand item are culled instead of protected. Closes the abuse path where players drop gear to mobs to keep them loaded indefinitely (vanilla sets `PersistenceRequired` on pickup). When `true` this overrides `respect_persistence` for armed mobs. Named / tamed / elder guardian exemptions still apply.
* **Enchanted-item age extension** — new `enchanted_age_multiplier` (default `2.0`). Mobs holding or wearing any enchanted item get their configured `age` limit multiplied by this value before culling. `1.0` disables the feature; `age: -1` mobs are unaffected.
* **Debug-gated despawn console log** — every plugin-initiated despawn logs to the server console when `debug` is on, including entity type, reason (`age limit`, `chunk unload cull`, `spawn cap radius`, `spawn cap chunk`), world, and block coordinates. (Upstream's separate `despawn_log_console` option was removed in favor of gating on `debug`.)
* **Despawn player notifications** — new `despawn_notify_players` flag (default `false`) and matching permission `moblimiter.notify` (default `op`). When enabled, every despawn is broadcast to online players holding the permission.
* **Configurable notify message** — `despawn_notify_message` template with `&`-color codes and placeholders `%mob%`, `%reason%`, `%world%`, `%x%`, `%y%`, `%z%`.
Version 2 of MobLimiter, featuring three configurable Limiter engines that control the mob population in different ways.
@@ -15,8 +30,9 @@ pairs of farm animals if it can, by not killing farm animals if there would be l
applicable mob type in a "view distance" (as a chunk radius) as well as in an individual chunk and blocking the addition
of extra mobs beyond the limit.
* **Chunk Unload:** The chunk-unload culling feature of MobLimiter 1.x is still available, if its use is desired.
* **Entity Unload:** Similar to version 2.0, the new entity-unload system checks for entities unloading within chunks and culls them down to numbers specified in the config, if desired.
MobLimiter also offers spawner modification protection to prevent unwanted changes to the mobs a spawner produces, as well as mob age locking for when you want to keep your baby a baby forever.
Configuration
-------------
@@ -28,6 +44,13 @@ Configuration
* `growth_ticks`: Ticks for a farm animal to grow up (-1 to disable)
* `logblock`: Enable LogBlock support. More below.
* `debug`: Print debugging info to console
* `limit_tamed`: If `true`, tamed mobs are subject to all limiters. Default `false`.
* `limit_elder_guardian`: If `true`, elder guardians are subject to all limiters. Default `false`.
* `respect_persistence`: If `true`, honor vanilla `PersistenceRequired` (spawn eggs, `/summon`, trader llamas). Default `true`.
* `limit_armed`: If `true`, cull mobs carrying armor or hand items; overrides `respect_persistence` for armed mobs. Default `true`.
* `enchanted_age_multiplier`: Multiplier applied to a mob's configured `age` when it is holding or wearing an enchanted item. Default `2.0`.
* `despawn_notify_players`: If `true`, broadcast every despawn to online players with `moblimiter.notify`. Default `false`.
* `despawn_notify_message`: Template for the notification. Supports `&`-color codes and placeholders `%mob%`, `%reason%`, `%world%`, `%x%`, `%y%`, `%z%`.
### Default Limits
@@ -86,6 +109,17 @@ tampering with vanilla breeding behavior.
This function only affects farm animals, and ignores other breedable entities like ocelots, wolves and villagers.
### Prevent Spawner Modification
MobLimiter blocks the modification of spawners through the use of spawn eggs. You can allow a player to modify them by
granting the permission `moblimiter.spawners.bypass`. You can also customize which spawn eggs are blocked in the config
under the `spawn_eggs` section.
### Mob Age Locking
You're able to lock the age of passive baby mobs by naming them with a nametag. These babies will never grow up, even
if fed with food. If one of these mobs is killed, a log will be made with the person who killed it, the type of mob,
location, and any features specific to that mob (coat colour, owner, etc.).
### Special Mobs
@@ -94,12 +128,14 @@ MobLimiter will not remove any mobs that are deemed to be "special" in some way
The criteria include:
* Mobs with custom names, such as from a name tag
* Tamed mobs
* Elder guardians. (Regular guardians can be limited, but Elder ones won't be touched.)
* Tamed mobs *(unless `limit_tamed: true`)*
* Any mob that is holding an item, as it may have picked up a player's equipment.
* Elder guardians. (Regular guardians can be limited, but Elder ones won't be touched unless `limit_elder_guardian: true`.)
* Mobs wearing armor or holding items in their main or off hand *(unless `limit_armed: true`, the default; players would otherwise abuse this by dropping gear to mobs to keep them loaded)*. Mobs holding or wearing any enchanted item still get culled under `limit_armed: true`, but receive an extended age threshold via `enchanted_age_multiplier` (default `2.0` = double the configured age).
* Mobs with vanilla `PersistenceRequired` set, including spawn-egg placements, `/summon` commands, and trader llamas *(unless `respect_persistence: false`)*. Armed mobs are still culled when `limit_armed: true`, since picking up an item also sets `PersistenceRequired` in vanilla and would otherwise reopen the abuse path.
### LogBlock Integration
@@ -128,3 +164,13 @@ name of `MobLimiter`.
All commands can be accessed with the `moblimiter.*` permission node.
### Permissions
* `moblimiter.reload` — `/moblimiter reload`
* `moblimiter.count` — `/moblimiter count`
* `moblimiter.limits` — `/moblimiter limits`
* `moblimiter.check` — `/moblimiter check`
* `moblimiter.spawners.bypass` — Modify spawners with spawn eggs.
* `moblimiter.notify` — Receive in-game despawn notifications when `despawn_notify_players` is enabled.
* `moblimiter.*` — All of the above.
+107
View File
@@ -0,0 +1,107 @@
/* ------------------------------ Plugins ------------------------------ */
plugins {
id("java")
id("java-library")
id("com.diffplug.spotless") version "8.1.0"
id("com.gradleup.shadow") version "8.3.9"
id("checkstyle")
eclipse
}
/* --------------------------- JDK ------------------------------------- */
java {
sourceCompatibility = JavaVersion.VERSION_17
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.GRAAL_VM)
}
}
/* ----------------------------- Metadata ------------------------------ */
group = "nu.nerd"
version = "2.3.2"
val apiVersion = "1.19"
/* ----------------------------- Resources ----------------------------- */
tasks.named<ProcessResources>("processResources") {
val props = mapOf("version" to version, "apiVersion" to apiVersion)
inputs.properties(props)
filesMatching("plugin.yml") { expand(props) }
from("LICENSE") { into("/") }
}
/* ---------------------------- Repos ---------------------------------- */
repositories {
mavenCentral()
gradlePluginPortal()
maven { url = uri("https://repo.purpurmc.org/snapshots") }
maven { url = uri("https://repo.papermc.io/repository/maven-public/") }
maven { url = uri("file://${System.getProperty("user.home")}/.m2/repository") }
System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let {
val dir = file(it)
if (dir.isDirectory) {
println("Using SELF_MAVEN_LOCAL_REPO at: $it")
maven { url = uri("file://${dir.absolutePath}") }
} else {
logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
mavenLocal()
}
} ?: logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
}
/* ---------------------- Java project deps ---------------------------- */
dependencies { compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") }
apply(from = "eclipse.gradle.kts")
/* ---------------------- Reproducible jars ---------------------------- */
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
/* ----------------------------- Shadow -------------------------------- */
tasks.shadowJar {
isEnableRelocation = true
relocationPrefix = "${project.group}.shadow"
archiveClassifier.set("")
minimize()
}
tasks.jar { archiveClassifier.set("part") }
tasks.build { dependsOn(tasks.spotlessApply, tasks.shadowJar) }
/* --------------------------- Javac opts ------------------------------- */
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("-parameters")
options.isFork = true
options.compilerArgs.add("-Xlint:deprecation")
options.encoding = "UTF-8"
}
/* ----------------------------- Auto Formatting ------------------------ */
spotless {
java {
eclipse().configFile("config/formatter/eclipse-java-formatter.xml")
leadingTabsToSpaces()
removeUnusedImports()
}
kotlinGradle {
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) }
target("build.gradle.kts", "settings.gradle.kts")
}
}
checkstyle {
toolVersion = "10.18.1"
configFile = file("config/checkstyle/checkstyle.xml")
isIgnoreFailures = true
isShowViolations = true
}
tasks.named("compileJava") { dependsOn("spotlessApply") }
tasks.named("spotlessCheck") { dependsOn("spotlessApply") }
-121
View File
@@ -1,121 +0,0 @@
radius: 3
breeding_ticks: 400
growth_ticks: 400
logblock: false
relative_age: false
debug: false
defaults:
age: -1
max: 150
chunk_max: 50
cull: 4
limits:
bat:
cull: 6
blaze:
age: 12000
cull: 0
cave_spider:
age: 12000
cull: 2
chicken: []
cow: []
creeper:
age: 24000
cull: 8
ender_dragon: []
enderman:
age: 18000
cull: 0
endermite:
cull: 6
evoker: []
ghast:
cull: 1
giant:
cull: -1
guardian:
age: 12000
max: 100
chunk_max: 25
cull: 4
horse:
age: -1
iron_golem: []
llama:
cull: 8
magma_cube:
age: 24000
mushroom_cow: []
ocelot:
cull: 8
pig: []
pig_zombie:
age: 18000
rabbit:
cull: 8
sheep_white:
cull: 2
sheep_orange:
cull: 2
sheep_magenta:
cull: 2
sheep_light_blue:
cull: 2
sheep_yellow:
cull: 2
sheep_lime:
cull: 2
sheep_pink:
cull: 2
sheep_silver:
cull: 2
sheep_cyan:
cull: 2
sheep_purple:
cull: 2
sheep_blue:
cull: 2
sheep_brown:
cull: 2
sheep_green:
cull: 2
sheep_red:
cull: 2
sheep_black:
cull: 2
shulker: []
silverfish:
cull: 6
skeleton:
age: 24000
slime:
age: 24000
snowman:
cull: -1
spider:
age: 12000
cull: 2
squid:
age: 12000
max: 80
chunk_max: 5
cull: 1
vex: []
villager:
chunk_max: 20
age: -1
vindicator: []
witch:
age: 24000
cull: 2
wither:
cull: -1
wolf:
cull: 8
zombie:
age: 24000
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="TreeWalker">
<module name="FinalLocalVariable"/>
<module name="AvoidStarImport"/>
<module name="Indentation">
<property name="severity" value="ignore"/>
</module>
</module>
<module name="RegexpSingleline">
<property name="format" value="\borg\.junit\.jupiter\.api\.Assertions\.fail\s*\(\s*&quot;Not yet implemented&quot;\s*\)\s*;"/>
<property name="message" value="Remove placeholder fail() and implement the test."/>
<property name="ignoreCase" value="true"/>
<property name="severity" value="warning"/>
</module>
<module name="RegexpSingleline">
<property name="format" value="(?&lt;!\w)fail\s*\(\s*&quot;Not yet implemented&quot;\s*\)\s*;"/>
<property name="message" value="Remove placeholder fail() and implement the test."/>
<property name="severity" value="warning"/>
</module>
</module>
+404
View File
@@ -0,0 +1,404 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="23">
<profile kind="CodeFormatterProfile" name="TrueOG Network Formatting" version="23">
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case_after_arrow" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.align_arrows_in_switch_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow" value="20"/>
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile>
</profiles>
+158
View File
@@ -0,0 +1,158 @@
/* This is free and unencumbered software released into the public domain */
import org.gradle.api.tasks.bundling.Jar
import org.gradle.plugins.ide.eclipse.model.EclipseModel
import java.nio.file.Files
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
val sourcePluginProjects =
configurations
.getByName("compileClasspath")
.allDependencies
.filterIsInstance<ProjectDependency>()
.map { it.path }
.filter { it.startsWith(":libs:") }
.distinct()
val sourcePluginProjectNames = sourcePluginProjects.map { it.substringAfterLast(":") }.toSet()
sourcePluginProjects.forEach { evaluationDependsOn(it) }
val ideLibs: Configuration by
configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
}
configure<EclipseModel> {
project {
name = "${rootProject.name}-Plugin"
}
classpath {
plusConfigurations.add(ideLibs)
}
}
val ideLibDir = layout.buildDirectory.dir("ide-libs")
val hashRegex = Regex("-[0-9a-fA-F]{10}(?=\\.jar$)") // Black magic.
val copyTasks = mutableListOf<TaskProvider<Copy>>()
sourcePluginProjects.forEach { path ->
val sub = project(path)
val archiveTaskName = if ("shadowJar" in sub.tasks.names) "shadowJar" else "jar"
val archiveTask = sub.tasks.named<Jar>(archiveTaskName)
val copyTask =
tasks.register<Copy>("ideCopy${sub.name.replaceFirstChar(Char::titlecase)}") {
dependsOn(archiveTask)
from(archiveTask.flatMap { it.archiveFile })
into(ideLibDir)
rename { it.replace(hashRegex, "") } // Remove git commit hash from jarfile.
}
copyTasks += copyTask
}
/* Ensure the jars exist before .classpath is generated */
tasks.named("eclipseClasspath").configure { dependsOn(copyTasks) }
/* Supply those jars to the ideLibs configuration (after evaluation) */
afterEvaluate { dependencies { add("ideLibs", fileTree(ideLibDir) { include("*.jar") }) } }
val injectIdeLibs =
tasks.register("injectIdeLibs") {
dependsOn("eclipse") // run after all eclipse files are generated
doLast {
val cpFile = file(".classpath")
if (!cpFile.exists()) return@doLast
val jars = fileTree(ideLibDir.get()) { include("*.jar") }.files.sortedBy { it.name }
if (jars.isEmpty()) return@doLast
// Parse DOM
val dbf = DocumentBuilderFactory.newInstance()
val doc = dbf.newDocumentBuilder().parse(cpFile)
val root = doc.documentElement
// Helper to see if an entry already exists
fun exists(path: String): Boolean =
root.getElementsByTagName("classpathentry").let { list ->
(0 until list.length).any { i ->
val n = list.item(i)
val kind = n.attributes?.getNamedItem("kind")?.nodeValue
val p = n.attributes?.getNamedItem("path")?.nodeValue
kind == "lib" && p == path
}
}
// Remove any entry that points to ide-libs dir or unresolved :libs: project refs.
val toRemove = mutableListOf<org.w3c.dom.Node>()
val list = root.getElementsByTagName("classpathentry")
val dirPath = ideLibDir.get().asFile.absolutePath
for (i in 0 until list.length) {
val n = list.item(i)
val kind = n.attributes?.getNamedItem("kind")?.nodeValue
val p = n.attributes?.getNamedItem("path")?.nodeValue ?: ""
val isIdeLibEntry = kind == "lib" && (p == dirPath || p.startsWith("$dirPath/"))
val sourceProjectPath = p.removePrefix("/")
val isHalfImportedSourceModule = kind == "src" && p.startsWith("/libs:")
val isSourcePluginModule = kind == "src" && sourceProjectPath in sourcePluginProjectNames
if (isIdeLibEntry || isHalfImportedSourceModule || isSourcePluginModule) {
toRemove += n
}
}
toRemove.forEach { root.removeChild(it) }
// Append our jar entries LAST
jars.forEach { f ->
val abs = f.absolutePath
if (!exists(abs)) {
val entry = doc.createElement("classpathentry")
entry.setAttribute("kind", "lib")
entry.setAttribute("path", abs)
root.appendChild(entry)
}
}
// Write back pretty
val tf =
TransformerFactory.newInstance().newTransformer().apply {
setOutputProperty(OutputKeys.INDENT, "yes")
setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1")
setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no")
setOutputProperty(OutputKeys.ENCODING, "UTF-8")
}
Files.newBufferedWriter(cpFile.toPath()).use { w -> tf.transform(DOMSource(doc), StreamResult(w)) }
}
}
tasks.named("eclipse").configure { finalizedBy(injectIdeLibs) }
val eclipseModel = extensions.getByType<EclipseModel>()
fun Project.addResolvableEclipseConfigs() {
val jarAttr =
objects.named(org.gradle.api.attributes.LibraryElements::class, org.gradle.api.attributes.LibraryElements.JAR)
val apiAttr = objects.named(org.gradle.api.attributes.Usage::class, org.gradle.api.attributes.Usage.JAVA_API)
val compileOnlyRes =
configurations.create("eclipseCompileOnly") {
extendsFrom(configurations.getByName("compileOnly"))
isCanBeResolved = true
isCanBeConsumed = false
attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr)
attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr)
}
val compileOnlyApiRes =
configurations.create("eclipseCompileOnlyApi") {
extendsFrom(configurations.getByName("compileOnlyApi"))
isCanBeResolved = true
isCanBeConsumed = false
attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr)
attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr)
}
eclipseModel.classpath.plusConfigurations.addAll(listOf(compileOnlyRes, compileOnlyApiRes))
}
addResolvableEclipseConfigs()
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+251
View File
@@ -0,0 +1,251 @@
#!/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.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# 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/platforms/jvm/plugins-application/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 -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || 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="\\\"\\\""
# 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, 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" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# 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" "$@"
Vendored
+94
View File
@@ -0,0 +1,94 @@
@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
@rem SPDX-License-Identifier: Apache-2.0
@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. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
: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
-63
View File
@@ -1,63 +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>nu.nerd</groupId>
<artifactId>MobLimiter</artifactId>
<version>2.0.1</version>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>md_5-releases</id>
<url>http://repo.md-5.net/content/repositories/releases/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.9-R0.1-SNAPSHOT</version>
</dependency>
<dependency>
<!-- This is required because LogBlock is a pain -->
<groupId>de.diddiz</groupId>
<artifactId>questioner</artifactId>
<version>${project.version}</version>
<scope>system</scope>
<systemPath>${project.basedir}/vendor/LogBlockQuestioner.jar</systemPath>
</dependency>
<dependency>
<groupId>de.diddiz</groupId>
<artifactId>logblock</artifactId>
<version>1.94</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${basedir}/src</sourceDirectory>
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>.</directory>
<includes>
<include>plugin.yml</include>
<include>config.yml</include>
</includes>
</resource>
</resources>
</build>
</project>
+1
View File
@@ -0,0 +1 @@
rootProject.name = "MobLimiter-OG"
@@ -0,0 +1,403 @@
package nu.nerd.moblimiter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import nu.nerd.moblimiter.configuration.ConfiguredDefaults;
import nu.nerd.moblimiter.configuration.ConfiguredMob;
import org.bukkit.Chunk;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.*;
import java.util.*;
public class CommandHandler implements CommandExecutor {
private MobLimiter plugin;
public CommandHandler() {
plugin = MobLimiter.instance;
plugin.getCommand("moblimiter").setExecutor(this);
}
// --------------------------------------------------------------------------------------------
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) {
reloadConfig(sender);
} else if (args.length > 0 && args[0].equalsIgnoreCase("count")) {
countCommand(sender);
} else if (args.length > 0 && args[0].equalsIgnoreCase("limits")) {
limitsCommand(sender, args);
} else if (args.length > 0 && args[0].equalsIgnoreCase("check")) {
checkCommand(sender);
} else if (args.length > 0 && args[0].equalsIgnoreCase("help")) {
helpText(sender, cmd, args);
} else {
commandsList(sender);
}
return true;
}
// --------------------------------------------------------------------------------------------
/**
* Command to reload the configuration
*/
private void reloadConfig(CommandSender sender) {
if (!sender.hasPermission("moblimiter.reload")) {
sender.sendMessage(
Component.text("You don't have permission to do this.").color(TextColor.fromHexString("#FF5555")));
return;
}
plugin.getConfiguration().load();
sender.sendMessage(Component.text("MobLimiter config reloaded!").color(TextColor.fromHexString("#FFAA00")));
}
// --------------------------------------------------------------------------------------------
/**
* When a player runs /moblimiter with no arguments, list available commands and
* guide newbies to /moblimiter help
*
* @param sender
*/
private void commandsList(CommandSender sender) {
Component helpMessage = LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6&lMobLimiter: Available Commands&r");
helpMessage = helpMessage.appendNewline().append(LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6/moblimiter help&r - What is MobLimiter and how does it work?"));
if (sender.hasPermission("moblimiter.limits")) {
helpMessage = helpMessage.appendNewline().append(LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6/moblimiter limits&r - List the limits for each mob type"));
}
if (sender.hasPermission("moblimiter.count")) {
helpMessage = helpMessage.appendNewline().append(LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6/moblimiter count&r - Count entities in the current chunk and radius"));
}
if (sender.hasPermission("moblimiter.check")) {
helpMessage = helpMessage.appendNewline().append(LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6/moblimiter check&r - Inspect limiting details for the mob you're looking at"));
}
if (sender.hasPermission("moblimiter.reload")) {
helpMessage = helpMessage.appendNewline().append(LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6/moblimiter reload&r - Reload configuration from disk"));
}
sender.sendMessage(helpMessage);
}
// --------------------------------------------------------------------------------------------
/**
* When a player runs /moblimiter help, print help text
*/
private void helpText(CommandSender sender, Command cmd, String[] args) {
int num = 1;
if (args.length > 1) {
try {
num = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
num = 1;
}
}
if (num == 1) {
Component helpMessage = LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6This server runs a plugin called &eMobLimiter"
+ " &6in order to manage the amount of mobs on the server, as too many mobs creates lag"
+ " which affects everyone. MobLimiter uses several methods to manage the mob population,"
+ " depending on how it is configured:")
.appendNewline();
helpMessage = helpMessage
.append(LegacyComponentSerializer.legacyAmpersand().deserialize("&e1. Entity unload culling"))
.appendNewline()
.append(LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6Mobs are culled down to a defined maximum"
+ " whenever a cluster of entities unloads or the server restarts."))
.appendNewline();
helpMessage = helpMessage
.append(LegacyComponentSerializer.legacyAmpersand().deserialize("&e2. Real-time spawn limiting"))
.appendNewline()
.append(LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6Mobs are tracked in real-time and new spawns"
+ " are prevented in an area if there are more than a certain amount"
+ " of that mob type there already."));
sender.sendMessage(helpMessage);
}
if (num == 2) {
sender.sendMessage(
LegacyComponentSerializer.legacyAmpersand().deserialize("&e3. Age limiting").appendNewline()
.append(LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6Mobs automatically die when they reach a certain age,"
+ " dropping their items when they do. "
+ "e.g. a skeleton may live for 15 minutes before dying."))
.appendNewline()
.append(LegacyComponentSerializer.legacyAmpersand()
.deserialize("&6In all cases, \"special\" mobs are always protected"
+ " and will not be removed by the plugin. This includes mobs with"
+ " custom names, tamed animals, and mobs wearing armor."))
.appendNewline()
.append(LegacyComponentSerializer.legacyAmpersand()
.deserialize("To make up for the culling of passive farm animals,"
+ " the plugin shortens the growth rate of baby animals and the"
+ " waiting time in between breeding events, to approximately &7"
+ (plugin.getConfiguration().getGrowthTicks() / 20) + " &6and &7"
+ (plugin.getConfiguration().getBreedingTicks() / 20) + " &6seconds "
+ "respectively.")));
}
sender.sendMessage(Component.text(String.format("[Page %d/2 - /moblimiter help #]", num)));
}
// --------------------------------------------------------------------------------------------
/**
* Command to count entities in the chunk and radius
*/
private void countCommand(CommandSender sender) {
if (!sender.hasPermission("moblimiter.count"))
return;
if (!(sender instanceof Player)) {
sender.sendMessage("Console can't do that.");
return;
}
Player player = (Player) sender;
Chunk playerChunk = player.getLocation().getChunk();
int chunkRadius = plugin.getConfiguration().getRadius();
// Count mobs in the chunk
HashMap<String, Integer> chunkCounts = EntityHelper.summarizeMobsInChunk(playerChunk);
// Count mobs in the radius
HashMap<String, Integer> radCounts = EntityHelper.summarizeMobsInRadius(playerChunk, chunkRadius);
// Print results
Component countMessage = LegacyComponentSerializer.legacyAmpersand().deserialize("&6Entities in chunk: &r");
sender.sendMessage(countMessager(chunkCounts, "&6Entities in chunk: &r"));
sender.sendMessage(countMessager(radCounts, "&6Entities in radius: &r"));
}
// --------------------------------------------------------------------------------------------
/**
* Outputs the number of mobs nearby
*
* @param map A hashmap of mob names and counts
* @param header
* @return
*/
private Component countMessager(HashMap<String, Integer> map, String header) {
Component countMessage = LegacyComponentSerializer.legacyAmpersand().deserialize(header);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
countMessage = countMessage
.append(Component.text("&f" + entry.getKey().toLowerCase() + ": &7" + entry.getValue() + " "));
}
return countMessage;
}
// --------------------------------------------------------------------------------------------
/**
* Command to display the configured limits
*/
private void limitsCommand(CommandSender sender, String[] args) {
if (!sender.hasPermission("moblimiter.limits"))
return;
List<String> lines = new ArrayList<>();
ConfiguredDefaults d = plugin.getConfiguration().getDefaults();
String values = String.format("Age: %d Max: %d Chunk: %d Cull: %d", d.getAge(), d.getMax(), d.getChunkMax(),
d.getCull());
lines.add(String.format("%s%s %s[%s]", "&9", "DEFAULT", "&e", values));
TreeMap<String, ConfiguredMob> limits = new TreeMap<>(plugin.getConfiguration().getAllLimits());
for (ConfiguredMob l : limits.values()) {
values = String.format("Age: %d Max: %d Chunk: %d Cull: %d", l.getAge(), l.getMax(), l.getChunkMax(),
l.getCull());
lines.add(String.format("%s%s %s[%s]", "&6", l.getKey(), "&e", values));
}
int page = 1;
if (args.length > 1) {
try {
page = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
page = 1;
}
}
if (lines.size() > 0) {
int offset = (page - 1) * 10;
int pages = ((lines.size() - 1) / 10) + 1;
for (int i = offset; i <= offset + 9; i++) {
if (i >= lines.size())
break;
sender.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(lines.get(i)));
}
sender.sendMessage(LegacyComponentSerializer.legacyAmpersand()
.deserialize(String.format("%s[Page %d/%d]", "&7", page, pages)));
} else {
sender.sendMessage(Component
.text("There are no limits configured. All mob types will fall back" + " to the default block.")
.color(TextColor.fromHexString("#FF5555")));
}
}
// --------------------------------------------------------------------------------------------
/**
* Command to inspect details about the mob the player is looking at
*/
private void checkCommand(CommandSender sender) {
if (!sender.hasPermission("moblimiter.check"))
return;
if (!(sender instanceof Player)) {
sender.sendMessage("Console can't do that.");
return;
}
Player player = (Player) sender;
LivingEntity entity = EntityHelper.getMobInLineOfSight(player);
if (entity == null || entity.isDead() || !EntityHelper.isLimitableMob(entity)) {
sender.sendMessage("No mob in sight");
return;
}
ConfiguredMob limits = plugin.getConfiguration().getLimits(entity);
Chunk chunk = entity.getLocation().getChunk();
int chunkRadius = plugin.getConfiguration().getRadius();
HashMap<String, Integer> chunkCounts = EntityHelper.summarizeMobsInChunk(chunk);
HashMap<String, Integer> radCounts = EntityHelper.summarizeMobsInRadius(chunk, chunkRadius);
int nearby = radCounts.get(limits.getKey());
int inChunk = chunkCounts.get(limits.getKey());
boolean isSpecial = EntityHelper.isSpecialMob(entity);
boolean isCullable = !entity.isDead() && (entity instanceof Animals || entity instanceof Monster);
boolean canSpawnChunk = (limits.getChunkMax() > inChunk || limits.getChunkMax() < 0);
boolean canSpawnNearby = (limits.getMax() > nearby || limits.getMax() < 0);
boolean moreCanSpawn = canSpawnChunk && canSpawnNearby;
sender.sendMessage(Component.text("---").color(TextColor.fromHexString("#FFAA00")));
sender.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(String
.format("&7 (%d/%d nearby, %d/%d in chunk)", nearby, limits.getMax(), inChunk, limits.getChunkMax())));
String ageStr;
if (limits.getAge() > -1) {
if (!EntityHelper.isBreedingPair(entity)) {
ageStr = String.format("%d/%d", plugin.getAgeLimiter().adjustedAge(entity), limits.getAge());
} else {
ageStr = "Breeding Pair Exempted";
}
} else {
ageStr = "Not Limited";
}
String chunkCullStr = (limits.getCull() > -1 && isCullable) ? String.format("%d", limits.getCull())
: "Unlimited";
sender.sendMessage(
LegacyComponentSerializer.legacyAmpersand().deserialize(String.format("Age: %s%s", "&7", ageStr)));
sender.sendMessage(LegacyComponentSerializer.legacyAmpersand()
.deserialize(String.format("Is Special: %s%b", "&7", isSpecial)));
sender.sendMessage(LegacyComponentSerializer.legacyAmpersand()
.deserialize(String.format("Chunk Unload Cap: %s%s", "&7", chunkCullStr)));
sender.sendMessage(LegacyComponentSerializer.legacyAmpersand()
.deserialize(String.format("More Can Spawn: %s%b", "&7", moreCanSpawn)));
sender.sendMessage(Component.text("---").color(TextColor.fromHexString("#FFAA00")));
}
}
@@ -0,0 +1,400 @@
package nu.nerd.moblimiter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import nu.nerd.moblimiter.configuration.Configuration;
import nu.nerd.moblimiter.configuration.ConfiguredMob;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.*;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.*;
/**
* Mob-related methods that may be shared across classes
*/
public class EntityHelper {
private static final HashSet<EntityType> BLACKLIST = new HashSet<>(
Arrays.asList(EntityType.ARMOR_STAND, EntityType.PLAYER));
/**
* Check if this is a mob that should ever be limited. Merely checking Animals
* or Monster subclassing is not sufficient due to Bukkit inconsistency. e.g.
* Armor Stands are LivingEntities, apparently. So to check whether something is
* a limitable mob, we check if it's a LivingEntity first and then apply a
* blacklist of edge cases such as Armor Stands.
*
* @param entity The entity to check
* @return true if the entity is a limitable mob
*/
public static boolean isLimitableMob(Entity entity) {
return entity instanceof LivingEntity && !BLACKLIST.contains(entity.getType());
}
/**
* Check if this is a "special mob" that shouldn't be removed in any
* circumstance
*
* @param entity entity to check
* @return true if this is a special mob
*/
public static boolean isSpecialMob(LivingEntity entity) {
Configuration cfg = getConfiguration();
// Keep mobs with custom names
if (entity.customName() != null) {
return true;
}
// Don't remove tamed mobs (unless limit_tamed is enabled)
if (!cfg.limitTamed() && entity instanceof Tameable) {
Tameable tameable = (Tameable) entity;
if (tameable.isTamed()) {
return true;
}
}
// Save the sponge! (unless limit_elder_guardian is enabled)
if (!cfg.limitElderGuardian() && entity.getType() == EntityType.ELDER_GUARDIAN) {
return true;
}
// Equipped mobs (armor or hand items) are handled specially. When
// limit_armed is true (default), armed mobs are culled and this
// overrides respect_persistence — picking up an item sets vanilla
// PersistenceRequired, which is the exact abuse path the toggle closes.
boolean armed = hasEquippedItem(entity);
if (armed) {
return !cfg.limitArmed();
}
// Honor vanilla PersistenceRequired for unarmed mobs: spawn-egg,
// /summon, trader llamas, etc. Natural-spawn mobs are not persistent,
// so farm cycling is unaffected.
if (cfg.respectPersistence() && entity.isPersistent()) {
return true;
}
return false;
}
/**
* True if the mob has any non-AIR item in armor slots, main hand, or off hand.
*/
private static boolean hasEquippedItem(LivingEntity entity) {
EntityEquipment equipment = entity.getEquipment();
if (equipment == null) {
return false;
}
for (ItemStack armor : equipment.getArmorContents()) {
if (armor != null && armor.getType() != Material.AIR) {
return true;
}
}
ItemStack main = equipment.getItemInMainHand();
if (main != null && main.getType() != Material.AIR) {
return true;
}
ItemStack off = equipment.getItemInOffHand();
if (off != null && off.getType() != Material.AIR) {
return true;
}
return false;
}
/**
* True if the mob has any enchanted item in armor slots, main hand, or off
* hand. Used to grant a longer age limit via enchanted_age_multiplier.
*/
public static boolean hasEnchantedItem(LivingEntity entity) {
EntityEquipment equipment = entity.getEquipment();
if (equipment == null) {
return false;
}
for (ItemStack armor : equipment.getArmorContents()) {
if (isEnchanted(armor)) {
return true;
}
}
return isEnchanted(equipment.getItemInMainHand()) || isEnchanted(equipment.getItemInOffHand());
}
private static boolean isEnchanted(ItemStack item) {
if (item == null || item.getType() == Material.AIR) {
return false;
}
return !item.getEnchantments().isEmpty();
}
/**
* If this is an Animal and there are two or less in the chunk, don't remove
* them. This protects breeding pairs for farm animals.
*
* @param entity the entity to check
* @return false if the entity doesn't match the criteria
*/
public static boolean isBreedingPair(Entity entity) {
if (!(entity instanceof Animals))
return false;
if (entity instanceof Tameable)
return false;
int count = 0;
ConfiguredMob mob = getConfiguration().getLimits(entity);
for (Entity e : entity.getLocation().getChunk().getEntities()) {
ConfiguredMob m = getConfiguration().getLimits(e);
if (m.getKey().equals(mob.getKey()) && !e.isDead()) {
count++;
}
}
return count < 3;
}
/**
* Return details about an entity for debugging
*
* @param entity the entity
* @return String representation of the mob name and location
*/
public static String getMobDescription(Entity entity) {
String type = entity.getType().toString();
String world = entity.getLocation().getWorld().getName();
Location loc = entity.getLocation();
return String.format("%s at (%s,%d,%d,%d)", type, world, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
}
/**
* Return the mob a player is looking at
*
* @param player The player to check
* @return null or LivingEntity
*/
public static LivingEntity getMobInLineOfSight(Player player) {
List<Entity> entities = player.getNearbyEntities(5, 1, 5);
Iterator<Entity> iterator = entities.iterator();
while (iterator.hasNext()) {
Entity ent = iterator.next();
if (!(ent instanceof LivingEntity))
iterator.remove();
}
Set<Material> nullSet = null;
for (Block block : player.getLineOfSight(nullSet, 6)) {
if (block.getType() != Material.AIR)
break; // view is obstructed
for (Entity ent : entities) {
Vector b = block.getLocation().toVector();
Vector head = ent.getLocation().toVector().add(new Vector(0, 1, 0));
Vector foot = ent.getLocation().toVector();
if (head.isInSphere(b, 1.25) || foot.isInSphere(b, 1.25)) {
return (LivingEntity) ent;
}
}
}
return null;
}
/**
* Build a HashMap of entity types and their counts in a chunk
*
* @param chunk The chunk to check
* @return A HashMap summarizing the entity breakdown of a chunk
*/
public static HashMap<String, Integer> summarizeMobsInChunk(Chunk chunk) {
HashMap<String, Integer> chunkCounts = new HashMap<String, Integer>();
for (Entity e : chunk.getEntities()) {
if (e.isDead() || !isLimitableMob(e))
continue;
ConfiguredMob mob = getConfiguration().getLimits(e);
if (chunkCounts.containsKey(mob.getKey())) {
int count = chunkCounts.get(mob.getKey()) + 1;
chunkCounts.put(mob.getKey(), count);
} else {
chunkCounts.put(mob.getKey(), 1);
}
}
return chunkCounts;
}
/**
* Build a HashMap of entity types and their counts in a chunk radius
*
* @param start The center of the chunk radius
* @param radius The radius of chunks to check
* @return A HashMap summarizing the entity breakdown within a chunk radius
*/
public static HashMap<String, Integer> summarizeMobsInRadius(Chunk start, int radius) {
HashMap<String, Integer> radCounts = new HashMap<String, Integer>();
World world = start.getWorld();
for (int x = start.getX() - radius; x <= start.getX() + radius; x++) {
for (int z = start.getZ() - radius; z <= start.getZ() + radius; z++) {
Chunk c = world.getChunkAt(x, z);
for (Entity e : c.getEntities()) {
if (e.isDead() || !isLimitableMob(e))
continue;
ConfiguredMob mob = getConfiguration().getLimits(e);
if (radCounts.containsKey(mob.getKey())) {
int count = radCounts.get(mob.getKey()) + 1;
radCounts.put(mob.getKey(), count);
} else {
radCounts.put(mob.getKey(), 1);
}
}
}
}
return radCounts;
}
/**
* Convenience method to get the plugin configuration
*/
public static Configuration getConfiguration() {
return MobLimiter.instance.getConfiguration();
}
/**
* Log/broadcast a despawn event per configuration. Console log gated by debug;
* player message gated by despawn_notify_players and moblimiter.notify
* permission.
*
* @param entity the entity that was despawned
* @param reason short reason string (e.g. "age limit", "chunk cull", "spawn cap
* chunk")
*/
public static void notifyDespawn(Entity entity, String reason) {
Configuration cfg = getConfiguration();
if (!cfg.debug() && !cfg.despawnNotifyPlayers()) {
return;
}
Location loc = entity.getLocation();
String mob = entity.getType().toString();
String world = loc.getWorld() != null ? loc.getWorld().getName() : "unknown";
String raw = cfg.getDespawnNotifyMessage().replace("%mob%", mob).replace("%reason%", reason)
.replace("%world%", world).replace("%x%", String.valueOf(loc.getBlockX()))
.replace("%y%", String.valueOf(loc.getBlockY())).replace("%z%", String.valueOf(loc.getBlockZ()));
Component component = LegacyComponentSerializer.legacyAmpersand().deserialize(raw);
if (cfg.debug()) {
String plain = PlainTextComponentSerializer.plainText().serialize(component);
MobLimiter.instance.getLogger().info(plain);
}
if (cfg.despawnNotifyPlayers()) {
for (Player p : Bukkit.getOnlinePlayers()) {
if (p.hasPermission("moblimiter.notify")) {
p.sendMessage(component);
}
}
}
}
}
@@ -2,52 +2,56 @@ package nu.nerd.moblimiter;
import nu.nerd.moblimiter.configuration.Configuration;
import nu.nerd.moblimiter.limiters.AgeLimiter;
import nu.nerd.moblimiter.limiters.ChunkUnloadLimiter;
import nu.nerd.moblimiter.limiters.EntityUnloadLimiter;
import nu.nerd.moblimiter.limiters.SpawnLimiter;
import nu.nerd.moblimiter.logblock.LogBlockWrapper;
import nu.nerd.moblimiter.listeners.ClickEvents;
import nu.nerd.moblimiter.listeners.EntityEvents;
import org.bukkit.Location;
import org.bukkit.plugin.java.JavaPlugin;
public class MobLimiter extends JavaPlugin {
public static MobLimiter instance;
private Configuration configuration;
private ChunkUnloadLimiter chunkUnloadLimiter;
private EntityUnloadLimiter chunkUnloadLimiter;
private AgeLimiter ageLimiter;
private LogBlockWrapper logBlock;
public void onEnable() {
MobLimiter.instance = this;
configuration = new Configuration();
chunkUnloadLimiter = new ChunkUnloadLimiter();
chunkUnloadLimiter = new EntityUnloadLimiter();
ageLimiter = new AgeLimiter();
logBlock = new LogBlockWrapper();
new SpawnLimiter();
new BreedingListener();
new ClickEvents();
new EntityEvents();
new CommandHandler();
}
}
public void onDisable() {
chunkUnloadLimiter.removeAllMobs();
}
chunkUnloadLimiter.removeAllMobs();
}
public Configuration getConfiguration() {
return configuration;
}
return configuration;
}
public AgeLimiter getAgeLimiter() {
return ageLimiter;
}
public static String locationToString(Location location) {
return String.format("X: %.2f Y: %.2f Z: %.2f World: %s", location.getX(), location.getY(), location.getZ(),
location.getWorld().getName());
public LogBlockWrapper getLogBlock() {
return logBlock;
}
}
@@ -1,17 +1,18 @@
package nu.nerd.moblimiter.configuration;
import nu.nerd.moblimiter.MobLimiter;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Sheep;
import javax.naming.ConfigurationException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class Configuration {
private MobLimiter plugin;
private int radius;
private boolean debug;
@@ -19,16 +20,24 @@ public class Configuration {
private int growthTicks;
private boolean logBlock;
private boolean relativeAge;
private boolean limitTamed;
private boolean limitElderGuardian;
private boolean respectPersistence;
private boolean limitArmed;
private double enchantedAgeMultiplier;
private boolean despawnNotifyPlayers;
private String despawnNotifyMessage;
private ConfiguredDefaults defaults;
private HashMap<String, ConfiguredMob> limits;
private List<Material> spawnEggs = new ArrayList<>();
public Configuration() {
plugin = MobLimiter.instance;
plugin.saveDefaultConfig();
load();
}
}
/**
* Reload the configuration from disk
@@ -43,106 +52,224 @@ public class Configuration {
this.defaults = new ConfiguredDefaults(plugin.getConfig());
this.logBlock = plugin.getConfig().getBoolean("logblock", false);
this.relativeAge = plugin.getConfig().getBoolean("relative_age", false);
this.limitTamed = plugin.getConfig().getBoolean("limit_tamed", false);
this.limitElderGuardian = plugin.getConfig().getBoolean("limit_elder_guardian", false);
this.respectPersistence = plugin.getConfig().getBoolean("respect_persistence", true);
this.limitArmed = plugin.getConfig().getBoolean("limit_armed", true);
this.enchantedAgeMultiplier = plugin.getConfig().getDouble("enchanted_age_multiplier", 2.0);
this.despawnNotifyPlayers = plugin.getConfig().getBoolean("despawn_notify_players", false);
this.despawnNotifyMessage = plugin.getConfig().getString("despawn_notify_message",
"&7[MobLimiter] Despawned &e%mob%&7 (%reason%) at &f%world% %x%,%y%,%z%");
spawnEggs = new ArrayList<>();
this.limits = new HashMap<String, ConfiguredMob>();
ConfigurationSection mobLimits = plugin.getConfig().getConfigurationSection("limits");
if (mobLimits != null) {
for (String key : mobLimits.getKeys(false)) {
try {
ConfigurationSection l = mobLimits.getConfigurationSection(key);
ConfiguredMob mob;
if (l != null) {
mob = new ConfiguredMob(l, defaults);
} else {
mob = new ConfiguredMob(key, defaults); //use default values for YAML "key: []" blocks
mob = new ConfiguredMob(key, defaults); // use default values for YAML "key: []" blocks
}
limits.put(key.toUpperCase(), mob);
} catch (ConfigurationException ex) {
plugin.getLogger().warning(ex.getMessage());
}
}
}
for (String eggName : plugin.getConfig().getStringList("spawn_eggs")) {
Material material = Material.matchMaterial(eggName);
if (material == null || !material.name().endsWith("_SPAWN_EGG")) {
plugin.getLogger().warning("Ignoring unsupported spawn egg material for this server version: " + eggName);
continue;
}
spawnEggs.add(material);
}
}
/**
* Get the "view distance," in chunks, to check for mobs when a new mob spawns.
*/
public int getRadius() {
return radius;
}
return radius;
}
/**
* Is debug mode on?
*/
public boolean debug() {
return debug;
}
return debug;
}
/**
* Ticks until a farm animal is ready to breed again.
*/
public int getBreedingTicks() {
return breedingTicks;
}
return breedingTicks;
}
/**
* Ticks until a farm animal grows up.
*/
public int getGrowthTicks() {
return growthTicks;
}
return growthTicks;
}
/**
* Whether support for LogBlock entity removal logging is enabled
*
* @return true if enabled
*/
public boolean logBlockEnabled() {
return logBlock;
}
return logBlock;
}
/**
* Whether mobs' age should be relative to the last time they target a player
*
* @return true if enabled
*/
public boolean relativeAgeEnabled() {
return relativeAge;
}
/** Include tamed mobs in limits. */
public boolean limitTamed() {
return limitTamed;
}
/** Include elder guardians in limits. */
public boolean limitElderGuardian() {
return limitElderGuardian;
}
/** Honor vanilla PersistenceRequired (spawn eggs, /summon, picked-up items). */
public boolean respectPersistence() {
return respectPersistence;
}
/**
* Cull mobs carrying armor or hand items. Overrides respect_persistence for
* armed mobs.
*/
public boolean limitArmed() {
return limitArmed;
}
/** Age-limit multiplier for mobs holding or wearing an enchanted item. */
public double getEnchantedAgeMultiplier() {
return enchantedAgeMultiplier;
}
/** Notify online players holding moblimiter.notify on despawn. */
public boolean despawnNotifyPlayers() {
return despawnNotifyPlayers;
}
/** Raw message template with &-color codes and placeholders. */
public String getDespawnNotifyMessage() {
return despawnNotifyMessage;
}
/**
* Get the global default limits
*/
public ConfiguredDefaults getDefaults() {
return defaults;
}
return defaults;
}
/**
* Get the limits for a specific mob type, gracefully falling back to values from the "default" block
* Gets a list of spawn eggs as defined in the config
*
* @return A list of spawn eggs as Strings
*/
public List<Material> getSpawnEggs() {
return spawnEggs;
}
/**
* Get the limits for a specific mob type, gracefully falling back to values
* from the "default" block
*/
public ConfiguredMob getLimits(Entity entity) {
String key = entity.getType().toString();
if (entity instanceof Sheep) {
key = key + "_" + ((Sheep) entity).getColor().name().toUpperCase();
}
if (limits.containsKey(key)) {
return limits.get(key);
} else {
return new ConfiguredMob(key, defaults);
}
}
key = key + "_" + ((Sheep) entity).getColor().name().toUpperCase();
}
if (limits.containsKey(key)) {
return limits.get(key);
} else {
return new ConfiguredMob(key, defaults);
}
}
public HashMap<String, ConfiguredMob> getAllLimits() {
return limits;
}
}
@@ -3,68 +3,74 @@ package nu.nerd.moblimiter.configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import javax.naming.ConfigurationException;
/**
* Represents global default limits that apply to any mob type, unless a ConfiguredMob overrides.
* Represents global default limits that apply to any mob type, unless a
* ConfiguredMob overrides.
*/
public class ConfiguredDefaults {
private int age;
private int max;
private int chunkMax;
private int cull;
public ConfiguredDefaults(FileConfiguration config) {
ConfigurationSection section = config.getConfigurationSection("defaults");
if (section != null) {
age = section.getInt("age", -1);
max = section.getInt("max", -1);
chunkMax = section.getInt("chunk_max", -1);
cull = section.getInt("cull", -1);
} else {
age = -1;
max = -1;
chunkMax = -1;
cull = -1;
}
}
}
}
/**
* The maximum age, in ticks, that this mob should live for before being removed
*/
public int getAge() {
return age;
}
return age;
}
/**
* The maximum number of this mob type that should be allowed to exist within a
* view distance centered on a spawning mob.
*/
public int getMax() {
return max;
}
return max;
}
/**
* The maximum number of this mob type that should be allowed to exist in a single chunk.
* The maximum number of this mob type that should be allowed to exist in a
* single chunk.
*/
public int getChunkMax() {
return chunkMax;
}
return chunkMax;
}
/**
* The number of mobs that should be left after a chunk unload cull
*/
public int getCull() {
return cull;
}
}
@@ -1,19 +1,15 @@
package nu.nerd.moblimiter.configuration;
import nu.nerd.moblimiter.MobLimiter;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.EntityType;
import javax.naming.ConfigurationException;
/**
* Represents configured limits for a specific mob type
*/
public class ConfiguredMob {
private String key;
private EntityType type;
private int age;
@@ -21,102 +17,125 @@ public class ConfiguredMob {
private int chunkMax;
private int cull;
/**
* Construct a ConfiguredMob from the YAML configuration. Unspecified values will fall back to the values
* defined in the default block.
* @param mob The configuration section
* Construct a ConfiguredMob from the YAML configuration. Unspecified values
* will fall back to the values defined in the default block.
*
* @param mob The configuration section
* @param defaults The default values to fall back to
* @throws ConfigurationException
*/
public ConfiguredMob(ConfigurationSection mob, ConfiguredDefaults defaults) throws ConfigurationException{
public ConfiguredMob(ConfigurationSection mob, ConfiguredDefaults defaults) throws ConfigurationException {
try {
String name;
if (mob.getName().toUpperCase().startsWith("SHEEP_")) {
name = "SHEEP";
} else {
name = mob.getName();
}
key = mob.getName().toUpperCase();
type = EntityType.valueOf(name.toUpperCase());
type = resolveEntityType(mob.getName());
age = mob.getInt("age", defaults.getAge());
max = mob.getInt("max", defaults.getMax());
chunkMax = mob.getInt("chunk_max", defaults.getChunkMax());
cull = mob.getInt("cull", defaults.getCull());
} catch (Exception ex) {
throw new ConfigurationException("Invalid configuration for mob type: " + mob.getName());
}
}
/**
* Create a dummy ConfiguredMob with default values, for when one isn't actually configured
* @param mobKey The key this mob type would have in the config
* Create a dummy ConfiguredMob with default values, for when one isn't actually
* configured
*
* @param mobKey The key this mob type would have in the config
* @param defaults The default values
*/
public ConfiguredMob(String mobKey, ConfiguredDefaults defaults) {
String name;
if (mobKey.toUpperCase().startsWith("SHEEP_")) {
name = "SHEEP";
} else {
name = mobKey.toUpperCase();
}
key = mobKey.toUpperCase();
type = EntityType.valueOf(name.toUpperCase());
type = resolveEntityType(mobKey);
age = defaults.getAge();
max = defaults.getMax();
chunkMax = defaults.getChunkMax();
cull = defaults.getCull();
}
}
/**
* Get the key identifying this mob classification
*/
public String getKey() {
return key;
}
return key;
}
/**
* The Bukkit EntityType these limits apply to
*/
public EntityType getType() {
return type;
}
return type;
}
/**
* The maximum age, in ticks, that this mob should live for before being removed
*/
public int getAge() {
return age;
}
return age;
}
/**
* The maximum number of this mob type that should be allowed to exist within a
* view distance centered on a spawning mob.
*/
public int getMax() {
return max;
}
return max;
}
/**
* The maximum number of this mob type that should be allowed to exist in a single chunk.
* The maximum number of this mob type that should be allowed to exist in a
* single chunk.
*/
public int getChunkMax() {
return chunkMax;
}
return chunkMax;
}
/**
* The number of mobs that should be left after a chunk unload cull
*/
public int getCull() {
return cull;
}
/**
* Resolve config keys to current Bukkit entity enum names.
*/
private static EntityType resolveEntityType(String mobKey) {
String name = mobKey.toUpperCase();
if (name.startsWith("SHEEP_")) {
name = "SHEEP";
} else if (name.equals("PIG_ZOMBIE") || name.equals("ZOMBIE_PIGMAN")) {
name = "ZOMBIFIED_PIGLIN";
}
return EntityType.valueOf(name);
}
}
@@ -13,115 +13,158 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Sweep loaded chunks every 30 seconds and remove applicable mobs over their type's age limit
* Sweep loaded chunks every 30 seconds and remove applicable mobs over their
* type's age limit
*/
public class AgeLimiter extends BukkitRunnable implements Listener {
private MobLimiter plugin;
private int removed;
private Map<UUID, Integer> lastTargeted;
public AgeLimiter() {
plugin = MobLimiter.instance;
lastTargeted = new HashMap<UUID, Integer>();
this.runTaskTimer(plugin, 600L, 600L); //sweep every 30 seconds
}
this.runTaskTimer(plugin, 600L, 600L); // sweep every 30 seconds
}
/**
* Call the sweep on every loaded chunk in every world
*/
public void run() {
removed = 0;
for (World world: plugin.getServer().getWorlds()) {
for (Chunk chunk : world.getLoadedChunks()) {
sweepChunk(chunk);
}
}
if (plugin.getConfiguration().debug()) {
plugin.getLogger().info(String.format("Age limit sweep removed %d entities", removed));
}
}
removed = 0;
for (World world : plugin.getServer().getWorlds()) {
for (Chunk chunk : world.getLoadedChunks()) {
sweepChunk(chunk);
}
}
if (plugin.getConfiguration().debug()) {
plugin.getLogger().info(String.format("Age limit sweep removed %d entities", removed));
}
}
/**
* Sweep the chunk and remove mobs that are past their shelf life
*
* @param chunk the chunk to check
*/
private void sweepChunk(Chunk chunk) {
for (Entity entity : chunk.getEntities()) {
// Constrain entities to be removed to limitable mobs, excluding villagers
if (entity.isDead() || !EntityHelper.isLimitableMob(entity) || entity instanceof Villager) continue;
if (entity.isDead() || !EntityHelper.isLimitableMob(entity) || entity instanceof Villager)
continue;
// Exempt special mobs
if (EntityHelper.isSpecialMob((LivingEntity) entity)) {
if (plugin.getConfiguration().debug()) {
plugin.getLogger().info("Special mob exempted from removal: " + EntityHelper.getMobDescription(entity));
plugin.getLogger()
.info("Special mob exempted from removal: " + EntityHelper.getMobDescription(entity));
}
continue;
}
// Leave two of any farm animals
if (EntityHelper.isBreedingPair(entity)) continue;
if (EntityHelper.isBreedingPair(entity))
continue;
// If relative ages are on, and the mob is targeting a player, don't remove it
if (isTargetingPlayer(entity)) continue;
if (isTargetingPlayer(entity))
continue;
// Remove mobs
// Remove mobs. Enchanted-item mobs get a multiplied age threshold.
ConfiguredMob limits = plugin.getConfiguration().getLimits(entity);
if (!entity.isDead() && adjustedAge(entity) > limits.getAge() && limits.getAge() > -1) {
int ageLimit = limits.getAge();
if (ageLimit > -1 && EntityHelper.hasEnchantedItem((LivingEntity) entity)) {
ageLimit = (int) (ageLimit * plugin.getConfiguration().getEnchantedAgeMultiplier());
}
if (!entity.isDead() && adjustedAge(entity) > ageLimit && ageLimit > -1) {
EntityHelper.notifyDespawn(entity, "age limit");
((LivingEntity) entity).damage(1000); // Kill the entity and drop its items
removed++;
lastTargeted.remove(entity.getUniqueId());
plugin.getLogBlock().logEntityRemoval(entity, 347);
if (plugin.getConfiguration().debug()) {
plugin.getLogger().info("Removed mob (age limit): " + EntityHelper.getMobDescription(entity));
}
}
}
}
/**
* Don't let the sweep kill entities currently targeting a player.
* Also, keep resetting the relative age, since EntityTargetEvent
* only fires when the target changes.
* Don't let the sweep kill entities currently targeting a player. Also, keep
* resetting the relative age, since EntityTargetEvent only fires when the
* target changes.
*
* @param entity the entity to check
* @return true if the Entity is a Creature type and is targeting a player.
*/
private boolean isTargetingPlayer(Entity entity) {
if (!plugin.getConfiguration().relativeAgeEnabled()) return false;
if (!plugin.getConfiguration().relativeAgeEnabled())
return false;
if (entity instanceof Creature) {
Creature creature = (Creature) entity;
if (creature.getTarget() != null && creature.getTarget().getType().equals(EntityType.PLAYER)) {
lastTargeted.put(entity.getUniqueId(), entity.getTicksLived());
return true;
}
}
return false;
}
/**
* Returns the adjusted age of a mob.
* If relative age is on, and the mob recently tageted a player, the delta ticks will be returned.
* Otherwise, the regular getTicksLived() will be used.
* Returns the adjusted age of a mob. If relative age is on, and the mob
* recently tageted a player, the delta ticks will be returned. Otherwise, the
* regular getTicksLived() will be used.
*
* @param entity the entity to check
* @return the age in ticks
*/
public int adjustedAge(Entity entity) {
if (lastTargeted.containsKey(entity.getUniqueId())) {
return entity.getTicksLived() - lastTargeted.get(entity.getUniqueId());
} else {
return entity.getTicksLived();
}
}
}
@@ -7,54 +7,65 @@ import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.event.world.EntitiesUnloadEvent;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Cull applicable mobs on chunk unload.
* This is based on behavior from the original MobLimiter 1.x.
* Cull applicable mobs on chunk unload. This is based on behavior from the
* original MobLimiter 1.x.
*/
public class ChunkUnloadLimiter implements Listener {
public class EntityUnloadLimiter implements Listener {
private MobLimiter plugin;
private Chunk chunk;
public EntityUnloadLimiter() {
public ChunkUnloadLimiter() {
plugin = MobLimiter.instance;
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@EventHandler
public void onEntitiesUnloaded(EntitiesUnloadEvent event) {
removeMobs(event.getEntities());
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onChunkUnload(final ChunkUnloadEvent event) {
removeMobs(event.getChunk());
}
/**
* Remove excess mobs in a chunk, if chunk unload culling is enabled for the mob type.
* This is the default behavior from MobLimiter 1.x.
* @param chunk The chunk to cull mobs in
* Remove excess mobs in a chunk, if chunk unload culling is enabled for the mob
* type. This is the default behavior from MobLimiter 1.x.
*
* @param entities The list of entities being unloaded
*/
private void removeMobs(Chunk chunk) {
private void removeMobs(List<Entity> entities) {
Map<String, Integer> count = new HashMap<String, Integer>();
for (Entity entity : chunk.getEntities()) {
for (Entity entity : entities) {
// Constrain entities to be removed to limitable mobs, excluding villagers
if (entity.isDead() || !EntityHelper.isLimitableMob(entity) || entity instanceof Villager) continue;
if (entity.isDead() || !EntityHelper.isLimitableMob(entity) || entity instanceof Villager)
continue;
// Exempt special mobs
if (EntityHelper.isSpecialMob((LivingEntity) entity)) {
if (plugin.getConfiguration().debug()) {
plugin.getLogger().info("Special mob exempted from removal: " + EntityHelper.getMobDescription(entity));
plugin.getLogger()
.info("Special mob exempted from removal: " + EntityHelper.getMobDescription(entity));
}
continue;
}
ConfiguredMob limits = plugin.getConfiguration().getLimits(entity);
@@ -66,27 +77,36 @@ public class ChunkUnloadLimiter implements Listener {
count.put(key, mobCount);
if (cap > -1 && mobCount > cap) {
plugin.getLogBlock().logEntityRemoval(entity, 283);
EntityHelper.notifyDespawn(entity, "chunk unload cull");
entity.remove();
if (plugin.getConfiguration().debug()) {
plugin.getLogger().info("Chunk unload removed: " + EntityHelper.getMobDescription(entity));
}
}
}
}
}
/**
* Run removeMobs() on all loaded chunks
*/
public void removeAllMobs() {
for (World world : plugin.getServer().getWorlds()) {
for (Chunk chunk : world.getLoadedChunks()) {
removeMobs(chunk);
removeMobs(Arrays.asList(chunk.getEntities()));
}
}
}
}
@@ -15,27 +15,25 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
import java.util.ArrayList;
import java.util.List;
/**
* Limit newly spawning mobs if there are too many
*/
public class SpawnLimiter implements Listener {
private MobLimiter plugin;
private List<SpawnReason> reasons;
public SpawnLimiter() {
plugin = MobLimiter.instance;
plugin.getServer().getPluginManager().registerEvents(this, plugin);
reasons = new ArrayList<SpawnReason>();
reasons = new ArrayList<>();
reasons.add(SpawnReason.BREEDING);
reasons.add(SpawnReason.DEFAULT);
reasons.add(SpawnReason.NATURAL);
reasons.add(SpawnReason.SPAWNER);
}
}
/**
* Handle mob limting on creature spawn
@@ -46,77 +44,104 @@ public class SpawnLimiter implements Listener {
SpawnReason reason = event.getSpawnReason();
ConfiguredMob limits = plugin.getConfiguration().getLimits(event.getEntity());
if (!reasons.contains(reason)) return;
if (!reasons.contains(reason))
return;
// Cancel spawn of mobs over the radius limit
if (countEntitiesInSpawnRadius(event.getEntity()) >= limits.getMax() && limits.getMax() > -1) {
log(event.getEntity(), reason, "radius", limits.getMax());
EntityHelper.notifyDespawn(event.getEntity(), "spawn cap radius");
event.getEntity().remove();
}
// Cancel spawn of mobs over the chunk limit
if (countEntitiesInChunk(event.getEntity()) >= limits.getChunkMax() && limits.getChunkMax() > -1) {
log(event.getEntity(), reason, "chunk", limits.getChunkMax());
EntityHelper.notifyDespawn(event.getEntity(), "spawn cap chunk");
event.getEntity().remove();
}
}
/**
* Count entities of the same type within a "view distance" in chunks from the original entity
* Count entities of the same type within a "view distance" in chunks from the
* original entity
*
* @param entity the entity to check
* @return number of matching entities
*/
private int countEntitiesInSpawnRadius(Entity entity) {
int count = 0;
int radius = plugin.getConfiguration().getRadius();
ConfiguredMob mob = plugin.getConfiguration().getLimits(entity);
World world = entity.getWorld();
Chunk start = entity.getLocation().getChunk();
for (int x = start.getX() - radius; x <= start.getX() + radius; x++) {
for (int z = start.getZ() - radius; z <= start.getZ() + radius; z++) {
Chunk c = world.getChunkAt(x, z);
for (Entity e : c.getEntities()) {
ConfiguredMob m = plugin.getConfiguration().getLimits(e);
if (m.getKey().equals(mob.getKey()) && !e.isDead()) {
count++;
}
}
}
}
return count;
}
count++;
}
}
}
}
return count;
}
/**
* Count entities of the same type in an individual chunk
*
* @param entity The entity to check
* @return number of matching entities
*/
private int countEntitiesInChunk(Entity entity) {
int count = 0;
ConfiguredMob mob = plugin.getConfiguration().getLimits(entity);
for (Entity e : entity.getLocation().getChunk().getEntities()) {
ConfiguredMob m = plugin.getConfiguration().getLimits(e);
if (m.getKey().equals(mob.getKey()) && !e.isDead()) {
count++;
}
}
return count;
}
count++;
}
}
return count;
}
/**
* Log entity removal for diagnostic purposes if debug mode is on
*/
private void log(Entity entity, SpawnReason reason, String capType, int cap) {
if (!plugin.getConfiguration().debug()) return;
if (!plugin.getConfiguration().debug())
return;
String mob = EntityHelper.getMobDescription(entity);
String details = String.format("[reason: %s, cap type: %s, cap: %d]", reason.toString(), capType, cap);
String msg = String.format("Cancelled spawn of %s %s", mob, details);
plugin.getLogger().info(msg);
}
}
@@ -0,0 +1,142 @@
package nu.nerd.moblimiter.listeners;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import nu.nerd.moblimiter.MobLimiter;
import org.bukkit.Material;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class ClickEvents implements Listener {
MobLimiter plugin;
private List<Material> spawnEggs;
public ClickEvents() {
plugin = MobLimiter.instance;
spawnEggs = MobLimiter.instance.getConfiguration().getSpawnEggs();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
// --------------------------------------------------------------------------------------------
/**
* Formerly part of LimitSpawnEggs Stops players from changing the mob of a
* spawner with spawn eggs
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent event) {
Player player = event.getPlayer();
if (player.hasPermission("moblimiter.spawners.bypass")) {
return;
}
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getClickedBlock().getType() == Material.SPAWNER
&& event.getItem() != null && spawnEggs.contains(event.getItem().getType()))
{
event.setCancelled(true);
player.sendMessage(Component.text("You don't have permission to edit spawners.")
.color(TextColor.fromHexString("#FF5555")));
}
}
// --------------------------------------------------------------------------------------------
/**
* Formerly part of KeepBabyMobs Handles the locking of baby mobs. Originally
* handled by KeepBabyMobs.
*/
@EventHandler
public void onPlayerInteractAtEntityEvent(PlayerInteractAtEntityEvent event) {
Entity entity = event.getRightClicked();
Player player = event.getPlayer();
ItemStack hand = player.getEquipment().getItemInMainHand();
if (hand.getType().equals(Material.NAME_TAG)) {
lockMobAge(entity, player, hand);
}
}
// --------------------------------------------------------------------------------------------
/**
* Locks the mob's age and makes it a baby forever
*
* @param entity The entity being age locked
* @param player The player initiating the age lock
* @param hand The item being used to lock the entity's age
*/
public void lockMobAge(Entity entity, Player player, ItemStack hand) {
// If the held item is a nametag
if (hand.getItemMeta().hasDisplayName()) {
// Tadpoles are checked separately due to a different handling of their growth.
if (entity.getType().equals(EntityType.TADPOLE)) {
((Tadpole) entity).setAgeLock(true);
logLock(player, entity);
// Check other mobs that can age and breed after.
} else if (entity instanceof Breedable) {
Breedable breedable = (Breedable) entity;
if (!breedable.isAdult()) {
breedable.setAgeLock(true);
if (!(breedable instanceof Horse)) {
breedable.setAge(Integer.MIN_VALUE);
}
logLock(player, breedable);
}
}
}
}
// --------------------------------------------------------------------------------------------
/**
* Logs any locked mobs to console.
*
* @param player The player locking the mob
* @param entity The entity being locked
*/
public void logLock(Player player, Entity entity) {
player.sendMessage(Component.text("That mob has now been age locked. How adorable!")
.color(TextColor.fromHexString("#FFAA00")));
MobLimiter.instance.getComponentLogger()
.info(Component.text(String.format("%s age locked %s named %s at %s", player.getName(),
entity.getType(), entity.customName(), MobLimiter.locationToString(entity.getLocation()))));
}
}
@@ -0,0 +1,169 @@
package nu.nerd.moblimiter.listeners;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import nu.nerd.moblimiter.MobLimiter;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDeathEvent;
public class EntityEvents implements Listener {
MobLimiter plugin;
public EntityEvents() {
plugin = MobLimiter.instance;
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
// --------------------------------------------------------------------------------------------
/**
* Formerly part of KeepBabyMobs. Logs the death of an entity.
*/
@EventHandler(priority = EventPriority.NORMAL)
public void onEntityDeath(EntityDeathEvent event) {
Entity entity = event.getEntity();
Player player = event.getEntity().getKiller();
if (player == null || !(entity instanceof Breedable)) {
if (entity instanceof Tadpole) {
logDeath(player, entity, "");
}
return;
}
Breedable breedable = (Breedable) entity;
if (breedable.getAgeLock()) {
String extrainfo = "";
if (entity instanceof Cat) {
extrainfo = "type: " + ((Cat) entity).getCatType().name();
}
if (entity instanceof Tameable) {
Tameable tameable = ((Tameable) entity);
if (tameable.isTamed()) {
extrainfo = "owner: " + ((Tameable) entity).getOwner().getName();
}
}
logDeath(player, breedable, extrainfo);
}
}
// --------------------------------------------------------------------------------------------
/**
* Outputs the death details of an age locked baby animal
*
* @param player The player who killed the animal
* @param entity The mob killed
* @param extrainfo Extra information like coat colour and owner
*/
public void logDeath(Player player, Entity entity, String extrainfo) {
MobLimiter.instance.getComponentLogger()
.info(Component.text(String.format("%s killed %s named %s at %s %s", player.getName(), entity.getType(),
PlainTextComponentSerializer.plainText().serialize(entity.customName()),
MobLimiter.locationToString(entity.getLocation()), extrainfo)));
}
// --------------------------------------------------------------------------------------------
/**
* Apply the breeding changes when a new animal is bred
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onCreatureSpawnEvent(final CreatureSpawnEvent event) {
CreatureSpawnEvent.SpawnReason reason = event.getSpawnReason();
if ((reason == CreatureSpawnEvent.SpawnReason.BREEDING || reason == CreatureSpawnEvent.SpawnReason.EGG
|| reason == CreatureSpawnEvent.SpawnReason.DISPENSE_EGG))
{
if (isFarmAnimal(event.getEntity())) {
applyBreedingChanges((Animals) event.getEntity());
for (Entity en : event.getEntity().getNearbyEntities(4, 4, 4)) {
if (isFarmAnimal(en)) {
applyBreedingChanges((Animals) en);
}
}
}
}
}
// --------------------------------------------------------------------------------------------
/**
* Controls the rate farm animals grow up and the length of their breeding
* cooldown. growthTicks: How many ticks until the animal becomes an adult.
* breedingTicks: How many ticks until the animal can breed again. A value of
* zero makes the condition instant, and -1 disables tampering with vanilla
* behavior. This functionality was called "agecapbaby" and "agecapbaby"
* respectively in MobLimiter 1.x.
*
* @param animal the animal to apply the changes to
*/
private void applyBreedingChanges(Animals animal) {
if (animal.getAgeLock())
return;
int growthTicks = plugin.getConfiguration().getGrowthTicks();
int breedingTicks = plugin.getConfiguration().getBreedingTicks();
if (growthTicks > -1 && !animal.isAdult()) {
animal.setAge(Math.max(animal.getAge(), -growthTicks));
} else if (breedingTicks > -1 && animal.isAdult()) {
animal.setAge(Math.min(animal.getAge(), breedingTicks));
}
}
// --------------------------------------------------------------------------------------------
/**
* Whether the entity in question is a farm animal or not
*
* @param entity the entity to check
* @return true if this is a farm animal
*/
private boolean isFarmAnimal(Entity entity) {
return (entity instanceof Animals) && !(entity instanceof Tameable);
}
}
+255
View File
@@ -0,0 +1,255 @@
# Spawn-radius check in chunks. 2 = 5x5 = 25 chunks around a spawn event.
# Smaller radius lets multiple nearby farms coexist without starving each other.
radius: 2
# Breeding cooldown / baby growth accelerators. -1 disables override (vanilla timings).
# Vanilla: breeding cooldown 5 min (6000t), baby growth 20 min (24000t).
breeding_ticks: -1
growth_ticks: -1
logblock: false
relative_age: false
debug: false
# Tamed mobs (pets, horses, etc.) are exempt from limits. 200-player servers
# accumulate lots of pets; leaving this false prevents silent pet losses.
limit_tamed: false
# Elder guardians are rare/unique.
limit_elder_guardian: true
# Honor vanilla PersistenceRequired. Spawn-egg / /summon / picked-up-item mobs
# survive limits, matching vanilla "never despawn" semantics. Natural-spawn
# (farm spawners, mob spawning) is unaffected — those are not persistent.
respect_persistence: true
# Cull mobs carrying armor or hand items. Closes the abuse path where players
# drop gear to mobs (which sets PersistenceRequired in vanilla) to keep them
# loaded indefinitely. When true this overrides respect_persistence for armed
# mobs. Named / tamed / elder guardian exemptions still apply.
limit_armed: true
# Multiplier applied to a mob's configured age limit when it is holding or
# wearing an enchanted item. Lets admins give rarer geared mobs more airtime
# without disabling limits. 1.0 = no extra time, 2.0 = double. Mobs with
# age: -1 are unaffected.
enchanted_age_multiplier: 2.0
# In-game broadcast to moblimiter.notify holders. At 200 players + stacked farms
# this spams chat heavily. Leave off; /moblimiter stats is enough.
despawn_notify_players: false
despawn_notify_message: "&7[&cMobLimiter&f-&4OG&7] Despawned &e%mob%&7 (%reason%) at &f%world% %x%,%y%,%z%"
# Defaults tuned for farm-friendly + stacker-friendly operation.
# ChunkCap-OG enforces the hard per-chunk total entity ceiling; these caps are
# the per-type soft caps that only trip on excessive single-type density.
defaults:
age: -1 # Passive/unspecified mobs do not age-despawn (vanilla)
max: 800 # Same-type cap across the 5x5 spawn radius
chunk_max: 200 # Same-type cap per chunk (stackers easily stay under)
cull: 12 # Keep this many per type per chunk on chunk unload
# Per-mob overrides. age in ticks (20t = 1s, 24000t = 20min).
# cull = count retained per chunk on unload cull; -1 = never cull.
limits:
bat:
cull: 8
blaze:
age: 36000
cull: 8
cave_spider:
age: 48000
cull: 8
chicken: []
cod: []
cow: []
creeper:
age: 48000
cull: 16
dolphin: []
ender_dragon: []
enderman:
age: 36000
cull: 8
endermite:
cull: 6
evoker: []
ghast:
cull: 4
giant:
cull: -1
guardian:
age: 24000
max: 600
chunk_max: 100
cull: 12
horse:
age: -1
illusioner: []
iron_golem: []
llama:
cull: 8
magma_cube:
age: 48000
cull: 12
mushroom_cow: []
ocelot:
cull: 8
parrot:
cull: 8
phantom: []
pig: []
zombified_piglin:
age: 36000
cull: 16
pufferfish: []
rabbit:
cull: 8
salmon: []
sheep_white:
cull: 8
sheep_orange:
cull: 8
sheep_magenta:
cull: 8
sheep_light_blue:
cull: 8
sheep_yellow:
cull: 8
sheep_lime:
cull: 8
sheep_pink:
cull: 8
sheep_silver:
cull: 8
sheep_cyan:
cull: 8
sheep_purple:
cull: 8
sheep_blue:
cull: 8
sheep_brown:
cull: 8
sheep_green:
cull: 8
sheep_red:
cull: 8
sheep_black:
cull: 8
shulker: []
silverfish:
cull: 6
skeleton:
age: 48000
cull: 16
slime:
age: 48000
cull: 12
snowman:
cull: -1
spider:
age: 48000
cull: 12
squid:
age: 24000
max: 400
chunk_max: 50
cull: 8
tropical_fish: []
turtle: []
vex: []
villager:
chunk_max: 40
age: -1
vindicator: []
witch:
age: 48000
cull: 12
wither:
cull: -1
wolf:
cull: 8
zombie:
age: 48000
cull: 16
spawn_eggs:
- 'ALLAY_SPAWN_EGG'
- 'AXOLOTL_SPAWN_EGG'
- 'BAT_SPAWN_EGG'
- 'BEE_SPAWN_EGG'
- 'BLAZE_SPAWN_EGG'
- 'CAT_SPAWN_EGG'
- 'CAMEL_SPAWN_EGG'
- 'CAVE_SPIDER_SPAWN_EGG'
- 'COD_SPAWN_EGG'
- 'COW_SPAWN_EGG'
- 'CREEPER_SPAWN_EGG'
- 'DOLPHIN_SPAWN_EGG'
- 'DONKEY_SPAWN_EGG'
- 'DROWNED_SPAWN_EGG'
- 'ELDER_GUARDIAN_SPAWN_EGG'
- 'ENDER_DRAGON_SPAWN_EGG'
- 'ENDERMAN_SPAWN_EGG'
- 'ENDERMITE_SPAWN_EGG'
- 'EVOKER_SPAWN_EGG'
- 'FOX_SPAWN_EGG'
- 'FROG_SPAWN_EGG'
- 'GHAST_SPAWN_EGG'
- 'GLOW_SQUID_SPAWN_EGG'
- 'GOAT_SPAWN_EGG'
- 'GUARDIAN_SPAWN_EGG'
- 'HOGLIN_SPAWN_EGG'
- 'HORSE_SPAWN_EGG'
- 'HUSK_SPAWN_EGG'
- 'IRON_GOLEM_SPAWN_EGG'
- 'LLAMA_SPAWN_EGG'
- 'MAGMA_CUBE_SPAWN_EGG'
- 'MOOSHROOM_SPAWN_EGG'
- 'MULE_SPAWN_EGG'
- 'OCELOT_SPAWN_EGG'
- 'PANDA_SPAWN_EGG'
- 'PARROT_SPAWN_EGG'
- 'PHANTOM_SPAWN_EGG'
- 'PIG_SPAWN_EGG'
- 'PIGLIN_SPAWN_EGG'
- 'PIGLIN_BRUTE_SPAWN_EGG'
- 'PILLAGER_SPAWN_EGG'
- 'POLAR_BEAR_SPAWN_EGG'
- 'PUFFERFISH_SPAWN_EGG'
- 'RABBIT_SPAWN_EGG'
- 'RAVAGER_SPAWN_EGG'
- 'SALMON_SPAWN_EGG'
- 'SHEEP_SPAWN_EGG'
- 'SHULKER_SPAWN_EGG'
- 'SILVERFISH_SPAWN_EGG'
- 'SKELETON_SPAWN_EGG'
- 'SKELETON_HORSE_SPAWN_EGG'
- 'SLIME_SPAWN_EGG'
- 'SNIFFER_SPAWN_EGG'
- 'SNOW_GOLEM_SPAWN_EGG'
- 'SPIDER_SPAWN_EGG'
- 'SQUID_SPAWN_EGG'
- 'STRAY_SPAWN_EGG'
- 'STRIDER_SPAWN_EGG'
- 'TADPOLE_SPAWN_EGG'
- 'TRADER_LLAMA_SPAWN_EGG'
- 'TROPICAL_FISH_SPAWN_EGG'
- 'TURTLE_SPAWN_EGG'
- 'VEX_SPAWN_EGG'
- 'VILLAGER_SPAWN_EGG'
- 'VINDICATOR_SPAWN_EGG'
- 'WANDERING_TRADER_SPAWN_EGG'
- 'WARDEN_SPAWN_EGG'
- 'WITCH_SPAWN_EGG'
- 'WITHER_SPAWN_EGG'
- 'WITHER_SKELETON_SPAWN_EGG'
- 'WOLF_SPAWN_EGG'
- 'ZOGLIN_SPAWN_EGG'
- 'ZOMBIE_SPAWN_EGG'
- 'ZOMBIE_HORSE_SPAWN_EGG'
- 'ZOMBIE_VILLAGER_SPAWN_EGG'
- 'ZOMBIFIED_PIGLIN_SPAWN_EGG'
+10 -1
View File
@@ -1,8 +1,9 @@
name: MobLimiter
version: ${project.version}
version: '${version}'
description: Control the mob population
authors: ["Travis Watkins", "redwall_hp"]
softdepend: ["LogBlock"]
api-version: '${apiVersion}'
main: nu.nerd.moblimiter.MobLimiter
@@ -15,9 +16,14 @@ permissions:
moblimiter.count: true
moblimiter.limits: true
moblimiter.check: true
moblimiter.spawners.bypass: true
moblimiter.notify: true
moblimiter.reload:
description: Access to reload command
default: op
moblimiter.notify:
description: Receive in-game despawn notifications
default: op
moblimiter.count:
description: Access to count command
default: op
@@ -27,6 +33,9 @@ permissions:
moblimiter.check:
description: Access to check command
default: op
moblimiter.spawners.bypass:
description: Able to replace the mob in a spawner
default: op
commands:
moblimiter:
@@ -1,77 +0,0 @@
package nu.nerd.moblimiter;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Tameable;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
/**
* Handle faster breeding
*/
public class BreedingListener implements Listener {
private MobLimiter plugin;
public BreedingListener() {
plugin = MobLimiter.instance;
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
/**
* Apply the breeding changes when a new animal is bred
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onCreatureSpawnEvent(final CreatureSpawnEvent event) {
CreatureSpawnEvent.SpawnReason reason = event.getSpawnReason();
if ((reason == SpawnReason.BREEDING || reason == SpawnReason.EGG || reason == SpawnReason.DISPENSE_EGG)) {
if (isFarmAnimal(event.getEntity())) {
applyBreedingChanges((Animals) event.getEntity());
for (Entity en : event.getEntity().getNearbyEntities(4, 4, 4)) {
if (isFarmAnimal(en)) {
applyBreedingChanges((Animals) en);
}
}
}
}
}
/**
* Controls the rate farm animals grow up and the length of their breeding cooldown.
* growthTicks: How many ticks until the animal becomes an adult.
* breedingTicks: How many ticks until the animal can breed again.
* A value of zero makes the condition instant, and -1 disables tampering with vanilla behavior.
* This functionality was called "agecapbaby" and "agecapbaby" respectively in MobLimiter 1.x.
* @param animal the animal to apply the changes to
*/
private void applyBreedingChanges(Animals animal) {
if (animal.getAgeLock()) return;
int growthTicks = plugin.getConfiguration().getGrowthTicks();
int breedingTicks = plugin.getConfiguration().getBreedingTicks();
if (growthTicks > -1 && !animal.isAdult()) {
animal.setAge(Math.max(animal.getAge(), -growthTicks));
} else if (breedingTicks > -1 && animal.isAdult()) {
animal.setAge(Math.min(animal.getAge(), breedingTicks));
}
}
/**
* Whether the entity in question is a farm animal or not
* @param entity the entity to check
* @return true if this is a farm animal
*/
private boolean isFarmAnimal(Entity entity) {
return (entity instanceof Animals) && !(entity instanceof Tameable);
}
}
-292
View File
@@ -1,292 +0,0 @@
package nu.nerd.moblimiter;
import nu.nerd.moblimiter.configuration.ConfiguredDefaults;
import nu.nerd.moblimiter.configuration.ConfiguredMob;
import org.bukkit.ChatColor;
import org.bukkit.Chunk;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.*;
import java.util.*;
public class CommandHandler implements CommandExecutor {
private MobLimiter plugin;
public CommandHandler() {
plugin = MobLimiter.instance;
plugin.getCommand("moblimiter").setExecutor(this);
}
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) {
reloadConfig(sender);
}
else if (args.length > 0 && args[0].equalsIgnoreCase("count")) {
countCommand(sender);
}
else if (args.length > 0 && args[0].equalsIgnoreCase("limits")) {
limitsCommand(sender, args);
}
else if (args.length > 0 && args[0].equalsIgnoreCase("check")) {
checkCommand(sender);
}
else if (args.length > 0 && args[0].equalsIgnoreCase("help")) {
helpText(sender, cmd, args);
}
else {
commandsList(sender);
}
return true;
}
/**
* Command to reload the configuration
*/
private void reloadConfig(CommandSender sender) {
if (!sender.hasPermission("moblimiter.reload")) return;
plugin.getConfiguration().load();
sender.sendMessage(ChatColor.GOLD + "MobLimiter config reloaded");
}
/**
* When a player runs /moblimiter with no arguments, list available commands
* and guide newbies to /moblimiter help
* @param sender
*/
private void commandsList(CommandSender sender) {
StringBuilder msg = new StringBuilder();
msg.append("&6&lMobLimiter: Available Commands\n");
msg.append("&6/moblimiter help&r - What is MobLimiter and how does it work?\n");
if (sender.hasPermission("moblimiter.limits")) {
msg.append("&6/moblimiter limits&r - List the limits for each mob type\n");
}
if (sender.hasPermission("moblimiter.count")) {
msg.append("&6/moblimiter count&r - Count entities in the current chunk and radius\n");
}
if (sender.hasPermission("moblimiter.check")) {
msg.append("&6/moblimiter check&r - Inspect limiting details for the mob you're looking at\n");
}
if (sender.hasPermission("moblimiter.reload")) {
msg.append("&6/moblimiter reload&r - Reload configuration from disk\n");
}
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', msg.toString()));
}
/**
* When a player runs /moblimiter help, print help text
*/
private void helpText(CommandSender sender, Command cmd, String[] args) {
int num = 1;
if (args.length > 1) {
try {
num = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
num = 1;
}
}
if (num == 1) {
StringBuilder msg = new StringBuilder();
msg.append(ChatColor.GOLD);
msg.append("This server runs a plugin called " + ChatColor.YELLOW + "MobLimiter"
+ ChatColor.GOLD + " in order to manage the "
+ "amount of mobs on the server, as too many mobs creates lag which affects "
+ "everyone. MobLimiter uses several methods to manage the mob population, depending on how" +
" it is configured:\n");
msg.append(ChatColor.YELLOW);
msg.append("1. Chunk unload culling\n");
msg.append(ChatColor.GOLD);
msg.append("Mobs are culled down to a defined maximum whenever a chunk unloads or the server restarts.\n");
msg.append(ChatColor.YELLOW);
msg.append("2. Real-time spawn limiting\n");
msg.append(ChatColor.GOLD);
msg.append("Mobs are tracked in real-time and new spawns are prevented in an area if there are more than a " +
"certain amount of that mob type there already.\n");
sender.sendMessage(msg.toString());
}
if (num == 2) {
StringBuilder msg = new StringBuilder();
msg.append(ChatColor.YELLOW);
msg.append("3. Age limiting\n");
msg.append(ChatColor.GOLD);
msg.append("Mobs automatically die when they reach a certain age, dropping their items when they do. " +
"e.g. a skeleton may live for 15 minutes before dying.\n");
msg.append("In all cases, \"special\" mobs are always protected and will not be removed by the plugin. This " +
"includes mobs with custom names, tamed animals, and mobs wearing armor.\n");
msg.append("To make up for the culling of passive farm animals, the plugin shortens the growth rate of " +
"baby animals and the waiting time in between breeding events, to approximately ");
msg.append(ChatColor.GRAY);
msg.append(plugin.getConfiguration().getGrowthTicks() / 20);
msg.append(ChatColor.GOLD);
msg.append(" and ");
msg.append(ChatColor.GRAY);
msg.append(plugin.getConfiguration().getBreedingTicks() / 20);
msg.append(ChatColor.GOLD);
msg.append(" seconds respectively.");
sender.sendMessage(msg.toString());
}
sender.sendMessage(String.format("[Page %d/2 - /moblimiter help #]", num));
}
/**
* Command to count entities in the chunk and radius
*/
private void countCommand(CommandSender sender) {
if (!sender.hasPermission("moblimiter.count")) return;
if (!(sender instanceof Player)) {
sender.sendMessage("Console can't do that.");
return;
}
Player player = (Player) sender;
Chunk playerChunk = player.getLocation().getChunk();
int chunkRadius = plugin.getConfiguration().getRadius();
// Count mobs in the chunk
HashMap<String, Integer> chunkCounts = EntityHelper.summarizeMobsInChunk(playerChunk);
// Count mobs in the radius
HashMap<String, Integer> radCounts = EntityHelper.summarizeMobsInRadius(playerChunk, chunkRadius);
// Print results
StringBuilder sb = new StringBuilder(ChatColor.GOLD + "Entities in chunk: ");
for (Map.Entry<String, Integer> entry : chunkCounts.entrySet()) {
sb.append(ChatColor.WHITE);
sb.append(entry.getKey().toLowerCase());
sb.append(": ");
sb.append(ChatColor.GRAY);
sb.append(entry.getValue());
sb.append(" ");
}
sender.sendMessage(sb.toString());
sb = new StringBuilder(ChatColor.GOLD + "Entities in radius: ");
for (Map.Entry<String, Integer> entry : radCounts.entrySet()) {
sb.append(ChatColor.WHITE);
sb.append(entry.getKey().toLowerCase());
sb.append(": ");
sb.append(ChatColor.GRAY);
sb.append(entry.getValue());
sb.append(" ");
}
sender.sendMessage(sb.toString());
}
/**
* Command to display the configured limits
*/
private void limitsCommand(CommandSender sender, String[] args) {
if (!sender.hasPermission("moblimiter.limits")) return;
List<String> lines = new ArrayList<String>();
ConfiguredDefaults d = plugin.getConfiguration().getDefaults();
String values = String.format("Age: %d Max: %d Chunk: %d Cull: %d", d.getAge(), d.getMax(), d.getChunkMax(), d.getCull());
lines.add(String.format("%s%s %s[%s]", ChatColor.GOLD, "DEFAULT", ChatColor.YELLOW, values));
TreeMap<String, ConfiguredMob> limits = new TreeMap<String, ConfiguredMob>(plugin.getConfiguration().getAllLimits());
for (ConfiguredMob l : limits.values()) {
values = String.format("Age: %d Max: %d Chunk: %d Cull: %d", l.getAge(), l.getMax(), l.getChunkMax(), l.getCull());
lines.add(String.format("%s%s %s[%s]", ChatColor.GOLD, l.getKey(), ChatColor.YELLOW, values));
}
int page = 1;
if (args.length > 1) {
try {
page = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
page = 1;
}
}
if (lines.size() > 0) {
int offset = (page - 1) * 10;
int pages = ((lines.size()-1) / 10) + 1;
for (int i = offset; i <= offset + 9; i++) {
if (i >= lines.size()) break;
sender.sendMessage(lines.get(i));
}
sender.sendMessage(String.format("%s[Page %d/%d]", ChatColor.GRAY, page, pages));
} else {
sender.sendMessage("There are no limits configured. All mob types will fall back to the default block.");
}
}
/**
* Command to inspect details about the mob the player is looking at
*/
private void checkCommand(CommandSender sender) {
if (!sender.hasPermission("moblimiter.check")) return;
if (!(sender instanceof Player)) {
sender.sendMessage("Console can't do that.");
return;
}
Player player = (Player) sender;
LivingEntity entity = EntityHelper.getMobInLineOfSight(player);
if (entity == null || entity.isDead() || !EntityHelper.isLimitableMob(entity)) {
sender.sendMessage("No mob in sight");
return;
}
ConfiguredMob limits = plugin.getConfiguration().getLimits(entity);
Chunk chunk = entity.getLocation().getChunk();
int chunkRadius = plugin.getConfiguration().getRadius();
HashMap<String, Integer> chunkCounts = EntityHelper.summarizeMobsInChunk(chunk);
HashMap<String, Integer> radCounts = EntityHelper.summarizeMobsInRadius(chunk, chunkRadius);
int nearby = radCounts.get(limits.getKey());
int inChunk = chunkCounts.get(limits.getKey());
boolean isSpecial = EntityHelper.isSpecialMob(entity);
boolean isCullable = !entity.isDead() && (entity instanceof Animals || entity instanceof Monster);
boolean canSpawnChunk = (limits.getChunkMax() > inChunk || limits.getChunkMax() < 0);
boolean canSpawnNearby = (limits.getMax() > nearby || limits.getMax() < 0);
boolean moreCanSpawn = canSpawnChunk && canSpawnNearby;
sender.sendMessage(ChatColor.GOLD + "---");
StringBuilder title = new StringBuilder(ChatColor.GOLD + limits.getKey());
title.append(ChatColor.GRAY);
title.append(String.format(" (%d/%d nearby, %d/%d in chunk)", nearby, limits.getMax(), inChunk, limits.getChunkMax()));
sender.sendMessage(title.toString());
String ageStr;
if (limits.getAge() > -1) {
if (!EntityHelper.isBreedingPair(entity)) {
ageStr = String.format("%d/%d", plugin.getAgeLimiter().adjustedAge(entity), limits.getAge());
} else {
ageStr = "Breeding Pair Exempted";
}
} else {
ageStr = "Not Limited";
}
String chunkCullStr = (limits.getCull() > -1 && isCullable) ? String.format("%d", limits.getCull()) : "Unlimited";
sender.sendMessage(String.format("Age: %s%s", ChatColor.GRAY, ageStr));
sender.sendMessage(String.format("Is Special: %s%b", ChatColor.GRAY, isSpecial));
sender.sendMessage(String.format("Chunk Unload Cap: %s%s", ChatColor.GRAY, chunkCullStr));
sender.sendMessage(String.format("More Can Spawn: %s%b", ChatColor.GRAY, moreCanSpawn));
sender.sendMessage(ChatColor.GOLD + "---");
}
}
-196
View File
@@ -1,196 +0,0 @@
package nu.nerd.moblimiter;
import nu.nerd.moblimiter.configuration.Configuration;
import nu.nerd.moblimiter.configuration.ConfiguredMob;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.*;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.*;
/**
* Mob-related methods that may be shared across classes
*/
public class EntityHelper {
/**
* Check if this is a mob that should ever be limited.
* Merely checking Animals or Monster subclassing is not sufficient due to Bukkit inconsistency.
* e.g. Armor Stands are LivingEntities, apparently.
* So to check whether something is a limitable mob, we check if it's a LivingEntity first and then
* apply a blacklist of edge cases such as Armor Stands.
* @param entity The entity to check
* @return true if the entity is a limitable mob
*/
public static boolean isLimitableMob(Entity entity) {
List<EntityType> blacklist = new ArrayList<EntityType>();
blacklist.add(EntityType.ARMOR_STAND);
blacklist.add(EntityType.PLAYER);
return entity instanceof LivingEntity && !blacklist.contains(entity.getType());
}
/**
* Check if this is a "special mob" that shouldn't be removed in any circumstance
* @param entity entity to check
* @return true if this is a special mob
*/
public static boolean isSpecialMob(LivingEntity entity) {
// Keep mobs with custom names
if (entity.getCustomName() != null) {
return true;
}
// Don't remove tamed mobs
if (entity instanceof Tameable) {
Tameable tameable = (Tameable) entity;
if (tameable.isTamed()) {
return true;
}
}
// Save the sponge!
if (entity.getType() == EntityType.GUARDIAN && ((Guardian)entity).isElder()) {
return true;
}
// Don't remove mobs that are holding something, which they may have picked up
EntityEquipment equipment = entity.getEquipment();
for (ItemStack armor : equipment.getArmorContents()) {
// Unarmored mobs, even animals, spawn with 1xAIR as armor.
if (armor != null && armor.getType() != Material.AIR) {
return true;
}
}
return false;
}
/**
* If this is an Animal and there are two or less in the chunk, don't remove them.
* This protects breeding pairs for farm animals.
* @param entity the entity to check
* @return false if the entity doesn't match the criteria
*/
public static boolean isBreedingPair(Entity entity) {
if (!(entity instanceof Animals)) return false;
if (entity instanceof Tameable) return false;
int count = 0;
ConfiguredMob mob = getConfiguration().getLimits(entity);
for (Entity e : entity.getLocation().getChunk().getEntities()) {
ConfiguredMob m = getConfiguration().getLimits(e);
if (m.getKey().equals(mob.getKey()) && !e.isDead()) {
count++;
}
}
return count < 3;
}
/**
* Return details about an entity for debugging
* @param entity the entity
* @return String representation of the mob name and location
*/
public static String getMobDescription(Entity entity) {
String type = entity.getType().toString();
String world = entity.getLocation().getWorld().getName();
Location loc = entity.getLocation();
return String.format("%s at (%s,%d,%d,%d)", type, world, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
}
/**
* Return the mob a player is looking at
* @param player The player to check
* @return null or LivingEntity
*/
public static LivingEntity getMobInLineOfSight(Player player) {
List<Entity> entities = player.getNearbyEntities(5, 1, 5);
Iterator<Entity> iterator = entities.iterator();
while (iterator.hasNext()) {
Entity ent = iterator.next();
if (!(ent instanceof LivingEntity)) iterator.remove();
}
Set<Material> nullSet = null;
for (Block block : player.getLineOfSight(nullSet, 6)) {
if (block.getType() != Material.AIR) break; //view is obstructed
for (Entity ent : entities) {
Vector b = block.getLocation().toVector();
Vector head = ent.getLocation().toVector().add(new Vector(0, 1, 0));
Vector foot = ent.getLocation().toVector();
if (head.isInSphere(b, 1.25) || foot.isInSphere(b, 1.25)) {
return (LivingEntity) ent;
}
}
}
return null;
}
/**
* Build a HashMap of entity types and their counts in a chunk
* @param chunk The chunk to check
* @return A HashMap summarizing the entity breakdown of a chunk
*/
public static HashMap<String, Integer> summarizeMobsInChunk(Chunk chunk) {
HashMap<String, Integer> chunkCounts = new HashMap<String, Integer>();
for (Entity e : chunk.getEntities()) {
if (e.isDead() || !isLimitableMob(e)) continue;
ConfiguredMob mob = getConfiguration().getLimits(e);
if (chunkCounts.containsKey(mob.getKey())) {
int count = chunkCounts.get(mob.getKey()) + 1;
chunkCounts.put(mob.getKey(), count);
} else {
chunkCounts.put(mob.getKey(), 1);
}
}
return chunkCounts;
}
/**
* Build a HashMap of entity types and their counts in a chunk radius
* @param start The center of the chunk radius
* @param radius The radius of chunks to check
* @return A HashMap summarizing the entity breakdown within a chunk radius
*/
public static HashMap<String, Integer> summarizeMobsInRadius(Chunk start, int radius) {
HashMap<String, Integer> radCounts = new HashMap<String, Integer>();
World world = start.getWorld();
for (int x = start.getX() - radius; x <= start.getX() + radius; x++) {
for (int z = start.getZ() - radius; z <= start.getZ() + radius; z++) {
Chunk c = world.getChunkAt(x, z);
for (Entity e : c.getEntities()) {
if (e.isDead() || !isLimitableMob(e)) continue;
ConfiguredMob mob = getConfiguration().getLimits(e);
if (radCounts.containsKey(mob.getKey())) {
int count = radCounts.get(mob.getKey()) + 1;
radCounts.put(mob.getKey(), count);
} else {
radCounts.put(mob.getKey(), 1);
}
}
}
}
return radCounts;
}
/**
* Convenience method to get the plugin configuration
*/
public static Configuration getConfiguration() {
return MobLimiter.instance.getConfiguration();
}
}
@@ -1,45 +0,0 @@
package nu.nerd.moblimiter.logblock;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.Consumer;
import de.diddiz.LogBlock.LogBlock;
import nu.nerd.moblimiter.MobLimiter;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
/**
* Handle communication with the LogBlock API.
* This will be used by LogBlockWrapper to safely avoid creating a hard dependency.
*/
public class LogBlockBackend {
private MobLimiter plugin;
private Consumer lbConsumer = null;
public LogBlockBackend() {
plugin = MobLimiter.instance;
Plugin plugin = Bukkit.getServer().getPluginManager().getPlugin("LogBlock");
if (plugin != null) lbConsumer = ((LogBlock) plugin).getConsumer();
}
/**
* Log an entity removal to LogBlock with the reason specified
* @param entity The entity being removed
* @param name The "name" LogBlock will store. e.g. MobLimiterCull
* @param itemId The item to ascribe the kill to. Zero for fist.
*/
public void logEntityRemoval(Entity entity, String name, int itemId) {
if (lbConsumer != null) {
Actor killer = new Actor(name);
Actor victim = Actor.actorFromEntity(entity);
lbConsumer.queueKill(entity.getLocation(), killer, victim, itemId);
}
}
}
@@ -1,45 +0,0 @@
package nu.nerd.moblimiter.logblock;
import nu.nerd.moblimiter.MobLimiter;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
/**
* Wraps LogBlock API calls so it can be a soft dependency
*/
public class LogBlockWrapper {
private MobLimiter plugin;
private LogBlockBackend logBlock = null;
public LogBlockWrapper() {
plugin = MobLimiter.instance;
if (plugin.getConfiguration().logBlockEnabled() && logBlockPresent()) {
logBlock = new LogBlockBackend();
}
}
/**
* Log an entity removal to LogBlock with the reason specified
* @param entity The entity being removed
* @param itemId The "weapon" used. Zero for first
*/
public void logEntityRemoval(Entity entity, int itemId) {
if (logBlock != null) {
logBlock.logEntityRemoval(entity, "MobLimiter", itemId);
}
}
private boolean logBlockPresent() {
Plugin plugin = Bukkit.getServer().getPluginManager().getPlugin("LogBlock");
return (plugin != null);
}
}
BIN
View File
Binary file not shown.