diff --git a/parts/eventsets/classic_e.lua b/parts/eventsets/classic_e.lua index 98061047..7913e843 100644 --- a/parts/eventsets/classic_e.lua +++ b/parts/eventsets/classic_e.lua @@ -26,7 +26,7 @@ end return { das=16,arr=6, sddas=6,sdarr=6, - irs=false,ims=false, + logicalIRS=false,logicalIMS=false, drop=6,lock=6, wait=10,fall=25, freshLimit=0, diff --git a/parts/eventsets/classic_h.lua b/parts/eventsets/classic_h.lua index 0cd353e5..83fb3196 100644 --- a/parts/eventsets/classic_h.lua +++ b/parts/eventsets/classic_h.lua @@ -26,7 +26,7 @@ end return { das=16,arr=6, sddas=3,sdarr=3, - irs=false,ims=false, + logicalIRS=false,logicalIMS=false, drop=3,lock=3, wait=10,fall=25, freshLimit=0, diff --git a/parts/eventsets/classic_l.lua b/parts/eventsets/classic_l.lua index 1b603412..893972bd 100644 --- a/parts/eventsets/classic_l.lua +++ b/parts/eventsets/classic_l.lua @@ -26,7 +26,7 @@ end return { das=16,arr=6, sddas=2,sdarr=2, - irs=false,ims=false, + logicalIRS=false,logicalIMS=false, drop=2,lock=2, wait=10,fall=25, freshLimit=0, diff --git a/parts/eventsets/classic_u.lua b/parts/eventsets/classic_u.lua index f6c9d14e..72cfc184 100644 --- a/parts/eventsets/classic_u.lua +++ b/parts/eventsets/classic_u.lua @@ -7,7 +7,7 @@ end return { das=16,arr=6, sddas=1,sdarr=1, - irs=false,ims=false, + logicalIRS=false,logicalIMS=false, drop=1,lock=1, wait=10,fall=25, freshLimit=0, diff --git a/parts/eventsets/master_g.lua b/parts/eventsets/master_g.lua index 1f869da5..2fcc12b8 100644 --- a/parts/eventsets/master_g.lua +++ b/parts/eventsets/master_g.lua @@ -149,7 +149,7 @@ return { keyCancel={10,11,12,14,15,16,17,18,19,20}, das=16,arr=1, minsdarr=1, - ihs=true,irs=true,ims=false, + logicalIRS=true,logicalIHS=true,logicalIMS=false, mesDisp=function(P) local D=P.modeData GC.setColor(1,1,1,1) diff --git a/parts/gameFuncs.lua b/parts/gameFuncs.lua index 8cd0dd56..73ca7584 100644 --- a/parts/gameFuncs.lua +++ b/parts/gameFuncs.lua @@ -975,7 +975,7 @@ end do-- function dumpBasicConfig() local gameSetting={ -- Tuning - 'das','arr','dascut','dropcut','sddas','sdarr', + 'das','arr','dascut','irscut','dropcut','sddas','sdarr', 'ihs','irs','ims','RS', -- System @@ -1021,7 +1021,7 @@ do-- function resetGameData(args) end local gameSetting={ -- Tuning - 'das','arr','dascut','dropcut','sddas','sdarr', + 'das','arr','dascut','irscut','dropcut','sddas','sdarr', 'ihs','irs','ims','RS', -- System @@ -1236,7 +1236,7 @@ do-- function pressKey(k) end do-- SETXXX(k) & ROOMXXX(k) local warnList={ - 'das','arr','dascut','dropcut','sddas','sdarr', + 'das','arr','dascut','irscut','dropcut','sddas','sdarr', 'ihs','irs','ims','RS', 'frameMul','highCam', 'VKSwitch','VKIcon','VKTrack','VKDodge', diff --git a/parts/gameTables.lua b/parts/gameTables.lua index 7543b25e..3700bd58 100644 --- a/parts/gameTables.lua +++ b/parts/gameTables.lua @@ -605,7 +605,7 @@ do-- Userdata tables SETTING={-- Settings -- Tuning das=10,arr=2, - dascut=0,dropcut=0, + dascut=0,irscut=6,dropcut=0, sddas=0,sdarr=2, ihs=true,irs=true,ims=true, holdMode='hold', diff --git a/parts/language/dict_en.lua b/parts/language/dict_en.lua index 213f114a..21b9a697 100644 --- a/parts/language/dict_en.lua +++ b/parts/language/dict_en.lua @@ -865,6 +865,11 @@ FNNS and {"Support 3", "term", "A special delay applied to DAS when a new block is spawned. When this happens, a small delay is added before the DAS starts timing, so that a piece doesn't start moving immediately when a sideways direction key is pressed.\nOther games may have similar mechanisms, but they may work differently.", }, + {"IRS cut", + "irscut icd", + "term", + "A special delay applied to IRS when a new block is spawned. When entry delay is disabled, this will delay IRS from being applied, allowing you to release the rotation button in the period to avoid a misdrop.", + }, {"Auto-lock cut", "autolockcut mdcut", "term", diff --git a/parts/language/dict_ja.lua b/parts/language/dict_ja.lua index 00aebf9d..9fe8b8a6 100644 --- a/parts/language/dict_ja.lua +++ b/parts/language/dict_ja.lua @@ -961,6 +961,11 @@ FNNS and {"サポート3", "term", "*Techmino用語*通常、ミノが出現する前にDAS時間以上入力をしているとミノが出現した瞬間に動き出します\nDASカットはこのような現象を減らすためにDAS時間以上入力していても出現時にDASカット分減算する機能です\n他のゲームにも似たようなものがありますが恐らく異なるでしょう", }, + -- {"IRS cut", + -- "irscut icd", + -- "term", + -- "A special delay applied to IRS when a new block is spawned. When entry delay is disabled, this will delay IRS from being applied, allowing you to release the rotation button in the period to avoid a misdrop.", + -- }, {"Auto-lock cut(自動設置カット)", "autolockcut mdcut 自動 カット", "term", diff --git a/parts/language/dict_vi.lua b/parts/language/dict_vi.lua index 6bb54c31..943782eb 100644 --- a/parts/language/dict_vi.lua +++ b/parts/language/dict_vi.lua @@ -654,6 +654,11 @@ Xem mục tiếp theo để biết thêm. "term", "Cơ chế đặc biệt sẽ được kích hoạt khi gạch mới xuất hiện. Khi kích hoạt, cơ chế này sẽ tăng DAS lên một chút để gạch không tự di chuyển ngay khi đang có phím được giữ.\n\nCác game khác có thể có tính năng tương tự nhưng cách hoạt động có thể khác nhau.", }, + -- {"IRS cut", + -- "irscut icd", + -- "term", + -- "A special delay applied to IRS when a new block is spawned. When entry delay is disabled, this will delay IRS from being applied, allowing you to release the rotation button in the period to avoid a misdrop.", + -- }, {"Auto-lock cut", "nhom05e2 autolockcut", "term", diff --git a/parts/language/dict_zh.lua b/parts/language/dict_zh.lua index d714f9a0..2c486538 100644 --- a/parts/language/dict_zh.lua +++ b/parts/language/dict_zh.lua @@ -853,6 +853,11 @@ FNNS and {"赞助3", "term", "Techmino中指玩家的操作焦点转移到新方块的瞬间,此时减小(重置)DAS计时器,让自动移动不会立刻生效,减少 “移动键松开晚了导致下一块一出来就立即开始移动” 的情况\n注:其他游戏中的DAS打断机制可能和Techmino的有区别,仅供参考。", }, + {"IRS打断(ICD)", + "irscut icd daduan", + "term", + "(由Electra设计)新方块生成时触发IRS的特殊延迟。在没有生成延迟时,这会让预输入的旋转动作延迟一段时间再生效,允许晚一点松开旋转键防止md。", + }, {"误硬降打断(HCD)", "autolockcut mdcut daduan", "term", diff --git a/parts/language/lang_en.lua b/parts/language/lang_en.lua index 9c82f4d5..b4404396 100644 --- a/parts/language/lang_en.lua +++ b/parts/language/lang_en.lua @@ -622,6 +622,7 @@ C. Gamepad das="DAS",arr="ARR", dascut="DAS Cut", + irscut="IRS Cut", dropcut="Auto-lock Cut", sddas="Soft Drop DAS",sdarr="Soft Drop ARR", ihs="Initial Hold", diff --git a/parts/player/gameEnv0.lua b/parts/player/gameEnv0.lua index e057d0ae..2d237f54 100644 --- a/parts/player/gameEnv0.lua +++ b/parts/player/gameEnv0.lua @@ -1,8 +1,9 @@ return { das=10,arr=2, - dascut=0,dropcut=0, + dascut=0,irscut=6,dropcut=0, sddas=2,sdarr=2, ihs=true,irs=true,ims=true, + logicalIHS=true,logicalIRS=true,logicalIMS=true, ghostType='gray', block=true,ghost=.3,center=1, diff --git a/parts/player/player.lua b/parts/player/player.lua index 8f820231..744e7463 100644 --- a/parts/player/player.lua +++ b/parts/player/player.lua @@ -270,27 +270,61 @@ function Player:act_rotRight() if not self.control then return end if self.cur then self.ctrlCount=self.ctrlCount+1 + if self.bufferedIRS then + -- Ensure IRS is spent before the rotation is processed so it doesn't throw things off. + -- This is so that if you for instance, are holding left IRS and then rotate right, it doesn't process + -- the left and right rotates in the reverse order. + self.keyPressing[3]=false + self:resolveIRS() + self.keyPressing[3]=true + end self:spin(1) self:_triggerEvent('hook_rotate',1) - self.keyPressing[3]=false + + -- Disable held inputs if IRS is off + if not self.gameEnv.irs then + self.keyPressing[3]=false + end end end function Player:act_rotLeft() if not self.control then return end if self.cur then self.ctrlCount=self.ctrlCount+1 + if self.bufferedIRS then + -- Ensure IRS is spent before the rotation is processed so it doesn't throw things off. + -- This is so that if you for instance, are holding left IRS and then rotate right, it doesn't process + -- the left and right rotates in the reverse order. + self.keyPressing[4]=false + self:resolveIRS() + self.keyPressing[4]=true + end self:spin(3) self:_triggerEvent('hook_rotate',3) - self.keyPressing[4]=false + -- Disable held inputs if IRS is off + if not self.gameEnv.irs then + self.keyPressing[4]=false + end end end function Player:act_rot180() if not self.control then return end if self.cur then self.ctrlCount=self.ctrlCount+2 + if self.bufferedIRS then + -- Ensure IRS is spent before the rotation is processed so it doesn't throw things off. + -- This is so that if you for instance, are holding left IRS and then rotate right, it doesn't process + -- the left and right rotates in the reverse order. + self.keyPressing[5]=false + self:resolveIRS() + self.keyPressing[5]=true + end self:spin(2) self:_triggerEvent('hook_rotate',2) - self.keyPressing[5]=false + -- Disable held inputs if IRS is off + if not self.gameEnv.irs then + self.keyPressing[5]=false + end end end function Player:act_hardDrop() @@ -300,6 +334,10 @@ function Player:act_hardDrop() if self.lastPiece.autoLock and self.frameRun-self.lastPiece.frameself.ghoY then self:createDropFX() self.curY=self.ghoY @@ -344,7 +382,10 @@ function Player:act_hold() if not self.control then return end if self.cur then if self:hold() then - self.keyPressing[8]=false + -- Disable held inputs if IHS is off + if not self.gameEnv.ihs then + self.keyPressing[8]=false + end self:_triggerEvent('hook_hold') end end @@ -1164,34 +1205,67 @@ function Player:resetBlock()-- Reset Block's position and execute I*S self.curY=y self.minY=y+sc[1] + local ENV=self.gameEnv + + -- In the game settings, there are user-set control flags for irs,irs,ims + -- These control in what way the user can buffer their rotate/hold/move inputs. + -- (If enabled, they may hold these inputs from the previous piece instead of just Entry Delay) + + -- And mode-set flags for logicalIRS,logicalIHS,logicalIMS + -- These control whether IRS/IHS/IMS are effective in modifying what you can do. + -- (For instance, changing your piece's spawn position in 20g, or saving you from a death). + -- If logical IRS is disabled, the player may still IRS, but it will just buffer their input, + -- not actually allowing them to survive in a way they could not without. + local pressing=self.keyPressing - -- IMS - if self.gameEnv.ims and (pressing[1] and self.movDir==-1 or pressing[2] and self.movDir==1) and self.moving>=self.gameEnv.das then - local x=self.curX+self.movDir - if not self:ifoverlap(C.bk,x,y) then - self.curX=x + -- IMS is enabled only when logicalIMS is enabled, because otherwise, it's just faster DAS. + if ENV.logicalIMS and (pressing[1] and self.movDir==-1 or pressing[2] and self.movDir==1) and self.moving>=self.gameEnv.das then + -- To avoid a top-out + if self:ifoverlap(C.bk,self.curX,self.curY) then + -- Always perform the shift, since you're topped out anyway + self.curX=self.curX+self.movDir + elseif ENV.wait>0 and ENV.ims then + -- Otherwise, only check IMS if it's enabled and you're in a mode with entry delay (20g) + local x=self.curX+self.movDir + if not self:ifoverlap(C.bk,x,y) then + self.curX=x + end end end - -- IRS - if self.gameEnv.irs then + if not ENV.logicalIRS then + -- If logical IRS is disabled, all IRS inputs will be buffered to prevent survival. + self.bufferedIRS=true + self.bufferedDelay=0 + if ENV.wait==0 then + self.bufferedDelay=ENV.irscut + end + elseif ENV.wait==0 and ENV.irscut>0 and not self:ifoverlap(C.bk,self.curX,self.curY) then + -- If IRS cut delay is enabled and we aren't currently dying, buffer the input instead. + self.bufferedIRS=true + self.bufferedDelay=ENV.irscut + else + -- If we're currently dying or in an entry-delay mode (20g), perform the rotation right away. if pressing[5] then - self:spin(2,true) + self:act_rot180() elseif pressing[3] then if pressing[4] then - self:spin(2,true) + self:act_rot180() else - self:spin(1,true) + self:act_rotRight() end elseif pressing[4] then - self:spin(3,true) + self:act_rotLeft() end + end + -- Disable held inputs if IRS is off + if not ENV.irs then pressing[3],pressing[4],pressing[5]=false,false,false end -- DAS cut - if self.gameEnv.dascut>0 then - self.moving=self.moving-(self.moving>0 and 1 or -1)*self.gameEnv.dascut + if ENV.dascut>0 then + self.moving=self.moving-(self.moving>0 and 1 or -1)*ENV.dascut end -- Spawn SFX @@ -1511,9 +1585,29 @@ function Player:_popNext(ifhold)-- Pop nextQueue to hand local pressing=self.keyPressing -- IHS - if not ifhold and pressing[8] and ENV.ihs and self.holdTime>0 then - self:hold(true) - pressing[8]=false + if not ifhold and pressing[8] and self.holdTime>0 then + if not ENV.logicalIHS then + -- If logical IHS is disabled, all IHS inputs will be buffered to prevent survival. + self.bufferedIRS=true + self.bufferedIHS=true + self.bufferedDelay=0 + if ENV.wait==0 then + self.bufferedDelay=ENV.irscut + end + elseif ENV.wait==0 and ENV.irscut>0 and not self:willDieWith(self.cur) then + -- If IRS cut delay is enabled and we're not currently dying, buffer the input instead. + self.bufferedIRS=true + self.bufferedIHS=true + self.bufferedDelay=ENV.irscut + self:resetBlock() + else + -- If we're currently dying or in an entry-delay mode (20g), perform the hold immediately. + self:hold(true) + end + -- Disable held inputs if IHS is off + if not ENV.ihs then + pressing[8]=false + end else self:resetBlock() end @@ -1827,7 +1921,7 @@ do end end - local yomi = "" + local yomi='' piece.spin,piece.mini=dospin,false piece.pc,piece.hpc=false,false @@ -1838,7 +1932,7 @@ do cscore=(spinSCR[C.name] or spinSCR[8])[cc] if self.b2b>800 then self:showText(text.b3b..text.block[C.name]..text.spin..text.clear[cc],0,-30,35,'stretch') - yomi = yomi..text.b3b..text.block[C.name]..text.spin..text.clear[cc] + yomi=yomi..text.b3b..text.block[C.name]..text.spin..text.clear[cc] atk=b2bATK[cc]+cc*.5 exblock=exblock+1 cscore=cscore*2 @@ -1848,7 +1942,7 @@ do end elseif self.b2b>=50 then self:showText(text.b2b..text.block[C.name]..text.spin..text.clear[cc],0,-30,35,'spin') - yomi = yomi..text.b2b..text.block[C.name]..text.spin..text.clear[cc] + yomi=yomi..text.b2b..text.block[C.name]..text.spin..text.clear[cc] atk=b2bATK[cc] cscore=cscore*1.2 Stat.b2b=Stat.b2b+1 @@ -1857,13 +1951,13 @@ do end else self:showText(text.block[C.name]..text.spin..text.clear[cc],0,-30,45,'spin') - yomi = yomi..text.block[C.name]..text.spin..text.clear[cc] + yomi=yomi..text.block[C.name]..text.spin..text.clear[cc] atk=2*cc end sendTime=20+atk*20 if mini then self:showText(text.mini,0,-80,35,'appear') - yomi = text.mini..' '..yomi + yomi=text.mini..' '..yomi atk=atk*.25 sendTime=sendTime+60 cscore=cscore*.5 @@ -1884,7 +1978,7 @@ do cscore=clearSCR[cc] if self.b2b>800 then self:showText(text.b3b..text.clear[cc],0,-30,50,'fly') - yomi = text.b3b..text.clear[cc]..yomi + yomi=text.b3b..text.clear[cc]..yomi atk=4*cc-10 sendTime=100 exblock=exblock+1 @@ -1895,7 +1989,7 @@ do end elseif self.b2b>=50 then self:showText(text.b2b..text.clear[cc],0,-30,50,'drive') - yomi = text.b2b..text.clear[cc]..yomi + yomi=text.b2b..text.clear[cc]..yomi sendTime=80 atk=3*cc-7 cscore=cscore*1.3 @@ -1905,7 +1999,7 @@ do end else self:showText(text.clear[cc],0,-30,70,'stretch') - yomi = text.clear[cc]..yomi + yomi=text.clear[cc]..yomi sendTime=60 atk=2*cc-4 end @@ -1913,7 +2007,7 @@ do piece.special=true else self:showText(text.clear[cc],0,-30,35,'appear',(8-cc)*.3) - yomi = text.clear[cc]..yomi + yomi=text.clear[cc]..yomi atk=cc-.5 sendTime=20+floor(atk*20) cscore=cscore+clearSCR[cc] @@ -1932,7 +2026,7 @@ do atk=atk+1 end self:showText(text.cmb[min(cmb,21)],0,25,15+min(cmb,15)*5,cmb<10 and 'appear' or 'flicker') - yomi = yomi..' '..text.cmb[min(cmb,21)] + yomi=yomi..' '..text.cmb[min(cmb,21)] cscore=cscore+min(50*cmb,500)*(2*cc-1) end @@ -2460,6 +2554,28 @@ local function _updateFX(P,dt) end end end + +function Player:resolveIRS() + if self.bufferedIHS then + self:hold(true) + self.bufferedIHS=false + end + + self.bufferedIRS=false + local pressing=self.keyPressing + if pressing[5] then + self:act_rot180() + elseif pressing[3] then + if pressing[4] then + self:act_rot180() + else + self:act_rotRight() + end + elseif pressing[4] then + self:act_rotLeft() + end +end + local function update_alive(P,dt) local ENV=P.gameEnv @@ -2518,6 +2634,18 @@ local function update_alive(P,dt) end end + -- Buffer IRS after IRS cut delay has elapsed. + -- The purpose of this is to allow the player to release their rotate key during the IRS cut delay, + -- which will allow them to avoid accidentally using IRS. + if P.bufferedDelay then + P.bufferedDelay=P.bufferedDelay-1 + if P.bufferedDelay<=0 then + if P.bufferedIRS then + P:resolveIRS() + end + end + end + -- Moving pressed if P.movDir~=0 then local das,arr=ENV.das,ENV.arr @@ -2877,7 +3005,7 @@ function Player:revive() SFX.play('emit') end function Player:torikanEnd(requiredTime) - if self.stat.time < requiredTime then + if self.stat.time