Welcome!

Join our community of MMO enthusiasts and game developers! By registering, you'll gain access to discussions on the latest developments in MMO server files and collaborate with like-minded individuals. Join us today and unlock the potential of MMO server development!

Join Today!

Effect delay of Mob skills

Skilled Illusionist
Joined
Jul 17, 2010
Messages
333
Reaction score
165
It seems that most released servers don't use proper delay for mobskill, so I'll provide a little information I know.

*Attention* I am no good at English so my sentences may be terrible.


First, check if the mobskill effect(buff,debuff,spawn,etc...) is not applied before the actual motion.

in MobHandler(MOVE_LIFE)
PHP:
int unk = slea.readInt();
this should be
PHP:
int skillID = slea.readByte() & 0xFF;//skill_1
int skillLv = slea.readByte() & 0xFF;//skill_2
short option = slea.readShort();//skill_3,skill_4
The applyEffect has to be applied to 'skillID/skillLv' rather than 'realskill/level'.
(mob movement's flow chart)
Code:
[R]:Controller's client sends. [S]:Server sends.

//monster spawned
[S] SPAWN_MONSTER //for all players in the map
[S] SPAWN_MONSTER_CONTROLLER //for one player=controller
↓
[R] MOVE_LIFE:1 //current movement
↓
[S] MOVE_MONSTER_RESPONSE:1 //request next movement; enable attacks or a specified skill/(attack)
[S] MOVE_MONSTER:1 //current movement, for all players except the controller
↓
[R] MOVE_LIFE:2
↓
[S] MOVE_MONSTER_RESPONSE:2
[S] MOVE_MONSTER:2
↓
...
This 'option' mean duration of skill motion (when it is attack motion, it'll be used for shootAttack or something idk.)


Then, add this 'option' (effectDelay) to codes (monster buff, player debuff, spawn, mist) through the applyEffect. (I'll omit explanation of this)

in giveDebuff(v117)
PHP:
    mplew.writeInt(duration);
    mplew.writeZeroBytes(3);//idk
    mplew.writeShort(effectDelay);
in applyMonsterStatus(v117)
PHP:
    mplew.writeShort(skil.getSkillLevel());
    mplew.writeShort((int) (skil.getDuration() / 500));////duration
    mplew.writeShort(effectDelay);
for already buffed
PHP:
    if (ms.isMonsterSkill()) {
        mplew.writeShort(ms.getMobSkill().getSkillId());
        mplew.writeShort(ms.getMobSkill().getSkillLevel());
    } else if (ms.getSkill() > 0) {
        mplew.writeInt(ms.getSkill());
    }
    mplew.writeShort((int) ((ms.getCancelTask() - System.currentTimeMillis()) / 500));//duration
    mplew.writeShort(0);
for damage reflect
PHP:
        mplew.writeShort(skil.getSkillLevel());
        mplew.writeShort((int) (skil.getDuration() / 500));////duration
    }
    for (Integer ref : reflection) {
        mplew.writeInt(ref);
    }
    mplew.writeInt(0);
    mplew.writeInt(0);
    mplew.writeShort(effectDelay);
...but buff/debuff's effect delay is applied only to the effect visually, so please do not expect. (e.g. Pinkbean or Cygnus's DR)

in spawnMonster
PHP:
        mplew.write(summonType);//0+:summon effect, -2:fade in, -3:after remove link mob, -4:fake
        if ((summonType == -3) || (summonType >= 0)) {
            mplew.writeInt(summonOption);//summonType= -3:link mob oid, 0+:effect delay
        }

in spawnMist
PHP:
        mplew.write(mist.getSkillLevel());
        mplew.writeShort(mist.getSkillDelay());//effectDelay / 100 (e.g. 3240ms -> 32)
--Edit--
sample video

----


After Von Leon released(?), they started using 'skillAfter' for general skills.
Server sends MobSkillDelay (contains oid, sid, slv, delay, option), and after a specified delay, will receive MobSkillDelayEnd (contains oid, sid, slv, option) from Client.
This is the difference between Pinkbean, Cygnus's DR and Vonleon, Arkarium's DR.


[Send] MobSkillDelay(v95+?) : Opcode is before MONSTER_PROPERTIES? or around it
(for older version)
PHP:
    public static byte[] MobSkillDelay(int objectId, int skillID, int skillLv, int skillAfter, int option) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();

        mplew.writeShort(SendPacketOpcode.MOB_SKILL_DELAY.getValue());
        mplew.writeInt(objectId);
        mplew.writeInt(skillAfter);
        mplew.writeInt(skillID);
        mplew.writeInt(skillLv);
        mplew.writeInt(option);

        return mplew.getPacket();
    }
