An effort to improve episode 6 support for private servers

@Bowie

C++:
CUser::AddApplySkillBuff(user, skill);

Will this sends Packet 0x50D on its own or do I have to do it by myself?

I tried this

C++:
auto charName = args.at(0);  // character name
    auto skillId = std::stoi(args.at(1));
    auto skillLevel = std::stoi(args.at(2));

    auto user = CWorld::FindUser(charName.c_str());
    if (!user) {
        std::stringstream output_format;
        output_format << "Character " << charName << " not found";
        output = output_format.str();
        return 0; // Exit if user is not found
    }

    auto skill = CGameData::GetSkillInfo(skillId, skillLevel);
    if (!skill) {
        std::stringstream output_format;
        output_format << "Skill with ID " << skillId << " and level " << skillLevel << " not found";
        output = output_format.str();
        return 0; // Exit if skill is not found
    }

    // Prepare the SkillUseOutgoing packet
    SkillUseOutgoing outgoing{};
    outgoing.senderId = user->connection.object.id;
    outgoing.targetId = user->connection.object.id;
    outgoing.skillId = skill->skillId;
    outgoing.skillLv = skill->skillLv;

    // Send the skill usage packet to the user
    Helpers::Send(user, &outgoing, sizeof(SkillUseOutgoing));

    // Apply the skill buff
    CUser::AddApplySkillBuff(user, skill);

    EnterCriticalSection(&user->applySkills.cs);

    // Start from the first node after the sentinel tail
    auto node = user->applySkills.sentinel.tail;
    node = node->next;  // Move to the first node
    user->applySkills.sentinel.head = node;

    int index = 0; // Index to track the skill position in the list
    bool skillFound = false;

    while (node && node != user->applySkills.sentinel.tail)
    {
        auto skillIn = reinterpret_cast<CSkill*>(node);

        // Check if the skill matches
        if (skillIn->skillId == skill->skillId && skillIn->skillLv == skill->skillLv)
        {
            skillFound = true;

            // Correctly set the id for addBuff
            SkillApplyOutgoing addBuff{};
            addBuff.id = index; // Send the matching skill's index
            addBuff.skillId = skill->skillId;
            addBuff.skillLv = skill->skillLv;

            Helpers::Send(user, &addBuff, sizeof(SkillApplyOutgoing));
            break;
        }

        node = node->next;  // Move to the next node
        user->applySkills.sentinel.head = node;
        index++; // Increment index
    }

    LeaveCriticalSection(&user->applySkills.cs);

    if (!skillFound)
    {
        std::stringstream output_format;
        output_format << "Skill " << skillId << " (Level " << skillLevel << ") could not be applied for " << charName;
        output = output_format.str();
        return 0; // Exit after reporting error
    }

    // Output a success message
    std::stringstream output_format;
    output_format << "Skill " << skillId << " (Level " << skillLevel << ") triggered for " << charName;
    output = output_format.str();
    return 0;

Upon testing, it says Example

Code:
Skill 287 (Level 1) could not be applied for Character
 
Will this sends Packet 0x50D on its own or do I have to do it by myself?
I don't see it sending that packet.

I think you should use "nostrum" skills and do it this way:

C++:
auto charName = args.at(0);  // character name
auto skillId = std::stoi(args.at(1));
auto skillLevel = std::stoi(args.at(2));

auto user = CWorld::FindUser(charName.c_str());
if (!user) {
    std::stringstream output_format;
    output_format << "Character " << charName << " not found";
    output = output_format.str();
    return 0; // Exit if user is not found
}

auto skill = CGameData::GetSkillInfo(skillId, skillLevel);
if (!skill) {
    std::stringstream output_format;
    output_format << "Skill with ID " << skillId << " and level " << skillLevel << " not found";
    output = output_format.str();
    return 0; // Exit if skill is not found
}

if (!CUser::UseItemSkill(user, skill)) {
    std::stringstream output_format;
    output_format << "Skill " << skillId << " (Level " << skillLevel << ") could not be applied for " << charName;
    output = output_format.str();
    return 0; // Exit after reporting error
}

// Output a success message
std::stringstream output_format;
output_format << "Skill " << skillId << " (Level " << skillLevel << ") triggered for " << charName;
output = output_format.str();
return 0;

I had to update the return type of UseItemSkill, so please change it from void to bool in your code.
 
Last edited:
1734086358554.png


I want to modify the code of this in the server side. Anyone knows the address to get started (in Essentail files)
 
@Zhein

If you want to overwrite the case and write your own handler, you could set a detour at 0x47A010, with 8 bytes as the length and jmp to 0x47A018 at the end of the assembly function. The user is in edi and the packet is in esi.

Capture.PNG


