HyCodeYourTale

NPC Behavior System

NPC Behavior System

Detailní dokumentace k NPC behavior systému v Hytale.

---

Přehled Architektury

NPC Behavior System

├── NPCPlugin
│ ├── spawnNPC() / spawnEntity()
│ ├── Builder registrations
│ └── Role asset loading

├── NPCEntity (Component)
│ ├── Role reference
│ ├── PathManager
│ ├── DamageData
│ └── Blackboard views

├── Role (Behavior definition)
│ ├── CombatSupport
│ ├── StateSupport
│ ├── WorldSupport
│ ├── EntitySupport
│ ├── PositionCache
│ └── MotionControllers

└── Instruction (Behavior tree node)
├── Sensor (podmínka)
├── BodyMotion (pohyb těla)
├── HeadMotion (pohyb hlavy)
└── ActionList (akce)

---

NPCPlugin

Hlavní plugin pro správu NPC:

public class NPCPlugin extends JavaPlugin {
public static final String ROLE_ASSETS_PATH = "Server/NPC/Roles";

public static final PluginManifest MANIFEST = PluginManifest.corePlugin(NPCPlugin.class)
.depends(EntityModule.class)
.depends(EntityStatsModule.class)
// ... další závislosti
.build();

// Singleton
public static NPCPlugin get() { return instance; }

// Spawn NPC
public Pair, INonPlayerCharacter> spawnNPC(
@Nonnull Store store,
@Nonnull String npcType,
@Nullable String groupType,
@Nonnull Vector3d position,
@Nonnull Vector3f rotation
);

// Spawn entity
public Pair, NPCEntity> spawnEntity(
@Nonnull Store store,
int roleIndex,
@Nonnull Vector3d position,
@Nullable Vector3f rotation,
@Nullable Model spawnModel,
@Nullable TriConsumer, Store> postSpawn
);

// Role management
public int getIndex(String roleName);
public String getName(int roleIndex);
public Builder tryGetCachedValidRole(int roleIndex);
}

Spawn NPC

// Základní spawn
World world = player.getWorld();
world.execute(() -> {
Store store = world.getEntityStore().getStore();

Pair, INonPlayerCharacter> result = NPCPlugin.get().spawnNPC(
store,
"Zombie", // npcType (role name)
null, // groupType (flock)
position, // Vector3d pozice
new Vector3f(0, 0, 0) // Vector3f rotace
);

if (result != null) {
Ref npcRef = result.first();
INonPlayerCharacter npc = result.second();
// NPC spawnut
}
});

Spawn s Parametry

world.execute(() -> {
Store store = world.getEntityStore().getStore();
NPCPlugin npcPlugin = NPCPlugin.get();

int roleIndex = npcPlugin.getIndex("Zombie");
if (roleIndex < 0) {
// Role neexistuje
return;
}

Pair, NPCEntity> result = npcPlugin.spawnEntity(
store,
roleIndex,
position,
rotation,
null, // custom model (null = default)
(npcEntity, ref, s) -> {
// Post-spawn callback
// Můžeš modifikovat NPC
}
);
});

---

NPCEntity

Komponenta NPC entity:

public class NPCEntity extends LivingEntity implements INonPlayerCharacter {
public static final BuilderCodec CODEC;

// Role
@Nullable private Role role;
private int roleIndex = Integer.MIN_VALUE;
private String roleName;

// Path
private PathManager pathManager = new PathManager();

// Damage tracking
private final DamageData damageData = new DamageData();

// Leash (spawn) position
private final Vector3d leashPoint = new Vector3d();
private float leashHeading;
private float leashPitch;

// Despawn
private boolean isDespawning;
private float despawnRemainingSeconds;

// ComponentType
public static ComponentType getComponentType() {
return EntityModule.get().getComponentType(NPCEntity.class);
}

// Role
@Nullable
public Role getRole() {
return this.role;
}

// Animace
public void playAnimation(
@Nonnull Ref ref,
@Nonnull AnimationSlot animationSlot,
@Nullable String animationId,
@Nonnull ComponentAccessor componentAccessor
);

// Inventory
@Override
protected Inventory createDefaultInventory() {
return new Inventory((short)0, Inventory.DEFAULT_ARMOR_CAPACITY, (short)3, (short)0, (short)0);
}

// Damage
public boolean getCanCauseDamage(
@Nonnull Ref attackerRef,
@Nonnull ComponentAccessor componentAccessor
) {
return this.role.getCombatSupport().getCanCauseDamage(attackerRef, componentAccessor);
}
}

---

Role