(for newer version *not checked)
PHP:
    public static byte[] MobSkillDelay(int objectId, int skillID, int skillLv, int skillAfter) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();

        mplew.writeShort(SendPacketOpcode.MOB_SKILL_DELAY.getValue());
        mplew.writeInt(objectId);
        mplew.writeInt(skillAfter);
        mplew.writeInt(skillID);
        mplew.writeInt(skillLv);
        mplew.writeInt(0);//rect size

        return mplew.getPacket();
    }
(for latest version)
PHP:
    public static byte[] MobSkillDelay(int objectId, int skillID, int skillLv, int skillAfter, short sequenceDelay, List<Rectangle> skillRectInfo) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();

        mplew.writeShort(SendPacketOpcode.MOB_SKILL_DELAY.getValue());
        mplew.writeInt(objectId);
        mplew.writeInt(skillAfter);
        mplew.writeInt(skillID);
        mplew.writeInt(skillLv);
        if (skillRectInfo== null || skillRectInfo.isEmpty()) {
            mplew.writeInt(0);
            mplew.writeInt(0);
        } else {
            mplew.writeInt(sequenceDelay);
            mplew.writeInt(skillRectInfo.size());
            for (Rectangle rect : skillRectInfo) {
                mplew.writeInt(rect.x);
                mplew.writeInt(rect.y);
                mplew.writeInt(rect.x + rect.width);
                mplew.writeInt(rect.y + rect.height);
            }
        }
        return mplew.getPacket();
    }

[Recv] MobSkillDelayEnd(v95+?) : Opcode is between HYPNOTIZE_DMG and MOB_BOMB? or around them
(for older version)
PHP:
    public static final void MobSkillDelayEnd(LittleEndianAccessor slea, MapleCharacter chr) {
        MapleMonster monster = chr.getMap().getMonsterByOid(slea.readInt());
        if (monster != null) {
            int skillID = slea.readInt();
            int skillLv = slea.readInt();
            int option = slea.readInt();
            if (monster.hasSkill(skillID, skillLv)) {
                MobSkillFactory.getMobSkill(skillID, skillLv).applyEffect(chr, monster, true, (short) option);
            }
        }
    }
(for newer version *not checked)
PHP:
    public static final void MobSkillDelayEnd(LittleEndianAccessor slea, MapleCharacter chr) {
        MapleMonster monster = chr.getMap().getMonsterByOid(slea.readInt());
        if (monster != null) {
            int skillID = slea.readInt();
            int skillLv = slea.readInt();
            if (monster.hasSkill(skillID, skillLv)) {
                MobSkillFactory.getMobSkill(skillID, skillLv).applyEffect(chr, monster, true);
            }
        }
    }
(for latest version)
PHP:
    public static final void MobSkillDelayEnd(LittleEndianAccessor slea, MapleCharacter chr) {
        MapleMonster monster = chr.getMap().getMonsterByOid(slea.readInt());
        if (monster != null) {
            int skillID = slea.readInt();
            int skillLv = slea.readInt();
            short remainCount = 0;
            if (slea.readBool()) {
                remainCount = (short) slea.readInt();
            }
            if (skillID == monster.getSkillCommand() && skillLv == monster.getSLV()) {
                MobSkillFactory.getMobSkill(skillID, skillLv).applyEffect(c.getPlayer(), monster, remainCount);
                if (remainCount <= 1) {
                    monster.resetSkillCommand();
                }
            }
        }
    }

Then, make MobSkillInfo for each mob's skills (like MobAttackInfo). (omitted agein.)

Finally, edit your MobHandler so that it applies effectdelay properly.
(this is just an example)
(old ver.)
PHP:
    final int nAction = skill >> 1;
    final int skillIdx = nAction - 22;//at v117
~~~
    if (skillIdx >= 0 && skillIdx <= 10) {//at v117
        MobSkillInfo msi = monster.getStats().getMobSkill(skillIdx);
        if (msi != null) {
            if (msi.getSkillAfter() > 0) {//Note:should except Teleport (instead, send Teleport packet)
                c.getSession().write(MobPacket.MobSkillDelay(oid, skillID, skillLv, msi.getSkillAfter()/*, msi.getEffectAfter()*/));
            } else if (msi.getEffectAfter() <= 0) {
                msi.getMobSkill().applyEffect(chr, monster, true, option);
            } else {//idk
                msi.getMobSkill().applyEffect(chr, monster, true, msi.getEffectAfter());
            }
        }
    }
