• 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!

Feature Simple passive monsters

Lordfire

Intermediate OT User
TFS Developer
Joined
Jul 16, 2008
Messages
248
Solutions
3
Reaction score
138
GitHub
ranisalt
Hello again, today I bring you a very simple patch I made to enable monster passivity to be set on a monster XML configuration.

The monster will not attack on sight, instead it will wait until it is attacked to fight back. Video example:
(sorry for the shameless self promotion)

Monsters will remember who attacked them for the same amount of time players are battle locked after a fight, which is generally defaulted to 60 seconds, and this is non configurable as this system is very simple. You can use this instead if you want a more complex approach.

Link to the patch: https://gist.github.com/ranisalt/a238128104b7af3a17f1
How to apply: if you are on Linux, put the patch on the root folder of your server (the folder that contains src/ and config.lua) and run:
Code:
patch -p1 -i the-name-of-the-patch-file.patch
Otherwise if you run Windows, download patch from here and run:
Code:
patch.exe -p1 --binary -i the-name-of-the-patch-file.patch

How to configure:
Edit your monster XML file and add the following flag:
Code:
<flag passive="1" />
Monsters are non-passive (AKA aggressive, attack on sight) by default, so you need to set the flag to have the new behavior.

Liked it? Don't forget to drop a star by my Forgotten fork, it has more interesting stuff :D

Thanks and please report any issues you might find! See you again very soon ;)
 
Amazing job! Does this work for tfs 0.3.6pl?

Edit:

Yes, it does. I had to change some lines, but it works perfectly.

Thx!
 
Last edited:
Great but, they start attacking only after get some dmg?
If I attack them and don't do any dmg, they won't attack me? :)
 
Great but, they start attacking only after get some dmg?
If I attack them and don't do any dmg, they won't attack me? :)
that is because this feature relies on hasBeenAttacked function, if you want to monsters attack even when you don't do damage you have to track the block and poof and i have no idea on how do that
 
Hm, I dont understand how to install the patch. Could someone explain to me again but simplified? Do I need the source files?
 
Hey LordFire, this is a really nice script, and the best past is that there is minimal src edit... thanks for the contribution
 
Can someone rewrite this to 1.3?
A few minimalistic changes is all you need for TFS 1.3 support.

e.g.
monsterType->isPassive to monsterType->info.isPassive
mType->isPassive to mType->info.isPassive​
 
@Ninja
For you it is few simple minimalistic changes for me it is black magic :p
 
I've made some changes to adjust this code to TFS 1.3.
I haven't tested deeply, so feel free to use.

monster.cpp
C++:
// FROM
bool Monster::selectTarget(Creature* creature)
{
    if (!isTarget(creature)) {
        return false;
    }

    auto it = std::find(targetList.begin(), targetList.end(), creature);
    if (it == targetList.end()) {
        //Target not found in our target list.
        return false;
    }

    if (isHostile() || isSummon()) {
        if (setAttackedCreature(creature) && !isSummon()) {
            g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID())));
        }
    }
    return setFollowCreature(creature);
}
// TO
bool Monster::selectTarget(Creature* creature)
{
    if (!isTarget(creature)) {
        return false;
    }

    if (isPassive() && !wasAttacked) {
        return false;
    }

    auto it = std::find(targetList.begin(), targetList.end(), creature);
    if (it == targetList.end()) {
        //Target not found in our target list.
        return false;
    }

    if (isHostile() || isSummon()) {
        if (setAttackedCreature(creature) && !isSummon()) {
            g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID())));
        }
    }
    return setFollowCreature(creature);
}

// FROM
void Monster::onAttackedCreatureDisappear(bool)
{
    attackTicks = 0;
    extraMeleeAttack = true;
}

// TO
void Monster::onAttackedCreatureDisappear(bool)
{
    attackTicks = 0;
    extraMeleeAttack = true;
}

void Monster::onAttacked()
{
    wasAttacked = true;
}

monster.h
C++:
// FROM
bool isHostile() const {
    return mType->info.isHostile;
}

// TO
bool isHostile() const {
    return mType->info.isHostile;
}
bool isPassive() const {
    return mType->info.isPassive;
}

// FROM
        void onAttackedCreatureDisappear(bool isLogout) override;

// TO
        void onAttackedCreatureDisappear(bool isLogout) override;
        void onAttacked() override;

// FROM
        Position masterPos;

        bool isIdle = true;
        bool extraMeleeAttack = false;
        bool isMasterInRange = false;
        bool randomStepping = false;
        bool ignoreFieldDamage = false;

// TO
        Position masterPos;

        bool isIdle = true;
        bool wasAttacked = false;
        bool extraMeleeAttack = false;
        bool isMasterInRange = false;
        bool randomStepping = false;
        bool ignoreFieldDamage = false;

