• 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!

C++ Party shared experience issue

Joined
Sep 24, 2023
Messages
36
Solutions
1
Reaction score
8
Hello everyone,

First of all, I have some programming experience and familiarity with it. However, this is my first encounter with Tibia's code, and it's also a different programming language than the one I'm used to.

I'm facing an issue in my C++ code related to calculating shared experience in an online game. I have a party system where players can hunt together and share the experience gained from creature kills. However, I'm having difficulties ensuring that the experience is evenly distributed among all party members when one player deals the majority of the damage.

Here's a summary of my problem:

  • When a party is hunting, and only one of the players can deal around 80% to 90% of the creature's damage, while the other party members deal the remaining damage, the experience is being distributed based on the damage dealt by the attacker, rather than evenly among all party members.
  • If only the party leader deals 100% of the damage, the experience is incorrectly share.
    1695515234563.png

  • If only one party member deals 100% of the damage, the experience is share incorrectly.1695514933273.png

  • Here, the player "Teste02" dealt approximately 80% to 90% of the creature's health, while the player "Teste01" dealt the remaining damage.
    1695515095004.png

  • If each member, including the party leader, deals around 50% of the creature's health, the experience is divided correctly.
    1695515184814.png

  • I need the experience to be shared equally among all party members, regardless of who dealt the most damage. Since my server version is 7.2 to 7.6, and the vocations deal higher damage than usual, not all players will be able to deal a high percentage of damage to the creatures they are hunting when they are in a party.
I've made some modifications to my code, but I haven't been able to find the correct solution. Could someone help me understand how I can adjust the logic for shared experience calculation so that it's evenly distributed among all party members?
Here's the relevant code snippet where I'm facing this issue.

With my experience, I've managed to identify some relevant functions, but I must admit that I'm confused. Here they are:

getDamageRatio Function
C++:
double Creature::getDamageRatio(Creature* attacker) const
{
    uint32_t totalDamage = 0;
    uint32_t attackerDamage = 0;

    for (const auto& it : damageMap) {
        const CountBlock_t& cb = it.second;
        totalDamage += cb.total;
        if (it.first == attacker->getID()) {
            attackerDamage += cb.total;
        }
    }

    if (totalDamage == 0) {
        return 0;
    }

    return (static_cast<double>(attackerDamage) / totalDamage);
}


onDeath Function
C++:
void Creature::onDeath()
{
    bool lastHitUnjustified = false;
    bool mostDamageUnjustified = false;
    Creature* lastHitCreature = g_game.getCreatureByID(lastHitCreatureId);
    Creature* lastHitCreatureMaster;
    if (lastHitCreature) {
        lastHitUnjustified = lastHitCreature->onKilledCreature(this);
        lastHitCreatureMaster = lastHitCreature->getMaster();
    } else {
        lastHitCreatureMaster = nullptr;
    }

    Creature* mostDamageCreature = nullptr;

    const int64_t timeNow = OTSYS_TIME();
    const uint32_t inFightTicks = g_config.getNumber(ConfigManager::PZ_LOCKED);
    int32_t mostDamage = 0;
    std::map<Creature*, uint64_t> experienceMap;
    for (const auto& it : damageMap) {
        if (Creature* attacker = g_game.getCreatureByID(it.first)) {
            CountBlock_t cb = it.second;
            if ((cb.total > mostDamage && (timeNow - cb.ticks <= inFightTicks))) {
                mostDamage = cb.total;
                mostDamageCreature = attacker;
            }

            if (attacker != this) {
                uint64_t gainExp = getGainedExperience(attacker);
                if (Player* attackerPlayer = attacker->getPlayer()) {
                    attackerPlayer->removeAttacked(getPlayer());

                    Party* party = attackerPlayer->getParty();
                    if (party && party->getLeader() && party->isSharedExperienceActive() &&
                        party->isSharedExperienceEnabled()) {
                        attacker = party->getLeader();
                    }
                }

                auto tmpIt = experienceMap.find(attacker);
                if (tmpIt == experienceMap.end()) {
                    experienceMap[attacker] = gainExp;
                } else {
                    tmpIt->second += gainExp;
                }
            }
        }
    }

    for (const auto& it : experienceMap) {
        it.first->onGainExperience(it.second, this);
    }

    if (mostDamageCreature) {
        if (mostDamageCreature != lastHitCreature && mostDamageCreature != lastHitCreatureMaster) {
            Creature* mostDamageCreatureMaster = mostDamageCreature->getMaster();
            if (lastHitCreature != mostDamageCreatureMaster &&
                (!lastHitCreatureMaster || mostDamageCreatureMaster != lastHitCreatureMaster)) {
                mostDamageUnjustified = mostDamageCreature->onKilledCreature(this, false);
            }
        }
    }

    bool droppedCorpse = dropCorpse(lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified);
    death(lastHitCreature);

    if (master) {
        setMaster(nullptr);
    }

    if (droppedCorpse) {
        g_game.removeCreature(this, false);
    }
}

