""""!!!!""""\e^^U U \ e 3334334034340D0DDDC333C33C3C3DDDD!! """ """++ ^^\^UUUU\UUUUUUUUUUUUUUUUUUUUUUU^UUUUUU \ DD3D0@DDD3KK  2#2#  C42#!2#!!2#C4!2#e^^^UUUUUUUUUUUUU^UUUUUU^UUUUUUnVUffUUfffUefUUfUUe  \e^U^U \e  ݭݻݪݫݻݪ   ݝݪݙݚݪݙ            9999999p9P90999999::;;<<==>>????? pppp 02468:<=>?????????????????????b0p1p2`3`4P5P6@7@8090: ; <=>???????????????b0p1p2`3`4P5P6@7@8090: ; <=>???????????????a   0000000000  {   6789:;<=>?????????????????????t6789:;<=>?????????????????????.+($$%&'()*+,-.///////////////b#$%&'()*+,--..////////////////b1P1P2@2@30304 4 5566778899::;;<<==>>??L $$$*//////////////////////////$$$$$$$$$$$$$$$$$$$%&'()*+,-./$$$$$$$///////////////////////$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$5         TTTTTT q q q Q qQQQQQ qQQQ qQQQQQ qQ Q qQQQQQ qQQQ qQQQQQ qQ Q qQQQQQ qQQQ qQQQQQ qQ T T TT   TT T T T Q qQQQQQ qQQQ qQQQQQ qQ caacacacaaca cacaaca cccc cccbc aa ca d dd@PQR@OO?           "  3  3 "            #E-- title: Long Shot -- author: Altrix Studios -- desc: A bullet hell game where you can only shoot every 10 seconds -- script: lua -- input: keyboard -- saveid: LongShot_ld51 player={} intro=true introTimer=0 menu=false menuScr=0 menuSel=0 tutScreen=0 playing=false alive=false function BOOT() initBestiary() initWaves() initMovePaths() initPatterns() initBurstLibrary() sfx(12) highScores={} for i=1,10 do table.insert(highScores,pmem(i)) end end function TIC() if playing then if alive then gameCtrl() scoreTimer() else gameEndCtrl() end moveEntities() gameDraw() processWave() elseif menu then menuCtrl() menuDraw() elseif intro then introDraw() end end function startGame() player={x=120,y=96,c=0,l=5,inv=0,f=0} enemies={} pBullets={} eBullets={} spawnStarfield() menu=false playing=true alive=true endTimer=0 st=0 score=0 nextWave() music(1) end function gameCtrl() player.f=player.f+1 if player.inv>0 then player.inv=player.inv-1 end if btn(2) then if player.x>67 then player.x=player.x-1 end elseif btn(3) then if player.x<172 then player.x=player.x+1 end end if btn(0) then if player.y>8 then player.y=player.y-1 end elseif btn(1) then if player.y<128 then player.y=player.y+1 end end if player.c<600 then player.c=player.c+1 if player.c==600 then sfx(1,nil,nil,3) end end if btnp(4) and player.c==600 then --fire sfx(0,nil,nil,3) for i=1,-1,-0.2 do table.insert(pBullets, {x=player.x,y=player.y-8,vx=i,vy=-2,f=0}) end player.c=0 end end function gameDraw() cls() --player if alive then local ps=1 if player.inv>0 and player.inv%30>15 then ps=49 end spr(ps,player.x-8,player.y-8,0,1,0,0,2,2) if player.f%16>8 then spr(34,player.x-4,player.y+8,0) else spr(33,player.x-4,player.y+8,0) end end --player bullets for k,v in pairs(pBullets) do if v.f%16>8 then spr(32,v.x-4,v.y-4,0) else spr(16,v.x-4,v.y-4,0) end end --enemies for k,v in pairs(enemies) do spr(v.s,v.x-4*v.w,v.y-4*v.h,0,1,0,0,v.w,v.h) if v.s~=5 and v.s~=41 then local fl=35 if v.f%16>8 then fl=36 end if sp==9 then spr(fl,v.x,player.y-18,0) spr(fl,v.x-8,player.y-16,0) else spr(fl,v.x-4,v.y-16,0) end end end --enemy bullets for k,v in pairs(eBullets) do if v.f%16>8 then spr(31,v.x-4,v.y-4,0) else spr(15,v.x-4,v.y-4,0) end end --bursts renderParticles() --UI rect(180,0,240,136,0) rect(0,0,59,136,0) line(180,0,180,136,12) line(181,0,181,136,13) line(182,0,182,136,14) line(183,0,183,136,15) line(59,0,59,136,12) line(58,0,58,136,13) line(57,0,57,136,14) line(56,0,56,136,15) print("Score:",185,2,12) print(score,185,10,12,false,1,true) print("Charge:",185,25,12) if player.c==600 then spr(224,185,33,0,1,0,0,7,1) else local len=math.floor(player.c/600*52) spr(240,185,33,0,1,0,0,7,1) rect(186+len,34,50-len,6,0) end print("Lives:",185,50,12) for i=0,player.l-1 do local y=math.floor(i/5) local x=i%5 spr(48,185+10*x,58+10*y) end if endTimer>100 then local c=15 if endTimer>124 then c=12 elseif endTimer>116 then c=13 elseif endTimer>108 then c=14 end printCenter("GAME OVER",60,c,false) end if endTimer>180 then local c=15 if endTimer>204 then c=12 elseif endTimer>196 then c=13 elseif endTimer>188 then c=14 end printCenter("Z: Retry",75,c,true) printCenter("X: Back to Menu",83,c,true) end end function gameEndCtrl() if endTimer==240 then if btnp(4) then startGame() elseif btnp(5) then goToMenu() end else endTimer=endTimer+1 if btnp(4) or btnp(5) then endTimer=240 end end end function menuCtrl() menuTimer=menuTimer+1 if menuScr==0 then if btnp(0) and menuSel>0 then menuSel=menuSel-1 sfx(13,nil,nil,3) elseif btnp(1) and menuSel<2 then menuSel=menuSel+1 sfx(13,nil,nil,3) end if btnp(4) then if menuSel==0 then startGame() else menuScr=menuSel sfx(14,nil,nil,3) end end elseif menuScr==1 then if btnp(2) and tutScreen==1 then tutScreen=0 sfx(13,nil,nil,3) elseif btnp(3) and tutScreen==0 then tutScreen=1 sfx(13,nil,nil,3) elseif btnp(5) then menuScr=0 tutScreen=0 sfx(14,nil,nil,3) end elseif menuScr==2 then if btnp(5) then menuScr=0 sfx(14,nil,nil,3) end end end function menuDraw() cls() renderParticles() if menuScr==0 then spr(256,41,15,0,1,0,0,10,3) spr(320,126,15,0,1,0,0,9,3) local b1,b2=15,16 if menuTimer%16>8 then b1,b2=31,32 end spr(b1,63,23,0) spr(b2,168,23,0) printCenter("Start Game",60,12,true) printCenter("Instructions",70,12,true) printCenter("High Scores",80,12,true) spr(208,86,59+menuSel*10,0) spr(209,145,59+menuSel*10,0) print("Created in 72 hours for Ludum Dare 51. (c) Altrix Studios 2022",1,130,12,false,1,true) elseif menuScr==1 then printCenter("X: Back",130,12,true) if tutScreen==0 then printCenter("You're testing an experimental space fighter.",10,12,true) printCenter("Its weapon is powerful, but prone to overheating.",18,12,true) printCenter("You can only fire it once every 10 seconds.",26,12,true) printCenter("Dodge the endless enemy fire while you",34,12,true) printCenter("wait for your weapon to recharge.",42,12,true) printCenter("How long can you survive for?",50,12,true) spr(1,112,80,0,1,0,0,2,2) local j=33 if menuTimer%16>8 then j=34 end spr(j,116,96,0) print("Next >",210,130,12,false,1,true) elseif tutScreen==1 then print("< Back",1,130,12,false,1,true) printCenter("Use the arrow keys to move, and Z to shoot",10,12,true) printCenter("once the Charge gauge is completely full.",18,12,true) printCenter("Only your cockpit (the light blue portion",34,12,true) printCenter("of your fighter) is vulnerable to enemy attacks.",42,12,true) printCenter("Graze against enemy bullets to earn bonus points!",50,12,true) spr(224,65,84,0,1,0,0,7,1) local s=1 if menuTimer%40>20 then s=51 end spr(s,152,80,0,1,0,0,2,2) local j=33 if menuTimer%16>8 then j=34 end spr(j,156,96,0) end elseif menuScr==2 then printCenter("HIGH SCORES",5,12,false) for k,v in pairs(highScores) do local p,s=formatScore(v) print(k..")",92,22+8*k,12,false,1,true) print(p,107,22+8*k,13,false,1,true) print(s,107+4*string.len(p),22+8*k,12,false,1,true) end printCenter("X: Back",130,12,true) end end function introDraw() cls() introTimer=introTimer+1 local l,tc,ic=0,0,introTimer if (ic>=10 and ic<20) or (ic>=240 and ic<250) then l=222 tc=15 elseif (ic>=20 and ic<30) or (ic>=230 and ic<240) then l=220 tc=14 elseif (ic>=30 and ic<40) or (ic>=220 and ic<230) then l=218 tc=13 elseif ic>=40 and ic<220 then l=216 tc=12 end if ic==300 then goToMenu() end if l>0 then spr(l,112,44,0,1,0,0,2,3) end print("ALTRIX",108,71,tc,false,1,true) end function moveEntities() for k,v in pairs(enemies) do moveEntity(v) v.cd=v.cd-1 v.f=v.f+1 if v.cd==0 then enemyShoot(v) end end for k,v in pairs(pBullets) do moveEntity(v) v.f=v.f+1 if v.y<-8 then table.remove(pBullets,k) else --collision detection local i=isBulletColliding(v) if i~=nil then local enemy=enemies[i] enemy.hp=enemy.hp-1 table.remove(pBullets,k) if enemy.hp==0 then addPoints(enemy.sc) if enemy.sc>10000 then sfx(3) spawnBurst(enemy.x,enemy.y,2) else sfx(2) spawnBurst(enemy.x,enemy.y,1) end table.remove(enemies,i) end end end end --player hitbox local pCol={ xa=player.x-1, xb=player.x, ya=player.y-7, yb=player.y-4 } for k,v in pairs(eBullets) do moveEntity(v) v.f=v.f+1 if v.x<50 or v.x>190 or v.y<-8 or v.y>144 then table.remove(eBullets,k) elseif alive then --basic collision detection local bCol={ {x=v.x-2,y=v.y-2}, {x=v.x-2,y=v.y+1}, {x=v.x+1,y=v.y-2}, {x=v.x+1,y=v.y+1} } for j,u in pairs(bCol) do if (u.x>player.x-2 and u.xplayer.y-8 and u.xplayer.x-6 and u.xplayer.x+4 and u.xpCol.xa and u.xpCol.ya and u.y=u.x and eCol.ya<=u.y and eCol.yb>=u.y then return k end end end end function moveEntity(e) if e.mp~=nil and e.mp>0 then local mp=movePaths[e.mp] e.x=e.x+mp.vx e.y=e.y+mp.vy e.mf=e.mf+1 if mp.f~=nil and e.mf>mp.f then e.mf=0 if mp.next~=nil then e.mp=mp.next else e.mp=0 end end else e.x=e.x+e.vx e.y=e.y+e.vy end end function enemyShoot(e) local pt=patterns[e.pt] for k,v in pairs(pt) do --get angle of shot local r=v.r[1] if v.r[2]~=nil then r=math.random(v.r[1],v.r[2]) end local rot=math.rad(r) if r==-1 then --get angle between enemy and player rot=-math.atan2(e.x-player.x,e.y-player.y) end local vel=v.v if v.v2~=nil then vel=math.random(v.v*1000,v.v2*1000)/1000 end local vx=math.sin(rot)*vel local vy=-math.cos(rot)*vel table.insert(eBullets,{ x=e.x+(v.ox or 0), y=e.y+(v.oy or 0), vx=vx, vy=vy, f=0 }) --set cooldown local p=pt[1] if p.s~=nil then e.ps=e.ps+1 if e.ps==p.s then e.cd=p.cd e.ps=0 else e.cd=p.sf end else e.cd=p.cd end end end function scoreTimer() st=st+1 if st==60 then st=0 addPoints(1000) end end function processWave() waveTimer=waveTimer-1 if waveTimer==0 and alive then nextWave() end end function nextWave() local r=math.random(1,#waves) local nw=waves[r] waveTimer=nw[1] for i=2,#nw do local e=nw[i] local mp=e.mp[math.random(1,#e.mp)] local path=movePaths[mp] local x=math.random(e.x[1],e.x[2]) local y=math.random(e.y[1],e.y[2]) local b=bestiary[e.b] local pt=patterns[b.pt] for j=1,e.r[3] do table.insert(enemies,{ s=b.s, x=x, y=y, w=b.w, h=b.h, f=0, mf=0, mp=mp, hp=b.hp, pt=b.pt, cd=pt[1].cd, ps=0, sc=b.sc }) x=x+e.r[1] y=y+e.r[2] end end end function initBestiary() bestiary={ {s=3,w=2,h=2,pt=1,hp=1,sc=1000}, {s=9,w=2,h=2,pt=2,hp=1,sc=2000}, {s=11,w=2,h=2,pt=3,hp=1,sc=1500}, {s=5,w=4,h=4,pt=4,hp=10,sc=25000}, {s=41,w=4,h=4,pt=5,hp=10,sc=41000}, } end function initWaves() waves={ {400, {b=1,x={50,50},y={20,40},r={-32,0,8},mp={1,5,7}} }, {400, {b=1,x={190,190},y={20,40},r={32,0,8},mp={2,6,9}} }, {400, {b=1,x={50,50},y={32,48},r={-32,0,8},mp={1,3,7}} }, {400, {b=1,x={190,190},y={32,48},r={32,0,8},mp={2,4,9}} }, {320, {b=2,x={120,120},y={-10,-10},r={0,0,1},mp={11}}, {b=1,x={108,108},y={-30,-30},r={-12,-20,3},mp={11}}, {b=1,x={132,132},y={-30,-30},r={12,-20,3},mp={11}} }, {420, {b=2,x={70,90},y={-10,-10},r={0,-50,4},mp={11}}, {b=2,x={150,170},y={-10,-10},r={0,-50,4},mp={11}} }, {450, {b=4,x={40,40},y={24,50},r={0,0,1},mp={12,13,14}} }, {450, {b=4,x={200,200},y={24,50},r={0,0,1},mp={15,16,17}} }, {275, {b=3,x={120,120},y={-10,-10},r={0,0,1},mp={11}}, {b=1,x={108,108},y={-30,-30},r={-12,-20,3},mp={11}}, {b=1,x={132,132},y={-30,-30},r={12,-20,3},mp={11}} }, {400, {b=3,x={50,50},y={20,40},r={-32,0,8},mp={1,5,7}} }, {400, {b=3,x={190,190},y={20,40},r={32,0,8},mp={2,6,9}} }, {450, {b=5,x={40,40},y={24,50},r={0,0,1},mp={12,13,14}} }, {450, {b=5,x={200,200},y={24,50},r={0,0,1},mp={15,16,17}} }, {400, {b=1,x={50,50},y={80,130},r={-24,24,8},mp={18}} }, {400, {b=1,x={50,50},y={8,48},r={-24,-24,8},mp={19}} }, {400, {b=1,x={190,190},y={80,130},r={24,24,8},mp={20}} }, {400, {b=1,x={190,190},y={8,48},r={24,-24,8},mp={21}} }, {270, {b=1,x={70,70},y={-10,-10},r={20,0,6},mp={22}} }, {300, {b=1,x={70,70},y={-10,-10},r={20,-8,6},mp={22}} }, {300, {b=1,x={170,170},y={-10,-10},r={-20,-8,6},mp={22}} }, {270, {b=3,x={70,70},y={-10,-10},r={20,0,6},mp={22}} }, {300, {b=3,x={70,70},y={-10,-10},r={20,-8,6},mp={22}} }, {300, {b=3,x={170,170},y={-10,-10},r={-20,-8,6},mp={22}} }, {270, {b=2,x={70,70},y={-10,-10},r={0,0,1},mp={22}}, {b=1,x={90,90},y={-10,-10},r={20,0,4},mp={22}}, {b=2,x={170,170},y={-10,-10},r={0,0,1},mp={22}} }, {270, {b=3,x={70,70},y={-10,-10},r={0,0,1},mp={22}}, {b=1,x={90,90},y={-10,-10},r={20,0,4},mp={22}}, {b=3,x={170,170},y={-10,-10},r={0,0,1},mp={22}} }, {400, {b=1,x={50,50},y={10,24},r={-32,0,8},mp={1}}, {b=1,x={190,190},y={40,60},r={32,0,8},mp={2}} }, } end function initPatterns() patterns={ {--pt1 {r={160,200},v=0.75,cd=30,oy=8} }, {--pt2 {r={150,180},v=0.7,cd=60,s=3,sf=12,ox=4}, {r={180,210},v=0.7,ox=-4} }, {--pt3 {r={-1},v=0.6,cd=45,oy=8} }, {--pt4 {r={340,380},v=0.5,cd=12,oy=-12}, {r={25,65},v=0.5,oy=-9,ox=9}, {r={70,110},v=0.5,ox=12}, {r={115,155},v=0.5,oy=9,ox=9}, {r={160,200},v=0.5,oy=12}, {r={205,245},v=0.5,oy=9,ox=-9}, {r={250,290},v=0.5,ox=-12}, {r={295,335},v=0.5,oy=-9,ox=-9} }, {--pt5 {r={0,360},v=0.3,v2=0.5,cd=10}, {r={0,360},v=0.3,v2=0.5}, {r={0,360},v=0.3,v2=0.5}, {r={0,360},v=0.3,v2=0.5}, {r={0,360},v=0.3,v2=0.5}, {r={0,360},v=0.3,v2=0.5}, {r={0,360},v=0.3,v2=0.5}, {r={0,360},v=0.3,v2=0.5} } } end function initMovePaths() movePaths={ {vx=1,vy=0},--1 {vx=-1,vy=0}, {vx=0.8,vy=-0.1}, {vx=-0.8,vy=-0.1}, {vx=0.8,vy=0.1},--5 {vx=-0.8,vy=0.1}, {vx=0.5,vy=0.3,f=45,next=8}, {vx=0.5,vy=-0.3,f=45,next=7}, {vx=-0.5,vy=0.3,f=45,next=10}, {vx=-0.5,vy=-0.3,f=45,next=9},--10 {vx=0,vy=1}, {vx=0.4,vy=0}, {vx=0.4,vy=0.05}, {vx=0.4,vy=-0.05}, {vx=-0.4,vy=0},--15 {vx=-0.4,vy=0.05}, {vx=-0.4,vy=-0.05}, {vx=0.5,vy=-0.5}, {vx=0.5,vy=0.5}, {vx=-0.5,vy=-0.5},--20 {vx=-0.5,vy=0.5}, {vx=0,vy=0.75}, } end function initBurstLibrary() burstLibrary={ --small explosion {n=24,r={0,359},v={0.7,1},c={3,4},pl={25,40}}, --big explosion {n=36,r={0,359},v={0.8,1.2},c={3,4},pl={32,48}}, --stars {n=64,r={360,360},v={0.1,0.5},c={12,13,14,15}, pl={99999,99999}} } end function goToMenu() spawnStarfield() intro=false playing=false menu=true menuTimer=0 music(0) end function spawnStarfield() --clear existing starfield particles={} --create new one local s=burstLibrary[3] for i=1,s.n do local x=math.random(0,239) local y=math.random(0,135) spawnStar(x,y) end end function spawnStar(x,y) local v=math.random(10,50)/100 local c={15} if v>=0.4 then c={12,13,14,15} elseif v>=0.3 then c={13,14,15} elseif v>=0.2 then c={14,15} end table.insert(particles,{ x=x, y=y, vx=0, vy=v, l=9999, sl=9999, c=c }) end function spawnParticle(obj,x,y) local r=math.random(obj.r[1],obj.r[2]) local l=math.random(obj.pl[1],obj.pl[2]) local v=math.random(obj.v[1]*10,obj.v[2]*10)/10 local c=obj.c[math.random(#obj.c)] --calculate momentum local rot=math.rad(r) local vx=math.sin(rot)*v local vy=-math.cos(rot)*v table.insert(particles,{ x=x, y=y, vx=vx, vy=vy, l=l, sl=l, c=getPartColors(c) }) end function spawnBurst(x,y,i) local b=burstLibrary[i] for j=1,b.n do spawnParticle(b,x,y) end end function spawnEmitter(x,y,i) local p=emitterLibrary[i] table.insert(emitters,{ x=x, y=y, l=p.l, i=p.i, r=p.r, v=p.v, c=p.c, pl=p.pl, vx=p.vx or 0, vy=p.vy or 0 }) end function getPartColors(c) if c==1 then return {1} elseif c==2 then return {2,1} elseif c==3 then return {3,2,1} elseif c==4 then return {4,3,2,1} elseif c==5 then return {5,6,7} elseif c==6 then return {6,7} elseif c==7 then return {7} elseif c==8 then return {8} elseif c==9 then return {9,8} elseif c==10 then return {10,9,8} elseif c==11 then return {11,10,9,8} elseif c==12 then return {12,13,14,15} elseif c==13 then return {13,14,15} elseif c==14 then return {14,15} elseif c==15 then return {15} end end function getPartColor(obj) local l=obj.l/obj.sl local c=0 for k,v in pairs(obj.c) do if 1/k > l then c=v end end return c end function renderParticles() for k,v in pairs(particles) do moveEntity(v) v.l=v.l-1 if v.l==0 then table.remove(particles,k) elseif v.sl==9999 and v.y>137 then --star table.remove(particles,k) local x=math.random(0,239) spawnStar(x,0) else pix(v.x,v.y,getPartColor(v)) end end end function printCenter(txt,y,clr,small) local w=print(txt,0,-6,0,false,1,small) print(txt,(240-w)/2,y,clr,false,1,small) end function saveHighScore() table.insert(highScores,score) table.sort(highScores, function(a,b) return a>b end) table.remove(highScores,11) for i=1,10 do pmem(i,highScores[i]) end end function addPoints(s) local oldScore=score score=score+s --add bonus lives local blt={ 1000000, 2500000, 5000000, 10000000, 41000000 } for k,v in pairs(blt) do if oldScore=v then player.l=player.l+1 end end end function formatScore(s) local pad=string.sub("000000000",string.len(s)) return pad,s end