Hlavní třída definující chování NPC:

public class Role implements IAnnotatedComponentCollection {
public static final double INTERACTION_PLAYER_DISTANCE = 10.0;

// Support třídy
@Nonnull protected final CombatSupport combatSupport;
@Nonnull protected final StateSupport stateSupport;
@Nonnull protected final MarkedEntitySupport markedEntitySupport;
@Nonnull protected final WorldSupport worldSupport;
@Nonnull protected final EntitySupport entitySupport;
@Nonnull protected final PositionCache positionCache;
@Nonnull protected final DebugSupport debugSupport;

// Stats
protected final int initialMaxHealth;
protected final double knockbackScale;
protected final double inertia;

// Motion
protected final Steering bodySteering = new Steering();
protected final Steering headSteering = new Steering();
@Nonnull protected Map motionControllers;
protected MotionController activeMotionController;

// Instructions
protected Instruction rootInstruction;
@Nullable protected Instruction interactionInstruction;
@Nullable protected Instruction deathInstruction;

// Properties
protected String roleName;
protected int roleIndex;
protected String appearance;
protected boolean invulnerable;
protected boolean breathesInAir;
protected boolean breathesInWater;
protected String dropListId;

// Konstruktor
public Role(@Nonnull BuilderRole builder, @Nonnull BuilderSupport builderSupport) {
// Inicializace supports
this.combatSupport = new CombatSupport(npcComponent, builder, builderSupport);
this.stateSupport = new StateSupport(builder, builderSupport);
this.markedEntitySupport = new MarkedEntitySupport(npcComponent);
this.worldSupport = new WorldSupport(npcComponent, builder, builderSupport);
this.entitySupport = new EntitySupport(npcComponent, builder);
this.positionCache = new PositionCache(this);
this.debugSupport = new DebugSupport(npcComponent, builder);

// Načti instructions
List instructionList = builder.getInstructionList(builderSupport);
Instruction[] instructions = instructionList.toArray(Instruction[]::new);
this.rootInstruction = Instruction.createRootInstruction(instructions, builderSupport);
this.interactionInstruction = builder.getInteractionInstruction(builderSupport);
this.deathInstruction = builder.getDeathInstruction(builderSupport);
}

// Tick
public void tick(@Nonnull Ref ref, float tickTime, @Nonnull Store store) {
// Zpracuj deferred actions
// Compute actions and steering
this.computeActionsAndSteering(ref, tickTime, this.bodySteering, this.headSteering, store);
}

// Lifecycle
public void loaded();
public void spawned(@Nonnull Holder holder, @Nonnull NPCEntity npcComponent);
public void unloaded();
public void removed();
public void teleported(@Nonnull World from, @Nonnull World to);
}

Role Supports

// Combat support
CombatSupport combatSupport = role.getCombatSupport();
boolean canDamage = combatSupport.getCanCauseDamage(attackerRef, componentAccessor);

// State support
StateSupport stateSupport = role.getStateSupport();
// Správa stavů NPC

// World support
WorldSupport worldSupport = role.getWorldSupport();
// Interakce se světem

// Entity support
EntitySupport entitySupport = role.getEntitySupport();
// Správa entity

// Position cache
PositionCache positionCache = role.getPositionCache();
// Cache pozic

// Marked entity support
MarkedEntitySupport markedEntitySupport = role.getMarkedEntitySupport();
// Správa označených entit (targets)

---

Instruction

Uzel behavior tree obsahující sensor, motion a actions:

public class Instruction implements RoleStateChange, IAnnotatedComponentCollection {
public static final Instruction[] EMPTY_ARRAY = new Instruction[0];

// Identifikace
@Nullable protected final String name;
@Nullable protected final String tag;
protected int index;

// Behavior komponenty
protected final Sensor sensor;
@Nullable protected final BodyMotion bodyMotion;
@Nullable protected final HeadMotion headMotion;
@Nonnull protected final ActionList actions;

// Child instructions
protected final Instruction[] instructionList;

// Properties
protected final double weight;
protected final boolean treeMode;
protected final boolean continueAfter;

// Konstruktor
public Instruction(@Nonnull BuilderInstruction builder, Sensor sensor,
@Nullable Instruction[] instructionList,
@Nonnull BuilderSupport support) {
this.name = builder.getName();
this.tag = builder.getTag();
this.sensor = sensor;

if (instructionList != null) {
// Branch node
this.instructionList = instructionList;
this.bodyMotion = null;
this.headMotion = null;
this.actions = ActionList.EMPTY_ACTION_LIST;
} else {
// Leaf node
this.instructionList = EMPTY_ARRAY;
this.bodyMotion = builder.getBodyMotion(support);
this.headMotion = builder.getHeadMotion(support);
this.actions = builder.getActionList(support);
}
}

// Gettery
public Sensor getSensor() { return this.sensor; }
public BodyMotion getBodyMotion() { return this.bodyMotion; }
public HeadMotion getHeadMotion() { return this.headMotion; }

// Lifecycle
public void loaded(Role role);
public void spawned(Role role);
public void unloaded(Role role);
public void removed(Role role);
public void teleported(Role role, World from, World to);

// Execution
public void execute(Ref ref, Role role, double tickTime, Store store);
}

