# AI Goals System NpcAPI provides a powerful AI goal system that allows NPCs to perform autonomous behaviors. Goals represent specific behaviors that an NPC can execute, such as walking to locations, attacking entities, or looking around. ## Goal Basics ### Creating a Custom Goal ```java import de.eisi05.npc.api.ai.Goal; import de.eisi05.npc.api.objects.NPC; import org.jetbrains.annotations.NotNull; public class MyCustomGoal extends Goal { public MyCustomGoal() { super(Priority.MEDIUM); // Priority: ALWAYS, HIGHEST, HIGH, MEDIUM, LOW } @Override protected boolean canUse(@NotNull NPC npc) { // Check if this goal can be used right now // Called each tick to determine if goal should be considered return npc.getLocation().distance(target) < 50; } @Override protected void start(@NotNull NPC npc) { // Called when this goal starts executing // Initialize any state needed for the goal startTime = System.currentTimeMillis(); } @Override protected void tick(@NotNull NPC npc) { // Called each tick while this goal is active // Update the goal's behavior npc.lookAt(target); } @Override protected void stop(@NotNull NPC npc) { // Called when this goal stops executing // Clean up any state or cancel ongoing tasks target = null; } @Override protected boolean canContinue(@NotNull NPC npc) { // Check if this goal can continue executing // If false, goal will be stopped and new goal selected return canUse(npc) && (System.currentTimeMillis() - startTime) < 10000; } @Override protected boolean canBeInterrupted(@NotNull NPC npc) { // Check if this goal can be interrupted by higher priority goals // Default is false (goals cannot be interrupted) return true; } @Override protected boolean canBeRemovedNow(@NotNull NPC npc) { // Check if this goal can be removed immediately without being queued // Default implementation returns !canContinue(npc) // Override this for goals that need special removal logic return !canContinue(npc); } } ``` ### Goal Priority System Goals use a priority-based selection system: - **ALWAYS (100)**: Always selected over other goals - **HIGHEST (4)**: Very high selection chance - **HIGH (3)**: High selection chance - **MEDIUM (2)**: Medium selection chance - **LOW (1)**: Low selection chance For non-ALWAYS priorities, the system uses weighted random selection. ## Goal Management ### Adding Goals ```java import de.eisi05.npc.api.ai.goals.WalkToLocationGoal; import de.eisi05.npc.api.ai.goals.LookAtGoal; // Add goals to NPC npc.addGoal(new WalkToLocationGoal(targetLocation, 0.3)); npc.addGoal(new LookAtGoal(player)); ``` ### Removing Goals ```java // Remove a goal - if currently running, it will be queued for removal // and removed once it finishes naturally to prevent weird positions boolean removed = npc.removeGoal(goal); // The system uses a queue-based removal: // - If goal is not running: removed immediately // - If goal is running: queued for removal, removed when it finishes // - Removal happens before new goal selection ``` ### Goal Selector ```java import de.eisi05.npc.api.ai.GoalSelector; GoalSelector selector = npc.getGoalSelector(); // Start/stop goal evaluation selector.start(); selector.stop(); // Check if running boolean running = selector.isRunning(); // Set tick interval (default: 1 tick) selector.setTickInterval(5); // Evaluate every 5 ticks // Get current goal Goal current = selector.getCurrentGoal(); // Force a specific goal to start selector.forceGoal(myGoal); ``` ## Built-in Goals ### WalkToLocationGoal Makes the NPC walk to a specific location using pathfinding: ```java import de.eisi05.npc.api.ai.goals.WalkToLocationGoal; Location target = new Location(world, 100, 64, 100); WalkToLocationGoal walkGoal = new WalkToLocationGoal.Builder(target) .speed(0.4) .build(); // With custom settings WalkToLocationGoal walkGoal = new WalkToLocationGoal.Builder(targetLocation) .speed(0.4) // speed (0.1-1.0) .maxIterations(5000) // max pathfinding iterations .allowDiagonal(true) // allow diagonal movement .withRotation(true) // include rotation packets .completionCallback(result -> { // completion callback if(result == WalkingResult.SUCCESS) { Bukkit.broadcastMessage("NPC reached destination!"); } }) .build(); ``` ### LookAtGoal Makes the NPC look at a specific entity or location: ```java import de.eisi05.npc.api.ai.goals.LookAtGoal; // Look at entity LookAtGoal goal1 = new LookAtGoal(player); // Look at location LookAtGoal goal2 = new LookAtGoal(targetLocation); npc.addGoal(goal1); ``` ### AttackEntityGoal Makes the NPC attack nearby entities that match a predicate. Behavior varies based on held item: - Bow/Crossbow/Trident: Long range (15 blocks) - Sword/Axe/Other: Short range (3 blocks) - Only activates when target is in line of sight - Dynamically discovers targets within range ```java import de.eisi05.npc.api.ai.goals.AttackEntityGoal; // Attack all players AttackEntityGoal attackGoal = new AttackEntityGoal(entity -> entity instanceof Player); // Attack low health players AttackEntityGoal attackGoal = new AttackEntityGoal( entity -> entity instanceof Player && ((Player) entity).getHealth() < 10 ); // With custom attack range AttackEntityGoal attackGoal = new AttackEntityGoal( entity -> entity instanceof Monster, 15.0 // custom attack range ); ``` ### LookAroundGoal Makes the NPC look around randomly. Can serve as an idle/wait behavior. ```java import de.eisi05.npc.api.ai.goals.LookAroundGoal; // Default settings (1-4 seconds) LookAroundGoal lookGoal = new LookAroundGoal(); // Custom duration LookAroundGoal lookGoal = new LookAroundGoal(40, 200); // 2-10 seconds ``` ### WaitGoal Makes the NPC wait/idle for a specified duration. ```java import de.eisi05.npc.api.ai.goals.WaitGoal; // Wait for 5 seconds (100 ticks) WaitGoal waitGoal = new WaitGoal(100); ``` ### WanderGoal Makes the NPC wander randomly to nearby locations. ```java import de.eisi05.npc.api.ai.goals.WanderGoal; // Default settings (10 block radius) WanderGoal wanderGoal = new WanderGoal(); // Custom radius and speed WanderGoal wanderGoal = new WanderGoal(15, 60, 200, 0.3); ``` ### FollowEntityGoal Makes the NPC follow a target entity by UUID, maintaining a specified distance. ```java import de.eisi05.npc.api.ai.goals.FollowEntityGoal; // Default settings (10 block follow distance, 1.5 block stop distance) FollowEntityGoal followGoal = new FollowEntityGoal(playerToFollow.getUniqueId()); // Custom settings FollowEntityGoal followGoal = new FollowEntityGoal( playerToFollow.getUniqueId(), 5.0, // follow distance 2.0, // stop distance 0.5 // speed ); ``` ## Usage Examples ### Basic Setup ```java // Create NPC NPC npc = new NPC(location); // Add goals npc.addGoal(new WanderGoal(10)); npc.addGoal(new LookAroundGoal()); npc.addGoal(new WalkToLocationGoal.Builder(target).build()); // Start goal system (goals auto-save when added) npc.startGoals(); ``` ### Guard NPC Example ```java NPC guard = new NPC(spawnLocation); // Equip with sword Map equipment = new HashMap<>(); equipment.put(EquipmentSlot.HAND, new ItemStack(Material.IRON_SWORD)); guard.setOption(NpcOption.EQUIPMENT, equipment); // Add goals - attack has ALWAYS priority, others are randomizable guard.addGoal(new AttackEntityGoal(entity -> entity instanceof Monster)); guard.addGoal(new WanderGoal(8)); guard.addGoal(new LookAroundGoal()); guard.startGoals(); ``` ### Following Player Example ```java NPC companion = new NPC(player.getLocation()); companion.addGoal(new FollowEntityGoal(player.getUniqueId(), 5.0, 2.0, 0.4)); companion.addGoal(new LookAroundGoal()); companion.startGoals(); ``` ## Goal Removal Queue System The goal system includes a queue-based removal mechanism to prevent NPCs from ending up in weird positions when goals are removed mid-execution. ### How It Works 1. **Removal Check**: When `removeGoal()` is called, the system checks `canBeRemovedNow()` 2. **Immediate Removal**: If `canBeRemovedNow()` returns true, the goal is removed immediately 3. **Queued Removal**: If `canBeRemovedNow()` returns false, the goal is added to a removal queue 4. **Continued Execution**: The goal continues running until it can be safely removed 5. **Automatic Removal**: Once `canBeRemovedNow()` returns true or the goal naturally finishes, it's removed 6. **New Selection**: The system then selects a new goal ### The canBeRemovedNow() Method The `canBeRemovedNow(@NotNull NPC npc)` method determines if a goal can be removed immediately: ```java @Override protected boolean canBeRemovedNow(@NotNull NPC npc) { // Default implementation - can be removed if goal can't continue return !canContinue(npc); } ``` #### Built-in Goal Behavior - **AttackEntityGoal**: Cannot be removed if NPC is floating during movement - **WalkToLocationGoal**: Cannot be removed if NPC is floating while walking - **FollowEntityGoal**: Cannot be removed if NPC is floating during movement - **Other goals**: Use default behavior (can be removed when can't continue) ### Benefits - Prevents NPCs from being removed while in unstable positions (like floating) - Ensures smooth transitions between goals - Maintains realistic NPC behavior - Automatic handling of edge cases ### Example Scenario ```java // NPC is currently walking to a location WalkToLocationGoal walkGoal = new WalkToLocationGoal(target, 0.3); npc.addGoal(walkGoal); // Later, you want to remove it while it's still walking npc.removeGoal(walkGoal); // The goal will: // 1. Check if it can be removed immediately (canBeRemovedNow()) // 2. If NPC is floating during movement, it will be queued for removal // 3. Continue walking until NPC has solid ground beneath it // 4. Then be automatically removed from the goal list // 5. A new goal will be selected (if available) ``` ## Best Practices for Custom Goals 1. **Goals auto-save**: Goals are automatically saved when added or removed via `addGoal()` and `removeGoal()`. No manual saving required. 2. **Goals auto-start**: The goal system automatically starts when an NPC is loaded from disk if it has saved goals. 3. **Use predicates for target filtering**: AttackEntityGoal uses predicates to dynamically discover targets within range, making it more flexible than fixed target suppliers. 4. **Combine goals strategically**: Use a mix of ALWAYS priority reactive goals (like AttackEntityGoal) and randomizable idle goals (like WanderGoal, LookAroundGoal). 5. **Set appropriate priorities**: Use ALWAYS for critical behaviors that should always execute (like combat), and use HIGH/MEDIUM/LOW for behaviors that can be randomly selected. 6. **Test pathfinding**: The WalkToLocationGoal uses A* pathfinding which can be CPU-intensive. Adjust `maxIterations` based on your server's performance. 7. **Use Appropriate Priorities**: Choose priorities that match the goal's importance 8. **Handle Interruption**: Override `canBeInterrupted()` for goals that can pause safely 9. **Clean Resources**: Always clean up in `stop()` method 10. **Check Conditions**: Use `canUse()` and `canContinue()` for proper goal lifecycle 11. **Avoid Long Operations**: Keep `tick()` method lightweight for performance 12. **Override canBeRemovedNow**: For movement-based goals, check if NPC is in stable position before allowing removal ## Notes - The AStarPathfinder is suitable for constant movement, but for very frequent pathfinding, consider caching paths or using simpler movement for short distances. - Goals run on the main server thread, so avoid heavy computations in `canUse()` or `tick()`. - Goals implement `Serializable` and are saved with the NPC. Transient fields (like entity references) are handled via custom serialization. - The goal selector runs every tick by default. Adjust with `npc.getGoalSelector().setTickInterval(ticks)` (internal API). - `getGoalSelector()` is now private - use the public goal management methods (`addGoal`, `removeGoal`, `startGoals`, `stopGoals`). ## Troubleshooting ### Goal Not Executing - Check `canUse()` returns true - Verify goal priority isn't being overridden - Ensure GoalSelector is started ### Goal Stuck - Check `canContinue()` implementation - Verify goal isn't marked as non-interruptable when it should be - Look for exceptions in goal methods ### Performance Issues - Limit expensive operations in `canUse()` and `tick()` - Use appropriate tick intervals - Consider goal priorities to reduce frequent switching