LUA scripting switching on/off a script - lua

I'm writing a script in LUA/logitech scripting API. The script should perform the following: mouse key 4 switch on/off the scriptmouse key 5 switch from one feature to other ( force move and autoattack )
The code is the follow:
forceMove = false
on = false
function OnEvent(event, arg)
--OutputLogMessage("event = %s, arg = %s\n", event, arg);
if IsMouseButtonPressed(5) then
forceMove = not forceMove
while(on) do
if(forceMove) then
ForceMove()
else
StartAttack()
end
end
ReleaseMouseButton(5)
end
if IsMouseButtonPressed(4) then
on = not on
ReleaseMouseButton(4)
end
end
function StartAttack()
PressAndReleaseMouseButton(1)
Sleep(1000)
end
function ForceMove()
MoveMouseWheel(1)
Sleep(20)
MoveMouseWheel(-1)
end
but once in game,if i activate the script with mouse button 4, i get stuck in "force move" mode, and "auto attack" mode never works. Can't figure why.

When you press mouse button 5, you activate the 'force move' mode. If the 'on' mode is simultaneously enabled, you result in a infinite loop:
while(on) do
if(forceMove) then
ForceMove()
else
StartAttack()
end
end -- loops regardless of mouse buttons
You will stay here forever, regardless of the mouse buttons you press.
You need to move to executing code out of the mouse event handler. The handler should only update values like forceMove, another function is needed to carry out the action. In these function, you only do ONE step, not many.
Then you check again for pressed mouse buttons, carry out the actions and so on.
Code example:
function update()
if IsMouseButtonPressed(4) then
on = not on
end
if IsMouseButtonPressed(5) then
forceMove = not forceMove
end
end
function actions()
if on then
if forceMove then
ForceMove()
end
end
end
How to put it together:
You have to use some kind of loop, but ideally the game engine should do this for you. It would look something like this:
local is_running = true
while is_running do
update()
actions()
end
Now, if you press a button, you save the current state in some global variables which are accessed both by update and actions. The functions get called every cycle (which can be the calculation of one frame). Assuming you don't press any further buttons, update() does nothing, so forceMove and on remain the same.
This way, you have a continuous movement without a loop in action().

Related

Logitech/LGHUB Lua - Loop with break

