• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!
  • 2026 staff recruitment is open! Check it out and consider applying!

Exp/Kill/Drop Tracker Module [OTCV8] [TFS 1.4.2]

I had this system working fine for me, but after i added rarity system on the server the loottracker is not working anymore, tried to fix it but no sucess.

My files are like this now, if someone maybe can help i would appreciate
 

Attachments

I had this system working fine for me, but after i added rarity system on the server the loottracker is not working anymore, tried to fix it but no sucess.

My files are like this now, if someone maybe can help i would appreciate
It's because of the droploot eventcallback changes, so check there.
 
Last edited:
Greetings.

I made a drop and kill tracker module together with the public exp analyzer module found here.
Module does not use opcode, but by network messages.

Im still learning modules and programming in general, so if you find flaws or bad coding, please comment this thread, so that others and me can correct it.
  • Copy Kill or Drop session to Clipboard:
  • Start new Hunt Button (opens all trackers)
  • Reset singular trackers (ex Reset kills, resets kill tracker only)
  • Trackers needs to be visible to track.
  • Ctrl+H to open Window
  • Tracks Number of killed creatures and items since the respective tracker was opened(resets on death or logout)
  • Exp Session




Killer Tracker / Exp Tracker / Drop Tracker
View attachment 84393View attachment 84394View attachment 84401
Analyzer Menu:
View attachment 84396
So what do you need to do?

