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

Lua Remove Target from Player

mackerel

Well-Known Member
Joined
Apr 26, 2017
Messages
398
Solutions
18
Reaction score
72
I iterate through the array to see who has targeted the player who uses the spell but then I cannot remove the target because of this error;

attempt to call method 'removeTarget' (a nil value)

But it should work ? Look my code;

Lua:
function onCastSpell(cid, var)
    local attackers = Game.getSpectators(Player(cid):getPosition(), false, false, 0, 10, 0, 10)
    for i = 1, #attackers do
        if attackers[i]:getTarget(Player(cid)) then
            attackers[i]:removeTarget(Player(cid))
        end
    end
return true
end

TFS 1.0
 
Last edited:
Solution

This seems interesting. I didn't even know NetworkMessage is available in lua. I guess I need to take back what I said about not being able to do this in lua then :) So just to complete my solution:
Lua:
local function sendCancelTarget(player)
    local msg = NetworkMessage()
    msg:addByte(0xA3)
    msg:addU32(0x00)
    msg:sendToPlayer(player)
    msg:delete()
end

local playersOnly = false

function onCastSpell(cid, var)
    local player = Player(cid)
    local attackers = Game.getSpectators(player:getPosition(), false, playersOnly, 0, 10, 0, 10)
    for i = 1, #attackers do
        if attackers[i]:getTarget() == Player(cid) then
           if...
I iterate through the array to see who has targeted the player who uses the spell but then I cannot remove the target because of this error;



But it should work ? Look my code;

Lua:
function onCastSpell(cid, var)
    local attackers = Game.getSpectators(Player(cid):getPosition(), false, false, 0, 10, 0, 10)
    for i = 1, #attackers do
        if attackers[i]:getTarget(Player(cid)) then
            attackers[i]:removeTarget(Player(cid))
        end
    end
return true
end

TFS 1.0

Try this:

Lua:
function onCastSpell(cid, var)
    local player = Player(cid)
    local attackers = Game.getSpectators(player:getPosition(), false, false, 0, 10, 0, 10)
    for i = 1, #attackers do
        if attackers[i]:getTarget() == player then
            attackers[i]:setTarget(nil)
        end
    end

    return true
end

or if it does not work, you can try to replace attackers:setTarget(nil) with attackers:setTarget(). I'm not sure which one will work.

removeTarget is only available for monsters, you can't call it if player is the attacker. setTarget is available for all creatures
 
Last edited:
Try this:

Lua:
function onCastSpell(cid, var)
    local player = Player(cid)
    local attackers = Game.getSpectators(player:getPosition(), false, false, 0, 10, 0, 10)
    for i = 1, #attackers do
        if attackers[i]:getTarget() == player then
            attackers[i]:setTarget(nil)
        end
    end

    return true
end

nope, the getTarget needs arguments cannot be equals to. (just checked)

But also, I've noticed that

Lua:
        if attackers[i]:getTarget(Player(cid)) then
            print(attackers[i])
        end

doesn't work exactly as expected. I think just because of the function that is being used. (Meaning; it is going to print every person who has targeted, no matter who is the target, in this case Player(cid) doesn't come up.

but I tried using this

Lua:
getSpectators(centerPos, rangex, rangey, multifloor, onlyPlayers)

as

Lua:
print(getSpectators(Player(cid):getPosition(), 5, 5, false, false))

^ but that doesn't work at all

V
attempt to call global 'getSpectators' (a nil value)
 
nope, the getTarget needs arguments cannot be equals to. (just checked)

###### getTarget() > **Description:** N/A > **Parameters:** None > **Returns:** N/A > **Example:** ```Lua local creature = Creature(...) creature:getTarget() ``` > **Added in version:** 1.0

As for getSpectators:

Lua:
Game.getSpectators(position[, multifloor = false[, onlyPlayer = false[, minRangeX = 0[, maxRangeX = 0[, minRangeY = 0[, maxRangeY = 0]]]]]])
 
