Skip to content

CustomMonsters Skills

wezzzyrek1 edited this page Sep 18, 2026 · 1 revision

Monster skills

A custom monster can use a player skill. The server decides when and whom it hits; the client draws part of it by itself, and a script draws the rest.

Giving a monster a skill

In the server's MonsterList.xml, AttackType = 100 + the skill number. The monster then casts that skill on a random one attack in five - the same chance for every monster - and attacks normally the rest of the time. The Pharaoh (2499) has AttackType="2123", Raining Arrow.

The cast hits the players within 6 tiles of the monster, never other monsters.

In a script, m.skill is the skill number while the monster casts it, and 0 on its ordinary attacks.

What the client draws, and what a script draws

A skill is drawn in two halves:

  • On the targets - the client's own, no script needed. A custom monster's cast goes out with the packet a player's multi-target skill sends, and the client draws these from it: 241-243, 246, 247, 283, 288, 293, 294, 727, 1501, 2004-2006, 2023, 2028-2030, 2040, 2041, 2044-2046, 2059, 2060, 2069-2084, 2091-2096, 2101-2104, 2106, 2107, 2109. A strengthened skill counts as its base one. Any other skill shows nothing on its targets.
  • Round the caster - always the script's. The client draws that half for players only.
Skill Round the caster (script) On the targets (client)
14 Inferno ring of fire, the client's effect found by its file - Helpers.ClientModel(R, "inferno") nothing
2023 Raining Arrow the client's own rain on its target, its id worked out from neighbouring files the blasts

SkillEffects.lua

SkillEffects.lua ships with the sample scripts and holds the caster half, one entry per skill in SkillEffects.Caster. Call it from OnAttack; it draws once an attack, on the attack's first frame (m.attackTime == 1), the only frame every attack reaches:

local SkillEffects = require("SkillEffects")

Monster.OnAttack(function(o, m)
    SkillEffects.OnAttack(o, m)
end)

An entry added to SkillEffects.Caster is drawn by every monster script that calls SkillEffects.OnAttack:

-- Inferno: the ring of fire round the caster - the client's own effect, found by its file.
[14] = function(o, m)
    local inferno = Helpers.ClientModel(R, "inferno")

    if inferno then
        local x, y, z = o:GetPosition()
        o:SpawnEffect(inferno, 0, 0, x, y, z)
    end
end,

Spawning a skill's effect

m:SpawnSkillEffect(type, subType, scale, x, y, z) creates an effect the way the client does for a skill's caster: owned by the monster and carrying its key (subType 0, scale 1 and the monster's position when left out). An effect that looks its caster up by that key shows nothing when spawned any other way.

m:TargetPosition() is where the monster's current target stands - the one the client itself uses for attack effects - or nothing while it has none. SkillEffects lays Raining Arrow's ring there and starts its rain on it, so the arrows fall on the target rather than on the monster.

An effect the client builds in code

Most of the client's effects can be named by their model file with Helpers.ClientModel. A few are built in its code and load no file at all - Raining Arrow's rain is one - so nothing identifies them but a number, and that number moves with every client version. Scripts use none of them: what the client has no file for, they draw themselves out of the pieces that are files.

Raining Arrow is the worked example. The rain itself is code-built, but the ring it lays on the ground is six ordinary models and what falls is Data\Skill\Arrow.bmd, so SkillEffects lays the ring by file and shoots the arrows one by one:

local RAIN =
{
    arrows = 11, spread = 200, height = 200,
    arrow = "arrow",

    rings =
    {
        { model = "elfRing05", subType = 0, scale = 0.5 },
        { model = "elfRing04", subType = 0, scale = 0.25 },
        -- the other four pieces
    },
}

Shooting arrows yourself

m:ShootArrow(model, skill, x, y, z, r, g, b, angleX, angleY, angleZ) shoots one arrow from a spot: model is what falls and skill decides what the client draws where it lands. Colour and angles are yours, so a single call covers a rain, a volley or one shot; left out, the colour is the monster's own light and the angles are the drop (90, a scatter of -10..10, 180).

angleX is worth knowing about: 90 drops the arrow, while 270 sends it climbing until its life runs out, so it never lands and never leaves a mark. The client's own rain does put 270 on the source object it hands to CreateArrow, which works the direction out for itself - an angle on the effect is not the same thing.

An arrow needs somebody to fly at or it dies in the air instead of landing, and it takes that from the server: the packet that tells the client who a multi-target skill hit is kept per monster, and m:ShootArrow gives the arrow the character out of it. So an arrow lands on somebody the server really damaged, which a guess of your own - everybody within so many units - would get wrong. A skill the server sends no such packet for still shoots, but its arrows have nothing to fall towards.

SkillEffects builds Raining Arrow this way: one arrow a 40 ms step, 11 of them, from 200 above the ground, the spot wandering by up to 200 with each.

The client's own arrow skills - the ones it draws without a script - take their arrows from the bow or crossbow in the caster's right hand and shoot none without one. m:ShootArrow does not, since the script names the model. Give an archer monster its bow anyway in CustomMonster.xml - <Weapon Slot="0" Cat="4" Index="0" /> is the Short Bow and its plain arrow; the Pharaoh has that one.

See Also

Clone this wiki locally