Allow to detect delegate players for some contexts. Other fix(es).

Attempt to treat fake players less Concept is subject to change, might
want fall-back methods or skipping native access in general where it's
not needed (thus not need to check for native entities).

Other
* Don't insert dataMan into disableListeners twice.
This commit is contained in:
asofold 2016-06-16 00:46:19 +02:00
parent d5b45e53f9
commit 0868e30994
8 changed files with 159 additions and 37 deletions

View File

@ -16,6 +16,7 @@ package fr.neatmonster.nocheatplus.checks.fight;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
@ -45,20 +46,22 @@ public class Direction extends Check {
* the damaged
* @return true, if successful
*/
public boolean check(final Player player, final Location loc, final Entity damaged, final Location dLoc, final FightData data, final FightConfig cc) {
public boolean check(final Player player, final Location loc,
final Entity damaged, final boolean damagedIsFake, final Location dLoc,
final FightData data, final FightConfig cc) {
boolean cancel = false;
// Safeguard, if entity is complex, this check will fail due to giant and hard to define hitboxes.
// if (damaged instanceof EntityComplex || damaged instanceof EntityComplexPart)
if (mcAccess.isComplexPart(damaged)) {
if (!damagedIsFake && mcAccess.isComplexPart(damaged)) {
return false;
}
// Find out how wide the entity is.
final double width = mcAccess.getWidth(damaged);
final double width = damagedIsFake ? 0.6 : mcAccess.getWidth(damaged);
// entity.height is broken and will always be 0, therefore. Calculate height instead based on boundingBox.
final double height = mcAccess.getHeight(damaged);
final double height = damagedIsFake ? (damaged instanceof LivingEntity ? ((LivingEntity) damaged).getEyeHeight() : 1.75) : mcAccess.getHeight(damaged);
// TODO: allow any hit on the y axis (might just adapt interface to use foot position + height)!
@ -109,11 +112,20 @@ public class Direction extends Check {
* @param cc
* @return
*/
public DirectionContext getContext(final Player player, final Location loc, final Entity damaged, final Location damagedLoc, final FightData data, final FightConfig cc, final SharedContext sharedContext) {
public DirectionContext getContext(final Player player, final Location loc,
final Entity damaged, final boolean damagedIsFake, final Location damagedLoc,
final FightData data, final FightConfig cc, final SharedContext sharedContext) {
final DirectionContext context = new DirectionContext();
context.damagedComplex = mcAccess.isComplexPart(damaged);
// Find out how wide the entity is.
context.damagedWidth = mcAccess.getWidth(damaged);
if (damagedIsFake) {
// Assume player / default.
context.damagedComplex = false; // Later prefer bukkit based provider.
context.damagedWidth = 0.6;
}
else {
context.damagedComplex = mcAccess.isComplexPart(damaged);
context.damagedWidth = mcAccess.getWidth(damaged);
}
// entity.height is broken and will always be 0, therefore. Calculate height instead based on boundingBox.
context.damagedHeight = sharedContext.damagedHeight;
context.direction = loc.getDirection();

View File

@ -54,6 +54,7 @@ import fr.neatmonster.nocheatplus.checks.moving.util.MovingUtil;
import fr.neatmonster.nocheatplus.compat.Bridge1_9;
import fr.neatmonster.nocheatplus.compat.BridgeEnchant;
import fr.neatmonster.nocheatplus.compat.BridgeHealth;
import fr.neatmonster.nocheatplus.compat.IBridgeCrossPlugin;
import fr.neatmonster.nocheatplus.compat.MCAccess;
import fr.neatmonster.nocheatplus.components.registry.feature.JoinLeaveListener;
import fr.neatmonster.nocheatplus.permissions.Permissions;
@ -109,6 +110,9 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
private final Counters counters = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class);
private final int idCancelDead = counters.registerKey("canceldead");
// Assume it to stay the same all time.
private final IBridgeCrossPlugin crossPlugin = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(IBridgeCrossPlugin.class);
public FightListener() {
super(CheckType.FIGHT);
}
@ -119,18 +123,22 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
}
/**
* A player attacked something with DamageCause ENTITY_ATTACK. That's most
* likely what we want to really check.
* A player attacked something with DamageCause ENTITY_ATTACK.
*
* @param player
* The attacking player.
* @param damaged
* @param originalDamage Damage before applying modifiers.
* @param finalDamage Damage after applying modifiers.
* @param originalDamage
* Damage before applying modifiers.
* @param finalDamage
* Damage after applying modifiers.
* @param tick
* @param data
* @return
*/
private boolean handleNormalDamage(final Player player, final Entity damaged, final double originalDamage, final double finalDamage, final int tick, final FightData data) {
private boolean handleNormalDamage(final Player player, final boolean attackerIsFake,
final Entity damaged, final boolean damagedIsFake,
final double originalDamage, final double finalDamage, final int tick, final FightData data) {
final FightConfig cc = FightConfig.getConfig(player);
// Hotfix attempt for enchanted books.
@ -295,15 +303,15 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
if (reachEnabled || directionEnabled) {
if (damagedTrace != null) {
// Checks that use the LocationTrace instance of the attacked entity/player.
cancelled = locationTraceChecks(player, loc, data, cc, damaged, damagedLoc, damagedTrace, tick, now, reachEnabled, directionEnabled);
cancelled = locationTraceChecks(player, loc, data, cc, damaged, damagedIsFake, damagedLoc, damagedTrace, tick, now, reachEnabled, directionEnabled);
}
else {
// Still use the classic methods for non-players. maybe[]
if (reachEnabled && reach.check(player, loc, damaged, damagedLoc, data, cc)) {
if (reachEnabled && reach.check(player, loc, damaged, damagedIsFake, damagedLoc, data, cc)) {
cancelled = true;
}
if (directionEnabled && direction.check(player, loc, damaged, damagedLoc, data, cc)) {
if (directionEnabled && direction.check(player, loc, damaged, damagedIsFake, damagedLoc, data, cc)) {
cancelled = true;
}
}
@ -399,16 +407,18 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
* @param directionEnabled
* @return If to cancel (true) or not (false).
*/
private boolean locationTraceChecks(final Player player, final Location loc, final FightData data, final FightConfig cc,
final Entity damaged, final Location damagedLoc, LocationTrace damagedTrace,
private boolean locationTraceChecks(final Player player, final Location loc,
final FightData data, final FightConfig cc,
final Entity damaged, final boolean damagedIsFake,
final Location damagedLoc, LocationTrace damagedTrace,
final long tick, final long now, final boolean reachEnabled, final boolean directionEnabled) {
// TODO: Order / splitting off generic stuff.
boolean cancelled = false;
// (Might pass generic context to factories, for shared + heavy properties.)
final SharedContext sharedContext = new SharedContext(damaged, mcAccess);
final SharedContext sharedContext = new SharedContext(damaged, damagedIsFake, mcAccess);
final ReachContext reachContext = reachEnabled ? reach.getContext(player, loc, damaged, damagedLoc, data, cc, sharedContext) : null;
final DirectionContext directionContext = directionEnabled ? direction.getContext(player, loc, damaged, damagedLoc, data, cc, sharedContext) : null;
final DirectionContext directionContext = directionEnabled ? direction.getContext(player, loc, damaged, damagedIsFake, damagedLoc, data, cc, sharedContext) : null;
final long traceOldest = tick - cc.loopMaxLatencyTicks; // TODO: Set by latency-window.
// TODO: Iterating direction, which, static/dynamic choice.
@ -484,9 +494,12 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
final Player damagedPlayer = damaged instanceof Player ? (Player) damaged : null;
final FightData damagedData = damagedPlayer == null ? null : FightData.getData(damagedPlayer);
final boolean damagedIsDead = damaged.isDead();
final boolean damagedIsFake = !crossPlugin.isNativeEntity(damaged);
if (damagedPlayer != null && !damagedIsDead) {
// God mode check.
if (godMode.isEnabled(damagedPlayer) && godMode.check(damagedPlayer, BridgeHealth.getDamage(event), damagedData)) {
// (Do not test the savage.)
if (godMode.isEnabled(damagedPlayer)
&& godMode.check(damagedPlayer, damagedIsFake, BridgeHealth.getDamage(event), damagedData)) {
// It requested to "cancel" the players invulnerability, so set their noDamageTicks to 0.
damagedPlayer.setNoDamageTicks(0);
}
@ -508,7 +521,8 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
}
// Attacking entities.
if (event instanceof EntityDamageByEntityEvent) {
onEntityDamageByEntity(damaged, damagedPlayer, damagedIsDead, damagedData, (EntityDamageByEntityEvent) event);
onEntityDamageByEntity(damaged, damagedPlayer, damagedIsDead, damagedIsFake,
damagedData, (EntityDamageByEntityEvent) event);
}
}
@ -522,7 +536,8 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
* @param event
*/
private void onEntityDamageByEntity(final Entity damaged, final Player damagedPlayer,
final boolean damagedIsDead, final FightData damagedData, final EntityDamageByEntityEvent event) {
final boolean damagedIsDead, final boolean damagedIsFake,
final FightData damagedData, final EntityDamageByEntityEvent event) {
final Entity damager = event.getDamager();
final int tick = TickTask.getTick();
if (damagedPlayer != null && !damagedIsDead) {
@ -556,6 +571,7 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
if (attackerData.debug) {
// TODO: Pass result to further checks for reference?
// TODO: attackerData.debug flag.
// TODO: Fake players likely have unused velocity, just clear unused?
UnusedVelocity.checkUnusedVelocity(attacker, CheckType.FIGHT);
}
// Workaround for subsequent melee damage eventsfor explosions. TODO: Legacy or not, need a KB.
@ -577,7 +593,8 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
attackerData.lastExplosionDamageTick = -1;
attackerData.lastExplosionEntityId = Integer.MAX_VALUE;
}
else if (handleNormalDamage(player, damaged,
else if (handleNormalDamage(player, !crossPlugin.isNativePlayer(player),
damaged, damagedIsFake,
BridgeHealth.getOriginalDamage(event), BridgeHealth.getFinalDamage(event),
tick, attackerData)) {
event.setCancelled(true);

View File

@ -22,6 +22,7 @@ import fr.neatmonster.nocheatplus.checks.Check;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.net.NetData;
import fr.neatmonster.nocheatplus.compat.BridgeHealth;
import fr.neatmonster.nocheatplus.compat.IBridgeCrossPlugin;
import fr.neatmonster.nocheatplus.utilities.CheckUtils;
import fr.neatmonster.nocheatplus.utilities.TickTask;
@ -43,11 +44,11 @@ public class GodMode extends Check {
* @param damage
* @return
*/
public boolean check(final Player player, final double damage, final FightData data){
public boolean check(final Player player, final boolean playerIsFake, final double damage, final FightData data){
final int tick = TickTask.getTick();
final int noDamageTicks = Math.max(0, player.getNoDamageTicks());
final int invulnerabilityTicks = mcAccess.getInvulnerableTicks(player);
final int invulnerabilityTicks = playerIsFake ? 0 : mcAccess.getInvulnerableTicks(player);
// TODO: cleanup this leugique beume...
@ -193,7 +194,8 @@ public class GodMode extends Check {
public void death(final Player player) {
// TODO: Is this still relevant ?
// First check if the player is really dead (e.g. another plugin could have just fired an artificial event).
if (BridgeHealth.getHealth(player) <= 0.0 && player.isDead()) {
if (BridgeHealth.getHealth(player) <= 0.0 && player.isDead()
&& NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(IBridgeCrossPlugin.class).isNativeEntity(player)) {
try {
// Schedule a task to be executed in roughly 1.5 seconds.
// TODO: Get plugin otherwise !?

View File

@ -20,6 +20,7 @@ import org.bukkit.Location;
import org.bukkit.entity.EnderDragon;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Giant;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
@ -67,7 +68,9 @@ public class Reach extends Check {
* the damaged
* @return true, if successful
*/
public boolean check(final Player player, final Location pLoc, final Entity damaged, final Location dRef, final FightData data, final FightConfig cc) {
public boolean check(final Player player, final Location pLoc,
final Entity damaged, final boolean damagedIsFake, final Location dRef,
final FightData data, final FightConfig cc) {
boolean cancel = false;
// The maximum distance allowed to interact with an entity in survival mode.
@ -80,7 +83,7 @@ public class Reach extends Check {
final double distanceLimit = player.getGameMode() == GameMode.CREATIVE ? CREATIVE_DISTANCE : SURVIVAL_DISTANCE + getDistMod(damaged);
final double distanceMin = (distanceLimit - DYNAMIC_RANGE) / distanceLimit;
final double height = mcAccess.getHeight(damaged);
final double height = damagedIsFake ? (damaged instanceof LivingEntity ? ((LivingEntity) damaged).getEyeHeight() : 1.75) : mcAccess.getHeight(damaged);
// Refine y position.
// TODO: Make a little more accurate by counting in the actual bounding box.

View File

@ -15,13 +15,20 @@
package fr.neatmonster.nocheatplus.checks.fight;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import fr.neatmonster.nocheatplus.compat.MCAccess;
public class SharedContext {
public final double damagedHeight;
public SharedContext(Entity damaged, MCAccess mcAccess) {
this.damagedHeight = mcAccess.getHeight(damaged);
public SharedContext(Entity damaged, boolean damagedIsFake, MCAccess mcAccess) {
if (damagedIsFake) {
// Assume something lenient then.
damagedHeight = damaged instanceof LivingEntity ? ((LivingEntity) damaged).getEyeHeight() : 1.75;
}
else {
this.damagedHeight = mcAccess.getHeight(damaged);
}
}
}

View File

@ -0,0 +1,32 @@
package fr.neatmonster.nocheatplus.compat;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
/**
* Registered as generic instance.
*
* @author asofold
*
*/
public interface IBridgeCrossPlugin {
/**
* Safety check, enabling to skip certain checks or tests for delegate
* players.
*
* @param player
* @return
*/
public boolean isNativePlayer(Player player);
/**
* Safety check, enabling to skip certain checks or tests for delegate
* entities.
*
* @param player
* @return
*/
public boolean isNativeEntity(Entity entity);
}

View File

@ -70,6 +70,7 @@ import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.compat.MCAccess;
import fr.neatmonster.nocheatplus.compat.blocks.BlockChangeTracker;
import fr.neatmonster.nocheatplus.compat.blocks.BlockChangeTracker.BlockChangeListener;
import fr.neatmonster.nocheatplus.compat.meta.BridgeCrossPlugin;
import fr.neatmonster.nocheatplus.compat.registry.AttributeAccessFactory;
import fr.neatmonster.nocheatplus.compat.registry.DefaultComponentFactory;
import fr.neatmonster.nocheatplus.compat.registry.EntityAccessFactory;
@ -905,6 +906,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
registerGenericInstance(new WRPT());
registerGenericInstance(new Random(System.currentTimeMillis() ^ ((long) this.hashCode() * (long) listenerManager.hashCode() * (long) logManager.hashCode())));
registerGenericInstance(new TraceEntryPool(1000)); // Random number.
addComponent(new BridgeCrossPlugin());
// Initialize MCAccess.
initMCAccess(config);
@ -913,7 +915,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
initBlockProperties(config);
// Initialize data manager.
disableListeners.add(0, dataMan);
dataMan.onEnable();
// Register components.
@ -975,6 +976,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
// Set up the tick task.
TickTask.start(this);
// dataMan expiration checking.
this.dataManTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
@Override
public void run() {
@ -982,6 +984,10 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
}
}, 1207, 1207);
// Ensure dataMan is first on disableListeners.
disableListeners.remove(dataMan);
disableListeners.add(0, dataMan);
// Set up consistency checking.
scheduleConsistencyCheckers();
@ -1211,6 +1217,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
public void setMCAccess(final MCAccess mcAccess) {
// Just sets it and propagates it.
// TODO: Might fire a NCPSetMCAccessEvent (include getting and setting)!
// TODO: Store a list of MCAccessHolder.
this.mcAccess = mcAccess;
for (final Object obj : this.allComponents) {
if (obj instanceof MCAccessHolder) {

View File

@ -1,9 +1,15 @@
package fr.neatmonster.nocheatplus.compat.meta;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.compat.IBridgeCrossPlugin;
import fr.neatmonster.nocheatplus.compat.MCAccess;
import fr.neatmonster.nocheatplus.compat.cbreflect.reflect.ReflectBase;
import fr.neatmonster.nocheatplus.components.registry.feature.IPostRegisterRunnable;
import fr.neatmonster.nocheatplus.components.registry.feature.MCAccessHolder;
import fr.neatmonster.nocheatplus.utilities.ReflectionUtil;
/**
* Utility to probe for cross-plugin issues, such as Player delegates.
@ -12,16 +18,47 @@ import fr.neatmonster.nocheatplus.components.registry.feature.MCAccessHolder;
* @author asofold
*
*/
public class BridgeCrossPlugin implements MCAccessHolder {
public class BridgeCrossPlugin implements IBridgeCrossPlugin, IPostRegisterRunnable, MCAccessHolder {
// TODO: More sophisticated checking ?
private MCAccess mcAccess;
public BridgeCrossPlugin(MCAccess mcAccess) {
this.mcAccess = mcAccess;
private final Class<?> playerClass;
private final Class<?> entityClass;
public BridgeCrossPlugin() {
ReflectBase reflectBase = new ReflectBase();
this.playerClass = getEntityClass(reflectBase, "Player");
this.entityClass = getEntityClass(reflectBase, "Entity", "");
}
private Class<?> getEntityClass(ReflectBase reflectBase, String entityName) {
return getEntityClass(reflectBase, entityName, entityName);
}
private Class<?> getEntityClass(ReflectBase reflectBase, String obcSuffix, String nmsSuffix) {
if (reflectBase.nmsPackageName == null || reflectBase.obcPackageName == null) {
return null;
}
Class<?> obcPlayer = ReflectionUtil.getClass(reflectBase.obcPackageName + ".entity.Craft" + obcSuffix);
Class<?> nmsPlayer = ReflectionUtil.getClass(reflectBase.nmsPackageName + ".Entity" + nmsSuffix);
if (obcPlayer == null || nmsPlayer == null) {
return null;
}
else {
return obcPlayer;
}
}
@Override
public void runPostRegister() {
NCPAPIProvider.getNoCheatPlusAPI().registerGenericInstance(IBridgeCrossPlugin.class, this);
}
@Override
public void setMCAccess(MCAccess mcAccess) {
// TODO: Should adapt to mcAccess?
this.mcAccess = mcAccess;
}
@ -30,9 +67,14 @@ public class BridgeCrossPlugin implements MCAccessHolder {
return mcAccess;
}
@Override
public boolean isNativePlayer(final Player player) {
// Possibly better within MCAccess later on.
return player.getClass().getSimpleName().equals("CraftPlayer");
return playerClass != null && playerClass.isAssignableFrom(player.getClass());
}
@Override
public boolean isNativeEntity(final Entity entity) {
return entityClass != null && entityClass.isAssignableFrom(entity.getClass());
}
}