/
BlockBreakListener.java
276 lines (232 loc) · 11.8 KB
/
BlockBreakListener.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
package fr.neatmonster.nocheatplus.checks.blockbreak;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockDamageEvent;
import org.bukkit.event.player.PlayerAnimationEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.inventory.ItemStack;
import fr.neatmonster.nocheatplus.checks.CheckListener;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.inventory.Items;
import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
import fr.neatmonster.nocheatplus.permissions.Permissions;
import fr.neatmonster.nocheatplus.utilities.BlockProperties;
import fr.neatmonster.nocheatplus.utilities.TickTask;
/*
* M#"""""""'M dP dP M#"""""""'M dP
* ## mmmm. `M 88 88 ## mmmm. `M 88
* #' .M 88 .d8888b. .d8888b. 88 .dP #' .M 88d888b. .d8888b. .d8888b. 88 .dP
* M# MMMb.'YM 88 88' `88 88' `"" 88888" M# MMMb.'YM 88' `88 88ooood8 88' `88 88888"
* M# MMMM' M 88 88. .88 88. ... 88 `8b. M# MMMM' M 88 88. ... 88. .88 88 `8b.
* M# .;M dP `88888P' `88888P' dP `YP M# .;M dP `88888P' `88888P8 dP `YP
* M#########M M#########M
*
* M""MMMMMMMM oo dP
* M MMMMMMMM 88
* M MMMMMMMM dP .d8888b. d8888P .d8888b. 88d888b. .d8888b. 88d888b.
* M MMMMMMMM 88 Y8ooooo. 88 88ooood8 88' `88 88ooood8 88' `88
* M MMMMMMMM 88 88 88 88. ... 88 88 88. ... 88
* M M dP `88888P' dP `88888P' dP dP `88888P' dP
* MMMMMMMMMMM
*/
/**
* Central location to listen to events that are relevant for the block break checks.
*
* @see BlockBreakEvent
*/
public class BlockBreakListener extends CheckListener {
/** The direction check. */
private final Direction direction = addCheck(new Direction());
/** The fast break check (per block breaking speed). */
private final FastBreak fastBreak = addCheck(new FastBreak());
/** The frequency check (number of blocks broken) */
private final Frequency frequency = addCheck(new Frequency());
/** The no swing check. */
private final NoSwing noSwing = addCheck(new NoSwing());
/** The reach check. */
private final Reach reach = addCheck(new Reach());
/** The wrong block check. */
private final WrongBlock wrongBlock = addCheck(new WrongBlock());
private boolean isInstaBreak = false;
public BlockBreakListener(){
super(CheckType.BLOCKBREAK);
}
/**
* We listen to BlockBreak events for obvious reasons.
*
* @param event
* the event
*/
@EventHandler(
ignoreCancelled = false, priority = EventPriority.LOWEST)
public void onBlockBreak(final BlockBreakEvent event) {
/*
* ____ _ _ ____ _
* | __ )| | ___ ___| | __ | __ ) _ __ ___ __ _| | __
* | _ \| |/ _ \ / __| |/ / | _ \| '__/ _ \/ _` | |/ /
* | |_) | | (_) | (__| < | |_) | | | __/ (_| | <
* |____/|_|\___/ \___|_|\_\ |____/|_| \___|\__,_|_|\_\
*/
final Player player = event.getPlayer();
// Illegal enchantments hotfix check.
if (Items.checkIllegalEnchantments(player, player.getItemInHand())) event.setCancelled(true);
// Cancelled events only leads to resetting insta break.
if (event.isCancelled()){
isInstaBreak = false;
return;
}
// TODO: maybe invalidate instaBreak on some occasions.
final Block block = event.getBlock();
boolean cancelled = false;
// Do the actual checks, if still needed. It's a good idea to make computationally cheap checks first, because
// it may save us from doing the computationally expensive checks.
final BlockBreakConfig cc = BlockBreakConfig.getConfig(player);
final BlockBreakData data = BlockBreakData.getData(player);
final long now = System.currentTimeMillis();
final GameMode gameMode = player.getGameMode();
// Has the player broken a block that was not damaged before?
if (wrongBlock.isEnabled(player) && wrongBlock.check(player, block, cc, data, isInstaBreak))
cancelled = true;
// Has the player broken more blocks per second than allowed?
if (!cancelled && frequency.isEnabled(player) && frequency.check(player, cc, data))
cancelled = true;
// Has the player broken blocks faster than possible?
if (!cancelled && gameMode != GameMode.CREATIVE && fastBreak.isEnabled(player) && fastBreak.check(player, block, isInstaBreak, cc, data))
cancelled = true;
// Did the arm of the player move before breaking this block?
if (!cancelled && noSwing.isEnabled(player) && noSwing.check(player, data))
cancelled = true;
// Is the block really in reach distance?
if (!cancelled && reach.isEnabled(player) && reach.check(player, block, data))
cancelled = true;
// Did the player look at the block at all?
if (!cancelled && direction.isEnabled(player) && direction.check(player, block, data))
cancelled = true;
// Destroying liquid blocks.
if (!cancelled && BlockProperties.isLiquid(block.getTypeId()) && !player.hasPermission(Permissions.BLOCKBREAK_BREAK_LIQUID) && !NCPExemptionManager.isExempted(player, CheckType.BLOCKBREAK_BREAK)){
cancelled = true;
}
// At least one check failed and demanded to cancel the event.
if (cancelled){
event.setCancelled(cancelled);
// Reset damage position:
data.clickedX = block.getX();
data.clickedY = block.getY();
data.clickedZ = block.getZ();
}
else{
// Invalidate last damage position:
// data.clickedX = Integer.MAX_VALUE;
}
if (isInstaBreak){
data.wasInstaBreak = now;
}
else
data.wasInstaBreak = 0;
// Adjust data.
data.fastBreakBreakTime = now;
// data.fastBreakfirstDamage = now;
isInstaBreak = false;
}
/**
* We listen to PlayerAnimation events because it is (currently) equivalent to "player swings arm" and we want to
* check if they did that between block breaks.
*
* @param event
* the event
*/
@EventHandler(
priority = EventPriority.MONITOR)
public void onPlayerAnimation(final PlayerAnimationEvent event) {
/*
* ____ _ _ _ _ _
* | _ \| | __ _ _ _ ___ _ __ / \ _ __ (_)_ __ ___ __ _| |_(_) ___ _ __
* | |_) | |/ _` | | | |/ _ \ '__| / _ \ | '_ \| | '_ ` _ \ / _` | __| |/ _ \| '_ \
* | __/| | (_| | |_| | __/ | / ___ \| | | | | | | | | | (_| | |_| | (_) | | | |
* |_| |_|\__,_|\__, |\___|_| /_/ \_\_| |_|_|_| |_| |_|\__,_|\__|_|\___/|_| |_|
* |___/
*/
// Just set a flag to true when the arm was swung.
// System.out.println("Animation");
BlockBreakData.getData(event.getPlayer()).noSwingArmSwung = true;
}
/**
* We listen to BlockInteract events to be (at least in many cases) able to distinguish between block break events
* that were triggered by players actually digging and events that were artificially created by plugins.
*
* @param event
* the event
*/
@EventHandler(
ignoreCancelled = false, priority = EventPriority.LOWEST)
public void onPlayerInteract(final PlayerInteractEvent event) {
/*
* ____ _ ___ _ _
* | _ \| | __ _ _ _ ___ _ __ |_ _|_ __ | |_ ___ _ __ __ _ ___| |_
* | |_) | |/ _` | | | |/ _ \ '__| | || '_ \| __/ _ \ '__/ _` |/ __| __|
* | __/| | (_| | |_| | __/ | | || | | | || __/ | | (_| | (__| |_
* |_| |_|\__,_|\__, |\___|_| |___|_| |_|\__\___|_| \__,_|\___|\__|
* |___/
*/
// System.out.println("Interact("+event.isCancelled()+"): " + event.getClickedBlock());
// The following is to set the "first damage time" for a block.
// Return if it is not left clicking a block.
// (Allows right click to be ignored.)
isInstaBreak = false;
if (event.getAction() != Action.LEFT_CLICK_BLOCK) return;
checkBlockDamage(event.getPlayer(), event.getClickedBlock(), event);
}
@EventHandler(
ignoreCancelled = false, priority = EventPriority.MONITOR)
public void onBlockDamage(final BlockDamageEvent event) {
// System.out.println("Damage("+event.isCancelled()+"): " + event.getBlock());
if (!event.isCancelled() && event.getInstaBreak()) isInstaBreak = true;
else isInstaBreak = false;
checkBlockDamage(event.getPlayer(), event.getBlock(), event);
}
private void checkBlockDamage(final Player player, final Block block, final Cancellable event){
final long now = System.currentTimeMillis();
final BlockBreakData data = BlockBreakData.getData(player);
// if (event.isCancelled()){
// // Reset the time, to avoid certain kinds of cheating. => WHICH ?
// data.fastBreakfirstDamage = now;
// data.clickedX = Integer.MAX_VALUE; // Should be enough to reset that one.
// return;
// }
// Do not care about null blocks.
if (block == null)
return;
final int tick = TickTask.getTick();
// Skip if already set to the same block without breaking within one tick difference.
final ItemStack stack = player.getItemInHand();
final Material tool = stack == null ? null: stack.getType();
if (data.toolChanged(tool)) {
// Update.
} else if (tick < data.clickedTick) {
// Update.
} else if (data.fastBreakBreakTime < data.fastBreakfirstDamage && data.clickedX == block.getX() && data.clickedZ == block.getZ() && data.clickedY == block.getY()){
if (tick - data.clickedTick <= 1 ) return;
}
// (Always set, the interact event only fires once: the first time.)
// Only record first damage:
data.setClickedBlock(block, tick, now, tool);
}
@EventHandler(ignoreCancelled = false, priority = EventPriority.MONITOR)
public void onItemHeld(final PlayerItemHeldEvent event) {
// Reset clicked block.
// TODO: Not for 1.5.2 and before?
final Player player = event.getPlayer();
final BlockBreakData data = BlockBreakData.getData(player);
if (data.toolChanged(player.getInventory().getItem(event.getNewSlot()))) {
data.resetClickedBlock();
}
}
}