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!

[xmas release] V Matrix System (5th Job)

Newbie Spellweaver
Joined
Sep 11, 2016
Messages
59
Reaction score
81
Here's the 5th job V Matrix system packets and handlers. (Version 179).
Note that I have taken out code from massive functions in my source to give you just the related code, you will have to add them to wherever you handle or decide to handle the matrix related code.

OutPacket Opcodes
PHP:
VMatrixUpdate(383)
NodeStoneResult(384)

InPacket Opcodes
PHP:
UserUpdateMatrix(569)

VMatrixRecord

PHP:
/**
 *
 * @author PacketBakery
 */

public class VMatrixRecord {
    public int nState, nIconID, nSkillID, nSkillID2, nSkillID3, nSLV, nMasterLev, nRow, nExp;
    public long dwCRC;
    public FileTime ftExpirationDate = FileTime.GetPermanentTime();
    public static int Disassembled = 0, Inactive = 1, Active = 2;

    public void Encode(OutPacket oPacket) {        
        oPacket.EncodeLong(dwCRC);
        oPacket.EncodeInt(this.nIconID);
        oPacket.EncodeInt(this.nSLV);
        oPacket.EncodeInt(this.nExp);
        oPacket.EncodeInt(this.nState);
        oPacket.EncodeInt(this.nSkillID);
        oPacket.EncodeInt(this.nSkillID2);
        oPacket.EncodeInt(this.nSkillID3);      
        oPacket.EncodeInt(this.ftExpirationDate.dwLowTime);
        oPacket.EncodeInt(this.ftExpirationDate.dwHighTime);
    }
}

WvsContext
PHP:
public static OutPacket OnVMatrixUpdate(List<VMatrixRecord> aVMatrixRecord, boolean bRemove) {
    OutPacket oPacket = new OutPacket(LoopbackPacket.VMatrixUpdate);
    oPacket.EncodeInt(aVMatrixRecord.size());
    for (VMatrixRecord pMatrix : aVMatrixRecord) {
        pMatrix.Encode(oPacket);
    }
    oPacket.EncodeBoolean(bRemove);
    return oPacket;
}
    
public static OutPacket OnNodeStoneResult(int nItemID, int nSkillID1, int nSkillID2, int nSkillID3) {
    OutPacket oPacket = new OutPacket(LoopbackPacket.NodeStoneResult);
    oPacket.EncodeInt(nItemID);
    oPacket.EncodeInt(1);
    oPacket.EncodeInt(nSkillID1);
    oPacket.EncodeInt(nSkillID2);
    oPacket.EncodeInt(nSkillID3);
    return oPacket;
}

OnPacket is where you handle functions based on what packet is received.
PHP:
public void OnPacket(final ClientPacket eType, final InPacket iPacket) {
    switch (eType) {
      case UserUpdateMatrix:
          OnUpdateMatrix(iPacket);
          break;
      case UserScriptItemUseRequest:
          OnScriptItemUseRequest(iPacket);
          break;
    }
}

OnScriptItemUseRequest should be in UseScriptedItemHandler or a function in PlayerHandler
called UseScriptedItem or similar. Just add the case to your function / handler and adjust variable names.

