i can't get this bot go combo no matter what i change:

U

/u/Morad_Tarazan

Guest
i can't get this bot go combo no matter what i change:

hi so i have been working on a Spigot plugin for 1.21.11 with Citizens API, a bot that fights the player perfectly but no matter what i do i can't get it to combo me perfectly, he either keep messing up his timings or fails to keep the right distance.

i am letting Ai do the heavy lifting in coding since idk how to code but no matter how many times i prompt it to fix the issue it just can't. here is the block of code that controls it's behavior. if there is anyone that knows how to program please help, i will forever be grateful you.

private void startBotAI(NPC npcWrapper, String botName) { // This creates a loop that runs every single server tick (20 times a second) new BukkitRunnable() { // --- INTERNAL AI TIMERS & STATS --- int attackCooldown = 0; boolean swingNextTick = false; int wTapTicks = 0; double internalHunger = 20.0; // Mimics the vanilla food bar (0-20) double internalSaturation = 5.0; // Mimics the hidden vanilla saturation buffer int regenTickTimer = 0; // Timer for natural health regeneration int strafeTicks = 0; // (Unused in pure forward-combat, but useful for zigzagging) int strafeDir = 1; // Direction of strafe (1 or -1) Location lastTargetLoc = null; // Tracks where the player was 1 tick ago to predict movement int ticksSinceLastHit = 100; // Tracks aggression/combos java.util.Random rand = new java.util.Random(); double nextSwingThreshold = -1.0; // Mimics vanilla exhaustion (sprinting/jumping burns hunger) private void applyExhaustion(double amount) { if (internalSaturation > 0) { // Burn saturation first double leftOver = amount - internalSaturation; internalSaturation = Math.max(0, internalSaturation - amount); if (leftOver > 0) internalHunger = Math.max(0, internalHunger - leftOver); } else { // If saturation is empty, burn actual hunger internalHunger = Math.max(0, internalHunger - amount); } } // Helper method to slowly turn the bot's head (used in lower grades, Grade X uses native staring) private float smoothAngle(float current, float target, float maxSpeed) { float diff = (target - current) % 360.0f; if (diff > 180.0f) diff -= 360.0f; if (diff < -180.0f) diff += 360.0f; if (diff > maxSpeed) return current + maxSpeed; if (diff < -maxSpeed) return current - maxSpeed; return current + diff; } u/Override public void run() { // 1. LIFECYCLE CHECK: If the bot is dead, despawned, or deleted, stop this loop completely to prevent server crashes. if (!npcWrapper.isSpawned() || !(npcWrapper.getEntity() instanceof Player bot) || bot.isDead() || !bot.isValid()) { java.util.List<NPC> squad = activeBots.get(botName.toLowerCase()); if (squad != null) { squad.remove(npcWrapper); if (squad.isEmpty()) activeBots.remove(botName.toLowerCase()); } if (npcWrapper.isSpawned() && npcWrapper.getEntity() != null) { botInventories.remove(npcWrapper.getEntity().getUniqueId()); botBrains.remove(npcWrapper.getEntity().getUniqueId()); } npcWrapper.destroy(); this.cancel(); return; } // Tick down the W-Tap timer if (wTapTicks > 0) wTapTicks--; // 2. NATURAL REGENERATION: Mimics vanilla healing based on food levels double maxHealth = bot.getAttribute(org.bukkit.attribute.Attribute.MAX_HEALTH).getValue(); if (bot.getHealth() < maxHealth && bot.getHealth() > 0) { if (internalSaturation > 0 && internalHunger >= 20.0) { // Fast regen (requires full hunger + saturation) regenTickTimer++; if (regenTickTimer >= 10) { bot.setHealth(Math.min(bot.getHealth() + 1.0, maxHealth)); applyExhaustion(1.0); regenTickTimer = 0; } } else if (internalHunger >= 18.0) { // Slow regen (requires just high hunger) regenTickTimer++; if (regenTickTimer >= 80) { bot.setHealth(Math.min(bot.getHealth() + 1.0, maxHealth)); applyExhaustion(1.0); regenTickTimer = 0; } } else { regenTickTimer = 0; } } // 3. NAMEPLATE UPDATER: Displays the bot's health and hunger above its head double totalHealth = bot.getHealth() + bot.getAbsorptionAmount(); int displayHealth = (int) Math.ceil(totalHealth); int displayHunger = (int) Math.ceil(internalHunger); org.bukkit.ChatColor healthColor = (bot.getAbsorptionAmount() > 0) ? org.bukkit.ChatColor.GOLD : org.bukkit.ChatColor.RED; bot.setCustomName(org.bukkit.ChatColor.WHITE + botName + healthColor + " [" + displayHealth + "\u2764] " + org.bukkit.ChatColor.GOLD + "[" + displayHunger + "\uD83C\uDF7D]"); // 4. DIFFICULTY SETTINGS: Defines how smart/fast the bot is based on its grade Grade currentGrade = getGrade(botName); double aimLagTicks = 0.0; double aimSpread = 0.0; int maxErrorTicks = 0; boolean canSprintJump = true; boolean canStrafe = true; boolean isSprinting = true; float maxTurnSpeed = 360.0f; double wTapSuccessRate = 1.0; double minSpacing = 2.2; double swingMean = 3.0; double swingStdDev = 0.0; double safeEatDistance = 3.0; double critRate = 0.0; double sTapSuccessRate = 0.0; double emergencyEatHp = 0.0; // Adjustments based on grade... if (currentGrade == Grade.D) { aimLagTicks = 2.5; aimSpread = 0.6; maxErrorTicks = 5; canSprintJump = false; canStrafe = false; isSprinting = false; maxTurnSpeed = 15.0f; wTapSuccessRate = 0.0; minSpacing = 2.0; swingMean = 2.2; swingStdDev = 0.6; safeEatDistance = 1.6; critRate = 0.0; sTapSuccessRate = 0.0; emergencyEatHp = 6.0; } else if (currentGrade == Grade.C) { aimLagTicks = 1.2; aimSpread = 0.45; maxErrorTicks = 3; canSprintJump = false; canStrafe = true; isSprinting = true; maxTurnSpeed = 22.0f; wTapSuccessRate = 0.05; minSpacing = 1.5; swingMean = 2.4; swingStdDev = 0.5; safeEatDistance = 3.0; critRate = 0.2; sTapSuccessRate = 0.2; emergencyEatHp = 5.0; } else if (currentGrade == Grade.B) { aimLagTicks = 0.8; aimSpread = 0.3; maxErrorTicks = 2; canSprintJump = true; canStrafe = true; isSprinting = true; maxTurnSpeed = 35.0f; wTapSuccessRate = 0.3; minSpacing = 2.0; swingMean = 2.65; swingStdDev = 0.35; safeEatDistance = 3.0; critRate = 0.4; sTapSuccessRate = 0.4; emergencyEatHp = 3.0; } else if (currentGrade == Grade.A) { aimLagTicks = 0.3; aimSpread = 0.15; maxErrorTicks = 1; canSprintJump = true; canStrafe = true; isSprinting = true; maxTurnSpeed = 60.0f; wTapSuccessRate = 0.65; minSpacing = 2.5; swingMean = 2.85; swingStdDev = 0.15; safeEatDistance = 3.0; critRate = 0.6; sTapSuccessRate = 0.7; emergencyEatHp = 0.0; } else if (currentGrade == Grade.S) { aimLagTicks = 0.0; aimSpread = 0.05; maxErrorTicks = 0; canSprintJump = true; canStrafe = true; isSprinting = true; maxTurnSpeed = 120.0f; wTapSuccessRate = 0.9; minSpacing = 2.75; swingMean = 2.95; swingStdDev = 0.05; safeEatDistance = 3.0; critRate = 0.8; sTapSuccessRate = 0.9; emergencyEatHp = 0.0; } else if (currentGrade == Grade.X) { // Grade X has zero aim lag, 360-degree turning, and perfect 3.06 spacing goals aimLagTicks = 0.0; aimSpread = 0.0; maxErrorTicks = 0; canSprintJump = true; canStrafe = true; isSprinting = true; maxTurnSpeed = 360.0f; wTapSuccessRate = 1.0; minSpacing = 3.06; swingMean = 3.0; swingStdDev = 0.0; safeEatDistance = 4.0; critRate = 1.0; sTapSuccessRate = 1.0; emergencyEatHp = 0.0; } if (nextSwingThreshold < 0) { nextSwingThreshold = Math.max(1.0, Math.min(4.5, swingMean + (rand.nextGaussian() * swingStdDev))); } // Tick down the sword cooldown timer if (attackCooldown > 0) attackCooldown--; ticksSinceLastHit++; // 5. TARGET ACQUISITION Player target = getNearestSurvivalPlayer(bot, 50.0); if (target != null) { Location currentTargetLoc = target.getLocation(); Vector targetTrueVelocity = new Vector(0, 0, 0); if (lastTargetLoc != null && lastTargetLoc.getWorld().equals(currentTargetLoc.getWorld())) { targetTrueVelocity = currentTargetLoc.toVector().subtract(lastTargetLoc.toVector()); } // Mathematical calculations to find exactly where the target's hitbox is Vector laggedEyeLoc = target.getEyeLocation().toVector(); Vector laggedCenter = target.getBoundingBox().getCenter(); org.bukkit.util.BoundingBox box = target.getBoundingBox(); Vector botEye = bot.getEyeLocation().toVector(); // Calculates the closest point on the target's 3D box to the bot's eye double closestX = Math.max(box.getMinX(), Math.min(botEye.getX(), box.getMaxX())); double closestY = Math.max(box.getMinY(), Math.min(botEye.getY(), box.getMaxY())); double closestZ = Math.max(box.getMinZ(), Math.min(botEye.getZ(), box.getMaxZ())); // exactReach is true 3D distance. horizontalReach ignores Y-level (used for running) double exactReach = botEye.distance(new Vector(closestX, closestY, closestZ)); double horizontalReach = Math.sqrt(Math.pow(closestX - botEye.getX(), 2) + Math.pow(closestZ - botEye.getZ(), 2)); Vector closestOnBox = new Vector(closestX, closestY, closestZ); // 6. RAYTRACE (Line of Sight Check): Prevents hitting through blocks! boolean hasLineOfSight = true; if (exactReach > 0.05) { Vector traceDir = closestOnBox.clone().subtract(botEye).normalize(); // Shoots an invisible laser from the bot's eye to the target's body org.bukkit.util.RayTraceResult trace = bot.getWorld().rayTraceBlocks( bot.getEyeLocation(), traceDir, exactReach, org.bukkit.FluidCollisionMode.NEVER, true ); // If the laser hits a block, the bot cannot see you if (trace != null && trace.getHitBlock() != null) { hasLineOfSight = false; } } // ========================================== // 7. GROUND CALCULATION // ========================================== double currentY = box.getMinY(); double groundY = currentY - 5.0; // Check up to 5 blocks down int bx = target.getLocation().getBlockX(); int bz = target.getLocation().getBlockZ(); for (double y = currentY; y >= currentY - 5.0; y -= 0.125) { org.bukkit.block.Block b = bot.getWorld().getBlockAt(bx, (int) Math.floor(y), bz); if (b.getType().isSolid()) { groundY = b.getY() + 1.0; break; } } double distFromGround = currentY - groundY; if (distFromGround < 0) distFromGround = 0.0; // ========================================== // THE FIX: Precise Parameter Coloring // ========================================== if (currentGrade == Grade.X) { // The danger zone: If the bot dips under 2.85 during a juggle, it is hittable. org.bukkit.ChatColor rColor = (exactReach <= 2.85) ? org.bukkit.ChatColor.RED : org.bukkit.ChatColor.GREEN; // Only flashes red when you are mathematically touching the ground (0.00 to 0.01 buffer) org.bukkit.ChatColor gColor = (distFromGround <= 0.01) ? org.bukkit.ChatColor.RED : org.bukkit.ChatColor.AQUA; String hud = org.bukkit.ChatColor.YELLOW + "Reach: " + rColor + String.format("%.3f", exactReach) + org.bukkit.ChatColor.GRAY + " | " + org.bukkit.ChatColor.YELLOW + "Air: " + gColor + String.format("%.3f", distFromGround); target.spigot().sendMessage(net.md_5.bungee.api.ChatMessageType.ACTION_BAR, net.md_5.bungee.api.chat.TextComponent.fromLegacyText(hud)); } // Determine vector pointing toward target Vector directionToTarget = laggedEyeLoc.subtract(bot.getEyeLocation().toVector()); if (directionToTarget.lengthSquared() < 0.0001) directionToTarget = new Vector(0.1, 0, 0.1); else directionToTarget.normalize(); ItemStack offhand = bot.getEquipment().getItemInOffHand(); Vector forwardVec = directionToTarget.clone().setY(0); if (forwardVec.lengthSquared() > 0.0001) forwardVec.normalize(); else forwardVec = new Vector(1, 0, 0); BotBrain brain = botBrains.get(bot.getUniqueId()); BotInventory backpack = botInventories.get(bot.getUniqueId()); boolean isGapple = offhand.getType() == Material.GOLDEN_APPLE || offhand.getType() == Material.ENCHANTED_GOLDEN_APPLE; // Tick the external brain class (handles shielding, potion switching, logic intents) BotBrain.Intent intent = (brain != null && backpack != null) ? brain.tickBrain(target, exactReach, safeEatDistance, emergencyEatHp, internalHunger, isGapple, attackCooldown) : BotBrain.Intent.COMBAT; float targetYaw = bot.getLocation().getYaw(); float targetPitch = bot.getLocation().getPitch(); // 8. COMBAT & MOVEMENT LOGIC if (intent == BotBrain.Intent.COMBAT) { targetYaw = (float) Math.toDegrees(Math.atan2(-directionToTarget.getX(), directionToTarget.getZ())); targetPitch = (float) Math.toDegrees(Math.atan2(-directionToTarget.getY(), Math.sqrt(directionToTarget.getX() * directionToTarget.getX() + directionToTarget.getZ() * directionToTarget.getZ()))); // ========================================== // 1. SMOOTH POCKET MOVEMENT (Vanilla Compliant) // ========================================== double WALK_SPEED = 0.21585; double SPRINT_SPEED = 0.2806; boolean isWTapping = (wTapTicks > 0); boolean shouldSprint = (internalHunger > 6.0) && !isWTapping; double distanceError = horizontalReach - 2.9; Vector finalMove = new Vector(0, bot.getVelocity().getY(), 0); // WIDENED THE POCKET: // The bot only pushes forward if you are more than 0.15 blocks away (3.05+) if (distanceError > 0.15) { double speed = isWTapping ? 0.0 : SPRINT_SPEED; Vector push = forwardVec.clone().multiply(speed); finalMove.setX(push.getX()); finalMove.setZ(push.getZ()); bot.setSprinting(shouldSprint); // SOFTENED THE BACKPEDAL: // The bot only backs up if it gets dangerously close (under 2.6 blocks). // And when it does, it walks backward, it doesn't sprint backward. } else if (distanceError < -0.3) { Vector push = forwardVec.clone().multiply(-WALK_SPEED); finalMove.setX(push.getX()); finalMove.setZ(push.getZ()); bot.setSprinting(false); // THE GOLDEN POCKET (2.6 to 3.05): // If it's anywhere in this huge range, it stops fighting the keys // and just coasts on the target's momentum and its own W-Tapping friction. } else { finalMove.setX(target.getVelocity().getX()); finalMove.setZ(target.getVelocity().getZ()); bot.setSprinting(shouldSprint); } if (bot.getNoDamageTicks() <= 10) { bot.setVelocity(finalMove); } applyExhaustion(0.005); // ========================================== // 2. THE 1-TICK DELAY TIMING // ========================================== boolean inSwingRange = false; // Check if we queued a swing from the previous tick if (swingNextTick) { inSwingRange = true; swingNextTick = false; // Reset the queue } else if (exactReach <= 3.0 && hasLineOfSight) { if (attackCooldown <= 0 && target.getNoDamageTicks() <= 10) { boolean isGrounded = target.isOnGround() || distFromGround <= 0.01; boolean isFalling = target.getVelocity().getY() < 0; // Detect the 0.312 or 0.121 tick boolean isPenultimateTick = isFalling && (distFromGround > 0.02 && distFromGround <= 0.40); if (isPenultimateTick) { // Trust the data: Wait exactly one tick (50ms) to hit them at 0.000 swingNextTick = true; } else if (isGrounded) { // Normal swing if they are already on the ground inSwingRange = true; } } } if (inSwingRange) { ItemStack weapon = bot.getEquipment().getItemInMainHand(); if (!isWTapping) bot.setSprinting(true); bot.swingMainHand(); bot.attack(target); attackCooldown = getAttackCooldownTicks(weapon); wTapTicks = 2; } } // 9. NATIVE STARING // Hands over the head rotation to Citizens API for smooth, packet-level head tracking npcWrapper.faceLocation(target.getEyeLocation()); lastTargetLoc = currentTargetLoc; } } }.runTaskTimer(plugin, 0L, 1L); }
submitted by /u/Morad_Tarazan
[link] [comments]

Continue reading...
 
Back
Top