require 'const' require 'util' constr = require 'constructors' assets = require 'lib.assets' ease = require 'lib.ease' bench = require 'lib.benchmark' sprites = {} sound_path = {} music_path = {} fonts = {} debug = false food = {} feesh = {} footerbuttons = { { cost = 100, sprite = 'guppy', openanim = 1, open = true, func = function() table.insert(feesh, constr.fish(math.random(), 0.3)) end }, { cost = 200, sprite = 'food', tier = 1, openanim = 1, open = true, func = function() --table.insert(feesh, constr.fish(math.random(), 0.3)) end }, { cost = 300, sprite = 'foodcount', tier = 3, openanim = 1, open = true, func = function() --table.insert(feesh, constr.fish(math.random(), 0.3)) end } } local sheets = {} local function newAnimation(image, width, height) local animation = {} animation.spriteSheet = image; animation.quads = {}; animation.width = width animation.height = height for y = 0, image:getHeight() - height, height do for x = 0, image:getWidth() - width, width do table.insert(animation.quads, love.graphics.newQuad(x, y, width, height, image:getDimensions())) end end return animation end local function fishsprite(size, hungry, anim) -- anim is turn, swim, eat or die if anim == 'die' then hungry = false end local spritename = size .. '_' .. (hungry and 'hungry_' or '') .. anim local spr = sprites['fish/' .. spritename] return newAnimation(spr, spr:getWidth()/10, spr:getHeight()) end function love.load() assets.clear() assets.load('assets') sheets.wavecenter = newAnimation(sprites['wave/wavecenter'], sprites['wave/wavecenter']:getWidth(), sprites['wave/wavecenter']:getHeight()/12) sheets.waveside = newAnimation(sprites['wave/waveside'], sprites['wave/waveside']:getWidth(), sprites['wave/waveside']:getHeight()/12) sheets.food1 = newAnimation(sprites['food/1'], sprites['food/1']:getWidth()/10, sprites['food/1']:getHeight()) sheets.food2 = newAnimation(sprites['food/2'], sprites['food/2']:getWidth()/10, sprites['food/2']:getHeight()) sheets.food3 = newAnimation(sprites['food/3'], sprites['food/3']:getWidth()/10, sprites['food/3']:getHeight()) sheets.buttonopen = newAnimation(sprites['footer/button_open'], sprites['footer/button_open']:getWidth()/3, sprites['footer/button_open']:getHeight()) for i = 1, 3 do table.insert(feesh, constr.fish(math.random(), math.random())) end fonts.pix = love.graphics.newFont('assets/fonts/pix.ttf', 6) fonts.continuum = love.graphics.newFont('assets/fonts/cont.ttf', 14) fonts.default = love.graphics.newFont(12) end local frame = 0 local unfocusinterval = 5 function love.update(dt) frame = frame + 1 bench.update() bench.startBenchmark('update') bench.startBenchmark('update_food') for i,f in ipairs(food) do f.y = f.y + dt * f.speed f.time = f.time + dt f.y = clamp(f.y, 0, 0.9) if f.y == 0.9 then f.deathtimer = f.deathtimer + dt end if f.deathtimer > 1 then table.remove(food, i) end end bench.stopBenchmark('update_food') bench.startBenchmark('update_fish') for fi, n in ipairs(feesh) do bench.startBenchmark('update_fish_eases') for _, e in ipairs(n.eases) do e.a = e.a + dt * e.speed end for i, e in ipairs(n.eases) do if e.a > 1 then local sumx = 0 local sumy = 0 for i2, e2 in ipairs(n.eases) do if i ~= i2 then sumx = sumx + mix(e2.fromx, e2.x, FISH_EASE(math.min(e2.a, 1))) sumy = sumy + mix(e2.fromy, e2.y, FISH_EASE(math.min(e2.a, 1))) end end sumx = sumx + e.x sumy = sumy + e.y n.x = sumx / #n.eases n.y = sumy / #n.eases e.fromx = e.x e.fromy = e.y e.a = e.a - 1 local angle = math.random(-FISH_ANGLE, FISH_ANGLE) local str = math.random(70, 200)/200/4 if n.eattimer <= 0 and not n.dead then -- needs to follow something local mx, my if n.eattimer <= 0 then if n.shortestfood and food[n.shortestfood] then local f = food[n.shortestfood] mx, my = f.x, f.y elseif frame % FISH_FOOD_CHECK_FREQ == 0 then local minfood = 0 local mindist = 9e9 for i,f in ipairs(food) do local dist = math.sqrt(math.pow(math.abs(f.x - n.render.x), 2) + math.pow(math.abs(f.y - n.render.y), 2)) if dist < mindist then mindist = dist minfood = i end end if minfood ~= 0 then n.shortestfood = minfood end end end if mx and my then angle = math.deg(math.atan2(my - n.y, mx - n.x)) + math.random(-FISH_FOLLOW_RANDOM, FISH_FOLLOW_RANDOM) str = math.random(70, 200)/200/8 end end local x = math.cos(math.rad(angle)) * str local y = math.sin(math.rad(angle)) * str if not ((n.shortestfood and food[n.shortestfood]) or n.dead) then x = x * math.sign(n.render.x - n.render.prevx) end e.x = n.x + x e.y = n.y + y e.x = 1 - math.abs(e.x%2-1) e.y = 1 - math.abs(e.y%2-1) local fheight = (FOOTER_HEIGHT * love.graphics.getWidth()/640)/love.graphics.getHeight() if e.y < fheight then e.y = e.y + (fheight - e.y) * 2 end e.speed = 1 / (math.sqrt(math.pow(math.abs(e.x - e.fromx), 2) + math.pow(math.abs(e.y - e.fromy), 2))/2) / 15 if n.eattimer < FISH_FOOD_HUNGRY or (n.shortestfood and food[n.shortestfood]) then e.speed = e.speed * 1.3 end if n.dead then e.speed = e.speed * 0.2 end end end bench.stopBenchmark('update_fish_eases') bench.startBenchmark('update_fish_position') n.x = clamp(n.x, 0, 1) n.y = clamp(n.y, 0, 0.5) n.lifetime = n.lifetime + dt n.eattimer = n.eattimer - dt local sumx = 0 local sumy = 0 for _, e2 in ipairs(n.eases) do sumx = sumx + mix(e2.fromx, e2.x, FISH_EASE(e2.a)) sumy = sumy + mix(e2.fromy, e2.y, FISH_EASE(e2.a)) end n.render.prevx = n.render.x n.render.prevy = n.render.y n.render.x = sumx / #n.eases n.render.y = sumy / #n.eases n.render.x = clamp(n.render.x, 0.05, 0.95) n.render.y = clamp(n.render.y, 0.1, 0.85) bench.stopBenchmark('update_fish_position') bench.startBenchmark('update_fish_colission') if n.shortestfood and food[n.shortestfood] and frame % FISH_COLISSION_CHECK_FREQ == 0 then local f = food[n.shortestfood] if f then local dist = math.abs(n.render.x - f.x) + math.abs(n.render.y - f.y) if dist < FOOD_HITBOX then table.remove(food, n.shortestfood) n.eattimer = FISH_FOOD_COOLDOWN n.render.eattimer = 0 n.shortestfood = -1 if n.lifetime > FISH_AGE_MEDIUM then n.size = 1 end if n.lifetime > FISH_AGE_BIG then n.size = 2 end end end end bench.stopBenchmark('update_fish_colission') bench.startBenchmark('update_fish_render') if frame % FISH_RENDER_FREQ == 0 then local dt = dt * FISH_RENDER_FREQ local iter = FISH_RENDER_ITERS local iteroff = FISH_RENDER_ITEROFF local xspeed = {} local yspeed = {} local angle = {} local sumxiter = {} local sumyiter = {} for i = 1, iter do local off = i * iteroff local sumx2 = 0 local sumy2 = 0 for _, e2 in ipairs(n.eases) do sumx2 = sumx2 + mix(e2.fromx, e2.x, FISH_EASE(e2.a + off)) sumy2 = sumy2 + mix(e2.fromy, e2.y, FISH_EASE(e2.a + off)) end table.insert(xspeed, (sumx2 - (sumxiter[i - 1] or sumx)) / #n.eases) table.insert(yspeed, (sumy2 - (sumyiter[i - 1] or sumy)) / #n.eases) table.insert(angle, math.atan2(sumy2 - (sumyiter[i - 1] or sumy), sumx2 - (sumxiter[i - 1] or sumx))) table.insert(sumxiter, sumx2) table.insert(sumyiter, sumy2) end n.render.xspeed = sum(xspeed) n.render.yspeed = sum(yspeed) n.render.angle = sum(angle) end n.render.turn = n.render.turn + dt * math.sign(n.render.x - n.render.prevx) * 2 n.render.turn = clamp(n.render.turn, 0, 1) if n.render.turn == 0 or n.render.turn == 1 then n.render.swim = (n.render.swim + dt * math.abs(n.render.xspeed) * 300) % 1 else n.render.swim = 0 end n.render.eattimer = n.render.eattimer + dt * 3 if n.render.eattimer < 1 then n.render.swim = 0 end if not n.dead then n.render.turndir = math.sign(n.render.x - n.render.prevx) end local m = n.eattimer < FISH_FOOD_HUNGRY and 1 or -1 n.render.hungry = clamp(n.render.hungry + dt * m * 3, 0, 1) bench.stopBenchmark('update_fish_render') if n.eattimer <= FISH_FOOD_DEAD then n.dead = true local timeSinceDead = math.abs(n.eattimer - FISH_FOOD_DEAD) n.render.y = n.render.y + timeSinceDead/5 * math.min(timeSinceDead, 1) n.render.y = clamp(n.render.y, 0, 0.85) if n.render.y == 0.85 then n.render.deathanim = n.render.deathanim + dt if n.render.deathanim > 1 then table.remove(feesh, fi) end end end end bench.stopBenchmark('update_fish') bench.stopBenchmark('update') end function love.draw() love.graphics.setFont(fonts.default) bench.startBenchmark('render') bench.startBenchmark('render_tank') love.graphics.setColor(1, 1, 1) local sw, sh = love.graphics.getDimensions() local footerheight = FOOTER_HEIGHT * sw/640 local yscale = (sh-footerheight)/sh local sample = fishsprite('medium', false, 'swim') local spritescale = (math.min(sw, sh)/FISH_SIZE) / math.min(sample.width, sample.height) stretchto(sprites['bg/1'], 0, footerheight - FOOTER_HEIGHT, 0, sw, sh - (footerheight - FOOTER_HEIGHT)) -- waves bench.startBenchmark('render_wave') local wavecount = round(sw / sprites['wave/wavecenter']:getWidth()) local wavescale = sw / (wavecount * sprites['wave/wavecenter']:getWidth()) for i = 1, wavecount do local a = (i - 1) / wavecount local x = a * sw local frame = round((1 - math.abs(love.timer.getTime()%2-1)) * #sheets.wavecenter.quads) love.graphics.setBlendMode('add') local sheet = sheets.wavecenter local sizex = 1 if i == 1 or i == wavecount then sheet = sheets.waveside end if i == wavecount then sizex = -1 end love.graphics.draw(sheet.spriteSheet, sheet.quads[math.max(frame, 1)], x + (sprites['wave/wavecenter']:getWidth() * wavescale)/2, footerheight + 20, 0, wavescale * sizex, wavescale, sprites['wave/wavecenter']:getWidth()/2) end love.graphics.setBlendMode('alpha') bench.stopBenchmark('render_wave') -- shadow bench.startBenchmark('render_shadow') for i, n in ipairs(feesh) do love.graphics.setColor(1, 1, 1, n.render.y + 0.2 - n.render.deathanim) local sizes = {0.55, 0.7, 1} local size = sizes[n.size + 1] or 1 love.graphics.draw(sprites['shadow'], n.render.x * sw - (sprites['shadow']:getWidth() * spritescale * size)/2, sh - sh * 0.18 - (sprites['shadow']:getHeight() * spritescale * size)/2 + n.render.y * sh * 0.08, 0, spritescale * size, spritescale * size) end bench.stopBenchmark('render_shadow') -- all the fish bench.startBenchmark('render_fish') for i, n in ipairs(feesh) do local x = n.render.x * sw local y = n.render.y * sh -- rest of feesh local size = 'small' local anim = 'swim' local sample = fishsprite('medium', false, 'swim') local sizex = 1 local turn = n.render.turn if n.render.turndir == -1 then turn = 1 - turn; sizex = -1 end local turnframe = math.floor(turn * (#sample.quads - 1)) + 1 if #sample.quads == turnframe then sizex = -1 * sizex turnframe = 1 end local frame = math.floor(n.render.swim * (#sample.quads - 1)) + 1 if turnframe ~= 1 and turnframe ~= #sample.quads then anim = 'turn' frame = turnframe end if n.render.eattimer <= 1 then anim = 'eat' frame = math.floor(n.render.eattimer * (#sample.quads - 1)) + 1 end if n.dead then anim = 'die' local a = math.min(math.abs(n.eattimer - FISH_FOOD_DEAD) * 1.4, 1) - n.render.deathanim * 0.4 frame = math.floor(a * (#sample.quads - 1)) + 1 end local angle = n.render.angle if angle > math.pi/2 then angle = angle - math.pi end if angle < -math.pi/2 then angle = angle + math.pi end angle = angle * math.max(math.min((-math.abs(angle) + math.pi * 0.5) / (math.pi * 0.5) * 2, 1), 0) angle = angle * 0.5 if n.size == 0 then size = 'small' elseif n.size == 1 then size = 'medium' elseif n.size == 2 then size = 'big' elseif n.size == 3 then size = 'king' end local sheet = fishsprite(size, false, anim) local sadsheet = fishsprite(size, true, anim) local alpha = n.render.hungry == 1 and 0 or 1 love.graphics.setColor(1, 1, 1, alpha - n.render.deathanim) love.graphics.draw(sheet.spriteSheet, sheet.quads[math.max(math.min(frame, #sample.quads), 1)], x, y, angle, sizex * spritescale, spritescale, sample.width/2, sample.height/2) love.graphics.setColor(1, 1, 1, n.render.hungry - n.render.deathanim) love.graphics.draw(sadsheet.spriteSheet, sheet.quads[math.max(math.min(frame, #sample.quads), 1)], x, y, angle, sizex * spritescale, spritescale, sample.width/2, sample.height/2) love.graphics.setColor(1, 1, 1) if debug then love.graphics.print(shrt(n.eattimer), x + 20, y + 20) end if debug then for _,e in ipairs(n.eases) do love.graphics.setColor(1, 0, 0, 0.75) love.graphics.line(e.fromx * sw, e.fromy * sh, e.x * sw, e.y * sh) love.graphics.setColor(0, 0, 1, 0.75) love.graphics.line(mix(e.fromx, e.x, FISH_EASE(e.a)) * sw, mix(e.fromy, e.y, FISH_EASE(e.a)) * sh, n.render.x * sw, n.render.y * sh) end local subdiv = DEBUG_FISH_PATH_SUBDIVISIONS local adv = DEBUG_FISH_PREDICT_AMOUNT local pos = {} local valid = {} for i = 1, subdiv do local a = ((i - 1) / (subdiv - 1)) * (adv * 2) - adv local sumx = 0 local sumy = 0 local mina = 0 local maxa = 1 for _, e in ipairs(n.eases) do local a = e.a + a mina = math.min(mina, a) maxa = math.max(maxa, a) sumx = sumx + mix(e.fromx, e.x, FISH_EASE(a)) sumy = sumy + mix(e.fromy, e.y, FISH_EASE(a)) end table.insert(pos, sumx / #n.eases * sw) table.insert(pos, sumy / #n.eases * sh) table.insert(valid, not (maxa > 1 or mina < 0)) end for i = 0, #pos/2 - 1 do local x1 = pos[i * 2 + 1] local y1 = pos[i * 2 + 1 + 1] local x2 = pos[i * 2 + 2 + 1] local y2 = pos[i * 2 + 3 + 1] local valid = valid[i + 1] if not x2 or not y2 then break end love.graphics.setColor(0, 1, 0, 1) if not valid then love.graphics.setColor(0, 0.5, 1, 0.7) end love.graphics.line(x1, y1, x2, y2) end end end bench.stopBenchmark('render_fish') bench.startBenchmark('render_food') for _,f in ipairs(food) do local sheet = sheets.food1 local x = f.x * sw local y = f.y * sh local frame = math.floor((f.time%1) * #sheet.quads) + 1 love.graphics.setColor(1, 1, 1, 1 - f.deathtimer) love.graphics.draw(sheet.spriteSheet, sheet.quads[math.max(math.min(frame, #sheet.quads), 1)], x, y, 0, spritescale, spritescale, sheet.width/2, sheet.height/2) end bench.stopBenchmark('render_food') bench.stopBenchmark('render_tank') bench.startBenchmark('render_footer') local base = sprites['footer/base'] local size = footerheight / FOOTER_HEIGHT love.graphics.setColor(1, 1, 1, 1) love.graphics.draw(base, 0, 0, 0, size, size) -- the game is making me do this. im sorry local x = 19 local y = 3 for b = 1, 7 do local hovered = mouseOverBox(x * size, y * size, sprites['footer/buttonbg']:getWidth() * size, sprites['footer/buttonbg']:getHeight() * size) local btn = footerbuttons[b] if (btn and not btn.open) or not btn then -- draw nothing elseif hovered and love.mouse.isDown(1) then love.graphics.draw(sprites['footer/buttonbg_down'], x * size, y * size, 0, size, size) elseif hovered then love.graphics.draw(sprites['footer/buttonbg_hover'], x * size, y * size, 0, size, size) else love.graphics.draw(sprites['footer/buttonbg'], x * size, y * size, 0, size, size) end if btn and btn.open then -- sprite inside if btn.sprite == 'guppy' then local sheet = fishsprite('medium', false, 'swim') local frame = math.floor((love.timer.getTime() * 2) % 1 * #sheet.quads) + 1 local scale = (sprites['footer/buttonbg']:getWidth() / sheet.width) * 0.9 local offset = (sprites['footer/buttonbg']:getWidth() * size) / 2 love.graphics.draw(sheet.spriteSheet, sheet.quads[frame], x * size + offset, y * size + offset*0.75, 0, size * scale, size * scale, sheet.width/2, sheet.width/2) elseif btn.sprite == 'food' then local sheets = {sheets.food2, sheets.food3} local sheet = sheets[btn.tier] local scale = (sprites['footer/buttonbg']:getWidth() / sheet.width) * 0.65 local offset = (sprites['footer/buttonbg']:getWidth() * size) / 2 love.graphics.draw(sheet.spriteSheet, sheet.quads[1], x * size + offset, y * size + offset*0.75, 0, size * scale, size * scale, sheet.width/2, sheet.width/2) elseif btn.sprite == 'foodcount' then love.graphics.setFont(fonts.continuum) local offset = (sprites['footer/buttonbg']:getWidth() * size) / 2 local bordersize = 1 for _,p in ipairs({{0, 1}, {1, 0}, {1, 1}, {-1, 0}, {0, -1}, {-1, -1}, {1, -1}, {-1, 1}}) do love.graphics.setColor(0, 0, 0, 0.5) love.graphics.printf(btn.tier + 1, round(x * size) + p[1] * bordersize, round(y * size + offset*0.75 - fonts.continuum:getHeight()/2) + p[2] * bordersize, round(sprites['footer/buttonbg']:getWidth() * size), 'center') end love.graphics.setColor(0, 1, 0) love.graphics.printf(btn.tier + 1, round(x * size), round(y * size + offset*0.75 - fonts.continuum:getHeight()/2), round(sprites['footer/buttonbg']:getWidth() * size), 'center') end -- price love.graphics.setFont(fonts.pix) local font = love.graphics.getFont() love.graphics.setColor(0, 1, 0) love.graphics.printf('$' .. btn.cost, round(x * size), round(y * size + 51 * size - font:getHeight()/2), round(sprites['footer/buttonbg']:getWidth() * size), 'center') love.graphics.setColor(1, 1, 1) love.graphics.setFont(fonts.default) -- reflection love.graphics.setBlendMode('add') love.graphics.draw(sprites['footer/reflection'], x * size, y * size, 0, size, size) love.graphics.setBlendMode('alpha') -- open/close anim if (btn and not btn.openanim == 1) then local sheet = sheets.buttonopen local anim = 0 if btn then anim = btn.openanim end local frame = math.floor(anim % 1 * #sheet.quads) + 1 love.graphics.draw(sheet.spriteSheet, sheet.quads[frame], x * size, y * size, 0, size, size) end end local incr = 69 -- its like button positions but forcefully shoved into a recursive function :D if b == 2 then incr = 57 end if b >= 3 then incr = 73 end x = x + incr end bench.stopBenchmark('render_footer') love.graphics.setColor(1, 1, 1, 1) love.graphics.print('FPS: ' .. 1 / love.timer.getDelta(), 0, sh - 16) if debug then bench.renderBenchmark() end bench.stopBenchmark('render') end function love.mousepressed(x, y, b) if b == 2 then table.insert(food, constr.food(x/love.graphics.getWidth(), y/love.graphics.getHeight(), 1)) end end function love.mousereleased(x, y, b) local footerheight = FOOTER_HEIGHT * love.graphics.getWidth()/640 local size = footerheight / FOOTER_HEIGHT if b == 1 then local x = 19 for i = 1, 7 do local hovered = mouseOverBox(x * size, 3 * size, sprites['footer/buttonbg']:getWidth() * size, sprites['footer/buttonbg']:getHeight() * size) if hovered then if footerbuttons[i] and footerbuttons[i].open then footerbuttons[i].func() end end local incr = 69 -- its like button positions but forcefully shoved into a recursive function :D if b == 2 then incr = 57 end if b >= 3 then incr = 73 end x = x + incr end end end function love.keypressed(key) if key == 'f3' then debug = not debug end end