PHP:
public void OnScriptItemUseRequest(InPacket iPacket) {
        int dwCRC = iPacket.DecodeInt();
        short nSlot = iPacket.DecodeShort();
        int nItemID = iPacket.DecodeInt();
        int nCount = iPacket.DecodeInt();
        switch(nItemID) {
            case Consume.Nodestone: // ItemID: 2435902
                VMatrixRecord pRecord = new VMatrixRecord();
                pRecord.nState = VMatrixRecord.Inactive;
                /*
                 * This is where you get to do work yourself.
                 * Write your own formula for what skills you want to gain by using the nodestone
                 * and set the variables in MatrixRecord
                */                
                this.GetCharacterData().aVMatrixRecord.add(pRecord);          
                SendPacket(WvsContext.OnNodeStoneResult(nItemID, pRecord.nSkillID, pRecord.nSkillID2, pRecord.nSkillID3);
                this.GetCharacterData().RemoveItem(InventoryType.Consume, nSlot, nCount);       
                Message.Encode(new Message(MessageResult.SystemMessage, "You used the Nodestone and got a Core."));
                SendCharacterStat(1, 0);
                UserEffect.EncodeForLocal(new UserEffect(UserEffect.Quest, Consume.Nodestone));
                break;
        }
    }

OnUpdateMatrix you will probably add to a PlayerHandler or its own class (if you use a packet processor method)

PHP:
public void OnUpdateMatrix(InPacket iPacket) {
        int nState = iPacket.DecodeInt();
        int nSlot = iPacket.DecodeInt();
        int nNode = iPacket.DecodeInt();
        VMatrixRecord pRecord = this.GetCharacterData().aVMatrixRecord.get(nSlot);
        if (pRecord != null) {
            if (nState == 1 || nState == 2) {
                pRecord.nState = VMatrixRecord.Active;
            } else if(nState == 3) {
                pRecord.nState = VMatrixRecord.Disassembled;
            }
            if(pRecord.nState != VMatrixRecord.Active) {
                this.GetCharacterData().mSkillRecord.remove(pRecord.nSkillID);
                this.GetCharacterData().mSkillExpired.remove(pRecord.nSkillID);
                this.GetCharacterData().mSkillMasterLev.remove(pRecord.nSkillID);
            } else {
                this.GetCharacterData().mSkillRecord.put(pRecord.nSkillID, pRecord.nSLV);
                this.GetCharacterData().mSkillExpired.put(pRecord.nSkillID, pRecord.ftExpirationDate);
                this.GetCharacterData().mSkillMasterLev.put(pRecord.nSkillID, pRecord.nMasterLev);
            }
            SendPacket(WvsContext.OnVMatrixUpdate(this.GetCharacterData().aVMatrixRecord, nState == 2 || nState == 3));
        }
    }

Now for some things you are missing from these functions... Wherever you put your CharacterData, you will need these.
Note: You probably already do SkillRecord your own way so you will have to adjust the code to your method.

PHP:
public List<VMatrixRecord> aVMatrixRecord = new ArrayList<>();
public Map<Integer, Integer> mSkillRecord = new HashMap<>();
public Map<Integer, Integer> mSkillExpired = new HashMap<>();
public Map<Integer, Integer> mSkillMasterLev = new HashMap<>();

You will have to replace
PHP:
this.GetCharacterData().RemoveItem(InventoryType.Consume, nSlot, nCount);
with however you remove an item from your inventory.

You will have to replace the
PHP:
Message.Encode(new Message(MessageResult.SystemMessage, "You used the Nodestone and got a Core."));
with however you send a "Message" packet. I believe odin calls this SHOW_STATUS_INFO.
The MessageResult is SystemMessage aka 12.

You will have to replace
PHP:
SendCharacterStat(1, 0);
with however you send the ChangeStat Packet. I believe odin calls this packet updatePlayerStats or something similar.

You will have to replace
PHP:
UserEffect.EncodeForLocal(new UserEffect(UserEffect.Quest, Consume.Nodestone));
with however you send the UserEffectLocal packet, I believe odin has about 5000 packets for it, however it's part of 1 packet in a switch which looks like this. (Replace below with however you would do it)
Note: nUserEffect is Quest aka 8.

PHP:
public void EncodeForLocal() {
    OutPacket oPacket = new OutPacket(LoopbackPacket.UserEffectLocal);
    oPacket.EncodeByte(this.nUserEffect);
    switch(this.nUserEffect) {
        case Quest:
            oPacket.EncodeByte(this.aQuestItem.size());
            if(this.aQuestItem.size().isEmpty()) {
                oPacket.EncodeString(this.sScrMsg);
                oPacket.EncodeInt(this.nEffect);
            } else {
                for(ItemPickup pItem : this.aQuestItem) {
                    oPacket.EncodeInt(pItem.nItemID);
                    oPacket.EncodeInt(pItem.nAmount);
                }
            }
        break;
      }
}

Now you also need to add a conditional near the bottom in SetField so your node matrix is updated when you migrate.

SetField (CharacterData::Encode Portion)
Note: This is very close to the bottom of the function, DBChar.WildHunterInfo is 0x200000 so just find that and place this in the conditonal. (It is below EquipExt and above CharacterPotentialSkill)

PHP:
public void Encode(OutPacket oPacket, long dbCharFlag, boolean bBackwardUpdate) {
    if ((dbCharFlag & DBChar.WildHunterInfo.Get()) > 0) {
        oPacket.EncodeInt(this.aVMatrixRecord.size());
        for (VMatrixRecord pRecord : this.aVMatrixRecord) {
            pRecord.Encode(oPacket);
        }
    }
}

I don't know if I forgot anything, it took so long to write this and I believe I got everything. Anyway enjoy.
 
Last edited:
Skilled Illusionist
Joined
Apr 26, 2015
Messages
302
Reaction score
77
Can I get the release of full server source code?
 
Initiate Mage
Joined
Nov 6, 2016
Messages
3
Reaction score
0
Hello, could you elaborate more at specifics ? I know It's pseudo-java or perhaps something converted:

In OnScriptItemUseReques, OnUpdateMatrix you wrote:
Code:
this.GetCharacterData()
one can only assume this is a reference to user/player/character, is it the case ?
As well as for:
Code:
Message.Encode(new Message(MessageResult.SystemMessage, "You used the Nodestone and got a Core."));
I assume Message is an inner-class ?

...
 
Joined
Apr 10, 2008
Messages
4,087
Reaction score
1,264
Hello, could you elaborate more at specifics ? I know It's pseudo-java or perhaps something converted:

In OnScriptItemUseReques, OnUpdateMatrix you wrote:
Code:
this.GetCharacterData()
one can only assume this is a reference to user/player/character, is it the case ?
As well as for:
Code:
Message.Encode(new Message(MessageResult.SystemMessage, "You used the Nodestone and got a Core."));
I assume Message is an inner-class ?

...

He's using his own files inspired by Neckson coding conventions so that's why you don't get it.

PHP:
this.GetCharacterData()

refers to the user's CharacterData class, which contains all the "extras" aka data. aVMatrixRecord is just a reference to the class he mentioned at the beginning of his post, so just create the same member in your MapleCharacter class, or better- separate the character's looks/items/etcetera from that big butt class.

As for:

PHP:
Message.Encode(new Message(MessageResult.SystemMessage, "You used the Nodestone and got a Core."));

This probably jsut sends a user a "system message", aka either red or blue notice. So you can use "serverMessage" packet from CWvsContext.
 
Initiate Mage
Joined
Nov 6, 2016
Messages
3
Reaction score
0
He's using his own files inspired by Neckson coding conventions so that's why you don't get it.

PHP:
this.GetCharacterData()

refers to the user's CharacterData class, which contains all the "extras" aka data. aVMatrixRecord is just a reference to the class he mentioned at the beginning of his post, so just create the same member in your MapleCharacter class, or better- separate the character's looks/items/etcetera from that big butt class.

As for:

PHP:
Message.Encode(new Message(MessageResult.SystemMessage, "You used the Nodestone and got a Core."));

This probably jsut sends a user a "system message", aka either red or blue notice. So you can use "serverMessage" packet from CWvsContext.

I don't follow Odin's structure either, I was just wondering why 'this' reference used in the handler.
For CharacterData, I guess it is reasonable to split the structure from user/player/character, albeit client-side structure.
That's only partially the structure: , I do not assume the client decodes every bit of information to this ?
 
Joined
Apr 10, 2008
Messages
4,087
Reaction score
1,264
I don't follow Odin's structure either, I was just wondering why 'this' reference used in the handler.
For CharacterData, I guess it is reasonable to split the structure from user/player/character, albeit client-side structure.
That's only partially the structure: , I do not assume the client decodes every bit of information to this ?

Yeah I guess so. The this keyword is because GetCharacterData is a member of User, and the packets are probably handled inside that class.
 
Custom Title Activated
Loyal Member
Joined
Jan 18, 2010
Messages
3,109
Reaction score
1,139
Hello, could you elaborate more at specifics ? I know It's pseudo-java or perhaps something converted:

In OnScriptItemUseReques, OnUpdateMatrix you wrote:
Code:
this.GetCharacterData()
one can only assume this is a reference to user/player/character, is it the case ?
As well as for:
Code:
Message.Encode(new Message(MessageResult.SystemMessage, "You used the Nodestone and got a Core."));
I assume Message is an inner-class ?

...

Correct. Following Nexon's styles, User-related handling belongs in CUser, so this class would be inside of his User class itself. An incoming packet would go from ClientSocket.ProcessPacket, filter down to ClientSocket.ProcessUserPacket, and if the pUser (User reference) isn't null, it should call to pUser.OnPacket, which will hit both of the provided functions from a switch. CharacterData is the portion of User encoded within SetField.

As for Message.Encode, that shouldn't be an inner-class within User, it should just be a Message class. The way Nexon handles "Message" (or CWvsContext::OnMessage) is through 10 different functions leading to a major function with massive amounts of parameters. To simplify it, we just made a class. In this case the MessageResult enum points to SystemMessage, which is going to display the common "Red" notice equivalent to ServerNotice 5 in OdinMS. It is actually its own function (SendSystemMessage to be exact, like Fraysa was pointing out), but he just sends the packet itself here. However.. assuming the Encode is a standard static OutPacket returned function like it's supposed to be, then it should be passed into SendPacket, as that won't do anything at all currently -- Encode just means to write data within that class.

As for your question towards the client reading in CharacterData, not all but most of all that data is indeed read in entirely by the client within CharacterData::Decode (the rest of the variables are handled from the data you do send/read). You can control how much you want to Encode or Decode through the "DBChar" flag which is the first 8-byte flag in the Encode packet. In higher versions there are some classes that are encoded always, regardless of flag though..sadly.

From a server-end standpoint, you may refer to BMS v53's files. This will help explain the similarities between Nexon's client-end and server-end if you wish to know more. The point of CharacterData is to simply split up a character's Items, Skill Data, and their Records (Mini Game information, Rings/Marriage information, etc) from the main User class and have it all stored and encoded elsewhere. CharacterData also holds an additional class reference for GW_CharacterStat which holds the character's stat information, and all of this is the data that Nexon saves/loads from their databases (plus, this is the information they send between servers to migrate your character). This is why it makes sense to why they store the matrix here as it was done. ;P
 
Newbie Spellweaver
Joined
Sep 11, 2016
Messages
59
Reaction score
81
Hello, could you elaborate more at specifics ? I know It's pseudo-java or perhaps something converted:

In OnScriptItemUseReques, OnUpdateMatrix you wrote:
Code:
this.GetCharacterData()
one can only assume this is a reference to user/player/character, is it the case ?
As well as for:
Code:
Message.Encode(new Message(MessageResult.SystemMessage, "You used the Nodestone and got a Core."));
I assume Message is an inner-class ?

...
It seems the people above me explained it pretty well, GetCharacterData is just retrieving the reference of my CharacterData class via an initialized variable in User. All of my User related decode packets (handlers) are inside my user class.

Message is not an inner class but it's own class. It is a switch case encode for the packet using opcode "Message".
 
Back
Top