how do you make an event happen only once in a runtime event listener

I have an auto attack set up for my game. When the characters reach a specific X and Y location they start to attack. The goal is for the auto attack to run every 3 seconds.

to do this, I have a runtime event listener that controls the movement and also tracks the X and Y in order to trigger the attacking.

the problem that I have seemed to realize is that due to the frame rate, this actually triggers many times.

So if I make a single attack do 10 damage, it actually attacks for 10 damage multiplied by whatever the frame rate is.

I’ve tried using a timer.performWithDelay, but that was not a good solution.

anyone have an Idea of how I can make the character attack once they reach a certain point and from then on, attack only once every 3 seconds?

Well timer.performWithDelay() may still be your best option, but you’re going to need a flag to know when to fire off the timer.

local needToStartAttackTimer = true local attackTimer = nil local function yourRunTimeListener( event )       if (code to determine you're in range to attack) then            if needToStartAttackTimer then                needToStartAttackTimer = false                attackTimer = timer.performWithDelay( 3000, doAttack, 0 )            end       end      if (code to stop the attack) then           timer.cancel( attackTimer )           needToStartAttackTimer = true      end end

or something like that.

Rob

thanks Rob, I tried what you have suggested and it still does not seem to work

I have the following code

if self.frame == 6 then if self.attackingTF == true then self.attackingTF = false self.attackTimer = timer.performWithDelay( 10000, self:attack(self.target), 1 ) end end

I have noticed that there seems to be something wrong with the timer itself. I have adjusted the delay time from 3000 all the way up to 10000 as you can see in my code, but it does not actually create any delay. both 3000 and 10000 seem to fire off instantly. I even specified that the iteration only be 1 to try and ensure that this only triggers once, but that does not work either.

I will say that before your suggestion, the function was firing off like 10 times, by using this chunk of code I have it tracked that it only fires off 3 times. It has been cut down, but I cant for the life of me figure out why it wont fire just once and why it seems as if the parameters I put into the timer delay dont seem to matter.

Any help would be great

I did forget to mention that I did put in the timer.cancel, but that as well does not seem to produce anything

if self.frame == 6 then if self.attackingTF == true then self.attackingTF = false self.attackTimer = timer.performWithDelay( 10000, self:attack(self.target), 1 ) elseif self.attackingTF == false then timer.cancel( self.attackTimer ) end end

You can’t do this:

self.attackTimer = timer.performWithDelay( 10000, self:attack(self.target), 1 )

The 2nd parameter to timer.performWithDelay is an “address to a function”, not the return value from a function. When you put the () (in this case (self.target), you’re telling Lua to execute self:attack(self.target) and with the return value from that function, telling timer.performDelay() to execute that function in 10 seconds.

Normally, if you didn’t need the parameter, you would write:

self.attackTimer = timer.performWithDelay( 10000, self:attack, 1 )

but since you need the parameter you need a Closure (basically an anonymous function);

self.attackTimer = timer.performWithDelay( 10000, function() self:attack(self.target); end, 1 )

Give that a try!

question about the anonymous function.

I changed my timer to what you had suggested, but I moved the time back down to 1 second

self.attackTimer = timer.performWithDelay( 1000, function() self:attack(self.target); end, 1 ) 

this works and it actually waits for the delay before attacking, but the self:attack function is still firing off 3 times when it is called.

any idea why this is still happening

Can you show more of your code?

sure, I used a template for OOP I believe I got from develephant. I’ll try and post a simple version of what I have to help keep it clean.

local function Enemy(name, image, class) enemy.class = class enemy.image = image enemy.name = name enemy.maxHealth = 100 enemy.health = 100 enemy.strength = 5 enemy.armor = 10 enemy.target = Hero1 enemy.attackingTF = false function enemyFollow(self, event) transition.moveTo (self, x, y, time = 1000) if self.x == target.x and self.y == target.y then self.attackingTF = true end if self.attackingTF == true then self:play() -- starts attack animation end if self.frame == 6 then                     if self.attackingTF == true then self.attackingTF = false     self.attackTimer = timer.performWithDelay( 1000, function () self:attack(self.target); end, 1 ) elseif self.attackingTF == false then timer.cancel( self.attackTimer ) end                     end end enemy.enterFrame = enemyFollow -- moves enemies allows function to recognize self attributes Runtime:addEventListener("enterFrame", enemy) -- moves enemies function enemy:takeDamage( forHp, newTarget ) forHp = forHp or 0 self.health = math.max( 0, (self.health - forHp)) end function enemy:attack( character ) print("enemy attacks") if ( character ) then character:takeDamage (self.strength, self.armor, self.name) end end end return Enemy

This is just a quick and dirty example of what I have, not exact word for word. I hope this helps

How often is self.frame = 6? Your enterFrame listener is firing 30 or 60 times per second. If you’re value of self.frame isn’t updating fast enough that codes going to execute multiple times.

Rob

frame is based on the attack animation.

sequence\_attack = { {name = "attack", start = 1, count = 10, time = 1000, loopcount = 1, loopDirection = "forward"} }

this is the animation info used, so I figure about 1/10th of a second is spent on frame 6.

I would not think that if I take self.frame 6 out for being a requirement I would think that it would only make the issue worse, because I would assume time spent on self.attackingTF = true would be a lot longer than the time spent on frame 6.

I may be wrong though

Well timer.performWithDelay() may still be your best option, but you’re going to need a flag to know when to fire off the timer.

local needToStartAttackTimer = true local attackTimer = nil local function yourRunTimeListener( event )       if (code to determine you're in range to attack) then            if needToStartAttackTimer then                needToStartAttackTimer = false                attackTimer = timer.performWithDelay( 3000, doAttack, 0 )            end       end      if (code to stop the attack) then           timer.cancel( attackTimer )           needToStartAttackTimer = true      end end

or something like that.

Rob

thanks Rob, I tried what you have suggested and it still does not seem to work

I have the following code

if self.frame == 6 then if self.attackingTF == true then self.attackingTF = false self.attackTimer = timer.performWithDelay( 10000, self:attack(self.target), 1 ) end end

I have noticed that there seems to be something wrong with the timer itself. I have adjusted the delay time from 3000 all the way up to 10000 as you can see in my code, but it does not actually create any delay. both 3000 and 10000 seem to fire off instantly. I even specified that the iteration only be 1 to try and ensure that this only triggers once, but that does not work either.

I will say that before your suggestion, the function was firing off like 10 times, by using this chunk of code I have it tracked that it only fires off 3 times. It has been cut down, but I cant for the life of me figure out why it wont fire just once and why it seems as if the parameters I put into the timer delay dont seem to matter.

Any help would be great

I did forget to mention that I did put in the timer.cancel, but that as well does not seem to produce anything

if self.frame == 6 then if self.attackingTF == true then self.attackingTF = false self.attackTimer = timer.performWithDelay( 10000, self:attack(self.target), 1 ) elseif self.attackingTF == false then timer.cancel( self.attackTimer ) end end

You can’t do this:

self.attackTimer = timer.performWithDelay( 10000, self:attack(self.target), 1 )

The 2nd parameter to timer.performWithDelay is an “address to a function”, not the return value from a function. When you put the () (in this case (self.target), you’re telling Lua to execute self:attack(self.target) and with the return value from that function, telling timer.performDelay() to execute that function in 10 seconds.

Normally, if you didn’t need the parameter, you would write:

self.attackTimer = timer.performWithDelay( 10000, self:attack, 1 )

but since you need the parameter you need a Closure (basically an anonymous function);

self.attackTimer = timer.performWithDelay( 10000, function() self:attack(self.target); end, 1 )

Give that a try!

question about the anonymous function.

I changed my timer to what you had suggested, but I moved the time back down to 1 second

self.attackTimer = timer.performWithDelay( 1000, function() self:attack(self.target); end, 1 ) 

this works and it actually waits for the delay before attacking, but the self:attack function is still firing off 3 times when it is called.

any idea why this is still happening

Can you show more of your code?

sure, I used a template for OOP I believe I got from develephant. I’ll try and post a simple version of what I have to help keep it clean.

local function Enemy(name, image, class) enemy.class = class enemy.image = image enemy.name = name enemy.maxHealth = 100 enemy.health = 100 enemy.strength = 5 enemy.armor = 10 enemy.target = Hero1 enemy.attackingTF = false function enemyFollow(self, event) transition.moveTo (self, x, y, time = 1000) if self.x == target.x and self.y == target.y then self.attackingTF = true end if self.attackingTF == true then self:play() -- starts attack animation end if self.frame == 6 then                     if self.attackingTF == true then self.attackingTF = false     self.attackTimer = timer.performWithDelay( 1000, function () self:attack(self.target); end, 1 ) elseif self.attackingTF == false then timer.cancel( self.attackTimer ) end                     end end enemy.enterFrame = enemyFollow -- moves enemies allows function to recognize self attributes Runtime:addEventListener("enterFrame", enemy) -- moves enemies function enemy:takeDamage( forHp, newTarget ) forHp = forHp or 0 self.health = math.max( 0, (self.health - forHp)) end function enemy:attack( character ) print("enemy attacks") if ( character ) then character:takeDamage (self.strength, self.armor, self.name) end end end return Enemy

This is just a quick and dirty example of what I have, not exact word for word. I hope this helps

How often is self.frame = 6? Your enterFrame listener is firing 30 or 60 times per second. If you’re value of self.frame isn’t updating fast enough that codes going to execute multiple times.

Rob

frame is based on the attack animation.

sequence\_attack = { {name = "attack", start = 1, count = 10, time = 1000, loopcount = 1, loopDirection = "forward"} }

this is the animation info used, so I figure about 1/10th of a second is spent on frame 6.

I would not think that if I take self.frame 6 out for being a requirement I would think that it would only make the issue worse, because I would assume time spent on self.attackingTF = true would be a lot longer than the time spent on frame 6.

I may be wrong though