Architecture¶
Better Ecology uses a data-driven architecture for vanilla mob behavior modification with minimal per-mob code. All behavior derives from YAML/JSON profiles and archetypes, compiled once on reload, and dispatched through unified hooks.
Design Principles¶
- Single source of truth: YAML profiles and archetypes
- Minimal mixins: One Mob mixin plus one Animal mixin
- Handle-based systems: Each subsystem is a handle that enables itself via config
- Performance: Parse once, cache aggressively, avoid per-tick heavy work
Data Layout¶
File Locations¶
| Type | Path |
|---|---|
| Base template | data/better-ecology/templates/mod_registry.yaml |
| Mob profiles | data/<namespace>/mobs/**/<mob>.yaml or .yml |
| Archetypes | data/<namespace>/archetypes/<path>.yaml |
Profile Structure¶
Profiles can include archetypes for reusable behavior templates:
Merge Rules¶
Order: base template -> archetypes (in listed order) -> mob profile
Overlay Semantics: - null does not override - Empty list does not override - Non-empty list replaces - Maps merge recursively
If identity.mob_id is missing or invalid, the profile is skipped with a warning.
Runtime Flow¶
1. Reload Phase¶
EcologyResourceReloaderreads YAMLs on datapack reload- Builds
EcologyProfileobjects EcologyProfileRegistry.reload()stores profiles, resolves handles, increments generation
2. Entity Lifecycle¶
MobEcologyMixinattachesEcologyComponentto every Mob- Goal registration:
EcologyHooks.onRegisterGoals()(idempotent) - Tick:
EcologyHooks.onTick() - NBT:
EcologyHooks.onSave()/EcologyHooks.onLoad()
3. Food Checks¶
AnimalEcologyMixin redirects Animal.mobInteract isFood check to EcologyHooks.overrideIsFood(...)
Core Classes¶
| Class | Purpose |
|---|---|
EcologyProfile | Merged map with typed getters and cached values |
EcologyMerge | Deep merge with null/empty list rules |
EcologyProfileLoader | Reads base, profiles, archetypes; normalizes YAML |
EcologyProfileRegistry | Profile map, handle cache, generation counter |
EcologyComponent | Per-mob cache, handle NBT tags, goal registration flag |
EcologyHandle | System interface (goals, tick, NBT, food override) |
EcologyHandleRegistry | Register and resolve handles per profile |
EcologyHooks | Mixin dispatch for lifecycle, NBT, food override |
EcologyResourceReloader | Fabric resource reload listener |
EcologyBootstrap | Registers handles and reload listener |
EcologyAccess | Mixin-access interface for component access |
AnimalItemStorage | Shared component for animals that carry items |
Mixins¶
MobEcologyMixin¶
Inject Points: - <init> after registerGoals() call - registerGoals tail (secondary safeguard) - tick tail - addAdditionalSaveData tail - readAdditionalSaveData tail
Rationale: Many mobs override registerGoals() without calling super, so the constructor hook is the only universal point. The hook is idempotent to avoid double registration.
AnimalEcologyMixin¶
Redirect: - Animal.mobInteract call to isFood(ItemStack) -> EcologyHooks.overrideIsFood(...)
NBT Format¶
Root key: BetterEcology
Per-handle format:
Each handle reads/writes only its own tag.
Handle Model¶
Each handle: 1. supports(profile) - Checks if config fields are present 2. registerGoals() - Adds vanilla goals using config values 3. tick() - Updates internal state or performs actions 4. readNbt()/writeNbt() - Persists handle state
Example: DietHandle¶
Reads: - player_interaction.player_breeding.items - diet.food_sources.primary entries with type: ITEM
Supports: - Item IDs and tags (#tag) - Caches item and tag sets per profile - overrideIsFood() returns true when stack matches
Performance Guardrails¶
- No YAML parsing during tick
- Profile caches per reload
- Handle caches per profile
- Use interval ticks for expensive logic
Dependencies¶
- SnakeYAML (
org.yaml:snakeyaml) - Fabric API resource reload listener
Source Reference¶
Key Minecraft classes: - Mob.java - registerGoals, tick, NBT - Animal.java - mobInteract, isFood - ResourceManager.java - Resource loading