C++:
void item_remake_handler(CUser* user, ItemRemakeIncoming* incoming)
{
}


Code:
pushad

push esi // packet
push edi // user
call item_remake_handler
add esp,0x8

popad

jmp u0x47A018
 
Last edited:
@nurum

Here's the source that was in the repo before I removed it.
 

Attachments

  • character_effect_01.zip
    2 KB · Views: 10
@Bowie, do you have discord?

C++:
void item_remake_handler(CUser* user, ItemRemakeIncoming* incoming) {


    // Check items in the specified slots
    auto* item1 = user->inventory[incoming->bag1][incoming->slot1];
    auto* item2 = user->inventory[incoming->bag2][incoming->slot2];
    auto* item3 = user->inventory[incoming->bag3][incoming->slot3];


    // Ensure all items are present
    if (!item1 || !item2 || !item3) {
        Helpers::SendNotice("LOG: One or more items are missing.");
        return;
    }


    // Ensure all items have the same ID
    if (item1->itemInfo->itemId != item2->itemInfo->itemId ||
        item1->itemInfo->itemId != item3->itemInfo->itemId) {
        Helpers::SendNotice("LOG: Items do not match.");
        return;
    }


    // Handle specific item creation
    ItemInfo* resultItem = nullptr;
    if (item1->itemInfo->itemId == 72001) {
        int resultType = 72;
        int resultTypeId = 2;
        resultItem = CGameData::GetItemInfo(resultType, resultTypeId);
        if (resultItem) {
            std::string resultLog = "LOG: Created result item with Type: " +
            std::to_string(resultType) + ", TypeID: " + std::to_string(resultTypeId);
            Helpers::SendNotice(resultLog.c_str());
        }
        else {
            Helpers::SendNotice("LOG: Failed to create result item.");
            return;
        }
    }
    else {
        Helpers::SendNotice("LOG: Item ID does not match special handling conditions.");


        ItemRemakeOutgoing outgoing{};
        outgoing.result = ItemRemakeResult::Failure;
        Helpers::Send(user, &outgoing, sizeof(ItemRemakeOutgoing));


        return;
    }


    
    uint8_t bag = 1; // Result item bag
    uint8_t slot = -1; // Result item slot
    while (bag <= user->bagsUnlocked) {
        slot = Helpers::GetFreeItemSlot(user, bag);
        if (slot != -1) {
            if (CUser::ItemCreate(user, resultItem, 1)) {
                break;
            }
        }
        ++bag;
    }


    // Validate the resulting item placement
    auto* findResult = user->inventory[bag][slot];
    if (!findResult) {
        Helpers::SendNotice("LOG: Result item was not created.");
        return;
    }


    // Transfer the details of the first item to the result
    findResult->gems = item1->gems;
    findResult->craftName = item1->craftName;
    findResult->quality = item1->quality;


    // Assign the findResult back to the inventory
    user->inventory[bag][slot] = findResult;


    //Log the inventory[bag][slot] if already have its gems, craftname and quality
    Helpers::ItemRemove(user, item1->itemInfo->itemId, 1);
    Helpers::ItemRemove(user, item2->itemInfo->itemId, 1);
    Helpers::ItemRemove(user, item3->itemInfo->itemId, 1);


    // Send outgoing packet with result details
    ItemRemakeOutgoing outgoing{};
    outgoing.result = ItemRemakeResult::Success;
    outgoing.bag = bag;
    outgoing.slot = slot;
    outgoing.type = resultItem->type;
    outgoing.typeId = resultItem->typeId;
    outgoing.gems = findResult->gems;
    outgoing.count = findResult->count;
    outgoing.craftName = findResult->craftName;
    Helpers::Send(user, &outgoing, sizeof(ItemRemakeOutgoing));


    GameLogItemRemakeIncoming remakeLog(user, findResult, item1->uniqueId, item2->uniqueId, item3->uniqueId);
    Helpers::SendGameLog(&remakeLog, sizeof(GameLogItemRemakeIncoming));


    Helpers::SendNotice("LOG: Handler completed successfully.");


}

Can I ask why the item gems, craftname and quality is not persistent?
 
Can I ask why the item gems, craftname and quality is not persistent?

You have to send a packet to the dbAgent service. Looks like the remake function sends 0x701. I added it to the library.

Please add range checks for the bags and slots. :p

Edit: sending 0x711 and 0x717 to the dbAgent service resolved the issue.
 
Last edited:
@Bowie hi i find the Item Ability Transfer have Bug in attribute overlay. The attributes of the equipment worn on the body are transferred to the new equipment, and then the new equipment is put on. It will add the attribute of additional gems embedded in the equipment.
Repeating this operation can infinitely increase the attributes of the character.
 
Back
Top