• 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++ Monsters killed by other monsters don't drop loot

X X X

Newb
Joined
Jul 26, 2015
Messages
146
Reaction score
13
As the title says. Just in case that's not clear enough, I'll give some examples:

I /summon a trainer. I /m a whole bunch of wolves, and one demon. The "wild" wolves and demon all attack my trainer and eventually the demon will inevitably use GFB and kill all of the wolves. Even if I set the wolves' loot drop rate to 100% for anything, meat, worms, whatever, all the wolf corpses will be empty. However if I /m a whole bunch of wolves and kill them myself with UE, they drop loot like normal.

Same thing happens if a "wild" creature (I'll use a wolf again) runs on a naturally occurring magic field or a magic field created by a wild monster. However if I (or any player) is the source of the magic field, loot will be dropped like normal.

And a final example for good measure: If I /summon a trainer then /m some nightmares, the nightmares will eventually kill each other with their poison bomb and when they eventually die, the corpses will not contain any loot.

Using OTX3, version 7.6. I tried checking game.cpp, actions.cpp, combat.cpp, but I can't seem to find anything that would be responsible for this type of behavior.

Any help on this would be appreciated as I have been trying to fix this for weeks now and am at a loss.
Thx
 
Solution
The guys who "downgraded" this distribution missed so much stuff. They failed to remove the entire corpse ownership system. An easy fix:

In monsters.cpp, find the call to create loot (line ~49):
C++:
void MonsterType::createLoot(Container* corpse)

Then, within that function, find the third "if" statement:
C++:
if (owner)
add:
C++:
if (owner || !owner)

The problem is that right off the bat inside void MonsterType::createLoot, the game is only counting a corpse as a true corpse (i.e. one that can drop loot), if it has an owner:
C++:
Player* owner = g_game.getPlayerByID(corpse->getCorpseOwner());
This is also the case with monster.cpp inside the function Item* Monster::getCorpse:
C++:
Item* corpse =...
What is /summon, post the script.
Or did you mean utevo res~ ?

The "bug" should be in monster.cpp, maybe creature.cpp with some death function.
 
Thanks for the reply. I will post my /summon when I get home, but that's besides the point. Say I'm just playing as a regular player, not a God or GM, and I'm fighting a dragon and an elf at the same time. Eventually the dragon will kill the elf with firewave or some other AOE and the elf won't drop loot. The fact that I /summoned a monster in the above example is irrelevant, it's just to paint a scenario where two "wild" monsters are forced to damage each other, the /summoned monster isn't attacking anything at all.
 
Thanks for the reply. I will post my /summon when I get home, but that's besides the point. Say I'm just playing as a regular player, not a God or GM, and I'm fighting a dragon and an elf at the same time. Eventually the dragon will kill the elf with firewave or some other AOE and the elf won't drop loot. The fact that I /summoned a monster in the above example is irrelevant, it's just to paint a scenario where two "wild" monsters are forced to damage each other, the /summoned monster isn't attacking anything at all.

Except it does matter.
I have no clue about OTX but since it's based on TFS it might be 1.x?
Ex here;
Lua:
function Creature:addSummon(monster)
    local summon = Monster(monster)
    if not summon then
        return false
    end

    summon:setTarget(nil)
    summon:setFollowCreature(nil)
    summon:setDropLoot(false)
    summon:setSkillLoss(false)
    summon:setMaster(self)

    return true
end

Ref; forgottenserver/creature.lua at 5ba1ef3926695237a8da3d969bd99e779a0ae7f1 · otland/forgottenserver · GitHub

Lua:
summon:setDropLoot(false)
 
Alright forget the /summon part altogether, my bad for including that example as it is making things confusing for the reader. The problem is with normal, regular, monsters that are placed in Remere's OR created by /m.

I'll give another example to show my point, and I'll also provide my /summon as you asked.

On my map.otbm, in Remere's, I place, in a room, a regular spawn of 5 nightmares. I startup my server, login on a normal character, run into that room with the nightmares and never attack them once, just sit there and heal and let them slowly kill each other with poison AOE. Eventually, 4 of them well be dead due to the their own AOE and there will be one lone survivor with low HP. I finally attack this last one and because I participated in the fight, the last one that I kill will drop loot. The other 4 that were killed be each other will not drop any loot.

Here is my place_summon.lua:

Code:
function onSay(player, words, param)
    if not player:getGroup():getAccess() then
        return true
    end

    if player:getAccountType() < ACCOUNT_TYPE_GOD then
        return false
    end

    local position = player:getPosition()
    local monster = Game.createMonster(param, position)
    if monster ~= nil then
        monster:setMaster(player)
        position:sendMagicEffect(CONST_ME_MAGIC_RED)
    else
        player:sendCancelMessage("There is not enough room.")
        position:sendMagicEffect(CONST_ME_POFF)
    end
    return false
end

and just in case you wanted it as well my place_monster.lua:

Code:
function onSay(player, words, param)
    if not player:getGroup():getAccess() then
        return true
    end

    if player:getAccountType() < ACCOUNT_TYPE_GOD then
        return false
    end

    local position = player:getPosition()
    local monster = Game.createMonster(param, position)
    if monster ~= nil then
        if monster:getType():isRewardBoss() then
            monster:setReward(true)
        end
        monster:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
        position:sendMagicEffect(CONST_ME_MAGIC_RED)
    else
        player:sendCancelMessage("There is not enough room.")
        position:sendMagicEffect(CONST_ME_POFF)
    end
    return false
end

Sorry if I'm seeming picky here but I can't see how /summon can be the issue if it happens to normal monsters that have regular spawns that I set in Remere's. But hey if you can see something in those two files then go for it.

Except it does matter.
Lua:
summon:setDropLoot(false)

I totally understand what you're getting at here, but we might not be on the same page. I know that monsters I utevo res or /summon into existence won't drop anything, and I already know that monsters I /m into existence DO drop stuff...but only IF killed by a player, and that's what I'm trying to figure out.

The "bug" should be in monster.cpp, maybe creature.cpp with some death function.

I was already thinking this same thing, but I'm having trouble finding where that is determined.

Thanks
 
Last edited:
Alright forget the /summon part altogether, my bad for including that example as it is making things confusing for the reader. The problem is with normal, regular, monsters that are placed in Remere's OR created by /m.

I'll give another example to show my point, and I'll also provide my /summon as you asked.

On my map.otbm, in Remere's, I place, in a room, a regular spawn of 5 nightmares. I startup my server, login on a normal character, run into that room with the nightmares and never attack them once, just sit there and heal and let them slowly kill each other with poison AOE. Eventually, 4 of them well be dead due to the their own AOE and there will be one lone survivor with low HP. I finally attack this last one and because I participated in the fight, the last one that I kill will drop loot. The other 4 that were killed be each other will not drop any loot.

Here is my place_summon.lua:

Code:
function onSay(player, words, param)
    if not player:getGroup():getAccess() then
        return true
    end

    if player:getAccountType() < ACCOUNT_TYPE_GOD then
        return false
    end

    local position = player:getPosition()
    local monster = Game.createMonster(param, position)
    if monster ~= nil then
        monster:setMaster(player)
        position:sendMagicEffect(CONST_ME_MAGIC_RED)
    else
        player:sendCancelMessage("There is not enough room.")
        position:sendMagicEffect(CONST_ME_POFF)
    end
    return false
end

and just in case you wanted it as well my place_monster.lua:

Code:
function onSay(player, words, param)
    if not player:getGroup():getAccess() then
        return true
    end

    if player:getAccountType() < ACCOUNT_TYPE_GOD then
        return false
    end

    local position = player:getPosition()
    local monster = Game.createMonster(param, position)
    if monster ~= nil then
        if monster:getType():isRewardBoss() then
            monster:setReward(true)
        end
        monster:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
        position:sendMagicEffect(CONST_ME_MAGIC_RED)
    else
        player:sendCancelMessage("There is not enough room.")
        position:sendMagicEffect(CONST_ME_POFF)
    end
    return false
end

Sorry if I'm seeming picky here but I can't see how /summon can be the issue if it happens to normal monsters that have regular spawns that I set in Remere's. But hey if you can see something in those two files then go for it.



I totally understand what you're getting at here, but we might not be on the same page. I know that monsters I utevo res or /summon into existence won't drop anything, and I already know that monsters I /m into existence DO drop stuff...but only IF killed by a player, and that's what I'm trying to figure out.



I was already thinking this same thing, but I'm having trouble finding where that is determined.

Thanks

In that case take a look here;
forgottenserver/creature.cpp at 68ab2c606782fe9a38c5a0013a0df25588cabe26 · otland/forgottenserver · GitHub
 
Hmmm I've gone through this line by line and I can't seem to find where the problem is. I'm going to post that bool for Creature:dropcorpse and hopefully someone can point out the issue because I am stuck Dx

Code:
bool Creature::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified)
{
    if (!lootDrop && getMonster() && !(!g_config.getBoolean(ConfigManager::SUMMONS_DROP_CORPSE) && master && master->getPlayer())) {
        if (master) {
            //scripting event - onDeath
            const CreatureEventList& deathEvents = getCreatureEvents(CREATURE_EVENT_DEATH);
            for (CreatureEvent* deathEvent : deathEvents) {
                deathEvent->executeOnDeath(this, nullptr, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified);
            }
        }

        g_game.addMagicEffect(getPosition(), CONST_ME_POFF);
    } else {
        Item* splash;
        switch (getRace()) {
            case RACE_VENOM:
                splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_GREEN);
                break;

            case RACE_BLOOD:
                splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD);
                break;

            default:
                splash = nullptr;
                break;
        }

        Tile* tile = getTile();

        if (splash) {
            g_game.internalAddItem(tile, splash, INDEX_WHEREEVER, FLAG_NOLIMIT);
            g_game.startDecay(splash);
        }

        Item* corpse = getCorpse(lastHitCreature, mostDamageCreature);
        if (corpse) {
            g_game.internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT);
            g_game.startDecay(corpse);
        }

        //scripting event - onDeath
        for (CreatureEvent* deathEvent : getCreatureEvents(CREATURE_EVENT_DEATH)) {
            deathEvent->executeOnDeath(this, corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified);
        }

        if (corpse) {
            dropLoot(corpse->getContainer(), lastHitCreature);
        }
    }

    return true;
}
 
