From 120dc453d7680274832028a419ebd2b671ab4c40 Mon Sep 17 00:00:00 2001 From: NOT_A_ROBOT Date: Fri, 6 Oct 2023 00:45:36 +0700 Subject: [PATCH] Add Autoskip feature to TAS (#992) --- parts/scenes/app_console.lua | 2 +- parts/scenes/game.lua | 130 ++++++++++++++++++++++++++--------- 2 files changed, 100 insertions(+), 32 deletions(-) diff --git a/parts/scenes/app_console.lua b/parts/scenes/app_console.lua index bef47d7b2..7b7916eb7 100644 --- a/parts/scenes/app_console.lua +++ b/parts/scenes/app_console.lua @@ -853,7 +853,7 @@ local commands={} do saveSettings() log("Allow TAS: "..bool) if bool then - log("Hot keys: f1=play/pause f2=slow down f3=speed up/next frame") + log("Hot keys: f1=play/pause f2=slow down f3=speed up/next frame f4=adjust skip settings") end else log{C.A,"Usage: tas "} diff --git a/parts/scenes/game.lua b/parts/scenes/game.lua index a0dad185b..b2eafcd2e 100644 --- a/parts/scenes/game.lua +++ b/parts/scenes/game.lua @@ -10,6 +10,7 @@ local setFont,mStr=FONT.set,GC.mStr local noTouch,noKey=false,false local touchMoveLastFrame=false local trigGameRate,gameRate +local autoSkip=0 local modeTextPos,modeTextWidK local replaying @@ -17,55 +18,70 @@ local repRateStrings={[0]="pause",[.125]="0.125x",[.5]="0.5x",[1]="1x",[2]="2x", local scene={} +-- Widget hashmap (key = widget name, value = index) +local widgetHashmap={} +local function widgetWithName(name) + if #widgetHashmap==0 then widgetHashmap=TABLE.kvSwap(TABLE.extract(scene.widgetList,'name')) end + return scene.widgetList[widgetHashmap[name]] +end + local function _updateMenuButtons() - scene.widgetList.restart.hide=replaying + widgetWithName('restart').hide=replaying local pos=(GAME.tasUsed or replaying) and 'right' or SETTING.menuPos modeTextWidK=math.min(280/TEXTOBJ.modeName:getWidth(),1) if SETTING.portrait then - scene.widgetList.restart.y=25-400 - scene.widgetList.pause.y=25-400 + widgetWithName('restart').y=25-400 + widgetWithName('pause').y=25-400 else - scene.widgetList.restart.y=25 - scene.widgetList.pause.y=25 + widgetWithName('restart').y=25 + widgetWithName('pause').y=25 end if GAME.replaying then - scene.widgetList.pause.x=1195 + widgetWithName('pause').x=1195 modeTextPos=1185-TEXTOBJ.modeName:getWidth()*modeTextWidK elseif pos=='right' then - scene.widgetList.restart.x=1125 - scene.widgetList.pause.x=1195 + widgetWithName('restart').x=1125 + widgetWithName('pause').x=1195 modeTextPos=1115-TEXTOBJ.modeName:getWidth()*modeTextWidK elseif pos=='middle' then - scene.widgetList.restart.x=360 - scene.widgetList.pause.x=860 + widgetWithName('restart').x=360 + widgetWithName('pause').x=860 modeTextPos=940 elseif pos=='left' then - scene.widgetList.restart.x=120 - scene.widgetList.pause.x=190 + widgetWithName('restart').x=120 + widgetWithName('pause').x=190 modeTextPos=1200-TEXTOBJ.modeName:getWidth()*modeTextWidK end end + +local speedButtons={'rep0','repP8','repP2','rep1','rep2','rep5'} +local stepButtons={'step','autoSkip'} +local replayButtons=TABLE.combine(speedButtons,stepButtons) local function _updateRepButtons() local L=scene.widgetList if replaying or GAME.tasUsed then - for i=1,6 do L[i].hide=false end L[7].hide=true + for i=1,#speedButtons do + widgetWithName(speedButtons[i]).hide=false + end + for i=1,#stepButtons do + widgetWithName(stepButtons[i]).hide=gameRate~=0 + end if gameRate==0 then - L[1].hide=true - L[7].hide=false + widgetWithName('rep0').hide=true elseif gameRate==.125 then - L[2].hide=true + widgetWithName('repP8').hide=true elseif gameRate==.5 then - L[3].hide=true + widgetWithName('repP2').hide=true elseif gameRate==1 then - L[4].hide=true + widgetWithName('rep1').hide=true elseif gameRate==2 then - L[5].hide=true + widgetWithName('rep2').hide=true elseif gameRate==5 then - L[6].hide=true + widgetWithName('rep5').hide=true end else - for i=1,7 do L[i].hide=true end + for i=1,#replayButtons do widgetWithName(replayButtons[i]).hide=true end end end local function _speedUp() @@ -115,7 +131,46 @@ local function _rep5() gameRate=5 _updateRepButtons() end -local function _step() trigGameRate=trigGameRate+1 end +local function _skip(P) + if P.frameRun<179 then + trigGameRate=trigGameRate+(179-P.frameRun) + else + trigGameRate=trigGameRate+MATH.clamp(P.waiting+P.falling,1,300) + end +end +local fastForwardObj = GC.newText(FONT.get(40),CHAR.icon.fastForward) +local nextFrameObj = GC.newText(FONT.get(40),CHAR.icon.nextFrame) +local function _updateStepButton() + local w=PLAYERS[1].waiting+PLAYERS[1].falling + if (autoSkip==1 and w>1) or (autoSkip>0 and PLAYERS[1].frameRun<178) then + widgetWithName('step').obj=fastForwardObj + else + widgetWithName('step').obj=nextFrameObj + end +end +local function _step() + local P=PLAYERS[1] + if autoSkip>0 then + _skip(P) + else + trigGameRate=trigGameRate+1 + end +end +local function _fullSkipCheck() + if autoSkip<2 or PLAYERS[1].waiting+PLAYERS[1].falling<=0 then return end + _step() +end +local function _setAS(v) + autoSkip=v + if v==1 then _updateStepButton() end +end +local function _autoSkipDisp() + return ( + autoSkip==0 and 'No skip' or + autoSkip==1 and 'Semi-skip' or + autoSkip==2 and 'Full skip' + ) +end local function _restart() resetGameData(PLAYERS[1].frameRun<240 and 'q') @@ -131,6 +186,8 @@ local function _checkGameKeyDown(key) if noKey then return end PLAYERS[1]:pressKey(k) VK.press(k) + _updateStepButton() + _fullSkipCheck() return elseif not GAME.fromRepMenu then _restart() @@ -155,11 +212,14 @@ function scene.enter() elseif not replaying then if GAME.tasUsed then trigGameRate,gameRate=0,0 + autoSkip=1 else trigGameRate,gameRate=0,1 + autoSkip=0 end end + _updateStepButton() _updateRepButtons() _updateMenuButtons() end @@ -172,6 +232,8 @@ function scene.touchDown(x,y) if t then PLAYERS[1]:pressKey(t) VK.touch(t,x,y) + _updateStepButton() + _fullSkipCheck() end end function scene.touchUp(x,y) @@ -250,6 +312,8 @@ function scene.keyDown(key,isRep) elseif not isRep then _speedUp() end + elseif key=='f4' then + autoSkip=(autoSkip+1)%3 end end if key=='escape' then @@ -275,6 +339,8 @@ function scene.gamepadDown(key) if k>0 then PLAYERS[1]:pressKey(k) VK.press(k) + _updateStepButton() + _fullSkipCheck() else _restart() end @@ -314,6 +380,7 @@ function scene.update(dt) while trigGameRate>=1 do trigGameRate=trigGameRate-1 _update_common(dt) + _updateStepButton() end end @@ -403,15 +470,16 @@ function scene.draw() drawWarning() end scene.widgetList={ - WIDGET.newKey{name='rep0', x=40, y=50,w=60,code=_rep0, font=40,fText=CHAR.icon.pause}, - WIDGET.newKey{name='repP8', x=105,y=50,w=60,code=_repP8, font=40,fText=CHAR.icon.speedOneEights}, - WIDGET.newKey{name='repP2', x=170,y=50,w=60,code=_repP2, font=40,fText=CHAR.icon.speedOneHalf}, - WIDGET.newKey{name='rep1', x=235,y=50,w=60,code=_rep1, font=40,fText=CHAR.icon.speedOne}, - WIDGET.newKey{name='rep2', x=300,y=50,w=60,code=_rep2, font=40,fText=CHAR.icon.speedTwo}, - WIDGET.newKey{name='rep5', x=365,y=50,w=60,code=_rep5, font=40,fText=CHAR.icon.speedFive}, - WIDGET.newKey{name='step', x=430,y=50,w=60,code=_step, font=40,fText=CHAR.icon.nextFrame}, - WIDGET.newKey{name='restart',x=0, y=25,w=60,code=_restart, font=40,fText=CHAR.icon.retry_spin}, - WIDGET.newKey{name='pause', x=0, y=25,w=60,code=pauseGame,font=40,fText=CHAR.icon.pause}, + WIDGET.newKey {name='rep0', x=40, y=50, w=60, code=_rep0, font=40, fText=CHAR.icon.pause}, -- 1 + WIDGET.newKey {name='repP8', x=105,y=50, w=60, code=_repP8, font=40, fText=CHAR.icon.speedOneEights}, -- 2 + WIDGET.newKey {name='repP2', x=170,y=50, w=60, code=_repP2, font=40, fText=CHAR.icon.speedOneHalf}, -- 3 + WIDGET.newKey {name='rep1', x=235,y=50, w=60, code=_rep1, font=40, fText=CHAR.icon.speedOne}, -- 4 + WIDGET.newKey {name='rep2', x=300,y=50, w=60, code=_rep2, font=40, fText=CHAR.icon.speedTwo}, -- 5 + WIDGET.newKey {name='rep5', x=365,y=50, w=60, code=_rep5, font=40, fText=CHAR.icon.speedFive}, -- 6 + WIDGET.newKey {name='step', x=40, y=50, w=60, code=_step, font=40, fText=CHAR.icon.nextFrame}, -- 7 + WIDGET.newSlider{name='autoSkip',x=40, y=130,w=100,code=_setAS, axis={0,2,1}, disp=function()return autoSkip end,show=_autoSkipDisp}, + WIDGET.newKey {name='restart', x=0, y=25, w=60, code=_restart, font=40, fText=CHAR.icon.retry_spin}, -- 10 + WIDGET.newKey {name='pause', x=0, y=25, w=60, code=pauseGame,font=40, fText=CHAR.icon.pause}, -- 11 } return scene