Repository navigation
CustomMonsters Adding
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.
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.
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.
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\.
| 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
|
| 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.
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)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.
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> 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) andRun(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 |
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.
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,DARKandLIGHTMAPare alternatives. The first one found wins, in that order, and the rest are ignored. -
LIGHTMAPthrows 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.
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.
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.
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.
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.
- model and its textures under
Path,Filewithout the extension -
<Monster ID>added toCustomMonster.xmland 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"inMonsterList.xml, and its spot inMonsterSpawn.xmlorShopList.xml - a row for the id in
npcName.txtinLang.mpr - script named after the monster id, if it needs one
- Monster scripts - handlers, render passes, attacks
-
Monster skills - player skills on monsters,
SkillEffects.lua -
Render flags - what each
<Render Flags>name does - Objects and the sandbox - the object API a script uses
- Custom Maps & Monsters - turning the plugin on
- A window from an NPC - Lua UI windows and client panels opened by talking
MuLua Scripting Plugin | Home
🖼️ Lua UI
- File map
- Creating windows
- Client and server
- Designing a window
- Protocol
- Scrolling and scrollbars
- Controls
- Custom text
- Text input
- Debug console
- Bitmap Slicer
- Encrypting scripts
Maps
Monsters
Shared
Version 1.14 | 2026-09-09