• 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
18
GitHub
RodrigoTolomeotti
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:
I don't believe that it will, but it wouldn't be too hard to make the changes necessary to add this code to your server. I've since updated it and made party members list by GUID, allowing players to stay in the party if they die or for some reason get disconnected. I would have to see the code. The client itself wont need any modification for these changes.
 
I don't believe that it will, but it wouldn't be too hard to make the changes necessary to add this code to your server. I've since updated it and made party members list by GUID, allowing players to stay in the party if they die or for some reason get disconnected. I would have to see the code. The client itself wont need any modification for these changes.

I tried to translate this code to my TFS 1.4.2 by making changes to party.cpp, but the shared code still didn't work as it should, maybe I'm doing something wrong or forgot something.

I will upload Party Shared Exp in party.cpp. Maybe I have something configured differently in the code, if you need the entire file I can send it





LUA:
void Party::shareExperience(uint64_t experience, Creature* source/* = nullptr*/)
{
    uint64_t shareExperience = experience;
    g_events->eventPartyOnShareExperience(this, shareExperience);

    for (Player* member : memberList) {
        member->onGainSharedExperience(shareExperience, source);
    }
    leader->onGainSharedExperience(shareExperience, source);
}

bool Party::canUseSharedExperience(const Player* player) const
{
    return getMemberSharedExperienceStatus(player) == SHAREDEXP_OK;
}

SharedExpStatus_t Party::getMemberSharedExperienceStatus(const Player* player) const
{
    if (memberList.empty()) {
        return SHAREDEXP_EMPTYPARTY;
    }

    uint32_t highestLevel = leader->getLevel();
    for (Player* member : memberList) {
        if (member->getLevel() > highestLevel) {
            highestLevel = member->getLevel();
        }
    }

    uint32_t minLevel = static_cast<uint32_t>(std::ceil((static_cast<float>(highestLevel) * 2) / 3));
    if (player->getLevel() < minLevel) {
        return SHAREDEXP_LEVELDIFFTOOLARGE;
    }

    if (!Position::areInRange<EXPERIENCE_SHARE_RANGE, EXPERIENCE_SHARE_RANGE, EXPERIENCE_SHARE_FLOORS>(leader->getPosition(), player->getPosition())) {
        return SHAREDEXP_TOOFARAWAY;
    }

    if (!player->hasFlag(PlayerFlag_NotGainInFight)) {
        //check if the player has healed/attacked anything recently
        auto it = ticksMap.find(player->getID());
        if (it == ticksMap.end()) {
            return SHAREDEXP_MEMBERINACTIVE;
        }

        uint64_t timeDiff = OTSYS_TIME() - it->second;
        if (timeDiff > static_cast<uint64_t>(g_config.getNumber(ConfigManager::PZ_LOCKED))) {
            return SHAREDEXP_MEMBERINACTIVE;
        }
    }
    return SHAREDEXP_OK;
}

SharedExpStatus_t Party::getSharedExperienceStatus()
{
    SharedExpStatus_t leaderStatus = getMemberSharedExperienceStatus(leader);
    if (leaderStatus != SHAREDEXP_OK) {
        return leaderStatus;
    }

    for (Player* member : memberList) {
        SharedExpStatus_t memberStatus = getMemberSharedExperienceStatus(member);
        if (memberStatus != SHAREDEXP_OK) {
            return memberStatus;
        }
    }
    return SHAREDEXP_OK;
}
 
What portions of the code are you looking to implement? According to the code and what I can see on github for 1.4.2, your exp is currently looking to be shared 100% across the party. I may be missing something, I probably am. But I jumped around a bit to look at each function and it does look to already be coded that way.

Can anyone else chime in on this? I don't have 1.4.2 to play around with right yet. I'm going to hopefully be working towards 1.5-1.6 soon and transferring all of my content, which is quite the process.

If this is the case and all members are currently gaining 100% of the experience, do you want to cut it adding party members and create a bonus, or do you want to add a bonus for having party members, eliminate exp gain for members in safe zone? give me a description and I'll write up a quick code for this. I will throw some code down below to get you started if this is 100% exp, and comment sections you can change to iterate party experience.
In order for this to work, you will disable party activity check, or modify activity check to your liking if you are looking to have the system like mine entirely.

I'm unsure where EXPERIENCE_SHARE_RANGE is configured, I do not see it in config manager, but here you go.

