• 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++ [TFS 1.4] Increase monster movement/non-idle range but keep monster targeting range

krafttomten

Well-Known Member
Joined
Jul 23, 2017
Messages
103
Solutions
1
Reaction score
74
Location
Sweden
The way I remember, monsters in real Tibia had a different non-idle range from their targeting range. In TFS 1.X, this does not seem to be the case. Let me explain what I mean. If I stand still in any hunting location on my TFS server, as soon as I have killed all the visible monsters, I am safe. But in real Tibia, the monsters could walk around at random, even if they were too far away from me to see. As a result, if I were to stand still in a cave, a nearby monster would eventually walk close enough to come within targeting range.

I'm not sure if TFS 1.X is coded this way. Maybe it is the same value that determines the monsters non-idle range and the monsters targeting range, or maybe the values are so close to eachother so that it doesn't make a difference.

I would like to change the monster behaviour so that the monsters start to move around when they are within ~30 squares of any player, and target any player that are within ~10 squares. Also, the player should only prevent spawn within ~10 squares, so that it doesn't feel safe to go AFK, even for a minute, while hunting. It would also be good if the monsters would walk back to spawn if they moved too far away.

Is this possible? I am currently using TFS 1.3, but will change into 1.4 so I prefer an answer that works with TFS 1.4 (if there is a difference) Any help is much appreciated!


p.s. I have tried to find the relevant code for several hours now, and rewritten this post three times with different ideas of how the code works, only to find that I had misunderstood something fundamentally, resulting in me starting over from scratch. I figured it's maybe better to just ask someone who actually knows how the system works for an answer. If I cannot get an answer, maybe I will be able to get a hint on where to start.

edit*

From what I have found, this is way beyond me to implement. I need to learn a lot more C++. But I would highly encourage the implementation of this system in the official TFS. The first reason is that the original Tibia worked like this. The second reason is that it is a really important feature of the game. Without it, the world don't feel dangerous. If the monsters are walking around, it is terrifying to go close to a dragon spawn with a low level character. Not to mention a giant spider spawn. Like the GS spawn on the way to the dark cathedral. It used to be terrifying to go there because you never knew if you would get annihilated on the way by a giant spider. That feeling of not being able to know where the enemies are lurking is an amazing and terrifying game mechanic.

The monsters moving around also creates a lot of randomness at the hunting grounds. Sometimes you will face two monsters in a room, the next time you face four of them. This makes hunting a lot less linear and because of that, less booring.
 

Attachments

Last edited:
Solution
Nope, and didn't have to either! Or maybe later, because I found that the targeting distance is different for ranged and melee creatuers, because the monsters don't actually target the player, they target a square from where they can attack their target. This seems to be connected to the targetdistance in the .xml file, because a monster with both melee and ranged attacks, like a valkyrie, tries to get into melee range, and will thus look for a square next to the player.

But I solved it! The final piece of the puzzle is this piece of code:
C++:
in Creature.cpp
line 917 to 924