i recently picked up Lua while making a macro script for my LG mouse.
Unfortunately the Lua-API is really restricted with debug, io and file not working (source: http://www.softpanorama.org/Hardware/Peripherals/Keyboards/Logitech_G_keyboard_macros/lua_scripting.shtml#Limitations)
Here is also the official reference: https://douile.github.io/logitech-toggle-keys/APIDocs.pdf
The script is running a LGHUB macro while i want to attach mouse movement to that macro while it is running. There is no function for checking if a macro is currently running to just loop and stop the lua-side.
So I'm looking for a possibility in Lua to 'run a loop independent' which does not stop the execution of the rest of my script (to check if a status variable [isMacroRunning] has changed). I also want to break that macro if i hit another button while the macro is running.
BUT if i loop the mouse-movement, i cannot trigger another onEvent-function because the script-pointer(?) is still stuck in my loop. My current idea is to break that with a coroutine but iam not sure how to continue while no key-input is happening.
The script doesnt stop and wait if it encounters 'PlayMacro()', also this API-Function has no return value to just attach lua actions to the PlayMacro-function.
There would be also the possibility to transfer the LGHUB-macro (essentially its just pressButton A and leftclick for 40s then release the buttons) but that doesnt solve the problem (at least i cant find a solution within this) with the function being stuck in the loop and no way to break it.
For example, if MouseButton11 triggers a loop and i press MouseButton10, the $arg variable does not change to 10 but stays at 11 because the script-pointer(?) is still in the loop instead of triggering the onEvent function.
The script seems to get executed once at load (example on switching mouse-profiles) and then the onEvent-function listens. If i could somehow run a looping check on [isMacroRunning] while still be able to trigger the onEvent-function, i can make this work. But otherwise i know to little about Lua and its behavior.
Edit1: Added "essential script" which only describes the needed core-functions. This is not working due to a 2nd onEvent cannot be triggered until the 1st onEvent has finished. But the 1st onEvent is designed to break on a variable change. The variable change is triggered in the 2nd onEvent. Needed solution: some kind of workaround or use of other Lua functions to seperate the 1st onEvent execution from the 2nd onEvent.
Essential Script:
```
isMacroRunning = false
function wiggle()
PressKey("a")
while true do
if not isMacroRunning then break end
OutputLogMessage("wiggle\n")
MoveMouseRelative (5, 0)
Sleep(150)
MoveMouseRelative (-5, 0)
Sleep(150)
end
end
function OnEvent(event, arg)
if (event == "MOUSE_BUTTON_PRESSED" and arg == 11) then
isMacroRunning = true
wiggle()
end
if(event == "MOUSE_BUTTON_PRESSED" and arg == 10 and isMacroRunning) then
isMacroRunning = false
ReleaseKey("a")
end
end
´´´
Full Script:
local isMacroRunning = false
coco = coroutine.create(function()
while true do
if not isMacroRunning then break end
MoveMouseRelative (5, 0)
Sleep(150)
MoveMouseRelative (-5, 0)
Sleep(150)
coroutine.yield()
end
end)
function OnEvent(event, arg)
if (event == "MOUSE_BUTTON_PRESSED" and arg == 11) then
isMacroRunning = true
runMacro()
elseif(event == "MOUSE_BUTTON_PRESSED" and arg == 10 and isMacroRunning) then
isMacroRunning = false
runMacro()
end
end
function runMacro()
if isMacroRunning then
PlayMacro('farm')
coroutine.resume(coco)
OutputLogMessage("Start Macro\n")
else
AbortMacro()
OutputLogMessage("Aborted\n")
end
end
coroutine.resume(coco)
´´´
Step 1.
Make sure you're not using MB#4 ("backward") in the game.
If some action is assigned to MB#4 in the game, do the following:
choose keyboard button you don't currently use in the game (for example, F12)
goto GHUB(KEYS tab); bind F12 to your physical MB#4
goto game options; bind the action to F12 instead of MB#4
Now when you press physical MB#4, the game sees F12 and executes your old action.
Step 2.
Goto GHUB(SYSTEM tab); bind "Back" to MB#10
Step 3.
The script.
function OnEvent(event, arg)
if event == "MOUSE_BUTTON_PRESSED" and arg == 11 then
PressMouseButton(1) -- down LMB
PressKey("A") -- down "A"
repeat
MoveMouseRelative(5,0)
Sleep(150)
MoveMouseRelative(-5,0)
Sleep(150)
until IsMouseButtonPressed(4) -- btn#4 is bound to btn#10
ReleaseKey("A") -- up "A"
ReleaseMouseButton(1) -- up LMB
end
end
How does it work:
When you press MB#11, the loop starts.
When you later press MB#10, IsMouseButtonPressed() sees that button#4 is pressed, and the loop exits.

Is there a way to add time to a Wait class inside an If statement?

I started learning LUA a few days ago, started my own project inside Tabletop Simulator game, but I've hit a brick wall. I can't add time to a Wait class.
This is an example of what I tried:
function state_check()
--This function checks if the state of obj_1 and obj_2 is 0 or 1
end
function press_button()
--This function activates other functions based on the state of obj_1 and obj_2
i = 1 --Function starts with a wait of 1 second
if obj_1_state == 1 then --If state is 1, then the function is triggered and 1 second is added to i
Wait.time(func_1, i)
i = i + 1
end
if obj_2_state == 1 then
Wait.time(func_2, i)
end
end
I need for the function to check the first part and if true, do the second part 1 second later. If not, do the second part normally and skip the "i = i + 1".
My problem is that the function does everything at the same time. I know I'm doing something wrong, but I can't figure out what. Is there a way to create some for of gate to do everything in order or anything similar?
Your code seems correct.
I don't know what is the problem.
But I know that one of possible solutions is to follow the "callback hell" style of programming:
local function second_part(obj_2_state)
if obj_2_state == 1 then
Wait.time(func_2, 1)
end
end
local function first_part(obj_1_state, obj_2_state)
if obj_1_state == 1 then
Wait.time(
function()
func_1()
second_part(obj_2_state)
end, 1)
else
second_part(obj_2_state)
end
end
function press_button()
local obj_1_state, obj_2_state = --calculate states of obj_1 and obj_2 here
first_part(obj_1_state, obj_2_state)
end

How do I make os.pullEvent not yield?

I'm trying to create a while true do loop, that reacts to clicks, using os.pullEvent, and also updates a monitor.
Problem being, it only updates the screen when I press one of the on screen buttons, and I've found out that's because pullEvent stops the script, until an event is fired.
Is it possible to make it so pullEvent doesn't stop me updating the monitor?
function getClick()
event,side,x,y = os.pullEvent("monitor_touch")
button.checkxy(x,y)
end
local tmp = 0;
while true do
button.label(2, 2, "Test "..tmp)
button.screen()
tmp++
getClick()
end
You can easily use the parallel api to run both codes essentially at the same time. How it works is it runs them in sequence until it hits something that uses os.pullEvent and then swaps over and does the other side, and if both stop at something that does os.pullEvent then it keeps swapping between until one yields and continues from there.
local function getClick()
local event,side,x,y = os.pullEvent("monitor_touch")
buttoncheckxy(x,y)
end
local tmp = 0
local function makeButtons()
while true do
button.label(2,2,"Test "..tmp)
button.screen()
tmp++
sleep(0)
end
end
parallel.waitForAny(getClick,makeButtons)
Now if you notice, first thing, I've made your while loop into a function and added a sleep inside it, so that it yields and allows the program to swap. At the end you see parallel.waitForAny() which runs the two functions that are specified and when one of them finishes, which in this case whenever you click on a button, then it ends. Notice however inside the arguments that I'm not calling the functions, I'm just passing them.
I don't have computercraft handy right now or look up the functions but i know that you can use the function os.startTimer(t) that will cause an event in t seconds (I think it is seconds)
usage:
update_rate = 1
local _timer = os.startTimer(update_rate)
while true do
local event = os.pullEvent()
if event == _timer then
--updte_screen()
_timer = os.startTimer(update_rate)
elseif event == --some oter events you want to take action for
--action()
end
end
note: the code is not tested and I didn't use computercraft in quite a while so pleas correct me if i did a mistake.

game Lua scripting - using couroutine or polling?

I am starting to learn how to use Lua scripting for different game profile with logitech software.
First I tried to use onevent (I know it isn't very advanced) and created this attack combo script
function OnEvent(event, arg)
if event == "MOUSE_BUTTON_PRESSED" and arg == 1 then --set flag for mb1
mb1_pressed = true
elseif event == "MOUSE_BUTTON_RELEASED" and arg == 1 then --set flag for mb1=false
mb1_pressed = false
end
end
if mb1_pressed then --using flags to determine whether to start attack or not
repeat
presskey("A")
Sleep(50)
releasekey("A")
Sleep(100)
--if MB1 is release, it will also break script. if i only tap mb1, this will only execute the first line of attack without the rest below
if not (**argument**, can be MB1/ismouse1) then break end
presskey("S")
Sleep(50)
releasekey("")
Sleep(120)
presskey("A")
Sleep(50)
releasekey("A")
Sleep(200)
if not (**argument**, can be MB1/ismouse1) then break end --if MB1 is release, it will also break script. this point will prevent script from looping from start if mb1 release
until not (**argument**, i use ismouse1) --end the loop of script
end
So I am trying to bind this to G6 button of my logiech mouse (using mouse_button_press == 6)
Setting a flag with MB6 works, but ending a loop/breaking a loop cannot be triggered by MB6
After some research on SDK/Lua forum of logitech support, it seems that there is a problem with my script
Flags cannot be used/detect as an argument while a script is performing a loop sequence
IsMouseButtonPressed (reads windows keypress) can be used in place or arguments
Windows only detects MB1-5, so binding to G6 is not possible (registers as 6th button)
I read that using couroutine.yield() or polling can be used for stopping repeat scripts in loop. But I cannot find a tutorial for beginners online.
Sorry for the noobish question!
I don't know anything about Logitech mice so I will try to explain things using a simplified, pure Lua example. Lets model the autoattack script as a loop that prints "A" and "B" alternatively. The "A" corresponds to the first part of your loop (press and release A) and the "B" represents the second part (press and release S and A).
function autoattack()
while true do
print("A")
print("B")
end
end
autoattack()
So far we are OK but the loop will obviously run forever and we need to add a way to stop it. I think what you are trying to do is something along the lines of:
local autoattacking = false
function autoattack()
autoattacking = true
while true do
print("A")
if not autoattacking then break end
print("B")
if not autoattacking then break end
end
end
function stop_autoattack()
autoattacking = false
end
autoattack()
stop_autoattack()
However, since autoattack is an infinite loop, stop_autoattack never runs and the autoattacking flag never gets updated. How can we fix this?
Polling
Instead of calling a function and setting a flag to stop the loop, what if we could call some code to see if the loop should be stopped or not?
function continue_autoattack()
print("continue autoattacking? y/n")
return (io.read("*l") == "y")
end
function autoattack()
while true do
print("A")
if not continue_autoattack() then break end
print("B")
if not continue_autoattack() then break end
end
end
autoattack()
In your mouse this would probably mean using some sort of isKeyPressed function, if its available in the API. Its also important to note that the autoattack loop is still an infinite loop - its just that we changed it so it is in control of its stopping condition.
Coroutines
If we want to keep the code to stop the loop outside the loop we will need a way to run the autoattack loop one step at a time. Here is an example:
local state = 1
function autoattack_step()
if state == 1 then
print("A")
state = 2
elseif state == 2
print("B")
state = 1
elseif state == 3
print("STOPPED")
--state remains as 3
else
error("bad state") -- defensive programming; I hate if/elseif without an else
end
end
function stop_autoattack()
state = 3
end
autoattack_step()
autoattack_step()
autoattack_step()
stop_autoattack()
autoattack_step()
Since we broke up the autoattack loop, we now have a chance to call stop_autoattack between calls to autoattack_step. To do this in your mouse script, I think stop_autoattack can go in "release button" handlers but I dont know where I would put the autoattack_step calls. Maybe the API includes something similar to setTimeout or setInterval in Javascript.
As for coroutines, where do they come in? Did you notice how we needed to do some substantial code refactoring to break the loop into single step chunks for autoattack_step? Coroutines are a Lua feature that lets you write code using loops while still being able to run them "one step at a time". When a coroutine reaches a coroutine.yield, it returns back to its caller. The thing is that when you call coroutine.resume again the coroutine will continue executing from where it stopped instead of going back to the start like a normal function would.
local autoattacking = true
autoattack = coroutine.create(function()
while true do
print("A")
coroutine.yield()
if not autoattacking then break end
print("B")
coroutine.yield()
if not autoattacking then break end
end
end)
function stop_autoattack()
autoattacking = false
end
coroutine.resume(autoattack)
coroutine.resume(autoattack)
coroutine.resume(autoattack)
stop_autoattack()
coroutine.resume(autoattack)
coroutine.resume(autoattack)
Very often, coroutines let you keep code more readable, without turning inside out with lots of explicit "state" variables. We still need to have some "higher up" code calling coroutine.resume though, just like we needed to have some higher level code calling autoattack_step.
Ok, so specific to Logitech's implementation of lua in the Logitech Gaming Software suite, you need to use polling.
Once you press a G-key (mouse, pad or keyboard) the OnEvent() function is called. Once inside on event no new OnEvent() events can be called until you exit, your process will become 'stuck' in any loop (as it can't exit the loop, it can't exit the OnEvent() call.
What you need is an interrupt to poll for.
There are three:- IsMouseButtonPressed( button), IsMKeyPressed( key ), IsModifierPressed( modifier ).
If you want your routiene to run while you hold the (any specified) mouse button, you can use IsMouseButtonPressed(n) thus:-
while IsMouseButtonPressed(n) do
doStuff()
end
If you wish to to say, have a toggle switch to start firing (ie: to auto-press a mouse button), then you have to use one of the other two available interrupts, ie:-
PressMouseButton(n);
while not IsModifierPressed("ctrl") do
doStuff()
end
Here your loop will run until you hold down the ctrl key. So not a puristic toggle switch (a G-key to turn on and ctrl to turn off), but passable I believe.
Note:- after further playing, er, testing, I have found IsMouseButtonPressed(n) is independent of PressMouseButton(n), rather this is read from the i/o device, so you can use IsMouseButtonPressed as the interrupt for auto-mouse pressing.
Using a G-Key to kick the action off and a mouse click to interrupt (end) the action , (or you could use both mouse and/or modifier).

Corona Active Battle Scene in Composer

I am currently working on a card battle game. In the main battle scene I am trying to show the cards actively battling one another with their health bars decreasing and eventually weapons moving around the scene as well.
At present, when it enters the battle loop, the display freezes, but I have been logging what is happening and the battle is still happening, just behind the scene. I have separated the battle loop into its own function at the top of the code and call that function with a tap event.
I verify that it is running by using the print statements within the while loop which prints out the current health of the card and the name of the card to the console. The current health of the cards are changing, it's just not changing scenes, but instead freezing on the old one, without actively displaying what is happening.
Here is the code for the entire scene:
function battleScene(playerCards, enemyCards, allCards, cardHealth)
while not checkIfDead(playerCards) and not checkIfDead(enemyCards) do
for i=1, 6 do
if allCards[i]~=0 then
allCards[i]:battle()
end
print( allCards[i]:getCurHealth().." "..allCards[i]:getName() )--The test to see current health of card
cardHealth[i]:setHealth(allCards[i]:getCurHealth(),allCards[i]:getHealth())
if checkIfDead(playerCards) or checkIfDead(enemyCards) then
break
end
usleep(2000)
end
end
end
---------------------------------------------------------------------------------
-- "scene:show()"
function scene:show( event )
local sceneGroup = self.view
local phase = event.phase
if ( phase == "will" ) then
-- Called when the scene is still off screen (but is about to come on screen).
elseif ( phase == "did" ) then
--The current health of each card is set to max
--and then the card is rendered along with health bars
local card1=test1:render()
card1.x=display.contentCenterX-100
card1.y=display.contentCenterY-100
sceneGroup:insert(card1)
local card1Health=HealthBar:new()
card1Health.x=display.contentCenterX-100
card1Health.y=display.contentCenterY-40
card1Health:setHealth(test1:getCurHealth(), test1:getHealth())
sceneGroup:insert(card1Health)
playerCards={test4, test5, test6}
enemyCards={test1, test2, test3}
for i=1, 3 do
if playerCards[i]:getClass()=="Tank" or playerCards[i]:getClass()=="Damage" then
playerCards[i]:setBattleSet(enemyCards)
else
playerCards[i]:setBattleSet(playerCards)
end
end
for i=1, 3 do
if enemyCards[i]:getClass()=="Tank" or enemyCards[i]:getClass()=="Damage" then
enemyCards[i]:setBattleSet(playerCards)
else
enemyCards[i]:setBattleSet(enemyCards)
end
end
local allCards={test1, test2, test3, test4, test5, test6}
bubbleSort(allCards)
local cardHealth= {card1Health,card2Health,card3Health,card4Health,card5Health,card6Health}
local startBattleButton=display.newText( "Start Battle", 0, 0, globals.font.regular, 18 )
startBattleButton.x = display.contentCenterX
startBattleButton.y = display.contentCenterY
local function onTap(event)
startBattleButton.isVisible=false
battleScene(playerCards, enemyCards, allCards, cardHealth)
end
startBattleButton:addEventListener( "tap", onTap )
sceneGroup:insert(startBattleButton)
if checkIfDead(playerCards) then
win=false
end
end
end
The problem is that your battle scene function is looping and modifying the scene, however the scene engine updates the scene only between event handling calls. I.e., if your tap function gets called and you modify it, you will see the changes only after the tap function returns and the scene engine has processed the new scene state.
So instead of doing this:
function battleScene(args)
while condition do
do stuff
end
end
do instead
function battleScene(args)
if condition then
do stuff
timer.performWithDelay(2, function() battleScene(args) end)
end
end
This executes "do stuff" when the condition is true, and schedules a call to battleScene for later. The battleScene will return immediately after that, given the display engine the chance to update GUI, and 2 ms later, will call battleScene again, until eventually the condition is false and no re-call will be scheduled. Note that I had to create a temp anonymous function because battleScene takes arguments whereas performWithDelay does not pass any arguments to the scheduled function, but they can be given implicitly as upvalues via anonymous function. To be clear, if battleScene had taken no args, you could have just done this:
function battleScene()
if condition then
do stuff
timer.performWithDelay(2, battleScene)
end
end

Resources