I may have missed something, it's hard to go back and forth on github, coding program makes this much easier haha, but essentially this will get all players within range of the monster killed, and allow them to share experience with eachother.

C++:
//party.h
//FIND
    void shareExperience(uint64_t experience, Creature* source = nullptr);
//REPLACE WITH
    void shareExperience(uint64_t experience, Creature* source = nullptr, const Position& monsterPosition);

//party.cpp
//REPLACE WHOLE FUNCTION shareExperience
void Party::shareExperience(uint64_t experience, Creature* source/* = nullptr*/, const Position& monsterPosition)
{
    // Range constants
    int rangeX = g_config.getNumber(ConfigManager::EXPERIENCE_SHARE_RANGE);
    int rangeY = g_config.getNumber(ConfigManager::EXPERIENCE_SHARE_RANGE);
    int rangeZ = g_config.getNumber(ConfigManager::EXPERIENCE_SHARE_FLOORS);
   
    // Create a temporary list to hold eligible members for experience sharing
    std::vector<Player*> eligibleMembers;
   
    // Check if the leader can receive experience, range of monster killed, not in PZ
    if (leader->getZone() != ZONE_PROTECTION && Position::areInRange<rangeX, rangeY, rangeZ>(monsterPosition, leader->getPosition())) {
        eligibleMembers.push_back(leader);
    }
   
    // Check if party members can receive experience, range of monster killed, not in PZ
    for (Player* member : memberList) {
        if (member->getZone() != ZONE_PROTECTION && Position::areInRange<rangeX, rangeY, rangeZ>(monsterPosition, player->getPosition())) {
            eligibleMembers.push_back(member);
        }
    }
   
    uint64_t shareExperience = experience;
    g_events->eventPartyOnShareExperience(this, shareExperience);
   
    uint64_t expShare = experience;
    if (eligibleMembers.size() >= 6) { //If members in range is greater than 5 divide exp among all players
        expShare /= eligibleMembers.size();
    }
    else if (eligibleMembers.size() == 1)
    {    //Exp only one party member or leader in range of monster killed
        expShare = expShare;
    }
    else
    {    //Members and or leader in range start share calculations
        expShare = (experience * 0.75);    //How much experience does everyone get? 0.75 is 75% change to desired value. 1 is 100%
        double expAddBonus = ((eligibleMembers.size() - 1) * //- 1 for members count, only 2 members no bonus, remove - 1 to make bonus for 2 members
            (3 / 100);    //3 represents the % bonus per member above 2, 3 members 78%, 4 members 81% etc.
            //I do not know if you have a config for party exp bonus, if you do that can be added here easily
        expShare += expAddBonus; //This adds 0.03 for 3% to 0.75 for exp per member above
    }

    double tmpExperience = expShare;
    if (std::find(eligibleMembers.begin(), eligibleMembers.end(), leader) != eligibleMembers.end()) {
        leader->onGainSharedExperience(tmpExperience, source);
    }
   
    for (auto it = memberList.begin(); it != memberList.end(); ++it) {
        if (std::find(eligibleMembers.begin(), eligibleMembers.end(), (*it)) != eligibleMembers.end()) {
            tmpExperience = expShare; //This ensures that the expShare amount is not multiplied in any way
            (*it)->onGainSharedExperience(shareExperience, source);
        }
    }
}

//player.cpp
//FIND
void Player::onGainSharedExperience(uint64_t gainExp, Creature* source)
{
    gainExperience(gainExp, source);
}
//REPLACE WITH
void Player::onGainSharedExperience(uint64_t gainExp, Creature* source, const Position& monsterPosition)
{
    gainExperience(gainExp, source, monsterPosition);
}

//FIND
void Player::onGainExperience(uint64_t gainExp, Creature* target)
{
    if (hasFlag(PlayerFlag_NotGainExperience)) {
        return;
    }

    if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) {
        party->shareExperience(gainExp, target);
        //We will get a share of the experience through the sharing mechanism
        return;
    }

    Creature::onGainExperience(gainExp, target);
    gainExperience(gainExp, target);
}

//REPLAE WITH
void Player::onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition)
{
    if (hasFlag(PlayerFlag_NotGainExperience)) {
        return;
    }

    if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) {
        // && party->isSharedExperienceActive() && party->isSharedExperienceEnabled() is party share enable and active, remove this section to always share
        party->shareExperience(gainExp, target, monsterPosition);
        //We will get a share of the experience through the sharing mechanism
        return;
    }

    Creature::onGainExperience(gainExp, target, monsterPosition);
    gainExperience(gainExp, target);
}