Server-side Code
First of all, you need commit from the official TFS branch : Kill tracking (#3860) · otland/forgottenserver@ba72ce6 (https://github.com/otland/forgottenserver/commit/ba72ce6787668974b2f0b9db24747d4ac1693507)

And in addition, you need to add the item name to the network:
// msg:addString(item:getName())
LUA:
function Player.updateKillTracker(self, monster, corpse)
    local monsterType = monster:getType()
    if not monsterType then
        return false
    end

    local msg = NetworkMessage()
    msg:addByte(0xD1)
    msg:addString(monster:getName())

    local monsterOutfit = monsterType:getOutfit()
    msg:addU16(monsterOutfit.lookType or 19)
    msg:addByte(monsterOutfit.lookHead)
    msg:addByte(monsterOutfit.lookBody)
    msg:addByte(monsterOutfit.lookLegs)
    msg:addByte(monsterOutfit.lookFeet)
    msg:addByte(monsterOutfit.lookAddons)

    local corpseSize = corpse:getSize()
    msg:addByte(corpseSize)
    for index = corpseSize - 1, 0, -1 do
        local item = corpse:getItem(
            index)
        msg:addItem(item,true)
        msg:addString(item:getName()) --- ADD THIS
    end

    local party = self:getParty()
    if party then
        local members = party:getMembers()
        members[#members + 1] = party:getLeader()

        for _, member in ipairs(members) do
            msg:sendToPlayer(member)
        end
    else
        msg:sendToPlayer(self)
    end

    msg:delete()
    return true
end
As you might have noticed, there is a “true” in addItem. If the code works without true for you, you don’t need to add it. But if you need to, add this in luascript.cpp.
(This caused a lot of bugs for me in client, adding this to the additem method, at least solved the issue for me.)
Code:
int LuaScriptInterface::luaNetworkMessageAddItem(lua_State* L)
{
    // networkMessage:addItem(item, false/true)
    Item* item = getUserdata<Item>(L, 2);

    if (!item) {
        reportErrorFunc(L, getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND));
        lua_pushnil(L);
        return 1;
    }


    NetworkMessage* message = getUserdata<NetworkMessage>(L, 1);
    bool description = getBoolean(L, 3, false);
    if (message) {
        message->addItem(item, description);
        pushBoolean(L, true);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

Client side Code
Since we are not using opcodes, we need to edit the protocolgameparse.cpp. In OTCV8, the parsing for kill tracker is already reserved.
Replace this:
Code:
void ProtocolGame::parseKillTracker(const InputMessagePtr& msg)
{
    msg->getString();
    msg->getU16();
    msg->getU8();
    msg->getU8();
    msg->getU8();
    msg->getU8();
    msg->getU8();
    int corpseSize = msg->getU8(); // corpse size
    for (int i = 0; i < corpseSize; i++) {
        getItem(msg); // corpse item
    }
}

With this:
Code:
void ProtocolGame::parseKillTracker(const InputMessagePtr& msg)
{
   std::string name =  msg->getString(); //Monster Name
   uint16_t lookType = msg->getU16();      //Monster Looktype
   uint8_t lookHead = msg->getU8();       // Monster Head
   uint8_t lookBody = msg->getU8();       // Monster Body
   uint8_t lookLegs = msg->getU8();       // Monster Legs
   uint8_t lookFeet = msg->getU8();       // Monster Feet
   uint8_t addons = msg->getU8();       //Monster Addons
   uint8_t corpseSize = msg->getU8(); // corpse size
   std::vector<std::tuple<std::string, ItemPtr>> items;
    for (int i = 0; i < corpseSize; i++) {
        ItemPtr item = getItem(msg);
        std::string itemName = msg->getString();
        items.push_back(std::make_tuple(itemName, item));
    }

    m_localPlayer->updateKillTracker(name, lookType, lookHead, lookBody, lookLegs, lookFeet, addons, corpseSize, items);
}

In localplayer.h, below ‘void setBlessings(int blessings);’

void updateKillTracker(std::string name, uint16_t lookType, uint8_t lookHead, uint8_t lookBody, uint8_t lookLegs, uint8_t lookFeet, uint8_t addons, uint8_t corpseSize, const   std::vector<std::tuple<std::string, ItemPtr>> items);

in localplayer.h, below ‘void setBlessings(int blessings);’ add:
Code:
void updateKillTracker(std::string name, uint16_t lookType, uint8_t lookHead, uint8_t lookBody, uint8_t lookLegs, uint8_t lookFeet, uint8_t addons, uint8_t corpseSize, const   std::vector<std::tuple<std::string, ItemPtr>> items);

In localplayer.cpp, at end of file, add:
Code:
void LocalPlayer::updateKillTracker(std::string name, uint16_t lookType, uint8_t lookHead, uint8_t lookBody, uint8_t lookLegs, uint8_t lookFeet, uint8_t addons, uint8_t corpseSize, const  std::vector<std::tuple<std::string, ItemPtr>> items)
{
        callLuaField("onUpdateKillTracker", name, lookType, lookHead, lookBody, lookLegs, lookFeet, addons,corpseSize, items);
}

That’s about it. Feel free to improve or add more features to the module.
Hi, this looks very good, but I don't know how to compile, don't you have any release already done. Thanks
 
then you have to «unpack» the bag, list them out and send it in the update killtracker. How? I dont know, Im not able to test this out for a period. Its not compatible with the old loot systems how it is now.

Hi, did you manage to solve so it also sends items from inside containers to the Loot Tracker?
I am on TFS 1.5
 
Hi, did you manage to solve so it also sends items from inside containers to the Loot Tracker?
I am on TFS 1.5
Uhm, i never looked into it.. Chat GPT should resolve this in seconds..

Here ( I only checked if it worked like normal, i do not have items inside bags in my distro version):

LUA:
function Player.updateKillTracker(self, monster, corpse)
    local monsterType = monster:getType()
    if not monsterType then
        return false
    end

    local name = monsterType:getName()

    local msg = NetworkMessage()
    msg:addByte(0x2D)
    msg:addString(name)

    -- Monster outfit
    local monsterOutfit = monsterType:getOutfit()
    msg:addU16(monsterOutfit.lookType or 19)
    msg:addByte(monsterOutfit.lookHead)
    msg:addByte(monsterOutfit.lookBody)
    msg:addByte(monsterOutfit.lookLegs)
    msg:addByte(monsterOutfit.lookFeet)
    msg:addByte(monsterOutfit.lookAddons)

    -- Collect all items (including items inside containers)
    local allItems = {}

    local function addContainerItems(container)
        for i = 0, container:getSize() - 1 do
            local subItem = container:getItem(i)
            if subItem then
                table.insert(allItems, subItem)
                if subItem:isContainer() then
                    addContainerItems(subItem)
                end
            end
        end
    end

    for i = 0, corpse:getSize() - 1 do
        local item = corpse:getItem(i)
        if item then
            table.insert(allItems, item)
            if item:isContainer() then
                addContainerItems(item)
            end
        end
    end

    -- Send the total item count (corpse items + container items)
    msg:addByte(#allItems)

    for _, item in ipairs(allItems) do
        local clientId = ItemType(item:getId()):getClientId()
        msg:addU32(clientId)
        msg:addU16(item:getCount())
        msg:addString(item:getName())
    end

    -- Send to party or self
    local party = self:getParty()
    if party then
        local members = party:getMembers()
        members[#members + 1] = party:getLeader()
        for _, member in ipairs(members) do
            msg:sendToPlayer(member)
        end
    else
        msg:sendToPlayer(self)
    end

    msg:delete()
    return true
end
 
Back
Top