471 lines
19 KiB
Java
471 lines
19 KiB
Java
/*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package fr.neatmonster.nocheatplus.checks.moving.vehicle;
|
|
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
|
|
import org.bukkit.entity.Boat;
|
|
import org.bukkit.entity.Entity;
|
|
import org.bukkit.entity.EntityType;
|
|
import org.bukkit.entity.Minecart;
|
|
import org.bukkit.entity.Pig;
|
|
import org.bukkit.entity.Player;
|
|
|
|
import fr.neatmonster.nocheatplus.actions.ParameterName;
|
|
import fr.neatmonster.nocheatplus.checks.Check;
|
|
import fr.neatmonster.nocheatplus.checks.CheckType;
|
|
import fr.neatmonster.nocheatplus.checks.ViolationData;
|
|
import fr.neatmonster.nocheatplus.checks.moving.MovingConfig;
|
|
import fr.neatmonster.nocheatplus.checks.moving.MovingData;
|
|
import fr.neatmonster.nocheatplus.checks.moving.location.setback.SetBackEntry;
|
|
import fr.neatmonster.nocheatplus.checks.moving.magic.MagicVehicle;
|
|
import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData;
|
|
import fr.neatmonster.nocheatplus.checks.moving.model.VehicleMoveData;
|
|
import fr.neatmonster.nocheatplus.checks.moving.model.VehicleMoveInfo;
|
|
import fr.neatmonster.nocheatplus.checks.workaround.WRPT;
|
|
import fr.neatmonster.nocheatplus.players.IPlayerData;
|
|
import fr.neatmonster.nocheatplus.utilities.ReflectionUtil;
|
|
import fr.neatmonster.nocheatplus.utilities.StringUtil;
|
|
|
|
/**
|
|
* Vehicle moving envelope check, for Minecraft 1.9 and higher.
|
|
*
|
|
* @author asofold
|
|
*
|
|
*/
|
|
public class VehicleEnvelope extends Check {
|
|
|
|
/**
|
|
* Check specific details for re-use.
|
|
*
|
|
* @author asofold
|
|
*
|
|
*/
|
|
public class CheckDetails {
|
|
|
|
public boolean canClimb;
|
|
public boolean canRails;
|
|
public boolean canJump, canStepUpBlock; // TODO: Model as heights?
|
|
public double maxAscend;
|
|
public double gravityTargetSpeed;
|
|
|
|
/** Simplified type, like BOAT, MINECART. */
|
|
public EntityType simplifiedType; // Not sure can be kept up.
|
|
|
|
public boolean checkAscendMuch;
|
|
public boolean checkDescendMuch;
|
|
|
|
/** From could be a new set back location. */
|
|
public boolean fromIsSafeMedium;
|
|
/** To could be a new set back location. */
|
|
public boolean toIsSafeMedium;
|
|
|
|
/** Interpreted differently depending on check. */
|
|
public boolean inAir;
|
|
|
|
public void reset() {
|
|
canClimb = canRails = canJump = canStepUpBlock = false;
|
|
maxAscend = 0.0;
|
|
checkAscendMuch = checkDescendMuch = true;
|
|
fromIsSafeMedium = toIsSafeMedium = inAir = false;
|
|
simplifiedType = null;
|
|
gravityTargetSpeed = MagicVehicle.boatVerticalFallTarget;
|
|
}
|
|
|
|
}
|
|
|
|
/** Tags for checks. */
|
|
private final List<String> tags = new LinkedList<String>();
|
|
|
|
/** Extra details to log on debug. */
|
|
private final List<String> debugDetails = new LinkedList<String>();
|
|
|
|
/** Details for re-use. */
|
|
private final CheckDetails checkDetails = new CheckDetails();
|
|
|
|
private final Class<?> bestHorse;
|
|
|
|
public VehicleEnvelope() {
|
|
super(CheckType.MOVING_VEHICLE_ENVELOPE);
|
|
Class<?> clazz = ReflectionUtil.getClass("org.bukkit.entity.AbstractHorse");
|
|
bestHorse = clazz == null ? ReflectionUtil.getClass("org.bukkit.entity.Horse") : clazz;
|
|
}
|
|
|
|
public SetBackEntry check(final Player player, final Entity vehicle,
|
|
final VehicleMoveData thisMove, final boolean isFake,
|
|
final MovingData data, final MovingConfig cc,
|
|
final IPlayerData pData) {
|
|
final boolean debug = pData.isDebugActive(type);
|
|
// Delegate to a sub-check.
|
|
tags.clear();
|
|
tags.add("entity." + vehicle.getType());
|
|
if (debug) {
|
|
debugDetails.clear();
|
|
data.ws.setJustUsedIds(debugDetails); // Add just used workaround ids to this list directly, for now.
|
|
}
|
|
final boolean violation = checkEntity(player, vehicle, thisMove, isFake,
|
|
data, cc, debug);
|
|
if (debug && !debugDetails.isEmpty()) {
|
|
debugDetails(player);
|
|
debugDetails.clear();
|
|
}
|
|
if (violation) {
|
|
// Add up one for now.
|
|
data.vehicleEnvelopeVL += 1.0;
|
|
final ViolationData vd = new ViolationData(this, player, data.vehicleEnvelopeVL, 1, cc.vehicleEnvelopeActions);
|
|
vd.setParameter(ParameterName.TAGS, StringUtil.join(tags, "+"));
|
|
if (executeActions(vd).willCancel()) {
|
|
return data.vehicleSetBacks.getValidSafeMediumEntry();
|
|
}
|
|
}
|
|
else {
|
|
data.vehicleEnvelopeVL *= 0.99; // Random cool down for now.
|
|
// Do not set a set back here.
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void debugDetails(final Player player) {
|
|
if (!tags.isEmpty()) {
|
|
debugDetails.add("tags:");
|
|
debugDetails.add(StringUtil.join(tags, "+"));
|
|
}
|
|
final StringBuilder builder = new StringBuilder(500);
|
|
builder.append("Details:\n");
|
|
for (final String detail : debugDetails) {
|
|
builder.append(" , ");
|
|
builder.append(detail);
|
|
}
|
|
debug(player, builder.toString());
|
|
debugDetails.clear();
|
|
}
|
|
|
|
private double getHDistCap(final EntityType type, final MovingConfig cc) {
|
|
final Double v = cc.vehicleEnvelopeHorizontalSpeedCap.get(type);
|
|
if (v == null) {
|
|
return cc.vehicleEnvelopeHorizontalSpeedCap.get(null);
|
|
}
|
|
else {
|
|
return v;
|
|
}
|
|
}
|
|
|
|
private boolean checkEntity(final Player player, final Entity vehicle,
|
|
final VehicleMoveData thisMove, final boolean isFake,
|
|
final MovingData data, final MovingConfig cc,
|
|
final boolean debug) {
|
|
boolean violation = false;
|
|
if (debug) {
|
|
debugDetails.add("inair: " + data.sfJumpPhase);
|
|
}
|
|
|
|
// Medium dependent checking.
|
|
|
|
// TODO: Try pigs on layered snow. Consider actual bounding box / lost-ground / ...
|
|
|
|
// Maximum thinkable horizontal speed.
|
|
// TODO: Further distinguish, best set in CheckDetails.
|
|
if (maxDistHorizontal(thisMove, getHDistCap(checkDetails.simplifiedType, cc))) { // Override type for now.
|
|
return true;
|
|
}
|
|
|
|
// TODO: Could limit descend by 2*maxDescend, ascend by much less.
|
|
|
|
// TODO: Split code to methods.
|
|
// TODO: Get extended liquid specs (allow confine to certain flags, here: water). Contains info if water is only flowing down, surface properties (non liquid blocks?), still water.
|
|
if (thisMove.from.inWeb) {
|
|
// TODO: Check anything?
|
|
if (debug) {
|
|
debugDetails.add("");
|
|
}
|
|
// if (thisMove.yDistance > 0.0) {
|
|
// tags.add("ascend_web");
|
|
// return true;
|
|
// }
|
|
// TODO: Enforce not ascending ?
|
|
// TODO: max speed.
|
|
}
|
|
else if (checkDetails.canClimb && thisMove.from.onClimbable) {
|
|
// TODO: Order.
|
|
checkDetails.checkAscendMuch = checkDetails.checkDescendMuch = false;
|
|
if (Math.abs(thisMove.yDistance) > MagicVehicle.climbSpeed) {
|
|
violation = true;
|
|
tags.add("climbspeed");
|
|
}
|
|
}
|
|
else if (checkDetails.canRails && thisMove.fromOnRails) {
|
|
// TODO: Might invert to trigger violation if exceeds distance (always disable a/d_much).
|
|
if (Math.abs(thisMove.yDistance) < MagicVehicle.maxRailsVertical) {
|
|
checkDetails.checkAscendMuch = checkDetails.checkDescendMuch = false;
|
|
}
|
|
}
|
|
else if (thisMove.from.inWater && thisMove.to.inWater) {
|
|
// Default in-medium move.
|
|
if (debug) {
|
|
debugDetails.add("water-water");
|
|
}
|
|
// TODO: Should still cover extreme moves here.
|
|
|
|
// Special case moving up after falling.
|
|
// TODO: Move to MagicVehicle.oddInWater
|
|
// TODO: Check past moves for falling (not yet available).
|
|
// TODO: Check if the target location somehow is the surface.
|
|
if (MagicVehicle.oddInWater(thisMove, checkDetails, data)) {
|
|
// (Assume players can't control sinking boats for now.)
|
|
checkDetails.checkDescendMuch = checkDetails.checkAscendMuch = false;
|
|
violation = false;
|
|
}
|
|
}
|
|
else if (thisMove.from.onGround && thisMove.to.onGround) {
|
|
// Default on-ground move.
|
|
// TODO: Should still cover extreme moves here.
|
|
if (checkDetails.canStepUpBlock && thisMove.yDistance > 0.0 && thisMove.yDistance <= 1.0) {
|
|
checkDetails.checkAscendMuch = false;
|
|
tags.add("step_up");
|
|
}
|
|
if (thisMove.from.onIce && thisMove.to.onIce) {
|
|
// Default on-ice move.
|
|
if (debug) {
|
|
debugDetails.add("ice-ice");
|
|
}
|
|
// TODO: Should still cover extreme moves here.
|
|
}
|
|
else {
|
|
// (TODO: actually a default on-ground move.)
|
|
if (debug) {
|
|
debugDetails.add("ground-ground");
|
|
}
|
|
}
|
|
}
|
|
else if (checkDetails.inAir) {
|
|
// In-air move.
|
|
if (checkInAir(thisMove, data, debug)) {
|
|
violation = true;
|
|
}
|
|
}
|
|
else {
|
|
// Some transition to probably handle.
|
|
if (debug) {
|
|
debugDetails.add("?-?");
|
|
}
|
|
// TODO: Lift off speed etc.
|
|
// TODO: Clearly overlaps other cases.
|
|
// TODO: Skipped vehicle move events happen here as well (...).
|
|
if (!checkDetails.toIsSafeMedium) {
|
|
// TODO: At least do something here?
|
|
}
|
|
}
|
|
// Maximum ascend speed.
|
|
if (checkDetails.checkAscendMuch && thisMove.yDistance > checkDetails.maxAscend) {
|
|
tags.add("ascend_much");
|
|
violation = true;
|
|
}
|
|
// Maximum descend speed.
|
|
if (checkDetails.checkDescendMuch && thisMove.yDistance < -MagicVehicle.maxDescend) {
|
|
// TODO: At times it looks like one move is skipped, resulting in double distance ~ -5 and at the same time 'vehicle moved too quickly'.
|
|
// TODO: Test with log this to console to see the order of things.
|
|
tags.add("descend_much");
|
|
violation = true;
|
|
}
|
|
|
|
if (!violation) {
|
|
// No violation.
|
|
// TODO: sfJumpPhase is abused for in-air move counting here.
|
|
if (checkDetails.inAir) {
|
|
data.sfJumpPhase ++;
|
|
}
|
|
else {
|
|
// Adjust set back.
|
|
if (checkDetails.toIsSafeMedium) {
|
|
data.vehicleSetBacks.setSafeMediumEntry(thisMove.to);
|
|
data.sfJumpPhase = 0;
|
|
}
|
|
else if (checkDetails.fromIsSafeMedium) {
|
|
data.vehicleSetBacks.setSafeMediumEntry(thisMove.from);
|
|
data.sfJumpPhase = 0;
|
|
}
|
|
// Reset the resetNotInAir workarounds.
|
|
data.ws.resetConditions(WRPT.G_RESET_NOTINAIR);
|
|
}
|
|
data.vehicleSetBacks.setLastMoveEntry(thisMove.to);
|
|
}
|
|
|
|
return violation;
|
|
}
|
|
|
|
/**
|
|
* Prepare checkDetails according to vehicle-specific interpretation of side
|
|
* conditions.
|
|
*
|
|
* @param vehicle
|
|
* @param moveInfo Cheating.
|
|
* @param thisMove
|
|
*/
|
|
protected void prepareCheckDetails(final Entity vehicle, final VehicleMoveInfo moveInfo, final VehicleMoveData thisMove) {
|
|
checkDetails.reset();
|
|
// TODO: These properties are for boats, might need to distinguish further.
|
|
checkDetails.fromIsSafeMedium = thisMove.from.inWater || thisMove.from.onGround || thisMove.from.inWeb;
|
|
checkDetails.toIsSafeMedium = thisMove.to.inWater || thisMove.to.onGround || thisMove.to.inWeb;
|
|
checkDetails.inAir = !checkDetails.fromIsSafeMedium && !checkDetails.toIsSafeMedium;
|
|
// Distinguish by entity class (needs future proofing at all?).
|
|
if (vehicle instanceof Boat) {
|
|
checkDetails.simplifiedType = EntityType.BOAT;
|
|
checkDetails.maxAscend = MagicVehicle.maxAscend;
|
|
}
|
|
else if (vehicle instanceof Minecart) {
|
|
checkDetails.simplifiedType = EntityType.MINECART;
|
|
// Bind to rails.
|
|
checkDetails.canRails = true;
|
|
thisMove.setExtraMinecartProperties(moveInfo); // Cheating.
|
|
if (thisMove.fromOnRails) {
|
|
checkDetails.fromIsSafeMedium = true;
|
|
checkDetails.inAir = false;
|
|
}
|
|
if (thisMove.toOnRails) {
|
|
checkDetails.toIsSafeMedium = true;
|
|
checkDetails.inAir = false;
|
|
}
|
|
checkDetails.gravityTargetSpeed = 0.79;
|
|
}
|
|
else if (bestHorse != null && bestHorse.isAssignableFrom(vehicle.getClass())) {
|
|
// TODO: Climbable? -> seems not.
|
|
checkDetails.simplifiedType = EntityType.HORSE; // TODO: 1.11 - Use AbstractHorse?
|
|
checkDetails.canJump = checkDetails.canStepUpBlock = true;
|
|
}
|
|
else if (vehicle instanceof Pig) {
|
|
// TODO: Climbable!
|
|
checkDetails.simplifiedType = EntityType.PIG;
|
|
checkDetails.canJump = false;
|
|
checkDetails.canStepUpBlock = true;
|
|
checkDetails.canClimb = true;
|
|
}
|
|
else {
|
|
checkDetails.simplifiedType = thisMove.vehicleType;
|
|
}
|
|
|
|
// Generic settings.
|
|
// (maxAscend is not checked for stepping up blocks)
|
|
if (checkDetails.canJump) {
|
|
checkDetails.maxAscend = 1.0; // Coarse envelope. Actual lift off gain should be checked on demand.
|
|
}
|
|
// Climbable
|
|
if (checkDetails.canClimb) {
|
|
if (thisMove.from.onClimbable) {
|
|
checkDetails.fromIsSafeMedium = true;
|
|
checkDetails.inAir = false;
|
|
}
|
|
if (thisMove.to.onClimbable) {
|
|
checkDetails.toIsSafeMedium = true;
|
|
checkDetails.inAir = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generic in-air check.
|
|
* @param thisMove
|
|
* @param data
|
|
* @return
|
|
*/
|
|
private boolean checkInAir(final VehicleMoveData thisMove, final MovingData data,
|
|
final boolean debug) {
|
|
|
|
// TODO: Distinguish sfJumpPhase and inAirDescendCount (after reaching the highest point).
|
|
|
|
if (debug) {
|
|
debugDetails.add("air-air");
|
|
}
|
|
|
|
if (checkDetails.canJump) {
|
|
// TODO: Max. y-distance to set back.
|
|
// TODO: Friction.
|
|
}
|
|
else {
|
|
if (thisMove.yDistance > 0.0) {
|
|
tags.add("ascend_at_all");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
boolean violation = false;
|
|
// Absolute vertical distance to set back.
|
|
// TODO: Add something like this.
|
|
// final double setBackYdistance = to.getY() - data.vehicleSetBacks.getValidSafeMediumEntry().getY();
|
|
// if (data.sfJumpPhase > 4) {
|
|
// double estimate = Math.min(2.0, MagicVehicle.boatGravityMin * ((double) data.sfJumpPhase / 4.0) * ((double) data.sfJumpPhase / 4.0 + 1.0) / 2.0);
|
|
// if (setBackYdistance > -estimate) {
|
|
// tags.add("slow_fall_vdistsb");
|
|
// return true;
|
|
// }
|
|
// }
|
|
// Enforce falling speed (vdist) envelope by in-air phase count.
|
|
// Slow falling (vdist), do not bind to descending in general.
|
|
final double minDescend = -(thisMove.yDistance < -MagicVehicle.boatLowGravitySpeed ? MagicVehicle.boatGravityMinAtSpeed : MagicVehicle.boatGravityMin) * (checkDetails.canJump ? Math.max(data.sfJumpPhase - MagicVehicle.maxJumpPhaseAscend, 0) : data.sfJumpPhase);
|
|
final double maxDescend = getInAirMaxDescend(thisMove, data);
|
|
if (data.sfJumpPhase > (checkDetails.canJump ? MagicVehicle.maxJumpPhaseAscend : 1)
|
|
&& thisMove.yDistance > Math.max(minDescend, -checkDetails.gravityTargetSpeed)) {
|
|
tags.add("slow_fall_vdist");
|
|
violation = true;
|
|
}
|
|
// Fast falling (vdist).
|
|
else if (data.sfJumpPhase > 1 && thisMove.yDistance < maxDescend) {
|
|
// TODO: Allow one skipped move per jump phase (1, 2, 3).
|
|
tags.add("fast_fall_vdist");
|
|
violation = true;
|
|
}
|
|
if (violation) {
|
|
// Post violation detection workarounds.
|
|
if (MagicVehicle.oddInAir(thisMove, minDescend, maxDescend, checkDetails, data)) {
|
|
violation = false;
|
|
checkDetails.checkDescendMuch = checkDetails.checkAscendMuch = false; // (Full envelope has been checked.)
|
|
}
|
|
if (debug) {
|
|
debugDetails.add("maxDescend: " + maxDescend);
|
|
}
|
|
}
|
|
return violation;
|
|
}
|
|
|
|
private double getInAirMaxDescend(final PlayerMoveData thisMove, final MovingData data) {
|
|
double maxDescend = -MagicVehicle.boatGravityMax * data.sfJumpPhase - 0.5;
|
|
final VehicleMoveData firstPastMove = data.vehicleMoves.getFirstPastMove();
|
|
if (thisMove.yDistance < maxDescend && firstPastMove.toIsValid) {
|
|
if (firstPastMove.yDistance < maxDescend && firstPastMove.yDistance > maxDescend * 2.5) {
|
|
// Simply continue with friction.
|
|
maxDescend = Math.min(maxDescend, firstPastMove.yDistance - (MagicVehicle.boatGravityMax + MagicVehicle.boatGravityMin) / 2.0);
|
|
debugDetails.add("desc_frict");
|
|
}
|
|
else if (firstPastMove.specialCondition && thisMove.yDistance > -1.5) {
|
|
// After special set-back confirm move, observed ca. -1.1.
|
|
maxDescend = Math.min(maxDescend, -1.5);
|
|
debugDetails.add("desc_special");
|
|
}
|
|
}
|
|
return maxDescend;
|
|
}
|
|
|
|
private boolean maxDistHorizontal(final VehicleMoveData thisMove, final double maxDistanceHorizontal) {
|
|
if (thisMove.hDistance > maxDistanceHorizontal) {
|
|
tags.add("hdist");
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
}
|