//player.h
//FIND
    void onGainExperience(uint64_t gainExp, Creature* target) override;
    void onGainSharedExperience(uint64_t gainExp, Creature* source);
   
//REPLACE WITH
    void onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition) override;
    void onGainSharedExperience(uint64_t gainExp, Creature* source, const Position& monsterPosition);
   
//creature.cpp
//FIND
    for (const auto& it : experienceMap) {
        it.first->onGainExperience(it.second, this);
    }
//REPLACE WITH
    Position monsterPosition = this->getPosition();
    for (const auto& it : experienceMap) {
        it.first->onGainExperience(it.second, this, monsterPosition);
    }

//FIND
    void Creature::onGainExperience(uint64_t gainExp, Creature* target)
   
//REPLACE WITH
    void Creature::onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition)
   
//FIND
    master->onGainExperience(gainExp, target);
   
//REPLACE WITH
    master->onGainExperience(gainExp, target, monsterPosition);
   
//creature.h
//FIND
    virtual void onGainExperience(uint64_t gainExp, Creature* target);

//REPLACE WITH
    virtual void onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition);

You will have to disable point map by doing this, because point map causes the party experience share to disable, this will still disable exp share if the level difference is too much.

C++:
//REPLACE WHOLE FUNCTION getMemberSharedExperienceStatus
SharedExpStatus_t Party::getMemberSharedExperienceStatus(const Player* player) const
{
    if (memberList.empty()) {
        return SHAREDEXP_EMPTYPARTY;
    }

    uint32_t highestLevel = leader->getLevel();
    for (Player* member : memberList) {
        if (member->getLevel() > highestLevel) {
            highestLevel = member->getLevel();
        }
    }

    uint32_t minLevel = static_cast<uint32_t>(std::ceil((static_cast<float>(highestLevel) * 2) / 3));
    if (player->getLevel() < minLevel) {
        return SHAREDEXP_LEVELDIFFTOOLARGE;
    }

    return SHAREDEXP_OK;
}
 
Last edited:
@dchampag
There were a lot of errors, especially with one specific function when trying to compile. Normally, I try to handle such things myself, but in the case of shared, I prefer to ask so as not to spoil the code somehow, and in "certain situations" it won't work because of my actions.😅
  • “Party::shareExperience”: missing default argument for parameter 3
  • Element of class "ConfigManager" has no member "EXPERIENCE_SHARE_RANGE"
  • Element of class "ConfigManager" has no member "EXPERIENCE_SHARE_RANGE"
  • Element of class "ConfigManager" has no member "EXPERIENCE_SHARE_FLOORS"
  • No instance of overloaded function "Position::areInRange" matches the argument list
  • No instance of overloaded function "Position::areInRange" matches the argument list
  • Identifier "player" is undefined
  • Too few arguments in function call
  • Too few arguments in function call
  • "EXPERIENCE_SHARE_RANGE" is not a member of "ConfigManager"
  • “int32_t ConfigManager::getNumber(ConfigManager::integer_config_t) const”: cannot convert argument 1 from "const int32_t" to "ConfigManager::integer_config_t"
  • "EXPERIENCE_SHARE_RANGE" is not a member of "ConfigManager"
  • “int32_t ConfigManager::getNumber(ConfigManager::integer_config_t) const”: cannot convert argument 1 from "const int32_t" to "ConfigManager::integer_config_t"
  • "EXPERIENCE_SHARE_FLOORS" is not a member of "ConfigManager"
  • “int32_t ConfigManager::getNumber(ConfigManager::integer_config_t) const”: cannot convert argument 1 from "const int32_t" to "ConfigManager::integer_config_t"
  • “Position::areInRange”: no matching overloaded function found
  • "player": undeclared identifier
  • “Position::areInRange”: no matching overloaded function found
  • "Player:OnGainSharedExperience": function does not take 2 arguments
  • "Player:OnGainSharedExperience": function does not take 2 arguments
  • "Party::shareExperience": missing default argument for parameter 3 (repeated 38 times)
  • "Player::gainExperience": function does not take 3 arguments
  • "Player:OnGainExperience": function does not take 2 arguments

