diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 39466915e..be654dafc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,7 +38,7 @@ jobs: local version = require "version" os.execute('echo "app-name=Techmino" >> $GITHUB_OUTPUT') os.execute('echo "version-name=' .. version.name .. '" >> $GITHUB_OUTPUT') - os.execute('echo "version-string=' .. version.string:gsub("%a", "") .. '" >> $GITHUB_OUTPUT') + os.execute('echo "version-string=' .. version.string:gsub(" ", "_") .. '" >> $GITHUB_OUTPUT') os.execute('echo "version-code=' .. tostring(version.code) .. '" >> $GITHUB_OUTPUT') local f = io.open("updateLog.txt", 'r') @@ -179,10 +179,10 @@ jobs: import re with open(os.getenv('GITHUB_OUTPUT'), 'a') as f: if "${{ env.BUILD_TYPE }}" == "dev": - f.write('bundle-id=org.f26_studio.' + re.sub(r'[^A-Za-z0-9]+', '_', '${{ needs.get-info.outputs.app-name }}') + '.snapshot\n') + f.write('bundle-id=org.electra.' + re.sub(r'[^A-Za-z0-9]+', '_', '${{ needs.get-info.outputs.app-name }}') + '.snapshot\n') f.write('product-name=' + re.sub(r'[^A-Za-z0-9]+', '-', '${{ needs.get-info.outputs.app-name }}') + '_Snapshot\n') else: - f.write('bundle-id=org.f26_studio.' + re.sub(r'[^A-Za-z0-9]+', '_', '${{ needs.get-info.outputs.app-name }}') + '\n') + f.write('bundle-id=org.electra.' + re.sub(r'[^A-Za-z0-9]+', '_', '${{ needs.get-info.outputs.app-name }}') + '\n') f.write('product-name=' + re.sub(r'[^A-Za-z0-9]+', '-', '${{ needs.get-info.outputs.app-name }}') + '\n') - name: Download core love package uses: actions/download-artifact@v4 @@ -252,7 +252,7 @@ jobs: import re product_name = re.sub(r'[^A-Za-z0-9]+', '-', '${{ needs.get-info.outputs.app-name }}').strip('-').lower() with open(os.getenv('GITHUB_OUTPUT'), 'a') as f: - f.write('bundle-id=org.26f-studio.' + product_name + '\n') + f.write('bundle-id=org.electra.' + product_name + '\n') f.write('product-name=' + product_name + '\n') - name: Download core love package uses: actions/download-artifact@v4 diff --git a/parts/player/gameEnv0.lua b/parts/player/gameEnv0.lua index 5ecace2cf..4e03416d7 100644 --- a/parts/player/gameEnv0.lua +++ b/parts/player/gameEnv0.lua @@ -65,11 +65,11 @@ return { -- Some Events are registered in player/init.lua, see "tableNeedMerge" extraEvent={ - {'attack',4}, + {'attack',5}, }, extraEventHandler={ - attack=function(P,P2,...) - P:beAttacked(P2,...) + attack=function(P,source,...) + P:beAttacked(source,...) end, }, diff --git a/parts/player/init.lua b/parts/player/init.lua index cafd04ec2..900a0fdb6 100644 --- a/parts/player/init.lua +++ b/parts/player/init.lua @@ -236,7 +236,17 @@ local function _loadRemoteEnv(P,confStr)-- Load gameEnv end end end -local tableNeedMerge={ +local function _mergeFuncTable(f,L) + if type(f)=='function' then + ins(L,f) + elseif type(f)=='table' then + for i=1,#f do + ins(L,f[i]) + end + end + return L +end +local tableNeedMerge = { 'task', 'mesDisp', 'hook_left', @@ -250,63 +260,53 @@ local tableNeedMerge={ 'hook_spawn', 'hook_hold', 'hook_die', - 'extraEvent', + 'hook_atk_calculation', + 'task', } -for _,k in next,tableNeedMerge do gameEnv0[k]={} end -local function _mergeFuncTable(f,L) - if type(f)=='function' then - ins(L,f) - elseif type(f)=='table' then - for i=1,#f do - ins(L,f[i]) - end - end - return L -end local function _applyGameEnv(P)-- Finish gameEnv processing local ENV=P.gameEnv - -- Create event tables + -- Apply events for i=1,#tableNeedMerge do ENV[tableNeedMerge[i]]=_mergeFuncTable(ENV[tableNeedMerge[i]],{}) end -- Apply eventSet - while true do - if not (ENV.eventSet and ENV.eventSet~="X") then - break - end - if type(ENV.eventSet)~='string' then - MES.new('warn',"Wrong event set type: "..type(ENV.eventSet)) - break - end - local eventSet=require('parts.eventsets.'..ENV.eventSet) - if not eventSet then - MES.new('warn',"No event set called: "..ENV.eventSet) - break - end - for k,v in next,eventSet do - if k=='extraEventHandler' then - for ev,handler in next,v do - if ENV.extraEventHandler[ev] then - local prevHandler=ENV.extraEventHandler[ev] - ENV.extraEventHandler[ev]=function(...) - prevHandler(...) - handler(...) + if ENV.eventSet and ENV.eventSet~="X" then + if type(ENV.eventSet)=='string' then + local eventSet=require('parts.eventsets.'..ENV.eventSet) + if eventSet then + for k,v in next,eventSet do + if k == "extraEvent" then + for _,ev in ipairs(v) do + table.insert(ENV.extraEvent, ev) end + elseif k == "extraEventHandler" then + for ev,handler in pairs(v) do + if ENV.extraEventHandler[ev] then + local prevHandler = ENV.extraEventHandler[ev] + ENV.extraEventHandler[ev] = function(...) + prevHandler(...) + handler(...) + end + else + ENV.extraEventHandler[ev] = handler + end + end + elseif TABLE.find(tableNeedMerge,k) then + _mergeFuncTable(v,ENV[k]) + elseif type(v)=='table' then + ENV[k]=TABLE.copy(v) else - ENV.extraEventHandler[ev]=handler + ENV[k]=v end end - elseif TABLE.find(tableNeedMerge,k) then - _mergeFuncTable(v,ENV[k]) - elseif type(v)=='table' then - ENV[k]=TABLE.copy(v) else - ENV[k]=v + MES.new('warn',"No event set called: "..ENV.eventSet) end + else + MES.new('warn',"Wrong event set type: "..type(ENV.eventSet)) end - break end P._20G=ENV.drop==0 @@ -512,4 +512,4 @@ function PLY.newPlayer(id,mini,p) _applyGameEnv(P) end ---------------------------------------------------- -return PLY +return PLY \ No newline at end of file diff --git a/parts/player/player.lua b/parts/player/player.lua index aa58531af..4b365c46e 100644 --- a/parts/player/player.lua +++ b/parts/player/player.lua @@ -274,13 +274,13 @@ function Player:act_rotRight() -- 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.keyPressing[3] = false self:resolveIRS() - self.keyPressing[3]=true + self.keyPressing[3] = true end self:spin(1) self:_triggerEvent('hook_rotate',1) - + -- Disable held inputs if IRS is off if not self.gameEnv.irs then self.keyPressing[3]=false @@ -295,9 +295,9 @@ function Player:act_rotLeft() -- 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.keyPressing[4] = false self:resolveIRS() - self.keyPressing[4]=true + self.keyPressing[4] = true end self:spin(3) self:_triggerEvent('hook_rotate',3) @@ -315,9 +315,9 @@ function Player:act_rot180() -- 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.keyPressing[5] = false self:resolveIRS() - self.keyPressing[5]=true + self.keyPressing[5] = true end self:spin(2) self:_triggerEvent('hook_rotate',2) @@ -909,12 +909,45 @@ function Player:ifoverlap(bk,x,y) end end function Player:attack(R,send,time,line) - self:extraEvent('attack',R.sid,send,time,line) -end -function Player:beAttacked(P2,sid,send,time,line) - if self==P2 or self.sid~=sid then return end - self:receive(P2,send,time,line) - P2:createBeam(self,send) + local sid = R.sid + -- Add the attack to the list of in-transit attacks. + -- These attacks will be able to cancel with incoming attacks that cross them. + if not self.inTransitAttacks then + self.inTransitAttacks = {} + end + if not self.inTransitAttacks[sid] then + self.inTransitAttacks[sid] = {seenAttacks = 0} + end + table.insert(self.inTransitAttacks[sid], {send=send, time=time, line=line}) + -- Send the attack + -- We also send the number of seen attacks from this player. + -- This allows that player to know which attacks are still in transit, and which have already arrived. + -- This is because... if a player already saw an attack before sending this one, the attacks did not cross. + -- But if they didn't see the attack, then the attacks must have crossed (and should cancel each other) + self:extraEvent('attack',sid,send,time,line,self.inTransitAttacks[sid].seenAttacks) +end +function Player:beAttacked(source,target_sid,send,time,line,seenCount) + -- Only recieve the attack if you are the target. + if self==source or self.sid~=target_sid then return end + + if not self.inTransitAttacks then + self.inTransitAttacks = {} + end + if not self.inTransitAttacks[source.sid] then + self.inTransitAttacks[source.sid] = {seenAttacks = 0} + end + -- Increment the number of seen attacks from that player. + self.inTransitAttacks[source.sid].seenAttacks = self.inTransitAttacks[source.sid].seenAttacks + 1 + -- Block against any in-transit attacks before recieving (this prevents passhtrough) + for i=seenCount+1,#self.inTransitAttacks[source.sid] do + local atk = self.inTransitAttacks[source.sid][i] + local cancel = MATH.min(atk.send, send) + atk.send = atk.send - cancel + send = send - cancel + end + + self:receive(source,send,time,line) + source:createBeam(self,send) end function Player:receive(A,send,time,line) self.lastRecv=A @@ -1205,7 +1238,7 @@ function Player:resetBlock()-- Reset Block's position and execute I*S self.curY=y self.minY=y+sc[1] - local ENV=self.gameEnv + 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. @@ -1221,10 +1254,10 @@ function Player:resetBlock()-- Reset Block's position and execute I*S -- 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 + 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 + 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 @@ -1235,15 +1268,15 @@ function Player:resetBlock()-- Reset Block's position and execute I*S 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 + 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 + 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 + 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 @@ -1588,17 +1621,17 @@ function Player:_popNext(ifhold)-- Pop nextQueue to hand 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 + 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.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. @@ -2558,11 +2591,11 @@ end function Player:resolveIRS() if self.bufferedIHS then self:hold(true) - self.bufferedIHS=false + self.bufferedIHS = false end - self.bufferedIRS=false - local pressing=self.keyPressing + self.bufferedIRS = false + local pressing = self.keyPressing if pressing[5] then self:act_rot180() elseif pressing[3] then @@ -2633,13 +2666,13 @@ local function update_alive(P,dt) end 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 + P.bufferedDelay = P.bufferedDelay - 1 + if P.bufferedDelay <= 0 then if P.bufferedIRS then P:resolveIRS() end diff --git a/parts/scenes/setting_control.lua b/parts/scenes/setting_control.lua index 52a18d67e..512d10ce7 100644 --- a/parts/scenes/setting_control.lua +++ b/parts/scenes/setting_control.lua @@ -9,7 +9,6 @@ function scene.enter() das,arr=SETTING.das,SETTING.arr pos,dir,wait=0,1,30 BG.set('bg1') - DiscordRPC.update("Tweaking control settings") end local trigFrame=0 @@ -97,11 +96,11 @@ scene.widgetList={ WIDGET.newSlider{name='dascut', x=250, y=420,lim=230,w=600,axis={0,20,1},disp=SETval('dascut'), show=_sliderShow,code=SETsto('dascut')}, WIDGET.newSlider{name='irscut', x=250, y=480,lim=230,w=600,axis={0,20,1},disp=SETval('irscut'), show=_sliderShow,code=SETsto('irscut')}, WIDGET.newSlider{name='dropcut',x=250, y=540,lim=230,w=300,axis={0,10,1},disp=SETval('dropcut'),show=_sliderShow,code=SETsto('dropcut')}, - + WIDGET.newSwitch{name='ihs', x=1100, y=240,lim=300, disp=SETval('ihs'), code=SETrev('ihs')}, WIDGET.newSwitch{name='irs', x=1100, y=300,lim=300, disp=SETval('irs'), code=SETrev('irs')}, WIDGET.newSwitch{name='ims', x=1100, y=360,lim=300, disp=SETval('ims'), code=SETrev('ims')}, - + WIDGET.newButton{name='reset', x=160, y=640,w=200,h=100,color='lR',font=40, code=function() local _=SETTING