Skip to main content

Extending Monster Data

Source Tier Only

This page documents internal engine code available exclusively to Source tier licensees.

MCE's monster data model is designed for extension. This page covers the key data structures, how to add new monsters with custom forms, how the evolution system works, and how to safely extend the model without breaking existing save files.

Data Model Overview

MonsterEntry (ScriptableObject -- one per species)
├── DexNumber
├── AvailableForms: List<Form>
└── DataByForm: List<DataByFormEntry> ← one entry per form
├── Graphics (sprites, materials, icons, world sprites)
├── Types (FirstType, SecondType)
├── Dex (description, species, height, weight, cry)
├── Abilities (normal + hidden)
├── Training (EVYield, CatchRate, BaseFriendship, BaseExperience, GrowthRate)
├── Breeding (EggGroups, EggCycles, BreedingData, gender ratios)
├── Wild (run chance, held items)
├── Moves (by level, on evolution, egg moves, other/TM)
├── BaseStats: Dictionary<Stat, byte>
├── Evolutions: List<EvolutionData> ← polymorphic
├── MegaEvolutions: Dictionary<Item, Form>
└── Flags (IsLegendary, IsMythical, IsUltraBeast, CanLearnAnyMove)

At runtime, a MonsterInstance wraps a MonsterEntry with instance-specific state: level, IVs, EVs, current HP, nickname, held item, friendship, and the active form.

Adding a New Monster

Step 1: Create the MonsterEntry Asset

Use the menu MCE > Monster Database > Monster (or right-click Create > MCE > Monster Database > Monster) to create a new MonsterEntry ScriptableObject.

Set these fields:

  • DexNumber -- The monster's index in the dex.
  • LocalizableName -- Localization key (auto-filled by the "Auto" button based on the asset name, following the pattern Monsters/{name}).
  • AvailableForms -- At minimum, add one Form asset (typically "Normal"). The engine reads shiny variants automatically from each form's ShinyVersion.

Step 2: Configure DataByFormEntry

For each form in AvailableForms, a DataByFormEntry is stored in the DataByForm list. Each entry contains:

Graphics

FieldTypePurpose
Front / BackMaterialBattle sprite materials (female/default)
FrontMale / BackMaleMaterialMale override materials (shown if HasMaleMaterialOverride)
FrontShiny / BackShinyMaterialShiny variant materials
Icon / IconShiny / IconMale / IconShinyMaleSpriteMenu/party icons
EggMaterial / EggIconMaterial / SpriteEgg visuals
WorldIdleSprite* / WorldWalkingSprite*Sprite / List<Sprite>Overworld follower sprites (4 directions, with male/shiny variants)

Types and Dex

public MonsterType FirstType;     // Primary type
public MonsterType SecondType; // Secondary type (can be null/None)
public string DexDescriptionKey; // Localization key for the dex entry
public MonsterSpecies Species; // e.g., "Seed Monster"
public float Height; // In meters
public float Weight; // In kilograms
public AudioReference Cry; // Sound effect

Abilities

public List<Ability> Abilities;        // Normal abilities (1-2)
public List<Ability> HiddenAbilities; // Hidden abilities

Training

public List<StatByteValuePair> EVYield;  // EV points awarded on defeat
public byte CatchRate; // 0-255
public byte BaseFriendship; // Starting friendship value
public uint BaseExperience; // Base XP yield
public GrowthRate GrowthRate; // XP curve type

Breeding

public List<EggGroup> EggGroups;         // Compatible breeding groups
public byte EggCycles; // Steps to hatch
public List<BreedingData> BreedingData; // What species can hatch from eggs
public List<PrehatchCallback> PrehatchCallbacks; // Pre-hatch form changes
public bool HasBinaryGender;
public float FemaleRatio; // 0.0 to 1.0

Base Stats

public SerializableDictionary<Stat, byte> BaseStats;  // HP, Atk, Def, SpA, SpD, Spe
public SerializableDictionary<Stat, uint> StatLimits; // Hard caps (e.g., Shedinja HP=1)

Move Assignments

Moves are organized into four categories:

FieldWhen Learned
MovesByLevel (List<MoveLevelPair>)On reaching a specific level
OnEvolutionMoves (List<Move>)Immediately after evolving into this form
EggMoves (List<Move>)Inherited through breeding
OtherLearnMoves (List<Move>)TMs, tutors, or other methods

The CanLearnAnyMove flag bypasses the OtherLearnMoves list entirely, allowing the monster to learn any move in the database (used for special monsters).

The Inspector validates moves in real-time and flags duplicates across categories.

Step 3: Use the Monster Creation Helper

Click the Open Helper button on any DataByFormEntry to launch the MCE > Monster Creation Helper editor window, which provides a streamlined workflow for filling in all required data.