Party.h:
void shareExperience(uint64_t experience, Creature* source = nullptr, const Position& monsterPosition);

Party.cpp:

void Party::shareExperience(uint64_t experience, Creature* source/* = nullptr*/, const Position& monsterPosition)
{
// Range constants
int rangeX = g_config.getNumber(ConfigManager::EXPERIENCE_SHARE_RANGE);
int rangeY = g_config.getNumber(ConfigManager::EXPERIENCE_SHARE_RANGE);
int rangeZ = g_config.getNumber(ConfigManager::EXPERIENCE_SHARE_FLOORS);

// Create a temporary list to hold eligible members for experience sharing
std::vector<Player*> eligibleMembers;

// Check if the leader can receive experience, range of monster killed, not in PZ
if (leader->getZone() != ZONE_PROTECTION && Position::areInRange<rangeX, rangeY, rangeZ>(monsterPosition, leader->getPosition())) {
eligibleMembers.push_back(leader);
}

// Check if party members can receive experience, range of monster killed, not in PZ
for (Player* member : memberList) {
if (member->getZone() != ZONE_PROTECTION && Position::areInRange<rangeX, rangeY, rangeZ>(monsterPosition, player->getPosition())) {
eligibleMembers.push_back(member);
}
}

uint64_t shareExperience = experience;
g_events->eventPartyOnShareExperience(this, shareExperience);

uint64_t expShare = experience;
if (eligibleMembers.size() >= 6) { //If members in range is greater than 5 divide exp among all players
expShare /= eligibleMembers.size();
}
else if (eligibleMembers.size() == 1)
{ //Exp only one party member or leader in range of monster killed
expShare = expShare;
}
else
{ //Members and or leader in range start share calculations
expShare = (experience * 0.75); //How much experience does everyone get? 0.75 is 75% change to desired value. 1 is 100%
double expAddBonus = (eligibleMembers.size() - 1) * //- 1 for members count, only 2 members no bonus, remove - 1 to make bonus for 2 members
(3 / 100); //3 represents the % bonus per member above 2, 3 members 78%, 4 members 81% etc.
//I do not know if you have a config for party exp bonus, if you do that can be added here easily
expShare += expAddBonus; //This adds 0.03 for 3% to 0.75 for exp per member above
}
double tmpExperience = expShare;
if (std::find(eligibleMembers.begin(), eligibleMembers.end(), leader) != eligibleMembers.end()) {
leader->onGainSharedExperience(tmpExperience, source);
}

for (auto it = memberList.begin(); it != memberList.end(); ++it) {
if (std::find(eligibleMembers.begin(), eligibleMembers.end(), (*it)) != eligibleMembers.end()) {
tmpExperience = expShare; //This ensures that the expShare amount is not multiplied in any way
(*it)->onGainSharedExperience(shareExperience, source);
}
}
}

SharedExpStatus_t Party::getMemberSharedExperienceStatus(const Player* player) const
{
if (memberList.empty()) {
return SHAREDEXP_EMPTYPARTY;
}
uint32_t highestLevel = leader->getLevel();
for (Player* member : memberList) {
if (member->getLevel() > highestLevel) {
highestLevel = member->getLevel();
}
}
uint32_t minLevel = static_cast<uint32_t>(std::ceil((static_cast<float>(highestLevel) * 2) / 3));
if (player->getLevel() < minLevel) {
return SHAREDEXP_LEVELDIFFTOOLARGE;
}
return SHAREDEXP_OK;
}

Player.h:
void onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition) override;
void onGainSharedExperience(uint64_t gainExp, Creature* source, const Position& monsterPosition);

Creature.cpp:
void Creature::onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition)
{
if (gainExp == 0 || !master) {
return;
}
gainExp /= 2;
master->onGainExperience(gainExp, target, monsterPosition);
SpectatorVec spectators;
g_game.map.getSpectators(spectators, position, false, true);
if (spectators.empty()) {
return;
}
TextMessage message(MESSAGE_EXPERIENCE_OTHERS, ucfirst(getNameDescription()) + " gained " + std::to_string(gainExp) + (gainExp != 1 ? " experience points." : " experience point."));
message.position = position;
message.primary.color = TEXTCOLOR_WHITE_EXP;
message.primary.value = gainExp;
for (Creature* spectator : spectators) {
spectator->getPlayer()->sendTextMessage(message);
}
}

