combat folia anti-cheat
This commit is contained in:
@@ -37,10 +37,12 @@ import net.minecraft.world.inventory.Slot;
|
|||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.item.trading.MerchantOffer;
|
import net.minecraft.world.item.trading.MerchantOffer;
|
||||||
import net.minecraft.world.item.trading.MerchantOffers;
|
import net.minecraft.world.item.trading.MerchantOffers;
|
||||||
|
import net.minecraft.world.level.ClipContext;
|
||||||
import net.minecraft.world.level.block.Blocks;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.phys.AABB;
|
import net.minecraft.world.phys.AABB;
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
import net.minecraft.world.phys.EntityHitResult;
|
import net.minecraft.world.phys.EntityHitResult;
|
||||||
|
import net.minecraft.world.phys.HitResult;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
final class AutoTradeClientTick {
|
final class AutoTradeClientTick {
|
||||||
@@ -68,6 +70,16 @@ final class AutoTradeClientTick {
|
|||||||
private int outputContainerHighlightTicks = 0;
|
private int outputContainerHighlightTicks = 0;
|
||||||
private int postMerchantInventorySyncTicks = 0;
|
private int postMerchantInventorySyncTicks = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity we have already snapped the camera onto and are waiting for line of
|
||||||
|
* sight to clear on. Prevents re-rotating to the same villager every tick
|
||||||
|
* while a block briefly obstructs the view; reset once we interact (or after
|
||||||
|
* a timeout) so the next villager can be acquired.
|
||||||
|
*/
|
||||||
|
private int rotatingTargetId = -1;
|
||||||
|
private int rotatingTargetTicks = 0;
|
||||||
|
private static final int LOS_TIMEOUT_TICKS = 60;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity to draw in-world highlight for; {@code null} when inactive or unknown
|
* Entity to draw in-world highlight for; {@code null} when inactive or unknown
|
||||||
* id.
|
* id.
|
||||||
@@ -157,20 +169,38 @@ final class AutoTradeClientTick {
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
if (!newVillagersInRange.contains(entity)) {
|
if (!newVillagersInRange.contains(entity)) {
|
||||||
found = true;
|
found = true;
|
||||||
newVillagersInRange.add(entity);
|
|
||||||
// Paper/Folia rejects entity-interact packets unless the player is
|
// Paper/Folia rejects entity-interact packets unless the player is
|
||||||
// actually facing the entity, so align the look vector first and
|
// actually facing a visible part of the entity hitbox. Aim at any
|
||||||
// mirror vanilla's full INTERACT_AT + INTERACT + swing sequence.
|
// point we have a clean line of sight to (eyes, head, chest, feet);
|
||||||
|
// if none of them are visible yet, wait without flooding the camera
|
||||||
|
// with re-rotations every tick.
|
||||||
|
Vec3 aimPoint = firstVisiblePoint(mc, entity);
|
||||||
|
if (rotatingTargetId != entity.getId()) {
|
||||||
|
rotatingTargetId = entity.getId();
|
||||||
|
rotatingTargetTicks = 0;
|
||||||
|
Vec3 lookAt = aimPoint != null ? aimPoint : entity.getEyePosition();
|
||||||
Vec3 eyePos = mc.player.getEyePosition();
|
Vec3 eyePos = mc.player.getEyePosition();
|
||||||
Vec3 targetEye = entity.getEyePosition();
|
Vec3 delta = lookAt.subtract(eyePos);
|
||||||
Vec3 delta = targetEye.subtract(eyePos);
|
|
||||||
double horiz = Math.sqrt(delta.x * delta.x + delta.z * delta.z);
|
double horiz = Math.sqrt(delta.x * delta.x + delta.z * delta.z);
|
||||||
float yaw = (float) (Math.toDegrees(Math.atan2(delta.z, delta.x)) - 90.0);
|
float yaw = (float) (Math.toDegrees(Math.atan2(delta.z, delta.x)) - 90.0);
|
||||||
float pitch = (float) -Math.toDegrees(Math.atan2(delta.y, horiz));
|
float pitch = (float) -Math.toDegrees(Math.atan2(delta.y, horiz));
|
||||||
mc.player.setYRot(yaw);
|
mc.player.setYRot(yaw);
|
||||||
mc.player.setXRot(pitch);
|
mc.player.setXRot(pitch);
|
||||||
mc.player.yHeadRot = yaw;
|
mc.player.yHeadRot = yaw;
|
||||||
EntityHitResult ehr = new EntityHitResult(entity, targetEye);
|
} else {
|
||||||
|
rotatingTargetTicks++;
|
||||||
|
}
|
||||||
|
if (aimPoint == null) {
|
||||||
|
if (rotatingTargetTicks > LOS_TIMEOUT_TICKS) {
|
||||||
|
// Give up so the player can move past this villager.
|
||||||
|
newVillagersInRange.add(entity);
|
||||||
|
rotatingTargetId = -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
newVillagersInRange.add(entity);
|
||||||
|
rotatingTargetId = -1;
|
||||||
|
EntityHitResult ehr = new EntityHitResult(entity, aimPoint);
|
||||||
AutoTrade.autoInteracting = true;
|
AutoTrade.autoInteracting = true;
|
||||||
try {
|
try {
|
||||||
//? if mc26 {
|
//? if mc26 {
|
||||||
@@ -459,6 +489,38 @@ final class AutoTradeClientTick {
|
|||||||
traderGlowTicksRemaining = TRADER_HIGHLIGHT_TICKS;
|
traderGlowTicksRemaining = TRADER_HIGHLIGHT_TICKS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first point on {@code target}'s bounding box (eyes, head,
|
||||||
|
* chest, feet, or one of the four upper corners) that has an unobstructed
|
||||||
|
* block ray-cast from the player's eyes; {@code null} when the entire body
|
||||||
|
* is occluded.
|
||||||
|
*/
|
||||||
|
private static Vec3 firstVisiblePoint(Minecraft mc, Entity target) {
|
||||||
|
Vec3 eye = mc.player.getEyePosition();
|
||||||
|
AABB box = target.getBoundingBox();
|
||||||
|
double cx = (box.minX + box.maxX) * 0.5;
|
||||||
|
double cz = (box.minZ + box.maxZ) * 0.5;
|
||||||
|
Vec3[] candidates = {
|
||||||
|
target.getEyePosition(),
|
||||||
|
new Vec3(cx, (box.minY + box.maxY) * 0.5, cz),
|
||||||
|
new Vec3(cx, box.maxY - 0.05, cz),
|
||||||
|
new Vec3(cx, box.minY + 0.1, cz),
|
||||||
|
new Vec3(box.minX + 0.05, box.maxY - 0.2, box.minZ + 0.05),
|
||||||
|
new Vec3(box.maxX - 0.05, box.maxY - 0.2, box.minZ + 0.05),
|
||||||
|
new Vec3(box.minX + 0.05, box.maxY - 0.2, box.maxZ - 0.05),
|
||||||
|
new Vec3(box.maxX - 0.05, box.maxY - 0.2, box.maxZ - 0.05),
|
||||||
|
};
|
||||||
|
for (Vec3 p : candidates) {
|
||||||
|
BlockHitResult hit = mc.level.clip(new ClipContext(eye, p, ClipContext.Block.COLLIDER,
|
||||||
|
ClipContext.Fluid.NONE, mc.player));
|
||||||
|
if (hit.getType() == HitResult.Type.MISS
|
||||||
|
|| eye.distanceToSqr(hit.getLocation()) >= eye.distanceToSqr(p) - 1.0E-4) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static Entity findEntityById(Minecraft mc, int entityId) {
|
private static Entity findEntityById(Minecraft mc, int entityId) {
|
||||||
for (Entity e : mc.level.entitiesForRendering()) {
|
for (Entity e : mc.level.entitiesForRendering()) {
|
||||||
if (e.getId() == entityId) {
|
if (e.getId() == entityId) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
|
|||||||
import net.fabricmc.fabric.api.client.screen.v1.Screens;
|
import net.fabricmc.fabric.api.client.screen.v1.Screens;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.components.Button;
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.components.StringWidget;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
|
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
@@ -48,36 +49,55 @@ public final class MerchantScreenButtonInjector {
|
|||||||
// Position buttons safely to the right of the merchant GUI (276 px wide, centered).
|
// Position buttons safely to the right of the merchant GUI (276 px wide, centered).
|
||||||
int x = scaledWidth / 2 + 140;
|
int x = scaledWidth / 2 + 140;
|
||||||
int y = scaledHeight / 2 - 83;
|
int y = scaledHeight / 2 - 83;
|
||||||
int w = 160;
|
int bw = 160;
|
||||||
|
int lw = 110;
|
||||||
int h = 20;
|
int h = 20;
|
||||||
int gap = 2;
|
int gap = 2;
|
||||||
|
int labelX = x + bw + 4;
|
||||||
|
|
||||||
Button openSettings = Button
|
Button openSettings = Button
|
||||||
.builder(Component.literal("Open Settings"), btn -> GuiBase.openGui(new GuiConfigs()))
|
.builder(Component.literal("Open Settings"), btn -> {
|
||||||
.bounds(x, y, w, h).build();
|
// vanilla Screen.onClose -> setScreen(null) sends the container-close
|
||||||
|
// packet; switching the screen out from under the merchant GUI via
|
||||||
|
// GuiBase.openGui skips that path, so the server still thinks we are
|
||||||
|
// trading with this villager and rejects the next interact packet.
|
||||||
|
if (client.player != null) {
|
||||||
|
client.player.closeContainer();
|
||||||
|
}
|
||||||
|
GuiBase.openGui(new GuiConfigs());
|
||||||
|
})
|
||||||
|
.bounds(x, y, bw, h).build();
|
||||||
|
|
||||||
Button selectSell = Button
|
Button selectSell = Button
|
||||||
.builder(sellButtonLabel(), btn -> applySelectedTradeAsSell(client, merchantScreen))
|
.builder(sellButtonLabel(null), btn -> onSellButton(client, merchantScreen))
|
||||||
.bounds(x, y + (h + gap), w, h).build();
|
.bounds(x, y + (h + gap), bw, h).build();
|
||||||
|
StringWidget sellCurrent = new StringWidget(labelX, y + (h + gap), lw, h,
|
||||||
|
currentSellLabel(), client.font);
|
||||||
|
|
||||||
Button selectBuy = Button
|
Button selectBuy = Button
|
||||||
.builder(buyButtonLabel(), btn -> applySelectedTradeAsBuy(client, merchantScreen))
|
.builder(buyButtonLabel(null), btn -> onBuyButton(client, merchantScreen))
|
||||||
.bounds(x, y + 2 * (h + gap), w, h).build();
|
.bounds(x, y + 2 * (h + gap), bw, h).build();
|
||||||
|
StringWidget buyCurrent = new StringWidget(labelX, y + 2 * (h + gap), lw, h,
|
||||||
|
currentBuyLabel(), client.font);
|
||||||
|
|
||||||
Button enableAutotrade = Button
|
Button enableAutotrade = Button
|
||||||
.builder(autotradeButtonLabel(), btn -> toggleAutotrade(btn))
|
.builder(autotradeButtonLabel(), btn -> toggleAutotrade(btn))
|
||||||
.bounds(x, y + 3 * (h + gap), w, h).build();
|
.bounds(x, y + 3 * (h + gap), bw, h).build();
|
||||||
|
|
||||||
Screen asScreen = merchantScreen;
|
Screen asScreen = merchantScreen;
|
||||||
//? if mc26 {
|
//? if mc26 {
|
||||||
Screens.getWidgets(asScreen).add(openSettings);
|
Screens.getWidgets(asScreen).add(openSettings);
|
||||||
Screens.getWidgets(asScreen).add(selectSell);
|
Screens.getWidgets(asScreen).add(selectSell);
|
||||||
|
Screens.getWidgets(asScreen).add(sellCurrent);
|
||||||
Screens.getWidgets(asScreen).add(selectBuy);
|
Screens.getWidgets(asScreen).add(selectBuy);
|
||||||
|
Screens.getWidgets(asScreen).add(buyCurrent);
|
||||||
Screens.getWidgets(asScreen).add(enableAutotrade);
|
Screens.getWidgets(asScreen).add(enableAutotrade);
|
||||||
//?} else {
|
//?} else {
|
||||||
Screens.getButtons(asScreen).add(openSettings);
|
Screens.getButtons(asScreen).add(openSettings);
|
||||||
Screens.getButtons(asScreen).add(selectSell);
|
Screens.getButtons(asScreen).add(selectSell);
|
||||||
|
Screens.getButtons(asScreen).add(sellCurrent);
|
||||||
Screens.getButtons(asScreen).add(selectBuy);
|
Screens.getButtons(asScreen).add(selectBuy);
|
||||||
|
Screens.getButtons(asScreen).add(buyCurrent);
|
||||||
Screens.getButtons(asScreen).add(enableAutotrade);
|
Screens.getButtons(asScreen).add(enableAutotrade);
|
||||||
//?}
|
//?}
|
||||||
|
|
||||||
@@ -93,27 +113,74 @@ public final class MerchantScreenButtonInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MerchantOffer current = currentSelectedOffer(merchantScreen);
|
MerchantOffer current = currentSelectedOffer(merchantScreen);
|
||||||
selectSell.active = current != null && isSellOffer(current);
|
MerchantOffer sellOffer = (current != null && isSellOffer(current)) ? current : null;
|
||||||
selectBuy.active = current != null && isBuyOffer(current);
|
MerchantOffer buyOffer = (current != null && isBuyOffer(current)) ? current : null;
|
||||||
selectSell.setMessage(sellButtonLabel());
|
boolean sellOn = Configs.Generic.ENABLE_SELL.getBooleanValue();
|
||||||
selectBuy.setMessage(buyButtonLabel());
|
boolean buyOn = Configs.Generic.ENABLE_BUY.getBooleanValue();
|
||||||
|
// Button doubles as a "Disable" toggle when no applicable trade is selected
|
||||||
|
// but the corresponding ENABLE flag is currently on — so the user can turn
|
||||||
|
// it off without leaving the merchant screen.
|
||||||
|
selectSell.active = sellOffer != null || sellOn;
|
||||||
|
selectBuy.active = buyOffer != null || buyOn;
|
||||||
|
selectSell.setMessage(sellOffer != null ? sellButtonLabel(sellOffer)
|
||||||
|
: (sellOn ? Component.literal("Disable sell") : sellButtonLabel(null)));
|
||||||
|
selectBuy.setMessage(buyOffer != null ? buyButtonLabel(buyOffer)
|
||||||
|
: (buyOn ? Component.literal("Disable buy") : buyButtonLabel(null)));
|
||||||
|
sellCurrent.setMessage(currentSellLabel());
|
||||||
|
buyCurrent.setMessage(currentBuyLabel());
|
||||||
enableAutotrade.setMessage(autotradeButtonLabel());
|
enableAutotrade.setMessage(autotradeButtonLabel());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void onSellButton(Minecraft client, MerchantScreen screen) {
|
||||||
|
MerchantOffer offer = currentSelectedOffer(screen);
|
||||||
|
if (offer != null && isSellOffer(offer)) {
|
||||||
|
applySelectedTradeAsSell(client, screen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Configs.Generic.ENABLE_SELL.getBooleanValue()) {
|
||||||
|
Configs.Generic.ENABLE_SELL.setBooleanValue(false);
|
||||||
|
Configs.saveToFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onBuyButton(Minecraft client, MerchantScreen screen) {
|
||||||
|
MerchantOffer offer = currentSelectedOffer(screen);
|
||||||
|
if (offer != null && isBuyOffer(offer)) {
|
||||||
|
applySelectedTradeAsBuy(client, screen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Configs.Generic.ENABLE_BUY.getBooleanValue()) {
|
||||||
|
Configs.Generic.ENABLE_BUY.setBooleanValue(false);
|
||||||
|
Configs.saveToFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Component autotradeButtonLabel() {
|
private static Component autotradeButtonLabel() {
|
||||||
boolean on = Configs.Generic.ENABLED.getBooleanValue();
|
boolean on = Configs.Generic.ENABLED.getBooleanValue();
|
||||||
return Component.literal(on ? "Disable Autotrade" : "Enable Autotrade");
|
return Component.literal(on ? "Disable Autotrade" : "Enable Autotrade");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Component sellButtonLabel() {
|
/** Button label previews the item that would be written if clicked. */
|
||||||
String enabled = Configs.Generic.ENABLE_SELL.getBooleanValue() ? "" : " (off)";
|
private static Component sellButtonLabel(MerchantOffer offer) {
|
||||||
return Component.literal("Sell: " + describeSpec(Configs.Generic.SELL_ITEM.getStringValue()) + enabled);
|
String item = offer == null ? "-" : describeSpec(TradeItemSpec.encodeFromStack(offer.getCostA()));
|
||||||
|
return Component.literal("Set " + item + " as sell item");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Component buyButtonLabel() {
|
private static Component buyButtonLabel(MerchantOffer offer) {
|
||||||
String enabled = Configs.Generic.ENABLE_BUY.getBooleanValue() ? "" : " (off)";
|
String item = offer == null ? "-" : describeSpec(TradeItemSpec.encodeFromStack(offer.getResult()));
|
||||||
return Component.literal("Buy: " + describeSpec(Configs.Generic.BUY_ITEM.getStringValue()) + enabled);
|
return Component.literal("Set " + item + " as buy item");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Side-label shows the currently-configured sell/buy item from config. */
|
||||||
|
private static Component currentSellLabel() {
|
||||||
|
String off = Configs.Generic.ENABLE_SELL.getBooleanValue() ? "" : " (off)";
|
||||||
|
return Component.literal(describeSpec(Configs.Generic.SELL_ITEM.getStringValue()) + off);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Component currentBuyLabel() {
|
||||||
|
String off = Configs.Generic.ENABLE_BUY.getBooleanValue() ? "" : " (off)";
|
||||||
|
return Component.literal(describeSpec(Configs.Generic.BUY_ITEM.getStringValue()) + off);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user