Step 4: Register in the Database

Add the new MonsterEntry to the MonsterDatabaseInstance asset so it appears in the dex and is available to all engine systems.

The Form System

Forms allow a single species to have multiple visual and mechanical variants (e.g., regional forms, Mega Evolutions, gender differences).

Each Form is a ScriptableObject with:

  • A name identifier
  • A HasShinyVersion flag and a reference to its ShinyVersion form
  • An IsShiny flag on the shiny variant

MonsterEntry.AvailableForms lists all non-shiny forms. Shiny forms are automatically derived via AvailableFormsWithShinnies.

Adding a New Form

  1. Create a Form ScriptableObject asset.
  2. If the form has a shiny variant, create a second Form asset and link them via ShinyVersion / IsShiny.
  3. Add the normal form to the monster's AvailableForms list.
  4. A new DataByFormEntry slot will appear in DataByForm -- fill in the form-specific data (types, stats, sprites, moves, evolutions).

The Evolution System

Architecture

Evolution is driven by a polymorphic command pattern:

EvolutionData (abstract base)
└── TargetSpeciesAndFormEvolutionData (abstract -- adds TargetSpecies/TargetForm/KeepShinyIfItIs)
├── EvolveByLevel
├── EvolveByFriendship
│ └── EvolveByFriendshipAtSpecificTime
├── EvolveOnItemUse
├── EvolveWhenTraded
├── EvolveOnLevelUpHoldingItem
├── EvolveOnLevelUpWhenAMoveHasBeenLearnt
└── ... (30+ built-in evolution types)

Each DataByFormEntry has a List<EvolutionData> Evolutions field using [SerializeReference] for polymorphic serialization. This means you can mix and match different evolution types in the Inspector.

EvolutionData Abstract API

Every evolution subclass must implement these methods:

public abstract class EvolutionData
{
// Check triggers -- return true if the condition is met
public abstract bool CanEvolveAfterLevelUp(MonsterInstance monster,
DayMoment currentTime,
PlayerCharacter playerCharacter,
out bool consumeHeldItem);

public abstract bool CanEvolveAfterBattleThroughExtraData(MonsterInstance monster,
DayMoment currentTime,
out bool consumeHeldItem);

public abstract bool CanEvolveWhenUsingItem(MonsterInstance monster,
DayMoment currentTime,
Item item,
PlayerCharacter playerCharacter,
out bool consumeHeldItem);

public abstract bool CanEvolveWhenTrading(MonsterInstance monster,
DayMoment currentTime,
MonsterInstance otherMonster,
PlayerCharacter playerCharacter,
out bool consumeHeldItem);

// What it evolves into
public abstract (MonsterEntry, Form) GetTargetEvolution(MonsterInstance monster, DayMoment currentTime);

// Post-evolution hook (optional)
public virtual void AfterEvolutionCallback(MonsterInstance evolvedMonster, ...);

// Dex display data
public abstract List<DexMonsterRelationshipData> GetDexRelationships(...);

// Deep copy
public abstract EvolutionData Clone();
}

Built-In Evolution Types

MCE ships with 30+ evolution types. Here are the most commonly used:

ClassTrigger
EvolveByLevelMonster reaches a target level
EvolveByLevelAtSpecificTimeLevel + time of day
EvolveByLevelWhenSpecificGenderLevel + gender check
EvolveByFriendshipFriendship threshold reached
EvolveByFriendshipAtSpecificTimeFriendship + time of day
EvolveByFriendshipWithMoveOfTypeFriendship + knows a move of a certain type
EvolveOnItemUsePlayer uses a specific item on the monster
EvolveOnItemUseAtSpecificTimeItem use + time of day
EvolveOnItemUseOnSpecificGenderItem use + gender check
EvolveOnLevelUpHoldingItemLevel up while holding a specific item
EvolveOnLevelUpWhenAMoveHasBeenLearntLevel up + knows a specific move
EvolveOnLevelUpWithOtherMonInPartyLevel up + another species is in the party
EvolveWhenTradedTraded to another player
EvolveWhenTradedHoldingItemTraded while holding a specific item
EvolveWhenTradedWithMonsterTraded for a specific species
EvolveByCriticalHitCounterLands N critical hits in a single battle
EvolveByDefeatingRivalsDefeats N monsters of a specific species
EvolveByUsingMoveXTimesUses a specific move N times
EvolveByEvolutionCounterEvolves after a counter threshold
EvolveByLevelWithSceneTagLevel up in a scene with a specific tag
EvolveToRandomSpeciesByLevelRandom target species on level up
NincadaEvolutionSpecial dual-evolution (creates a second monster)

Creating a Custom Evolution Type

