0,]0]>S}Wupe%qy)4o;]AsVl3ND@ BD4N03N>ND@0 ̓ ̙   """       "   """""3#""2#莈興mnffnnnhfnnfffhfmffnfffh"3"""3"3##"3#""""#3#2#2#2#"""""""!"!!!"!"!```f`f`f`f``fffnffnononfmof ލ݈펈"",",,,"","""",","""""""",","","""""","",""",""3"""ffffff`f``""  "2"""""2"#"""""##"#""""#2"""""2"!!!``f```fff``f" """"" """ """"",",,""","",""",,","""""  ݍݍݎݎ툈舎@4DDD4@40@DCDD4DDDDDDDDnnff``~nvf D@J D4 0@DCDD4DDDDDDDDD@4@@D04DD04DD@D@DDD@@@DDD4DDDDDDDDDD}wwwwwwwwwwwwwwwwwwwwxx}wwwwwwwwwwwwwwwww}wwwwwwwwwwwwwwwwwwww}wwwwwwwwwwwwwwwwwwwwww DD@D@DDD@@@DDD4DDDDDDDDDD0CCD0D44C@@@DD@DDDDDDDDDDDDDDDDDDDDD4׍}xwwxwwwxwwxxxwwxxw݈wwwwxww}wwxwww}www}wwwwwwwwwwwwwwwwwwwxxwwwwwwwwwwwwwwwwwwwwww 0 C@@@@DD@DDDDDDDDDDDDDDDDDDDDD4@@@@0DDDD0DDDDDDDDDDDDDCD4D4ݍ @4C0DDDD0DDDDDDDDDDDDDCD4D@D4"""" "" """"""""""""""""@@@@@@@@@@@@003PUUPPPPPPUbtututututututututtttttttttttttttttttttttttttutututuutututututututututuututuutuututututttututututututttttt tttttttttttt ttttuututututututututututuuututuututututubcbbtutututuutt tttttt ttututututututututututuututututuggtudededetututuut tt tutututusssstututuŐuttututuggtutututuu bttb tutuutgtutututuggtuddetutudedetuu tuutuŠtututuggtutututuwxwxtuu bb tuututututuggtu@tutu}}tuu  ssuttststutuggggtuuutttttttttttttttttttttututuu   ssssutttutttuuutu}bbbbbbu tutuutttututuuutu}tutuŴu utu bblluttutudedddedededdedtuutuutudeub butu  tussssjllutjtutugtuggssssssgtuuutututuuuutu  tututututuutzzzztututuuutuu uutu  tututututtutuuĵĵŴut|zztutubbedbbddetuuutuubbcccccccccccccuutu  tutuuuut{tutugtututugssstuuutudeddefŴututututututututuuutu tututuŴuuŴutedddedftututuuutututututututututuuututututuuuutttttttttttttttttttututututuuutututututututututuuututututuŴuuŴututututtututuwxtutuuututuu  ututuuuutututusstu}tutututututuuututuussututuŴŴuuŴutustutuvEEtutuuutuu utututuuuutubtututvtuwxwxtutuuututuuutututuŴuuŴutustutut}wxtutuuutuussutuuutututututututututututututuutudetutt}tututuuutuwxtututuuutuŴuutututututututututututututuutuEwxctuutu}tututuuutvwutuuuuutttttttututuuutututututututututututututuututtttttuwxtutututtututuuutxtuuccccccccccccccccccuuŴtututuuututustutttuuutuitutuuuuutututuuututubtututuiiyigggtuuutututuiyztutututuuŠŠŠŴuutututututuuutustutuyyyyyyzstututututuuuŃyyzjtuuuutututututuuutugtutu{y{|ututuuuutuyyyzztuuŴuututututuuutugtutufdebttututuuutuyyztuuccccccuututututuuutugtututututubtutuuuutu{|tuuŴuututututuuutugtutu@@tudfetutuuutudedfdeftuuŴuututuŴsssuutugdgtutuefddtutusdftutuuutututututututututuututututututŴuuŴŴsŴuututuututututututututtututuuuuuuuuuuuuutututututututututuuttutututtuttuuuŴsuututututututudefetutudtuuuuuuuuuuuututuuuuuuutuusŴuutugdddtututututudetutuuuttuutuuuuuuuuusuutug@tttttwxtututututututuuutututuuuuuuuuuuuuuuuuŴsŴuutueeefed@}tututuuuutttutuuuuuuuuuututuŴsuutuddtutuetuedftutuedfeddetutuutututututuuuuuuuuuutututusŴuutututututututtttttvtutututututututuuuuutu}}}tuuuuuuuuuuctututusuututwxtututudededddettuuuu}uuuuusssssssssstutututusŴuut@}dedetutututututututuuuu}tuuuuĴtutusuutuwxtutututututuuuuuuucccccccccuuututusŴuuutututu@tutututututututuuuuuututututuuuuuuuuuucuusssssssssssssssttutuuututututututututututututuuuuuututuuuuuuuuuuuuuututtututututututututututuuuuuutuuuuuu uŴuuuutuwutuwxtutusssgggggggtuuuuuutuuuuuuuuuuuutu}EtututugtuuuuuuuutuuuuŴuutututuutu}ttuttuttutututugggtuuuuutuuudefeftuuuutuuuutuutuEttuttutututututututututuuuuuuuutututututututututuuuŴuuuututututtsttutututututututututututuuuuuuuutututututututututuututututuuuuccccuututubbtutututututututututututututuuutuuuuututuuutututuuŴuuuststusssstuԴuuutubbtubbbtutufedetusstutuuuuuuuutututuuuututuccccccccccccuuuututututuututuusuutuggtubtugtututututusstututuutuututuututustuuuututttttttttttttttuŴuuuĴtutututuututuuuututuggtubugddtutusstutuuuuuuuutututuuuuuutttttttttttttuuuututututuututuuuututggtubtugttututuisstutututututuutututututututuuuuuutttttutttttttuŴuuuĴtutututuututuusuutuggggtuttutuyyzsstutuutututuutuututututustuuuuuutttttttttttttuuuututututuututuuuutuggtutudeddettuttutututututuututuuutututujituuuuuutttttttttttttuŴuuututututuututuuuutugEggggggtutttututusstutututututuututuutututuzyyjtuuuuuuttttttttttttuuuuuuusuututttutututttuututututututututtutututututututututuututusiyyyzuuuuutttttttttttttuŴuuuŴuuŴuuutututututttuututututututuuttututututututututututututuiyyyyzjŴuuuuutttttuttttttuuuustututututututututuuuutusttuttututustuyyyyyzzuuuuuuuutuggttttututusŴyyyyzzzŴussuuccccccuutubsbbsbttuttututututututuyyyzztusuutttttttttttttttttttttututubbttuggttututuŴ{|ttututtutututudedeedefttuttuttuttutuetutututututututututututututututtuttututttttttttttttttttttutututututututututututututututututututututututudegtugttututututubbbccccbbbbtutuwwggwwggiitututtutututututuwwggwwyyygtutututuututututututubtu}}{|gtutututututututu}gtuttutututuututututubtuwwbbwwtutututugtutututututututututubtuwwbbwwtutututugtutututututubbtugtututututututubbtututugtutututututubbbtutuiizzitututututusstutututubtutuyyyf{tutuuututubtutu{|tuwxwxtutututttuututtututuftutututtutttuttutututututtututututubtutututututututubbbccccbbtututututututututututututututututututututututuńtututututututututututututututututututututututustututututututu}}tutututututututudetututututututututututututututuddddedetutututututututu}tututututututututugbtutututututututututututututustututugggtututututututubbtuutututugggtututututututustututustutugbgŅtututututututututugtututututututututututututtutututututututututututututtutututututustututubisstututututututututututututubiyytututututuitutututututututubyyyztutututu tutuyyyjtututututututututututututubtututututuyyyztutututututuyyyztututututubbbbtustututubstutututus{|tutututututubsddftutucccctutus0eddeeddetubbtutututubututututututututuwxtututututututututututututututututututututututututututututututututututututututubutututututututututututututututututuŴtututututututututututututuńńtututututubtutututtututututututututututubcŝtututututututututuńtuggtubtutututututututŴutututututututuńńtuttututuŤbsftututututuututututututututututubbtutututuutututututuńtutututubbŴtututututututututututututututututubbtu78bbbtututututututututututututututuutututututuGHbbbtuőtutututuuttutuWXbbbbtutuŴuttutucbbőtuubbttutucbbbbbtuubttutucbbbbŒtuuttutubbsbbbtutututututututuwxtutuuttueeftuąbbbbtututututututuuttutubbbtutututuuttuŴtutuąbtutututututututuuttututucbbtututututututututututututuuttutxidetutcctubbbbtutututututututututututututututututututututututututututututuuttuiyyidetucbbbbccccbitutututututututututututututututututututututututututstutututuuttuyyyytutuctutubiyzjtutututututututututututututututututututututututututututututuuttufyyytutuccbbyyyziitututututututututututututututututututututututututututututututuutttu{|tututuŴcbyyyzyyyzjtutututututututututututututututututututututututututututututuuuuttubEtutucccbbbbbfdtuyyyzztutututututututututututututututututututututututututututututuuuttubtutuŴtusstututuŴtutututututututututututututututututututututuuuttubggtutuswutututututxstututututtutututututututututututututututututututuuuttutusbtu}tutututututututututututututututututututututututuuutudedededetutututututututututŴututututututututututututututututuuutututuōbbbbbbbbbstututututututututututututututututututututututututututututuuututudftutubbbbtutututututututututututututututututuutututututuuttuutututudetuŴtutututututututututututututututututuutututututuutututtuutututututututututututututututututututututututututututututututuutuutuututututtuuutututututututututututututututututttututututututututututututututuuttuttututtututtuutuutugtututututututututututututututututututututututututututututututututututututututuutututtutututuu c ]VPRZ\]]][Z[\\]^^^^___^^^^____ЙKDBBEGIJKLLLMMNNNOOOOOOOOOOOOO       d`Xiz,lM]d  @|`P@@0 " 000                         >>=< < >>>>>>>?LAAAAABBCDDEEFFFGGGGGGGHHHHHHH "        ?8888 7 7788888999 =`=P=P=P=>>>>>>>>?L;4 +  - . 7OX` >;>>??????????????????????????uP@ 0  :=?? ?? ??????????????????????  000@    #De gH( Tq 0@PPPPPPP@0T=` `  &@TewxЉЮЮЪЫЫЬЬS>)*;LLMMMM=s0lkZZ<9=>>?????????????????????? 04 M\lkjiBS  tP@23356778899:::;;;;;;<<<==>>>>?r !d  BS tP@BS  tP@    ЪqqabSTUEFGHYZZ[LT         ~\sPp@r@s0s0w0x yzz{{{|||}}~~~n c`bccghijjkkklllmmnnnoooooooo,AC d bdCC b cBB d abCC b c\d C d/ecCee A Cda a bC Cbd cB Cdd a aC Cbd c\d C /ed Cee [d X " /ed  e e e e e" B Cd aa dB Cba cBCd a adC a B d Ac Ccd  Dcb  Bad aa Ccb bUpc@`pp  pPP T@   %',' Bc da Cc b  Bc d a b Cc b c_@p  pPP@T i %',' `X  fO `@  C@if O `@ F@ Bc da Cc b Bc d a aCc b cPf  fR f  ff ff ff ff f, L L, , LL L L L L L L L L L L L LL l  L l  Lll l Lll xk Lll  Lll  Ll l ll Ll l l Lll  Lll lll llll l0*:FO??+>5{=q r pq{=s p rsr q r rq m m{=r r sp s p  J * j JJj jjj8,L,L,L,L,L,L,L,L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L,L,L,L,Lp:q r p qr s  r q r rq m m p s p s p p ssp qr o0*FM]??-8>LLLLLLLL L L L L L L L L,,,,,,,,,,,,,,,,LLLLLLLL , , , , , , , ,,,,,,,,, , LLLL,, ,@j @j@î ʮ@n HO@p`P@0 @ezczO I e0q 0 (@4Q@VYiiq y`  @ xۢۮۮٺڢע.DDDDDDDDD3DDDDDDDD3DDDDDDD3DD3DDDDDDDD3DDDDD33DD3DDDDD33DDDDDDDDDDDD3DDDDDDDDDDDD33DDDDDDDDD3DDDDDDDDDDDDDDD3DDDDDD3DDDDDDDDDDDDDDDDDD3DDDDDD3DDDDDDDDDDDDDDDDDDDDDDDDDDDDD3DDDDD3DDDDD3DDDDD333DDDDDD33DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD3DDDDDD3DDDDDDDDDDDDDD3DDDDDDDD3DDDDDDDD33DDDDDDDDDD3D33DDDDDDDDDD3DDD33DDDDDDDDDDDDDDDDDDDD33DDDDDDDDDDDDDDDDDD3DDDDDDDDDDDDDDD3D3DDDDDDDDDDDDDDD3D33DDDDDDDDDD33DDDDDDDDDD3DDDD33DDDD30 0330 000 000 00030  $`lrrrrrrrrrrrrrrrrrrrrrrrrtutututututututututututuruffffffffffffqqfffffffffffftrtututututututututuruffqqfftrtutututututurufCCqfqqfqqgftrtutututututurufqgggffqqftrtutututututurufqqqggffqqftrtttutttuttturufffqqfftrtutututututuruffqqffftrtutututututuruffffffffffffqqfffffffffffftrtttutttuttturuqqqqqqqqqqqqtrtutututututuruqqqqqqqqqqqqtrtutututututurufffffffffffqqffffffffffftrtutututututurufqgqfqqfqgqqftrtutututututurufqqqqffqqqftrtutututututurufffftrtutututututurufffffffffffffffffffffffftrtutututututurrrrrrrrrrrrrrrrrrrrrruututututututututututututututututututututututututututututututututututututututututututuuututututututuuututututuuututututuuututututuuutttutttuuututututuuututututuuutttutttuuututututuuututututuuututututuuututututuuututututuuututututuuututututuuututututututututututututututututututututututututututututututuuutututututuututututututututututuiyyiituttubctutututututuiyyyyyyjtutututututututuiyyyyyyyztututtutututututuyyyyyyyyztutututututututuyyyyyyyzituddetutttutubbtuttyyiyztuwxwxtututututustutututu{l|yyztuwxwxwxttutututututudefdldddddtuwxwx}dbbtututututututututututututututututututututututututututswxwxwxwxwxwxtutu}ttuitutututututututututututututututututututututuwxwxtvwxtvwxvstutututututuiyyjtutututututu}}}}}tutuiyyzitutututuitutu}}tututuyyyzzztututututu9:;tutuyyyjtutu}}tutuyyzzwxvtututuyyyztutugwxvugtutu{|wxtu{|tututu {|tutututuddedxtutudtututudefdededdedeeddetutututututututuututututtututututututututututututututututututututututututututututututututututututututututututututudedetuttugggcggcbggtutututucgtugtutugtututugtututuedEdeggtuutuggututututututututucgtuggtuutututtututututtedddedeguttubbbggbggtututututucgtugggggttutututututututuededeEggtututututututuututtututututututututututututututututututubbbbbbtututututustututututututtuggttsgtututugstutututubbbtutu@tugggstututututuutugtututututututugtustutututututututututututugtututustutututututututututututututututututututuutututututtutuiyyitututtuttuiyyyyyytutututtuyyyyyyyztutucccccctttuyyyztutututututtudedde|{|tutututututttutututufdetutututuyyttututututututugtutututu{|tttutututututuctttugtutucctuttucttututucttututucttututubbtucttututubbwxvcttututubwxtucttututubbbbxtutucccccccttutututututuututututtututtutututuuttutututtutututuututututusutututututuutututtustutututututuuuutttutustututuuuuuuuuuuuuuuuuuuuuuuuuuututuustutututuuuuuuuuuuuuuuuuuuuututututututuuuuuuuuuuuuuuuuuuuttsttutuuuuuuuuuuccuuuuuuuuutustutuuuuccsuucccccutuutututuuuutusuuuttutudededededetuucccccccccuucuuucccccccccututututututututututuuuuuuuuuucsuuuuuuuuuutugEtututututututuuuuuuuuuuuuuuuuuuuutugtututututututuuuuuu ucuu uuuuuutugtutututuuuuuuuuscuuuuuuuutugtutututuuuuuuuuutugtututututututuuuutuucuutuuuutugtututututututuuucsuuuutututututututututututututututuututututuuuuutttttttuuutututuuuutuuuuututucccccccccccccuuccccccccccccctuuuuututttttttttttttttuutttttttttttttttuuutuccccccccccuuuuuuuD/xuccccccccucuccusuccusucuucuucucucuuuuuucucuucsuccuuuucucuuuccuuucucucuucuccuucuuscuctuuucuuucucuucucuctttttttuucsuctuuuuccccccccccccctuuuutttttttttttttttuuccccccccccuuuutuuucuucccuucccuuccccuucccuucccuuuucuuuucuuccuuccuuuuuuuucuuuuuuuuututuuuuuŐuuuuuuuuuuuuuuuuuuuuuuuuuuuuĴĴuuŵŵuuĴtuuuuutututŵtututututututuuuutututututuŴtututuuuutututututututututututuuuututututututututututututututuuuuuusstutusssssssssssstusssstutussuuuuusssssssssstutusssssssssssstussuuuuussssssssssssstusssssssssssssssuuuuussssssssssssssssssuuuuuuuuuuutuuuuuuuuuuuuuuuuututuuutuuuuutuuuuuuuutututuutuututuututuuuuuuuututututututuututututututuutututuutuutututututututuututuuutututututututuututuututuututututututututtutututututututututuututuututututututuuttututututututututututututusstutututuuuststutuԴucuuututututuututuuscuuuĴtutututuututuuuuututututuututuucuuuĴtutututuututuucsuuututututuututuuuuututututuututuucuuuuuuscuuuŴuuŴuuuustututututututututuuuutuutuuutuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu-- title: o moku e mun -- author: jan Wiwijen, jan Lin -- desc: pipi li tawa sewi li kama ante -- site: roachbones.itch.io/o-moku-e-mun -- license: -- script: lua -- input: gamepad -- menu: o nanpa e tenpo -- version: 2.0 VERSION = "#2.0" function tablestr(t) local s = "{" for k,v in pairs(t) do if type(v) == "function" then s = s..k.."();" else s = s..k..":"..tostring(v)..";" end end return s.."}" end function eztrace(...) local t = {} for k,v in ipairs{...} do if v==nil then t[#t+1] = "nil" elseif type(v)=='table' then t[#t+1] = tablestr(v) else t[#t+1] = tostring(v) end end trace(table.concat(t,"|")) end add = table.insert -- as in pico-8 unpack = table.unpack inf, ceil, floor = math.huge, math.ceil, math.floor sqrt, sin, cos = math.sqrt, math.sin, math.cos min, max, abs = math.min, math.max, math.abs random = math.random tau = 2*math.pi function uniform(n1,n2) return random()*(n2-n1)+n1 end function choice(t) return t[random(#t)] end function shuffled(t) local shuf = {} while #t>0 do add(shuf,table.remove(t,random(#t))) end return shuf end function round(n) return floor(n+0.5) end function bit(b) return b and 1 or 0 end function ss(k) if k<0 then return 0 end if k>1 then return 1 end return k*k*(3-2*k) end function parabola_interpolate(x,p) -- Lagrange interpolation. x ranges from x1 to x3 local x1,y1,x2,y2,x3,y3=p.x1,p.y1,p.x2,p.y2,p.x3,p.y3 y = y1*(x-x2)*(x-x3)/(x1-x2)/(x1-x3) + y2*(x-x1)*(x-x3)/(x2-x1)/(x2-x3) + y3*(x-x1)*(x-x2)/(x3-x1)/(x3-x2) return y end DEBUG = nil SCENIC = false MAX_FLOORS = 30 -- Not including end rooms GODMODE = false -- DEBUG FASTTRAVEL = DEBUG WPOWER = 0.1 -- p/f per frame of flap GRAVITY = 0.04 -- pixels per frame squared DRAGCO = 0.02 -- p/f to decelerate per (p/f)^2 BEE_ACCEL = 0.025 FROG_WAKE_RADIUS = 60 -- pixels FROG_LEAP_COOLDOWN = 99 -- frames FROG_LEAP_DURATION = 60 -- frames GRASS_NUTRITION = 0.2 -- Probability of one kili's worth LEAF_NUTRITION = 0.3 TREE_NUTRITION = 0.1 -- Probability per tree tile eaten CAN_SMASH = true -- Always have default smash ability -- Set to true to start with extra abilities CAN_VERTICATE = false CAN_EAT_WHOLE_TREE = false CAN_GRAZE = false CAN_EAT_WORDS = false SP = { kule="\x01", sinpin="\x02", sitelen="\x03", open="\x04", leko="\x05", utala="\x06", akesi="\x07", lape="\x08", waso="\x09", nl="\n", lawa="\x11", len="\x12", sin="\x16", kin="\x18", kijetesantakalu="\x0b", moli="\x0c", poka="\x0d", kute="\x0e", jelo="\x10", nasa="\x1b", left="\x1c", right="\x1d", kiwen="\x1e", awen="\x1f", a="!", nimi="\"", nanpa="#", mani="$", pakala="%", jaki="&", pi="'", palisa="(", la=")", lete="*", en="+", [","]=" ",["."]=".", sike="0", wan="1", tu="2", mute="3", lipu="4", luka="5", kili="6", ale="8", [":"]=":", sama="=", li=">", seme="?", monsi="@", anpa="A", sewi="B", nena="C", alasa="D", esun="E", kasi="F", jo="G", lon="H", pini="I", pipi="J", kepeken="K", lukin="L", ma="M", nasin="N", o="O", pali="P", moku="Q", soweli="S", tenpo="T", suli="V", wawa="W", weka="X", anu="Y", supa="Z",["["]="[", taso="\\",["]"]="]", suwi="^", noka="`", ante="a", sina="b", mun="c", tan="d", e="e", pilin="f", musi="g", olin="h", ni="i", jan="j", ken="k", ilo="l", tomo="m", ike="n", ijo="o", mi="p", ona="q", kon="r", sona="s", toki="t", pona="u", lili="v", wile="w", ala="x", linja="y", kama="{", tawa="}", ["|"]="|" } function char_width(i) for x=7,-1,-1 do for y=0,7 do a = 2*0x6000+64*i+8*y+x col = peek4(a) if col ~= 1 then return x+2 end end end return 1 end CHAR_WIDTH = {} for i=0,127 do CHAR_WIDTH[string.char(i)] = char_width(i) end CHAR_WIDTH[" "] = 4 function measure_line(text) width = -1 for i=1,#text do width = width + CHAR_WIDTH[text:sub(i,i)] end return max(width, 0) end function sfx_wing(vol) sfx(16,90,5,0,vol or 15) end function sfx_bonk(vol) vol=vol or 15; sfx(17,10+vol,-1,0,vol) end function sfx_kili(vol) sfx(18,60,-1,0,vol or 15) end function sfx_leaf(vol) sfx(19,60,-1,0,vol or 3) end function sfx_powerup() sfx(22,50,-1,0,15,-1) end function sfx_chord() if pipi.damned then sfx(36,66,-1,0,15,-1) return end local bar = peek(0x13ffd)*4+(peek(0x13ffe)+4)//12 bar = bar & 31 -- match with music local roots = {4,4,4,4,2,2,2,2,4,4,4,4,0,2,4,4, 9,9,8,8,7,7,6,6,9,9,8,8,7,7,11,11} local types = {32,32,32,32,32,32,32,32,32,32,32,32,32,32,37,37, 32,32,36,36,32,32,36,36,32,32,36,36,32,32,37,37} sfx(types[bar+1],roots[bar+1]+55,-1,3,15,-1) end spawned_bg_things = {} function bgspawn(a) add(spawned_bg_things, a) end function spawn(a) if type(a)~="table" then eztrace('ERR spawning',a) error("Bad spawn",2) end add(things, a) end function overlaps(box, other) return box.x + box.width >= other.x and box.y + box.height >= other.y and box.x < other.x + other.width and box.y < other.y + other.height end function thitbox(t) return t.hitbox and t:hitbox() or { x=t.x,y=t.y, width=t.width or t.size, height=t.height or t.size} end function toverlaps(t1, t2) return not t1.gone and not t2.gone and overlaps(thitbox(t1),thitbox(t2)) end function physics(t) -- TODO can we move this somewhere better t.vx=t.vx or 0; t.vy=t.vy or 0 --Apply gravity if t.gravitous then t.vy = t.vy + GRAVITY end --Apply drag if t.dragco then t.vx = t.vx-(t.vx*abs(t.vx))*t.dragco t.vy = t.vy-(t.vy*abs(t.vy))*t.dragco end if t.what ~= "Butterfly" then t.x=t.x+t.vx t.y=t.y+t.vy end end function dist(t1,t2) return sqrt((t1.x-t2.x)^2+(t1.y-t2.y)^2) end function near(t1,t2,within) return dist(t1,t2) <= (within or 20) end function BeeEnemy(x,y) local bee = {what="BeeEnemy", x=x,y=y,size=8,dragco=0.05,gravitous=false, ax=0,ay=0,dead=false,heart_timer=0} function bee:draw(cx,cy) local flip=self.dead and 2 or 0 if self.ax<0 then flip=flip~1 end if pipi.suwi then flip=flip~1 end spr(64, round(self.x-cx), round(self.y-cy), 0, 1, flip) end function bee:can_see(other) return abs(self.y-other.y)<60 and abs(self.x-other.x)<90 and line_of_sight( self.x,self.y, other.x,other.y ) end function bee:hunt() if pipi.standing or not self:can_see(pipi) then self.ax = 0 self.ay = 0 return end -- alasa e pipi local distance_x = pipi.x - self.x local distance_y = pipi.y - self.y local distance = dist(self,pipi) local c = BEE_ACCEL/distance self.ax,self.ay = c*distance_x, c*distance_y if pipi.suwi then if distance < 16 then self.ax = self.ax+ sin(time()/1000000+self.y)/9 end if self.heart_timer == 0 then spawn(Particle{ x=self.x,y=self.y,size=8,col=4,life=60, sprite=choice{433,433,433,433,434}, -- suwi/olin gravitous=false,dragco=0.18, vx=self.vx+uniform(-0.1,0.1), vy=self.vy - uniform(0.5,2) }) self.heart_timer = 120 end self.heart_timer = self.heart_timer - 1 end end function bee:tick() physics(self) if self.dead then if abs(self.y-pipi.y)>100 then self.gone = true end return end self:hunt() self.vx = self.vx + self.ax self.vy = self.vy + self.ay end function bee:touched(pipi) if pipi.suwi then return end -- sting pipi:die() self.dead = true self.vx = -0.3*self.vx self.vy = -abs(self.ay)-1 self.dragco = 0 self.gravitous = true end return bee end function FrogEnemy(x,y) local frog = {what="FrogEnemy", x=x,y=y,size=8,dragco=0,gravitous=false, leaping=false,parabola=nil,facing=choice{-1,1},dx=nil, cooldown=FROG_LEAP_COOLDOWN,asleep=true,stuck=false, lape_timer=0,dead=false,bashable=true} function frog:draw(cx,cy) spr( self.asleep and 69 or (self.leaping and 68 or (self.cooldown<20 and 67 or 66)), round(self.x-cx), round(self.y-cy)-(self.leaping and 1 or 0), 0,1, (self.facing==-1 and 1 or 0)|(self.dead and 2 or 0) ) end function frog:can_jump_to(x,y) if abs(self.x-x)<7 then return false end --Cover at least 1 tile if abs(self.x-x)>99 then return false end --but not too far if abs(self.y-y)>60 then return false end --Not too high/low return solid_at(x,y+8) and not solid_at(x,y) end function frog:available_targets() --Iterate over tiles local targets = {} local start_x = (floor(self.x)//8)*8-8*16 for x=start_x,start_x+8*16-8,8 do local start_y = (floor(self.y)//8)*8-24 for y=start_y,floor(self.y)+56-8,8 do if self:can_jump_to(x,y-8) then add(targets, {x=x,y=y-8}) end end end return targets end function frog:choose_target() --Resting near frogs is dangerous: local px, py = pipi.x, (pipi.y//8)*8 if self:can_jump_to(px,py) then return {x=px,y=py} end local targets = self:available_targets() if #targets==0 then self.stuck = true self.asleep = true return nil end return choice(targets) end function frog:tick() physics(self) if self.dead then if abs(self.y-pipi.y)>250 then self.gone=true end return end if self.asleep then if not self.stuck and dist(self,pipi) < FROG_WAKE_RADIUS then self.asleep = false return end self.lape_timer = self.lape_timer - 1 if self.lape_timer <= 0 then lape=Particle{x=self.x,y=self.y-2, size=2,col=4,life=50, dragco=0.5,gravitous=false,sprite=70} lape.vx = uniform(-0.2,0.2) lape.vy = -0.2 spawn(lape) self.lape_timer = 110 end return end if self.leaping then self.x = self.x+self.dx self.y = parabola_interpolate(self.x,self.parabola) if (self.facing > 0) == (self.x > self.parabola.x3) then if solid_at(self.parabola.x3,self.parabola.y3+8) then self.leaping = false self.x, self.y = self.parabola.x3, self.parabola.y3 else --despawn after falling offscreen if self.y-pipi.y>300 then self.gone = true end end end elseif self.cooldown>0 then self.cooldown = self.cooldown-1 else self:try_leap() end end function frog:try_leap() if self.cooldown>0 then return end local target = self:choose_target() if not target then return end local x1, y1 = self.x, self.y local x3, y3 = target.x, target.y local x2 = (x1+x3)/2 local y2 = (y1+y3)/2 - 20 self.facing = x1 < x3 and 1 or -1 self.dx = (x3-x1)/FROG_LEAP_DURATION self.parabola = {x1=x1,y1=y1,x2=x2,y2=y2,x3=x3,y3=y3} self.leaping = true if abs(self.y-pipi.y)<80 then sfx(28,random(40,42),-1) elseif abs(self.y-pipi.y)<130 then -- quiet hop sfx(28,random(38,40),-1,0,5) end self.cooldown = FROG_LEAP_COOLDOWN end function frog:touched(pipi) if not self.asleep and not self.dead then pipi:die() end end function frog:die() self.dead = true self.gravitous = true self.vx = -1 self.vy = -1 end return frog end function Choir(x,y,size,track) local choir={x=x,y=y,size=size,track=track} function choir:tick() if toverlaps(self,pipi) and pipi.dead==0 then self.gone=true music(self.track) self:extra() end end function choir:extra() end function choir:draw(cx,cy) if DEBUG then rectb(round(self.x-cx),round(self.y-cy), self.size,self.size,1) end end return choir end function Fireball(ball) ball.xperiod=222 ball.yperiod=333 ball.xangfreq=tau/ball.xperiod ball.yangfreq=tau/ball.yperiod ball.sim_dist=ball.ymag+200 ball.embers={} ball.x=ball.centerx ball.y=ball.centery ball.size=3 ball.wavey=(ball.centery)//4 function ball:tick() if toverlaps(self,pipi) then pipi:die() end local f = frame + self.wavey local new_x=sin(f*self.xangfreq)*self.xmag+self.centerx local new_y=sin(f*self.yangfreq)*self.ymag+self.centery local vx = new_x-self.x local vy = new_y-self.y self.x,self.y = new_x,new_y if random()<0.1 then ember = Ember( self.x+random(0,6), self.y+random(0,6) ) ember.vx = vx + uniform(-0.1,0.1) ember.vy = vy + uniform(-0.1,0.1) ember.dragco = 0.1 add(self.embers,ember) end for _,ember in ipairs(self.embers) do ember:tick() end while self.embers[1] and self.embers[1].gone do table.remove(self.embers, 1) end end function ball:draw(cx,cy) spr(self.s,round(self.x-cx)+1, round(self.y-cy),0) for _,ember in ipairs(self.embers) do ember:draw(cx,cy) end end return ball end SMASHER_START = nil smashers = {} function Smasher(x,y,dir) local smasher = { what="smasher", x=x,y=y,ext=0, period=100, width=8*4, max_ext=8*7, dir = dir or 1, on = false, sim_dist = 300, far_draw = 8*7} function smasher:tick() self.start = (smasher_start or frame)-10 local p = self.period local f=(frame-self.start+self.period//4*self.dir)%p local midpoint = p*(4/5) local frac if fmidpoint and overlaps(thitbox(pipi),pushbox) then if self.dir == 1 then pipi.y = pushbox.y + pushbox.height pipi.vy = max(pipi.vy,speed) else pipi.y = pushbox.y-7 pipi.vy = min(pipi.vy,-speed) end if pipi:can_move(pipi.x,pipi.y) ~= nil then pipi:die() end end self.ext = new_ext if DEBUG then self.pushbox=pushbox end end function smasher:draw(cx,cy) local rx=round(self.x-cx) local ry=round(self.y-cy) if self.dir == 1 then clip(rx,ry,self.width,self.ext) for y=self.y+self.ext-8,self.y-8,-8 do spr(185,rx,round(y-cy),-1,1,0,0,4,1) end clip() for x = self.x,self.x+self.width-1,8 do spr(184,round(x-cx),ry+self.ext-8) end else clip(rx,ry-self.ext,self.width,self.ext) for y=self.y-self.ext+8,self.y+8,8 do spr(185,rx,round(y-cy),-1,1,0,0,4,1) end clip() for x = self.x,self.x+self.width-1,8 do spr(184,round(x-cx),ry-self.ext) end end --[[if DEBUG then rect(round(self.pushbox.x-cx), round(self.pushbox.y-cy), self.pushbox.width,self.pushbox.height, 2) end]] end function smasher:hitbox() if self.dir==1 then return { x=self.x,y=self.y,width=self.width,height=self.ext } else return { x=self.x,y=self.y-self.ext, width=self.width,height=self.ext } end end table.insert(smashers,smasher) return smasher end function Butterfly(x,y) local butterfly = { what="Butterfly", x=x,y=y,xdir=1, size=7,dragco=DRAGCO, gravitous=true, flap={[1]=0,[0]=0,[-1]=0}, kili=0,max_kili=10,tawa=10,max_tawa=10, pod_sprites={},powerups=0, standing=false, jiggle=0,dead=0, viral_chomps={}, save_x=x,save_y=y, buying=0,buy_timer=0,buying_pod=nil, damned=false,end_timer=nil, } function butterfly:init_powers() self.can_smash = CAN_SMASH self.can_verticate = CAN_VERTICATE self.can_graze = CAN_GRAZE self.can_eat_whole_tree = CAN_EAT_WHOLE_TREE self.can_eat_words = CAN_EAT_WORDS self.can_stick = false self.sticking = false self.scorpio = false self.smash_midas = DEBUG self.bonkproof = false self.suwi = DEBUG self.max_tawa = 10 self.tawa = 10 self.flap_frames = 8 end butterfly:init_powers() function butterfly:try_move(tx,ty) local cx,cy,tile = self:can_move(tx,ty) if not tile then self.x,self.y = tx,ty end return cx,cy,tile end function butterfly:can_move(x,y) -- None: yes, we can move here''' -- (x,y,tile): no, we collide with this map tile''' if self.dead>0 then return nil end -- We can get away with only checking the corners local corners = {{x=x,y=y},{x=x+self.size-1,y=y}, {x=x,y=y+self.size-1}, {x=x+self.size-1,y=y+self.size-1}} for _,corner in ipairs(corners) do local tx = floor(corner.x/8) local ty = floor(corner.y/8) local tile = mget2(tx,ty) if fget(tile,0) then return tx,ty,tile end end if self.damned then hb = {x=x+2,y=y+2, width=self.size-4,height=self.size-4} for _,smasher in ipairs(smashers) do if toverlaps(smasher,hb) then return floor(smasher.x/8),floor(smasher.y/8),smasher end end end return nil end function butterfly:hitbox() return {x=self.x+2,y=self.y+2, width=self.size-4,height=self.size-4} end function butterfly:flapping() return self.flap[-1]>0 or self.flap[1]>0 end function butterfly:flap_to(dir) if self.sticking and dir == self.xdir then return end self.sticking = false if self.flap[dir]>0 or self.tawa==0 or self.dead>0 then return end self.flap[dir] = self.flap_frames self.xdir = dir self.tawa = self.tawa-1 if self.tawa<3 then for _=1,random(1,3) do sweat = Particle{x=self.x+random(0,7), y=self.y-random(0,2), size=1, col=10, life=20} sweat.vx = uniform(-0.4,0.4) sweat.vy = uniform(-0.4,0) spawn(sweat) end end if self.tawa==0 then sfx(24,70) elseif dir==0 then sfx(15,round(uniform(58,62))) else sfx_wing() end end function butterfly:move() STEPS = 4 --Mario moves in quarter steps local mx,my,tile for i=0,STEPS-1 do mx,my,tile = self:try_move(self.x+self.vx/STEPS,self.y) if mx and self.smash_midas and try_smash(mx,my) then mx,my,tile = nil,nil,nil -- Smashed through; keep going end if mx~=nil then if self.can_stick and self.tawa and tile~=162 and not self.sticking then self.sticking = true self.vx,self.vy = 0,0 sfx(27,57) if self.tawa 0 then --alight if not self.standing then self.jiggle = 6 sfx(25,35) end self.vy = 0 self.vx = self.vx*0.6 self.standing = true if self.tawa < self.max_tawa then self.tawa = self.tawa+1 end elseif tile~=162 then --bonk if self.vy < -1.2 then camera.shake = 1.5 if not self.bonkproof then self.tawa = 0 end if self.can_smash then try_smash(mx,my) end self.standing = false -- in case smasher launched if frog_hurtbox(mx*8+3,my*8-4,4,8) then sfx(30,45) else sfx_bonk() end elseif self.vy < -0.8 then sfx_bonk(8) end self.vy = -self.vy/2 end break end end end function butterfly:die() if GODMODE then return end sfx(21,random(55,60)) if self.dead==0 then camera.wipe = -120 self.dead = 120 end self.vy = -2 self.vx = -2 * self.xdir end function butterfly:revive() --called when dead reaches 0 self.dead = 0 self.x, self.y = self.save_x, self.save_y self.vx,self.vy = 0,0 self.tawa = self.max_tawa self.kili = 0 for k,v in pairs(self.flap) do self.flap[k] = 0 end for i,thing in ipairs(things) do _= thing.pipi_revived and thing:pipi_revived(self) end end function butterfly:damn() self.damned = true self.save_x,self.save_y = 1325,1177--stumproom fallback procgen.head = -inf --prevent procgen -- Wipe out all above-hell things for _,t in ipairs(things) do if t ~= self then t.gone = true end end reify(90,85,90,136) -- Set upgrades to wawa only self:init_powers() self.smash_midas = true self.pod_sprites = {40} -- Block return mset2(150,99,115) mset2(150,100,115) music(3) for i=0,4 do for _,d in ipairs( {{16+i*4,-16-i*8},{-16-i*4,-16-i*8},{0,-20-i*9}} ) do spawn(Fireball{ centerx=1076-240+d[1],centery=1300+d[2], xmag=64,ymag=64, s=224}) end end spawn(Fireball{ centerx=1076-240,centery=1300, xmag=64,ymag=64, s=192 }) spawn(Choir(1130,1815,70,3)) spawn(smasher_trigger) copy_room(0,8,90,64+136,30*2,17*4-8,1,false) -- wipe map, so it's not visible after helldive -- maybe we should do this regardless of damnation memset(0x8000,0,32640) realized = {} end function butterfly:tick_buy() self.x = self.buying_pod.x self.y = self.buying_pod.y self.buy_timer = self.buy_timer-1 if self.buy_timer > 0 then return end if self.buying > 0 then sfx_bonk() self.buying = self.buying-1 self.kili = self.kili-1 self.buy_timer = 30 return end sfx_powerup() camera.shake = 10 self.vx,self.vy = 0,-1 self.buying_pod:consume() self.buying_pod = nil self.powerups = self.powerups+1 if self.powerups<2 then music(1) else music(0) end end function butterfly:tick() physics(self) self:move() if self.end_timer then self.end_timer = self.end_timer-1 if self.end_timer==0 then show_end=true end end if self.buy_timer > 0 then self:tick_buy() return end if self.dead>0 then self.dead = self.dead-1 if self.dead==0 then self:revive() end return end if self.damned and self.y>2300 then self.y = 9999 -- Make the map disappear if devil then return end wintime=frame devil=true won=true SCENIC=true show_end=true things={} pipi.gone = true pipi.pod_sprites={} music() sfx_powerup() self.end_timer = 300 end if self.sticking then if not solid_at(self.x+self.xdir*8+4,self.y+1) and not solid_at(self.x+self.xdir*8+4,self.y+6) then self.sticking = false else self.vy=-0.04 self.vx=0 if self.tawa < 5 then self.tawa = self.tawa+1 end end end self.jiggle = self.jiggle - 1 if self.flap[-1]>0 then self.vx = self.vx - WPOWER self.vy = self.vy - WPOWER self.flap[-1] = self.flap[-1]-1 self.standing = false end if self.flap[1]>0 then self.vx = self.vx + WPOWER self.vy = self.vy - WPOWER self.flap[1] = self.flap[1]-1 self.standing = false end if self.flap[0]>0 then -- verticate self.vx = 0 self.vy = self.vy - WPOWER*4 self.flap[0] = self.flap[0]-1 self.standing = false --Summon one sparkle per frame sparkle = Particle{ x=random(floor(self.x),floor(self.x)+8),y=self.y+6, size=1,col=choice({9,10}),life=random(30,60), gravitous=false,dragco=0.1} sparkle.vy = self.vy*0.8 bgspawn(sparkle) end local mx,my = floor((self.x+4)/8),floor((self.y+4)/8) local tile = mget2(mx,my) if my>101 and not (self.dead>0 or self.damned) then self:damn() end -- cosmetic effects if leaves_at(self.x+4,self.y+4) then sfx_leaf() boom{x=self.x+4,y=self.y+4,cols={12,13},count=2} mset2(mx,my,tile+32) end if flag_at(self.x+4,self.y+4,7) then --water spawn(water_particle( self.x+random(0,7),self.y-random(1,2) )) end -- Hazards if IS_SPIKE_TILE[tile] then self:die() end -- Special eating if self.kili < self.max_kili then if self.can_graze and IS_GRASS_TILE[tile] then boom{x=self.x+4,y=self.y+8,cols={1},count=2} mset2(mx,my,0) if random()=cue then local m=mget2(mx,my) if not (m==105 or m==106 or m==137 or m==138) then boom{x=x,y=y,cols={1,2,3},count=3,min_size=2} if random()0 then return end local sprite = 32 if self:flapping() then sprite=33 end if self.tawa==0 or self.dead>0 then sprite=sprite+2 end rotate = 0 if self.standing or self.sticking then sprite = 48 if self.sticking then rotate=3 end --idle animation if self.jiggle > 0 then sprite = 49 end if time() % 2000 < 100 then sprite = 49 end if 2000) spr(sprite,x,y,0,1,flip,rotate) end function butterfly:animate_buy(pod) --Timers processed in tick() camera.shake = 2 self.buying_pod = pod self.buying = pod.price self.buy_timer = 70 music() sfx_wing() end return butterfly end function frog_hurtbox(x,y,w,h) local hurtbox = {x=x,y=y,width=w,height=h} for _,thing in ipairs(things) do if thing.bashable and overlaps(hurtbox, thitbox(thing)) and not thing.dead then thing:die() return true end end end function try_smash(mx,my) if mx==nil then return end local smashed = false local frogged = false for _,c in ipairs{{132,133},{228,229}} do LEFT,RIGHT=c[1],c[2] if mget2(mx,my)==LEFT and mget2(mx+1,my)==RIGHT then remove_solid(mx,my) remove_solid(mx+1,my) boom{x=mx*8+8,y=my*8+4,cols={8,8,13,14},count=7, max_size=4} frogged=frogged or frog_hurtbox(mx*8+4,my*8-7,8,8) smashed=true elseif mget2(mx,my)==RIGHT and mget2(mx-1,my)==LEFT then remove_solid(mx,my) remove_solid(mx-1,my) boom{x=mx*8,y=my*8+4,cols={8,8,13,14},count=7,max_size=4} frogged=frogged or frog_hurtbox(mx*8-4,my*8-7,8,8) smashed=true end end if tile_is_breakable(mx,my) then remove_solid(mx,my) boom{x=mx*8+4,y=my*8+4,cols={8,8,13,14},count=5,max_size=3} frogged=frogged or frog_hurtbox(mx*8+3,my*8-7,2,2) smashed=true end if frogged then sfx(30,45) elseif smashed then sfx(19,random(30,40)) end return smashed end function remove_solid(mx,my) --Remove a block, clearing any grass atop. mset2(mx,my,0) local left = mget2(mx-1,my) local right = mget2(mx+1,my) local above = mget2(mx,my-1) local below = mget2(mx,my+1) -- Grass above if IS_GRASS_TILE[above] then mset2(mx,my-1,0) end -- Spikes above if above==98 or above==99 then mset2(mx,my-1,0) end -- Spikes/web to right if right==11 or right==27 or right==163 then mset2(mx+1,my,0) end -- Spikes/web below if below==12 or below==28 or below==163 or below==131 then mset2(mx,my+1,0) end -- Spikes/web to left if left==13 or left==29 or left==131 then mset2(mx-1,my,0) end end function nanpa(n,color) color = color or "" if n==0 then return color..'ala' end local words = {} while n>=100 do add(words, color..'ale') n=n-100 end while n>=20 do add(words, color..'mute') n=n-20 end while n>=5 do add(words, color..'luka') n=n-5 end while n>=2 do add(words, color..'tu') n=n-2 end while n>=1 do add(words, color..'wan') n=n-1 end return table.concat(words, " ") end function Pod(x,y,s,price,apply,desc) -- kepeken tomo lili ni la, -- sina ken ante e sina. local p=nanpa(price,'10') pod = { what="Pod",p=p, x=x,y=y,s=s,price=price,apply=apply,size=16, desc=desc..' nl o ante kepeken kili '..p..' .', speech=Speech{ x=x,y=y,speech="sina jo ala e kili "..p.." .", speed=8,bg=15,color=12,radius=30 } } function pod:tick() if pipi.buy_timer==0 and pipi.kili500), 0,2 --colorkey,scale ) end function pod:touched(pipi) if pipi.kili < self.price then return end if pipi.buy_timer > 0 or self.gone then return end --Handled in animation --pipi.kili -= self.price pipi:animate_buy(self) end function pod:consume() self.gone = true boom{x=self.x+4,y=self.y+4,cols={9,10},count=30, min_size=1,max_size=1,min_life=30,max_life=80, v=0.5,gravitous=false} self.apply(pipi) add(pipi.pod_sprites,self.s) end return pod end function tawafy(pipi) pipi.max_tawa=pipi.max_tawa+6 end function lukafy(pipi) pipi.flap_frames=12 end function stickify(pipi) pipi.can_stick=true end function wordify(pipi) pipi.can_eat_words=true end function treeify(pipi) pipi.can_eat_whole_tree=true end function grazify(pipi) pipi.can_graze=true end function vertify(pipi) pipi.can_verticate=true end function wawafy(pipi) pipi.smash_midas=true end function scorpify(pipi) pipi.scorpio=true end function suwify(pipi) pipi.suwi=true end function bonkify(pipi) pipi.bonkproof=true end pod_tawa = {39,5,tawafy,'sina ken 3waso 3mute .'} pod_eat = choice{ {61,3,wordify,'sina ken 3moku 3e 3sitelen .'}, {62,3,treeify,'sina ken 3moku 3ale 3e 3kasi 3suli .'}, {63,2,grazify,'sina ken 3moku 3e 3kasi 3lili sama soweli mani .'}, } pod_pool = { pod_eat,pod_tawa,pod_tawa, {38,3,lukafy,'3luka sina li kama wawa .'}, {46,8,vertify,'sina luka left right la nl sina tawa sewi lon nasin palisa .'}, {40,5,wawafy,'sina kama 3wawa . nl sina luka e kiwen pi wawa ala la ona li pakala .'}, {41,4,bonkify,'lawa sina li kama wawa .'}, {47,4,suwify,'sina kama suwi . nl ni li seme , mi sona ala .'}, {78,2,stickify,'3luka sina li kama jo e jaki .'}, } pod_pool = shuffled(pod_pool) add(pod_pool,pod_tawa) --tawa pod first (pop from end) function random_pod(x,y) if #pod_pool==0 then return Kili(x,y) end return Pod(x,y,unpack(table.remove(pod_pool))) end function epitaph() return choice{ "o lape pona .", "jan pona li lape lon ma ni .", "ale li pona .", "jan ni li utala wawa li moli .", "sona pi jan ni o awen .", "jan | kasi | li lape lon ni .", "jan ni li pona tawa ale ." } end function advice() return choice({ --sensible advice "sina lukin e sinpin ni tan seme nl pipi o tawa a", "sina utala e anpa leko la nl ona li ken pakala .", "o tawa ala tawa palisa kiwen ike .", "akesi li lape la nl o awen e lape ona .", "sina lape la akesi li moku e sina .", "o lukin e nasin sina .", "sina ken esun e wawa lon poka .", "o kama jo e kili mute . nl ni la sina ken kama wawa .", "kute la , mun pi pona lukin li lon sewi tomo .", "kute la , sina moku e mun la sina kama jan sewi .", "sina utala e anpa pi supa akesi la akesi li weka .", "sina lon supa la pipi jelo li alasa ala e sina .", --jokes "o moli ala .", "sina pipi a", "kijetesantakalu li pipi ala .", "sina lukin e sinpin ni tan seme nl pipi o tawa a", }) end function split(s) local words = {} for w in s:gmatch("%S+") do add(words, w) end return words end function Speech(t) --jan li toki e nimi. local speech = t --x,y,speech speech.what = "Speech" speech.speed = speech.speed or 8 speech.color = speech.color or 0 speech.bg = speech.bg or 12 speech.radius = speech.radius or 17 speech.always = speech.always or false speech.timer = -20 speech.words = split(speech.speech) speech.visible = false speech.size = 0 speech.gravitous = false function speech:tick() if self.always or near(pipi,self,self.radius) then self.visible = true else self.visible = false self.timer = -10 return end if self.timer == self.speed*(#self.words-1) then return end self.timer = self.timer + 1 if self.timer < 0 then return end if self.timer % self.speed == 0 then local c = self.words[self.timer//self.speed+1] if c == "nl" or c == "," then --pass elseif c == "." then sfx(20,40) else sfx(20,random(45,50)) end end end function speech:draw(cx,cy) if self.gone or not self.visible then return end local x=round(self.x-cx) local y=round(self.y-cy) local n=0 if self.timer>=0 then n=self.timer//self.speed+1 end toki(self.speech,x+4,y-10,self.color,self.bg,n) end return speech end function Particle(p) p.what="Particle" -- p should have x,y,size,col,life if not p.x then error() end if p.gravitous==nil then p.gravitous=true end --? p.dragco = p.dragco or 0 p.life0 = p.life p.colorkey = p.colorkey or 0 p.ax,p.ay = 0,0 p.sim_dist = 999 -- Don't let them build up offscreen function p:tick() physics(self) self.life = self.life - 1 if self.life <= 0 then self.gone = true end end function p:draw(cx,cy) if self.gone then return end local x = round(self.x-cx) local y = round(self.y-cy) if self.sprite then spr(self.sprite,x,y,self.colorkey) else local size = ceil(self.size * (self.life/self.life0)) rect(x,y,size,size,self.col) end end return p end function Ember(x,y) --jelo lili li tawa sewi, li kama loje, li weka. local ember = Particle({ x=x,y=y,size=random(1,2),col=3,life=30,gravitous=false, vy=uniform(-1,0),vx=uniform(-0.2,0.2),dragco=0.5}) ember.base_tick = ember.tick ember.what="Ember" function ember:tick() self:base_tick() if self.life==20 then self.col = 2 end if self.life==10 then self.col = 1 end end return ember end function Torch(x,y,flipped) --palisa seli local torch = { what="Torch", x=x,y=y,flip=flipped and 1 or 0 } function torch:tick() if frame%4==0 then spawn(Ember(self.x+random(0,1)+6*(self.flip),self.y+2)) end end function torch:draw(cx,cy) spr(144,round(self.x-cx),round(self.y-cy),0,1,self.flip) end return torch end function boom(args) --x,y,cols,count,min_size=1,max_size=2, --min_life=10,max_life=30,v=1,gravitous=True for _=1,args.count do local v=args.v or 1 spawn(Particle{ x=args.x,y=args.y, size=uniform(args.min_size or 1,args.max_size or 2), col=choice(args.cols), life=uniform(args.min_life or 10,args.max_life or 30), gravitous=args.gravitous==nil or args.gravitous==true, vx=uniform(-v,v), vy=uniform(-v,v) }) end end function Kili(x,y,tile) local kili = { what="Kili", x=x,y=y,tile=tile or random(1,2),size=9, eaten=false,promethean=tile==17 } function kili:draw(cx,cy) if self.eaten then return end local y = bit(time()%1000>500) spr(self.tile,round(self.x-cx),round(self.y-cy)+y,0) end function kili:touched(pipi) if self.eaten then return end if pipi.dead>0 then return end if pipi.kili >= pipi.max_kili then return end if self.promethean then self.eaten = true else self.gone = true end pipi:eat(1) sfx_kili() for _=1,3 do spawn(Particle{ x=self.x+4,y=self.y+4, size=random(1,3), col=random(5,6), life=random(10,20), vx=uniform(-2, 2), vy=uniform(-2, 2) }) end end function kili:pipi_revived(self,pipi) self.eaten = false end return kili end function SavePoint(x,y) local savepoint = {what="SavePoint", x=x,y=y,t=0,size=8} function savepoint:touched(pipi) --No cheesing savepoints in hell if pipi.dead>0 and pipi.damned then return end if self.active then return end pipi.save_x = self.x pipi.save_y = self.y sfx_chord() --#(32,randint(33,36)*2,speed=-1,channel=1) spawn(Particle{ x=self.x,y=self.y,size=1,col=1,life=50,sprite=7, vx=uniform(-1,1),vy=uniform(-1,0) }) end function savepoint:tick() self.t = self.t + 1 self.active=pipi.save_x==self.x and pipi.save_y==self.y end function savepoint:draw(cx,cy) if self.active then poke4(0x3ff0*2+12, 4) poke4(0x3ff0*2+13, 10) poke4(0x3ff0*2+14, 9) poke4(0x3ff0*2+15, 8) else poke4(0x3ff0*2+12, 13) poke4(0x3ff0*2+13, 14) poke4(0x3ff0*2+14, 1) poke4(0x3ff0*2+15, 15) end spr( self.t//11%4+3, round(self.x-cx), round(self.y-cy), 0 ) poke4(0x3ff0*2+12, 12) poke4(0x3ff0*2+13, 13) poke4(0x3ff0*2+14, 14) poke4(0x3ff0*2+15, 15) end return savepoint end function water_particle(x,y) return Particle{ x=x,y=y,size=2,col=12,life=random(10,20), vx=uniform(-0.3,0.3),vy=uniform(-0.3,0)} end function Animate(x,y,s,n,speed) local animate = { what="Animate", x=x,y=y,size=8, s=s,n=n,t=random(0,n-1),speed=speed } function animate:tick() if frame%self.speed == 0 then self.t = (self.t+1)%self.n end end function animate:draw(cx,cy) x,y = round(self.x-cx),round(self.y-cy) s = self.s+self.t spr(s,x,y,0) end return animate end function Fountain(x,y) local fountain = { what="Fountain", x=x,y=y,size=8,t=0 } function fountain:tick() if abs(self.y-pipi.y) < 150 then x=self.x+random(0,7) y=self.y+random(6,7) spawn(water_particle(x,y)) end self.t = self.t + 1 end function fountain:draw(cx,cy) end return fountain end function Glass(x,y) local glass = { what="Glass", x=x, y=y, size=1, shine=random(0,20) } function glass:draw(cx,cy) local x,yy=round(self.x-cx), round(self.y-cy) for y=yy,yy+3 do local i=floor(x//3+y+cy/2+self.shine)//3 local t={7,12,9,15} local c=t[1+min(3,i%20)] rect(x,y,7,1,c) if c == 12 and self.shine > 18 then n=self.shine*2 line(x+4,y,x-n*2,y+n,12) end end end return glass end function Jan(x,y,devil) local jan = { what="Jan", x=x, y=y, size=1, devil=devil, t=0 } function jan:tick() self.t = self.t+1 end function jan:draw(cx,cy) local x,y=round(self.x-cx), round(self.y-cy) local s=self.devil and 59 or 55 -- oko li open ala open? local b=time()//200 % 70 local blink=b==0 or b==2 or b==17 or b>=50 and b<=53 local white = ss((self.t-100)/20)-ss((self.t-420)/20) if white>0.01 then local a=self.t/2 local d=5 + ss((self.t-100)/60)*20 - ss((self.t-420)/20)*5 spawn(Particle{ x=self.x+7+cos(a)*d,y=self.y+12+sin(a)*d, size=random(1,3), col=choice(self.devil and {1,2,3,4,12} or {10,11,12}), life=random(20,50), vx=-cos(a)*0.2, vy=-sin(a)*0.2, gravitous=false }) music() if self.t%5==0 then if self.devil then sfx(35,choice{50,53,56,59}+choice{-12,0,12}, round(random(1,100)),self.t%4,d/5) else sfx(35, ({58,58+7,58+14,58+21,58+16,58+19})[self.t%25/5+1]-12, -- choice{50,57,61,64,66}+choice{-12,0,12}, round(random(1,100)),self.t%4,d/5) end end blink=true for i=16-round(white*15),15 do poke4(0x3ff0*2+i,12) end end local clip_h=round(ss((self.t-250)/120)*24) clip(0,y+clip_h,240,24-clip_h) spr(32,x+4,y+8) clip(0,y,240,clip_h) spr(s-(blink and 0 or 2),x,y,0,1,0,0,2,1) -- head spr(s+16,x,y+8,0,1,0,0,2,2) -- body clip() if white>0.01 then for i=1,15 do poke4(0x3ff0*2+i,i) end end end return jan end function Moon(x,y) local moon = { what="Moon",x=x,y=y,t=0,size=24, sim_dist=150,far_draw=32, timer=nil, } function moon:tick() if self.timer then -- counting down to ending local a=random()*99 spawn(Particle{ -- wawa nasa x=self.x+8-cos(a)*16,y=self.y+16-sin(a)*16, size=random(1,4),col=choice({1,8,9,10}), life=random(10,500),gravitous=false, vx=cos(a)/4,vy=sin(a)/4 }) self.timer = self.timer - 1 if self.timer <= 0 then self.timer = nil things={} show_end=true end end spawn(Particle{ -- snowstorm x=self.x+random(-240,240),y=self.y+random(-60,130), size=1,col=12,life=random(50,500),gravitous=false, vx=uniform(-1.1,2.1),vy=uniform(-1.1,-0.5) }) self.t = self.t + 1 end function moon:touched(pipi) if pipi.gone then return end pipi.gone = true camera.shake = 5 wintime = frame won = true self.timer = 280 sfx_powerup() music(2,nil,nil,false) -- loop=false end function moon:draw(cx,cy) spr(193, round(self.x-cx),round(self.y-cy-4+sin(time()/1000)*4), 0,1,0,0,3,4 -- colorkey,w,h,scale ) end return moon end pipi = Butterfly(127*8, 100*8) function sitelen(text,x,y,color) --Draw sitelen pona text at x,y in the given color. poke4(2*0x3FF0+12, color or 12) font(text,x,y,1,1,1) poke4(2*0x3FF0+12, 12) end function rich(speech,x,y,color) --Convert text with highlights to SP and draw it local x0 = x local cartouche = nil for _,word in ipairs(split(speech)) do local bare = word:gsub("%d","") local c = SP[bare] or "&" -- jaki fallback local col = color or 12 if #word>#bare then col = word:gsub("%a","") end if c == "\n" then x = x0; y = y+10 elseif c == "|" and cartouche==nil then x = x+1; cartouche = x; rect(x,y-1,1,10,col) x = x+3 elseif c == "|" then x = x+1; rect(x,y-2,1,10,col) rect(cartouche+1,y-2,x-cartouche-1,1,col) rect(cartouche+1,y+8,x-cartouche-1,1,col) x = x+3; cartouche = nil else sitelen(c,x,y,col) x = x+CHAR_WIDTH[c] end end end function toki(text,x,y,color,bg,count)--[[ Convert Latin with colors to SP and draw it in a correctly sized bubble, with the bottom center at (x,y).]] if count==0 then return end color = color or 0 local lw = {0} -- line widths for _,word in ipairs(split(text)) do if word == "nl" then add(lw,0) elseif word == "|" then lw[#lw]=lw[#lw]+4 else local w=CHAR_WIDTH[SP[word:gsub("%d","")]] or 0 lw[#lw]=lw[#lw]+w end end local h = 10 * #lw local w = max(unpack(lw)) local mx = 4 local top = y-2-h local left = x-w//2-mx --Keep it on-screen: left = max(left, 8) left = min(left, 240-8-w-2*mx) if bg~=nil then rect(left+1,top,w+2*mx-2,h+6,bg) rect(left,top+1,w+2*mx,h+4,bg) end -- trimmed = " ".join(split(text)[:count]) local trimmed = "" local i = 0 for w in text:gmatch("%S+") do trimmed=trimmed..(trimmed=="" and "" or " ")..w i = i + 1 if i == count then break end end rich(trimmed,left+mx,top+4,color) end function tile_xy(x,y) return floor(x/8),floor(y/8) end function flag_at(x,y,f) return fget(mget2(floor(x/8),floor(y/8)),f) end function solid_at(x,y) return flag_at(x,y,0) end function leaves_at(x,y) return flag_at(x,y,1) end function tile_is_solid(tx,ty) return fget(mget2(tx,ty),0) end function tile_is_breakable(tx,ty) return fget(mget2(tx,ty),2) end function line_of_sight(x1,y1,x2,y2) local x_distance = x2-x1 local y_distance = y2-y1 local distance = sqrt(x_distance^2+y_distance^2) local steps = floor(distance/3) for i=0,steps-1 do local check_x = x1+(x_distance*i/steps) local check_y = y1+(y_distance*i/steps) local tile = mget2(floor(check_x/8),floor(check_y/8)) if fget(tile,0) or fget(tile,1) then -- solid/leaves return false end end return true end function Camera() camera = { x=0,y=0,shake=0,show_timer=pmem(0)>0, --If 0, the screen is dark. Increments every frame. --Set to -60ish to schedule a screen transition wipe=0 } function camera:toggle_timer() self.show_timer = not self.show_timer pmem(0,self.show_timer and 1 or 0) end function camera:tick() local screen_y = pipi.y - self.y if screen_y < 60 then self.y = pipi.y - 60 end if screen_y > 80 then self.y = pipi.y - 80 end --Don't pan below the floor, unless in hell if not pipi.damned then self.y = min(self.y, 690) end --Don't pan much higher than the moon if moon then self.y = max(self.y, moon.y-30) end if pipi.dead<=0 then local column = floor(pipi.x/240) self.x = column*240 + round(self.shake) self.map_x = (column&7)*30 end self.shake = -0.7*self.shake self.wipe = self.wipe+1 end function camera:draw() map( self.map_x, floor(self.y/8), 30,18, -round(self.shake), -round(self.y-floor(self.y/8)*8) ) for _,thing in ipairs(things) do if not thing.speech then --eztrace(thing) if not thing.y then eztrace("draw no y?", thing) else if self.y-8700 then what="kama pona ." else what=advice() end last_speech = Speech{x=wx,y=wy,speech=what} bgspawn(last_speech) elseif tile==TILE_ANTE then mset2(mx,my,0) last_pod = random_pod(wx,wy) spawn(last_pod) if mx < TOWER_MX then --shop on the left side last_pod.x = last_pod.x-8 --center pod end elseif tile==TILE_ESUN then mset2(mx,my,0) elseif tile==32 or tile==48 then mset2(mx,my,0) pipi.x,pipi.y = wx,wy-1 elseif tile==145 or tile==146 then mset2(mx,my,145+(my&1)) if tile==146 then bgspawn(Fountain(wx,wy)) end elseif tile == 193 then mset2(mx,my,0) moon = Moon(wx,wy) -- global on purpose bgspawn(moon) elseif tile==98 or tile==99 then --spike if tile_is_solid(mx,my-1) then mset2(mx,my,12) elseif tile_is_solid(mx-1,my) then mset2(mx,my,11) elseif tile_is_solid(mx+1,my) then mset2(mx,my,13) end elseif tile == 131 then --cobweb if not tile_is_solid(mx+1,my) then mset2(mx,my,163) end elseif tile == 163 then --cobweb if not tile_is_solid(mx-1,my) then mset2(mx,my,131) end elseif tile == 150 then --glass bgspawn(Glass(wx,wy)) elseif tile == 166 then -- smasher bgspawn(Smasher(wx,wy)) mset2(mx,my,0) elseif tile == 167 then -- upward smasher bgspawn(Smasher(wx,wy+8,-1)) mset2(mx,my,0) elseif tile == 16 then -- placeholder mset2(mx,my,0) end end end for _,t in ipairs(late_things) do spawn(t) end --things.extend(late_things) if last_speech and last_pod and last_pod.p then local desc = '10ijo 10ante 10ni 10la 10. 10. 10. nl '..last_pod.desc last_speech.speech = desc last_speech.words = split(last_speech.speech) end end function draw_meter(sprite,x,y,color,value,maximum) if SCENIC then return end spr(sprite,x,y,15) if maximum==inf then maximum=value end rect(x+10,y,2*maximum+1,7,0) for i=0,maximum-1 do rect(x+11+2*i,y+1,1,5,i80 else btn_left = false btn_right = false end btn_left = btn_left or btn(2) or btn(4) or btn(6) btn_right = btn_right or btn(3) or btn(5) or btn(7) btnp_left = btn_left and not prev_btn_left btnp_right = btn_right and not prev_btn_right prev_btn_left = btn_left prev_btn_right = btn_right local ver = pipi.can_verticate if btnp_left then pipi:flap_to(ver and btn_right and 0 or -1) end if btnp_right then pipi:flap_to(ver and btn_left and 0 or 1) end if FASTTRAVEL then pipi.y = pipi.y + 10*bit(btn(5))-10*bit(btn(4)) end if btn(4) and btnp(5) or btn(5) and btnp(4) then camera:toggle_timer() end SIM_DIST = 150 --px. Things further don't tick. for _,t in ipairs(things) do if abs(t.y-pipi.y)<=(t.sim_dist or SIM_DIST) then if t.tick then t:tick() end if t~=pipi and t.touched and toverlaps(pipi,t) then t:touched(pipi) end end end for _,t in ipairs(spawned_bg_things) do add(things,1,t) end spawned_bg_things={} local ungone_things = {} for _,thing in ipairs(things) do if not thing.gone then add(ungone_things,thing) end end things = ungone_things fargo() camera:tick() camera:draw() end function today() return tstamp()//(60*60*24)%255+1 end function daycolor(day) -- maybe scrap this? local colors={2,3,4,5,6,10,11} return colors[day%1+1] end function reset_to_daily() day = today() -- get last played daily from pmem if pmem(2)==day then -- daily already attempted sfx(24) return end pmem(1,1) -- Remember to begin daily pmem(0,1) -- Enable speedrun timer for daily reset() end function sfx_title(n) --sfx(id,note,duration,channel,volume,speed) sfx(32,50+n,nil,1,nil,-1) sfx(33,50+n,nil,2,3,-1) sfx(34,30+n,nil,3,nil,-1) end frame = 0 since_last_frame = 0 won = nil wintime = nil devil = false PALETTE = {} for i=0,47 do PALETTE[i] = peek(0x3fc0+i) end function BDR(row) if won and not devil then t=frame-wintime m=min(1,max(0,t/300-0.2)) m=min(1,t/300) if t>400 then m=1-min(1,(t-400)/5) end --show end screen for i=0,47 do poke(0x3fc0+i, floor(PALETTE[i]*(1-m^0.5))) end if t<400 then poke(0x3FF9,floor(m*50*sin((row*4+time()/2)/100))) else poke(0x3FF9,0) end end if pipi.damned then y = floor(camera.y) --+ row h = max(0,y-1100-bit(devil)*frame*5)//4 set_hell_palette(h) end end for h=0,19 do for i,c in pairs(PALETTE) do a=0x15360+48*h+i if i%3==0 then --red poke(a,min(255,c+h*2)) else --green, blue poke(a,max(0,floor(c^(1-0.02*h)))) end end end pal_num = 0 function set_hell_palette(h) local p = max(0,min(19,h//20)) if p ~= pal_num then memcpy(0x3fc0,0x15360+48*p,48) pal_num = p end end function fmt_time(ms) return string.format("%02d:%02d.%03d", ms//60000,ms//1000%60,ms%1000) end end_frame = 0 function draw_end() set_hell_palette(0) rect(0,0,240,136,0) local y=-10 if end_frame==90 then spawn(Jan(80,56+y,devil)) end end_frame=end_frame+1 --[[ local b=time()//200 % 70 local open=b==0 or b==2 or b==17 or b>=50 and b<=53 map(219,106,10,8,80-8*3,cy-8*2) local offset = devil and 4 or 0 spr(55+offset,80,cy,-1,1,0,0,2,3) if not open then spr(53+offset,80,cy,-1,1,0,0,2,1) end ]] camera.x = 0 camera.y = 0 if end_frame==630 then spawn( Speech{x=130,y=70+y,speech="mi kama jan . . . , anu seme",always=true} ) end if end_frame==830 then local txt = devil and "nasin len jan li nasa kin ." or "nasin mun li wawa nasa ." spawn(Speech{x=140,y=90+y,speech=txt,always=true}) end for i,t in ipairs(things) do if t.tick then t:tick() end if t.draw then t:draw(0,0) end end if end_frame == 990 then music(4) end if end_frame >= 990 then toki("pini musi .", 120,110+y,14,nil) print(fmt_time(wintime*1000//60), 2, 130) end end function TIC() if key(63) and keyp(18) then reset() end -- ctrl+r if key(63) and keyp(5) then -- ctrl+e pmem(1,0) pmem(2,0) reset() -- reset daily for testing end frame = frame+1 if show_end then draw_end() return end if screen == -2 then if btnp(0) or btnp(1) or btnp(2) or btnp(3) or btnp(4) or btnp(5) or btnp(6) or btnp(7) then camera.wipe=0 screen=0 frame=0 music(3) if seed then pmem(1,0) -- Resetting just does a normal run pmem(2,seed) -- Remember we started this daily end end -- ctrl+d at title to play daily challenge if key(63) and keyp(4) then reset_to_daily() end rect(0,0,240,136,0) if seed then local c = daycolor(seed) toki("nasin "..c.."nanpa "..nanpa(seed,c), 120,120,14) end if frame == 40 then music(5,nil,nil,false) end if frame >= 40 then spr(200,240//2-32*2,136//2-32*2,-1,2,0,0,8,4) end if frame >= 140 then toki("tan jan | 3luka 3ijo 3nena | tan jan | 10wan 10ijo 10wan 10ijo 10jan 10e 10nena |", 120,80,14) end if frame >= 200 then toki("o luka e nena , 10right",120,96,12) --print(text,x,y,color,fixed,scale,smallfont) print(VERSION,1,130,8,false,1,nil) end else --animate falling water water=145 for x=0,7 do old = peek4(0x8000+water*64+15*8+x) if frame%(x%3+2)>0 then for y=0,15 do a=0x8000+water*64+y*8+x p=peek4(a);poke4(a,old);old=p end end end game() end t2 = time() if frame%5==0 then since_last_frame = t2-t1 end if DEBUG and not SCENIC then print(string.format("%.1f", since_last_frame), 2,128,3+since_last_frame//20,false,1,true) end t1 = t2 end function MENU(i) if i==0 then --o nanpa e tenpo camera:toggle_timer() end end TILE_KENTAWA = 16 TILE_MAYBESPIKES = 103 TILE_SPIKES = 98 TILE_ESUN = 134 TILE_ANTE = 135 -- Shops by door height SHOPS_BY_HEIGHT = { [3]={ {x=210,y=68} }, [4]={ {x=210,y=85} }, } TOWER_MX = 120 PREFABS = {{x=30,y=68},{x=30,y=68+17}} for i=0,7 do PREFABS[#PREFABS+1]={x=60,y=i*17} end procgen = {head=68,last_prefab=nil,floors=0,shops=0} function procgen:worldbuild() while self.head > -inf do self:next() end end function procgen:next() my = self.head if self.floors0 then --There's a shop gap. Put a shop there. self:gen_shop( my+top_esun,mx+x, esuns,(x==0)) end end end function procgen:gen_shop(door_top,door_left,door_height,shop_to_left) shops = SHOPS_BY_HEIGHT[door_height] if (#shops<1) or --no shops fit door; shouldn't happen (random()<0.25 and not self:shopdesperate()) then --Replace esuns with wall self:gen_repl_door(door_top,door_left,door_height,true) return end r = choice(shops) --Find the bottom of the shop's doorway local last_dy for dy=0,16 do last_dy=dy if mget(r.x,r.y+dy)==TILE_ESUN then break end end mx = shop_to_left and (door_left-30) or (door_left+2) my = door_top-last_dy copy_room( r.x,r.y,mx,my, 30,13,0,shop_to_left) reify(mx,my,30,17) self.shops = self.shops + 1 end function procgen:gen_repl_door(door_top,door_left,door_height,solid) for mx=door_left,door_left+1 do tile = solid and (mx==door_left and 116 or 117) or 0 for my=door_top,door_top+door_height-1 do mset2(mx,my,tile) end end end function procgen:gen_house(mx,my,spikeprob) --Maps containing T-room quadrants -- (T-rooms are rooms with up, down, & middle passages) local thouses = {} for y=0,119,17 do add(thouses, {x=180,y=y}) end --Rare ones if random()<0.5 then for y=0,51,17 do add(thouses, {x=210,y=y}) end end -- thouses = [(0,51)] --Test rooms only upper_trooms = {} lower_trooms = {} for _,h in ipairs(thouses) do add(upper_trooms, {x=h.x,y=h.y}) add(upper_trooms, {x=h.x+15,y=h.y}) add(lower_trooms, {x=h.x+15,y=h.y+9}) add(lower_trooms, {x=h.x+15,y=h.y+9}) --Upper-left quadrant r = choice(upper_trooms) copy_room(r.x,r.y,mx,my,15,9,spikeprob, r.x%30~=mx%30) --Upper-right quadrant r = choice(upper_trooms) copy_room(r.x,r.y,mx+15,my,15,9,spikeprob, r.x%30~=(mx+15)%30) --Lower-left quadrant r = choice(lower_trooms) copy_room(r.x,r.y,mx,my+9,15,8,spikeprob, r.x%30~=mx%30) --Lower-right quadrant r = choice(lower_trooms) copy_room(r.x,r.y,mx+15,my+9,15,8,spikeprob, r.x%30~=(mx+15)%30) end end function procgen:shopdesperate() return self.floors==MAX_FLOORS-1 and self.shops<1 end TILE_FLIPS = { [116]=117,[117]=116, [132]=133,[133]=132, [119]=120,[120]=119, [123]=156,[156]=123, [124]=155,[155]=124, [139]=172,[172]=139, [140]=171,[171]=140, [160]=161,[161]=160 } -- big blocks for i=0,3 do TILE_FLIPS[148+16*i]=149+16*i TILE_FLIPS[149+16*i]=148+16*i end -- make invisible wall invisible memcpy(0x4000 + 162*32, 0x4000, 32) COLOR_BRICK = { [116]=212, [117]=213, [132]=228, [133]=229, [180]=214, [181]=215, [196]=230, [197]=231, } IS_COLOR_BRICK = {} for k,v in pairs(COLOR_BRICK) do IS_COLOR_BRICK[v]=true end function copy_room(rx,ry,mx,my,w,h,spikeprob,flip) local spiky = random()0 then seed = today() math.randomseed(seed) end spawn(pipi) procgen:worldbuild() reify(120,85,60,17*2) smasher_trigger = Choir(1140,1125,40,6) function activate_smashers() smasher_start = frame end function smasher_trigger:extra() activate_smashers() end -- unscroll the screen when resetting original_reset = reset function reset() poke(0x3FF9,0) original_reset() end