[BLEEDING] One more step on slimes with pistons.

* Less fall damage.
* Flag all velocity added due to slime bouncing appropriately.
* Experimental concept for splitting up velocity, likely to be altered /
removed.
* Add a flag for (faked) pvp velocity.

NOTE: Invalidation of past entries has been deactivated to progress on detecting the stupid past bouncing at all. This will need another iteration.
This commit is contained in:
asofold 2016-12-15 20:56:14 +01:00
parent 6d28354f11
commit cf3ea4a1e2
11 changed files with 238 additions and 55 deletions

View File

@ -51,6 +51,7 @@ import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveInfo;
import fr.neatmonster.nocheatplus.checks.moving.player.UnusedVelocity;
import fr.neatmonster.nocheatplus.checks.moving.util.AuxMoving;
import fr.neatmonster.nocheatplus.checks.moving.util.MovingUtil;
import fr.neatmonster.nocheatplus.checks.moving.velocity.VelocityFlags;
import fr.neatmonster.nocheatplus.compat.Bridge1_9;
import fr.neatmonster.nocheatplus.compat.BridgeEnchant;
import fr.neatmonster.nocheatplus.compat.BridgeHealth;
@ -646,7 +647,7 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
if (damagedData.debug || mdata.debug) {
debug(damagedPlayer, "Received knockback level: " + level);
}
mdata.addVelocity(damagedPlayer, mcc, vx, vy, vz);
mdata.addVelocity(damagedPlayer, mcc, vx, vy, vz, VelocityFlags.ORIGIN_PVP);
}
/**

View File

@ -776,6 +776,7 @@ public class MovingData extends ACheckData {
/**
* Add velocity to internal book-keeping.
*
* @param player
* @param data
* @param cc
@ -783,7 +784,25 @@ public class MovingData extends ACheckData {
* @param vy
* @param vz
*/
public void addVelocity(final Player player, final MovingConfig cc, final double vx, final double vy, final double vz) {
public void addVelocity(final Player player, final MovingConfig cc,
final double vx, final double vy, final double vz) {
addVelocity(player, cc, vx, vy, vz, 0L);
}
/**
* Add velocity to internal book-keeping.
*
* @param player
* @param data
* @param cc
* @param vx
* @param vy
* @param vz
* @param flags
* Flags to use with velocity entries.
*/
public void addVelocity(final Player player, final MovingConfig cc,
final double vx, final double vy, final double vz, final long flags) {
final int tick = TickTask.getTick();
// TODO: Slightly odd to call this each time, might switch to a counter-strategy (move - remove).
@ -794,7 +813,7 @@ public class MovingData extends ACheckData {
}
// Always add vertical velocity.
verVel.add(new SimpleEntry(tick, vy, cc.velocityActivationCounter));
verVel.add(new SimpleEntry(tick, vy, flags, cc.velocityActivationCounter));
// TODO: Should also switch to adding always.
if (vx != 0.0 || vz != 0.0) {
@ -824,11 +843,13 @@ public class MovingData extends ACheckData {
/**
* Get the first element without using it.
*
* @param amount
* @param minActCount
* @param maxActCount
* @return
*/
public SimpleEntry peekVerticalVelocity(final double amount, final int maxActCount) {
return verVel.peek(amount, maxActCount, TOL_VVEL);
public SimpleEntry peekVerticalVelocity(final double amount, final int minActCount, final int maxActCount) {
return verVel.peek(amount, minActCount, maxActCount, TOL_VVEL);
}
public void addVerticalVelocity(final SimpleEntry entry) {

View File

@ -76,6 +76,7 @@ import fr.neatmonster.nocheatplus.checks.moving.util.MovingUtil;
import fr.neatmonster.nocheatplus.checks.moving.vehicle.VehicleChecks;
import fr.neatmonster.nocheatplus.checks.moving.velocity.AccountEntry;
import fr.neatmonster.nocheatplus.checks.moving.velocity.SimpleEntry;
import fr.neatmonster.nocheatplus.checks.moving.velocity.VelocityFlags;
import fr.neatmonster.nocheatplus.checks.net.NetConfig;
import fr.neatmonster.nocheatplus.checks.net.NetData;
import fr.neatmonster.nocheatplus.compat.Bridge1_9;
@ -144,6 +145,14 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// WEAK_PUSH <- TBD: with edge on slime, or with falling inside of the new slime block position?
}
/**
*
*/
private static final long FLAGS_VELOCITY_BOUNCE_BLOCK = VelocityFlags.ORIGIN_BLOCK_BOUNCE;
private static final long FLAGS_VELOCITY_BOUNCE_BLOCK_MOVE_ASCEND = FLAGS_VELOCITY_BOUNCE_BLOCK
| VelocityFlags.SPLIT_ABOVE_0_42 | VelocityFlags.SPLIT_RETAIN_ACTCOUNT | VelocityFlags.ORIGIN_BLOCK_MOVE;
/** The no fall check. **/
public final NoFall noFall = addCheck(new NoFall());
@ -718,7 +727,8 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
&& onPreparedBounceSupport(player, from, to, thisMove, lastMove, tick, data)
// Past state bounce (includes prepending velocity / special calls).
|| useBlockChangeTracker
&& thisMove.yDistance >= 0.415 && thisMove.yDistance <= 1.515 // TODO: MAGIC
// 0-dist moves count in: && thisMove.yDistance >= 0.415
&& thisMove.yDistance <= 1.515 // TODO: MAGIC
&& checkPastStateBounceAscend(player, pFrom, pTo, thisMove, lastMove, tick, data, cc)
) {
checkNf = false;
@ -854,7 +864,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// Allowed move.
// Bounce effects.
if (verticalBounce != BounceType.NO_BOUNCE) {
processBounce(player, pFrom.getY(), pTo.getY(), verticalBounce, data, cc);
processBounce(player, pFrom.getY(), pTo.getY(), verticalBounce, tick, data, cc);
}
// Finished move processing.
if (processingEvents.containsKey(playerName)) {
@ -949,6 +959,20 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// TODO: Might need to cover push by slime block (center/feet off block).
// TODO: Low fall distance cases and where no slime block was underneath at descend have to be included here.
// TODO: Note that onPreparedBounceSupport etc. has to be called in here, if needed.
// TODO: Work around 0-dist?
// TODO: set data.verticalBounce and call data.useVerticalBounce right away (!) -> fall damage !?
// TODO: typically up to 1.5 above max level of pushed block ! (typically 2.0 total with 0.0 between).
/*
* TODO: Other concepts? a) maxCoordinate for SimpleEntry? (alters
* method calls to SimpleAxisVelocity) b) use past move tracking and
* store more moves (!) and store more context (e.g. bounce entry for
* center). c) combine both approaches, possibly use an interface to be
* set within SimpleEntry.
*/
final BlockChangeEntry entryBelowY_POS = blockChangeTracker.getBlockChangeEntryMatchFlags(
data.blockChangeRef, tick, worldId, from.getBlockX(), from.getBlockY() - 1, from.getBlockZ(),
Direction.Y_POS, BlockProperties.F_BOUNCE25);
@ -956,7 +980,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// Center push.
entryBelowY_POS != null
// Off center push (2x 0.5(015) only, (sum below 1.015 ?)).
|| thisMove.yDistance < 0.5015 && from.matchBlockChangeMatchResultingFlags(blockChangeTracker,
|| thisMove.yDistance < 1.015 && from.matchBlockChangeMatchResultingFlags(blockChangeTracker,
data.blockChangeRef, Direction.Y_POS, Math.min(.415, thisMove.yDistance),
BlockProperties.F_BOUNCE25)
) {
@ -970,15 +994,32 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
if (data.debug) {
debug(player, "checkPastStateBounceAscend: " + (entryBelowY_POS == null ? "off_center" : "center"));
}
final double amount = Math.max(0.505, thisMove.yDistance);
if (data.peekVerticalVelocity(amount, 2) == null) {
// TODO: Nail down to more precise side conditions for larger jumps, if possible.
// TODO: Adjust amount based on side conditions (center push or off center, distance to block top).
// TODO: Center push, without being hit by the block (2 below!) -> 0.5 off ground + 1.5 roughly !
// TODO: Push up, without being inside the pushed block (0.5 + 0.9x).
// TODO: Add two entries, split based on current yDistance?
/*
* TODO: USE EXISTING velocity with bounce flag set first, then peek
* / add. (might while peek -> has bounce flag: remove velocity)
*/
final double amount = Math.min(Math.max(0.505, 1.0 + (double) from.getBlockY() - from.getY() + 1.515),
2.525); // TODO: EXACT MAGIC.
if (lastMove.toIsValid && lastMove.yDistance < 0.42 ||
data.peekVerticalVelocity(amount, 2, 3) == null) {
// (Could skip peek for low distances around 0.5.)
if (amount > 1.38) {
// TODO: HACK - More decrease than expected.
// TODO: Consider to detect used velocity with vdistrel exceptions using past move tracking.
data.prependVerticalVelocity(new SimpleEntry(tick, 0.925, 3));
}
data.prependVerticalVelocity(new SimpleEntry(tick, amount, 2));
/*
* TODO: Concepts for limiting... max amount based on side
* conditions such as block height+1.5, max coordinate, max
* amount per use, ALLOW_ZERO flag/boolean and set in
* constructor, demand max. 1 zero dist during validity. Bind
* use to initial xz coordinates... Too precise = better with
* past move tracking, or a sub-class of SimpleEntry with better
* access signatures including thisMove.
*/
final SimpleEntry vel = new SimpleEntry(tick, amount, FLAGS_VELOCITY_BOUNCE_BLOCK_MOVE_ASCEND, 4);
data.verticalBounce = vel;
data.useVerticalBounce(player);
/*
* TODO: Update span or not ... [could use a class that extends
* SimpleEntry but which also contains a block change id to update
@ -987,8 +1028,14 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
if (entryBelowY_POS != null) {
data.blockChangeRef.updateSpan(entryBelowY_POS);
}
if (data.debug) {
debug(player, "checkPastStateBounceAscend: add velocity: " + vel);
}
return true;
}
else if (data.debug) {
debug(player, "checkPastStateBounceAscend: Don't add velocity.");
}
}
return false;
}
@ -1210,7 +1257,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
* @param cc
*/
private void processBounce(final Player player,final double fromY, final double toY,
final BounceType bounceType, final MovingData data, final MovingConfig cc) {
final BounceType bounceType, final int tick, final MovingData data, final MovingConfig cc) {
// Prepare velocity.
final double fallDistance = MovingUtil.getRealisticFallDistance(player, fromY, toY, data);
final double base = Math.sqrt(fallDistance) / 3.3;
@ -1239,7 +1286,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
debug(player, "Set bounce effect (dY=" + fallDistance + " / " + bounceType + "): " + effect);
}
data.noFallSkipAirCheck = true;
data.verticalBounce = new SimpleEntry(effect, 1);
data.verticalBounce = new SimpleEntry(tick, effect, FLAGS_VELOCITY_BOUNCE_BLOCK, 1); // Just bounce for now.
}
/**

View File

@ -39,12 +39,10 @@ import fr.neatmonster.nocheatplus.checks.moving.model.LiftOffEnvelope;
import fr.neatmonster.nocheatplus.checks.moving.model.LocationData;
import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData;
import fr.neatmonster.nocheatplus.checks.moving.util.AuxMoving;
import fr.neatmonster.nocheatplus.checks.moving.velocity.SimpleEntry;
import fr.neatmonster.nocheatplus.checks.workaround.WRPT;
import fr.neatmonster.nocheatplus.compat.Bridge1_9;
import fr.neatmonster.nocheatplus.compat.BridgeEnchant;
import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker;
import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker.BlockChangeEntry;
import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker.Direction;
import fr.neatmonster.nocheatplus.components.modifier.IAttributeAccess;
import fr.neatmonster.nocheatplus.components.registry.event.IGenericInstanceHandle;
@ -156,9 +154,10 @@ public class SurvivalFly extends Check {
// Set some flags.
final boolean fromOnGround = thisMove.from.onGround;
final boolean toOnGround = thisMove.to.onGround;
final boolean resetTo = toOnGround || to.isResetCond()
// TODO: Work in the past ground stuff differently (thisMove, touchedGround?, from/to ...)
final boolean toOnGround = thisMove.to.onGround
|| useBlockChangeTracker && toOnGroundPastStates(from, to, thisMove, tick, data, cc);
final boolean resetTo = toOnGround || to.isResetCond();
// Determine if the player is actually sprinting.
final boolean sprinting;
@ -678,20 +677,28 @@ public class SurvivalFly extends Check {
// TODO: Other conditions/filters ... ?
// Push (/pull) up.
if (yDistance > 0.0) {
if ((yDistance <= 1.0
// Extra condition for full blocks: slightly more possible.
// Extreme case: 1.51 blocks up (details pending).
|| yDistance <= 1.015 && to.getY() - to.getBlockY() < 0.015)) {
if (yDistance <= 1.015) {
/*
* (Full blocks: slightly more possible, ending up just above
* the block. Bounce allows other end positions.)
*/
// TODO: Is the air block wich the slime block is pushed onto really in?
if (from.matchBlockChange(blockChangeTracker, data.blockChangeRef, Direction.Y_POS,
Math.min(yDistance, 1.0))) {
if (yDistance > 1.0) {
final BlockChangeEntry entry = blockChangeTracker.getBlockChangeEntryMatchFlags(data.blockChangeRef,
tick, from.getWorld().getUID(), from.getBlockX(), from.getBlockY() - 1, from.getBlockZ(),
Direction.Y_POS, BlockProperties.F_BOUNCE25);
if (entry != null) {
data.blockChangeRef.updateSpan(entry);
data.prependVerticalVelocity(new SimpleEntry(tick, 0.5015, 3)); // TODO: HACK
tags.add("past_bounce");
// // TODO: Push of box off-center has the same effect.
// final BlockChangeEntry entry = blockChangeTracker.getBlockChangeEntryMatchFlags(data.blockChangeRef,
// tick, from.getWorld().getUID(), from.getBlockX(), from.getBlockY() - 1, from.getBlockZ(),
// Direction.Y_POS, BlockProperties.F_BOUNCE25);
// if (entry != null) {
// data.blockChangeRef.updateSpan(entry);
// data.prependVerticalVelocity(new SimpleEntry(tick, 0.5015, 3)); // TODO: HACK
// tags.add("past_bounce");
// }
// else
if (to.getY() - to.getBlockY() >= 0.015) {
// Exclude ordinary cases for this condition.
return null;
}
}
tags.add("blkmv_y_pos");

View File

@ -30,6 +30,11 @@ import fr.neatmonster.nocheatplus.utilities.TickTask;
*/
public class SimpleAxisVelocity {
private static final long FILTER_SPLIT = VelocityFlags.SPLIT_ABOVE_THIRD | VelocityFlags.SPLIT_ABOVE_0_42;
/** Flags for fast exclusion check in the end of the use(double, double) method. */
private static final long FILTER_POST_USE = FILTER_SPLIT;
/** Margin for accepting a demanded 0.0 amount, regardless sign. */
private static final double marginAcceptZero = 0.005;
@ -87,22 +92,55 @@ public class SimpleAxisVelocity {
*/
public SimpleEntry use(final double amount, final double tolerance) {
final Iterator<SimpleEntry> it = queued.iterator();
SimpleEntry entry = null;
while (it.hasNext()) {
final SimpleEntry entry = it.next();
entry = it.next();
it.remove();
if (matchesEntry(entry, amount, tolerance)) {
// Success.
return entry;
break;
}
else {
// Track unused velocity.
if (unusedActive) {
addUnused(entry);
}
entry = null;
}
}
// None found.
return null;
if (entry == null) {
// None found.
return null;
}
else {
if ((entry.flags & FILTER_POST_USE) != 0L) {
return processFlagsPostUse(entry, amount);
}
else {
return entry;
}
}
}
private SimpleEntry processFlagsPostUse(SimpleEntry entry, double amount) {
// Check flags for splitting entries.
if (allowsSplit(entry, amount)) {
addToFront(new SimpleEntry(entry.tick, entry.value - amount, entry.flags,
(entry.flags & VelocityFlags.SPLIT_RETAIN_ACTCOUNT) == 0
? entry.actCount : Math.max(entry.actCount, 2))
);
// TODO: For performance reasons we don't return the used amount.
}
return entry;
}
private final boolean allowsSplit(final SimpleEntry entry, final double amount) {
if ((entry.flags & FILTER_SPLIT) == 0L) {
return false;
}
final double remain = entry.value - amount;
return (entry.flags & VelocityFlags.SPLIT_ABOVE_THIRD) != 0L && remain > entry.value / 3.0
|| (entry.flags & VelocityFlags.SPLIT_ABOVE_0_42) != 0L && remain > 0.42;
}
/**
@ -114,11 +152,13 @@ public class SimpleAxisVelocity {
* @param tolerance
* @return
*/
public SimpleEntry peek(final double amount, final int maxActCount, final double tolerance) {
public SimpleEntry peek(final double amount, final int minActCount, final int maxActCount,
final double tolerance) {
final Iterator<SimpleEntry> it = queued.iterator();
while (it.hasNext()) {
final SimpleEntry entry = it.next();
if (entry.actCount < maxActCount && matchesEntry(entry, amount, tolerance)) {
if (entry.actCount >= minActCount && entry.actCount <= maxActCount
&& matchesEntry(entry, amount, tolerance)) {
return entry;
}
}
@ -138,7 +178,7 @@ public class SimpleAxisVelocity {
* Must be equal or greater than 0.0.
* @return
*/
public boolean matchesEntry(final SimpleEntry entry, final double amount, final double tolerance) {
public boolean matchesEntry(final SimpleEntry entry, final double amount, double tolerance) {
return Math.abs(amount) <= Math.abs(entry.value) + tolerance &&
(amount > 0.0 && entry.value > 0.0 && amount <= entry.value + tolerance
|| amount < 0.0 && entry.value < 0.0 && entry.value - tolerance <= amount

View File

@ -32,24 +32,35 @@ public class SimpleEntry {
/** Initial value for the actCount. */
public final int initialActCount;
/**
* Flags to indicate special cases, such as splitting a used entry, if not
* all gets used.
*/
public final long flags;
/** Count down for invalidation. */
public int actCount;
// TODO: Add more conditions (max tick, real time ?)
public SimpleEntry(double value, int actCount){
public SimpleEntry(double value, int actCount) {
this(TickTask.getTick(), value, actCount);
}
public SimpleEntry(int tick, double value, int actCount){
public SimpleEntry(int tick, double value, int actCount) {
this(tick, value, 0L, actCount);
}
public SimpleEntry(int tick, double value, long flags, int actCount) {
this.tick = tick;
this.value = value;
this.actCount = actCount;
this.initialActCount = actCount;
this.flags = flags;
}
public String toString(){
return "SimpleEntry(tick=" + tick + " value=" + value + " activate=" + actCount + "/" + initialActCount + ")";
public String toString() {
return "SimpleEntry(tick=" + tick + " value=" + value + " flags=" + flags + " activate=" + actCount + "/" + initialActCount + ")";
}
}

View File

@ -0,0 +1,44 @@
package fr.neatmonster.nocheatplus.checks.moving.velocity;
/**
* Flags for use with velocity entries. There may be flags specific to certain
* types of velocity entries only, these may get prefixed somehow some day.
*
* @author asofold
*
*/
public class VelocityFlags {
/** Create new entries with an actActCount increased by one. */
public static final long SPLIT_RETAIN_ACTCOUNT = 0x0001;
/**
* Create a new entry if the used amount leaves at least a third of the set
* amount. Entries generated by splitting are prepended by default.
*/
public static final long SPLIT_ABOVE_THIRD = 0x0002;
/**
* Create a new entry if the used amount leaves at least an amount of 0.42.
* Entries generated by splitting are prepended by default.
*/
public static final long SPLIT_ABOVE_0_42 = 0x0004;
/**
* Entries with this flag originate from moving blocks (typically pistons).
*/
public static final long ORIGIN_BLOCK_MOVE = 0x0100;
/**
* Entries with this flag originate from bouncing off blocks (typically
* slime blocks).
*/
public static final long ORIGIN_BLOCK_BOUNCE = 0x0200;
/**
* Entries with this flag originate from pvp (likely faked, due to the
* server not sending properly).
*/
public static final long ORIGIN_PVP = 0x0400;
}

View File

@ -63,7 +63,11 @@ public class BlockChangeReference {
* ordered in a relevant way, if they reference the same tick. Even
* one tick may be too narrow.
*/
return this.lastUsedEntry == null || entry.tick > this.lastUsedEntry.tick || entry.tick == this.lastUsedEntry.tick && valid;
// TODO: ONCE STUFF WORKS: reinstate invalidation and boil it down to something useful.
//return this.lastUsedEntry == null || entry.tick > this.lastUsedEntry.tick || entry.tick == this.lastUsedEntry.tick && valid;
return true; // TODO: TEST
}
/**
@ -104,7 +108,7 @@ public class BlockChangeReference {
if (lastSpanEntry != null && (lastUsedEntry == null || lastSpanEntry.id > lastUsedEntry.id)) {
lastUsedEntry = lastSpanEntry;
if (to != null && to.isBlockIntersecting(
lastSpanEntry.x, lastSpanEntry.y, lastSpanEntry.z, lastSpanEntry.direction.blockFace)) {
lastSpanEntry.x, lastSpanEntry.y, lastSpanEntry.z, lastSpanEntry.direction.blockFace)) {
valid = true;
}
else {

View File

@ -268,6 +268,18 @@ public class BlockChangeTracker {
);
}
/**
* Test if ref can be updated with this entry, taking the given
* direction into account, allowing arguments to be null.
*
* @param ref
* @param direction
* @return
*/
public boolean canUpdate(final BlockChangeReference ref, final Direction direction) {
return (direction == null || direction == this.direction) && (ref == null || ref.canUpdateWith(this));
}
}
/** Change id/count, increasing with each entry added internally. */
@ -580,9 +592,7 @@ public class BlockChangeTracker {
final LinkedList<BlockChangeEntry> entries = getValidBlockChangeEntries(tick, worldNode, x, y, z);
if (entries != null) {
for (final BlockChangeEntry entry : entries) {
if (ref == null
|| ref.canUpdateWith(entry)
&& (direction == null || entry.direction == direction)) {
if (entry.canUpdate(ref, direction)) {
return entry;
}
}
@ -624,10 +634,7 @@ public class BlockChangeTracker {
final LinkedList<BlockChangeEntry> entries = getValidBlockChangeEntries(tick, worldNode, x, y, z);
if (entries != null) {
for (final BlockChangeEntry entry : entries) {
if ((ref == null
|| ref.canUpdateWith(entry)
&& (direction == null || entry.direction == direction))
//
if (entry.canUpdate(ref, direction)
&& (matchFlags == 0
|| (matchFlags & BlockProperties.getBlockFlags(entry.previousState.getId())) != 0)) {
return entry;

View File

@ -2295,6 +2295,7 @@ public class BlockProperties {
return true;
}
else {
// TODO: F_GROUND ?
return (flags & F_SOLID) == 0;
}
}

View File

@ -18,7 +18,7 @@ Tips
Compiling NoCheatPlus
---------
* NoCheatPlus is compiled for Java 6 (same as Minecraft/Spigot).
* NoCheatPlus used to be compiled with java 6 compliance (note OpenJDK, possibly we'll switch to 8 directly, once appropriate, e.g. with ProtocolLib dropping support for 7.).
* We use [Maven] (http://maven.apache.org/download.cgi) 3 to handle the dependencies.
* You can compile with this Maven goal: `mvn clean package`, if you don't want any dedicated CraftBukkit modules, or if you are lacking the jar files - **for best performance and compatibility, choose the appropriate build profile(s) from below**. If you do have all the legacy dependencies, you can set the parameter `cbdedicated` to `true` and activate the profile `all` adding `-P all` to the maven goals. For more options, see the table below. If your specific needs are not met by the provided options, you can still build only using the compat module(s) that you need, e.g. by adjusting the build/dependency profiles or adding your own profile, which means changing/adding a profile both in the root pom.xml for modules to have and in NCPPlugin/pom.xml for the dependency inclusion. The preset profiles should be enough of a hint for that. If you add custom modules with a different package naming than `fr.neatmonster`, you might have to add the source inclusion to the NoCheatPlus/pom.xml as well.
* Jar files for the dedicated compat modules, which your local maven repository might be missing, can be installed manually.