Skip to content

Personality System

Brennan Hatton edited this page Jun 7, 2026 · 1 revision

Personality System

A PlayerMob holds one disposition per category of entity — it can be passive toward players but aggressive toward zombies, friendly to villagers but shy of nothing. This page covers the Java model behind that. For the no-code/NBT view, see Spawning and NBT.

Three public classes make up the model, all in games.brennan.playermob.entity:

Class What it is
Personality An enum of dispositions (AGGRESSIVE … SHY).
TargetCategory An enum of entity groups + the allow-matrix / defaults.
PersonalityProfile The per-mob mapping TargetCategory → Personality, plus save/roll/provoke logic.

Personality

public enum Personality { AGGRESSIVE, FRIENDLY, PASSIVE, SKEPTICAL, SHY }

The behaviour each disposition drives is generalised across categories (target & attack / approach & gift / ignore / stop & stare / flee & hide). Ordinals are frozen as a save contract — use Personality.byOrdinal(int) to decode a stored value (it falls back to PASSIVE for out-of-range input rather than throwing).

TargetCategory

public enum TargetCategory { PLAYERS, HOSTILE_MOBS, ANIMALS, VILLAGERS }

Each category owns its valid personalities and a default:

Method Purpose
allowed() Immutable list of personalities valid for this category.
allows(Personality) Is this personality valid here?
defaultPersonality() Safe fallback / clamp target (always in allowed()).
randomAllowed(RandomSource) Uniform random pick from allowed() (used at spawn).
static classify(LivingEntity) Map a live entity to its category, or null if none.

classify order matters and is worth knowing: other PlayerMobs classify as PLAYERS (the mob treats its own kind like players), villagers are checked before animals, and the hostile check is last. An entity in no category (returns null) is simply never a personality subject.

See Spawning and NBT for the full allow-matrix + defaults table.

PersonalityProfile

The per-mob state. Each PlayerMobEntity owns one. Key methods:

Method Purpose
get(TargetCategory) The mob's disposition toward that category.
set(TargetCategory, Personality) Set it (clamped to the allow-matrix; marks the category explicit).
personalityToward(LivingEntity) Classify the entity, then return the disposition (or null).
rollUnsetRandom(RandomSource) Randomise only the categories not explicitly set. Called once at spawn.
provoke(TargetCategory, boolean armed) React to being attacked — see below.
save(CompoundTag) / load(CompoundTag) NBT round-trip (keys from Spawning and NBT).

Explicit-set tracking is the clever bit: a category set via NBT, spawn egg, or set(...) is marked explicit and never re-rolled at spawn. That's what lets the archetype spawn eggs pin one category and randomise the rest.


Reading a mob's disposition from Java

The entity exposes read access directly:

// Disposition toward a specific live entity (null if uncategorised):
Personality p = playerMob.personalityToward(someLivingEntity);

if (playerMob.personalityToward(player) == Personality.AGGRESSIVE) {
    // this mob will hunt that player
}

// Find the nearest entity the mob feels a given way about:
LivingEntity friend = playerMob.nearestWithPersonality(Personality.FRIENDLY, 16.0);

You can also classify any entity yourself without a PlayerMob in hand:

TargetCategory cat = TargetCategory.classify(entity);   // PLAYERS / HOSTILE_MOBS / ...

Setting a mob's disposition from Java

In v0.15.0 the entity keeps its PersonalityProfile private and exposes no per-category setter — the supported external write path is NBT. The cleanest way is a capture → modify → apply round-trip through the entity's public save/load (this preserves inventory, skin, etc.):

import games.brennan.playermob.entity.PersonalityProfile;
import games.brennan.playermob.entity.Personality;

CompoundTag tag = new CompoundTag();
playerMob.addAdditionalSaveData(tag);                              // capture current state
tag.putInt(PersonalityProfile.TAG_PLAYERS, Personality.SHY.ordinal());  // "PlayerPersonality"
tag.putInt("HostilePersonality", Personality.AGGRESSIVE.ordinal());     // see key table
playerMob.readAdditionalSaveData(tag);                            // apply

Only the players-category key is exposed as a constant (PersonalityProfile.TAG_PLAYERS); the other three keys (HostilePersonality, AnimalPersonality, VillagerPersonality) are listed in Spawning and NBT. Values are Personality ordinals, clamped to each category's allow-matrix on load.

If you need a first-class setter for your integration, a small setPersonality(TargetCategory, Personality) on the entity would be a welcome contribution — open an issue.


Provocation: how dispositions flip in combat

When a categorisable entity hits the mob, PersonalityProfile.provoke(category, armed) runs (from PlayerMobEntity.hurt):

  • A Friendly / Passive / Skeptical disposition flips to Aggressive if the mob is armed, otherwise to Shy.
  • Shy is clamped back to Aggressive for categories that disallow Shy (animals, villagers) — "Shy disallowed → stand and fight".
  • An already-Aggressive or already-Shy disposition is left unchanged.

On a flip to Shy the mob also drops its retaliation target so the flee goal takes over. isArmed() (a recognised weapon in the main hand) decides the fight-or-flee branch.


How the AI consumes personality

The dispositions are read live every tick — flipping one at runtime instantly re-wires behaviour, no goal re-registration:

  • The target selector attacks anything the mob is Aggressive toward.
  • FleeFromCategoryGoal (Shy), SkepticalWatchGoal (Skeptical), and FriendlyGreetGoal (Friendly) each self-gate on the live disposition.
  • Passive has no goal — it's the "do nothing" disposition.

Details in AI Goals.

Clone this wiki locally