Goals

The EdGoal system provides an AI-like behavior framework for EdEntity objects. Goals represent specific tasks or behaviors that entities can execute in sequence.

Overview

The goal system operates on a queue-based approach where entities execute goals one at a time. When a goal completes, the entity automatically moves to the next goal in the queue. This allows for creating complex behavior sequences and AI patterns.


EdGoal Base Class

The EdGoal abstract class serves as the foundation for all entity behaviors.

Core Properties

EdEntity getEntity()

  • Description: Returns the entity this goal is assigned to

  • Returns: EdEntity that will execute this goal

  • Usage: Entity access within goal logic

void setEntity(EdEntity entity)

  • Description: Sets the entity that will execute this goal

  • Parameters: entity - Target entity

  • Usage: Goal assignment (automatically called by the system)

boolean isForceStopped()

  • Description: Checks if the goal was forcefully stopped

  • Returns: True if force stopped, false otherwise

  • Usage: Cleanup logic, state checking

Lifecycle Management

void init()

  • Description: Initializes and starts the goal execution

  • Usage: Called automatically by the entity system

void start()

  • Description: Abstract method called when the goal begins

  • Usage: Override to implement goal initialization logic

boolean shouldExecute()

  • Description: Abstract method that determines if the goal should continue

  • Returns: True to continue execution, false to complete the goal

  • Usage: Override to implement goal completion conditions

void tick()

  • Description: Abstract method called every game tick while the goal is active

  • Usage: Override to implement goal behavior per tick

void forceStop()

  • Description: Forces the goal to stop without triggering the next goal

  • Usage: Emergency stops, goal cancellation

boolean isRunning()

  • Description: Checks if the goal is currently executing

  • Returns: True if running, false otherwise

  • Usage: Goal state checking, conditional logic

Event Callbacks

void setStartRunnable(Runnable startRunnable)

  • Description: Sets a callback to run when the goal starts

  • Parameters: startRunnable - Code to execute on start

  • Usage: Custom initialization, event handling

void setEndRunnable(Runnable endRunnable)

  • Description: Sets a callback to run when the goal ends

  • Parameters: endRunnable - Code to execute on completion

  • Usage: Cleanup logic, completion events

void setEachTickRunnable(Runnable eachTickRunnable)

  • Description: Sets a callback to run every tick during execution

  • Parameters: eachTickRunnable - Code to execute each tick

  • Usage: Additional per-tick logic, monitoring


Built-in Goal Implementations

EdGoalMove

Moves an entity to a specific location with customizable movement behavior.

public EdGoalMove(Vector moveGoal, double speed)

Parameters:

  • moveGoal - Target position vector

  • speed - Movement speed (blocks per tick)

Configuration Options:

  • setAffectY(boolean) - Whether to move vertically (default: true)

  • setSendRotation(boolean) - Whether to rotate towards target (default: true)

  • setSendRotationEachTick(boolean) - Whether to update rotation each tick (default: false)

  • setInvertRotation(boolean) - Whether to invert rotation calculations (default: false)

Usage Example:

// Move to coordinates (10, 64, 5) at 0.2 blocks per tick
EdGoalMove moveGoal = new EdGoalMove(new Vector(10, 64, 5), 0.2);
moveGoal.setAffectY(false); // Don't change Y coordinate
entity.addGoal(moveGoal);

EdGoalFollowEntity

Makes an entity follow another entity or player at a specified distance.

public EdGoalFollowEntity(EntityHolder target, double followDistance, double speed, long duration)

Parameters:

  • target - EntityHolder wrapping the target to follow

  • followDistance - Distance to maintain from target

  • speed - Movement speed

  • duration - How long to follow (in seconds)

Usage Example:

// Follow a player for 30 seconds
EntityHolder playerHolder = new EntityHolder(player);
EdGoalFollowEntity followGoal = new EdGoalFollowEntity(playerHolder, 3.0, 0.15, 30);
entity.addGoal(followGoal);

EdGoalOrbit

Makes an entity orbit around a central point with customizable radius and speed.

public EdGoalOrbit(Vector center, double radius, double angularSpeed, boolean clockwise, int ticksDuration)

Parameters:

  • center - Center point to orbit around

  • radius - Orbital radius in blocks

  • angularSpeed - Angular speed (radians per tick)

  • clockwise - Direction of orbit

  • ticksDuration - Duration in ticks (0 for infinite)

Configuration Options:

  • setAffectY(boolean) - Whether to orbit vertically

  • setSendRotation(boolean) - Whether to face the center

  • setSendRotationEachTick(boolean) - Update rotation each tick

Usage Example:

// Orbit around a point for 10 seconds
Vector center = new Vector(0, 64, 0);
EdGoalOrbit orbitGoal = new EdGoalOrbit(center, 5.0, 0.05, true, 200);
orbitGoal.setAffectY(false); // Keep same Y level
entity.addGoal(orbitGoal);

EdGoalParabolicMove

Moves an entity in a parabolic arc to a target location.

public EdGoalParabolicMove(Vector end, double height, long duration)

Parameters:

  • end - Target end position

  • height - Arc height modifier

  • duration - Movement duration in milliseconds

Usage Example:

// Jump to location with a high arc over 2 seconds
EdGoalParabolicMove jumpGoal = new EdGoalParabolicMove(
    new Vector(10, 64, 10), 
    3.0, // 3 block high arc
    2000 // 2 seconds
);
entity.addGoal(jumpGoal);

EdGoalArchMove

Moves an entity in a simple sinusoidal arc to a target location.

public EdGoalArchMove(Vector end, double speed, long duration)

Parameters:

  • end - Target position

  • speed - Movement speed

  • duration - Movement duration in milliseconds

Usage Example:

