-- Archatrep for Ludum Dare by GlowingSnail, 19/04/2015-- See http://ludumdare.com/compo/ludum-dare-32/?action=preview&uid=36700-- Coordinates:---- +------->-- | x-- |-- |y-- v---- ^ 270°-- |-- 180° | 0°-- <----+---->-- |-- |-- v 90°-- Globalstau=2*math.piFonts={}Pics={}Sounds={}Level={}HeroIndex=-1TX,TY=0,0-- Tile Size in PixelZoo={}Bullets={}GravGens={}InkClouds={}GameOver=falseShowGravField=falsePaused=false-- Localslocal FullScreen=false-- Change to true to start directly in fullscreen, without having to press Flocal ScreenShotting=false-- Make the game go slower, make right click aim at a given height, and fix window size to 548x548local function CoinFlip() return math.random()>0.5endlocal function sign(x) if x>0 then return 1 elseif x<0 then return -1 else return 0 endendlocal function InvertAngle(r) local r=(r+tau/4)%tau if r>tau/2 then return (7*tau/4-r)%tau else return (7*tau/4-r)%tau endendlocal function Rotate(x,y,angle) return x*math.cos(angle)-y*math.sin(angle), x*math.sin(angle)+y*math.cos(angle)endlocal function DrawCenteredText(font,text,x,y) love.graphics.setFont(Fonts[48]) love.graphics.print(text,x-font:getWidth(text)/2,y-font:getLineHeight()/2)endlocal function NewAnimal(kind,x,y,size) local pic=Pics[kind] assert(pic,"Cannot find graphic for \""..kind.."\"") local sx,sy=pic:getDimensions() local scale=math.min(size/sx,size/sy) return table.insert(Zoo,{kind=kind,pic=pic,x=x,y=y,r=math.random()*tau,size=size,scale=scale,ox=sx/2,oy=sy/2,dx=0,dy=0,speed=0})endlocal function NewBullet(kind,x,y,dx,dy,ttl) local pic=Pics[kind] assert(pic,"Cannot find graphic for \""..kind.."\"") local sx,sy=pic:getDimensions() return table.insert(Bullets,{kind=kind,pic=pic,x=x,y=y,size=size,scale,ox=sx/2,oy=sy/2,dx=dx or 0,dy=dy or 0,ttl=ttl})endlocal function UpdateScreenMode() if FullScreen then -- Fullscreen local AvailableFullScreenModes=love.window.getFullscreenModes() table.sort(AvailableFullScreenModes,function(a,b) return a.width*a.height>b.width*b.height end) local Biggest=AvailableFullScreenModes[1] love.window.setMode(Biggest.width,Biggest.height,{fullscreen=true,fullscreentype=(false and "desktop" or "normal"),}) else -- Windowed local dsx,dsy=love.window.getDesktopDimensions() if ScreenShotting then love.window.setMode(548,548,{resizable=false}) else love.window.setMode(dsx*4/5,dsy*4/5,{resizable=true, minwidth=200, minheight=200}) end endendlocal function InitBubbling() Bubbles=love.graphics.newParticleSystem(Pics.bubble,100)-- Second param: Max particle count Bubbles:setColors(255,255,255,255, 255,255,255,0)-- Up to eight r,g,b,a that particle will go through Bubbles:setSizes(0.2,1.5)-- A series of up to eight size Bubbles:setSizeVariation(0) Bubbles:setPosition(Zoo[HeroIndex].x,Zoo[HeroIndex].y)-- Position of emitter Bubbles:setAreaSpread("normal",10,10)-- The area particles are created in Bubbles:setDirection(0)-- Doesn't matter with a 360° spread Bubbles:setSpread(tau)-- Spread their direction all around Bubbles:setEmissionRate(0)-- How many particles per second Bubbles:setEmitterLifetime(-1)-- Emit forever Bubbles:setParticleLifetime(1,3)-- min and max in seconds Bubbles:setSpeed(2,5)-- Min and max speed Bubbles:setLinearAcceleration(0,-8,0,-8)-- xmin,ymin,xmax,ymax Bubbles:setInsertMode("random") Bubbles:setRotation(0,0)-- Min and max image rotation of particle in radians Bubbles:start() --echo("Bubbling activated")endlocal function InitBleeding() BloodBlots=love.graphics.newParticleSystem(Pics.droplet,4321)-- Second param: Max particle count BloodBlots:setColors(255,0,0,255, 91,29,29,255, 91,29,29,0)-- Up to eight r,g,b,a that particle will go through BloodBlots:setSizes(1,0.2)-- A series of up to eight size BloodBlots:setSizeVariation(1) BloodBlots:setPosition(0,0)-- Position of emitter BloodBlots:setAreaSpread("normal",7,7)-- The area particles are created in BloodBlots:setDirection(0)-- Doesn't matter with a 360° spread BloodBlots:setSpread(tau)-- Spread their direction all around BloodBlots:setEmissionRate(0)-- How many particles per second BloodBlots:setEmitterLifetime(-1)-- Emit forever BloodBlots:setParticleLifetime(2,4)-- min and max in seconds BloodBlots:setSpeed(5,50)-- Min and max speed BloodBlots:setLinearAcceleration(0,40,0,40)-- xmin,ymin,xmax,ymax BloodBlots:setInsertMode("random") BloodBlots:setRotation(0,tau)-- Min and max image rotation of particle in radians BloodBlots:start() --echo("Bleeding activated")endlocal function InitInking() InkBlots=love.graphics.newParticleSystem(Pics.droplet,4321)-- Second param: Max particle count InkBlots:setColors(0,0,64,255, 0,62,55,255, 26,62,0,0)-- Up to eight r,g,b,a that particle will go through InkBlots:setSizes(0.2,1)-- A series of up to eight size InkBlots:setSizeVariation(1) InkBlots:setPosition(0,0)-- Position of emitter InkBlots:setAreaSpread("normal",8,8)-- The area particles are created in InkBlots:setDirection(0)-- Doesn't matter with a 360° spread InkBlots:setSpread(tau)-- Spread their direction all around InkBlots:setEmissionRate(0)-- How many particles per second InkBlots:setEmitterLifetime(-1)-- Emit forever InkBlots:setParticleLifetime(12,12)-- min and max in seconds InkBlots:setSpeed(0,200)-- Min and max speed InkBlots:setLinearDamping(0.9,2.2)-- min,max InkBlots:setInsertMode("top") InkBlots:setRotation(0,tau)-- Min and max image rotation of particle in radians InkBlots:start() --echo("Inking activated")endlocal function InitSpinnyShards() SpinnyShards=love.graphics.newParticleSystem(Pics.spinnyshard,50)-- Second param: Max particle count SpinnyShards:setColors(255,255,255,255, 255,255,255,0)-- Up to eight r,g,b,a that particle will go through SpinnyShards:setSizes(0.12,0.12) SpinnyShards:setPosition(0,0)-- Position of emitter SpinnyShards:setAreaSpread("normal",20,20)-- The area particles are created in SpinnyShards:setDirection(0)-- Doesn't matter with a 360° spread SpinnyShards:setSpread(tau)-- Spread their direction all around SpinnyShards:setEmissionRate(0)-- How many particles per second SpinnyShards:setEmitterLifetime(-1)-- Emit forever SpinnyShards:setParticleLifetime(4,8)-- min and max in seconds SpinnyShards:setSpeed(90,500)-- Min and max speed SpinnyShards:setInsertMode("random") SpinnyShards:setRotation(0,tau)-- Min and max image rotation of particle in radians SpinnyShards:setSpin(-3*tau,-tau/4) SpinnyShards:start()endlocal function InitSquidShards() SquidShards=love.graphics.newParticleSystem(Pics.squidshard,50)-- Second param: Max particle count SquidShards:setColors(255,255,255,255, 255,255,255,0)-- Up to eight r,g,b,a that particle will go through SquidShards:setSizes(0.07,0.13) SquidShards:setPosition(0,0)-- Position of emitter SquidShards:setAreaSpread("normal",20,20)-- The area particles are created in SquidShards:setDirection(0)-- Doesn't matter with a 360° spread SquidShards:setSpread(tau)-- Spread their direction all around SquidShards:setEmissionRate(0)-- How many particles per second SquidShards:setEmitterLifetime(-1)-- Emit forever SquidShards:setParticleLifetime(3,6)-- min and max in seconds SquidShards:setSpeed(70,400)-- Min and max speed SquidShards:setInsertMode("random") SquidShards:setRotation(0,tau)-- Min and max image rotation of particle in radians SquidShards:setSpin(-2*tau,2*tau) SquidShards:start()endlocal function InitCrabShards() CrabShards=love.graphics.newParticleSystem(Pics.crabshard,50)-- Second param: Max particle count CrabShards:setColors(255,255,255,255, 255,255,255,0)-- Up to eight r,g,b,a that particle will go through CrabShards:setSizes(0.14,0.14) CrabShards:setPosition(0,0)-- Position of emitter CrabShards:setAreaSpread("normal",20,20)-- The area particles are created in CrabShards:setDirection(0)-- Doesn't matter with a 360° spread CrabShards:setSpread(tau)-- Spread their direction all around CrabShards:setEmissionRate(0)-- How many particles per second CrabShards:setEmitterLifetime(-1)-- Emit forever CrabShards:setParticleLifetime(4,8)-- min and max in seconds CrabShards:setSpeed(90,500)-- Min and max speed CrabShards:setInsertMode("random") CrabShards:setRotation(0,tau)-- Min and max image rotation of particle in radians CrabShards:setSpin(-3*tau,-tau/4) CrabShards:start()endlocal function Emit(particles,x,y,quantity) if love.getVersion then particles:moveTo(x,y) else particles:setPosition(x,y) end particles:emit(quantity)endlocal function InitWaving() Waves=love.graphics.newParticleSystem(Pics.wave,4321) Waves:setColors(255,255,255,255, 255,255,255,0)-- Up to eight r,g,b,a that particle will go through Waves:setSizes(0.2,3)-- A series of up to eight size Waves:setSizeVariation(0) Waves:setPosition(0,0)-- Position of emitter Waves:setAreaSpread("normal",1,1)-- The area particles are created in Waves:setDirection(0)-- Doesn't matter with a 360° spread Waves:setSpread(tau)-- Spread their direction all around Waves:setRelativeRotation(true) Waves:setEmissionRate(0)-- How many particles per second Waves:setEmitterLifetime(-1)-- Emit forever Waves:setParticleLifetime(2,3)-- min and max in seconds Waves:setSpeed(70,90)-- Min and max speed Waves:setInsertMode("top") Waves:setRotation(0,0)-- Min and max image rotation of particle in radians Waves:start() --echo("Waving activated")endlocal function GetLocalGravity(x,y) local lgx,lgy=0,0 for _,gg in ipairs(GravGens) do if gg.ttl~=-1 and (x-gg.x)^2+(y-gg.y)^2>20^2 then-- ignore brand new fields, and ignore self-centered fields local dx,dy=gg.x-x,gg.y-y local nrm=math.sqrt(dx^2+dy^2) --lgx,lgy=lgx+dx/nrm*gg.g,lgy+dy/nrm*gg.g -- SE type gravity --lgx,lgy=lgx+gg.g*dx/nrm^2,lgy+gg.g*dy/nrm^2 -- Linearly decreasing gravity lgx,lgy=lgx+gg.g*dx/nrm^3,lgy+gg.g*dy/nrm^3 -- Inverse square low, like real gravity end end return lgx,lgyendlocal function IsSolidTileCoord(i,j) if i<1 or i>Level.u or j<1 or j>Level.v then return true else return Level[i][j] endendlocal function IsSolidWorldCoord(x,y) return IsSolidTileCoord(math.floor(x/TX),math.floor(y/TY))endlocal function GetTileIndex(i,j) local function GetSN(n,i,j) return IsSolidTileCoord(i,j) and n or 0 end return GetSN(1,i,j)*(1+GetSN(2,i+1,j)+GetSN(4,i-1,j)+GetSN(8,i,j+1)+GetSN(16,i,j-1))endlocal function CreateLevelSpriteBatch() -- Create the quads over the rock texture local quads={} for n=0,31 do quads[n]=love.graphics.newQuad(n*TX,0,TX,TY,TX*32,TY) end -- Create the level sprite batch local bx,by=50,50-- Number of extra tiles at the border outside level local LevelBatch=love.graphics.newSpriteBatch(Pics.rocks,(Level.u+2*bx)*(Level.v+2*by)-(Level.u*Level.v-Level.WallCount)) for x=1-bx,Level.u+bx do for y=1-by,Level.v+by do local n=GetTileIndex(x,y) if n>0 then LevelBatch:add(quads[n],x*TX,y*TY,0,1.01,1.01) end end end Level.Batch=LevelBatchendlocal function LoadLevel() local imgdata=love.image.newImageData("level.png") local WallCount=0 Level={u=imgdata:getWidth(),v=imgdata:getHeight()} for x=1,imgdata:getWidth() do Level[x]={} for y=1,imgdata:getHeight() do local r,g,b,a = imgdata:getPixel(x-1,y-1) Level[x][y]=(r+g+b==0) if Level[x][y] then WallCount=WallCount+1 end if r==255 and g==0 then if b==10 then NewAnimal("spinny",TX*(x+0.5),TY*(y+0.5),80+math.floor(20*math.random())) elseif b==20 then NewAnimal("squid",TX*(x+0.5),TY*(y+0.5),70+math.floor(50*math.random())) elseif b==30 then NewAnimal("jelly",TX*(x+0.5),TY*(y+0.5),60+math.floor(40*math.random())) elseif b==40 then NewAnimal("crab",TX*(x+0.5),TY*(y+0.5),110) end end if r==0 and g==255 and b==0 then Zoo[HeroIndex].x=TX*(x+0.5) Zoo[HeroIndex].y=TY*(y+0.5) end end end Level.WallCount=WallCount CreateLevelSpriteBatch()endlocal function LoadImages() love.graphics.setDefaultFilter("linear","linear",2) local LoadImg=love.graphics.newImage Pics={} Pics.hero=LoadImg("hero.png") Pics.spinny=LoadImg("spinny.png") Pics.squid=LoadImg("squid.png") Pics.jelly=LoadImg("jelly.png") Pics.crab=LoadImg("crab.png") Pics.bubble=LoadImg("bubble.png") Pics.droplet=LoadImg("droplet.png") Pics.spinnyshard=LoadImg("spinnyshard.png") Pics.squidshard=LoadImg("squidshard.png") Pics.crabshard=LoadImg("crabshard.png") Pics.bullet=LoadImg("shot.png") Pics.bulletexp={LoadImg("shotex1.png"),LoadImg("shotex2.png")} Pics.wave=LoadImg("wave.png") --- Load rocks Pics.rocks={} local earth=love.image.newImageData("earth.png") local sand=love.image.newImageData("sand.png") local msk=love.image.newImageData("r31.png") TX,TY=msk:getWidth(),msk:getHeight() local function IsWaterPixel(x,y) if x<0 or x>TX-1 or y<0 or y>TY-1 then return true else local r,g,b=msk:getPixel(x,y) return r+g+b>700 end end local function IsRockPixel(x,y) local r,g,b=msk:getPixel(math.max(0,math.min(TX-1,x)),math.max(0,math.min(TY-1,y))) return r+g+b<10 end local tiles=love.image.newImageData(TX*32,TY) for n=1,31,2 do msk=love.image.newImageData("r"..n..".png") local offset=n*TX for x=0,TX-1 do for y=0,TY-1 do local r,g,b=msk:getPixel(x,y) if r+g+b<10 then--black local inside=true for i=-1,1 do for j=-1,1 do if not IsRockPixel(x+i,y+j) then inside=false end end end if inside then local r,g,b=earth:getPixel(x,y) tiles:setPixel(offset+x,y,r,g,b,255)-- as rock texture else tiles:setPixel(offset+x,y,0,0,0,255)-- as rock border end elseif r+g+b>700 then-- white local r,g,b=sand:getPixel(x,y) tiles:setPixel(offset+x,y,r,g,b,0)-- transparent sand local border=false for i=-1,1 do for j=-1,1 do if not IsWaterPixel(x+i,y+j) then border=true end end end tiles:setPixel(offset+x,y,0,0,0,border and 255 or 0) else-- yellow local r,g,b=sand:getPixel(x,y) tiles:setPixel(offset+x,y,r,g,b,255)-- opaque sand end end end Pics.rocks=love.graphics.newImage(tiles) endendlocal function LoadSounds() love.audio.setDistanceModel("linear") local LoadSound=function(src,vol) local snd=love.audio.newSource(src,"static") snd:setAttenuationDistances(500,1000) if vol then snd:setVolume(vol) end return snd end Sounds={} Sounds.GravGen={LoadSound("gravgen1.wav"),LoadSound("gravgen2.wav"),LoadSound("gravgen3.wav"), LoadSound("gravgen1.wav"),LoadSound("gravgen2.wav"),LoadSound("gravgen3.wav")} Sounds.Plick={LoadSound("plick1.wav"),LoadSound("plick2.wav")} Sounds.Splode={LoadSound("claplow1.wav"),LoadSound("claplow2.wav"),LoadSound("tube.wav")} Sounds.Knell=LoadSound("knell.wav",0.1) Sounds.Knife=LoadSound("knife.wav",0.1)endlocal function IsOnScreen(x,y) local wx,wy=love.window.getWidth(),love.window.getHeight() local cx,cy=Zoo[HeroIndex].x,Zoo[HeroIndex].y return (x-cx)<wx/2 and (y-cy)<wy/2endlocal function PlaySoundAt(sound,x,y) sound:setPosition(x,y,0) love.audio.play(sound)endlocal function HasSight(oe,tx,ty,fov,dst) -- ao: angle, absolute, of line from e to target -- ar: angle, relative to the direction of e, of the line from e to target local ao=math.atan2(ty-oe.y,tx-oe.x) local ar=(ao-oe.r+4*tau)%tau if ar>tau/2 then ar=ar-tau end if fov and math.abs(ar)>fov/2 then return false end local d=math.sqrt((oe.x-tx)^2+(oe.y-ty)^2) if dst and d>dst then return false end for ds=0,d,math.min(TX,TY)/2 do -- Check points along the line, spaced by half a tile local x,y=oe.x+ds*math.cos(ao),oe.y+ds*math.sin(ao) -- Check if point is in a wall if IsSolidWorldCoord(x,y) then return false end -- Check if point is in an ink cloud for _,ic in ipairs(InkClouds) do if (x-ic.x)^2+(y-ic.y)^2<ic.r^2 then if not dst then InkBlocked=true end return false end end end return trueendlocal function WouldHitWallAt(e,d) local hs=math.ceil(e.size*0.3) for i=-hs,hs,hs do for j=-hs,hs,hs do if IsSolidWorldCoord(i+e.x+d*math.cos(e.r),j+e.y+d*math.sin(e.r)) then return true end end end return falseendfunction love.load(arguments) love.window.setTitle("Archatrep") UpdateScreenMode() Fonts[12] = love.graphics.newFont(12) Fonts[24] = love.graphics.newFont(24) Fonts[48] = love.graphics.newFont(48) love.mouse.setVisible(false) love.mouse.setGrabbed(false) love.keyboard.setKeyRepeat(true) LoadImages() LoadSounds() Zoo={} Bullets={} GravGens={} InkClouds={} NewAnimal("hero",3520,288,50) HeroIndex=#Zoo InitBubbling() InitBleeding() InitInking() InitSpinnyShards() InitSquidShards() InitCrabShards() InitWaving() LoadLevel()endfunction love.draw() --love.graphics.setBlendMode("additive") --love.graphics.setBlendMode("alpha") love.graphics.setBlendMode("alpha") love.graphics.origin() local wx,wy=love.window.getWidth(),love.window.getHeight() love.graphics.translate(wx/2-Zoo[HeroIndex].x,wy/2-Zoo[HeroIndex].y) -- Never seen, except far outside the level love.graphics.setBackgroundColor(190,170,80,255) -- Water for darkness=1,256,5 do local factor=65536/(Level.v+2)^2 local y1,y2=(darkness-5)^2/factor,darkness^2/factor love.graphics.setColor(0,128-darkness/2,256-darkness,255) love.graphics.polygon("fill",Zoo[HeroIndex].x-wx,y1,Zoo[HeroIndex].x+wx,y1,Zoo[HeroIndex].x+wx,y2,Zoo[HeroIndex].x-wx,y2) end -- Particles systems, hidden by level walls love.graphics.draw(BloodBlots) love.graphics.draw(SquidShards) love.graphics.draw(SpinnyShards) love.graphics.draw(CrabShards) -- Level love.graphics.setColor(255,255,255,255) love.graphics.draw(Level.Batch) -- Particles systems, drawn over walls love.graphics.draw(Bubbles) love.graphics.draw(Waves) -- Animals for ie=#Zoo,1,-1 do local e=Zoo[ie] if e.drawn then local xmouth,ymouth=10,15 love.graphics.setColor(0,0,0,255) love.graphics.setLineWidth(4) for xms=-1,1,2 do local ymouth=ymouth-1 love.graphics.line( e.x+ymouth*math.cos(e.r)-xmouth*xms*math.sin(e.r), e.y+ymouth*math.sin(e.r)+xmouth*xms*math.cos(e.r), e.x+math.max(e.drawn,ymouth)*math.cos(e.r)-xmouth*xms*math.sin(e.r), e.y+math.max(e.drawn,ymouth)*math.sin(e.r)+xmouth*xms*math.cos(e.r)) end love.graphics.setColor(152,26,26,255) love.graphics.setLineWidth(2) for xms=-1,1,2 do love.graphics.line( e.x+ymouth*math.cos(e.r)-xmouth*xms*math.sin(e.r), e.y+ymouth*math.sin(e.r)+xmouth*xms*math.cos(e.r), e.x+math.max(e.drawn,ymouth)*math.cos(e.r)-xmouth*xms*math.sin(e.r), e.y+math.max(e.drawn,ymouth)*math.sin(e.r)+xmouth*xms*math.cos(e.r)) end love.graphics.setColor(255,255,255,255) love.graphics.setLineWidth(1) end love.graphics.draw(e.pic,e.x,e.y,e.r+tau/4,e.scale,e.scale,e.ox,e.oy) end -- Bullets for _,b in ipairs(Bullets) do love.graphics.draw(b.pic,b.x,b.y,0,1,1,b.ox,b.oy) end -- Particles hiding everything love.graphics.draw(InkBlots) if ShowGravField then -- Fade screen to white a bit love.graphics.setColor(255,255,255,128) love.graphics.rectangle("fill",Zoo[HeroIndex].x-0.6*wx,Zoo[HeroIndex].y-0.6*wy,1.2*wx,1.2*wy) -- Draw the grav gen love.graphics.setColor(128,128,255,255)-- Blueish for _,gg in ipairs(GravGens) do love.graphics.circle("fill",gg.x,gg.y,2.5,16) end -- Draw the gravity field local GridSpacing=20 local VectorLength=30 for x=GridSpacing*(math.floor((Zoo[HeroIndex].x-wx/2)/GridSpacing)-1.5), GridSpacing*(math.floor((Zoo[HeroIndex].x+wx/2)/GridSpacing)+1.5),GridSpacing do for y=GridSpacing*(math.floor((Zoo[HeroIndex].y-wy/2)/GridSpacing)-1.5), GridSpacing*(math.floor((Zoo[HeroIndex].y+wy/2)/GridSpacing)+1.5),GridSpacing do local lgx,lgy=GetLocalGravity(x,y)--local gravity vector local ng=math.sqrt(lgx^2+lgy^2)--local gravity norm if ng>0.1 then-- Don't show neglible one if ng*VectorLength>40 then -- Too strong gravity to be shown -- Draw black dots love.graphics.setColor(0,0,64,255)-- Near black dots love.graphics.circle("fill",x,y,3.2,35) else -- Draw dots love.graphics.setColor(85,51,7,255)-- Dark Brown love.graphics.circle("fill",x,y,1.9,35) -- Draw vectors love.graphics.setColor(255,64,64,255)-- Red love.graphics.line(x,y,x+VectorLength*lgx,y+VectorLength*lgy) -- Draw the side wings of the vector arrow local ngx,ngy=lgx/ng,lgy/ng local a1x,a1y=Rotate(ngx,ngy,tau/10) local a2x,a2y=Rotate(ngx,ngy,-tau/10) local tipx,tipy=x+VectorLength*lgx,y+VectorLength*lgy love.graphics.line(tipx,tipy,tipx-a1x*5,tipy-a1y*5) love.graphics.line(tipx,tipy,tipx-a2x*5,tipy-a2y*5) end end end end -- Draw the ink clouds love.graphics.setColor(61,206,16,255) for _,ic in ipairs(InkClouds) do love.graphics.circle("line",ic.x,ic.y,ic.r,64) end end -- Instructions and other textual info love.graphics.origin() love.graphics.setFont(Fonts[12]) love.graphics.setColor(240,255,240,255) if InkBlocked then love.graphics.print("Blinded!",120,wy-46) end if Zoo[HeroIndex].HasEnemyInSight then love.graphics.print("Enemy in Sight!",4,wy-46) end love.graphics.print("Swim with WASD or arrow keys, fire with space, E or A key.",4,wy-30) love.graphics.print("Press F to toggle fullscreen, B for debug, P for pause, N for pic dump.",4,wy-14) --love.graphics.print("Hero position: x="..math.floor(0.5+Zoo[HeroIndex].x)..", y="..math.floor(0.5+Zoo[HeroIndex].y).." r:"..math.floor(0.5+Zoo[HeroIndex].r*360/tau).."°",4,wy-78) --love.graphics.print("Blood particle count: "..BloodBlots:getCount(),4,wy-94) love.graphics.setFont(Fonts[24]) --love.graphics.print("Score: "..Score,wx-89-Fonts[24]:getWidth(Score),5) if GameOver then local fading=math.min(9,(love.timer.getTime()-GameOver.since))/9 love.graphics.setColor(0,0,0,255*(1-(1-fading)^2)) love.graphics.rectangle("fill",0,0,wx,wy) love.graphics.setColor(255,50,50,255) DrawCenteredText(Fonts[48],"Game Over",wx/2,wy/2) end if #Zoo==1 then love.graphics.setColor(50,255,50,255) DrawCenteredText(Fonts[48],"You Won!",wx/2,wy/2) end if Paused then love.graphics.setColor(10,10,185,255) DrawCenteredText(Fonts[48],"Paused",wx/2,wy*0.3) endendfunction love.keypressed(key) if key=="f" then FullScreen=not FullScreen UpdateScreenMode() end if key=="n" then ScreenShotting=not ScreenShotting UpdateScreenMode() end if key=="p" or key=="pause" then Paused=not Paused end if false and key=="x" then --echo("Fire!!") local h=Zoo[HeroIndex] local dx,dy=math.cos(h.r),math.sin(h.r) NewBullet("bullet",h.x+50*dx,h.y+50*dy,h.dx+3*dx,h.dy+3*dy,30) end if key=="a" or key=="e" or key==" " then if not Zoo[HeroIndex].drawn and not Zoo[HeroIndex].carrying then Zoo[HeroIndex].drawn=-1 end if Zoo[HeroIndex].carrying then Zoo[HeroIndex].carrying.wriggle=true end end if key=="b" then ShowGravField=not ShowGravField end if GameOver then if love.timer.getTime()-GameOver.since>2 then GameOver=false love.load()-- Let's hope reloading everything at each death won't leak too much memory... end end if key=="escape" then if love.event.quit then love.event.quit() -- 0.8.0 else love.event.push('q') -- 0.7.2 end endendfunction DecayGravGens(dt) for igg,gg in pairs(GravGens) do gg.ttl=gg.ttl-dt if gg.ttl<0 then table.remove(GravGens,igg) end endendfunction DecayInkClouds(dt) for iic,ic in pairs(InkClouds) do ic.ttl=ic.ttl-dt if ic.ttl<=0 then table.remove(InkClouds) end endendfunction UpdateBullets(dt) for ib,b in pairs(Bullets) do b.ttl=b.ttl-dt-- Where ttl is TimeToLive lgx,lgy=GetLocalGravity(b.x,b.y) b.dx,b.dy=b.dx+lgx,b.dy+lgy b.x=b.x+b.dx b.y=b.y+b.dy if IsSolidWorldCoord(b.x,b.y) then b.ttl=math.min(0,b.ttl) b.dx=0 b.dy=0 end if b.ttl<=0 and b.ttl+dt>0 then PlaySoundAt(Sounds.Plick[2],b.x,b.y) end if b.ttl<-0.9 then table.remove(Bullets,ib) elseif b.ttl<-0.6 then b.pic=Pics.bulletexp[2] elseif b.ttl<-0.3 then b.pic=Pics.bulletexp[1] end endendfunction UpdateZoo(dt) -- For all animals for ie,e in ipairs(Zoo) do ---------------------------- -- Before position update -- ---------------------------- -- Default of constant values local maxspeed=10 local accel=1 local decel=3 if e.kind=="hero" then -- Player react to keyboard local wdx,wdy=0,0 if love.keyboard.isDown("up") or love.keyboard.isDown("w") then wdy=wdy-1 end if love.keyboard.isDown("down") or love.keyboard.isDown("s") then wdy=wdy+1 end if love.keyboard.isDown("left") or love.keyboard.isDown("a") then wdx=wdx-1 end if love.keyboard.isDown("right") or love.keyboard.isDown("d") then wdx=wdx+1 end e.dx=e.dx*0.95 e.dy=e.dy*0.95 e.dx=e.dx+accel*wdx e.dy=e.dy+accel*wdy if e.drawn then if e.drawn==-1 then e.drawn=100 PlaySoundAt(Sounds.Knife,e.x,e.y) local x,y=e.x+e.drawn*math.cos(e.r),e.y+e.drawn*math.sin(e.r) for ic,c in ipairs(Zoo) do if ic~=ie and (c.x-x)^2+(c.y-y)^2<=(e.size*0.6+18)^2 then if c.kind~="crab" then c.caught={c=e,d=math.sqrt((c.x-e.x)^2+(c.y-e.y)^2),a=math.atan2(c.y-e.y,c.x-e.x)-e.r,r=c.r-e.r} e.carrying=c break end end end end e.drawn=e.drawn-3 if e.drawn<=0 then e.drawn=nil end end if e.carrying then e.carrying.caught.d=math.max(0,e.carrying.caught.d-3) if e.carrying.kind=="jelly" and love.keyboard.isDown("a") or love.keyboard.isDown("e") or love.keyboard.isDown(" ") then e.carrying.wriggle=true end end elseif e.kind=="spinny" then maxspeed=5 -- Spin e.r=e.r-tau*dt/5 -- Brownian drift e.dx=e.dx+0.5*(2*math.random()-1) e.dy=e.dy+0.5*(2*math.random()-1) -- Attack if hero come close if HasSight(e,Zoo[HeroIndex].x,Zoo[HeroIndex].y,false,384) then e.attack=(e.attack or 0)+dt e.dx=0 e.dy=0 e.speed=0 e.r=e.r-4*e.attack/3*tau*dt/5 if e.attack>3 then local at=math.atan2(Zoo[HeroIndex].y-e.y,Zoo[HeroIndex].x-e.x) for a=at-tau/8,at+tau/8+tau/64,tau/12 do NewBullet("bullet",e.x,e.y,5*math.cos(a),5*math.sin(a),30) end e.attack=nil PlaySoundAt(Sounds.Plick[1],e.x,e.y) end else e.attack=nil end elseif e.kind=="squid" then -- Squid movement maxspeed=20 e.dx,e.dy=e.dx*0.9,e.dy*0.9 if e.dr then e.r=e.r+e.dr e.dr=e.dr*0.97 if e.dr<tau*dt/9 then e.dr=nil end end local free,ilx,ily=true,math.floor(e.x/TX),math.floor(e.y/TY) for i=ilx-1,ilx+1 do for j=ily-1,ily+1 do if IsSolidTileCoord(i,j) then free=false end end end if not free then e.dr=e.dr or (CoinFlip() and tau or -tau)*dt/(2+math.random()) end e.pulse=((e.pulse or 2*math.random())+dt)%2 if e.pulse%2<(e.pulse-dt)%2 then e.dx=11*math.cos(e.r) e.dy=11*math.sin(e.r) end -- Drop ink cloud if hero come close e.inkload=(e.inkload or 30)-- Start with full charge if e.caught and e.inkload>0 then e.inkload=9999-- insta recharge when caught end if e.inkload<0 then Emit(InkBlots,e.x,e.y,math.ceil(300*dt))-- How many blot per seconds table.insert(InkClouds,{x=e.x,y=e.y,r=160,ttl=12}) end if e.inkload>=30 then-- Recharge time if ((HasSight(e,Zoo[HeroIndex].x,Zoo[HeroIndex].y,false,256) and not e.caught) or e.wriggle) then e.inkload=-2 -- Duration of emission table.insert(InkClouds,{x=e.x,y=e.y,r=160,ttl=12}) end end if e.inkload<5000 then-- Just a big number to avoid increasing inkload indefinitly e.inkload=e.inkload+dt end elseif e.kind=="jelly" then -- Jelly movement maxspeed=3 e.dx,e.dy=e.dx*0.9,e.dy*0.9 if e.dr then e.r=e.r+e.dr e.dr=e.dr*0.97 if e.dr<tau*dt/9 then e.dr=nil end end local free,ilx,ily=true,math.floor(e.x/TX),math.floor(e.y/TY) for i=ilx-1,ilx+1 do for j=ily-1,ily+1 do if IsSolidTileCoord(i,j) then free=false end end end if not free then e.dr=e.dr or (CoinFlip() and tau or -tau)*dt/(2+math.random()) end e.dx=2*math.cos(e.r) e.dy=2*math.sin(e.r) -- Wait randomly 5s to 10s between every burst e.ttn=(e.ttn or 5+10*math.random())-dt if (not e.caught and (e.ttn<0 and not e.caught)) or e.wriggle then-- Then fire e.sometimes=(e.sometimes or 0)+1 table.insert(GravGens,{x=e.x,y=e.y,r=150,g=1000,ttl=0.05}) if e.sometimes%5==1 then if IsOnScreen(e.x,e.y) then PlaySoundAt(Sounds.GravGen[e.caught and math.random(1,3) or math.random(3,6)],e.x,e.y) end Emit(Waves,e.x,e.y,1) end if e.ttn<-5 then-- Duration of burst e.ttn=nil end elseif e.sometimes then e.sometimes=0 end elseif e.kind=="crab" then e.r=tau/4 if e.dx==0 then e.dx=CoinFlip() and -1 or 1 end local i,j=math.floor((e.x+50*e.dx)/TX),math.floor(e.y/TY) if IsSolidTileCoord(i,j) or not IsSolidTileCoord(i,j-1) then e.dx=-e.dx end e.sometimes=(e.sometimes or 0)+1 if HasSight(e,Zoo[HeroIndex].x,Zoo[HeroIndex].y,3*tau/8,false) then if e.sometimes%5==1 then local at=math.atan2(Zoo[HeroIndex].y-e.y,Zoo[HeroIndex].x-e.x) for claw=-40,40,80 do NewBullet("bullet",e.x+claw,e.y+40,15*math.cos(at),15*math.sin(at),10) end end end end --------------------- -- Update position -- --------------------- if not (e.kind=="crab") then-- Crab cling to wall, so no gravity pull and wall check for them -- Add Gravity Fields Effect lgx,lgy=GetLocalGravity(e.x,e.y) e.dx,e.dy=e.dx+lgx,e.dy+lgy -- Limit to max speed e.speed=math.sqrt(e.dx^2+e.dy^2) if e.speed>maxspeed then local a=math.atan2(e.dy,e.dx) e.dx=maxspeed*math.cos(a) e.dy=maxspeed*math.sin(a) e.speed=maxspeed end -- Check for wall collision local hs=math.ceil(e.size*0.3) for i=-hs,hs,hs do for j=-hs,hs,hs do if IsSolidWorldCoord(i+e.x+e.dx,j+e.y+e.dy) and not (e.kind=="crab") then e.dx=0 e.dy=0 e.speed=0 end end end end -- Proceed to new position e.x=e.x+e.dx e.y=e.y+e.dy -- If attached, disregard all that and place relative to carrier if e.caught then local c=e.caught.c local d,a,r=e.caught.d,e.caught.a,e.caught.r e.x=c.x+d*math.cos(c.r+a) e.y=c.y+d*math.sin(c.r+a) if e.kind~="spinny" then e.r=c.r+r end e.wriggle=false end --------------------------- -- After position update -- --------------------------- -- Check for collision against bullets for _,b in ipairs(Bullets) do if b.ttl>0 and (e.x-b.x)^2+(e.y-b.y)^2<=(e.size*0.4+8)^2 then if ie==HeroIndex then PlaySoundAt(Sounds.Knell,e.x,e.y) GameOver={since=love.timer.getTime()} --echo("Touched a deadly bullet!") elseif e.kind=="spinny" then local a=(math.atan2(b.dy,b.dx)-math.atan2(e.y-b.y,e.x-b.x)+4*tau)%tau if (a<tau/4 or a>3*tau/4) and (e.y-b.y)^2+(e.y-b.y)^2>3^2 then if e.caught then e.caught.c.carrying=nil end b.ttl,b.dx,b.dy=0,0,0 Emit(SpinnyShards,e.x,e.y,7) table.remove(Zoo,ie) PlaySoundAt(Sounds.Splode[1],e.x,e.y) end elseif e.kind=="squid" or e.kind=="jelly" then if e.caught then e.caught.c.carrying=nil end b.ttl,b.dx,b.dy=0,0,0 Emit(SquidShards,e.x,e.y,7) table.remove(Zoo,ie) PlaySoundAt(Sounds.Splode[3],e.x,e.y) elseif e.kind=="crab" then local a=(math.atan2(b.dy,b.dx)-math.atan2(e.y-b.y,e.x-b.x)+4*tau)%tau if (a<0.8*tau/4 or a>tau-0.8*tau/4) and (e.y-b.y)^2+(e.y-b.y)^2>3^2 then b.ttl,b.dx,b.dy=0,0,0 Emit(CrabShards,e.x,e.y,11) table.remove(Zoo,ie) PlaySoundAt(Sounds.Splode[1],e.x,e.y) end end end end if e.kind=="hero" then --Make it face where it is going if e.speed>0.5 then e.r=(math.atan2(e.dy,e.dx)+4*tau)%tau end -- Position sound listener love.audio.setPosition(e.x,e.y,0) -- Emit bubbles if love.getVersion then Bubbles:moveTo(e.x,e.y) else Bubbles:setPosition(e.x,e.y) end Bubbles:setEmissionRate(e.speed) -- Check for collision against enemy e.HasEnemyInSight=false InkBlocked=false for ic,c in ipairs(Zoo) do if ic~=ie and (not (c.caught and c.caught.c==e) or c.kind=="spinny") then if HasSight(e,c.x,c.y,tau/4) then e.HasEnemyInSight=true end if (e.x-c.x)^2+(e.y-c.y)^2<=(e.size*0.3+c.size*0.3)^2 then PlaySoundAt(Sounds.Knell,e.x,e.y) GameOver={since=love.timer.getTime()} --echo("Touched a deadly enemy!") end end end end endendlocal function WarnParticuleFull() for _,p in ipairs({{Waves,"waves"},{Bubbles,"bubbles"}, {BloodBlots,"blood blots"},{InkBlots,"ink blots"}, {SpinnyShards,"spinny hards"},{SquidShards,"squid shards"},{CrabShards,"crab shards"}}) do if p[1]:getCount()==p[1]:getBufferSize() then --echo("Too many "..p[2]) end endendfunction love.update(dt) local ForcedUpdateRate=0.030 if (not GameOver) and (not Paused) then WarnParticuleFull() UpdateZoo(ForcedUpdateRate) UpdateBullets(ForcedUpdateRate) DecayGravGens(ForcedUpdateRate) DecayInkClouds(ForcedUpdateRate) Bubbles:update(ForcedUpdateRate) BloodBlots:update(ForcedUpdateRate) InkBlots:update(ForcedUpdateRate) SpinnyShards:update(ForcedUpdateRate) SquidShards:update(ForcedUpdateRate) CrabShards:update(ForcedUpdateRate) Waves:update(ForcedUpdateRate) end if ScreenShotting then FrameCount=(FrameCount or 0)+1 love.graphics.newScreenshot():encode(string.format("Archatrep%04d.png",FrameCount)) else love.timer.sleep(ForcedUpdateRate) -- 0.8.0 endend