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

TFS 1.4.2 Shared Exp

Crystals

Member
Joined
Sep 28, 2024
Messages
58
Reaction score
10
Hello! :)

I found some information on github about how to create shared EXP, but after a few tries I decided it would be better to ask someone :D


In party.cpp I looked for how to change shared current to:

If the mob has 100 exp:

2 people
in party get 50 exp but:

(100exp : 2 players) + 25% exp party bonus (62.5 exp each gets)

3+ people (100:3)+25%

and so on.

The difference is that if there are 4 different classes in the team (Sorc, druid, knight, paladin), then the XP bonus is not 25% but 50% (the condition is 4 different classes).



I'm attaching my party.cpp and party.h, maybe if someone wants to help me, it will be easier for them to look at the files :)

Thank you in advance and have a nice weekend :D

I have tfs 1.4.2 otcv8 10.98 :)
 

Attachments

Any idea? :D Shared doesnt work. I use a distribution:


I tried setting shared but it doesn't work

data\scripts\lib\event_callbacks.lua
LUA:
-- Party
EVENT_CALLBACK_ONJOIN = 6
EVENT_CALLBACK_ONLEAVE = 7
EVENT_CALLBACK_ONDISBAND = 8
EVENT_CALLBACK_ONSHAREEXPERIENCE = 9

Code:
-- Party
["onJoin"] = EVENT_CALLBACK_ONJOIN,
["onLeave"] = EVENT_CALLBACK_ONLEAVE,
["onDisband"] = EVENT_CALLBACK_ONDISBAND,
["onShareExperience"] = EVENT_CALLBACK_ONSHAREEXPERIENCE,

data\events\scripts\party.lua
LUA:
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

Party.cpp
LUA:
void Party::updateSharedExperience()
{
    if (sharedExpActive) {
        bool result = getSharedExperienceStatus() == SHAREDEXP_OK;
        if (result != sharedExpEnabled) {
            sharedExpEnabled = result;
            updateAllPartyIcons();
        }
    }
}

namespace {

const char* getSharedExpReturnMessage(SharedExpStatus_t value)
{
    switch (value) {
        case SHAREDEXP_OK:
            return "Shared Experience is now active.";
        case SHAREDEXP_TOOFARAWAY:
            return "Shared Experience has been activated, but some members of your party are too far away.";
        case SHAREDEXP_LEVELDIFFTOOLARGE:
            return "Shared Experience has been activated, but the level spread of your party is too wide.";
        case SHAREDEXP_MEMBERINACTIVE:
            return "Shared Experience has been activated, but some members of your party are inactive.";
        case SHAREDEXP_EMPTYPARTY:
            return "Shared Experience has been activated, but you are alone in your party.";
        default:
            return "An error occured. Unable to activate shared experience.";
    }
}

}

bool Party::setSharedExperience(Player* player, bool sharedExpActive)
{
    if (!player || leader != player) {
        return false;
    }

    if (this->sharedExpActive == sharedExpActive) {
        return true;
    }

    this->sharedExpActive = sharedExpActive;

    if (sharedExpActive) {
        SharedExpStatus_t sharedExpStatus = getSharedExperienceStatus();
        this->sharedExpEnabled = sharedExpStatus == SHAREDEXP_OK;
        leader->sendTextMessage(MESSAGE_INFO_DESCR, getSharedExpReturnMessage(sharedExpStatus));
    } else {
        leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been deactivated.");
    }

    updateAllPartyIcons();
    return true;
}

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;
}

void Party::updatePlayerTicks(Player* player, uint32_t points)
{
    if (points != 0 && !player->hasFlag(PlayerFlag_NotGainInFight)) {
        ticksMap[player->getID()] = OTSYS_TIME();
        updateSharedExperience();
    }
}

void Party::clearPlayerPoints(Player* player)
{
    auto it = ticksMap.find(player->getID());
    if (it != ticksMap.end()) {
        ticksMap.erase(it);
        updateSharedExperience();
    }
}

Party.h

LUA:
// Copyright 2022 The Forgotten Server Authors. All rights reserved.
// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file.

#ifndef FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044
#define FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044

#include "player.h"
#include "monsters.h"

class Player;
class Party;

using PlayerVector = std::vector<Player*>;