Creature.h:
virtual void onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition);
 
@dchampag
There were a lot of errors, especially with one specific function when trying to compile. Normally, I try to handle such things myself, but in the case of shared, I prefer to ask so as not to spoil the code somehow, and in "certain situations" it won't work because of my actions.😅


Party.h:


Party.cpp:





Player.h:


Creature.cpp:


Creature.h:

I’ll download the source tonight and finish the repairs on this, I figured I had missed some things. Config manager must not hold range values so to fix them you just put EXP_SHARE_RANGE as x
EXP_SHARE_RANGE as y
EXP_SHARE_FLOOR as z or whatever the exact syntax is for those, removing all of the other stuff around it and that will fix that.

The other few errors are header files I missed and a 3rd argument in the call. I was jumping around on GITHUB making it harder on myself. Probably should have just downloaded the source code to begin with.

But, as I said before, is this exactly what you’re looking for or was there other details? You didn’t really say! Haha.
 
I’ll download the source tonight and finish the repairs on this, I figured I had missed some things. Config manager must not hold range values so to fix them you just put EXP_SHARE_RANGE as x
EXP_SHARE_RANGE as y
EXP_SHARE_FLOOR as z or whatever the exact syntax is for those, removing all of the other stuff around it and that will fix that.

The other few errors are header files I missed and a 3rd argument in the call. I was jumping around on GITHUB making it harder on myself. Probably should have just downloaded the source code to begin with.

But, as I said before, is this exactly what you’re looking for or was there other details? You didn’t really say! Haha.

Oh sorry! I was going to write about it and it slipped my mind 😂

I would like to divide it equally among the number of members and add a bonus depending on the number of professions:

Dragon (700 XP) : Party members (2) = 350 + bonus
Dragon (700 XP) : Party members (3) = 233 + bonus
.
.
.


Bonus to professions in party shared:

1 profession bonus - 10% (example 2x sorc)
2 profession bonus - 15% (example sorc + druid)
3 profession bonus - 30% (example sorc + druid + knight)
4 profession bonus - 50% (all professions)


If it is possible to activate Shared (apart from inclusion in the party):

1) For shared to work for a given player, he must hit a monster once every 3 minutes (then without hitting other monsters, shared is also active) after 3 minutes without attacking - shared stops working
2) Shared works even if someone is on a different floor or far away. That's why these 3 minutes too
3)Level difference allowed in shared: 50 - but if you have a better idea to make it work well, I'm willing to listen :D

Or if you have any suggestion :)
 
Oh sorry! I was going to write about it and it slipped my mind 😂

I would like to divide it equally among the number of members and add a bonus depending on the number of professions:

Dragon (700 XP) : Party members (2) = 350 + bonus
Dragon (700 XP) : Party members (3) = 233 + bonus
.
.
.


Bonus to professions in party shared:

1 profession bonus - 10% (example 2x sorc)
2 profession bonus - 15% (example sorc + druid)
3 profession bonus - 30% (example sorc + druid + knight)
4 profession bonus - 50% (all professions)


If it is possible to activate Shared (apart from inclusion in the party):

1) For shared to work for a given player, he must hit a monster once every 3 minutes (then without hitting other monsters, shared is also active) after 3 minutes without attacking - shared stops working
2) Shared works even if someone is on a different floor or far away. That's why these 3 minutes too
3)Level difference allowed in shared: 50 - but if you have a better idea to make it work well, I'm willing to listen :D

Or if you have any suggestion :)

So does this mean currently the exp is shared 100% for all members? From what I see in the code I haven't found anywhere that it's currently divided. If this is the case, dividing it is easy, if this is not the case we will have to figure out where the experience is mapped.
 
So does this mean currently the exp is shared 100% for all members? From what I see in the code I haven't found anywhere that it's currently divided. If this is the case, dividing it is easy, if this is not the case we will have to figure out where the experience is mapped.
Unfortunately, I was not able to compile it after making the changes you suggested.

Originally, despite the inclusion of shared and the information that it was enabled, the amount of exp the players received depended on how much damage they dealt to the pot (as if shared was not active)