(new ver. for v160+)
PHP:
    final int nAction = skill >> 1;//or also called 'action'
    final int skillIdx = nAction - 30;
~~~
    if (skillIdx >= 0 && skillIdx <= 16) {
        MobSkillInfo msi = monster.getStats().getMobSkill(skillIdx);
        if (msi!= null) {
            if (msi.getAfterAttack() != -1) {
                monster.getMap().broadcastMessage(MobPacket.setAfterAttack(monster.getObjectId(), msi.getAfterAttack(), nAction, (skill & 1) != 0));
            }
            if (msi.getSkillAfter() > 0) {
                msi.getMobSkill().setMobSkillDelay(c.getPlayer(), monster, msi.getSkillAfter(),  option);
                return;
            }
            monster.resetSkillCommand();
            msi.getMobSkill().applyEffect(c.getPlayer(), monster, true, option);
        }
    }
now you have the proper effect delay of mobskills. ;)

Thank you for reading :)
 
Last edited:
Custom Title Activated
Loyal Member
Joined
Jan 18, 2010
Messages
3,109
Reaction score
1,139
Nice work! It looks like someone knows what Nexon is doing! You even have the correct summon options when encoding initdata, perfect. Did you reference the BMS leak for any of this by any chance?

Rather than spawnType and link, nexon calls them these (and reads an int and shifts the data instead of individually reading bytes, but same diff):
Code:
if (pMob.m_nSummonType == -3 || pMob.m_nSummonType >= 0)
            oPacket.Encode4(pMob.m_dwSummonOption);

Skill delays aren't in v83 though.. sucks :(
 
Skilled Illusionist
Joined
Jul 17, 2010
Messages
333
Reaction score
165
Looks cool. Well done.
Thanks.

Nice work! It looks like someone knows what Nexon is doing! You even have the correct summon options when encoding initdata, perfect. Did you reference the BMS leak for any of this by any chance?

Rather than spawnType and link, nexon calls them these (and reads an int and shifts the data instead of individually reading bytes, but same diff):
Code:
if (pMob.m_nSummonType == -3 || pMob.m_nSummonType >= 0)
            oPacket.Encode4(pMob.m_dwSummonOption);

Skill delays aren't in v83 though.. sucks :(
Thanks. For MobHandler, yes.

Oh, I forgot to refer to the MobEnterField from BMS's leaked files.

