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> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.20.4-R0.1-SNAPSHOT</version> <version>1.21.5-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -125,9 +125,9 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version> <version>3.13.0</version>
<configuration> <configuration>
<release>17</release> <release>21</release>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
@ -153,7 +153,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version> <version>3.5.3</version>
<configuration> <configuration>
</configuration> </configuration>
<executions> <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.blockstate.BlockStateCodecs;
import de.diddiz.LogBlock.util.BukkitUtils; import de.diddiz.LogBlock.util.BukkitUtils;
import de.diddiz.LogBlock.util.ItemStackAndAmount;
import de.diddiz.LogBlock.util.Utils; import de.diddiz.LogBlock.util.Utils;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; 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.Sign;
import org.bukkit.block.data.type.Switch; import org.bukkit.block.data.type.Switch;
import org.bukkit.block.data.type.WallSign; import org.bukkit.block.data.type.WallSign;
import org.bukkit.inventory.ItemStack;
public class BlockChange implements LookupCacheElement { public class BlockChange implements LookupCacheElement {
public final long id, date; public final long id, date;
@ -75,7 +75,7 @@ public class BlockChange implements LookupCacheElement {
typeState = p.needType ? rs.getBytes("typeState") : null; typeState = p.needType ? rs.getBytes("typeState") : null;
ChestAccess catemp = null; ChestAccess catemp = null;
if (p.needChestAccess) { if (p.needChestAccess) {
ItemStack stack = Utils.loadItemStack(rs.getBytes("item")); ItemStackAndAmount stack = Utils.loadItemStack(rs.getBytes("item"));
if (stack != null) { if (stack != null) {
catemp = new ChestAccess(stack, rs.getBoolean("itemremove"), rs.getInt("itemtype")); catemp = new ChestAccess(stack, rs.getBoolean("itemremove"), rs.getInt("itemtype"));
} }
@ -113,7 +113,7 @@ public class BlockChange implements LookupCacheElement {
} }
@Override @Override
public BaseComponent[] getLogMessage(int entry) { public BaseComponent getLogMessage(int entry) {
TextComponent msg = new TextComponent(); TextComponent msg = new TextComponent();
if (date > 0) { if (date > 0) {
msg.addExtra(prettyDate(date)); msg.addExtra(prettyDate(date));
@ -127,7 +127,7 @@ public class BlockChange implements LookupCacheElement {
BlockData replaced = getBlockReplaced(); BlockData replaced = getBlockReplaced();
if (type == null || replaced == null) { if (type == null || replaced == null) {
msg.addExtra("did an unknown block modification"); msg.addExtra("did an unknown block modification");
return new BaseComponent[] { msg }; return msg;
} }
// Process type details once for later use. // Process type details once for later use.
@ -254,7 +254,7 @@ public class BlockChange implements LookupCacheElement {
msg.addExtra(" at "); msg.addExtra(" at ");
msg.addExtra(prettyLocation(loc, entry)); msg.addExtra(prettyLocation(loc, entry));
} }
return new BaseComponent[] { msg }; return msg;
} }
public BlockData getBlockReplaced() { public BlockData getBlockReplaced() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -101,19 +101,11 @@ public class LogBlock extends JavaPlugin {
return; return;
} }
final Statement st = conn.createStatement(); final Statement st = conn.createStatement();
try { final ResultSet rs = st.executeQuery("SHOW CHARACTER SET where charset='utf8mb4';");
final ResultSet rs = st.executeQuery("SHOW CHARACTER SET where charset='utf8mb4';"); if (rs.next()) {
if (rs.next()) { Config.mb4 = true;
Config.mb4 = true; // Allegedly JDBC driver since 2010 hasn't needed this. I did.
// Allegedly JDBC driver since 2010 hasn't needed this. I did. st.executeUpdate("SET NAMES utf8mb4;");
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());
} }
conn.close(); conn.close();
Updater updater = new Updater(this); 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)) { if (isLogging(Logging.CHAT) || isLogging(Logging.PLAYER_COMMANDS) || isLogging(Logging.CONSOLE_COMMANDS) || isLogging(Logging.COMMANDBLOCK_COMMANDS)) {
pm.registerEvents(new ChatLogging(this), this); pm.registerEvents(new ChatLogging(this), this);
} }
if (isLogging(Logging.ENDERMEN)) { if (isLogging(Logging.WITHER) || isLogging(Logging.ENDERMEN)) {
pm.registerEvents(new EndermenLogging(this), this); pm.registerEvents(new EntityChangeBlockLogging(this), this);
}
if (isLogging(Logging.WITHER)) {
pm.registerEvents(new WitherLogging(this), this);
} }
if (isLogging(Logging.NATURALSTRUCTUREGROW)) { if (isLogging(Logging.NATURALSTRUCTUREGROW)) {
pm.registerEvents(new StructureGrowLogging(this), this); pm.registerEvents(new StructureGrowLogging(this), this);

View File

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

View File

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

View File

@ -32,7 +32,7 @@ public class SummedBlockChanges implements LookupCacheElement {
} }
@Override @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); 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 @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); 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 @Override
public BaseComponent[] getLogMessage(int entry) { public BaseComponent getLogMessage(int entry) {
return MessagingUtil.formatSummarizedChanges(kills, killed, new TextComponent(player.getName()), 6, 7, spaceFactor); 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.Config;
import de.diddiz.LogBlock.config.WorldConfig; import de.diddiz.LogBlock.config.WorldConfig;
import de.diddiz.LogBlock.util.ComparableVersion; import de.diddiz.LogBlock.util.ComparableVersion;
import de.diddiz.LogBlock.util.ItemStackAndAmount;
import de.diddiz.LogBlock.util.UUIDFetcher; import de.diddiz.LogBlock.util.UUIDFetcher;
import de.diddiz.LogBlock.util.Utils; import de.diddiz.LogBlock.util.Utils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -534,7 +535,7 @@ class Updater {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
ItemStack stack = weaponMaterial.getMaxDurability() > 0 ? new ItemStack(weaponMaterial, Math.abs(amount), (short) itemdata) : new ItemStack(weaponMaterial, Math.abs(amount)); ItemStack stack = weaponMaterial.getMaxDurability() > 0 ? new ItemStack(weaponMaterial, Math.abs(amount), (short) itemdata) : new ItemStack(weaponMaterial, Math.abs(amount));
insertChestData.setLong(1, id); 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(3, amount >= 0 ? 0 : 1);
insertChestData.setInt(4, MaterialConverter.getOrAddMaterialId(weaponMaterial)); insertChestData.setInt(4, MaterialConverter.getOrAddMaterialId(weaponMaterial));
insertChestData.addBatch(); insertChestData.addBatch();
@ -869,150 +870,49 @@ class Updater {
} }
final Statement state = conn.createStatement(); final Statement state = conn.createStatement();
conn.setAutoCommit(true); conn.setAutoCommit(true);
createTable(state, "lb-players", 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);
"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
);
// Players table must not be empty or inserts won't work - bug #492 // 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()) { 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)) { if (isLogging(Logging.CHAT) || isLogging(Logging.PLAYER_COMMANDS) || isLogging(Logging.CONSOLE_COMMANDS) || isLogging(Logging.COMMANDBLOCK_COMMANDS)) {
try { try {
createTable(state, "lb-chat", 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);
"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) { } catch (SQLException e) {
createTable(state, "lb-chat", 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);
"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", createTable(state, "lb-materials", "(id INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
"id INT UNSIGNED NOT NULL, " + createTable(state, "lb-blockstates", "(id INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
"name VARCHAR(255) NOT NULL, " + createTable(state, "lb-entitytypes", "(id INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
"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()) { for (final WorldConfig wcfg : getLoggedWorlds()) {
createTable(state, wcfg.table + "-blocks", 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))");
"id INT UNSIGNED NOT NULL AUTO_INCREMENT, " + createTable(state, wcfg.table + "-chestdata", "(id INT UNSIGNED NOT NULL, item MEDIUMBLOB, itemremove TINYINT, itemtype SMALLINT NOT NULL DEFAULT '0', PRIMARY KEY (id))");
"date DATETIME NOT NULL, " + createTable(state, wcfg.table + "-state", "(id INT UNSIGNED NOT NULL, replacedState MEDIUMBLOB NULL, typeState MEDIUMBLOB NULL, PRIMARY KEY (id))");
"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)) { if (wcfg.isLogging(Logging.KILL)) {
createTable(state, wcfg.table + "-kills", 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))");
"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", 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))");
"entityid INT UNSIGNED NOT NULL AUTO_INCREMENT, " + 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))");
"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(); state.close();
conn.close(); conn.close();
} }
private void createTable(Statement state, String table, String columns, String additional) throws SQLException { private void createTable(Statement state, String table, String query) throws SQLException {
logblock.getLogger().log(Level.INFO, "Creating table " + table + "."); try (ResultSet tableResult = state.executeQuery("SHOW TABLES LIKE '" + table + "'")) {
String sql = "CREATE TABLE IF NOT EXISTS \"" + table + "\" (" + if (!tableResult.next()) {
( logblock.getLogger().log(Level.INFO, "Creating table " + table + ".");
columns state.execute("CREATE TABLE `" + table + "` " + query);
.replace("INT UNSIGNED NOT NULL AUTO_INCREMENT", "SERIAL NOT NULL") try (ResultSet tableResultNew = state.executeQuery("SHOW TABLES LIKE '" + table + "'")) {
.replace("INT UNSIGNED", "INT") if (!tableResultNew.next()) {
.replace("DATETIME", "TIMESTAMP") throw new SQLException("Table " + table + " not found and failed to create");
) }
+ ") " /* + 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");
// }
//}
} }
/** /**

View File

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

View File

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

View File

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

View File

@ -7,8 +7,8 @@ import java.util.List;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
public enum EntityLogging { public enum EntityLogging {
SPAWN(new String[] { EntityType.ARMOR_STAND.name(), EntityType.ITEM_FRAME.name(), EntityType.SNOWMAN.name() }), 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.SNOWMAN.name(), "ANIMAL" }), DESTROY(new String[] { EntityType.ARMOR_STAND.name(), EntityType.ITEM_FRAME.name(), EntityType.VILLAGER.name(), EntityType.SNOW_GOLEM.name(), "ANIMAL" }),
MODIFY(new String[] { "ALL" }); MODIFY(new String[] { "ALL" });
public static final int length = EntityLogging.values().length; 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.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ArmorStand; import org.bukkit.entity.ArmorStand;
@ -192,18 +193,17 @@ public class AdvancedEntityLogging extends LoggingListener {
LivingEntity entity = event.getEntity(); LivingEntity entity = event.getEntity();
if (Config.isLogging(entity.getWorld(), EntityLogging.DESTROY, entity)) { if (Config.isLogging(entity.getWorld(), EntityLogging.DESTROY, entity)) {
Actor actor = null; Actor actor = null;
EntityDamageEvent lastDamage = entity.getLastDamageCause(); Entity cause = event.getDamageSource().getCausingEntity();
if (lastDamage instanceof EntityDamageByEntityEvent) { Entity damager = LoggingUtil.getRealDamager(cause);
Entity damager = LoggingUtil.getRealDamager(((EntityDamageByEntityEvent) lastDamage).getDamager()); if (damager != null) {
if (damager != null) { actor = Actor.actorFromEntity(damager);
actor = Actor.actorFromEntity(damager);
}
} }
if (actor == null && entity.getKiller() != null) { if (actor == null && entity.getKiller() != null) {
actor = Actor.actorFromEntity(entity.getKiller()); actor = Actor.actorFromEntity(entity.getKiller());
} }
if (actor == null) { 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); 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.Actor;
import de.diddiz.LogBlock.LogBlock; import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging; import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.util.ItemStackAndAmount;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.block.DecoratedPot;
import org.bukkit.block.DoubleChest; import org.bukkit.block.DoubleChest;
import org.bukkit.block.data.type.ChiseledBookshelf;
import org.bukkit.entity.HumanEntity; import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryOpenEvent; 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.Inventory;
import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -69,9 +79,10 @@ public class ChestAccessLogging extends LoggingListener {
for (Entry<ItemStack, Integer> e : modifications.entrySet()) { for (Entry<ItemStack, Integer> e : modifications.entrySet()) {
ItemStack stack = e.getKey(); ItemStack stack = e.getKey();
int amount = e.getValue(); int amount = e.getValue();
stack.setAmount(Math.abs(amount)); if (amount != 0) {
// consumer.getLogblock().getLogger().info("Store container: " + stack + " take: " + (amount < 0)); // consumer.getLogblock().getLogger().info("Store container: " + stack + " take: " + (amount < 0));
consumer.queueChestAccess(Actor.actorFromEntity(actor), location, location.getWorld().getBlockAt(location).getBlockData(), stack, amount < 0); consumer.queueChestAccess(Actor.actorFromEntity(actor), location, location.getWorld().getBlockAt(location).getBlockData(), new ItemStackAndAmount(stack, Math.abs(amount)), amount < 0);
}
} }
modifications.clear(); 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.config.WorldConfig;
import de.diddiz.LogBlock.util.BukkitUtils; import de.diddiz.LogBlock.util.BukkitUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ExplosionResult;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
@ -41,6 +42,9 @@ public class ExplosionLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityExplode(EntityExplodeEvent event) { public void onEntityExplode(EntityExplodeEvent event) {
if (event.getExplosionResult() == ExplosionResult.KEEP || event.getExplosionResult() == ExplosionResult.TRIGGER_BLOCK) {
return;
}
final WorldConfig wcfg = getWorldConfig(event.getLocation().getWorld()); final WorldConfig wcfg = getWorldConfig(event.getLocation().getWorld());
if (wcfg != null) { if (wcfg != null) {
Actor actor = new Actor("Explosion"); Actor actor = new Actor("Explosion");
@ -174,6 +178,9 @@ public class ExplosionLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockExplode(BlockExplodeEvent event) { public void onBlockExplode(BlockExplodeEvent event) {
if (event.getExplosionResult() == ExplosionResult.KEEP || event.getExplosionResult() == ExplosionResult.TRIGGER_BLOCK) {
return;
}
Player bedCause = null; Player bedCause = null;
if (lastBedInteractionPlayer != null && lastBedInteractionLocation != null) { if (lastBedInteractionPlayer != null && lastBedInteractionLocation != null) {
Location block = event.getBlock().getLocation(); 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.LogBlock;
import de.diddiz.LogBlock.Logging; import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.Config.*; import de.diddiz.LogBlock.config.Config.*;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Monster; import org.bukkit.entity.Monster;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityDeathEvent;
import static de.diddiz.LogBlock.config.Config.*; import static de.diddiz.LogBlock.config.Config.*;
@ -24,27 +23,26 @@ public class KillLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityDeath(EntityDeathEvent deathEvent) { public void onEntityDeath(EntityDeathEvent deathEvent) {
EntityDamageEvent event = deathEvent.getEntity().getLastDamageCause(); if (isLogging(deathEvent.getEntity().getWorld(), Logging.KILL)) {
// For a death event, there should always be a damage event and it should not be cancelled. Check anyway. LivingEntity victim = deathEvent.getEntity();
if (event != null && event.isCancelled() == false && isLogging(event.getEntity().getWorld(), Logging.KILL) && event.getEntity() instanceof LivingEntity) { Entity killer = deathEvent.getDamageSource().getCausingEntity();
final LivingEntity victim = (LivingEntity) event.getEntity(); if (killer != null) {
if (event instanceof EntityDamageByEntityEvent) {
final Entity killer = ((EntityDamageByEntityEvent) event).getDamager();
if (logKillsLevel == LogKillsLevel.PLAYERS && !(victim instanceof Player && killer instanceof Player)) { if (logKillsLevel == LogKillsLevel.PLAYERS && !(victim instanceof Player && killer instanceof Player)) {
return; return;
} else if (logKillsLevel == LogKillsLevel.MONSTERS && !((victim instanceof Player || victim instanceof Monster) && killer instanceof Player || killer instanceof Monster)) { } else if (logKillsLevel == LogKillsLevel.MONSTERS && !((victim instanceof Player || victim instanceof Monster) && killer instanceof Player || killer instanceof Monster)) {
return; return;
} }
consumer.queueKill(killer, victim); consumer.queueKill(killer, victim);
} else if (deathEvent.getEntity().getKiller() != null) {
consumer.queueKill(deathEvent.getEntity().getKiller(), victim);
} else if (logEnvironmentalKills) { } else if (logEnvironmentalKills) {
if (logKillsLevel == LogKillsLevel.PLAYERS && !(victim instanceof Player)) { if (logKillsLevel == LogKillsLevel.PLAYERS && !(victim instanceof Player)) {
return; return;
} else if (logKillsLevel == LogKillsLevel.MONSTERS && !((victim instanceof Player || victim instanceof Monster))) { } else if (logKillsLevel == LogKillsLevel.MONSTERS && !((victim instanceof Player || victim instanceof Monster))) {
return; 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; package de.diddiz.LogBlock.listeners;
import static de.diddiz.LogBlock.config.Config.getWorldConfig; 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.Actor;
import de.diddiz.LogBlock.LogBlock; import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging; import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.WorldConfig; import de.diddiz.LogBlock.config.WorldConfig;
import org.bukkit.Bukkit;
import org.bukkit.Material; 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.block.Lectern;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; 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.event.player.PlayerTakeLecternBookEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
public class LecternLogging extends LoggingListener { public class LecternLogging extends LoggingListener {
@ -21,25 +27,31 @@ public class LecternLogging extends LoggingListener {
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) { public void onPlayerInteract(PlayerInteractEvent event) {
final WorldConfig wcfg = getWorldConfig(event.getPlayer().getWorld()); final Block clicked = event.getClickedBlock();
if (wcfg != null && wcfg.isLogging(Logging.LECTERNBOOKCHANGE)) { if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getHand() != EquipmentSlot.HAND || !event.hasBlock() || clicked == null) {
final BlockState before = event.getBlockReplacedState(); return;
final BlockState after = event.getBlockPlaced().getState(); }
if (before.getType() == Material.LECTERN && after.getType() == Material.LECTERN) { final Player player = event.getPlayer();
Lectern lecternBefore = (Lectern) before.getBlock().getState(); if (!isLogging(player.getWorld(), Logging.LECTERNBOOKCHANGE)) {
ItemStack book = lecternBefore.getSnapshotInventory().getItem(0); return;
try { }
lecternBefore.getSnapshotInventory().setItem(0, null); final Material type = clicked.getType();
} catch (NullPointerException e) { if (type == Material.LECTERN) {
//ignored ItemStack mainHand = player.getInventory().getItemInMainHand();
} if (mainHand != null && mainHand.getType() != Material.AIR && Tag.ITEMS_LECTERN_BOOKS.isTagged(mainHand.getType()) && clicked.getState() instanceof Lectern lectern) {
lecternBefore.setBlockData(before.getBlockData()); ItemStack currentInLectern = lectern.getSnapshotInventory().getItem(0);
consumer.queueBlockReplace(Actor.actorFromEntity(event.getPlayer()), lecternBefore, after); if (currentInLectern == null || currentInLectern.getType() == Material.AIR) {
try { ItemStack stack = mainHand.clone();
lecternBefore.getSnapshotInventory().setItem(0, book); stack.setAmount(1);
} catch (NullPointerException e) { Lectern newLectern = (Lectern) clicked.getState();
//ignored 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 de.diddiz.LogBlock.LogBlock;
import java.io.File; import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -147,8 +148,11 @@ public class BukkitUtils {
// https://minecraft.fandom.com/wiki/Tag#blocks_small_flowers // https://minecraft.fandom.com/wiki/Tag#blocks_small_flowers
Set<Material> smallFlowers = Tag.SMALL_FLOWERS.getValues(); Set<Material> smallFlowers = Tag.SMALL_FLOWERS.getValues();
// https://minecraft.fandom.com/wiki/Tag#blocks_tall_flowers Set<Material> tallFlowers = Set.of(Material.SUNFLOWER,
Set<Material> tallFlowers = Tag.TALL_FLOWERS.getValues(); Material.LILAC,
Material.PEONY,
Material.ROSE_BUSH,
Material.PITCHER_PLANT);
Set<Material> bannerStanding = Set.of(Material.WHITE_BANNER, Set<Material> bannerStanding = Set.of(Material.WHITE_BANNER,
Material.ORANGE_BANNER, Material.ORANGE_BANNER,
@ -331,6 +335,7 @@ public class BukkitUtils {
containerBlocks.add(Material.BLAST_FURNACE); containerBlocks.add(Material.BLAST_FURNACE);
containerBlocks.add(Material.SMOKER); containerBlocks.add(Material.SMOKER);
containerBlocks.add(Material.CHISELED_BOOKSHELF); containerBlocks.add(Material.CHISELED_BOOKSHELF);
containerBlocks.add(Material.DECORATED_POT);
// Doesn't actually have a block inventory // Doesn't actually have a block inventory
// containerBlocks.add(Material.ENDER_CHEST); // containerBlocks.add(Material.ENDER_CHEST);
@ -341,12 +346,13 @@ public class BukkitUtils {
projectileItems.put(EntityType.ENDER_PEARL, Material.ENDER_PEARL); projectileItems.put(EntityType.ENDER_PEARL, Material.ENDER_PEARL);
projectileItems.put(EntityType.SMALL_FIREBALL, Material.FIRE_CHARGE); // Fire charge projectileItems.put(EntityType.SMALL_FIREBALL, Material.FIRE_CHARGE); // Fire charge
projectileItems.put(EntityType.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.SNOWBALL, Material.SNOWBALL);
projectileItems.put(EntityType.SPLASH_POTION, Material.SPLASH_POTION); 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.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 = new HashSet<>();
nonFluidProofBlocks.addAll(carpets); nonFluidProofBlocks.addAll(carpets);
@ -498,24 +504,22 @@ public class BukkitUtils {
return diff.toArray(new ItemStack[diff.size()]); return diff.toArray(new ItemStack[diff.size()]);
} }
public static ItemStack[] compressInventory(ItemStack[] items) { public static Collection<ItemStackAndAmount> compressInventory(ItemStack[] items) {
final ArrayList<ItemStack> compressed = new ArrayList<>(); final HashMap<ItemStack, Integer> compressed = new HashMap<>();
for (final ItemStack item : items) { for (final ItemStack item : items) {
if (item != null) { if (item != null && item.getType() != Material.AIR && item.getAmount() > 0) {
boolean found = false; int amount = item.getAmount();
for (final ItemStack item2 : compressed) { ItemStack stack = item.clone();
if (item2.isSimilar(item)) { stack.setAmount(1);
item2.setAmount(item2.getAmount() + item.getAmount()); Integer old = compressed.get(stack);
found = true; compressed.put(stack, (old == null ? 0 : old) + amount);
break;
}
}
if (!found) {
compressed.add(item.clone());
}
} }
} }
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) { public static String friendlyWorldname(String worldName) {
@ -602,15 +606,13 @@ public class BukkitUtils {
return y; return y;
} }
public static int modifyContainer(BlockState b, ItemStack item, boolean remove) { public static int modifyContainer(BlockState b, ItemStackAndAmount item, boolean remove) {
if (b instanceof InventoryHolder) { if (item.amount() > 0 && b instanceof InventoryHolder c) {
final Inventory inv = ((InventoryHolder) b).getInventory(); final Inventory inv = c.getInventory();
if (remove) { if (remove) {
final ItemStack tmp = inv.removeItem(item).get(0); return InventoryUtils.removeFromInventory(inv, item);
return tmp != null ? tmp.getAmount() : 0; } else {
} else if (item.getAmount() > 0) { return InventoryUtils.addToInventory(inv, item);
final ItemStack tmp = inv.addItem(item).get(0);
return tmp != null ? tmp.getAmount() : 0;
} }
} }
return 0; return 0;
@ -656,16 +658,16 @@ public class BukkitUtils {
return m == Material.AIR || m == Material.CAVE_AIR || m == Material.VOID_AIR; return m == Material.AIR || m == Material.CAVE_AIR || m == Material.VOID_AIR;
} }
public static TextComponent toString(ItemStack stack) { public static TextComponent toString(ItemStackAndAmount stack) {
if (stack == null || stack.getAmount() == 0 || isEmpty(stack.getType())) { if (stack == null || stack.stack() == null || stack.amount() == 0 || isEmpty(stack.stack().getType())) {
return prettyMaterial("nothing"); return prettyMaterial("nothing");
} }
TextComponent msg = MessagingUtil.createTextComponentWithColor(stack.getAmount() + "x ", TypeColor.DEFAULT.getColor()); TextComponent msg = MessagingUtil.createTextComponentWithColor(stack.amount() + "x ", TypeColor.DEFAULT.getColor());
msg.addExtra(prettyMaterial(stack.getType())); msg.addExtra(prettyMaterial(stack.stack().getType()));
try { try {
String itemTag = getItemTag(stack); String itemTag = stack.stack().getItemMeta().getAsString();
msg.setHoverEvent(new HoverEvent(Action.SHOW_ITEM, new Item(stack.getType().getKey().toString(), 1, itemTag != null ? ItemTag.ofNbt(itemTag) : null))); msg.setHoverEvent(new HoverEvent(Action.SHOW_ITEM, new Item(stack.stack().getType().getKey().toString(), 1, itemTag != null ? ItemTag.ofNbt(itemTag) : null)));
} catch (Exception e) { } catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Failed to convert Itemstack to JSON", 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()) }))); 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; 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) { public static String formatMinecraftKey(String s) {
char[] cap = s.toCharArray(); char[] cap = s.toCharArray();
boolean lastSpace = true; 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; import org.bukkit.entity.EntityType;
public class MessagingUtil { 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 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()); 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) { 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.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -45,7 +46,7 @@ public class UUIDFetcher {
} }
private static HttpURLConnection createConnection() throws Exception { private static HttpURLConnection createConnection() throws Exception {
URL url = new URL(PROFILE_URL); URL url = new URI(PROFILE_URL).toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST"); connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json"); connection.setRequestProperty("Content-Type", "application/json");

View File

@ -231,20 +231,35 @@ public class Utils {
return untrusted.replace("\\", "\\\\").replace("'", "\\'"); return untrusted.replace("\\", "\\\\").replace("'", "\\'");
} }
public static ItemStack loadItemStack(byte[] data) { public static ItemStackAndAmount loadItemStack(byte[] data) {
if (data == null || data.length == 0) { if (data == null || data.length == 0) {
return null; return null;
} }
YamlConfiguration conf = deserializeYamlConfiguration(data); 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) { public static byte[] saveItemStack(ItemStackAndAmount stack) {
if (stack == null || BukkitUtils.isEmpty(stack.getType())) { if (stack == null || stack.stack() == null || BukkitUtils.isEmpty(stack.stack().getType())) {
return null; return null;
} }
YamlConfiguration conf = new YamlConfiguration(); 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); return serializeYamlConfiguration(conf);
} }

View File

@ -2,10 +2,10 @@ package de.diddiz.LogBlock.worldedit;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
@ -17,15 +17,15 @@ import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.util.BlockVector; import org.bukkit.util.BlockVector;
import com.sk89q.jnbt.CompoundTag; import org.enginehub.linbus.stream.LinBinaryIO;
import com.sk89q.jnbt.DoubleTag; import org.enginehub.linbus.stream.LinStream;
import com.sk89q.jnbt.FloatTag; import org.enginehub.linbus.tree.LinCompoundTag;
import com.sk89q.jnbt.ListTag; import org.enginehub.linbus.tree.LinDoubleTag;
import com.sk89q.jnbt.NBTInputStream; import org.enginehub.linbus.tree.LinIntArrayTag;
import com.sk89q.jnbt.NBTOutputStream; import org.enginehub.linbus.tree.LinListTag;
import com.sk89q.jnbt.NamedTag; import org.enginehub.linbus.tree.LinLongTag;
import com.sk89q.jnbt.ShortTag; import org.enginehub.linbus.tree.LinRootEntry;
import com.sk89q.jnbt.Tag; import org.enginehub.linbus.tree.LinTagType;
import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.bukkit.BukkitAdapter; 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.entity.BaseEntity;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.concurrency.LazyReference;
import de.diddiz.LogBlock.LogBlock; import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.util.CuboidRegion; 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.world.entity.EntityType weType = BukkitAdapter.adapt(type);
com.sk89q.worldedit.util.Location weLocation = BukkitAdapter.adapt(location); com.sk89q.worldedit.util.Location weLocation = BukkitAdapter.adapt(location);
try { try {
NBTInputStream nbtis = new NBTInputStream(new ByteArrayInputStream(serialized)); LinStream stream = LinBinaryIO.read(new DataInputStream(new ByteArrayInputStream(serialized)));
NamedTag namedTag = nbtis.readNamedTag(); LinRootEntry namedTag = LinRootEntry.readFrom(stream);
nbtis.close();
UUID newUUID = null; UUID newUUID = null;
if (namedTag.getName().equals("entity") && namedTag.getTag() instanceof CompoundTag) { if (namedTag.name().equals("entity")) {
CompoundTag serializedState = (CompoundTag) namedTag.getTag(); LinCompoundTag serializedState = namedTag.value();
BaseEntity state = new BaseEntity(weType, serializedState); BaseEntity state = new BaseEntity(weType, LazyReference.computed(serializedState));
com.sk89q.worldedit.entity.Entity weEntity = weLocation.getExtent().createEntity(weLocation, state); com.sk89q.worldedit.entity.Entity weEntity = weLocation.getExtent().createEntity(weLocation, state);
if (weEntity != null) { if (weEntity != null) {
CompoundTag newNbt = weEntity.getState().getNbtData(); LinCompoundTag newNbt = weEntity.getState().getNbt();
int[] uuidInts = newNbt.getIntArray("UUID"); LinIntArrayTag uuidTag = newNbt.findTag("UUID", LinTagType.intArrayTag());
int[] uuidInts = uuidTag == null ? null : uuidTag.value();
if (uuidInts != null && uuidInts.length >= 4) { if (uuidInts != null && uuidInts.length >= 4) {
newUUID = new UUID(((long) uuidInts[0] << 32) | (uuidInts[1] & 0xFFFFFFFFL), ((long) uuidInts[2] << 32) | (uuidInts[3] & 0xFFFFFFFFL)); newUUID = new UUID(((long) uuidInts[0] << 32) | (uuidInts[1] & 0xFFFFFFFFL), ((long) uuidInts[2] << 32) | (uuidInts[3] & 0xFFFFFFFFL));
} else { } 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(); BaseEntity state = weEntity.getState();
if (state != null) { if (state != null) {
try { 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(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
NBTOutputStream nbtos = new NBTOutputStream(baos); try (DataOutputStream dos = new DataOutputStream(baos)) {
CompoundTag nbt = state.getNbtData(); LinBinaryIO.write(dos, root);
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();
return baos.toByteArray(); return baos.toByteArray();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("This IOException should be impossible", e); throw new RuntimeException("This IOException should be impossible", e);
@ -175,7 +182,7 @@ public class WorldEditHelper {
} }
BlockVector3 min = selection.getMinimumPoint(); BlockVector3 min = selection.getMinimumPoint();
BlockVector3 max = selection.getMaximumPoint(); 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()));
} }
} }
} }