I use: Release The Forgotten Server 1.4.2 · otland/forgottenserver (https://github.com/otland/forgottenserver/releases/tag/v1.4.2)
 
UPDATED - a couple of my calculations were wrong for addBonus, I accidently added instead of multiplying. This is in shareExperiene function.

Alright, I've downloaded the sources and come up with this, compiled successfully.

This does not contain vocation party bonus or point map for activity status, but this should get you headed in the right direction. Adding vocations for bonus gets a little bit more complicated, it's not too bad though, you will just edit the code below where it shows comments.

Let me know how this works, and I will look into the experience mapping when I have more time.

What I was asking about 100% is before any modification how much exp did each member recieve?

for both areas, leader and memberList

you will create another table for vocations, find the getVocation function and you will push_back each vocation into the new table

Then do something like

C++:
std::vector<Player*> bonusMembers;

//ADD
bonusMembers.push_back(leader);
//UNDER
eligibleMembers.push_back(leader);

//ADD
bonusMembers.push_back(member);
//UNDER
eligibleMembers.push_back(member);

uint64_t expShare = experience
expShare /= eligibleMembers.size(); //Divide exp among players in party evenly

double expAddBonus = 1; //Bonus exp before adding do not change

for (auto it = bonusMembers.begin(); it != bonusMembers.end(); ++it) {
    if(eligibleMembers.begin()->getVocation() != (*it)->getVocation()) {
        expAddbonus += 0.10; //0.10 represents 10%
    }
}

expShare *= expAddBonus; //Bonus percent multiplied by exp to be shared

C++:
//party.h
//FIND
    void shareExperience(uint64_t experience, Creature* source = nullptr);
//REPLACE WITH
    void shareExperience(uint64_t experience, Creature* source = nullptr, const Position& monsterPosition = Position());

//party.cpp
//REPLACE WHOLE FUNCTION shareExperience
void Party::shareExperience(uint64_t experience, Creature* source/* = nullptr*/, const Position& monsterPosition)
{
    // Create a temporary list to hold eligible members for experience sharing
    std::vector<Player*> eligibleMembers;

    // Check if the leader can receive experience, range of monster killed, not in PZ
    if (leader->getZone() != ZONE_PROTECTION && Position::areInRange<EXPERIENCE_SHARE_RANGE, EXPERIENCE_SHARE_RANGE, EXPERIENCE_SHARE_FLOORS>(monsterPosition, leader->getPosition())) {
        eligibleMembers.push_back(leader);
    }

    // Check if party members can receive experience, range of monster killed, not in PZ
    for (Player* member : memberList) {
        if (member->getZone() != ZONE_PROTECTION && Position::areInRange<EXPERIENCE_SHARE_RANGE, EXPERIENCE_SHARE_RANGE, EXPERIENCE_SHARE_FLOORS>(monsterPosition, member->getPosition())) {
            eligibleMembers.push_back(member);
        }
    }

    uint64_t shareExperience = experience;
    g_events->eventPartyOnShareExperience(this, shareExperience);

    uint64_t expShare = experience;
    if (eligibleMembers.size() >= 6) { //If members in range is greater than 5 divide exp among all players
        expShare /= eligibleMembers.size();
    }
    else if (eligibleMembers.size() == 1)
    {    //Exp only one party member or leader in range of monster killed
        expShare = expShare;
    }
    else
    {    //Members and or leader in range start share calculations
        expShare = (experience * 0.75);    //How much experience does everyone get? 0.75 is 75% change to desired value. 1 is 100%
        double expAddBonus = ((eligibleMembers.size() - 1) * //- 1 for members count, only 2 members no bonus, remove - 1 to make bonus for 2 members
            (3 / 100));    //3 represents the % bonus per member above 2, 3 members 78%, 4 members 81% etc.
        //I do not know if you have a config for party exp bonus, if you do that can be added here easily
        expShare *= expAddBonus; //This adds 0.03 for 3% to 0.75 for exp per member above
    }

//REPLACE WHOLE FUNCTION getMemberSharedExperienceStatus
SharedExpStatus_t Party::getMemberSharedExperienceStatus(const Player* player) const
{
    if (memberList.empty()) {
        return SHAREDEXP_EMPTYPARTY;
    }

    uint32_t highestLevel = leader->getLevel();
    for (Player* member : memberList) {
        if (member->getLevel() > highestLevel) {
            highestLevel = member->getLevel();
        }
    }

    uint32_t minLevel = static_cast<uint32_t>(std::ceil((static_cast<float>(highestLevel) * 2) / 3));
    if (player->getLevel() < minLevel) {
        return SHAREDEXP_LEVELDIFFTOOLARGE;
    }

    return SHAREDEXP_OK;
}

//player.cpp

//FIND
void Player::onGainExperience(uint64_t gainExp, Creature* target)
{
    if (hasFlag(PlayerFlag_NotGainExperience)) {
        return;
    }

    if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) {
        party->shareExperience(gainExp, target);
        //We will get a share of the experience through the sharing mechanism
        return;
    }

    Creature::onGainExperience(gainExp, target);
    gainExperience(gainExp, target);
}

//REPLAE WITH
void Player::onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition)
{
    if (hasFlag(PlayerFlag_NotGainExperience)) {
        return;
    }

    if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) {
        // && party->isSharedExperienceActive() && party->isSharedExperienceEnabled() is party share enable and active, remove this section to always share
        party->shareExperience(gainExp, target, monsterPosition);
        //We will get a share of the experience through the sharing mechanism
        return;
    }

    Creature::onGainExperience(gainExp, target, monsterPosition);
    gainExperience(gainExp, target);
}