// Move with a gentle arc motion
EdGoalArchMove archGoal = new EdGoalArchMove(
    new Vector(5, 64, 5),
    0.2,
    3000 // 3 seconds
);
entity.addGoal(archGoal);

EdGoalDelay

Creates a delay/wait period in the goal sequence.

public EdGoalDelay(int delayTicks)

Parameters:

  • delayTicks - Number of ticks to wait (20 ticks = 1 second)

Utility Methods:

  • getProgress() - Returns completion percentage (0.0 to 1.0)

  • getRemainingTicks() - Returns ticks remaining

  • getRemainingSeconds() - Returns seconds remaining

Usage Example:

// Wait for 3 seconds between actions
EdGoalDelay waitGoal = new EdGoalDelay(60); // 60 ticks = 3 seconds
entity.addGoal(waitGoal);

EntityHolder Utility

The EntityHolder class provides a unified way to reference both Bukkit entities and EdEntities.

// For Bukkit entities
EntityHolder holder = new EntityHolder(bukkitEntity);

// For EdEntities
EntityHolder holder = new EntityHolder(edEntity);

// Get position from either type
Vector position = holder.getPosition();

Creating Custom Goals

Basic Custom Goal Structure

public class CustomGoal extends EdGoal {
    private boolean completed = false;
    
    @Override
    public void start() {
        // Initialize the goal
        System.out.println("Goal started!");
    }
    
    @Override
    public boolean shouldExecute() {
        // Return false when goal should complete
        return !completed;
    }
    
    @Override
    public void tick() {
        // Execute goal logic each tick
        // Set completed = true when done
        completed = checkCompletionCondition();
    }
}

Advanced Custom Goal Example

public class EdGoalDance extends EdGoal {
    private final int durationTicks;
    private int currentTick = 0;
    private int danceStep = 0;
    
    public EdGoalDance(int durationTicks) {
        this.durationTicks = durationTicks;
    }
    
    @Override
    public void start() {
        getEntity().playAnimation(EntityAnimation.SWING_MAIN_HAND);
    }
    
    @Override
    public boolean shouldExecute() {
        return currentTick < durationTicks;
    }
    
    @Override
    public void tick() {
        currentTick++;
        
        // Change dance moves every 20 ticks
        if (currentTick % 20 == 0) {
            danceStep++;
            switch (danceStep % 3) {
                case 0:
                    getEntity().rotateBody(45, 0);
                    break;
                case 1:
                    getEntity().rotateBody(-45, 0);
                    break;
                case 2:
                    getEntity().rotateBody(0, 0);
                    break;
            }
        }
    }
}

Goal with Conditions

public class EdGoalWaitForPlayer extends EdGoal {
    private final Player targetPlayer;
    private final double requiredDistance;
    
    public EdGoalWaitForPlayer(Player targetPlayer, double requiredDistance) {
        this.targetPlayer = targetPlayer;
        this.requiredDistance = requiredDistance;
    }
    
    @Override
    public void start() {
        getEntity().setDisplayName("§eWaiting for " + targetPlayer.getName());
    }
    
    @Override
    public boolean shouldExecute() {
        if (!targetPlayer.isOnline()) {
            return false; // Complete if player goes offline
        }
        
        double distance = targetPlayer.getLocation().distance(
            getEntity().getPosition().toLocation(targetPlayer.getWorld())
        );
        
        return distance > requiredDistance;
    }
    
    @Override
    public void tick() {
        // Look at the player while waiting
        Vector playerPos = targetPlayer.getLocation().toVector();
        Vector entityPos = getEntity().getPosition();
        
        Vector direction = playerPos.subtract(entityPos).normalize();
        float yaw = (float) Math.toDegrees(Math.atan2(-direction.getX(), direction.getZ()));
        
        getEntity().rotateHead(yaw);
    }
}

Goal Sequence Examples

Simple Patrol Route

// Create a patrol route
entity.addGoal(new EdGoalMove(new Vector(10, 64, 0), 0.2));
entity.addGoal(new EdGoalDelay(40)); // Wait 2 seconds
entity.addGoal(new EdGoalMove(new Vector(10, 64, 10), 0.2));
entity.addGoal(new EdGoalDelay(40));
entity.addGoal(new EdGoalMove(new Vector(0, 64, 10), 0.2));
entity.addGoal(new EdGoalDelay(40));
entity.addGoal(new EdGoalMove(new Vector(0, 64, 0), 0.2));

Complex Behavior Sequence

// Guard behavior: patrol, then follow player if nearby
entity.addGoal(new EdGoalMove(new Vector(5, 64, 5), 0.15));
entity.addGoal(new EdGoalDelay(60));

entity.addGoal(new EdGoal() {
    @Override
    public void start() {
        // Check for nearby players
        for (Player p : getEntity().getWatchers()) {
            if (p.getLocation().distance(getEntity().getPosition().toLocation(p.getWorld())) < 8) {
                // Add follow goal if player is close
                getEntity().addGoal(new EdGoalFollowEntity(new EntityHolder(p), 3.0, 0.2, 10));
                break;
            }
        }
    }
    
    @Override
    public boolean shouldExecute() { return false; } // Instant goal
    @Override
    public void tick() {}
});

entity.addGoal(new EdGoalMove(new Vector(0, 64, 0), 0.15));

Animation Sequence

// Performance routine
entity.addGoal(new EdGoalDelay(20)); // Preparation pause

// Spin move
entity.addGoal(new EdGoalOrbit(entity.getPosition(), 2.0, 0.1, true, 100));

entity.addGoal(new EdGoalDelay(20)); // Brief pause

// Jump to new location
entity.addGoal(new EdGoalParabolicMove(new Vector(5, 64, 5), 3.0, 2000));

entity.addGoal(new EdGoalDelay(40)); // Final pose

Last updated

Was this helpful?