[BLEEDING] Elytra boost: track and allow ascend.

Lots of issues remain with elytra, with and without boost. Selection:
* maxheight will trigger with the rocket feature, naturally. Mendable by
increasing it via configuration
(checks.moving.creativelfly.model.elytra.vertical.maxheight). Not sure
we'll just increase the limit or alter how it's dealt with (e.g. also
for sf: lock to a max / high slope value, independently of the set-back
and world height, alter as necessary).
* All sorts of transitions, e.g. onto ground, into water.
* Loss of boost right after adding (not sure if already fixed).
* What with hover, actually?
* Is the flight duration infinite with power 127?
* Issues with ascending after descending, even without boost?
This commit is contained in:
asofold 2017-01-29 18:32:43 +01:00
parent 8237fbca57
commit c2449ac08a
6 changed files with 224 additions and 64 deletions

View File

@ -30,10 +30,14 @@ import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.checks.CheckListener;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.combined.CombinedConfig;
import fr.neatmonster.nocheatplus.checks.moving.MovingData;
import fr.neatmonster.nocheatplus.checks.moving.magic.Magic;
import fr.neatmonster.nocheatplus.compat.Bridge1_9;
import fr.neatmonster.nocheatplus.compat.BridgeHealth;
import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.stats.Counters;
import fr.neatmonster.nocheatplus.utilities.InventoryUtil;
import fr.neatmonster.nocheatplus.utilities.TickTask;
import fr.neatmonster.nocheatplus.utilities.location.LocUtil;
import fr.neatmonster.nocheatplus.utilities.map.BlockProperties;
@ -72,8 +76,7 @@ public class BlockInteractListener extends CheckListener {
* @param event
* the event
*/
@EventHandler(
ignoreCancelled = false, priority = EventPriority.LOWEST)
@EventHandler(ignoreCancelled = false, priority = EventPriority.LOWEST)
protected void onPlayerInteract(final PlayerInteractEvent event) {
final Player player = event.getPlayer();
// Cancel interact events for dead players.
@ -86,51 +89,51 @@ public class BlockInteractListener extends CheckListener {
return;
}
// TODO: Re-arrange for interact spam, possibly move ender pearl stuff to a method.
// TODO: Re-arrange for interact spamming.
final Action action = event.getAction();
final Block block = event.getClickedBlock();
if (block == null) {
return;
}
final BlockInteractData data = BlockInteractData.getData(player);
final int previousLastTick = data.lastTick;
data.setLastBlock(block, action);
// TODO: Last block setting: better on monitor !?.
if (block == null) {
data.resetLastBlock();
}
else {
data.setLastBlock(block, action);
}
final BlockFace face = event.getBlockFace();
final ItemStack stack;
switch(action) {
case RIGHT_CLICK_AIR:
// TODO: What else to adapt?
case LEFT_CLICK_AIR:
// TODO: What else to adapt?
case LEFT_CLICK_BLOCK:
stack = null;
break;
case RIGHT_CLICK_BLOCK:
final ItemStack stack = Bridge1_9.getUsedItem(player, event);
stack = Bridge1_9.getUsedItem(player, event);
if (stack != null && stack.getType() == Material.ENDER_PEARL) {
if (!BlockProperties.isPassable(block.getType())) {
final CombinedConfig ccc = CombinedConfig.getConfig(player);
if (ccc.enderPearlCheck && ccc.enderPearlPreventClickBlock) {
event.setUseItemInHand(Result.DENY);
if (data.debug) {
final BlockInteractConfig cc = BlockInteractConfig.getConfig(player);
genericDebug(player, block, face, event, "click block: deny use ender pearl", previousLastTick, data, cc);
}
}
}
checkEnderPearlRightClickBlock(player, block, face, event, previousLastTick, data);
}
break;
default:
return;
}
boolean cancelled = false;
if (event.isCancelled() && event.useInteractedBlock() != Result.ALLOW) {
data.subsequentCancel ++;
return;
}
final BlockInteractConfig cc = BlockInteractConfig.getConfig(player);
boolean cancelled = false;
boolean preventUseItem = false;
final Location loc = player.getLocation(useLoc);
// TODO: Always run all checks, also for !isBlock ?
// Interaction speed.
if (!cancelled && speed.isEnabled(player) && speed.check(player, data, cc)) {
cancelled = true;
@ -154,36 +157,7 @@ public class BlockInteractListener extends CheckListener {
// If one of the checks requested to cancel the event, do so.
if (cancelled) {
if (event.isCancelled()) {
// Just prevent using the block.
event.setUseInteractedBlock(Result.DENY);
if (data.debug) {
genericDebug(player, block, face, event, "already cancelled: deny use block", previousLastTick, data, cc);
}
} else {
final Result previousUseItem = event.useItemInHand();
event.setCancelled(true);
event.setUseInteractedBlock(Result.DENY);
if (
previousUseItem == Result.DENY || preventUseItem
// Allow consumption still.
|| !InventoryUtil.isConsumable(Bridge1_9.getUsedItem(player, event))
) {
event.setUseItemInHand(Result.DENY);
if (data.debug) {
genericDebug(player, block, face, event, "deny item use", previousLastTick, data, cc);
}
}
else {
// Consumable and not prevented otherwise.
// TODO: Ender pearl?
event.setUseItemInHand(Result.ALLOW);
if (data.debug) {
genericDebug(player, block, face, event, "allow edible item use", previousLastTick, data, cc);
}
}
}
data.subsequentCancel ++;
onCancelInteract(player, block, face, event, previousLastTick, preventUseItem, data, cc);
}
else {
data.subsequentCancel = 0;
@ -191,6 +165,87 @@ public class BlockInteractListener extends CheckListener {
useLoc.setWorld(null);
}
private void onCancelInteract(final Player player, final Block block, final BlockFace face,
final PlayerInteractEvent event, final int previousLastTick, final boolean preventUseItem,
final BlockInteractData data, final BlockInteractConfig cc) {
if (event.isCancelled()) {
// Just prevent using the block.
event.setUseInteractedBlock(Result.DENY);
if (data.debug) {
genericDebug(player, block, face, event, "already cancelled: deny use block", previousLastTick, data, cc);
}
} else {
final Result previousUseItem = event.useItemInHand();
event.setCancelled(true);
event.setUseInteractedBlock(Result.DENY);
if (
previousUseItem == Result.DENY || preventUseItem
// Allow consumption still.
|| !InventoryUtil.isConsumable(Bridge1_9.getUsedItem(player, event))
) {
event.setUseItemInHand(Result.DENY);
if (data.debug) {
genericDebug(player, block, face, event, "deny item use", previousLastTick, data, cc);
}
}
else {
// Consumable and not prevented otherwise.
// TODO: Ender pearl?
event.setUseItemInHand(Result.ALLOW);
if (data.debug) {
genericDebug(player, block, face, event, "allow edible item use", previousLastTick, data, cc);
}
}
}
data.subsequentCancel ++;
}
@EventHandler(ignoreCancelled = false, priority = EventPriority.MONITOR)
public void onPlayerInteractMonitor(final PlayerInteractEvent event) {
// Elytra boost.
if (event.getAction() == Action.RIGHT_CLICK_AIR
&& event.isCancelled() && event.useItemInHand() != Result.DENY) {
final Player player = event.getPlayer();
final ItemStack stack = Bridge1_9.getUsedItem(player, event);
if (stack != null && BridgeMisc.maybeElytraBoost(player, stack.getType())) {
final BlockInteractData data = BlockInteractData.getData(player);
final int power = BridgeMisc.getFireworksPower(stack);
/*
* The default Material.FIREWORK has power 0 and roughly does 10
* blocks in one seconds. With power 127, the flight duration
* roughly is 7 seconds, so we assume the effect duration to be
* something like 20 + power. What does the wiki tell? However
* the actual flight duration with elytra + fireworks seems to
* be higher, or the friction behavior has changed.
*/
final MovingData mData = MovingData.getData(player);
mData.fireworksBoostDuration = power + Magic.FIREWORKS_BOOST_EXTRA_TICKS;
// Expiration tick: not general latency, rather a minimum margin for sudden congestion.
mData.fireworksBoostTickExpire = TickTask.getTick() + power + Magic.FIREWORKS_BOOST_EXTRA_TICKS;
// TODO: Invalidation mechanics: by tick/time well ?
// TODO: Implement using it in CreativeFly.
if (data.debug) {
debug(player, "Elytra boost (power " + power + "): " + stack);
}
}
}
}
private void checkEnderPearlRightClickBlock(final Player player, final Block block,
final BlockFace face, final PlayerInteractEvent event,
final int previousLastTick, final BlockInteractData data) {
if (!BlockProperties.isPassable(block.getType())) {
final CombinedConfig ccc = CombinedConfig.getConfig(player);
if (ccc.enderPearlCheck && ccc.enderPearlPreventClickBlock) {
event.setUseItemInHand(Result.DENY);
if (data.debug) {
final BlockInteractConfig cc = BlockInteractConfig.getConfig(player);
genericDebug(player, block, face, event, "click block: deny use ender pearl", previousLastTick, data, cc);
}
}
}
}
private void genericDebug(final Player player, final Block block, final BlockFace face,
final PlayerInteractEvent event, final String tag, final int previousLastTick,
final BlockInteractData data, final BlockInteractConfig cc) {

View File

@ -220,6 +220,13 @@ public class MovingData extends ACheckData {
/** Horizontal velocity modeled as an axis (always positive) */
private final FrictionAxisVelocity horVel = new FrictionAxisVelocity();
/** Duration of the boost effect in ticks. */
public int fireworksBoostDuration = 0;
/**
* Expire at this tick.
*/
public int fireworksBoostTickExpire = 0;
// Coordinates.
/** Moving trace (to-positions, use tick as time). This is initialized on "playerJoins, i.e. MONITOR, and set to null on playerLeaves." */
private final LocationTrace trace;
@ -291,6 +298,7 @@ public class MovingData extends ACheckData {
public double sfCobwebVL = 0;
public long sfVLTime = 0;
// Accounting info.
public final ActionAccumulator vDistAcc = new ActionAccumulator(3, 3);
/**
@ -389,8 +397,7 @@ public class MovingData extends ACheckData {
sfZeroVdistRepeat = 0;
clearAccounting();
clearNoFallData();
removeAllVelocity();
sfHorizontalBuffer = 0.0;
removeAllPlayerSpeedModifiers();
lostSprintCount = 0;
sfHoverTicks = sfHoverLoginTicks = -1;
sfDirty = false;
@ -421,14 +428,13 @@ public class MovingData extends ACheckData {
// Keep jump amplifier
// Keep bunny-hop delay (?)
// keep jump phase.
sfHorizontalBuffer = 0.0;
lostSprintCount = 0;
sfHoverTicks = -1; // 0 ?
sfDirty = false;
sfLowJump = false;
liftOffEnvelope = defaultLiftOffEnvelope;
insideMediumCount = 0;
removeAllVelocity();
removeAllPlayerSpeedModifiers();
vehicleConsistency = MoveConsistency.INCONSISTENT; // Not entirely sure here.
lastFrictionHorizontal = lastFrictionVertical = 0.0;
verticalBounce = null;
@ -496,7 +502,7 @@ public class MovingData extends ACheckData {
* Called when a player leaves the server.
*/
public void onPlayerLeave() {
removeAllVelocity();
removeAllPlayerSpeedModifiers();
trace.reset();
playerMoves.invalidate();
vehicleMoves.invalidate();
@ -864,7 +870,21 @@ public class MovingData extends ACheckData {
}
/**
* Remove all vertical and horizontal velocity.
* Remove/reset all speed modifier tracking, like vertical and horizontal
* velocity, elytra boost, buffer.
*/
private void removeAllPlayerSpeedModifiers() {
// Velocity
removeAllVelocity();
// Elytra boost best fits velocity / effects.
fireworksBoostDuration = 0;
fireworksBoostTickExpire = 0;
// Horizontal buffer.
sfHorizontalBuffer = 0.0;
}
/**
* Reset velocity tracking (h+v).
*/
public void removeAllVelocity() {
horVel.clear();
@ -1210,7 +1230,7 @@ public class MovingData extends ACheckData {
sfCobwebTime = Math.min(sfCobwebTime, time);
sfVLTime = Math.min(sfVLTime, time);
clearAccounting(); // Not sure: adding up might not be nice.
removeAllVelocity(); // TODO: This likely leads to problems.
removeAllPlayerSpeedModifiers(); // TODO: This likely leads to problems.
// (ActionFrequency can handle this.)
}

View File

@ -822,7 +822,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
else if (checkCf) {
// CreativeFly
if (newTo == null) {
newTo = creativeFly.check(player, pFrom, pTo, data, cc, time, useBlockChangeTracker);
newTo = creativeFly.check(player, pFrom, pTo, data, cc, time, tick, useBlockChangeTracker);
}
data.sfHoverTicks = -1;
data.sfLowJump = false;

View File

@ -82,6 +82,7 @@ public class Magic {
* moving direction).
*/
public static final double GLIDE_DESCEND_GAIN_MAX_POS = GRAVITY_ODD / 1.95;
public static final int FIREWORKS_BOOST_EXTRA_TICKS = 50;
// On-ground.
public static final double Y_ON_GROUND_MIN = 0.00001;

View File

@ -71,7 +71,8 @@ public class CreativeFly extends Check {
* @return
*/
public Location check(final Player player, final PlayerLocation from, final PlayerLocation to,
final MovingData data, final MovingConfig cc, final long time, final boolean useBlockChangeTracker) {
final MovingData data, final MovingConfig cc, final long time, final int tick,
final boolean useBlockChangeTracker) {
// Reset tags, just in case.
tags.clear();
@ -87,6 +88,18 @@ public class CreativeFly extends Check {
thisMove.modelFlying = model;
final PlayerMoveData lastMove = data.playerMoves.getFirstPastMove();
// Proactive reset of elytraBoost (MC 1.11.2).
if (data.fireworksBoostDuration > 0) {
if (!lastMove.valid || lastMove.flyCheck != CheckType.MOVING_CREATIVEFLY
|| lastMove.modelFlying != model
|| data.fireworksBoostTickExpire < tick ) {
data.fireworksBoostDuration = 0;
}
else {
data.fireworksBoostDuration --;
}
}
// Calculate some distances.
final double yDistance = thisMove.yDistance;
final double hDistance = thisMove.hDistance;
@ -361,9 +374,9 @@ public class CreativeFly extends Check {
}
}
// TODO: Hack, move / config / something.
// TODO: Confine more. hdist change relates to ydist change
// Related to elytra.
if (limitV == 0.0 && Bridge1_9.isGlidingWithElytra(from.getPlayer())) {
// TODO: Better detection of an elytra model (extra flags?).
limitV = hackLytra(yDistance, limitV, thisMove, lastMove, data);
}
@ -423,6 +436,8 @@ public class CreativeFly extends Check {
}
private double hackLytra(final double yDistance, final double limitV, final PlayerMoveData thisMove, final PlayerMoveData lastMove, final MovingData data) {
// TODO: Hack, move / config / something.
// TODO: Confine more. hdist change relates to ydist change
// TODO: Further: jumpphase vs. y-distance to set-back. Problem: velocity
// TODO: Further: record max h and descend speeds and relate to those.
// TODO: Demand total speed to decrease.
@ -461,8 +476,33 @@ public class CreativeFly extends Check {
}
}
}
}
// Elytra boost with fireworks rockets.
if (yDistance > limitV && data.fireworksBoostDuration > 0 && lastMove.toIsValid
&& (
yDistance >= lastMove.yDistance
|| yDistance - lastMove.yDistance < Magic.GRAVITY_MAX
// TODO: Head blocked -> friction does it?
)
&& (
yDistance - lastMove.yDistance < 0.77 // TODO
|| lastMove.yDistance < 0.0 && yDistance < 1.54
)
&& yDistance < 1.67 // Last resort, check / TODO
) {
/*
* TODO: Do cross check item consumption (do other events fire?).
* [?on tick: expectations framework, check before tick and before
* other inventory events, once set]
*/
// TODO: Remove fumbling with magic constants.
// TODO: Relate horizontal to vertical + relate to looking direction.
// TODO: More invalidation conditions, like total age (checked elsewhere?).
tags.add("fw_boost_asc");
return yDistance;
}
return limitV;
}
@ -527,6 +567,9 @@ public class CreativeFly extends Check {
if (thisMove.verVelUsed != null) {
builder.append(" , vVelUsed: " + thisMove.verVelUsed);
}
if (data.fireworksBoostDuration > 0 && model.id.equals("jetpack.elytra")) {
builder.append(" , boost: " + data.fireworksBoostDuration);
}
builder.append(" , model: " + model.id);
if (!tags.isEmpty()) {
builder.append(" , tags: ");

View File

@ -20,9 +20,13 @@ import java.util.Collection;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.FireworkMeta;
import org.bukkit.inventory.meta.ItemMeta;
import fr.neatmonster.nocheatplus.utilities.ReflectionUtil;
@ -42,9 +46,11 @@ public class BridgeMisc {
}
public static final GameMode GAME_MODE_SPECTATOR = getSpectatorGameMode();
private static final Method Bukkit_getOnlinePlayers = ReflectionUtil.getMethodNoArgs(Bukkit.class, "getOnlinePlayers");
public static final Material FIREWORK = Material.getMaterial("FIREWORK");
/**
* Return a shooter of a projectile if we get an entity, null otherwise.
*/
@ -104,4 +110,39 @@ public class BridgeMisc {
return new Player[0];
}
/**
* Test side conditions for fireworks boost with elytra on, for interaction
* with the item in hand. Added with Minecraft 1.11.2.
*
* @param player
* @param materialInHand
* The type of the item used with interaction.
* @return
*/
public static boolean maybeElytraBoost(final Player player, final Material materialInHand) {
// TODO: Account for MC version (needs configuration override or auto adapt to protocol support).
// TODO: Non-static due to version checks (...).
return FIREWORK != null && materialInHand == FIREWORK && Bridge1_9.isGlidingWithElytra(player);
}
/**
* Get the power for a firework(s) item (stack).
*
* @param item
* @return The power. Should be between 0 and 127 including edges, in case
* of valid items, according to javadocs (Spigot MC 1.11.2). In case
* the item is null or can't be judged, -1 is returned.
*/
public static int getFireworksPower(final ItemStack item) {
if (item == null || item.getType() != FIREWORK) {
return 0;
}
final ItemMeta meta = item.getItemMeta();
if (!(meta instanceof FireworkMeta)) { // INDIRECT: With elytra, this already exists.
return 0;
}
final FireworkMeta fwMeta = (FireworkMeta) meta;
return fwMeta.getPower();
}
}