Crevasse
惡名昭彰
It's common knowledge at this point that monsters in oldschool Tibia had a melee skill that would actually train and level up as they fought. I've never seen anyone write/share code for this, so I'd like to share mine.
This is written for/implemented in Sabrehaven (Nostalrius), but could likely be adapted for TFS 1.x really easily.
First, we need to declare some new ints in monsters.h, in the MonsterInfo struct (I put mine directly under "uint32_t baseSpeed"):
These are attributes that we will assign to each monster that is created, just like any other attribute (HP, exp, speed, etc). We will use/modify them later.
Next, in monsters.cpp, in "bool Monsters::loadMonster", underneath this bit
add this:
This allows use to add these new attributes to monsters' XML files.
Next, we'll move on to monster.h. In the public member, right above "void removeList() final;", declare this new function:
We also need to declare this in the private member (I put mine underneath "std::string strDescription;"):
Now, in monster.cpp, we need to initialize the new variable in "Monster::Monster(MonsterType* mtype)". Under "hiddenHealth = mType->info.hiddenhealth" add:
Now, let's create the function in monster.cpp. I added mine right underneath
Here is the function:
As you can see, I included several prints (commented out). If you want to see the code in action or modify it, these can be useful. Or, you can just remove them. This code is pretty straightforward, read the comments on each line to understand what it does.
There is one final step: you'll notice this new function accepts int count as a parameter, we need to run this function every time a monster hits someone. That is done in combat.cpp. In "bool Combat::closeAttack", find the line "if (Monster* monster = attacker->getMonster()) {", and right underneath it, add:
That's the end of the source code edits, you can compile and run. However, we aren't technically done. If you care about being truly "accurate" to oldschool, you will need to edit the monsters XML files. If you look at each of the original CipSoft .mon files, each monster has line of code with their Fist Fighting skill, and a number between 1000 and 2000 which is a multiplier that affects to how many tries are needed for each subsequent level. In my code, this is represented by the new "multiplier" attribute, which is defaulted to 2000.
Trolls, for example, have their multiplier set at 1500 in the .mon file code, so I changed "troll.xml" to look like this:
This means that after 100 hits, the troll's attack skill will level up from 15 to 16, and it will then require 100 * (1500/1000) = 150 tries to get to the next level, then 150 * 1.5 = 225 tries to get to the next level, and so on. Each time a monster levels up, the "tries" value, which is initiated as 100 for all monsters, is updated to the new number of tries required for the next level.
I wrote bash scripts to automate the process of extracting the the monster names and multpliers from the .mon files to a table, then to insert the respective attribute to my XML monster files, I'll leave it up to you to figure out if/how you want to update your own monster XML files.
Here is a rat that I let train while I was at work:

This is written for/implemented in Sabrehaven (Nostalrius), but could likely be adapted for TFS 1.x really easily.
First, we need to declare some new ints in monsters.h, in the MonsterInfo struct (I put mine directly under "uint32_t baseSpeed"):
C++:
uint32_t multiplier = 2000;
uint64_t tries = 100;
Next, in monsters.cpp, in "bool Monsters::loadMonster", underneath this bit
C++:
if ((attr = monsterNode.attribute("script"))) {
monsterScriptList.emplace_back(mType, attr.as_string());
}
C++:
if ((attr = monsterNode.attribute("multiplier"))) {
mType->info.multiplier = pugi::cast<uint32_t>(attr.value());
}
if ((attr = monsterNode.attribute("tries"))) {
mType->info.tries = pugi::cast<uint64_t>(attr.value());
}
Next, we'll move on to monster.h. In the public member, right above "void removeList() final;", declare this new function:
C++:
void addSkillAdvance(int count);
C++:
uint64_t skillTries = 0;
C++:
skillTries = 0;
C++:
void Monster::addList()
{
g_game.addMonster(this);
}
C++:
void Monster::addSkillAdvance(int count) {
const float TRIES_INCREASE_FACTOR = (float)mType->info.multiplier / 1000; // Factor by which tries increase for each subsequent level
const uint64_t MAX_TRIES_VALUE = std::numeric_limits<uint64_t>::max(); // Maximum value for uint64_t
// std::cout << "TRIES_INCREASE_FACTOR: " << TRIES_INCREASE_FACTOR << std::endl;
skillTries += count;// Increment skillTries by the count
// std::cout << "Current skill level: " << mType->info.skill << std::endl;
// std::cout << "Current skill tries: " << skillTries << std::endl;
uint64_t levelUpThreshold = mType->info.tries; // the level up threshold is taken from the monster itself, if he's leveled up already then this has been updated in the while loop
// std::cout << "Current level up threshhold: " << levelUpThreshold << std::endl;
while (skillTries >= levelUpThreshold) { // While skillTries exceeds or equals the level up threshold
skillTries -= levelUpThreshold;// Reset skillTries to 0
mType->info.skill++;// Increment the skill level by 1
levelUpThreshold *= TRIES_INCREASE_FACTOR; // Update the level up threshold for the next level
if (levelUpThreshold > MAX_TRIES_VALUE) { // Check for overflow, this function is exponential so eventually the tries needed to level up will be greater than uint64_t can handle
std::cout << "Overflow detected. Exiting function." << std::endl;
return;
}
mType->info.tries = static_cast<uint64_t>(levelUpThreshold); //convert float back to int and write back to the "tries" attribute for the monster
// std::cout << "Monster's skill leveled up! New skill level: " << mType->info.skill << std::endl;
// std::cout << "New skill level up threshhold: " << levelUpThreshold << std::endl;
}
}
There is one final step: you'll notice this new function accepts int count as a parameter, we need to run this function every time a monster hits someone. That is done in combat.cpp. In "bool Combat::closeAttack", find the line "if (Monster* monster = attacker->getMonster()) {", and right underneath it, add:
C++:
monster->addSkillAdvance(1);
That's the end of the source code edits, you can compile and run. However, we aren't technically done. If you care about being truly "accurate" to oldschool, you will need to edit the monsters XML files. If you look at each of the original CipSoft .mon files, each monster has line of code with their Fist Fighting skill, and a number between 1000 and 2000 which is a multiplier that affects to how many tries are needed for each subsequent level. In my code, this is represented by the new "multiplier" attribute, which is defaulted to 2000.
Trolls, for example, have their multiplier set at 1500 in the .mon file code, so I changed "troll.xml" to look like this:
XML:
<monster name="Troll" nameDescription="a troll" race="blood" experience="20" speed="23" manacost="290" multiplier="1500">
I wrote bash scripts to automate the process of extracting the the monster names and multpliers from the .mon files to a table, then to insert the respective attribute to my XML monster files, I'll leave it up to you to figure out if/how you want to update your own monster XML files.
Here is a rat that I let train while I was at work:

Last edited: