-,@xŒ ™„¹¾’īĢ’°£’‹’is8l†¾’’<•ĘeĀŽīīņ”°Āe Ļ’’’’’’’’2Tv˜ŗÜžļĶ«‰gE#2Tv˜ŗÜž2Tv˜ŗÜž2Tv˜ŗÜžļĶ«‰gE#2e¹ÜF#E3333333333333333333333333333333333333333333333333333333333333333UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻīŻŻŻīŻŻŻŻŻŻŻŻŻŻŻŻŻŻ33333333333333333333333333333333333333333#233""33#23333333333333UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUŻŻŻŻŻŻŻŻŻŻŻŻŻķŽŻŻķŽŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ3333U3U3UUUUUUUUffffffffUUUUUUUU33333S333UUU5UUUffffffffUUUUUUUUUUUUUUUUUUUUUUUUffffffffUUUUUUUUUUUUUUUUUUUUUUUUffffffffUUUUUUUUŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻīīīīīīīīŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻīīīīīīīīŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ™ŻŻ™™Ż™ŁŻ™ŁŻŻ™ŻŻ™ŻŻŁŻŻŻŻŻŻ™ŻŻŻ™™ŻŻ™ŁŻ™™ŻŁ™ŻŁ™ŁŻŁŁ"""""bffbfffbfffbfff"fff"bff""""""""&"f&f&f"""""ffffffffffffffffUUUUUUUUUUUUUUUUffffffffffffffffUUUUUUUUUUUUUUUUffffffffffffffffUUUUUUUUUUUUUUUUffffffffffffffffUUUUUUUUUUUUUUUUīīīīīīīīīīīīīīīīŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻīīīīīīīīīīīīīīīīŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŁŻŻ™ŻŻŻ™ŻŻŻ™ŁŻŻ™ŁŻ™™ŻŁ™ŻŻŁŻŻŁŁŻŻ™ŁŻŻ™ŻŻ™Ż™ŁŻ™™ŻŻ™ŻŻŻŻŻŻŻ2cff2fff‚ˆˆˆ‚ˆˆˆƒˆˆˆ†ˆˆˆ†ˆˆˆ†ˆˆˆ2"""&#"cˆ3"fˆ22fˆ(bfˆ(bfˆhbfˆhff&#"ff"bff#fff&ffffffffffffffUfffff""ff6"ffV"fff%fff6fffVfffVffff2cff2fffBDDDBDDDCDDDFDDDFDDDFDDD2"&#D3D2D$D$DdDd113133131133333333333333333333331D@UGTfuTfuTfuTfuTfu4pwffwgffwgfDwgFDwgFtwgFdwgFdwgFtffffffffDDDfDDDdwDDdfDDdf"Bdw"BdfffffffffVUfbVUfbVUf2fff2bff"#ffffffffffffffffffffcff6bff&"ff""bUffUfffUUffUU5fUU%fUU#bfU"2f&""#UfffUfffUfffUff6Uff#ffb"&f""""""fffffffffVUfbVUfbVUf2fff2bff"#fffffffffffff6f&f"f1ffffffffffffffffffff33331331UfUUfUUfUUfUUfUUUUUUUUUUUUUUUUU1313313f1fffffffffffCUG33D43333333331331UfUUfUU3333UUUUUUUUUwgFtwgftwgffwgffwgffwgfVwgfVwgfVw"Bdw"BfffffffffffffUUUfUUUfUUUffffaffafafUUfUUfUUfUUaUUUUUUffffffffffffaffafafUUfUUfUUfUUfUUfUUfUUaUUUUUUUUUUUUwgfVwgffwgffUUUfffffffff3033303333333333"333333333333""""33333333333"""33333333303033303#03"33"0333333#3#""#"""""""""""""D$"BDD#""""""""""""""""DD$BDD$B B$""""""""""""""D"D"D"DBD"DBD"DBD"DB""""""""""""DD""DD$D@$D$B$D$B$D"233""23"""""""""""""D""$DBD$DBD333233"23""33""23"""3$""3333"3#"3#"3#"3#"3#"3#"3#""DD"D"D"D"D"DD"DDD"DD"D"D"D"DB$""BDD"BDD"B"B$""B$""B$""B$""D"DBD"DBDDDBDDDBDB"D"B"D"B"D"B$B$D$B$D$B$D$B$D$B$D$B$D$B$D$B$D$DBD$DBDDB$DDB$DDB$DDB$@DB$BDB$D""3D$"2@$"2B$"2B$"2B$"2B$"2B$"23333333333333333#"3#"3#"3#"3#"3#"03#"33#""DDD"DD" """""""D""BU""Tf""TfBDD$BDD$ """"$"G"u$u$""D"B"D""""""""!!"DD$DDD D"""""!!!!! !"DBD"DBD""""""BD""tU$BWfEBWfED$"2D "2""2"""2"""2"""2"""2"""233333333333333330333333333""3#""3#""3#""3#""3#""33333333""""""""""""""""D"DBD"DB33""3#""""""""""""""""""$B$D$B$D""Tf""Tf""Tf""BU"""D""""D$"DDDBDu$"u$"u$"G""$""""""D$DDD$DD""!"!"!" """"$"DDDBDD!"!"!! """""DD$$DDDBWfEBWfEBWfE"tU$"BD""""""DD"BDD$""""""""""""""""""""""""D"DBD"DB33332333""""""""""""""""$B$D$B$D33333333""""""""""""""""D$""DD""3333323232323233#""3#""3#""3#""3#""3#""73""73#"D"DBD"DBD"DBD"DBD"DBD$DB@DDBDDBDB$DDB$DDB$DDD$DDD$DDD$DDD$DDD$DDB"DB$"DB$"DB$"DB$"DBD"DBD"DB D""D"""D"""D"""DBD"DBD"DD"DDDB@DB$BDB$BDB$BDB$B$" B$D$B$D$DD$D"D$D"D$D"D DBD"DB$DD $DD$B@$B$B$B$B$B$B$B$B$B$B$B$B$B$B$D"DBD"DBD"DBD"DBD"DBD"DBD"DBD"DBDB$DDB$DDB$DDD$DDD$DDD$DDD$DDD$DD"""D"""D"""D"""D""BD""D""D "22323232323233s3sp33"p73#w33p73w3p7wp"DB""B"""B3""33""333"7333w733D$D$D$D$@$D B$D" D"""""""3#"""DB$"DB$"DB$DDB$DB$D BD"D"""""D@""D""D"""D"""D"D$D"D$D" D"DB$@DB$BDB$BDB$BDB$BDB$BDBDDDDD$D@D$DD$D"D$D"D$D"D$D"D$D"D D"DB$B$B$B$B$B$B$B$B$B$BDD$DD ""D"DBD"DBD"DBDDDB@DD """"""""D$D$@$$B$"$ " """"""3""332333""3""23""33"33s333w33s3swsw3swpw73pwwpw333#3333w733www7pwwp""""33""33333333ww33wwwwww"""""""33""33333333w733wwwwpww"""""""""333333333333wwwwwwww""""""""""333333333333wwwwwwww""""""""333333333333swwwwwww""233333333333swwwwwww333s33wwswwwwwD@"$"@""@""@"@"bD""B""R"U!UFD@"$"@""@""@"@"bD""B""R"U!UFDD@UuTfVGTfVGTfVGTfVG@UuDD ĄĢ ĄĢĢ ĢÜĄ ĄĢĢ ĢÜĄ @""@"‚@"‚@"R@"‚$R@T@%BˆBˆBUBˆBRT@@""@"‚@"‚@"R@"‚$R@T@%BˆBˆBUBˆBRT@@tG@rw@w'twwG@TETE@@$@ABA@@``ffffff`f````fffpff`wff""""""pppwgwwwwppw`pffffgfp`"""!!!!!DD@UuTfVGTfVGTfVGTfVG@UuDDDD@@@@@@DD!"!"""!@tG@ww@Uf@Uf@UfTF@@@@@@@@@°   ° Ė     ° ˰° °    ° ° ° °@īN@īN@NN@īNä@īN@īN@NN@N@īä@ī@ī@N@īäNNNNNN@N@īä@ī@ī@N@īäNNNNNN@īNä@īN@īN@NN@īNä@īN@īdī@v~dwēdvwdPUddNn~FwWwUUPPP@ī@ī@ī@ē@w@@TNNNGGEEP@īN@NN@īNä@īN@īN@NN@īNddddd@df@DPPPPPEfD@TTT@T@UDE PŠPPEPEä@īN@īN@NN@īNä@īN@īN@NN@īNä@īN@īN@NN@īNä ē 48‰;Ž<€;€9€:€;€;€<€=€=€=€=€>€>€>€>€>€>€?€>€>€>€>€>>>?€?€RM| #Bbq!Cw       °°°ĄĄĄĄŠŠąš*x%p"x"p x p p p!p"y#q$t%&p'y'p'p(p(p(z)p)p)p)z*p+y,p-y.x/xPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĪŹĆĄĄĮĆÅĘČČŹĖĖ Ė Ģ ĢĢĶĶĶĪĪĪĻĻĻĻĻĻ4      p p p pppppŒ0 06;47:;<=>???????????????????ō ,ƒƒ ƒ›¦ ¦›¦[¦ƒCc¦„ † f†ō`ą@ąą€ą@C#A f ††¦accƒ ƒ ƒ ƒƒ£ ƒƒƒ£ ƒ ƒƒƒ Cc c[†[¦›£[ƒƒ #Cgś‡ C‡PPƒƒ£ ƒƒƒ£ £ ƒ ƒ ƒƒƒ ƒ ƒƒ `/ƒO£/ƒŸ£ƒŸ£_ƒ£_ƒ_£/ƒ ƒƒƒ¦_¦?¦¦ż‡C‡PP§£@£ƒ ƒƒš C‡E§PP§ @§ § § #C C fC † ¦@–cCƒ†† † † ††¦¦¦ ¦ ¦ ¦c§§#fA f† †¦ ¦¦‡§ g g gg§ § § § § §‡‡§§ § § § § §§C§A‡§§§§ § § §§Ē†† † f†`ąą`@`ąą ą@```€ą`ą@ą@ą@ąƒƒ ƒ›¦ †‘Qƒ g‡‡‡§‡‡‡‡§‡‡‡§‡ gg‡g ‡‡g gg‡ ‡ ‡‡§ § GgggGgg gggggg‡ g gg‡ ‡‡g ‡‡ ‡g‡§‡ g‡§ §’¦ C F g‡‡‡§‡‡‡‡§‡‡‡§‡ gg‡g ‡Aƒc ccƒ ƒ ƒ ƒƒcƒ‡‡‡§‡‡‡‡§‡‡‡§‡ §g‡g ‡‡g gg‡ ‡ ‡‡§ §ū# C# #C #  # C˜÷0Aap„ ³I³p¬UŸĢ£`Ī£ŸĪ£į ¤7Ī šĪ »?@NN@īN@D@DäDD@@īNDDD@īNDD@NN@D@D@īNä@īN@īN@NN@īNä@īN @īNĄĢ@NN @īNĄäĢ @īNĄ@īN@NN`@īN`@äDfff`f@īN`fffff@īN`fffffff@ēGffffffff@wGfffffffff@E`gfffffff@E`wwfffffffTPpwwgffffgf@EpwwvwfffvgT PDD`wwffwvfffvTŠP@Uuvgfwwwfff`TPTfVGfff`fgffvwwfff@ETfVG`ffffpfffgfgffTP33333333TfVG`ffgfffffffwff@UE0333333333333ĄTfVGfvg`ffffffggD3333333333333333Ģ @Uuffv`fffvffff033333""""""""33333ĆĄĢDD`ffvffffwfff0333#""""""""""""2333 wwffffgfffffff0333#""""""""""""""2333`pwwgfffwfffffff33#""""""""""""""""""233ĄĢĢmffvfgfvffffffvf03#"""""""D"D"DD"""""""23ĢÜffff†ffgffffffgD33"""""DD$D"DBDD$D""""""33Ą ffpffhfffffgfvf6@UG03#""""BDD$D"DB@$D"D""""23wgfffff`f`wfffffffvgff†1ƒTfu03"""D$B D"DB$B$D$DBD"""3swfffffff ```ffgfffffffvvff13ƒTfu33""BDDB$""D"DB$B$D$DBD$""33wfFDDDdf ""pvfffffvvffgfffw333ƒTfu33""DDB$""D"DB$B$D$DBDD""33wfDDDDDf bfb""wgffffsgwvfffff333ƒTfu3#""D"DBDD"D"DB$B$D$DBD$"23wfDwGDDf "ffff""rgffcgffwwffff63333ƒ1Tfu43#""D"DBDD"DDDB$B$DDDB$@$"23wfDfFDDf""bffff&"rgvsfffvwwfff1333333ˆ13CUG333#""DDB"DDDB$B$DDDB$B$"23wfDf&"Df2"bffff&""rvwgffwvwggf633333ˆ3ƒ1333D43333#""DDDB$""DB$B$DDDB$B$"23wfDw'"Df2c"bfff&""rwwwgfvgwvqc33333ˆ333ƒˆ13333333#""DDB$"""D"B$B$DDDB$B$"23wfDw'"Df‚ff""bf&"#bwwsxgwvwcƒ86333ˆ333333ˆf13333U3#""D"DB$"""D"B$B$D@DB$B$"23wfFw'"df‚ˆff""""f#rwwhswfr63ƒ833ˆ33333333ˆff133UU3#""D"DB$"""D"B$B$DBDB$B$"23wfffffff‚ˆˆf2""bf#bxx33s3"133ƒ8ˆ3333333333ˆfff1UUU3#""DDDBDD$"D"BDD$DDBDD$"23wfffffffˆ‚ˆˆˆ&""cff2†83333"3333ˆ8333333333ˆ3ƒffffUUUU3#""DDBDD$"D"DD D"DBDD "23wfffffffˆ3‚ˆˆˆˆ3"fff‚3ƒ8333"333ˆ3ƒ83333333ˆ333ƒffffUUUU3#""  """""""23wffUUUefˆ33‚ˆˆˆˆ22fff233ƒ833"33ˆ333ƒˆ33333ˆ33333ƒffffUUUU3#""""""""""""""""""""""""23wffUUUefˆ333bˆˆˆˆ(2fff2333ƒ83!2ˆ333333ˆ333ˆ3333333ƒffffUUUU3#""""D$"!!!"BD""""23wffUUUefˆ3333bfˆˆˆ(bfff23333ƒ8!‚33333333ˆ3ˆ333333333ƒffffUUUU3#"""BUG"!!!"tU$"""23wffUUUefˆ33333bfffˆhbfUf233333ƒ!2333333333ˆ83333333333ˆffffUUU53#"""Tfu$! BWfE"""23sfffffff€833333bffffhbfUf233333!233333333ˆ3ƒ833333333ˆ…a333333333#"""Tfu$""!"BWfE"""2333333333€8ƒ83333bVUfffbfff233333!23333333ˆ333ƒ8333333ˆU…0333333333""""Tfu$""!"BWfE""""333333333€833ƒ8333bVUfffbfV623333ƒ!"ˆ33333ˆ33333ƒ83333ˆUU…333333333#""""Tfu$""!!"BWfE""""2333333333€83333ƒ8332bVfffbfU%2333ƒX!"Uˆ333ˆ3333333ƒ833ˆUUU…33""""""""""""Tfu$""!!BWfE""""""""""""33€8333333ƒ83"cffffbfU%233ƒX"RUˆ3ˆ333333333ƒ8ˆUUUU…3#""""""""""""BUG"""!!"tU$""""""""""""23€833333333ƒ8""ffffbfU#‚8ƒXU"RUUˆ83333333333ƒXUUUU…3#"""""""""""""D$"""  "BD"""""""""""""23ˆ3333333333ƒˆ"ffffbfU"2ƒˆUUU!UU…8ƒ833333333ƒ8ƒXUUU…3#""""""""""""""""""""""""""""""""""""""""""23Xˆ33333333ƒ83ˆbfffbfU"‚83ˆUUUUU…833ƒ8333333ƒ833ƒXUU…3#""D"DB$B$DD$"DD$DD$"DD"DD$"DD"D"DB$B$DD$""23XUˆ333333ƒ8333ˆbf6bf&‚8333ˆXUUUU…83333ƒ83333ƒ83333ƒXU…3#""D"DB$B$DDDBDD$DDDBDD$DDDBDD$D"DB$B$DDD""23XUUˆ3333ƒ833333ˆb""b‚833333ƒXUU…8333333ƒ833ƒ8333333ƒX…3#""D"DBDB$DDB DDB@$DDB@$D"DBDB$DD""23XUUUˆ33ƒ83333333ˆ""‚83333333ƒX…833333333ƒ8ƒ833333333ƒˆ3#""D"DBDB$D"DB$""D"DB$B$D"DB$B$D"DBDB$D"D""23XUUUUˆƒ8333333333ˆ‚8333333333ƒˆ3333333333ƒˆ3333333333ƒ3#""D"DBDB$D"DB$""D"DB$B$D"DB$B$D"DBDB$D"D""23XUUUUUˆ3333333333ƒˆ333333333ƒ83ˆ33333333ƒ83ˆ33333333ƒX3#""D"DBDD$D"DB$""D"DB$B$D"DB$B$D"DBDD$D"D""23XUUUUUXˆ33333333ƒ83ˆ3333333ƒ8333ˆ333333ƒ8333ˆ333333ƒXU3#""D"DBDD$D"DB$""DBDB$ DBDB$B$D"DBDD$D"D""23XUUUUUXUˆ333333ƒ8333ˆ83333ƒ83DD33ˆ3333ƒ833333ˆ3333ƒXUU3#""D$DBDD$D"DBDD"DBB$""DBB$B$D"DBDD$DBD""23XUUUUUXUUˆ3333ƒ833333ƒ833ƒ83C""433ˆ33ƒ83333333ˆ33ƒXUUU73""@DDBDD$D"DBDD"DD B$D$DD B$B$D"DBDD$DD""3sXUUUUUXUUUˆ33ƒ83333333ƒ8ƒ833$""B333ˆƒ8333333333ˆƒXUUUU73#"DDBDD$D"DB"DD$B$D$DD$B$B$D"DBDD$DD "23sXUUUUUXUUUUˆƒ8333333333ƒˆ33C""""433ƒˆ3333333333ƒXUUUUUp33""DBD$D"DB$""D@DB$@$D@DB$B$D"DBD$D""33XUUUUUXUUUUUˆ333333333ƒ83ˆ3C""R"43ƒ83ˆ33333333ƒXXUUUUUp73#""B$D$D"DB$""DDB$B$DDB$B$D"DB$@$""23sXUUUUUXUUUUUXˆ3333333ƒ8333ˆC"U!4ƒ8333ˆ333333ƒXUXUUUUUw33"""B$@$D"DB$""D"DB$B$D"DB$B$D"DB$B$"""33wˆUUUUUXUUUUUXUˆ83333ƒ833333H"bUFƒ833333ˆ3333ƒXUUXUUUUUp733"" B$DDDB$""D"DB$B$D"DB$B$DDDB$ ""33sˆUUUUXUUUUUXUU…833ƒ8333333C""%B83333333ˆ33ƒXUUUXUUUUUw333""" DDB$""D"DB$B$D"DB$B$@D """333wˆUUUXUUUUUXUUU…8ƒ83333333C"‚ˆB333333333ˆƒXUUUUXUUUUUp7333""""D BDD$D"DB$B$D"DBDD$D """"333sˆUUXUUUUUXUUUU…ˆ33333333C"‚ˆB333333333ƒXUUUUUXUUUUUw7333"""""DD$D"DBDD$D"DDD """""333swˆUXUUUUUXUUUUUXˆ3333333C"RUB33333333ƒXXUUUUUXUUUUUpw7333#""""" D"DDD D"D""""""2333swˆXUUUUUXUUUUUXUˆ333333C"‚ˆBˆ333333ƒXUXUUUUUXUUUU…pw73333#""""""""""""""""23333swˆUUUUUXUUUUUXUUˆ3333ƒ8$RR43ˆ3333ƒXUUXUUUUUXUUU…pww333333""""""""""""""""""333333wwXˆUUUUXUUUUUXUUUˆ33ƒ83CTT333ˆ33ƒXUUUXUUUUUXUU…8ˆpww733333333""""""""""33333333sww€XUˆUUUXUUUUUXUUUUˆƒ8333CC3333ˆƒXUUUUXUUUUUXU…8ˆ3ƒwww73333333333333333333333swww€XUUˆUUXUUUUUXUUUUUˆ3333333333ƒXUUUUUXUUUUUX…8ˆ333ƒpwwww333333333333333333wwww€XUUUˆUXUUUUUXUUUUUXˆ33333333ƒXXUUUUUXUUUUUˆ8ˆ33333ƒpwwwww73333333333swwwww€XUUUUˆXUUUUUXUUUUUXUˆ333333ƒXUXUUUUUXUUUU…8ˆ3333333ƒwwwwwwwwwwwwwwwwww€XUUUUUˆUUUUUXUUUUUXUUˆ3333ƒXUUXUUUUUXUUU…ˆˆ333333333ƒpwwwwwwwwww€XUUUUUXˆUUUUXUUUUUXUUUˆ33ƒXUUUXUUUUUXUU…Ų83333333333ƒ€XUUUUUXUˆXUUXUUUUUXUUUUˆƒXUUUUXUUUUUXU…Ųˆ…833333333ƒˆ€XUUUUUXUU…XUXUUUUUXUUUUUXUUUUUXUUUUUX…ŲŲU…8333333ƒX…€XUUUUUXUUU…XXUUUUUXUUUUUXUUUUUXUUUUUˆŲŲŻUU…83333ƒXU…€XUUUUUXUUUU…ˆUUUUUXUUUUUXUUUUUXUUUU…ŲŲŻŻUUU…833ƒXUU…DD@@@€XUUUUUXUUUUDDˆUUUUXUUUUUXUUUUUXUUU…XˆŲŻŻŻUUUU…8ƒXUUU…DfDD@D@D@DDDDD@D@DDD@DDDDD€XUUUUUXUUUEUuԈUUUXUUUUUXUUUUUXUU…XUŲŻŻŻŻUUUUU…XUUUU…DDfDFDdDdfDFdDfDdFddFDfFd€ˆUUUUUXUUUTfVG݈UUXUUUUUXUUUUUXU…XUUŲŻŻŻŻUUUUUUXUUUU…DDDDdfDfD@d@DfD@@D@€XˆUUUUXUUUTfVGŻŻˆUXUUUUUXUUUUUX…XUUUŲŻŻŻŻUUUUUUXUUUU…DfDFDDDDDDD`DFDDD`DFDD`D€XUˆUUUXUU…TfVGˆŻŻˆXUUUUUXUUUUUˆXUUUUŲŻŻŻŻUUUUUUXUUUU…ff`ffffffff`ffff`fffXUUˆUUXU…8TfVG3ˆŻŻˆUUUUUXUUUU…XUUUUUŲŻŻŻŻUUUUUUXUUUU…XUUUˆUX…83CUu433ˆŻŲˆUUUUXUUU…XXUUUUUŲŻŻŻŻUUUUUUXUUUU…XUUUUˆˆ8333DD3333ˆŲ݈UUUXUU…XUXUUUUUŲŻŻŻŻUUUUUUXUUUU…XUUUUUˆ3333333333ƒˆŻŻˆUUXU…XUUXUUUUUŲŻŻŻŻUUUUUUXUUUU…XUUUUUXˆ33333333ƒXŲˆŻŻˆUX…XUUUXUUUUUŲŻŻŻŻXUUUUUXUUUU…XUUUUUXUˆ333333ƒXUŲŻˆŲ݈ˆXUUUUXUUUUUŲŻŻŻˆ€XUUUUXUUU…XUUUUUXUUˆ3333ƒXUUŲŻŻŲŻXUUUUUXUUUUUŲŻŻŲ€XUUUXUU…XUUUUUXUUUˆ33ƒXUUUŲŻŻŻŲXUUUUUXUUUUUŲŻŲŻ€XUUXU…XUUUUUXUUUUˆƒXUUUUŲŻŻŻŻˆUUUUUXUUUU…ŲŲŻŻ€XUX…XUUUUUXUUUUUXUUUUUŲŻŻŻŻŻXˆUUUUXUUU…XˆŲŻŻŻ€XˆXUUUUUXUUUUUXUUUUUŲŻŻŻŻŻXUˆUUUXUU…XUˆŻŻŻŻ€ˆUUUUUXUUUUUXUUUUUŲŻŻŻŻŻXUUˆUUXU…XUU8ˆŻŻŻˆˆUUUUXUUUUUXUUUUUŲŻŻŻŻŻXUUUˆUX…XUUU83ˆŻŻ€83ˆUUUXUUUUUXUUUUUŲŻŻŻŻŻXUUUUˆˆXUUUU833ˆŻˆ€8ƒ83ˆUUXUUUUUXUUUUUŲŻŻŻŻŻXUUUUUXUUUUU8333ˆˆ3ƒ€833ƒ83ˆUXUUUUUXUUUUUŲŻŻŻŻŻXUUUUUXUUUUU83333ˆˆ333ƒˆ€83333ƒ83ˆXUUUUUXUUUUUŲŻŻŻŻŻXUUUUUXUUUUU83333ƒˆ333333ˆ€8333333ƒ83ˆUUUUUXUUUU…ˆŻŻŻŻŻXUUUUUXUUUUU8333ƒXˆ33333333ˆ€833333333ƒ8؈UUUUXUUU…ŲŲˆŻŻŻŻXUUUUUXUUUUU833ƒXUˆ3333333333ˆˆ3333333333ƒˆŻˆUUUXUU…ŲŻŲŻˆŲŻŻXUUUUUXUUUUU83ƒXUU8333333333ƒˆXˆ33333333ƒ83ˆŻˆUUXU…ŲŻŻŲŻŻŲŻXUUUUUXUUUUU8ƒXUUUˆ83333333ƒX…XUˆ333333ƒ8333ˆŻˆUX…ŲŻŻŻŲŻŻŻŲXUUUUUXUUUUUˆXUUUU8ƒˆ33333ƒXU…XUUˆ3333ƒ833333ˆŻˆˆŲŻŻŻŻŲŻŻŻŻˆUUUUUXUUUU…XUUUUU833ˆ833ƒXUU…XUUUˆ33ƒ83333333ˆŻŲŻŻŻŻŻŲŻŻŻ83ˆUUUUXUUU…XXUUUUU8333ƒ8ƒXUUU…XUUUUˆƒ8333333333ˆŲŻŻŻŻŻŲŻŻ8333ˆUUUXUU…XUXUUUUU83333ƒXUUUU…XUUUUUˆ3333333333ƒˆŻŻŻŻŻŲŻ833333ˆUUXU…XUUXUUUUU8333ƒXXUUUU…XUUUUUXˆ33333333ƒ83ˆŻŻŻŻŲ83333333ˆUX…XUUUXUUUUU833ƒXUXUUUU…XUUUUUXUˆ333333ƒ8333ˆŻŻŻˆ8333333333ˆˆXUUUUXUUUUU83ƒXUUXUUUU…XUUUUUXUUˆ3333ƒ833333ˆŻŻˆ3333333333ƒXUUUUUXUUUUU8ƒXUUUXUUUU…XUUUUUXUUUˆ33ƒ83333333ˆŻXˆ33333333ƒXXUUUUUXUUUUUˆXUUUUXUUUU…XUUUUUXUUUUˆƒ8333333333ˆXUˆ333333ƒXUXUUUUUXUUUU…XUUUUUXUUUU…Ѿ-- title: Beyond the UnderGround -- author: pke1029 and starling -- desc: An entry for the GitHub Game Off 2021. -- script: lua WIDTH = 240 HEIGHT = 136 W2 = 120 H2 = 68 PI = 3.1415 sin = math.sin cos = math.cos max = math.max min = math.min abs = math.abs sqrt = math.sqrt sqrt2 = sqrt(2) ceil = math.ceil floor = math.floor random = math.random sysmouse = mouse systime = time sysmusic = music time = { t = 0, dt = 0, last_t = 0, count = 0, fps = 60, ts = 0, show = false, update = function(self) if keyp(20) then self.show = not self.show end local t = systime() self.dt = t - self.t self.t = t self.ts = self.t // 1000 if t - self.last_t > 1000 then self.last_t = t self.fps = self.count self.count = 0 else self.count = self.count + 1 end end, draw = function(self) if self.show then print(self.fps, 228, 1, 4, true) end end } debugger = { task = {}, update = function(self) self.task = {} end, draw = function(self) for i = 1,#self.task do print(self.task[i], 0, 7*(i-1), 1) end end, trace = function(a) debugger.task[#debugger.task+1] = a end, } mathFun = { between = function(x, a, b) return x >= a and x < b end, clamp = function(x, a, b) if mathFun.between(x, a, b) then return x end if x < a then return a end if x >= b then return b end end, bool2int = function(a) return a and 1 or 0 end, mod = function(x, a) return x - floor(x/a)*a end, sign = function(x) return x > 0 and 1 or -1 end, maprange = function(x, a1, b1, a2, b2) return (x - a1)/(b1 - a1)*(b2 - a2) + a2 end, isnan = function(x) return x ~= x end, round = function(x) return math.floor(x + 0.5) end, lerp = function(x, a, b) return (1-x)*a + x*b end, isin = function(a, list) local flag = false for i = 1,#list do if list[i] == a then flag = true break end end return flag end, randlist = function(list, n) if n == nil then n = #list end local i = random(1, n) return list[i] end, randprob = function(list, prob) local a = random() local cumsum = 0 for i = 1,#list do if a <= prob[i] then return a end cumsum = cumsum + prob[i+1] end end, sum = function(list) local tot = 0 for i = 1,#list do tot = tot + list[i] end return tot end, } Vec = { new = function(x, y, z) local v = {x, y, z} setmetatable(v, Vec.mt) return v end, mt = { __add = function (u, v) return Vec.new(u[1]+v[1], u[2]+v[2], u[3]+v[3]) end, __sub = function(u, v) return Vec.new(u[1]-v[1], u[2]-v[2], u[3]-v[3]) end, __mul = function(k, v) if type(k) == "table" then return Vec.new(k[1]*v[1], k[2]*v[2], k[3]*v[3]) else return Vec.new(k*v[1], k*v[2], k*v[3]) end end, __div = function(v, k) if type(k) == "table" then return Vec.new(v[1]/k[1], v[2]/k[2], v[3]/k[3]) else return Vec.new(v[1]/k, v[2]/k, v[3]/k) end end, __pow = function(v, k) return Vec.new(v[1]^k, v[2]^k, v[3]^k) end, __eq = function(u, v) return u[1] == v[1] and u[2] == v[2] and u[3] == v[3] end, __tostring = function(v) return "(" .. v[1] .. "," .. v[2] .. "," .. v[3] .. ")" end, __concat = function(s, v) return s .. "(" .. v[1] .. "," .. v[2] .. "," .. v[3] .. ")" end, __index = { normalise = function(self) local r = Vec.norm(self) self[1] = self[1] / r self[2] = self[2] / r self[3] = self[3] / r return self end, proj = function(self, camera) local x = Vec.dot(self, camera.x) local y = Vec.dot(self, camera.y) return x+W2 + camera.x2, -y+H2 - camera.y2 end, rotate = function(self, ax, ay, az) local cx = cos(ax) local sx = sin(ax) local cy = cos(ay) local sy = sin(ay) local cz = cos(az) local sz = sin(az) local x1 = self[1] local y1 = self[2] * cx - self[3] * sx local z1 = self[2] * sx + self[3] * cx local x2 = x1 * cy + z1 * sy local y2 = y1 local z2 = -x1 * sy + z1 * cy self[1] = x2 * cz - y2 * sz self[2] = x2 * sz + y2 * cz self[3] = z2 return self end, draw = function(self, col) if col == nil then col = 4 end local x, y = self:proj(camera) circb(x, y, 2, col) end, } }, norm = function(v) return sqrt(v[1]*v[1] + v[2]*v[2] + v[3]*v[3]) end, dot = function(u, v) return u[1]*v[1] + u[2]*v[2] + u[3]*v[3] end, cross = function(u, v) return Vec.new(u[2]*v[3]-v[2]*u[3], v[1]*u[3]-u[1]*v[3], u[1]*v[2]-v[1]*u[2]) end, avg = function(u) return (u[1] + u[2] + u[3]) / 3 end, dist = function(u, v) return Vec.norm(u - v) end, mid = function(u, v) return (u + v) / 2 end, line = function(x0, y0, u, v, camera, col) local x1, y1 = u:proj(camera) local x2, y2 = v:proj(camera) line(x1, y1, x2, y2, col) end, yrotate = function(v, c, s) local x = v[1]*c + v[3]*s local y = v[2] local z = -v[1]*s + v[3]*c return Vec.new(x, y, z) end, xmirror = function(v) return Vec.new(-v[1], v[2], v[3]) end } Mat = { new = function(A) setmetatable(A, Mat.mt) return A end, newZeros = function(n, m, c) local A = {} if c == nil then c = 0 end for i = 1,n do A[i] = {} for j = 1,m do A[i][j] = c end end setmetatable(A, Mat.mt) return A end, mt = { __add = function(A, B) local n, m = A:size() C = {} if getmetatable(B) == Mat.mt then for i = 1,n do C[i] = {} for j = 1,m do C[i][j] = A[i][j] + B[i][j] end end else for i = 1,n do C[i] = {} for j = 1,m do C[i][j] = A[i][j] + B end end end return Mat.new(C) end, __sub = function(A, B) local n, m = A:size() C = {} if getmetatable(B) == Mat.mt then for i = 1,n do C[i] = {} for j = 1,m do C[i][j] = A[i][j] - B[i][j] end end else for i = 1,n do C[i] = {} for j = 1,m do C[i][j] = A[i][j] - B end end end return Mat.new(C) end, __mul = function(k, A) local n, m = A:size() B = {} if getmetatable(k) == Mat.mt then for i = 1,n do B[i] = {} for j = 1,m do B[i][j] = k[i][j] * A[i][j] end end else for i = 1,n do B[i] = {} for j = 1,m do B[i][j] = k * A[i][j] end end end return Mat.new(B) end, __div = function(A, k) local n, m = A:size() B = {} for i = 1,n do B[i] = {} for j = 1,m do B[i][j] = A[i][j] / k end end return Mat.new(B) end, __tostring = function(A) local text = "" local n, m = A:size() for i = 1,n do for j = 1,m do if j < m then text = text .. A[i][j] else text = text .. A[i][j] .. "\n" end end end return text end, __index = { size = function(self) local n = #self local m = 0 if n ~= 0 then m = #self[1] end return n, m end, } } } Face = { new = function(verts, uvs, col, is_map) local f = {verts=verts, uvs=uvs, col=col, is_map=is_map} setmetatable(f, Face.mt) return f end, mt = { __index = { drawCol = function(self) local x1, y1 = self.verts[1]:proj(camera) local x2, y2 = self.verts[2]:proj(camera) local x3, y3 = self.verts[3]:proj(camera) local x4, y4 = self.verts[4]:proj(camera) if Face.isccw(x1, y1, x2, y2, x3, y3) then return end tri(x1, y1, x2, y2, x3, y3, self.col) tri(x2, y2, x3, y3, x4, y4, self.col) Face.border(x1, y1, x2, y2, x3, y3, x4, y4, 8) end, drawTex = function(self, bcol) local x1, y1 = self.verts[1]:proj(camera) local x2, y2 = self.verts[2]:proj(camera) local x3, y3 = self.verts[3]:proj(camera) local x4, y4 = self.verts[4]:proj(camera) if Face.isccw(x1, y1, x2, y2, x3, y3) then return end tri(x1, y1, x2, y2, x3, y3, self.col) tri(x2, y2, x3, y3, x4, y4, self.col) if self.uvs[1] ~= nil then local u1, v1, u2, v2 = table.unpack(self.uvs) textri(x1, y1, x2, y2, x3, y3, u1, v1, u2, v1, u1, v2, false, 0) textri(x2, y2, x3, y3, x4, y4, u2, v1, u1, v2, u2, v2, false, 0) end if bcol == nil then Face.border(x1, y1, x2, y2, x3, y3, x4, y4, 8) elseif bcol ~= 0 then Face.border(x1, y1, x2, y2, x3, y3, x4, y4, bcol) end end, drawOutline = function(self) local x1, y1 = self.verts[1]:proj(camera) local x2, y2 = self.verts[2]:proj(camera) local x3, y3 = self.verts[3]:proj(camera) local x4, y4 = self.verts[4]:proj(camera) if Face.isccw(x1, y1, x2, y2, x3, y3) then return end Face.border(x1, y1, x2, y2, x3, y3, x4, y4, self.col) end, flip = function(self) local u1, u2, u3, u4 = table.unpack(self.verts) self.verts = {u2, u1, u4, u3} return self end, draw = function(self) local x1, y1 = self.verts[1]:proj(camera) local x2, y2 = self.verts[2]:proj(camera) local x3, y3 = self.verts[3]:proj(camera) if Face.isccw(x1, y1, x2, y2, x3, y3) then return end local x4, y4 = self.verts[4]:proj(camera) tri(x1, y1, x2, y2, x3, y3, self.col) tri(x2, y2, x3, y3, x4, y4, self.col) Face.border(x1, y1, x2, y2, x3, y3, x4, y4, 8) end, } }, isccw = function(x1, y1, x2, y2, x3, y3) local a1 = x2 - x1 local a2 = y2 - y1 local b1 = x3 - x1 local b2 = y3 - y1 if (a1*b2 - b1*a2) < 0 then return true else return false end return flag end, border = function(x1, y1, x2, y2, x3, y3, x4, y4, col) line(x1, y1, x2, y2, col) line(x2, y2, x4, y4, col) line(x4, y4, x3, y3, col) line(x3, y3, x1, y1, col) end, zsort = function(u, v) return Vec.dot(camera.o, u) < Vec.dot(camera.o, u) end, } camera = { o = Vec.new(0, 0, 1), x = Vec.new(1, 0, 0), y = Vec.new(0, 1, 0), target_ax = -PI/6, target_ay = PI/4, lerp_fac = 0.1, pos = 0, ax = 0, ay = 0, target_y2 = 0, y2 = 0, offset_y2 = 0, x2 = 0, target_x2 = 0, tick_x = 190, tick_y = H2-3, load = function(self) end, update = function(self) -- if key(23) then self.ax = self.ax + 0.03 end -- if key(19) then self.ax = self.ax - 0.03 end -- if key(1) then self.target_ay = self.target_ay + 0.03 end -- if key(4) then self.target_ay = self.target_ay - 0.03 end self.ax = mathFun.lerp(self.lerp_fac, self.ax, self.target_ax) self.ay = mathFun.lerp(self.lerp_fac, self.ay, self.target_ay) self.o = Vec.new(0, 0, 1):rotate(self.ax, self.ay, 0) self.x = Vec.new(1, 0, 0):rotate(self.ax, self.ay, 0) self.y = Vec.new(0, 1, 0):rotate(self.ax, self.ay, 0) -- vertical motion -- if key(23) then self.offset_y2 = self.offset_y2 + 1 end -- if key(19) then self.offset_y2 = self.offset_y2 - 1 end -- self.target_y2 = player.i * terrain.h*cos(self.ax) + self.offset_y2 self.target_y2 = -player.y*cos(self.ax) + self.offset_y2 self.y2 = mathFun.lerp(self.lerp_fac, self.y2, self.target_y2) -- horizontal offset self.x2 = mathFun.lerp(self.lerp_fac, self.x2, self.target_x2) end, rotateRightAngle = function(self, n) self.target_ay = self.target_ay + n*PI/2 self.pos = self.pos + n end, drawTicks = function(self) local c = cos(self.ax) for i = 0,terrain.n,5 do print(-i, self.tick_x, self.tick_y+(i-0.5)*terrain.h*c-self.y2, 13) end end, } Voxel = { render_radius = 13, new = function(f1, f2, f3, f4, f5, o, r) if r == nil then r = Voxel.render_radius end local v = {f1=f1, f2=f2, f3=f3, f4=f4, f5=f5, o=o, r=r} setmetatable(v, Voxel.mt) return v end, mt = { __index = { draw = function(self) if self.f1 ~= nil then self.f1:draw() end if self.f2 ~= nil then self.f2:draw() end if self.f3 ~= nil then self.f3:draw() end if self.f4 ~= nil then self.f4:draw() end if self.f5 ~= nil then self.f5:draw() end end, drawTex = function(self) if self.f1 ~= nil then self.f1:drawTex() end if self.f2 ~= nil then self.f2:drawTex() end if self.f3 ~= nil then self.f3:drawTex() end if self.f4 ~= nil then self.f4:drawTex() end if self.f5 ~= nil then self.f5:drawTex() end end, isonscreen = function(self) local x, y = self.o:proj(camera) return y+self.r > 0 and y-self.r < HEIGHT end, } }, type1 = function() end } terrain = { n = 11, -- height m = 5, -- depth w = 12*sqrt2, -- voxel width (12) h = 28/sqrt(3), -- voxel height (14) mesh = {}, faces = {}, data = {{}}, voxels = {{{}}}, order = {}, deadend = {}, vents = {{}}, mesh_id = { {1,1}, {1,2}, {1,3}, {1,4}, {1,5}, {2,5}, {3,5}, {4,5}, {5,5}, {5,4}, {5,3}, {5,2}, {5,1}, {4,1}, {3,1}, {2,1} }, color_id = {[0]=0, [1]=3, [2]=5, [3]=13, [4]=0}, tile_id = {[0]=0, [1]=2, [2]=4, [3]=6, [4]=8}, data_id = { {1, 2, 3, 4, 5}, {16, nil, nil, nil, 6}, {15, nil, nil, nil, 7}, {14, nil, nil, nil, 8}, {13, 12, 11, 10, 9}, }, load = function(self) self:loadMesh() local deadend self.data, self.deadend = maze.generate(self.n, 16) self:loadVoxels() self.vents = maze.getVents(self.data) end, loadMesh = function(self) local mesh = {} for i = 1,self.m+1 do mesh[i] = {} for j = 1,self.m+1 do mesh[i][j] = Vec.new(i-1, 0, j-1) end end setmetatable(mesh, Mat.mt) mesh = self.w * mesh - self.m/2 * self.w * Vec.new(1, 0, 1) self.mesh = mesh self.l = self.w * (self.m - 1) end, loadVoxels = function(self) local voxels = {} local n = self.n local m = self.m for i = 1,n do voxels[i] = {} for j = 1,m do voxels[i][j] = {} for k = 1,m do voxels[i][j][k] = self:getVoxel(i, j, k) end end end -- tree voxel local u1 = self.mesh[3][3] local u2 = self.mesh[4][3] local u3 = self.mesh[3][4] local u4 = self.mesh[4][4] local f5 = Face.new({u1, u2, u3, u4}, {}, 5) voxels[1][3][3] = Voxel.new(nil, nil, nil, nil, f5, (u1+u3)/2) self.voxels = voxels -- create list for sorting local order = {} for j = 1,m do for k = 1,m do table.insert(order, {terrain.mesh[j][k], j, k}) end end self.order = order end, getVoxel = function(self, i, j, k) -- coords local dy = Vec.new(0, -self.h, 0) local y = (i-1)*dy local u1 = self.mesh[j][k] + y local u2 = self.mesh[j+1][k] + y local u3 = self.mesh[j][k+1] + y local u4 = self.mesh[j+1][k+1] + y local v1 = u1 + dy local v2 = u2 + dy local v3 = u3 + dy local v4 = u4 + dy local o = (u1+v4)/2 local data_id = self.data_id[j][k] if data_id ~= nil then local type = self.data[i][data_id] local voxel = voxeltype[type](u1, u2, u3, u4, v1, v2, v3, v4) -- remove inner if mathFun.isin(data_id, {2, 3, 4}) then voxel.f3 = nil elseif mathFun.isin(data_id, {6, 7, 8}) then voxel.f4 = nil elseif mathFun.isin(data_id, {10, 11, 12}) then voxel.f1 = nil elseif mathFun.isin(data_id, {14, 15, 16}) then voxel.f2 = nil end -- remove adjacent if mathFun.isin(data_id, {2, 3, 4, 5}) then if self.data[i][data_id-1] ~= 0 then voxel.f4 = nil end elseif mathFun.isin(data_id, {6, 7, 8, 9}) then if self.data[i][data_id-1] ~= 0 then voxel.f1 = nil end elseif mathFun.isin(data_id, {10, 11, 12, 13}) then if self.data[i][data_id-1] ~= 0 then voxel.f2 = nil end elseif mathFun.isin(data_id, {14, 15, 16}) then if self.data[i][data_id-1] ~= 0 then voxel.f3 = nil end end if mathFun.isin(data_id, {1, 2, 3, 4}) then if self.data[i][data_id+1] ~= 0 then voxel.f2 = nil end elseif mathFun.isin(data_id, {5, 6, 7, 8}) then if self.data[i][data_id+1] ~= 0 then voxel.f3 = nil end elseif mathFun.isin(data_id, {9, 10, 11, 12}) then if self.data[i][data_id+1] ~= 0 then voxel.f4 = nil end elseif mathFun.isin(data_id, {13, 14, 15}) then if self.data[i][data_id+1] ~= 0 then voxel.f1 = nil end end -- remove top if i > 1 and self.data[i-1][data_id] ~= 0 then voxel.f5 = nil end return voxel elseif j == 3 and k == 3 then -- center column return nil else local type = 3 local col = self.color_id[type] local uvs = {} local f1, f2, f3, f4, f5 if j == 2 and self.data[i][self.data_id[j-1][k]] == 0 then f1 = Face.new({u1, u3, v1, v3}, uvs, col) end if j == 4 and self.data[i][self.data_id[j+1][k]] == 0 then f3 = Face.new({u4, u2, v4, v2}, uvs, col) end if k == 2 and self.data[i][self.data_id[j][k-1]] == 0 then f4 = Face.new({u2, u1, v2, v1}, uvs, col) end if k == 4 and self.data[i][self.data_id[j][k+1]] == 0 then f2 = Face.new({u3, u4, v3, v4}, uvs, col) end if i == 1 then f5 = Face.new({u1, u2, u3, u4}, uvs, 3) end return Voxel.new(f1, f2, f3, f4, f5, o) end end, sortOrder = function(u, v) return Vec.dot(camera.o, u[1]) < Vec.dot(camera.o, v[1]) end, } voxeltype = { -- empty [0] = function(u1, u2, u3, u4, v1, v2, v3, v4) local o = (u1+v4)/2 return Voxel.new(nil, nil, nil, nil, nil, o) end, -- dirt [1] = function(u1, u2, u3, u4, v1, v2, v3, v4) local f1 = Face.new({u1, u3, v1, v3}, {}, 5) local f2 = Face.new({u3, u4, v3, v4}, {}, 5) local f3 = Face.new({u4, u2, v4, v2}, {}, 5) local f4 = Face.new({u2, u1, v2, v1}, {}, 5) local f5 = Face.new({u1, u2, u3, u4}, {}, 5) local o = (u1+v4)/2 return Voxel.new(f1, f2, f3, f4, f5, o) end, -- dirt with grass [2] = function(u1, u2, u3, u4, v1, v2, v3, v4) local f1 = Face.new({u1, u3, v1, v3}, {}, 5) local f2 = Face.new({u3, u4, v3, v4}, {}, 5) local f3 = Face.new({u4, u2, v4, v2}, {}, 5) local f4 = Face.new({u2, u1, v2, v1}, {}, 5) local f5 = Face.new({u1, u2, u3, u4}, {}, 3) local o = (u1+v4)/2 return Voxel.new(f1, f2, f3, f4, f5, o) end, -- rock [3] = function(u1, u2, u3, u4, v1, v2, v3, v4) local f1 = Face.new({u1, u3, v1, v3}, {}, 13) local f2 = Face.new({u3, u4, v3, v4}, {}, 13) local f3 = Face.new({u4, u2, v4, v2}, {}, 13) local f4 = Face.new({u2, u1, v2, v1}, {}, 13) local f5 = Face.new({u1, u2, u3, u4}, {}, 13) local o = (u1+v4)/2 return Voxel.new(f1, f2, f3, f4, f5, o) end, -- rock [4] = function(u1, u2, u3, u4, v1, v2, v3, v4) local f1 = Face.new({u1, u3, v1, v3}, {}, 0) local f2 = Face.new({u3, u4, v3, v4}, {}, 0) local f3 = Face.new({u4, u2, v4, v2}, {}, 0) local f4 = Face.new({u2, u1, v2, v1}, {}, 0) local f5 = Face.new({u1, u2, u3, u4}, {}, 0) local o = (u1+v4)/2 return Voxel.new(f1, f2, f3, f4, f5, o) end, } Sprite = { new = function(spr, o, w, h) local s = {spr=spr, o=o, w=w, h=h} setmetatable(s, Sprite.mt) return s end, mt = { __index = { draw = function(self) local x, y = self.o:proj(camera) spr(self.spr, x-8*self.w/2, y-8*self.h, 0, 1, 0, 0, self.w, self.h) end } }, ysort = function(u, v) return u[2] < v[2] end, } player = { dr = 0, o = Vec.new(0, 0, 0), speed_x = 0.7, is_left = false, range = terrain.w * (terrain.m - 1), r = 2 * terrain.w * (terrain.m - 1), i = 0, j = 9, njump = 1, jump_height = 18, jump_dist = 1.3*terrain.w, y = 0, speed_y = 0, gravity = -500, jump_vel = 0, h = 14, ncoin = 0, load = function(self) self.r = 2 * self.range self.o = Vec.new(self.range/2, 0, self.range/2) self.j = 2 * terrain.m - 1 self.jump_vel = 4*self.jump_height*self.speed_x*60 / self.jump_dist self.maxgravity = -2*self.speed_x*60*self.jump_vel / self.jump_dist self.gravity = self.maxgravity self.maxvel = 1.2*self.jump_vel end, update = function(self) -- movement local coll = self:getCollision() local rwall, lwall = self:getWall() if btn(2) then self.is_left = true if not (coll[3] or coll[5]) then self.r = self.r - self.speed_x self.dr = self.dr - self.speed_x elseif self.r-self.speed_x >= lwall then self.r = self.r - self.speed_x self.dr = self.dr - self.speed_x end end if btn(3) then self.is_left = false if not (coll[4] or coll[6]) then self.r = self.r + self.speed_x self.dr = self.dr + self.speed_x elseif self.r + self.speed_x < rwall then self.r = self.r + self.speed_x self.dr = self.dr + self.speed_x end end self.r = mathFun.mod(self.r, 4*self.range) self.j = mathFun.mod(floor(self.r/terrain.w + 0.5), 16) + 1 -- warp if self.y < -(terrain.n+8)*terrain.h then local dy = camera.y2 - camera.target_y2 self.y = 9*terrain.h camera.target_y2 = -self.y*cos(camera.ax) + camera.offset_y2 camera.y2 = camera.target_y2 + dy reload() end -- jump coll = self:getCollision() if (keyp(48) or btnp(4)) and self.njump > 0 then if vending.ison == false then sfx(49, 'A-4', -1, 3) self.speed_y = self.jump_vel self.njump = self.njump - 1 end end -- update gravity on vents self.gravity = self.maxgravity self.maxvel = 1.2*self.jump_vel if self.i > 0 then if terrain.vents[self.i][self.j] == 1 then self.gravity = self.maxgravity / 2 self.maxvel = 0.7*self.jump_vel end end self.speed_y = mathFun.clamp(self.speed_y + self.gravity*time.dt/1000, -self.maxvel, 1.2*self.jump_vel) self.y = self.y + self.speed_y*time.dt/1000 -- count jump if coll[1] then self.speed_y = min(self.speed_y, 0) self.y = min(-(self.i-1) * terrain.h, self.y) end if (self.y < -self.i * terrain.h and coll[2]) then self.njump = 1 elseif self.i > 0 then if terrain.vents[self.i][self.j] == 1 then self.njump = 1 end else self.njump = 0 end -- collision if coll[2] then self.y = max(-self.i * terrain.h, self.y) end self.i = mathFun.clamp(ceil(-self.y/terrain.h), 0, terrain.n) -- update position local dx = mathFun.clamp(-abs(self.r-2.5*self.range)+1.5*self.range, 0, self.range) local dz = mathFun.clamp(-abs(self.r-1.5*self.range)+1.5*self.range, 0, self.range) self.o = Vec.new(-self.range/2+dx, self.y, -self.range/2+dz) -- rotate camera if abs(self.dr) >= self.range then camera:rotateRightAngle(mathFun.sign(self.dr)) self.dr = 0 end -- collect coins if self.i > 0 then if field.data[self.i][self.j] ~= nil then if field.data[self.i][self.j].spr == 324 then local a = (self.r/terrain.w + 0.5) % 1 if mathFun.between(a, 0.3, 0.7) then sfx(50, 'C-6', -1, 3) field.data[self.i][self.j] = nil self.ncoin = self.ncoin + 1 end end end end end, draw = function(self) local x, y = self.o:proj(camera) if self.is_left then spr(256, x-8, y-16, 0, 1, 1, 0, 2, 2) else spr(256, x-8, y-16, 0, 1, 0, 0, 2, 2) end end, getCollision = function(self) local coll = {false, false, false, false, false, false} -- up if self.i > 1 then coll[1] = terrain.data[self.i-1][self.j] ~= 0 end -- down if self.i < terrain.n then coll[2] = terrain.data[self.i+1][self.j] ~= 0 end -- layer 0 if self.i == 0 then return coll end -- left and right if self.j == 1 then jm1 = 16 else jm1 = self.j-1 end if self.j == 16 then jp1 = 1 else jp1 = self.j+1 end coll[3] = terrain.data[self.i][jm1] ~= 0 coll[4] = terrain.data[self.i][jp1] ~= 0 if self.i > 1 and self.y + self.h > -(self.i-1) * terrain.h then coll[5] = terrain.data[self.i-1][jm1] ~= 0 coll[6] = terrain.data[self.i-1][jp1] ~= 0 end return coll end, getWall = function(self) local j = self.j-1 local lwall, rwall if j ~= 0 then rwall = (j+0.5)*terrain.w lwall = mathFun.mod(j-0.5, 16)*terrain.w else if self.r >= 15.5*terrain.w then lwall = 15.5*terrain.w rwall = 16.5*terrain.w else lwall = -0.5*terrain.w rwall = 0.5*terrain.w end end return rwall, lwall end, drawHUD = function(self) spr(260, 2, 2, 0) print(self.ncoin, 12, 4, 4) end } Color = { addr = 0x03FC0, new = function(r, g, b) local c = Vec.new(r, g, b) return c end, getcol = function(col) local addr = Color.addr + 3*col local r = peek(addr) local g = peek(addr+1) local b = peek(addr+3) return Vec.new(r, g, b) end, setcol = function(c, col) local addr = Color.addr + 3*col poke(addr, c[1]) poke(addr+1, c[2]) poke(addr+2, c[3]) end, } background = { c1 = Color.new(0x13, 0x27, 0x43), c2 = Color.new(0x40, 0x70, 0x88), stars = {}, scn = function(self, line) local k = line/HEIGHT Color.setcol(mathFun.lerp(k, self.c1, self.c2), 15) end, load = function(self) self.stars = {} for i = 1,15 do local x = random(0, WIDTH) local y = random(0, HEIGHT) table.insert(self.stars, {x, y, t=random(2000)}) end end, draw = function(self) -- local t = time.t // 1000 % 2 for i = 1,#self.stars do local x = mathFun.mod(self.stars[i][1] + camera.ay*100, WIDTH) -- pix(x, self.stars[i][2], 12) spr(261 + (self.stars[i].t+time.t)//2000 % 2, x, self.stars[i][2], 0) end end } tree = { trunk = nil, leaves = {}, t = 0, load = function(self) self.leaves = {} for i = 2,4 do local z = i*10 for j = 1,i^2 do local r = random()*z/2 local t = 2*PI * random() local v = Vec.new(r*cos(t), -z+65, r*sin(t)) table.insert(self.leaves, v) end end -- load trunk self.trunk = Sprite.new(288, Vec.new(0, -5, 0), 4, 4) end, update = function(self) self.t = self.t + 1 end, draw = function(self) self.trunk:draw() for i = 1,#self.leaves do local x, y = self.leaves[i]:proj(camera) local a = 0.6*sin(0.1*(x+y+self.t*0.3)) spr(292, x-8-a, y-8+a, 0, 1, 0, 0, 2, 2) end end, } vending = { voxel = nil, faces = {}, ison = false, ncoin = 0, load = function(self) local j = 2 local k = 4 local dy = Vec.new(0, -9/7*terrain.h, 0) local y = -1*dy local u1 = terrain.mesh[j][k] + y + Vec.new(0, 0, terrain.w/3) local u2 = terrain.mesh[j+1][k] + y + Vec.new(0, 0, terrain.w/3) local u3 = terrain.mesh[j][k+1] + y local u4 = terrain.mesh[j+1][k+1] + y local v1 = u1 + dy local v2 = u2 + dy local v3 = u3 + dy local v4 = u4 + dy local f1 = Face.new({u1, u3, v1, v3}, {12, 32, 20, 48}, 2) local f3 = Face.new({u4, u2, v4, v2}, {12, 32, 20, 48}, 2) local f4 = Face.new({u2, u1, v2, v1}, {19, 32, 32, 48}, 2) local f5 = Face.new({u1, u2, u3, u4}, {0, 24, 13, 32}, 2) local o = (u1+v4)/2 self.voxel = Voxel.new(f1, nil, f3, f4, f5, o) local f21 = Face.new({u3, u4, v3, v4}, {0, 32, 13, 49}, 2) local f22 = Face.new({u3, u4, v3, v4}, {32, 32, 45, 49}, 2) self.faces = {f21, f22} end, update = function(self) self.ison = false if player.j == 6 then if player.i == 0 and player.y < 9/7*terrain.h then self.ison = true end end if keyp(48) or btnp(4) then if self.ison then if dialog.co ~= nil then if coroutine.status(dialog.co) == "suspended" then return end -- if dialog.waiting == false then -- return -- end end if player.ncoin > 0 then sfx(51, 'F-5', -1, 3) player.ncoin = player.ncoin - 1 field.ncoin = field.ncoin - 1 self.ncoin = self.ncoin + 1 dialog:show(dialog.bank[self.ncoin]) boid:spawn() if field.ncoin == 0 then sfx(52, 'A-5', -1, 3) end else sfx(53, 'C-5', -1, 3) end end end end, draw = function(self) self.voxel.f1:drawTex(2) self.voxel.f3:drawTex(2) self.voxel.f4:drawTex(2) self.voxel.f5:drawTex(2) if self.ison then self.faces[2]:drawTex(2) else self.faces[1]:drawTex(2) end if self.ncoin == 0 then local x, y = self.voxel.o:proj(camera) local offset = time.t // 1000 % 2 spr(324, x-4, y-28+offset, 0) spr(263 + offset, x-4, y-20, 0) end end, } field = { data = {{}}, ncoin = 0, coin_pos = {}, load = function(self) local n, m = terrain.data:size() self.ncoin = 0 self.coin_pos = {} setmetatable(self.coin_pos, Mat.mt) local data = {} for i = 1,n do data[i] = {} end self.data = data setmetatable(self.data, Mat.mt) -- load vents self:loadVents(terrain.vents) -- load coins for _,v in ipairs(terrain.deadend) do local i, j = table.unpack(v) if terrain.data[i][j] == 0 and terrain.data[i+1][j] ~= 0 and self.data[i][j] == nil then local a, b = table.unpack(terrain.mesh_id[j]) local o = terrain.mesh[a][b] + Vec.new(terrain.w/2, -i*terrain.h, terrain.w/2) self.data[i][j] = Sprite.new(324, o, 1, 1) table.insert(self.coin_pos, {i, j}) self.ncoin = self.ncoin + 1 if self.ncoin == 5 then break end end end -- add more coins local r = 5 - self.ncoin local tempi = {2,4,6,8,10} for k = 1,r do local i = table.remove(tempi, random(1, #tempi)) local tempj = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16} while #tempj > 0 do local j = table.remove(tempj, random(1, #tempj)) if terrain.data[i][j] == 0 and terrain.data[i+1][j] ~= 0 and self.data[i][j] == nil then local a, b = table.unpack(terrain.mesh_id[j]) local o = terrain.mesh[a][b] + Vec.new(terrain.w/2, -i*terrain.h, terrain.w/2) self.data[i][j] = Sprite.new(324, o, 1, 1) table.insert(self.coin_pos, {i, j}) self.ncoin = self.ncoin + 1 break end end end end, zsort = function(u, v) return Vec.dot(camera.o, u.o) < Vec.dot(camera.o, v.o) end, loadVents = function(self, B) local n, m = B:size() for j = 1,m do local h = 0 for i = 0,n-1 do if B[i+1][j] == 1 then h = h+1 elseif h > 0 then local a, b = table.unpack(terrain.mesh_id[j]) local o = terrain.mesh[a][b] + Vec.new(terrain.w/2, -i*terrain.h, terrain.w/2) self.data[i][j] = Pslib.new(o, h) h = 0 end end end end, } maze = { generate = function(n, m) local A = {} for i = 1,n do A[i] = {} for j = 1,m do -- A[i][j] = mathFun.randlist({2, 2, 2, 2, 3}) A[i][j] = 2 end end setmetatable(A, Mat.mt) -- Randomized depth-first search local stack = {{2*random(1, (n-1)/2), 2*random(1, m/2) - 1}} local deadend = {} local islast = true while #stack > 0 do local current = table.remove(stack, #stack) local neighbours = maze.getneighbours(A, current[1], current[2]) if #neighbours > 0 then local new = maze.randbias(neighbours) if new[3] == 1 then A[current[1]][current[2]+1] = 0 elseif new[3] == 2 then local jm1 if current[2] == 1 then jm1 = 16 else jm1 = current[2]-1 end A[current[1]][jm1] = 0 elseif new[3] == 3 then A[current[1]-1][current[2]] = 0 elseif new[3] == 4 then A[current[1]+1][current[2]] = 0 end A[new[1]][new[2]] = 0 table.insert(stack, current) table.insert(stack, new) islast = true else if islast then table.insert(deadend, current) end islast = false end end -- beautify maze.beautify(A) -- start and end A[1][13] = 0 A[n][5] = 0 A[n-1][4] = 0 A[n-1][5] = 0 A[n-1][6] = 0 A[n-2][4] = 0 A[n-2][5] = 0 A[n-2][6] = 0 return A, deadend end, getneighbours = function(A, i, j) local n, m = A:size() local neighbours = {} local jp2, jm2 if j == 15 then jp2 = 1 else jp2 = j+2 end if j == 1 then jm2 = 15 else jm2 = j-2 end if A[i][jp2] ~= 0 then table.insert(neighbours, {i, jp2, 1}) -- right end if A[i][jm2] ~= 0 then table.insert(neighbours, {i, jm2, 2}) -- left end if i ~= 2 then if A[i-2][j] ~= 0 then table.insert(neighbours, {i-2, j, 3}) -- up end end if i ~= n-1 then if A[i+2][j] ~= 0 then table.insert(neighbours, {i+2, j, 4}) -- down end end return neighbours end, randbias = function(t) for i = 1,#t do if t[i][3] < 3 then table.insert(t, t[i]) table.insert(t, t[i]) end end return mathFun.randlist(t) end, beautify = function(A) local n, m = A:size() for i = 4,n,2 do for j = 1,m,2 do if A[i][j] == 0 then local im1 = i-1 local ip1 = i+1 local jm1 = j-1 local jp1 = j+1 if j == 1 then jm1 = 16 end local t = {0, 0, 0, 0} if A[im1][j] == 0 then t[1] = 1 end -- up if A[i][jm1] == 0 then t[2] = 1 end -- left if A[i][jp1] == 0 then t[3] = 1 end -- right if A[ip1][j] == 0 then t[4] = 1 end -- down if mathFun.sum(t) == 2 then if t[1] == 1 and t[2] == 1 then A[i][j] = A[im1][jm1] A[im1][jm1] = 0 elseif t[1] == 1 and t[3] == 1 then A[i][j] = A[im1][jp1] A[im1][jp1] = 0 end end end end end end, getVents = function(A) local n, m = A:size() local B = Mat.newZeros(n, m, 0) for i = 3,n-2 do for j = 1,m do local im1 = i-1 if A[i][j] ~= 0 and A[im1][j] == 0 then local ip1 = i+1 local jm1 = j-1 local jp1 = j+1 if j == 1 then jm1 = 16 end if j == 16 then jp1 = 1 end if A[im1][jp1] == 0 and A[i][jp1] == 0 and A[ip1][jp1] == 0 then local ii = i-1 while ii < n do B[ii][jp1] = 1 ii = ii + 1 if A[ii][jp1] ~= 0 then break end end end if A[im1][jm1] == 0 and A[i][jm1] == 0 and A[ip1][jm1] == 0 then local ii = i-1 while ii < n do B[ii][jm1] = 1 ii = ii + 1 if A[ii][jm1] ~= 0 then break end end end end end end local ii = 1 while ii < n do B[ii][13] = 1 ii = ii + 1 if A[ii][13] ~= 0 then break end end return B end, } Butterfly = { restframe = { 3*Vec.new(0, 0, 0), 3*Vec.new(0, 1.79, 1.003), 3*Vec.new(0, 0.649, -1.256), 3*Vec.new(0, 1.79, 1.003), 3*Vec.new(0, 0.649, -1.256) }, frames = { {3*Vec.new(0, 0, 0), 3*Vec.new(0.726, 1.636, 1.004), 3*Vec.new(0.24, 0.603, -1.256), 3*Vec.new(-0.726, 1.636, 1.004), 3*Vec.new(-0.24, 0.603, -1.256)}, {3*Vec.new(0, 0, 0), 3*Vec.new(1.486, -0.997, 1.004), 3*Vec.new(0.553, -0.340, -1.256), 3*Vec.new(-1.486, -0.997, 1.004), 3*Vec.new(-0.553, -0.340, -1.256)} }, rframes = {0, 0, 1, 2, 3, 3, 4, 4, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0}, new = function(v) setmetatable(v, Butterfly.mt) return v end, mt = { __index = { draw = function(self) local offset = self.t // 10 % 2 local v0, v1, v2, v3, v4 = table.unpack(Butterfly.frames[1+offset]) -- self.dir = 0.48020792115976 local c = cos(self.dir) local s = sin(self.dir) local u1 = Vec.yrotate(v1, c, s) local u2 = Vec.yrotate(v2, c, s) local u3 = Vec.yrotate(v3, c, s) local u4 = Vec.yrotate(v4, c, s) local x0, y0 = (self.o+v0):proj(camera) local x1, y1 = (self.o+u1):proj(camera) local x2, y2 = (self.o+u2):proj(camera) local x3, y3 = (self.o+u3):proj(camera) local x4, y4 = (self.o+u4):proj(camera) tri(x0, y0, x1, y1, x2, y2, self.col) tri(x0, y0, x3, y3, x4, y4, self.col) end, drawdot = function(self) local x, y = self.o:proj(camera) local i = self.t // 200 % #Butterfly.rframes + 1 circ(x, y, Butterfly.rframes[i]-1, self.col) -- circ(x, y, 3, self.col) end } }, } boid = { n = 0, particles = {}, r = terrain.m*terrain.w, w = 2*PI*terrain.m*terrain.w, h = terrain.n*terrain.h, load = function(self) for i = 1,self.n do local v = { x = random()*self.w, y = random()*self.h, r = boid.r, s = 0.5, dx = 1, dy = 0, col = 9, o = Vec.new(0, 0, 0), t = random(60), dir = 0, } local curv = 0.05*(2*random()-1) v.rot = {cos(curv), sin(curv)} setmetatable(v, Butterfly.mt) table.insert(self.particles, v) end end, update = function(self) for _,b in ipairs(self.particles) do -- velocity b.dx = b.dx*b.rot[1] - b.dy*b.rot[2] b.dy = b.dx*b.rot[2] + b.dy*b.rot[1] local n = sqrt(b.dx^2 + b.dy^2) if n > 0 then b.dx = b.dx / n b.dy = b.dy / n else b.dx = 1 b.dy = 0 end -- position b.x = mathFun.mod(b.x + b.dx * b.s, self.w) b.y = mathFun.clamp(b.y + b.dy * b.s, -2*terrain.h, self.h) b.r = mathFun.lerp(0.01, b.r, self.r) -- map 3d position local a = b.x / self.w * 2*PI local new = Vec.new(b.r*cos(a), -b.y, b.r*sin(a)) local d = new - b.o local r = Vec.norm(d) if r > 0 then b.dir = -mathFun.sign(d[3])*math.acos(mathFun.clamp(d[1]/r, -1, 1)) + PI/2 end b.o = new b.isfront = (camera.o[1]*b.o[1] + camera.o[3]*b.o[3]) > 0 -- rot if b.t // 60 % 2 < 1 then local curv = 0.05*(2*random()-1) b.rot = {cos(curv), sin(curv)} end -- time b.t = b.t + 1 end -- if keyp(2) then -- self:spawn() -- end end, drawfront = function(self) for _,b in ipairs(self.particles) do if b.isfront then b:draw() end end end, drawback = function(self) for _,b in ipairs(self.particles) do if not b.isfront then b:draw() end end end, spawn = function(self) local v = { x = 2.6/8*self.w, y = -2*terrain.h, r = 2*terrain.w, s = 0.5, dx = -1, dy = 0, col = 9, o = Vec.new(0, 0, 0), t = random(2), dir = 0, } local a = random() * 2*PI v.dx = cos(a) v.dy = sin(a) local curv = 0.05*(2*random()-1) v.rot = {cos(curv), sin(curv)} setmetatable(v, Butterfly.mt) table.insert(self.particles, v) end, } Pslib = { objs = {}, frames = {352,352,353,353,353,354,354,354,354,354,354,354,354,354,354,355}, new = function(o, h) local ps = { particles = {}, o = o, h = h*terrain.h, minvy = 0.2, maxvy = 0.8, next = -100, t = 0, } ps.maxlife = ps.h / ps.maxvy * 60 ps.minlife = terrain.h / ps.maxvy * 60 setmetatable(ps, Pslib.mt) table.insert(Pslib.objs, ps) return ps end, mt = { __index = { draw = function(self) for i,p in ipairs(self.particles) do local x, y = p.o:proj(camera) local n = floor(p.fac*16)+1 local f = Pslib.frames[n] spr(f, x-4, y-8, 0) end end, update = function(self) self:emittimmer() for k,p in pairs(self.particles) do p.o[2] = p.o[2] + p.vy p.fac = (time.t - p.start) / (p.death - p.start) -- remove if p.fac > 1 or p.o[2]-self.o[2] > self.h then table.remove(self.particles, k) end end self.t = self.t + 1 end, emittimmer = function(self) if self.next <= self.t then self:emit() self.next = self.next + 15 end end, emit = function(self) local p = {} p.o = self.o + terrain.w * Vec.new(random()-0.5, 0, random()-0.5) p.vy = random()*(self.maxvy-self.minvy)+self.minvy p.fac = 0 p.start = time.t p.death = time.t+random()*(self.maxlife-self.minlife)+self.minlife table.insert(self.particles, p) end, isonscreen = function(self) local x, y = self.o:proj(camera) return y+self.h < HEIGHT and y > 0 end, } }, update = function(self) for i,ps in ipairs(self.objs) do ps:update() end end, } lantern = { o = terrain.w*Vec.new(2, 2, -2), drawtop = function(self) local x, y = self.o:proj(camera) if field.ncoin == 0 then circ(x, y-6, 8+(time.t//1000%2), 4) end spr(387, x-7, y-16, 0, 1, 0, 0, 2, 2) spr(368, x-3, y-40-16, 0, 1, 0, 0, 1, 5) spr(368, x-3, y-80-16, 0, 1, 0, 0, 1, 5) spr(368, x-3, y-120-16, 0, 1, 0, 0, 1, 5) spr(368, x-3, y-160-16, 0, 1, 0, 0, 1, 5) end, drawbot = function(self) local x, y = self.o:proj(camera) spr(368, x-3, y+terrain.n*terrain.h, 0, 1, 0, 0, 1, 5) spr(368, x-3, y+terrain.n*terrain.h+40, 0, 1, 0, 0, 1, 5) spr(368, x-3, y+terrain.n*terrain.h+80, 0, 1, 0, 0, 1, 5) spr(368, x-3, y+terrain.n*terrain.h+120, 0, 1, 0, 0, 1, 5) spr(368, x-3, y+terrain.n*terrain.h+160, 0, 1, 0, 0, 1, 5) end } dialog = { text = {}, convo = { {col={4, 3}, "...", "Hello!", "It's me.", "Did you miss me?", "I missed you;&you were gone&for so long."}, {col={4, 3}, "I'm not&expecting you&to remember&who I am.", "After all, it's&been so long&since you even&let yourself&look in my&direction.", "But guess&what...", "Today's your&lucky day!", "Don't be&worried.", "Even though&you don't&remember me&now, you will...&soon."}, -- {col={4, 3}, "I'm a little&scared that&you won't&recognise me&when you see&me.", "But sometimes&you have to&let yourself&climb down,&to grow up.", "Or grow up, &to go down...", "I'm not making&any sense.", "Anyway,&let yourself&climb down.", "To know you&have something,&you have to&lose it first.", "But you haven't&lost me."}, {col={4, 3}, "You're doing&great. Keep&climbing down.", "Sometimes you&have to let&yourself climb&down, to grow&up.", "Or grow up,&to go down&deep...", "To find your&own tranquil&ocean of&butterflies.", "After all, to&know you had&something, you&have to lose&it first.", "But you haven't&lost me."}, {""} }, stray = {col={4, 3}, "Are you getting&thirsty from all&this jumping?", "You could try&checking out&the vending&machine...", "That is,&if you haven't&done so&already."}, bank = { {"A coin becomes&a butterfly."}, {"Another one.", "They remind you&of souls winging&to the sky and&beyond."}, {"Interesting."}, {"You find &yourself&comforted by&their steady&path...", "Unconcerned&with earthly&issues."}, {"But what else&do they remind&you of?"}, {"Each one&released lets&you breathe&more easily."}, {"Blue...", "like the ocean."}, {col={4, 3}, "Are you&starting to&remember?", "You used to&love them."}, {"A stormy day...&waves crashing.", "But you ran&into the ocean,&laughing,&unafraid."}, {"As we gather&fragments of&memories, we&return to our&deepest&selves."}, {"The deeper you&go, the crisper&the taste of&the air...", "The brighter&the pink of&the trees."}, {"It's as though&they are your&younger selves,&flying free."}, {"They are¬ched into&you now.", "You breathe&butterfly&dreams, tear&up at the&sight of all&that blue."}, {"Fall further,&fly farther."}, {col={4, 3}, "Welcome back!", "It's so good&to see you."} }, credit = {"", "CREDIT", "&Programming&and Art&by pke1029", "&Music and&Writing&by starling", "&Title art&by im_erique_k", "&Thank you&for playing!"}, co = nil, waiting = false, col1 = 4, col2 = 6, t = 0, current = 1, update = function(self) -- coroutine if type(self.co) == "thread" then coroutine.resume(self.co) end -- move camera if #self.text > 0 then camera.target_x2 = -40 else camera.target_x2 = 0 end -- timer self.t = self.t + 1 if self.t == 100 then self:show(self.convo[self.current]) self.current = mathFun.clamp(self.current + 1, 1, 4) elseif self.t == 10000 and coroutine.status(self.co) == "dead" then self:show(self.stray) end end, drawleft = function(self) for i = 1,#self.text do local w = print(self.text[i], 0, -6) print(self.text[i], 90-w, 50+(i-1)*8+1, self.col2) print(self.text[i], 90-w, 50+(i-1)*8, self.col1) end end, draw = function(self) for i = 1,#self.text do print(self.text[i], 150, 50+(i-1)*8+1, self.col2) print(self.text[i], 150, 50+(i-1)*8, self.col1) end end, speak = function(lines) dialog.waiting = false if type(lines.col) ~= "table" then dialog.col1, dialog.col2 = 4, 6 else dialog.col1, dialog.col2 = table.unpack(lines.col) end for i = 1,#lines do dialog.text = {""} local k = 1 for j = 1,#lines[i] do local c = lines[i]:sub(j,j) if c == "&" then k = k+1 dialog.text[k] = "" else dialog.text[k] = dialog.text[k] .. c end coroutine.yield() coroutine.yield() -- coroutine.yield() end if i == #lines then dialog.waiting = true end local count = 0 while not (vending.ison and (keyp(48) or btnp(4))) and count < 200 do -- while count < 200 do count = count + 1 coroutine.yield() end end dialog.text = {} end, speak2 = function(lines) dialog.waiting = false if type(lines.col) ~= "table" then dialog.col1, dialog.col2 = 4, 6 else dialog.col1, dialog.col2 = table.unpack(lines.col) end for i = 1,#lines do dialog.text = {""} local k = 1 for j = 1,#lines[i] do local c = lines[i]:sub(j,j) if c == "&" then k = k+1 dialog.text[k] = "" else dialog.text[k] = dialog.text[k] .. c end coroutine.yield() coroutine.yield() -- coroutine.yield() end if i == #lines then dialog.waiting = true end local count = 0 while count < 200 do count = count + 1 coroutine.yield() end end dialog.text = {} end, show = function(self, lines) self.co = coroutine.create(self.speak) coroutine.resume(self.co, lines) end, next = function(self) self.t = 0 end } function reload() if field.ncoin <= 0 then terrain.data = maze.generate(terrain.n, 16) terrain:loadVoxels() terrain.vents = maze.getVents(terrain.data) field:load() dialog:next() end end function draw() local n = terrain.n local m = terrain.m table.sort(terrain.order, terrain.sortOrder) for _,v in ipairs(terrain.order) do local j = v[2] local k = v[3] local data_id = terrain.data_id[j][k] for i = n,1,-1 do if terrain.voxels[i][j][k] ~= nil then -- check if voxel is on screen if terrain.voxels[i][j][k]:isonscreen() then terrain.voxels[i][j][k]:draw() end -- draw sprite if data_id ~= nil then if field.data[i][data_id] ~= nil then field.data[i][data_id]:draw() end end end if i == player.i and data_id == player.j then if field.data[i][data_id] ~= nil then local temp = {field.data[i][data_id], player} table.sort(temp, field.zsort) temp[1]:draw() temp[2]:draw() else player:draw() end end end -- layer 0 if player.i == 0 and data_id == player.j then player:draw() end if j == 3 and k == 3 then tree:draw() end if j == 2 and k == 4 then vending:draw() end if j == 5 and k == 1 then lantern:drawtop() end end end function drawHUD() for i = 1,field.ncoin do spr(325, (i-1)*10+2, 2, 0) end for i = 1,player.ncoin do spr(324, (i-1)*10+2, 2, 0) end end scene = { currnt = nil, tic_game = function() player:update() camera:update() vending:update() tree:update() boid:update() Pslib:update() dialog:update() if vending.ncoin == 15 then if coroutine.status(dialog.co) == "dead" then vending.ncoin = 1 -- dialog:show(dialog.credit) dialog.co = coroutine.create(dialog.speak2) coroutine.resume(dialog.co, dialog.credit) scene.current = scene.tic_ending end end cls(15) background:draw() boid:drawback() lantern:drawbot() draw() dialog:draw() boid:drawfront() drawHUD() end, tic_title = function() -- start game if keyp(48) or btnp(4) or btnp(2) or btnp(3) then scene.current = scene.tic_game end -- camera rotate camera.ax = -PI/6 camera.ay = PI/4 camera.o = Vec.new(0, 0, 1):rotate(camera.ax, camera.ay, 0) camera.x = Vec.new(1, 0, 0):rotate(camera.ax, camera.ay, 0) camera.y = Vec.new(0, 1, 0):rotate(camera.ax, camera.ay, 0) camera.x2 = 50 tree:update() cls(15) background:draw() lantern:drawbot() draw() spr(76, 83, 44+sin(time.t/500)+0.5, 0, 1, 0, 0, 3, 3) spr(73, 8, 44+cos(time.t/500+1)+0.5, 0, 1, 0, 0, 3, 3) spr(128, 10, 30+2*sin(time.t/500)+0.5, 0, 1, 0, 0, 13, 8) if time.ts % 2 == 0 then local w = print("Press Z to start", 0, -6) print("Press z to start", 57-w/2, 100+1, 6) print("Press z to start", 57-w/2, 100, 4) end end, tic_ending = function() -- update dialog if type(dialog.co) == "thread" then coroutine.resume(dialog.co) end if #dialog.text > 0 then camera.target_x2 = 40 else camera.target_x2 = 0 end -- camera rotate camera.ax = -PI/6 camera.ay = mathFun.mod(camera.ay + 0.01, 2*PI) camera.o = Vec.new(0, 0, 1):rotate(camera.ax, camera.ay, 0) camera.x = Vec.new(1, 0, 0):rotate(camera.ax, camera.ay, 0) camera.y = Vec.new(0, 1, 0):rotate(camera.ax, camera.ay, 0) camera.x2 = mathFun.lerp(camera.lerp_fac, camera.x2, camera.target_x2) if btn(2) then camera.y2 = camera.y2 + 0.5 end if btn(3) then camera.y2 = camera.y2 - 0.5 end -- if camera.y2 > terrain.h*terrain.n + 130 then camera.y2 = -150 end -- if camera.y2 < -150 then camera.y2 = terrain.h*terrain.n + 130 end camera.y2 = mathFun.clamp(camera.y2, -20, terrain.h*terrain.n-20) -- update boids for _,b in ipairs(boid.particles) do b.isfront = (camera.o[1]*b.o[1] + camera.o[3]*b.o[3]) > 0 end cls(15) background:draw() boid:drawback() lantern:drawbot() draw() dialog:drawleft() boid:drawfront() if coroutine.status(dialog.co) == "dead" and time.ts % 2 == 0 then local w = print("Refresh page to start a new session", 0, -6) print("Refresh page to start a new session", W2-w/2, 128+1, 6) print("Refresh page to start a new session", W2-w/2, 128, 4) end end } background:load() camera:load() terrain:load() player:load() tree:load() vending:load() field:load() boid:load() -- Butterfly:load() music(1) scene.current = scene.tic_title function TIC() time:update() debugger:update() -- if keyp(28) then scene.current = scene.tic_title end -- if keyp(29) then scene.current = scene.tic_game end -- if keyp(30) then -- vending.ncoin = 1 -- dialog:show(dialog.credit) -- scene.current = scene.tic_ending -- end scene.current() -- debug time:draw() -- camera:drawTicks() -- debugger:draw() end function SCN(line) background:scn(line) end