//player.h
//FIND
    void onGainExperience(uint64_t gainExp, Creature* target) override;
 
//REPLACE WITH
    void onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition) override;
 
//creature.cpp
//FIND
    void Creature::onGainExperience(uint64_t gainExp, Creature* target)
 
//REPLACE WITH
    void Creature::onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition)
 
//FIND
    for (const auto& it : experienceMap) {
        it.first->onGainExperience(it.second, this);
    }
//REPLACE WITH
    Position monsterPosition = this->getPosition();
    for (const auto& it : experienceMap) {
        it.first->onGainExperience(it.second, this, monsterPosition);
    }

 
//FIND
    master->onGainExperience(gainExp, target);
 
//REPLACE WITH
    master->onGainExperience(gainExp, target, monsterPosition);
 
//creature.h
//FIND
    virtual void onGainExperience(uint64_t gainExp, Creature* target);

//REPLACE WITH
    virtual void onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition);
 
Last edited:
I'm just learning how to create OTS codes :D Shared is, unfortunately, completely beyond me, so I don't want to mess with anything you give me. Once I compile the whole thing, I will test it on a separate server with the basis.

I don't know why, but for me there are errors after compilation, and I checked it twice 🤔
Maybe I changed something in my previous attempts


  • "Player::OnGainExperience": function does not take 2 arguments
  • too many arguments in function call
  • "Player::gainExperience": function does not take 3 arguments

I probably didn't put a stamp somewhere. Recently, I was sitting on the code for half a day, scratching my head until I was almost bald, and it turned out that one character had not been inserted (and it was not showing me in the errors) 😂
 

Attachments

Did you start over before changes from yesterday? I removed some unnecessary code, it was moving the monsterPosition to functions that didn't need it. Code is a bit different than mine.
 
Did you start over before changes from yesterday? I removed some unnecessary code, it was moving the monsterPosition to functions that didn't need it. Code is a bit different than mine.
Actually, no, I replaced the previous functions. I'll go back to the initial settings and make the changes in a moment
 
Here is the code, fresh 1.4 with updates, you can compare your header and cpp files with this one.
Post automatically merged:


So looking here, it looks like the code for party exp share is in lua, do you have this luascript? These source edits are actually going to likely bypass these luascripts if so, that would explain some things here and why I couldn't find the dividers.
 

Attachments

Last edited:
Okay, thanks, Boss.
Your solution got rid of other errors. The only error is with the tasks I have built-in. I need to think a bit about how to solve this problem and I'll let you know after I start it :D I'll spend another hour on it, so I'll let you know when I manage to do it. :D

void onGainExperience(uint64_t gainExp, Creature* target, const Position& monsterPosition) override;
Ok, I fixed the error and compiled.

Orc give 25 exp

After being killed (no matter how much damage), both characters received 27 exp
Post automatically merged:

Simple knight only hit Orca once

Asasa(druid) reduced ~90% HP.

they both got 25 exp (100%) + 2 exp
 

Attachments

Last edited:
So we are headed in the right direction yes? I think that if you have party.lua, we should modify the shareExperience script to still use the lua, but then use the lua to iterate the other things that you are looking for. It looks like this party.lua already has the function for vocation bonus that you are looking for.

This code will still generate the exp gain for only members within range of the monster killed.

