0,]']ą>Sď}W˙Íu§đp8ˇd%qy)6o;]ÉAŚösď÷ôôô”°ÂVl†315 then dSfxChirp(self.t) self.speech.lastChirp = random(3,4) else self.speech.lastChirp = self.speech.lastChirp-1 end local mlText=MLText:new(self.speech.text, SCRW*.6, 2) self.postDraw = function() drawSpeechBubble(mlText, 4,3, pScrO) end end end function drawSpeechBubble(mlText, mX,mY, pActor) local bW = mX + mlText:getW() + mX local bH = mY + mlText:getH() + mY local bX=pActor.x local bY=pActor.y-60 local xIdeal,scrMX=30,8 local scrMY=8 if bX <= SCRWH then -- print to the right bX=bX+xIdeal if bX+bW>SCRW-scrMX then bX=SCRW-scrMX-bW end t1x,t1y = pActor.x + 10, pActor.y - 20 t2x,t2y = bX + 5, bY + bH t3x,t3y = bX + 15, bY + bH else -- print to the left bX=bX-xIdeal-mlText:getW() if bX= self.startTime) end function StageSync:draw() dCls(0) local d=SCRHH*.8 local a=self.timeF-self.microAdjust/60 local x,y=-cos(a)*d,sin(a)*d local cSize = 60 dElliF(dPMake(SCRWH,SCRHH),dPMake(cSize,cSize),3) line(SCRWH,SCRHH,SCRWH+x,SCRHH+y,7) local count=flrdiv(self.startTime-self.adjustedTime,1) local w=dPrintW(count) dPrint(count,dPMake(0,0),12) local drawP=dPMake(SCRWH-w*5/2,SCRHH-5*5/2) for y=0,5 do for x=0,w do local c=dPixGet(dPMakeS(x,y),0) if c>0 then dRectF(dPAdd(drawP, x*5,y*5),dPMake(4,4),c) end end end dRectF(dPMake(SCRWH,SCRH-10),dPMake(self.microAdjust*2,4),2) if self.flash then dRectF(dPMake(0,0), dPMake(SCRW,16), 7) end end end rift_stagescript=function() StageScript = {} StageScript.__index = StageScript function StageScript:new(script, scene) local o = {} setmetatable(o, StageScript) o.script = script o.scene = scene o.scriptItem = 1 o.ticksToPass = 0 o.isScriptAllRead = false return o end -- If there is a duration or tstamp, then decode it, otherwise return nil function decodeDuration(si) if si.d ~= nil then return si.d end return nil end -- return boolean [isFinished (for script)] function StageScript:update() if self.ticksToPass > 0 then self.ticksToPass = self.ticksToPass - 1 else -- Read all non-blocking script items... repeat if self.scriptItem > #self.script then self.isScriptAllRead = true else self.ticksToPass = self:doScriptItem() self.scriptItem = self.scriptItem + 1 end until(self.ticksToPass > 0 or self.isScriptAllRead) end self.scene:update() return (self.isScriptAllRead and self.ticksToPass == 0) end -- return boolean [doTimeTick] function StageScript:doScriptItem() local si = self.script[self.scriptItem] if si.p ~= nil and si.p ~= PLATFORM then return 0 end local doTimeTicks = 0 if si.a == 'wait' then doTimeTicks = decodeDuration(si) elseif si.a == 'omake' then self.scene:objMake(si) elseif si.a == 'cset' then self.scene:camSet(si.x) elseif si.a == 'cmove' then doTimeTicks = self.scene:camMove(si.x, decodeDuration(si)) elseif si.a == 'speak' then doTimeTicks = self.scene:speak(si.c, si.t, decodeDuration(si)) elseif si.a == 'aflag' then self.scene:aflag(si) elseif si.a == 'aanim' then self.scene:anim(si.c, si.m) elseif si.a == 'tstamp' then -- self.scene:timeStamp(si.n) elseif si.a == 'chyron' then self.scene:doChyron(si.t) elseif si.a == 'effect' then self.scene:effectSet(si.k,si.e) else trace("Unknown script action: " .. si.a) end if doTimeTicks == nil then trace("Script item " .. self.scriptItem .. " has no duration or tstamp") end return doTimeTicks end function StageScript:draw() self.scene:draw() end end rift_shimtic80=function() PI=math.pi TAU=PI*2 SCRW,SCRH,SCRWH,SCRHH=240,136,120,68 SMULT=1.3 BG_COL=8 MIDX=120 MIDY=68 PLATFORM='t' OTHER_PLATFORM='PICO-8' OBJSCREEN_W=80 OBJSCREEN_H=60 HOST_F="Katie O'Tic" HOST_Fs="Katie" HOST_M="Mick Atee" HOST_Ms="Mick" HOST_S="Scienticst" -- default to PICO-8's 0..1 rather than 0..PI*2 sin=function(a) return -math.sin(a*TAU) end cos=function(a) return math.cos(a*TAU) end atan2=function(a,b) return (math.atan2(-a,-b)/PI+1)/2 end sqrt=function(a) return math.sqrt(a) end min,max,abs=math.min,math.max,math.abs -- returns the current second of the hour function getTSync() return tstamp()%(60*60) end function tblAdd(t, v) return table.insert(t, v) end function tblShift(t) return table.remove(t, 1) end function tblSort(t, fn) return table.sort(t, fn) end function flrdiv(a,b) return a//b end function dFloor(a) return math.floor(a) end function dCeil(a) return math.ceil(a) end function dPMake(x,y) return {x=x,y=y} end -- Multiplies on TIC-80 function dPMakeS(x,y) return {x=dFloor(x*SMULT+.5),y=dFloor(y*SMULT+.5)} end function dPAdd(p,dx,dy) return {x=p.x+dx,y=p.y+dy} end function dPAddS(p,dx,dy) return {x=dCeil(p.x+dx*SMULT),y=dCeil(p.y+dy*SMULT)} end function dCls(c) cls(c) end function dPixSet(p, c) pix(p.x,p.y,c) end function dPixGet(p, c) return pix(p.x,p.y) end function dRectF(p,d,c) rect(p.x,p.y,d.x,d.y,c) end function dElliF(p,d,c) elli(p.x,p.y,d.x,d.y,c) end function dTriF(p0,p1,p2,c) tri(p0.x,p0.y,p1.x,p1.y,p2.x,p2.y,c) end function dSubStr(s,i,j) return s:sub(i,j) end -- Takes a string, returns table function dSplitTextByChar(s, ch) local o={} local last=1 for i=1,#s do if s:sub(i,i) == ch then o[#o+1] = s:sub(last,i-1) last=i+1 end end if last < #s then o[#o+1] = s:sub(last,#s) end return o end -- Takes a string, returns table function dSplitTextByNewlines(s) return dSplitTextByChar(s, "\n") end -- Takes a string, returns table function dSplitTextBySpaces(s) return dSplitTextByChar(s, " ") end function dPrintW(line) return print(line,0,140,0) end function dPrintH(lines, gutterH) local textH = 5 return #lines * textH + (#lines - 1) * gutterH end -- Print a line of text function dPrint(l,p,c) print(l,p.x,p.y,c) end function dBtn(i) local sw={2,3,0,1,4,5} return btn(sw[i+1]) end function dBtnP(i) local sw={2,3,0,1,4,5} return btnp(sw[i+1]) end function dModfr(x) _, r = math.modf(x) return r end -- Convert to seconds to match PICO-8 function dTime() return time()/1000 end function random(start,range) return math.random(start,start+range) end function dMusic() if HAS_SOUND then music(0,0,0,false) end end function dSfxChirp(t) if HAS_SOUND then local pitch if t == "katie" then pitch=random(60,10) elseif t == "mick" then pitch=random(40,10) elseif t == "scientist" then pitch=random(30,15) elseif t == "ps" then pitch=random(35,10) end sfx(0,pitch,2,0,10) end end end rift_shapes=function() -- 'e': ELLI xc,yc w,h c -- 'r': RECT xc,yc w,h c -- 't': TRI xc,yc x1,y1 x2,y2 x3,y3 c -- t:type -- c: -- m:mouth (0-1) function getPrimsActor(t,c) local fns={ katie=getPrimsKatie, mick=getPrimsMick, scientist=getPrimsScientist, ps=getPrimsPS } return fns[t](c) end function getPrimIris(xc,yc,w,h,a,d) return {'e', xc+cos(a)*d*w,yc+sin(a)*d*h, 2,2, 0} end function getPrimsHat(w,y,b) local p={ {'t', 0,y, -w-1,0, w+1,0, 0,-32, 15}, {'t', 0,y-30, 0,-2, 0,10, w+2,17-b*5+2, 15}, {'t', 0,y, -w,0, w,0, 0,4, 2}, {'t', 0,y, -w,0, w,0, 0,-30, 2}, {'t', 0,y-30, 0,0, 0,10, w,17-b*5, 2}, {'e', w-2,y-b*5-12, 3,3, 12}, } for i=-4,4 do local xb=i*(w/4) local yb=y+cos(i/(12))*3 p[#p+1]={'e', xb,yb, 3,3, 12} end return p end -- Katie O'Tic (TV Presenter) function getPrimsKatie(c) local headY=-7+c.m*2 local bunY=headY-19 local eyeY=headY-3 local mouthY=headY+9+cos(c.m/19)*2 local p={ {'t', 0,25, -20,-20, 20,-20, 0,50, 5}, -- dress {'e', 0,headY, 20,17, 4}, -- face {'e', -7,eyeY, 4,6, 12}, -- left eyeball getPrimIris(-7,eyeY,3,4,c.ela,c.eld), -- left iris {'e', 7,eyeY, 4,6, 12}, -- right eyeball getPrimIris(7,eyeY,3,4,c.era,c.erd), -- right iris {'e', sin(c.m/26)*2,mouthY, 1+c.m*3,1+c.m*3, 2}, -- mouth {'e', 8,bunY, 5,5, 0}, -- hair bun } return c.f['hat'] and tblAppend(p, getPrimsHat(15,headY-13,c.m)) or p end -- Mick McAtee (Also a TV Presenter) function getPrimsMick(c) local headY=-15+c.m*2 local hairY=headY-28+c.m*2 local eyeY=headY-5 local mouthY=0 local p={ {'r', 0,37, 38,60, 8}, -- jacket {'r', 0,headY, 30,50, 4}, -- face {'e', -7,eyeY, 6,3, 12}, -- left eyeball getPrimIris(-7,eyeY,4,1,c.ela,c.eld), -- left iris {'e', 7,eyeY, 6,3, 12}, -- right eyeball getPrimIris(7,eyeY,4,1,c.era,c.erd), -- right iris {'t', 0,hairY, -20,0, -5,0, -20,10, 0}, -- left hair {'t', 0,hairY, -5,0, 20,0, 20,10, 0}, -- right hair {'r',0,mouthY,30,1+c.m*4,1}, -- mouth } return c.f['hat'] and tblAppend(p, getPrimsHat(20,headY-23,c.m)) or p end -- A Scienticst (Resident Hexpert) -- m=0-1 function getPrimsScientist(c) local headY=-15+c.m*2 local eyeY=headY-1 local mouthY=headY+15 local fluffY=headY-11 local p={ {'e',0,40,30,60,12}, -- labcoat {'e',0,headY,18,25,4}, -- head {'e',-8,eyeY,5,5,12}, -- left eyeball getPrimIris(-8,eyeY,3,3,c.ela,c.eld), -- left iris {'e',8,eyeY,5,5,12}, -- right eyeball getPrimIris(8,eyeY,3,3,c.era,c.erd), -- right iris {'e',-16,fluffY,2,6,12}, -- left fluff {'e',16,fluffY,2,6,12}, -- right fluff {'e',0,mouthY,1+6*c.m,1+3*c.m,0}, -- mouth } return c.f['hat'] and tblAppend(p, getPrimsHat(20,headY-15,c.m)) or p end function getPrimsPS(c) local headY=-c.m*1.5 local p={ {'r',0,headY+45, 35,50, 0}, -- shirt {'e',0,headY-5, 15,25, 0}, -- hair {'r',0,headY+20, 28,30, 0}, -- hair {'e',0,headY, 14,20,4}, -- face {'r',-14,headY-2, 5,10, 4}, -- left ear {'r',14,headY-2, 5,10, 4}, -- right ear {'r',0,headY-8, 32,2, 0}, -- glasses top {'r',-7,headY-5, 10,9, 0}, -- glasses rim left {'r',7,headY-5, 10,9, 0}, -- glasses rim right {'r',-7,headY-4, 7,5, 4}, -- glasses hole left {'r',7,headY-4, 7,5, 4}, -- glasses hole right {'e',-7,headY-4, 3,2, 12}, -- eyeball left {'e',7,headY-4, 3,2, 12}, -- eyeball right {'e',-7,headY-4, 1,1, 0}, -- iris left {'e',7,headY-4, 1,1, 0}, -- iris right {'e',-2,headY+6, 1,1, 3}, -- nostril left {'e',2,headY+6, 1,1, 3}, -- nostril right {'r',0,12, 10,2+c.m, 1}, -- mouth {'t',-6,headY-18, 0,0, 10,-8, 18,8, 4}, -- fringe {'t',0,headY+20, -10,0, 10,0, 0,10, 4}, -- neck } return c.f['hat'] and tblAppend(p, getPrimsHat(14,headY-17,c.m)) or p end function getPrimsTree() return { {'t',0,0, 0,-50,30,50,0,50, 5}, {'t',0,0, 0,-50,-30,50,0,50, 6}, } end function getPrimsTreeBaubles() return { {'e',-7,0, 3,3,2}, {'e',-4,-20, 3,3,3}, {'e',10,13, 3,3,4}, {'e',6,-9, 3,3,7}, {'e',14,37, 3,3,8}, {'e',-11,27, 3,3,9}, {'e',0,-50,2,1,4}, -- 'star' } end function getPrimsFireplace() return { {'r',0,30,30,30,15}, --hearth {'r',-20,25,10,40,13},--side {'r',20,25,10,40,13}, --side {'r',0,10,60,10,14}, --mantle {'t',0,10, 0,10,-10,34,10,34, 4}, --flame {'t',0,10, 0,20,-10,34,10,34, 3}, --flame } end function getPrimsCandleBridge() return { {'r',0,-5,2,10,4}, --candle 1 {'r',10,0,2,10,4}, --candle 2 {'r',-10,0,2,10,4}, --candle 2 {'r',20,2,2,8,4}, --candle 3 {'r',-20,2,2,8,4}, --candle 3 {'t',0,-29, 0,25,-20,34,20,34, 14}, --candle bridge } end function getPrimsLeftStocking() return { --lstocking {'r',-18,13,15,5,12}, --stocking trim {'r',-18,22,10,15,2}, --stocking calf? {'e',-14,28,8,5,2}, --stocking toe? } end function getPrimsRightStocking() return { --rstocking {'r',18,13,15,5,12}, --stocking trim {'r',18,22,10,15,2}, --stocking calf? {'e',14,28,8,5,2}, --stocking toe? } end -- s is scale (default: 1) function drawPrims(prims,pScrO) for i,p in pairs(prims) do -- local p=prims[i] if p[1] == 'e' then dElliF(dPAddS(pScrO,p[2],p[3]),dPMakeS(p[4],p[5]),p[6]) elseif p[1] == 'r' then local pC=dPAddS(pScrO,p[2],p[3]) dRectF(dPAddS(pC,-p[4]/2,-p[5]/2),dPMakeS(p[4],p[5]),p[6]) elseif p[1] == 't' then local pC=dPAddS(pScrO,p[2],p[3]) dTriF(dPAddS(pC,p[4],p[5]),dPAddS(pC,p[6],p[7]),dPAddS(pC,p[8],p[9]),p[10]) end end end end rift_camera=function() Camera = {} Camera.__index = Camera function Camera:new(x) local o = {} setmetatable(o, Camera) o.pos = {x=x} o.movement = nil return o end function Camera:set(x) self.movement = nil self.pos.x = x end function Camera:get() return {x=self.pos.x} end function Camera:move(x, d) self.movement = { t=0, duration=d, fromPos={x=self.pos.x}, toPos={x=x}, } end function Camera:update() if self.movement then self.movement.t=self.movement.t+1 local fr = self.movement.t/self.movement.duration self.pos.x = self.movement.fromPos.x + (self.movement.toPos.x - self.movement.fromPos.x) * fr if self.movement.t >= self.movement.duration then self.movement = nil end end end end rift_stagecredits=function() StageCredits = {} StageCredits.__index = StageCredits CREDITS={{ t="The Fantasy Console News Network", l="\nThanks you for tuning in\n\nLovely to see you\n\nWe hope you enjoyed!", }, { t='Crew', l="Director and Camera Operator\n\njtruk\n\n-\n\nAV and Set Decoration\n\nDave84\n\n-\n\nPico Triangle Code\n\nMantratronic\n\n-\n\nPicowide font idea\n\nLokiStriker\n\n-\n\nPS\n\nHimself", }, { t='Greetings', l="Inercia\nField-FX\nByteJammers\nRiFT\nSlipstream\nps\nsuperogue\nZep\nNesbox", }, { t='Saluting the TCC Class of 2022!', l=" \n0xSim\nadr\nalia\nawildbrysen\nbeejay\nbenthor\nbrendan\ndnull\nevolutional\nFarbs\n" .. "ghostlevel\ngigabates\ngoleztrol\nHeNeArXn\nivanjoukov\njoeygibson\njtruk\njuni\n" .. "koorogi\nleft_adjoint\nLiquidream\nluchak\nlustlion\nmario_afk\nmarqueedesign\n" .. "maxotaf\nmdales\nmike\nneauoire\nnewobject\nnihilazo\nnobuh\npancelor\npauljan\n" .. "pixelbath\npopolon\nqbi_\nremi\nresevoir\nsbrt\nsellorm\nsergrass\nsntcns\nspook\n" .. "tanoxyz\nTruck\nwhatabouttheken\nWillFlux\nwrench\nz303" }} function StageCredits:new() local o = {} setmetatable(o, StageCredits) o.blocks = {CreditBlock:new(CREDITS[1])} o.thisBlock = 1 o.isFinished = false o.time=0 return o end function StageCredits:update() for _,b in pairs(self.blocks) do if not b:update() then self.thisBlock = self.thisBlock + 1 if self.thisBlock > #CREDITS then self.isFinished = true else self.blocks[1] = CreditBlock:new(CREDITS[self.thisBlock]) end end end self.time=self.time+1 return self.isFinished end function StageCredits:draw() dCls(6) --[[ local strs={"Tiny Code Christmas", "tcc.lovebyte.party", "LOVEBYTETCC"} for i=0,5 do local s=strs[i%#strs+1] local y=(100-self.time*.3+150/6*i)%150-10 local w=dPrintW(s) local x=SCRWH-w/2+sin(self.time*.0008+i*.63)*80 +cos(.82+self.time*.0006+i*.39)*80 dPrint(s, dPMake(x, y), 7) end --]] for _,b in pairs(self.blocks) do b:draw() end end end rift_tcc2023script=function() -- Actions -- immediate actions execute and don't block -- duration actions execute and block for the duration -- p=platform (t, p, or omitted) -- a='omake' (immediate) c=class k=key t=type x,z=position -- a='cset' (immediate) x=position -- a='cmove' (duration) x=position (ts=tstamp, d=duration, or default) -- a='tstamp' (immediate) id (must be unique) -- a='wait' (duration) t=time -- a='speak' (duration) c=character t=text (ts=tstamp, d=duration, or default) -- a='chyron' (immediate) t=text -- a='effect' (immediate) e=effect SCRIPT = { {a='omake',k='mick',c=ObjActor,t='mick',x=-50,z=1}, {a='omake',k='katie',c=ObjActor,t='katie',x=50,z=1}, {a='omake',k='scientist',c=ObjActor,t='scientist',x=300,z=1}, {a='omake',k='screen',c=ObjScreen,e='blank',w=OBJSCREEN_W,h=OBJSCREEN_H,x=225,z=.9}, {a='wait',d=60}, {a='speak',c='katie',t="Hello!"}, {a='speak',c='mick',t="HELLO!"}, {a='speak',c='katie',t="And welcome to Fantasy Console News"}, {a='aanim',c='mick',m={ela=0,eld=1,era=0,erd=1}}, {a='speak',c='katie',t="I'm your host"}, {a='chyron',t=HOST_F}, {a='speak',c='katie',t=HOST_Fs,d=100}, {a='aanim',c='mick',m={eld=0,erd=0}}, {a='aanim',c='katie',m={ela=0.5,eld=1,era=0.5,erd=1}}, {a='speak',c='mick',t="And I'm your other host"}, {a='chyron',t=HOST_M}, {a='speak',c='mick',t=HOST_Ms,d=100}, {a='aanim',c='katie',m={eld=0,erd=0}}, {a='speak',c='mick',t="Today we've got an exciting feature"}, {a='speak',c='katie',t="It's technology"}, {a='speak',c='mick',t="It's community"}, {a='speak',c='katie',t="It's an opportunity to get involved"}, {a='speak',c='mick',t="To be creative"}, {a='speak',c='katie',t="To learn"}, {a='speak',c='mick',t="And it'll be a lot of fun!"}, {a='speak',c='mick',t="First, something you might know about"}, {a='speak',c='mick',t="...but if not..."}, {a='speak',c='mick',t="...hold onto your seat!"}, {a='speak',c='mick',t="THE DEMOSCENE!"}, {a='speak',c='katie',t="Way back when the internet was a pixel in Tim Berners-Lee's eye."}, {a='speak',c='katie',t="Smart folks would make things to show off their skills."}, {a='speak',c='katie',t="Trying to create amazing effects, for satisfaction, and glory."}, {a='speak',c='mick',t="One challenge would be squeezing lots into a tiny program."}, {a='speak',c='mick',t="We now call this 'sizecoding'"}, {a='speak',c='katie',t="Oh, that'd be fun to try!"}, {a='speak',c='mick',t="It sure would!"}, {a='speak',c='katie',t="And you can learn how, with..."}, {a='speak',c='katie',t="TINY"}, {a='speak',c='katie',t="CODE"}, {a='speak',c='katie',t="CHRISTMAS"}, {a='speak',c='mick',t="Oooooh!"}, {a='speak',c='mick',t="So... How?"}, {a='speak',c='katie',t="We ought to introduce Fantasy Consoles..."}, {a='speak',c='katie',t="These are cute, pretend machines which run inside your computer."}, {p='p',a='speak',c='katie',t="One of these is called PICO-8 and our hexpert will tell us more.",p=120}, {p='t',a='speak',c='katie',t="One of these is called TIC-80 and our hexpert will tell us more.",p=120}, {a='cmove',x=250}, {a='speak',c='scientist',t="Oh, heh, hello."}, {a='omake',k='fireplace',c=ObjDecor,t='fireplace',x=0,z=.8}, {a='chyron',t=HOST_S}, {p='p',a='speak',c='scientist',t="Yes, yes. Let me tell you about PICO-8",d=120}, {p='p',a='speak',c='scientist',t="It has a feisty 128x128 screen with 16 colours.",d=120}, {p='p',a='speak',c='scientist',t="4 channel beepy-boop sound.",d=120}, {p='p',a='speak',c='scientist',t="Built-in code editor, graphics and music tools.",d=120}, {p='p',a='speak',c='scientist',t="And it runs on Lua, a friendly programming language.",d=120}, {p='p',a='speak',c='scientist',t="There's also a Fantasy Console called TIC-80",d=120}, {p='t',a='speak',c='scientist',t="Yes, yes. Let me tell you about TIC-80",d=120}, {p='t',a='speak',c='scientist',t="It has a bubbly 240x136 screen with 16 colours.",d=120}, {p='t',a='speak',c='scientist',t="4 channel bloopy-beep sound.",d=120}, {p='t',a='speak',c='scientist',t="Built-in code editor, graphics and music tools.",d=120}, {p='t',a='speak',c='scientist',t="And it can run Lua, a friendly programming language.",d=120}, {p='t',a='speak',c='scientist',t="There's also a Fantasy Console called PICO-8",d=120}, {a='speak',c='scientist',t="They're free and easy to get started with."}, {a='speak',c='scientist',t="(Though the developers like donations!)"}, {a='speak',c='scientist',t="And they're a lot of fun!"}, {a='cmove',x=0}, {a='speak',c='katie',t="So... what's Tiny Code Christmas?"}, {a='speak',c='mick',t="Great question!"}, {a='speak',c='mick',t="It's a series of twelve tutorials, to make pretty things on a screen."}, {a='speak',c='mick',t="Suitable for all, but experience will help, and tiny coders may need an adult!"}, {a='speak',c='mick',t="Each day, you'll learn something new, and make something new."}, {a='speak',c='mick',t="And by the end of the twelve days, you'll be inspired to make your own things."}, {a='speak',c='katie',t="What sort of things will I be making?"}, {a='aflag',c='scientist',f='hat',v=true}, {a='cmove',x=250}, {a='omake',k='candles',c=ObjDecor,t='candles',x=0,z=.801}, {a='aanim',c='scientist',m={ela=0.5,eld=1,era=0.5,erd=1}}, {a='speak',c='scientist',t="here's one - it's called a plasma."}, {a='effect',k='screen',e='plasma'}, {a='wait',d=100}, {a='aanim',c='scientist',m={eld=0,erd=0}}, {a='aflag',c='katie',f='hat',v=true}, {a='cmove',x=100}, {a='speak',c='katie',t="Neat!"}, {a='cmove',x=250}, {a='omake',k='leftstocking',c=ObjDecor,t='leftstocking',x=0,z=.802}, {a='speak',c='scientist',t="Yes! And you'll learn how to make your own!"}, {a='speak',c='scientist',t="And the best thing is sharing your creations on social networks."}, {a='speak',c='scientist',t="And seeing everyone else's"}, {a='speak',c='scientist',t="Last year's participants made such a great community!"}, {a='cmove',x=0}, {a='speak',c='mick',t="So, what else will we make?"}, {a='cmove',x=250}, {a='effect',k='screen',e='tunnel'}, {a='aanim',c='scientist',m={ela=0.5,eld=1,era=0.5,erd=1}}, {a='speak',c='scientist',t="There are lots of effects, like this tunnel."}, {a='wait',d=100}, {a='aanim',c='scientist',m={eld=0,erd=0}}, {a='aflag',c='mick',f='hat',v=true}, {a='cmove',x=0}, {a='speak',c='katie',t="Ooh, that's trippy!"}, {a='speak',c='mick',t="And I can take part with PICO-8 or TIC-80?"}, {a='effect',k='screen',e='blank'}, {a='speak',c='mick',t="How do I choose between them?"}, {a='cmove',x=250}, {a='omake',k='rightstocking',c=ObjDecor,t='rightstocking',x=0,z=.803}, {a='speak',c='scientist',t="It's personal preference. TIC might be easier, but PICO is cuter."}, {a='cmove',x=0}, {a='speak',c='mick',t="And if I really can't choose?"}, {a='omake',k='ps',c=ObjActor,t='ps',x=-150,z=1}, {a='cmove',x=-100}, {a='speak',c='ps',t="Why not both"}, {a='omake',k='tree',c=ObjDecor,t='tree',x=135,z=.8}, {a='wait',d=20}, {a='aanim',c='mick',m={ela=0.5,eld=1,era=0.5,erd=1}}, {a='wait',d=80}, {a='aanim',c='mick',m={ela=0,eld=1,era=0,erd=1}}, {a='cmove',x=250}, {a='aanim',c='scientist',m={ela=0.25,eld=1,era=0.25,erd=1}}, {a='speak',c='scientist',t="That's how they get you!"}, {a='aanim',c='mick',m={eld=0,erd=0}}, {a='cmove',x=100}, {a='aanim',c='scientist',m={eld=0,erd=0}}, {a='speak',c='katie',t="But what if I took part last year, or I already know some of this stuff?"}, {a='cmove',x=250}, {a='speak',c='scientist',t="The TCC Elves already thought of that!"}, {a='speak',c='scientist',t="You can still take part, and there are advanced challenges"}, {a='cmove',x=0}, {a='speak',c='katie',t="So I can stretch my skills?"}, {a='speak',c='katie',t="Nice!"}, {a='speak',c='mick',t="So. If this sounds good, then make sure you're ready to take notes..."}, {a='cmove',x=250}, {a='speak',c='scientist',t="Yes, yes! Pen, pencil, or pixel."}, {a='cmove',x=0}, {a='omake',k='baubles',c=ObjDecor,t='baubles',x=135,z=.801}, {a='speak',c='katie',t="In summary:"}, {a='speak',c='mick',t="The demoscene is a community of people creating things on computers."}, {a='speak',c='katie',t="PICO-8 and TIC-80 are Fantasy Consoles, which are cute, pretend computers."}, {a='speak',c='mick',t="Tiny Code Christmas is a lovely, warm set of tutorials."}, {a='speak',c='katie',t="There's a big, friendly group of people, keen to help you play!"}, {a='speak',c='mick',t="That all sounds good."}, {a='speak',c='katie',t="Very good indeed."}, {a='speak',c='mick',t="Where do I find out more?"}, {a='speak',c='katie',t="There's a website..."}, {a='cmove',x=250}, {a='speak',c='scientist',t="(Are you ready to take those notes?)"}, {a='cmove',x=0}, {a='speak',c='katie',t="tcc.lovebyte.party"}, {a='speak',c='mick',t="Tiny Code Christmas runs from 11th-22nd December"}, {a='speak',c='katie',t="And you can share on the LOVEBYTETCC hashtag"}, {a='speak',c='mick',t="That's all for now!"}, {a='speak',c='katie',t="Hope you have a lovely, maybe Tiny, Christmas!"}, {a='cmove',x=250}, {a='speak',c='scientist',t="Bye!"}, {a='speak',c='scientist',t="Yes, yes. Bye!"}, {a='aflag',c='ps',f='hat',v=true}, {a='cmove',x=-120}, {a='speak',c='ps',t="Thanks for watching"}, {a='speak',c='ps',t="Bye bye everybody"}, {a='speak',c='ps',t="Have a great day!"}, {a='wait',d=50}, } end rift_mltext=function() MLText = {} MLText.__index = MLText -- Take in a string, and break it into lines (first \n, and then to fit maxW) function MLText:new(t, maxW, gutterH) local o = {} setmetatable(o, MLText) o.w, o.h = 0,0 o.gutterH = gutterH local strings = dSplitTextByNewlines(t) o.lines = {} for _,s in ipairs(strings) do lines, w = breakStringToFit(s, maxW) for _,l in ipairs(lines) do o.lines[#o.lines+1] = l end o.w = w > o.w and w or o.w end o.h = o.h + dPrintH(o.lines, o.gutterH) return o end function MLText:getW() return self.w end function MLText:getH() return self.h end function MLText:getLines() return self.lines end -- align = -1 (left), 0 (center), 1 (right) function MLText:print(pText,c,align) local x,y=pText.x,pText.y for _,l in ipairs(self.lines) do if align == 0 then x = pText.x - l.w / 2 elseif align == 1 then x = pText.x - l.w end dPrint(l.l,dPMake(x,y),c) y = y + 5 + self.gutterH end end -- return lines[l,w], maxWidth function breakStringToFit(s, maxW) local w = dPrintW(s) if w <= maxW then return {{l=s,w=w}},w -- fits one line. Cool beans. end local w = 0 local words = dSplitTextBySpaces(s) local lines = {} local line = "" repeat local newWord = tblShift(words) line = line .. (line=="" and "" or " ") .. newWord lineW = dPrintW(line) if lineW > maxW then -- break line, but put the word back lines[#lines+1] = {l=line,w=lineW} w = lineW > w and lineW or w line = "" end until #words == 0 if line ~= "" then lines[#lines+1] = {l=line,w=lineW} w = lineW > w and lineW or w end return lines, w end end rift_demo=function() Demo = {} Demo.__index = Demo function Demo:new() local o = {} setmetatable(o, Demo) o.iStage = 0 o.stage = StagePreprocess:new() o.cueNextStage = false return o end function Demo:setStage(doStage, stage) if doStage then self.stage=stage self.cueNextStage = false end end -- return boolean [isFinished] function Demo:update() while self.cueNextStage do self.iStage = self.iStage + 1 if self.iStage == 1 then self:setStage(DO_STAGE_CONFIG, StageConfig:new()) elseif self.iStage == 2 then self:setStage(DO_STAGE_SYNC, StageSync:new()) elseif self.iStage == 3 then self:setStage(DO_STAGE_IDENT, StageIdent:new()) elseif self.iStage == 4 then self:setStage(DO_STAGE_SCRIPT, StageScript:new(SCRIPT, Scene:new())) elseif self.iStage == 5 then self:setStage(true, StageCredits:new()) else return true end end self.cueNextStage = self.stage:update() return false end function Demo:draw() self.stage:draw() end end rift_objscreen=function() ObjScreen = Obj:new() -- e: effect key -- w, h function ObjScreen:new(o, c) o = o or Obj:new(o, nil, c.x,c.z, true) setmetatable(o, self) self.__index = self o.effectKey = c.e o.dims = dPMakeS(c.w,c.h) return o end function ObjScreen:setEffectKey(key) Obj.update(self) self.effectKey = key end function ObjScreen:update() Obj.update(self) end function ObjScreen:blank(scrPTL,dims) dRectF(scrPTL,dims, 2) end function ObjScreen:curve(scrPTL,dims) local hx,hy,iw,ih = dims.x/2,dims.y/2,dims.x*.4,dims.y*.4 dRectF(scrPTL,dims, 0) for i=0,20 do local a=(self.tAlive + i)*.01 local x,y=hx+sin(a)*iw,hy+cos(a*.3)*ih dPixSet(dPAdd(scrPTL,x, y), i) end end function ObjScreen:plasma(scrPTL,dims) local s = PLATFORM == 'p' and 2 or 1 local alive=self.tAlive*.01 for y=0,dims.y-1,s do local ySin=sin(y/50+alive)+sin(y/20) for x=0,dims.x-1,s do local c=sin(x/50+alive)+ySin dPixSet(dPAdd(scrPTL,x,y),dFloor(c*8)%16) end end end function ObjScreen:tunnel(scrPTL,dims) local s = PLATFORM == 'p' and 2 or 1 local alive=self.tAlive*.1 for y=0,dims.y-1,s do for x=0,dims.x-1,s do local ex,ey = x-dims.x/2,y-dims.y/2 local a = (atan2(ey,ex))*16 local d = 100/sqrt(ex*ex+ey*ey+1)+alive dPixSet(dPAdd(scrPTL,x,y),dFloor(a+d)%16) end end end function ObjScreen:_draw(scrPos) local scrPTL = dPAdd(scrPos,-self.dims.x/2,-self.dims.y/2) if scrPTL.x>SCRW or scrPTL.x+self.dims.x<0 then return end dRectF(dPAdd(scrPTL,-2,-2),dPAdd(self.dims,4,8), 0) -- stretch out monitor border if self.effectKey == 'curve' then self:curve(scrPTL,self.dims) elseif self.effectKey == 'plasma' then self:plasma(scrPTL,self.dims) elseif self.effectKey == 'tunnel' then self:tunnel(scrPTL,self.dims) else self:blank(scrPTL,self.dims) end end end rift_creditblock=function() CreditBlock = {} CreditBlock.__index = CreditBlock function CreditBlock:new(data) local o = {} setmetatable(o, CreditBlock) o.mlTitleText=MLText:new(data.t, MINSCRW*.7, 3) o.mlBodyText=MLText:new(data.l, MINSCRW*.7, 3) o.y=146 o.h=o.mlTitleText:getH() + o.mlBodyText:getH() + 60 o.visible=true return o end function CreditBlock:update() self.y=self.y-.65 if self.y+self.h+10<0 then self.visible=false end return self.visible end function CreditBlock:draw() if not self.visible then return end local pBox = dPMake(0, self.y) local y1=self.y + self.h - 1 local pBoxSz = dPMake(SCRW, self.h) dRectF(pBox, pBoxSz, 2) for x=-10,SCRW+10,20 do dTriF(dPMake(x, self.y-10), dPMake(x-10, self.y), dPMake(x+10, self.y), 2) dTriF(dPMake(x, y1+10), dPMake(x-10, y1), dPMake(x+10, y1), 2) end local lineX,lineY=SCRWH, self.y + 20 local pTitleText=dPMake(lineX,lineY) self.mlTitleText:print(dPAdd(pTitleText,1,1), 0, 0) self.mlTitleText:print(pTitleText, 12, 0) local lineX=SCRWH local yTitleGap=15 local pBodyText=dPMake(lineX,pTitleText.y+yTitleGap) self.mlBodyText:print(dPAdd(pBodyText,1,1), 0, 0) self.mlBodyText:print(pBodyText, 12, 0) end end rift_stageident=function() StageIdent = {} StageIdent.__index = StageIdent function StageIdent:new() local o = {} setmetatable(o, StageIdent) o.time = 0 o.zoomTime = 200 o.maxTime = 310 o.mlTitleText=MLText:new("FANTASY\nCONSOLE\nNEWS", MINSCRW*.7, 3) return o end function StageIdent:update() if self.time == 0 then dMusic() end self.time = self.time + 1 return (self.time > self.maxTime) end function StageIdent:draw() dCls(1) local nStripes=6 local stripeH=SCRH/nStripes for i=0,6 do local c=i%2==0 and 4 or 11 dRectF(dPMake(0,i*stripeH),dPMake(SCRW,stripeH),c) end local pTitleText=dPMake(SCRWH, SCRHH-self.mlTitleText:getH()/2) local m=min(self.time/self.zoomTime,1) local x,y,s=SCRWH,SCRHH,10+m*50 dElliF(dPMake(x,y),dPMake(s,s),15) local c={2,5,8} for i=1,3 do local a=self.time/100+i/3 local d=40+m*10 local x,y,s=SCRWH+cos(a)*d,SCRHH+sin(a*.7)*d,4 dElliF(dPMake(x,y),dPMake(s,s),c[i]) end self.mlTitleText:print(dPAdd(pTitleText,1,1),0, 0) self.mlTitleText:print(pTitleText,12,0) end end rift_stageconfig=function() StageConfig = {} StageConfig.__index = StageConfig OPTIONS = {{ key='sync', title="Platform Sync", detail="You can run this simultaneously with the " .. OTHER_PLATFORM .. " version.\nSet OFF unless you're doing that!", type="bool", }, { key='sound', title="Sound", detail="Do you want music and bloops?\nSet OFF if you have sound ON for the other platform", type="bool", }, { key='start', title="Start!", type="action", }} -- aTime: initial time, and time when A is pressed -- bTime: time when b is pressed function StageConfig:new(aTime, bTime) local o = {} setmetatable(o, StageConfig) o.iOptionSelected = 1 o.time = 0 o.autoStart = true OPTIONS[1].value = DO_STAGE_SYNC OPTIONS[2].value = HAS_SOUND return o end function StageConfig:update() isFinished = false if dBtnP(0) or dBtnP(1) or dBtnP(2) or dBtnP(3) or dBtnP(4) then self.autoStart = false end if dBtnP(2) and self.iOptionSelected > 1 then self.iOptionSelected = self.iOptionSelected - 1 elseif dBtnP(3) and self.iOptionSelected < #OPTIONS then self.iOptionSelected = self.iOptionSelected + 1 end local optType = OPTIONS[self.iOptionSelected].type if optType == "bool" then if dBtnP(0) then OPTIONS[self.iOptionSelected].value = false elseif dBtnP(1) then OPTIONS[self.iOptionSelected].value = true elseif dBtnP(4) then OPTIONS[self.iOptionSelected].value = not OPTIONS[self.iOptionSelected].value end else --if optType == "action" then if dBtnP(4) then isFinished = true end end self.time=self.time+1 if self.autoStart and self.time > 500 then isFinished = true end if isFinished then DO_STAGE_SYNC = OPTIONS[1].value HAS_SOUND = OPTIONS[2].value end return isFinished end function StageConfig:draw() dCls(0) local i=1 local y=20 local letterW=4.5*SMULT local heading="TINY CODE CHRISTMAS INVITE" for i=1,#heading do local x=SCRWH-((#heading-1)*letterW)/2+(i-1)*letterW local y=5+sin(i*.3+self.time*.03)*1+sin(i*.1+self.time*.012)*1 dPrint(dSubStr(heading,i,i), dPMake(x, y), i%2==0 and 6 or 2) end for k,option in pairs(OPTIONS) do if i==self.iOptionSelected then dPrint(">", dPMake(0, y), 4) end dPrint(option.title, dPMake(6, y), 12) local val = option.value if option.type == "bool" then val = (option.value) and "< on >" or "< off >" elseif option.type == "action" then val = "" end dPrint(val, dPMake(90, y), 4) if option.detail then local mlDetail = MLText:new(option.detail, SCRW - 53, 2) mlDetail:print(dPMake(8, y+8), 13, -1) y=y+mlDetail:getH() end i=i+1 y=y+16 end if self.autoStart then dPrint("AUTO: " .. 5 - flrdiv(self.time, 100), dPMake(SCRW-36, SCRH-10), 4) end end end rift_globals=function() MINSCRW=128 DO_STAGE_CONFIG=true DO_STAGE_SYNC=false DO_STAGE_IDENT=true DO_STAGE_SCRIPT=true HAS_SOUND=true function tblAppend(t1, t2) for i=1,#t2 do t1[#t1+1] = t2[i] end return t1 end end rift_obj=function() Obj = {} function Obj:new(o, prims, x,z, isVisible) local o = o or {} setmetatable(o, self) self.__index = self o.isVisible = isVisible o.tAlive = 0 -- shift x starting pos a little, for perspective o.pos = {x=x,z=z} if o.pos.z then -- class constructor can make this nil?? o.pos.x = o.pos.x * o.pos.z end o.prims = prims return o end function Obj:get() return {x=self.pos.x} end function Obj:speak(text, duration) self.speech = { text=text, duration=duration, } end function Obj:update() self.tAlive = self.tAlive + 1 end function Obj:draw(camShiftX) if not self.isVisible then return end self:_draw(dPMake((self.pos.x + camShiftX)*SMULT + SCRWH, SCRHH+10)) end function Obj:_draw(pScrO) self:_drawPrims(pScrO) end function Obj:_drawPrims(pScrO) drawPrims(self.prims, pScrO) end end rift_stagepreprocess=function() StagePreprocess = {} StagePreprocess.__index = StagePreprocess function StagePreprocess:new() local o = {} setmetatable(o, StagePreprocess) return o end function StagePreprocess:update() return true end function StagePreprocess:draw() end end rift_shimtic80() rift_globals() rift_shapes() rift_mltext() rift_camera() rift_obj() rift_objactor() rift_objdecor() rift_objscreen() rift_scene() rift_creditblock() rift_stagepreprocess() rift_stageconfig() rift_stagesync() rift_stageident() rift_stagescript() rift_stagecredits() rift_demo() rift_tcc2023script() DEMO=Demo:new() function TIC() poke(0x3FFB,1) local isFinished = DEMO:update() DEMO:draw() if isFinished then trace("-> tcc.lovebyte.party") exit() end end