Merge branch 'development'

This commit is contained in:
ceze88 2023-04-13 17:12:46 +02:00
commit 833640b03f
39 changed files with 1289 additions and 1264 deletions

328
LICENSE
View File

@ -1,9 +1,327 @@
Copyright (c) 2018 Brianna OKeefe
Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
International Public License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software with minimal restriction, including the rights to use, copy, modify or merge while excluding the rights to publish, (re)distribute, sub-license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-NonCommercial-NoDerivatives 4.0 International Public
License ("Public License"). To the extent this Public License may be
interpreted as a contract, You are granted the Licensed Rights in
consideration of Your acceptance of these terms and conditions, and the
Licensor grants You such rights in consideration of benefits the
Licensor receives from making the Licensed Material available under
these terms and conditions.
The same distribution rights and limitations above shall similarly apply to any and all source code, and other means that can be used to emulate this work.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Section 1 -- Definitions.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
c. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
d. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
e. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
f. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
g. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
h. NonCommercial means not primarily intended for or directed towards
commercial advantage or monetary compensation. For purposes of
this Public License, the exchange of the Licensed Material for
other material subject to Copyright and Similar Rights by digital
file-sharing or similar means is NonCommercial provided there is
no payment of monetary compensation in connection with the
exchange.
i. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
j. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
k. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part, for NonCommercial purposes only; and
b. produce and reproduce, but not Share, Adapted Material
for NonCommercial purposes only.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties, including when
the Licensed Material is used other than for NonCommercial
purposes.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material, You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
For the avoidance of doubt, You do not have permission under
this Public License to Share Adapted Material.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database for NonCommercial purposes
only and provided You do not Share Adapted Material;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material; and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================

View File

@ -2,7 +2,7 @@
<groupId>com.songoda</groupId>
<artifactId>UltimateStacker</artifactId>
<modelVersion>4.0.0</modelVersion>
<version>2.3.3</version>
<version>2.4.0</version>
<build>
<defaultGoal>clean install</defaultGoal>
<finalName>UltimateStacker-${project.version}</finalName>
@ -122,7 +122,7 @@
<dependency>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<scope>compile</scope>
</dependency>

View File