Last edited:
###### getTarget() > **Description:** N/A > **Parameters:** None > **Returns:** N/A > **Example:** ```Lua local creature = Creature(...) creature:getTarget() ``` > **Added in version:** 1.0

As for getSpectators:

Lua:
Game.getSpectators(position[, multifloor = false[, onlyPlayer = false[, minRangeX = 0[, maxRangeX = 0[, minRangeY = 0[, maxRangeY = 0]]]]]])

You're right, this code is correct.

Lua:
    local attackers = Game.getSpectators(Player(cid):getPosition(), false, false, 10, 10, 10, 10)
    for i = 1, #attackers do
        print(attackers[i])
    end

^ prints everyone on the screen

but this one is not working:

it should only print userdata for those who have targeted the caster (player who casts this spell), instead it prints everyone who are targeting someone

Lua:
    local attackers = Game.getSpectators(Player(cid):getPosition(), false, false, 10, 10, 10, 10)
    for i = 1, #attackers do
    print(attackers[i]:getTarget(Player(cid)))
    end
 
You're right, this code is correct.

Lua:
    local attackers = Game.getSpectators(Player(cid):getPosition(), false, false, 10, 10, 10, 10)
    for i = 1, #attackers do
        print(attackers[i])
    end

^ prints everyone on the screen

but this one is not working:

it should only print userdata for those who have targeted the caster (player who casts this spell), instead it prints everyone who are targeting someone

Lua:
    local attackers = Game.getSpectators(Player(cid):getPosition(), false, false, 10, 10, 10, 10)
    for i = 1, #attackers do
    print(attackers[i]:getTarget(Player(cid)))
    end

Yes, because your code is wrong. You need to check if target is equal to the player. Just like it is done in my code. Creature:getTarget() takes no arguments.

Lua:
local playersOnly = false
local player = Player(cid)
local attackers = Game.getSpectators(player:getPosition(), false, playersOnly, 10, 10, 10, 10)

for i = 1, #attackers do
    if attackers[i]:getTarget() == player then
        print(attackers[i])
    end
end
 
Yes, because your code is wrong. You need to check if target is equal to the player. Just like it is done in my code. Creature:getTarget() takes no arguments.

Lua:
local playersOnly = false
local player = Player(cid)
local attackers = Game.getSpectators(player:getPosition(), false, playersOnly, 10, 10, 10, 10)

for i = 1, #attackers do
    if attackers[i]:getTarget() == player then
        print(attackers[i])
    end
end

completely missed the parentheses after getTarget thats why it didn't work

still, we're back at the beginning;:p

The only functions I think of when unselecting the player are

1. creature:setTarget(target)
2. monster:removeTarget(creature)

2nd one does not want to cooperate for some reason;

Lua:
        if attackers[i]:getTarget() == Player(cid) then
            attackers[i]:removeTarget(Player(cid))
        end

attempt to call method 'removeTarget' (a nil value)

and the first one, needs to specify new target but I need to unselect the target so I thought i could do nil

Lua:
        if attackers[i]:getTarget() == Player(cid) then
            attackers[i]:setTarget(nil)
        end

but then, it somehow stops working;

- attacker does not attack player
- no matter the distance, we can't see notification 'target lost'
- red square around the player stays, no matter the distance

just checked your edited post

both

attackers:setTarget(nil)

and

attackers:setTarget()

don't seem to work
 
Last edited:
completely missed the parentheses after getTarget thats why it didn't work

still, we're back at the beginning;:p

The only functions I think of when unselecting the player are

1. creature:setTarget(target)
2. monster:removeTarget(creature)

2nd one does not want to cooperate for some reason;

Lua:
        if attackers[i]:getTarget() == Player(cid) then
            attackers[i]:removeTarget(Player(cid))
        end



and the first one, needs to specify new target but I need to unselect the target so I thought i could do nil

Lua:
        if attackers[i]:getTarget() == Player(cid) then
            attackers[i]:setTarget(nil)
        end

