0>8BPt{oEFrYޝcʞ鸿MM$ѧ~Z O2TvͫgE#!Ceg5 PT"1@PUU@D@DUUUUDD@DDD@D@DDD@DDDUUDDDD@D@DDD@DDD@DDDDDwwww ""pw'pw' "" """"""""""""xxxpxxx@D@D@D@DDD@DDD@D@DDD@DDDDD@DDD@DDDDDDDDDDD@DDD@D@DDD@DDD@D@DDD@DDD@D@DDD@DDDDD@DDD@D@DDD@DDDDDDDxxxxxxxxxxx0""""""""3""30""""""""3""322 "" "" """"#333"""""""""""""###33"""""2##22(""(""(""""#333"""""""""""""###33"""""2##TUTUU@UUU@UU\@UU@U@UU_UUUUUUUU\UXUTUTUU@UUU@UU\@UU@UU_UUUUUUUU\U"" "" """"""""""""!!"""2""""""""""""""""""""""""""""22"#"""""""""""""""""""(""(""""""""""""!!"""2""""""""""""""""""""""""""""22"#"""""""""""""""""T@K@$TE"!T!"T1"@@$#IXE!["A[!TE"LE"LE"BI@UT@K@$TE"!T!"T1"@@$#XUXE!["A[!TE"LE"LE"B!!! !! "! """""""""!""""""!!""!!!!""!""""!!!!!!(!!("!("""""""""!""""""!!""!!!!""!""""!!! UUUPUUUUUUUU@D@DDDDDDDDDUUUXUUUUUUUUHDHDDDDDDDDDPUDDDDDDDUUDDTUDDTUEUUUDDTUDTUUDDTUDDTUDDUUUUUUUUU53SUSU55UU_SUUSUUUDDUUUUUUUUSU5U5UXUDDDDDDDUUDDTUDDTUEUUUDDTUDTUUDDTUDDTUDDUUUUUUUUU53SUSU55UU_SUUSUUUDDUUUUUUUUSU5U5UDDDDDDDTU@TUTUDTUDTUDDTTTTDDTUSUUSUUU5UUUUSU5U53SUUUUTUUUUUUU5U5USUUUUUUUUUUUDDDDDDDTU@TUTUDTUDTUDDTTTTDDTUSUUSUUU5UUUUSU5U53SUUUUTUUUUUUU5U5USUUUUUUUUUUU 0003003333000300030300333033030333030000000000303333330333333000303000030330030303303030003303033330300030000333303303030000030000033033000033330033003300003333 030003300000030033Q,-,-!"#<=()<=XY89XYhiHIhi  012@ABPQR     " 000000000000000000000000000000 [[[[[[[[[[ [ [ [ [[\\\]]]]^^^^^^__            2 #   !$%'(.///!#&(*,-./////////d'       a a a a       ddddd d d d d D D DC C c cd e E AF             A A aaA A aa  d c   c       ddddd d d  d D D DC Ccc d % % A % A D   c        b bBbbbbb""BBbbbbb""B b 10Ja?333033033003003000330003300030003000000000000000000000000003300003000300000330030030000330330010033310030333000033100010000010003033003330000000003033000333331033011301030100000000013300013010010030003300033001003000000000000xxx0030000301330003133pxxx3303330xxx101031x33003300x00300100013303010333003300033030010031000100000000000300033000033300003300000300003000001033000000100100003003013303033010330000030?03033"""""0"""3""3TU"#323333TUUU_ """""""""@UUUUUUUUU@DDD """"""""2@UU\UUUPUUUDDDD """"""""#@UUUUUUUUU@DDD"""22222@U̜\UPUUUDDDD""""""##@U̼XUDDDTUUUUUUU """""""22ŤXEDDDTUUUUUUU """""""""@K![DEUUUU53SUU""""""""""@$"A[DDDTUUSU5UU""""""""""TE"!!TEDDTUU5UU_SU""""""""""T!""LEDDDTUSUU5U""""""""T1""LEDDDTUSUUU5U"!!!""""""@@$#"BDDTUSUU5U"""""" D@TUSUUU5U"!!!"""""!wwTU5UUUSU """""wwDDTUUSU5UU "!!!""""! DDTUU53SUU""" """""DDDTTUUUUUU)"!!pw'"""TTTUUUUU pw'"""DDDTUUUUUUU """""PUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDDyA-- title: Runic Moon -- author: pk1029, starling, Gs_presents -- desc: An entry for the LD50 game jam -- script: lua WIDTH = 240 HEIGHT = 136 W2 = 120 H2 = 68 PI = 3.1415 sin = math.sin cos = math.cos max = math.max min = math.min abs = math.abs sqrt = math.sqrt sqrt2 = sqrt(2) ceil = math.ceil floor = math.floor random = math.random sysmouse = mouse systime = time sysmusic = music time = { t = 0, dt = 0, last_t = 0, count = 0, fps = 60, ts = 0, show = true, update = function(self) if keyp(20) then self.show = not self.show end local t = systime() self.dt = t - self.t self.t = t self.ts = self.t // 1000 if t - self.last_t > 1000 then self.last_t = t self.fps = self.count self.count = 0 else self.count = self.count + 1 end end, draw = function(self) if self.show then print(self.fps, 228, 2, 1, true) end end } mouse = { x = 120, y = 68, dx = 0, dy = 0, md = false, mp = false, mu = false, right = false, middle = false, scroll = 0, update = function(self) local mx, my, md, middle, right, scrollx, scrolly = sysmouse() self.dx = mx - self.x self.dy = my - self.y self.x = mx self.y = my self.mp = not self.md and md self.mu = self.md and not md self.md = md self.right = right self.middle = middle self.scroll = scrolly end, draw = function(self) line(0, self.y, WIDTH, self.y, 6) line(self.x, 0, self.x, HEIGHT, 6) print('(' .. self.x .. ',' .. self.y .. ')', self.x+2, self.y+2, 6) local w = print(self.x, 0, -6) print(self.x, self.x-w, 0, 6) print(self.y, 0, self.y-6 , 6) end } mathFun = { between = function(x, a, b) return x >= a and x < b end, clamp = function(x, a, b) if mathFun.between(x, a, b) then return x end if x < a then return a end if x >= b then return b end end, bool2int = function(a) return a and 1 or 0 end, randlist = function(list, n) if n == nil then n = #list end local i = random(1, n) return list[i] end, randfloat = function(a, b) return random() * (b - a) + a end, isin = function(a, list) local flag = false for i = 1,#list do if list[i] == a then flag = true break end end return flag end, } Vec = { new = function(x, y, z) local v = {x, y, z} setmetatable(v, Vec.mt) return v end, mt = { __add = function (u, v) return Vec.new(u[1]+v[1], u[2]+v[2], u[3]+v[3]) end, __sub = function(u, v) return Vec.new(u[1]-v[1], u[2]-v[2], u[3]-v[3]) end, __mul = function(k, v) if type(k) == "table" then return Vec.new(k[1]*v[1], k[2]*v[2], k[3]*v[3]) else return Vec.new(k*v[1], k*v[2], k*v[3]) end end, __div = function(v, k) if type(k) == "table" then return Vec.new(v[1]/k[1], v[2]/k[2], v[3]/k[3]) else return Vec.new(v[1]/k, v[2]/k, v[3]/k) end end, __pow = function(v, k) return Vec.new(v[1]^k, v[2]^k, v[3]^k) end, __eq = function(u, v) return u[1] == v[1] and u[2] == v[2] and u[3] == v[3] end, __tostring = function(v) return "(" .. v[1] .. "," .. v[2] .. "," .. v[3] .. ")" end, __concat = function(s, v) return s .. "(" .. v[1] .. "," .. v[2] .. "," .. v[3] .. ")" end, __index = { normalise = function(self) local r = Vec.norm(self) self[1] = self[1] / r self[2] = self[2] / r self[3] = self[3] / r return self end, rotatex = function(self, ax) local c = cos(ax) local s = sin(ax) self[2], self[3] = self[2]*c-self[3]*s, self[2]*s+self[3]*c return self end, } }, norm = function(v) return sqrt(v[1]*v[1] + v[2]*v[2] + v[3]*v[3]) end, dot = function(u, v) return u[1]*v[1] + u[2]*v[2] + u[3]*v[3] end, cross = function(u, v) return Vec.new(u[2]*v[3]-v[2]*u[3], v[1]*u[3]-u[1]*v[3], u[1]*v[2]-v[1]*u[2]) end, avg = function(u) return (u[1] + u[2] + u[3]) / 3 end, rotatex = function(v, ax) local c = cos(ax) local s = sin(ax) return Vec.new(v[1], v[2]*c-v[3]*s, v[2]*s+v[3]*c) end, } Bubble = { particles = {}, list = {224, 225, 226, 227, 228, 229}, pos = {{0,0},{0,1},{1,1},{1,0}}, r = 7, dt = 0.1, speed = 6, grav = -0.5, damping = 1.2, over = nil, hold = nil, dclick = nil, dtime = 0, new = function(x, y) local b = {x=x, y=y} b.u = 2*(random()-0.5) b.v = -10*random() b.id = mathFun.randlist(Bubble.list) b.mo = false b.t = random(1000) table.insert(Bubble.particles, b) setmetatable(b, Bubble.mt) return b end, mt = { __index = { update = function(self) -- velocity if self.y>60 then self.v = self.v + Bubble.grav * Bubble.dt end -- position self.x = self.x + self.u * Bubble.dt self.y = self.y + self.v * Bubble.dt -- wall collision if self.x < 7 and self.u < 0 then self.u = -self.u/Bubble.damping end if self.x > 233 and self.u > 0 then self.u = -self.u/Bubble.damping end if self.y < 9 and self.v < 0 then self.v = -self.v/Bubble.damping end if self.y > 114 and self.v > 0 then self.v = -self.v/Bubble.damping end -- mouse over self.mo = false end, draw = function(self) local frame = (time.t + self.t) // 200 % 4 + 1 spr(self.id, self.x-4+Bubble.pos[frame][1], self.y-4+Bubble.pos[frame][2], 0) -- spr(self.id, self.x-4, self.y-3, 0) spr(256, self.x-7, self.y-7, 0, 1, 0, 0, 2, 2) if self.mo == true then circb(self.x, self.y, 8, 8) end end, draw_empty = function(self) spr(256, self.x-7, self.y-7, 0, 1, 0, 0, 2, 2) if self.mo == true then circb(self.x, self.y, 8, 8) end end, remove = function(self) for i,b in ipairs(Bubble.particles) do if b == self then table.remove(Bubble.particles, i) end end end } }, load = function(self) Bubble.particles = {} for i = 1,20 do local x = random()*240 local y = random()*107 Bubble.new(x, y) end end, update = function(self) for i,b in ipairs(self.particles) do b:update() end -- drag if not mouse.md and not mouse.mu then -- not clicking: update over self.hold = nil self.over = nil for i,b in ipairs(self.particles) do local r = sqrt((b.x - mouse.x)^2 + (b.y - mouse.y)^2) if r < 8 then b.mo = true self.over = b break end end elseif mouse.mp then -- on click: hold <- over self.hold = self.over if self.dclick == self.over and self.dclick ~= nil then self.dclick:remove() sfx(49) if scene.current == scene.game then Ps.emit(self.dclick.id, self.dclick.x-4, self.dclick.y-3) end else self.dclick = self.over self.dtime = 0 end end if mouse.md and self.hold ~= nil then -- is holding: move object self.hold.mo = true self.hold.x, self.hold.u = mouse.x, (mouse.x-self.hold.x) self.hold.y, self.hold.v = mouse.y, (mouse.y-self.hold.y) elseif mouse.mu and self.hold ~= nil then -- on release: check cauldron if pot.mo and pot.id == self.hold.id then player.health = mathFun.clamp(player.health+10, 0, 100) pot.id = mathFun.randlist(Bubble.list) sfx(48) self.hold:remove() Ps.emit(self.hold.id, 113, 88+time.ts%2) level.score = level.score + 1 end end self.dtime = self.dtime + 1 if self.dtime > 60 then self.dclick = nil end -- collision for i = 1,#self.particles do for j = (i+1),#self.particles do Bubble.collision(self.particles[i], self.particles[j]) end end end, draw = function(self) for i,b in ipairs(self.particles) do b:draw() end end, draw_empty = function(self) for i,b in ipairs(self.particles) do b:draw_empty() end end, collision = function(b1, b2) local x = b1.x - b2.x local y = b1.y - b2.y local r = sqrt(x*x + y*y) if r > 14 then return end local nx = x / r local ny = y / r local per1 = b1.u * nx + b1.v * ny local per2 = b2.u * nx + b2.v * ny if per1 > 0 and per2 < 0 then return end local tan1 = b1.u * ny - b1.v * nx local tan2 = b2.u * ny - b2.v * nx b1.u = per2 * nx + tan1 * ny b1.v = per2 * ny - tan1 * nx b2.u = per1 * nx + tan2 * ny b2.v = per1 * ny - tan2 * nx end, } box = { x = 48, y = 96, bbox = {50, 101, 79, 120}, mo = false, update = function(self) -- self.mo = false -- if mathFun.between(mouse.x, self.bbox[1], self.bbox[3]) and mathFun.between(mouse.y, self.bbox[2], self.bbox[4]) then -- self.mo = true -- end -- if mouse.mp and self.mo and #Bubble.particles<20 then if #Bubble.particles<25 then self.spawn(1) end end, draw = function(self) if self.mo then spr(164, self.x, self.y, 0, 1, 0, 0, 4, 3) else spr(160, self.x, self.y, 0, 1, 0, 0, 4, 3) end end, spawn = function(n) for i = 1,n do Bubble.new(65, 112) end end } pot = { x = 105, y = 96, bbox = {106, 88, 127, 120}, id = mathFun.randlist(Bubble.list), mo = false, update = function(self) self.mo = false if mathFun.between(mouse.x, self.bbox[1], self.bbox[3]) and mathFun.between(mouse.y, self.bbox[2], self.bbox[4]) then self.mo = true end end, draw = function(self) if self.mo then spr(51, self.x, self.y, 0, 1, 0, 0, 3, 3) else spr(48, self.x, self.y, 0, 1, 0, 0, 3, 3) end spr(self.id+16, self.x+8, self.y-8+time.ts%2, 0) end, } fire = { id = {96, 112, 128}, draw = function(self) spr(self.id[(time.t//200)%3+1], 104, 112, 0, 1, 0, 0, 3, 1) end } player = { t = 1, health = 70, rate = 0.01, level = 1, update = function(self) self.health = self.health - self.rate self.t = self.t + 1 if self.t % 1800 == 0 then -- self.rate = 2*self.rate self.level = self.level + 1 level:setlevel(self.level) end end, update_health = function(self) self.health = self.health - self.rate end, draw = function(self) rect(0, 0, self.health/100*240, 1, 1) end } character = { id = {40, 42}, draw = function(self) spr(self.id[(time.t//500)%2+1], 144, 88, 0, 1, 0, 0, 2, 3) end } moon = { x = 104, y = 32, ax = 0, light = Vec.new(0, 0, 1):normalise(), elements = {}, health = 100, load = function(self) cls() spr(1, 0, 0, 0, 1, 0, 0, 3, 3) for i = 0,23 do for j = 0,23 do local c = peek4(i*WIDTH+j) if c > 0 then local x = i - 11.5 local y = 11.5 - j local z = sqrt(12^2 - x^2 -y^2) local n = Vec.new(x, y, z): normalise() local p = {i=j, j=i, n=n, c=c} table.insert(self.elements, p) end end end end, update = function(self) self.ax = -PI * (1-player.health/100) self.light = Vec.rotatex(Vec.new(0,0,1), self.ax) -- if btnp(2) then self.light:rotatex(-0.1)end -- if btnp(3) then self.light:rotatex(0.1) end for i,p in ipairs(self.elements) do p.f = Vec.dot(p.n, self.light) end end, draw = function(self) for i,p in ipairs(self.elements) do if p.f > -0.3 then pix(p.i+self.x, p.j+self.y, p.c) else pix(p.i+self.x, p.j+self.y, 0) end end end } Ps = { dt = 0.1, particles = {}, minlife = 30, maxlife = 60, new = function(x, y) local p = {x=x, y=y, vy=0, c=15, fac=0} p.g = -3*random() -- p.g = 0 p.dt = 1/random(Ps.minlife, Ps.maxlife) setmetatable(p, Ps.mt) table.insert(Ps.particles, p) return p end, mt = { __index = { update = function(self) self.vy = self.vy + self.g * Ps.dt self.y = self.y + self.vy * Ps.dt self.fac = self.fac + self.dt end, draw = function(self) pix(self.x, self.y, self.c) end, } }, emit = function(id, x, y) local addr = 0x8000 + id*64 for i = 0,7 do for j = 0,7 do if peek4(addr + i*8 + j) > 0 then Ps.new(x+j, y+i) end end end end, update = function(self) -- if mouse.md then -- Ps.new(mouse.x, mouse.y) -- end for i,p in ipairs(self.particles) do if p.fac > 1 then table.remove(self.particles, i) end end for i,p in ipairs(self.particles) do p:update() end end, draw = function(self) for i,p in ipairs(self.particles) do p:draw() end end } level = { current = 1, score = 0, lvls = { {n=1, list={224, 225, 226, 227, 228}, rate=0.02}, {n=2, list={224, 225, 226, 227, 228}, rate=0.03}, {n=3, list={224, 225, 226, 227, 228}, rate=0.04}, {n=4, list={224, 225, 226, 227, 228, 229}, rate=0.05}, {n=5, list={224, 225, 226, 227, 228, 229}, rate=0.06}, {n=6, list={224, 225, 226, 227, 228, 229}, rate=0.07}, {n=7, list={224, 225, 226, 227, 228, 229, 230}, rate=0.08}, {n=8, list={224, 225, 226, 227, 228, 229, 230}, rate=0.09}, {n=9, list={224, 225, 226, 227, 228, 229, 230}, rate=0.10}, {n=10, list={224, 225, 226, 227, 228, 229, 230, 231}, rate=0.11}, {n=11, list={224, 225, 226, 227, 228, 229, 230, 231}, rate=0.12}, {n=12, list={224, 225, 226, 227, 228, 229, 230, 231}, rate=0.13}, {n=13, list={224, 225, 226, 227, 228, 229, 230, 231, 232}, rate=0.14}, {n=14, list={224, 225, 226, 227, 228, 229, 230, 231, 232}, rate=0.15}, {n=15, list={224, 225, 226, 227, 228, 229, 230, 231, 232}, rate=0.16}, }, draw = function(self) print('level ' .. self.current, 0, 130, 1) -- local w = print('score ' .. self.score, 0, -6) -- print('score ' .. self.score, WIDTH-w, 130, 1) end, setlevel = function(self, n) if n <= 15 then if mathFun.isin(n, {4, 7, 10, 13}) then for i = 1,10 do local b = Bubble.particles[1] Ps.emit(b.id, b.x-4, b.y-3) b:remove() end sfx(49) end local lv = self.lvls[n] self.current = lv.n Bubble.list = lv.list player.rate = lv.rate else self.current = self.current + 1 player.rate = player.rate + 0.01 end end } scene = { title = { load = function(self) music(0) Bubble:load() player.rate = 0.05 pot.mo = false pot.id = 0 end, tic = function(self) if btnp(4) then scene.current = scene.game scene.game:load() return end player:update_health() moon:update() Bubble:update() box:update() cls() map() character:draw() moon:draw() Bubble:draw_empty() pot:draw() spr(160, 48, 96, 0, 1, 0, 0, 4, 3) fire:draw() spr(304, W2-64, 60+time.ts%2, 0, 1, 0, 0, 16, 3) if (time.t//700)%2 == 0 then scene.credit.print_center("Press 'z' to start", 124) end time:draw() end }, game = { load = function(self) level.score = 0 player.t = 1 player.level = 1 player.health = 100 player.rate = 0.01 level:setlevel(1) pot.id = mathFun.randlist(Bubble.list) end, tic = function(self) -- if player.health <= 0 or btnp(4) then if player.health <= 0 then scene.current = scene.credit scene.credit:load() return end Bubble:update() box:update() pot:update() player:update() moon:update() Ps:update() cls() map() character:draw() moon:draw() Bubble:draw() box:draw() pot:draw() Ps:draw() fire:draw() level:draw() time:draw() -- mouse:draw() player:draw() end }, credit = { id1 = {40, 42}, id2 = {88, 90}, texts = {'Game Over', 'Thank you for playing', "Press 'z' to continue"}, load = function(self) self.t = 0 music() sfx(50) end, tic = function(self) if btnp(4) then -- if mouse.mp and self.t > 480 then scene.current = scene.title scene.title:load() return end cls() map(30, 0) self.print_center(self.texts[(self.t//240)%3+1], 22+time.ts%2) self.print_center('Score: ' .. level.score, 30+time.ts%2) self.print_credit(97) self.t = self.t + 1 local k = (time.t//500)%2 spr(self.id1[k+1], 112, 48, -1, 1, 0, 0, 2, 3) spr(self.id2[k+1], 88, 56, -1, 1, 1, 0, 2, 2) spr(self.id2[k+1], 136, 56, -1, 1, 0, 0, 2, 2) spr(44, 127+k, 41, -1, 1, 0, 0, 2, 2) spr(44, 97-k, 41, -1, 1, 1, 0, 2, 2) end, print = function(text, x, y, c1, c2) print(text, x, y+1, c1) print(text, x, y, c2) end, print_center = function(text, y) local w = print(text, 0, -6) scene.credit.print(text, W2-w/2, y+1, 6, 15) end, print_credit = function(y) scene.credit.print('Credits', 0, y, 6, 15) scene.credit.print('Programming', 0, y+8, 6, 15) scene.credit.print('Concept art', 0, y+16, 6, 15) scene.credit.print('Pixel art', 0, y+24, 6, 15) scene.credit.print('Music & SFX', 0, y+32, 6, 15) local x = 70 scene.credit.print('@pke1029', x, y+8, 2, 15) scene.credit.print('@Gs_presents', x, y+16, 9, 15) scene.credit.print('@pke1029', x, y+24, 2, 15) scene.credit.print('@starling', x, y+32, 10, 15) end } } scene.current = scene.title moon:load() scene.current:load() function TIC() time:update() mouse:update() scene.current:tic() -- time:draw() -- mouse:draw() -- player:draw() end