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 480 additions and 238 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

@ -205,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();

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()));
}
}
}