but then, it somehow stops working;

- attacker does not attack player
- no matter the distance, we can't see notification 'target lost'
- red square around the player stays, no matter the distance

Yes, you can't use Monster:removeTarget if your attacker is a player. so Creature:setTarget does actually work (attacker stops attacking the player), but not like you expected (no update on client side). I did find the reason for this in source code and I believe this is expected behaviour of TFS, it only sends "cancelTarget" to client application if the target is out of range and/or on a different floor. I don't think this can be easily fixed with lua only.

If you have time to play with it, you can make this source code change and see if it works:

C++:
bool Player::setAttackedCreature(Creature* creature)
{
    if (!Creature::setAttackedCreature(creature)) {
        sendCancelTarget();
        return false;
    }

    if (chaseMode && creature) {
        if (followCreature != creature) {
            //chase opponent
            setFollowCreature(creature);
        }
    } else if (followCreature) {
        setFollowCreature(nullptr);
    }

    if (creature) {
        g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID())));
    }
    return true;
}

to

C++:
bool Player::setAttackedCreature(Creature* creature)
{
    if (!Creature::setAttackedCreature(creature)) {
        sendCancelTarget();
        return false;
    }

    if (chaseMode && creature) {
        if (followCreature != creature) {
            //chase opponent
            setFollowCreature(creature);
        }
    } else if (followCreature) {
        setFollowCreature(nullptr);
    }

    if (creature) {
        g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID())));
    } else {
        sendCancelTarget();
    }
    return true;
}

But this will probably also send cancelTarget to client application during other events, e.g. killing the monster/player.
 
Yes, you can't use Monster:removeTarget if your attacker is a player. so Creature:setTarget does actually work (attacker stops attacking the player), but not like you expected (no update on client side). I did find the reason for this in source code and I believe this is expected behaviour of TFS, it only sends "cancelTarget" to client application if the target is out of range and/or on a different floor. I don't think this can be easily fixed with lua only.

No way

can you point me to the right source?
 
No way

can you point me to the right source?

Just to clarify things, the code (lua) I gave you make attackers stop attacking, right? Just the client state is not updated (the red square marker)?

I just pointed you to source code responsible for this behaviour. Here the Creature::setAttackedTarget is called and if this returns false, sendCancelTarget is send to the client application:
forgottenserver/player.cpp at 1.0 · otland/forgottenserver · GitHub

Now if you look here:
forgottenserver/creature.cpp at 1.0 · otland/forgottenserver · GitHub

You will see the only time that method returns false is when the creature is either on a different floor or cannot be longer seen, check this if statement:
C++:
if (creaturePos.z != getPosition().z || !canSee(creaturePos))
 
Just to clarify things, the code (lua) I gave you make attackers stop attacking, right? Just the client state is not updated (the red square marker)?

I just pointed you to source code responsible for this behaviour. Here the Creature::setAttackedTarget is called and if this returns false, sendCancelTarget is send to the client application:
forgottenserver/player.cpp at 1.0 · otland/forgottenserver · GitHub

Now if you look here:
forgottenserver/creature.cpp at 1.0 · otland/forgottenserver · GitHub

You will see the only time that method returns false is when the creature is either on a different floor or cannot be longer seen, check this if statement:
C++:
if (creaturePos.z != getPosition().z || !canSee(creaturePos))

That is correct.

and yes, else statement returns sendCancelTarget when player kills monster

I was thinking of possible solutions;

- if creature(target) says 'spell name' then send cancel target - anyone can use it to abuse this bug in order to get rid of target from themselves
- if creature(target) says 'spell name' and vocid="X" then send cancel target - the issue has been mitigated, but to only one vocation
- if creature(target) says 'spell name' is storageID="XXX" then send cancel target - that could do it, not sure if this would be the proper way though/limited to only one spell
- write another function :D !
 
Last edited:
That is correct.

and yes, else statement returns sendCancelTarget when player kills monster

I was thinking of possible solutions;