I've tried to change it and reformulate the logic a bit to what I need, but without any success.

I'd be grateful if anyone could help!
 
Last edited:
change
void Party::shareExperience(uint64_t experience)
after
(*it)->onGainSharedExperience(tmpExperience);
add
std::cout << "Share Experience: " << tmpExperience << " Player: " << (*it)->getName() << std::endl;

and see if it's printing the leader name too
I made this adjustment. Note, this is only when leader kills the monster alone.
1695777293689.png


This is only member kills the monster alone.
1695777395185.png

Note that my memberList only contains party members. The leader is not included.

Now with this debug, I'll go to the end, to find out what experience is being put to the player.

Thanks man!
 
Last edited:
I made this adjustment. Note, this is only when leader kills the monster alone.
View attachment 78844


This is only member kills the monster alone.
View attachment 78845

Note that my memberList only contains party members. The leader is not included.

Now with this debug, I'll go to the end, to find out what experience is being put to the player.

Thanks man!
What’s the level of player 1 and 2?
Post automatically merged:

Yeah, i think you have to put some other prints there and try to find whats wrong
 
What’s the level of player 1 and 2?
Post automatically merged:

Yeah, i think you have to put some other prints there and try to find whats wrong
Both 200
1695780597767.png1695780630510.png

This is only leader kills the monster alone. Check, i print exp throughout the process until Player::addExperience.
1695780747908.png

C++:
void Party::shareExperience(uint64_t experience)
{
    uint64_t shareExperience = experience;

    shareExperience = (experience*1.0);
    
 
    uint64_t tmpExperience = shareExperience;

    leader->onGainSharedExperience(tmpExperience);
    for(PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it)
    {
        tmpExperience = shareExperience;
                std::cout << "Share Experience: " << tmpExperience << " Player: " << (*it)->getName() << std::endl;
        (*it)->onGainSharedExperience(tmpExperience);
    }
}

void Player::onGainSharedExperience(uint64_t gainExp)
{
    std::cout << "Share Experience onGainSharedExperience: " << gainExp << std::endl;

    gainExperience(gainExp);
}

void Player::gainExperience(uint64_t gainExp)
{
    if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0) {
        return;
    }
    std::cout << "Share Experience gainExperience: " << gainExp << std::endl;


    addExperience(gainExp, true);
}