Here is an example of a "evolve when HP is below 50%" evolution:

using System;
using System.Collections.Generic;
using UnityEngine;
using OpenMon.MCE.Characters;
using OpenMon.MCE.GameFlow;
using OpenMon.MCE.MonsterDatabase.Items;
using OpenMon.MCE.MonsterDex;
using OpenMon.MCE.UI.Dex;
using OpenMon.Foundation.Localization;

namespace OpenMon.MCE.Monster.Evolution
{
[Serializable]
public class EvolveWhenLowHP : TargetSpeciesAndFormEvolutionData
{
[SerializeField]
[Range(0f, 1f)]
public float HPThreshold = 0.5f;

public override bool CanEvolveAfterLevelUp(MonsterInstance monster,
DayMoment currentTime,
PlayerCharacter playerCharacter,
out bool consumeHeldItem)
{
consumeHeldItem = false;
float hpRatio = (float)monster.CurrentHP / monster.GetMaxHP();
return monster.AllowEvolution(this) && hpRatio <= HPThreshold;
}

// Return false for triggers this evolution does not use
public override bool CanEvolveAfterBattleThroughExtraData(MonsterInstance m,
DayMoment t, out bool c) { c = false; return false; }
public override bool CanEvolveWhenUsingItem(MonsterInstance m, DayMoment t,
Item i, PlayerCharacter p, out bool c) { c = false; return false; }
public override bool CanEvolveWhenTrading(MonsterInstance m, DayMoment t,
MonsterInstance o, PlayerCharacter p, out bool c) { c = false; return false; }

public override List<DexMonsterRelationshipData> GetDexRelationships(
MonsterDexEntry entry, FormDexEntry formEntry, MonsterGender gender,
ILocalizer localizer)
{
return new List<DexMonsterRelationshipData>
{
new()
{
Species = TargetSpecies,
Form = TargetForm,
Gender = gender,
Mode = DexMonsterRelationshipData.RelationShipDisplayType.Text,
Text = "HP < " + (HPThreshold * 100) + "%",
LocalizableDescriptionKey = "Dex/Evolutions/LowHP"
}
};
}

public override EvolutionData Clone() =>
new EvolveWhenLowHP
{
HPThreshold = HPThreshold,
TargetSpecies = TargetSpecies,
TargetForm = TargetForm,
KeepShinyIfItIs = KeepShinyIfItIs
};
}
}

Because Evolutions uses [SerializeReference], your new subclass will automatically appear in the Inspector's type picker dropdown when adding evolution entries.

How Evolution is Triggered at Runtime

The EvolutionManager ScriptableObject orchestrates evolution at runtime:

  1. After a level-up, TriggerEvolutionsAfterLevelUp() iterates all party monsters with NeedsLevelUpEvolutionCheck set to true.
  2. For each monster, it iterates FormData.Evolutions and calls CanEvolveAfterLevelUp().
  3. On the first match, it calls TriggerEvolution(), which:
    • Loads the evolution scene (fade to black).
    • Plays the evolution animation (can be cancelled by the player).
    • If not cancelled, calls AfterEvolutionCallback() for post-evolution logic.
  4. After battle, TriggerEvolutionAfterBattleThroughExtraData() handles counter-based evolutions (critical hits, move usage, etc.).

Serialization Considerations

[SerializeField] vs [SerializeReference]

  • [SerializeField] -- Used for concrete types (stats, sprites, basic data). Unity serializes by value.
  • [SerializeReference] -- Used for polymorphic fields like List<EvolutionData>, List<BreedingData>, and List<PrehatchCallback>. Unity stores the concrete type name, enabling the Inspector to show a type picker.

Save Migration

Critical

Never rename serialized fields without a migration plan. Use [FormerlySerializedAs("OldName")] to preserve data when renaming. MCE already uses this pattern extensively (e.g., FrontShinny was renamed to FrontShiny).

When adding new fields to DataByFormEntry or MonsterInstance:

  1. Add the field with a sensible default value.
  2. Existing assets will deserialize with the default -- no data loss.
  3. Save files use JSON serialization via MCESavesSerializer. New fields will be null/default when loading old saves. Handle this in your code with null checks or default initialization.
  4. Never remove fields from serialized classes. Mark them [Obsolete] and ignore them instead.

Extending MonsterEntry

To add a new top-level field to MonsterEntry itself (not per-form data):

  1. Add the field with [SerializeField] in MonsterEntry.cs.
  2. Use [FormerlySerializedAs] if renaming an existing field.
  3. Existing monster assets will load with the default value -- update them in batch using an editor script or the Inspector.

To add new per-form data, add fields to DataByFormEntry following the existing group/section pattern with TriInspector attributes.