---

Sensor

Interface pro detekci podmínek:

public interface Sensor extends RoleStateChange, IAnnotatedComponent {
Sensor NULL = new NullSensor();

// Hlavní metoda - kontroluje podmínku
boolean matches(
@Nonnull Ref ref,
@Nonnull Role role,
double tickTime,
@Nonnull Store store
);

// Informace o sensoru
@Nullable
InfoProvider getSensorInfo();

// Cleanup
default void done() {}
}

Typy Sensorů (z NPCPlugin)

| Sensor | Popis |
|--------|-------|
| Player | Detekce hráče |
| Mob | Detekce mobu |
| State | Kontrola stavu |
| Damage | Přijatý damage |
| Kill | Kill event |
| Beacon | Beacon detekce |
| Time | Časová podmínka |
| Path | Pathfinding |
| Weather | Počasí |
| Block | Blok detekce |
| Random | Náhodná podmínka |
| Flag | Flag kontrola |
| Target | Target kontrola |
| Timer | Timer event |
| InAir | V povětří |
| OnGround | Na zemi |
| InWater | Ve vodě |
| Light | Světlo |
| Age | Stáří NPC |

Kombinační Sensory

| Sensor | Popis |
|--------|-------|
| Any | Libovolný (OR) |
| And | Všechny (AND) |
| Or | Libovolný (OR) |
| Not | Negace |

---

Motion

Interface pro pohyb (tělo/hlava):

public interface Motion extends RoleStateChange, IAnnotatedComponent {

// Aktivace/Deaktivace
default void activate(@Nonnull Ref ref, @Nonnull Role role,
@Nonnull ComponentAccessor componentAccessor) {}

default void deactivate(@Nonnull Ref ref, @Nonnull Role role,
@Nonnull ComponentAccessor componentAccessor) {}

// Před výpočtem steering
default void preComputeSteering(@Nonnull Ref ref, @Nonnull Role role,
@Nullable InfoProvider provider,
@Nonnull Store store) {}

// Hlavní výpočet steering
boolean computeSteering(
@Nonnull Ref ref,
@Nonnull Role role,
@Nullable InfoProvider provider,
double tickTime,
@Nonnull Steering steering,
@Nonnull ComponentAccessor componentAccessor
);
}

BodyMotion

public interface BodyMotion extends Motion {
@Nullable
default BodyMotion getSteeringMotion() {
return this;
}
}

HeadMotion

public interface HeadMotion extends Motion {
}

Typy BodyMotion (z NPCPlugin)

| BodyMotion | Popis |
|------------|-------|
| Nothing | Žádný pohyb |
| Wander | Náhodné putování |
| WanderInCircle | Putování v kruhu |
| WanderInRect | Putování v obdélníku |
| Flee | Útěk |
| Seek | Hledání cíle |
| Path | Pathfinding |
| Teleport | Teleportace |
| Land | Přistání |
| TakeOff | Vzlet |
| MatchLook | Kopírování pohledu |
| MaintainDistance | Udržení vzdálenosti |
| Timer | Časovaný |
| Sequence | Sekvence |

Typy HeadMotion (z NPCPlugin)

| HeadMotion | Popis |
|------------|-------|
| Nothing | Žádný pohyb |
| Aim | Zamíření |
| Watch | Sledování |
| Observe | Pozorování |
| Sequence | Sekvence |
| Timer | Časovaný |

---

Action

Interface pro akce:

public interface Action extends RoleStateChange, IAnnotatedComponent {
Action[] EMPTY_ARRAY = new Action[0];

// Kontrola možnosti provedení
boolean canExecute(
@Nonnull Ref ref,
@Nonnull Role role,
@Nullable InfoProvider provider,
double tickTime,
@Nonnull Store store
);

// Provedení akce
boolean execute(
@Nonnull Ref ref,
@Nonnull Role role,
@Nullable InfoProvider provider,
double tickTime,
@Nonnull Store store
);

// Aktivace/Deaktivace
void activate(Role role, InfoProvider provider);
void deactivate(Role role, InfoProvider provider);
boolean isActivated();
}

