Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79b631698c | ||
|
|
dffe3fde66 | ||
|
|
feacc56bf2 | ||
|
|
682e47f731 | ||
|
|
9a480ec6e6 | ||
|
|
c8fc5ea4ed | ||
|
|
d1629ac8a5 | ||
|
|
6ea3862e62 | ||
|
|
ac49c287b0 | ||
|
|
a603e025b1 | ||
|
|
f18306cc57 | ||
|
|
912e7a1269 | ||
|
|
c53d5fbea2 | ||
|
|
ff28b09fd9 | ||
|
|
5c3882cee5 | ||
|
|
27cf4d8951 | ||
|
|
a5c6270e5c | ||
|
|
d88ba025c1 | ||
|
|
41bd5016e8 | ||
|
|
1ee6292027 | ||
|
|
3fd7236086 | ||
|
|
b634b33a54 | ||
|
|
61f769bd17 | ||
|
|
0d96454388 | ||
|
|
a5d5cef457 | ||
|
|
3709d5a072 | ||
|
|
c822bc3182 | ||
|
|
92bbfc9565 | ||
|
|
1ac1f11b26 | ||
|
|
2673f521dd | ||
|
|
c5f410ca4a | ||
|
|
81ddf25ee9 | ||
|
|
85c046cc55 | ||
|
|
0fac0edcb4 | ||
|
|
220459f5b7 | ||
|
|
a845051047 | ||
|
|
7f220834a1 | ||
|
|
a80d2df85c | ||
|
|
192596e755 | ||
|
|
d5edf101c0 | ||
|
|
8007bb77dd | ||
|
|
a85e14e14e | ||
|
|
9331ade285 |
No files matched your search
@@ -1,6 +1,7 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2012 Travis Watkins <amaranth@probablycoding.com>
|
||||
Version 1.x: Copyright (c) 2012 Travis Watkins <amaranth@probablycoding.com>
|
||||
Version 2.x: Copyright (c) 2016 redwall_hp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,49 +1,130 @@
|
||||
MobLimiter
|
||||
===========
|
||||
|
||||
Version 2 of MobLimiter, featuring three configurable Limiter engines that control the mob population in different ways.
|
||||
|
||||
While the original version of MobLimiter was primarily designed to cull mobs on chunk unload, with limited support to
|
||||
prevent new spawns in real time, MobLimiter 2 is designed from the ground up to be more proactive about managing the
|
||||
spawning and removal of entities. The Limiters are:
|
||||
|
||||
* **Age:** Mobs can be configured to have a maximum lifespan (in ticks) that results in the entity being killed and
|
||||
dropping its items after the limit is reached. ("Special" mobs are exempt.) MobLimiter will try to not kill breeding
|
||||
pairs of farm animals if it can, by not killing farm animals if there would be less than two in the chunk.
|
||||
|
||||
* **Spawning:** MobLimiter can be configured to limit the spawning of new mobs in real time, checking the number of the
|
||||
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.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
### Mob caps
|
||||
### General Settings
|
||||
|
||||
The config.yml is made up of lines of `mob: limit`
|
||||
* `radius`: The "view distance" to check for mobs, as a chunk radius (e.g. 3 would be a 7x7 area)
|
||||
* `breeding_ticks`: Farm animal breeding cooldown in ticks (-1 to disable)
|
||||
* `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
|
||||
|
||||
**Mob** is the bukkit name for the entity. For example *cave spider* is displayed as *cave_spider* in the configuration
|
||||
|
||||
To limit a mobs numbers on restart/unload we define it like so:
|
||||
### Default Limits
|
||||
|
||||
zombie: 4
|
||||
creeper: 10
|
||||
The `defaults` block defines limits that will globally apply to any mob type that doesn't have an explicit override
|
||||
defined in the `limits` block. (Undefined values fall back to `-1`, for disabled.) Specific mob limits *inherit* the
|
||||
default block, with any defined fields overriding the value from `defaults`.
|
||||
|
||||
The name of the **entity** followed by a valid **int** after a **:** is placed on a single line
|
||||
```
|
||||
defaults:
|
||||
age: 18000 #15 minutes in ticks
|
||||
max: 200 #200 in "view distance"
|
||||
chunk_max: 50 #50 in a single chunk
|
||||
cull: 4 #cull mobs down to this maximum on chunk unload
|
||||
```
|
||||
|
||||
Any mobs not given numbers in the config will be ignored and no limiting will happen to that specific mob type. However, most mobs have default limits configured in the config.yml file built into the plugin. You can explicitly specify unlimited numbers of a particular mob using a negative number. For example:
|
||||
* `age`: Enable age limiting and remove the mob after a number of ticks. (e.g. 18000 for 15 minutes)
|
||||
* `max`: The maximum number of a mob type to be allowed to spawn in a "view distance" defined by `radius`.
|
||||
* `chunk_max`: The maximum number of a mob type to be allowed to spawn in a single chunk.
|
||||
* `cull`: If set to a value other than `-1`, the number of mobs to *not* be removed on chunk unload.
|
||||
|
||||
ocelot: -1
|
||||
wolf: -1
|
||||
|
||||
*Please note, leaving emtpy lines in the config is a bad idea, don't do this, it won't crash or corrupt the config, it just makes things messy*
|
||||
### Individual Mob Limits
|
||||
|
||||
### Farm animal breeding caps
|
||||
The `limits` block allows you to specify limits that apply to individual mob types. These inherit the values defined in
|
||||
`defaults`, overriding the values.
|
||||
|
||||
There are two additional options that can be specified in config.yml: `agecapbaby` and `agecapbreed`. These specify how long a farm animal should remain a baby and the adult breeding cooldown, respectively.
|
||||
Mob types are named using their Bukkit EntityType string, with the exception of sheep, which are addressed in the form of `sheep_white` or `sheep_red` so they can be handled individually for farming purposes.
|
||||
|
||||
These are measured in ticks. A tick is at most 1/20th of a second, possibly longer depending on server load. The following example sets both values to 300 ticks (15+ seconds):
|
||||
```
|
||||
limits:
|
||||
skeleton:
|
||||
max: 100
|
||||
chunkMax: 30
|
||||
age: 12000
|
||||
cow:
|
||||
chunkMax: 75
|
||||
age: 12000
|
||||
horse:
|
||||
age: -1
|
||||
villager:
|
||||
max: 200
|
||||
chunkMax: 50
|
||||
age: -1
|
||||
```
|
||||
|
||||
agecapbaby: 300
|
||||
agecapbreed: 300
|
||||
|
||||
If the number of ticks is 0, baby farm animals will instantly become adults, and adults will have no breeding cooldown. If the number of ticks is less than 0, farm animal breeding is left to vanilla settings.
|
||||
### Farm Animal Breeding Tweaks
|
||||
|
||||
Other breedable entities (ocelots, wolves, villagers) are unaffected by these options.
|
||||
The `breeding_ticks` and `growth_ticks` fields define how many ticks a farm animal will remain a baby and the breeding
|
||||
cooldown, respectively. If your server is running at a full 20 ticks per second, a value of 400 for each would make
|
||||
the respective values approximately 20 seconds.
|
||||
|
||||
Running
|
||||
-------
|
||||
If the value is set to zero, there will be no delay and the condition will be instantaneous. A value of -1 will disable
|
||||
tampering with vanilla breeding behavior.
|
||||
|
||||
The running of MobLimiter happens on **chunk unload** as well as **plugin disable** events.
|
||||
This function only affects farm animals, and ignores other breedable entities like ocelots, wolves and villagers.
|
||||
|
||||
This means when players navigate away from an area or log out with nobody nearby, the limits will be enforced to those chunks unloaded at that time.
|
||||
|
||||
It is also worth noting that limiting will happen on server shutdown **as well as reloads** due to the need for *onDisable* hooks.
|
||||
### Special Mobs
|
||||
|
||||
MobLimiter will not remove any mobs that are deemed to be "special" in some way that may make their removal undesirable.
|
||||
|
||||
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.)
|
||||
|
||||
* Any mob that is holding an item, as it may have picked up a player's equipment.
|
||||
|
||||
|
||||
### LogBlock Integration
|
||||
|
||||
If LogBlock is running on the server, you can enable LogBlock integration by setting the `logblock` field to true in the
|
||||
config file. When enabled, mob removals will be tracked as kills in LogBlock when MobLimiter performs a chunk unload
|
||||
cull or age limit kill.
|
||||
|
||||
Age limit kills are logged with a weapon of `watch` and chunk unload culling uses `gold sword`, both using a "player"
|
||||
name of `MobLimiter`.
|
||||
|
||||
|
||||
### Commands
|
||||
|
||||
* `/moblimiter` — Lists all subcommands. Available to all users.
|
||||
|
||||
* `/moblimiter help` — Prints a description of what MobLimiter does. Available to all users.
|
||||
|
||||
* `/moblimiter reload` — Reload the plugin configuration. Requires `moblimiter.reload`.
|
||||
|
||||
* `/moblimiter count` — Count all living entities in your chunk and view radius. Requires `moblimiter.count`.
|
||||
|
||||
* `/moblimiter limits` — Print all configured limits. Requires `moblimiter.limits`.
|
||||
|
||||
* `/moblimiter check` — Inspect the mob you're looking at, printing its age, limits and statuses. Requires `moblimiter.check`.
|
||||
|
||||
All commands can be accessed with the `moblimiter.*` permission node.
|
||||
|
||||
Any issues or bugs feel free to create an issue via the [github issues](https://github.com/NerdNu/MobLimiter/issues) tracker.
|
||||
+121
-47
@@ -1,47 +1,121 @@
|
||||
sheepwhite: 4
|
||||
sheeporange: 2
|
||||
sheepmagenta: 2
|
||||
sheeplight_blue: 2
|
||||
sheepyellow: 2
|
||||
sheeplime: 2
|
||||
sheeppink: 2
|
||||
sheepgray: 2
|
||||
sheepsilver: 2
|
||||
sheepcyan: 2
|
||||
sheeppurple: 2
|
||||
sheepblue: 2
|
||||
sheepbrown: 2
|
||||
sheepgreen: 2
|
||||
sheepred: 2
|
||||
sheepblack: 2
|
||||
chicken: 4
|
||||
cow: 4
|
||||
mushroom_cow: 4
|
||||
ocelot: 4
|
||||
pig: 4
|
||||
wolf: 4
|
||||
rabbit: 4
|
||||
blaze: 4
|
||||
cave_spider: 4
|
||||
creeper: 4
|
||||
enderman: 4
|
||||
guardian: 4
|
||||
ghast: 1
|
||||
pig_zombie: 4
|
||||
silverfish: 6
|
||||
skeleton: 4
|
||||
spider: 4
|
||||
zombie: 1
|
||||
agecapbaby: 300
|
||||
agecapbreed: 300
|
||||
settings:
|
||||
debug:
|
||||
limit_spawn: false
|
||||
limits: false
|
||||
chunk_unload: false
|
||||
age_cap: false
|
||||
special: false
|
||||
limit_natural_spawn: false
|
||||
limit_spawner_spawn: false
|
||||
spawner_limited: []
|
||||
natural_limited: []
|
||||
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
|
||||
+26
-13
@@ -1,20 +1,33 @@
|
||||
name: MobLimiter
|
||||
version: ${project.version}
|
||||
description: ${project.description}
|
||||
|
||||
author: Travis Watkins
|
||||
website: ${project.url}
|
||||
description: Control the mob population
|
||||
authors: ["Travis Watkins", "redwall_hp"]
|
||||
softdepend: ["LogBlock"]
|
||||
|
||||
main: nu.nerd.moblimiter.MobLimiter
|
||||
|
||||
commands:
|
||||
moblimiter:
|
||||
description: Gives information about MobLimiter
|
||||
permissions:
|
||||
moblimit.cull:
|
||||
description: Access to cull command
|
||||
default: op
|
||||
moblimiter.check:
|
||||
description: Check if a mob is special
|
||||
default: op
|
||||
moblimiter.*:
|
||||
description: Access all commands
|
||||
default: op
|
||||
children:
|
||||
moblimiter.reload: true
|
||||
moblimiter.count: true
|
||||
moblimiter.limits: true
|
||||
moblimiter.check: true
|
||||
moblimiter.reload:
|
||||
description: Access to reload command
|
||||
default: op
|
||||
moblimiter.count:
|
||||
description: Access to count command
|
||||
default: op
|
||||
moblimiter.limits:
|
||||
description: Access to limits command
|
||||
default: op
|
||||
moblimiter.check:
|
||||
description: Access to check command
|
||||
default: op
|
||||
|
||||
commands:
|
||||
moblimiter:
|
||||
description: Gives information about MobLimiter
|
||||
@@ -1,61 +1,49 @@
|
||||
<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">
|
||||
<?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>1.1.8</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>MobLimiter</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<version>2.0.1</version>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.8-R0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>bukkit</artifactId>
|
||||
<version>1.8-R0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<distributionManagement>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>repobo-rel</id>
|
||||
<name>repo.bukkit.org Releases</name>
|
||||
<url>http://repo.bukkit.org/content/repositories/releases/</url>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>repobo-snap</id>
|
||||
<name>repo.bukkit.org Snapshots</name>
|
||||
<url>http://repo.bukkit.org/content/repositories/snapshots/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
<id>md_5-releases</id>
|
||||
<url>http://repo.md-5.net/content/repositories/releases/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</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>
|
||||
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>bukkit-plugins</id>
|
||||
<url>http://repo.bukkit.org/content/groups/public</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
<description>Limit mobs per chunk</description>
|
||||
<url>https://github.com/nerdnu/MobLimiter</url>
|
||||
<scm>
|
||||
<connection>scm:git:https://github.com/nerdnu/MobLimiter.git</connection>
|
||||
<developerConnection>scm:git:https://github.com/nerdnu/MobLimiter.git</developerConnection>
|
||||
<url>https://github.com/nerdnu/MobLimiter</url>
|
||||
</scm>
|
||||
<build>
|
||||
<sourceDirectory>${basedir}/src</sourceDirectory>
|
||||
<resources>
|
||||
@@ -66,34 +54,10 @@
|
||||
<includes>
|
||||
<include>plugin.yml</include>
|
||||
<include>config.yml</include>
|
||||
<include>LICENSE</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<defaultGoal>clean install</defaultGoal>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.1</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<addMavenDescriptor>false</addMavenDescriptor>
|
||||
</archive>
|
||||
<finalName>MobLimiter-v${project.version}</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.0.2</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,77 @@
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
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 + "---");
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
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,530 +1,53 @@
|
||||
package nu.nerd.moblimiter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
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.CreatureSpawnEvent.SpawnReason;
|
||||
import org.bukkit.event.world.ChunkUnloadEvent;
|
||||
import org.bukkit.inventory.EntityEquipment;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import nu.nerd.moblimiter.configuration.Configuration;
|
||||
import nu.nerd.moblimiter.limiters.AgeLimiter;
|
||||
import nu.nerd.moblimiter.limiters.ChunkUnloadLimiter;
|
||||
import nu.nerd.moblimiter.limiters.SpawnLimiter;
|
||||
import nu.nerd.moblimiter.logblock.LogBlockWrapper;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
public class MobLimiter extends JavaPlugin implements Listener {
|
||||
|
||||
/**
|
||||
* Map from lower case EntityType.name() to max number of entities of that
|
||||
* type. Note that EntityType.name() is different from EntityType.getName().
|
||||
* The former is MUSHROOM_COW while the latter is MushroomCow.
|
||||
*/
|
||||
protected Map<String, Integer> limits = new HashMap<String, Integer>();
|
||||
protected int ageCapBaby = -1;
|
||||
protected int ageCapBreed = -1;
|
||||
public class MobLimiter extends JavaPlugin {
|
||||
|
||||
/**
|
||||
* If true, per-chunk mob caps are enforced for non-farm-animal mobs when
|
||||
* they spawn, in addition to when the chunk is unloaded.
|
||||
*/
|
||||
protected boolean limitNaturalSpawn;
|
||||
|
||||
/**
|
||||
* If true, the mob cap applies even when the mob was spawned from a
|
||||
* mob-spawner.
|
||||
*/
|
||||
protected boolean limitSpawnerSpawn;
|
||||
public static MobLimiter instance;
|
||||
private Configuration configuration;
|
||||
private ChunkUnloadLimiter chunkUnloadLimiter;
|
||||
private AgeLimiter ageLimiter;
|
||||
private LogBlockWrapper logBlock;
|
||||
|
||||
/**
|
||||
* If true, cancelled spawns are shown in the server log.
|
||||
*/
|
||||
protected boolean debugLimitSpawn;
|
||||
|
||||
/**
|
||||
* If true, limits on numbers of each mob type are logged as the are parsed
|
||||
* from the configuration file.
|
||||
*/
|
||||
protected boolean debugLimits;
|
||||
|
||||
/**
|
||||
* If true, mobs removed during chunk unloading are logged.
|
||||
*/
|
||||
protected boolean debugChunkUnload;
|
||||
|
||||
/**
|
||||
* If true, do debug traces of the age cap code.
|
||||
*/
|
||||
protected boolean debugAgeCap;
|
||||
|
||||
/**
|
||||
* If true, do debug traces of preservation of special mobs on chunk unload.
|
||||
*/
|
||||
protected boolean debugSpecial;
|
||||
|
||||
/**
|
||||
* The set of Entity types whose caps are enforced when spawning naturally.
|
||||
*
|
||||
* Note that farm animals are explicitly excluded from that cap. Players can
|
||||
* breed them over the limit.
|
||||
*/
|
||||
protected HashMap<EntityType, Integer> naturalLimitedEntities = new HashMap<EntityType, Integer>();
|
||||
|
||||
/**
|
||||
* The set of Entity types whose caps are enforced when spawned by a mobspawner.
|
||||
*
|
||||
* Note that farm animals are explicitly excluded from that cap. Players can
|
||||
* breed them over the limit.
|
||||
*/
|
||||
protected HashMap<EntityType, Integer> spawnerLimitedEntities = new HashMap<EntityType, Integer>();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
|
||||
saveDefaultConfig();
|
||||
|
||||
limitNaturalSpawn = getConfig().getBoolean("settings.limit_natural_spawn");
|
||||
limitSpawnerSpawn = getConfig().getBoolean("settings.limit_spawner_spawn");
|
||||
debugLimitSpawn = getConfig().getBoolean("settings.debug.limit_spawn");
|
||||
debugLimits = getConfig().getBoolean("settings.debug.limits");
|
||||
debugChunkUnload = getConfig().getBoolean("settings.debug.chunk_unload");
|
||||
debugAgeCap = getConfig().getBoolean("settings.debug.age_cap");
|
||||
debugSpecial = getConfig().getBoolean("settings.debug.special");
|
||||
|
||||
for (Map.Entry<String, Object> entry : this.getConfig().getValues(false).entrySet()) {
|
||||
if (entry.getKey() != null && entry.getValue() != null && entry.getValue() instanceof Integer) {
|
||||
String name = entry.getKey().toLowerCase();
|
||||
int value = (Integer) entry.getValue();
|
||||
if (name.equals("agecapbaby")) {
|
||||
ageCapBaby = value;
|
||||
} else if (name.equals("agecapbreed")) {
|
||||
ageCapBreed = value;
|
||||
} else {
|
||||
// A negative limit allows unlimited numbers of that mob.
|
||||
if (value >= 0) {
|
||||
limits.put(name, value);
|
||||
if (debugLimits) {
|
||||
getLogger().info(name + " limit: " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getConfig().isConfigurationSection("settings.natural_limited")) {
|
||||
for (String entityTypeName : getConfig().getConfigurationSection("settings.natural_limited").getKeys(false)) {
|
||||
EntityType entityType = EntityType.valueOf(entityTypeName.toUpperCase());
|
||||
if (entityType == null) {
|
||||
getLogger().severe("Invalid entity type for natural spawn limiting: " + entityTypeName);
|
||||
} else {
|
||||
int count = getConfig().getInt(String.format("settings.natural_limited.%s", entityTypeName));
|
||||
naturalLimitedEntities.put(entityType, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getConfig().isConfigurationSection("settings.spawner_limited")) {
|
||||
for (String entityTypeName : getConfig().getConfigurationSection("settings.spawner_limited").getKeys(false)) {
|
||||
EntityType entityType = EntityType.valueOf(entityTypeName.toUpperCase());
|
||||
if (entityType == null) {
|
||||
getLogger().severe("Invalid entity type for natural mobspawner limiting: " + entityTypeName);
|
||||
} else {
|
||||
int count = getConfig().getInt(String.format("settings.spawner_limited.%s", entityTypeName));
|
||||
spawnerLimitedEntities.put(entityType, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.getServer().getPluginManager().registerEvents(this, this);
|
||||
|
||||
MobLimiter.instance = this;
|
||||
configuration = new Configuration();
|
||||
chunkUnloadLimiter = new ChunkUnloadLimiter();
|
||||
ageLimiter = new AgeLimiter();
|
||||
logBlock = new LogBlockWrapper();
|
||||
new SpawnLimiter();
|
||||
new BreedingListener();
|
||||
new CommandHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public void onDisable() {
|
||||
removeAllMobs();
|
||||
chunkUnloadLimiter.removeAllMobs();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onChunkUnload(final ChunkUnloadEvent e) {
|
||||
removeMobs(e.getChunk());
|
||||
|
||||
public Configuration getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void onCreatureSpawnEvent(final CreatureSpawnEvent event) {
|
||||
|
||||
SpawnReason reason = event.getSpawnReason();
|
||||
|
||||
if ((reason == SpawnReason.BREEDING || reason == SpawnReason.EGG || reason == SpawnReason.DISPENSE_EGG) && isFarmAnimal(event.getEntity())) {
|
||||
applyAgeCap((Animals) event.getEntity());
|
||||
for (Entity en : event.getEntity().getNearbyEntities(4, 4, 4)) {
|
||||
if (isFarmAnimal(en)) {
|
||||
applyAgeCap((Animals) en);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Villager breeding has a spawn reason of SpawnReason.DEFAULT
|
||||
// or SpawnReason.BREEDING depending on CraftBukkit version.
|
||||
boolean doLimitNaturalSpawn = limitNaturalSpawn &&
|
||||
(event.getSpawnReason() == SpawnReason.BREEDING ||
|
||||
event.getSpawnReason() == SpawnReason.NATURAL ||
|
||||
event.getSpawnReason() == SpawnReason.DEFAULT);
|
||||
boolean doLimitSpawnerSpawn = limitSpawnerSpawn && event.getSpawnReason() == SpawnReason.SPAWNER;
|
||||
int cap;
|
||||
if (doLimitNaturalSpawn && naturalLimitedEntities.containsKey(event.getEntityType())) {
|
||||
cap = naturalLimitedEntities.get(event.getEntityType());
|
||||
}
|
||||
else if (doLimitSpawnerSpawn && spawnerLimitedEntities.containsKey(event.getEntityType())) {
|
||||
cap = spawnerLimitedEntities.get(event.getEntityType());
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
int count = 0;
|
||||
for (Entity otherEntity : event.getLocation().getChunk().getEntities()) {
|
||||
if (otherEntity.getType() == event.getEntityType()) {
|
||||
++count;
|
||||
if (count >= cap) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count >= cap) {
|
||||
if (debugLimitSpawn) {
|
||||
getLogger().info("Cancel spawn of " + getMobDescription(event.getEntity()) +
|
||||
" (reason = " + event.getSpawnReason().name().toLowerCase() + ", cap = " + cap + ")");
|
||||
}
|
||||
|
||||
// See: https://github.com/NerdNu/NerdBugs/issues/180
|
||||
// Trying remove() instead since we already use that.
|
||||
event.getEntity().remove();
|
||||
}
|
||||
|
||||
public AgeLimiter getAgeLimiter() {
|
||||
return ageLimiter;
|
||||
}
|
||||
|
||||
public boolean isFarmAnimal(Entity en) {
|
||||
return (en instanceof Animals) && !(en instanceof Tameable);
|
||||
|
||||
public LogBlockWrapper getLogBlock() {
|
||||
return logBlock;
|
||||
}
|
||||
|
||||
public void applyAgeCap(Animals en) {
|
||||
if (en.getAgeLock() == true) {
|
||||
if (debugAgeCap) {
|
||||
getLogger().info("Age locked " + getMobDescription(en) + " with age " + en.getAge() + " was not age capped");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ageCapBaby >= 0 && !en.isAdult()) {
|
||||
en.setAge(Math.max(en.getAge(), -ageCapBaby));
|
||||
} else if (ageCapBreed >= 0 && en.isAdult()) {
|
||||
en.setAge(Math.min(en.getAge(), ageCapBreed));
|
||||
}
|
||||
if (debugAgeCap) {
|
||||
getLogger().info("Age of " + getMobDescription(en) + " capped to " + en.getAge());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a String describing an entity's type (including color for sheep)
|
||||
* and location.
|
||||
*
|
||||
* @param entity the entity.
|
||||
* @return the description, including location.
|
||||
*/
|
||||
public String getMobDescription(Entity entity) {
|
||||
String key = getCapKey(entity);
|
||||
Location loc = entity.getLocation();
|
||||
return String.format("%s at (%s,%d,%d,%d)", key,
|
||||
loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key string used to access the cap on a given mob type.
|
||||
*
|
||||
* For sheep, the color is considered to be part of the type. Each sheep
|
||||
* color has its own distinct cap.
|
||||
*
|
||||
* @param entity the entity.
|
||||
* @return the key used to access the limits map.
|
||||
*/
|
||||
public String getCapKey(Entity entity) {
|
||||
StringBuilder key = new StringBuilder(entity.getType().name());
|
||||
if (entity instanceof Sheep) {
|
||||
key.append(((Sheep) entity).getColor().name());
|
||||
}
|
||||
return key.toString().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the specified Entity has a cap on numbers.
|
||||
*
|
||||
* @param entity the entity.
|
||||
* @return true if the specified Entity has a cap on numbers.
|
||||
*/
|
||||
public boolean hasCap(Entity entity) {
|
||||
return getCap(getCapKey(entity)) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the per chunk limit on entities with the specified cap key, or -1
|
||||
* if uncapped.
|
||||
*
|
||||
* @param key the mob type as defined by getCapKey().
|
||||
* @return the per chunk cap for entities with that key, or -1.
|
||||
*/
|
||||
public int getCap(String key) {
|
||||
Integer cap = limits.get(key);
|
||||
return (cap != null) ? cap : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the specified LivingEntity is special, meaning it has been
|
||||
* named, tamed, or is wearing items in its armor slots.
|
||||
*
|
||||
* Such "special" mobs are exempted from being removed on chunk unload.
|
||||
* However, for hostiles, this is not a guarantee as the server can and will
|
||||
* despawn them without any event from Bukkit.
|
||||
*
|
||||
* @return true if the mob is special and should be exempt from limits.
|
||||
*/
|
||||
public boolean isSpecialMob(LivingEntity living) {
|
||||
if (living.getCustomName() != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (living instanceof Tameable) {
|
||||
Tameable tameable = (Tameable) living;
|
||||
if (tameable.isTamed()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (living.getType() == EntityType.GUARDIAN && ((Guardian)living).isElder()) {
|
||||
return true; //save the sponge!
|
||||
}
|
||||
|
||||
EntityEquipment equipment = living.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;
|
||||
}
|
||||
|
||||
public void removeMobs(Chunk chunk) {
|
||||
Map<String, Integer> count = new HashMap<String, Integer>();
|
||||
for (Entity entity : chunk.getEntities()) {
|
||||
if (!entity.isDead() && (entity instanceof Animals || entity instanceof Monster)) {
|
||||
// Exempt "special mobs" from removal.
|
||||
// Animals and Monsters are both LivingEntitys so this cast
|
||||
// succeeds.
|
||||
boolean special = isSpecialMob((LivingEntity) entity);
|
||||
String key = getCapKey(entity);
|
||||
if (special) {
|
||||
if (debugSpecial) {
|
||||
getLogger().info("Special mob exempted from removal: " + getMobDescription(entity));
|
||||
}
|
||||
} else {
|
||||
Integer oldCount = count.get(key);
|
||||
int mobCount = (oldCount == null) ? 1 : oldCount + 1;
|
||||
count.put(key, mobCount);
|
||||
|
||||
int cap = getCap(key);
|
||||
if (cap >= 0 && mobCount > cap) {
|
||||
entity.remove();
|
||||
if (debugChunkUnload) {
|
||||
getLogger().info("Chunk unload removes " + getMobDescription(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeAllMobs() {
|
||||
for (Chunk c : getServer().getWorlds().get(0).getLoadedChunks()) {
|
||||
removeMobs(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param player
|
||||
* @return
|
||||
*/
|
||||
public static Entity getTarget(final Player player) {
|
||||
assert player != null;
|
||||
Entity target = null;
|
||||
double targetDistanceSquared = 0;
|
||||
final double radiusSquared = 1;
|
||||
final Vector l = player.getEyeLocation().toVector(), n = player.getLocation().getDirection().normalize();
|
||||
final double cos45 = Math.cos(Math.PI / 4);
|
||||
|
||||
for (final LivingEntity other : player.getWorld().getEntitiesByClass(LivingEntity.class)) {
|
||||
if (other == player)
|
||||
continue;
|
||||
|
||||
if (target == null || targetDistanceSquared > other.getLocation().distanceSquared(player.getLocation())) {
|
||||
final Vector t = other.getLocation().add(0, 1, 0).toVector().subtract(l);
|
||||
if (n.clone().crossProduct(t).lengthSquared() < radiusSquared && t.normalize().dot(n) >= cos45) {
|
||||
target = other;
|
||||
targetDistanceSquared = target.getLocation().distanceSquared(player.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Informative /moblimiter command to describe the purpose of MobLimiter to players
|
||||
* in game and distribute information about the current configuration.
|
||||
*/
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||
if (cmd.getName().equalsIgnoreCase("moblimiter")) {
|
||||
if (args.length == 1 && args[0].equalsIgnoreCase("cull") && sender.hasPermission("moblimiter.cull")) {
|
||||
sender.sendMessage("[MobLimiter] Culling mobs in all loaded chunks");
|
||||
removeAllMobs();
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (args.length == 1 && args[0].equalsIgnoreCase("check") && sender.hasPermission("moblimiter.check")) {
|
||||
if (sender instanceof Player) {
|
||||
LivingEntity le = (LivingEntity) getTarget((Player) sender);
|
||||
boolean animalOrMonster = (le instanceof Animals) || (le instanceof Monster);
|
||||
if (le == null) {
|
||||
sender.sendMessage("[MobLimiter] No mob in sight");
|
||||
} else {
|
||||
if (!isSpecialMob(le) && animalOrMonster) {
|
||||
sender.sendMessage("[MobLimiter] Mob will be culled");
|
||||
} else {
|
||||
sender.sendMessage("[MobLimiter] Mob will not be culled");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(ChatColor.GOLD);
|
||||
message.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. Mobs are culled down to the following values per chunk on chunk "
|
||||
+ "unload, (-1 indicates no culling): ");
|
||||
|
||||
// if sheep are all configured to the same amount, just say one value
|
||||
// else put an asterisk and don't report the separate colors anyway,
|
||||
// since there's so many
|
||||
|
||||
if ((getConfig().getInt("sheepwhite") == getConfig().getInt("sheeporange")) &&
|
||||
(getConfig().getInt("sheeporange") == getConfig().getInt("sheepmagenta")) &&
|
||||
(getConfig().getInt("sheepmagenta") == getConfig().getInt("sheeplight_blue")) &&
|
||||
(getConfig().getInt("sheeplight_blue") == getConfig().getInt("sheepyellow")) &&
|
||||
(getConfig().getInt("sheepyellow") == getConfig().getInt("sheeplime")) &&
|
||||
(getConfig().getInt("sheeplime") == getConfig().getInt("sheeppink")) &&
|
||||
(getConfig().getInt("sheeppink") == getConfig().getInt("sheepgray")) &&
|
||||
(getConfig().getInt("sheepgray") == getConfig().getInt("sheepsilver")) &&
|
||||
(getConfig().getInt("sheepsilver") == getConfig().getInt("sheepcyan")) &&
|
||||
(getConfig().getInt("sheepcyan") == getConfig().getInt("sheeppurple")) &&
|
||||
(getConfig().getInt("sheeppurple") == getConfig().getInt("sheepblue")) &&
|
||||
(getConfig().getInt("sheepblue") == getConfig().getInt("sheepbrown")) &&
|
||||
(getConfig().getInt("sheepbrown") == getConfig().getInt("sheepgreen")) &&
|
||||
(getConfig().getInt("sheepgreen") == getConfig().getInt("sheepred")) &&
|
||||
(getConfig().getInt("sheepred") == getConfig().getInt("sheepblack")))
|
||||
{
|
||||
message.append(ChatColor.YELLOW + "sheep: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("sheepwhite"));
|
||||
}
|
||||
else
|
||||
{
|
||||
message.append(ChatColor.YELLOW + "sheep*: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("sheepwhite"));
|
||||
}
|
||||
|
||||
message.append(ChatColor.YELLOW + " chicken: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("chicken"));
|
||||
message.append(ChatColor.YELLOW + " cow: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("cow"));
|
||||
message.append(ChatColor.YELLOW + " mushroom_cow: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("mushroom_cow"));
|
||||
message.append(ChatColor.YELLOW + " ocelot: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("ocelot"));
|
||||
message.append(ChatColor.YELLOW + " pig: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("pig"));
|
||||
message.append(ChatColor.YELLOW + " wolf: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("wolf"));
|
||||
message.append(ChatColor.YELLOW + " rabbit: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("rabbit"));
|
||||
message.append(ChatColor.YELLOW + " blaze: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("blaze"));
|
||||
message.append(ChatColor.YELLOW + " cave_spider: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("cave_spider"));
|
||||
message.append(ChatColor.YELLOW + " creeper: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("creeper"));
|
||||
message.append(ChatColor.YELLOW + " enderman: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("enderman"));
|
||||
message.append(ChatColor.YELLOW + " ghast: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("ghast"));
|
||||
message.append(ChatColor.YELLOW + " guardian: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("guardian"));
|
||||
message.append(ChatColor.YELLOW + " silverfish: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("silverfish"));
|
||||
message.append(ChatColor.YELLOW + " skeleton: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("skeleton"));
|
||||
message.append(ChatColor.YELLOW + " spider: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("spider"));
|
||||
message.append(ChatColor.YELLOW + " squid: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("squid"));
|
||||
message.append(ChatColor.YELLOW + " villager: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("villager"));
|
||||
message.append(ChatColor.YELLOW + " zombie: " + ChatColor.GRAY
|
||||
+ getConfig().getInt("zombie"));
|
||||
message.append(ChatColor.GOLD);
|
||||
message.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 "
|
||||
+ ChatColor.GRAY + getConfig().getInt("agecapbaby") / 20
|
||||
+ ChatColor.GOLD + " and "
|
||||
+ ChatColor.GRAY + getConfig().getInt("agecapbreed") / 20
|
||||
+ ChatColor.GOLD + " seconds respectively. ");
|
||||
|
||||
// explain spawn-limited, if set
|
||||
|
||||
if (getConfig().isList("settings.spawn_limited")) {
|
||||
message.append("Note that mobs of type " + ChatColor.YELLOW
|
||||
+ getConfig().getStringList("settings.spawn_limited") + ChatColor.GOLD
|
||||
+ " are configured to be only new-spawn-limited and not culled, once their numbers "
|
||||
+ "have reached the integer in the previous list no new mobs of that type will spawn "
|
||||
+ "in that respective chunk. ");
|
||||
}
|
||||
|
||||
// on nerd.nu we use KitchenSink to plump drops, which is because of
|
||||
// mob limiting, so mention that here
|
||||
|
||||
message.append("Additionally, the drops of passive mobs "
|
||||
+ "are plumped so that more items drop which allows for fewer animals to produce "
|
||||
+ "more resources. Tamed pet mobs, mobs custom named with a nametag, and mobs "
|
||||
+ "wearing armor are not culled. If you have any questions please feel free "
|
||||
+ "to contact a server administrator.");
|
||||
|
||||
sender.sendMessage(message.toString());
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package nu.nerd.moblimiter.configuration;
|
||||
|
||||
|
||||
import nu.nerd.moblimiter.MobLimiter;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Sheep;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class Configuration {
|
||||
|
||||
|
||||
private MobLimiter plugin;
|
||||
private int radius;
|
||||
private boolean debug;
|
||||
private int breedingTicks;
|
||||
private int growthTicks;
|
||||
private boolean logBlock;
|
||||
private boolean relativeAge;
|
||||
private ConfiguredDefaults defaults;
|
||||
private HashMap<String, ConfiguredMob> limits;
|
||||
|
||||
|
||||
public Configuration() {
|
||||
plugin = MobLimiter.instance;
|
||||
plugin.saveDefaultConfig();
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reload the configuration from disk
|
||||
*/
|
||||
public void load() {
|
||||
|
||||
plugin.reloadConfig();
|
||||
this.radius = plugin.getConfig().getInt("radius", 3);
|
||||
this.debug = plugin.getConfig().getBoolean("debug", false);
|
||||
this.breedingTicks = plugin.getConfig().getInt("breeding_ticks", 300);
|
||||
this.growthTicks = plugin.getConfig().getInt("growth_ticks", 300);
|
||||
this.defaults = new ConfiguredDefaults(plugin.getConfig());
|
||||
this.logBlock = plugin.getConfig().getBoolean("logblock", false);
|
||||
this.relativeAge = plugin.getConfig().getBoolean("relative_age", false);
|
||||
|
||||
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
|
||||
}
|
||||
limits.put(key.toUpperCase(), mob);
|
||||
} catch (ConfigurationException ex) {
|
||||
plugin.getLogger().warning(ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the "view distance," in chunks, to check for mobs when a new mob spawns.
|
||||
*/
|
||||
public int getRadius() {
|
||||
return radius;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is debug mode on?
|
||||
*/
|
||||
public boolean debug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ticks until a farm animal is ready to breed again.
|
||||
*/
|
||||
public int getBreedingTicks() {
|
||||
return breedingTicks;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ticks until a farm animal grows up.
|
||||
*/
|
||||
public int getGrowthTicks() {
|
||||
return growthTicks;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Whether support for LogBlock entity removal logging is enabled
|
||||
* @return true if enabled
|
||||
*/
|
||||
public boolean logBlockEnabled() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the global default limits
|
||||
*/
|
||||
public ConfiguredDefaults getDefaults() {
|
||||
return defaults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public HashMap<String, ConfiguredMob> getAllLimits() {
|
||||
return limits;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The maximum number of this mob type that should be allowed to exist in a single chunk.
|
||||
*/
|
||||
public int getChunkMax() {
|
||||
return chunkMax;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The number of mobs that should be left after a chunk unload cull
|
||||
*/
|
||||
public int getCull() {
|
||||
return cull;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
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;
|
||||
private int max;
|
||||
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
|
||||
* @param defaults The default values to fall back to
|
||||
* @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());
|
||||
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
|
||||
* @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());
|
||||
age = defaults.getAge();
|
||||
max = defaults.getMax();
|
||||
chunkMax = defaults.getChunkMax();
|
||||
cull = defaults.getCull();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the key identifying this mob classification
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Bukkit EntityType these limits apply to
|
||||
*/
|
||||
public EntityType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The maximum age, in ticks, that this mob should live for before being removed
|
||||
*/
|
||||
public int getAge() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The maximum number of this mob type that should be allowed to exist in a single chunk.
|
||||
*/
|
||||
public int getChunkMax() {
|
||||
return chunkMax;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The number of mobs that should be left after a chunk unload cull
|
||||
*/
|
||||
public int getCull() {
|
||||
return cull;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package nu.nerd.moblimiter.limiters;
|
||||
|
||||
import nu.nerd.moblimiter.MobLimiter;
|
||||
import nu.nerd.moblimiter.EntityHelper;
|
||||
import nu.nerd.moblimiter.configuration.ConfiguredMob;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.*;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
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
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Exempt special mobs
|
||||
if (EntityHelper.isSpecialMob((LivingEntity) entity)) {
|
||||
if (plugin.getConfiguration().debug()) {
|
||||
plugin.getLogger().info("Special mob exempted from removal: " + EntityHelper.getMobDescription(entity));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Leave two of any farm animals
|
||||
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;
|
||||
|
||||
// Remove mobs
|
||||
ConfiguredMob limits = plugin.getConfiguration().getLimits(entity);
|
||||
if (!entity.isDead() && adjustedAge(entity) > limits.getAge() && limits.getAge() > -1) {
|
||||
((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.
|
||||
* @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 (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.
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package nu.nerd.moblimiter.limiters;
|
||||
|
||||
import nu.nerd.moblimiter.EntityHelper;
|
||||
import nu.nerd.moblimiter.MobLimiter;
|
||||
import nu.nerd.moblimiter.configuration.ConfiguredMob;
|
||||
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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Cull applicable mobs on chunk unload.
|
||||
* This is based on behavior from the original MobLimiter 1.x.
|
||||
*/
|
||||
public class ChunkUnloadLimiter implements Listener {
|
||||
|
||||
|
||||
private MobLimiter plugin;
|
||||
|
||||
|
||||
public ChunkUnloadLimiter() {
|
||||
plugin = MobLimiter.instance;
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
|
||||
@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
|
||||
*/
|
||||
private void removeMobs(Chunk chunk) {
|
||||
Map<String, Integer> count = new HashMap<String, Integer>();
|
||||
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;
|
||||
|
||||
// Exempt special mobs
|
||||
if (EntityHelper.isSpecialMob((LivingEntity) entity)) {
|
||||
if (plugin.getConfiguration().debug()) {
|
||||
plugin.getLogger().info("Special mob exempted from removal: " + EntityHelper.getMobDescription(entity));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ConfiguredMob limits = plugin.getConfiguration().getLimits(entity);
|
||||
String key = limits.getKey();
|
||||
int cap = limits.getCull();
|
||||
|
||||
Integer oldCount = count.get(key);
|
||||
int mobCount = (oldCount == null) ? 1 : oldCount + 1;
|
||||
count.put(key, mobCount);
|
||||
|
||||
if (cap > -1 && mobCount > cap) {
|
||||
plugin.getLogBlock().logEntityRemoval(entity, 283);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package nu.nerd.moblimiter.limiters;
|
||||
|
||||
import nu.nerd.moblimiter.EntityHelper;
|
||||
import nu.nerd.moblimiter.MobLimiter;
|
||||
import nu.nerd.moblimiter.configuration.ConfiguredMob;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.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.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.add(SpawnReason.BREEDING);
|
||||
reasons.add(SpawnReason.DEFAULT);
|
||||
reasons.add(SpawnReason.NATURAL);
|
||||
reasons.add(SpawnReason.SPAWNER);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle mob limting on creature spawn
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void onCreatureSpawnEvent(final CreatureSpawnEvent event) {
|
||||
|
||||
SpawnReason reason = event.getSpawnReason();
|
||||
ConfiguredMob limits = plugin.getConfiguration().getLimits(event.getEntity());
|
||||
|
||||
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());
|
||||
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());
|
||||
event.getEntity().remove();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
Reference in new issue
Block a user