mirror of
https://github.com/funkemunky/KauriV3.git
synced 2026-06-10 01:37:36 +00:00
Added new Aim check and utils
- Fixed TickTimer not functioning - Fixed generalCancel always cancelling - Removed debug from places that I am not currently in need of debugging - Attempting to fix login error when reloading when injecting packethandler - Added sensitivity to playerInfo command - Fixed runaway buffer fp on Fly (A)
This commit is contained in:
@@ -68,8 +68,6 @@ public class Anticheat extends JavaPlugin {
|
||||
.setNameFormat("Anticheat Schedular")
|
||||
.setUncaughtExceptionHandler((t, e) -> RunUtils.task(e::printStackTrace))
|
||||
.build());
|
||||
packetProcessor = new PacketProcessor();
|
||||
HandlerAbstract.init();
|
||||
|
||||
commandManager = new BukkitCommandManager(this);
|
||||
commandManager.enableUnstableAPI("help");
|
||||
@@ -77,11 +75,14 @@ public class Anticheat extends JavaPlugin {
|
||||
new CommandPropertiesManager(commandManager, getDataFolder(),
|
||||
getResource("command-messages.properties"));
|
||||
|
||||
this.keepaliveProcessor = new KeepaliveProcessor();
|
||||
packetProcessor = new PacketProcessor();
|
||||
this.checkManager = new CheckManager();
|
||||
this.playerRegistry = new PlayerRegistry();
|
||||
this.keepaliveProcessor = new KeepaliveProcessor();
|
||||
this.packetHandler = new PacketHandler();
|
||||
|
||||
HandlerAbstract.init();
|
||||
|
||||
alog(Color.Green + "Loading WorldInfo system...");
|
||||
Bukkit.getWorlds().forEach(w -> worldInfoMap.put(w.getUID(), new WorldInfo(w)));
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package dev.brighten.ac.check.impl.combat;
|
||||
|
||||
import dev.brighten.ac.check.Action;
|
||||
import dev.brighten.ac.check.Check;
|
||||
import dev.brighten.ac.check.CheckData;
|
||||
import dev.brighten.ac.check.CheckType;
|
||||
import dev.brighten.ac.data.APlayer;
|
||||
import dev.brighten.ac.packet.wrapper.in.WPacketPlayInFlying;
|
||||
import dev.brighten.ac.utils.timer.Timer;
|
||||
import dev.brighten.ac.utils.timer.impl.TickTimer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@CheckData(name = "Aim", type = CheckType.COMBAT)
|
||||
public class Aim extends Check {
|
||||
public Aim(APlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private float buffer;
|
||||
protected Timer lastGrid = new TickTimer(3);
|
||||
|
||||
@Action
|
||||
public void flying(WPacketPlayInFlying packet) {
|
||||
if(!packet.isLooked()) return;
|
||||
|
||||
final float sens = getPlayer().getMovement().getSensitivityMcp(),
|
||||
deltaYaw = Math.abs(getPlayer().getMovement().getDeltaYaw()),
|
||||
deltaPitch = Math.abs(getPlayer().getMovement().getDeltaPitch());
|
||||
final float deltaX = deltaYaw / getPlayer().getMovement().getYawMode();
|
||||
final float deltaY = deltaPitch / getPlayer().getMovement().getPitchMode();
|
||||
|
||||
if(getPlayer().getMovement().getYawGcdList().size() < 40)
|
||||
return;
|
||||
|
||||
final double gridX = getGrid(getPlayer().getMovement().getYawGcdList()),
|
||||
gridY = getGrid(getPlayer().getMovement().getPitchGcdList());
|
||||
|
||||
if(gridX < 0.005 || gridY < 0.005) lastGrid.reset();
|
||||
|
||||
if(deltaX > 200 || deltaY > 200) {
|
||||
if(buffer > 0) buffer--;
|
||||
getPlayer().getBukkitPlayer().sendMessage("Unstable");
|
||||
return;
|
||||
}
|
||||
|
||||
if(getPlayer().getMovement().getPitchGCD() < 0.007
|
||||
&& lastGrid.isPassed()
|
||||
&& getPlayer().getMovement().getLastHighRate().isNotPassed(3)) {
|
||||
if(deltaPitch < 10 && ++buffer > 8) {
|
||||
flag("%s", getPlayer().getMovement().getPitchGCD());
|
||||
}
|
||||
getPlayer().getBukkitPlayer().sendMessage("Flagged b:" + buffer);
|
||||
} else buffer = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is an attempt to reverse the logistics of cinematic camera without having to run a full on prediction using
|
||||
* mouse filters. Otherwise, we would need to run more heavy calculations which is not really production friendly.
|
||||
* It may be more accurate but it is not really worth it if in the end of the day we're eating server performance.
|
||||
*/
|
||||
protected static double getGrid(final List<Float> entry) {
|
||||
/*
|
||||
* We're creating the variables average min and max to start calculating the possibility of cinematic camera.
|
||||
* Why does this work? Cinematic camera is essentially a slowly increasing slowdown (which is why cinematic camera
|
||||
* becomes slower the more you use it) which in turn makes it so the min max and average are extremely close together.
|
||||
*/
|
||||
double average = 0.0;
|
||||
double min = 0.0, max = 0.0;
|
||||
|
||||
/*
|
||||
* These are simple min max calculations done manually for the sake of simplicity. We're using the numbers 0.0
|
||||
* since we also want to account for the possibility of a negative number. If there are no negative numbers then
|
||||
* there is absolutely no need for us to care about that number other than getting the max.
|
||||
*/
|
||||
for (final double number : entry) {
|
||||
if (number < min) min = number;
|
||||
if (number > max) max = number;
|
||||
|
||||
/*
|
||||
* Instead of having a sum variable we can use an average variable which we divide
|
||||
* right after the loop is over. Smart programming trick if you want to use it.
|
||||
*/
|
||||
average += number;
|
||||
}
|
||||
|
||||
/*
|
||||
* We're dividing the average by the length since this is the formula to getting the average.
|
||||
* Specifically its (sum(n) / length(n)) = average(n) -- with n being the entry set we're analyzing.
|
||||
*/
|
||||
average /= entry.size();
|
||||
|
||||
/*
|
||||
* This is going to estimate how close the average and the max were together with the possibility of a min
|
||||
* variable which is going to represent a negative variable since the preset variable on min is 0.0.
|
||||
*/
|
||||
return (max - average) - min;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ public class Reach extends Check {
|
||||
if(packet.getAction() == WPacketPlayInUseEntity.EnumEntityUseAction.ATTACK
|
||||
&& allowedEntityTypes.contains(packet.getEntity(getPlayer().getBukkitPlayer().getWorld()).getType())) {
|
||||
attacks.add(packet.getEntity(getPlayer().getBukkitPlayer().getWorld()));
|
||||
getPlayer().getBukkitPlayer().sendMessage("Attacked");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ public class FlyA extends Check {
|
||||
@Action
|
||||
public void onFlying(WPacketPlayInFlying packet) {
|
||||
if(!packet.isMoved() || (getPlayer().getMovement().getDeltaXZ() == 0
|
||||
&& getPlayer().getMovement().getDeltaY() == 0)) return;
|
||||
|
||||
&& getPlayer().getMovement().getDeltaY() == 0))
|
||||
return;
|
||||
|
||||
boolean onGround = getPlayer().getMovement().getTo().isOnGround(),
|
||||
fromGround = getPlayer().getMovement().getFrom().isOnGround();
|
||||
@@ -55,6 +55,7 @@ public class FlyA extends Check {
|
||||
|
||||
if(!getPlayer().getInfo().isGeneralCancel() && deltaPredict > 0.016) {
|
||||
if(++buffer > 5) {
|
||||
buffer = 5;
|
||||
flag("dY=%.3f p=%.3f dx=%.3f", getPlayer().getMovement().getDeltaY(), predicted,
|
||||
getPlayer().getMovement().getDeltaXZ());
|
||||
}
|
||||
|
||||
@@ -35,10 +35,8 @@ public class Speed extends Check {
|
||||
.toLocation(getPlayer().getBukkitPlayer().getWorld())
|
||||
.subtract(0, 1, 0));
|
||||
|
||||
if (underBlock == null || lastUnderBlock == null) {
|
||||
getPlayer().getBukkitPlayer().sendMessage("Null");
|
||||
if (underBlock == null || lastUnderBlock == null)
|
||||
return;
|
||||
}
|
||||
|
||||
float friction = CraftMagicNumbers.getBlock(underBlock).frictionFactor,
|
||||
lfriction = CraftMagicNumbers.getBlock(lastUnderBlock).frictionFactor;
|
||||
|
||||
@@ -92,6 +92,7 @@ public class AnticheatCommand extends BaseCommand {
|
||||
sender.sendMessage(MiscUtils.line(Color.Dark_Gray));
|
||||
sender.sendMessage(Color.translate("&6&lPing&8: &f" + player.getLagInfo().getTransPing() * 50 + "ms"));
|
||||
sender.sendMessage(Color.translate("&6&lVersion&8: &f" + player.getPlayerVersion().name()));
|
||||
sender.sendMessage(Color.translate("&6&lSensitivity&8: &f" + player.getMovement().getSensXPercent() + "%"));
|
||||
sender.sendMessage(MiscUtils.line(Color.Dark_Gray));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import dev.brighten.ac.packet.handler.HandlerAbstract;
|
||||
import dev.brighten.ac.utils.Tuple;
|
||||
import dev.brighten.ac.utils.reflections.impl.MinecraftReflection;
|
||||
import dev.brighten.ac.utils.reflections.types.WrappedMethod;
|
||||
import dev.brighten.ac.utils.timer.Timer;
|
||||
import dev.brighten.ac.utils.timer.impl.MillisTimer;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.val;
|
||||
@@ -52,6 +54,8 @@ public class APlayer {
|
||||
@Getter
|
||||
private int playerTick;
|
||||
@Getter
|
||||
private Timer creation = new MillisTimer();
|
||||
@Getter
|
||||
//TODO Actually grab real player version once finished implementing version grabber from Atlas
|
||||
private ProtocolVersion playerVersion = ProtocolVersion.UNKNOWN;
|
||||
@Getter
|
||||
@@ -87,9 +91,11 @@ public class APlayer {
|
||||
this.lagInfo = new LagInformation();
|
||||
this.blockInformation = new BlockInformation(this);
|
||||
|
||||
// Grabbing the protocol version of the player.
|
||||
Anticheat.INSTANCE.getScheduler().execute(() ->
|
||||
playerVersion = ProtocolVersion.getVersion(ProtocolAPI.INSTANCE.getPlayerVersion(getBukkitPlayer())));
|
||||
|
||||
// Enabling alerts for players on join if they have the permissions to
|
||||
if(getBukkitPlayer().hasPermission("anticheat.command.alerts")
|
||||
|| getBukkitPlayer().hasPermission("anticheat.alerts")) {
|
||||
Check.alertsEnabled.add(getUuid());
|
||||
|
||||
@@ -14,6 +14,7 @@ import dev.brighten.ac.utils.world.types.SimpleCollisionBox;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.val;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
|
||||
@@ -33,22 +34,35 @@ public class MovementHandler {
|
||||
private double deltaX, deltaY, deltaZ, deltaXZ,
|
||||
lDeltaX, lDeltaY, lDeltaZ, lDeltaXZ;
|
||||
@Getter
|
||||
private float deltaYaw, deltaPitch, lDeltaYaw, lDeltaPitch;
|
||||
private float lookX, lookY, lastLookX, lastLookY;
|
||||
@Getter
|
||||
private float deltaYaw, deltaPitch, lDeltaYaw, lDeltaPitch, smoothYaw, smoothPitch, lsmoothYaw, lsmoothPitch, pitchGCD, lastPitchGCD, yawGCD, lastYawGCD;
|
||||
private int moveTicks;
|
||||
private final List<KLocation> posLocs = new ArrayList<>();
|
||||
@Getter
|
||||
private boolean checkMovement;
|
||||
private boolean checkMovement, accurateYawData, cinematicMode;
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean excuseNextFlying;
|
||||
|
||||
@Getter
|
||||
private final Timer lastTeleport = new TickTimer();
|
||||
private final Timer lastTeleport = new TickTimer(), lastHighRate = new TickTimer();
|
||||
|
||||
private int teleportsToConfirm;
|
||||
|
||||
@Getter
|
||||
private final LinkedList<Float> yawGcdList = new EvictingList<>(45),
|
||||
pitchGcdList = new EvictingList<>(45);
|
||||
@Getter
|
||||
private float sensitivityX, sensitivityY, currentSensX, currentSensY, sensitivityMcp, yawMode, pitchMode;
|
||||
@Getter
|
||||
private int sensXPercent, sensYPercent;
|
||||
@Getter
|
||||
private TickTimer lastCinematic = new TickTimer(2);
|
||||
private MouseFilter mxaxis = new MouseFilter(), myaxis = new MouseFilter();
|
||||
private float smoothCamFilterX, smoothCamFilterY, smoothCamYaw, smoothCamPitch;
|
||||
private Timer lastReset = new TickTimer(2), generalProcess = new TickTimer(3);
|
||||
private final EvictingList<Integer> sensitivitySamples = new EvictingList<>(50);
|
||||
|
||||
|
||||
public void process(WPacketPlayInFlying packet, long currentTime) {
|
||||
@@ -82,6 +96,132 @@ public class MovementHandler {
|
||||
|
||||
checkForTeleports(packet);
|
||||
|
||||
if (packet.isLooked()) {
|
||||
float deltaYaw = Math.abs(this.deltaYaw), lastDeltaYaw = Math.abs(this.lDeltaYaw);
|
||||
final double differenceYaw = Math.abs(this.deltaYaw - lastDeltaYaw);
|
||||
final double differencePitch = Math.abs(this.deltaPitch - this.lDeltaPitch);
|
||||
|
||||
final double joltYaw = Math.abs(differenceYaw - deltaYaw);
|
||||
final double joltPitch = Math.abs(differencePitch - this.deltaPitch);
|
||||
|
||||
final float yawThreshold = Math.max(1.0f, deltaYaw / 2f),
|
||||
pitchThreshold = Math.max(1.f, Math.abs(this.deltaPitch) / 2f);
|
||||
|
||||
if (joltYaw > yawThreshold && joltPitch > pitchThreshold) this.lastHighRate.reset();
|
||||
this.lastPitchGCD = this.pitchGCD;
|
||||
this.lastYawGCD = this.yawGCD;
|
||||
this.yawGCD = MathUtils
|
||||
.gcdSmall(this.deltaYaw, this.lDeltaYaw);
|
||||
this.pitchGCD = MathUtils
|
||||
.gcdSmall(this.deltaPitch, this.lDeltaPitch);
|
||||
|
||||
val origin = this.to.getLoc().clone();
|
||||
|
||||
origin.y+= player.getInfo().isSneaking() ? 1.54 : 1.62;
|
||||
|
||||
if(lastTeleport.isPassed(1)) {
|
||||
predictionHandling:
|
||||
{
|
||||
float yawGcd = this.yawGCD,
|
||||
pitchGcd = this.pitchGCD;
|
||||
|
||||
//Adding gcd of yaw and pitch.
|
||||
if (this.yawGCD > 0.01 && this.yawGCD < 1.2) {
|
||||
yawGcdList.add(yawGcd);
|
||||
}
|
||||
if (this.pitchGCD > 0.01 && this.pitchGCD < 1.2)
|
||||
pitchGcdList.add(pitchGcd);
|
||||
|
||||
if(yawGcdList.size() < 20 || pitchGcdList.size() < 20) {
|
||||
accurateYawData = false;
|
||||
break predictionHandling;
|
||||
}
|
||||
|
||||
accurateYawData = true;
|
||||
|
||||
//Making sure to get shit within the std for a more accurate result.
|
||||
|
||||
//Making sure to get shit within the std for a more accurate result.
|
||||
currentSensX = getSensitivityFromYawGCD(yawGcd);
|
||||
currentSensY = getSensitivityFromPitchGCD(pitchGcd);
|
||||
if (lastReset.isPassed()) {
|
||||
pitchMode = MathUtils.getMode(pitchGcdList);
|
||||
lastReset.reset();
|
||||
sensXPercent = sensToPercent(sensitivityX = getSensitivityFromYawGCD(yawMode));
|
||||
sensYPercent = sensToPercent(sensitivityY = getSensitivityFromPitchGCD(pitchMode));
|
||||
|
||||
table: {
|
||||
sensitivitySamples.add(Math.max(sensXPercent, sensYPercent));
|
||||
|
||||
if (sensitivitySamples.size() > 30) {
|
||||
final long mode = MathUtils.getMode(sensitivitySamples);
|
||||
|
||||
sensitivityMcp = AimbotUtil.SENSITIVITY_MAP.getOrDefault((int) mode, -1.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lastLookX = lookX;
|
||||
lastLookY = lookY;
|
||||
lookX = getExpiermentalDeltaX(player);
|
||||
lookY = getExpiermentalDeltaY(player);
|
||||
|
||||
if ((this.pitchGCD < 0.006 && this.yawGCD < 0.006) && smoothCamFilterY < 1E6
|
||||
&& smoothCamFilterX < 1E6 && player.getCreation().isPassed(1000L)) {
|
||||
float sens = MovementHandler.percentToSens(95);
|
||||
float f = sens * 0.6f + .2f;
|
||||
float f1 = f * f * f * 8;
|
||||
float f2 = lookX * f1;
|
||||
float f3 = lookY * f1;
|
||||
|
||||
smoothCamFilterX = mxaxis.smooth(smoothCamYaw, .05f * f1);
|
||||
smoothCamFilterY = myaxis.smooth(smoothCamPitch, .05f * f1);
|
||||
|
||||
this.smoothCamYaw += f2;
|
||||
this.smoothCamPitch += f3;
|
||||
|
||||
f2 = smoothCamFilterX * 0.5f;
|
||||
f3 = smoothCamFilterY * 0.5f;
|
||||
|
||||
//val clampedFrom = (Math.abs(this.from.yaw) > 360 ? this.from.yaw % 360 : this.from.yaw);
|
||||
val clampedFrom = MathUtils.yawTo180F(getFrom().getLoc().yaw);
|
||||
float pyaw = clampedFrom + f2 * .15f;
|
||||
float ppitch = this.getFrom().getLoc().pitch - f3 * .15f;
|
||||
|
||||
this.lsmoothYaw = smoothYaw;
|
||||
this.lsmoothPitch = smoothPitch;
|
||||
this.smoothYaw = pyaw;
|
||||
this.smoothPitch = ppitch;
|
||||
|
||||
float yaccel = Math.abs(this.deltaYaw) - Math.abs(this.lDeltaYaw),
|
||||
pAccel = Math.abs(this.deltaPitch) - Math.abs(this.lDeltaPitch);
|
||||
|
||||
if (MathUtils.getDelta(smoothYaw, clampedFrom) > (yaccel > 0 ? (yaccel > 10 ? 2.5 : 1) : 0.3)
|
||||
|| MathUtils.getDelta(smoothPitch, this.getFrom().getLoc().pitch)
|
||||
> (pAccel > 0 ? (pAccel > 10 ? 2.5 : 1) : 0.3)) {
|
||||
smoothCamYaw = smoothCamPitch = 0;
|
||||
this.cinematicMode = false;
|
||||
mxaxis.reset();
|
||||
myaxis.reset();
|
||||
} else this.cinematicMode = true;
|
||||
} else {
|
||||
mxaxis.reset();
|
||||
myaxis.reset();
|
||||
this.cinematicMode = false;
|
||||
}
|
||||
|
||||
lastLookX = lookX;
|
||||
lastLookY = lookY;
|
||||
lookX = getExpiermentalDeltaX(player);
|
||||
lookY = getExpiermentalDeltaY(player);
|
||||
}
|
||||
} else {
|
||||
yawGcdList.clear();
|
||||
pitchGcdList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
player.getInfo().setCreative(player.getBukkitPlayer().getGameMode() == GameMode.CREATIVE
|
||||
|| player.getBukkitPlayer().getGameMode() == GameMode.SPECTATOR);
|
||||
|
||||
@@ -103,27 +243,104 @@ public class MovementHandler {
|
||||
|
||||
/*
|
||||
ata.playerInfo.generalCancel = data.getPlayer().getAllowFlight()
|
||||
|| data.playerInfo.creative
|
||||
|| this.creative
|
||||
|| hasLevi
|
||||
|| data.excuseNextFlying
|
||||
|| data.getPlayer().isSleeping()
|
||||
|| (data.playerInfo.lastGhostCollision.isNotPassed() && data.playerInfo.lastBlockPlace.isPassed(2))
|
||||
|| data.playerInfo.doingTeleport
|
||||
|| data.playerInfo.lastTeleportTimer.isNotPassed(1)
|
||||
|| data.playerInfo.riptiding
|
||||
|| data.playerInfo.gliding
|
||||
|| data.playerInfo.vehicleTimer.isNotPassed(3)
|
||||
|| data.playerInfo.lastPlaceLiquid.isNotPassed(5)
|
||||
|| data.playerInfo.inVehicle
|
||||
|| ((data.playerInfo.lastChunkUnloaded.isNotPassed(35) || data.playerInfo.doingBlockUpdate)
|
||||
&& MathUtils.getDelta(-0.098, data.playerInfo.deltaY) < 0.0001)
|
||||
|| timeStamp - data.playerInfo.lastRespawn < 2500L
|
||||
|| data.playerInfo.lastToggleFlight.isNotPassed(40)
|
||||
|| (this.lastGhostCollision.isNotPassed() && this.lastBlockPlace.isPassed(2))
|
||||
|| this.doingTeleport
|
||||
|| this.lastTeleportTimer.isNotPassed(1)
|
||||
|| this.riptiding
|
||||
|| this.gliding
|
||||
|| this.vehicleTimer.isNotPassed(3)
|
||||
|| this.lastPlaceLiquid.isNotPassed(5)
|
||||
|| this.inVehicle
|
||||
|| ((this.lastChunkUnloaded.isNotPassed(35) || this.doingBlockUpdate)
|
||||
&& MathUtils.getDelta(-0.098, this.deltaY) < 0.0001)
|
||||
|| timeStamp - this.lastRespawn < 2500L
|
||||
|| this.lastToggleFlight.isNotPassed(40)
|
||||
|| timeStamp - data.creation < 4000
|
||||
|| Kauri.INSTANCE.lastTickLag.isNotPassed(5);
|
||||
*/
|
||||
}
|
||||
|
||||
private static float getDeltaX(float yawDelta, float gcd) {
|
||||
return MathHelper.ceiling_float_int(yawDelta / gcd);
|
||||
}
|
||||
|
||||
private static float getDeltaY(float pitchDelta, float gcd) {
|
||||
return MathHelper.ceiling_float_int(pitchDelta / gcd);
|
||||
}
|
||||
|
||||
public static float getExpiermentalDeltaX(APlayer data) {
|
||||
float deltaPitch = data.getMovement().getDeltaYaw();
|
||||
float sens = data.getMovement().sensitivityX;
|
||||
float f = sens * 0.6f + .2f;
|
||||
float calc = f * f * f * 8;
|
||||
|
||||
float result = deltaPitch / (calc * .15f);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static float getExpiermentalDeltaY(APlayer data) {
|
||||
float deltaPitch = data.getMovement().getDeltaPitch();
|
||||
float sens = data.getMovement().sensitivityY;
|
||||
float f = sens * 0.6f + .2f;
|
||||
float calc = f * f * f * 8;
|
||||
|
||||
float result = deltaPitch / (calc * .15f);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int sensToPercent(float sensitivity) {
|
||||
return MathHelper.floor_float(sensitivity / .5f * 100);
|
||||
}
|
||||
|
||||
public static float percentToSens(int percent) {
|
||||
return percent * .0070422534f;
|
||||
}
|
||||
|
||||
public static float getSensitivityFromYawGCD(float gcd) {
|
||||
return ((float) Math.cbrt(yawToF2(gcd) / 8f) - .2f) / .6f;
|
||||
}
|
||||
|
||||
private static float getSensitivityFromPitchGCD(float gcd) {
|
||||
return ((float)Math.cbrt(pitchToF3(gcd) / 8f) - .2f) / .6f;
|
||||
}
|
||||
|
||||
private static float getF1FromYaw(float gcd) {
|
||||
float f = getFFromYaw(gcd);
|
||||
|
||||
return f * f * f * 8;
|
||||
}
|
||||
|
||||
private static float getFFromYaw(float gcd) {
|
||||
float sens = getSensitivityFromYawGCD(gcd);
|
||||
return sens * .6f + .2f;
|
||||
}
|
||||
|
||||
private static float getFFromPitch(float gcd) {
|
||||
float sens = getSensitivityFromPitchGCD(gcd);
|
||||
return sens * .6f + .2f;
|
||||
}
|
||||
|
||||
private static float getF1FromPitch(float gcd) {
|
||||
float f = getFFromPitch(gcd);
|
||||
|
||||
return (float)Math.pow(f, 3) * 8;
|
||||
}
|
||||
|
||||
private static float yawToF2(float yawDelta) {
|
||||
return yawDelta / .15f;
|
||||
}
|
||||
|
||||
private static float pitchToF3(float pitchDelta) {
|
||||
int b0 = pitchDelta >= 0 ? 1 : -1; //Checking for inverted mouse.
|
||||
return (pitchDelta / b0) / .15f;
|
||||
}
|
||||
|
||||
public void addPosition(WPacketPlayOutPosition packet) {
|
||||
int i = 0;
|
||||
KLocation loc = new KLocation(packet.getX(), packet.getY(), packet.getZ(),
|
||||
@@ -241,7 +458,7 @@ public class MovementHandler {
|
||||
deltaY = to.getLoc().y - from.getLoc().y;
|
||||
deltaZ = to.getLoc().z - from.getLoc().z;
|
||||
deltaXZ = Math.hypot(deltaX, deltaZ); // Calculating here to cache since hypot() can be heavy.
|
||||
deltaYaw = MathUtils.getAngleDelta(to.getLoc().yaw, from.getLoc().yaw);
|
||||
deltaYaw = to.getLoc().yaw - from.getLoc().yaw;
|
||||
deltaPitch = to.getLoc().pitch - from.getLoc().pitch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import dev.brighten.ac.handler.thread.ThreadHandler;
|
||||
import dev.brighten.ac.packet.handler.HandlerAbstract;
|
||||
import dev.brighten.ac.packet.wrapper.PacketType;
|
||||
import dev.brighten.ac.utils.Init;
|
||||
import dev.brighten.ac.utils.RunUtils;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -74,10 +75,9 @@ public class JoinListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
System.out.println("Generating for " + event.getPlayer().getName());
|
||||
APlayer player = Anticheat.INSTANCE.getPlayerRegistry().generate(event.getPlayer());
|
||||
|
||||
HandlerAbstract.getHandler().add(event.getPlayer());
|
||||
RunUtils.taskLater(() -> HandlerAbstract.getHandler().add(event.getPlayer()), 3);
|
||||
|
||||
player.callEvent(event);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
package dev.brighten.ac.utils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AimbotUtil {
|
||||
public static final Map<Integer, Float> SENSITIVITY_MAP = Maps.newHashMap();
|
||||
|
||||
static {
|
||||
SENSITIVITY_MAP.put(1, 0.0070422534F);
|
||||
SENSITIVITY_MAP.put(2, 0.014084507F);
|
||||
SENSITIVITY_MAP.put(4, 0.02112676F);
|
||||
SENSITIVITY_MAP.put(5, 0.028169014F);
|
||||
SENSITIVITY_MAP.put(6, 0.0281690166F);
|
||||
SENSITIVITY_MAP.put(7, 0.03521127F);
|
||||
SENSITIVITY_MAP.put(8, 0.04225352F);
|
||||
SENSITIVITY_MAP.put(9, 0.049295776F);
|
||||
SENSITIVITY_MAP.put(10, 0.0492957736F);
|
||||
SENSITIVITY_MAP.put(11, 0.056338027F);
|
||||
SENSITIVITY_MAP.put(12, 0.06338028F);
|
||||
SENSITIVITY_MAP.put(14, 0.07042254F);
|
||||
SENSITIVITY_MAP.put(15, 0.07746479F);
|
||||
SENSITIVITY_MAP.put(16, 0.08450704F);
|
||||
SENSITIVITY_MAP.put(18, 0.09154929F);
|
||||
SENSITIVITY_MAP.put(19, 0.09859155F);
|
||||
SENSITIVITY_MAP.put(21, 0.1056338F);
|
||||
SENSITIVITY_MAP.put(22, 0.112676054F);
|
||||
SENSITIVITY_MAP.put(23, 0.11971831F);
|
||||
SENSITIVITY_MAP.put(25, 0.12676056F);
|
||||
SENSITIVITY_MAP.put(26, 0.13380282F);
|
||||
SENSITIVITY_MAP.put(28, 0.14084508F);
|
||||
SENSITIVITY_MAP.put(29, 0.14788732F);
|
||||
SENSITIVITY_MAP.put(30, 0.15492958F);
|
||||
SENSITIVITY_MAP.put(32, 0.16197184F);
|
||||
SENSITIVITY_MAP.put(33, 0.16901408F);
|
||||
SENSITIVITY_MAP.put(35, 0.17605634F);
|
||||
SENSITIVITY_MAP.put(36, 0.18309858F);
|
||||
SENSITIVITY_MAP.put(38, 0.19014084F);
|
||||
SENSITIVITY_MAP.put(39, 0.1971831F);
|
||||
SENSITIVITY_MAP.put(40, 0.20422535F);
|
||||
SENSITIVITY_MAP.put(42, 0.2112676F);
|
||||
SENSITIVITY_MAP.put(43, 0.21830986F);
|
||||
SENSITIVITY_MAP.put(45, 0.22535211F);
|
||||
SENSITIVITY_MAP.put(46, 0.23239437F);
|
||||
SENSITIVITY_MAP.put(47, 0.23943663F);
|
||||
SENSITIVITY_MAP.put(49, 0.24647887F);
|
||||
SENSITIVITY_MAP.put(50, 0.2535211F);
|
||||
SENSITIVITY_MAP.put(52, 0.26056337F);
|
||||
SENSITIVITY_MAP.put(53, 0.26760563F);
|
||||
SENSITIVITY_MAP.put(54, 0.2746479F);
|
||||
SENSITIVITY_MAP.put(56, 0.28169015F);
|
||||
SENSITIVITY_MAP.put(57, 0.28873238F);
|
||||
SENSITIVITY_MAP.put(59, 0.29577464F);
|
||||
SENSITIVITY_MAP.put(60, 0.3028169F);
|
||||
SENSITIVITY_MAP.put(61, 0.30985916F);
|
||||
SENSITIVITY_MAP.put(63, 0.31690142F);
|
||||
SENSITIVITY_MAP.put(64, 0.32394367F);
|
||||
SENSITIVITY_MAP.put(66, 0.3309859F);
|
||||
SENSITIVITY_MAP.put(67, 0.33802816F);
|
||||
SENSITIVITY_MAP.put(68, 0.34507042F);
|
||||
SENSITIVITY_MAP.put(70, 0.35211268F);
|
||||
SENSITIVITY_MAP.put(71, 0.35915494F);
|
||||
SENSITIVITY_MAP.put(73, 0.36619717F);
|
||||
SENSITIVITY_MAP.put(74, 0.37323943F);
|
||||
SENSITIVITY_MAP.put(76, 0.3802817F);
|
||||
SENSITIVITY_MAP.put(77, 0.38732395F);
|
||||
SENSITIVITY_MAP.put(78, 0.3943662F);
|
||||
SENSITIVITY_MAP.put(80, 0.40140846F);
|
||||
SENSITIVITY_MAP.put(81, 0.4084507F);
|
||||
SENSITIVITY_MAP.put(83, 0.41549295F);
|
||||
SENSITIVITY_MAP.put(84, 0.4225352F);
|
||||
SENSITIVITY_MAP.put(85, 0.42957747F);
|
||||
SENSITIVITY_MAP.put(87, 0.43661973F);
|
||||
SENSITIVITY_MAP.put(88, 0.44366196F);
|
||||
SENSITIVITY_MAP.put(90, 0.45070422F);
|
||||
SENSITIVITY_MAP.put(91, 0.45774648F);
|
||||
SENSITIVITY_MAP.put(92, 0.46478873F);
|
||||
SENSITIVITY_MAP.put(94, 0.471831F);
|
||||
SENSITIVITY_MAP.put(95, 0.47887325F);
|
||||
SENSITIVITY_MAP.put(97, 0.48591548F);
|
||||
SENSITIVITY_MAP.put(98, 0.49295774F);
|
||||
SENSITIVITY_MAP.put(100, 0.5F);
|
||||
SENSITIVITY_MAP.put(101, 0.5070422F);
|
||||
SENSITIVITY_MAP.put(102, 0.5140845F);
|
||||
SENSITIVITY_MAP.put(104, 0.52112675F);
|
||||
SENSITIVITY_MAP.put(105, 0.52816904F);
|
||||
SENSITIVITY_MAP.put(107, 0.53521127F);
|
||||
SENSITIVITY_MAP.put(108, 0.5422535F);
|
||||
SENSITIVITY_MAP.put(109, 0.5492958F);
|
||||
SENSITIVITY_MAP.put(111, 0.556338F);
|
||||
SENSITIVITY_MAP.put(112, 0.5633803F);
|
||||
SENSITIVITY_MAP.put(114, 0.57042253F);
|
||||
SENSITIVITY_MAP.put(115, 0.57746476F);
|
||||
SENSITIVITY_MAP.put(116, 0.58450705F);
|
||||
SENSITIVITY_MAP.put(118, 0.5915493F);
|
||||
SENSITIVITY_MAP.put(119, 0.59859157F);
|
||||
SENSITIVITY_MAP.put(121, 0.6056338F);
|
||||
SENSITIVITY_MAP.put(122, 0.6126761F);
|
||||
SENSITIVITY_MAP.put(123, 0.6197183F);
|
||||
SENSITIVITY_MAP.put(125, 0.62676054F);
|
||||
SENSITIVITY_MAP.put(126, 0.63380283F);
|
||||
SENSITIVITY_MAP.put(128, 0.64084506F);
|
||||
SENSITIVITY_MAP.put(129, 0.647887350F);
|
||||
SENSITIVITY_MAP.put(130, 0.6549296F);
|
||||
SENSITIVITY_MAP.put(132, 0.6619718F);
|
||||
SENSITIVITY_MAP.put(133, 0.6690141F);
|
||||
SENSITIVITY_MAP.put(135, 0.6760563F);
|
||||
SENSITIVITY_MAP.put(136, 0.6830986F);
|
||||
SENSITIVITY_MAP.put(138, 0.69014084F);
|
||||
SENSITIVITY_MAP.put(139, 0.6971831F);
|
||||
SENSITIVITY_MAP.put(140, 0.70422536F);
|
||||
SENSITIVITY_MAP.put(142, 0.7112676F);
|
||||
SENSITIVITY_MAP.put(143, 0.7183099F);
|
||||
SENSITIVITY_MAP.put(145, 0.7253521F);
|
||||
SENSITIVITY_MAP.put(146, 0.73239434F);
|
||||
SENSITIVITY_MAP.put(147, 0.7394366F);
|
||||
SENSITIVITY_MAP.put(149, 0.74647886F);
|
||||
SENSITIVITY_MAP.put(150, 0.75352114F);
|
||||
SENSITIVITY_MAP.put(152, 0.7605634F);
|
||||
SENSITIVITY_MAP.put(153, 0.76760566F);
|
||||
SENSITIVITY_MAP.put(154, 0.7746479F);
|
||||
SENSITIVITY_MAP.put(156, 0.7816901F);
|
||||
SENSITIVITY_MAP.put(157, 0.7887324F);
|
||||
SENSITIVITY_MAP.put(159, 0.79577464F);
|
||||
SENSITIVITY_MAP.put(160, 0.8028169F);
|
||||
SENSITIVITY_MAP.put(161, 0.80985916F);
|
||||
SENSITIVITY_MAP.put(163, 0.8169014F);
|
||||
SENSITIVITY_MAP.put(164, 0.8239437F);
|
||||
SENSITIVITY_MAP.put(166, 0.8309859F);
|
||||
SENSITIVITY_MAP.put(167, 0.8380282F);
|
||||
SENSITIVITY_MAP.put(169, 0.8450704F);
|
||||
SENSITIVITY_MAP.put(170, 0.85211265F);
|
||||
SENSITIVITY_MAP.put(171, 0.85915494F);
|
||||
SENSITIVITY_MAP.put(173, 0.86619717F);
|
||||
SENSITIVITY_MAP.put(174, 0.87323946F);
|
||||
SENSITIVITY_MAP.put(176, 0.8802817F);
|
||||
SENSITIVITY_MAP.put(177, 0.8873239F);
|
||||
SENSITIVITY_MAP.put(178, 0.8943662F);
|
||||
SENSITIVITY_MAP.put(180, 0.90140843F);
|
||||
SENSITIVITY_MAP.put(181, 0.9084507F);
|
||||
SENSITIVITY_MAP.put(183, 0.91549295F);
|
||||
SENSITIVITY_MAP.put(184, 0.92253524F);
|
||||
SENSITIVITY_MAP.put(185, 0.92957747F);
|
||||
SENSITIVITY_MAP.put(187, 0.9366197F);
|
||||
SENSITIVITY_MAP.put(188, 0.943662F);
|
||||
SENSITIVITY_MAP.put(190, 0.9507042F);
|
||||
SENSITIVITY_MAP.put(191, 0.9577465F);
|
||||
SENSITIVITY_MAP.put(192, 0.96478873F);
|
||||
SENSITIVITY_MAP.put(194, 0.97183096F);
|
||||
SENSITIVITY_MAP.put(195, 0.97887325F);
|
||||
SENSITIVITY_MAP.put(197, 0.9859155F);
|
||||
SENSITIVITY_MAP.put(198, 0.9929578F);
|
||||
SENSITIVITY_MAP.put(200, 1.0F);
|
||||
}
|
||||
|
||||
/*
|
||||
* Essentially what we're doing here is enclosing the possible rotations the player
|
||||
* would've made in a normal framed scenario. The problem with the method we're using
|
||||
* to verify the player's rotation (prediction) has one problem. And that problem is
|
||||
* frame updates or as the client calls them "partial ticks". With this method, we can
|
||||
* directly check if the player's rotation was in the range of possible values, and we can
|
||||
* get that range with a simple limit check by complying the prediction vs the rotation
|
||||
*
|
||||
* Essentially it accounts for rotations that couldn't have been accurately predicted because
|
||||
* they got updated many times outside of the tick.
|
||||
*/
|
||||
public static boolean enclosed(final float a, final float b, final float x) {
|
||||
final float distance = MathUtils.getAngleDelta(a, b);
|
||||
|
||||
return MathUtils.getAngleDelta(a, x) < distance && MathUtils.getAngleDelta(b, x) < distance;
|
||||
}
|
||||
|
||||
/*
|
||||
* This gets the players sensitivity through reversing the GCD of the player which
|
||||
* is the constant of the past 40 rotations, directly using minecraft math to achieve that.
|
||||
*
|
||||
* // Update the mouse position per frame
|
||||
* this.mouseHelper.updateXY();
|
||||
*
|
||||
* // Grab the new deltaXY mouse values made
|
||||
* final int deltaX = mouseHelper.getX();
|
||||
* final int deltaY = mouseHelper.getY();
|
||||
*
|
||||
* // Run the sensitivity formula to construct the new yaw/pitch values
|
||||
* final float sensitivityFormat = (float) sensitivity * 0.6F + 0.2F;
|
||||
* final float sensitivityProduct = var132 * var132 * var132 * 8.0F;
|
||||
*
|
||||
* // Run the construction formula for yaw and pitch
|
||||
* final float constructedYaw = (float) deltaX * sensitivityFormat;
|
||||
* final float constructedPitch = (float) deltaY * sensitivityFormat
|
||||
*
|
||||
* // Account for rotation slowdown using the 0.15 as a constant number
|
||||
* final float productYaw = Math.abs(rotationYaw) + constructedYaw * 0.15
|
||||
* final float productPitch = Math.abs(rotationPitch) + constructedPitch * 0.15
|
||||
*
|
||||
* // Account for inversion
|
||||
* this.rotationYaw = productYaw * inverseYaw;
|
||||
* this.rotationPitch = productPitch * inversePitch;
|
||||
*/
|
||||
public static float getSensitivityFromYawGCD(float gcd) {
|
||||
return ((float)Math.cbrt(yawToF2(gcd) / 8f) - .2f) / .6f;
|
||||
}
|
||||
|
||||
public static float getSensitivityFromPitchGCD(float gcd) {
|
||||
return ((float)Math.cbrt(pitchToF3(gcd) / 8f) - .2f) / .6f;
|
||||
}
|
||||
|
||||
private static float yawToF2(float yawDelta) {
|
||||
return yawDelta / .15f;
|
||||
}
|
||||
|
||||
private static float pitchToF3(float pitchDelta) {
|
||||
int b0 = pitchDelta >= 0 ? 1 : -1; //Checking for inverted mouse.
|
||||
return (pitchDelta / b0) / .15f;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is going to be used for our inverse method for rotations since the method we are currently
|
||||
* using for inverse is a little inaccurate in some places due to the implementation of the game.
|
||||
* This should have a non existent margin of error since it will always be accurate to the degree.
|
||||
*/
|
||||
private static float toRegularCircle(float angles) {
|
||||
angles %= 360.F;
|
||||
return angles < 0 ? angles + 360.F : angles;
|
||||
}
|
||||
|
||||
public static int getInverseValue(final float current, final float previous, final InverseType type) {
|
||||
int result;
|
||||
/*
|
||||
* We're not using a switch statement since there is only really two possibilities so a switch
|
||||
* statement for my taste would simply be a little messy.
|
||||
*/
|
||||
if (type == InverseType.YAW) {
|
||||
final float distance = MathUtils.getAngleDelta(previous, current);
|
||||
|
||||
/*
|
||||
* We're expressing it as a regular circle since there is not a real limit to how the yaw is expressed
|
||||
* meaning if we do the check dynamically like we used to, it will fail when resetting or on larger rotations
|
||||
*/
|
||||
final float circleX = toRegularCircle(previous);
|
||||
final float circleY = toRegularCircle(current);
|
||||
|
||||
/*
|
||||
* This is the valid expression to the delta which is going to help us understand the direction
|
||||
* the player moved their head to get a more valid reading on their inversion.
|
||||
*/
|
||||
final double polarX = MathUtils.getAngleDelta(circleX + distance, circleY);
|
||||
final double polarY = MathUtils.getAngleDelta(circleX - distance, circleY);
|
||||
|
||||
/*
|
||||
* This is simply printing the result regularly without any issues. Simply, if a direction change,
|
||||
* print -1 which will change the "way" we're running the prediction, otherwise print 1 which wont change it
|
||||
*/
|
||||
result = polarX < polarY ? 1 : -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the check for the pitch variant of this. This has to be checked differently since one is
|
||||
* a vertical value when the other one is an expression of a circle. They are not the same.
|
||||
*/
|
||||
else {
|
||||
final float distance = current - previous;
|
||||
|
||||
/*
|
||||
* The check for pitch is the same we did before. It is much simpler since the rotation does not need
|
||||
* to be expressed in the likes of a circle since it is clamped normally from the client. So the method is
|
||||
* not as complex in comparison to the one we have for yaw. However, this should work fine as mentioned.
|
||||
*/
|
||||
result = distance > 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is an attempt to reverse the logistics of cinematic camera without having to run a full on prediction using
|
||||
* mouse filters. Otherwise, we would need to run more heavy calculations which is not really production friendly.
|
||||
* It may be more accurate but it is not really worth it if in the end of the day we're eating server performance.
|
||||
*/
|
||||
public static double getGrid(final List<Float> entry) {
|
||||
/*
|
||||
* We're creating the variables average min and max to start calculating the possibility of cinematic camera.
|
||||
* Why does this work? Cinematic camera is essentially a slowly increasing slowdown (which is why cinematic camera
|
||||
* becomes slower the more you use it) which in turn makes it so the min max and average are extremely close together.
|
||||
*/
|
||||
double average = 0.0;
|
||||
double min = 0.0, max = 0.0;
|
||||
|
||||
/*
|
||||
* These are simple min max calculations done manually for the sake of simplicity. We're using the numbers 0.0
|
||||
* since we also want to account for the possibility of a negative number. If there are no negative numbers then
|
||||
* there is absolutely no need for us to care about that number other than getting the max.
|
||||
*/
|
||||
for (final double number : entry) {
|
||||
if (number < min) min = number;
|
||||
if (number > max) max = number;
|
||||
|
||||
/*
|
||||
* Instead of having a sum variable we can use an average variable which we divide
|
||||
* right after the loop is over. Smart programming trick if you want to use it.
|
||||
*/
|
||||
average += number;
|
||||
}
|
||||
|
||||
/*
|
||||
* We're dividing the average by the length since this is the formula to getting the average.
|
||||
* Specifically its (sum(n) / length(n)) = average(n) -- with n being the entry set we're analyzing.
|
||||
*/
|
||||
average /= entry.size();
|
||||
|
||||
/*
|
||||
* This is going to estimate how close the average and the max were together with the possibility of a min
|
||||
* variable which is going to represent a negative variable since the preset variable on min is 0.0.
|
||||
*/
|
||||
return (max - average) - min;
|
||||
}
|
||||
|
||||
public enum InverseType {
|
||||
YAW,
|
||||
PITCH
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,13 @@ public class MathUtils {
|
||||
return playerMoved(from.toVector(), to.toVector());
|
||||
}
|
||||
|
||||
public static float gcdSmall(float current, float previous) {
|
||||
if(current < previous) return gcdSmall(Math.abs(previous), Math.abs(current));
|
||||
//The larger number has to be first.
|
||||
return (Math.abs(previous) <= 0.001f) ? current : gcdSmall(previous,
|
||||
current - (float)Math.floor(current / previous) * previous);
|
||||
}
|
||||
|
||||
public static double getDistanceWithoutRoot(KLocation one, KLocation two) {
|
||||
double deltaX = one.x - two.x, deltaY = one.y - two.y, deltaZ = one.z - two.z;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ public class TickTimer implements Timer {
|
||||
|
||||
public TickTimer(long defaultPassed) {
|
||||
this.defaultPassed = defaultPassed;
|
||||
currentStamp = Anticheat.INSTANCE.getCurrentTick();
|
||||
currentStamp = Anticheat.INSTANCE.getKeepaliveProcessor().tick;
|
||||
}
|
||||
|
||||
public TickTimer() {
|
||||
@@ -49,7 +49,7 @@ public class TickTimer implements Timer {
|
||||
|
||||
@Override
|
||||
public long getPassed() {
|
||||
return Anticheat.INSTANCE.getCurrentTick()- currentStamp;
|
||||
return Anticheat.INSTANCE.getKeepaliveProcessor().tick - currentStamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,6 +62,6 @@ public class TickTimer implements Timer {
|
||||
if(getPassed() <= 1) resetStreak++;
|
||||
else resetStreak = 0;
|
||||
|
||||
currentStamp = Anticheat.INSTANCE.getCurrentTick();
|
||||
currentStamp = Anticheat.INSTANCE.getKeepaliveProcessor().tick;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user