0 @$404mJJJL04e$FHuqa },m,m^ `2Tv޼xV4!CeͫTvxBe޼V4fUD3#"""23CTevQwOr}E UUUUDDDDDDDDIDDDDDIDDDDDDDDDDDDDDDDDDIDDDDDIDDDD77ssw37s7s3w77ssw37s7s3wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww7777wwwws37333333s373333s37s33333s3s3333s3s333333s373333s37s33333333ws7w33337ws73333s7ws3333ws7w3s3s3333s3s333333s373333s37s3333wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwppwDDDDDDDD33333333-"--"""""""""""""""""""""""뻻UUU[UUUUU[UUUUU[UUUUUUUUUUUUUUUUUUU[UUUUU[UUUUIIDIIDIIDIIDDDDD-"/-"""""""""""""""""""""""wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww7ww7ww7ww77www70333wwww7w7ww7w7w7www70333wwww7w77w7w7www70333pwppwppwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww@@@@@@@@00000000""""""""""""""""""""""""""""""""ݪڭڪݭݪڭڪY^T^UUUUDDDDDDDDIDDDDDIDDDDUUUUDDDNDNDDNDNDDDDNDNDDwwwww7w77ww7ww7www70333wwwww7ww77ww7ww7www70333wwwww7ww77w7ww7www70333pwpwppw@@DD@DDDDDDDDDD@DD@DD@0033033333333330330330""""""""""""""""""""DDDDNNDNNDNNDNND>>33>>33>>33>>""""""""""""""""""""wwwww7ww77ww7ww7www70333NDNDDDDNDNDDNDNDDDDNDNDDUUUUUUZUUUUU[UUUUUZUUUUUZUUUUUUUUUZUUUUU[UUUUUZUUUUUwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwpppppwpp`ff63?c6?c63?c63?c63?c6c`ff`ff6?c63c63?c63c6?3c6c`ff`ff6?c63c6?c63c63c6?c`ffwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwDID@Dwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwpwppwppw030000003000000003000303`ff6c6?3c6?c6?3c6?3c6?3c`ffDDDDDD4DDDDDDDDCDDDD4DIDDDD7sx7s8ww3s7sx7x3w7swwwwPUUUP UU UUUUUUUUUUpwwwpwwwwwwwwwwwwppUU[[UUUQ[U[P U[ UUU U Ppwppwpppwwww7ww7ww7ww77www70333pwww`ffff``ffffpwwwffffffff UPUU[UUUUUU UPUP[UU[UUUUUw0333s033303s033C43300003s033=33000UUwwwpwpwwwpwwwwwwpwwPPP[ P[U[UUUUUU7383333338373333s38333338s37333377ss7s3ww37s77ss7s3ww37swwwwwwww7w7ww7w7w7www70333fff```fffffffffffwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww03033=3300003033C43300003330333wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwаPPP[P[U[UUUUUU 00070237333333000?0?3?333333ppppwwwwwww3333s7w33337w833338ws3333ws7wwwww7w77ww7ww7www70333 DDD DDD [U[U -"-"@@@@`ff ^^__0333033333333333300wwwww7ww77ww7ww7www70333wwwww7ww77w7ww7www70333wwwww7ww77ww7ww7www70333   [U[U  [U[U   3330303330333333033PPPPUU^ ^PUUUUUUPwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww  @DD@OO@OODDDD@DDPUU^^^^PUUUUPPwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww`ff`nn`nnff`ff` pppwwpwwppp~wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww wpwwwwwwwpwwwp wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww ```ffn n`ff```  PPPPUU^ ^PUUUUUUP 陙 ssss333377773333ssss3333II䙞NINDIDzzwwzww NNDD3333 @Dpppwwpwwpppwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwnnnf UU[[UUUT[U[P U[ UUU U PwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwY^T^UUUUDDDDDDUUUUUUZUUUUU[UUUUUZUUUUUUUUZ ݪڭڪݭݪڭڪwwwwwwww""""UUUUUUUUUUUUUUUUUUUUUUUU""""IIDIIDIIDIIDDDDD77ssw37s7s3w77ssw37s7s3wwwwwݪ 3333ws7w33337ws73333s7ws3333ws7wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwPPPPUU^ ^PUUUUUUP@@@@DD@PPP[ P[U[UUUUUUwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwPPPPUU^ ^PUUUUUUPwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww`f```ff`ff``ff`f`ffff`ff`f`ff`ff`ff``ff```fffff`ff`ffff`ff`ffff`````ff`f`ff`ff`ff`f`ff`ff`ff``ff```````f`ff`f`````ff`ff``f````f`ffwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww0??0333?0003330003330 PPP[ P[U[UUUUUUn`ffff`fff0033000330wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww0??0333?00ww77ww033ww0s70 UPUP[UU[UUUUU ??0033?00??0333 ?0  `fffwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww _U[U [U_U ```ffnnn `f`f`````ffnn`nn`ff`f``     @@ @@@   zzwwzwwNDDNODDODDNNDD@@@DD@C4@C4@33@DD PPPUU^^^ PUUUUUPPPPUU^^P^^PUUUUUUPP    ~y 3333 { @@@@@@@@@@@@@D"--"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""00DI33  @DD@OO@OODDDD@DDDDDD PUU^^^^PUUUPUP`ff`nn`nnff``ff`ff`nn`nnff`ff`wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww3333   `f`ff`f`f`f` wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww 陙 wwwwwwwwwwwwwwwwwwwwwwwwpppwwpwpwpppppwwppwwpwpp~ pppwwppwwpwpp`f``f`fff`f` wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"/-"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""wwwwwwwwwwpwww wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww   /"-" -"/"wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwpppwwpwpwppwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwnknf wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww  [U^U  ^U[U  [U^U  ^U[UwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwPPPUU^^^ PUUUUUPPPPUU^^P^^PUUUUUUPPwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwPPPUU^^^ PUUUUUPPPPUU^^P^^PUUUUUUPPwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwPPPUU^^^ PUUUUUPPPPUU^^P^^PUUUUUUPPwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww ;33;33;3;;;;3;33 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwppppwpwwpwpppwwpwwpwwwwwpwwwppppppppwppwppwpwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwP99993330000000099000030000^::000000000`awj9::009900000033099999999:::00::000000000:::::00::9003330000000:Y::::00:::000000000:wwwww:9:::00:::90090000000:999999999:::00::::00:90000000:::::9 :9:::00::::9900::Y03000000::::::9::9wwwj::990000099:00::::::w00:^:Y00009:::::::9:::999999:0000000::::::9999999900:w:999039999000::::: ::::9::::::0000000:::::::999ww9:::::::00:wwww99:999999:::033::::000::::::::::9::::::Y^900000900::::::::::99::::::::9900:9999999999999::::::::9::::0::::900999:::::::::::99j9::::::`ajww:9990000099999:00::::::::::::::::::::::00:::::::::::::::::::::::::::0:::::00::::9999999999999999999999::::999999999999:::::::9:999:::::::9999999999999$$$$$$$$$$$$$$$$$$$$$$$$$$$000000$$$$00000000000000000000BB00000000000000000000UB0000000000`aj$0000000000$$$$$$$$$$$$$$$$$$$^000000^0000$ B“$$$000000$$0000$ $$$$$$$$00$$$$00$ B$$$$$$BU$$0000 $$  $0000$$UB$$$$$$$$$$$$^BU$$$   $U$0000$$bb$  $$$b$$$$$0000$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$3333300011111110003jj`a000000111111111133333100000000002B2203330300000000223B20000000000112B23203000000033300202B2331^20030000Y00Y100202B211122030B0000100200202jjB213333322111B111200333331B30000112110033311111U^20120211111112111222233303333330B33B33231BB000022222110022220220B2122B22333003 332B1^3200002222222Y0032111112022011111121112222333003B33112232B3333000022222221001122222211022YB211222B22333003322111112B211111133321111111111112222222211111111122332222221111111022331221112221112233303330332222222222222223332B222222222222222222222222222222222222222222220211111111111112211111111112111111112222222222333003333333332222222222222223332222122222222222222222222222222222222!!!!!!!!#########""""""555########55555#"""""555##5##555####^55#555# ^#5#####555555#555#########5##yzz555005#5##55#!555###########5##!!!!!555555####555#!"555###5##"""y###555W555#555#!!"555## #5##!!!#555z555#555#""###555# #5!!!##"t#####555!!!!!555#555#"555### #5######555"""""555######5#55#"!!555##5####555"""""555###555#!!!!""555## #yr5####555"""""555#####zzz!z!""!!""555# #!!!!!!##########^555"""""555ryz##rz!!!!!####"###!!!!!!!""""!""555##""""""###zzrzzy###zyrz555"""""555y!!!WX!!!!!!!!!##!!!!!!!""!!!""""!!""""""""""""!""555##""""""##!!!!!!!!!!!!!!!!!!!!!!!!!!!555"""""555!!!!!!!"""ghyz!"""""""""##"""""""""""""""""""""""""""""""!""555###########################"!!""""!!!"#####"""""""""""""""""""""""""""555"""""555""""""""""!!!!!!""""""""""#############000^000  000000000ll00000000000ll000000000000  000000000000000bc0000000000000000000000000U^0000000  0000000Y00eɷ000 Y000000000000000000000^000ޔ00   000000000jb000000 b00000000UjB`ajUj`aBBBjjjjBjjjj`a`aBBˆBBBBBBB€BBBBU`ajjj`aaBBj`aBUjcjjjBB``ac`aBBBBjjjjژ€Baaa`jjjUjjcj`a`a^`a`aBBjjcjj^B`BB~`ajj`aj`aj`j`jB22222=>>? =222222222222RST..22222? =/>.222222RST222222}|MN MN222222]m22222RST}= ?"""""2222?.>. MN\"""""""222 """""""""2UV^_.>= ?= ?> """""""""MN,jefj`jcno """"MN<111111111111111111111111111111MNL222222222222222222222222222222|] {KJIHHGGFFEEEEFFGHHIIJJKKKLLMMMYZ[]]^^^______________________ 000000000000000000000000000000komololokoiojolojoiojokomononoS            S                       gjnnooooooooooooooooooooooooooreihiiYjYkIl9lm mnnnoooooooooooooooooo,FFFffFFFffFFFffFFFffFFFffFFFffFFFffFFFff$$$$$$$$$$FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF$$$$$$$$$$$$$$$$jJjJjJjJjJjJjJjJjJjJjJjJjJjJjJjJ$$$$$$$$$$$$$$$$fFffFffFffFffFffFffFffFfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff$$$$$$$$$$$$$$$$hhhhhhhhhhh``````````````````00P`TeTemTaTaTuTThhL\nnm?""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""/"""""/""""/"/"/""/"////"/""""""""""""""""""""""""""""""""""""""""""""""""""/"""//"""""/"///"/"/""///"///"/""//"""//""""""""""""""""""""""""""""""""""""""""""""""""""/"/"""""/"//"/"/""/"////""//"""//""""""""""""""""""""""""""""""""""""""""""""""""""/"""//"""""//"//""////"/""///""///""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""/"/""/""/"///""""/"//"""//"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""    [[[UUU[[[UUU    [[[NUUUN[[[DDUUU77ss77ss77ss77ss77ss77ss77ss77ssw37sw37sw37sw37sw37sw37sw37sw37s7s3w7s3w7s3w7s3w7s3w7s3w7s3w7s3w77ss77ss77ss77ss77ss77ss77ss77ssw37sw37sw37sw37sw37sw37sw37sw37s7s3w7s3w7s3w7s3w7s3w7s3w7s3w7s3wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwpPU pwwwwwwUUUU`ffffUUUUffffPUUUU` `UU ffffffff00UU `` PPP0ffffff `ff PUU3s` n ^ 033`PPPP  n ^PPC4`333P[ P[ ݰU`ff PUṴP[ 33ffP[UP[U PUU` UUP[U0ff[UU[UU [UU`  UU[UU0ff0UUUUUUUU  UUUU` PUUUU0ffDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDwwpwwwwwpwpwpwwwwpwwwwwwppwpppwwwwwpwwwwwpppwwpwpwpwwwwpwpwpwwpwpwwpwwpwwpwppwwpwpwpwwwpwwwwwwwpwwwwwpwwwwwpppwpwpwwwwpwwwwwpwwwpwwpwpwpwwpppwwwpwwwwwpwpwpw-- title: 8 Bit Panda Water Mountain -- author: OtherGuy24 -- original made by Bruno Oliveira -- some assets (modified) taken from -- 8bp Reborn, by RomanKalinin (aka RS) -- desc: new levels -- script: lua -- saveid: 8bpWaterMountain1.4 NAME="8-BIT PANDA WATER MOUNTAIN" C=8 ROWS=17 COLS=30 SCRW=240 SCRH=136 JUMP_DY={-3,-3,-3,-3,-2,-2,-2,-2, -1,-1,0,0,0,0,0} SWIM_JUMP_DY={-2,-2,-1,-1,-1,-1,0,0,0,0,0} RESURF_DY={-3,-3,-2,-2,-1,-1,0,0,0,0,0} ATK_SEQ={1,1,1,1,2,3,3,3} DIE_SEQ={{-1,-1},{-2,-2},{-3,-3},{-4,-4}, {-5,-5},{-6,-5},{-7,-4},{-8,-3},{-8,-2}, {-8,1},{-8,3},{-8,5},{-8,9},{-8,13}, {-8,17},{-8,21},{-8,26},{-8,32},{-8,39} } SX_MIN=50 SX_MAX=70 SOL={ NOT=0, HALF=1, FULL=2, } FIRE={ DUR=1000, INTERVAL=20, OFFY=2,OFFX=7,OFFX_FLIP=-2, OFFX_PLANE=14,OFFY_PLANE=8, COLL={x=0,y=0,w=3,h=3}, } T={ EMPTY=0, HPLAF=4, HPLAF2=66, SURF=16, SURFTWO=20, WATER=32, WFALL=48, WFALLTWO=53, TARMAC=52, FIRST_DECO=80, GATE_L=110,GATE_R=111, GATE_L2=142,GATE_R=143, FIRST_ENT=128, FIRST_META=240, META_NUM_0=240, META_A=254, META_B=255 } TPAT={ [85]={w=2,h=2}, [87]={w=2,h=2}, [94]={w=2,h=2}, [89]={w=2,h=2}, [205]={w=2,h=2}, [137]={w=2,h=2}, } TSOL={ [T.EMPTY]=SOL.NOT, [T.HPLAF]=SOL.HALF, [T.HPLAF2]=SOL.HALF, [T.SURF]=SOL.NOT, [T.SURFTWO]=SOL.NOT, [T.WATER]=SOL.NOT, [T.WFALL]=SOL.NOT, [T.WFALLTWO]=SOL.NOT, } TANIM={ [T.SURF]={T.SURF,332}, [T.SURFTWO]={T.SURFTWO,380}, [T.WFALL]={T.WFALL,333,334,335}, [T.WFALLTWO]={T.WFALLTWO,381,382,383}, } S={ PLR={ STAND=257, WALK1=258, WALK2=259, JUMP=273, SWING=276, SWING_C=260, HIT=277, HIT_C=278, DIE=274, SWIM1=267,SWIM2=268, FIRE_BAMBOO=262, FIRE_F=265, FIRE_P=266, FIRE_S=284, SUPER_F=281, SUPER_P=282, SUPER_S=283, }, EN={ A=176, A2=160, B=177, B2=161, DEMON=178, DEMON_THROW=293, DEMONTWO=162, DEMON_THROWTWO=342, --DEMONTHREE shoots less frequently. --search SHOOT4 to modify. DEMONTHREE=144, DEMON_THROWTHREE=374, SLIME=180, SLIMETWO=164, SLIME3=148, SLIME4=149, BAT=181, BAT2=165, HSLIME=182, DASHER=183, DASHER2=167, VBAT=184, VBAT2=168, SDEMON=185, SDEMON_THROW=300, --SDEOMN2 shoots less frequently --(search SHOOT4 to modify. note: --also changes PDEMON6 and --and DEMONTHREE shoot frequency.) SDEMON2=169, SDEMON2_THROW=372, SDEMON3=201, SDEMON3_THROW=376, PDEMON=188, PDEMON_THROW=317, PDEMON2=172, PDEMON2_THROW=363, PDEMON3=204, PDEMON3_THROW=395, --four,five,six shoot less frequently --(search SHOOT2,SHOOT3,SHOOT4) to modify PDEMON4=220, PDEMON4_THROW=411, PDEMON5=236, PDEMON5_THROW=427, PDEMON6=156, PDEMON6_THROW=443, --search [EID.EN.FISH]={data={ --for more about each fish FISH=189, FISH3=173, FISH2=190, FISH4=174, }, CRUMBLE=193,CRUMBLE_2=304,CRUMBLE_3=305, FIREBALL=179, FIREBALLTWO=163, FIREBALLTHREE=145, FIRE_1=263,FIRE_2=264, LIFT=192, LIFT2=208, LIFT3=209, LIFT4=210, LIFT5=211, LIFT6=212, LIFT7=213, LIFT8=214, LIFT9=215, LIFT10=216, LIFT11=217, LIFT12=218, PFIRE=263, FIRE_PWUP=129, BGMNT={DIAG=496,FULL=497}, SCRIM=498, SPIKE=194, CHEST=195,CHEST_OPEN=311, TPLAF=196,TPLAF_HALF=312,TPLAF_OFF=313, SUPER_PWUP=130, SIGN=197, SNOWBALL=186, SNOWBALL2=170, FLAG=198, FLAG_T=326, ICICLE=187, ICICLE_F=303, PLANE=132, AVIATOR=336, AVIATOR_PROP_1=339, AVIATOR_PROP_2=340, PLASMA=279, PLASMA2=361, SICICLE=199, SICICLE_F=319, FUEL=200, IC_FUEL=332, TINY_NUM_00=480, TINY_NUM_50=481, TINY_NUM_R1=482, FOOD={LEAF=128,LEAF2=152,LEAF3=131,LEAF4=147, LEAF5=146,A=133,B=134,C=135,D=136,E=150,F=151}, SURF1=332,SURF2=333, WLD={ ROADS={13,14,15,30,31,46,47}, LVL1=61,LVL2=62,LVL3=63, LVLF=79, LVLC=463, }, POP=600, } EID=S ANIM={ [EID.EN.A]={S.EN.A,290}, [EID.EN.A2]={S.EN.A2,357}, [EID.EN.B]={S.EN.B,291}, [EID.EN.B2]={S.EN.B2,356}, [EID.EN.DEMON]={S.EN.DEMON,292}, [EID.EN.DEMONTWO]={S.EN.DEMONTWO,341}, [EID.EN.DEMONTHREE]={S.EN.DEMONTHREE,358}, [EID.EN.SLIME]={S.EN.SLIME,295}, [EID.EN.SLIME4]={S.EN.SLIME4,375}, [EID.EN.SLIME3]={S.EN.SLIME3,359}, [EID.EN.SLIMETWO]={S.EN.SLIMETWO,343}, [EID.EN.BAT]={S.EN.BAT,296}, [EID.EN.BAT2]={S.EN.BAT2,345}, [EID.FIREBALL]={S.FIREBALL,294}, [EID.FIREBALLTWO]={S.FIREBALLTWO,344}, [EID.FIREBALLTHREE]={S.FIREBALLTHREE,360}, [EID.FOOD.LEAF]={S.FOOD.LEAF,288,289}, [EID.FOOD.LEAF2]={S.FOOD.LEAF2,387,388}, [EID.FOOD.LEAF3]={S.FOOD.LEAF3,389,390}, [EID.FOOD.LEAF4]={S.FOOD.LEAF4,403,404}, [EID.FOOD.LEAF5]={S.FOOD.LEAF5,405,406}, [EID.PFIRE]={S.PFIRE,264}, [EID.FIRE_PWUP]={S.FIRE_PWUP,306,307}, [EID.EN.HSLIME]={S.EN.HSLIME,297}, [EID.SPIKE]={S.SPIKE,308}, [EID.CHEST]={S.CHEST,309,310}, [EID.EN.DASHER]={S.EN.DASHER,314}, [EID.EN.DASHER2]={S.EN.DASHER2,346}, [EID.EN.VBAT]={S.EN.VBAT,298}, [EID.EN.VBAT2]={S.EN.VBAT2,378}, [EID.SUPER_PWUP]={S.SUPER_PWUP,320,321}, [EID.EN.SDEMON]={S.EN.SDEMON,299}, [EID.EN.SDEMON2]={S.EN.SDEMON2,371}, [EID.EN.SDEMON3]={S.EN.SDEMON3,392}, [EID.SNOWBALL]={S.SNOWBALL,301}, [EID.SNOWBALL2]={S.SNOWBALL2,373}, [EID.FOOD.D]={S.FOOD.D,322,323,324}, [EID.FLAG]={S.FLAG,325}, [EID.ICICLE]={S.ICICLE,302}, [EID.SICICLE]={S.SICICLE,318}, [EID.PLANE]={S.PLANE,327,328,329}, [EID.EN.PDEMON]={S.EN.PDEMON,316}, [EID.EN.PDEMON2]={S.EN.PDEMON2,362}, [EID.EN.PDEMON3]={S.EN.PDEMON3,394}, [EID.EN.PDEMON4]={S.EN.PDEMON4,410}, [EID.EN.PDEMON5]={S.EN.PDEMON5,426}, [EID.EN.PDEMON6]={S.EN.PDEMON6,442}, [EID.PLASMA]={S.PLASMA,280}, [EID.PLASMA2]={S.PLASMA2,377}, [EID.FUEL]={S.FUEL,330,331}, [EID.EN.FISH]={S.EN.FISH,368}, [EID.EN.FISH4]={S.EN.FISH4,385}, [EID.EN.FISH3]={S.EN.FISH3,384}, [EID.EN.FISH2]={S.EN.FISH2,369}, } PLANE={ START_FUEL=2000, MAX_FUEL=4000, FUEL_INC=1000, FUEL_BAR_W=50 } M={ BOOT=0, TITLE=1, TUT=2, RESTORE=3, WLD=4, PREROLL=5, PLAY=6, DYING=7, EOL=8, GAMEOVER=9, WIN=10, } CR={ PLR={x=2,y=0,w=4,h=8}, AVIATOR={x=-6,y=2,w=18,h=10}, DFLT={x=2,y=0,w=4,h=8}, FULL={x=0,y=0,w=8,h=8}, BALL={x=2,y=2,w=3,h=3}, TOP={x=0,y=0,w=8,h=2}, ATK={x=6,y=0,w=7,h=8}, ATK_FLIP_X=-5, FOOD={x=1,y=1,w=6,h=6}, } ENT_MAX_DIST=220 ALWAYS_UPDATED_EIDS={ [EID.LIFT]=true, [EID.LIFT2]=true, [EID.LIFT3]=true, [EID.LIFT4]=true, [EID.LIFT5]=true, [EID.LIFT6]=true, [EID.LIFT7]=true, [EID.LIFT8]=true, [EID.LIFT9]=true, [EID.LIFT10]=true, [EID.LIFT11]=true, [EID.LIFT12]=true, } DMG={ MELEE=0, FIRE=1, PLANE_FIRE=2, } PAL={ [0]=0x000000, [1]=0x402434, [2]=0x30346d, [3]=0x4a4a4a, [4]=0x854c30, [5]=0x346524, [6]=0xd04648, [7]=0x757161, [8]=0x34446d, [9]=0xd27d2c, [10]=0x8595a1, [11]=0x6daa2c, [12]=0x1ce68d, [13]=0x6dc2ca, [14]=0xdad45e, [15]=0xdeeed6, } BGM={A=0,B=1,EOL=2,C=3,WLD=4,TITLE=5, FINAL=6,WIN=7} BGMM={ [M.TITLE]=BGM.TITLE, [M.WLD]=BGM.WLD, [M.EOL]=BGM.EOL, [M.WIN]=BGM.WIN, } LVL={ { name="1-1",bg=1, palor={[8]=0x553838}, pkstart=8,pklen=3, mus=BGM.A, }, { name="1-2",bg=0, palor={[8]=0x553838}, snow={clr=2}, pkstart=11,pklen=2, mus=BGM.B, }, { name="1-3",bg=1, palor={[8]=0x553838}, pkstart=13,pklen=3, mus=BGM.C, save=true, }, { name="2-1",bg=1, palor={[8]=0x090910}, snow={clr=2}, pkstart=21,pklen=3, mus=BGM.A, }, { name="2-2",bg=0, palor={[8]=0x090910}, pkstart=19,pklen=2, snow={clr=3}, mus=BGM.B, }, { name="2-3",bg=0, palor={[8]=0x090910}, pkstart=16,pklen=3, mus=BGM.C, save=true, }, { name="3-1",bg=1, palor={[8]=0x303030,[8]=0x504410}, pkstart=24,pklen=3, mus=BGM.A, }, { name="3-2",bg=1, palor={[8]=0x303030,[8]=0x504410}, pkstart=27,pklen=2, mus=BGM.B, }, { name="3-3",bg=0, palor={[8]=0x504410,[8]=0x102428}, pkstart=29,pklen=3, mus=BGM.C, save=true, }, { name="4-1",bg=1, palor={[8]=0x553838}, pkstart=40,pklen=3, mus=BGM.A, }, { name="4-2",bg=1, palor={[8]=0x553838}, pkstart=45,pklen=3, mus=BGM.B, }, { name="4-3",bg=1, palor={[8]=0x553838}, pkstart=43,pklen=2, mus=BGM.C, save=true, }, { name="5-1",bg=2, palor={[8]=0x7171ae}, pkstart=32,pklen=3, snow={clr=10}, mus=BGM.A, }, { name="5-2",bg=0, palor={[8]=0x261f00}, pkstart=35,pklen=2, snow={clr=15}, mus=BGM.B, }, { name="5-3",bg=2, palor={[8]=0x7171ae}, pkstart=37,pklen=3, snow={clr=10}, mus=BGM.C, save=true, }, { name="6-1",bg=0, palor={[8]=0x0f0800}, pkstart=48,pklen=3, mus=BGM.FINAL, snow={clr=8}, }, { name="6-2",bg=0, palor={[8]=0x0f0800}, pkstart=51,pklen=2, mus=BGM.FINAL, snow={clr=8}, }, { name="6-3",bg=0, palor={[8]=0x0f0800}, pkstart=53,pklen=3, mus=BGM.FINAL, snow={clr=8}, }, } LVL_LEN=240 SND={ KILL={sfxid=62,note=30,dur=5}, JUMP={sfxid=61,note=30,dur=4}, SWIM={sfxid=61,note=50,dur=3}, ATTACK={sfxid=62,note=40,dur=4}, POINT={sfxid=60,note=60,dur=5,speed=3}, DIE={sfxid=63,note=18,dur=20,speed=-1}, HURT={sfxid=63,note="C-4",dur=4}, PWUP={sfxid=60,note=45,dur=15,speed=-2}, ONEUP={sfxid=60,note=40,dur=60,speed=-3}, PLANE={sfxid=59,note="C-4",dur=70,speed=-3}, OPEN={sfxid=62,note="C-3",dur=4,speed=-2}, } WLD={ FPAGE=61, BPAGE=62, } POI={ LVL=0, } Sett={ snd=true, mus=true } Game={ m=M.BOOT, t=0, lvlNo=0, lvl=nil, scr=0, bgmnt=nil, snow=nil, topLvl=-1, } Wld={ pois={}, spos={}, plr={ x0=-1,y0=-1, x=0,y=0, dx=0,dy=0, ldx=0,ldy=0, flipped=false, } } Plr={} PLR_INIT_STATE={ lives=3, x=0,y=0, dx=0,dy=0, flipped=false, jmp=0, jmpSeq=JUMP_DY, grounded=false, swim=false, surf=false, atk=0, dying=0, nudgeX=0,nudgeY=0, firePwup=0, fireCd=0, invuln=0, super=false, drag=0, signMsg=0, signC=0, respX=-1,respY=-1, plane=0, score=0, scoreDisp={text=nil,value=-1}, scoreMt=-999, locked=0, } SIGN_C_MAX=10 SIGN_MSGS={ [0]={ l1="Green bamboo protects", l2="against one enemy attack.", }, [1]={ l1="Yellow bamboo allows you to throw", l2="bamboo shoots (limited time).", }, [2]={ l1="Pick up leaves and food to get", l2="points. 10,000 = extra life.", }, [4]={ l1="Bon voyage!", l2="Don't run out of fuel.", }, } Ents={} Parts={} Toasts={} Tanims={} function SetMode(m) Game.m=m Game.t=0 if m~=M.PLAY and m~=M.DYING and m~=M.EOL then ResetPal() end UpdateMus() end function UpdateMus() if Game.m==M.PLAY then PlayMus(Game.lvl.mus) else PlayMus(BGMM[Game.m] or -1) end end function TIC() Game.t=Game.t+1 TICF[Game.m]() end function Boot() ResetPal() WldInit() SetMode(M.TITLE) end function ResetPal(palor) for c=0,15 do local clr=PAL[c] if palor and palor[c] then clr=palor[c] end poke(0x3fc0+c*3+0,(clr>>16)&255) poke(0x3fc0+c*3+1,(clr>>8)&255) poke(0x3fc0+c*3+2,clr&255) end end function TitleTic() ResetPal() w=print(NAME) cls(16) local m=MapPageStart(63) map(m.c,m.r,30,17,0,0,0) spr(S.PLR.WALK1+(time()//128)%2,16,104,0) rect(0,0,240,24,2) print(NAME,((239-w)//2),10) rect(0,24,240,1,15) rect(0,26,240,1,2) rect(0,SCRH-8,SCRW,8,0) print("2022/26 OtherGuy24, 2017 btco",40,SCRH-7,7) if (time()//512)%2>0 then print("- PRESS 'Z' TO START -",65,84,15) end RendSpotFx(COLS//2,ROWS//2,Game.t) if btnp(4) then SetMode(M.RESTORE) end end function RestoreTic() local saveLvl=pmem(0) or 0 if saveLvl<1 then StartGame(1) return end Game.restoreSel=Game.restoreSel or 0 cls(0) local X=40 local Y1=30 local Y2=60 print("CONTINUE (LEVEL ".. LVL[saveLvl].name ..")",X,Y1) print("START NEW GAME",X,Y2) spr(S.PLR.STAND,X-20, Iif(Game.restoreSel>0,Y2,Y1)) if btnp(0) or btnp(1) then Game.restoreSel= Iif(Game.restoreSel>0,0,1) elseif btnp(4) then StartGame(Game.restoreSel>0 and 1 or saveLvl) end end function TutTic() cls(0) if Game.tutdone then StartLvl(1) return end local p=MapPageStart(56) map(p.c,p.r,COLS,ROWS) print("CONTROLS",100,10) print("JUMP",56,55); print("ATTACK",72,90); print("MOVE",160,50); print("10,000 PTS = EXTRA LIFE",60,110,3); if Game.t>150 and 0==((Game.t//16)%2) then print("- Press Z to continue -",60,130) end if Game.t>150 and btnp(4) then Game.tutdone=true StartLvl(1) end end function WldTic() WldUpdate() WldRend() end function PrerollTic() cls(0) print("LEVEL "..Game.lvl.name,100,40) spr(S.PLR.STAND,105,60) print("X " .. Plr.lives,125,60) if Game.t>60 then SetMode(M.PLAY) end end function PlayTic() UpdatePlr() UpdateEnts() UpdateParts() DetectColl() ApplyNudge() CheckEndLvl() AdjustScroll() Rend() if Game.m==M.PLAY then RendSpotFx((Plr.x-Game.scr)//C, Plr.y//C,Game.t) end end function EolTic() if Game.t>160 then AdvanceLvl() return end Rend() print("LEVEL CLEAR",85,20) end function DyingTic() Plr.dying=Plr.dying+1 if Game.t>100 then if Plr.lives>1 then Plr.lives=Plr.lives-1 SetMode(M.WLD) else SetMode(M.GAMEOVER) end else Rend() end end function GameOverTic() cls(0) print("GAME OVER!",92,50) if Game.t>150 then SetMode(M.TITLE) end end function WinTic() pmem(0,0)--resets saves cls(0) Game.scr=0 local m=MapPageStart(57) map(m.c,m.r, math.min(30,(Game.t-300)//8),17,0,0,0) print("THE END!",100, math.max(20,SCRH-Game.t//2)) print("Thanks for playing!",70, math.max(30,120+SCRH-Game.t//2)) if Game.t%100==0 then SpawnParts(PFX.FW,Rnd(40,SCRW-40), Rnd(40,SCRH-40),Rnd(2,15)) end UpdateParts() RendParts() if Game.t>1200 and btnp(4) then SetMode(M.TITLE) end end TICF={ [M.BOOT]=Boot, [M.TITLE]=TitleTic, [M.TUT]=TutTic, [M.RESTORE]=RestoreTic, [M.WLD]=WldTic, [M.PREROLL]=PrerollTic, [M.PLAY]=PlayTic, [M.DYING]=DyingTic, [M.GAMEOVER]=GameOverTic, [M.EOL]=EolTic, [M.WIN]=WinTic, } function StartGame(startLvlNo) Game.topLvl=startLvlNo-1 Plr=DeepCopy(PLR_INIT_STATE) -- put player at the right start pos local sp=Wld.spos[startLvlNo] or {x=Wld.plr.x0,y=Wld.plr.y0} Wld.plr.x=sp.x Wld.plr.y=sp.y SetMode(M.WLD) end function StartLvl(lvlNo) local oldLvlNo=Game.lvlNo Game.lvlNo=lvlNo Game.lvl=LVL[lvlNo] Game.scr=0 local old=Plr Plr=DeepCopy(PLR_INIT_STATE) -- preserve lives, score Plr.lives=old.lives Plr.score=old.score Plr.super=old.super if oldLvlNo==lvlNo then Plr.respX=old.respX Plr.respY=old.respY end SetMode(M.PREROLL) Ents={} Parts={} Toasts={} Tanims={} UnpackLvl(lvlNo,UMODE.GAME) GenBgMnt() GenSnow() ResetPal(Game.lvl.palor) AdjustRespawnPos() end function AdjustRespawnPos() if Plr.respX<0 then return end for i=1,#Ents do local e=Ents[i] if e.eid==EID.FLAG and e.xMAX_Y then -- keep same y but change direction cd=Rnd(MIN_CD,MAX_CD) dy=-dy else y=y+dy end end RndSeed(time()) end function GenSnow() if not Game.lvl.snow then Game.snow=nil return end Game.snow={} for r=0,ROWS-1,2 do for c=0,COLS-1,2 do Ins(Game.snow,{ x=c*C+Rnd(-8,8), y=r*C+Rnd(-8,8) }) end end end -- Whether player is on solid ground. function IsOnGround() return not CanMove(Plr.x,Plr.y+1) end -- Get level tile at given point function LvlTileAtPt(x,y) return LvlTile(x//C,y//C) end -- Get level tile. function LvlTile(c,r) if c<0 or c>=LVL_LEN then return 0 end if r<0 then return 0 end -- bottom-most tile repeats infinitely -- below (to allow player to swim -- when bottom tile is water). if r>=ROWS then r=ROWS-1 end return mget(c,r) end function SetLvlTile(c,r,t) if c<0 or c>=LVL_LEN then return false end if r<0 or r>=ROWS then return false end mset(c,r,t) end function UpdatePlr() local oldx=Plr.x local oldy=Plr.y Plr.plane=Max(Plr.plane-1,0) Plr.fireCd=Max(Plr.fireCd-1,0) Plr.firePwup=Max(Plr.firePwup-1,0) Plr.invuln=Max(Plr.invuln-1,0) Plr.drag=Iif2(Plr.drag>0,Plr.drag-1, Plr.drag<0,Plr.drag+1,0) Plr.signC=Max(Plr.signC-1,0) Plr.locked=Max(Plr.locked-1,0) UpdateSwimState() local swimmod=Plr.swim and Game.t%2 or 0 if (Plr.plane==0 and Plr.jmp==0 and not IsOnGround()) then -- fall Plr.y=Plr.y+1-swimmod end -- check if player fell into pit if Plr.y>SCRH+8 then StartDying() return end -- horizontal movement local dx=0 local dy=0 local wantLeft=Plr.locked==0 and Iif(Plr.drag==0,btn(2),Plr.drag<0) local wantRight=Plr.locked==0 and Iif(Plr.drag==0,btn(3),Plr.drag>0) local wantJmp=Plr.locked==0 and Plr.plane==0 and btnp(4) and Plr.drag==0 local wantAtk=Plr.locked==0 and btnp(5) and Plr.drag==0 if wantLeft then dx=-1+swimmod -- plane doesn't flip Plr.flipped=true elseif wantRight then dx=1-swimmod Plr.flipped=false end -- vertical movement (plane only) dy=dy+Iif2(Plr.plane>0 and btn(0) and Plr.y>8,-1, Plr.plane>0 and btn(1) and Plr.y0,false,btn(2),true, btn(3),false,Plr.flipped) TryMoveBy(dx,dy) Plr.grounded=Plr.plane==0 and IsOnGround() local canJmp=Plr.grounded or Plr.swim -- jump if wantJmp and canJmp then Plr.jmp=1 Plr.jmpSeq=Plr.surf and RESURF_DY or (Plr.swim and SWIM_JUMP_DY or JUMP_DY) Snd(Plr.surf and SND.JUMP or Plr.swim and SND.SWIM or SND.JUMP) -- TODO play swim snd if swim end if Plr.jmp>#Plr.jmpSeq then -- end jump Plr.jmp=0 elseif Plr.jmp>0 then local ok=TryMoveBy( 0,Plr.jmpSeq[Plr.jmp]) -- if blocked, cancel jump Plr.jmp=ok and Plr.jmp+1 or 0 end -- attack if Plr.atk==0 then if wantAtk then -- start attack sequence if Plr.plane==0 then Plr.atk=1 end Snd(SND.ATTACK) TryFire() end elseif Plr.atk>#ATK_SEQ then -- end of attack sequence Plr.atk=0 else -- advance attack sequence Plr.atk=Plr.atk+1 end -- check plane landing if Plr.plane>0 then CheckTarmac() end Plr.dx=Plr.x-oldx Plr.dy=Plr.y-oldy end function IsWater(t) return t==T.WATER or t==T.SURF or t==T.WFALL or t==T.SURFTWO or t==T.WFALLTWO end function UpdateSwimState() local wtop=IsWater( LvlTileAtPt(Plr.x+4,Plr.y+1)) local wbottom=IsWater( LvlTileAtPt(Plr.x+4,Plr.y+7)) local wtop2=IsWater( LvlTileAtPt(Plr.x+4,Plr.y-8)) Plr.swim=wtop and wbottom -- is plr near surface? Plr.surf=wbottom and not wtop2 end function TryFire() if Plr.firePwup<1 and Plr.plane==0 then return end if Plr.fireCd>0 then return end Plr.fireCd=FIRE.INTERVAL local x=Plr.x if Plr.plane==0 then x=x+(Plr.flipped and FIRE.OFFX_FLIP or FIRE.OFFX) else -- end of plane x=x+FIRE.OFFX_PLANE end local y=Plr.y+Iif(Plr.plane>0, FIRE.OFFY_PLANE,FIRE.OFFY) local e=EntAdd(EID.PFIRE,x,y) e.moveDx=Plr.plane>0 and 2 or (Plr.flipped and -1 or 1) e.ttl=Plr.plane>0 and e.ttl//2 or e.ttl end function ApplyNudge() Plr.y=Plr.y+Plr.nudgeY Plr.x=Plr.x+Plr.nudgeX Plr.nudgeX=0 Plr.nudgeY=0 end function TryMoveBy(dx,dy) if CanMove(Plr.x+dx,Plr.y+dy) then Plr.x=Plr.x+dx Plr.y=Plr.y+dy return true end return false end function GetPlrCr() return Iif(Plr.plane>0,CR.AVIATOR,CR.PLR) end -- Check if plr can move to given pos. function CanMove(x,y) local dy=y-Plr.y local pcr=GetPlrCr() local r=CanMoveEx(x,y,pcr,dy) if not r then return false end -- check if would bump into solid ent local pr=RectXLate(pcr,x,y) for i=1,#Ents do local e=Ents[i] local effSolid=(e.sol==SOL.FULL) or (e.sol==SOL.HALF and dy>0 and Plr.y+5=T.FIRST_DECO,SOL.NOT,SOL.FULL) end -- x,y: candidate pos; cr: collision rect -- dy: y direction of movement function CanMoveEx(x,y,cr,dy) local x1=x+cr.x local y1=y+cr.y local x2=x1+cr.w-1 local y2=y1+cr.h-1 -- check all tiles touched by the rect local startC=x1//C local endC=x2//C local startR=y1//C local endR=y2//C for c=startC,endC do for r=startR,endR do local sol=GetTileSol(LvlTile(c,r)) if sol==SOL.FULL then return false end end end -- special case: check for half-solidity -- tiles. Only solid when standing on -- top of them (y2%C==0) and going -- down (dy>0). local sA=GetTileSol(LvlTileAtPt(x1,y2)) local sB=GetTileSol(LvlTileAtPt(x2,y2)) if dy>0 and (sA==SOL.HALF or sB==SOL.HALF) and y2%C==0 then return false end return true end function EntWouldFall(e,x) return EntCanMove(e,x,e.y+1) end -- check if player landed plane on tarmac function CheckTarmac() local pr=RectXLate( CR.AVIATOR,Plr.x,Plr.y) local bottom=pr.y+pr.h+1 local t1=LvlTileAtPt(pr.x,bottom) local t2=LvlTileAtPt(pr.x+pr.w,bottom) if t1==T.TARMAC and t2==T.TARMAC then -- landed Plr.plane=0 SpawnParts(PFX.POP,Plr.x+4,Plr.y,14) -- TODO: more vfx, sfx end end function AdjustScroll() local dispx=Plr.x-Game.scr if dispx>SX_MAX then Game.scr=Plr.x-SX_MAX elseif dispx1000 or (rem~=50 and rem~=0) then return end local sp2=rem==50 and S.TINY_NUM_50 or S.TINY_NUM_00 local sp1=points>=100 and (S.TINY_NUM_R1-1+points//100) or 0 Ins(Toasts,{ x=Iif(points>=100,x-8,x-12), y=y,ttl=40,sp1=sp1,sp2=sp2}) end -- tx,ty: position where to show toast -- (optional) function AddScore(points,tx,ty) local old=Plr.score Plr.score=Plr.score+points Plr.scoreMt=Game.t if (old//10000)<(Plr.score//10000) then Snd(SND.ONEUP) Plr.lives=Plr.lives+1 -- TODO: vfx else Snd(SND.POINT) end if tx and ty then AddToast(points,tx,ty) end end function StartDying() SetMode(M.DYING) Snd(SND.DIE) Plr.dying=1 -- start die anim Plr.super=false Plr.firePwup=0 Plr.plane=0 end function EntAdd(newEid,newX,newY) local e={ eid=newEid, x=newX, y=newY } Ins(Ents,e) EntInit(e) return e end function EntInit(e) -- check if we have an animation for it if ANIM[e.eid] then e.anim=ANIM[e.eid] e.sprite=e.anim[1] else -- default to static sprite image e.sprite=e.eid end -- whether ent sprite is flipped e.flipped=false -- collider rect e.coll=CR.DFLT -- solidity (defaults to not solid) e.sol=SOL.NOT -- EBT entry local ebte=EBT[e.eid] -- behaviors e.beh=ebte and ebte.beh or {} -- copy initial behavior data to entity for _,b in pairs(e.beh) do ShallowMerge(e,b.data) end -- overlay the entity-defined data. if ebte and ebte.data then ShallowMerge(e,ebte.data) end -- call the entity init funcs for _,b in pairs(e.beh) do if b.init then b.init(e) end end end function EntRepl(e,eid,data) e.dead=true local newE=EntAdd(eid,e.x,e.y) if data then ShallowMerge(newE,data) end end function EntHasBeh(e,soughtBeh) for _,b in pairs(e.beh) do if b==soughtBeh then return true end end return false end function EntAddBeh(e,beh) if EntHasBeh(e,beh) then return end -- note: can't mutate the original -- e.beh because it's a shared ref. e.beh=DeepCopy(e.beh) ShallowMerge(e,beh.data,true) Ins(e.beh,beh) end function UpdateEnts() -- iterate backwards so we can delete for i=#Ents,1,-1 do local e=Ents[i] UpdateEnt(e) if e.dead then -- delete Rem(Ents,i) end end end function UpdateEnt(e) if not ALWAYS_UPDATED_EIDS[e.eid] and Abs(e.x-Plr.x)>ENT_MAX_DIST then -- too far, don't update return end -- update anim frame if e.anim then e.sprite=e.anim[1+(time()//128)%#e.anim] end -- run update behaviors for _,b in pairs(e.beh) do if b.update then b.update(e) end end end function GetEntAt(x,y) for i=1,#Ents do local e=Ents[i] if e.x==x and e.y==y then return e end end return nil end -- detect collisions function DetectColl() -- player rect local pr=RectXLate(GetPlrCr(), Plr.x,Plr.y) -- attack rect local ar=nil if ATK_SEQ[Plr.atk]==2 then -- player is attacking, so check if -- entity was hit by attack ar=RectXLate(CR.ATK,Plr.x,Plr.y) if Plr.flipped then ar.x=Plr.x+CR.ATK_FLIP_X end end for i=1,#Ents do local e=Ents[i] local er=RectXLate(e.coll,e.x,e.y) if RectIsct(pr,er) then -- collision between player and ent HandlePlrColl(e) elseif ar and RectIsct(ar,er) then -- ent hit by player attack HandleDamage(e,DMG.MELEE) end end end function CheckEndLvl() local t=LvlTileAtPt( Plr.x+C//2,Plr.y+C//2) if t==T.GATE_L or t==T.GATE_R or t==T.GATE_L2 or t==T.GATE_R2 then EndLvl() end end function EndLvl() Game.topLvl=Max( Game.topLvl,Game.lvlNo) SetMode(M.EOL) end function AdvanceLvl() -- save game if we should if Game.lvl.save then pmem(0,Max(pmem(0) or 0, Game.lvlNo+1)) end if Game.lvlNo>=#LVL then -- end of game. SetMode(M.WIN) else -- go back to map. SetMode(M.WLD) end end -- handle collision w/ given ent function HandlePlrColl(e) for _,b in pairs(e.beh) do if b.coll then b.coll(e) end if e.dead then break end end end function HandleDamage(e,dtype) for _,b in pairs(e.beh) do if b.dmg then b.dmg(e,dtype) end if e.dead then SpawnParts(PFX.POP,e.x+4,e.y+4,e.clr) Snd(SND.KILL) break end end end function HandlePlrHurt() if Plr.invuln>0 then return end if Plr.plane==0 and Plr.super then Snd(SND.HURT) Plr.super=false Plr.invuln=100 Plr.drag=Iif(Plr.dx>=0,-10,10) Plr.jmp=0 else StartDying() end end function Snd(spec) if not Sett.snd then return end sfx(spec.sfxid,spec.note,spec.dur, 0,spec.vol or 15,spec.speed or 0) end function PlayMus(musid) if Sett.mus or musid==-1 then music(musid) end end --------------------------------------- -- PARTICLES --------------------------------------- -- possible effects PFX={ POP={ rad=4, count=15, speed=4, fall=true, ttl=15 }, FW={ -- fireworks rad=3, count=40, speed=1, fall=false, ttl=100 } } -- fx=one of the effects in PFX -- cx,cy=center, clr=the color function SpawnParts(fx,cx,cy,clr) for i=1,fx.count do local r=Rnd01()*fx.rad local phi=Rnd01()*math.pi*2 local part={ x=cx+r*Cos(phi), y=cy+r*Sin(phi), vx=fx.speed*Cos(phi), vy=fx.speed*Sin(phi), fall=fx.fall, ttl=fx.ttl, age=0, clr=clr } Ins(Parts,part) end end function UpdateParts() -- iterate backwards so we can delete for i=#Parts,1,-1 do local p=Parts[i] p.age=p.age+1 if p.age>=p.ttl then -- delete Rem(Parts,i) else p.x=p.x+p.vx p.y=p.y+p.vy+(p.fall and p.age//2 or 0) end end end function RendParts() for i,p in pairs(Parts) do pix(p.x-Game.scr,p.y,p.clr) end end --------------------------------------- -- WLD MAP --------------------------------------- -- convert "World W-L" into index function Wl(w,l) return (w-1)*3+l end -- Init world (runs once at start of app). function WldInit() for r=0,ROWS-1 do for c=0,COLS-1 do local t=WldFgTile(c,r) local lval=WldLvlVal(t) if t==T.META_A then -- player start pos Wld.plr.x0=c*C Wld.plr.y0=(r-1)*C elseif t==T.META_B then local mv=WldGetTag(c,r) -- savegame start pos Wld.spos[Wl(mv,1)]={ x=c*C,y=(r-1)*C} elseif lval>0 then local mv=WldGetTag(c,r) -- It's a level tile. local poi={c=c,r=r, t=POI.LVL,lvl=Wl(mv,lval)} Ins(Wld.pois,poi) end end end end -- Looks around tc,tr for a numeric tag. function WldGetTag(tc,tr) for r=tr-1,tr+1 do for c=tc-1,tc+1 do local mv=MetaVal(WldFgTile(c,r),0) if mv>0 then return mv end end end trace("No WLD tag @"..tc..","..tr) return 0 end -- Returns the value (1, 2, 3) of a WLD -- level tile. function WldLvlVal(t) return Iif4(t==S.WLD.LVLF,1, t==S.WLD.LVL1,1, t==S.WLD.LVL2,2, t==S.WLD.LVL3,3,0) end function WldFgTile(c,r) return MapPageTile(WLD.FPAGE,c,r) end function WldBgTile(c,r) return MapPageTile(WLD.BPAGE,c,r) end function WldPoiAt(c,r) for i=1,#Wld.pois do local poi=Wld.pois[i] if poi.c==c and poi.r==r then return poi end end return nil end function WldHasRoadAt(c,r) local t=WldFgTile(c,r) for i=1,#S.WLD.ROADS do if S.WLD.ROADS[i]==t then return true end end return false end function WldUpdate() local p=Wld.plr -- shorthand if p.dx~=0 or p.dy~=0 then -- Just move. p.x=p.x+p.dx p.y=p.y+p.dy if p.x%C==0 and p.y%C==0 then -- reached destination. p.ldx=p.dx p.ldy=p.dy p.dx=0 p.dy=0 end return end if btn(0) then WldTryMove(0,-1) end if btn(1) then WldTryMove(0,1) end if btn(2) then WldTryMove(-1,0) end if btn(3) then WldTryMove(1,0) end Wld.plr.flipped=Iif( Iif(Wld.plr.flipped,btn(3),btn(2)), not Wld.plr.flipped, Wld.plr.flipped) -- wtf if btnp(4) then local poi=WldPoiAt(p.x//C,p.y//C) if poi and poi.lvl>Game.topLvl then if poi.lvl==1 then SetMode(M.TUT) else StartLvl(poi.lvl) end end end end function WldTryMove(dx,dy) local p=Wld.plr -- shorthand -- if we are in locked POI, we can only -- come back the way we came. local poi=WldPoiAt(p.x//C,p.y//C) if not Plr.dbgFly and poi and poi.lvl>Game.topLvl and (dx ~= -p.ldx or dy ~= -p.ldy) then return end -- target row,col local tc=p.x//C+dx local tr=p.y//C+dy if WldHasRoadAt(tc,tr) or WldPoiAt(tc,tr) then -- Destination is a road or level. -- Move is valid. p.dx=dx p.dy=dy return end end function WldFgRemapFunc(t) return t=LVL_LEN then trace("ERROR: level too long: "..lvlNo) return end end end -- if in gameplay, expand patterns and -- remove special markers -- (first META_A is player start pos) if mode==UMODE.GAME then for c=0,LVL_LEN-1 do for r=0,ROWS-1 do local t=mget(c,r) local tpat=TPAT[t] if tpat then ExpandTpat(tpat,c,r) end if Plr.x==0 and Plr.y==0 and t==T.META_A then -- player start position. Plr.x=c*C Plr.y=r*C end if t>=T.FIRST_META then mset(c,r,0) end end end if Plr.x==0 and Plr.y==0 then trace("*** start pos UNSET L"..lvlNo) end FillWater() SetUpTanims() end end -- expand tile pattern at c,r function ExpandTpat(tpat,c,r) local s=mget(c,r) for i=0,tpat.w-1 do for j=0,tpat.h-1 do mset(c+i,r+j,s+j*16+i) end end end -- Sets up tile animations. function SetUpTanims() for c=0,LVL_LEN-1 do for r=0,ROWS-1 do local t=mget(c,r) if TANIM[t] then TanimAdd(c,r) end end end end function FindLvlEndCol(c0,r0,len) -- iterate backwards until we find a -- non-empty col. for c=c0+len-1,c0,-1 do for r=r0,r0+ROWS-1 do if mget(c,r)>0 then -- rightmost non empty col return c end end end return c0 end function FillWater() local surfs={} local waters={} for c=LVL_LEN-1,0,-1 do for r=ROWS-1,0,-1 do if mget(c,r)==T.SURF then Ins(surfs,{c=c,r=r}) elseif mget(c,r)==T.SURFTWO then Ins(surfs,{c=c,r=r}) elseif mget(c,r)==T.WATER then Ins(waters,{c=c,r=r}) end end end for i=1,#surfs do local s=surfs[i] FillWaterAt(s.c,s.r,1) end for i=1,#waters do local s=waters[i] FillWaterAt(s.c,s.r,-1) FillWaterAt(s.c,s.r,1) end end -- Fill water starting (but not including) -- given tile, in the given direction -- (1:down, -1:up) function FillWaterAt(c,r0,dir) local from=r0+dir local to=Iif(dir>0,ROWS-1,0) for r=from,to,dir do if mget(c,r)==T.EMPTY then mset(c,r,T.WATER) else return end end end function TanimAdd(c,r) if Tanims[c] then Ins(Tanims[c],r) else Tanims[c]={r} end end -- pack lvl from 0,0-239,16 to the packed -- level area of the indicated level function PackLvl(lvlNo) local lvl=LVL[lvlNo] local start=MapPageStart(lvl.pkstart) local outc=start.c local outr=start.r local len=lvl.pklen*30 local endc=FindLvlEndCol(0,0,LVL_LEN) -- pack local reps=0 MapClear(outc,outr,len,ROWS) for c=0,endc do if c>0 and MapColsEqual(c,c-1,0) and reps<12 then -- increment repeat marker on prev col local m=mget(outc-1,outr) m=Iif(m==0,T.META_NUM_0+2,m+1) mset(outc-1,outr,m) reps=reps+1 else reps=1 -- copy col to packed level MapCopy(c,0,outc,outr,1,ROWS) outc=outc+1 if outc>=start.c+len then trace("Capacity exceeded.") return false end end end trace("packed "..(endc+1).." -> ".. (outc+1-start.c)) return true end -- Create map col (dstc,0)-(dstc,ROWS-1) -- from source col located at -- (srcc,offr)-(srcc,offr+ROWS-1). -- if ie, instantiates entities. function CreateCol(srcc,dstc,offr,ie) -- copy entire column first MapCopy(srcc,offr,dstc,0,1,ROWS) mset(dstc,0,T.EMPTY) -- top cell is empty if not ie then return end -- instantiate entities for r=1,ROWS-1 do local t=mget(dstc,r) if t>=T.FIRST_ENT and EBT[t] then -- entity tile: create entity mset(dstc,r,T.EMPTY) EntAdd(t,dstc*C,r*C) end end end --------------------------------------- -- RENDERING --------------------------------------- function Rend() RendBg() if Game.snow then RendSnow() end RendMap() RendTanims() RendEnts() RendToasts() if Game.m==M.EOL then RendScrim() end RendPlr() RendParts() RendHud() RendSign() end function RendBg() local END_R=ROWS cls(Game.lvl.bg) local offset=Game.scr//2+50 -- If i is a col# of mountains (starting -- at index 1), then its screen pos -- sx=(i-1)*C-off -- Solving for i, i=1+(sx+off)/C -- so at the left of screen, sx=0, we -- have i=1+off/C local startI=Max(1,1+offset//C) local endI=Min( startI+COLS,#Game.bgmnt) for i=startI,endI do local sx=(i-1)*C-offset local part=Game.bgmnt[i] for r=part.y,END_R do local spid=Iif(r==part.y, S.BGMNT.DIAG,S.BGMNT.FULL) spr(spid,(i-1)*C-offset,r*C,0,1, Iif(part.dy>0,1,0)) end end end function RendSnow() local dx=-Game.scr local dy=Game.t//2 for _,p in pairs(Game.snow) do local sx=((p.x+dx)%SCRW+SCRW)%SCRW local sy=((p.y+dy)%SCRH+SCRH)%SCRH pix(sx,sy,Game.lvl.snow.clr) end end function RendToasts() for i=#Toasts,1,-1 do local t=Toasts[i] t.ttl=t.ttl-1 if t.ttl<=0 then Toasts[i]=Toasts[#Toasts] Rem(Toasts) else t.y=t.y-1 spr(t.sp1,t.x-Game.scr,t.y,0) spr(t.sp2,t.x-Game.scr+C,t.y,0) end end end function RendMap() -- col c is rendered at -- sx=-Game.scr+c*C -- Setting sx=0 and solving for c -- c=Game.scr//C local c=Game.scr//C local sx=-Game.scr+c*C local w=Min(COLS+1,LVL_LEN-c) if c<0 then sx=sx+C*(-c) c=0 end map( -- col,row,w,h c,0,w,ROWS, -- sx,sy,colorkey,scale sx,0,0,1) end function RendPlr() local spid local walking=false if Plr.plane>0 then RendPlane() return end if Plr.dying>0 then spid=S.PLR.DIE elseif Plr.atk>0 then spid= ATK_SEQ[Plr.atk]==1 and S.PLR.SWING or S.PLR.HIT elseif Plr.grounded then if btn(2) or btn(3) then spid=S.PLR.WALK1+time()%2 walking=true else spid=S.PLR.STAND end elseif Plr.swim then spid=S.PLR.SWIM1+(Game.t//4)%2 else spid=S.PLR.JUMP end local sx=Plr.x-Game.scr local sy=Plr.y local flip=Plr.flipped and 1 or 0 -- apply dying animation if spid==S.PLR.DIE then if Plr.dying<=#DIE_SEQ then sx=sx+DIE_SEQ[Plr.dying][1] sy=sy+DIE_SEQ[Plr.dying][2] else sx=-1000 sy=-1000 end end -- if invulnerable, blink if Plr.invuln>0 and 0==(Game.t//4)%2 then return end spr(spid,sx,sy,0,1,flip) -- extra sprite for attack states if spid==S.PLR.SWING then spr(S.PLR.SWING_C,sx,sy-C,0,1,flip) elseif spid==S.PLR.HIT then spr(S.PLR.HIT_C, sx+(Plr.flipped and -C or C), sy,0,1,flip) end -- draw super panda overlay if player -- has the super panda powerup if Plr.super then local osp=Iif3(Plr.atk>0,S.PLR.SUPER_F, Plr.swim and not Plr.grounded, S.PLR.SUPER_S, walking,S.PLR.SUPER_P,S.PLR.SUPER_F) spr(osp,sx,Plr.y,0,1,flip) end -- draw overlays (blinking bamboo and -- yellow body) if powerup if spid~=S.PLR.SWING and Plr.firePwup>0 and (time()//128)%2==0 then spr(S.PLR.FIRE_BAMBOO,sx,Plr.y,0,1,flip) end if Plr.firePwup>100 or 1==(Plr.firePwup//16)%2 then local osp=Iif3(Plr.atk>0,S.PLR.FIRE_F, Plr.swim and not Plr.grounded, S.PLR.FIRE_S, walking,S.PLR.FIRE_P,S.PLR.FIRE_F) spr(osp,sx,Plr.y,0,1,flip) end -- if just respawned, highlight player if Game.m==M.PLAY and Plr.dying==0 and Plr.respX>=0 and Game.t<100 and (Game.t//8)%2==0 then rectb(Plr.x-Game.scr-2,Plr.y-2, C+4,C+4,15) end end function RendPlane() local ybias=(Game.t//8)%2==0 and 1 or 0 local sx=Plr.x-Game.scr spr(S.AVIATOR, sx-C,Plr.y+ybias,0,1,0,0,3,2) local spid=(Game.t//4)%2==0 and S.AVIATOR_PROP_1 or S.AVIATOR_PROP_2 spr(spid,sx+C, Plr.y+ybias+4,0) end function RendHud() rect(0,0,SCRW,C,3) if Plr.scoreDisp.value~=Plr.score then Plr.scoreDisp.value=Plr.score Plr.scoreDisp.text=Lpad(Plr.score,6) end local clr=15 print(Plr.scoreDisp.text,192,1,clr,true) print((Game.m==M.WLD and "WORLD MAP" or ("LEVEL "..Game.lvl.name)),95,1,7) spr(S.PLR.STAND,5,0,0) print("x "..Plr.lives,16,1) if Plr.plane>0 then local barw=PLANE.FUEL_BAR_W local lx=120-barw//2 local y=8 local clr=(Plr.plane<800 and (Game.t//16)%2==0) and 6 or 14 local clrLo=(clr==14 and 4 or clr) print("E",lx-7,y,clr) print("F",lx+barw+1,y,14) rectb(lx,y,barw,6,clrLo) local bw=Plr.plane* (PLANE.FUEL_BAR_W-2)//PLANE.MAX_FUEL rect(lx+1,y+1,Max(bw,1),4,clr) pix(lx+barw//4,y+4,clrLo) pix(lx+barw//2,y+4,clrLo) pix(lx+barw//2,y+3,clrLo) pix(lx+3*barw//4,y+4,clrLo) end end function RendEnts() for i=1,#Ents do local e=Ents[i] local sx=e.x-Game.scr if sx>-C and sx45,0, Game.t>30,S.SCRIM+2, Game.t>15,S.SCRIM+1, S.SCRIM) for r=0,ROWS-1 do for c=0,COLS-1 do spr(sp,c*C,r*C,15) end end end -- Render spotlight effect. -- fc,fr: cell at center of effect -- t: clock (ticks) function RendSpotFx(fc,fr,t) local rad=Max(0,t//2-2) -- radius if rad>COLS then return end for r=0,ROWS-1 do for c=0,COLS-1 do local d=Max(Abs(fc-c), Abs(fr-r)) local sa=d-rad -- scrim amount local spid=Iif2(sa<=0,-1,sa<=3, S.SCRIM+sa-1,0) if spid>=0 then spr(spid,c*C,r*C,15) end end end end function RendSign() if 0==Plr.signC then return end local w=Plr.signC*20 local h=Plr.signC*3 local x=SCRW//2-w//2 local y=SCRH//2-h//2-20 local s=SIGN_MSGS[Plr.signMsg] rect(x,y,w,h,15) if Plr.signC==SIGN_C_MAX then print(s.l1,x+6,y+8,0) print(s.l2,x+6,y+8+C,0) end end -- Rend tile animations function RendTanims() local c0=Max(0,Game.scr//C) local cf=c0+COLS for c=c0,cf do local anims=Tanims[c] if anims then for i=1,#anims do local r=anims[i] local tanim=TANIM[mget(c,r)] if tanim then local spid=tanim[ 1+(Game.t//16)%#tanim] spr(spid,c*C-Game.scr,r*C) end end end end end -------------------------------------- -- ENTITY BEHAVIORS --------------------------------------- -- move hit modes: what happens when -- entity hits something solid. MOVE_HIT={ NONE=0, STOP=1, BOUNCE=2, DIE=3, } -- aim mode AIM={ NONE=0, -- just shoot in natural -- direction of projectile HORIZ=1, -- adjust horizontal vel to -- go towards player VERT=2, -- adjust vertical vel to go -- towards player FULL=3, -- adjust horiz/vert to aim -- at player } -- moves horizontally -- moveDen: every how many ticks to move -- moveDx: how much to move -- moveHitMode: what to do on wall hit -- noFall: if true, flip instead of falling function BehMove(e) if e.moveT>0 then e.moveT=e.moveT-1 end if e.moveT==0 then return end if e.moveWaitPlr>0 then if Abs(Plr.x-e.x)>e.moveWaitPlr then return else e.moveWaitPlr=0 end end e.moveNum=e.moveNum+1 if e.moveNum0 elseif e.moveHitMode==MOVE_HIT.NONE or EntCanMove(e,e.x+e.moveDx,e.y) then e.x=e.x+(e.moveDx or 0) e.y=e.y+(e.moveDy or 0) elseif e.moveHitMode==MOVE_HIT.BOUNCE then e.moveDx=-(e.moveDx or 0) e.flipped=e.moveDx>0 elseif e.moveHitMode==MOVE_HIT.DIE then e.dead=true end end -- Moves up/down. -- e.yamp: amplitude function BehUpDownInit(e) e.maxy=e.y e.miny=e.maxy-e.yamp end function BehUpDown(e) e.ynum=e.ynum+1 if e.ynum=e.maxy then e.dy=-1 end end function BehFacePlr(e) e.flipped=Plr.x>e.x if e.moveDx then e.moveDx=Abs(e.moveDx)* (e.flipped and 1 or -1) end end -- automatically flips movement -- flipDen: every how many ticks to flip function BehFlip(e) e.flipNum=e.flipNum+1 if e.flipNum#JUMP_DY then -- end jump e.jmp=0 else local dy=JUMP_DY[e.jmp] if EntCanMove(e,e.x,e.y+dy) then e.y=e.y+dy else e.jmp=0 end end end end function BehFall(e) e.grounded=not EntCanMove(e,e.x,e.y+1) if not e.grounded and e.jmp==0 then e.y=e.y+1 end end function BehTakeDmg(e,dtype) if not ArrayContains(e.dtypes,dtype) then return end e.hp=e.hp-1 if e.hp>0 then return end e.dead=true -- drop loot? local roll=Rnd(0,99) -- give bonus probability to starting -- levels (decrease roll value) roll=Max(Iif2(Game.lvlNo<2,roll-50, Game.lvlNo<4,roll-25,roll),0) if rollb then e.boty=a e.topy=b e.dir=1 else e.topy=a e.boty=b e.dir=-1 end e.coll=CR.FULL end function BehLift(e) e.liftNum=e.liftNum+1 if e.liftNum0 and e.y>e.boty or e.dir<0 and e.yPlr.y,-1,1) end function BehShootInit(e) e.shootNum=Rnd(0,e.shootDen-1) end function BehShoot(e) e.shootNum=e.shootNum+1 if e.shootNum<30 then e.sprite=e.shootSpr or e.sprite end if e.shootNume.x and 1 or -1)* Abs(shot.moveDx) elseif e.aim==AIM.VERT then shot.moveDy=(Plr.y>e.y and 1 or -1)* Abs(shot.moveDy) elseif e.aim==AIM.FULL then local tx=Plr.x-shot.x local ty=Plr.y-shot.y local mag=math.sqrt(tx*tx+ty*ty) local spd=math.sqrt( shot.moveDx*shot.moveDx+ shot.moveDy*shot.moveDy) shot.moveDx=math.floor(0.5+tx*spd/mag) shot.moveDy=math.floor(0.5+ty*spd/mag) if shot.moveDx==0 and shot.moveDy==0 then shot.moveDx=-1 end end end function BehCrumble(e) if not e.crumbling then -- check if player on tile if Plr.xe.x+8 then return end -- check if player is standing on it local pr=RectXLate( GetPlrCr(),Plr.x,Plr.y) local er=RectXLate(e.coll,e.x,e.y-1) e.crumbling=RectIsct(pr,er) end if e.crumbling then -- count down to destruction e.cd=e.cd-1 e.sprite=Iif(e.cd>66,S.CRUMBLE, Iif(e.cd>33,S.CRUMBLE_2,S.CRUMBLE_3)) if e.cd<0 then e.dead=true end end end function BehTtl(e) e.ttl=e.ttl-1 if e.ttl <= 0 then e.dead = true end end function BehDmgEnemy(e) local fr=RectXLate(FIRE.COLL,e.x,e.y) for i=1,#Ents do local ent=Ents[i] local er=RectXLate(ent.coll,ent.x,ent.y) if e~=ent and RectIsct(fr,er) and EntHasBeh(ent,BE.VULN) then -- ent hit by player fire HandleDamage(ent,Plr.plane>0 and DMG.PLANE_FIRE or DMG.FIRE) e.dead=true end end end function BehGrantFirePwupColl(e) Plr.firePwup=FIRE.DUR e.dead=true Snd(SND.PWUP) end function BehGrantSuperPwupColl(e) Plr.super=true e.dead=true Snd(SND.PWUP) end function BehReplace(e) local d=Abs(e.x-Plr.x) if d1 then EntAdd(c,cx,ty) end if e.mul>2 then EntAdd(c,lx,by) end if e.mul>3 then EntAdd(c,rx,by) end if e.mul>4 then EntAdd(c,lx,ty) end if e.mul>5 then EntAdd(c,rx,ty) end end function BehTplafInit(e) e.phase=MetaVal(FetchEntTag(e),0) end function BehTplaf(e) local UNIT=40 -- in ticks local PHASE_LEN=3 -- in units local uclk=e.phase+Game.t//UNIT local open=((uclk//PHASE_LEN)%2==0) local tclk=e.phase*UNIT+Game.t e.sprite=Iif2( (tclk%(UNIT*PHASE_LEN)<=6), S.TPLAF_HALF,open,S.TPLAF,S.TPLAF_OFF) e.sol=Iif(open,SOL.HALF,SOL.NOT) end function BehDashInit(e) assert(EntHasBeh(e,BE.MOVE)) e.origAnim=e.anim e.origMoveDen=e.moveDen end function BehDash(e) local dashing=e.cddr2.x and r2.x+r2.w>r1.x and r1.y+r1.h>r2.y and r2.y+r2.h>r1.y end function DeepCopy(t) if type(t)~="table" then return t end local r={} for k,v in pairs(t) do if type(v)=="table" then r[k]=DeepCopy(v) else r[k]=v end end return r end -- if preserve, fields that already exist -- in the target won't be overwritten function ShallowMerge(target,src, preserve) if not src then return end for k,v in pairs(src) do if not preserve or not target[k] then target[k]=DeepCopy(src[k]) end end end function MapCopy(sc,sr,dc,dr,w,h) for r=0,h-1 do for c=0,w-1 do mset(dc+c,dr+r,mget(sc+c,sr+r)) end end end function MapClear(dc,dr,w,h) for r=0,h-1 do for c=0,w-1 do mset(dc+c,dr+r,0) end end end function MapColsEqual(c1,c2,r) for i=0,ROWS-1 do if mget(c1,r+i)~=mget(c2,r+i) then return false end end return true end function MetaVal(t,deflt) return Iif( t>=T.META_NUM_0 and t<=T.META_NUM_0+12, t-T.META_NUM_0,deflt) end -- finds marker m on column c of level -- return row of marker, -1 if not found function FetchTile(m,c,nowarn) for r=0,ROWS-1 do if LvlTile(c,r)==m then if erase then SetLvlTile(c,r,0) end return r end end if not nowarn then trace("Marker not found "..m.." @"..c) end return -1 end -- Gets the entity's "tag marker", -- that is the marker tile that's sitting -- just above it. Also erases it. -- If no marker found, returns 0 function FetchEntTag(e) local t=mget(e.x//C,e.y//C-1) if t>=T.FIRST_META then mset(e.x//C,e.y//C-1,0) return t else return 0 end end function Max(x,y) return math.max(x,y) end function Min(x,y) return math.min(x,y) end function Abs(x,y) return math.abs(x,y) end function Rnd(lo,hi) return math.random(lo,hi) end function Rnd01() return math.random() end function RndSeed(s) return math.randomseed(s) end function Ins(tbl,e) return table.insert(tbl,e) end function Rem(tbl,e) return table.remove(tbl,e) end function Sin(a) return math.sin(a) end function Cos(a) return math.cos(a) end