• 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.X+ [TFS 1.4] No loot drop in severely unfair fights (PvP)

krafttomten

Well-Known Member
Joined
Jul 23, 2017
Messages
103
Solutions
1
Reaction score
75
Location
Sweden
Hello OTLand,

I am making a RPPvP server. I have modified AoL and blessings so that they do not prevent item loss in PvP. But now I want to disable it in case there was a severely unfair fight (level 200 killing a level 50 is an exaggerated example). But I need some help.

First, I have tried to implement this strictly in Lua.

Lua
I figured that the onDeath() function used in droploot.lua could be changed so that it calculates if the kill was severely unfair.
Lua:
function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
-- I am using the standard TFS 1.4 code here, because it is familiar to other people.
-- I can implement any solution into my own modified code without problem.
-- I will release the final solution in case anyone is interested.
    if player:hasFlag(PlayerFlag_NotGenerateLoot) or player:getVocation():getId() == VOCATION_NONE then
        return true
    end

    local amulet = player:getSlotItem(CONST_SLOT_NECKLACE)
    local isRedOrBlack = table.contains({SKULL_RED, SKULL_BLACK}, player:getSkull())
    if amulet and amulet.itemid == ITEM_AMULETOFLOSS and not isRedOrBlack then
        local isPlayer = false
        if killer then
            if killer:isPlayer() then
                isPlayer = true
            else
                local master = killer:getMaster()
                if master and master:isPlayer() then
                    isPlayer = true
                end
            end
        end

        if not isPlayer or not player:hasBlessing(6) then
            player:removeItem(ITEM_AMULETOFLOSS, 1, -1, false)
        end
    else
        for i = CONST_SLOT_HEAD, CONST_SLOT_AMMO do
            local item = player:getSlotItem(i)
            local lossPercent = player:getLossPercent()
            if item then
                if isRedOrBlack or math.random(item:isContainer() and 100 or 1000) <= lossPercent then
                    if (isRedOrBlack or lossPercent ~= 0) and not item:moveTo(corpse) then
                        item:remove()
                    end
                end
            end
        end
    end

    if not player:getSlotItem(CONST_SLOT_BACKPACK) then
        player:addItem(ITEM_BAG, 1, false, CONST_SLOT_BACKPACK)
    end
    return true
end
The problem is that I do not have access to all the killers in the onDeath() function, I can only see player, corpse, killer, mostDamageKiller, lastHitUnjustified, and mostDamageUnjustified. I don't see how that is enough information to cover all scenarios of an unfair fight. For instance, if a level 50 was killed by ten level 45:s, it would not be possible, to my knowledge, to deduce that it was an unfair fight in Lua, and thus prevent item loss. At best I have managed to cover the scenario where one high level kills a low level, but that is far from satisfying.


The second method I have tried is to change the parameters of the onDeath() lua function through source editing.

C++
In C++ there are two parts I think I could combine somehow, but I am very inexperienced with C++, and I don't know how to make it work.
In creature.cpp there is a code that.. makes the lua function.. somehow..
Void Creature::eek:nDeath()
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 == nullptr || 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 figured, if I could add a parameter into this function, a parameter containing all the killers, then I could solve the rest in Lua. There is a C++ function called getKillers(), but I don't know how to implement that as a parameter in the Creature::eek:nDeath() function.

I guess my question is:
How do I add a new parameter containing all killers in the Lua function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)?

My secondary question is:
Is there any other way I could prevent item loss in severely unfair pvp situations?
 
Solution
Hello OTLand,

I am making a RPPvP server. I have modified AoL and blessings so that they do not prevent item loss in PvP. But now I want to disable it in case there was a severely unfair fight (level 200 killing a level 50 is an exaggerated example). But I need some help.

First, I have tried to implement this strictly in Lua.

Lua
I figured that the onDeath() function used in droploot.lua could be changed so that it calculates if the kill was severely unfair.
Lua:
function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
-- I am using the standard TFS 1.4 code here, because it is familiar to other people.
-- I can implement any solution into my own modified code without problem...
Hello OTLand,

I am making a RPPvP server. I have modified AoL and blessings so that they do not prevent item loss in PvP. But now I want to disable it in case there was a severely unfair fight (level 200 killing a level 50 is an exaggerated example). But I need some help.

First, I have tried to implement this strictly in Lua.

Lua
I figured that the onDeath() function used in droploot.lua could be changed so that it calculates if the kill was severely unfair.
Lua:
function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
-- I am using the standard TFS 1.4 code here, because it is familiar to other people.
-- I can implement any solution into my own modified code without problem.
-- I will release the final solution in case anyone is interested.
    if player:hasFlag(PlayerFlag_NotGenerateLoot) or player:getVocation():getId() == VOCATION_NONE then
        return true
    end

    local amulet = player:getSlotItem(CONST_SLOT_NECKLACE)
    local isRedOrBlack = table.contains({SKULL_RED, SKULL_BLACK}, player:getSkull())
    if amulet and amulet.itemid == ITEM_AMULETOFLOSS and not isRedOrBlack then
        local isPlayer = false
        if killer then
            if killer:isPlayer() then
                isPlayer = true
            else
                local master = killer:getMaster()
                if master and master:isPlayer() then
                    isPlayer = true
                end
            end
        end

        if not isPlayer or not player:hasBlessing(6) then
            player:removeItem(ITEM_AMULETOFLOSS, 1, -1, false)
        end
    else
        for i = CONST_SLOT_HEAD, CONST_SLOT_AMMO do
            local item = player:getSlotItem(i)
            local lossPercent = player:getLossPercent()
            if item then
                if isRedOrBlack or math.random(item:isContainer() and 100 or 1000) <= lossPercent then
                    if (isRedOrBlack or lossPercent ~= 0) and not item:moveTo(corpse) then
                        item:remove()
                    end
                end
            end
        end
    end

    if not player:getSlotItem(CONST_SLOT_BACKPACK) then
        player:addItem(ITEM_BAG, 1, false, CONST_SLOT_BACKPACK)
    end
    return true