Hmmm I've gone through this line by line and I can't seem to find where the problem is. I'm going to post that bool for Creature:dropcorpse and hopefully someone can point out the issue because I am stuck Dx

Code:
bool Creature::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified)
{
    if (!lootDrop && getMonster() && !(!g_config.getBoolean(ConfigManager::SUMMONS_DROP_CORPSE) && master && master->getPlayer())) {
        if (master) {
            //scripting event - onDeath
            const CreatureEventList& deathEvents = getCreatureEvents(CREATURE_EVENT_DEATH);
            for (CreatureEvent* deathEvent : deathEvents) {
                deathEvent->executeOnDeath(this, nullptr, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified);
            }
        }

        g_game.addMagicEffect(getPosition(), CONST_ME_POFF);
    } else {
        Item* splash;
        switch (getRace()) {
            case RACE_VENOM:
                splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_GREEN);
                break;

            case RACE_BLOOD:
                splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD);
                break;

            default:
                splash = nullptr;
                break;
        }

        Tile* tile = getTile();

        if (splash) {
            g_game.internalAddItem(tile, splash, INDEX_WHEREEVER, FLAG_NOLIMIT);
            g_game.startDecay(splash);
        }

        Item* corpse = getCorpse(lastHitCreature, mostDamageCreature);
        if (corpse) {
            g_game.internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT);
            g_game.startDecay(corpse);
        }

        //scripting event - onDeath
        for (CreatureEvent* deathEvent : getCreatureEvents(CREATURE_EVENT_DEATH)) {
            deathEvent->executeOnDeath(this, corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified);
        }

        if (corpse) {
            dropLoot(corpse->getContainer(), lastHitCreature);
        }
    }

    return true;
}