Typy Akcí (z NPCPlugin)

| Action | Popis |
|--------|-------|
| Attack | Útok |
| Spawn | Spawn entity |
| State | Změna stavu |
| Inventory | Manipulace s inventářem |
| PlaySound | Přehrání zvuku |
| Despawn | Despawn |
| PlayAnimation | Přehrání animace |
| SpawnParticles | Spawn částic |
| Die | Smrt |
| Role | Změna role |
| SetFlag | Nastavení flagu |
| DropItem | Zahození itemu |
| PickUpItem | Sebrání itemu |
| Beacon | Beacon akce |
| DisplayName | Změna jména |
| Crouch | Přikrčení |
| PlaceBlock | Položení bloku |
| ApplyEntityEffect | Aplikace efektu |
| SetStat | Nastavení statu |

Timer Actions

| Action | Popis |
|--------|-------|
| TimerStart | Start timeru |
| TimerContinue | Pokračování timeru |
| TimerPause | Pauza timeru |
| TimerModify | Modifikace timeru |
| TimerStop | Stop timeru |
| TimerRestart | Restart timeru |

---

EntityFilter

Filtry pro výběr entit:

// Typy filtrů (z NPCPlugin)
public enum EntityFilterType {
Attitude, // Podle attitude
LineOfSight, // Přímý pohled
HeightDifference, // Výškový rozdíl
ViewSector, // Úhel pohledu
Combat, // Combat stav
ItemInHand, // Item v ruce
NPCGroup, // NPC skupina
MovementState, // Stav pohybu
SpotsMe, // Vidí mě
StandingOnBlock, // Stojí na bloku
Stat, // Stat hodnota
Inventory, // Inventář
Not, // Negace
And, // AND
Or, // OR
Altitude, // Nadmořská výška
InsideBlock // Uvnitř bloku
}

---

MotionController

Kontrolér pro pohyb NPC:

public abstract class MotionController {
protected Role role;
protected double inertia;
protected double knockbackScale;

public void setRole(Role role) { this.role = role; }
public void setInertia(double inertia) { this.inertia = inertia; }
public void setKnockbackScale(double knockbackScale) { this.knockbackScale = knockbackScale; }

public abstract void activate();
public abstract void deactivate();
public abstract void spawned();

public abstract void updateMovementState(
Ref ref,
MovementStates movementStates,
Steering steering,
Vector3d velocity,
ComponentAccessor componentAccessor
);

public abstract void addForce(Vector3d velocity, VelocityConfig velocityConfig);
public abstract void forceVelocity(Vector3d velocity, VelocityConfig velocityConfig, boolean ignoreDamping);
public abstract boolean onGround();
}

---

NPC Systémy

RoleSystems.BehaviourTickSystem

Hlavní tick systém pro NPC behavior:

public static class BehaviourTickSystem extends TickingSystem {

@Override
public void tick(float dt, int systemIndex, @Nonnull Store store) {
// Pro každou NPC entitu
store.forEachChunk(npcComponentType, (archetypeChunk, commandBuffer) -> {
for (int index = 0; index < archetypeChunk.size(); index++) {
entities.add(archetypeChunk.getReferenceTo(index));
}
});

// Tick každé NPC
for (Ref entityReference : entities) {
if (entityReference.isValid()) {
NPCEntity npcComponent = store.getComponent(entityReference, npcComponentType);

try {
Role role = npcComponent.getRole();
role.tick(entityReference, tickLength, store);
} catch (Exception e) {
// Error handling - remove NPC
store.removeEntity(entityReference, RemoveReason.REMOVE);
}
}
}
}
}

NPCSystems.AddedSystem

Systém při přidání NPC:

public static class AddedSystem extends RefSystem {

@Override
public void onEntityAdded(Ref ref, AddReason reason,
Store store, CommandBuffer commandBuffer) {
NPCEntity npcComponent = store.getComponent(ref, npcComponentType);
Role role = npcComponent.getRole();

if (role == null) {
// NPC bez role - odstranit
commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
return;
}

// Inicializace
npcComponent.initBlockChangeBlackboardView(ref, commandBuffer);
role.loaded();

// Přidej komponenty
commandBuffer.ensureComponent(ref, PrefabCopyableComponent.getComponentType());
commandBuffer.ensureComponent(ref, PositionDataComponent.getComponentType());
commandBuffer.ensureComponent(ref, MovementAudioComponent.getComponentType());

if (reason == AddReason.SPAWN) {
NewSpawnComponent newSpawn = new NewSpawnComponent(role.getSpawnLockTime());
commandBuffer.addComponent(ref, NewSpawnComponent.getComponentType(), newSpawn);
}
}

@Override
public void onEntityRemove(Ref ref, RemoveReason reason,
Store store, CommandBuffer commandBuffer) {
NPCEntity npcComponent = store.getComponent(ref, npcComponentType);

switch (reason) {
case REMOVE:
npcComponent.getRole().removed();
break;
case UNLOAD:
npcComponent.getRole().unloaded();
break;
}
}

@Override
public Query getQuery() {
return npcComponentType;
}
}

---

Role Assets

Role jsou definovány v Server/NPC/Roles/:

{
"Type": "Role",
"Model": "mob_zombie",
"MaxHealth": 20,
"Appearance": "zombie_default",
"Invulnerable": false,
"BreathesInAir": true,
"BreathesInWater": false,
"KnockbackScale": 1.0,
"DropListId": "zombie_drops",
"MotionControllers": {
"ground": {
"Type": "GroundMotionController",
"MaxSpeed": 5.0
}
},
"InitialMotionController": "ground",
"Instructions": [
{
"Sensor": {
"Type": "Player",
"Range": 16.0
},
"BodyMotion": {
"Type": "Seek"
},
"HeadMotion": {
"Type": "Watch"
},
"Actions": [
{
"Type": "Attack"
}
]
},
{
"Sensor": {
"Type": "Any"
},
"BodyMotion": {
"Type": "Wander"
}
}
]
}

---

Příklady

Custom NPC Spawner

public class CustomNPCSpawner {

public void spawnZombie(World world, Vector3d position) {
world.execute(() -> {
Store store = world.getEntityStore().getStore();

Pair, INonPlayerCharacter> result =
NPCPlugin.get().spawnNPC(store, "Zombie", null, position, new Vector3f(0, 0, 0));

if (result != null) {
Ref npcRef = result.first();
getLogger().atInfo().log("Spawned zombie at %s", position);
}
});
}
}

NPC Event Listener

public class NPCDeathListener extends DeathSystems.OnDeathSystem {

@Override
public Query getQuery() {
return NPCEntity.getComponentType();
}

@Override
public void onComponentAdded(Ref ref, DeathComponent component,
Store store, CommandBuffer commandBuffer) {

NPCEntity npc = store.getComponent(ref, NPCEntity.getComponentType());
if (npc == null) return;

String roleName = npc.getRoleName();
getLogger().atInfo().log("NPC %s died!", roleName);

// Custom death handling
}
}

NPC Damage Modifier

public class NPCDamageModifier extends DamageEventSystem {

@Override
public Query getQuery() {
return NPCEntity.getComponentType();
}

@Override
public void handle(int index, ArchetypeChunk chunk,
Store store, CommandBuffer commandBuffer,
Damage damage) {

NPCEntity npc = chunk.getComponent(index, NPCEntity.getComponentType());
if (npc == null) return;

Role role = npc.getRole();
if (role.isInvulnerable()) {
damage.setCancelled(true);
return;
}

// Modifikace damage
float amount = damage.getAmount();
damage.setAmount(amount * 0.8f); // 20% redukce
}
}

---

Shrnutí

| Třída | Účel |
|-------|------|
| NPCPlugin | Hlavní NPC plugin |
| NPCEntity | NPC komponenta |
| Role | Behavior definice |
| Instruction | Behavior tree uzel |
| Sensor | Detekce podmínek |
| BodyMotion | Pohyb těla |
| HeadMotion | Pohyb hlavy |
| Action | Akce |
| MotionController | Kontrolér pohybu |

| Support | Účel |
|---------|------|
| CombatSupport | Combat logika |
| StateSupport | Správa stavů |
| WorldSupport | Interakce se světem |
| EntitySupport | Správa entity |
| PositionCache | Cache pozic |
| MarkedEntitySupport | Správa targets |

| Metoda | Popis |
|--------|-------|
| NPCPlugin.spawnNPC() | Spawn NPC podle jména role |
| NPCPlugin.spawnEntity() | Spawn NPC podle role indexu |
| NPCPlugin.getIndex() | Získá index role |
| Role.tick() | Tick behavior |
| Sensor.matches() | Kontrola podmínky |
| Motion.computeSteering() | Výpočet pohybu |
| Action.execute() | Provedení akce |

Last updated: 20. ledna 2026