0 @$404mJJJL04e$FHuqa },m,m^ `2Tv޼xV4!CeͫTvxBe޼V4fUD3#"""23CTevQwOr}E UUUUDDDDDDDDIDDDDDIDDDDDDDDDDDDDDDDDDIDDDDDIDDDD77ssw37s7s3w77ssw37s7s3wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww7777wwwws37333333s373333s37s33333s3s3333s3s333333s373333s37s33333333ws7w33337ws73333s7ws3333ws7wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwppwDDDDDDDD33333333-"--""""""""""""""""""""""""뻻UUUUUUUU[UUUUU[UUUUUUUUUUUUUUUUUU[UUUUU[UUUUIIDIIDIIDIIDDDDDwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwpwppwppwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww@@@@@@@@00000000""""""""""""""""""""""""""""""""ݪڭڪݭݪڭڪwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwPPP[ P[U[UUUUUU [U[Uwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwpwpwppw@@DD@DDDDDDDDDD@DD@DD@0033033333333330330330""""""""""""""""""""DDDDNNDNNDNNDNND>>33>>33>>33>>wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwpppppwpp`ff63?c6?c63?c63?c63?c6c`ff`ff6?c63c63?c63c6?3c6c`ff`ff6?c63c6?c63c63c6?c`ffwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwpwppwppw030000003000000003000303`ff6c6?3c6?c6?3c6?3c6?3c`ffDDDDDDDDDDDDDDIDDDDDIDDDD77ssw37s7s3w77ssw37s7s3swwwwPUUUP UU UUUUUUUUUUpwwwpwwwwwwwwwwwwppUU[[UUUQ[U[P U[ UUU U Pwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwpwppwpppwwww7ww7ww7ww77www70333pwww`ffff``ffffpwwwffffffff UPUU[UUUUUU UPUP[UU[UUUUUw0333s033303s033C433000UUwwwpwpwwwpwwwwwwpwwPPP[ P[U[UUUUUU [U[Uwwww7w7ww7w7w7www70333fff```fffffffffffwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww`f`ff`ff`ff``ff```ffwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww7w77ww7ww7www70333 DDD DDD [U[Uwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww@@@@`ff ^^__wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww7ww77ww7ww7www70333wwwww7ww77w7ww7www70333wwwww7ww77ww7ww7www70333wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww ```ffn n`ff```  PPPPUU^ ^PUUUUUUP 陙 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwssss333377773333ssss3333II䙞NINDIDzzwwzww NNDDDDDDDDDDDDDDDDD@@3333 @Dwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww`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 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwn`ffff`fff0033000330wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww0??0333?00ww77ww033ww0s70wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww ??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 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww3333wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww 陙 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww ;33;33;3;;;;3;33 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwppppwpwwpwpppwwpwwpwwwwwpwwwppppppppwppwppwpwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwP00000^00000000000000000RSTRST0000000000000000~^_0000000uvno000000RSTxw&QQQQPPPP  00000$ ,N-(-'-/?OQQQQPPPPll(&lƅQQQQPPPPRSTlUVWXYZ\]m|[kj'&kj(UWY^^!"1234QQQQPPPP`abcefghijk[}jjk&'&&(````````````````````````````````````````````````````````````^^^^^^!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!444444444444444444444^^^111111111111111111111111111111111111111111111^^^   ^~"""""=>>? =""""""""""""//"""""? =.>.""""""""""""}|MN MN""""""]m2222"""""}= ?222222"""".>.MN\22222222"""22222222"^_>= ?= ?>22222222MN,b&&`&cnoMNL22222MN<222|] {KJIHHGGFFEEEEFFGHHIIJJKKKLLMMMYZ[]]^^^______________________ 000000000000000000000000000000komololokoiojolojoiojokomononoS            S                       gjnnooooooooooooooooooooooooooreihiiYjYkIl9lm mnnnoooooooooooooooooo,FFFffFFFffFFFffFFFffFFFffFFFffFFFffFFFff$$$$$$$$$$FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF$$$$$$$$$$$$$$$$jJjJjJjJjJjJjJjJjJjJjJjJjJjJjJjJ$$$$$$$$$$$$$$$$fFffFffFffFffFffFffFffFfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff$$$$$$$$$$$$$$$$hhhhhhhhhhh``````````````````00P`TeTemTaTaTuTThhL\nnm<UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU^UU^^UU^U^^^U^UU^U^^UUU^U^^^UU^U^U^^^UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU^UU^UU^UU^^^^UU^U^U^^U^UUU^^^^U^UUUUU^^^^UUU^UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU^^UU^UU^^^^UU^U^^^^UUU^U^^UUUUU^^^^UUU^UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU^UU^UU^UU^^^UU^U^UU^^U^UUU^^^U^UUUUU^^^^UU^UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU^UU^^^UU^U^^^^UUUUUUU^^^UUUUU^U^^^U^^UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU^^[[[_UUU_[[[UUU[[[NUUUN[[[DDUUU77ss77ss77ss77ss77ss77ss77ss77ssw37sw37sw37sw37sw37sw37sw37sw37s7s3w7s3w7s3w7s3w7s3w7s3w7s3w7s3w77ss77ss77ss77ss77ss77ss77ss77ssw37sw37sw37sw37sw37sw37sw37sw37s7s3w7s3w7s3w7s3w7s3w7s3w7s3w7s3wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwqqwwwwwwaffffffffaaffffffff11aaQQQ1ffffffaffQUU3san^133aQQQQn^QQC4a333wQ[Q[ݱUaffQUU̱Q[33ff13Q[UQ[UQUUaUUQ[U1ff33s[UU[UU[UUaUU[UU1ff11333UUUUUUUUUUUUaQUUUU1ffUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDIDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDIDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD-- title: 8bp Level Maker Tutorial -- author: OtherGuy24 -- original by Bruno Oliveira -- desc: some info about how to make levels -- script: lua -- saveid: LevelMakerTutorial1.0 -- -- WARNING: this file must be kept under -- 64kB (TIC-80 limit)! --[[tutorial comment: ----------------------------------- There is a Debug Menu where you can fly, be invulnerable, warp to other levels, etc. To get to the menu, (while being in a level): -hold down A -press up 4 times -press down 2 times once you're in the menu: -use arrow keys left/right/up/down -use z to select things -go to close and press z ----------------------------------- ]] NAME="8-BIT PANDA LEVEL MAKER TUTORIAL" C=8 ROWS=17 COLS=30 SCRW=240 SCRH=136 -- jump sequence (delta y at each frame) JUMP_DY={-3,-3,-3,-3,-2,-2,-2,-2, -1,-1,0,0,0,0,0} -- swimming seq (implemented as a "jump") 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} -- attack sequence (1=preparing, -- 2=attack,3=recovery) ATK_SEQ={1,1,1,1,2,3,3,3} -- die sequence (dx,dy) 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} } -- display x coords in which -- to keep the player (for scrolling) SX_MIN=50 SX_MAX=70 -- entity/tile solidity SOL={ NOT=0, -- not solid HALF=1, -- only when going down, -- allows movement upward. FULL=2, -- fully solid } FIRE={ -- duration of fire powerup DUR=1000, -- time between successive fires. INTERVAL=20, -- offset from player pos OFFY=2,OFFX=7,OFFX_FLIP=-2, OFFX_PLANE=14,OFFY_PLANE=8, -- projectile collision rect COLL={x=0,y=0,w=3,h=3}, } -- Tiles -- 0: empty -- 1-79: static solid blocks -- 80-127: decorative -- 128-239: entities -- 240-255: special markers T={ EMPTY=0, -- platform that's only solid when -- going down, but allows upward move HPLAF=4, SURF=16, WATER=32, WFALL=48, TARMAC=52, -- (where plane can land). -- sprite id above which tiles are -- non-solid decorative elements FIRST_DECO=80, -- level-end gate components GATE_L=110,GATE_R=111, GATE_L2=142,GATE_R=143, -- tile id above which tiles are -- representative of entities, not bg FIRST_ENT=128, -- tile id above which tiles have special -- meanings FIRST_META=240, -- number markers (used for level -- packing and annotations). META_NUM_0=240, -- followed by nums 1-9. -- A/B markers (entity-specific meaning) META_A=254, META_B=255 } -- Autocomplete of tiles patterns. -- Auto filled when top left map tile -- is present. TPAT={ [85]={w=2,h=2}, [87]={w=2,h=2}, [94]={w=2,h=2}, [89]={w=2,h=2}, } -- solidity of tiles (overrides) TSOL={ [T.EMPTY]=SOL.NOT, [T.HPLAF]=SOL.HALF, [T.SURF]=SOL.NOT, [T.WATER]=SOL.NOT, [T.WFALL]=SOL.NOT, } -- animated tiles TANIM={ [T.SURF]={T.SURF,332}, [T.WFALL]={T.WFALL,333,334,335}, } -- sprites S={ PLR={ -- player sprites STAND=257, WALK1=258, WALK2=259, JUMP=273, SWING=276, SWING_C=260, HIT=277, HIT_C=278, DIE=274, SWIM1=267,SWIM2=268, -- overlays for fire powerup FIRE_BAMBOO=262, -- bamboo powerup FIRE_F=265, -- suit, front FIRE_P=266, -- suit, profile FIRE_S=284, -- suit, swimming -- overlays for super panda powerup SUPER_F=281, -- suit, front SUPER_P=282, -- suit, profile SUPER_S=283, -- suit, swimming }, EN={ -- enemy sprites A=176, B=177, DEMON=178, DEMON_THROW=293, SLIME=180, BAT=181, HSLIME=182, -- hidden slime DASHER=183, VBAT=184, SDEMON=185, -- snow demon SDEMON_THROW=300, PDEMON=188, -- plasma demon PDEMON_THROW=317, FISH=189, FISH2=190, }, -- crumbling block CRUMBLE=193,CRUMBLE_2=304,CRUMBLE_3=305, FIREBALL=179, FIRE_1=263,FIRE_2=264, LIFT=192, PFIRE=263, -- player fire (bamboo) FIRE_PWUP=129, -- background mountains BGMNT={DIAG=496,FULL=497}, SCRIM=498, -- also 499,500 SPIKE=194, CHEST=195,CHEST_OPEN=311, -- timed platform (opens and closes) TPLAF=196,TPLAF_HALF=312,TPLAF_OFF=313, SUPER_PWUP=130, SIGN=197, SNOWBALL=186, FLAG=198, FLAG_T=326, -- flag after taken ICICLE=187, -- icicle while hanging ICICLE_F=303, -- icicle falling PLANE=132, -- plane (item) AVIATOR=336, -- aviator sprite (3x2) AVIATOR_PROP_1=339, -- propeller anim AVIATOR_PROP_2=340, -- propeller anim PLASMA=279, -- plasma ball SICICLE=199, -- stone-themed icicle, -- while hanging SICICLE_F=319, -- stone-themed icicle, -- while falling FUEL=200, -- fuel item IC_FUEL=332, -- icon for HUD TINY_NUM_00=480, -- "00" sprite TINY_NUM_50=481, -- "50" sprite TINY_NUM_R1=482, -- 1-10, right aligned -- food items FOOD={LEAF=128,A=133,B=134,C=135,D=136}, SURF1=332,SURF2=333, -- water surface fx -- world map tiles WLD={ -- tiles that player can walk on ROADS={13,14,15,30,31,46,47}, -- level tiles LVL1=61,LVL2=62,LVL3=63, LVLF=79, -- finale level -- "cleared level" tile LVLC=463, }, -- Special EIDs that don't correspond to -- sprites. ID must be > 512 POP=600, -- entity that dies immediately -- with a particle effect } -- Sprite numbers also function as entity -- IDs. For readability we write S.FOO -- when it's a sprite but EID.FOO when -- it identifies an entity type. EID=S -- anims for each entity ID ANIM={ [EID.EN.A]={S.EN.A,290}, [EID.EN.B]={S.EN.B,291}, [EID.EN.DEMON]={S.EN.DEMON,292}, [EID.EN.SLIME]={S.EN.SLIME,295}, [EID.EN.BAT]={S.EN.BAT,296}, [EID.FIREBALL]={S.FIREBALL,294}, [EID.FOOD.LEAF]={S.FOOD.LEAF,288,289}, [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.VBAT]={S.EN.VBAT,298}, [EID.SUPER_PWUP]={S.SUPER_PWUP,320,321}, [EID.EN.SDEMON]={S.EN.SDEMON,299}, [EID.SNOWBALL]={S.SNOWBALL,301}, [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.PLASMA]={S.PLASMA,280}, [EID.FUEL]={S.FUEL,330,331}, [EID.EN.FISH]={S.EN.FISH,368}, [EID.EN.FISH2]={S.EN.FISH2,369}, } PLANE={ START_FUEL=2000, MAX_FUEL=4000, FUEL_INC=1000, FUEL_BAR_W=50 } -- modes M={ BOOT=0, TITLE=1, -- title screen TUT=2, -- instructions RESTORE=3, -- prompting to restore game WLD=4, -- world map PREROLL=5, -- "LEVEL X-Y" banner PLAY=6, DYING=7, -- die anim EOL=8, -- end of level GAMEOVER=9, WIN=10, -- beat entire game } -- collider rects CR={ PLR={x=2,y=0,w=4,h=8}, AVIATOR={x=-6,y=2,w=18,h=10}, -- default DFLT={x=2,y=0,w=4,h=8}, FULL={x=0,y=0,w=8,h=8}, -- small projectiles BALL={x=2,y=2,w=3,h=3}, -- just top rows TOP={x=0,y=0,w=8,h=2}, -- player attack ATK={x=6,y=0,w=7,h=8}, -- what value to use for x instead if -- player is flipped (facing left) ATK_FLIP_X=-5, FOOD={x=1,y=1,w=6,h=6}, } -- max dist entity to update it ENT_MAX_DIST=220 -- EIDs to always update regardless of -- distance. ALWAYS_UPDATED_EIDS={ -- lifts need to always be updated for -- position determinism. [EID.LIFT]=true } -- player damage types DMG={ MELEE=0, -- melee attack FIRE=1, -- fire from fire powerup PLANE_FIRE=2, -- fire from plane } -- default palette 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, } -- music tracks BGM={A=0,B=1,EOL=2,C=3,WLD=4,TITLE=5, FINAL=6,WIN=7} -- bgm for each mode (except M.PLAY, which -- is special) BGMM={ [M.TITLE]=BGM.TITLE, [M.WLD]=BGM.WLD, [M.EOL]=BGM.EOL, [M.WIN]=BGM.WIN, } -- map data is organized in pages. -- Each page is 30x17. TIC80 has 64 map -- pages laid out as an 8x8 grid. We -- number them in reading order, so 0 -- is top left, 63 is bottom right. -- Level info. -- Levels in the cart are packed -- (RLE compressed). When a level is loaded, -- it gets unpacked to the top 8 map pages -- (0,0-239,16). -- palor: palette overrides -- pkstart: map page where packed -- level starts. -- pklen: length of level. Entire level -- must be on same page row, can't -- span multiple page rows. LVL={ { name="1-1",bg=1, palor={[8]=0x003300}, pkstart=8,pklen=5, mus=BGM.A, }, { name="1-2",bg=0, palor={[8]=0xcccc00}, pkstart=13,pklen=1, mus=BGM.B, }, { name="1-3",bg=2, pkstart=14,pklen=2, mus=BGM.C, save=true, }, { name="2-1",bg=1, palor={[8]=0x553838}, pkstart=16,pklen=3, mus=BGM.A, }, { name="2-2",bg=0, palor={[8]=0x553838}, pkstart=19,pklen=2, snow={clr=2}, mus=BGM.B, }, { name="2-3",bg=1, palor={[8]=0x553838}, pkstart=21,pklen=3, mus=BGM.C, save=true, }, { name="3-1",bg=2, palor={[8]=0x7171ae}, pkstart=24,pklen=3, snow={clr=10}, mus=BGM.A, }, { name="3-2",bg=0, palor={[8]=0x3c3c50}, pkstart=27,pklen=2, snow={clr=10}, mus=BGM.B, }, { name="3-3",bg=2, palor={[8]=0x7171ae}, pkstart=29,pklen=3, mus=BGM.C, save=true, }, { name="4-1",bg=2, palor={[2]=0x443c14,[8]=0x504410}, pkstart=32,pklen=3, mus=BGM.A, }, { name="4-2",bg=2, palor={[2]=0x443c14,[8]=0x504410}, pkstart=35,pklen=2, mus=BGM.B, }, { name="4-3",bg=2, palor={[2]=0x443c14,[8]=0x504410}, pkstart=37,pklen=3, mus=BGM.C, save=true, }, { name="5-1",bg=1, palor={[8]=0x553838}, pkstart=40,pklen=3, mus=BGM.A, }, { name="5-2",bg=1, palor={[8]=0x553838}, pkstart=43,pklen=2, mus=BGM.B, }, { name="5-3",bg=1, palor={[8]=0x553838}, pkstart=45,pklen=3, mus=BGM.C, save=true, }, { name="6-1",bg=0, palor={[8]=0x303030}, pkstart=48,pklen=3, mus=BGM.FINAL, snow={clr=8}, }, { name="6-2",bg=0, palor={[8]=0x303030}, pkstart=51,pklen=5, mus=BGM.FINAL, snow={clr=8}, }, } -- length of unpacked level, in cols -- 240 means the top 8 map pages LVL_LEN=240 -- sound specs 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}, } -- world map consts WLD={ -- foreground tile page FPAGE=61, -- background tile page BPAGE=62, } -- WLD point of interest types POI={ LVL=0, } -- settings Sett={ snd=true, mus=true } -- game state Game={ -- mode m=M.BOOT, -- ticks since mode start t=0, -- current level# we're playing lvlNo=0, lvl=nil, -- shortcut to LVL[lvlNo] -- scroll offset in current level scr=0, -- auto-generated background mountains bgmnt=nil, -- snow flakes (x,y pairs). These don't -- change, we just shift when rendering. snow=nil, -- highest level cleared by player, -- -1 if no level cleared topLvl=-1, } -- world map state Wld={ -- points of interest (levels, etc) pois={}, -- savegame start pos (maps start level -- to col,row) spos={}, plr={ -- start pos x0=-1,y0=-1, -- player pos, in pixels not row/col x=0,y=0, -- player move dir, if moving. Will move -- until plr arrives at next cell dx=0,dy=0, -- last move dir ldx=0,ldy=0, -- true iff player facing left flipped=false, } } -- player Plr={} -- deep-copied from PLR_INIT_STATE PLR_INIT_STATE={ lives=3, x=0,y=0, -- current pos dx=0,dy=0, -- last movement flipped=false, -- if true, is facing left jmp=0, -- 0=not jumping, otherwise -- it's the cur jump frame jmpSeq=JUMP_DY, -- set during jump grounded=false, swim=false, -- true if plr is near surface of water surf=false, -- attack state. 0=not attacking, -- >0 indexes into ATK_SEQ atk=0, -- die animation frame, 0=not dying -- indexes into DIE_SEQ dying=0, -- nudge (movement resulting from -- collisions) nudgeX=0,nudgeY=0, -- if >0, has fire bamboo powerup -- and this is the countdown to end firePwup=0, -- if >0 player has fired bamboo. -- This is ticks until player can fire -- again. fireCd=0, -- if >0, is invulnerable for this -- many ticks, 0 if not invulnerable invuln=0, -- if true, has the super panda powerup super=false, -- if != 0, player is being dragged -- horizontally (forced to move in that -- direction -- >0 is right, <0 is left) -- The abs value is how many frames -- this lasts for. drag=0, -- the sign message (index) the player -- is currently reading. signMsg=0, -- sign cycle counter: 1 when just -- starting to read sign, increases. -- when player stops reading sign, -- decreases back to 0. signC=0, -- respawn pos, 0,0 if unset respX=-1,respY=-1, -- if >0, the player is on the plane -- and this is the fuel left (ticks). plane=0, -- current score score=0, -- for performance, we keep the -- stringified score ready for display scoreDisp={text=nil,value=-1}, -- time (Game.t) when score last changed scoreMt=-999, -- if >0, player is blocked from moving -- for that many frames. locked=0, } -- max cycle counter for signs SIGN_C_MAX=10 -- sign texts. SIGN_MSGS={ [0]={ l1="Tiles 1-79 are solid(except water(16,", l2="32,48) and half solid platforms (4)).", }, [1]={ l1="Decorations work if on tiles 80-239.", l2="Entities work if on 128-239 (w/code).", }, [2]={ l1="Read the comment at the top of the", l2="code. Also, search for TPAT table.", }, [3]={ l1="Tiles 240-255 are special markers:", l2="(red numbers and yellow letters).", }, [4]={ l1="Red numbers in the top row multiply", l2="columns. One column becomes 12:", }, [5]={ l1="Yellow 'A's at the beginning of", l2="levels set player starting points.", }, [7]={ l1="In LVL table, also edit bg colors,", l2="start/length of levels, and more.", }, [6]={ l1="PAL table sets palette for game", l2="(hex colors).LVL table, for each lvl.", }, [8]={ l1="For lifts, use the yellow A and B to", l2="mark top and bottom of movement.", }, [9]={ l1="Tiles 80 and above are not solid.", l2="", }, [10]={ l1="Above chests, put an entity. Above", l2="that, a red number (6 maximum).", }, [11]={ l1="Red numbers above signs correspond", l2="to the SIGN_MSGS table in the code.", }, [12]={ l1="Search tile numbers to find their", l2="names.Srch names for related code.", }, } -- entities Ents={} -- particles Parts={} -- score toasts Toasts={} -- animated tiles, for quick lookup -- indexed by COLUMN. -- Tanims[c] is a list of integers -- indicating rows of animated tiles. 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() CheckDbgMenu() if Plr.dbg then DbgTic() return end Game.t=Game.t+1 TICF[Game.m]() end function CheckDbgMenu() if not btn(6) then Game.dbgkc=0 return end if btnp(0) then Game.dbgkc=10+(Game.dbgkc or 0) end if btnp(1) then Game.dbgkc=1+(Game.dbgkc or 0) end if Game.dbgkc==42 then Plr.dbg=true end end function Boot() ResetPal() WldInit() SetMode(M.TITLE) end -- restores default palette with -- the given overrides. 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() name=print(NAME) cls(1) 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,5) print(NAME,(240-name)//2,10,14) rect(0,24,240,1,11) rect(0,26,240,1,5) rect(0,SCRH-8,SCRW,8,0) print("",60,SCRH-7,7) if (time()//512)%2>0 then print("- PRESS 'Z' TO START -",65,84,11) 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() if Plr.dbgFly then UpdateDbgFly() else UpdatePlr() UpdateEnts() UpdateParts() DetectColl() ApplyNudge() CheckEndLvl() end 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() 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 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 UpdateDbgFly() local d=Iif(btn(4),5,1) if btn(0) then Plr.y=Plr.y-d end if btn(1) then Plr.y=Plr.y+d end if btn(2) then Plr.x=Plr.x-d end if btn(3) then Plr.x=Plr.x+d end if btn(5) then Plr.dbgFly=false end 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() -- We fill downward from surface tiles, -- Downward AND upward from water tiles. local surfs={} -- surface tiles local waters={} -- water tiles 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.WATER then Ins(waters,{c=c,r=r}) end end end for i=1,#surfs do local s=surfs[i] -- fill water below this tile FillWaterAt(s.c,s.r,1) end for i=1,#waters do local s=waters[i] -- fill water above AND below this tile 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.cdd0, waits until player is less -- than this dist away to start motion moveWaitPlr=0, -- if >=0, how many ticks to move -- for (after that, stop). moveT=-1, }, update=BehMove, }, FALL={ data={grounded=false,jmp=0}, update=BehFall, }, FLIP={ data={flipNum=0,flipDen=20}, update=BehFlip, }, FACEPLR={update=BehFacePlr}, JUMP={ data={jmp=0,jmpNum=0,jmpDen=50}, update=BehJump, }, VULN={ -- can be damaged by player data={hp=1, -- damage types that can hurt this. dtypes={DMG.MELEE,DMG.FIRE,DMG.PLANE_FIRE}, -- loot drop probability (0-100) lootp=0, -- possible loot to drop (EIDs) loot={EID.FOOD.A}, }, dmg=BehTakeDmg, }, SHOOT={ data={shootNum=0,shootDen=100, aim=AIM.NONE}, init=BehShootInit, update=BehShoot, }, UPDOWN={ -- yamp is amplitude of y movement data={yamp=16,dy=-1,yden=3,ynum=0}, init=BehUpDownInit, update=BehUpDown, }, POINTS={ data={value=50}, coll=BehPoints, }, HURT={ -- can hurt player coll=BehHurt }, LIFT={ data={liftNum=0,liftDen=3}, init=BehLiftInit, update=BehLift, coll=BehLiftColl, }, CRUMBLE={ -- cd: countdown to crumble data={cd=50,coll=CR.FULL,crumbling=false}, update=BehCrumble, }, TTL={ -- time to live (auto destroy) data={ttl=150}, update=BehTtl, }, DMG_ENEMY={ -- damage enemies update=BehDmgEnemy, }, GRANT_FIRE={ coll=BehGrantFirePwupColl, }, REPLACE={ -- replaces by another ent when plr near -- replDist: distance from player -- replEid: EID to replace by data={replDist=50,replEid=EID.LEAF}, update=BehReplace, }, CHEST={ init=BehChestInit, dmg=BehChestDmg, }, TPLAF={ init=BehTplafInit, update=BehTplaf, }, DASH={ data={ ddur=20, -- dash duration cdur=60, -- full cycle duration cdd=0, -- cycle counter }, init=BehDashInit, update=BehDash, }, GRANT_SUPER={ coll=BehGrantSuperPwupColl, }, SIGN={ init=BehSignInit, coll=BehSignColl }, ONEUP={coll=BehOneUp}, FLAG={coll=BehFlag}, REPL_ON_GND={ -- replace EID when grounded -- replData -- extra data to add to data={replEid=EID.LEAF}, update=BehReplOnGnd }, POP={update=BehPop}, PLANE={coll=BehBoardPlane}, FUEL={coll=BehFuel}, } --------------------------------------- -- ENTITY BEHAVIOR TABLE --------------------------------------- EBT={ [EID.EN.SLIME]={ data={ hp=1,moveDen=3,clr=11,noFall=true, lootp=20,loot={EID.FOOD.A}, }, beh={BE.MOVE,BE.FALL,BE.VULN,BE.HURT}, }, [EID.EN.HSLIME]={ data={replDist=50,replEid=EID.EN.SLIME}, beh={BE.REPLACE}, }, [EID.EN.A]={ data={ hp=1,moveDen=5,clr=14,flipDen=120, lootp=30, loot={EID.FOOD.A,EID.FOOD.B}, }, beh={BE.MOVE,BE.JUMP,BE.FALL,BE.VULN, BE.HURT,BE.FLIP}, }, [EID.EN.B]={ data={ hp=1,moveDen=5,clr=13, lootp=30, loot={EID.FOOD.A,EID.FOOD.B, EID.FOOD.C}, }, beh={BE.JUMP,BE.FALL,BE.VULN,BE.HURT, BE.FACEPLR}, }, [EID.EN.DEMON]={ data={hp=1,moveDen=5,clr=7, aim=AIM.HORIZ, shootEid=EID.FIREBALL, shootSpr=S.EN.DEMON_THROW, lootp=60, loot={EID.FOOD.C,EID.FOOD.D}}, beh={BE.JUMP,BE.FALL,BE.SHOOT, BE.HURT,BE.FACEPLR,BE.VULN}, }, [EID.EN.SDEMON]={ data={hp=1,moveDen=5,clr=7, flipDen=50, shootEid=EID.SNOWBALL, shootSpr=S.EN.SDEMON_THROW, aim=AIM.HORIZ, lootp=75, loot={EID.FOOD.C,EID.FOOD.D}}, beh={BE.JUMP,BE.FALL,BE.SHOOT, BE.MOVE,BE.FLIP,BE.VULN,BE.HURT}, }, [EID.EN.PDEMON]={ data={hp=1,clr=11,flipDen=50, shootEid=EID.PLASMA, shootSpr=S.EN.PDEMON_THROW, aim=AIM.FULL, lootp=80, loot={EID.FOOD.D}}, beh={BE.JUMP,BE.FALL,BE.SHOOT, BE.FLIP,BE.VULN,BE.HURT}, }, [EID.EN.BAT]={ data={hp=1,moveDen=2,clr=9,flipDen=60, lootp=40, loot={EID.FOOD.A,EID.FOOD.B}}, beh={BE.MOVE,BE.FLIP,BE.VULN,BE.HURT}, }, [EID.EN.FISH]={ data={ hp=1,moveDen=3,clr=9,flipDen=120, lootp=40, loot={EID.FOOD.A,EID.FOOD.B}, }, beh={BE.MOVE,BE.FLIP,BE.VULN, BE.HURT}, }, [EID.EN.FISH2]={ data={hp=1,clr=12,moveDen=1, lootp=60, loot={EID.FOOD.B,EID.FOOD.C}}, beh={BE.MOVE,BE.DASH,BE.VULN,BE.HURT}, }, [EID.FIREBALL]={ data={hp=1,moveDen=2,clr=7, coll=CR.BALL, moveHitMode=MOVE_HIT.DIE}, beh={BE.MOVE,BE.HURT,BE.TTL}, }, [EID.PLASMA]={ data={hp=1,moveDen=2,clr=7, moveDx=2, coll=CR.BALL, moveHitMode=MOVE_HIT.NONE}, beh={BE.MOVE,BE.HURT,BE.TTL}, }, [EID.SNOWBALL]={ data={hp=1,moveDen=1,clr=15, coll=CR.BALL, moveHitMode=MOVE_HIT.DIE}, beh={BE.MOVE,BE.FALL,BE.VULN,BE.HURT}, }, [EID.LIFT]={ data={sol=SOL.FULL}, beh={BE.LIFT}, }, [EID.CRUMBLE]={ data={ sol=SOL.FULL,clr=14, -- only take melee and plane fire dmg dtypes={DMG.MELEE,DMG.PLANE_FIRE}, }, beh={BE.CRUMBLE,BE.VULN}, }, [EID.PFIRE]={ data={ moveDx=1,moveDen=1,ttl=80, moveHitMode=MOVE_HIT.DIE, coll=FIRE.COLL, }, beh={BE.MOVE,BE.TTL,BE.DMG_ENEMY}, }, [EID.FIRE_PWUP]={ beh={BE.GRANT_FIRE}, }, [EID.SPIKE]={ data={coll=CR.FULL}, beh={BE.HURT}, }, [EID.CHEST]={ data={coll=CR.FULL, sol=SOL.FULL}, beh={BE.CHEST}, }, [EID.TPLAF]={ data={sol=SOL.HALF, coll=CR.TOP}, beh={BE.TPLAF}, }, [EID.EN.DASHER]={ data={hp=1,clr=12,moveDen=1,noFall=true, dashAnim={S.EN.DASHER,315}, lootp=60, loot={EID.FOOD.B,EID.FOOD.C}}, beh={BE.MOVE,BE.DASH,BE.VULN,BE.HURT}, }, [EID.EN.VBAT]={ data={hp=1,clr=14,yden=2, lootp=40, loot={EID.FOOD.B,EID.FOOD.C}}, beh={BE.UPDOWN,BE.VULN,BE.HURT}, }, [EID.SUPER_PWUP]={beh={BE.GRANT_SUPER}}, [EID.SIGN]={beh={BE.SIGN}}, [EID.FLAG]={beh={BE.FLAG}}, [EID.ICICLE]={ data={replEid=EID.ICICLE_F,replDist=8}, beh={BE.REPLACE}, }, [EID.ICICLE_F]={ data={replEid=EID.POP,replData={clr=15}}, beh={BE.FALL,BE.HURT,BE.REPL_ON_GND} }, [EID.SICICLE]={ data={replEid=EID.SICICLE_F,replDist=8}, beh={BE.REPLACE}, }, [EID.SICICLE_F]={ data={replEid=EID.POP,replData={clr=14}}, beh={BE.FALL,BE.HURT,BE.REPL_ON_GND} }, [EID.POP]={beh={BE.POP}}, [EID.PLANE]={beh={BE.PLANE}}, [EID.FUEL]={beh={BE.FUEL}}, [EID.FOOD.LEAF]={ data={value=50,coll=CR.FOOD}, beh={BE.POINTS}}, [EID.FOOD.A]={ data={value=100,coll=CR.FOOD}, beh={BE.POINTS}}, [EID.FOOD.B]={ data={value=200,coll=CR.FOOD}, beh={BE.POINTS}}, [EID.FOOD.C]={ data={value=500,coll=CR.FOOD}, beh={BE.POINTS}}, [EID.FOOD.D]={ data={value=1000,coll=CR.FOOD}, beh={BE.POINTS}}, } --------------------------------------- -- DEBUG MENU --------------------------------------- function DbgTic() if Plr.dbgResp then cls(1) print(Plr.dbgResp) if btnp(4) then Plr.dbgResp=nil end return end Game.dbglvl=Game.dbglvl or 1 if btnp(3) then Game.dbglvl=Iif(Game.dbglvl+1>#LVL,1,Game.dbglvl+1) elseif btnp(2) then Game.dbglvl=Iif(Game.dbglvl>1,Game.dbglvl-1,#LVL) end local menu={ {t="(Close)",f=DbgClose}, {t="Warp to test lvl",f=DbgWarpTest}, {t="Warp to L"..Game.dbglvl,f=DbgWarp}, {t="End lvl",f=DbgEndLvl}, {t="Grant super pwup",f=DbgSuper}, {t="Fly mode ".. Iif(Plr.dbgFly,"OFF","ON"),f=DbgFly}, {t="Invuln mode ".. Iif(Plr.invuln and Plr.invuln>0, "OFF","ON"), f=DbgInvuln}, {t="Unpack L"..Game.dbglvl,f=DbgUnpack}, {t="Pack L"..Game.dbglvl,f=DbgPack}, {t="Clear PMEM",f=DbgPmem}, {t="Win the game",f=DbgWin}, {t="Lose the game",f=DbgLose}, } cls(5) print("DEBUG") rect(110,0,140,16,11) print("DBG LVL:",120,4,3) print(LVL[Game.dbglvl].name,170,4) Plr.dbgSel=Plr.dbgSel or 1 for i=1,#menu do print(menu[i].t,10,10+i*10, Plr.dbgSel==i and 15 or 0) end if btnp(0) then Plr.dbgSel=Iif(Plr.dbgSel>1, Plr.dbgSel-1,#menu) elseif btnp(1) then Plr.dbgSel=Iif(Plr.dbgSel<#menu, Plr.dbgSel+1,1) elseif btnp(4) then (menu[Plr.dbgSel].f)() end end function DbgClose() Plr.dbg=false end function DbgSuper() Plr.super=true end function DbgEndLvl() EndLvl() Plr.dbg=false end function DbgPmem() pmem(0,0) end function DbgWarp() StartLvl(Game.dbglvl) end function DbgWarpNext() StartLvl(Game.lvlNo+1) end function DbgWarpTest() StartLvl(#LVL) end function DbgUnpack() UnpackLvl(Game.dbglvl,UMODE.EDIT) sync() Plr.dbgResp="Unpacked & synced L"..Game.dbglvl end function DbgPack() local succ=PackLvl(Game.dbglvl) --MapClear(0,0,LVL_LEN,ROWS) sync() Plr.dbgResp=Iif(succ, "Packed & synced L"..Game.dbglvl, "** ERROR packing L"..Game.dbglvl) end function DbgFly() Plr.dbgFly=not Plr.dbgFly Plr.dbgResp="Fly mode "..Iif(Plr.dbgFly, "ON","OFF") end function DbgInvuln() Plr.invuln=Iif(Plr.invuln>0,0,9999999) Plr.dbgResp="Invuln mode "..Iif( Plr.invuln>0,"ON","OFF") end function DbgWin() SetMode(M.WIN) Plr.dbg=false end function DbgLose() SetMode(M.GAMEOVER) Plr.dbg=false end --------------------------------------- -- UTILITIES --------------------------------------- function Iif(cond,t,f) if cond then return t else return f end end function Iif2(cond,t,cond2,t2,f2) if cond then return t end return Iif(cond2,t2,f2) end function Iif3(cond,t,cond2,t2,cond3,t3,f3) if cond then return t end return Iif2(cond2,t2,cond3,t3,f3) end function Iif4(cond,t,cond2,t2,cond3,t3, cond4,t4,f4) if cond then return t end return Iif3(cond2,t2,cond3,t3,cond4,t4,f4) end function ArrayContains(a,val) for i=1,#a do if a[i]==val then return true end end return false end function Lpad(value, width) local s=value.."" while string.len(s) < width do s="0"..s end return s end function RectXLate(r,dx,dy) return {x=r.x+dx,y=r.y+dy,w=r.w,h=r.h} end -- rects have x,y,w,h function RectIsct(r1,r2) return r1.x+r1.w>r2.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