You have to know that the thing you want is not a problem, it's designed to be like this, so it's more where to fix it xD
Well the function calls Creature::dropLoot; forgottenserver/monster.cpp at 155ea351565b8091393ff27d69c587a7baedf5a9 · otland/forgottenserver · GitHub
That then calls MonsterType::createLoot; forgottenserver/monsters.cpp at 967d82305e7bdbc54a5dbe92c45bd9caee736117 · otland/forgottenserver · GitHub

I have no ide tbh, you have to try and add some prints to see what happens but the main problem should be in Creature::dropCorpse.
But take a look here;
C++:
    if (!lootDrop && getMonster() && !(!g_config.getBoolean(ConfigManager::SUMMONS_DROP_CORPSE) && master && master->getPlayer())) {
        if (master) {
 
You have to know that the thing you want is not a problem, it's designed to be like this, so it's more where to fix it xD

Is it supposed to be this way? I swear I remember monsters accidentally killing each other in real Tibia and being able to go open the corpse and grab loot...but w/e it is what it is.

Also, my monsters.cpp does not have the same line that you linked in forgottenserver/monster.cpp at 155ea351565b8091393ff27d69c587a7baedf5a9 · otland/forgottenserver · GitHub

I know, my C++ has a long way to go. Like I understand roughly what's going on when I look through this, checking player stamina and then voiding the loot if it is too low, but it takes me a long time to find what I am looking for and then actually change it the right way, lots of failed rebuilds...but hey that's how to learn the best I guess. Well I'll keep playing with this and doing prints but anyone else reading this please don't hesitate to enlighten me if you know how to solve this or need me to other source info. Thanks~
 
Is it supposed to be this way? I swear I remember monsters accidentally killing each other in real Tibia and being able to go open the corpse and grab loot...but w/e it is what it is.

Also, my monsters.cpp does not have the same line that you linked in forgottenserver/monster.cpp at 155ea351565b8091393ff27d69c587a7baedf5a9 · otland/forgottenserver · GitHub

I know, my C++ has a long way to go. Like I understand roughly what's going on when I look through this, checking player stamina and then voiding the loot if it is too low, but it takes me a long time to find what I am looking for and then actually change it the right way, lots of failed rebuilds...but hey that's how to learn the best I guess. Well I'll keep playing with this and doing prints but anyone else reading this please don't hesitate to enlighten me if you know how to solve this or need me to other source info. Thanks~

Didn't mean that, I can't remember how it should it.
But seeing as it's coded like that it most likely was like that in the past alot of the code we run is "outdated", ex the PvP system, monsters AI etc

The link is dead, can you use the code tags insted? How to display CODE properly in your post

Well most of us here needs more experience with C++ (and some with Lua) to be able to do things good / great.
I could probbly help you out, but since this is the support board and not request board we are mainly here to assist - to help you get better.

Insted of feeding you with things we try to tech you how to ex read an error to see what the problem is.
But keep the thread updated it that dosn't work and ill try to help you out, but as I said it's a great "lesson" to try things like this where you don't have alot to lose :p
 
Yeah my bad I just copied the link wrong, but its the exact same one you posted so anyone who really cares can just click on the one literally right above. Anyways, yeah I know how what to expect from the support board and request board but I guess I assumed it was an error and not intentionally programmed this way. Although I've had to fix so much stupid stuff in this distro that I shouldn't be surprised that they made it wrong.

Anyways I'll keep messing with it and give it a bump every few days if I can't get it.
 
Yeah my bad I just copied the link wrong, but its the exact same one you posted so anyone who really cares can just click on the one literally right above. Anyways, yeah I know how what to expect from the support board and request board but I guess I assumed it was an error and not intentionally programmed this way. Although I've had to fix so much stupid stuff in this distro that I shouldn't be surprised that they made it wrong.

Anyways I'll keep messing with it and give it a bump every few days if I can't get it.

I linked 3 of them and you talked about a specific line, you mean this one? forgottenserver/monsters.cpp at 967d82305e7bdbc54a5dbe92c45bd9caee736117 · otland/forgottenserver · GitHub
What line are you missing from it? Or are you missing the function itself?

Well the main problem is that ppl don't upload the "fixes", so a tip is to upload the fixes you make.

But then IMO there has to be a blind guy approving and merging code there seeing to the amount of bugs they have, just look at the last 5-10 support pages and count how many OTX / TFS threads there are with C++ issues / bugs.
 
I'm just getting back into a project after several years and I was never able to solve this problem. I didn't think to create a new thread since this I was never able to solve this one.
 
The guys who "downgraded" this distribution missed so much stuff. They failed to remove the entire corpse ownership system. An easy fix:

In monsters.cpp, find the call to create loot (line ~49):
C++:
void MonsterType::createLoot(Container* corpse)

Then, within that function, find the third "if" statement:
C++:
if (owner)
add:
C++:
if (owner || !owner)

The problem is that right off the bat inside void MonsterType::createLoot, the game is only counting a corpse as a true corpse (i.e. one that can drop loot), if it has an owner:
C++:
Player* owner = g_game.getPlayerByID(corpse->getCorpseOwner());
This is also the case with monster.cpp inside the function Item* Monster::getCorpse:
C++:
Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature);
    if (corpse) {
        if (mostDamageCreature) {
            if (mostDamageCreature->getPlayer()) {
                corpse->setCorpseOwner(mostDamageCreature->getID());
            } else {
                const Creature* mostDamageCreatureMaster = mostDamageCreature->getMaster();
                if (mostDamageCreatureMaster && mostDamageCreatureMaster->getPlayer()) {
                    corpse->setCorpseOwner(mostDamageCreatureMaster->getID());
                }
            }
        }
    }
The game is defining a "corpse" ONLY as a monster that was killed by a player (the 'owner') or a player's summon.

Long story short, whenever a monster kills another monster, there was no owner, so the game didn't think it was supposed to drop items. The "right" way to fix this would be by re-writing the functions to not include any of the corpse ownership garbage. The fix I'm providing basically tells the game to drop loot, whether or not there is an owner.
 
Solution
The guys who "downgraded" this distribution missed so much stuff. They failed to remove the entire corpse ownership system. An easy fix:

In monsters.cpp, find the call to create loot (line ~49):
C++:
void MonsterType::createLoot(Container* corpse)

Then, within that function, find the third "if" statement:
C++:
if (owner)
add:
C++:
if (owner || !owner)

The problem is that right off the bat inside void MonsterType::createLoot, the game is only counting a corpse as a true corpse (i.e. one that can drop loot), if it has an owner:
C++:
Player* owner = g_game.getPlayerByID(corpse->getCorpseOwner());
This is also the case with monster.cpp inside the function Item* Monster::getCorpse:
C++:
Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature);
    if (corpse) {
        if (mostDamageCreature) {
            if (mostDamageCreature->getPlayer()) {
                corpse->setCorpseOwner(mostDamageCreature->getID());
            } else {
                const Creature* mostDamageCreatureMaster = mostDamageCreature->getMaster();
                if (mostDamageCreatureMaster && mostDamageCreatureMaster->getPlayer()) {
                    corpse->setCorpseOwner(mostDamageCreatureMaster->getID());
                }
            }
        }
    }
The game is defining a "corpse" ONLY as a monster that was killed by a player (the 'owner') or a player's summon.

Long story short, whenever a monster kills another monster, there was no owner, so the game didn't think it was supposed to drop items. The "right" way to fix this would be by re-writing the functions to not include any of the corpse ownership garbage. The fix I'm providing basically tells the game to drop loot, whether or not there is an owner.
This is not going to work when you kill a fire elemental or a slime because they do not drop any corpse and then the server will crah.
 
This code has been running for almost a year now, lots of fire elementals and slimes have been killed and no crash lol
 
Back
Top