Yes, sadly we have to use Timer, instead :(
 
Skilled Illusionist
Joined
Jul 17, 2010
Messages
333
Reaction score
165
Updated main post; adapted for AreaSequenceDelayAttack
this is an example
MobSkill
Code:
    public void setMobSkillDelay(MapleCharacter player, MapleMonster monster, short skillAfter, short option) {
        List<Rectangle> skillRectInfo = null;
        switch(skillID) {
            case 226: {
                option = 0;
                MapleMap map = monster.getMap();
                skillRectInfo = map.makeRandomSplitAreas(monster.getPosition(), lt.y, rb.x - lt.x, rb.y - lt.y, 3, true);
                map.setTempAreas(monster.getId(), skillRectInfo);
                break;
            }
            case 230: {
                option = 900;//areaSequenceDelay
                //6 : areaSequenceRandomSplit
                MapleMap map = monster.getMap();
                skillRectInfo = map.makeRandomSplitAreas(monster.getPosition(), lt.y, rb.x - lt.x, rb.y - lt.y, 10, false);
                Collections.shuffle(skillRectInfo);
                map.setTempAreas(monster.getId(), skillRectInfo);
                break;
            }
            default:
                break;
        }
        if (this.skillAfter > 0) {
            skillAfter = this.skillAfter;//load 'skillAfter' from MobSkill.img
        }
        player.sendPacket(MobPacket.MobSkillDelay(monster.getId(), skillID, skillLv, skillAfter, option, skillRectInfo));
    }


    public void applyEffect(MapleCharacter player, MapleMonster monster, boolean skill) {
        applyEffect(player, monster, skill, (short) 0);
    }
    public void applyEffect(MapleCharacter player, MapleMonster monster, short remainCount) {
        applyEffect(player, monster, true, remainCount);
    }
    public void applyEffect(MapleCharacter player, MapleMonster monster, boolean skill, short option) {
        ~snip~
        switch(skillID) {
            ~snip~
            case 226://TOOS
            {
                List<Rectangle> skillRectInfo = monster.getMap().getTempAreas(monster.getId());
                //Use option as remainCount
                for (int i = 0, n = option == 0 ? skillRectInfo.size() : 1; i < n; i++) {
                    Rectangle rect = skillRectInfo.remove(0);
                    for (MapleCharacter chr : monster.getMap().getAllCharacters()) {
                        if (rect.contains(chr.getPosition().x, chr.getPosition().y - 1)) {
                            monster.getMap().broadcastPacket(CField.userTossedBySkill(chr.getId(), monster.getId(), skillID, skillLv, x), false);
                        }
                    }
                }
                break;
            }
            case 230://FIRE_AT_RANDOM_ATTACK
            {
                List<Rectangle> skillRectInfo = monster.getMap().getTempAreas(monster.getId());
                //Use option as remainCount
                for (int i = 0, n = option == 0 ? skillRectInfo.size() : 1; i < n; i++) {
                    Rectangle rect = skillRectInfo.remove(0);
                    for (MapleCharacter chr : monster.getMap().getAllCharacters()) {
                        if (rect.contains(chr.getPosition().x, chr.getPosition().y - 1)) {
                            /*depends on your 'source'*/
                            int damage = (int) (chr.getMaxHp() / 100.0 * fixDamR);//load 'fixDamR' from MobSkill.img
                            chr.sendPacket(EffectPacket.showMobSkillHitEffect(230, skillLv));
                            chr.sendPacket(EffectPacket.incHPEffectEx(-damage));
                            chr.incHP(-damage, false);
                            chr.sendCharacterStat(false, MapleStat.HP.getValue());
                            /**/
                        }
                    }
                }
                break;
            }
        }
    }
*TODO: adjust the code to your source, make a few method that I didn't mention.
image
DJR2pA5 - Effect delay of Mob skills - RaGEZONE Forums
 

Attachments

You must be registered for see attachments list
Junior Spellweaver
Joined
Apr 4, 2015
Messages
113
Reaction score
6
int skillID = slea.readByte() & 0xFF;//skill_1
int skillLv = slea.readByte() & 0xFF;//skill_2
short option = slea.readShort();//skill_3,skill_4

My skillID always return 0, do you know how to make it changed?
 
Junior Spellweaver
Joined
Apr 4, 2015
Messages
113
Reaction score
6
Oh sorry, it works normally. It's just sometime I get the kind of bug. I fix it by taking random skill in mob's skill list (That means I don't use that skillID anymore).
 
Skilled Illusionist
Joined
Jul 17, 2010
Messages
333
Reaction score
165
@learningjava
Hmm...?

Case "attack": the int (skillID/skillLv/option) is usually 0. (except bullet-attack or area-attack or etc...)
Case "skill": skillID/skillLv = skillID/skillLv, option = delay(duration) of the skill motion.

1.You choose a next skill(called as 'realskill/level') by taking random skill in mob's skill list, and set the skill by MOVE_MONSTER_RESPONSE packet.
2.Next time the handler will get MOVE_LIFE packet with skillID/skillLv/(option) that you've set. (Now playing skill motion on the controller's client)
3.Immediately broadcast current movement (MOVE_MONSTER packet) to players on the map (excluding the controller).

So, you should apply effect from 'skillID/skillLv/(option)' rather than 'realskill/level'.


Oh, and here are some examples of the 'unk' int.
Skill
action(aka 'skill'):61 skillID(byte):129 skillLv(byte):13 delay(short):1620
MOB_ACTION code: action >> 1 = 30 -> SKILL1

Attack type0[range]
action:30 unk(int):0
MOB_ACTION code: 15 -> ATTACK3

Attack type1[shoot]
action:33 unk(int):4
MOB_ACTION code: 16 -> ATTACK4

Attack type2(/9)[pierce]
action:29 xpos(short):-435 ypos(short):60
MOB_ACTION code: 14 -> ATTACK2

Attack type3/4(/8)[area]
action:37 hit_area(int, bitmask):174 (area count no.1,2,3,5,7)
MOB_ACTION code: 18 -> ATTACK6

Attack type6[foothold]
action:26 unk(int):0
MOB_ACTION code: 13 -> ATTACK1

Hit
action:14 unk(int):0
MOB_ACTION code: 7 -> HIT1
 
Junior Spellweaver
Joined
Apr 4, 2015
Messages
113
Reaction score
6
Okay so this is what I get:

[R]:Controller's client sends. :Server sends.

MOVE_MONSTER_RESPONSE (skillID: x)
[R] MOVE_LIFE (Return skillID = x)


And what I saw in my MoveMonster is the server always send MOVE_MONSTER_RESPONSE and send MOVE_MONSTER after that. So I think I don't need to use that skillID. Btw, thanks for those values and for explaning to me.
 
Skilled Illusionist
Joined
Jul 17, 2010
Messages
333
Reaction score
165
Okay so this is what I get:
.
And what I saw in my MoveMonster is the server always send MOVE_MONSTER_RESPONSE and send MOVE_MONSTER after that. So I think I don't need to use that skillID. Btw, thanks for those values and for explaning to me.
Ah...Uh...yeah, you don't need to use them, if you don't care about its timing. This explanation is about skill effect (I mean, both visually and functionally) + delay timing problem.
 
Junior Spellweaver
Joined
Apr 4, 2015
Messages
113
Reaction score
6
Ah...Uh...yeah, you don't need to use them, if you don't care about its timing. This explanation is about skill effect (I mean, both visually and functionally) + delay timing problem.

Oh now I get it:

to controller: MOVE_MONSTER_RESPONSE
[R] to server: MOVE_LIFE
to everyone exclude controller: MOVE_MONSTER


It's that the timing you're talking about?
 
Skilled Illusionist
Joined
Jul 17, 2010
Messages
333
Reaction score
165
@learningjava
Code:
Example:  Attack4 -> Skill6

[R]MOVE_LIFE : action:32/33, unk:0  (3900ms)
    [Controller] Displaying Attack4. 
    [Server] Choose a next skill. (Skill6 ID:145,Lv:9)

[S]MOVE_MONSTER_RESPONSE : skill145/9
    [Server] Request the next movement. 
                 (You wanna apply skill effect during the Attack4??)
    [Controller] Receive the request.

([S]MOVE_MONSTER : [Other Players] Displaying Attack4.)

↓(3900ms later)

[R]MOVE_LIFE : action:70/71, skillID:145, skillLv:9, option:2640
    [Controller] Displaying Skill6. 
    [U][Server] Applying skill145/9 effect.[/U] (Then choose a next skill?)
failed ver.
RHWGSw7 - Effect delay of Mob skills - RaGEZONE Forums
 

Attachments

You must be registered for see attachments list
Junior Spellweaver
Joined
Apr 4, 2015
Messages
113
Reaction score
6
Oh now I really get it. Thanks for explaning it to me. I will use this from now on.
 
Newbie Spellweaver
Joined
Jun 15, 2013
Messages
31
Reaction score
0
What does sending the MobSkillDelay packet do exactly? is it supposed to be sent along side MoveMonsterResponse? Or is it just a different way of doing the same thing?
 
Skilled Illusionist
Joined
Jul 17, 2010
Messages
333
Reaction score
165
What does sending the MobSkillDelay packet do exactly? is it supposed to be sent along side MoveMonsterResponse?Or is it just a different way of doing the same thing?
It has the client send response (MobSkillDelayEnd) packet to the server to delay a mob skill's effect to be applied.
Without the packet, you'll need to use "Timer" to delay mob skill's effect...

And yes, you should send it alongside MoveMonsterResponse.
 
Newbie Spellweaver
Joined
Jun 15, 2013
Messages
31
Reaction score
0
Hmm, so writing the Apply_Monster_Status packet with the delay option set in the packet is not enough to delay the buff being applied, this MobSkillDelay must be sent with it to delay applying the buff?
 
Skilled Illusionist
Joined
Jul 17, 2010
Messages
333
Reaction score
165
Hmm, so writing the Apply_Monster_Status packet with the delay option set in the packet is not enough to delay the buff being applied, this MobSkillDelay must be sent with it to delay applying the buff?
It depends on the skill, but almost all modern mob skills have "skillAfter" option, so it should be used.
Old skills such as Horntail's seduce and Pinkbean's damage reflect (that have not "skillAfter" but "effectAfter") don't use this feature, thus it applies the effect right after a monster started the skill motion, even if it showed the "effect" (I mean, buff icon) delayed. However, It's by design.
 
Back
Top