@ -13,6 +13,7 @@ import com.songoda.core.database.SQLiteConnector;
import com.songoda.core.gui.GuiManager;
import com.songoda.core.hooks.EntityStackerManager;
import com.songoda.core.hooks.HologramManager;
import com.songoda.core.hooks.ProtectionManager;
import com.songoda.core.hooks.WorldGuardHook;
import com.songoda.core.utils.TextUtils;
import com.songoda.ultimatestacker.commands.CommandConvert;
@ -26,8 +27,7 @@ import com.songoda.ultimatestacker.database.DataManager;
import com.songoda.ultimatestacker.database.migrations._1_InitialMigration;
import com.songoda.ultimatestacker.database.migrations._2_EntityStacks;
import com.songoda.ultimatestacker.database.migrations._3_BlockStacks;
import com.songoda.ultimatestacker.database.migrations._4_DataPurge;
import com.songoda.ultimatestacker.database.migrations._5_StackedEntitiesTableUpdate;
import com.songoda.ultimatestacker.database.migrations._6_RemoveStackedEntityTable;
import com.songoda.ultimatestacker.hook.StackerHook;
import com.songoda.ultimatestacker.hook.hooks.JobsHook;
import com.songoda.ultimatestacker.listeners.*;
@ -49,11 +49,13 @@ import com.songoda.ultimatestacker.stackable.spawner.SpawnerStackManager;
import com.songoda.ultimatestacker.tasks.StackingTask;
import com.songoda.ultimatestacker.utils.Async;
import com.songoda.ultimatestacker.utils.Methods;
import io.lumine.mythic.bukkit.listeners.ChunkListeners;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
@ -106,9 +108,10 @@ public class UltimateStacker extends SongodaPlugin {
@Override
public void onPluginDisable() {
this.stackingTask.cancel();
this.stackingTask.stop();
this.stackingTask = null;
this.dataManager.bulkUpdateSpawners(this.spawnerStackManager.getStacks());
this.dataManager.bulkUpdateBlocks(this.blockStackManager.getStacks());
HologramManager.removeAllHolograms();
Async.shutdown();
}
@ -170,6 +173,10 @@ public class UltimateStacker extends SongodaPlugin {
spawnerFile.load();
spawnerFile.saveChanges();
if (Bukkit.getPluginManager().isPluginEnabled("BentoBox")) {
ProtectionManager.load(Bukkit.getPluginManager().getPlugin("BentoBox"));
}
this.spawnerStackManager = new SpawnerStackManager();
this.entityStackManager = new EntityStackManager(this);
this.blockStackManager = new BlockStackManager();
@ -235,8 +242,7 @@ public class UltimateStacker extends SongodaPlugin {
new _1_InitialMigration(),
new _2_EntityStacks(),
new _3_BlockStacks(),
new _4_DataPurge(),
new _5_StackedEntitiesTableUpdate());
new _6_RemoveStackedEntityTable());
this.dataMigrationManager.runMigrations();
}
@ -260,12 +266,8 @@ public class UltimateStacker extends SongodaPlugin {
}
}
});
this.dataManager.getEntities((entities) -> {
entityStackManager.addStacks(entities.values());
entityStackManager.tryAndLoadColdEntities();
this.stackingTask = new StackingTask(this);
getServer().getPluginManager().registerEvents(new ChunkListeners(entityStackManager), this);
});
this.stackingTask = new StackingTask(this);
final boolean useBlockHolo = Settings.BLOCK_HOLOGRAMS.getBoolean();
this.dataManager.getBlocks((blocks) -> {
this.blockStackManager.addBlocks(blocks);
@ -302,7 +304,7 @@ public class UltimateStacker extends SongodaPlugin {
this.setLocale(getConfig().getString("System.Language Mode"), true);
this.locale.reloadMessages();
this.stackingTask.cancel();
this.stackingTask.stop();
this.stackingTask = new StackingTask(this);
this.mobFile.load();

View File

@ -3,6 +3,7 @@ package com.songoda.ultimatestacker.commands;
import com.songoda.core.commands.AbstractCommand;
import com.songoda.core.utils.TextUtils;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.stackable.entity.EntityStack;
import com.songoda.ultimatestacker.stackable.entity.EntityStackManager;
import com.songoda.ultimatestacker.utils.Methods;
import org.bukkit.Bukkit;
@ -46,10 +47,11 @@ public class CommandRemoveAll extends AbstractCommand {
for (Entity entityO : world.getEntities()) {
if (entityO instanceof Player) continue;
if (entityO instanceof LivingEntity && (stackManager.isStackedAndLoaded((LivingEntity)entityO) || all)
if (entityO instanceof LivingEntity && (stackManager.isStackedEntity(entityO) || all)
&& type.equalsIgnoreCase("entities")) {
entityO.remove();
plugin.getEntityStackManager().removeStack(entityO);
EntityStack stack = plugin.getEntityStackManager().getStack((LivingEntity) entityO);
if (stack == null) continue;
stack.destroy();
amountRemoved++;
} else if (entityO.getType() == EntityType.DROPPED_ITEM && type.equalsIgnoreCase("items")) {
if (!UltimateStacker.hasCustomAmount((Item)entityO) && !all)

View File

@ -56,9 +56,7 @@ public class CommandSpawn extends AbstractCommand {
sender.sendMessage(TextUtils.formatText("&6" + list));
} else {
LivingEntity entity = (LivingEntity)player.getWorld().spawnEntity(player.getLocation(), type);
EntityStack stack = plugin.getEntityStackManager().addStack(entity);
stack.createDuplicates(((Methods.isInt(args[1])) ? Integer.parseInt(args[1]) : 1) - 1);
stack.updateStack();
EntityStack stack = plugin.getEntityStackManager().createStack(entity, Integer.parseInt(args[1]));
plugin.getStackingTask().attemptSplit(stack, entity);
}

View File

@ -36,14 +36,6 @@ public class StackMobConvert implements Convert {
@Override
public void convertEntities() {
EntityStackManager entityStackManager = plugin.getEntityStackManager();
for (Map.Entry<UUID, Integer> entry : stackMob.getStorageManager().getAmountCache().entrySet()) {
if (!entityStackManager.isStackedAndLoaded(entry.getKey())) {
entityStackManager.addLegacyColdStack(entry.getKey(), entry.getValue());
continue;
}
}
}
@Override

View File

@ -43,23 +43,6 @@ public class WildStackerConvert implements Convert {
@Override
public void convertEntities() {
EntityStackManager entityStackManager = plugin.getEntityStackManager();
DatabaseConnector connector = new SQLiteConnector(this.wildStacker);
connector.connect(connection -> {
try (Statement statement = connection.createStatement()) {
ResultSet result = statement.executeQuery("SELECT uuid, stackAmount FROM entities");
while (result.next()) {
UUID uuid = UUID.fromString(result.getString("uuid"));
int amount = result.getInt("stackAmount");
if (!entityStackManager.isEntityInColdStorage(uuid))
entityStackManager.addLegacyColdStack(uuid, amount);
}
}
});
}
@Override

View File

@ -6,7 +6,6 @@ import com.songoda.core.database.DatabaseConnector;
import com.songoda.core.database.DatabaseType;
import com.songoda.ultimatestacker.settings.Settings;
import com.songoda.ultimatestacker.stackable.block.BlockStack;
import com.songoda.ultimatestacker.stackable.entity.ColdEntityStack;
import com.songoda.ultimatestacker.stackable.entity.EntityStack;
import com.songoda.ultimatestacker.stackable.entity.StackedEntity;
import com.songoda.ultimatestacker.stackable.spawner.SpawnerStack;
@ -69,7 +68,7 @@ public class DataManager extends DataManagerAbstract {
public void createSpawner(SpawnerStack spawnerStack) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String createSpawner = "INSERT INTO " + getSyntax("OR REPLACE ", DatabaseType.SQLITE) + this.getTablePrefix() + "spawners (amount, world, x, y, z) VALUES (?, ?, ?, ?, ?)";
String createSpawner = "INSERT " + getSyntax("INTO ", DatabaseType.MYSQL) + getSyntax("OR REPLACE INTO ", DatabaseType.SQLITE) + this.getTablePrefix() + "spawners (amount, world, x, y, z) VALUES (?, ?, ?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(createSpawner);
statement.setInt(1, spawnerStack.getAmount());
@ -87,6 +86,7 @@ public class DataManager extends DataManagerAbstract {
}
public void updateBlock(BlockStack blockStack) {
if (blockStack.getAmount() == 0) return;
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String updateBlock = "UPDATE " + this.getTablePrefix() + "blocks SET amount = ? WHERE id = ?";
@ -104,7 +104,7 @@ public class DataManager extends DataManagerAbstract {
public void createBlock(BlockStack blockStack) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String createSpawner = "INSERT INTO " + getSyntax("OR REPLACE ", DatabaseType.SQLITE) + this.getTablePrefix() + "blocks (amount, material, world, x, y, z) VALUES (?, ?, ?, ?, ?, ?)";
String createSpawner = "INSERT " + getSyntax("INTO ", DatabaseType.MYSQL) + getSyntax("OR REPLACE INTO ", DatabaseType.SQLITE) + this.getTablePrefix() + "blocks (amount, material, world, x, y, z) VALUES (?, ?, ?, ?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(createSpawner);
statement.setInt(1, blockStack.getAmount());
statement.setString(2, blockStack.getMaterial().name());
@ -122,130 +122,6 @@ public class DataManager extends DataManagerAbstract {
});
}
public void createHostEntity(ColdEntityStack stack) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String createSerializedEntity = "INSERT INTO " + getSyntax("OR REPLACE ", DatabaseType.SQLITE) + this.getTablePrefix() + "host_entities (uuid, create_duplicates) VALUES (?, ?)";
PreparedStatement statement = connection.prepareStatement(createSerializedEntity);
if (stack == null || stack.getHostUniqueId() == null) return;
statement.setString(1, stack.getHostUniqueId().toString());
statement.setInt(2, stack.getCreateDuplicates());
statement.executeUpdate();
int stackId = this.lastInsertedId(connection, "host_entities");
this.sync(() -> stack.setId(stackId));
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void createStackedEntity(EntityStack hostStack, StackedEntity stackedEntity) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()){
String createSerializedEntity = "INSERT INTO " + getSyntax("OR REPLACE ", DatabaseType.SQLITE) + this.getTablePrefix() + "stacked_entities (uuid, host, serialized_entity) VALUES (?, ?, ?) "
+ (Settings.MYSQL_ENABLED.getBoolean() ? "ON DUPLICATE KEY UPDATE host = ?, serialized_entity = ?" : "ON CONFLICT(uuid) DO UPDATE SET host = ?, serialized_entity = ?");
PreparedStatement statement = connection.prepareStatement(createSerializedEntity);
if (hostStack.getHostUniqueId() == null) return;
statement.setString(1, stackedEntity.getUniqueId().toString());
statement.setInt(2, hostStack.getId());
statement.setBytes(3, stackedEntity.getSerializedEntity());
statement.setInt(4, hostStack.getId());
statement.setBytes(5, stackedEntity.getSerializedEntity());
statement.executeUpdate();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void createStackedEntities(ColdEntityStack hostStack, List<StackedEntity> stackedEntities) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String createSerializedEntity = "REPLACE INTO " + this.getTablePrefix() + "stacked_entities (uuid, host, serialized_entity) VALUES (?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(createSerializedEntity);
if (hostStack.getHostUniqueId() == null) return;
for (StackedEntity entity : stackedEntities) {
statement.setString(1, entity.getUniqueId().toString());
statement.setInt(2, hostStack.getId());
statement.setBytes(3, entity.getSerializedEntity());
statement.addBatch();
}
statement.executeBatch();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void updateHost(ColdEntityStack hostStack) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String updateHost = "UPDATE " + this.getTablePrefix() + "host_entities SET uuid = ?, create_duplicates = ?, updated_at = current_timestamp WHERE id = ?";
PreparedStatement statement = connection.prepareStatement(updateHost);
if (hostStack.getHostUniqueId() == null) return;
statement.setString(1, hostStack.getHostUniqueId().toString());
statement.setInt(2, hostStack.getCreateDuplicates());
statement.setInt(3, hostStack.getId());
statement.executeUpdate();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void deleteHost(ColdEntityStack stack) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String deleteHost = "DELETE FROM " + this.getTablePrefix() + "host_entities WHERE id = ?";
try (PreparedStatement statement = connection.prepareStatement(deleteHost)) {
statement.setInt(1, stack.getId());
statement.executeUpdate();
}
String deleteStackedEntities = "DELETE FROM " + this.getTablePrefix() + "stacked_entities WHERE host = ?";
try (PreparedStatement statement = connection.prepareStatement(deleteStackedEntities)) {
statement.setInt(1, stack.getId());
statement.executeUpdate();
}
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void deleteStackedEntity(UUID uuid) {
if (uuid == null)
return;
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String deleteStackedEntity = "DELETE FROM " + this.getTablePrefix() + "stacked_entities WHERE uuid = ?";
PreparedStatement statement = connection.prepareStatement(deleteStackedEntity);
statement.setString(1, uuid.toString());
statement.executeUpdate();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void deleteStackedEntities(List<StackedEntity> entities) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String deleteStackedEntities = "DELETE FROM " + this.getTablePrefix() + "stacked_entities WHERE uuid = ?";
PreparedStatement statement = connection.prepareStatement(deleteStackedEntities);
for (StackedEntity entity : entities) {
if (entity == null) continue;
statement.setString(1, entity.getUniqueId().toString());
statement.addBatch();
}
statement.executeBatch();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void deleteSpawner(SpawnerStack spawnerStack) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
@ -272,76 +148,6 @@ public class DataManager extends DataManagerAbstract {
});
}
public void getEntities(Consumer<Map<Integer, ColdEntityStack>> callback) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
Map<Integer, ColdEntityStack> entities = new HashMap<>();
boolean mysql = Settings.MYSQL_ENABLED.getBoolean();
int databasePurge = Settings.DATABASE_PURGE.getInt();
String whereStatement = mysql ? "WHERE updated_at < NOW() - INTERVAL " + databasePurge + " DAY" : "WHERE updated_at <= date('now','-" + databasePurge + " day')";
String selectOldEntities = "SELECT * FROM " + this.getTablePrefix() + "host_entities " + whereStatement;
try (Statement statement = connection.createStatement()) {
List<String> toDelete = new ArrayList<>();
ResultSet result = statement.executeQuery(selectOldEntities);
while (result.next()) {
int hostId = result.getInt("id");
toDelete.add(String.valueOf(hostId));
}
if (!toDelete.isEmpty()) {
statement.execute("DELETE FROM " + this.getTablePrefix() + "host_entities " + whereStatement);
statement.execute("DELETE FROM " + this.getTablePrefix() + "stacked_entities WHERE host IN (" + String.join(", ", toDelete) + ")");
}
} catch (Exception e) {
e.printStackTrace();
}
String selectEntities = "SELECT * FROM " + this.getTablePrefix() + "host_entities";
try (Statement statement = connection.createStatement()) {
ResultSet result = statement.executeQuery(selectEntities);
while (result.next()) {
int hostId = result.getInt("id");
UUID host = UUID.fromString(result.getString("uuid"));
int createDuplicates = result.getInt("create_duplicates");
ColdEntityStack stack = new ColdEntityStack(host, hostId);
stack.createDuplicates(createDuplicates);
entities.put(hostId, stack);
}
} catch (Exception e) {
e.printStackTrace();
}
String selectStackedEntities = "SELECT * FROM " + this.getTablePrefix() + "stacked_entities";
try (Statement statement = connection.createStatement()) {
ResultSet result = statement.executeQuery(selectStackedEntities);
while (result.next()) {
UUID uuid = UUID.fromString(result.getString("uuid"));
int hostId = result.getInt("host");
byte[] serializedEntity = result.getBytes("serialized_entity");
ColdEntityStack stack = entities.get(hostId);
if (stack == null) continue;
stack.addEntityToStackSilently(new StackedEntity(uuid, serializedEntity));
}
} catch (Exception e) {
e.printStackTrace();
}
this.sync(() -> callback.accept(entities));
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void getSpawners(Consumer<Map<Location, SpawnerStack>> callback) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()){
@ -410,6 +216,11 @@ public class DataManager extends DataManagerAbstract {
BlockStack blockStack = new BlockStack(material, location, amount);
blockStack.setId(blockId);
if (amount == 0) {
//remove from database
this.deleteBlock(blockStack);
continue;
}
blocks.put(location, blockStack);
this.sync(() -> callback.accept(blocks));
@ -419,4 +230,19 @@ public class DataManager extends DataManagerAbstract {
}
});
}
public void bulkUpdateBlocks(Collection<BlockStack> stacks) {
try (Connection connection = this.databaseConnector.getConnection()) {
String updateSpawner = "UPDATE " + this.getTablePrefix() + "blocks SET amount = ? WHERE id = ?";
PreparedStatement statement = connection.prepareStatement(updateSpawner);
for (BlockStack spawnerStack : stacks) {
statement.setInt(1, spawnerStack.getAmount());
statement.setInt(2, spawnerStack.getId());
statement.addBatch();
}
statement.executeBatch();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

View File

@ -20,7 +20,7 @@ public class _1_InitialMigration extends DataMigration {
// Create spawners table
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE " + tablePrefix + "spawners (" +
statement.execute("CREATE TABLE IF NOT EXISTS " + tablePrefix + "spawners (" +
"id INTEGER PRIMARY KEY" + autoIncrement + ", " +
"amount INTEGER NOT NULL," +
"world TEXT NOT NULL, " +

View File

@ -20,20 +20,11 @@ public class _2_EntityStacks extends DataMigration {
// Create host entities table
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE " + tablePrefix + "host_entities (" +
statement.execute("CREATE TABLE IF NOT EXISTS " + tablePrefix + "host_entities (" +
"id INTEGER PRIMARY KEY" + autoIncrement + ", " +
"uuid VARCHAR(36) NOT NULL," +
"create_duplicates INTEGER NOT NULL DEFAULT 0" +
")");
}
// Create stacked entities table
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE " + tablePrefix + "stacked_entities (" +
"uuid VARCHAR(36) PRIMARY KEY NOT NULL," +
"host INTEGER NOT NULL," +
"serialized_entity VARBINARY(9999) NOT NULL" +
")");
}
}
}

View File

@ -20,7 +20,7 @@ public class _3_BlockStacks extends DataMigration {
// Create blocks table
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE " + tablePrefix + "blocks (" +
statement.execute("CREATE TABLE IF NOT EXISTS " + tablePrefix + "blocks (" +
"id INTEGER PRIMARY KEY" + autoIncrement + ", " +
"amount INTEGER NOT NULL," +
"material TEXT NOT NULL," +

View File

@ -1,21 +0,0 @@
package com.songoda.ultimatestacker.database.migrations;
import com.songoda.core.database.DataMigration;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class _5_StackedEntitiesTableUpdate extends DataMigration {
public _5_StackedEntitiesTableUpdate() {
super(5);
}
@Override
public void migrate(Connection connection, String tablePrefix) throws SQLException {
try (Statement statement = connection.createStatement()) {
statement.execute("ALTER TABLE " + tablePrefix + "stacked_entities MODIFY serialized_entity VARBINARY(9999)");
}
}
}

View File

@ -1,23 +1,23 @@
package com.songoda.ultimatestacker.database.migrations;
import com.songoda.core.database.DataMigration;
import com.songoda.core.database.MySQLConnector;
import com.songoda.ultimatestacker.UltimateStacker;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class _4_DataPurge extends DataMigration {
public class _6_RemoveStackedEntityTable extends DataMigration {
public _4_DataPurge() {
super(4);
public _6_RemoveStackedEntityTable() {
super(6);
}
@Override
public void migrate(Connection connection, String tablePrefix) throws SQLException {
public void migrate(Connection connection, String tablePrefix) {
try (Statement statement = connection.createStatement()) {
statement.execute("ALTER TABLE " + tablePrefix + "host_entities ADD COLUMN updated_at datetime DEFAULT NULL");
statement.execute("DROP TABLE " + tablePrefix + "stacked_entities");
} catch (SQLException e) {
e.printStackTrace();
}
}
}

View File

@ -55,7 +55,7 @@ public class GUIConvertWhat extends Gui {
private void run(Player player) {
if (entities) {
convertFrom.convertEntities();
UltimateStacker.getInstance().getEntityStackManager().tryAndLoadColdEntities();
//UltimateStacker.getInstance().getEntityStackManager().tryAndLoadColdEntities();
}
if (spawners) {
convertFrom.convertSpawners();

View File

@ -2,6 +2,8 @@ package com.songoda.ultimatestacker.listeners;
import com.songoda.core.compatibility.CompatibleHand;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.hooks.ProtectionManager;
import com.songoda.core.hooks.protection.BentoBoxProtection;
import com.songoda.core.third_party.de.tr7zw.nbtapi.NBTItem;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.events.SpawnerBreakEvent;
@ -11,14 +13,17 @@ import com.songoda.ultimatestacker.stackable.block.BlockStack;
import com.songoda.ultimatestacker.stackable.block.BlockStackManager;
import com.songoda.ultimatestacker.stackable.spawner.SpawnerStack;
import com.songoda.ultimatestacker.utils.Methods;
import io.lumine.mythic.bukkit.utils.menu.ClickAction;
import org.apache.commons.lang.math.NumberUtils;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@ -40,73 +45,112 @@ public class BlockListeners implements Listener {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockInteract(PlayerInteractEvent event) {
if (event.useInteractedBlock() == Event.Result.DENY) return;
Block block = event.getClickedBlock();
Player player = event.getPlayer();
CompatibleHand hand = CompatibleHand.getHand(event);
ItemStack inHand = hand.getItem(player);
if (block == null) return;
if (Settings.STACK_BLOCKS.getBoolean()) {
BlockStackManager blockStackManager = plugin.getBlockStackManager();
if (!ProtectionManager.canInteract(player, block.getLocation()) || !ProtectionManager.canBreak(player, block.getLocation())) {
if (!player.isOp()) {
return;
}
}
boolean isStacked = blockStackManager.isBlock(block.getLocation());
CompatibleHand hand = CompatibleHand.getHand(event);
ItemStack inHand = hand.getItem(player);
boolean isSneaking = player.isSneaking();
Action clickAction = event.getAction();
int inHandAmount = inHand.getAmount();
//Stacking blocks
if (Settings.STACK_BLOCKS.getBoolean()
&& Settings.STACKABLE_BLOCKS.getStringList().contains(block.getType().name()) //Is block stackable
&& !block.getType().equals(CompatibleMaterial.SPAWNER.getMaterial()) //Don't stack spawners here
) {
CompatibleMaterial blockType = CompatibleMaterial.getMaterial(block);
if (blockType == null) return;
if (isStacked || Settings.STACKABLE_BLOCKS.getStringList().contains(blockType.name())) {
BlockStack stack = blockStackManager.getBlock(block, blockType);
if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
if (!isStacked) plugin.getDataManager().createBlock(stack);
if (stack.getMaterial() == CompatibleMaterial.getMaterial(inHand)) {
int amountToAdd = player.isSneaking() || Settings.ALWAYS_ADD_ALL.getBoolean() ? inHand.getAmount() : 1;
if (!isStacked) amountToAdd++;
stack.add(amountToAdd);
event.setCancelled(true);
if (player.getGameMode() != GameMode.CREATIVE)
hand.takeItem(player, amountToAdd);
plugin.updateHologram(stack);
}
plugin.getDataManager().updateBlock(stack);
} else if (event.getAction() == Action.LEFT_CLICK_BLOCK && stack.getAmount() != 0) {
event.setCancelled(true);
int amountToRemove = player.isSneaking()
? Math.min(Settings.MAX_REMOVEABLE.getInt(), stack.getAmount()) - 1 : 1;
BlockStackManager blockStackManager = plugin.getBlockStackManager();
boolean isStacked = blockStackManager.isBlock(block.getLocation());
BlockStack stack = blockStackManager.getBlock(block.getLocation());
ItemStack removed = stack.getMaterial().getItem();
removed.setAmount(amountToRemove);
stack.take(amountToRemove);
int maxStack = removed.getMaxStackSize();
while (amountToRemove > 0) {
int subtract = Math.min(amountToRemove, maxStack);
amountToRemove -= subtract;
ItemStack newItem = removed.clone();
newItem.setAmount(subtract);
if (Settings.ADD_TO_INVENTORY.getBoolean()) {
Map<Integer, ItemStack> result = player.getInventory().addItem(newItem);
if (result.get(0) != null) {
amountToRemove += result.get(0).getAmount();
break;
}
} else {
block.getWorld().dropItemNaturally(block.getLocation().clone().add(.5, 1, .5), newItem);
//Modify stack
if (isStacked) {
event.setCancelled(true);
//Add to stack
if (clickAction == Action.RIGHT_CLICK_BLOCK) {
if (inHand.getType().equals(Material.AIR)) return;
if(!blockType.equals(CompatibleMaterial.getMaterial(inHand))) return;
//Add all held items to stack
if (Settings.ALWAYS_ADD_ALL.getBoolean() || isSneaking) {
stack.add(inHandAmount);
if (player.getGameMode() != GameMode.CREATIVE) {
hand.takeItem(player, inHandAmount);
}
} else {
//Add one held item to stack
stack.add(1);
if (player.getGameMode() != GameMode.CREATIVE) {
hand.takeItem(player, 1);
}
}
stack.add(amountToRemove);
if (stack.getAmount() < 2)
}
//Remove from stack
if (clickAction == Action.LEFT_CLICK_BLOCK) {
if (isSneaking) {
//Remove all items from stack
int amountToRemove = Math.min(Settings.MAX_REMOVEABLE.getInt(), stack.getAmount());
ItemStack removed = stack.getMaterial().getItem();
removed.setAmount(amountToRemove);
stack.take(amountToRemove);
if (Settings.ADD_TO_INVENTORY.getBoolean()) {
player.getInventory().addItem(removed);
} else {
player.getWorld().dropItemNaturally(block.getLocation(), removed);
}
} else {
//Remove one item from stack
stack.take(1);
if (Settings.ADD_TO_INVENTORY.getBoolean()) {
player.getInventory().addItem(stack.getMaterial().getItem());
} else {
player.getWorld().dropItemNaturally(block.getLocation(), stack.getMaterial().getItem());
}
}
if (stack.getAmount() == 0) {
//Remove stack
stack.destroy();
else {
plugin.updateHologram(stack);
plugin.getDataManager().updateBlock(stack);
return;
}
}
//update hologram
plugin.updateHologram(stack);
plugin.getDataManager().updateBlock(stack);
return;
} else {
if (isSneaking) return;
//Check if player clicked the same type as the clicked block
if (inHand.getType().equals(Material.AIR)) return;
if(!blockType.equals(CompatibleMaterial.getMaterial(inHand))) return;
if (clickAction != Action.RIGHT_CLICK_BLOCK) return;
//Create new stack
event.setCancelled(true);
if (player.getGameMode() != GameMode.CREATIVE) {
hand.takeItem(player, 1);
}
BlockStack newStack = blockStackManager.createBlock(block);
plugin.getDataManager().createBlock(newStack);
plugin.updateHologram(newStack);
}
return;
}
//Stacking spawners
if (block.getType() != CompatibleMaterial.SPAWNER.getMaterial()
|| inHand.getType() != CompatibleMaterial.SPAWNER.getMaterial()
|| event.getAction() == Action.LEFT_CLICK_BLOCK) return;
@ -166,8 +210,9 @@ public class BlockListeners implements Listener {
}
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockPlace(BlockPlaceEvent event) {
if (event.isCancelled()) return;
Block block = event.getBlock();
Player player = event.getPlayer();
@ -196,8 +241,9 @@ public class BlockListeners implements Listener {
plugin.updateHologram(stack);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockBreak(BlockBreakEvent event) {
if (event.isCancelled()) return;
Block block = event.getBlock();
if (block.getType() != CompatibleMaterial.SPAWNER.getMaterial()) return;

View File

@ -1,41 +0,0 @@
package com.songoda.ultimatestacker.listeners;
import com.songoda.ultimatestacker.stackable.entity.EntityStackManager;
import org.bukkit.Chunk;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
public class ChunkListeners implements Listener {
private final EntityStackManager entityStackManager;
public ChunkListeners(EntityStackManager entityStackManager) {
this.entityStackManager = entityStackManager;
}
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
Chunk chunk = event.getChunk();
for (Entity entity : chunk.getEntities()) {
if (!(entity instanceof LivingEntity)) continue;
if (entityStackManager.isEntityInColdStorage((LivingEntity) entity)) {
entityStackManager.loadStack((LivingEntity) entity);
}
}
}
@EventHandler
public void onChunkUnload(ChunkUnloadEvent event) {
Chunk chunk = event.getChunk();
for (Entity entity : chunk.getEntities()) {
if (!(entity instanceof LivingEntity)) continue;
if (entityStackManager.isStackedAndLoaded((LivingEntity) entity)) {
entityStackManager.unloadStack((LivingEntity) entity);
}
}
}
}

View File

@ -18,8 +18,8 @@ public class ClearLagListeners implements Listener {
@EventHandler
public void onClearLaggTask(EntityRemoveEvent event) {
for (Entity entity : event.getWorld().getEntities()) {
if (entity instanceof LivingEntity && plugin.getEntityStackManager().isStackedAndLoaded((LivingEntity)entity)) {
plugin.getEntityStackManager().removeStack(entity);
if (entity instanceof LivingEntity && plugin.getEntityStackManager().isStackedEntity(entity)) {
plugin.getEntityStackManager().getStack((LivingEntity) entity).destroy();
event.addEntity(entity);
}
}

View File

@ -7,6 +7,7 @@ import com.songoda.core.lootables.loot.DropUtils;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.settings.Settings;
import com.songoda.ultimatestacker.stackable.entity.EntityStack;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.GameRule;
import org.bukkit.Material;
@ -82,7 +83,11 @@ public class DeathListeners implements Listener {
drops.clear();
if (plugin.getCustomEntityManager().getCustomEntity(entity) == null) {
if (plugin.getEntityStackManager().isStackedAndLoaded(event.getEntity())) {
//replace %player% in drop commands with the last player to damage the entity
//Run commands here, or it will be buggy
runCommands(entity, drops);
if (plugin.getEntityStackManager().isStackedEntity(event.getEntity())) {
plugin.getEntityStackManager().getStack(event.getEntity()).onDeath(entity, drops, custom, event.getDroppedExp(), event);
} else {
DropUtils.processStackedDrop(event.getEntity(), drops, event);
@ -92,6 +97,25 @@ public class DeathListeners implements Listener {
finalItems.remove(entity.getUniqueId());
}
private void runCommands(LivingEntity entity, List<Drop> drops) {
String lastDamage = plugin.getEntityStackManager().getLastPlayerDamage(entity);
if (lastDamage != null) {
List<String> commands = new ArrayList<>();
drops.forEach(drop -> {
if (drop.getCommand() != null) {
String command = drop.getCommand().replace("%player%", lastDamage);
drop.setCommand(null);
commands.add(command);
}
});
Bukkit.getScheduler().runTask(plugin, () -> {
for (String command : commands) {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
}
});
}
}
private boolean shouldDrop(LivingEntity entity, Material material) {
if (entity.getEquipment() != null && entity.getEquipment().getArmorContents().length != 0) {
if (Settings.DONT_DROP_ARMOR.getBoolean())
@ -157,7 +181,7 @@ public class DeathListeners implements Listener {
if (!(event.getEntity() instanceof LivingEntity)) return;
LivingEntity entity = (LivingEntity) event.getEntity();
if (!plugin.getEntityStackManager().isStackedAndLoaded(entity)) return;
if (!plugin.getEntityStackManager().isStackedEntity(entity)) return;
EntityStack stack = plugin.getEntityStackManager().getStack(entity);
Player player = (Player) event.getDamager();

View File

@ -38,7 +38,7 @@ public class InteractListeners implements Listener {
ItemStack item = player.getInventory().getItemInHand();
if (!plugin.getEntityStackManager().isStackedAndLoaded(entity)) return;
if (!plugin.getEntityStackManager().isStackedEntity(entity)) return;
if (item.getType() != Material.NAME_TAG && !correctFood(item, entity)) return;

View File

@ -38,7 +38,7 @@ public class ShearListeners implements Listener {
&& entity.getType() != EntityType.MUSHROOM_COW
&& entity.getType() != EntityType.SNOWMAN) return;
EntityStackManager stackManager = plugin.getEntityStackManager();
if (!stackManager.isStackedAndLoaded(entity)) return;
if (!stackManager.isStackedEntity(entity)) return;
if (event.getEntity().getType() == EntityType.SHEEP
&& Settings.SPLIT_CHECKS.getStringList().stream().noneMatch(line -> Split.valueOf(line) == Split.SHEEP_SHEAR)

View File

@ -24,7 +24,7 @@ public class SheepDyeListeners implements Listener {
LivingEntity entity = event.getEntity();
EntityStackManager stackManager = plugin.getEntityStackManager();
if (!stackManager.isStackedAndLoaded(entity)) return;
if (!stackManager.isStackedEntity(entity)) return;
if (Settings.SPLIT_CHECKS.getStringList().stream().noneMatch(line -> Split.valueOf(line) == Split.SHEEP_DYE))
return;

View File

@ -39,6 +39,7 @@ public class SpawnerListeners implements Listener {
@EventHandler
public void onSpawn(SpawnerSpawnEvent event) {
if (plugin.getStackingTask() == null) return; //Don't spam error when reloading the plugin
if (!Settings.STACK_ENTITIES.getBoolean()
|| !plugin.spawnersEnabled()
|| plugin.getStackingTask().isWorldDisabled(event.getLocation().getWorld())) return;
@ -67,12 +68,16 @@ public class SpawnerListeners implements Listener {
SpawnerStack spawnerStack = spawnerStackManager.getSpawner(location);
spawnerStack.spawn(spawnerStack.calculateSpawnCount(), "EXPLOSION_NORMAL", null, (e) -> {
int amountToSpawn = spawnerStack.calculateSpawnCount();
spawnerStack.spawn(amountToSpawn, "EXPLOSION_NORMAL", null, (e) -> {
if (Settings.NO_AI.getBoolean())
EntityUtils.setUnaware(e);
if (mcmmo)
entity.setMetadata("mcMMO: Spawned Entity", new FixedMetadataValue(plugin, true));
UltimateStacker.getInstance().getEntityStackManager().setStack(e, amountToSpawn);
return true;
}, event.getEntityType());
}

View File

@ -22,7 +22,7 @@ public class TameListeners implements Listener {
LivingEntity entity = event.getEntity();
EntityStackManager stackManager = plugin.getEntityStackManager();
if (!stackManager.isStackedAndLoaded(entity)) return;
if (!stackManager.isStackedEntity(entity)) return;
EntityStack stack = plugin.getEntityStackManager().getStack(entity);

View File

@ -1,13 +1,21 @@
package com.songoda.ultimatestacker.listeners.entity;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.settings.Settings;
import com.songoda.ultimatestacker.stackable.entity.EntityStack;
import com.songoda.ultimatestacker.stackable.entity.EntityStackManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntityTransformEvent;
import org.bukkit.event.world.EntitiesLoadEvent;
import java.util.Arrays;
import java.util.List;
public class EntityCurrentListener implements Listener {
@ -20,13 +28,16 @@ public class EntityCurrentListener implements Listener {
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onSpawn(EntityTransformEvent event) {
EntityStackManager stackManager = plugin.getEntityStackManager();
if (stackManager.isStackedAndLoaded(event.getEntity().getUniqueId())
if (stackManager.isStackedEntity(event.getEntity())
&& event.getEntity() instanceof LivingEntity
&& event.getTransformedEntity() instanceof LivingEntity) {
EntityStack stack = stackManager.updateStack((LivingEntity) event.getEntity(),
(LivingEntity) event.getTransformedEntity());
if (event.getTransformReason().equals(EntityTransformEvent.TransformReason.SPLIT)) {
event.setCancelled(true);
return;
}
EntityStack stack = stackManager.updateStack((LivingEntity) event.getEntity(), (LivingEntity) event.getTransformedEntity());
if (stack == null) return;
stack.releaseHost();
stack.updateStack();
}
}
}

View File

@ -59,7 +59,7 @@ public class EntityListeners implements Listener {
EntityStackManager stackManager = plugin.getEntityStackManager();
if (!stackManager.isStackedAndLoaded(entity)) return;
if (!stackManager.isStackedEntity(entity)) return;
EntityStack stack = stackManager.getStack(entity);
@ -78,7 +78,12 @@ public class EntityListeners implements Listener {
Entity entity = event.getEntity();
if (entity instanceof LivingEntity && plugin.getEntityStackManager().isStackedAndLoaded((LivingEntity) entity)
if (!(entity instanceof LivingEntity)) return;
if (event.getDamager() instanceof Player) {
plugin.getEntityStackManager().setLastPlayerDamage(entity, (Player) event.getDamager());
}
if (plugin.getEntityStackManager().isStackedEntity(entity)
&& Settings.DISABLE_KNOCKBACK.getBoolean()
&& ((Player) event.getDamager()).getItemInHand().getEnchantmentLevel(Enchantment.KNOCKBACK) == 0) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {

View File

@ -101,6 +101,10 @@ public class LootablesManager {
looting);
}
public List<ItemStack> getItemStackDrops(LivingEntity entity, int times) {
return getDrops(entity, times).stream().map(Drop::getItemStack).collect(Collectors.toList());
}
public List<Drop> getDrops(LivingEntity entity, int times) {
return getDrops(entity, times, 3);
}
@ -848,6 +852,9 @@ public class LootablesManager {
lootManager.addLootable(new Lootable("SILVERFISH",
new LootBuilder().build()));
lootManager.addLootable(new Lootable("WOLF",
new LootBuilder().build()));
lootManager.saveLootables(true);
}

View File

@ -22,7 +22,7 @@ public class BlockStack implements Stackable, Hologramable {
// The id that identifies this stack in the database.
private int id;
private int amount = 0;
private int amount = 1;
private final CompatibleMaterial material;
private final Location location;

View File

@ -26,12 +26,20 @@ public class BlockStackManager {
return registeredBlocks.remove(roundLocation(location));
}
public BlockStack getBlock(Location location, CompatibleMaterial material) {
return this.registeredBlocks.computeIfAbsent(location, b -> new BlockStack(material, location));
public BlockStack getBlock(Location location) {
return this.registeredBlocks.get(location);
}
public BlockStack getBlock(Block block, CompatibleMaterial material) {
return this.getBlock(block.getLocation(), material);
return this.getBlock(block.getLocation());
}
public BlockStack createBlock(Location location, CompatibleMaterial material) {
return this.registeredBlocks.computeIfAbsent(location, b -> new BlockStack(material, location));
}
public BlockStack createBlock(Block block) {
return this.createBlock(block.getLocation(), CompatibleMaterial.getMaterial(block));
}
public boolean isBlock(Location location) {

View File

@ -1,169 +0,0 @@
package com.songoda.ultimatestacker.stackable.entity;
import com.songoda.core.nms.NmsManager;
import com.songoda.core.nms.nbt.NBTEntity;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.stackable.entity.custom.CustomEntity;
import com.songoda.ultimatestacker.utils.Stackable;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.Collectors;
public class ColdEntityStack implements Stackable {
protected static final UltimateStacker plugin = UltimateStacker.getInstance();
// The id to identify this stack in the database.
private int id;
// The unique id of the stacks host.
protected UUID hostUniqueId;
// These are the entities below the host.
protected final Deque<StackedEntity> stackedEntities = new ConcurrentLinkedDeque<>();
// The amount of duplicates of the host to create when loaded.
protected int createDuplicates = 0;
public ColdEntityStack(UUID hostUniqueId, int id) {
this.hostUniqueId = hostUniqueId;
this.id = id;
}
public ColdEntityStack(UUID hostUniqueId) {
this.hostUniqueId = hostUniqueId;
}
public StackedEntity addEntityToStackSilently(Entity entity) {
return addEntityToStackSilently(getStackedEntity(entity));
}
public List<StackedEntity> addRawEntitiesToStackSilently(List<LivingEntity> unstackedEntities) {
List<StackedEntity> stackedEntities = unstackedEntities.stream()
.map(this::getStackedEntity).collect(Collectors.toList());
addEntitiesToStackSilently(stackedEntities);
return stackedEntities;
}
public void addEntitiesToStackSilently(List<StackedEntity> stackedEntities) {
stackedEntities.removeIf(Objects::isNull);
this.stackedEntities.addAll(stackedEntities);
}
public synchronized StackedEntity addEntityToStackSilently(StackedEntity stackedEntity) {
if (stackedEntity == null) return null;
stackedEntities.push(stackedEntity);
return stackedEntity;
}
public void moveEntitiesFromStack(EntityStack stack, int amount) {
List<StackedEntity> stackedEntities = stack.takeEntities(amount);
this.stackedEntities.addAll(stackedEntities);
plugin.getDataManager().createStackedEntities(this, stackedEntities);
}
public List<StackedEntity> takeEntities(int amount) {
List<StackedEntity> entities = new LinkedList<>();
for (int i = 0; i < amount; i++) {
StackedEntity entity = stackedEntities.pollFirst();
if (entity != null)
entities.add(entity);
}
plugin.getDataManager().deleteStackedEntities(entities);
return entities;
}
public List<StackedEntity> takeAllEntities() {
List<StackedEntity> entities = new LinkedList<>(stackedEntities);
stackedEntities.clear();
return entities;
}
public LivingEntity takeOneAndSpawnEntity(Location location) {
if (stackedEntities.isEmpty()) return null;
NBTEntity nbtEntity = NmsManager.getNbt().newEntity();
nbtEntity.deSerialize(stackedEntities.getFirst().getSerializedEntity());
for (CustomEntity customEntity : plugin.getCustomEntityManager().getRegisteredCustomEntities()) {
String identifier = customEntity.getPluginName() + "_UltimateStacker";
if (!nbtEntity.has(identifier)) continue;
LivingEntity entity = customEntity.spawnFromIdentifier(nbtEntity.getString(identifier), location);
if (entity == null) continue;
stackedEntities.removeFirst();
plugin.getDataManager().deleteStackedEntity(entity.getUniqueId());
return entity;
}
LivingEntity newEntity = null;
for (int i = 0; i < 5; i++) {
newEntity = (LivingEntity) nbtEntity.spawn(location);
if (newEntity != null) {
stackedEntities.removeFirst();
plugin.getDataManager().deleteStackedEntity(newEntity.getUniqueId());
break;
}
}
if (newEntity == null)
plugin.getDataManager().deleteStackedEntity(hostUniqueId);
return newEntity;
}
@Override
public int getAmount() {
return stackedEntities.size() + 1;
}
@Override
public boolean isValid() {
return true;
}
public void createDuplicates(int duplicates) {
this.createDuplicates = duplicates;
}
public int getCreateDuplicates() {
return createDuplicates;
}
protected StackedEntity getStackedEntity(Entity entity) {
return getStackedEntity(entity, false);
}
protected synchronized StackedEntity getStackedEntity(Entity entity, boolean newUUID) {
if (entity == null) return null;
UUID uuid = entity.getUniqueId();
NBTEntity nbtEntity = NmsManager.getNbt().of(entity);
CustomEntity customEntity = plugin.getCustomEntityManager().getCustomEntity(entity);
if (customEntity != null)
nbtEntity.set(customEntity.getPluginName() + "_UltimateStacker", customEntity.getNBTIdentifier(entity));
return new StackedEntity(uuid, nbtEntity.serialize());
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public UUID getHostUniqueId() {
return hostUniqueId;
}
public void setHostUniqueId(UUID hostUniqueId) {
this.hostUniqueId = hostUniqueId;
}
}

View File

@ -3,9 +3,12 @@ package com.songoda.ultimatestacker.stackable.entity;
import com.songoda.core.compatibility.ServerVersion;
import com.songoda.core.lootables.loot.Drop;
import com.songoda.core.lootables.loot.DropUtils;
import com.songoda.core.nms.NmsManager;
import com.songoda.core.nms.nbt.NBTEntity;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.events.EntityStackKillEvent;
import com.songoda.ultimatestacker.settings.Settings;
import com.songoda.ultimatestacker.stackable.entity.custom.CustomEntity;
import com.songoda.ultimatestacker.utils.Async;
import com.songoda.ultimatestacker.utils.Methods;
import org.bukkit.Bukkit;
@ -20,97 +23,44 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
public class EntityStack extends ColdEntityStack {
public class EntityStack extends StackedEntity {
// This is the host entity which is not stored in serialized nbt.
private LivingEntity hostEntity;
private static final UltimateStacker plugin = UltimateStacker.getInstance();
public EntityStack(LivingEntity hostEntity) {
super(hostEntity.getUniqueId());
this.hostEntity = hostEntity;
super(hostEntity);
}
public EntityStack(LivingEntity hostEntity, ColdEntityStack coldEntityStack) {
this(hostEntity);
this.setId(coldEntityStack.getId());
this.stackedEntities.addAll(coldEntityStack.stackedEntities);
public EntityStack(LivingEntity hostEntity, int amount) {
super(hostEntity, amount);
}
public StackedEntity addEntityToStack(Entity entity) {
StackedEntity stackedEntity = addEntityToStackSilently(entity);
updateStack();
return stackedEntity;
public synchronized EntityStack addEntityToStack(int amount) {
setAmount(getAmount() + amount);
return this;
}
@Override
public List<StackedEntity> takeEntities(int amount) {
List<StackedEntity> entities = super.takeEntities(amount);
if (this.stackedEntities.isEmpty())
destroy(true);
return entities;
}
@Override
public List<StackedEntity> takeAllEntities() {
destroy(true);
return super.takeAllEntities();
}
public void updateStack() {
Async.run(() -> {
if (createDuplicates != 0) {
List<StackedEntity> stackedEntities = new ArrayList<>();
try {
for (int i = 0; i < createDuplicates; i++) {
StackedEntity entity = addEntityToStackSilently(getStackedEntity(hostEntity, true));
if (entity != null)
stackedEntities.add(entity);
}
plugin.getDataManager().createStackedEntities(this, stackedEntities);
createDuplicates = 0;
updateNametag();
} catch (Exception ignored) {
//Ignored for now
}
}
});
updateNametag();
}
public void updateNametag() {
if (hostEntity == null) {
//Delay with 1 tick to make sure the entity is loaded.
Bukkit.getScheduler().scheduleSyncDelayedTask(UltimateStacker.getInstance(), this::updateNametag, 1L);
return;
}
hostEntity.setCustomNameVisible(!Settings.HOLOGRAMS_ON_LOOK_ENTITY.getBoolean());
hostEntity.setCustomName(Methods.compileEntityName(hostEntity, getAmount()));
public synchronized EntityStack removeEntityFromStack(int amount) {
setAmount(getAmount() - amount);
return this;
}
public LivingEntity getHostEntity() {
if (hostEntity == null) {
plugin.getEntityStackManager().removeStack(this.hostUniqueId);
return null;
}
return hostEntity;
}
public StackedEntity getHostAsStackedEntity() {
return getStackedEntity(hostEntity);
}
protected void setHostEntity(LivingEntity hostEntity) {
protected synchronized void setHostEntity(LivingEntity hostEntity) {
this.hostEntity = hostEntity;
this.hostUniqueId = hostEntity.getUniqueId();
}
private void handleWholeStackDeath(LivingEntity killed, List<Drop> drops, boolean custom, int droppedExp, EntityDeathEvent event) {
plugin.getDataManager().deleteHost(this);
EntityStack stack = plugin.getEntityStackManager().getStack(killed);
// In versions 1.14 and below experience is not dropping. Because of this we are doing this ourselves.
if (ServerVersion.isServerVersionAtOrBelow(ServerVersion.V1_14)) {
Location killedLocation = killed.getLocation();
@ -133,14 +83,13 @@ public class EntityStack extends ColdEntityStack {
}
event.getDrops().clear();
plugin.getEntityStackManager().removeStack(event.getEntity());
stack.destroy();
if (killed.getKiller() == null) return;
plugin.addExp(killed.getKiller(), this);
}
private void handleSingleStackDeath(LivingEntity killed, List<Drop> drops, int droppedExp, EntityDeathEvent event) {
EntityStackManager stackManager = plugin.getEntityStackManager();
Bukkit.getPluginManager().callEvent(new EntityStackKillEvent(this, false));
Vector velocity = killed.getVelocity().clone();
@ -162,12 +111,6 @@ public class EntityStack extends ColdEntityStack {
newEntity.setVelocity(velocity);
stackManager.updateStack(killed, newEntity);
updateStack();
if (stackedEntities.isEmpty()) {
destroy();
}
}
public void onDeath(LivingEntity killed, List<Drop> drops, boolean custom, int droppedExp, EntityDeathEvent event) {
@ -177,9 +120,9 @@ public class EntityStack extends ColdEntityStack {
boolean killWholeStack = Settings.KILL_WHOLE_STACK_ON_DEATH.getBoolean()
|| plugin.getMobFile().getBoolean("Mobs." + killed.getType().name() + ".Kill Whole Stack");
if (killWholeStack && getAmount() != 1) {
if (killWholeStack && getAmount() > 1) {
handleWholeStackDeath(killed, drops, custom, droppedExp, event);
} else if (getAmount() != 1) {
} else if (getAmount() > 1) {
List<String> reasons = Settings.INSTANT_KILL.getStringList();
EntityDamageEvent lastDamageCause = killed.getLastDamageCause();
@ -195,42 +138,37 @@ public class EntityStack extends ColdEntityStack {
}
}
public void releaseHost() {
public synchronized LivingEntity takeOneAndSpawnEntity(Location location) {
if (amount <= 0) return null;
LivingEntity entity = Objects.requireNonNull(location.getWorld()).spawn(location, hostEntity.getClass());
this.hostEntity = entity;
setAmount(amount--);
updateNameTag();
return entity;
}
public synchronized void releaseHost() {
LivingEntity oldHost = hostEntity;
LivingEntity entity = takeOneAndSpawnEntity(hostEntity.getLocation());
if (!stackedEntities.isEmpty()) {
destroy(false);
if (getAmount() >= 0) {
plugin.getEntityStackManager().updateStack(oldHost, entity);
entity.setVelocity(new Vector(ThreadLocalRandom.current().nextDouble(-1, 1.01),
0, ThreadLocalRandom.current().nextDouble(-1, 1.01)).normalize().multiply(0.5));
updateNameTag();
} else {
destroy();
}
updateStack();
}
public void destroy() {
destroy(true);
}
public void destroy(boolean full) {
if (full)
plugin.getEntityStackManager().removeStack(this.hostUniqueId);
if (hostEntity != null) {
try {
hostEntity.setCustomNameVisible(false);
hostEntity.setCustomName(null);
} catch (NullPointerException ignored) {}
}
public synchronized void destroy() {
if (hostEntity == null) return;
Bukkit.getScheduler().runTask(plugin, hostEntity::remove);
hostEntity = null;
hostUniqueId = null;
}
@Override
public String toString() {
return "EntityStack{" +
"hostEntity=" + hostEntity +
", amount=" + amount +
'}';
}
}

View File

@ -7,7 +7,10 @@ import org.bukkit.ChatColor;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.FixedMetadataValue;
import java.util.Collection;
import java.util.Collections;
@ -17,157 +20,123 @@ import java.util.UUID;
public class EntityStackManager {
// These are all stacked mobs loaded into memory.
private static final Map<UUID, EntityStack> stacks = new HashMap<>();
// This will only be used for stacks that have not yet been loaded into the game.
private static final Map<UUID, ColdEntityStack> coldStacks = new HashMap<>();
private final UltimateStacker plugin;
public EntityStackManager(UltimateStacker plugin) {
this.plugin = plugin;
}
public EntityStack addStack(EntityStack stack) {
stacks.put(stack.getHostEntity().getUniqueId(), stack);
return stack;
public EntityStack createStack(LivingEntity entity, int amount) {
return new EntityStack(entity, amount);
}
public boolean isStackedEntity(Entity entity) {
return entity.hasMetadata("US_AMOUNT");
}
public int getAmount(Entity entity) {
if (!isStackedEntity(entity)) return 1;
if (entity.getMetadata("US_AMOUNT").isEmpty()) return 1;
return entity.getMetadata("US_AMOUNT").get(0).asInt();
}
public String getLastPlayerDamage(Entity entity) {
if (!entity.hasMetadata("US_LAST_PLAYER_DAMAGE")) return null;
if (entity.getMetadata("US_LAST_PLAYER_DAMAGE").isEmpty()) return null;
return entity.getMetadata("US_LAST_PLAYER_DAMAGE").get(0).asString();
}
public void setLastPlayerDamage(Entity entity, Player player) {
if (player == null) return;
if (entity == null) return;
if (entity instanceof Player) return;
entity.setMetadata("US_LAST_PLAYER_DAMAGE", new FixedMetadataValue(plugin, player.getName()));
}
public EntityStack addStack(LivingEntity entity) {
if (entity == null) return null;
EntityStack stack = new EntityStack(entity);
plugin.getDataManager().createHostEntity(stack);
stacks.put(entity.getUniqueId(), stack);
return stack;
return addStack(entity, getAmount(entity) == 1 ? 1 : getAmount(entity));
}
public EntityStack addStack(LivingEntity entity, int amount) {
if (entity == null) return null;
EntityStack stack = new EntityStack(entity);
plugin.getDataManager().createHostEntity(stack);
stacks.put(entity.getUniqueId(), stack);
stack.createDuplicates(amount - 1);
plugin.getDataManager().updateHost(stack);
stack.updateStack();
return stack;
}
@Deprecated
public EntityStack addSerializedStack(LivingEntity entity, String customName) {
if (customName != null && customName.contains(String.valueOf(ChatColor.COLOR_CHAR))) {
String name = customName.replace(String.valueOf(ChatColor.COLOR_CHAR), "")
.replace(";", "");
if (!name.contains(":")) return null;
String split = name.split(":")[0];
int amount = Methods.isInt(split) ? Integer.parseInt(split) : 0;
return addStack(entity, amount);
if (isStackedEntity(entity)) {
EntityStack stack = getStack(entity);
stack.addEntityToStack(amount);
return stack;
}
return null;
}
@Deprecated
public EntityStack addSerializedStack(LivingEntity entity) {
return addSerializedStack(entity, entity.getCustomName());
public EntityStack getStack(UUID uuid) {
Entity entity = Bukkit.getEntity(uuid);
if (entity == null) return null;
if (isStackedEntity(entity)) {
if (entity instanceof LivingEntity) {
return new EntityStack((LivingEntity) entity);
}
}
return null;
}
public EntityStack getStack(LivingEntity entity) {
EntityStack stack = getStack(entity.getUniqueId());
if (stack == null) stack = addSerializedStack(entity);
if (!isStackedEntity(entity)) return null;
return new EntityStack(entity);
}
public EntityStack decreaseStack(Entity entity) {
EntityStack stack = getStack((LivingEntity) entity);
if (stack == null) return null;
stack.removeEntityFromStack(1);
return stack;
}
public EntityStack getStack(UUID uuid) {
return stacks.get(uuid);
}
public EntityStack removeStack(Entity entity) {
return removeStack(entity.getUniqueId());
}
public EntityStack removeStack(UUID uuid) {
EntityStack stack = stacks.remove(uuid);
if (stack != null) {
plugin.getDataManager().deleteHost(stack);
stack.destroy();
}
public EntityStack decreaseStack(Entity entity, int amount) {
EntityStack stack = getStack((LivingEntity) entity);
if (stack == null) return null;
stack.removeEntityFromStack(amount);
return stack;
}
public Map<UUID, EntityStack> getStacks() {
return Collections.unmodifiableMap(stacks);
public EntityStack updateStack(LivingEntity entity) {
EntityStack stack = getStack(entity);
if (stack == null) return null;
stack.updateNameTag();
return stack;
}
/**
* Transfers the stack from one entity to another.
* (e.g. slimes split)
* @param oldEntity The old entity to transfer the stack from.
* @param newEntity The new entity to transfer the stack to.
* @return The new stack.
*/
public EntityStack transferStack(LivingEntity oldEntity, LivingEntity newEntity, boolean takeOne) {
EntityStack stack = getStack(oldEntity);
if (stack == null) return null;
EntityStack newStack = new EntityStack(newEntity, takeOne ? stack.getAmount()-1 : stack.getAmount());
stack.destroy();
return newStack;
}
public EntityStack updateStack(LivingEntity oldEntity, LivingEntity newEntity) {
EntityStack stack = stacks.remove(oldEntity.getUniqueId());
EntityStack stack = getStack(oldEntity);
if (stack == null) return null;
stack.setHostEntity(newEntity);
stacks.put(newEntity.getUniqueId(), stack);
plugin.getDataManager().updateHost(stack);
return stack;
}
@Deprecated
public boolean isStacked(UUID entity) {
return isStackedAndLoaded(entity);
}
public boolean isStackedAndLoaded(LivingEntity entity) {
return stacks.containsKey(entity.getUniqueId());
}
public boolean isStackedAndLoaded(UUID entity) {
return stacks.containsKey(entity);
}
public boolean isEntityInColdStorage(UUID entity) {
return coldStacks.containsKey(entity);
}
public boolean isEntityInColdStorage(LivingEntity entity) {
return isEntityInColdStorage(entity.getUniqueId());
}
public void loadStack(LivingEntity entity) {
ColdEntityStack coldStack = coldStacks.get(entity.getUniqueId());
if (coldStack == null) return;
EntityStack stack = new EntityStack(entity, coldStack);
stack.updateStack();
stacks.put(entity.getUniqueId(), stack);
plugin.getDataManager().updateHost(coldStack);
}
public void unloadStack(LivingEntity entity) {
EntityStack stack = stacks.get(entity.getUniqueId());
if (stack == null) return;
ColdEntityStack coldStack = new EntityStack(entity, stack);
int amount = stack.getAmount()-1;
stack.destroy();
coldStacks.put(entity.getUniqueId(), coldStack);
if (amount == 0 && newEntity != null) {
newEntity.remove();
return null;
}
return createStack(newEntity, amount);
}
public void addStacks(Collection<ColdEntityStack> entities) {
for (ColdEntityStack stack : entities)
coldStacks.put(stack.hostUniqueId, stack);
}
public ColdEntityStack addLegacyColdStack(UUID entity, int amount) {
ColdEntityStack stack = new ColdEntityStack(entity);
plugin.getDataManager().createHostEntity(stack);
stack.createDuplicates(amount - 1);
plugin.getDataManager().updateHost(stack);
coldStacks.put(entity, stack);
return stack;
}
public void tryAndLoadColdEntities() {
for (World world : Bukkit.getWorlds()) {
for (Chunk chunk : world.getLoadedChunks()) {
for (Entity entity : chunk.getEntities()) {
if (entity instanceof LivingEntity)
loadStack((LivingEntity)entity);
}
}
public void setStack(LivingEntity newEntity, int amount) {
if (isStackedEntity(newEntity)) {
EntityStack stack = getStack(newEntity);
stack.setAmount(amount);
} else {
createStack(newEntity, amount);
}
}
}

View File

@ -1,22 +1,78 @@
package com.songoda.ultimatestacker.stackable.entity;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.settings.Settings;
import com.songoda.ultimatestacker.utils.Methods;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.metadata.FixedMetadataValue;
import java.util.UUID;
public class StackedEntity {
private final UUID uniqueId;
private final byte[] serializedEntity;
protected int amount;
protected LivingEntity hostEntity;
public StackedEntity(UUID uniqueId, byte[] serializedEntity) {
this.uniqueId = uniqueId;
this.serializedEntity = serializedEntity;
/**
* Gets an existing stack from an entity or creates a new one if it doesn't exist.
* @param entity The entity to get the stack from.
*/
public StackedEntity(LivingEntity entity) {
if (entity == null) return;
if (!UltimateStacker.getInstance().getEntityStackManager().isStackedEntity(entity)) {
entity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), 1));
this.amount = 1;
updateNameTag();
} else {
//get the amount from the entity
this.amount = UltimateStacker.getInstance().getEntityStackManager().getAmount(entity);
}
this.hostEntity = entity;
}
public UUID getUniqueId() {
return uniqueId;
/**
* Creates a new stack or overrides an existing stack.
* @param entity The entity to create the stack for.
* @param amount The amount of entities in the stack.
*/
public StackedEntity(LivingEntity entity, int amount) {
if (entity == null) return;
this.hostEntity = entity;
this.amount = amount;
entity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), amount));
updateNameTag();
}
public byte[] getSerializedEntity() {
return serializedEntity;
public EntityType getType() {
return hostEntity.getType();
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
this.hostEntity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), amount));
updateNameTag();
}
public UUID getUuid() {
return hostEntity.getUniqueId();
}
public LivingEntity getHostEntity() {
return hostEntity;
}
protected void updateNameTag() {
if (hostEntity == null) {
return;
}
hostEntity.setCustomNameVisible(!Settings.HOLOGRAMS_ON_LOOK_ENTITY.getBoolean());
hostEntity.setCustomName(Methods.compileEntityName(hostEntity, getAmount()));
}
}

View File

@ -2,8 +2,10 @@ package com.songoda.ultimatestacker.stackable.entity.custom;
import com.songoda.ultimatestacker.settings.Settings;
import com.songoda.ultimatestacker.stackable.entity.custom.entities.MythicMobsCustomEntity;
import com.songoda.ultimatestacker.stackable.entity.custom.entities.MythicMobsCustomEntityLegacy;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.Collections;
@ -18,8 +20,14 @@ public class CustomEntityManager {
private final List<CustomEntity> registeredCustomEntities = new ArrayList<>();
public void load() {
if (isEnabled("MythicMobs"))
registeredCustomEntities.add(new MythicMobsCustomEntity());
if (isEnabled("MythicMobs")) {
Plugin plugin = Bukkit.getPluginManager().getPlugin("MythicMobs");
if (plugin.getDescription().getVersion().startsWith("4.")) {
registeredCustomEntities.add(new MythicMobsCustomEntityLegacy());
} else {
registeredCustomEntities.add(new MythicMobsCustomEntity());
}
}
}
public boolean isEnabled(String plugin) {
@ -46,4 +54,11 @@ public class CustomEntityManager {
public boolean isCustomEntity(Entity entity) {
return getCustomEntity(entity) != null && getCustomEntity(entity).isCustomEntity(entity);
}
public boolean isStackable(Entity entity) {
CustomEntity customEntity = getCustomEntity(entity);
if (customEntity == null) return true;
String key = customEntity.getPluginName().toLowerCase() + "_" + customEntity.getNBTIdentifier(entity).toLowerCase();
return !Settings.BLACKLISTED_CUSTOM_ENTITIES.getStringList().contains(key);
}
}

View File

@ -11,7 +11,7 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
public class MythicMobsCustomEntity extends CustomEntity {
public class MythicMobsCustomEntity extends MythicMobsProvider {
public MythicMobsCustomEntity() {
super(Bukkit.getPluginManager().getPlugin("MythicMobs"));
@ -38,7 +38,7 @@ public class MythicMobsCustomEntity extends CustomEntity {
@Override
public boolean isSimilar(LivingEntity original, LivingEntity entity) {
if (!isMatchingType(original) || !isMatchingType(entity) || getMob(entity) == null) return false;
if (!isMatchingType(original) || getMob(entity) == null) return false;
return getMob(original).getType().equals(getMob(entity).getType());
}

View File

@ -0,0 +1,128 @@
package com.songoda.ultimatestacker.stackable.entity.custom.entities;
import com.songoda.ultimatestacker.stackable.entity.custom.CustomEntity;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.UUID;
public class MythicMobsCustomEntityLegacy extends MythicMobsProvider {
public MythicMobsCustomEntityLegacy() {
super(Bukkit.getPluginManager().getPlugin("MythicMobs"));
}
@Override
public String getPluginName() {
return "MythicMobs";
}
@Override
public boolean isMatchingType(Entity entity) {
try {
return (boolean) getMobManager().getClass().getMethod("isActiveMob", UUID.class).invoke(getMobManager(), entity.getUniqueId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String getDisplayName(Entity entity) {
try {
Object mob = getMob(entity);
if (mob == null) return null;
Object type = mob.getClass().getMethod("getType").invoke(mob);
return type.getClass().getMethod("getDisplayName").invoke(type).toString();
} catch (Exception e) {
return null;
}
}
@Override
public boolean isSimilar(LivingEntity original, LivingEntity entity) {
if (!isMatchingType(original) || getMob(entity) == null) return false;
try {
Object originalMob = getMob(original);
Object mob = getMob(entity);
return originalMob.getClass().getMethod("getType").invoke(originalMob).equals(mob.getClass().getMethod("getType").invoke(mob));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String getNBTIdentifier(Entity entity) {
try {
Object mob = getMob(entity);
Object type = mob.getClass().getMethod("getType").invoke(mob);
return getInternalName(type);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public LivingEntity spawnFromIdentifier(String string, Location location) {
return getMobTypes().stream().map(type -> {
try {
if (getInternalName(type).equals(string)) {
return (LivingEntity) getMobManager().getClass().getMethod("spawnMob", String.class, Location.class).invoke(getMobManager(), getInternalName(type), location);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}).filter(Objects::nonNull).findFirst().orElse(null);
}
@Override
public boolean isCustomEntity(Entity entity) {
return getMob(entity) != null;
}
private Object getMob(Entity entity) {
try {
return getMobManager().getClass().getMethod("getMythicMobInstance", Entity.class).invoke(getMobManager(), entity);
} catch (Exception e) {
return null;
}
}
private String getInternalName(Object type) {
try {
return type.getClass().getMethod("getInternalName").invoke(type).toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private Collection<Object> getMobTypes() {
try {
Method getMobTypes = getMobManager().getClass().getMethod("getMobTypes");
return (Collection<Object>) getMobTypes.invoke(getMobManager());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private Object getMobManager() {
try {
Field mobManager = plugin.getClass().getDeclaredField("mobManager");
mobManager.setAccessible(true);
return mobManager.get(plugin);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,11 @@
package com.songoda.ultimatestacker.stackable.entity.custom.entities;
import com.songoda.ultimatestacker.stackable.entity.custom.CustomEntity;
import org.bukkit.plugin.Plugin;
public abstract class MythicMobsProvider extends CustomEntity {
protected MythicMobsProvider(Plugin plugin) {
super(plugin);
}
}

View File

@ -14,6 +14,7 @@ import com.songoda.ultimatestacker.stackable.entity.custom.CustomEntity;
import com.songoda.ultimatestacker.utils.Async;
import com.songoda.ultimatestacker.utils.CachedChunk;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
@ -44,33 +45,40 @@ import org.bukkit.entity.TropicalFish;
import org.bukkit.entity.Villager;
import org.bukkit.entity.Wolf;
import org.bukkit.entity.Zombie;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class StackingTask extends BukkitRunnable {
public class StackingTask extends TimerTask {
private final UltimateStacker plugin;
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
private final EntityStackManager stackManager;
private final ConfigurationSection configurationSection = UltimateStacker.getInstance().getMobFile();
private final List<UUID> processed = new ArrayList<>();
private final Map<CachedChunk, Entity[]> cachedChunks = new HashMap<>();
private final Map<EntityType, Integer> entityStackSizes = new HashMap<>();
private final int maxEntityStackSize = Settings.MAX_STACK_ENTITIES.getInt(),
minEntityStackSize = Settings.MIN_STACK_ENTITIES.getInt(),
@ -90,55 +98,64 @@ public class StackingTask extends BukkitRunnable {
public StackingTask(UltimateStacker plugin) {
this.plugin = plugin;
this.stackManager = plugin.getEntityStackManager();
// Add loaded worlds.
for (World world : Bukkit.getWorlds())
loadedWorlds.add(new SWorld(world));
// Start the stacking task.
runTaskTimerAsynchronously(plugin, 0, Settings.STACK_SEARCH_TICK_SPEED.getInt());
//runTaskTimerAsynchronously(plugin, 0, Settings.STACK_SEARCH_TICK_SPEED.getInt());
executorService.scheduleAtFixedRate(this, 0, (Settings.STACK_SEARCH_TICK_SPEED.getInt()*50L), TimeUnit.MILLISECONDS);
}
public void stop() {
executorService.shutdown();
}
@Override
public void run() {
// Should entities be stacked?
if (!Settings.STACK_ENTITIES.getBoolean()) return;
//make sure if the task running if any error occurs
try {
// Should entities be stacked?
if (!Settings.STACK_ENTITIES.getBoolean()) return;
// Loop through each world.
for (SWorld sWorld : loadedWorlds) {
// If world is disabled then continue to the next world.
if (isWorldDisabled(sWorld.getWorld())) continue;
// Loop through each world.
for (SWorld sWorld : loadedWorlds) {
// If world is disabled then continue to the next world.
if (isWorldDisabled(sWorld.getWorld())) continue;
// Get the loaded entities from the current world and reverse them.
List<LivingEntity> entities;
try {
entities = getLivingEntitiesSync(sWorld).get();
} catch (ExecutionException | InterruptedException ex) {
ex.printStackTrace();
continue;
}
Collections.reverse(entities);
// Loop through the entities.
for (LivingEntity entity : entities) {
// Get entity location to pass around as its faster this way.
Location location = entity.getLocation();
// Check to see if entity is not stackable.
if (!isEntityStackable(entity))
// Get the loaded entities from the current world and reverse them.
List<LivingEntity> entities;
try {
entities = getLivingEntitiesSync(sWorld).get();
} catch (ExecutionException | InterruptedException ex) {
ex.printStackTrace();
continue;
}
Collections.reverse(entities);
// Make sure our entity has not already been processed.
// Skip it if it has been.
if (this.processed.contains(entity.getUniqueId())) continue;
Bukkit.getScheduler().runTask(plugin, () -> {
// Loop through the entities.
for (LivingEntity entity : entities) {
// Make sure our entity has not already been processed.
// Skip it if it has been.
if (this.processed.contains(entity.getUniqueId())) continue;
// Process the entity.
this.processEntity(entity, sWorld, location);
// Check to see if entity is not stackable.
if (!isEntityStackable(entity)) {
continue;
}
// Get entity location to pass around as its faster this way.
Location location = entity.getLocation();
// Process the entity.
this.processEntity(entity, sWorld, location);
}
});
}
}
// Clear caches in preparation for the next run.
this.processed.clear();
this.cachedChunks.clear();
// Clear caches in preparation for the next run.
this.processed.clear();
} catch (Exception ignored) {}
}
private Future<List<LivingEntity>> getLivingEntitiesSync(SWorld sWorld) {
@ -150,8 +167,7 @@ public class StackingTask extends BukkitRunnable {
private Future<Entity[]> getEntitiesInChunkSync(CachedChunk cachedChunk) {
CompletableFuture<Entity[]> future = new CompletableFuture<>();
Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> future.complete(cachedChunk.getEntities()));
Bukkit.getScheduler().runTask(this.plugin, () -> future.complete(cachedChunk.getEntities()));
return future;
}
@ -179,9 +195,10 @@ public class StackingTask extends BukkitRunnable {
// If only stack from spawners is enabled, make sure the entity spawned from a spawner.
if (!"SPAWNER".equals(spawnReason))
return false;
} else if (!(stackReasons = this.stackReasons).isEmpty() && !stackReasons.contains(spawnReason))
} else if (!(stackReasons = this.stackReasons).isEmpty() && !stackReasons.contains(spawnReason)) {
// Only stack if on the list of events to stack
return false;
}
// Cast our entity to living entity.
LivingEntity livingEntity = (LivingEntity) entity;
@ -197,32 +214,48 @@ public class StackingTask extends BukkitRunnable {
}
private void processEntity(LivingEntity livingEntity, SWorld sWorld, Location location) {
private void processEntity(LivingEntity baseEntity, SWorld sWorld, Location location) {
// Check our WorldGuard flag.
Boolean flag = WorldGuardHook.isEnabled() ? WorldGuardHook.getBooleanFlag(baseEntity.getLocation(), "mob-stacking") : null;
if (flag != null && !flag) {
return;
}
// Get the stack from the entity. It should be noted that this value will
// be null if our entity is not a stack.
EntityStack stack = plugin.getEntityStackManager().getStack(livingEntity);
// Is this entity stacked?
boolean isStack = stack != null;
// The amount that is stackable.
int amountToStack = isStack ? stack.getAmount() : 1;
// Attempt to split our stack. If the split is successful then skip this entity.
if (isStack && attemptSplit(stack, livingEntity)) return;
// If this entity is named, a custom entity or disabled then skip it.
if (!isStack && (livingEntity.getCustomName() != null
&& plugin.getCustomEntityManager().getCustomEntity(livingEntity) == null)
|| !configurationSection.getBoolean("Mobs." + livingEntity.getType().name() + ".Enabled"))
return;
EntityStack baseStack = plugin.getEntityStackManager().getStack(baseEntity);
// Get the maximum stack size for this entity.
int maxEntityStackSize = getEntityStackSize(livingEntity);
int maxEntityStackSize = getEntityStackSize(baseEntity);
// Is this entity stacked?
boolean isStack = baseStack != null;
if (isStack && baseStack.getAmount() == maxEntityStackSize) {
// If the stack is already at the max size then we can skip it.
processed.add(baseEntity.getUniqueId());
return;
}
// The amount that is stackable.
int amountToStack = isStack ? baseStack.getAmount() : 1;
// Attempt to split our stack. If the split is successful then skip this entity.
if (isStack && attemptSplit(baseStack, baseEntity)) return;
// If this entity is named, a custom entity or disabled then skip it.
if (!isStack && (baseEntity.getCustomName() != null
&& plugin.getCustomEntityManager().getCustomEntity(baseEntity) == null)
|| !configurationSection.getBoolean("Mobs." + baseEntity.getType().name() + ".Enabled")) {
processed.add(baseEntity.getUniqueId());
return;
}
// Get similar entities around our entity and make sure those entities are both compatible and stackable.
List<LivingEntity> stackableFriends = new LinkedList<>();
for (LivingEntity entity : getSimilarEntitiesAroundEntity(livingEntity, sWorld, location)) {
List<LivingEntity> list = getSimilarEntitiesAroundEntity(baseEntity, sWorld, location);
for (LivingEntity entity : list) {
// Check to see if entity is not stackable.
if (!isEntityStackable(entity))
continue;
@ -231,161 +264,65 @@ public class StackingTask extends BukkitRunnable {
}
// Loop through our similar stackable entities.
for (LivingEntity entity : stackableFriends) {
// Make sure the entity has not already been processed.
if (this.processed.contains(entity.getUniqueId())) continue;
// Check our WorldGuard flag.
Boolean flag = WorldGuardHook.isEnabled() ? WorldGuardHook.getBooleanFlag(livingEntity.getLocation(), "mob-stacking") : null;
if (flag != null && !flag)
continue;
for (LivingEntity friendlyEntity : stackableFriends) {
// Make sure the friendlyEntity has not already been processed.
if (this.processed.contains(friendlyEntity.getUniqueId())) continue;
// Get this entities friendStack.
EntityStack friendStack = stackManager.getStack(entity);
EntityStack friendStack = stackManager.getStack(friendlyEntity);
int amount = friendStack != null ? friendStack.getAmount() : 1;
// Check to see if this entity is stacked and friendStack plus
// Check to see if this friendlyEntity is stacked and friendStack plus
// our amount to stack is not above our max friendStack size
// for this entity.
if (friendStack != null && (friendStack.getAmount() + amountToStack) <= maxEntityStackSize) {
// for this friendlyEntity.
// If we are a stack lets merge our stack with the just found friend stack.
if (isStack) {
// Get the host entity.
StackedEntity host = stack.getHostAsStackedEntity();
// Get all the stacked entities in our stack and add them to a list.
List<StackedEntity> entities = stack.takeAllEntities();
// Add the host to this list.
entities.add(host);
// Add the collected entities to the new stack.
friendStack.addEntitiesToStackSilently(entities);
// Update friend stack to display changes.
friendStack.updateStack();
// Push changes to the database.
plugin.getDataManager().createStackedEntities(friendStack, entities);
} else {
// If we are not stacked add ourselves to the found friendStack.
plugin.getDataManager().createStackedEntity(friendStack, friendStack.addEntityToStack(livingEntity));
}
boolean overstack = (amount + amountToStack) > maxEntityStackSize;
// Drop lead if applicable then remove our entity and mark it as processed.
if (livingEntity.isLeashed())
Bukkit.getScheduler().runTask(plugin, () -> livingEntity.getWorld()
.dropItemNaturally(livingEntity.getLocation(), CompatibleMaterial.LEAD.getItem()));
Bukkit.getScheduler().runTask(plugin, livingEntity::remove);
processed.add(livingEntity.getUniqueId());
if (!overstack) {
stackManager.createStack(friendlyEntity, amount + amountToStack);
processed.add(baseEntity.getUniqueId());
return;
} else if (friendStack == null
&& isStack
&& (stack.getAmount() + 1) <= maxEntityStackSize
&& canFly(entity)
&& Settings.ONLY_STACK_FLYING_DOWN.getBoolean()
&& location.getY() > entity.getLocation().getY()) {
// Make the friend the new stack host.
EntityStack newStack = stackManager.updateStack(livingEntity, entity);
if (newStack == null) {
continue;
}
// Add our entity to that stack
plugin.getDataManager().createStackedEntity(newStack, newStack.addEntityToStack(livingEntity));
// Remove our entity and mark it as processed.
Bukkit.getScheduler().runTask(plugin, livingEntity::remove);
processed.add(livingEntity.getUniqueId());
Bukkit.getScheduler().runTask(plugin, () -> {
if (baseEntity.isLeashed()) {
baseEntity.getWorld().dropItemNaturally(baseEntity.getLocation(), CompatibleMaterial.LEAD.getItem());
}
baseEntity.remove();
});
return;
}
}
// If our entity is stacked then skip this entity.
if (isStack) return;
// Check our WorldGuard flag.
Boolean flag = WorldGuardHook.isEnabled() ? WorldGuardHook.getBooleanFlag(livingEntity.getLocation(), "mob-stacking") : null;
if (flag != null && !flag)
return;
// Remove all stacked entities from our stackable friends.
stackableFriends.removeIf(stackManager::isStackedAndLoaded);
// If the stack cap is met then delete this entity.
if (maxPerTypeStacksPerChunk != -1
&& (getSimilarStacksInChunk(sWorld, livingEntity) + 1) > maxPerTypeStacksPerChunk) {
Bukkit.getScheduler().runTask(plugin, livingEntity::remove);
this.processed.add(livingEntity.getUniqueId());
return;
}
// If there are none or not enough stackable friends left to create a new entity,
// the stack sizes overlap then skip this entity.
if (stackableFriends.isEmpty()
|| stackableFriends.size() < minEntityStackSize - 1
|| minEntityStackSize > maxEntityStackSize) return;
// If a stack was never found create a new one.
EntityStack newStack = stackManager.addStack(livingEntity);
List<LivingEntity> livingEntities = new LinkedList<>();
// Loop through the unstacked and unprocessed stackable friends while not creating
// a stack larger than the maximum.
stackableFriends.stream().filter(entity -> !stackManager.isStackedAndLoaded(entity)
&& !this.processed.contains(entity.getUniqueId())).limit(maxEntityStackSize).forEach(entity -> {
// Make sure we're not naming some poor kids pet.
if (entity.getCustomName() != null
&& plugin.getCustomEntityManager().getCustomEntity(entity) == null) {
processed.add(livingEntity.getUniqueId());
newStack.destroy();
return;
}
// Drop lead if applicable then remove our entity and mark it as processed.
if (entity.isLeashed()) {
Bukkit.getScheduler().runTask(plugin, () -> entity.getWorld().dropItemNaturally(entity.getLocation(), CompatibleMaterial.LEAD.getItem()));
}
livingEntities.add(entity);
Bukkit.getScheduler().runTask(plugin, entity::remove);
processed.add(entity.getUniqueId());
});
// Add our new approved entities to the new stack and commit them to the database.
plugin.getDataManager().createStackedEntities(newStack,
newStack.addRawEntitiesToStackSilently(livingEntities));
// Update our stack.
newStack.updateStack();
}
public boolean attemptSplit(EntityStack stack, LivingEntity livingEntity) {
int stackSize = stack.getAmount();
public boolean attemptSplit(EntityStack baseStack, LivingEntity livingEntity) {
int stackSize = baseStack.getAmount();
int maxEntityStackAmount = getEntityStackSize(livingEntity);
if (stackSize <= maxEntityStackAmount) return false;
// Destroy the stack.
stack.destroy();
baseStack.setAmount(maxEntityStackAmount);
Bukkit.getScheduler().runTask(plugin, () -> {
for (int i = stackSize; i > 0; i -= maxEntityStackAmount) {
LivingEntity entity = stack.takeOneAndSpawnEntity(livingEntity.getLocation());
if (entity == null) continue;
EntityStack newStack = plugin.getEntityStackManager().addStack(entity);
newStack.moveEntitiesFromStack(stack, Math.min(i, maxEntityStackAmount) - 1);
newStack.updateStack();
}
int finalStackSize = stackSize - maxEntityStackAmount;
do {
// Create a new stack, summon entity and add to stack.
LivingEntity newEntity = (LivingEntity) livingEntity.getWorld().spawnEntity(livingEntity.getLocation(), livingEntity.getType());
int toAdd = Math.min(finalStackSize, maxEntityStackAmount);
EntityStack newStack = stackManager.createStack(newEntity, toAdd);
processed.add(newEntity.getUniqueId());
finalStackSize -= maxEntityStackAmount;
} while (finalStackSize >= 0);
});
// Remove our entity and mark it as processed.
Bukkit.getScheduler().runTask(plugin, livingEntity::remove);
//Mark it as processed.
processed.add(livingEntity.getUniqueId());
return true;
}
private Set<CachedChunk> getNearbyChunks(SWorld sWorld, Location location, double radius, boolean singleChunk) {
//get current chunk
if (radius == -1) {
return new HashSet<>(Collections.singletonList(new CachedChunk(sWorld, location.getChunk().getX(), location.getChunk().getZ())));
}
World world = location.getWorld();
Set<CachedChunk> chunks = new HashSet<>();
if (world == null) return chunks;
@ -409,305 +346,289 @@ public class StackingTask extends BukkitRunnable {
return chunks;
}
private List<LivingEntity> getNearbyEntities(SWorld sWorld, Location location, double radius, boolean singleChunk) {
/**
* Get all entities around an entity within a radius which are similar to the entity.
* @param entity The entity to get similar entities around.
* @param radius The radius to get entities around.
* @param singleChunk Whether to only get entities in the same chunk as the entity.
* @return A list of similar entities around the entity.
*/
private List<LivingEntity> getFriendlyStacksNearby(LivingEntity entity, double radius, boolean singleChunk) {
List<LivingEntity> entities = new ArrayList<>();
for (CachedChunk chunk : getNearbyChunks(sWorld, location, radius, singleChunk)) {
if (chunk == null) continue;
Entity[] entityArray;
if (cachedChunks.containsKey(chunk)) {
entityArray = cachedChunks.get(chunk);
} else {
try {
entityArray = getEntitiesInChunkSync(chunk).get();
cachedChunks.put(chunk, entityArray);
} catch (ExecutionException | InterruptedException ex) {
ex.printStackTrace();
continue;
try {
Set<CachedChunk> chunks = getNearbyChunks(new SWorld(entity.getWorld()), entity.getLocation(), radius, singleChunk);
for (CachedChunk chunk : chunks) {
Entity[] entityList = chunk.getEntities();
for (Entity e : entityList) {
if (!processed.contains(e.getUniqueId()) && e.getType() == entity.getType() && e instanceof LivingEntity && e.isValid() && e.getLocation().distance(entity.getLocation()) <= radius) {
entities.add((LivingEntity) e);
}
}
}
if (entityArray == null) continue;
for (Entity e : entityArray) {
if (e == null) continue;
if (e.getWorld() != location.getWorld()
|| !(e instanceof LivingEntity)
|| (!singleChunk && location.distanceSquared(e.getLocation()) >= radius * radius)) continue;
entities.add((LivingEntity) e);
}
entities.removeIf(entity1 -> entity1.equals(entity) || !UltimateStacker.getInstance().getCustomEntityManager().isStackable(entity1));
} catch (Exception ex) {
ex.printStackTrace();
}
return entities;
}
public int getSimilarStacksInChunk(SWorld sWorld, LivingEntity entity) {
int count = 0;
for (LivingEntity e : getNearbyEntities(sWorld, entity.getLocation(), -1, true)) {
if (entity.getType() == e.getType() && plugin.getEntityStackManager().isStackedAndLoaded(e))
count++;
}
return count;
}
public List<LivingEntity> getSimilarEntitiesAroundEntity(LivingEntity initialEntity, SWorld sWorld, Location location) {
// Create a list of all entities around the initial entity of the same type.
List<LivingEntity> entityList = new LinkedList<>();
try {
// Create a list of all entities around the initial entity of the same type.
List<LivingEntity> entityList = new LinkedList<>(getFriendlyStacksNearby(initialEntity, searchRadius, stackWholeChunk));
for (LivingEntity entity : getNearbyEntities(sWorld, location, searchRadius, stackWholeChunk)) {
if (entity.getType() != initialEntity.getType() || entity == initialEntity)
continue;
entityList.add(entity);
}
CustomEntity customEntity = plugin.getCustomEntityManager().getCustomEntity(initialEntity);
if (customEntity != null)
entityList.removeIf(entity -> !customEntity.isSimilar(initialEntity, entity));
CustomEntity customEntity = plugin.getCustomEntityManager().getCustomEntity(initialEntity);
if (customEntity != null)
entityList.removeIf(entity -> !customEntity.isSimilar(initialEntity, entity));
if (stackFlyingDown && canFly(initialEntity))
entityList.removeIf(entity -> entity.getLocation().getY() > initialEntity.getLocation().getY());
if (stackFlyingDown && canFly(initialEntity))
entityList.removeIf(entity -> entity.getLocation().getY() > initialEntity.getLocation().getY());
for (Check check : checks) {
if (check == null) continue;
switch (check) {
case SPAWN_REASON: {
if (initialEntity.hasMetadata("US_REASON"))
entityList.removeIf(entity -> entity.hasMetadata("US_REASON") && !entity.getMetadata("US_REASON").get(0).asString().equals("US_REASON"));
}
case AGE: {
if (!(initialEntity instanceof Ageable)) break;
if (((Ageable) initialEntity).isAdult()) {
entityList.removeIf(entity -> !((Ageable) entity).isAdult());
} else {
entityList.removeIf(entity -> ((Ageable) entity).isAdult());
for (Check check : checks) {
if (check == null) continue;
switch (check) {
case SPAWN_REASON: {
if (initialEntity.hasMetadata("US_REASON"))
entityList.removeIf(entity -> entity.hasMetadata("US_REASON") && !entity.getMetadata("US_REASON").get(0).asString().equals("US_REASON"));
}
break;
}
case NERFED: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9)) break;
entityList.removeIf(entity -> entity.hasAI() != initialEntity.hasAI());
}
case IS_TAMED: {
if (!(initialEntity instanceof Tameable)) break;
if (((Tameable) initialEntity).isTamed()) {
entityList.removeIf(entity -> !((Tameable) entity).isTamed());
} else {
entityList.removeIf(entity -> ((Tameable) entity).isTamed());
}
}
case ANIMAL_OWNER: {
if (!(initialEntity instanceof Tameable)) break;
case AGE: {
if (!(initialEntity instanceof Ageable)) break;
Tameable tameable = ((Tameable) initialEntity);
entityList.removeIf(entity -> ((Tameable) entity).getOwner() != tameable.getOwner());
}
case PIG_SADDLE: {
if (!(initialEntity instanceof Pig)) break;
entityList.removeIf(entity -> ((Pig) entity).hasSaddle());
break;
}
case SKELETON_TYPE: {
if (!(initialEntity instanceof Skeleton)) break;
Skeleton skeleton = (Skeleton) initialEntity;
entityList.removeIf(entity -> ((Skeleton) entity).getSkeletonType() != skeleton.getSkeletonType());
break;
}
case SHEEP_COLOR: {
if (!(initialEntity instanceof Sheep)) break;
Sheep sheep = ((Sheep) initialEntity);
entityList.removeIf(entity -> ((Sheep) entity).getColor() != sheep.getColor());
break;
}
case SHEEP_SHEARED: {
if (!(initialEntity instanceof Sheep)) break;
Sheep sheep = ((Sheep) initialEntity);
if (sheep.isSheared()) {
entityList.removeIf(entity -> !((Sheep) entity).isSheared());
} else {
entityList.removeIf(entity -> ((Sheep) entity).isSheared());
}
break;
}
case SNOWMAN_DERPED: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9)
|| !(initialEntity instanceof Snowman)) break;
Snowman snowman = ((Snowman) initialEntity);
if (snowman.isDerp()) {
entityList.removeIf(entity -> !((Snowman) entity).isDerp());
} else {
entityList.removeIf(entity -> ((Snowman) entity).isDerp());
}
break;
}
case LLAMA_COLOR: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)
|| !(initialEntity instanceof Llama)) break;
Llama llama = ((Llama) initialEntity);
entityList.removeIf(entity -> ((Llama) entity).getColor() != llama.getColor());
break;
}
case LLAMA_STRENGTH: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)
|| !(initialEntity instanceof Llama)) break;
Llama llama = ((Llama) initialEntity);
entityList.removeIf(entity -> ((Llama) entity).getStrength() != llama.getStrength());
break;
}
case VILLAGER_PROFESSION: {
if (!(initialEntity instanceof Villager)) break;
Villager villager = ((Villager) initialEntity);
entityList.removeIf(entity -> ((Villager) entity).getProfession() != villager.getProfession());
break;
}
case SLIME_SIZE: {
if (!(initialEntity instanceof Slime)) break;
Slime slime = ((Slime) initialEntity);
entityList.removeIf(entity -> ((Slime) entity).getSize() != slime.getSize());
break;
}
case HORSE_CARRYING_CHEST: {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) {
if (!(initialEntity instanceof ChestedHorse)) break;
entityList.removeIf(entity -> ((ChestedHorse) entity).isCarryingChest());
} else {
if (!(initialEntity instanceof Horse)) break;
entityList.removeIf(entity -> ((Horse) entity).isCarryingChest());
}
break;
}
case HORSE_HAS_ARMOR: {
if (!(initialEntity instanceof Horse)) break;
entityList.removeIf(entity -> ((Horse) entity).getInventory().getArmor() != null);
break;
}
case HORSE_HAS_SADDLE: {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
&& initialEntity instanceof AbstractHorse) {
entityList.removeIf(entity -> ((AbstractHorse) entity).getInventory().getSaddle() != null);
if (((Ageable) initialEntity).isAdult()) {
entityList.removeIf(entity -> !((Ageable) entity).isAdult());
} else {
entityList.removeIf(entity -> ((Ageable) entity).isAdult());
}
break;
}
if (!(initialEntity instanceof Horse)) break;
entityList.removeIf(entity -> ((Horse) entity).getInventory().getSaddle() != null);
break;
}
case HORSE_JUMP: {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) {
if (!(initialEntity instanceof AbstractHorse)) break;
AbstractHorse horse = ((AbstractHorse) initialEntity);
entityList.removeIf(entity -> ((AbstractHorse) entity).getJumpStrength() != horse.getJumpStrength());
} else {
case NERFED: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9)) break;
entityList.removeIf(entity -> entity.hasAI() != initialEntity.hasAI());
}
case IS_TAMED: {
if (!(initialEntity instanceof Tameable)) break;
if (((Tameable) initialEntity).isTamed()) {
entityList.removeIf(entity -> !((Tameable) entity).isTamed());
} else {
entityList.removeIf(entity -> ((Tameable) entity).isTamed());
}
}
case ANIMAL_OWNER: {
if (!(initialEntity instanceof Tameable)) break;
Tameable tameable = ((Tameable) initialEntity);
entityList.removeIf(entity -> ((Tameable) entity).getOwner() != tameable.getOwner());
}
case PIG_SADDLE: {
if (!(initialEntity instanceof Pig)) break;
entityList.removeIf(entity -> ((Pig) entity).hasSaddle());
break;
}
case SKELETON_TYPE: {
if (!(initialEntity instanceof Skeleton)) break;
Skeleton skeleton = (Skeleton) initialEntity;
entityList.removeIf(entity -> ((Skeleton) entity).getSkeletonType() != skeleton.getSkeletonType());
break;
}
case SHEEP_COLOR: {
if (!(initialEntity instanceof Sheep)) break;
Sheep sheep = ((Sheep) initialEntity);
entityList.removeIf(entity -> ((Sheep) entity).getColor() != sheep.getColor());
break;
}
case SHEEP_SHEARED: {
if (!(initialEntity instanceof Sheep)) break;
Sheep sheep = ((Sheep) initialEntity);
if (sheep.isSheared()) {
entityList.removeIf(entity -> !((Sheep) entity).isSheared());
} else {
entityList.removeIf(entity -> ((Sheep) entity).isSheared());
}
break;
}
case SNOWMAN_DERPED: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9)
|| !(initialEntity instanceof Snowman)) break;
Snowman snowman = ((Snowman) initialEntity);
if (snowman.isDerp()) {
entityList.removeIf(entity -> !((Snowman) entity).isDerp());
} else {
entityList.removeIf(entity -> ((Snowman) entity).isDerp());
}
break;
}
case LLAMA_COLOR: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)
|| !(initialEntity instanceof Llama)) break;
Llama llama = ((Llama) initialEntity);
entityList.removeIf(entity -> ((Llama) entity).getColor() != llama.getColor());
break;
}
case LLAMA_STRENGTH: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)
|| !(initialEntity instanceof Llama)) break;
Llama llama = ((Llama) initialEntity);
entityList.removeIf(entity -> ((Llama) entity).getStrength() != llama.getStrength());
break;
}
case VILLAGER_PROFESSION: {
if (!(initialEntity instanceof Villager)) break;
Villager villager = ((Villager) initialEntity);
entityList.removeIf(entity -> ((Villager) entity).getProfession() != villager.getProfession());
break;
}
case SLIME_SIZE: {
if (!(initialEntity instanceof Slime)) break;
Slime slime = ((Slime) initialEntity);
entityList.removeIf(entity -> ((Slime) entity).getSize() != slime.getSize());
break;
}
case HORSE_CARRYING_CHEST: {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) {
if (!(initialEntity instanceof ChestedHorse)) break;
entityList.removeIf(entity -> ((ChestedHorse) entity).isCarryingChest());
} else {
if (!(initialEntity instanceof Horse)) break;
entityList.removeIf(entity -> ((Horse) entity).isCarryingChest());
}
break;
}
case HORSE_HAS_ARMOR: {
if (!(initialEntity instanceof Horse)) break;
entityList.removeIf(entity -> ((Horse) entity).getInventory().getArmor() != null);
break;
}
case HORSE_HAS_SADDLE: {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
&& initialEntity instanceof AbstractHorse) {
entityList.removeIf(entity -> ((AbstractHorse) entity).getInventory().getSaddle() != null);
break;
}
if (!(initialEntity instanceof Horse)) break;
entityList.removeIf(entity -> ((Horse) entity).getInventory().getSaddle() != null);
break;
}
case HORSE_JUMP: {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) {
if (!(initialEntity instanceof AbstractHorse)) break;
AbstractHorse horse = ((AbstractHorse) initialEntity);
entityList.removeIf(entity -> ((AbstractHorse) entity).getJumpStrength() != horse.getJumpStrength());
} else {
if (!(initialEntity instanceof Horse)) break;
Horse horse = ((Horse) initialEntity);
entityList.removeIf(entity -> ((Horse) entity).getJumpStrength() != horse.getJumpStrength());
}
break;
}
case HORSE_COLOR: {
if (!(initialEntity instanceof Horse)) break;
Horse horse = ((Horse) initialEntity);
entityList.removeIf(entity -> ((Horse) entity).getJumpStrength() != horse.getJumpStrength());
entityList.removeIf(entity -> ((Horse) entity).getColor() != horse.getColor());
break;
}
case HORSE_STYLE: {
if (!(initialEntity instanceof Horse)) break;
Horse horse = ((Horse) initialEntity);
entityList.removeIf(entity -> ((Horse) entity).getStyle() != horse.getStyle());
break;
}
case ZOMBIE_BABY: {
if (!(initialEntity instanceof Zombie)) break;
Zombie zombie = (Zombie) initialEntity;
entityList.removeIf(entity -> ((Zombie) entity).isBaby() != zombie.isBaby());
break;
}
case WOLF_COLLAR_COLOR: {
if (!(initialEntity instanceof Wolf)) break;
Wolf wolf = (Wolf) initialEntity;
entityList.removeIf(entity -> ((Wolf) entity).getCollarColor() != wolf.getCollarColor());
break;
}
case OCELOT_TYPE: {
if (!(initialEntity instanceof Ocelot)) break;
Ocelot ocelot = (Ocelot) initialEntity;
entityList.removeIf(entity -> ((Ocelot) entity).getCatType() != ocelot.getCatType());
}
case CAT_TYPE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14)
|| !(initialEntity instanceof Cat)) break;
Cat cat = (Cat) initialEntity;
entityList.removeIf(entity -> ((Cat) entity).getCatType() != cat.getCatType());
break;
}
case HAS_EQUIPMENT: {
if (initialEntity.getEquipment() == null) break;
boolean imEquipped = isEquipped(initialEntity);
if (imEquipped)
entityList = new ArrayList<>();
else
entityList.removeIf(this::isEquipped);
break;
}
case RABBIT_TYPE: {
if (!(initialEntity instanceof Rabbit)) break;
Rabbit rabbit = (Rabbit) initialEntity;
entityList.removeIf(entity -> ((Rabbit) entity).getRabbitType() != rabbit.getRabbitType());
break;
}
case PARROT_TYPE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_12)
|| !(initialEntity instanceof Parrot)) break;
Parrot parrot = (Parrot) initialEntity;
entityList.removeIf(entity -> ((Parrot) entity).getVariant() != parrot.getVariant());
break;
}
case PUFFERFISH_STATE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|| !(initialEntity instanceof PufferFish)) break;
PufferFish pufferFish = (PufferFish) initialEntity;
entityList.removeIf(entity -> ((PufferFish) entity).getPuffState() != pufferFish.getPuffState());
break;
}
case TROPICALFISH_PATTERN: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|| !(initialEntity instanceof TropicalFish)) break;
TropicalFish tropicalFish = (TropicalFish) initialEntity;
entityList.removeIf(entity -> ((TropicalFish) entity).getPattern() != tropicalFish.getPattern());
break;
}
case TROPICALFISH_PATTERN_COLOR: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|| !(initialEntity instanceof TropicalFish)) break;
TropicalFish tropicalFish = (TropicalFish) initialEntity;
entityList.removeIf(entity -> ((TropicalFish) entity).getPatternColor() != tropicalFish.getPatternColor());
break;
}
case TROPICALFISH_BODY_COLOR: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|| !(initialEntity instanceof TropicalFish)) break;
TropicalFish tropicalFish = (TropicalFish) initialEntity;
entityList.removeIf(entity -> ((TropicalFish) entity).getBodyColor() != tropicalFish.getBodyColor());
break;
}
case PHANTOM_SIZE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|| !(initialEntity instanceof Phantom)) break;
Phantom phantom = (Phantom) initialEntity;
entityList.removeIf(entity -> ((Phantom) entity).getSize() != phantom.getSize());
break;
}
break;
}
case HORSE_COLOR: {
if (!(initialEntity instanceof Horse)) break;
Horse horse = ((Horse) initialEntity);
entityList.removeIf(entity -> ((Horse) entity).getColor() != horse.getColor());
break;
}
case HORSE_STYLE: {
if (!(initialEntity instanceof Horse)) break;
Horse horse = ((Horse) initialEntity);
entityList.removeIf(entity -> ((Horse) entity).getStyle() != horse.getStyle());
break;
}
case ZOMBIE_BABY: {
if (!(initialEntity instanceof Zombie)) break;
Zombie zombie = (Zombie) initialEntity;
entityList.removeIf(entity -> ((Zombie) entity).isBaby() != zombie.isBaby());
break;
}
case WOLF_COLLAR_COLOR: {
if (!(initialEntity instanceof Wolf)) break;
Wolf wolf = (Wolf) initialEntity;
entityList.removeIf(entity -> ((Wolf) entity).getCollarColor() != wolf.getCollarColor());
break;
}
case OCELOT_TYPE: {
if (!(initialEntity instanceof Ocelot)) break;
Ocelot ocelot = (Ocelot) initialEntity;
entityList.removeIf(entity -> ((Ocelot) entity).getCatType() != ocelot.getCatType());
}
case CAT_TYPE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14)
|| !(initialEntity instanceof Cat)) break;
Cat cat = (Cat) initialEntity;
entityList.removeIf(entity -> ((Cat) entity).getCatType() != cat.getCatType());
break;
}
case HAS_EQUIPMENT: {
if (initialEntity.getEquipment() == null) break;
boolean imEquipped = isEquipped(initialEntity);
if (imEquipped)
entityList = new ArrayList<>();
else
entityList.removeIf(this::isEquipped);
break;
}
case RABBIT_TYPE: {
if (!(initialEntity instanceof Rabbit)) break;
Rabbit rabbit = (Rabbit) initialEntity;
entityList.removeIf(entity -> ((Rabbit) entity).getRabbitType() != rabbit.getRabbitType());
break;
}
case PARROT_TYPE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_12)
|| !(initialEntity instanceof Parrot)) break;
Parrot parrot = (Parrot) initialEntity;
entityList.removeIf(entity -> ((Parrot) entity).getVariant() != parrot.getVariant());
break;
}
case PUFFERFISH_STATE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|| !(initialEntity instanceof PufferFish)) break;
PufferFish pufferFish = (PufferFish) initialEntity;
entityList.removeIf(entity -> ((PufferFish) entity).getPuffState() != pufferFish.getPuffState());
break;
}
case TROPICALFISH_PATTERN: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|| !(initialEntity instanceof TropicalFish)) break;
TropicalFish tropicalFish = (TropicalFish) initialEntity;
entityList.removeIf(entity -> ((TropicalFish) entity).getPattern() != tropicalFish.getPattern());
break;
}
case TROPICALFISH_PATTERN_COLOR: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|| !(initialEntity instanceof TropicalFish)) break;
TropicalFish tropicalFish = (TropicalFish) initialEntity;
entityList.removeIf(entity -> ((TropicalFish) entity).getPatternColor() != tropicalFish.getPatternColor());
break;
}
case TROPICALFISH_BODY_COLOR: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|| !(initialEntity instanceof TropicalFish)) break;
TropicalFish tropicalFish = (TropicalFish) initialEntity;
entityList.removeIf(entity -> ((TropicalFish) entity).getBodyColor() != tropicalFish.getBodyColor());
break;
}
case PHANTOM_SIZE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|| !(initialEntity instanceof Phantom)) break;
Phantom phantom = (Phantom) initialEntity;
entityList.removeIf(entity -> ((Phantom) entity).getSize() != phantom.getSize());
break;
}
}
}
if (initialEntity.hasMetadata("breedCooldown")) {
entityList.removeIf(entity -> !entity.hasMetadata("breedCooldown"));
if (initialEntity.hasMetadata("breedCooldown")) {
entityList.removeIf(entity -> !entity.hasMetadata("breedCooldown"));
}
return entityList;
} catch (Exception ex) {
ex.printStackTrace();
}
return entityList;
return new ArrayList<>();
}
public boolean isEquipped(LivingEntity initialEntity) {

View File

@ -49,7 +49,7 @@ public class CachedChunk {
return new Entity[0];
}
Chunk chunk = getChunk();
return chunk == null ? new Entity[0] : sWorld.getEntitiesFromChunk(x, z);
return chunk == null ? new Entity[0] : chunk.getEntities();
}
@Override

View File

@ -1,7 +1,7 @@
name: UltimateStacker
description: UltimateStacker
version: maven-version-number
softdepend: [MythicMobs, HolographicDisplays, Holograms, CMI, WorldGuard, EpicSpawners, mcMMO, WildStacker, StackMob]
softdepend: [MythicMobs, HolographicDisplays, Holograms, DecentHolograms, CMI, WorldGuard, EpicSpawners, mcMMO, WildStacker, StackMob, BentoBox, ViaVersion]
loadbefore: [WorldGuard]
main: com.songoda.ultimatestacker.UltimateStacker
author: songoda