20 Commits

Author SHA1 Message Date
63f9d16891 1.21.5 2025-05-16 06:41:23 +02:00
c14b17a522 fix damage type actors 2025-02-26 05:43:29 +01:00
0a685bca27 store amount in a seperate field for item updates + 1.21.4 2024-12-06 04:42:13 +01:00
8c024532b2 fix npe in combination with some plugins 2024-10-04 10:40:24 +02:00
ecb281ccaa fix rare npe 2024-09-22 07:38:02 +02:00
dc9774b7c6 fix lectern logging 2024-09-21 07:41:32 +02:00
d1ecbe0efc log decorated pot and chiseled bookshelf inventory changes 2024-09-21 07:35:28 +02:00
73c221e8d0 use modern api for banner codec 2024-09-19 23:04:30 +02:00
0d04823ae1 improve rollbacks
- allow rollbacking up to 10k row/tick if the max editing time is not
exceeded
- better show percentages of long rollbacks
2024-09-19 23:04:07 +02:00
10b5bc8913 do not log explosions that do not remove blocks 2024-08-19 05:58:47 +02:00
d9a4d31509 ignore stepping on redstone ore 2024-08-10 05:15:49 +02:00
dd70021979 Minecraft 1.21 2024-07-25 06:13:40 +02:00
00bcb6ba99 replace deprecated api usage 2024-06-03 03:36:13 +02:00
659095a214 wind charges do not break blocks 2024-06-03 03:30:32 +02:00
aa206f0c7b use api, still won't work 2024-06-01 08:43:15 +02:00
1965d5f985 new logging options: GRASS_EAT and MISCENTITYCHANGEBLOCK 2024-05-31 07:33:15 +02:00
5f56f1677f no item tag logging at the moment (the method is no longer working) 2024-05-31 07:32:26 +02:00
20e70f91cd improve kill logging 2024-05-14 03:39:54 +02:00
82e6d5bf38 better entity damage cause detection (uses new api) 2024-05-14 03:28:10 +02:00
d2ec428381 1.20.6 2024-05-10 01:48:50 +02:00
35 changed files with 511 additions and 378 deletions

View File

@ -44,7 +44,7 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.20.4-R0.1-SNAPSHOT</version>
<version>1.21.5-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
@ -125,9 +125,9 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<version>3.13.0</version>
<configuration>
<release>17</release>
<release>21</release>
</configuration>
</plugin>
<plugin>
@ -153,7 +153,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<version>3.5.3</version>
<configuration>
</configuration>
<executions>

View File

