-
Notifications
You must be signed in to change notification settings - Fork 0
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. |
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).
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.
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.
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 / ...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); // applyOnly 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 arePersonalityordinals, 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.
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.
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), andFriendlyGreetGoal(Friendly) each self-gate on the live disposition. - Passive has no goal — it's the "do nothing" disposition.
Details in AI Goals.
Getting started
No-code (datapacks)
Java API
Patterns