• 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!
  • New resources must be posted under Resources tab. A discussion thread will be created automatically, you can't open threads manually anymore.

Feature [TFS 1.3] Monster Levels

Good afternoon, I use tfs 1.4.2 and when trying to compile this error, I did the same as the tutorial, can anyone help me?

Severity Code Description Project File Line Suppression State
Error LNK2001 unresolved external symbol "public: float __cdecl ConfigManager::getExperienceStage(unsigned int)const " (?getExperienceStage@ConfigManager@@QEBAMI@Z) theforgottenserver C:\Users\luand\OneDrive\Área de Trabalho\serv\vc17\luascript.obj 1

Severity Code Description Project File Line Suppression State
Error LNK1120 1 unresolved externals theforgottenserver C:\Users\luand\OneDrive\Área de Trabalho\serv\vc17\x64\Release\theforgottenserver-x64.exe 1



1704814771755.png
 
hello everyone!
are u good?

let me ask u something,

did u guys noticed something about the summons? i implemented as well you guys talking about and further i noticed the mages when summon a monster get a big kid with levels, maybe it can be cool considering different mechanics but not for a older protocol. any idea about how to solve it?

thanks in advance!

cheers.
 
hello everyone!
are u good?

let me ask u something,

did u guys noticed something about the summons? i implemented as well you guys talking about and further i noticed the mages when summon a monster get a big kid with levels, maybe it can be cool considering different mechanics but not for a older protocol. any idea about how to solve it?

thanks in advance!

cheers.
Simply, add Monster Levels to the monsters that cannot be summoned by a mage, in case, copy paste rat, and the rat that can be summoned will be different, change name or something else, there is a large possibility.
 
Simply, add Monster Levels to the monsters that cannot be summoned by a mage, in case, copy paste rat, and the rat that can be summoned will be different, change name or something else, there is a large possibility.
uoww, awesome idea i was thinking in something more complex about src code, but thats a easy way to deal with... thanks dude!
 
hello everyone!

i got another doubt about this system, i was trying in c++ some implementations for it, but until now without success, the thing is i was trying to implement a way to use the variable level of monster.xml for the attack and loot node inside the file, for example, for a troll.xml a way for the trolls of different levels has different behaviors, attacks and loots and for it just input in .xml of the monster something like this:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<monster name="Troll" nameDescription="a troll" race="blood" experience="20" speed="23" manacost="290">
    <health now="50" max="50" />
    <level min="0" max="4" />
    <look type="15" head="0" body="0" legs="0" feet="0" corpse="3987" />
    <targetchange interval="1000" chance="0" />
    <targetstrategy nearest="100" weakest="0" mostdamage="0" random="0" />
    <flags>
        <flag summonable="1" />
        <flag attackable="1" />
        <flag hostile="1" />
        <flag illusionable="1" />
        <flag convinceable="1" />
        <flag pushable="1" />
        <flag canpushitems="0" />
        <flag canpushcreatures="0" />
        <flag targetdistance="1" />
        <flag runonhealth="15" />
    </flags>
    <attacks attack="10" skill="15">
        <attack level="4" name="fire" min="-90" max="-170" range="7" radius="3" target="1" chance="3">
            <attribute key="shootEffect" value="fire" />
            <attribute key="areaEffect" value="firearea" />
        </attack>
    </attacks>
    <defenses armor="6" defense="8">
    </defenses>
    <voices>
        <voice sentence="Grrrr" />
        <voice sentence="Groar" />
        <voice sentence="Gruntz!" />
        <voice sentence="Hmmm, bugs." />
        <voice sentence="Hmmm, dogs." />
    </voices>
    <loot>
        <item id="3031" countmax="10" chance="600" /> <!-- a gold coin -->
        <item id="3268" countmax="1" chance="180" /> <!-- a hand axe -->
        <item id="3552" countmax="1" chance="100" /> <!-- leather boots -->
        <item id="3355" countmax="1" chance="100" /> <!-- a leather helmet -->
        <item id="3577" countmax="1" chance="150" /> <!-- meat -->
        <item id="3003" countmax="1" chance="80" /> <!-- a rope -->
        <item id="3054" countmax="1" chance="1" /> <!-- a silver amulet -->
        <item id="3277" countmax="1" chance="200" /> <!-- a spear -->
        <item id="3336" countmax="1" chance="50" /> <!-- a studded club -->
        <item id="3412" countmax="1" chance="150" /> <!-- a wooden shield -->
    </loot>
</monster>

if you take a look in the attacks section in the code above, i tried to declare a level that will give for the troll of that level differents types of attacks, any idea about how to implement it?
with my tests and changes inside monsters.cpp and monster.cpp in the respective funcions "Monsters::loadMonster" and "Monster::Monster(MonsterType* mtype)" was with no success...

thanks in advance!

cheers.
 
hello everyone!

i got another doubt about this system, i was trying in c++ some implementations for it, but until now without success, the thing is i was trying to implement a way to use the variable level of monster.xml for the attack and loot node inside the file, for example, for a troll.xml a way for the trolls of different levels has different behaviors, attacks and loots and for it just input in .xml of the monster something like this:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<monster name="Troll" nameDescription="a troll" race="blood" experience="20" speed="23" manacost="290">
    <health now="50" max="50" />
    <level min="0" max="4" />
    <look type="15" head="0" body="0" legs="0" feet="0" corpse="3987" />
    <targetchange interval="1000" chance="0" />
    <targetstrategy nearest="100" weakest="0" mostdamage="0" random="0" />
    <flags>
        <flag summonable="1" />
        <flag attackable="1" />
        <flag hostile="1" />
        <flag illusionable="1" />
        <flag convinceable="1" />
        <flag pushable="1" />
        <flag canpushitems="0" />
        <flag canpushcreatures="0" />
        <flag targetdistance="1" />
        <flag runonhealth="15" />
    </flags>
    <attacks attack="10" skill="15">
        <attack level="4" name="fire" min="-90" max="-170" range="7" radius="3" target="1" chance="3">
            <attribute key="shootEffect" value="fire" />
            <attribute key="areaEffect" value="firearea" />
        </attack>
    </attacks>
    <defenses armor="6" defense="8">
    </defenses>
    <voices>
        <voice sentence="Grrrr" />
        <voice sentence="Groar" />
        <voice sentence="Gruntz!" />
        <voice sentence="Hmmm, bugs." />
        <voice sentence="Hmmm, dogs." />
    </voices>
    <loot>
        <item id="3031" countmax="10" chance="600" /> <!-- a gold coin -->
        <item id="3268" countmax="1" chance="180" /> <!-- a hand axe -->
        <item id="3552" countmax="1" chance="100" /> <!-- leather boots -->
        <item id="3355" countmax="1" chance="100" /> <!-- a leather helmet -->
        <item id="3577" countmax="1" chance="150" /> <!-- meat -->
        <item id="3003" countmax="1" chance="80" /> <!-- a rope -->
        <item id="3054" countmax="1" chance="1" /> <!-- a silver amulet -->
        <item id="3277" countmax="1" chance="200" /> <!-- a spear -->
        <item id="3336" countmax="1" chance="50" /> <!-- a studded club -->
        <item id="3412" countmax="1" chance="150" /> <!-- a wooden shield -->
    </loot>
</monster>

if you take a look in the attacks section in the code above, i tried to declare a level that will give for the troll of that level differents types of attacks, any idea about how to implement it?
with my tests and changes inside monsters.cpp and monster.cpp in the respective funcions "Monsters::loadMonster" and "Monster::Monster(MonsterType* mtype)" was with no success...

thanks in advance!

cheers.


If monster is 10-20 loot 20 gold coins, if is 20-30, 30, etc..
I Dont know really how to do that.

But you could do it in another way, creating a function to get the level of monster, with this you can do everything, if monster is level 100 - 300 then loot x5 for example, more loot. Requires source changes to add the function.
The same way to do it more stronger, more hp, etc.
 
I added this system and it causes a server collision
Server Crash
Shouldn't crash, you added the level flag to the monsters? You'll have to add it to each monster, try this with a low amount of monster first, just for testing.
XML:
<level min="5" max="100" />

Also remember to add the config.lua lines
LUA:
monsterBonusHealth = 0.5
monsterBonusSpeed = 0.02
monsterBonusDamage = 0.02

Here's the whole code if you wish to merge it or cherry pick it
 
Shouldn't crash, you added the level flag to the monsters? You'll have to add it to each monster, try this with a low amount of monster first, just for testing.
XML:
<level min="5" max="100" />

Also remember to add the config.lua lines
LUA:
monsterBonusHealth = 0.5
monsterBonusSpeed = 0.02
monsterBonusDamage = 0.02

Here's the whole code if you wish to merge it or cherry pick it
There is a big error after adding this system. If you talk to any NPC, a crash occurs. If you try to give yourself some levels addskill, a crash occurs.
Yes, your system is what I am using now
Post automatically merged:

dddssd.png
 
Last edited:
There is a big error after adding this system. If you talk to any NPC, a crash occurs. If you try to give yourself some levels addskill, a crash occurs.
Yes, your system is what I am using now
Post automatically merged:

View attachment 84611
Yes! That's true, any event that adds experience will crash the server. I didn't remember that, it's been so much time using this system that I just forgot about it. But the bug is there. Anyways, i've been using it for a year or so and never had to use add experience events anymore, it's kinda tricky, becuase I even used this crash to get ensure that no one uses add experience commands anymore on server (otherwise will crash and i'll inmediatly know it).

I will take a look into it to see if I can solve it, since I didn't had the necessity, I just passed from that. If someone wishes to contribute on fixing this is welcome! Regards ^^
 
Yes! That's true, any event that adds experience will crash the server. I didn't remember that, it's been so much time using this system that I just forgot about it. But the bug is there. Anyways, i've been using it for a year or so and never had to use add experience events anymore, it's kinda tricky, becuase I even used this crash to get ensure that no one uses add experience commands anymore on server (otherwise will crash and i'll inmediatly know it).

I will take a look into it to see if I can solve it, since I didn't had the necessity, I just passed from that. If someone wishes to contribute on fixing this is welcome! Regards ^^
True but you can still use player:addExperience.


I add this monster level to a 13.32 so function on a script action on use or a chest wont work , the trick is create an item that you Will change it to an npc for experience , add to npc player:addExperience and I Will work.

Sol each time you want addExperience to a player do it by npc and no errors that the trick I found.
 
True but you can still use player:addExperience.


I add this monster level to a 13.32 so function on a script action on use or a chest wont work , the trick is create an item that you Will change it to an npc for experience , add to npc player:addExperience and I Will work.

Sol each time you want addExperience to a player do it by npc and no errors that the trick I found.
Yes! That's true, any event that adds experience will crash the server. I didn't remember that, it's been so much time using this system that I just forgot about it. But the bug is there. Anyways, i've been using it for a year or so and never had to use add experience events anymore, it's kinda tricky, becuase I even used this crash to get ensure that no one uses add experience commands anymore on server (otherwise will crash and i'll inmediatly know it).

I will take a look into it to see if I can solve it, since I didn't had the necessity, I just passed from that. If someone wishes to contribute on fixing this is welcome! Regards ^^

I posted the solution here: C++ - Monster Level&skull Crash tfs 1.5 860 (https://otland.net/threads/monster-level-skull-crash-tfs-1-5-860.289015/#post-2752958)
 
Monster Damage Bug

Greetings. I would like to point out a very impactful bug with this system and the fix for it:

onCombatChangeHealth is a recursive function and calls itself in case it has a onChangeHealth lua callback. In that case the damage is increases not only the first time but also the second time it goes into the function. Because the callback is only executed once, it is not infinitive. This is because they check "damage.origin != ORIGIN_NONE" to avoid that.
So this also must be checked for the monster bonus damage to not double it twice.

Example: (You use a onHealthChange creatureevent to reduce the damage by 10% )
1. you take damage and onCombatHealth activates, calculates monster damage
2. .. Reaches down to "target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE)", check any change of damage on the onChangeHealth lua callback.
3. Returns modified damage ( 10% reduced damage) to onCombatHealth and sets damage.origin = ORIGIN_NONE
4. onCombatHealth activate, calculating monster damage again (increasing it again).
5. Ignores events this time due to ORIGIN_NONE.
6. deal damage to player.

This means that the intention of reducing damage, will actually increase it instead (of course based on your bonus damage)


Solution
Replace this:
C++:
Monster* monster = attacker ? attacker->getMonster() : nullptr;
    if (monster && monster->getLevel() > 0) {
        float bonusDmg = g_config.getFloat(ConfigManager::MLVL_BONUSDMG) * monster->getLevel();
        if (bonusDmg != 0.0) {
            if (damage.primary.value < 0) {
                damage.primary.value += std::round(damage.primary.value * bonusDmg);
            }
            if (damage.secondary.value < 0) {
                damage.secondary.value += std::round(damage.secondary.value * bonusDmg);
            }
        }
    }

with this, (do this for CombatChangeHealth and CombatChangeMana):
Code:
    Monster* monster = attacker ? attacker->getMonster() : nullptr;
    if (monster && monster->getLevel() > 0 && damage.origin != ORIGIN_NONE) {
        float bonusDmg = g_config.getFloat(ConfigManager::MLVL_BONUSDMG) * monster->getLevel();
        if (bonusDmg != 0.0) {
            if (damage.primary.value < 0) {
                damage.primary.value += std::round(damage.primary.value * bonusDmg);
            }
            if (damage.secondary.value < 0) {
                damage.secondary.value += std::round(damage.secondary.value * bonusDmg);
            }
        }
    }


in combatChangeHealth, add "damage.origin = ORIGIN_NONE;" below "manaDamage = std::min<int32_t>(targetPlayer->getMana(), healthChange);"
This is to not activate onHealthChange events again if you took damage that broke your manashield.
C++:
SpectatorVec spectators;
if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) {
    int32_t manaDamage = std::min<int32_t>(targetPlayer->getMana(), healthChange);
    if (manaDamage != 0) {
        if (damage.origin != ORIGIN_NONE) {
            const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE);
            if (!events.empty()) {
                for (CreatureEvent* creatureEvent : events) {
                    creatureEvent->executeManaChange(target, attacker, damage);
                }
                healthChange = damage.primary.value + damage.secondary.value;
                if (healthChange == 0) {
                    return true;
                }
                manaDamage = std::min<int32_t>(targetPlayer->getMana(), healthChange);
                damage.origin = ORIGIN_NONE;
            }
        }
 
Extra Loot depending on HP (made for Monster Levels system)

1736813609919.webp
(these calculations were made for creatures with max 3 levels and max drop rate of 94%)


~ For normal monsters (level 0), no bonus is applied, base loot rates are set in your creature.xml

~ For level 1 monsters:
Takes 10% from the base drop chance, applies on top of base drop chance.
With a minimum bonus of 1%.

~ For level 2 monsters:
Takes 25% from the base drop chance, applies on top of base drop chance.
With a minimum bonus of 1.5%.

~ For level 3 monsters:
Takes 50% from the base drop chance, applies on top of base drop chance.
With a minimum bonus of 2%.

Notes:
  • 85000 sets the maximum % chance an item can drop to prevent insane drops, currently 85%, edit this if you want.
  • Rare items with a lower drop chance will always have a flat 1 or 2% extra chance of being dropped.
  • This calculates the loot based on the creatures bonus health gained with the Monster Levels system in difference with it's base health.
  • This is for 3 extra monster levels where level 0 is the base if you use higher numbers, adjust the script and rates accordingly.


data/creaturescripts/scripts/monster.lua
Above
LUA:
function Monster:onDropLoot(corpse)

Add the following:​
LUA:
local function calculateLevelBonus(healthRatio)
    if healthRatio >= 4 then -- Level 3
        return function(originalChance)
            local bonus = math.floor(originalChance * 0.5)
            return math.max(bonus, 2000)
        end
    elseif healthRatio >= 3 then -- Level 2
        return function(originalChance)
            local bonus = math.floor(originalChance * 0.25)
            return math.max(bonus, 1500)
        end
    elseif healthRatio >= 2 then -- Level 1
        return function(originalChance)
            local bonus = math.floor(originalChance * 0.1)
            return math.max(bonus, 1000)
        end
    end
    return function(originalChance) return 0 end
end


Inside function Monster;onDropLoot(corpse)
Below
LUA:
 local maxHealth = self:getMaxHealth()
    local baseHealth = self:getType():getHealth()
    local healthRatio = maxHealth / baseHealth
    local levelBonus = calculateLevelBonus(healthRatio)
    print(string.format("Health - Max: %d, Base: %d, Ratio: %.2f", maxHealth, baseHealth, healthRatio))
    local addedItems = {}
    local monsterType = self:getType()
    if monsterType then
        local lootList = monsterType:getLoot()
        if lootList then
            for _, lootData in ipairs(lootList) do
                local bonusAmount = levelBonus(lootData.chance)
                local finalChance = math.min(lootData.chance + bonusAmount, 85000)
                local baseChancePercent = (lootData.chance / 1000)
                local bonusPercent = (bonusAmount / 1000)
                local finalChancePercent = (finalChance / 1000)
                print(string.format("Loot ID: %d | Base Chance: %.2f%% | Bonus: %.2f%% | Final Chance: %.2f%%",
                    lootData.itemId, baseChancePercent, bonusPercent, finalChancePercent))
                if math.random(85000) <= finalChance then
                    if not addedItems[lootData.itemId] then
                        corpse:addItem(lootData.itemId, lootData.maxCount or 1)
                        addedItems[lootData.itemId] = true
                    end
                end
            end
        end
    end

Updated~
 
Last edited:
in combatChangeHealth, add "damage.origin = ORIGIN_NONE;" below "manaDamage = std::min<int32_t>(targetPlayer->getMana(), healthChange);"
This is to not activate onHealthChange events again if you took damage that broke your manashield.
8.6 doesn't need this change right? Already merged the code to see how it goes. So much thanks!
Extra Loot depending on HP (made for Monster Levels system)

View attachment 89516
(these calculations were made for creatures with max 3 levels and max drop rate of 94%)


~ For normal monsters (level 0), no bonus is applied, base loot rates are set in your creature.xml

~ For level 1 monsters:
Takes 10% from the base drop chance, applies on top of base drop chance.
With a minimum bonus of 1%.

~ For level 2 monsters:
Takes 25% from the base drop chance, applies on top of base drop chance.
With a minimum bonus of 1.5%.

~ For level 3 monsters:
Takes 50% from the base drop chance, applies on top of base drop chance.
With a minimum bonus of 2%.

Notes:
  • 85000 sets the maximum % chance an item can drop to prevent insane drops, currently 85%, edit this if you want.
  • Rare items with a lower drop chance will always have a flat 1 or 2% extra chance of being dropped.
  • This calculates the loot based on the creatures bonus health gained with the Monster Levels system in difference with it's base health.
  • This is for 3 extra monster levels where level 0 is the base if you use higher numbers, adjust the script and rates accordingly.


data/creaturescripts/scripts/monster.lua
Above
LUA:
function Monster:onDropLoot(corpse)

Add the following:​
LUA:
local function calculateLevelBonus(healthRatio)
    if healthRatio >= 4 then -- Level 3
        return function(originalChance)
            local bonus = math.floor(originalChance * 0.5)
            return math.max(bonus, 2000)
        end
    elseif healthRatio >= 3 then -- Level 2
        return function(originalChance)
            local bonus = math.floor(originalChance * 0.25)
            return math.max(bonus, 1500)
        end
    elseif healthRatio >= 2 then -- Level 1
        return function(originalChance)
            local bonus = math.floor(originalChance * 0.1)
            return math.max(bonus, 1000)
        end
    end
    return function(originalChance) return 0 end
end


Inside function Monster;onDropLoot(corpse)
Below
LUA:
 local maxHealth = self:getMaxHealth()
    local baseHealth = self:getType():getHealth()
    local healthRatio = maxHealth / baseHealth
    local levelBonus = calculateLevelBonus(healthRatio)
    print(string.format("Health - Max: %d, Base: %d, Ratio: %.2f", maxHealth, baseHealth, healthRatio))
    local addedItems = {}
    local monsterType = self:getType()
    if monsterType then
        local lootList = monsterType:getLoot()
        if lootList then
            for _, lootData in ipairs(lootList) do
                local bonusAmount = levelBonus(lootData.chance)
                local finalChance = math.min(lootData.chance + bonusAmount, 85000)
                local baseChancePercent = (lootData.chance / 1000)
                local bonusPercent = (bonusAmount / 1000)
                local finalChancePercent = (finalChance / 1000)
                print(string.format("Loot ID: %d | Base Chance: %.2f%% | Bonus: %.2f%% | Final Chance: %.2f%%",
                    lootData.itemId, baseChancePercent, bonusPercent, finalChancePercent))
                if math.random(85000) <= finalChance then
                    if not addedItems[lootData.itemId] then
                        corpse:addItem(lootData.itemId, lootData.maxCount or 1)
                        addedItems[lootData.itemId] = true
                    end
                end
            end
        end
    end

Updated~
How should it go if I have a range of, for example, white skulled monsters from level 1 to level 30, red skulled from level 30 to 90 and black from 90 to 9999 for example. I must place if healthRatio < 30 and healthRatio > 1 then, if healthRatio < 90 and healthRatio > 30 then, and so on? Thanks in advance!

how to add it here ?
Compilation works, it show:
You see a wolf, it is level 0.
But can't add monster.minLevel = 5 / monster.maxLevel = 100
or adding it into monster.defenses or something else, minLevel = 5 / maxLevel = 100
doesn't work, anyone know ?

Also tried:

LUA:
monster.level = {
    min = 5,
    max = 100
}
I wonder if someone managed to do this? Otherwise, there's any AI that could convert canary/otservbr creatures to old syntaxis? Please help me on this one! Regards
 
Back
Top