Init
This commit is contained in:
373
content/world/entities/npc/npc-ai.en.md
Normal file
373
content/world/entities/npc/npc-ai.en.md
Normal file
@@ -0,0 +1,373 @@
|
||||
---
|
||||
title: NPC AI
|
||||
type: docs
|
||||
weight: 3
|
||||
---
|
||||
|
||||
The NPC AI system provides intelligent decision-making through blackboards, decision makers, sensors, and instructions.
|
||||
|
||||
**Packages:**
|
||||
- `com.hypixel.hytale.server.npc.blackboard`
|
||||
- `com.hypixel.hytale.server.npc.decisionmaker`
|
||||
- `com.hypixel.hytale.server.npc.sensorinfo`
|
||||
- `com.hypixel.hytale.server.npc.instructions`
|
||||
|
||||
## Blackboard System
|
||||
|
||||
The Blackboard is a shared memory space where NPC components communicate through key-value pairs.
|
||||
|
||||
### Blackboard Class
|
||||
|
||||
```java
|
||||
public class Blackboard {
|
||||
private Map<BlackboardKey<?>, Object> data;
|
||||
|
||||
// Store value
|
||||
public <T> void set(BlackboardKey<T> key, T value);
|
||||
|
||||
// Retrieve value
|
||||
public <T> T get(BlackboardKey<T> key);
|
||||
public <T> T getOrDefault(BlackboardKey<T> key, T defaultValue);
|
||||
|
||||
// Check existence
|
||||
public boolean has(BlackboardKey<?> key);
|
||||
|
||||
// Remove value
|
||||
public void remove(BlackboardKey<?> key);
|
||||
|
||||
// Clear all
|
||||
public void clear();
|
||||
}
|
||||
```
|
||||
|
||||
### BlackboardKey
|
||||
|
||||
Type-safe keys for blackboard access:
|
||||
|
||||
```java
|
||||
// Predefined keys
|
||||
public class BlackboardKeys {
|
||||
public static final BlackboardKey<Entity> TARGET =
|
||||
new BlackboardKey<>("target", Entity.class);
|
||||
public static final BlackboardKey<Vector3d> HOME_POSITION =
|
||||
new BlackboardKey<>("home_position", Vector3d.class);
|
||||
public static final BlackboardKey<Float> ALERT_LEVEL =
|
||||
new BlackboardKey<>("alert_level", Float.class);
|
||||
public static final BlackboardKey<Boolean> IN_COMBAT =
|
||||
new BlackboardKey<>("in_combat", Boolean.class);
|
||||
}
|
||||
|
||||
// Custom keys
|
||||
BlackboardKey<String> CUSTOM_KEY = new BlackboardKey<>("custom_data", String.class);
|
||||
```
|
||||
|
||||
### Using the Blackboard
|
||||
|
||||
```java
|
||||
NPCEntity npc = // get NPC
|
||||
Blackboard bb = npc.getBlackboard();
|
||||
|
||||
// Set target
|
||||
bb.set(BlackboardKeys.TARGET, targetEntity);
|
||||
|
||||
// Get home position
|
||||
Vector3d home = bb.getOrDefault(BlackboardKeys.HOME_POSITION, npc.getPosition());
|
||||
|
||||
// Check combat status
|
||||
if (bb.getOrDefault(BlackboardKeys.IN_COMBAT, false)) {
|
||||
// Handle combat
|
||||
}
|
||||
```
|
||||
|
||||
## Decision Maker System
|
||||
|
||||
The Decision Maker evaluates options and selects the best action for the NPC to take.
|
||||
|
||||
### DecisionMaker Interface
|
||||
|
||||
```java
|
||||
public interface DecisionMaker {
|
||||
// Evaluate and select best option
|
||||
Option evaluate(NPCEntity npc, Blackboard blackboard);
|
||||
|
||||
// Get all available options
|
||||
List<Option> getOptions();
|
||||
|
||||
// Add option
|
||||
void addOption(Option option);
|
||||
}
|
||||
```
|
||||
|
||||
### Option Class
|
||||
|
||||
Options represent possible actions:
|
||||
|
||||
```java
|
||||
public class Option {
|
||||
private String id;
|
||||
private Evaluator evaluator;
|
||||
private Action action;
|
||||
private float basePriority;
|
||||
|
||||
// Calculate score based on context
|
||||
public float evaluate(NPCEntity npc, Blackboard blackboard);
|
||||
|
||||
// Execute the action
|
||||
public void execute(NPCEntity npc, Blackboard blackboard);
|
||||
}
|
||||
```
|
||||
|
||||
### Evaluator Interface
|
||||
|
||||
Evaluators calculate option scores:
|
||||
|
||||
```java
|
||||
public interface Evaluator {
|
||||
// Return score from 0.0 to 1.0
|
||||
float evaluate(NPCEntity npc, Blackboard blackboard);
|
||||
}
|
||||
|
||||
// Built-in evaluators
|
||||
public class Evaluators {
|
||||
// Returns 1.0 if target exists
|
||||
public static final Evaluator HAS_TARGET = (npc, bb) ->
|
||||
bb.has(BlackboardKeys.TARGET) ? 1.0f : 0.0f;
|
||||
|
||||
// Returns health percentage
|
||||
public static final Evaluator HEALTH_PERCENT = (npc, bb) ->
|
||||
npc.getHealth() / npc.getMaxHealth();
|
||||
|
||||
// Returns 1.0 if at home
|
||||
public static final Evaluator AT_HOME = (npc, bb) -> {
|
||||
Vector3d home = bb.get(BlackboardKeys.HOME_POSITION);
|
||||
return npc.getPosition().distance(home) < 5.0 ? 1.0f : 0.0f;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Creating a Decision Maker
|
||||
|
||||
```java
|
||||
DecisionMaker dm = new StandardDecisionMaker();
|
||||
|
||||
// Add attack option
|
||||
dm.addOption(new Option(
|
||||
"attack",
|
||||
Evaluators.HAS_TARGET,
|
||||
new AttackAction(),
|
||||
10.0f // High priority
|
||||
));
|
||||
|
||||
// Add flee option
|
||||
dm.addOption(new Option(
|
||||
"flee",
|
||||
(npc, bb) -> npc.getHealth() < 20 ? 1.0f : 0.0f,
|
||||
new FleeAction(),
|
||||
15.0f // Higher priority when triggered
|
||||
));
|
||||
|
||||
// Add wander option
|
||||
dm.addOption(new Option(
|
||||
"wander",
|
||||
(npc, bb) -> 0.3f, // Low constant score
|
||||
new WanderAction(),
|
||||
1.0f // Low priority fallback
|
||||
));
|
||||
|
||||
npc.setDecisionMaker(dm);
|
||||
```
|
||||
|
||||
## Sensor System
|
||||
|
||||
Sensors gather information about the world and update the blackboard.
|
||||
|
||||
### Sensor Interface
|
||||
|
||||
```java
|
||||
public interface Sensor {
|
||||
// Process sensor input
|
||||
void sense(NPCEntity npc, Blackboard blackboard, float deltaTime);
|
||||
|
||||
// Get sensor type
|
||||
String getSensorType();
|
||||
}
|
||||
```
|
||||
|
||||
### SensorInfo Classes
|
||||
|
||||
Sensor information containers:
|
||||
|
||||
```java
|
||||
// Visual detection info
|
||||
public class VisualSensorInfo {
|
||||
private List<Entity> visibleEntities;
|
||||
private float detectionRange;
|
||||
private float fieldOfView;
|
||||
|
||||
public List<Entity> getVisibleEntities();
|
||||
public boolean canSee(Entity entity);
|
||||
}
|
||||
|
||||
// Audio detection info
|
||||
public class AudioSensorInfo {
|
||||
private List<SoundEvent> heardSounds;
|
||||
private float hearingRange;
|
||||
|
||||
public List<SoundEvent> getHeardSounds();
|
||||
public Vector3d getLoudestSoundPosition();
|
||||
}
|
||||
|
||||
// Threat detection info
|
||||
public class ThreatSensorInfo {
|
||||
private List<Entity> threats;
|
||||
private Entity primaryThreat;
|
||||
private float threatLevel;
|
||||
}
|
||||
```
|
||||
|
||||
### Built-in Sensors
|
||||
|
||||
```java
|
||||
// Visual sensor - detects visible entities
|
||||
public class VisualSensor implements Sensor {
|
||||
private float range = 20.0f;
|
||||
private float fov = 120.0f; // degrees
|
||||
|
||||
@Override
|
||||
public void sense(NPCEntity npc, Blackboard bb, float dt) {
|
||||
List<Entity> visible = findVisibleEntities(npc);
|
||||
bb.set(BlackboardKeys.VISIBLE_ENTITIES, visible);
|
||||
}
|
||||
}
|
||||
|
||||
// Proximity sensor - detects nearby entities
|
||||
public class ProximitySensor implements Sensor {
|
||||
private float range = 5.0f;
|
||||
|
||||
@Override
|
||||
public void sense(NPCEntity npc, Blackboard bb, float dt) {
|
||||
List<Entity> nearby = findNearbyEntities(npc, range);
|
||||
bb.set(BlackboardKeys.NEARBY_ENTITIES, nearby);
|
||||
}
|
||||
}
|
||||
|
||||
// Damage sensor - reacts to damage taken
|
||||
public class DamageSensor implements Sensor {
|
||||
@Override
|
||||
public void sense(NPCEntity npc, Blackboard bb, float dt) {
|
||||
if (npc.wasRecentlyDamaged()) {
|
||||
bb.set(BlackboardKeys.LAST_ATTACKER, npc.getLastAttacker());
|
||||
bb.set(BlackboardKeys.ALERT_LEVEL, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Instruction System
|
||||
|
||||
Instructions define high-level behavioral goals.
|
||||
|
||||
### Instruction Interface
|
||||
|
||||
```java
|
||||
public interface Instruction {
|
||||
// Check if instruction should activate
|
||||
boolean shouldActivate(NPCEntity npc, Blackboard blackboard);
|
||||
|
||||
// Execute instruction logic
|
||||
void execute(NPCEntity npc, Blackboard blackboard, float deltaTime);
|
||||
|
||||
// Check if instruction is complete
|
||||
boolean isComplete(NPCEntity npc, Blackboard blackboard);
|
||||
|
||||
// Get priority
|
||||
float getPriority();
|
||||
}
|
||||
```
|
||||
|
||||
### Built-in Instructions
|
||||
|
||||
```java
|
||||
// Wander instruction
|
||||
public class WanderInstruction implements Instruction {
|
||||
private float wanderRadius;
|
||||
private float minWaitTime;
|
||||
private float maxWaitTime;
|
||||
|
||||
@Override
|
||||
public boolean shouldActivate(NPCEntity npc, Blackboard bb) {
|
||||
return !bb.has(BlackboardKeys.TARGET);
|
||||
}
|
||||
}
|
||||
|
||||
// Guard instruction
|
||||
public class GuardInstruction implements Instruction {
|
||||
private Vector3d guardPosition;
|
||||
private float guardRadius;
|
||||
|
||||
@Override
|
||||
public void execute(NPCEntity npc, Blackboard bb, float dt) {
|
||||
if (npc.getPosition().distance(guardPosition) > guardRadius) {
|
||||
bb.set(BlackboardKeys.MOVE_TARGET, guardPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Follow instruction
|
||||
public class FollowInstruction implements Instruction {
|
||||
private Entity followTarget;
|
||||
private float followDistance;
|
||||
}
|
||||
|
||||
// Patrol instruction
|
||||
public class PatrolInstruction implements Instruction {
|
||||
private List<Vector3d> patrolPoints;
|
||||
private int currentPoint;
|
||||
}
|
||||
```
|
||||
|
||||
## Combining AI Systems
|
||||
|
||||
```java
|
||||
public void setupNPCAI(NPCEntity npc) {
|
||||
// Configure blackboard
|
||||
Blackboard bb = npc.getBlackboard();
|
||||
bb.set(BlackboardKeys.HOME_POSITION, npc.getPosition());
|
||||
bb.set(BlackboardKeys.AGGRO_RANGE, 15.0f);
|
||||
|
||||
// Add sensors
|
||||
npc.addSensor(new VisualSensor(20.0f, 120.0f));
|
||||
npc.addSensor(new ProximitySensor(5.0f));
|
||||
npc.addSensor(new DamageSensor());
|
||||
|
||||
// Configure decision maker
|
||||
DecisionMaker dm = new StandardDecisionMaker();
|
||||
dm.addOption(new Option("attack", hasHostileTarget, attackAction, 10.0f));
|
||||
dm.addOption(new Option("flee", lowHealth, fleeAction, 15.0f));
|
||||
dm.addOption(new Option("patrol", isGuard, patrolAction, 5.0f));
|
||||
dm.addOption(new Option("idle", always, idleAction, 1.0f));
|
||||
npc.setDecisionMaker(dm);
|
||||
|
||||
// Add instructions
|
||||
npc.addInstruction(new GuardInstruction(guardPost, 10.0f));
|
||||
npc.addInstruction(new ReactToThreatInstruction());
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
{{< callout type="info" >}}
|
||||
**AI Guidelines:**
|
||||
- Use the Blackboard for all inter-component communication
|
||||
- Keep Evaluators simple and fast - they run frequently
|
||||
- Use appropriate sensor ranges to balance awareness vs performance
|
||||
- Design Instructions to be interruptible
|
||||
- Test AI behavior with various scenarios
|
||||
{{< /callout >}}
|
||||
|
||||
{{< callout type="warning" >}}
|
||||
**Performance:** Large numbers of NPCs with complex AI can impact performance. Consider:
|
||||
- Reducing sensor update frequency for distant NPCs
|
||||
- Using LOD (Level of Detail) for AI complexity
|
||||
- Limiting pathfinding requests
|
||||
{{< /callout >}}
|
||||
Reference in New Issue
Block a user