C++:
void Party::shareExperience(uint64_t experience, Creature* source/* = nullptr*/, const Position& monsterPosition)
{
    // Create a temporary list to hold eligible members for experience sharing
    std::vector<Player*> eligibleMembers;

    // Check if the leader can receive experience, range of monster killed, not in PZ
    if (leader->getZone() != ZONE_PROTECTION && Position::areInRange<EXPERIENCE_SHARE_RANGE, EXPERIENCE_SHARE_RANGE, EXPERIENCE_SHARE_FLOORS>(monsterPosition, leader->getPosition())) {
        eligibleMembers.push_back(leader);
    }

    // Check if party members can receive experience, range of monster killed, not in PZ
    for (Player* member : memberList) {
        if (member->getZone() != ZONE_PROTECTION && Position::areInRange<EXPERIENCE_SHARE_RANGE, EXPERIENCE_SHARE_RANGE, EXPERIENCE_SHARE_FLOORS>(monsterPosition, member->getPosition())) {
            eligibleMembers.push_back(member);
        }
    }

    uint64_t shareExperience = experience;
    g_events->eventPartyOnShareExperience(this, shareExperience);

    if (std::find(eligibleMembers.begin(), eligibleMembers.end(), leader) != eligibleMembers.end()) {
        leader->onGainSharedExperience(shareExperience, source);
    }

    for (auto it = memberList.begin(); it != memberList.end(); ++it) {
        if (std::find(eligibleMembers.begin(), eligibleMembers.end(), (*it)) != eligibleMembers.end()) {
            shareExperience = shareExperience; //This ensures that the expShare amount is not multiplied in any way
            (*it)->onGainSharedExperience(shareExperience, source);
        }
    }
}
 
Last edited:
So we are headed in the right direction yes? I think that if you have party.lua, we should modify the shareExperience script to still use the lua, but then use the lua to iterate the other things that you are looking for. It looks like this party.lua already has the function for vocation bonus that you are looking for.
Yes, you're right. In party.lua there is the following information:


function Party::OnShareExperience(exp)
local sharedExperienceMultiplier = 1.20 --20%
local vocationsIds = {}
local rawExp = exp
local vocationId = self:getLeader():getVocation():getBase():getId()
if vocationId ~= VOCATION_NONE then
table.insert(vocationsIds, vocationId)
end
for _, member in ipairs(self:getMembers()) do
vocationId = member:getVocation():getBase():getId()
if not table.contains(vocationsIds, vocationId) and vocationId ~= VOCATION_NONE then
table.insert(vocationsIds, vocationId)
end
end
local size = #vocationsIds
if size > 1 then
sharedExperienceMultiplier = 1.0 + ((size * (5 * (size - 1) + 10)) / 100)
end
exp = math.ceil((exp * sharedExperienceMultiplier) / (#self:getMembers() + 1))
return hasEventCallback(EVENT_CALLBACK_ONSHAREEXPERIENCE) and EventCallback(EVENT_CALLBACK_ONSHAREEXPERIENCE, self, exp, rawExp) or exp
end

Shared exp (20%) and what the vocations function looks like.

The only thing I don't like about it is that shared is 1.2 (120%).

Orc gives 25 exp.
120% = 30.

Since they received 27 exp each, it looks like the "bonus" is divided in half (apart from 1 indivisible)
That is:

Orc - 25 exp
Bonus +5 exp (20%)
2 players - gave "2.5" exp per player

Instead, each player receives a "20% bonus", -> + 5 exp
 
Yes, you're right. In party.lua there is the following information:




Shared exp (20%) and what the vocations function looks like.

The only thing I don't like about it is that shared is 1.2 (120%).

Orc gives 25 exp.
120% = 30.

Since they received 27 exp each, it looks like the "bonus" is divided in half (apart from 1 indivisible)
That is:

Orc - 25 exp
Bonus +5 exp (20%)
2 players - gave "2.5" exp per player

Sounds good, I updated the code for party.cpp above, so we will need to modify the party.lua code. What is the exp gain with this update?

With this we can use the point map that the game generates to instead of disabling the party experience share, it will only remove these players from eligible members table, but that can be added after we've made everything else work properly.
 
Before:
Orc: 25 exp

Shared - both 27 exp

After updating
Shared - both 36 exp

+44%

I need to give them XP and make a stronger mob xD moment
 

Attachments

Back
Top