monsters.cpp
C++:
// FROM
    if ((node = monsterNode.child("flags"))) {
        for (auto flagNode : node.children()) {
            attr = flagNode.first_attribute();
            const char* attrName = attr.name();
            if (strcasecmp(attrName, "summonable") == 0) {
                mType->info.isSummonable = attr.as_bool();
            } else if (strcasecmp(attrName, "attackable") == 0) {
                mType->info.isAttackable = attr.as_bool();
            } else if (strcasecmp(attrName, "hostile") == 0) {
                mType->info.isHostile = attr.as_bool();
               
// TO
    if ((node = monsterNode.child("flags"))) {
        for (auto flagNode : node.children()) {
            attr = flagNode.first_attribute();
            const char* attrName = attr.name();
            if (strcasecmp(attrName, "summonable") == 0) {
                mType->info.isSummonable = attr.as_bool();
            } else if (strcasecmp(attrName, "attackable") == 0) {
                mType->info.isAttackable = attr.as_bool();
            } else if (strcasecmp(attrName, "hostile") == 0) {
                mType->info.isHostile = attr.as_bool();
            } else if (strcasecmp(attrName, "passive") == 0) {
                mType->info.isPassive = attr.as_bool();

monsters.h
C++:
// FROM
        bool canPushItems = false;
        bool canPushCreatures = false;
        bool pushable = true;
        bool isSummonable = false;
        bool isIllusionable = false;
        bool isConvinceable = false;
        bool isAttackable = true;
        bool isHostile = true;
       
// TO
        bool canPushItems = false;
        bool canPushCreatures = false;
        bool pushable = true;
        bool isSummonable = false;
        bool isIllusionable = false;
        bool isConvinceable = false;
        bool isAttackable = true;
        bool isHostile = true;
        bool isPassive = true;
 
Change bool isPassive = true; to bool isPassive = false; in the very last line or all the creatures in the world will be passive by default. Works great though!
 
I've made some changes to adjust this code to TFS 1.3.
I haven't tested deeply, so feel free to use.

monster.cpp
C++:
// FROM
bool Monster::selectTarget(Creature* creature)
{
    if (!isTarget(creature)) {
        return false;
    }

    auto it = std::find(targetList.begin(), targetList.end(), creature);
    if (it == targetList.end()) {
        //Target not found in our target list.
        return false;
    }

    if (isHostile() || isSummon()) {
        if (setAttackedCreature(creature) && !isSummon()) {
            g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID())));
        }
    }
    return setFollowCreature(creature);
}
// TO
bool Monster::selectTarget(Creature* creature)
{
    if (!isTarget(creature)) {
        return false;
    }

    if (isPassive() && !wasAttacked) {
        return false;
    }

    auto it = std::find(targetList.begin(), targetList.end(), creature);
    if (it == targetList.end()) {
        //Target not found in our target list.
        return false;
    }

    if (isHostile() || isSummon()) {
        if (setAttackedCreature(creature) && !isSummon()) {
            g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID())));
        }
    }
    return setFollowCreature(creature);
}

// FROM
void Monster::onAttackedCreatureDisappear(bool)
{
    attackTicks = 0;
    extraMeleeAttack = true;
}

// TO
void Monster::onAttackedCreatureDisappear(bool)
{
    attackTicks = 0;
    extraMeleeAttack = true;
}

void Monster::onAttacked()
{
    wasAttacked = true;
}

monster.h
C++:
// FROM
bool isHostile() const {
    return mType->info.isHostile;
}

// TO
bool isHostile() const {
    return mType->info.isHostile;
}
bool isPassive() const {
    return mType->info.isPassive;
}

// FROM
        void onAttackedCreatureDisappear(bool isLogout) override;

// TO
        void onAttackedCreatureDisappear(bool isLogout) override;
        void onAttacked() override;

// FROM
        Position masterPos;

        bool isIdle = true;
        bool extraMeleeAttack = false;
        bool isMasterInRange = false;
        bool randomStepping = false;
        bool ignoreFieldDamage = false;

// TO
        Position masterPos;

        bool isIdle = true;
        bool wasAttacked = false;
        bool extraMeleeAttack = false;
        bool isMasterInRange = false;
        bool randomStepping = false;
        bool ignoreFieldDamage = false;

monsters.cpp
C++:
// FROM
    if ((node = monsterNode.child("flags"))) {
        for (auto flagNode : node.children()) {
            attr = flagNode.first_attribute();
            const char* attrName = attr.name();
            if (strcasecmp(attrName, "summonable") == 0) {
                mType->info.isSummonable = attr.as_bool();
            } else if (strcasecmp(attrName, "attackable") == 0) {
                mType->info.isAttackable = attr.as_bool();
            } else if (strcasecmp(attrName, "hostile") == 0) {
                mType->info.isHostile = attr.as_bool();
              
// TO
    if ((node = monsterNode.child("flags"))) {
        for (auto flagNode : node.children()) {
            attr = flagNode.first_attribute();
            const char* attrName = attr.name();
            if (strcasecmp(attrName, "summonable") == 0) {
                mType->info.isSummonable = attr.as_bool();
            } else if (strcasecmp(attrName, "attackable") == 0) {
                mType->info.isAttackable = attr.as_bool();
            } else if (strcasecmp(attrName, "hostile") == 0) {
                mType->info.isHostile = attr.as_bool();
            } else if (strcasecmp(attrName, "passive") == 0) {
                mType->info.isPassive = attr.as_bool();

monsters.h
C++:
// FROM
        bool canPushItems = false;
        bool canPushCreatures = false;
        bool pushable = true;
        bool isSummonable = false;
        bool isIllusionable = false;
        bool isConvinceable = false;
        bool isAttackable = true;
        bool isHostile = true;
      
// TO
        bool canPushItems = false;
        bool canPushCreatures = false;
        bool pushable = true;
        bool isSummonable = false;
        bool isIllusionable = false;
        bool isConvinceable = false;
        bool isAttackable = true;
        bool isHostile = true;
        bool isPassive = true;
old but maybe it can help someone,

if you want the monster to stay passive to creatures who have not attacked it ever then in "bool Monster::selectTarget(Creature* creature)",
C++:
    uint32_t attackerId = creature->getID();
    auto itp = damageMap.find(attackerId);
    if (isPassive() && itp == damageMap.end()) {
        return false;
    }
 
Back
Top