void Player::addExperience(uint64_t exp, bool sendText/* = false*/, bool applyStages/* = true*/)
{
    uint64_t currLevelExp = Player::getExpForLevel(level);
    uint64_t nextLevelExp = Player::getExpForLevel(level + 1);
    if (currLevelExp >= nextLevelExp) {
        //player has reached max level
        levelPercent = 0;
        sendStats();
        return;
    }
    std::cout << "Add Exp: " << exp << " Player: " << getName() << std::endl;

    /* if (getSoul() < getVocation()->getSoulMax() && exp >= level) {
        Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SOUL, 4 * 60 * 1000, 0);
        condition->setParam(CONDITION_PARAM_SOULGAIN, 1);
        condition->setParam(CONDITION_PARAM_SOULTICKS, vocation->getSoulGainTicks() * 1000);
        addCondition(condition);
    } */
    
    if (applyStages) {
        exp *= g_game.getExperienceStage(level);
    }
    std::cout << "Add Exp after stages: " << exp << " Player: " << getName() << std::endl;

    if (exp == 0) {
        return;
    }
    if(isPremium()){
        exp += (exp*10)/100;
    }
    experience += exp;
    std::cout << "Add Exp after isPremium: " << exp << " Player: " << getName() << std::endl;

    if (sendText) {
        g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(exp));
    }

    uint32_t prevLevel = level;
    while (experience >= nextLevelExp) {
        ++level;
        healthMax += vocation->getHPGain();
        health += vocation->getHPGain();
        manaMax += vocation->getManaGain();
        mana += vocation->getManaGain();
        capacity += vocation->getCapGain();

        currLevelExp = nextLevelExp;
        nextLevelExp = Player::getExpForLevel(level + 1);
        if (currLevelExp >= nextLevelExp) {
            //player has reached max level
            break;
        }
    }

    if (prevLevel != level) {
        updateBaseSpeed();
        setBaseSpeed(getBaseSpeed());

        g_game.changeSpeed(this, 0);
        g_game.addCreatureHealth(this);

        if (party) {
            party->updateSharedExperience();
        }

        g_creatureEvents->playerAdvance(this, SKILL_LEVEL, prevLevel, level);

        std::ostringstream ss;
        ss << "You advanced from Level " << prevLevel << " to Level " << level << '.';
        sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str());
    }

    if (nextLevelExp > currLevelExp) {
        levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp);
    } else {
        levelPercent = 0;
    }
    sendStats();
}
 
Both 200
View attachment 78847View attachment 78848

This is only leader kills the monster alone. Check, i print exp throughout the process until Player::addExperience.
View attachment 78849

C++:
void Party::shareExperience(uint64_t experience)
{
    uint64_t shareExperience = experience;

    shareExperience = (experience*1.0);
   
 
    uint64_t tmpExperience = shareExperience;

    leader->onGainSharedExperience(tmpExperience);
    for(PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it)
    {
        tmpExperience = shareExperience;
                std::cout << "Share Experience: " << tmpExperience << " Player: " << (*it)->getName() << std::endl;
        (*it)->onGainSharedExperience(tmpExperience);
    }
}

void Player::onGainSharedExperience(uint64_t gainExp)
{
    std::cout << "Share Experience onGainSharedExperience: " << gainExp << std::endl;

    gainExperience(gainExp);
}

void Player::gainExperience(uint64_t gainExp)
{
    if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0) {
        return;
    }
    std::cout << "Share Experience gainExperience: " << gainExp << std::endl;


    addExperience(gainExp, true);
}

void Player::addExperience(uint64_t exp, bool sendText/* = false*/, bool applyStages/* = true*/)
{
    uint64_t currLevelExp = Player::getExpForLevel(level);
    uint64_t nextLevelExp = Player::getExpForLevel(level + 1);
    if (currLevelExp >= nextLevelExp) {
        //player has reached max level
        levelPercent = 0;
        sendStats();
        return;
    }
    std::cout << "Add Exp: " << exp << " Player: " << getName() << std::endl;

    /* if (getSoul() < getVocation()->getSoulMax() && exp >= level) {
        Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SOUL, 4 * 60 * 1000, 0);
        condition->setParam(CONDITION_PARAM_SOULGAIN, 1);
        condition->setParam(CONDITION_PARAM_SOULTICKS, vocation->getSoulGainTicks() * 1000);
        addCondition(condition);
    } */
   
    if (applyStages) {
        exp *= g_game.getExperienceStage(level);
    }
    std::cout << "Add Exp after stages: " << exp << " Player: " << getName() << std::endl;

    if (exp == 0) {
        return;
    }
    if(isPremium()){
        exp += (exp*10)/100;
    }
    experience += exp;
    std::cout << "Add Exp after isPremium: " << exp << " Player: " << getName() << std::endl;

    if (sendText) {
        g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(exp));
    }

    uint32_t prevLevel = level;
    while (experience >= nextLevelExp) {
        ++level;
        healthMax += vocation->getHPGain();
        health += vocation->getHPGain();
        manaMax += vocation->getManaGain();
        mana += vocation->getManaGain();
        capacity += vocation->getCapGain();

        currLevelExp = nextLevelExp;
        nextLevelExp = Player::getExpForLevel(level + 1);
        if (currLevelExp >= nextLevelExp) {
            //player has reached max level
            break;
        }
    }

    if (prevLevel != level) {
        updateBaseSpeed();
        setBaseSpeed(getBaseSpeed());

        g_game.changeSpeed(this, 0);
        g_game.addCreatureHealth(this);

        if (party) {
            party->updateSharedExperience();
        }

        g_creatureEvents->playerAdvance(this, SKILL_LEVEL, prevLevel, level);

        std::ostringstream ss;
        ss << "You advanced from Level " << prevLevel << " to Level " << level << '.';
        sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str());
    }

    if (nextLevelExp > currLevelExp) {
        levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp);
    } else {
        levelPercent = 0;
    }
    sendStats();
}
where this party->shareExperience is been called?
Post automatically merged:

ok, i saw now.. its in the player onGainExperience
Post automatically merged:

Both 200
View attachment 78847View attachment 78848

This is only leader kills the monster alone. Check, i print exp throughout the process until Player::addExperience.
View attachment 78849

C++:
void Party::shareExperience(uint64_t experience)
{
    uint64_t shareExperience = experience;

    shareExperience = (experience*1.0);
   
 
    uint64_t tmpExperience = shareExperience;

    leader->onGainSharedExperience(tmpExperience);
    for(PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it)
    {
        tmpExperience = shareExperience;
                std::cout << "Share Experience: " << tmpExperience << " Player: " << (*it)->getName() << std::endl;
        (*it)->onGainSharedExperience(tmpExperience);
    }
}

void Player::onGainSharedExperience(uint64_t gainExp)
{
    std::cout << "Share Experience onGainSharedExperience: " << gainExp << std::endl;

    gainExperience(gainExp);
}

void Player::gainExperience(uint64_t gainExp)
{
    if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0) {
        return;
    }
    std::cout << "Share Experience gainExperience: " << gainExp << std::endl;


    addExperience(gainExp, true);
}

void Player::addExperience(uint64_t exp, bool sendText/* = false*/, bool applyStages/* = true*/)
{
    uint64_t currLevelExp = Player::getExpForLevel(level);
    uint64_t nextLevelExp = Player::getExpForLevel(level + 1);
    if (currLevelExp >= nextLevelExp) {
        //player has reached max level
        levelPercent = 0;
        sendStats();
        return;
    }
    std::cout << "Add Exp: " << exp << " Player: " << getName() << std::endl;

    /* if (getSoul() < getVocation()->getSoulMax() && exp >= level) {
        Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SOUL, 4 * 60 * 1000, 0);
        condition->setParam(CONDITION_PARAM_SOULGAIN, 1);
        condition->setParam(CONDITION_PARAM_SOULTICKS, vocation->getSoulGainTicks() * 1000);
        addCondition(condition);
    } */
   
    if (applyStages) {
        exp *= g_game.getExperienceStage(level);
    }
    std::cout << "Add Exp after stages: " << exp << " Player: " << getName() << std::endl;

    if (exp == 0) {
        return;
    }
    if(isPremium()){
        exp += (exp*10)/100;
    }
    experience += exp;
    std::cout << "Add Exp after isPremium: " << exp << " Player: " << getName() << std::endl;

    if (sendText) {
        g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(exp));
    }

    uint32_t prevLevel = level;
    while (experience >= nextLevelExp) {
        ++level;
        healthMax += vocation->getHPGain();
        health += vocation->getHPGain();
        manaMax += vocation->getManaGain();
        mana += vocation->getManaGain();
        capacity += vocation->getCapGain();

        currLevelExp = nextLevelExp;
        nextLevelExp = Player::getExpForLevel(level + 1);
        if (currLevelExp >= nextLevelExp) {
            //player has reached max level
            break;
        }
    }

    if (prevLevel != level) {
        updateBaseSpeed();
        setBaseSpeed(getBaseSpeed());

        g_game.changeSpeed(this, 0);
        g_game.addCreatureHealth(this);

        if (party) {
            party->updateSharedExperience();
        }

        g_creatureEvents->playerAdvance(this, SKILL_LEVEL, prevLevel, level);

        std::ostringstream ss;
        ss << "You advanced from Level " << prevLevel << " to Level " << level << '.';
        sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str());
    }

    if (nextLevelExp > currLevelExp) {
        levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp);
    } else {
        levelPercent = 0;
    }
    sendStats();
}
look..
the addExperience function is called 3 times
while the onGainSharedExperience and the gainExperience is called 2 times only..
so, search for this addExperience and try to find where it's been called this 1 extra time. (maybe some lua code onKill creature or something?)
 
Last edited:
where this party->shareExperience is been called?
Post automatically merged:

ok, i saw now.. its in the player onGainExperience
Post automatically merged:


look..
the addExperience function is called 3 times
while the onGainSharedExperience and the gainExperience is called 2 times only..
so, search for this addExperience and try to find where it's been called this 1 extra time. (maybe some lua code onKill creature or something?)
Heelo guy!
I found where addExperience is called. There is a lua file in the monsters' onKill event. It refers to an exp boost script for players, when they use a potion that doubles the experience gained for a few minutes.

Anyway, I'll provide the function below. To explain, I disabled creature:addExperience(exp, true) to test and the experience was divided correctly even when one of the party members didn't attack the monster, just as I wanted from the start. Or even when it only takes 1 hit.

My script boostexp.lua
Lua:
function onKill(creature, target)
    local exp = target:getType():getExperience()
    print(exp)
    if target:isPlayer() then
        -- Se o alvo for um jogador, não há bônus de experiência
    elseif creature:getStorageValue(81100) > os.time() then
        -- Se o atacante tiver o bônus ativo, adiciona 300 de experiência
        exp = exp * 3
    end
    print("EXP onKill")
    print(exp)

    -- creature:addExperience(exp, true)
    return true
end

Now the question remains, how can I maintain this script to give players the opportunity to have their experience doubled for a few minutes, without jeopardizing the experience gained when in a party?
 
Heelo guy!
I found where addExperience is called. There is a lua file in the monsters' onKill event. It refers to an exp boost script for players, when they use a potion that doubles the experience gained for a few minutes.

Anyway, I'll provide the function below. To explain, I disabled creature:addExperience(exp, true) to test and the experience was divided correctly even when one of the party members didn't attack the monster, just as I wanted from the start. Or even when it only takes 1 hit.

My script boostexp.lua
Lua:
function onKill(creature, target)
    local exp = target:getType():getExperience()
    print(exp)
    if target:isPlayer() then
        -- Se o alvo for um jogador, não há bônus de experiência
    elseif creature:getStorageValue(81100) > os.time() then
        -- Se o atacante tiver o bônus ativo, adiciona 300 de experiência
        exp = exp * 3
    end
    print("EXP onKill")
    print(exp)

    -- creature:addExperience(exp, true)
    return true
end

Now the question remains, how can I maintain this script to give players the opportunity to have their experience doubled for a few minutes, without jeopardizing the experience gained when in a party?
suspeitei desde o princípio kk
(i suspected from the beginning)

this code doesnt affect the share exp...
 
But why is it that when this creature:addExperience is deactivated, the experience is divided equally for both players?
the base experience is shared always.. always both got 175 exp
but, with that script, the lastHitKiller was getting some extra 175 exp..
so, the share exp is fine... either using this bonus exp (that is applied only to the last hit killer) or not.
 
