NoCheatPlus/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/magic/Magic.java

370 lines
15 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.magic;
import fr.neatmonster.nocheatplus.checks.moving.MovingData;
import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData;
/**
* Keeping some of the magic confined in here.
*
* @author asofold
*
*/
public class Magic {
// CraftBukkit/Minecraft constants.
public static final double DEFAULT_WALKSPEED = 0.2;
public static final double DEFAULT_FLYSPEED = 0.1;
// Gravity.
public static final double GRAVITY_MAX = 0.0834;
public static final double GRAVITY_MIN = 0.0624; // TODO: Special cases go down to 0.05.
public static final double GRAVITY_SPAN = GRAVITY_MAX - GRAVITY_MIN;
public static final double GRAVITY_ODD = 0.05; // 19; // TODO: This should probably be min. / cleanup.
/** Assumed minimal average decrease per move, suitable for regarding 3 moves. */
public static final float GRAVITY_VACC = (float) (GRAVITY_MIN * 0.6);
// Friction factor by medium (move inside of).
public static final double FRICTION_MEDIUM_AIR = 0.98;
/** Friction for water (default). */
public static final double FRICTION_MEDIUM_WATER = 0.89;
/** Friction for lava. */
public static final double FRICTION_MEDIUM_LAVA = 0.535;
// Horizontal speeds/modifiers.
public static final double WALK_SPEED = 0.221D;
public static final double modSneak = 0.13D / WALK_SPEED;
// public static final double modSprint = 0.29 / walkSpeed; // TODO: without bunny 0.29 / practical is 0.35
public static final double modBlock = 0.16D / WALK_SPEED;
public static final double modSwim = 0.115D / WALK_SPEED;
public static final double[] modDepthStrider = new double[] {
1.0,
0.1645 / modSwim / WALK_SPEED,
0.1995 / modSwim / WALK_SPEED,
1.0 / modSwim, // Results in walkspeed.
};
public static final double modWeb = 0.105D / WALK_SPEED; // TODO: walkingSpeed * 0.15D; <- does not work
public static final double modIce = 2.5D; //
/** Faster moving down stream (water mainly). */
public static final double modDownStream = 0.19 / (WALK_SPEED * modSwim);
/**
* Somewhat arbitrary horizontal speed gain maximum for advance glide phase.
*/
public static final double GLIDE_HORIZONTAL_GAIN_MAX = GRAVITY_MAX / 2.0;
// Vertical speeds/modifiers.
public static final double climbSpeedAscend = 0.119;
public static final double climbSpeedDescend = 0.151;
/**
* Some kind of minimum y descend speed (note the negative sign), for an
* already advanced gliding/falling phase with elytra.
*/
public static final double GLIDE_DESCEND_PHASE_MIN = -Magic.GRAVITY_MAX - Magic.GRAVITY_SPAN;
/**
* Somewhat arbitrary, advanced glide phase, maximum descend speed gain
* (absolute value is negative).
*/
public static final double GLIDE_DESCEND_GAIN_MAX_NEG = -GRAVITY_MAX;
/**
* Somewhat arbitrary, advanced glide phase, maximum descend speed gain
* (absolute value is positive, a negative gain seen in relation to the
* moving direction).
*/
public static final double GLIDE_DESCEND_GAIN_MAX_POS = GRAVITY_ODD / 1.95;
// On-ground.
public static final double Y_ON_GROUND_MIN = 0.00001;
public static final double Y_ON_GROUND_MAX = 0.0626;
// TODO: Model workarounds as lost ground, use Y_ON_GROUND_MIN?
public static final double Y_ON_GROUND_DEFAULT = 0.016; // Jump upwards, while placing blocks.
// public static final double Y_ON_GROUND_DEFAULT = 0.029; // Bounce off slime blocks.
/** The lower bound of fall distance for taking fall damage. */
public static final double FALL_DAMAGE_DIST = 3.0;
/** The minimum damage amount that actually should get applied. */
public static final double FALL_DAMAGE_MINIMUM = 0.5;
/**
* The maximum distance that can be achieved with bouncing back from slime
* blocks.
*/
public static final double BOUNCE_VERTICAL_MAX_DIST = 3.5;
// Other constants.
public static final double PAPER_DIST = 0.01;
/**
* Extreme move check threshold (Actual like 3.9 upwards with velocity,
* velocity downwards may be like -1.835 max., but falling will be near 3
* too.)
*/
public static final double EXTREME_MOVE_DIST_VERTICAL = 4.0;
public static final double EXTREME_MOVE_DIST_HORIZONTAL = 22.0;
/** Minimal xz-margin for chunk load. */
public static final double CHUNK_LOAD_MARGIN_MIN = 3.0;
/**
* The absolute per-tick base speed for swimming vertically.
*
* @return
*/
public static double swimBaseSpeedV() {
// TODO: Does this have to be the dynamic walk speed (refactoring)?
return WALK_SPEED * modSwim + 0.02;
}
/**
* Test if the player is (well) within in-air falling envelope.
* @param yDistance
* @param lastYDist
* @param extraGravity Extra amount to fall faster.
* @return
*/
public static boolean fallingEnvelope(final double yDistance, final double lastYDist,
final double lastFrictionVertical, final double extraGravity) {
if (yDistance >= lastYDist) {
return false;
}
// TODO: data.lastFrictionVertical (see vDistAir).
final double frictDist = lastYDist * lastFrictionVertical - GRAVITY_MIN;
// TODO: Extra amount: distinguish pos/neg?
return yDistance <= frictDist + extraGravity && yDistance > frictDist - GRAVITY_SPAN - extraGravity;
}
/**
* Friction envelope testing, with a different kind of leniency (relate
* off-amount to decreased amount), testing if 'friction' has been accounted
* for in a sufficient but not necessarily exact way.<br>
* In the current shape this method is meant for higher speeds rather (needs
* a twist for low speed comparison).
*
* @param thisMove
* @param lastMove
* @param friction
* Friction factor to apply.
* @param minGravity
* Amount to subtract from frictDist by default.
* @param maxOff
* Amount yDistance may be off the friction distance.
* @param decreaseByOff
* Factor, how many times the amount being off friction distance
* must fit into the decrease from lastMove to thisMove.
* @return
*/
public static boolean enoughFrictionEnvelope(final PlayerMoveData thisMove, final PlayerMoveData lastMove, final double friction,
final double minGravity, final double maxOff, final double decreaseByOff) {
// TODO: Elaborate... could have one method to test them all?
final double frictDist = lastMove.yDistance * friction - minGravity;
final double off = Math.abs(thisMove.yDistance - frictDist);
return off <= maxOff && Math.abs(thisMove.yDistance - lastMove.yDistance) <= off * decreaseByOff;
}
/**
* Test for a specific move in-air -> water, then water -> in-air.
*
* @param thisMove
* Not strictly the latest move in MovingData.
* @param lastMove
* Move before thisMove.
* @return
*/
static boolean splashMove(final PlayerMoveData thisMove, final PlayerMoveData lastMove) {
// Use past move data for two moves.
return !thisMove.touchedGround && thisMove.from.inWater && !thisMove.to.resetCond // Out of water.
&& !lastMove.touchedGround && !lastMove.from.resetCond && lastMove.to.inWater // Into water.
&& excludeStaticSpeed(thisMove) && excludeStaticSpeed(lastMove)
;
}
/**
* Test for a specific move ground/in-air -> water, then water -> in-air.
*
* @param thisMove
* Not strictly the latest move in MovingData.
* @param lastMove
* Move before thisMove.
* @return
*/
static boolean splashMoveNonStrict(final PlayerMoveData thisMove, final PlayerMoveData lastMove) {
// Use past move data for two moves.
return !thisMove.touchedGround && thisMove.from.inWater && !thisMove.to.resetCond // Out of water.
&& !lastMove.from.resetCond && lastMove.to.inWater // Into water.
&& excludeStaticSpeed(thisMove) && excludeStaticSpeed(lastMove)
;
}
/**
* Fully in-air move.
*
* @param thisMove
* Not strictly the latest move in MovingData.
* @return
*/
public static boolean inAir(final PlayerMoveData thisMove) {
return !thisMove.touchedGround && !thisMove.from.resetCond && !thisMove.to.resetCond;
}
/**
* A liquid -> liquid move. Exclude web and climbable.
*
* @param thisMove
* @return
*/
static boolean inLiquid(final PlayerMoveData thisMove) {
return thisMove.from.inLiquid && thisMove.to.inLiquid && excludeStaticSpeed(thisMove);
}
/**
* Test if either point is in reset condition (liquid, web, ladder).
*
* @param thisMove
* @return
*/
static boolean resetCond(final PlayerMoveData thisMove) {
return thisMove.from.resetCond || thisMove.to.resetCond;
}
/**
* Moving out of liquid, might move onto ground. Exclude web and climbable.
*
* @param thisMove
* @return
*/
static boolean leavingLiquid(final PlayerMoveData thisMove) {
return thisMove.from.inLiquid && !thisMove.to.inLiquid && excludeStaticSpeed(thisMove);
}
/**
* Moving into liquid., might move onto ground. Exclude web and climbable.
*
* @param thisMove
* @return
*/
static boolean intoLiquid(final PlayerMoveData thisMove) {
return !thisMove.from.inLiquid && thisMove.to.inLiquid && excludeStaticSpeed(thisMove);
}
/**
* Exclude moving from/to blocks with static (vertical) speed, such as web
* or climbable.
*
* @param thisMove
* @return
*/
public static boolean excludeStaticSpeed(final PlayerMoveData thisMove) {
return !thisMove.from.inWeb && !thisMove.to.inWeb
&& !thisMove.from.onClimbable && !thisMove.to.onClimbable;
}
/**
* First move after set back / teleport. Originally has been found with
* PaperSpigot for MC 1.7.10, however it also does occur on Spigot for MC
* 1.7.10.
*
* @param thisMove
* @param lastMove
* @param data
* @return
*/
public static boolean skipPaper(final PlayerMoveData thisMove, final PlayerMoveData lastMove, final MovingData data) {
// TODO: Confine to from at block level (offset 0)?
final double setBackYDistance = thisMove.to.getY() - data.getSetBackY();
return !lastMove.toIsValid && data.sfJumpPhase == 0 && thisMove.multiMoveCount > 0
&& setBackYDistance > 0.0 && setBackYDistance < PAPER_DIST
&& thisMove.yDistance > 0.0 && thisMove.yDistance < PAPER_DIST && inAir(thisMove);
}
/**
* Disregarding this move, test if all the way falling after head being
* obstructed.
*
* @param data
* @return
*/
public static boolean fallAfterHeadObstructed(final MovingData data, int limit) {
limit = Math.min(limit, data.playerMoves.getNumberOfPastMoves());
for (int i = 0; i < limit; i++) {
final PlayerMoveData move = data.playerMoves.getPastMove(i);
if (!move.toIsValid || move.yDistance >= 0.0) {
return false;
}
else if (move.headObstructed) {
return true;
}
}
return false;
}
/**
* Advanced glide phase vertical gain envelope.
* @param yDistance
* @param previousYDistance
* @return
*/
public static boolean glideVerticalGainEnvelope(final double yDistance, final double previousYDistance) {
return // Sufficient speed of descending.
yDistance < GLIDE_DESCEND_PHASE_MIN && previousYDistance < GLIDE_DESCEND_PHASE_MIN
// Controlled difference.
&& yDistance - previousYDistance > GLIDE_DESCEND_GAIN_MAX_NEG
&& yDistance - previousYDistance < GLIDE_DESCEND_GAIN_MAX_POS;
}
/**
* Test if this + last 2 moves are within the gliding envelope (elytra), in
* this case with horizontal speed gain.
*
* @param thisMove
* @param lastMove
* @param pastMove1
* Is checked for validity in here (needed).
* @return
*/
public static boolean glideEnvelopeWithHorizontalGain(final PlayerMoveData thisMove, final PlayerMoveData lastMove, final PlayerMoveData pastMove1) {
return pastMove1.toIsValid
&& Magic.glideVerticalGainEnvelope(thisMove.yDistance, lastMove.yDistance)
&& Magic.glideVerticalGainEnvelope(lastMove.yDistance, pastMove1.yDistance)
&& lastMove.hDistance > pastMove1.hDistance && thisMove.hDistance > lastMove.hDistance
&& Math.abs(lastMove.hDistance - pastMove1.hDistance) < Magic.GLIDE_HORIZONTAL_GAIN_MAX
&& Math.abs(thisMove.hDistance - lastMove.hDistance) < Magic.GLIDE_HORIZONTAL_GAIN_MAX
;
}
/**
* Jump off the top off a block with the ordinary jumping envelope, however
* from a slightly higher position with the initial gain being lower than
* typical, but the following move having the y distance as if jumped off
* with typical gain.
*
* @param yDistance
* @param maxJumpGain
* @param thisMove
* @param lastMove
* @param data
* @return
*/
public static boolean noobJumpsOffTower(final double yDistance, final double maxJumpGain,
final PlayerMoveData thisMove, final PlayerMoveData lastMove, final MovingData data) {
final PlayerMoveData secondPastMove = data.playerMoves.getSecondPastMove();
return (data.sfJumpPhase == 1 && lastMove.touchedGroundWorkaround // TODO: Not observed though.
|| data.sfJumpPhase == 2 && inAir(lastMove)
&& secondPastMove.valid && secondPastMove.touchedGroundWorkaround
)
&& inAir(thisMove)
&& lastMove.yDistance < maxJumpGain && lastMove.yDistance > maxJumpGain * 0.67
&& Magic.fallingEnvelope(yDistance, maxJumpGain, data.lastFrictionVertical, Magic.GRAVITY_SPAN);
}
}