-- 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° -- Globals tau=2*math.pi Fonts={} Pics={} Sounds={} Level={} HeroIndex=-1 TX,TY=0,0-- Tile Size in Pixel Zoo={} Bullets={} GravGens={} InkClouds={} GameOver=false ShowGravField=false Paused=false -- Locals local FullScreen=false-- Change to true to start directly in fullscreen, without having to press F local ScreenShotting=false-- Make the game go slower, make right click aim at a given height, and fix window size to 548x548 local function CoinFlip() return math.random()>0.5 end local function sign(x) if x>0 then return 1 elseif x<0 then return -1 else return 0 end end local 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 end end local function Rotate(x,y,angle) return x*math.cos(angle)-y*math.sin(angle), x*math.sin(angle)+y*math.cos(angle) end local function DrawCenteredText(font,text,x,y) love.graphics.setFont(Fonts[48]) love.graphics.print(text,x-font:getWidth(text)/2,y-font:getLineHeight()/2) end local 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}) end local 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}) end local 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 end end local 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") end local 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") end local 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") end local 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() end local 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() end local 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() end local function Emit(particles,x,y,quantity) if love.getVersion then particles:moveTo(x,y) else particles:setPosition(x,y) end particles:emit(quantity) end local 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") end local 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,lgy end local 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] end end local function IsSolidWorldCoord(x,y) return IsSolidTileCoord(math.floor(x/TX),math.floor(y/TY)) end local 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)) end local 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=LevelBatch end local 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() end local 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) end end local 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) end local 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)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