@ -12,6 +12,7 @@ import static de.diddiz.LogBlock.util.TypeColor.DEFAULT;
import de.diddiz.LogBlock.blockstate.BlockStateCodecs;
import de.diddiz.LogBlock.util.BukkitUtils;
import de.diddiz.LogBlock.util.ItemStackAndAmount;
import de.diddiz.LogBlock.util.Utils;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -35,7 +36,6 @@ import org.bukkit.block.data.type.Repeater;
import org.bukkit.block.data.type.Sign;
import org.bukkit.block.data.type.Switch;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.inventory.ItemStack;
public class BlockChange implements LookupCacheElement {
public final long id, date;
@ -75,7 +75,7 @@ public class BlockChange implements LookupCacheElement {
typeState = p.needType ? rs.getBytes("typeState") : null;
ChestAccess catemp = null;
if (p.needChestAccess) {
ItemStack stack = Utils.loadItemStack(rs.getBytes("item"));
ItemStackAndAmount stack = Utils.loadItemStack(rs.getBytes("item"));
if (stack != null) {
catemp = new ChestAccess(stack, rs.getBoolean("itemremove"), rs.getInt("itemtype"));
}
@ -113,7 +113,7 @@ public class BlockChange implements LookupCacheElement {
}
@Override
public BaseComponent[] getLogMessage(int entry) {
public BaseComponent getLogMessage(int entry) {
TextComponent msg = new TextComponent();
if (date > 0) {
msg.addExtra(prettyDate(date));
@ -127,7 +127,7 @@ public class BlockChange implements LookupCacheElement {
BlockData replaced = getBlockReplaced();
if (type == null || replaced == null) {
msg.addExtra("did an unknown block modification");
return new BaseComponent[] { msg };
return msg;
}
// Process type details once for later use.
@ -254,7 +254,7 @@ public class BlockChange implements LookupCacheElement {
msg.addExtra(" at ");
msg.addExtra(prettyLocation(loc, entry));
}
return new BaseComponent[] { msg };
return msg;
}
public BlockData getBlockReplaced() {

View File

@ -39,7 +39,7 @@ public class ChatMessage implements LookupCacheElement {
}
@Override
public BaseComponent[] getLogMessage(int entry) {
public BaseComponent getLogMessage(int entry) {
TextComponent msg = new TextComponent();
if (date > 0) {
msg.addExtra(prettyDate(date));
@ -50,10 +50,8 @@ public class ChatMessage implements LookupCacheElement {
msg.addExtra(" ");
}
if (message != null) {
for (BaseComponent messageComponent : TextComponent.fromLegacyText(message)) {
msg.addExtra(messageComponent);
}
msg.addExtra(TextComponent.fromLegacy(message));
}
return new BaseComponent[] { msg };
return msg;
}
}

View File

@ -1,13 +1,13 @@
package de.diddiz.LogBlock;
import org.bukkit.inventory.ItemStack;
import de.diddiz.LogBlock.util.ItemStackAndAmount;
public class ChestAccess {
public final ItemStack itemStack;
public final ItemStackAndAmount itemStack;
public final boolean remove;
public final int itemType;
public ChestAccess(ItemStack itemStack, boolean remove, int itemType) {
public ChestAccess(ItemStackAndAmount itemStack, boolean remove, int itemType) {
this.itemStack = itemStack;
this.remove = remove;
this.itemType = itemType;

View File

@ -443,9 +443,7 @@ public class CommandsHandler implements CommandExecutor {
if (lookupElements[i].getLocation() != null) {
message.addExtra(new TextComponent("(" + (i + 1) + ") "));
}
for (BaseComponent component : lookupElements[i].getLogMessage(i + 1)) {
message.addExtra(component);
}
message.addExtra(lookupElements[i].getLogMessage(i + 1));
sender.spigot().sendMessage(message);
}
if (setSessionPage) {

View File

@ -19,6 +19,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
@ -52,6 +53,7 @@ import de.diddiz.LogBlock.config.Config;
import de.diddiz.LogBlock.events.BlockChangePreLogEvent;
import de.diddiz.LogBlock.events.EntityChangePreLogEvent;
import de.diddiz.LogBlock.util.BukkitUtils;
import de.diddiz.LogBlock.util.ItemStackAndAmount;
import de.diddiz.LogBlock.util.Utils;
public class Consumer extends Thread {
@ -208,7 +210,7 @@ public class Consumer extends Thread {
* @param remove
* true if the item was removed
*/
public void queueChestAccess(Actor actor, BlockState container, ItemStack itemStack, boolean remove) {
public void queueChestAccess(Actor actor, BlockState container, ItemStackAndAmount itemStack, boolean remove) {
if (!(container instanceof InventoryHolder)) {
throw new IllegalArgumentException("Container must be instanceof InventoryHolder");
}
@ -229,8 +231,8 @@ public class Consumer extends Thread {
* @param remove
* true if the item was removed
*/
public void queueChestAccess(Actor actor, Location loc, BlockData type, ItemStack itemStack, boolean remove) {
queueBlock(actor, loc, type, type, null, null, new ChestAccess(itemStack, remove, MaterialConverter.getOrAddMaterialId(itemStack.getType())));
public void queueChestAccess(Actor actor, Location loc, BlockData type, ItemStackAndAmount itemStack, boolean remove) {
queueBlock(actor, loc, type, type, null, null, new ChestAccess(itemStack, remove, MaterialConverter.getOrAddMaterialId(itemStack.stack().getType())));
}
/**
@ -261,8 +263,8 @@ public class Consumer extends Thread {
* The inventory of the container block
*/
public void queueContainerBreak(Actor actor, Location loc, BlockData type, Inventory inv) {
final ItemStack[] items = compressInventory(inv.getContents());
for (final ItemStack item : items) {
final Collection<ItemStackAndAmount> items = compressInventory(inv.getContents());
for (final ItemStackAndAmount item : items) {
queueChestAccess(actor, loc, type, item, true);
}
queueBlockBreak(actor, loc, type);

View File

@ -77,7 +77,7 @@ public class EntityChange implements LookupCacheElement {
}
@Override
public BaseComponent[] getLogMessage(int entry) {
public BaseComponent getLogMessage(int entry) {
TextComponent msg = new TextComponent();
if (date > 0) {
msg.addExtra(prettyDate(date));
@ -128,7 +128,7 @@ public class EntityChange implements LookupCacheElement {
msg.addExtra(" at ");
msg.addExtra(prettyLocation(loc, entry));
}
return new BaseComponent[] { msg };
return msg;
}
@Override

View File

@ -49,7 +49,7 @@ public class Kill implements LookupCacheElement {
}
@Override
public BaseComponent[] getLogMessage(int entry) {
public BaseComponent getLogMessage(int entry) {
TextComponent msg = new TextComponent();
if (date > 0) {
msg.addExtra(prettyDate(date));
@ -65,7 +65,7 @@ public class Kill implements LookupCacheElement {
msg.addExtra(" with ");
msg.addExtra(prettyItemName(MaterialConverter.getMaterial(weapon)));
}
return new BaseComponent[] { msg };
return msg;
}
public TextComponent prettyItemName(Material t) {

View File

@ -101,19 +101,11 @@ public class LogBlock extends JavaPlugin {
return;
}
final Statement st = conn.createStatement();
try {
final ResultSet rs = st.executeQuery("SHOW CHARACTER SET where charset='utf8mb4';");
if (rs.next()) {
Config.mb4 = true;
// Allegedly JDBC driver since 2010 hasn't needed this. I did.
try {
st.executeUpdate("SET NAMES utf8mb4;");
} catch (Exception ex) {
getLogger().severe("could not set names to utf8mb4: " + ex.getMessage());
}
}
} catch (Exception ex) {
getLogger().severe("could not verify character set: " + ex.getMessage());
final ResultSet rs = st.executeQuery("SHOW CHARACTER SET where charset='utf8mb4';");
if (rs.next()) {
Config.mb4 = true;
// Allegedly JDBC driver since 2010 hasn't needed this. I did.
st.executeUpdate("SET NAMES utf8mb4;");
}
conn.close();
Updater updater = new Updater(this);
@ -213,11 +205,8 @@ public class LogBlock extends JavaPlugin {
if (isLogging(Logging.CHAT) || isLogging(Logging.PLAYER_COMMANDS) || isLogging(Logging.CONSOLE_COMMANDS) || isLogging(Logging.COMMANDBLOCK_COMMANDS)) {
pm.registerEvents(new ChatLogging(this), this);
}
if (isLogging(Logging.ENDERMEN)) {
pm.registerEvents(new EndermenLogging(this), this);
}
if (isLogging(Logging.WITHER)) {
pm.registerEvents(new WitherLogging(this), this);
if (isLogging(Logging.WITHER) || isLogging(Logging.ENDERMEN)) {
pm.registerEvents(new EntityChangeBlockLogging(this), this);
}
if (isLogging(Logging.NATURALSTRUCTUREGROW)) {
pm.registerEvents(new StructureGrowLogging(this), this);

View File

@ -39,6 +39,8 @@ public enum Logging {
BAMBOOGROWTH,
WITHER(true),
WITHER_SKULL(true),
GRASS_EAT,
MISCENTITYCHANGEBLOCK(true),
BONEMEALSTRUCTUREGROW,
WORLDEDIT,
TNTMINECARTEXPLOSION(true),

View File

@ -6,11 +6,11 @@ import org.bukkit.Location;
public interface LookupCacheElement {
public Location getLocation();
public default BaseComponent[] getLogMessage() {
public default BaseComponent getLogMessage() {
return getLogMessage(-1);
}
public BaseComponent[] getLogMessage(int entry);
public BaseComponent getLogMessage(int entry);
public default int getNumChanges() {
return 1;

View File

@ -32,7 +32,7 @@ public class SummedBlockChanges implements LookupCacheElement {
}
@Override
public BaseComponent[] getLogMessage(int entry) {
public BaseComponent getLogMessage(int entry) {
return MessagingUtil.formatSummarizedChanges(created, destroyed, actor != null ? new TextComponent(actor.getName()) : prettyMaterial(Objects.toString(MaterialConverter.getMaterial(type))), 10, 10, spaceFactor);
}

View File

@ -33,7 +33,7 @@ public class SummedEntityChanges implements LookupCacheElement {
}
@Override
public BaseComponent[] getLogMessage(int entry) {
public BaseComponent getLogMessage(int entry) {
return MessagingUtil.formatSummarizedChanges(created, destroyed, actor != null ? new TextComponent(actor.getName()) : prettyMaterial(Objects.toString(EntityTypeConverter.getEntityType(type))), 10, 10, spaceFactor);
}

View File

@ -25,7 +25,7 @@ public class SummedKills implements LookupCacheElement {
}
@Override
public BaseComponent[] getLogMessage(int entry) {
public BaseComponent getLogMessage(int entry) {
return MessagingUtil.formatSummarizedChanges(kills, killed, new TextComponent(player.getName()), 6, 7, spaceFactor);
}

View File

@ -4,6 +4,7 @@ import de.diddiz.LogBlock.blockstate.BlockStateCodecSign;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.LogBlock.config.WorldConfig;
import de.diddiz.LogBlock.util.ComparableVersion;
import de.diddiz.LogBlock.util.ItemStackAndAmount;
import de.diddiz.LogBlock.util.UUIDFetcher;
import de.diddiz.LogBlock.util.Utils;
import org.bukkit.Bukkit;
@ -534,7 +535,7 @@ class Updater {
@SuppressWarnings("deprecation")
ItemStack stack = weaponMaterial.getMaxDurability() > 0 ? new ItemStack(weaponMaterial, Math.abs(amount), (short) itemdata) : new ItemStack(weaponMaterial, Math.abs(amount));
insertChestData.setLong(1, id);
insertChestData.setBytes(2, Utils.saveItemStack(stack));
insertChestData.setBytes(2, Utils.saveItemStack(ItemStackAndAmount.fromStack(stack)));
insertChestData.setInt(3, amount >= 0 ? 0 : 1);
insertChestData.setInt(4, MaterialConverter.getOrAddMaterialId(weaponMaterial));
insertChestData.addBatch();
@ -869,150 +870,49 @@ class Updater {
}
final Statement state = conn.createStatement();
conn.setAutoCommit(true);
createTable(state, "lb-players",
"playerid INT UNSIGNED NOT NULL AUTO_INCREMENT, \n" +
"UUID varchar(36) NOT NULL, \n" +
"playername varchar(32) NOT NULL, \n" +
"firstlogin DATETIME NOT NULL, \n" +
"lastlogin DATETIME NOT NULL, \n" +
"onlinetime INT UNSIGNED NOT NULL, \n" +
"ip varchar(255) NOT NULL, \n" +
"PRIMARY KEY (playerid)\n",
//"INDEX (UUID), " +
//"INDEX (playername)",
"DEFAULT CHARSET " + charset
);
createTable(state, "lb-players", "(playerid INT UNSIGNED NOT NULL AUTO_INCREMENT, UUID varchar(36) NOT NULL, playername varchar(32) NOT NULL, firstlogin DATETIME NOT NULL, lastlogin DATETIME NOT NULL, onlinetime INT UNSIGNED NOT NULL, ip varchar(255) NOT NULL, PRIMARY KEY (playerid), INDEX (UUID), INDEX (playername)) DEFAULT CHARSET " + charset);
// Players table must not be empty or inserts won't work - bug #492
final ResultSet rs = state.executeQuery("SELECT NULL FROM \"lb-players\" LIMIT 1;");
final ResultSet rs = state.executeQuery("SELECT NULL FROM `lb-players` LIMIT 1;");
if (!rs.next()) {
state.execute("INSERT INTO \"lb-players\" (UUID, playername) VALUES ('log_dummy_record','dummy_record') ON CONFLICT DO NOTHING");
state.execute("INSERT IGNORE INTO `lb-players` (UUID,playername) VALUES ('log_dummy_record','dummy_record')");
}
if (isLogging(Logging.CHAT) || isLogging(Logging.PLAYER_COMMANDS) || isLogging(Logging.CONSOLE_COMMANDS) || isLogging(Logging.COMMANDBLOCK_COMMANDS)) {
try {
createTable(state, "lb-chat",
"id INT UNSIGNED NOT NULL AUTO_INCREMENT, " +
"date DATETIME NOT NULL, " +
"playerid INT UNSIGNED NOT NULL, " +
"message VARCHAR(256) NOT NULL, " +
"PRIMARY KEY (id), ",
// "KEY playerid (playerid), " +
// "FULLTEXT message (message)",
"DEFAULT CHARSET " + charset);
createTable(state, "lb-chat", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT, date DATETIME NOT NULL, playerid INT UNSIGNED NOT NULL, message VARCHAR(256) NOT NULL, PRIMARY KEY (id), KEY playerid (playerid), FULLTEXT message (message)) DEFAULT CHARSET " + charset);
} catch (SQLException e) {
createTable(state, "lb-chat",
"id INT UNSIGNED NOT NULL AUTO_INCREMENT," +
"date DATETIME NOT NULL," +
"playerid INT UNSIGNED NOT NULL," +
"message VARCHAR(256) NOT NULL," +
"PRIMARY KEY (id)," +
"KEY playerid (playerid)",
"DEFAULT CHARSET " + charset);
createTable(state, "lb-chat", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT, date DATETIME NOT NULL, playerid INT UNSIGNED NOT NULL, message VARCHAR(256) NOT NULL, PRIMARY KEY (id), KEY playerid (playerid)) DEFAULT CHARSET " + charset);
}
}
createTable(state, "lb-materials",
"id INT UNSIGNED NOT NULL, " +
"name VARCHAR(255) NOT NULL, " +
"PRIMARY KEY (id)",
"DEFAULT CHARSET " + charset);
createTable(state, "lb-blockstates",
"id INT UNSIGNED NOT NULL, " +
"name VARCHAR(255) NOT NULL, " +
"PRIMARY KEY (id)",
"DEFAULT CHARSET " + charset);
createTable(state, "lb-entitytypes",
"id INT UNSIGNED NOT NULL, " +
"name VARCHAR(255) NOT NULL, " +
"PRIMARY KEY (id)",
"DEFAULT CHARSET " + charset);
createTable(state, "lb-materials", "(id INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
createTable(state, "lb-blockstates", "(id INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
createTable(state, "lb-entitytypes", "(id INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
for (final WorldConfig wcfg : getLoggedWorlds()) {
createTable(state, wcfg.table + "-blocks",
"id INT UNSIGNED NOT NULL AUTO_INCREMENT, " +
"date DATETIME NOT NULL, " +
"playerid INT UNSIGNED NOT NULL, " +
"replaced SMALLINT UNSIGNED NOT NULL, " +
"replacedData SMALLINT NOT NULL, " +
"type SMALLINT UNSIGNED NOT NULL, " +
"typeData SMALLINT NOT NULL, " +
"x MEDIUMINT NOT NULL, " +
"y SMALLINT NOT NULL, " +
"z MEDIUMINT NOT NULL, " +
"PRIMARY KEY (id), " +
"KEY coords (x, z, y), " +
"KEY date (date), " +
"KEY playerid (playerid)",
"");
createTable(state, wcfg.table + "-chestdata",
"id INT UNSIGNED NOT NULL, " +
"item MEDIUMBLOB, " +
"itemremove TINYINT, " +
"itemtype SMALLINT NOT NULL DEFAULT '0', " +
"PRIMARY KEY (id)",
"");
createTable(state, wcfg.table + "-state",
"id INT UNSIGNED NOT NULL, " +
"replacedState MEDIUMBLOB NULL, " +
"typeState MEDIUMBLOB NULL, " +
"PRIMARY KEY (id)",
"");
createTable(state, wcfg.table + "-blocks", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT, date DATETIME NOT NULL, playerid INT UNSIGNED NOT NULL, replaced SMALLINT UNSIGNED NOT NULL, replacedData SMALLINT NOT NULL, type SMALLINT UNSIGNED NOT NULL, typeData SMALLINT NOT NULL, x MEDIUMINT NOT NULL, y SMALLINT NOT NULL, z MEDIUMINT NOT NULL, PRIMARY KEY (id), KEY coords (x, z, y), KEY date (date), KEY playerid (playerid))");
createTable(state, wcfg.table + "-chestdata", "(id INT UNSIGNED NOT NULL, item MEDIUMBLOB, itemremove TINYINT, itemtype SMALLINT NOT NULL DEFAULT '0', PRIMARY KEY (id))");
createTable(state, wcfg.table + "-state", "(id INT UNSIGNED NOT NULL, replacedState MEDIUMBLOB NULL, typeState MEDIUMBLOB NULL, PRIMARY KEY (id))");
if (wcfg.isLogging(Logging.KILL)) {
createTable(state, wcfg.table + "-kills",
"id INT UNSIGNED NOT NULL AUTO_INCREMENT, " +
"date DATETIME NOT NULL, " +
"killer INT UNSIGNED, " +
"victim INT UNSIGNED NOT NULL, " +
"weapon SMALLINT UNSIGNED NOT NULL, " +
"x MEDIUMINT NOT NULL, " +
"y SMALLINT NOT NULL, " +
"z MEDIUMINT NOT NULL, " +
"PRIMARY KEY (id)",
"");
createTable(state, wcfg.table + "-kills", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT, date DATETIME NOT NULL, killer INT UNSIGNED, victim INT UNSIGNED NOT NULL, weapon SMALLINT UNSIGNED NOT NULL, x MEDIUMINT NOT NULL, y SMALLINT NOT NULL, z MEDIUMINT NOT NULL, PRIMARY KEY (id))");
}
createTable(state, wcfg.table + "-entityids",
"entityid INT UNSIGNED NOT NULL AUTO_INCREMENT, " +
"entityuuid VARCHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, " +
"PRIMARY KEY (entityid), " +
"UNIQUE KEY (entityuuid)",
"");
createTable(state, wcfg.table + "-entities",
"id INT UNSIGNED NOT NULL AUTO_INCREMENT, " +
"date DATETIME NOT NULL, " +
"playerid INT UNSIGNED NOT NULL, " +
"entityid INT UNSIGNED NOT NULL, " +
"entitytypeid INT UNSIGNED NOT NULL, " +
"x MEDIUMINT NOT NULL, " +
"y SMALLINT NOT NULL, " +
"z MEDIUMINT NOT NULL, " +
"action TINYINT UNSIGNED NOT NULL, " +
"data MEDIUMBLOB NULL, " +
"PRIMARY KEY (id), " +
"KEY coords (x, z, y), " +
"KEY date (date), " +
"KEY playerid (playerid), " +
"KEY entityid (entityid)",
"");
createTable(state, wcfg.table + "-entityids", "(entityid INT UNSIGNED NOT NULL AUTO_INCREMENT, entityuuid VARCHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, PRIMARY KEY (entityid), UNIQUE KEY (entityuuid))");
createTable(state, wcfg.table + "-entities", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT, date DATETIME NOT NULL, playerid INT UNSIGNED NOT NULL, entityid INT UNSIGNED NOT NULL, entitytypeid INT UNSIGNED NOT NULL, x MEDIUMINT NOT NULL, y SMALLINT NOT NULL, z MEDIUMINT NOT NULL, action TINYINT UNSIGNED NOT NULL, data MEDIUMBLOB NULL, PRIMARY KEY (id), KEY coords (x, z, y), KEY date (date), KEY playerid (playerid), KEY entityid (entityid))");
}
state.close();
conn.close();
}
private void createTable(Statement state, String table, String columns, String additional) throws SQLException {
logblock.getLogger().log(Level.INFO, "Creating table " + table + ".");
String sql = "CREATE TABLE IF NOT EXISTS \"" + table + "\" (" +
(
columns
.replace("INT UNSIGNED NOT NULL AUTO_INCREMENT", "SERIAL NOT NULL")
.replace("INT UNSIGNED", "INT")
.replace("DATETIME", "TIMESTAMP")
)
+ ") " /* + additional */ ;
logblock.getLogger().info("sql=" + sql);
state.execute(sql);
//try (ResultSet tableResultNew = state.executeQuery("SHOW TABLES LIKE '" + table + "'")) {
// if (!tableResultNew.next()) {
// throw new SQLException("Table " + table + " not found and failed to create");
// }
//}
private void createTable(Statement state, String table, String query) throws SQLException {
try (ResultSet tableResult = state.executeQuery("SHOW TABLES LIKE '" + table + "'")) {
if (!tableResult.next()) {
logblock.getLogger().log(Level.INFO, "Creating table " + table + ".");
state.execute("CREATE TABLE `" + table + "` " + query);
try (ResultSet tableResultNew = state.executeQuery("SHOW TABLES LIKE '" + table + "'")) {
if (!tableResultNew.next()) {
throw new SQLException("Table " + table + " not found and failed to create");
}
}
}
}
}
/**

View File

@ -24,6 +24,7 @@ import org.bukkit.entity.Bee;
import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemFrame;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import de.diddiz.LogBlock.QueryParams.Order;
@ -35,6 +36,7 @@ import java.io.File;
import java.io.PrintWriter;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
@ -53,6 +55,8 @@ import static de.diddiz.LogBlock.util.BukkitUtils.*;
public class WorldEditor implements Runnable {
private final LogBlock logblock;
private final ArrayList<Edit> edits = new ArrayList<>();
private int rowsCompleted;
private int totalRows;
private final World world;
/**
@ -135,6 +139,7 @@ public class WorldEditor implements Runnable {
}
started = true;
final long start = System.currentTimeMillis();
totalRows = edits.size();
taskID = logblock.getServer().getScheduler().scheduleSyncRepeatingTask(logblock, this, 0, 1);
if (taskID == -1) {
throw new Exception("Failed to schedule task");
@ -151,8 +156,9 @@ public class WorldEditor implements Runnable {
public synchronized void run() {
final List<WorldEditorException> errorList = new ArrayList<>();
int counter = 0;
float size = edits.size();
while (!edits.isEmpty() && counter < 100) {
long t0 = System.nanoTime();
long maxEditTime = 5_000_000; // 5 ms
while (!edits.isEmpty() && counter < 10000 && (counter < 100 || counter % 10 != 0 || System.nanoTime() - t0 < maxEditTime)) {
try {
switch (edits.remove(edits.size() - 1).perform()) {
case SUCCESS:
@ -169,11 +175,12 @@ public class WorldEditor implements Runnable {
} catch (final Exception ex) {
logblock.getLogger().log(Level.WARNING, "[WorldEditor] Exeption: ", ex);
}
rowsCompleted++;
counter++;
if (sender != null) {
float percentage = ((size - edits.size()) / size) * 100.0F;
if (percentage % 20 == 0) {
sender.sendMessage(ChatColor.GOLD + "[LogBlock]" + ChatColor.YELLOW + " Rollback progress: " + percentage + "%" +
float percentage = rowsCompleted * 100.0f / totalRows;
if (rowsCompleted % 10000 == 0) {
sender.sendMessage(ChatColor.GOLD + "[LogBlock]" + ChatColor.YELLOW + " Rollback progress: " + NumberFormat.getNumberInstance().format(percentage) + "%" +
" Blocks edited: " + counter);
}
}
@ -368,10 +375,10 @@ public class WorldEditor implements Runnable {
BlockState state = block.getState();
if (setBlock.equals(replacedBlock)) {
if (ca != null) {
if (state instanceof Container && state.getType() == replacedBlock.getMaterial()) {
if (state instanceof InventoryHolder && state.getType() == replacedBlock.getMaterial()) {
int leftover;
try {
leftover = modifyContainer(state, new ItemStack(ca.itemStack), !ca.remove);
leftover = modifyContainer(state, ca.itemStack, !ca.remove);
} catch (final Exception ex) {
throw new WorldEditorException(ex.getMessage(), block.getLocation());
}
@ -486,8 +493,8 @@ public class WorldEditor implements Runnable {
}
@Override
public BaseComponent[] getLogMessage(int entry) {
return TextComponent.fromLegacyText(getMessage());
public BaseComponent getLogMessage(int entry) {
return TextComponent.fromLegacy(getMessage());
}
}
}

View File

@ -3,9 +3,8 @@ package de.diddiz.LogBlock;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.bukkit.inventory.ItemStack;
import de.diddiz.LogBlock.QueryParams.BlockChangeType;
import de.diddiz.LogBlock.util.ItemStackAndAmount;
import de.diddiz.LogBlock.util.Utils;
public class WorldEditorEditFactory {
@ -25,7 +24,7 @@ public class WorldEditorEditFactory {
return;
}
ChestAccess chestaccess = null;
ItemStack stack = Utils.loadItemStack(rs.getBytes("item"));
ItemStackAndAmount stack = Utils.loadItemStack(rs.getBytes("item"));
if (stack != null) {
chestaccess = new ChestAccess(stack, rs.getBoolean("itemremove") == rollback, rs.getInt("itemtype"));
}

View File

@ -1,9 +1,12 @@
package de.diddiz.LogBlock.blockstate;
import java.util.List;
import java.util.Locale;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.block.Banner;
import org.bukkit.block.BlockState;
import org.bukkit.block.banner.Pattern;
@ -30,10 +33,13 @@ public class BlockStateCodecBanner implements BlockStateCodec {
YamlConfiguration conf = new YamlConfiguration();
ConfigurationSection patternsSection = conf.createSection("patterns");
for (Pattern pattern : patterns) {
ConfigurationSection section = patternsSection.createSection(Integer.toString(nr));
section.set("color", pattern.getColor().name());
section.set("pattern", pattern.getPattern().name());
nr++;
NamespacedKey key = pattern.getPattern().getKey();
if (key != null) {
ConfigurationSection section = patternsSection.createSection(Integer.toString(nr));
section.set("color", pattern.getColor().name());
section.set("pattern", key.toString());
nr++;
}
}
return conf;
}
@ -55,8 +61,13 @@ public class BlockStateCodecBanner implements BlockStateCodec {
ConfigurationSection section = patternsSection.getConfigurationSection(key);
if (section != null) {
DyeColor color = DyeColor.valueOf(section.getString("color"));
PatternType type = PatternType.valueOf(section.getString("pattern"));
banner.addPattern(new Pattern(color, type));
NamespacedKey patternKey = NamespacedKey.fromString(section.getString("pattern").toLowerCase(Locale.ROOT));
if (patternKey != null) {
PatternType type = Registry.BANNER_PATTERN.get(patternKey);
if (type != null) {
banner.addPattern(new Pattern(color, type));
}
}
}
}
}

View File

@ -182,7 +182,7 @@ public class BlockStateCodecSign implements BlockStateCodec {
}
tc.addExtra("[");
if (line != null && !line.isEmpty()) {
tc.addExtra(new TextComponent(TextComponent.fromLegacyText(line)));
tc.addExtra(TextComponent.fromLegacy(line));
}
tc.addExtra("]");
}

View File

@ -7,8 +7,8 @@ import java.util.List;
import org.bukkit.entity.EntityType;
public enum EntityLogging {
SPAWN(new String[] { EntityType.ARMOR_STAND.name(), EntityType.ITEM_FRAME.name(), EntityType.SNOWMAN.name() }),
DESTROY(new String[] { EntityType.ARMOR_STAND.name(), EntityType.ITEM_FRAME.name(), EntityType.VILLAGER.name(), EntityType.SNOWMAN.name(), "ANIMAL" }),
SPAWN(new String[] { EntityType.ARMOR_STAND.name(), EntityType.ITEM_FRAME.name(), EntityType.SNOW_GOLEM.name() }),
DESTROY(new String[] { EntityType.ARMOR_STAND.name(), EntityType.ITEM_FRAME.name(), EntityType.VILLAGER.name(), EntityType.SNOW_GOLEM.name(), "ANIMAL" }),
MODIFY(new String[] { "ALL" });
public static final int length = EntityLogging.values().length;

View File

@ -2,6 +2,7 @@ package de.diddiz.LogBlock.listeners;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.BlockFace;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ArmorStand;
@ -192,18 +193,17 @@ public class AdvancedEntityLogging extends LoggingListener {
LivingEntity entity = event.getEntity();
if (Config.isLogging(entity.getWorld(), EntityLogging.DESTROY, entity)) {
Actor actor = null;
EntityDamageEvent lastDamage = entity.getLastDamageCause();
if (lastDamage instanceof EntityDamageByEntityEvent) {
Entity damager = LoggingUtil.getRealDamager(((EntityDamageByEntityEvent) lastDamage).getDamager());
if (damager != null) {
actor = Actor.actorFromEntity(damager);
}
Entity cause = event.getDamageSource().getCausingEntity();
Entity damager = LoggingUtil.getRealDamager(cause);
if (damager != null) {
actor = Actor.actorFromEntity(damager);
}
if (actor == null && entity.getKiller() != null) {
actor = Actor.actorFromEntity(entity.getKiller());
}
if (actor == null) {
actor = new Actor(lastDamage == null ? "UNKNOWN" : lastDamage.getCause().toString());
NamespacedKey key = event.getDamageSource().getDamageType().getKey();
actor = new Actor(key == null ? "unknown" : key.getKey().toUpperCase());
}
queueEntitySpawnOrKill(entity, actor, EntityChange.EntityChangeType.KILL);
}

View File

@ -3,21 +3,31 @@ package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.util.ItemStackAndAmount;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.DecoratedPot;
import org.bukkit.block.DoubleChest;
import org.bukkit.block.data.type.ChiseledBookshelf;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@ -69,9 +79,10 @@ public class ChestAccessLogging extends LoggingListener {
for (Entry<ItemStack, Integer> e : modifications.entrySet()) {
ItemStack stack = e.getKey();
int amount = e.getValue();
stack.setAmount(Math.abs(amount));
// consumer.getLogblock().getLogger().info("Store container: " + stack + " take: " + (amount < 0));
consumer.queueChestAccess(Actor.actorFromEntity(actor), location, location.getWorld().getBlockAt(location).getBlockData(), stack, amount < 0);
if (amount != 0) {
// consumer.getLogblock().getLogger().info("Store container: " + stack + " take: " + (amount < 0));
consumer.queueChestAccess(Actor.actorFromEntity(actor), location, location.getWorld().getBlockAt(location).getBlockData(), new ItemStackAndAmount(stack, Math.abs(amount)), amount < 0);
}
}
modifications.clear();
}
@ -300,4 +311,62 @@ public class ChestAccessLogging extends LoggingListener {
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent event) {
final Block clicked = event.getClickedBlock();
if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getHand() != EquipmentSlot.HAND || !event.hasBlock() || clicked == null) {
return;
}
final Player player = event.getPlayer();
if (!isLogging(player.getWorld(), Logging.CHESTACCESS)) {
return;
}
final Material type = clicked.getType();
if (type == Material.DECORATED_POT) {
ItemStack mainHand = player.getInventory().getItemInMainHand();
if (mainHand != null && mainHand.getType() != Material.AIR && clicked.getState() instanceof DecoratedPot pot) {
ItemStack currentInPot = pot.getSnapshotInventory().getItem();
if (currentInPot == null || currentInPot.getType() == Material.AIR || currentInPot.isSimilar(mainHand) && currentInPot.getAmount() < currentInPot.getMaxStackSize()) {
ItemStack stack = mainHand.clone();
stack.setAmount(1);
consumer.queueChestAccess(Actor.actorFromEntity(player), clicked.getLocation(), clicked.getBlockData(), ItemStackAndAmount.fromStack(stack), false);
}
}
} else if (type == Material.CHISELED_BOOKSHELF) {
if (clicked.getBlockData() instanceof ChiseledBookshelf blockData && blockData.getFacing() == event.getBlockFace() && clicked.getState() instanceof org.bukkit.block.ChiseledBookshelf bookshelf) {
// calculate the slot the same way as minecraft does it
Vector pos = event.getClickedPosition();
if (pos == null) {
return; // some plugins create this event without a clicked pos
}
double clickx = switch (blockData.getFacing()) {
case NORTH -> 1 - pos.getX();
case SOUTH -> pos.getX();
case EAST -> 1 - pos.getZ();
case WEST -> pos.getZ();
default -> throw new IllegalArgumentException("Unexpected facing for chiseled bookshelf: " + blockData.getFacing());
};
int col = clickx < 0.375 ? 0 : (clickx < 0.6875 ? 1 : 2); // 6/16 ; 11/16
int row = pos.getY() >= 0.5 ? 0 : 1;
int slot = col + row * 3;
ItemStack currentInSlot = bookshelf.getSnapshotInventory().getItem(slot);
if (blockData.isSlotOccupied(slot)) {
// not empty: always take
if (currentInSlot != null && currentInSlot.getType() != Material.AIR) {
consumer.queueChestAccess(Actor.actorFromEntity(player), clicked.getLocation(), clicked.getBlockData(), ItemStackAndAmount.fromStack(currentInSlot), true);
}
} else {
// empty: put if has tag BOOKSHELF_BOOKS
ItemStack mainHand = player.getInventory().getItemInMainHand();
if (mainHand != null && mainHand.getType() != Material.AIR && Tag.ITEMS_BOOKSHELF_BOOKS.isTagged(mainHand.getType())) {
ItemStack stack = mainHand.clone();
stack.setAmount(1);
consumer.queueChestAccess(Actor.actorFromEntity(player), clicked.getLocation(), clicked.getBlockData(), ItemStackAndAmount.fromStack(stack), false);
}
}
}
}
}
}

View File

@ -1,24 +0,0 @@
package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import org.bukkit.entity.Enderman;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import static de.diddiz.LogBlock.config.Config.isLogging;
public class EndermenLogging extends LoggingListener {
public EndermenLogging(LogBlock lb) {
super(lb);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityChangeBlock(EntityChangeBlockEvent event) {
if (event.getEntity() instanceof Enderman && isLogging(event.getBlock().getWorld(), Logging.ENDERMEN)) {
consumer.queueBlockReplace(new Actor("Enderman"), event.getBlock().getState(), event.getBlockData()); // Figure out how to get the data of the placed block;
}
}
}

View File

@ -0,0 +1,46 @@
package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import org.bukkit.Material;
import org.bukkit.entity.Enderman;
import org.bukkit.entity.Player;
import org.bukkit.entity.Sheep;
import org.bukkit.entity.Wither;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import static de.diddiz.LogBlock.config.Config.isLogging;
public class EntityChangeBlockLogging extends LoggingListener {
public EntityChangeBlockLogging(LogBlock lb) {
super(lb);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityChangeBlock(EntityChangeBlockEvent event) {
Material oldType = event.getBlock().getType();
if ((oldType == Material.REDSTONE_ORE || oldType == Material.DEEPSLATE_REDSTONE_ORE) && event.getBlockData().getMaterial() == oldType) {
return; // ignore redstone ore activation by stepping on it
}
if (event.getEntity() instanceof Wither) {
if (isLogging(event.getBlock().getWorld(), Logging.WITHER)) {
consumer.queueBlockReplace(Actor.actorFromEntity(event.getEntity()), event.getBlock().getState(), event.getBlockData());
}
} else if (event.getEntity() instanceof Enderman) {
if (isLogging(event.getBlock().getWorld(), Logging.ENDERMEN)) {
consumer.queueBlockReplace(Actor.actorFromEntity(event.getEntity()), event.getBlock().getState(), event.getBlockData());
}
} else if (event.getEntity() instanceof Sheep) {
if (isLogging(event.getBlock().getWorld(), Logging.GRASS_EAT)) {
consumer.queueBlockReplace(Actor.actorFromEntity(event.getEntity()), event.getBlock().getState(), event.getBlockData());
}
} else {
if (isLogging(event.getBlock().getWorld(), event.getEntity() instanceof Player ? Logging.BLOCKPLACE : Logging.MISCENTITYCHANGEBLOCK)) {
consumer.queueBlockReplace(Actor.actorFromEntity(event.getEntity()), event.getBlock().getState(), event.getBlockData());
}
}
}
}

View File

@ -7,6 +7,7 @@ import de.diddiz.LogBlock.config.Config;
import de.diddiz.LogBlock.config.WorldConfig;
import de.diddiz.LogBlock.util.BukkitUtils;
import org.bukkit.Bukkit;
import org.bukkit.ExplosionResult;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
@ -41,6 +42,9 @@ public class ExplosionLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityExplode(EntityExplodeEvent event) {
if (event.getExplosionResult() == ExplosionResult.KEEP || event.getExplosionResult() == ExplosionResult.TRIGGER_BLOCK) {
return;
}
final WorldConfig wcfg = getWorldConfig(event.getLocation().getWorld());
if (wcfg != null) {
Actor actor = new Actor("Explosion");
@ -174,6 +178,9 @@ public class ExplosionLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockExplode(BlockExplodeEvent event) {
if (event.getExplosionResult() == ExplosionResult.KEEP || event.getExplosionResult() == ExplosionResult.TRIGGER_BLOCK) {
return;
}
Player bedCause = null;
if (lastBedInteractionPlayer != null && lastBedInteractionLocation != null) {
Location block = event.getBlock().getLocation();

View File

@ -4,14 +4,13 @@ import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.Config.*;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Monster;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import static de.diddiz.LogBlock.config.Config.*;
@ -24,27 +23,26 @@ public class KillLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityDeath(EntityDeathEvent deathEvent) {
EntityDamageEvent event = deathEvent.getEntity().getLastDamageCause();
// For a death event, there should always be a damage event and it should not be cancelled. Check anyway.
if (event != null && event.isCancelled() == false && isLogging(event.getEntity().getWorld(), Logging.KILL) && event.getEntity() instanceof LivingEntity) {
final LivingEntity victim = (LivingEntity) event.getEntity();
if (event instanceof EntityDamageByEntityEvent) {
final Entity killer = ((EntityDamageByEntityEvent) event).getDamager();
if (isLogging(deathEvent.getEntity().getWorld(), Logging.KILL)) {
LivingEntity victim = deathEvent.getEntity();
Entity killer = deathEvent.getDamageSource().getCausingEntity();
if (killer != null) {
if (logKillsLevel == LogKillsLevel.PLAYERS && !(victim instanceof Player && killer instanceof Player)) {
return;
} else if (logKillsLevel == LogKillsLevel.MONSTERS && !((victim instanceof Player || victim instanceof Monster) && killer instanceof Player || killer instanceof Monster)) {
return;
}
consumer.queueKill(killer, victim);
} else if (deathEvent.getEntity().getKiller() != null) {
consumer.queueKill(deathEvent.getEntity().getKiller(), victim);
} else if (logEnvironmentalKills) {
if (logKillsLevel == LogKillsLevel.PLAYERS && !(victim instanceof Player)) {
return;
} else if (logKillsLevel == LogKillsLevel.MONSTERS && !((victim instanceof Player || victim instanceof Monster))) {
return;
}
consumer.queueKill(new Actor(event.getCause().toString()), victim);
NamespacedKey key = deathEvent.getDamageSource().getDamageType().getKey();
Actor actor = new Actor(key == null ? "unknown" : key.getKey().toUpperCase());
consumer.queueKill(actor, victim);
}
}
}

View File

@ -1,18 +1,24 @@
package de.diddiz.LogBlock.listeners;
import static de.diddiz.LogBlock.config.Config.getWorldConfig;
import static de.diddiz.LogBlock.config.Config.isLogging;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.WorldConfig;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.Lectern;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerTakeLecternBookEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
public class LecternLogging extends LoggingListener {
@ -21,25 +27,31 @@ public class LecternLogging extends LoggingListener {
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) {
final WorldConfig wcfg = getWorldConfig(event.getPlayer().getWorld());
if (wcfg != null && wcfg.isLogging(Logging.LECTERNBOOKCHANGE)) {
final BlockState before = event.getBlockReplacedState();
final BlockState after = event.getBlockPlaced().getState();
if (before.getType() == Material.LECTERN && after.getType() == Material.LECTERN) {
Lectern lecternBefore = (Lectern) before.getBlock().getState();
ItemStack book = lecternBefore.getSnapshotInventory().getItem(0);
try {
lecternBefore.getSnapshotInventory().setItem(0, null);
} catch (NullPointerException e) {
//ignored
}
lecternBefore.setBlockData(before.getBlockData());
consumer.queueBlockReplace(Actor.actorFromEntity(event.getPlayer()), lecternBefore, after);
try {
lecternBefore.getSnapshotInventory().setItem(0, book);
} catch (NullPointerException e) {
//ignored
public void onPlayerInteract(PlayerInteractEvent event) {
final Block clicked = event.getClickedBlock();
if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getHand() != EquipmentSlot.HAND || !event.hasBlock() || clicked == null) {
return;
}
final Player player = event.getPlayer();
if (!isLogging(player.getWorld(), Logging.LECTERNBOOKCHANGE)) {
return;
}
final Material type = clicked.getType();
if (type == Material.LECTERN) {
ItemStack mainHand = player.getInventory().getItemInMainHand();
if (mainHand != null && mainHand.getType() != Material.AIR && Tag.ITEMS_LECTERN_BOOKS.isTagged(mainHand.getType()) && clicked.getState() instanceof Lectern lectern) {
ItemStack currentInLectern = lectern.getSnapshotInventory().getItem(0);
if (currentInLectern == null || currentInLectern.getType() == Material.AIR) {
ItemStack stack = mainHand.clone();
stack.setAmount(1);
Lectern newLectern = (Lectern) clicked.getState();
newLectern.getSnapshotInventory().setItem(0, stack);
org.bukkit.block.data.type.Lectern blockDataOld = (org.bukkit.block.data.type.Lectern) newLectern.getBlockData();
org.bukkit.block.data.type.Lectern blockDataWithBook = (org.bukkit.block.data.type.Lectern) Bukkit.createBlockData("lectern[has_book=true]");
blockDataWithBook.setFacing(blockDataOld.getFacing());
blockDataWithBook.setPowered(blockDataOld.isPowered());
newLectern.setBlockData(blockDataWithBook);
consumer.queueBlockReplace(Actor.actorFromEntity(event.getPlayer()), lectern, newLectern);
}
}
}

View File

@ -1,24 +0,0 @@
package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import org.bukkit.entity.Wither;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import static de.diddiz.LogBlock.config.Config.isLogging;
public class WitherLogging extends LoggingListener {
public WitherLogging(LogBlock lb) {
super(lb);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityChangeBlock(EntityChangeBlockEvent event) {
if (event.getEntity() instanceof Wither && isLogging(event.getBlock().getWorld(), Logging.WITHER)) {
consumer.queueBlockReplace(Actor.actorFromEntity(event.getEntity()), event.getBlock().getState(), event.getBlockData()); // Wither walked through a block.
}
}
}

View File

@ -4,13 +4,14 @@ import static de.diddiz.LogBlock.util.MessagingUtil.prettyMaterial;
import de.diddiz.LogBlock.LogBlock;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.Set;
import java.util.UUID;
@ -147,8 +148,11 @@ public class BukkitUtils {
// https://minecraft.fandom.com/wiki/Tag#blocks_small_flowers
Set<Material> smallFlowers = Tag.SMALL_FLOWERS.getValues();
// https://minecraft.fandom.com/wiki/Tag#blocks_tall_flowers
Set<Material> tallFlowers = Tag.TALL_FLOWERS.getValues();
Set<Material> tallFlowers = Set.of(Material.SUNFLOWER,
Material.LILAC,
Material.PEONY,
Material.ROSE_BUSH,
Material.PITCHER_PLANT);
Set<Material> bannerStanding = Set.of(Material.WHITE_BANNER,
Material.ORANGE_BANNER,
@ -331,6 +335,7 @@ public class BukkitUtils {
containerBlocks.add(Material.BLAST_FURNACE);
containerBlocks.add(Material.SMOKER);
containerBlocks.add(Material.CHISELED_BOOKSHELF);
containerBlocks.add(Material.DECORATED_POT);
// Doesn't actually have a block inventory
// containerBlocks.add(Material.ENDER_CHEST);
@ -341,12 +346,13 @@ public class BukkitUtils {
projectileItems.put(EntityType.ENDER_PEARL, Material.ENDER_PEARL);
projectileItems.put(EntityType.SMALL_FIREBALL, Material.FIRE_CHARGE); // Fire charge
projectileItems.put(EntityType.FIREBALL, Material.FIRE_CHARGE); // Fire charge
projectileItems.put(EntityType.FISHING_HOOK, Material.FISHING_ROD);
projectileItems.put(EntityType.FISHING_BOBBER, Material.FISHING_ROD);
projectileItems.put(EntityType.SNOWBALL, Material.SNOWBALL);
projectileItems.put(EntityType.SPLASH_POTION, Material.SPLASH_POTION);
projectileItems.put(EntityType.THROWN_EXP_BOTTLE, Material.EXPERIENCE_BOTTLE);
projectileItems.put(EntityType.LINGERING_POTION, Material.LINGERING_POTION);
projectileItems.put(EntityType.EXPERIENCE_BOTTLE, Material.EXPERIENCE_BOTTLE);
projectileItems.put(EntityType.WITHER_SKULL, Material.WITHER_SKELETON_SKULL);
projectileItems.put(EntityType.FIREWORK, Material.FIREWORK_ROCKET);
projectileItems.put(EntityType.FIREWORK_ROCKET, Material.FIREWORK_ROCKET);
nonFluidProofBlocks = new HashSet<>();
nonFluidProofBlocks.addAll(carpets);
@ -498,24 +504,22 @@ public class BukkitUtils {
return diff.toArray(new ItemStack[diff.size()]);
}
public static ItemStack[] compressInventory(ItemStack[] items) {
final ArrayList<ItemStack> compressed = new ArrayList<>();
public static Collection<ItemStackAndAmount> compressInventory(ItemStack[] items) {
final HashMap<ItemStack, Integer> compressed = new HashMap<>();
for (final ItemStack item : items) {
if (item != null) {
boolean found = false;
for (final ItemStack item2 : compressed) {
if (item2.isSimilar(item)) {
item2.setAmount(item2.getAmount() + item.getAmount());
found = true;
break;
}
}
if (!found) {
compressed.add(item.clone());
}
if (item != null && item.getType() != Material.AIR && item.getAmount() > 0) {
int amount = item.getAmount();
ItemStack stack = item.clone();
stack.setAmount(1);
Integer old = compressed.get(stack);
compressed.put(stack, (old == null ? 0 : old) + amount);
}
}
return compressed.toArray(new ItemStack[compressed.size()]);
ArrayList<ItemStackAndAmount> result = new ArrayList<>();
for (Entry<ItemStack, Integer> e : compressed.entrySet()) {
result.add(new ItemStackAndAmount(e.getKey(), e.getValue()));
}
return result;
}
public static String friendlyWorldname(String worldName) {
@ -602,15 +606,13 @@ public class BukkitUtils {
return y;
}
public static int modifyContainer(BlockState b, ItemStack item, boolean remove) {
if (b instanceof InventoryHolder) {
final Inventory inv = ((InventoryHolder) b).getInventory();
public static int modifyContainer(BlockState b, ItemStackAndAmount item, boolean remove) {
if (item.amount() > 0 && b instanceof InventoryHolder c) {
final Inventory inv = c.getInventory();
if (remove) {
final ItemStack tmp = inv.removeItem(item).get(0);
return tmp != null ? tmp.getAmount() : 0;
} else if (item.getAmount() > 0) {
final ItemStack tmp = inv.addItem(item).get(0);
return tmp != null ? tmp.getAmount() : 0;
return InventoryUtils.removeFromInventory(inv, item);
} else {
return InventoryUtils.addToInventory(inv, item);
}
}
return 0;
@ -656,16 +658,16 @@ public class BukkitUtils {
return m == Material.AIR || m == Material.CAVE_AIR || m == Material.VOID_AIR;
}
public static TextComponent toString(ItemStack stack) {
if (stack == null || stack.getAmount() == 0 || isEmpty(stack.getType())) {
public static TextComponent toString(ItemStackAndAmount stack) {
if (stack == null || stack.stack() == null || stack.amount() == 0 || isEmpty(stack.stack().getType())) {
return prettyMaterial("nothing");
}
TextComponent msg = MessagingUtil.createTextComponentWithColor(stack.getAmount() + "x ", TypeColor.DEFAULT.getColor());
msg.addExtra(prettyMaterial(stack.getType()));
TextComponent msg = MessagingUtil.createTextComponentWithColor(stack.amount() + "x ", TypeColor.DEFAULT.getColor());
msg.addExtra(prettyMaterial(stack.stack().getType()));
try {
String itemTag = getItemTag(stack);
msg.setHoverEvent(new HoverEvent(Action.SHOW_ITEM, new Item(stack.getType().getKey().toString(), 1, itemTag != null ? ItemTag.ofNbt(itemTag) : null)));
String itemTag = stack.stack().getItemMeta().getAsString();
msg.setHoverEvent(new HoverEvent(Action.SHOW_ITEM, new Item(stack.stack().getType().getKey().toString(), 1, itemTag != null ? ItemTag.ofNbt(itemTag) : null)));
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Failed to convert Itemstack to JSON", e);
msg.setHoverEvent(new HoverEvent(Action.SHOW_TEXT, new Text(new BaseComponent[] { MessagingUtil.createTextComponentWithColor("Error", TypeColor.ERROR.getColor()) })));
@ -674,20 +676,6 @@ public class BukkitUtils {
return msg;
}
public static String getItemTag(ItemStack itemStack) throws ReflectiveOperationException {
Class<?> craftItemStackClazz = ReflectionUtil.getCraftBukkitClass("inventory.CraftItemStack");
Method asNMSCopyMethod = craftItemStackClazz.getMethod("asNMSCopy", ItemStack.class);
Class<?> nmsItemStackClazz = ReflectionUtil.getMinecraftClass("world.item.ItemStack");
Method getTagMethod = nmsItemStackClazz.getDeclaredMethod("getTagClone");
getTagMethod.setAccessible(true);
Object nmsItemStack = asNMSCopyMethod.invoke(null, itemStack);
Object itemTag = getTagMethod.invoke(nmsItemStack);
return itemTag != null ? itemTag.toString() : null;
}
public static String formatMinecraftKey(String s) {
char[] cap = s.toCharArray();
boolean lastSpace = true;

View File

@ -0,0 +1,113 @@
package de.diddiz.LogBlock.util;
import org.bukkit.Material;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
public class InventoryUtils {
public static int addToInventory(Inventory inventory, ItemStackAndAmount item) {
if (item == null || item.stack() == null || item.stack().getType() == Material.AIR) {
return 0;
}
int maxStackSize = Math.max(Math.min(inventory.getMaxStackSize(), item.stack().getMaxStackSize()), 1);
ItemStack[] contents = inventory.getStorageContents();
int remaining = item.amount();
int initialRemaining = remaining;
// fill partial stacks
int firstPartial = -1;
while (remaining > 0) {
firstPartial = getFirstPartial(item.stack(), maxStackSize, contents, firstPartial + 1);
if (firstPartial < 0) {
break;
}
ItemStack content = contents[firstPartial];
int add = Math.min(maxStackSize - content.getAmount(), remaining);
content.setAmount(content.getAmount() + add);
remaining -= add;
}
// create new stacks
int firstFree = -1;
while (remaining > 0) {
firstFree = getFirstFree(contents, firstFree + 1);
if (firstFree < 0) {
break;
}
ItemStack content = item.stack().clone();
contents[firstFree] = content;
int add = Math.min(maxStackSize, remaining);
content.setAmount(add);
remaining -= add;
}
if (remaining < initialRemaining) {
inventory.setStorageContents(contents);
}
return remaining;
}
public static int removeFromInventory(Inventory inventory, ItemStackAndAmount item) {
if (item == null || item.stack() == null || item.stack().getType() == Material.AIR) {
return 0;
}
ItemStack[] contents = inventory.getStorageContents();
int remaining = item.amount();
int initialRemaining = remaining;
int firstSimilar = -1;
while (remaining > 0) {
firstSimilar = getFirstSimilar(item.stack(), contents, firstSimilar + 1);
if (firstSimilar < 0) {
break;
}
ItemStack content = contents[firstSimilar];
int here = content.getAmount();
if (here > remaining) {
content.setAmount(here - remaining);
remaining = 0;
} else {
contents[firstSimilar] = null;
remaining -= here;
}
}
if (remaining < initialRemaining) {
inventory.setStorageContents(contents);
}
return remaining;
}
private static int getFirstSimilar(ItemStack item, ItemStack[] contents, int start) {
for (int i = start; i < contents.length; i++) {
ItemStack content = contents[i];
if (content != null && content.isSimilar(item)) {
return i;
}
}
return -1;
}
private static int getFirstPartial(ItemStack item, int maxStackSize, ItemStack[] contents, int start) {
for (int i = start; i < contents.length; i++) {
ItemStack content = contents[i];
if (content != null && content.isSimilar(item) && content.getAmount() < maxStackSize) {
return i;
}
}
return -1;
}
private static int getFirstFree(ItemStack[] contents, int start) {
for (int i = start; i < contents.length; i++) {
ItemStack content = contents[i];
if (content == null || content.getAmount() == 0 || content.getType() == Material.AIR) {
return i;
}
}
return -1;
}
}

View File

@ -0,0 +1,15 @@
package de.diddiz.LogBlock.util;
import org.bukkit.inventory.ItemStack;
public record ItemStackAndAmount(ItemStack stack, int amount) {
public static ItemStackAndAmount fromStack(ItemStack stack) {
int amount = stack.getAmount();
if (amount > 1) {
stack = stack.clone();
stack.setAmount(1);
}
return new ItemStackAndAmount(stack, amount);
}
}

View File

@ -18,10 +18,14 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.entity.EntityType;
public class MessagingUtil {
public static BaseComponent[] formatSummarizedChanges(int created, int destroyed, BaseComponent actor, int createdWidth, int destroyedWidth, float spaceFactor) {
public static BaseComponent formatSummarizedChanges(int created, int destroyed, BaseComponent actor, int createdWidth, int destroyedWidth, float spaceFactor) {
TextComponent textCreated = createTextComponentWithColor(created + spaces((int) ((10 - String.valueOf(created).length()) / spaceFactor)), CREATE.getColor());
TextComponent textDestroyed = createTextComponentWithColor(destroyed + spaces((int) ((10 - String.valueOf(destroyed).length()) / spaceFactor)), DESTROY.getColor());
return new BaseComponent[] { textCreated, textDestroyed, actor };
TextComponent result = new TextComponent();
result.addExtra(textCreated);
result.addExtra(textDestroyed);
result.addExtra(actor);
return result;
}
public static TextComponent createTextComponentWithColor(String text, ChatColor color) {

View File

@ -8,6 +8,7 @@ import com.google.gson.JsonObject;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
@ -45,7 +46,7 @@ public class UUIDFetcher {
}
private static HttpURLConnection createConnection() throws Exception {
URL url = new URL(PROFILE_URL);
URL url = new URI(PROFILE_URL).toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");

View File

@ -231,20 +231,35 @@ public class Utils {
return untrusted.replace("\\", "\\\\").replace("'", "\\'");
}
public static ItemStack loadItemStack(byte[] data) {
public static ItemStackAndAmount loadItemStack(byte[] data) {
if (data == null || data.length == 0) {
return null;
}
YamlConfiguration conf = deserializeYamlConfiguration(data);
return conf == null ? null : conf.getItemStack("stack");
if (conf == null) {
return null;
}
ItemStack stack = conf.getItemStack("stack");
if (stack == null) {
return null;
}
int amount = conf.contains("amount") ? conf.getInt("amount") : stack.getAmount();
stack.setAmount(1);
return new ItemStackAndAmount(stack, amount);
}
public static byte[] saveItemStack(ItemStack stack) {
if (stack == null || BukkitUtils.isEmpty(stack.getType())) {
public static byte[] saveItemStack(ItemStackAndAmount stack) {
if (stack == null || stack.stack() == null || BukkitUtils.isEmpty(stack.stack().getType())) {
return null;
}
YamlConfiguration conf = new YamlConfiguration();
conf.set("stack", stack);
ItemStack itemStack = stack.stack();
if (itemStack.getAmount() > 1) {
itemStack = itemStack.clone();
itemStack.setAmount(1);
}
conf.set("stack", itemStack);
conf.set("amount", stack.amount());
return serializeYamlConfiguration(conf);
}

View File

@ -2,10 +2,10 @@ package de.diddiz.LogBlock.worldedit;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.UUID;
import java.util.logging.Level;
@ -17,15 +17,15 @@ import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.BlockVector;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.DoubleTag;
import com.sk89q.jnbt.FloatTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.NBTOutputStream;
import com.sk89q.jnbt.NamedTag;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.Tag;
import org.enginehub.linbus.stream.LinBinaryIO;
import org.enginehub.linbus.stream.LinStream;
import org.enginehub.linbus.tree.LinCompoundTag;
import org.enginehub.linbus.tree.LinDoubleTag;
import org.enginehub.linbus.tree.LinIntArrayTag;
import org.enginehub.linbus.tree.LinListTag;
import org.enginehub.linbus.tree.LinLongTag;
import org.enginehub.linbus.tree.LinRootEntry;
import org.enginehub.linbus.tree.LinTagType;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
@ -33,6 +33,7 @@ import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.concurrency.LazyReference;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.util.CuboidRegion;
@ -107,21 +108,25 @@ public class WorldEditHelper {
com.sk89q.worldedit.world.entity.EntityType weType = BukkitAdapter.adapt(type);
com.sk89q.worldedit.util.Location weLocation = BukkitAdapter.adapt(location);
try {
NBTInputStream nbtis = new NBTInputStream(new ByteArrayInputStream(serialized));
NamedTag namedTag = nbtis.readNamedTag();
nbtis.close();
LinStream stream = LinBinaryIO.read(new DataInputStream(new ByteArrayInputStream(serialized)));
LinRootEntry namedTag = LinRootEntry.readFrom(stream);
UUID newUUID = null;
if (namedTag.getName().equals("entity") && namedTag.getTag() instanceof CompoundTag) {
CompoundTag serializedState = (CompoundTag) namedTag.getTag();
BaseEntity state = new BaseEntity(weType, serializedState);
if (namedTag.name().equals("entity")) {
LinCompoundTag serializedState = namedTag.value();
BaseEntity state = new BaseEntity(weType, LazyReference.computed(serializedState));
com.sk89q.worldedit.entity.Entity weEntity = weLocation.getExtent().createEntity(weLocation, state);
if (weEntity != null) {
CompoundTag newNbt = weEntity.getState().getNbtData();
int[] uuidInts = newNbt.getIntArray("UUID");
LinCompoundTag newNbt = weEntity.getState().getNbt();
LinIntArrayTag uuidTag = newNbt.findTag("UUID", LinTagType.intArrayTag());
int[] uuidInts = uuidTag == null ? null : uuidTag.value();
if (uuidInts != null && uuidInts.length >= 4) {
newUUID = new UUID(((long) uuidInts[0] << 32) | (uuidInts[1] & 0xFFFFFFFFL), ((long) uuidInts[2] << 32) | (uuidInts[3] & 0xFFFFFFFFL));
} else {
newUUID = new UUID(newNbt.getLong("UUIDMost"), newNbt.getLong("UUIDLeast")); // pre 1.16
LinLongTag uuidMostTag = newNbt.findTag("UUIDMost", LinTagType.longTag());
LinLongTag uuidLeastTag = newNbt.findTag("UUIDLeast", LinTagType.longTag());
if (uuidMostTag != null && uuidLeastTag != null) {
newUUID = new UUID(uuidMostTag.valueAsLong(), uuidLeastTag.valueAsLong()); // pre 1.16
}
}
}
}
@ -136,16 +141,18 @@ public class WorldEditHelper {
BaseEntity state = weEntity.getState();
if (state != null) {
try {
LinCompoundTag.Builder nbt = state.getNbt().toBuilder();
nbt.putFloat("Health", 20.0f);
nbt.put("Motion", LinListTag.builder(LinTagType.doubleTag()).add(LinDoubleTag.of(0)).add(LinDoubleTag.of(0)).add(LinDoubleTag.of(0)).build());
nbt.putShort("Fire", (short) -20);
nbt.putShort("HurtTime", (short) 0);
LinRootEntry root = new LinRootEntry("entity", nbt.build());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
NBTOutputStream nbtos = new NBTOutputStream(baos);
CompoundTag nbt = state.getNbtData();
LinkedHashMap<String, Tag<?, ?>> value = new LinkedHashMap<>(nbt.getValue());
value.put("Health", new FloatTag(20.0f));
value.put("Motion", new ListTag(DoubleTag.class, Arrays.asList(new DoubleTag[] { new DoubleTag(0), new DoubleTag(0), new DoubleTag(0) })));
value.put("Fire", new ShortTag((short) -20));
value.put("HurtTime", new ShortTag((short) 0));
nbtos.writeNamedTag("entity", new CompoundTag(value));
nbtos.close();
try (DataOutputStream dos = new DataOutputStream(baos)) {
LinBinaryIO.write(dos, root);
}
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("This IOException should be impossible", e);
@ -175,7 +182,7 @@ public class WorldEditHelper {
}
BlockVector3 min = selection.getMinimumPoint();
BlockVector3 max = selection.getMaximumPoint();
return new CuboidRegion(world, new BlockVector(min.getBlockX(), min.getBlockY(), min.getBlockZ()), new BlockVector(max.getBlockX(), max.getBlockY(), max.getBlockZ()));
return new CuboidRegion(world, new BlockVector(min.x(), min.y(), min.z()), new BlockVector(max.x(), max.y(), max.z()));
}
}
}