the base experience is shared always.. always both got 175 exp
but, with that script, the lastHitKiller was getting some extra 175 exp..
so, the share exp is fine... either using this bonus exp (that is applied only to the last hit killer) or not.
Got to the facts!

  1. When creature:addExperience() is ACTIVATED in boostexp.lua script.
    • When both players attacked the monster and killed. Exp is shared correctly. Look image below
      1695854129890.png

    • When this is only member killed the monster.
      1695854222379.png
  2. When creature:addExperience() is DESACTIVATED in boostexp.lua script.
    • When both players attacked the monster and killed. Exp is shared correctly. Look image below
      1695854837639.png

    • When this is only member killed the monster.
      1695854911791.png
Look at this.
Why is there such a difference when it's enabled, when the experience the dragon gives is 700. How can I get around this?
I understand that the experience is being divided up correctly, but the problem now is why when I activate the creature:addExperience() function in boostexp.lua script, the character with the last hit gains more experience than the other player in the party? That doesn't make much sense to me.

Maybe I'll have to rework the boost script so that this doesn't happen.
Now I don't know what's right, giving 350 experience to both players in the party (in this scenario), or keeping 175 for both.
 
Got to the facts!

  1. When creature:addExperience() is ACTIVATED in boostexp.lua script.
  2. When creature:addExperience() is DESACTIVATED in boostexp.lua script.
Look at this.
Why is there such a difference when it's enabled, when the experience the dragon gives is 700. How can I get around this?
I understand that the experience is being divided up correctly, but the problem now is why when I activate the creature:addExperience() function in boostexp.lua script, the character with the last hit gains more experience than the other player in the party? That doesn't make much sense to me.

Maybe I'll have to rework the boost script so that this doesn't happen.
Now I don't know what's right, giving 350 experience to both players in the party (in this scenario), or keeping 175 for both.
fact:
final exp of this dragon is 700
and it's split by 2 (members)
but, after EXP STAGES, it goes down to 175.. (maybe the exp stage is 0.25?)
so.. doesnt matter who attacked or not.. it always delivers 175 exp for each member..
so, SHARE EXP is OK... (if you think the exp is not correct, so, check stages, maths, etc)

the problem is the onKill event..
when the event is triggered, it gives double exp (check maths, check stages, and find out why)
my guess is:
this onKill event is being called 2 times (probably 1 for lastHitKiller and 1 for mostDamageKiller, depending on the situaton), put some print(creature:getName()) there and confirms.
I THINK, you can do something like (you have to check what is sent by source in this event...):
onKill(creature,target,lastHit)
if not lastHit then return end...

to prevent this double execution (if it's what you want)

but, as i said, the share exp of the killed mob looks fine. its splitting equally between all members..
 
Hello @pips, yesterday I carried out some tests to try to better understand what was happening and the main reason for giving extra experience to the player even if he doesn't have a boost activated and the creature:addExperience function is activated. However, I wasn't able to make much progress, as I'm in the final stages of my studies and I'm doing a lot of coursework.

I think I'll be able to work on it more tonight.

But as a stopgap and in parallel, I'm thinking of removing the exp boost from the onKill event and transferring it directly to the server's sourcer. But I don't know how it will behave, I don't think I'll have too many problems.

Do you think that makes sense?
 
Hello @pips, yesterday I carried out some tests to try to better understand what was happening and the main reason for giving extra experience to the player even if he doesn't have a boost activated and the creature:addExperience function is activated. However, I wasn't able to make much progress, as I'm in the final stages of my studies and I'm doing a lot of coursework.

I think I'll be able to work on it more tonight.

But as a stopgap and in parallel, I'm thinking of removing the exp boost from the onKill event and transferring it directly to the server's sourcer. But I don't know how it will behave, I don't think I'll have too many problems.

Do you think that makes sense?
no, i think it should keep in lua.
the problem is that i dont remember much of what resources you have in tfs1.2 to make it better..
my recommendation would be: use a newer tfs version, there's a lot of improvements with event callbacks that should solve your problem easily.
but.. if you have onDeath event in your server, you can use it instead of onKill.. then iterate over players in damageMap and add extra exp there.
 
I discovered that to use the onDeath event I would have to put a script in each monster's .xml file calling my boost event.

To me, that doesn't seem like an alternative. I don't know how much process this could cost me.
 
Back
Top