Skip to content

CustomMonsters Adding

wezzzyrek1 edited this page Sep 24, 2026 · 2 revisions

Adding a monster

A custom monster is one entry in a config file pointing at your own model. That is the whole minimum - no script, no client change. A script is what turns a model into something that looks alive; see Monster scripts.

The running example is Hydra, monster id 2498.

Numbering

Monster ids above the original range are yours - 989 and up on this client (Season 21 Part 2-3). The client keeps 988 and below for its own monsters and never looks those up in CustomMonster.xdc. The boundary moves up in newer seasons, as each one adds its monsters after the last.

Each entry of MonsterList.xml stands for one index, and with the plugin active the server holds 2500 of them, so the last id is 2499.

Hand ids out from the top down - 2499, 2498 and so on. Every update of the game adds its new monsters straight after its last one, so a custom monster low in the range is the first to end up on the same id as one of them.

The files

Data\Local\CustomMonster.xdc                     registration (encrypted)
Data\Custom\Monsters\Hydra\Monster38.bmd         the model
Data\Custom\Monsters\Hydra\*.OZJ / *.OZT         its textures
Data\Custom\Monsters\Sound\*.wav                 your own sounds (the Hydra plays the client's)
Data\Custom\Scripts\Game\Monsters\2498.gsc       the script (optional)
Data\Lang.mpr                                    npcName.txt - its name and name plate

Textures live next to the model and are named by the model itself, so a model folder copied whole simply works.

The client reads BMD versions 10 and 15. A model saved as version 12, which some tools write, is not read by this client - convert it first.

CustomMonster.xdc is optional. Without it the client starts normally and custom monsters stay off - which is what a client of a server without the plugin needs.

CustomMonster.xdc

Edit Data\Local\CustomMonster.xml, then encrypt it to CustomMonster.xdc with IGC File Encrypt in its Standard mode. The client only reads the .xdc.

<Monsters>
    <Setting>
        <Monster ID="2498" NPC="0" Name="Hydra">
            <Model Path="Data\Custom\Monsters\Hydra\" File="Monster38" Scale="1.0"
                   HiddenMesh="-1" BlendMesh="5" MoveSpeed="0" />
            <Weapon Slot="0" Cat="-1" Index="-1" />
            <Weapon Slot="1" Cat="-1" Index="-1" />
            <AnimSpeed A1="0.25" A2="0.25" A3="0.25" A4="0.25" A5="0.25"
                       A6="0.25" A7="0.25" A8="0.25" A9="0.25" A10="0.25" />
            <Render Flags="TEXTURE" />
            <AttackRate A1="5000" A2="5000" A3="0" A4="0" A5="0" />
            <Glow ColorR="-1" ColorG="-1" ColorB="-1" Power="1.0" Luminosity="0" />
            <Sound S1="1" S2="1" S3="2" S4="2" S5="1"
                   S6="-1" S7="-1" S8="-1" S9="-1" S10="-1" />
            <SoundAction S1="-1" S2="-1" S3="-1" S4="-1" S5="-1"
                         S6="-1" S7="-1" S8="-1" S9="-1" S10="-1" />
        </Monster>
    </Setting>

    <SoundList Path="Data\Sound\">
        <Sound ID="1" File="mHydra1.wav" />
        <Sound ID="2" File="mHydraAttack1.wav" />
    </SoundList>
</Monsters>

No comments inside tags - the parser will not survive them.

Every <Monster> lists every element and attribute, so all entries read alike. One the monster does not use keeps its off value, given with each element below.

The Hydra plays the game's own Hydra sounds, so its <SoundList> points at the client's Data\Sound\.

<Monster>

Attribute Meaning
ID the monster id, matching the server's monster type
NPC 1 for an NPC the player talks to instead of attacking, 0 for a monster. The server needs IsNpc as well - see A talking NPC
Name a label for you, not read - the name in game comes only from npcName.txt

<Model>

Attribute Meaning
Path folder holding the .bmd, with a trailing backslash
File model file name without the extension
Scale size multiplier, 1.0 is the model's own size
MoveSpeed walking speed - the same number as MoveSpeed in the server's MonsterList.xml, ms a tile. 0 for the client's own, which matches 400
HiddenMesh mesh index to hide, or -1 for none
BlendMesh mesh index drawn additively rather than solid, or -1 for none

HiddenMesh and BlendMesh are applied once per spawn. -1 is neutral for both.

BlendMesh is worth understanding, because it is how a model gets glowing parts. The named mesh is pulled out of the solid pass and drawn additively, at a brightness you control - invisible at 0, full at 1. Hydra's head beams are mesh 5: without BlendMesh they would be ordinary solid geometry and permanently on. With it, and a script driving the brightness, they fade in only while it attacks.

<Weapon>

One line per hand; -1 in Cat and Index leaves the hand empty:

<Weapon Slot="0" Cat="4" Index="0" />
<Weapon Slot="1" Cat="-1" Index="-1" />
Attribute Meaning
Slot 0 right hand, 1 left hand
Cat item category, as in ItemList.xml; -1 for an empty hand
Index item index, as in ItemList.xml; -1 for an empty hand

The client's own code reads it: an arrow skill the monster casts - Raining Arrow and the like - takes its arrows from the bow or crossbow in slot 0 and shoots none without one. See Monster skills.

<Weapon> arms every monster of the id alike. A script arms one monster at a time with m:SetWeapon(slot, cat, index), usually from Monster.OnCreate - see Monster scripts:

Monster.OnCreate(function(o, m)
    m:SetWeapon(0, 4, 0)    -- a Short Bow in the right hand
end)

<AnimSpeed>

Playback speed of the first ten animations: A1 is action 0 (Stop1), A10 is action 9 (Attack4) - see Actions. Lower is slower; 0.25 is a typical walk and the default for a missing attribute. Run (10) and Attack5 (11) have no attribute and always play at 0.25.

<AttackRate>

Which attack animation the monster uses, as relative weights. A1..A5 are Attack1..Attack5 (actions 3, 4, 8, 9, 11), and every attack picks one of them at random in proportion to its weight: A1="5000" A2="5000" is an even split between the first two, and 0 never picks that one. With every weight at 0, or no <AttackRate>, the client chooses as it does for its own monsters. Keep the total under 32767 - above it the later slots stop being picked. Enums.AttackAction lists the same five actions in the same order.

<Sound>, <SoundAction> and <SoundList>

<Sound> gives this monster up to ten sounds, S1..S10, each an id from <SoundList>; -1 or a missing slot is silence. What plays them depends on whether <SoundAction> sets any slot.

With every <SoundAction> slot at -1 the client plays the first five the way it does for its own monsters, and S6..S10 are never heard:

Slot Plays
S1 S2 now and then while it stands or walks, one of the two at random
S3 S4 when it attacks and when it is hit, one of the two at random
S5 when it dies

Fill both of a pair or just the first - a missing partner repeats the one given.

With any <SoundAction> slot set every slot is yours: it names the animation each slot plays on, and the client's own monster sounds are off for this monster.

<Sound       S1="10" S2="11" S3="12" S4="13" S5="14" S6="15" S7="16" S8="-1" S9="-1" S10="-1" />
<SoundAction S1="0"  S2="2"  S3="3"  S4="3"  S5="6"  S6="5"  S7="8"  S8="-1" S9="-1" S10="-1" />

Here 10 plays when it stops, 11 when it walks, 12 or 13 at random on the first attack, 14 when it dies, 15 when it is hit and 16 on the third attack. The numbers are the actions.

  • A sound plays when its animation starts - also when a script calls o:SetAction.
  • Several slots on one animation take turns at random.
  • An animation the model does not have never starts, so its sound never plays.
  • Appear (7) and Run (10) start on a custom monster only when a script sets them.
  • Stop1 (0) plays on every stop, including the one after each attack - the client's own idle sounds are much rarer.

<SoundList> resolves the ids to files, from one folder given on the element.

Element Attribute Meaning
<SoundList> Path folder holding the .wav files, with a trailing backslash - Data\Custom\Monsters\Sound\ when left out
<Sound> ID the id referenced from S1..S10, 0..1999
<Sound> File the file name

<Glow>

The no-script way to colour a monster.

<Glow ColorR="0" ColorG="255" ColorB="255" Power="3.0" Luminosity="1" />
Attribute Meaning
ColorR ColorG ColorB body colour, 0..255; -1 for none
Power brightness of the body pass, default 1.0
Luminosity 0 a steady colour; 1 makes the colour flicker and mesh 0 pulse, like the game's golden monsters - it takes over BlendMesh

A monster only glows when ColorR/G/B hold a colour. With all three at -1 the body colour is left alone. A value outside -1..255 stops the client with an error.

<Render>

Picks how the body is drawn.

<Render Flags="CHROME2|LIGHTMAP|BRIGHT" />

Pipe separated. Whitespace is ignored, repeated separators collapse, names are not case sensitive, and an unrecognised name is skipped silently - so a typo quietly drops one flag. Without <Render>, or with an empty or entirely unrecognised list, a monster with a <Glow> colour is drawn CHROME|BRIGHT and one without is drawn TEXTURE.

The full flag list and what each one does is in Render flags - they are the same values a script uses.

Two traps worth repeating here:

  • BRIGHT, DARK and LIGHTMAP are alternatives. The first one found wins, in that order, and the rest are ignored.
  • LIGHTMAP throws the body colour away. With it in the mask, <Glow ColorR/G/B> has no effect at all. If a glow colour does not show up, this is almost always why.

npcName.txt

Every monster needs a row in npcName.txt, inside Lang.mpr. The whole client takes the monster's name from it, name plate included, and the row sets how high the name plate and the HP bar sit. Without a row the monster has no name, and the name plate and the HP bar end up in the wrong place. The same row switches the elemental effect on.

//[EN] MonsterIndex	Ypose	MarkValue	ShowElemEffect	MeleeType	RangedType	MagicType	NPCName
2499	400	0	1	0	0	0	"Pharaoh"

One row per monster, columns separated by tabs, the name in quotes. Lines starting with // are comments and the file ends with a line reading end.

Column Meaning
MonsterIndex the monster id, the same as <Monster ID>
Ypose height of the name plate above the model, in world units - 0 leaves the client default
MarkValue 0 or 1, handed to the name plate as a mark. Only the four Chaos Goblins have 1 - leave it at 0
ShowElemEffect 1 shows the monster's elemental effect, 0 hides it. The element itself comes from the server (PentagramMainAttrib in MonsterList.xml); the game's own elemental monsters have 1
MeleeType RangedType MagicType 0 on every row of the game's own file, and nothing in the client uses them - leave them at 0
NPCName the name, in quotes

There is no limit on the number of rows or on the id - the client keeps the rows by monster id, so a row for 2499 works. The // MAX : 1024 line at the top of the game's own file is only a comment. Keep one row per id.

A talking NPC

NPC="1" makes the client talk to the monster instead of attacking it. The server has to agree: it decides which classes are NPCs from a list of its own, and a custom id is not on it. Mark the class in the server's MonsterList.xml:

<Monster Index="2493" IsTrap="0" IsNpc="1" NpcType="2" Name="Warehouse Keeper" Level="2" ... />
Attribute Meaning
IsNpc 1 makes the class an NPC - talked to, not fought, and passed to Lua onNpcTalk. The server's own NPC classes need no IsNpc
NpcType the game's own window it opens when onNpcTalk returns 0 - 0 none, 2 warehouse, 3 chaos machine. Any other value is logged and read as 0. Only with IsNpc

Without IsNpc a class placed in MonsterSpawn.xml stays a monster: talking to it never reaches onNpcTalk, area skills and summons hit it, and a talk leaves the player looking at an empty shop. A ShopList.xml NPC is made an NPC by the shop system itself.

What talking to it should do decides where it stands:

The NPC should Place it in And
open a Lua window or a client panel MonsterSpawn.xml bind it with NpcWindow.Bind
be a warehouse or a chaos machine MonsterSpawn.xml NpcType 2 or 3
be a shop ShopList.xml, not MonsterSpawn.xml a shop item file, the same as the game's own shops
run a script of your own MonsterSpawn.xml handle it in onNpcTalk

The shop system spawns a shop NPC from its ShopList.xml row and finds its shop by class, map and position. One also placed in MonsterSpawn.xml stands there twice, and that copy is no shop.

A model from Data\NPC\ can be reused by file (Path="Data\NPC\" File="Smith01"), but check it has meshes of its own. Some of the game's NPCs are assembled from part models, and their main .bmd is a bare skeleton that draws nothing - Man01 is one.

Config or script, not both

A monster with a script that registers OnRender is driven entirely from Lua, and the <Glow> path is skipped - so the two can never fight over the body colour. A script with only OnCreate still gets the config glow; to suppress it, register an OnRender that does nothing.

<Render Flags> and <Glow> are the config half of the same two values a script sets as m.renderFlags and m.bright.

Adding the script

Data\Custom\Scripts\Game\Monsters\2498.gsc      production, encrypted
Data\Custom\Scripts\Game\Monsters\2498.lua      dev mode only

The file name is the monster id. Nothing registers it - the client looks for a file of that name when the monster is set up.

Checklist

  • model and its textures under Path, File without the extension
  • <Monster ID> added to CustomMonster.xml and encrypted to .xdc
  • sound ids referenced in <Sound> exist in <SoundList>
  • with <SoundAction> slots set, each points at an animation the model has
  • monster defined on the server with the same id
  • an NPC: NPC="1" here, IsNpc="1" in MonsterList.xml, and its spot in MonsterSpawn.xml or ShopList.xml
  • a row for the id in npcName.txt in Lang.mpr
  • script named after the monster id, if it needs one

See Also

Clone this wiki locally