static constexpr int32_t EXPERIENCE_SHARE_RANGE = 30;
static constexpr int32_t EXPERIENCE_SHARE_FLOORS = 1;

enum SharedExpStatus_t : uint8_t {
    SHAREDEXP_OK,
    SHAREDEXP_TOOFARAWAY,
    SHAREDEXP_LEVELDIFFTOOLARGE,
    SHAREDEXP_MEMBERINACTIVE,
    SHAREDEXP_EMPTYPARTY
};

class Party
{
    public:
        explicit Party(Player* leader);

        Player* getLeader() const {
            return leader;
        }
        PlayerVector& getMembers() {
            return memberList;
        }
        const PlayerVector& getInvitees() const {
            return inviteList;
        }
        size_t getMemberCount() const {
            return memberList.size();
        }
        size_t getInvitationCount() const {
            return inviteList.size();
        }

        void disband();
        bool invitePlayer(Player& player);
        bool joinParty(Player& player);
        void revokeInvitation(Player& player);
        bool passPartyLeadership(Player* player);
        bool leaveParty(Player* player);

        bool removeInvite(Player& player, bool removeFromPlayer = true);

        bool isPlayerInvited(const Player* player) const;
        void updateAllPartyIcons();
        void broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations = false);
        bool empty() const {
            return memberList.empty() && inviteList.empty();
        }
        bool canOpenCorpse(uint32_t ownerId) const;

        void shareExperience(uint64_t experience, Creature* source = nullptr);
        bool setSharedExperience(Player* player, bool sharedExpActive);
        bool isSharedExperienceActive() const {
            return sharedExpActive;
        }
        bool isSharedExperienceEnabled() const {
            return sharedExpEnabled;
        }
        bool canUseSharedExperience(const Player* player) const;
        SharedExpStatus_t getMemberSharedExperienceStatus(const Player* player) const;
        void updateSharedExperience();

        void updatePlayerTicks(Player* player, uint32_t points);
        void clearPlayerPoints(Player* player);

    private:
        SharedExpStatus_t getSharedExperienceStatus();

        std::map<uint32_t, int64_t> ticksMap;

        PlayerVector memberList;
        PlayerVector inviteList;

        Player* leader;

        bool sharedExpActive = false;
        bool sharedExpEnabled = false;
};

#endif
 
Last edited:
I’ll look into this later when I finish the code for it. I must have missed where the exp division is being made unless that’s happening due to the fact that sharing experience isn’t enabled so it’s dividing it.
 
Your experience share is not activating? what do the shields look like, does it say it's activated?
Post automatically merged:

I haven't gotten into testing the 1.5 I'm working on yet, so sorry but this is what I can gather from reading the code changes below.

I think I messed up something in my RSA source code and that's preventing me from using my 1.5, i could not log in to start my migration then got caught up in my old project stability issues and expansion because it's working 100% I decided to move forward with updates, soon I'll attempt migration again haha.

LUA:
function Party:onJoin(player)
    if hasEventCallback(EVENT_CALLBACK_ONJOIN) then
        return EventCallback(EVENT_CALLBACK_ONJOIN, self, player)
    else
        return true
    end
end

function Party:onLeave(player)
    if hasEventCallback(EVENT_CALLBACK_ONLEAVE) then
        return EventCallback(EVENT_CALLBACK_ONLEAVE, self, player)
    else
        return true
    end
end

function Party:onDisband()
    if hasEventCallback(EVENT_CALLBACK_ONDISBAND) then
        return EventCallback(EVENT_CALLBACK_ONDISBAND, self)
    else
        return true
    end
end

function Party:onShareExperience(exp)
    local sharedExperienceMultiplier = 1.00 --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
    ]]--
    
    sharedExperienceMultiplier = sharedExperienceMultiplier + ((#self:getMembers() + 1) * 0.25) -- 0.25 is the % per player
    --exp = math.ceil((exp * sharedExperienceMultiplier) / (#self:getMembers() + 1))
    
    exp = math.ceil(exp / #self:getMembers() + 1 * sharedexperienceMultiplier)
    return hasEventCallback(EVENT_CALLBACK_ONSHAREEXPERIENCE) and EventCallback(EVENT_CALLBACK_ONSHAREEXPERIENCE, self, exp, rawExp) or exp
end
 
Last edited:
Back
Top