- if creature(target) says 'spell name' then send cancel target - anyone can use it to abuse this bug in order to get rid of target from themselves
- if creature(target) says 'spell name' and vocid="X" then send cancel target - the issue has been mitigated, but to only one vocation
- if creature(target) says 'spell name' is storageID="XXX" then send cancel target - that could do it, not sure if this would be the proper way though/limited to only one spell
- write another function :D !

The only feasible solution would be to expose the sendCancelTarget method via lua player::sentCancelTarget, if you really need this. Then you would just call it after setTarget.
 

This seems interesting. I didn't even know NetworkMessage is available in lua. I guess I need to take back what I said about not being able to do this in lua then :) So just to complete my solution:
Lua:
local function sendCancelTarget(player)
    local msg = NetworkMessage()
    msg:addByte(0xA3)
    msg:addU32(0x00)
    msg:sendToPlayer(player)
    msg:delete()
end

local playersOnly = false

function onCastSpell(cid, var)
    local player = Player(cid)
    local attackers = Game.getSpectators(player:getPosition(), false, playersOnly, 0, 10, 0, 10)
    for i = 1, #attackers do
        if attackers[i]:getTarget() == Player(cid) then
           if attackers[i]:isPlayer() then
               attackers[i]:setTarget(0)
               attackers[i]:sendCancelTarget()
           elseif attackers[i]:isMonster() then
               attackers[i]:searchTarget()
           end
       end
    end
    return true
end
 
Last edited:
Solution

Seems to solve the issue without changing with source files


This seems interesting. I didn't even know NetworkMessage is available in lua. I guess I need to take back what I said about not being able to do this in lua then :) So just to complete my solution:
Lua:
local function sendCancelTarget(player)
    local msg = NetworkMessage()
    msg:addByte(0xA3)
    msg:addU32(0x00)
    msg:sendToPlayer(player)
    msg:delete()
end

local playersOnly = false

function onCastSpell(cid, var)
    local player = Player(cid)
    local attackers = Game.getSpectators(player:getPosition(), false, playersOnly, 0, 10, 0, 10)
    for i = 1, #attackers do
        if attackers[i]:getTarget() == player then
            attackers[i]:setTarget(nil)

            if(attackers[i]:isPlayer())
                sendCancelTarget(attackers[i])
            end
        end
    end
    return true
end

Need 'then' after if statement

the problem is solved by teleporting the monster away and then teleporting it back

That's not the best way when it comes to players, especially those who have summons on them (they're most likely to disappear)

------------------
also there is a bug; once executed monsters will deal no damage and will not change target

then just add this function;
attackers:searchTarget() // aww still nothing |:

//edit 3
this will do:

Lua:
    local attackers = Game.getSpectators(Player(cid):getPosition(), false, false, 10, 10, 10, 10)
   for i = 1, #attackers do
       if attackers[i]:getTarget() == Player(cid) then
           if attackers[i]:isPlayer() then
               attackers[i]:setTarget(0)
               attackers[i]:sendCancelTarget()
           else if attackers[i]:isMonster() then
               attackers[i]:searchTarget()
           end
       end
   end

 
Last edited:
Seems to solve the issue without changing with source files




Need 'then' after if statement



That's not the best way when it comes to players, especially those who have summons on them (they're most likely to disappear)

------------------
also there is a bug; once executed monsters will deal no damage and will not change target

then just add this function;
attackers:searchTarget() // aww still nothing |:

//edit 3
this will do:

Lua:
    for i = 1, #attackers do
        if attackers[i]:getTarget() == Player(cid) then
            if(attackers[i]:isPlayer()) then
                attackers[i]:setTarget(0)
                attackers[i]:sendCancelTarget()
            end
            attackers[i]:searchTarget()
        end
    end

Great, I added missing then (damn lua, I will never get used to this) and searchTarget call for monsters to my post!

// edit
I applied rest of changes you have done. I'd have probably tested it myself, but I don't have any working Tibia / OTC :)
 
Last edited:
Back
Top