void Creature::getPathSearchParams(const Creature*, FindPathParams& fpp) const
{
    fpp.fullPathSearch = !hasFollowPath;
    fpp.clearSight = true...
here is the code that limits how far a monster can see:
Thank you! But do you know if there is another value that determines whether the creature should move around? For instance, I have found this:

C++:
in monster.h  line 205

bool randomStepping = false;

... which I think is being used when a creature on a +- 1 level from a player. Can this be triggered if the monster is at the same level but at a broader range than the monster view range maybe?

Because I don't just want to change the monster view range, or do I? If I increase the view range, the monsters will just target me from further away. This is not what I want. I want the monsters to see the player and attack when they are 1 to 11 tiles from the player, and walk around randomly when they are 12 to 30 tiles away from the player. This would remove any sharp safe/unsafe line and make the game feel much more alive in my opinion.


edit*

edit3* removed old code, it had changed for 1.4.

edit2*


Look at the attached picture. Imagine if a player stood 10 sqm to the east of my player. Then that player would risk getting detected by the behemoth because it is moving around. How can I change the TFS code so that the monsters starts walking around when a player is nearby, but not within view range?

edit again*

It would also be good if the creatures walk back to spawn if they move too far way...
 

Attachments

  • How the monsters should behave.png
    How the monsters should behave.png
    753.4 KB · Views: 26 · VirusTotal
Last edited:
Just increase number in canSee as evil puncker pointed, monsters will walk around randomly in that range in some point they will pop out on players screen and start to attack them.
If you dont want them to attack from outside of the player view just add static range limit to every monster attack/spell but since x view port is different from y view port better would be some if statement in canuseattack and canusespell or in searchTarget.
If you want to monsters walk directly toward player instead of random walking around add dummy spell with high range that will do nothing on cast.
 
Just increase number in canSee as evil puncker pointed, monsters will walk around randomly in that range in some point they will pop out on players screen and start to attack them.
If you dont want them to attack from outside of the player view just add static range limit to every monster attack/spell but since x view port is different from y view port better would be some if statement in canuseattack and canusespell or in searchTarget.
If you want to monsters walk directly toward player instead of random walking around add dummy spell with high range that will do nothing on cast.

edit*

No it does not work like that. All this change does is to move the range for how far the monsters will track the player.

By the way, this is the 1.4 code (from the release on Sep 18, it has changed since then to the code Puncker refered to):


C++:
bool Monster::canSee(const Position& pos) const
{
    return Creature::canSee(getPosition(), pos, 9, 9);
}


I have tested it now with 9 range, 10 range and 11 range. Every time 100% of the monsters in the given range will attack the player. If the monsters would walk around at random, some of the monsters would walk outside of the range and stay there. If I choose a range higher than 11, the monsters will not move at all, even if the player is standing next to them.
 
Last edited:
I have done similar thing in 0.3.6 so i dont think few weeks of development would change core mechanic
Main x viewport is i think 8+1 so you need increase it more to achieve what you want

C++:
return Creature::canSee(getPosition(), pos, Map::maxClientViewportX * 2, Map::maxClientViewportY * 2);

and in the canuseattack under

C++:
if (spellBlock.range != 0 && distance <= spellBlock.range) {
add
C++:
if ( Creature::canSee(pos, targetPos, Map::maxClientViewportX, Map::maxClientViewportY) || spellBlock.range == 99 ) {
and in canusespell on the beggining
C++:
if (!Creature::canSee(pos, targetPos, Map::maxClientViewportX, Map::maxClientViewportY) && sb.range != 99 )
return false;
I'm not familiar with TFS 1.x+ but it should work they will walk randomly outside of player range so they can go closer and pop out on screen or futher to player in some point completly stop if range exceed x*2, if you want to walk toward player add empty cast spell in spells.xml and add it to your monster.xml with range="99"
 
I have done similar thing in 0.3.6 so i dont think few weeks of development would change core mechanic
Main x viewport is i think 8+1 so you need increase it more to achieve what you want

C++:
return Creature::canSee(getPosition(), pos, Map::maxClientViewportX * 2, Map::maxClientViewportY * 2);

and in the canuseattack under

C++:
if (spellBlock.range != 0 && distance <= spellBlock.range) {
add
C++:
if ( Creature::canSee(pos, targetPos, Map::maxClientViewportX, Map::maxClientViewportY) || spellBlock.range == 99 ) {
and in canusespell on the beggining
C++:
if (!Creature::canSee(pos, targetPos, Map::maxClientViewportX, Map::maxClientViewportY) && sb.range != 99 )
return false;
I'm not familiar with TFS 1.x+ but it should work they will walk randomly outside of player range so they can go closer and pop out on screen or futher to player in some point completly stop if range exceed x*2, if you want to walk toward player add empty cast spell in spells.xml and add it to your monster.xml with range="99"

Thank you for your reply and for your tips. I will look into it and see if it is possible to achieve these results in 1.X. But I fear that this core mechanic has changed, because when I incease the viewing distance, the monsters do not move around at random. They move towards the player.

To prove this, I made a row of enemies at Y range = 9/10/11, depending on current server setting, with one blank space between all the monsters and moved with a player into the current view distance. I restarted the server to reset the monsters to spawning position. Every time I moved into the viewing distance, all of the monsters in the row would enter my screen at the same time. If I stood outside of the view distance, none of the monsters would move. If a monster had moved one step on the X axis it would be slightly behind the other monsters. I assume this is because of the time it took to walk one square west/east. I left a lot of space to the north for the monsters to move around freely. If the monsters were to move around at random, they should not have all reached me at the same time.

In regard to the rest of your suggestions, even if I make changes to the attack/spell range of the monsters, all I could prevent is monsters shooting me from out of screen, which is not a problem I currently have.

I will try using the maxClientViewportX/Y * 2 to see if it increases the range, or if it also causes the monsters to stand still no matter what. I will update this comment with my findings.

update*
Using maxClientViewportX * 2 and maxClientViewportY * 2 instead of the numbers also caused all monsters on the server to stand still no matter how close a player gets to it.
 

Attachments

  • experiment on monster movement.png
    experiment on monster movement.png
    987.6 KB · Views: 22 · VirusTotal
Last edited:
I dont know what is up with that. Try changing temporarly for testing maxViewportX and maxViewportY in map.h and rebuild whole project instead of compile.
For testing the best would be to run 2 clients one with GOD(max) access the monsters supose to ignore that player and second normal character for monster to target
 
I dont know what is up with that. Try changing temporarly for testing maxViewportX and maxViewportY in map.h and rebuild whole project instead of compile.
For testing the best would be to run 2 clients one with GOD(max) access the monsters supose to ignore that player and second normal character for monster to target
Okay I'll try to change the maxViewportX/Y, just to make sure. I am always rebuilding the whole project, didn't know only recompiling was an option haha.

But the header for the canSee variable looks like this:
C++:
monster.h, line 116
bool canSee(const Position& pos) const override;
which refers to the .cpp part:
C++:
bool Monster::canSee(const Position& pos) const
{
    return Creature::canSee(getPosition(), pos, 11, 11);
}
so the 11, 11 is just the const override, and it shouldn't matter how I describe a higher number for that code for it to work, as long as I give it an integer with correct syntax. Both 11 and (maxViewportX * 2 = 11) should yield the same result, right?

edit*
Stupid me, I now noticed you said maxViewportX and not maxClientViewportX, this makes much more sense. I'll try it!

And yes, I know a watching GOD would be best, but since I got no variation in results in any of my attempts during the experiment, I thought I had reached conclusive evidence. I'll do a complementary study with a GOD to make sure.

edit2*
Yes! Now it partially works! This time I tested with a GOD watching from above. My current Monster::canSee distance is 15, which is the same as the maxViewport. This gives me 3 squares of random movement for the monsters before they start moving straight for the player. This is true for both the X and the Y axis. This is also related to the amount of squares on the X axis that the monsters need to walk to reach it's target. This distance is calculated towards the square that the monster needs to stand on in order to hit it's target, not the distance to the target itself.

I also found that the first step the monsters take, even if they are within the targeting range, they would take one step in a random direction before going towards their target. The monsters will however walk straight towards the player if within viewing distance of the player.

Now, to solve my question of the thread. All we have done so far is to prove that the targeting range and the view range are different things. I still want to move the targeting range closer to the view range of the player. The original, unchanged targeting range of the monsters were 9 squares, which is good I think. Maybe X = 7 and Y = 9 would be optimal. But I want the monsters to activate random movement when they are ~20 squares away, which is achievable by increasing MaxViewportX & Y and increasing the Creature::canSee(getPosition(), pos, X, Y, to ~20.

edit3*
I changed monster view range to 20 and the targeting range was still 12 squares, like in the earlier experiment.
 
Last edited:
At least some progress ^_^ Do you changed canUseAttack and canUseSpell like I wrote earlier?
Nope, and didn't have to either! Or maybe later, because I found that the targeting distance is different for ranged and melee creatuers, because the monsters don't actually target the player, they target a square from where they can attack their target. This seems to be connected to the targetdistance in the .xml file, because a monster with both melee and ranged attacks, like a valkyrie, tries to get into melee range, and will thus look for a square next to the player.

But I solved it! The final piece of the puzzle is this piece of code:
C++:
in Creature.cpp
line 917 to 924

void Creature::getPathSearchParams(const Creature*, FindPathParams& fpp) const
{
    fpp.fullPathSearch = !hasFollowPath;
    fpp.clearSight = true;
    fpp.maxSearchDist = 12; // Change this number to change the targeting distance of monsters. Within this range, the monsters will move directly towards a square where it is within range to attack it's target. This range is different for ranged and melee units.
    fpp.minTargetDist = 1;
    fpp.maxTargetDist = 1;
}

and to sum the answer in one post, I post the rest here.
C++:
bool Monster::canSee(const Position& pos) const
{
    return Creature::canSee(getPosition(), pos, 20, 20); // These values gives the viewing distance of monsters. Within this range, the monsters will move around at random.
}

C++:
class Map
{
    public:
        static constexpr int32_t maxViewportX = 20; //min value: maxClientViewportX + 1 (Change this value to the view distance of your monsters in monster.cpp)
        static constexpr int32_t maxViewportY = 20; //min value: maxClientViewportY + 1    (Change this value to the view distance of your monsters in monster.cpp)
        static constexpr int32_t maxClientViewportX = 8;
        static constexpr int32_t maxClientViewportY = 6;
 
You kinda right changing getPathSearchParams will help for sure but since client view isnt a square you will need to tweak it anyway and it use canUseAttack anyway in overriden monster::getPathSearchParams
 
Nope, and didn't have to either! Or maybe later, because I found that the targeting distance is different for ranged and melee creatuers, because the monsters don't actually target the player, they target a square from where they can attack their target. This seems to be connected to the targetdistance in the .xml file, because a monster with both melee and ranged attacks, like a valkyrie, tries to get into melee range, and will thus look for a square next to the player.

But I solved it! The final piece of the puzzle is this piece of code:
C++:
in Creature.cpp
line 917 to 924

void Creature::getPathSearchParams(const Creature*, FindPathParams& fpp) const
{
    fpp.fullPathSearch = !hasFollowPath;
    fpp.clearSight = true;
    fpp.maxSearchDist = 12; // Change this number to change the targeting distance of monsters. Within this range, the monsters will move directly towards a square where it is within range to attack it's target. This range is different for ranged and melee units.
    fpp.minTargetDist = 1;
    fpp.maxTargetDist = 1;
}

and to sum the answer in one post, I post the rest here.
C++:
bool Monster::canSee(const Position& pos) const
{
    return Creature::canSee(getPosition(), pos, 20, 20); // These values gives the viewing distance of monsters. Within this range, the monsters will move around at random.
}

C++:
class Map
{
    public:
        static constexpr int32_t maxViewportX = 20; //min value: maxClientViewportX + 1 (Change this value to the view distance of your monsters in monster.cpp)
        static constexpr int32_t maxViewportY = 20; //min value: maxClientViewportY + 1    (Change this value to the view distance of your monsters in monster.cpp)
        static constexpr int32_t maxClientViewportX = 8;
        static constexpr int32_t maxClientViewportY = 6;
This was only partially the answer. Problems occured with monsters using spells while walking around at random. To fix this, implement the code that Pawcio6 suggested into the monster.cpp

I adjusted the code slightly to fit to my preferences. The code in question looks like this when it is finished.

C++:
bool Monster::canUseAttack(const Position& pos, const Creature* target) const
{
    if (isHostile()) {
        const Position& targetPos = target->getPosition();
        uint32_t distance = std::max<uint32_t>(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos));
        for (const spellBlock_t& spellBlock : mType->info.attackSpells) {
            if (spellBlock.range != 0 && distance <= spellBlock.range) {
                if ( Creature::canSee(pos, targetPos, Map::maxClientViewportX + 2, Map::maxClientViewportY + 2) || spellBlock.range == 99 ) {
                    return g_game.isSightClear(pos, targetPos, true);
                }
            }
        }
        return false;
    }
    return true;
}

bool Monster::canUseSpell(const Position& pos, const Position& targetPos,
                          const spellBlock_t& sb, uint32_t interval, bool& inRange, bool& resetTicks)
{
    if (!Creature::canSee(pos, targetPos, Map::maxClientViewportX + 2, Map::maxClientViewportY + 2) && sb.range != 99 ) {
        return false;
    }
    inRange = true;

    if (sb.isMelee) {
        if (isFleeing() || (OTSYS_TIME() - lastMeleeAttack) < sb.speed) {
            return false;
        }
    } else {
        if (sb.speed > attackTicks) {
            resetTicks = false;
            return false;
        }

        if (attackTicks % sb.speed >= interval) {
            //already used this spell for this round
            return false;
        }
    }

    if (sb.range != 0 && std::max<uint32_t>(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)) > sb.range) {
        inRange = false;
        return false;
    }
    return true;
}

If this solution still has any problems, I will return to this thread and update it.
 
Solution
Back
Top