diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/FastClick.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/FastClick.java index c2159759..d16fb8cd 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/FastClick.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/FastClick.java @@ -16,12 +16,14 @@ package fr.neatmonster.nocheatplus.checks.inventory; import org.bukkit.Material; import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import fr.neatmonster.nocheatplus.checks.Check; import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.ViolationData; +import fr.neatmonster.nocheatplus.checks.combined.Improbable; import fr.neatmonster.nocheatplus.permissions.Permissions; import fr.neatmonster.nocheatplus.players.IPlayerData; import fr.neatmonster.nocheatplus.utilities.InventoryUtil; @@ -63,7 +65,7 @@ public class FastClick extends Check { // final double difference = 45L - time + data.fastClickLastTime; // final InventoryConfig cc = InventoryConfig.getConfig(player); // final ViolationData vd = new ViolationData(this, player, data.fastClickVL + difference, difference, cc.fastClickActions); - // if (TickTask.getLag(150, true) > 1.7f){ + // if (TickTask.getLag(150, true) > 1.7f) { // // Don't increase vl here. // cancel = vd.hasCancel(); // } @@ -92,49 +94,48 @@ public class FastClick extends Check { public boolean check(final Player player, final long now, final InventoryView view, final int slot, final ItemStack cursor, final ItemStack clicked, final boolean isShiftClick, - final InventoryData data, final InventoryConfig cc, + final String inventoryAction, final InventoryData data, final InventoryConfig cc, final IPlayerData pData) { // Take time once. final float amount; - - if (cursor != null && cc.fastClickTweaks1_5){ - final Material cursorMat = cursor.getType(); - final int cursorAmount = Math.max(1, cursor.getAmount()); - final Material clickedMat = clicked == null ? Material.AIR : clicked.getType(); - if (cursorMat != data.fastClickLastCursor && (!isShiftClick || clicked == null || clicked.getType() != data.fastClickLastClicked) || cursorMat == Material.AIR || cursorAmount != data.fastClickLastCursorAmount){ - amount = 1f; - } - else{ - if (clickedMat == Material.AIR || clickedMat == cursorMat || isShiftClick && clickedMat == data.fastClickLastClicked ){ - amount = Math.min(cc.fastClickNormalLimit , cc.fastClickShortTermLimit) / (float) (isShiftClick && clickedMat != Material.AIR ? (1.0 + Math.max(cursorAmount, InventoryUtil.getStackCount(view, clicked))) : cursorAmount) * 0.75f; - } - else{ - amount = 1f; - } - } - data.fastClickLastCursor = cursorMat; - data.fastClickLastClicked = clickedMat; - data.fastClickLastCursorAmount = cursorAmount; + final Material clickedMat = clicked == null ? Material.AIR : clicked.getType(); + final Material cursorMat; + final int cursorAmount; + if (cursor != null) { + cursorMat = cursor.getType(); + cursorAmount = Math.max(1, cursor.getAmount()); } - else{ - data.fastClickLastCursor = null; - data.fastClickLastClicked = null; - data.fastClickLastCursorAmount = 0; + else { + cursorMat = null; + cursorAmount = 0; + } + + if (inventoryAction != null) { + amount = getAmountWithAction(view, slot, clicked, clickedMat, + cursorMat, cursorAmount, isShiftClick, inventoryAction, + data, cc); + } + else if (cursor != null && cc.fastClickTweaks1_5) { + // Detect shift-click features indirectly. + amount = detectTweaks1_5(view, slot, clicked, clickedMat, + cursorMat, cursorAmount, isShiftClick, data, cc); + } + else { amount = 1f; } data.fastClickFreq.add(now, amount); float shortTerm = data.fastClickFreq.bucketScore(0); - if (shortTerm > cc.fastClickShortTermLimit){ + if (shortTerm > cc.fastClickShortTermLimit) { // Check for lag. shortTerm /= (float) TickTask.getLag(data.fastClickFreq.bucketDuration(), true); } shortTerm -= cc.fastClickShortTermLimit; float normal = data.fastClickFreq.score(1f); - if (normal > cc.fastClickNormalLimit){ + if (normal > cc.fastClickNormalLimit) { // Check for lag. normal /= (float) TickTask.getLag(data.fastClickFreq.bucketDuration() * data.fastClickFreq.numberOfBuckets(), true); } @@ -144,16 +145,80 @@ public class FastClick extends Check { boolean cancel = false; - if (violation > 0){ + if (violation > 0) { data.fastClickVL += violation; final ViolationData vd = new ViolationData(this, player, data.fastClickVL, violation, cc.fastClickActions); cancel = executeActions(vd).willCancel(); } - if (pData.isDebugActive(type) && pData.hasPermission(Permissions.ADMINISTRATION_DEBUG, player)){ - player.sendMessage("FastClick: " + data.fastClickFreq.bucketScore(0) + " | " + data.fastClickFreq.score(1f) + " | cursor=" + cursor + " | clicked=" + clicked); + if (pData.isDebugActive(type) && pData.hasPermission(Permissions.ADMINISTRATION_DEBUG, player)) { + player.sendMessage("FastClick: " + data.fastClickFreq.bucketScore(0) + " | " + data.fastClickFreq.score(1f) + " | cursor=" + cursor + " | clicked=" + clicked + " | action=" + inventoryAction); } + data.fastClickLastClicked = clickedMat; + data.fastClickLastSlot = slot; + data.fastClickLastCursor = cursorMat; + data.fastClickLastCursorAmount = cursorAmount; + + // Feed the improbable. + Improbable.feed(player, 0.7f * amount, System.currentTimeMillis()); + return cancel; } + + private float detectTweaks1_5(final InventoryView view, + final int slot, final ItemStack clicked, + final Material clickedMat, final Material cursorMat, + final int cursorAmount, final boolean isShiftClick, + final InventoryData data, final InventoryConfig cc) { + if (cursorMat != data.fastClickLastCursor + && (!isShiftClick || clickedMat == Material.AIR || clickedMat != data.fastClickLastClicked) + || cursorMat == Material.AIR || cursorAmount != data.fastClickLastCursorAmount) { + return 1f; + } + else if (clickedMat == Material.AIR || clickedMat == cursorMat + || isShiftClick && clickedMat == data.fastClickLastClicked ) { + return Math.min(cc.fastClickNormalLimit , cc.fastClickShortTermLimit) + / (float) (isShiftClick && clickedMat != Material.AIR ? (1.0 + Math.max(cursorAmount, InventoryUtil.getStackCount(view, clicked))) : cursorAmount) * 0.75f; + } + else{ + return 1f; + } + } + + private float getAmountWithAction(final InventoryView view, + final int slot, final ItemStack clicked, + final Material clickedMat, final Material cursorMat, + final int cursorAmount, final boolean isShiftClick, + final String inventoryAction, + final InventoryData data, final InventoryConfig cc) { + // Continuous drop feature with open inventories. + if (inventoryAction.equals("DROP_ONE_SLOT") + && slot == data.fastClickLastSlot + && clickedMat == data.fastClickLastClicked + && view.getType() == InventoryType.CRAFTING + // TODO: Distinguish if the inventory is really open. + ) { + return 0.6f; + } + + // Collect to cursor. + if (inventoryAction.equals("COLLECT_TO_CURSOR")) { + final int stackCount = InventoryUtil.getStackCount(view, clicked); + return stackCount <= 0 ? 1f : + Math.min(cc.fastClickNormalLimit , cc.fastClickShortTermLimit) + / stackCount * 0.75f; + } + + // Shift click features. + if ((inventoryAction.equals("MOVE_TO_OTHER_INVENTORY")) + && cursorMat != Material.AIR && cc.fastClickTweaks1_5) { + // Let the legacy method do the side condition checks and counting for now. + return detectTweaks1_5(view, slot, clicked, clickedMat, + cursorMat, cursorAmount, isShiftClick, data, cc); + } + + // + return 1f; + } } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/InventoryData.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/InventoryData.java index fab8d995..610e8272 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/InventoryData.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/InventoryData.java @@ -15,6 +15,7 @@ package fr.neatmonster.nocheatplus.checks.inventory; import org.bukkit.Material; +import org.bukkit.inventory.InventoryView; import fr.neatmonster.nocheatplus.checks.access.ACheckData; import fr.neatmonster.nocheatplus.utilities.ds.count.ActionFrequency; @@ -40,8 +41,9 @@ public class InventoryData extends ACheckData { // Data of the fast click check. // public boolean fastClickLastCancelled; public final ActionFrequency fastClickFreq = new ActionFrequency(5, 200L); - public Material fastClickLastCursor = null; public Material fastClickLastClicked = null; + public int fastClickLastSlot = InventoryView.OUTSIDE; + public Material fastClickLastCursor = null; public int fastClickLastCursorAmount = 0; // Data of the instant bow check. diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/InventoryListener.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/InventoryListener.java index 092bd674..112fa1b2 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/InventoryListener.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/inventory/InventoryListener.java @@ -64,6 +64,7 @@ import fr.neatmonster.nocheatplus.players.IPlayerData; import fr.neatmonster.nocheatplus.players.PlayerFactoryArgument; import fr.neatmonster.nocheatplus.stats.Counters; import fr.neatmonster.nocheatplus.utilities.InventoryUtil; +import fr.neatmonster.nocheatplus.utilities.ReflectionUtil; import fr.neatmonster.nocheatplus.worlds.WorldFactoryArgument; /** @@ -89,6 +90,8 @@ public class InventoryListener extends CheckListener implements JoinLeaveListen private final Open open = addCheck(new Open()); + private final boolean hasInventoryAction; + /** For temporary use: LocUtil.clone before passing deeply, call setWorld(null) after use. */ private final Location useLoc = new Location(null, 0, 0, 0); @@ -128,6 +131,7 @@ public class InventoryListener extends CheckListener implements JoinLeaveListen .addToGroups(CheckType.INVENTORY, true, IData.class, ICheckData.class) .context() // ); + hasInventoryAction = ReflectionUtil.getClass("org.bukkit.event.inventory.InventoryAction") != null; } /** @@ -219,8 +223,9 @@ public class InventoryListener extends CheckListener implements JoinLeaveListen final IPlayerData pData = DataManager.getPlayerData(player); final InventoryData data = pData.getGenericInstance(InventoryData.class); final int slot = event.getSlot(); + final String inventoryAction = hasInventoryAction ? event.getAction().name() : null; if (pData.isDebugActive(checkType)) { - outputDebugInventoryClick(player, slot, event, data); + outputDebugInventoryClick(player, slot, event, inventoryAction, data); } if (slot == InventoryView.OUTSIDE || slot < 0) { data.lastClickTime = now; @@ -252,12 +257,10 @@ public class InventoryListener extends CheckListener implements JoinLeaveListen if (player.getGameMode() != GameMode.CREATIVE || !cc.fastClickSpareCreative) { if (fastClick.check(player, now, event.getView(), slot, cursor, clicked, event.isShiftClick(), - data, cc, pData)) { + inventoryAction, data, cc, pData)) { // The check requested the event to be cancelled. cancel = true; } - // Feed the improbable. - Improbable.feed(player, 0.7f, System.currentTimeMillis()); } } data.lastClickTime = now; @@ -275,7 +278,9 @@ public class InventoryListener extends CheckListener implements JoinLeaveListen * @param event * @param data */ - private void outputDebugInventoryClick(final Player player, final int slot, final InventoryClickEvent event, + private void outputDebugInventoryClick(final Player player, + final int slot, final InventoryClickEvent event, + final String action, final InventoryData data) { // TODO: Check if this breaks legacy compat (disable there perhaps). // TODO: Consider only logging where different from expected (CraftXY, more/other viewer than player). @@ -302,6 +307,11 @@ public class InventoryListener extends CheckListener implements JoinLeaveListen // Top inventory. addInventory(player, view.getBottomInventory(), " , Top: ", builder); + + if (action != null) { + builder.append(" , Action: "); + builder.append(action); + } // Event class. builder.append(" , Event: ");