end
The problem is that I do not have access to all the killers in the onDeath() function, I can only see player, corpse, killer, mostDamageKiller, lastHitUnjustified, and mostDamageUnjustified. I don't see how that is enough information to cover all scenarios of an unfair fight. For instance, if a level 50 was killed by ten level 45:s, it would not be possible, to my knowledge, to deduce that it was an unfair fight in Lua, and thus prevent item loss. At best I have managed to cover the scenario where one high level kills a low level, but that is far from satisfying.


The second method I have tried is to change the parameters of the onDeath() lua function through source editing.

C++
In C++ there are two parts I think I could combine somehow, but I am very inexperienced with C++, and I don't know how to make it work.
In creature.cpp there is a code that.. makes the lua function.. somehow..
Void Creature::eek:nDeath()
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 == nullptr || 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 figured, if I could add a parameter into this function, a parameter containing all the killers, then I could solve the rest in Lua. There is a C++ function called getKillers(), but I don't know how to implement that as a parameter in the Creature::eek:nDeath() function.

I guess my question is:
How do I add a new parameter containing all killers in the Lua function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)?

My secondary question is:
Is there any other way I could prevent item loss in severely unfair pvp situations?
Hello,

In LUA, you can do player:getDamageMap() (assuming that you are using 'player' as variable name, in fact its a 'Creature' UserData), you can check the function here, something like this should work:
Lua:
function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
    local damageMap = player:getDamageMap() --Get damage map from killed Creature
   
    local killersCount = 0 --Will store the amount of players that killed the target

    for cid, dmgInfo in pairs(damageMap) do --the key is the 'cid' of the creature and 'dmgInfo' is a value with 2 fields 'total' and 'ticks', respectively the amount of damage dealt by the cid and how much time in ticks since the first damage
        local tmpPlayer = Player(cid) --try to get the Player UserData using the cid
        if tmpPlayer then --if exists, we can use the info such as getLevel()...
            --You can check here for individual level of each player
            killersCount = killersCount + 1
        end
    end

    if killersCount >= 5 then --if more than 5 players in the kill
        print("Its a unfair battle!")
    else
        print("Owned!")
    end

    return true
end

I strongly recommend you to change the variable name to creature instead of player in your onDeath function.
 
Solution
Hello,

In LUA, you can do player:getDamageMap() (assuming that you are using 'player' as variable name, in fact its a 'Creature' UserData), you can check the function here, something like this should work:
Lua:
function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
    local damageMap = player:getDamageMap() --Get damage map from killed Creature
  
    local killersCount = 0 --Will store the amount of players that killed the target

    for cid, dmgInfo in pairs(damageMap) do --the key is the 'cid' of the creature and 'dmgInfo' is a value with 2 fields 'total' and 'ticks', respectively the amount of damage dealt by the cid and how much time in ticks since the first damage
        local tmpPlayer = Player(cid) --try to get the Player UserData using the cid
        if tmpPlayer then --if exists, we can use the info such as getLevel()...
            --You can check here for individual level of each player
            killersCount = killersCount + 1
        end
    end

    if killersCount >= 5 then --if more than 5 players in the kill
        print("Its a unfair battle!")
    else
        print("Owned!")
    end

    return true
end

I strongly recommend you to change the variable name to creature instead of player in your onDeath function.
Hah, I feel pretty stupid for missing the :getDamageMap function, it's such a simple solution! Thank you very much for pointing it out. I have implemented it on my server and it seems to work.
 
This is awesome! Its crazy how often thoughts connect. I was working on the onDeath event last week for the wiki, never uploaded the finished work for that event, but this is the script I had made


Lua:
function onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
    if creature:isMonster() and killer:isMonster() then
        return -- returning false would serve as same result as return here, just exits the script. So if monster killed monster, do nothing.
    end

    if not lastHitUnjustified and not mostDamageUnjustified then
        return -- exits script if this wasn't an unjust kill
    end

    if killer and mostDamageKiller then -- makes sure neither are nil
        if killer:isPlayer() and mostDamageKiller:isPlayer() then -- checks if both are player class
            if killer:getGuid() == mostDamageKiller:getGuid() then -- if both are one in the same
                return -- exits script, because we are looking for multiple players in an unjust kill
            end
            --- here we now know it has to be at least two players who took part in the kill
            corpse:remove() -- we destroyed the corpse so those pker's don't get it!
            return -- scripts purpose is served, we exit
        end
    end
end

Its funny, because although its not the same exact concept, its right there along the same lines, the above just makes sure there is more than one killer and that its an unjust kill, at which point it removes the corpse ondeath :D
 
Back
Top