Categories
Uncategorized

Hello world!

Welcome to CUNY Academic Commons. This is your first post. Edit or delete it, then start blogging!

Welcome to {wvy}The Leaf Collector{/wvy}{pg}Try talking to {wvy}Walt{/wvy} {drws “Walt intro”} Welcome to {wvy}The Leaf Collector{/wvy}{pg}Try talking to {wvy}Walt{/wvy} {drws “Walt intro”} # BITSY VERSION 8.4 ! VER_MAJ 8 ! VER_MIN 4 ! ROOM_FORMAT 1 ! DLG_COMPAT 0 ! TXT_MODE 0 PAL 0 30,142,9 255,218,104 255,249,192 NAME blueprint PAL 1 255,205,163 98,172,22 253,255,172 NAME spring PAL 2 7,2,156 235,211,53 230,235,255 NAME night PAL 3 213,176,95 0,142,255 189,229,229 NAME river PAL 4 165,235,255 255,255,255 236,59,67 NAME winter PAL 5 0,0,0 255,255,255 255,251,142 NAME dark mode PAL 6 146,145,255 7,14,115 255,189,0 NAME night beach PAL 7 170,216,255 255,21,25 255,245,36 NAME sunset PAL 8 1,16,85 54,79,8 208,255,124 NAME dark tree ROOM 0 2c,2c,2c,2c,2b,2b,0,0,0,0,0,2b,2b,2c,2c,2c 2c,2c,2c,2b,0,0,0,0,0,0,0,0,0,2b,2c,2c 2c,2c,2b,0,0,0,29,29,29,29,29,0,0,0,2b,2c 2c,2b,0,0,0,29,29,29,29,29,29,29,0,0,0,2b 2b,0,0,0,29,29,29,29,29,29,29,29,29,0,0,0 0,0,0,0,0,28,28,28,28,28,28,28,0,0,2o,0 0,2o,0,2c,2g,28,2d,28,2e,28,2d,28,2g,0,0,0 0,0,0,2b,2g,28,28,28,2f,28,28,28,2g,0,0,0 0,0,0,0,2g,2g,2g,2g,0,2g,2g,2g,2g,0,0,2c 0,0,0,0,0,0,0,0,0,0,0,0,0,0,2c,2b 2c,0,0,0,2o,0,0,0,0,0,0,0,0,0,2b,0 2c,2c,0,0,0,0,0,0,0,0,0,0,0,0,0,2c 2c,2c,2c,2c,0,0,0,0,0,0,0,2o,0,0,2c,2c 2c,2c,2c,2c,2c,0,0,0,0,0,0,0,0,2c,2c,2c 2c,2c,2c,2c,2c,2c,0,0,0,0,0,0,2c,2c,2c,2c 2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c NAME opening screen (outside the cabin) ITM 0 7,8 EXT 8,7 6 8,12 FX tunnel DLG 15 EXT 15,5 1 0,5 FX slide_r EXT 15,6 1 0,6 FX slide_r EXT 15,7 1 0,7 FX slide_r EXT 0,5 3 15,5 FX slide_l EXT 0,6 3 15,6 FX slide_l EXT 0,7 3 15,7 FX slide_l EXT 0,9 3 15,9 FX slide_l EXT 0,8 3 15,8 FX slide_l EXT 6,0 2 6,15 FX slide_u EXT 7,0 2 7,15 FX slide_u EXT 8,0 2 8,15 FX slide_u EXT 9,0 2 9,15 FX slide_u EXT 10,0 2 10,15 FX slide_u EXT 15,4 1 0,4 FX slide_l PAL 0 ROOM 1 2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c 2c,2c,2c,2c,2b,2b,2b,2b,2b,2b,2b,2b,2c,2c,2c,2c 2c,2b,2b,2b,0,0,0,0,0,0,0,0,2b,2c,2c,2c 2b,0,0,0,0,0,0,0,0,0,0,2g,0,2b,2c,2c 0,0,0,0,0,2g,0,0,0,0,0,0,0,0,2b,2c 0,0,2g,0,0,0,0,0,0,0,0,0,0,0,0,2c 0,0,0,0,0,0,0,0,0,2g,0,0,0,2g,0,2c 0,0,0,0,0,0,2g,0,0,0,0,0,0,0,0,2c 2c,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2c 2c,0,0,2g,0,0,0,0,0,0,0,0,2g,0,2c,2c 2c,2c,0,0,0,0,0,2g,0,0,0,0,0,0,2c,2c 2c,2b,2c,0,2c,0,0,0,0,0,0,0,0,2c,2c,2c 2c,0,2b,0,2b,0,0,0,0,0,0,0,2c,2b,2c,2c 2c,2c,2c,0,0,0,2c,2c,0,2c,0,0,2b,2c,2c,2c 2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c 2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c NAME the meadow ITM 0 10,10 EXT 0,5 0 15,5 FX slide_l EXT 0,6 0 15,6 FX slide_l EXT 0,7 0 15,7 FX slide_l PAL 1 ROOM 2 0,0,0,0,0,2u,2q,2n,2r,2r,0,0,0,0,0,0 0,0,2u,0,0,2n,2p,2m,0,2r,2r,2q,2r,0,2u,0 0,0,0,2q,2r,2s,2m,0,0,2r,0,2s,0,2r,0,0 0,0,2q,2m,0,2r,2n,2r,2i,0,2r,0,2r,0,2r,0 0,2n,2p,0,0,2q,2m,0,0,2q,2r,2r,0,0,2r,2r 2q,2p,2m,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,0,0,2r 2p,2c,2c,2c,2c,2c,2b,2b,2c,2c,2c,2c,2c,2c,2c,2c 2c,2c,2c,2b,2b,2b,0,0,2b,2b,2b,2c,2c,2c,2c,2c 2c,2c,2c,0,0,0,0,0,0,0,0,2b,2c,2c,2c,2c 2c,2c,2b,0,0,0,0,0,0,2t,2u,2t,2b,2c,2c,2c 2c,2b,0,2u,2t,0,0,0,0,0,2t,0,0,2b,2c,2c 2c,0,0,2t,0,0,0,0,0,0,0,0,0,0,2c,2c 2c,2c,0,0,0,0,0,0,0,0,0,0,0,0,2c,2c 2c,2c,2c,0,0,0,0,0,0,2t,2u,0,2c,2c,2c,2c 2c,2c,2c,2c,2c,0,0,0,0,0,2t,2c,2c,2c,2c,2c 2c,2c,2c,2c,2c,2c,0,0,0,0,0,2c,2c,2c,2c,2c NAME snowy mountain scene ITM 0 8,8 EXT 6,15 0 6,0 FX slide_d EXT 7,15 0 7,0 FX slide_d EXT 8,15 0 8,0 FX slide_d EXT 9,15 0 9,0 FX slide_d EXT 10,15 0 10,0 FX slide_d PAL 4 ROOM 3 0,0,0,0,2n,2k,2k,2k,2k,2k,2k,2c,2c,2c,2c,2c 0,0,0,2n,2k,2k,2k,2k,2k,2k,2m,2c,2c,2c,2c,2c 0,2n,2k,2k,2k,2k,2k,2k,2k,2m,0,2b,2b,2c,2c,2c 0,2k,2k,2k,2k,2k,2k,2k,2m,0,0,0,0,2b,2c,2c 2n,2k,2k,2k,2k,2k,2k,2m,0,0,0,0,0,0,2b,2b 2k,2k,2k,2k,2k,2k,2m,0,0,2o,0,0,2o,0,0,0 2k,2k,2k,2k,2k,2k,2l,0,0,0,0,0,0,0,0,0 2k,2k,2k,2k,2k,2k,2l,0,0,0,0,0,0,0,0,0 2k,2k,2k,2k,2k,2m,0,0,0,2o,0,0,0,0,0,0 2k,2k,2k,2k,2m,0,0,0,0,0,0,0,0,0,0,0 2k,2k,2k,2k,2l,0,0,2o,0,0,0,0,2o,0,0,2c 2k,2k,2k,2k,2l,0,0,0,0,0,0,0,0,0,2c,2c 2k,2k,2k,2k,2l,0,0,0,0,0,2o,0,0,2c,2c,2c 2k,2k,2k,2k,2l,0,0,0,0,0,0,0,2c,2c,2c,2c 2k,2k,2k,2k,2k,2l,2o,2c,2c,2c,2c,2c,2c,2c,2c,2c 2k,2k,2k,2k,2k,2k,2l,2b,2b,2c,2c,2c,2c,2c,2c,2c NAME river bed ITM 0 10,2 EXT 15,5 0 0,5 FX slide_r EXT 15,6 0 0,6 FX slide_r EXT 15,7 0 0,7 FX slide_r EXT 15,8 0 0,8 FX slide_r EXT 15,9 0 0,9 FX slide_r PAL 3 ROOM 5 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,1,2,0,0,3,4,5,0,6,7,0,0,0 0,0,0,0,8,9,j,k,l,m,n,o,p,0,0,0 0,0,0,q,r,s,t,u,v,w,x,y,0,0,0,0 0,0,0,0,0,0,0,0,z,10,0,0,0,0,0,0 0,0,0,0,0,0,0,11,12,0,0,0,0,0,0,0 0,0,0,0,0,0,13,14,15,0,0,0,0,0,0,0 0,0,0,0,0,0,0,16,17,0,0,0,0,0,0,0 0,0,0,0,18,19,1a,1b,1c,1d,1e,1f,0,0,0,0 0,0,0,0,1g,1h,1i,1j,1k,1l,1m,1n,0,0,0,0 0,0,0,0,1o,1p,1q,1r,1s,1t,1u,1v,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,0,1w,1x,1y,1z,0,0,0,0,0,0,0,0 0,0,0,0,20,21,22,23,24,25,26,27,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 NAME cover page ITM 0 12,9 PAL 0 ROOM 6 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g 2g,28,28,28,2d,28,28,28,28,28,28,2d,28,28,28,2g 2g,28,3g,3c,2h,2h,2h,3b,2h,2h,2h,2h,2h,2h,28,2g 2g,28,2h,3d,2h,2h,2h,2h,2h,2h,2h,3i,3j,2h,28,2g 2g,28,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,28,2g 2g,28,2h,2h,3k,3l,3m,2h,2h,2h,2h,2h,2h,2h,28,2g 2g,28,2h,2h,3n,3o,3p,2h,2h,3e,3g,3f,2h,2h,28,2g 2g,28,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,28,2g 2g,28,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,28,2g 2g,28,28,28,2d,28,28,2j,3h,28,28,2d,28,28,28,2g 2g,2g,2g,2g,2g,2g,2g,0,0,2g,2g,2g,2g,2g,2g,2g 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 NAME inside cabin ITM 0 8,5 EXT 8,12 7 8,7 EXT 7,12 7 8,7 FX tunnel PAL 0 ROOM 7 2c,2c,2c,2c,2b,2b,0,0,0,0,0,2b,2b,2c,2c,2c 2c,2c,2c,2b,0,0,0,0,0,0,0,0,0,2b,2c,2c 2c,2c,2b,0,0,0,29,29,29,29,29,0,0,0,2b,2c 2c,2b,0,0,0,29,29,29,29,29,29,29,0,0,0,2b 2b,0,0,0,29,29,29,29,29,29,29,29,29,0,0,0 0,0,0,0,0,28,28,28,28,28,28,28,0,0,2o,0 0,2o,0,2c,2g,28,2d,28,2e,28,2d,28,2g,0,0,0 0,0,0,2b,2g,28,28,28,2f,28,28,28,2g,0,0,0 0,0,0,0,2g,2g,2g,2g,0,2g,2g,2g,2g,0,0,2c 0,0,0,0,0,0,0,0,0,0,0,0,0,0,2c,2b 2c,0,0,0,2o,0,0,0,0,0,0,0,0,0,2b,0 2c,2c,0,0,0,0,0,0,0,0,0,0,0,0,0,2c 2c,2c,2c,2c,0,0,0,0,0,0,0,2o,0,0,2c,2c 2c,2c,2c,2c,2c,0,0,0,0,0,0,0,0,2c,2c,2c 2c,2c,2c,2c,2c,2c,0,0,0,0,0,0,2c,2c,2c,2c 2c,2c,2c,2c,2c,2c,2c,0,0,0,0,2c,2c,2c,2c,2c NAME outside the cabin: night EXT 15,5 8 0,10 FX slide_r EXT 15,7 8 0,12 FX slide_r EXT 15,6 8 0,11 FX slide_r EXT 7,15 9 7,0 FX slide_d EXT 8,15 9 8,0 FX slide_d EXT 9,15 9 9,0 FX slide_d EXT 10,15 9 10,0 FX slide_d EXT 6,0 a 6,15 FX slide_u EXT 8,0 a 8,15 FX slide_u EXT 9,0 a 9,15 FX slide_u EXT 10,0 a 10,15 FX slide_u EXT 7,0 a 7,15 FX slide_u EXT 15,4 8 0,10 FX slide_r EXT 0,8 c 15,9 FX slide_l EXT 0,9 c 15,12 FX slide_l EXT 0,7 c 15,13 FX slide_l EXT 0,6 c 15,11 FX slide_l EXT 0,5 c 15,10 FX slide_l EXT 8,7 d 8,12 EXT 8,7 d 7,12 FX tunnel PAL 2 ROOM 8 0,0,0,0,0,0,0,0,0,30,0,30,0,0,31,31 0,30,0,0,0,0,0,2u,0,0,0,0,3x,31,2u,31 0,0,0,0,3x,0,30,0,31,31,31,31,31,31,30,3x 0,0,30,3x,31,0,0,3y,3x,30,3x,31,2u,31,31,3x 0,0,0,31,30,31,31,30,31,3x,31,31,31,3x,0,0 0,30,0,2u,31,30,31,31,2u,31,31,2u,31,0,30,0 2u,30,31,30,31,3x,30,30,3x,31,30,0,3x,31,0,0 31,3x,3x,31,30,31,0,30,0,0,0,0,0,0,0,0 30,0,30,2u,0,0,30,0,0,2u,0,0,0,0,0,0 3x,3y,2i,2i,2i,2i,2i,2i,2i,2i,2i,2i,2i,2i,0,0 2x,2x,2x,2x,2x,2x,2x,2x,2x,2x,2x,2x,2i,2i,0,0 2p,2p,2p,2p,2p,2p,2p,2p,2p,2p,2p,2p,2y,2i,0,0 2p,2p,2p,2p,2p,2p,2p,2p,2w,2w,2w,2w,2v,2i,0,0 2w,2w,2w,2w,2w,2w,2w,2z,2p,2p,2p,2v,0,2i,0,0 2p,2p,2p,2p,2p,2p,2p,2p,2p,2p,2v,0,0,2i,0,0 2p,2p,2p,2p,2p,2p,2p,2p,2p,2v,0,0,0,2i,0,0 NAME observation cliff ITM 0 9,10 EXT 0,10 7 15,5 FX slide_l EXT 0,12 7 15,7 FX slide_l EXT 0,11 7 15,6 FX slide_l PAL 5 ROOM 9 2c,2c,2c,2c,2c,2b,2b,0,0,0,0,2b,2b,2b,2c,2c 2c,2b,2b,2b,2b,0,0,0,0,0,0,0,0,0,2b,2c 2b,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2b 33,0,33,0,0,0,0,0,0,0,0,0,0,0,33,0 33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33 0,33,0,0,0,0,0,0,0,0,0,33,0,0,0,0 35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,35 2p,35,0,0,0,0,0,0,0,0,0,0,0,0,35,2p 2p,2p,35,35,0,0,0,0,0,0,0,0,0,35,2p,2p 2k,2p,2p,2p,35,35,0,0,0,0,0,0,35,2p,2k,2p 2p,2p,2p,2k,2p,2p,35,35,35,35,35,35,2p,2p,2p,2k 2p,2p,2k,2p,2k,2p,2p,2p,2p,2p,2p,2p,2p,2p,2p,2p 2p,2p,2p,2p,2p,2p,2p,2p,2p,2p,2k,2p,2p,2p,2p,2p 2p,2p,2p,2p,2p,34,2p,2p,2p,2k,2p,2k,2p,2p,2p,2k 2p,2p,34,2p,2p,2p,2p,2p,2p,2p,2p,2p,2p,34,2p,2p 2p,2p,2p,2p,2k,2p,2p,2p,2p,2p,2p,2p,2p,2p,2p,2p NAME night beach ITM 0 12,2 EXT 7,0 7 7,15 FX slide_u EXT 8,0 7 8,15 FX slide_u EXT 9,0 7 9,15 FX slide_u EXT 10,0 7 10,15 FX slide_u PAL 6 ROOM a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,36,0,0,0,0,0,36,0,0,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,36,0,0 0,36,0,0,0,0,0,0,36,0,0,36,0,0,36,0 36,0,0,0,0,0,0,0,0,0,0,0,36,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 2c,2c,2c,2c,2c,2c,2c,2c,2c,0,0,0,0,0,0,0 2b,2b,2b,2b,2b,2b,2b,2b,2b,2c,2c,2c,2c,2c,2c,0 38,38,38,38,38,38,38,38,39,2b,2b,2b,2b,2b,2c,2c 2k,2k,2k,2k,2k,2k,2k,2k,2k,38,38,38,38,39,2b,2b 2c,2m,3a,3a,2k,2k,2k,2k,2k,2k,2k,2k,2k,2k,38,38 2c,2c,0,2o,3a,3a,3a,3a,3a,3a,3a,2k,2k,2k,2k,2k 2c,2c,2g,0,0,0,0,0,0,0,0,3a,3a,3a,2k,2c 2c,2c,0,0,0,0,0,0,0,0,0,2g,0,0,2c,2c 2c,2c,2c,2c,0,0,2o,0,0,0,0,0,0,2c,2b,2c 2c,2c,2c,2c,2c,2c,0,0,0,0,0,2c,2c,2c,2c,2c NAME sunset river ITM 0 2,11 EXT 6,15 7 6,0 FX slide_d EXT 8,15 7 8,0 FX slide_d EXT 9,15 7 9,0 FX slide_d EXT 10,15 7 10,0 FX slide_d EXT 7,15 7 7,0 FX slide_d PAL 7 ROOM b 0,0,0,0,0,0,0,0,0,30,0,30,0,0,31,31 0,30,0,0,0,0,0,2u,0,0,0,0,31,31,2u,31 0,0,0,0,31,0,30,0,31,3x,3x,31,31,3x,30,31 0,0,30,3x,3x,0,0,31,3x,30,0,31,2u,31,3x,3x 0,0,0,31,30,31,3x,30,31,3x,31,3x,31,3x,0,0 0,30,0,2u,31,30,0,31,2u,31,31,2u,31,0,30,0 2u,30,31,30,31,31,30,30,3x,31,30,0,31,3x,0,0 31,3x,31,0,30,3x,0,30,0,0,0,30,0,0,0,0 30,0,30,2u,0,0,30,0,0,2u,0,0,0,30,0,31 31,30,0,0,0,0,31,3x,0,0,0,0,0,0,31,3x 0,3x,0,0,0,0,0,0,0,0,3x,31,0,0,31,2u 0,0,0,0,30,3x,0,0,0,31,31,2u,0,30,0,0 0,3x,31,0,31,31,30,31,31,3x,31,0,31,0,0,31 0,31,30,31,2u,3x,30,0,30,0,31,3x,0,30,0,0 31,0,30,3x,30,31,30,30,31,2u,0,30,0,0,3x,2u 31,0,2u,0,3x,0,0,31,3x,31,0,0,0,31,0,3x NAME universe ITM 0 13,9 PAL 5 ROOM c 0,0,0,0,0,0,3v,3v,0,0,0,0,0,0,0,0 0,3w,0,0,0,3v,3v,3v,3v,3v,0,3w,0,0,3w,0 0,0,0,0,0,3v,3v,3v,3v,3v,0,0,0,0,0,0 0,0,3w,0,0,3v,3v,3v,3v,3v,3v,0,0,3w,0,0 0,0,0,0,3v,3v,3v,3v,3v,3v,3v,0,0,0,0,0 0,2c,0,0,3v,3v,3v,3v,3v,3v,3v,3v,0,2c,2c,0 2c,2c,2c,2c,3v,3v,3v,3v,3v,3v,3v,2c,2c,2c,2c,2c 2c,2c,2c,2b,0,3v,3v,3v,3v,3v,0,2b,2c,2b,2c,2c 2c,2c,2b,0,0,0,0,3s,3t,0,0,0,2b,0,2b,2b 2c,2b,0,0,0,0,0,3s,3u,0,0,2o,0,0,0,0 2c,0,0,2o,0,0,0,3r,3q,0,0,0,0,2o,0,0 2c,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 2c,0,0,0,0,2o,0,0,0,0,0,0,0,0,0,0 2c,2c,0,0,0,0,0,0,0,0,2o,0,0,0,0,0 2c,2c,2c,2c,0,0,0,0,0,0,0,0,0,2c,2c,2c 2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c,2c NAME live oak ITM 0 12,13 EXT 15,9 7 0,8 FX slide_r EXT 15,12 7 0,9 FX slide_r EXT 15,13 7 0,7 FX slide_r EXT 15,11 7 0,6 FX slide_r EXT 15,10 7 0,5 FX slide_r PAL 8 ROOM d 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g,2g 2g,28,28,28,2d,28,28,28,28,28,28,2d,28,28,28,2g 2g,28,3g,3c,2h,2h,2h,3b,2h,2h,2h,2h,2h,2h,28,2g 2g,28,2h,3d,2h,2h,2h,2h,2h,2h,2h,3i,3j,2h,28,2g 2g,28,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,28,2g 2g,28,2h,2h,3k,3l,3m,2h,2h,2h,2h,2h,2h,2h,28,2g 2g,28,2h,2h,3n,3o,3p,2h,2h,3e,3g,3f,2h,2h,28,2g 2g,28,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,28,2g 2g,28,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,2h,28,2g 2g,28,28,28,2d,28,28,2j,3h,28,28,2d,28,28,28,2g 2g,2g,2g,2g,2g,2g,2g,0,0,2g,2g,2g,2g,2g,2g,2g 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 NAME inside cabin night ITM 0 8,5 EXT 8,12 b 3,10 FX fade_b DLG 1h EXT 7,12 b 3,10 FX fade_b DLG 1h PAL 2 TIL 1 00000000 00000000 00000000 00000000 00000000 00000011 00000000 00000000 > 00000000 00000000 00000000 00000000 00000000 00000011 00000000 00000000 NAME 1 TIL 2 00000000 00000000 00000000 11110000 01100000 01100000 01100000 01100000 NAME 2 TIL 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000100 NAME 3 TIL 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 10000000 NAME 4 TIL 5 00000000 00000000 00000000 00000000 00000000 00100000 10100000 00000000 NAME 5 TIL 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 NAME 6 TIL 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00100000 NAME 7 TIL 8 01100000 01100000 01100000 01100000 01000000 01100000 01100000 01100000 NAME 8 TIL 9 00000000 00000000 00000011 00000010 00000110 00000111 00000110 01100100 NAME 9 TIL 10 00000000 00000000 00000000 10000000 10000000 00000000 00000000 00000000 NAME 27 TIL 11 00000000 00000000 00000000 00001000 00011100 00000110 11000110 10000110 NAME 28 TIL 12 00000000 00001000 00001000 00001000 00011011 00111100 00001000 00011000 NAME 29 TIL 13 00000000 00000000 00000000 00000000 00000110 00000011 00000010 00000100 NAME 30 TIL 14 10000110 10000110 10000100 10000100 10000000 10001000 11110000 11100000 NAME 31 TIL 15 00011000 00011000 00011000 00011000 00011000 00010000 00010000 00010000 NAME 32 TIL 16 00000000 00000000 00000000 00000000 00000000 00000100 00000011 00000000 NAME 33 TIL 17 00110000 00110000 00110000 00100000 00000000 01000000 10000000 00000000 NAME 34 TIL 18 00000000 00000000 00000011 00100110 00010100 00011100 00011100 00011110 NAME 35 TIL 19 01111111 10000111 00000011 00000001 00000011 00000000 00000000 00000000 NAME 36 TIL 20 00001001 00001000 00000000 00000100 00000000 00000000 00000000 00000000 NAME 63 TIL 21 00000000 00000000 10000000 10000000 10001100 00000000 00000000 00000000 NAME 64 TIL 22 10100000 10100000 10100000 10100000 10100000 00000000 00000000 00000000 NAME 65 TIL 23 10010000 00000000 00001000 01001000 01000000 00000000 00000000 00000000 NAME 66 TIL 24 00000000 00010000 00010000 00010000 00010000 00000000 00000000 00000000 NAME 67 TIL 25 10000101 10010100 00010000 00010000 10010000 00000000 00000000 00000000 NAME 68 TIL 26 00010000 00000010 00001000 00000000 00100010 00000000 00000000 00000000 NAME 69 TIL 27 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME 70 TIL 28 11111111 11111111 00000000 11111111 11111111 00000000 11111111 11111111 NAME cabin wall WAL true TIL 29 01010100 01011101 10101010 01101011 01110101 01011101 11001011 10101010 NAME roof meat WAL true TIL 30 00000000 00100000 00000000 00000010 00100000 01110000 00100010 00000000 NAME stars 1 WAL false TIL 31 00000100 00000000 00100000 00000100 00000000 00000000 01001000 00000000 NAME stars 2 WAL false TIL 32 00000000 00000000 00000000 00000000 00111100 01000010 10111101 01111110 > 00000000 01111110 11000011 10111101 01111110 11111111 11111111 11111111 NAME waves WAL true TIL 33 00000110 00001110 00000000 01100000 11110000 11110110 00000111 00000000 WAL true TIL 34 11100011 11000001 11000001 11000001 11010101 11010101 11010101 11111111 > 11111111 11100011 11000001 11000001 11000001 11010101 10110110 11110111 NAME jelly TIL 35 00000000 00000000 00000000 00111100 11111110 11111111 10100011 01011101 > 00000000 01001100 11111110 11111111 11111001 10010110 01101101 11111111 NAME ocean shoreline WAL true TIL 36 00000000 00000000 00000110 00111111 11111111 00111110 00000000 00000000 NAME cloud TIL 37 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 TIL 38 00000000 10011110 11111111 11111011 10101111 11111111 11111011 11011111 > 00000000 01111001 11111111 11011111 11110111 11111111 11101101 11111111 TIL 39 00000000 11111000 01101100 11111100 11011100 11111110 11111011 11111111 > 00000000 11111000 10111100 11111100 11101100 10111110 11111111 11110111 TIL j 00000000 11100000 00110000 00110000 00011000 01111000 00000000 00000000 NAME 10 TIL k 00000100 00011110 00001110 10000110 10000110 10000110 00001110 00110110 NAME 11 TIL l 00100000 01011101 00011000 00001100 00001100 00000100 00000110 00000110 NAME 12 TIL m 00001000 11111000 00100000 00100000 01000001 01000001 01000001 00000001 NAME 13 TIL n 00000000 00111000 00001100 10001100 11000110 11111100 10000000 00000010 NAME 14 TIL o 00000011 00001111 00010011 00010011 00110000 00110000 00111100 00001111 NAME 15 TIL p 00000000 00000000 00000000 10000000 00000000 00000000 00000000 00000000 NAME 16 TIL q 00000000 00000000 00000000 00000000 00000011 00000000 00000000 00000000 NAME 17 TIL r 01100000 01000000 11100000 11100001 11111111 00000000 00000000 00000000 NAME 18 TIL s 01000110 01000110 10100110 10100011 00000001 00000000 00000000 00000000 NAME 19 TIL t 00001000 00001000 00011000 10110000 11000000 00000000 00000000 00000000 NAME 20 TIL u 01000110 11000110 11000110 11111111 01100111 00000010 00000000 00000000 NAME 21 TIL v 00000110 00000010 10000011 10000011 00000001 00000011 00000000 00000000 NAME 22 TIL w 10000001 10000011 00000011 00000001 00000010 00000000 00000000 00000000 NAME 23 TIL x 00000000 10000000 10000110 11101100 11110000 00000000 00000000 00000000 NAME 24 TIL y 00000011 11000011 11100011 01100010 00111100 00000000 00000000 00000000 NAME 25 TIL z 00000000 00000000 00000000 00000000 00000001 00000010 00000100 00000100 NAME 26 TIL 1a 00000000 00000000 10000000 10000001 00000000 00000000 00000001 00000011 NAME 37 TIL 1b 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00110000 NAME 38 TIL 1c 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00111100 NAME 39 TIL 1d 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000111 NAME 40 TIL 1e 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000011 NAME 41 TIL 1f 00000000 00000000 00000000 00000000 00000000 00010000 10100000 11000000 NAME 42 TIL 1g 00011100 00011100 00011100 00011000 00011100 00001100 00001100 00001110 NAME 43 TIL 1h 00000000 00000000 00000011 00011111 00000111 00000011 00000011 00001011 NAME 44 TIL 1i 00000111 00000001 11100001 11000001 00000001 00000001 00000001 00000001 NAME 45 TIL 1j 01111000 00011001 00000001 00000001 00000000 00000000 00000000 00000001 NAME 46 TIL 1k 10001100 10001100 00000100 00000100 00011100 01000100 10000100 10000100 NAME 47 TIL 1l 00001001 00010001 00010001 00011000 00011110 00000111 00010001 00110001 NAME 48 TIL 1m 10000100 00001100 00001100 00001100 00001111 10000011 10000000 10011000 NAME 49 TIL 1n 11000000 10000000 00000000 00000000 10000000 11110000 11000000 01000000 NAME 50 TIL 1o 00000111 00000011 00000001 00000000 00000000 00000000 00000000 00000000 NAME 51 TIL 1p 00000011 10000011 11111111 00000000 00000000 00000000 00000000 00000000 NAME 52 TIL 1q 00000001 10000011 00000111 00001100 00000100 00000000 00000000 00000000 NAME 53 TIL 1r 00000001 10000001 11000000 00000000 00000000 00000000 00000000 00000000 NAME 54 TIL 1s 10001100 11101111 11000110 00000000 00000000 00000000 00000000 00000000 NAME 55 TIL 1t 00110001 00110001 01111100 00000000 00000000 00000000 00000000 00000000 NAME 56 TIL 1u 00101000 00011000 00001111 00000000 00000000 00000000 00000000 00000000 NAME 57 TIL 1v 11000000 10000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME 58 TIL 1w 00000000 00000000 00000000 00000000 00000000 00000000 00011000 00001001 NAME 59 TIL 1x 00000000 00000000 00000000 00000000 00000000 00000000 00100000 00100000 NAME 60 TIL 1y 00000000 00000000 00000000 00000000 00000000 10000000 10000001 10000000 NAME 61 TIL 1z 00000000 00000000 00000000 00000000 00000000 00000000 10000010 10010000 NAME 62 TIL 2b 00111000 00111000 00111010 00111100 00111000 00111000 00111000 00111000 NAME tree trunk WAL true TIL 2c 00111000 01111100 11111010 11111100 11111010 11111101 11111010 11111101 NAME tree top WAL true TIL 2d 11111111 10000001 10001001 10100101 10010001 10001001 10000001 11111111 > 11111111 10000001 10010001 10001001 10100101 10010001 10000001 11111111 NAME window WAL true TIL 2e 00000000 01111110 01000010 01000010 01000010 01000010 01111110 00000000 NAME door top TIL 2f 00000010 00000000 01111110 01000010 01000010 01000010 01111110 00000000 NAME door bottom TIL 2g 00000000 00010000 00101000 00010000 00000010 00100101 01010010 00100000 NAME flowers TIL 2h 00000000 10001111 00000000 00000000 00000000 11110001 00000000 00000000 NAME cabin floor TIL 2i 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME blank wall tile WAL true TIL 2j 00000000 00000000 00000000 00000000 11111111 11111111 00000000 00000000 NAME door top down TIL 2k 11111111 11111111 11000011 10111101 11111110 11111111 11111111 11111111 > 11111111 11111111 11111111 11111111 11000011 00111101 11111111 11111111 NAME river water WAL true TIL 2l 11110000 11011000 11110100 11110000 11110000 10100100 11110000 11111000 > 11110000 11111000 11100100 01110000 11110000 11110100 11010000 11111000 NAME shoreline R WAL true TIL 2m 01110111 11111110 11111000 11110100 10100000 11110000 11100000 11000000 > 11111011 10111110 11111000 11110100 11100000 10110000 11100000 11000000 NAME shoreline diag R TIL 2n 00000011 00000111 00001111 00001110 00010111 01111111 01011111 11111111 > 00000011 00000101 00001111 00001111 00011111 01111101 01101111 11111111 NAME shoreline diag L WAL false TIL 2o 00000000 00000000 00100000 10010100 01001010 00100101 00000000 00000000 NAME grass TIL 2p 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 NAME blank sprite TIL 2q 00000011 00000111 00001111 00011111 00011111 01111111 11111111 11111111 NAME mountain corner TIL 2r 10000000 01100000 00010000 00001000 00001100 00000010 00000001 00000000 NAME mountain right side TIL 2s 10110111 01111111 00111010 00011111 00001111 00000111 00000111 00000010 > 11011011 01111111 00101111 00011101 00001111 00000101 00000111 00000011 TIL 2t 00000000 00000000 00111100 01000010 10000001 00000000 00000000 00000000 NAME lil pile TIL 2u 00000010 00000111 00100010 00000000 00000000 01000100 11100000 01000000 > 00000000 00100010 01110000 00100000 00000100 00001110 01000100 00000000 NAME sparkles TIL 2v 01110111 11111111 11111001 11110100 10100000 11110000 11100000 11000000 NAME cliff bottom WAL true TIL 2w 11111111 11111110 11111111 11111010 11000000 00011100 01111111 11111111 NAME cliff bits 1 WAL true TIL 2x 00000000 00000000 00000000 00000000 00000000 01110110 11011101 11111111 NAME cliff bits 2 WAL false TIL 2y 10100000 11111100 11111100 11111110 10111110 11111110 11111111 11111011 NAME cliff bits 3 WAL true TIL 2z 11111110 11111000 11110011 11110111 01100111 00001111 11111111 11111111 NAME cliff bits again WAL true TIL 3a 11101111 11111101 00111111 00001110 00000000 00000000 00000000 00000000 > 11111111 11101111 00111011 00001110 00000000 00000000 00000000 00000000 NAME river bottom TIL 3b 00011000 00011000 11111111 10000101 10100001 10001001 10011001 10011001 > 00011000 00011000 11111111 10100001 10000101 10010001 10011001 10011001 NAME fireplace WAL true TIL 3c 10000001 11111111 10000001 10111101 10111101 10000001 11111111 10000001 NAME bed upper TIL 3d 10000001 10000001 10000001 10000001 10000001 10000001 11111111 10000001 NAME bed lower TIL 3e 00000000 01000000 01000000 01000000 01111100 01111100 01000100 01000100 NAME chair left TIL 3f 00000000 00000010 00000010 00000010 00111110 00111110 00100010 00100010 NAME chair right TIL 3g 00000000 11111111 10000001 10000001 11111111 10000001 10000001 10000001 NAME table WAL true TIL 3h 00000000 00000000 00000000 00001100 11111111 11111111 00001100 00000000 NAME topdown door handle TIL 3i 11111111 11111111 11111111 11111111 11000000 11000000 11000000 11000000 NAME desk left WAL true TIL 3j 11111111 11111111 11111111 11111111 00000011 00000011 00000011 00000011 NAME desk right WAL true TIL 3k 00000000 01111111 01100000 01011111 01011111 01011111 01011111 01011111 NAME rug 1:1 TIL 3l 00000000 11111111 00000000 11111111 11111111 11111111 11111111 11111111 NAME rug 1:2 TIL 3m 00000000 11111110 00000110 11111010 11111010 11111010 11111010 11111010 NAME rug 1:3 TIL 3n 01011111 01011111 01011111 01011111 01011111 01100000 01111111 00000000 NAME rug 2:1 TIL 3o 11111111 11111111 11111111 11111111 11111111 00000000 11111111 00000000 NAME rug 2:2 TIL 3p 11111010 11111010 11111010 11111010 11111010 00000110 11111110 00000000 TIL 3q 11111000 11111000 11111000 11111100 11111110 11111111 10011000 00001000 NAME oak base R WAL true TIL 3r 00011111 00011111 00011111 00111111 01111111 11111111 00100101 01000100 NAME oak base L WAL true TIL 3s 00011111 00011111 00011111 00011111 00011111 00011111 00011111 00011111 NAME oak middle L WAL true TIL 3t 11111000 11111000 11111000 11111000 11111000 11111000 11111000 11111000 NAME side oak R WAL true TIL 3u 11111001 11111110 11111100 11111000 11111000 11111000 11111000 11111000 WAL true TIL 3v 10000101 01100010 00000100 01010000 00100101 00001010 10100010 01001000 > 01100010 10000100 00000010 00100101 01010010 10010000 01001000 00000100 WAL true TIL 3w 00000000 01100100 00001010 00000010 11111100 00000000 00000111 00000000 > 00000000 00001111 00000000 11000100 00001010 00000010 11111100 00000000 NAME wind TIL 3x 00000000 01000000 00001000 00000000 00100000 00000000 00001000 00000000 WAL false TIL 3y 00000100 00000000 00100000 00000100 00000000 00000000 01001000 00000000 NAME stars2 wall WAL true SPR 10 00000000 00000000 00000000 01100110 01100110 01100110 11101110 00000000 NAME boots DLG v POS 6 6,11 SPR 11 00000000 00000000 00000000 00000000 00111000 01111100 01111000 10000000 > 00000000 00000000 00000000 00111000 01111000 01111000 01110000 10000000 NAME oak leaf DLG 18 POS c 9,8 SPR 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 SPR 13 00000000 00000000 00000000 01100110 01100110 01100110 11101110 00000000 NAME boots night DLG t POS d 6,11 SPR 14 00000000 00000000 00000000 01111110 01000101 01000101 01000110 01111100 > 00000000 00000000 00000000 01111110 01000101 01000101 01000110 01111100 NAME cold coffee DLG u POS d 10,8 SPR 15 00000000 01011110 01010010 01011110 01011110 01011110 01011110 00000000 NAME notebook 3 DLG 1f POS d 11,5 SPR 16 00000000 00000000 01000000 00100000 11111110 00000000 00000000 00000000 NAME stick of pine DLG s POS 1 4,4 SPR 17 00010000 00101000 00010000 00000010 00100101 01010010 00100000 00000000 NAME flowers DLG 19 POS 1 10,8 SPR A 00010000 00001100 00001011 00001100 11111110 01111110 00010100 00010100 > 00000000 00010000 00001100 00001011 00001100 11111110 01111110 00010100 POS 0 6,11 SPR a 00011000 00111100 00011000 00111100 01011010 00111100 00011000 00011000 > 00011000 00111110 00011010 00111100 01011000 00111000 00011000 00011000 NAME Walt intro DLG 4 POS 0 9,9 BLIP 1 SPR b 00000000 00100010 01110000 00100000 00000010 01000111 00000010 00000000 > 00000010 00000111 00100010 00000000 01000000 11100010 01000000 00000000 NAME sparkles DLG 1g POS b 9,8 SPR c 00001000 01100000 10010000 10010010 01100000 00001000 00000100 00010010 > 00000000 01100100 10010000 10010000 01100001 00001000 01000100 00000010 NAME seer’s glass SPR e 00000000 00000000 01100110 11111111 11111111 10111101 00111100 00111100 NAME shirt DLG 1a POS 3 6,9 SPR f 00000000 01010000 01010000 01110000 00100000 01010001 00111110 01111110 > 00000000 01010000 01010000 01110000 01110000 01010001 00111110 01111110 NAME rabbit DLG o POS 1 7,3 SPR g 00000000 00000010 00001110 00100000 00100000 00110000 00000000 00000000 NAME socks DLG 5 POS 3 10,10 SPR h 00000000 00000000 01111111 01111111 01111111 01110111 01110111 00000000 NAME shorts DLG 6 POS 3 11,6 SPR i 00000000 00001000 00010000 01111100 11110110 11111010 11111110 01111100 NAME apple DLG 8 POS 2 6,13 SPR j 00000000 00000001 00110011 11111111 01001011 00110001 00000000 00000000 NAME horn DLG 17 POS 2 5,8 SPR k 00000000 01100000 01100000 01110110 01100110 00001110 00000110 00000000 NAME mittens DLG a POS 2 10,11 SPR l 01001000 00011000 00111100 00111100 00011000 10000001 00111100 01011010 > 00010010 00011000 00111100 00111100 00011010 01000000 00111100 01011010 NAME fire DLG x POS 8 5,10 SPR m 01000111 11101001 01001001 00011110 00111000 01110000 11100010 11001000 > 00100111 00001001 00001001 10011110 00111000 01110010 11100111 11000010 NAME telescope DLG 1c POS 8 11,11 SPR n 00000000 01011110 01010010 01011110 01011110 01011110 01011110 00000000 NAME notebook DLG y POS 8 4,12 SPR o 01111110 11011011 11011011 11011011 11011011 01111110 00111100 01111110 NAME shell DLG 10 POS 9 11,7 SPR p 00000000 00000000 00010000 00001000 01111111 11111110 00000000 00000000 NAME driftwood DLG z POS 9 5,4 SPR q 00100000 00110000 00110110 10110101 11111110 01111100 00010000 00000000 > 00000000 10000110 10000101 11111110 01111100 00110000 00110000 00100000 NAME bird L POS a 5,3 SPR r 00001000 00001100 01101101 10101101 01111110 00111110 00001000 00000000 > 00000000 01100001 10100001 01111111 00111110 00001100 00001100 00001000 NAME bird R POS a 7,2 SPR s 11100000 10110000 11011000 01101100 00110100 00011100 00000010 00000001 NAME feather DLG 1d POS a 5,12 SPR u 00011000 00100100 01001010 10000001 10000001 10110101 01111110 00011000 NAME mushroom DLG r POS 1 5,10 SPR w 00000000 00001010 01001100 01101000 01001001 01011110 01110000 01100000 NAME coral DLG 1b POS 9 7,9 SPR x 00011000 00111100 00011000 00111100 01011010 00111100 00011000 00011000 > 00011000 00111100 00011010 00111100 01011000 00111000 00011000 00011000 NAME walt end DLG 1i POS 5 10,12 SPR y 00000000 01011110 01010010 01011110 01011110 01011110 01011110 00000000 NAME notebook 2 DLG 1e POS 6 11,5 SPR z 00100000 00010000 00000000 01111110 01000101 01000101 01000110 01111100 > 00001000 00010000 00000000 01111110 01000101 01000101 01000110 01111100 NAME coffee DLG w POS 6 10,8 ITM 0 00000000 00000000 00011100 00100100 01001000 01110000 01000000 01000000 > 00011000 00111000 00101000 01001000 01011000 01110000 01000000 01000000 NAME leaf DLG 1 ITM 1 00000000 00111100 00100100 00111100 00010000 00011000 00010000 00011000 NAME key DLG 2 BLIP 2 ITM 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME individualism ITM 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME senses ITM 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME body ITM 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME song ITM 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME humanity ITM 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME earth ITM 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME sea ITM 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME sky ITM a 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME stars ITM b 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME peace ITM c 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME universe ITM d 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 NAME tutorial DLG 0 Here the frailest leaves of me and yet my strongest lasting,{pg}Here I shade and hide my thoughts, I myself do not expose them,{pg}And yet they expose me more than all my other poems. NAME Here the Frailest Leaves of Me DLG 1 You find a {wvy}leaf of grass{/wvy}. NAME grass dialog DLG 2 A key! {wvy}What does it open?{wvy} NAME key dialog DLG 3 “”” { – {item “1”} >= 1 ? {property locked false} The key opens the door! – else ? {property locked true} The door is locked… } “”” NAME locked exit 1 DLG 4 “”” {sequence – Hello there, friend. Welcome to my poems. {pg}To progress to the end, you’ll need to collect some {wvy}leaves{/wvy}. {pg}Why don’t you bring me that one by the cabin door? – { – {item “3”} == 1 ? { – {item “4”} == 1 ? { – {item “5”} == 1 ? You’ve found all the poems in this area, friend. {pg}Why don’t you go inside and try to {wvy}write them down{/wvy}?{item “1” {{item “1”} + 1}} – else ? Try looking around for more poems. They’re bound to be laying around somewhere. } – else ? Try looking around for more poems. They’re bound to be laying around somewhere. } – else ? { – {item “d”} == 1 ? Try looking around for more poems. They’re bound to be laying around somewhere. – else ? { – {item “0”} >= 1 ? Here the frailest leaves of me and yet my strongest lasting,{pg}Here I shade and hide my thoughts, I myself do not expose them,{pg}And yet they expose me more than all my other poems.{item “0” {{item “0”} – 1}}{pg}…{pg}{item “d” {{item “d”} + 1}}It felt nice to share that with you, friend. {pg}Try looking around for more poems. They’re bound to be laying around somewhere. – else ? Can you bring me that {wvy}leaf{/wvy} by the cabin door? } } } } “”” NAME walt intro DLG 5 A pair of socks, shed on the way to the river. No poem here. {pg}Maybe in something closer to the {wvy}heart{/wvy}? NAME socks dialog DLG 6 A pair of shorts, shed on the way to the river. No poem here. {pg}Maybe in something closer to the {wvy}heart{/wvy}? NAME shorts dialog DLG 8 An apple, dropped on the journey. No poem here. {pg}Maybe in something with more of a {wvy}song{/wvy}? NAME apple dialog DLG 9 A horn. Not the sort you play, but the ind you use to amplify sound. There’s a poem here. Would you like to use a leaf to unlock it? NAME horn dialog DLG 10 A shell, shining in the glow of the moon. {pg}No poem here.{pg}Maybe in something with a bit more {wvy}life{/wvy}? NAME shell dialog DLG 14 NAME exit dialog 1 DLG 15 “”” { – {item “1”} >= 1 ? {property locked false} Beginning my studies the first step pleas’d me so much, {pg}The mere fact consciousness, these forms, the power of motion, {pg}The least insect or animal, the senses, eyesight, love, {pg}The first step I say awed me and pleas’d me so much, {pg}I have hardly gone and hardly wish’d to go any farther, {pg}But stop and loiter all the time to sing it in ecstatic songs. – else ? {property locked true}The door is locked… } “”” NAME beginning my studies DLG 17 “”” A horn. Not the sort for playing, but the kind used to listen. {pg}{ – {item “5”} == 1 ? [Poem collected] – else ? { – {item “0”} >= 1 ? Sounds of the winter too, {pg}Sunshine upon the mountains-many a distant strain{pg}From cheery railroad train-from nearer field, barn, house, {pg}The whispering air-even the mute crops, garner’d apples, corn, {pg}Children’s and women’s tones-rhythm of many a farmer and of flail, {pg}An old man’s garrulous lips among the rest, Think not we give out yet, {pg}Forth from these snowy hairs we keep up yet the lilt.{item “5” {{item “5”} + 1}}{item “0” {{item “0”} – 1}} – else ? There’s a poem here. } } “”” NAME sounds of winter DLG 18 “”” A live oak leaf, blowing lonely in the wind. {pg}{ – {item “7”} == 1 ? [Poem collected] – else ? { – {item “0”} >= 1 ? I saw in Louisiana a live-oak growing,{pg}All alone stood it and the moss hung down from the branches,{pg}Without any companion it grew there uttering joyous of dark green,{pg}And its look, rude, unbending, lusty, made me think of myself,{pg}But I wonder’d how it could utter joyous leaves standing alone there{pg}without its friend near, for I knew I could not,{pg}And I broke off a twig with a certain number of leaves upon it and{pg}twined around it a little moss,{pg}And brought it away, and I have placed it in sight in my room,{pg}It is not needed to remind me as of my own dear friends,{pg}(For I believe lately I think of little else than of them,){pg} Yet it remains to me a curious token, it makes me think of manly love;{pg}For all that, and though the live-oak glistens there in Louisiana{pg}solitary in a wide in a wide flat space,{pg}Uttering joyous leaves all its life without a friend a lover near,{pg}I know very well I could not.{item “7” {{item “7”} + 1}}{item “0” {{item “0”} – 1}} – else ? There’s a poem here. } } “”” NAME louisiana live oak DLG 19 “”” A battalion of wildflowers, standing tall in the breeze. {pg}{ – {item “3”} == 1 ? [Poem collected] – else ? { – {item “0”} >= 1 ? These I singing in spring collect for lovers, {pg} (For who but I should understand lovers and all their sorrow and joy? {pg}And who but I should be the poet of comrades?) {pg}Collecting I traverse the garden the world, but soon I pass the gates, {pg}Now along the pond-side, now wading in a little, fearing not the wet, {pg}Now by the post-and-rail fences where the old stones thrown there, {pg}pick’d from the fields, have accumulated, {pg} (Wild-flowers and vines and weeds come up through the stones and partly cover them, beyond these I pass,) {pg}Far, far in the forest, or sauntering later in summer, before I think where I go, {pg}Solitary, smelling the earthy smell, stopping now and then in the silence, {pg}Alone I had thought, yet soon a troop gathers around me, {pg}Some walk by my side and some behind, and some embrace my arms or neck, {pg}They the spirits of dear friends dead or alive, thicker they come, a great crowd, and I in the middle, {pg}Collecting, dispensing, singing, there I wander with them, {pg}Plucking something for tokens, tossing toward whoever is near me, {pg}Here, lilac, with a branch of pine, {pg}Here, out of my pocket, some moss which I pull’d off a live-oak in Florida as it hung trailing down, {pg}Here, some pinks and laurel leaves, and a handful of sage, {pg}And here what I now draw from the water, wading in the pondside, {pg}(O here I last saw him that tenderly loves me, and returns again{pg}never to separate from me, {pg}And this, O this shall henceforth be the token of comrades, this calamus-root shall, {pg}Interchange it youths with each other! let none render it back!) {pg}And twigs of maple and a bunch of wild orange and chestnut, {pg}And stems of currants and plum-blows, and the aromatic cedar, {pg}These I compass’d around by a thick cloud of spirits, {pg}Wandering, point to or touch as I pass, or throw them loosely from me, {pg}Indicating to each one what he shall have, giving something to each; {pg}But what I drew from the water by the pond-side, that I reserve, {pg}I will give of it, but only to them that love as I myself am capable{pg}of loving.{item “3” {{item “3”} + 1}}{item “0” {{item “0”} – 1}} – else ? There’s a poem here. } } “”” NAME these i singing in the spring DLG a Mittens, forgotten. {pg}They may a fuzzy sound when rubbed together. {pg}No poem here. Maybe in something with more of a {wvy}song{/wvy}? NAME mittens dialog DLG o There’s a rabbit here. {pg}They look happy. NAME rabbit DLG r A mushroom, growing from soft earth. {pg}No poem here, maybe in something more {wvy}fragrant{/wvy}? NAME mushroom dialog DLG s A fresh snapped twig of cedar. (pine?){pg}No poem here, maybe in something more {wvy}soft{/wvy}? NAME stick of pine dialog DLG t A pair of boots. {pg} The mud has long since dried. NAME boots night dialog DLG u A mug of strong coffee. {pg} It’s cold. NAME cold coffee dialog DLG v A pair of boots, caked in fresh mud from a recent walk. NAME boots dialog DLG w A mug of strong coffee, piping hot. NAME coffee dialog DLG x A warm campfire, crackling with life. {pg}Don’t stay too long, you won’t be able to see the {wvy}stars{/wvy}. NAME fire dialog DLG y A notebook full of observations of the heavens. {pg}No poem here. {pg}Maybe try to {wvy}see{/wvy} for yourself? NAME notebook dialog DLG z A piece of driftwood, settled in the sand. {pg}No poem here. Maybe in something with a little more {wvy}life{/wvy}? NAME driftwood dialog DLG 1a “”” A shirt, shed on the way to the river, soft from years of wear. {pg}{ – {item “4”} == 1 ? [Poem collected] – else ? { – {item “0”} >= 1 ? I sing the body electric, {pg}The armies of those I love engirth me and I engirth them, {pg}They will not let me off till I go with them, respond to them, {pg}And discorrupt them, and charge them full with the charge of the soul. {pg}Was it doubted that those who corrupt their own bodies conceal themselves? {pg}And if those who defile the living are as bad as they who defile the dead? {pg}And if the body does not do fully as much as the soul? {pg}And if the body were not the soul, what is the soul?{item “4” {{item “4”} + 1}}{item “0” {{item “0”} – 1}} – else ? There’s a poem here. } } “”” NAME sing the body electric DLG 1b “”” Brightly-colored coral, still shining with seawater. {pg}{ – {item “8”} == 1 ? [Poem collected] – else ? { – {item “0”} >= 1 ? The world below the brine, {pg}Forests at the bottom of the sea, the branches and leaves, {pg}Sea-lettuce, vast lichens, strange flowers and seeds, the thick tangle, openings, and pink turf, {pg}Different colors, pale gray and green, purple, white, and gold, the play of light through the water, {pg}Dumb swimmers there among the rocks, coral, gluten, grass, rushes, and the aliment of the swimmers, {pg}Sluggish existences grazing there suspended, or slowly crawling close to the bottom, {pg}The sperm-whale at the surface blowing air and spray, or disporting with his flukes, {pg}The leaden-eyed shark, the walrus, the turtle, the hairy sea-leopard, and the sting-ray, {pg}Passions there, wars, pursuits, tribes, sight in those ocean-depths, breathing that thick-breathing air, as so many do, {pg}The change thence to the sight here, and to the subtle air breathed by beings like us who walk this sphere, {pg}The change onward from ours to that of beings who walk other spheres.{item “8” {{item “8”} + 1}}{item “0” {{item “0”} – 1}} – else ? There’s a poem here. } } “”” NAME world below brine DLG 1c “”” A brass telescope, reflecting the stars in its lens. {pg}{ – {item “a”} == 1 ? [Poem collected] – else ? { – {item “0”} >= 1 ? When I heard the learn’d astronomer, {pg}When the proofs, the figures, were ranged in columns before me, {pg}When I was shown the charts and diagrams, to add, divide, and measure them, {pg}When I sitting heard the astronomer where he lectured with much applause in the lecture-room, {pg}How soon unaccountable I became tired and sick, {pg}Till rising and gliding out I wander’d off by myself, {pg}In the mystical moist night-air, and from time to time, {pg}Look’d up in perfect silence at the stars.{item “a” {{item “a”} + 1}}{item “0” {{item “0”} – 1}} – else ? There’s a poem here. } } “”” NAME astronomer DLG 1d “”” A feather, dropped from high above. {pg}{ – {item “9”} == 1 ? [Poem collected] – else ? { – {item “0”} >= 1 ? Skirting the river road, (my forenoon walk, my rest,){pg}Skyward in air a sudden muffled sound, the dalliance of the eagles, {pg}The rushing amorous contact high in space together, {pg}The clinching interlocking claws, a living, fierce, gyrating wheel, {pg}Four beating wings, two beaks, a swirling mass tight grappling, {pg}In tumbling turning clustering loops, straight downward falling, {pg}Till o’er the river pois’d, the twain yet one, a moment’s lull, {pg}A motionless still balance in the air, then parting, talons loosing, {pg}Upward again on slow-firm pinions slanting, their separate diverse flight, {pg}She hers, he his, pursuing.{item “9” {{item “9”} + 1}}{item “0” {{item “0”} – 1}} – else ? There’s a poem here. } } “”” NAME dalliance of eagles DLG 1e “”” A notebook, clearly well-used. {pg}{ – {item “2”} == 1 ? [Poem collected] – else ? { – {item “0”} >= 1 ? One’s-self I sing, a simple separate person, {pg}Yet utter the word Democratic, the word En-Masse. {pg}Of physiology from top to toe I sing, {pg}Not physiognomy alone nor brain alone is worthy for the Muse, I say{pg}the Form complete is worthier far, {pg}The Female equally with the Male I sing. {pg}Of Life immense in passion, pulse, and power, {pg}Cheerful, for freest action form’d under the laws divine, {pg}The Modern Man I sing.{item “2” {{item “2”} + 1}}{item “0” {{item “0”} – 1}} – else ? There’s a poem here. } } “”” NAME ones self i sing DLG 1f “”” A notebook, full, shrouded in dust. {pg}{ – {item “b”} == 1 ? [Poem collected] – else ? { – {item “0”} >= 1 ? At the last, tenderly, {pg}From the walls of the powerful fortress’d house, {pg}From the clasp of the knitted locks, from the keep of the well-closed doors, {pg}Let me be wafted. {pg}Let me glide noiselessly forth; {pg}With the key of softness unlock the locks-with a whisper, {pg}Set ope the doors O soul. {pg}Tenderly-be not impatient, {pg}(Strong is your hold O mortal flesh, {pg}Strong is your hold O love.){item “b” {{item “b”} + 1}}{item “0” {{item “0”} – 1}} – else ? There’s a poem here. } } “”” NAME last invocation DLG 1g “”” Stars, distant. Still, they feel close enough to touch. {pg}{ – {item “c”} == 1 ? [Poem collected] – else ? { – {item “0”} >= 1 ? Lo! keen-eyed towering science, {pg}As from tall peaks the modern overlooking, {pg}Successive absolute fiats issuing. {pg}Yet again, lo! the soul, above all science, {pg}For it has history gather’d like husks around the globe, {pg}For it the entire star-myriads roll through the sky. {pg}In spiral routes by long detours, {pg} (As a much-tacking ship upon the sea,) {pg}For it the partial to the permanent flowing, {pg}For it the real to the ideal tends. {pg}For it the mystic evolution, {pg}Not the right only justified, what we call evil also justified. {pg}Forth from their masks, no matter what, {pg}From the huge festering trunk, from craft and guile and tears, {pg}Health to emerge and joy, joy universal. {pg}Out of the bulk, the morbid and the shallow, {pg}Out of the bad majority, the varied countless frauds of men and {pg}states, {pg}Electric, antiseptic yet, cleaving, suffusing all, {pg}Only the good is universal.{item “c” {{item “c”} + 1}}{item “0” {{item “0”} – 1}}{exit “5” 5 12 “fade_w”} – else ? There’s a poem here. } } “”” NAME song of the universal DLG 1h “”” { – {item “b”} >= 1 ? {property locked false} – else ? {property locked true}[There’s something in here that you haven’t found yet.] } “”” NAME cabin to universe lock DLG 1i “”” {sequence – Hello again, friend.{pg}You sure have found a lot of poems.{pg}If you’re ready, why don’t you bring me one last {wvy}leaf{/wvy}? – { – {item “0”} >= 1 ? O me! O life! of the questions of these recurring, {pg}Of the endless trains of the faithless, of cities fill’d with the foolish, {pg}Of myself forever reproaching myself, (for who more foolish than I, {pg}and who more faithless?) {pg}Of eyes that vainly crave the light, of the objects mean, of the{pg}struggle ever renew’d, {pg}Of the poor results of all, of the plodding and sordid crowds I see{pg}around me, {pg}Of the empty and useless years of the rest, with the rest me intertwined, {pg}The question, O me! so sad, recurring—What good amid these, O me, O life? {pg}Answer. {pg}That you are here—that life exists and identity, {pg}That the powerful play goes on, and you may contribute a verse. {pg}{end}…{pg} Thank you for listening, friend.{pg}{wvy}The End.{/wvy} – else ? There’s one right over there, friend. Go ahead and grab it. } } “”” NAME walt ending VAR a 42 TUNE 1 3d,0,0,0,d5,m,s,2s,0,2l,0,0,3s,0,0,0 16d2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 > 4l,0,0,0,s,0,3l,0,0,0,2s,0,2m,0,2r,0 16m2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 > 3d,0,0,0,3d5,0,0,0,3l,0,0,0,3s,0,0,0 16l2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 > 3l,0,0,0,s,0,4m,0,0,0,4r,0,0,0,0,0 16s2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 NAME finale fanfare KEY C,D,E,F,G,A,B d,r,m,s,l TMP XFST SQR P2 P8 ARP INT8 TUNE 2 0,0,2G,0,A,0,B,0,2C5,0,B,A,G,0,2G,0 G3,0,D,0,G3,0,D,0,2A3,0,E,0,C,0,E,0 > 2F#,0,G,0,A,0,F#,0,2E,0,F#,E,4D,0,0,0 2D,0,E,0,F#,0,D,0,2C,0,2G3,0,2F#3,0,D2,0 > 0,0,2G,0,A,0,B,0,2C5,0,B,A,G,0,G,0 2G2,0,D,D5,G3,G,D,0,2C2,0,E,E5,C3,C,E5,0 > 2D,0,C5,B,A,0,A,0,4A,0,0,0,F#,0,0,0 A2,0,E3,0,C3,0,E3,0,D3,0,A3,0,D,0,0,0 > 2E5,0,2G,0,2G5,0,2G,0,2F#5,0,2E5,0,2D5,0,2C5,0 2C3,0,2E,0,2E5,0,2C,0,2A3,0,2C,0,2F#,0,2E,0 > 3B,0,0,0,2E5,0,D5,0,4A,0,0,0,G,0,0,0 2G3,0,B3,0,2D,0,D3,0,2C3,0,G3,0,D#,0,0,0 > 0,0,2G,0,A,0,2B,0,C5,0,B,C5,A,0,G,0 A2,0,A3,0,C,0,2D,0,D#,0,D,E,C,0,C3,0 > 8B,0,0,0,0,0,0,0,A,0,2F#,0,E,0,D,0 D3,0,A3,0,F#,0,D,0,C,0,2D3,0,C3,0,F#3,0 NAME tuneful town TMP FST SQR P4 P2 TUNE 3 3F5,0,0,A#,0,2C#5,0,A#,3F5,0,0,F#5,0,0,2F5,0 A#3,C#,F,0,0,F,C#,F,A#3,C#,F,A#,0,A#,C#,F > 3F5,0,0,A,0,2C#5,0,A,3F5,0,0,2A#5,0,A#,D#5,0 A3,C#,F,A3,0,F,C#,F,A,C#,F,0,D#,0,C#5,0 > 4F5,0,0,0,G#,2C#5,0,G#,3F5,0,0,D#5,0,F5,2C#5,0 G#3,C#,F,3F#,0,0,2F,0,G#3,C#,F,F#,B,A,F,D# > 4D#5,0,0,0,0,0,2A#,0,4A#,0,0,0,0,0,A#,C5 G3,D#,F,G,0,D#,F,G,G3,D#,G,F,0,F,D#,C#3 > 4C#5,0,0,0,0,0,2C#5,0,3C#5,0,0,D#5,0,0,2C#5,0 F#2,C#,F#3,A#3,0,F#3,C#3,F#3,F#2,A,F#3,C#,0,F#3,A3,F#3 > 3C#5,0,0,F,3C5,0,0,C,3C5,0,0,D#,3A#,0,0,0 F2,D#3,A3,0,0,F3,C#,0,F#2,A#3,D#3,0,0,F#3,C#,C > 3A#,0,0,0,C5,0,C#5,0,A#,0,C#,D#,G#,G#3,0,C#3 C3,A#3,C,E,A,0,A#,0,0,B2,B3,0,2F,0,0,0 > A#,0,A#3,0,C#,0,F,0,A#,0,0,0,0,0,0,0 A#2,0,C#3,0,F3,0,A#3,0,D,0,0,0,0,F#3,0,F3 NAME rhythmic ruins TMP MED SQR P4 P4 BLIP 1 E5,B5,B5 NAME meow ENV 40 99 4 185 138 BEAT 61 115 SQR P2 BLIP 2 D5,E5,D5 NAME pick up key ENV 99 65 6 96 152 BEAT 95 0 SQR P4 html { margin:0px; padding:0px; } body { margin:0px; padding:0px; overflow:hidden; background:#ffffff; } #game { background:black; width:100vw; max-width:100vh; margin:auto; display:block; } function startExportedGame() { var gameCanvas = document.getElementById(“game”); var gameData = document.getElementById(“exportedGameData”).text.slice(1); var defaultFontData = document.getElementById(defaultFontName).text.slice(1); loadGame(gameCanvas, gameData, defaultFontData); initSystem(); } function InputSystem() { var self = this; this.Key = { LEFT: 37, RIGHT: 39, UP: 38, DOWN: 40, SPACE: 32, ENTER: 13, W: 87, A: 65, S: 83, D: 68, R: 82, SHIFT: 16, CTRL: 17, ALT: 18, CMD: 224 }; var pressed; var ignored; var touchState; var isRestartComboPressed = false; var SwipeDir = { None : -1, Up : 0, Down : 1, Left : 2, Right : 3, }; function resetAll() { isRestartComboPressed = false; pressed = {}; ignored = {}; touchState = { isDown : false, startX : 0, startY : 0, curX : 0, curY : 0, swipeDistance : 30, swipeDirection : SwipeDir.None, tapReleased : false }; } resetAll(); function stopWindowScrolling(e) { if (e.keyCode == self.Key.LEFT || e.keyCode == self.Key.RIGHT || e.keyCode == self.Key.UP || e.keyCode == self.Key.DOWN || !isPlayerEmbeddedInEditor) { e.preventDefault(); } } function isRestartCombo(e) { return (e.keyCode === self.Key.R && (e.getModifierState(“Control”)|| e.getModifierState(“Meta”))); } function eventIsModifier(event) { return (event.keyCode == self.Key.SHIFT || event.keyCode == self.Key.CTRL || event.keyCode == self.Key.ALT || event.keyCode == self.Key.CMD); } function isModifierKeyDown() { return (self.isKeyDown(self.Key.SHIFT) || self.isKeyDown(self.Key.CTRL) || self.isKeyDown(self.Key.ALT) || self.isKeyDown(self.Key.CMD)); } this.ignoreHeldKeys = function() { for (var key in pressed) { if (pressed[key]) { // only ignore keys that are actually held ignored[key] = true; // bitsyLog(“IGNORE — ” + key, “system”); } } } this.onkeydown = function(event) { enableGlobalAudioContext(); // bitsyLog(“KEYDOWN — ” + event.keyCode, “system”); stopWindowScrolling(event); isRestartComboPressed = isRestartCombo(event); // Special keys being held down can interfere with keyup events and lock movement // so just don’t collect input when they’re held { if (isModifierKeyDown()) { return; } if (eventIsModifier(event)) { resetAll(); } } if (ignored[event.keyCode]) { return; } pressed[event.keyCode] = true; ignored[event.keyCode] = false; } this.onkeyup = function(event) { // bitsyLog(“KEYUP — ” + event.keyCode, “system”); pressed[event.keyCode] = false; ignored[event.keyCode] = false; } this.ontouchstart = function(event) { enableGlobalAudioContext(); event.preventDefault(); if( event.changedTouches.length > 0 ) { touchState.isDown = true; touchState.startX = touchState.curX = event.changedTouches[0].clientX; touchState.startY = touchState.curY = event.changedTouches[0].clientY; touchState.swipeDirection = SwipeDir.None; } } this.ontouchmove = function(event) { event.preventDefault(); if( touchState.isDown && event.changedTouches.length > 0 ) { touchState.curX = event.changedTouches[0].clientX; touchState.curY = event.changedTouches[0].clientY; var prevDirection = touchState.swipeDirection; if( touchState.curX – touchState.startX = touchState.swipeDistance ) { touchState.swipeDirection = SwipeDir.Right; } else if( touchState.curY – touchState.startY = touchState.swipeDistance ) { touchState.swipeDirection = SwipeDir.Down; } if( touchState.swipeDirection != prevDirection ) { // reset center so changing directions is easier touchState.startX = touchState.curX; touchState.startY = touchState.curY; } } } this.ontouchend = function(event) { event.preventDefault(); touchState.isDown = false; if( touchState.swipeDirection == SwipeDir.None ) { // tap! touchState.tapReleased = true; } touchState.swipeDirection = SwipeDir.None; } this.isKeyDown = function(keyCode) { return pressed[keyCode] != null && pressed[keyCode] == true && (ignored[keyCode] == null || ignored[keyCode] == false); } this.anyKeyDown = function() { var anyKey = false; for (var key in pressed) { if (pressed[key] && (ignored[key] == null || ignored[key] == false) && !(key === self.Key.UP || key === self.Key.DOWN || key === self.Key.LEFT || key === self.Key.RIGHT) && !(key === self.Key.W || key === self.Key.S || key === self.Key.A || key === self.Key.D)) { // detected that a key other than the d-pad keys are down! anyKey = true; } } return anyKey; } this.isRestartComboPressed = function() { return isRestartComboPressed; } this.swipeLeft = function() { return touchState.swipeDirection == SwipeDir.Left; } this.swipeRight = function() { return touchState.swipeDirection == SwipeDir.Right; } this.swipeUp = function() { return touchState.swipeDirection == SwipeDir.Up; } this.swipeDown = function() { return touchState.swipeDirection == SwipeDir.Down; } this.isTapReleased = function() { return touchState.tapReleased; } this.resetTapReleased = function() { touchState.tapReleased = false; } this.onblur = function() { // bitsyLog(“~~~ BLUR ~~”, “system”); resetAll(); } this.resetAll = resetAll; this.listen = function(canvas) { document.addEventListener(‘keydown’, self.onkeydown); document.addEventListener(‘keyup’, self.onkeyup); if (isPlayerEmbeddedInEditor) { canvas.addEventListener(‘touchstart’, self.ontouchstart, {passive:false}); canvas.addEventListener(‘touchmove’, self.ontouchmove, {passive:false}); canvas.addEventListener(‘touchend’, self.ontouchend, {passive:false}); } else { // creates a ‘touchTrigger’ element that covers the entire screen and can universally have touch event listeners added w/o issue. // we’re checking for existing touchTriggers both at game start and end, so it’s slightly redundant. var existingTouchTrigger = document.querySelector(‘#touchTrigger’); if (existingTouchTrigger === null) { var touchTrigger = document.createElement(“div”); touchTrigger.setAttribute(“id”,”touchTrigger”); // afaik css in js is necessary here to force a fullscreen element touchTrigger.setAttribute( “style”,”position: absolute; top: 0; left: 0; width: 100vw; height: 100vh; overflow: hidden;” ); document.body.appendChild(touchTrigger); touchTrigger.addEventListener(‘touchstart’, self.ontouchstart); touchTrigger.addEventListener(‘touchmove’, self.ontouchmove); touchTrigger.addEventListener(‘touchend’, self.ontouchend); } } window.onblur = self.onblur; } this.unlisten = function(canvas) { document.removeEventListener(‘keydown’, self.onkeydown); document.removeEventListener(‘keyup’, self.onkeyup); if (isPlayerEmbeddedInEditor) { canvas.removeEventListener(‘touchstart’, self.ontouchstart); canvas.removeEventListener(‘touchmove’, self.ontouchmove); canvas.removeEventListener(‘touchend’, self.ontouchend); } else { //check for touchTrigger and removes it var existingTouchTrigger = document.querySelector(‘#touchTrigger’); if (existingTouchTrigger !== null) { existingTouchTrigger.removeEventListener(‘touchstart’, self.ontouchstart); existingTouchTrigger.removeEventListener(‘touchmove’, self.ontouchmove); existingTouchTrigger.removeEventListener(‘touchend’, self.ontouchend); existingTouchTrigger.parentElement.removeChild(existingTouchTrigger); } } window.onblur = null; } } // init global audio context var audioContext = new AudioContext(); function enableGlobalAudioContext() { audioContext.resume(); } function SoundSystem() { var self = this; // volume var maxGain = 0.15; // curves for different pulse wave duties (ratios between on and off) var dutyCycle_1_8 = new Float32Array(256); for (var i = 0; i < 256; i++) { dutyCycle_1_8[i] = ((i / 256) * 2) – 1.75; } var dutyCycle_1_4 = new Float32Array(256); for (var i = 0; i < 256; i++) { dutyCycle_1_4[i] = ((i / 256) * 2) – 1.5; } var dutyCycle_1_2 = new Float32Array(256); for (var i = 0; i < 256; i++) { dutyCycle_1_2[i] = ((i / 256) * 2) – 1.0; } var dutyCycles = [ dutyCycle_1_8, dutyCycle_1_4, dutyCycle_1_2 // square wave ]; function createPulseWidthModulator() { // the base oscillator: start with a sawtooth wave that we'll shape into a pulse wave var oscillator = audioContext.createOscillator(); oscillator.type = "sawtooth"; // create a gain node to control the volume of the sound var volumeControl = audioContext.createGain(); volumeControl.gain.value = 0; // create a wave shaper that turns the sawtooth wave into a pulse // by mapping any negative value to -1 and any positive value to 1 var pulseCurve = new Float32Array(256); for (var i = 0; i < 128; i++) { pulseCurve[i] = -1; } for (var i = 128; i < 256; i++) { pulseCurve[i] = 1; } var pulseShaper = audioContext.createWaveShaper(); pulseShaper.curve = pulseCurve; var dutyShaper = audioContext.createWaveShaper(); dutyShaper.curve = dutyCycle_1_2; oscillator.connect(dutyShaper); dutyShaper.connect(pulseShaper); pulseShaper.connect(volumeControl); volumeControl.connect(audioContext.destination); oscillator.start(); return { oscillator: oscillator, volumeControl: volumeControl, dutyShaper: dutyShaper }; } var pulseChannels = [createPulseWidthModulator(), createPulseWidthModulator()]; this.setPulse = function(channel, pulse) { var pulseChannel = pulseChannels[channel]; pulseChannel.dutyShaper.curve = dutyCycles[pulse]; } this.setFrequency = function(channel, frequencyHz) { var pulseChannel = pulseChannels[channel]; // set frequency in hertz pulseChannel.oscillator.frequency.setValueAtTime(frequencyHz, audioContext.currentTime); } this.setVolume = function(channel, volumeNorm) { var pulseChannel = pulseChannels[channel]; pulseChannel.volumeControl.gain.value = volumeNorm * maxGain; } this.mute = function() { for (var i = 0; i < pulseChannels.length; i++) { pulseChannels[i].volumeControl.gain.value = 0; } } } var sound = new SoundSystem(); function GraphicsSystem() { var self = this; var canvas; var ctx; var scale; var textScale; var palette = []; var images = []; var imageFillColors = []; function makeFillStyle(color, isTransparent) { var i = color * 3; if (isTransparent) { return “rgba(” + palette[i + 0] + “,” + palette[i + 1] + “,” + palette[i + 2] + “, 0)”; } else { return “rgb(” + palette[i + 0] + “,” + palette[i + 1] + “,” + palette[i + 2] + “)”; } } this._images = images; this._getPalette = function() { return palette; }; // todo : do I really need to pass in size here? this.attachCanvas = function(c, size) { canvas = c; canvas.width = size * scale; canvas.height = size * scale; ctx = canvas.getContext(“2d”); }; this.getCanvas = function() { return canvas; }; this.getContext = function() { return ctx; }; this.setScale = function(s) { scale = s; }; this.setTextScale = function(s) { textScale = s; }; this.getTextScale = function() { return textScale; }; this.setPalette = function(p) { palette = p; }; // todo : rename this since it doesn’t always create a totally new canvas? this.createImage = function(id, width, height, pixels, useTextScale) { var imageScale = useTextScale === true ? textScale : scale; var widthScaled = width * imageScale; var heightScaled = height * imageScale; // try to use an existing image canvas if it is the right size, // instead of expensively creating a new one var imageCanvas = images[id]; if (imageCanvas === undefined || imageCanvas.width != widthScaled || imageCanvas.height != heightScaled) { imageCanvas = document.createElement(“canvas”); imageCanvas.width = widthScaled; imageCanvas.height = heightScaled; } var imageCtx = imageCanvas.getContext(“2d”); // if we know the fill color for this image, we can speed things up // by filling the whole image with that color var fillColor; if (imageFillColors[id] != undefined) { fillColor = imageFillColors[id]; var isTransparent = (fillColor === 0); if (isTransparent) { imageCtx.clearRect(0, 0, imageCanvas.width, imageCanvas.height); } else { imageCtx.fillStyle = makeFillStyle(fillColor, isTransparent); imageCtx.fillRect(0, 0, imageCanvas.width, imageCanvas.height); } } for (var i = 0; i < pixels.length; i++) { var x = i % width; var y = Math.floor(i / width); var color = pixels[i]; if (color != fillColor) { var isTransparent = (color === 0); imageCtx.fillStyle = makeFillStyle(color, isTransparent); imageCtx.fillRect(x * imageScale, y * imageScale, imageScale, imageScale); } } images[id] = imageCanvas; }; this.setImageFill = function(id, color) { imageFillColors[id] = color; }; this.drawImage = function(id, x, y, destId) { if (!images[id]) { bitsyLog("image doesn't exist: " + id, "graphics"); return; } var destCtx = ctx; if (destId != undefined) { // if there's a destination ID, that means we're drawing this image *onto* another image canvas var destCanvas = images[destId]; destCtx = destCanvas.getContext("2d"); } destCtx.drawImage(images[id], x * scale, y * scale, images[id].width, images[id].height); }; this.hasImage = function(id) { return images[id] != undefined; }; this.getImage = function(id) { return images[id]; }; this.deleteImage = function(id) { delete images[id]; delete imageFillColors[id]; }; this.getCanvas = function() { return canvas; }; this.clearCanvas = function(color) { bitsyLog("pal? " + palette.length + " / " + color, "graphics"); ctx.fillStyle = makeFillStyle(color); ctx.fillRect(0, 0, canvas.width, canvas.height); }; } /* LOGGING */ var DebugLogCategory = { // system input: false, sound: false, graphics: false, system: false, // engine bitsy: false, // editor editor: false, // tools room: false, tune: false, blip: false, }; var isLoggingVerbose = false; function bitsyLog(message, category) { if (!category) { category = “bitsy”; } var summary = category + “::” + message; if (DebugLogCategory[category] === true) { if (isLoggingVerbose) { console.group(summary); console.dir(message); console.group(“stack”) console.trace(); console.groupEnd(); console.groupEnd(); } else { console.log(summary); } } } /* GLOBALS */ var tilesize = 8; var mapsize = 16; var width = mapsize * tilesize; var height = mapsize * tilesize; var scale = 4; var textScale = 2; /* SYSTEM */ var updateInterval = null; var prevTime = 0; var deltaTime = 0; function initSystem() { prevTime = Date.now(); updateInterval = setInterval(updateSystem, 16); } function updateSystem() { var curTime = Date.now(); deltaTime = curTime – prevTime; // update all active processes for (var i = 0; i < processes.length; i++) { bitsy = processes[i].system; if (bitsy._active) { bitsyLog(bitsy._name + " img count: " + bitsy._graphics._images.length, "system"); var shouldContinue = bitsy._update(deltaTime); if (!shouldContinue) { // todo : do I really care about this _exit thing? if (bitsy._name != "bitsy") { bitsy._exit(); } } } } bitsy = mainProcess.system; prevTime = curTime; } function loadGame(canvas, gameData, defaultFontData) { bitsyLog("load!", "system"); // initialize bitsy system bitsy._attachCanvas(canvas); bitsy._write(bitsy._gameDataBlock, gameData); bitsy._write(bitsy._fontDataBlock, defaultFontData); bitsy._start(); } function quitGame() { // hack to press the menu button to force game over state bitsy._injectPreLoop = function() { bitsy._poke(bitsy._buttonBlock, bitsy.BTN_MENU, 1); }; // one last update to clean up (a little hacky to do this here?) bitsy._update(0); bitsy._exit(); // clean up this gross hack bitsy._injectPreLoop = null; } /* GRAPHICS */ var canvas; // can I get rid of these? var ctx; function attachCanvas(c) { // hack : tes tnew system bitsy._attachCanvas(c); // extra hacky canvas = bitsy._getCanvas(); ctx = bitsy._getContext(); } /* PROCESSES */ var processes = []; function addProcess(name) { var proc = {}; proc.system = new BitsySystem(name); processes.push(proc); return proc; } /* == SYSTEM v0.2 === */ function BitsySystem(name) { var self = this; if (!name) { name = "bitsy"; } // memory var memory = { blocks: [], changed: [] }; // input var input = new InputSystem(); // sound var sound = new SoundSystem(); var soundDurationIndex = 0; var soundFrequencyIndex = 1; var soundVolumeIndex = 2; var soundPulseIndex = 3; var maxVolume = 15; // graphics var graphics = new GraphicsSystem(); graphics.setScale(scale); graphics.setTextScale(textScale); var initialPaletteSize = 64; var tilePoolStart = null; var tilePoolSize = 512; // hack!!! (access for debugging) this._graphics = graphics; function updateTextScale() { // make sure the text scale matches the text mode var textMode = self._peek(modeBlock, 1); var textModeScale = (textMode === self.TXT_LOREZ) ? scale : textScale; if (graphics.getTextScale() != textModeScale) { graphics.setTextScale(textModeScale); memory.changed[self.TEXTBOX] = true; } } function updateInput() { // update input flags self._poke(self._buttonBlock, self.BTN_UP, (input.isKeyDown(input.Key.UP) || input.isKeyDown(input.Key.W) || input.swipeUp()) ? 1 : 0); self._poke(self._buttonBlock, self.BTN_DOWN, (input.isKeyDown(input.Key.DOWN) || input.isKeyDown(input.Key.S) || input.swipeDown()) ? 1 : 0); self._poke(self._buttonBlock, self.BTN_LEFT, (input.isKeyDown(input.Key.LEFT) || input.isKeyDown(input.Key.A) || input.swipeLeft()) ? 1 : 0); self._poke(self._buttonBlock, self.BTN_RIGHT, (input.isKeyDown(input.Key.RIGHT) || input.isKeyDown(input.Key.D) || input.swipeRight()) ? 1 : 0); self._poke(self._buttonBlock, self.BTN_OK, (input.anyKeyDown() || input.isTapReleased()) ? 1 : 0); self._poke(self._buttonBlock, self.BTN_MENU, (input.isRestartComboPressed()) ? 1 : 0); input.resetTapReleased(); } function updateSound(dt) { var changed0 = memory.changed[self.SOUND1]; var changed1 = memory.changed[self.SOUND2]; // update sound channel timers var timer0 = self._peek(self.SOUND1, soundDurationIndex); timer0 -= dt; if (timer0 0) { self._poke(self.SOUND1, soundVolumeIndex, 0); changed0 = true; } } self._poke(self.SOUND1, soundDurationIndex, timer0); var timer1 = self._peek(self.SOUND2, soundDurationIndex); timer1 -= dt; if (timer1 0) { self._poke(self.SOUND2, soundVolumeIndex, 0); changed1 = true; } } self._poke(self.SOUND2, soundDurationIndex, timer1); // send updated channel attributes to the sound system if (changed0) { sound.setPulse(0, self._peek(self.SOUND1, soundPulseIndex)); var freq = self._peek(self.SOUND1, soundFrequencyIndex); var freqHz = freq / 100; sound.setFrequency(0, freqHz); var volume = self._peek(self.SOUND1, soundVolumeIndex); volume = Math.max(0, Math.min(volume, maxVolume)); volumeNorm = (volume / maxVolume); sound.setVolume(0, volumeNorm); } if (changed1) { sound.setPulse(1, self._peek(self.SOUND2, soundPulseIndex)); var freq = self._peek(self.SOUND2, soundFrequencyIndex); var freqHz = freq / 100; sound.setFrequency(1, freqHz); var volume = self._peek(self.SOUND2, soundVolumeIndex); volume = Math.max(0, Math.min(volume, maxVolume)); volumeNorm = (volume / maxVolume); sound.setVolume(1, volumeNorm); } } function updateGraphics() { if (self._enableGraphics === false) { return; } bitsyLog(“update graphics”, “system”); if (memory.changed[paletteBlock]) { graphics.setPalette(self._dump()[paletteBlock]); } if (tilePoolStart != null) { for (var i = 0; i 0 && h > 0) { bitsyLog(“textbox changed! ” + memory.changed[self.TEXTBOX] + ” ” + memory.changed[textboxAttributeBlock] + ” ” + w + ” ” + h, “system”); var useTextBoxScale = true; // todo : check mode here? graphics.createImage(self.TEXTBOX, w, h, self._dump()[self.TEXTBOX], useTextBoxScale); } } var mode = self._peek(modeBlock, 0); if (mode === self.GFX_VIDEO) { if (memory.changed[self.VIDEO]) { graphics.clearCanvas(0); // update screen image graphics.createImage(self.VIDEO, self.VIDEO_SIZE, self.VIDEO_SIZE, self._dump()[self.VIDEO]); // render screen onto canvas graphics.drawImage(self.VIDEO, 0, 0); } } else if (mode === self.GFX_MAP) { // redraw any changed layers var layers = self._getTileMapLayers(); var anyMapLayerChanged = false; for (var i = 0; i < layers.length; i++) { var layerId = layers[i]; if (memory.changed[layerId]) { // need to redraw this map layer anyMapLayerChanged = true; // clear layer canvas graphics.setImageFill(layerId, 0); // fill transparent graphics.createImage(layerId, self.VIDEO_SIZE, self.VIDEO_SIZE, []); // render tiles onto layer canvas var layerData = self._dump()[layerId]; for (var ty = 0; ty < self.MAP_SIZE; ty++) { for (var tx = 0; tx 0) { graphics.drawImage(tile, tx * self.TILE_SIZE, ty * self.TILE_SIZE, layerId); } } } } } // redraw the main canvas if (textboxChanged || anyMapLayerChanged) { bitsyLog(“map changed? ” + memory.changed[self.MAP1] + ” ” + memory.changed[self.MAP2], “system”); graphics.clearCanvas(0); for (var i = 0; i 0 && w > 0 && h > 0) { graphics.drawImage(self.TEXTBOX, x, y); } } } } /* == PRIVATE / DEBUG == */ this._name = name; this._active = false; this._attachCanvas = function(c) { graphics.attachCanvas(c, self.VIDEO_SIZE); }; this._getCanvas = graphics.getCanvas; this._getContext = graphics.getContext; this._start = function() { input.listen(graphics.getCanvas()); updateTextScale(); self._active = true; }; // hacky… this._startNoInput = function() { updateTextScale(); self._active = true; }; this._exit = function() { input.unlisten(graphics.getCanvas()); sound.mute(); self._active = false; }; // hacky…. this._injectPreLoop = null; this._injectPostDraw = null; this._update = function(dt) { var shouldContinue = false; updateInput(); // too hacky??? if (self._injectPreLoop) { self._injectPreLoop(); } // run main loop if (onLoopFunction) { shouldContinue = onLoopFunction(dt); } if (memory.changed[modeBlock]) { updateTextScale(); } // update output systems updateSound(dt); updateGraphics(); if (self._injectPostDraw) { self._injectPostDraw(); } // reset memory block changed flags for (var i = 0; i < memory.changed.length; i++) { memory.changed[i] = false; } // todo : should the _exit() call go in here? return shouldContinue; }; this._updateGraphics = updateGraphics; this._allocate = function(args) { // find next available block in range var next = (args && args.start) ? args.start : 0; var count = (args && args.max) ? args.max : -1; while (memory.blocks[next] != undefined && count != 0) { next++; count–; } if (memory.blocks[next] != undefined) { // couldn't find any available block return null; } if (args && args.str) { memory.blocks[next] = args.str; } else { var size = args && args.size ? args.size : 0; memory.blocks[next] = []; for (var i = 0; i < size; i++) { memory.blocks[next].push(0); } } memory.changed[next] = false; return next; }; this._free = function(block) { delete memory.blocks[block]; delete memory.changed[block]; }; this._peek = function(block, index) { var memoryBlock = memory.blocks[block]; if (typeof(memoryBlock) === "string") { return memoryBlock.charCodeAt(index); } else { return memoryBlock[index]; } }; this._poke = function(block, index, value) { var memoryBlock = memory.blocks[block]; if (typeof(memoryBlock) === "string") { memory.blocks[block] = memoryBlock.substring(0, index) + String.fromCharCode(value) + memoryBlock.substring(index + 1); } else { var value = parseInt(value); if (!isNaN(value)) { memoryBlock[index] = value; } } memory.changed[block] = true; }; this._read = function(block) { var memoryBlock = memory.blocks[block]; if (typeof(memoryBlock) === "string") { return memoryBlock; } else { var str = ""; for (var i = 0; i < memoryBlock.length; i++) { str += String.fromCharCode(memoryBlock[i]); } return str; } }; this._write = function(block, str) { var memoryBlock = memory.blocks[block]; if (typeof(memoryBlock) === "string") { memory.blocks[block] = str; } else { memory.blocks[block] = []; for (var i = 0; i 0; }; this.getGameData = function() { return self._read(gameDataBlock); }; this.getFontData = function() { return self._read(fontDataBlock); }; /* == GRAPHICS == */ this.graphicsMode = function(mode) { // todo : store the mode flag indices somewhere? if (mode != undefined) { self._poke(modeBlock, 0, mode); } return self._peek(modeBlock, 0); }; this.textMode = function(mode) { // todo : test whether the requested mode is supported! if (mode != undefined) { self._poke(modeBlock, 1, mode); } return self._peek(modeBlock, 1); }; this.color = function(color, r, g, b) { self._poke(paletteBlock, (color * 3) + 0, r); self._poke(paletteBlock, (color * 3) + 1, g); self._poke(paletteBlock, (color * 3) + 2, b); // mark all graphics as changed memory.changed[self.VIDEO] = true; memory.changed[self.TEXTBOX] = true; memory.changed[self.MAP1] = true; memory.changed[self.MAP2] = true; if (tilePoolStart != null) { for (var i = 0; i < tilePoolSize; i++) { if (memory.blocks[tilePoolStart + i] != undefined) { memory.changed[tilePoolStart + i] = true; } } } }; this.tile = function() { return self._allocate({ start: tilePoolStart, max: tilePoolSize, size: (self.TILE_SIZE * self.TILE_SIZE) }); }; this.delete = function(tile) { if (graphics.hasImage(tile)) { graphics.deleteImage(tile); } self._free(tile); }; this.fill = function(block, value) { var len = memory.blocks[block].length; for (var i = 0; i = tilePoolStart && block < (tilePoolStart + tilePoolSize)); // optimize rendering by notifying the graphics system what the fill color is for this image if (isImage) { graphics.setImageFill(block, value); } }; this.set = function(block, index, value) { self._poke(block, index, value); }; this.textbox = function(visible, x, y, w, h) { if (visible != undefined) { self._poke(textboxAttributeBlock, 0, (visible === true) ? 1 : 0); } if (x != undefined) { self._poke(textboxAttributeBlock, 1, x); } if (y != undefined) { self._poke(textboxAttributeBlock, 2, y); } var prevWidth = self._peek(textboxAttributeBlock, 3); var prevHeight = self._peek(textboxAttributeBlock, 4); if (w != undefined) { self._poke(textboxAttributeBlock, 3, w); } if (h != undefined) { self._poke(textboxAttributeBlock, 4, h); } if (w != undefined && h != undefined && (prevWidth != w || prevHeight != h)) { // re-allocate the textbox block (should I have a helper function for this?) memory.blocks[self.TEXTBOX] = []; for (var i = 0; i < (w * h); i++) { memory.blocks[self.TEXTBOX].push(0); } memory.changed[self.TEXTBOX] = true; } }; /* == SOUND == */ // duration is in milliseconds (ms) this.sound = function(channel, duration, frequency, volume, pulse) { self._poke(channel, soundDurationIndex, duration); self._poke(channel, soundFrequencyIndex, frequency); self._poke(channel, soundVolumeIndex, volume); self._poke(channel, soundPulseIndex, pulse); }; // frequency is in decihertz (dHz) this.frequency = function(channel, frequency) { self._poke(channel, soundFrequencyIndex, frequency); }; // volume: min = 0, max = 15 this.volume = function(channel, volume) { self._poke(channel, soundVolumeIndex, volume); }; /* == EVENTS == */ this.loop = function(fn) { onLoopFunction = fn; }; /* == INTERNAL == */ // initialize memory blocks var gameDataBlock = this._allocate({ str: "" }); var fontDataBlock = this._allocate({ str: "" }); this.VIDEO = this._allocate({ size: self.VIDEO_SIZE * self.VIDEO_SIZE }); this.TEXTBOX = this._allocate(); this.MAP1 = this._allocate({ size: self.MAP_SIZE * self.MAP_SIZE }); tileMapLayers.push(this.MAP1); this.MAP2 = this._allocate({ size: self.MAP_SIZE * self.MAP_SIZE }); tileMapLayers.push(this.MAP2); var paletteBlock = this._allocate({ size: initialPaletteSize * 3 }); var buttonBlock = this._allocate({ size: 8 }); this.SOUND1 = this._allocate({ size: 4 }); this.SOUND2 = this._allocate({ size: 4 }); var modeBlock = this._allocate({ size: 8 }); var textboxAttributeBlock = this._allocate({ size: 8 }); tilePoolStart = (textboxAttributeBlock + 1); // access for debugging this._gameDataBlock = gameDataBlock; this._fontDataBlock = fontDataBlock; this._buttonBlock = buttonBlock; // events var onLoopFunction = null; } var mainProcess = addProcess(); var bitsy = mainProcess.system; /* BITSY VERSION */ // is this the right place for this to live? var version = { major: 8, // major changes minor: 4, // smaller changes devBuildPhase: “RELEASE”, }; function getEngineVersion() { return version.major + “.” + version.minor; } /* TEXT CONSTANTS */ var titleDialogId = “title”; // todo : where should this be stored? var tileColorStartIndex = 16; var TextDirection = { LeftToRight : “LTR”, RightToLeft : “RTL” }; var defaultFontName = “ascii_small”; /* TUNE CONSTANTS */ var barLength = 16; // sixteenth notes var minTuneLength = 1; var maxTuneLength = 16; // chromatic notes var Note = { NONE : -1, C : 0, // C C_SHARP : 1, // C sharp / D flat D : 2, // D D_SHARP : 3, // D sharp / E flat E : 4, // E F : 5, // F F_SHARP : 6, // F sharp / G flat G : 7, // G G_SHARP : 8, // G sharp / A flat A : 9, // A A_SHARP : 10, // A sharp / B flat B : 11, // B COUNT : 12 }; // solfa notes var Solfa = { NONE : -1, D : 0, // Do R : 1, // Re M : 2, // Mi F : 3, // Fa S : 4, // Sol L : 5, // La T : 6, // Ti COUNT : 7 }; var Octave = { NONE: -1, 2: 0, 3: 1, 4: 2, // octave 4: middle C octave 5: 3, COUNT: 4 }; var Tempo = { SLW: 0, // slow MED: 1, // medium FST: 2, // fast XFST: 3 // extra fast (aka turbo) }; var SquareWave = { P8: 0, // pulse 1 / 8 P4: 1, // pulse 1 / 4 P2: 2, // pulse 1 / 2 COUNT: 3 }; var ArpeggioPattern = { OFF: 0, UP: 1, // ascending triad chord DWN: 2, // descending triad chord INT5: 3, // 5 step interval INT8: 4 // 8 setp interval }; function createWorldData() { return { room : {}, tile : {}, sprite : {}, item : {}, dialog : {}, end : {}, // pre-7.0 ending data for backwards compatibility palette : { // start off with a default palette “default” : { name : “default”, colors : [[0,0,0],[255,255,255],[255,255,255]] } }, variable : {}, tune : {}, blip : {}, versionNumberFromComment : -1, // -1 indicates no version information found fontName : defaultFontName, textDirection : TextDirection.LeftToRight, flags : createDefaultFlags(), names : {}, // source data for all drawings (todo: better name?) drawings : {}, }; } // creates a drawing data structure with default property values for the type function createDrawingData(type, id) { // the avatar’s drawing id still uses the sprite prefix (for back compat) var drwId = (type === “AVA” ? “SPR” : type) + “_” + id; var drawingData = { type : type, id : id, name : null, drw : drwId, col : (type === “TIL”) ? 1 : 2, // foreground color bgc : 0, // background color animation : { isAnimated : false, frameIndex : 0, frameCount : 1, }, }; // add type specific properties if (type === “TIL”) { // default null value indicates it can vary from room to room (original version) drawingData.isWall = null; } if (type === “AVA” || type === “SPR”) { // default sprite location is “offstage” drawingData.room = null; drawingData.x = -1; drawingData.y = -1; drawingData.inventory = {}; } if (type === “AVA” || type === “SPR” || type === “ITM”) { drawingData.dlg = null; drawingData.blip = null; } return drawingData; } function createTuneData(id) { var tuneData = { id : id, name : null, melody : [], harmony : [], key: null, // a null key indicates a chromatic scale (all notes enabled) tempo: Tempo.MED, instrumentA : SquareWave.P2, instrumentB : SquareWave.P2, arpeggioPattern : ArpeggioPattern.OFF, }; return tuneData; } function createTuneBarData() { var bar = []; for (var i = 0; i < barLength; i++) { bar.push({ beats: 0, note: Note.C, octave: Octave[4] }); } return bar; } function createTuneKeyData() { var key = { notes: [], // mapping of the solfa scale degrees to chromatic notes scale: [] // list of solfa notes that are enabled for this key }; // initialize notes for (var i = 0; i < Solfa.COUNT; i++) { key.notes.push(Note.NONE); } return key; } function createBlipData(id) { var blipData = { id: id, name: null, pitchA: { beats: 0, note: Note.C, octave: Octave[4] }, pitchB: { beats: 0, note: Note.C, octave: Octave[4] }, pitchC: { beats: 0, note: Note.C, octave: Octave[4] }, envelope: { attack: 0, // attack time in ms decay: 0, // decay time in ms sustain: 0, // sustain volume length: 0, // sustain time in ms release: 0 // release time in ms }, beat : { time: 0, // time in ms between pitch changes delay: 0 // time in ms *before* first pitch change }, instrument: SquareWave.P2, doRepeat: false // TODO : consider for future update // doSlide: false, }; return blipData; } function createDefaultFlags() { return { // version VER_MAJ: -1, // major version number (-1 = no version information found) VER_MIN: -1, // minor version number (-1 = no version information found) // compatibility ROOM_FORMAT: 0, // 0 = non-comma separated (original), 1 = comma separated (default) DLG_COMPAT: 0, // 0 = default dialog behavior, 1 = pre-7.0 dialog behavior // config TXT_MODE: 0 // 0 = HIREZ (2x – default), 1 = LOREZ (1x) }; } function createDialogData(id) { return { src : "", name : null, id : id, }; } function parseWorld(file) { bitsy.log("create world data"); var world = createWorldData(); bitsy.log("init parse state"); var parseState = { lines : file.split("\n"), index : 0, spriteStartLocations : {} }; bitsy.log("start reading lines"); while (parseState.index < parseState.lines.length) { var i = parseState.index; var lines = parseState.lines; var curLine = lines[i]; // bitsy.log("LN " + i + " xx " + curLine); if (i == 0) { i = parseTitle(parseState, world); } else if (curLine.length <= 0 || curLine.charAt(0) === "#") { // collect version number from a comment (hacky but required for pre-8.0 compatibility) if (curLine.indexOf("# BITSY VERSION ") != -1) { world.versionNumberFromComment = parseFloat(curLine.replace("# BITSY VERSION ", "")); } //skip blank lines & comments i++; } else if (getType(curLine) === "PAL") { i = parsePalette(parseState, world); } else if (getType(curLine) === "ROOM" || getType(curLine) === "SET") { // SET for back compat i = parseRoom(parseState, world); } else if (getType(curLine) === "TIL") { i = parseTile(parseState, world); } else if (getType(curLine) === "SPR") { i = parseSprite(parseState, world); } else if (getType(curLine) === "ITM") { i = parseItem(parseState, world); } else if (getType(curLine) === "DLG") { i = parseDialog(parseState, world); } else if (getType(curLine) === "END") { // parse endings for back compat i = parseEnding(parseState, world); } else if (getType(curLine) === "VAR") { i = parseVariable(parseState, world); } else if (getType(curLine) === "DEFAULT_FONT") { i = parseFontName(parseState, world); } else if (getType(curLine) === "TEXT_DIRECTION") { i = parseTextDirection(parseState, world); } else if (getType(curLine) === "FONT") { i = parseFontData(parseState, world); } else if (getType(curLine) === "TUNE") { i = parseTune(parseState, world); } else if (getType(curLine) === "BLIP") { i = parseBlip(parseState, world); } else if (getType(curLine) === "!") { i = parseFlag(parseState, world); } else { i++; } parseState.index = i; } world.names = createNameMapsForWorld(world); placeSprites(parseState, world); if ((world.flags.VER_MAJ <= -1 || world.flags.VER_MIN -1) { var versionNumberStr = “” + world.versionNumberFromComment; versionNumberStr = versionNumberStr.split(“.”); world.flags.VER_MAJ = parseFloat(versionNumberStr[0]); world.flags.VER_MIN = parseFloat(versionNumberStr[1]); } // starting in version v7.0, there were two major changes to dialog behavior: // 1) sprite dialog was no longer implicitly linked by the sprite and dialog IDs matching // (see this commit: 5e1adb29faad4e50603c689d2dac143074117b4e) // 2) ending dialogs no longer had their own world data type (“END”) // for the v7.x versions I tried to automatically convert old dialog to the new format, // however, that process can be unreliable and lead to weird bugs. // with v8.0 and above I will no longer attempt to convert old files, and instead will use // a flag to indicate files that need to use the backwards compatible behavior – // this is more reliable & configurable (at the cost of making pre-7.0 games a bit harder to edit) if (world.flags.VER_MAJ < 7) { world.flags.DLG_COMPAT = 1; } return world; } function parseTitle(parseState, world) { var i = parseState.index; var lines = parseState.lines; var results; if (scriptUtils) { results = scriptUtils.ReadDialogScript(lines,i); } else { results = { script: lines[i], index: (i + 1) }; } world.dialog[titleDialogId] = createDialogData(titleDialogId); world.dialog[titleDialogId].src = results.script; i = results.index; i++; return i; } function parsePalette(parseState, world) { var i = parseState.index; var lines = parseState.lines; var id = getId(lines[i]); i++; var colors = []; var name = null; while (i 0) { //look for empty line var args = lines[i].split(” “); if (args[0] === “NAME”) { name = lines[i].split(/\s(.+)/)[1]; } else { var col = []; lines[i].split(“,”).forEach(function(i) { col.push(parseInt(i)); }); colors.push(col); } i++; } world.palette[id] = { id : id, name : name, colors : colors }; return i; } function createRoomData(id) { return { id: id, name: null, tilemap: [], walls: [], exits: [], endings: [], items: [], pal: null, ava: null, tune: “0” }; } function parseRoom(parseState, world) { var i = parseState.index; var lines = parseState.lines; var id = getId(lines[i]); var roomData = createRoomData(id); i++; // create tile map if (world.flags.ROOM_FORMAT === 0) { // old way: no commas, single char tile ids var end = i + bitsy.MAP_SIZE; var y = 0; for (; i < end; i++) { roomData.tilemap.push([]); for (x = 0; x < bitsy.MAP_SIZE; x++) { roomData.tilemap[y].push(lines[i].charAt(x)); } y++; } } else if (world.flags.ROOM_FORMAT === 1) { // new way: comma separated, multiple char tile ids var end = i + bitsy.MAP_SIZE; var y = 0; for (; i < end; i++) { roomData.tilemap.push([]); var lineSep = lines[i].split(","); for (x = 0; x < bitsy.MAP_SIZE; x++) { roomData.tilemap[y].push(lineSep[x]); } y++; } } while (i 0) { //look for empty line // bitsy.log(getType(lines[i])); if (getType(lines[i]) === “SPR”) { /* NOTE SPRITE START LOCATIONS */ var sprId = getId(lines[i]); if (sprId.indexOf(“,”) == -1 && lines[i].split(” “).length >= 3) { //second conditional checks for coords /* PLACE A SINGLE SPRITE */ var sprCoord = lines[i].split(” “)[2].split(“,”); parseState.spriteStartLocations[sprId] = { room : id, x : parseInt(sprCoord[0]), y : parseInt(sprCoord[1]) }; } else if ( world.flags.ROOM_FORMAT == 0 ) { // TODO: right now this shortcut only works w/ the old comma separate format /* PLACE MULTIPLE SPRITES*/ //Does find and replace in the tilemap (may be hacky, but its convenient) var sprList = sprId.split(“,”); for (row in roomData.tilemap) { for (s in sprList) { var col = roomData.tilemap[row].indexOf( sprList[s] ); //if the sprite is in this row, replace it with the “null tile” and set its starting position if (col != -1) { roomData.tilemap[row][col] = “0”; parseState.spriteStartLocations[ sprList[s] ] = { room : id, x : parseInt(col), y : parseInt(row) }; } } } } } else if (getType(lines[i]) === “ITM”) { var itmId = getId(lines[i]); var itmCoord = lines[i].split(” “)[2].split(“,”); var itm = { id: itmId, x : parseInt(itmCoord[0]), y : parseInt(itmCoord[1]) }; roomData.items.push( itm ); } else if (getType(lines[i]) === “WAL”) { /* DEFINE COLLISIONS (WALLS) */ roomData.walls = getId(lines[i]).split(“,”); } else if (getType(lines[i]) === “EXT”) { /* ADD EXIT */ var exitArgs = lines[i].split(” “); //arg format: EXT 10,5 M 3,2 [AVA:7 LCK:a,9] [AVA 7 LCK a 9] var exitCoords = exitArgs[1].split(“,”); var destName = exitArgs[2]; var destCoords = exitArgs[3].split(“,”); var ext = { x : parseInt(exitCoords[0]), y : parseInt(exitCoords[1]), dest : { room : destName, x : parseInt(destCoords[0]), y : parseInt(destCoords[1]) }, transition_effect : null, dlg: null, }; // optional arguments var exitArgIndex = 4; while (exitArgIndex 1; // read other properties while (i 0) { // look for empty line if (getType(lines[i]) === “COL”) { tileData.col = parseInt(getId(lines[i])); } else if (getType(lines[i]) === “BGC”) { var bgcId = getId(lines[i]); if (bgcId === “*”) { // transparent background tileData.bgc = (-1 * tileColorStartIndex); } else { tileData.bgc = parseInt(bgcId); } } else if (getType(lines[i]) === “NAME”) { /* NAME */ tileData.name = getNameArg(lines[i]); } else if (getType(lines[i]) === “WAL”) { var wallArg = getArg(lines[i], 1); if (wallArg === “true”) { tileData.isWall = true; } else if (wallArg === “false”) { tileData.isWall = false; } } i++; } // store tile data world.tile[id] = tileData; return i; } function parseSprite(parseState, world) { var i = parseState.index; var lines = parseState.lines; var id = getId(lines[i]); var type = (id === “A”) ? “AVA” : “SPR”; var spriteData = createDrawingData(type, id); // bitsy.log(spriteData); i++; // read & store sprite image source i = parseDrawingCore(lines, i, spriteData.drw, world); // update animation info spriteData.animation.frameCount = getDrawingFrameCount(world, spriteData.drw); spriteData.animation.isAnimated = spriteData.animation.frameCount > 1; // read other properties while (i 0) { // look for empty line if (getType(lines[i]) === “COL”) { /* COLOR OFFSET INDEX */ spriteData.col = parseInt(getId(lines[i])); } else if (getType(lines[i]) === “BGC”) { /* BACKGROUND COLOR */ var bgcId = getId(lines[i]); if (bgcId === “*”) { // transparent background spriteData.bgc = (-1 * tileColorStartIndex); } else { spriteData.bgc = parseInt(bgcId); } } else if (getType(lines[i]) === “POS”) { /* STARTING POSITION */ var posArgs = lines[i].split(” “); var roomId = posArgs[1]; var coordArgs = posArgs[2].split(“,”); parseState.spriteStartLocations[id] = { room : roomId, x : parseInt(coordArgs[0]), y : parseInt(coordArgs[1]) }; } else if(getType(lines[i]) === “DLG”) { spriteData.dlg = getId(lines[i]); } else if (getType(lines[i]) === “NAME”) { /* NAME */ spriteData.name = getNameArg(lines[i]); } else if (getType(lines[i]) === “ITM”) { /* ITEM STARTING INVENTORY */ var itemId = getId(lines[i]); var itemCount = parseFloat(getArg(lines[i], 2)); spriteData.inventory[itemId] = itemCount; } else if (getType(lines[i]) == “BLIP”) { var blipId = getId(lines[i]); spriteData.blip = blipId; } i++; } // store sprite data world.sprite[id] = spriteData; return i; } function parseItem(parseState, world) { var i = parseState.index; var lines = parseState.lines; var id = getId(lines[i]); var itemData = createDrawingData(“ITM”, id); i++; // read & store item image source i = parseDrawingCore(lines, i, itemData.drw, world); // update animation info itemData.animation.frameCount = getDrawingFrameCount(world, itemData.drw); itemData.animation.isAnimated = itemData.animation.frameCount > 1; // read other properties while (i 0) { // look for empty line if (getType(lines[i]) === “COL”) { /* COLOR OFFSET INDEX */ itemData.col = parseInt(getArg(lines[i], 1)); } else if (getType(lines[i]) === “BGC”) { /* BACKGROUND COLOR */ var bgcId = getId(lines[i]); if (bgcId === “*”) { // transparent background itemData.bgc = (-1 * tileColorStartIndex); } else { itemData.bgc = parseInt(bgcId); } } else if (getType(lines[i]) === “DLG”) { itemData.dlg = getId(lines[i]); } else if (getType(lines[i]) === “NAME”) { /* NAME */ itemData.name = getNameArg(lines[i]); } else if (getType(lines[i]) == “BLIP”) { var blipId = getId(lines[i]); itemData.blip = blipId; } i++; } // store item data world.item[id] = itemData; return i; } function parseDrawingCore(lines, i, drwId, world) { var frameList = []; //init list of frames frameList.push( [] ); //init first frame var frameIndex = 0; var y = 0; while (y < bitsy.TILE_SIZE) { var line = lines[i + y]; var row = []; for (x = 0; x “) { // start next frame! frameList.push([]); frameIndex++; //start the count over again for the next frame i++; y = 0; } } } storeDrawingData(world, drwId, frameList); return i; } function parseDialog(parseState, world) { var i = parseState.index; var lines = parseState.lines; // hacky but I need to store this so I can set the name below var id = getId(lines[i]); i = parseScript(lines, i, world.dialog); if (i 0 && getType(lines[i]) === “NAME”) { world.dialog[id].name = getNameArg(lines[i]); i++; } return i; } // keeping this around to parse old files where endings were separate from dialogs function parseEnding(parseState, world) { var i = parseState.index; var lines = parseState.lines; return parseScript(lines, i, world.end); } function parseScript(lines, i, data) { var id = getId(lines[i]); i++; var results; if (scriptUtils) { results = scriptUtils.ReadDialogScript(lines,i); } else { results = { script: lines[i], index: (i + 1)}; } data[id] = createDialogData(id); data[id].src = results.script; i = results.index; return i; } function parseVariable(parseState, world) { var i = parseState.index; var lines = parseState.lines; var id = getId(lines[i]); i++; var value = lines[i]; i++; world.variable[id] = value; return i; } function parseFontName(parseState, world) { var i = parseState.index; var lines = parseState.lines; world.fontName = getArg(lines[i], 1); i++; return i; } function parseTextDirection(parseState, world) { var i = parseState.index; var lines = parseState.lines; world.textDirection = getArg(lines[i], 1); i++; return i; } function parseFontData(parseState, world) { var i = parseState.index; var lines = parseState.lines; // NOTE : we’re not doing the actual parsing here — // just grabbing the block of text that represents the font // and giving it to the font manager to use later var localFontName = getId(lines[i]); var localFontData = lines[i]; i++; while (i < lines.length && lines[i] != "") { localFontData += "\n" + lines[i]; i++; } var localFontFilename = localFontName + fontManager.GetExtension(); fontManager.AddResource( localFontFilename, localFontData ); return i; } function parseTune(parseState, world) { var i = parseState.index; var lines = parseState.lines; var id = getId(lines[i]); i++; var tuneData = createTuneData(id); var barIndex = 0; while (barIndex < maxTuneLength) { // MELODY var melodyBar = createTuneBarData(); var melodyNotes = lines[i].split(","); for (var j = 0; j < barLength; j++) { // default to a rest var pitch = { beats: 0, note: Note.C, octave: Octave[4], }; if (j 1) { var blipId = pitchSplit[1]; pitch.blip = blipId; } } melodyBar[j] = pitch; } tuneData.melody.push(melodyBar); i++; // HARMONY var harmonyBar = createTuneBarData(); var harmonyNotes = lines[i].split(“,”); for (var j = 0; j < barLength; j++) { // default to a rest var pitch = { beats: 0, note: Note.C, octave: Octave[4], }; if (j 1) { var blipId = pitchSplit[1]; pitch.blip = blipId; } } harmonyBar[j] = pitch; } tuneData.harmony.push(harmonyBar); i++; // check if there’s another bar after this one if (lines[i] === “>”) { // there is! increment the index barIndex++; i++; } else { // we’ve reached the end of the tune! barIndex = maxTuneLength; } } // parse other tune properties while (i 0) { // look for empty line if (getType(lines[i]) === “KEY”) { tuneData.key = createTuneKeyData(); var keyNotes = getArg(lines[i], 1); if (keyNotes) { keyNotes = keyNotes.split(“,”); for (var j = 0; j < keyNotes.length && j < tuneData.key.notes.length; j++) { var pitch = parsePitch(keyNotes[j]); tuneData.key.notes[j] = pitch.note; } } var keyScale = getArg(lines[i], 2); if (keyScale) { keyScale = keyScale.split(","); for (var j = 0; j Solfa.NONE && pitch.note = 1) { blipData.pitchA = parsePitch(notes[0]); } if (notes.length >= 2) { blipData.pitchB = parsePitch(notes[1]); } if (notes.length >= 3) { blipData.pitchC = parsePitch(notes[2]); } i++; // blip parameters while (i 0) { // look for empty line if (getType(lines[i]) === “ENV”) { // envelope blipData.envelope.attack = parseInt(getArg(lines[i], 1)); blipData.envelope.decay = parseInt(getArg(lines[i], 2)); blipData.envelope.sustain = parseInt(getArg(lines[i], 3)); blipData.envelope.length = parseInt(getArg(lines[i], 4)); blipData.envelope.release = parseInt(getArg(lines[i], 5)); } else if (getType(lines[i]) === “BEAT”) { // pitch beat length blipData.beat.time = parseInt(getArg(lines[i], 1)); blipData.beat.delay = parseInt(getArg(lines[i], 2)); } else if (getType(lines[i]) === “SQR”) { // square wave var squareWaveId = getArg(lines[i], 1); if (SquareWave[squareWaveId] != undefined) { blipData.instrument = SquareWave[squareWaveId]; } } // TODO : consider for future update // else if (getType(lines[i]) === “SLD”) { // // slide mode // if (parseInt(getArg(lines[i], 1)) === 1) { // blipData.doSlide = true; // } // } else if (getType(lines[i]) === “RPT”) { // repeat mode if (parseInt(getArg(lines[i], 1)) === 1) { blipData.doRepeat = true; } } else if (getType(lines[i]) === “NAME”) { var name = lines[i].split(/\s(.+)/)[1]; blipData.name = name; } i++; } world.blip[id] = blipData; return i; } function parsePitch(pitchStr) { var pitch = { beats: 1, note: Note.C, octave: Octave[4], }; var i; // beats var beatsToken = “”; for (i = 0; i 0) { pitch.beats = parseInt(beatsToken); } // note var noteType; var noteName = “”; if (i < pitchStr.length) { if (pitchStr[i] === pitchStr[i].toUpperCase()) { // uppercase letters represent chromatic notes noteType = Note; noteName += pitchStr[i]; i++; // check for sharp if (i < pitchStr.length && pitchStr[i] === "#") { noteName += "_SHARP"; i++; } } else { // lowercase letters represent solfa notes noteType = Solfa; noteName += pitchStr[i].toUpperCase(); i++; } } if (noteType != undefined && noteType[noteName] != undefined) { pitch.note = noteType[noteName]; } // octave var octaveToken = ""; if (i < pitchStr.length) { octaveToken += pitchStr[i]; } if (Octave[octaveToken] != undefined) { pitch.octave = Octave[octaveToken]; } return pitch; } function parseFlag(parseState, world) { var i = parseState.index; var lines = parseState.lines; var id = getId(lines[i]); var valStr = lines[i].split(" ")[2]; world.flags[id] = parseInt( valStr ); i++; return i; } function getDrawingFrameCount(world, drwId) { return world.drawings[drwId].length; } function storeDrawingData(world, drwId, drawingData) { world.drawings[drwId] = drawingData; } function placeSprites(parseState, world) { for (id in parseState.spriteStartLocations) { world.sprite[id].room = parseState.spriteStartLocations[id].room; world.sprite[id].x = parseState.spriteStartLocations[id].x; world.sprite[id].y = parseState.spriteStartLocations[id].y; } } function createNameMapsForWorld(world) { var nameMaps = {}; function createNameMap(objectStore) { var map = {}; for (id in objectStore) { if (objectStore[id].name != undefined && objectStore[id].name != null) { map[objectStore[id].name] = id; } } return map; } nameMaps.room = createNameMap(world.room); nameMaps.tile = createNameMap(world.tile); nameMaps.sprite = createNameMap(world.sprite); nameMaps.item = createNameMap(world.item); nameMaps.dialog = createNameMap(world.dialog); nameMaps.palette = createNameMap(world.palette); nameMaps.tune = createNameMap(world.tune); nameMaps.blip = createNameMap(world.blip); return nameMaps; } function getType(line) { return getArg(line,0); } function getId(line) { return getArg(line,1); } function getCoord(line,arg) { return getArg(line,arg).split(","); } function getArg(line,arg) { return line.split(" ")[arg]; } function getNameArg(line) { var name = line.split(/\s(.+)/)[1]; return name; } /* PITCH HELPER FUNCTIONS */ function pitchToSteps(pitch) { return (pitch.octave * Note.COUNT) + pitch.note; } function stepsToPitch(steps) { var pitch = { beats: 1, note: Note.C, octave: Octave[2], }; while (steps >= Note.COUNT) { pitch.octave = (pitch.octave + 1) % Octave.COUNT; steps -= Note.COUNT; } pitch.note += steps; // make sure pitch isn’t outside a valid range if (pitch.note = Note.COUNT) { pitch.note = Note.B; } if (pitch.octave = Octave.COUNT) { pitch.octave = Octave[5]; } return pitch; } function adjustPitch(pitch, stepDelta) { return stepsToPitch(pitchToSteps(pitch) + stepDelta); } function pitchDistance(pitchA, pitchB) { return pitchToSteps(pitchB) – pitchToSteps(pitchA); } function isMinPitch(pitch) { return pitchToSteps(pitch) = pitchToSteps({ note: Note.B, octave: Octave[5] }); } function SoundPlayer() { // frequencies (in hertz) for octave 0 (or is it octave 4?) var frequencies = [ 261.7, // middle C 277.2, 293.7, 311.2, 329.7, 349.3, 370.0, 392.0, 415.3, 440.0, 466.2, 493.9, ]; // tempos are calculated as the duration of a 16th note, rounded to the nearest millisecond var tempos = {}; tempos[Tempo.SLW] = 250; // 60bpm (adagio) tempos[Tempo.MED] = 188; // ~80bpm (andante) [exact would be 187.5 ms] tempos[Tempo.FST] = 125; // 120bpm (moderato) tempos[Tempo.XFST] = 94; // ~160bpm (allegro) [exact would be 93.75 ms] // arpeggio patterns expressed in scale degrees var arpeggioPattern = {}; arpeggioPattern[ArpeggioPattern.UP] = [0, 2, 4, 7]; arpeggioPattern[ArpeggioPattern.DWN] = [7, 4, 2, 0]; arpeggioPattern[ArpeggioPattern.INT5] = [0, 4]; arpeggioPattern[ArpeggioPattern.INT8] = [0, 7]; this.getArpeggioSteps = function(tune) { return arpeggioPattern[tune.arpeggioPattern]; }; function isPitchPlayable(pitch, key) { if (pitch.beats -1) && (key.notes[pitch.note] > Note.NONE) && (key.notes[pitch.note] = Solfa.COUNT) ? 1 : 0; return { beats: pitch.beats, octave: pitch.octave + octaveOffset, // todo : what about the scale limits? note: key.notes[(pitch.note % Solfa.COUNT)], blip: pitch.blip }; } function makePitchFrequency(pitch) { // todo : this clamp shouldn’t be required.. there’s a bug in the pitch shifting somewhere var note = Math.max(0, pitch.note); var octave = (pitch.octave != undefined ? pitch.octave : Octave[4]); var octaveMin = Octave[2]; var octaveMax = Octave[5]; // make sure octave is in valid range octave = Math.max(octaveMin, Math.min(octave, octaveMax)); var distFromMiddleC = octave – 2; var freq = frequencies[note] * Math.pow(2, distFromMiddleC); if (isNaN(freq)) { bitsy.log(“invalid frequency ” + pitch, “sound”); } return freq; } var maxVolume = 15; // todo : should this be a system constant? var noteVolume = 5; var curTune = null; var isTunePaused = false; var barIndex = -1; var curArpeggio = []; var beat16 = 0; var beat16Timer = 0; var beat16Index = 0; // special settings var isLooping = false; var isMelodyMuted = false; var maxBeatCount = null; var muteTimer = 0; // allow temporary muting of all notes function arpeggiateBar(bar, key, pattern) { var arpeggio = []; if (key != undefined && key != null && isPitchPlayable(bar[0], key)) { for (var i = 0; i < arpeggioPattern[pattern].length; i++) { var pitch = { beats: 1, note: bar[0].note + arpeggioPattern[pattern][i], octave: bar[0].octave }; arpeggio.push(pitchToChromatic(pitch, key)); } } for (var i = 0; i < arpeggio.length; i++) { bitsy.log(i + ": " + serializeNote(arpeggio[i].note)); } return arpeggio; }; function playNote(pitch, instrument, options) { if (pitch.beats <= 0) { return; } var channel = bitsy.SOUND1; if (options != undefined && options.channel != undefined) { channel = options.channel; } var key = null; if (options != undefined && options.key != undefined) { key = options.key; } var beatLen = beat16; if (options != undefined && options.beatLen != undefined) { beatLen = options.beatLen; } if (isPitchPlayable(pitch, key)) { var freq = makePitchFrequency(pitchToChromatic(pitch, key)); bitsy.sound(channel, (pitch.beats * beatLen), freq * 100, noteVolume, instrument); } } function sfxFrequencyAtTime(sfx, time) { var beatDelay = sfx.blip.beat.delay; var beatTime = sfx.blip.beat.time; var delta = Math.max(0, time – beatDelay) / beatTime; var pitchDelta = sfx.blip.doRepeat ? (delta % sfx.frequencies.length) : Math.min(delta, sfx.frequencies.length – 1); sfx.pitchIndex = Math.floor(pitchDelta); var curFreq = sfx.frequencies[sfx.pitchIndex]; // TODO : consider for future update // if (sfx.blip.doSlide) { // var nextPitchIndex = (sfx.pitchIndex + 1) % sfx.frequencies.length; // var nextFreq = sfx.frequencies[nextPitchIndex]; // var d = pitchDelta – sfx.pitchIndex; // curFreq = curFreq + ((nextFreq – curFreq) * d); // } return curFreq; } function sfxVolumeAtTime(sfx, time) { var volume = 0; // use envelope settings to calculate volume var attack = sfx.blip.envelope.attack; var decay = sfx.blip.envelope.decay; var length = sfx.blip.envelope.length; var release = sfx.blip.envelope.release; if (time < attack) { // attack var t = time / attack; volume = Math.floor(sfxPeakVolume * t); } else if (time < attack + decay) { // decay var t = (time – attack) / decay; var d = sfx.blip.envelope.sustain – sfxPeakVolume; volume = Math.floor(sfxPeakVolume + (d * t)); } else if (time < attack + decay + length) { // sustain volume = sfx.blip.envelope.sustain; } else if (time = sfx.duration) { sfx.timer = sfx.duration; } if (sfx.frequencies.length > 0) { // update pitch var prevPitchIndex = sfx.pitchIndex; var freq = sfxFrequencyAtTime(sfx, sfx.timer); if (prevPitchIndex != sfx.pitchIndex) { // pitch changed! bitsy.frequency(bitsy.SOUND1, freq * 100); } // update volume envelope bitsy.volume(bitsy.SOUND1, sfxVolumeAtTime(sfx, sfx.timer)); } if (sfx.timer >= sfx.duration) { // turn off sound bitsy.volume(bitsy.SOUND1, 0); activeSfx = null; } } if (isMusicPausedForBlip && !isAnyBlipPlaying) { isMusicPausedForBlip = false; } } function updateTune(dt) { if (curTune === undefined || curTune === null) { return; } beat16Timer += dt; if (muteTimer > 0) { muteTimer -= dt; } if (beat16Timer >= beat16) { beat16Timer = 0; beat16Index++; if (beat16Index >= 16) { beat16Index = 0; if (!isLooping) { barIndex = (barIndex + 1) % curTune.melody.length; if (curTune.arpeggioPattern != ArpeggioPattern.OFF && curTune.key != null) { curArpeggio = arpeggiateBar(curTune.harmony[barIndex], curTune.key, curTune.arpeggioPattern); } } } if (muteTimer 0) { // since they’re played on the same channel, any melody note will cancel a blip activeSfx = null; } if (pitchA.blip != undefined && pitchA.beats > 0) { playBlip(blip[pitchA.blip], { interruptMusic: false, pitch: pitchA, key: curTune.key }); } else { playNote(pitchA, curTune.instrumentA, { channel: bitsy.SOUND1, key: curTune.key }); } } if (curTune.arpeggioPattern === ArpeggioPattern.OFF) { // harmony note var pitchB = curTune.harmony[barIndex][beat16Index]; if (pitchB.blip != undefined && pitchB.beats > 0) { playBlip(blip[pitchB.blip], { interruptMusic: false, pitch: pitchB, key: curTune.key }); } else { playNote(pitchB, curTune.instrumentB, { channel: bitsy.SOUND2, key: curTune.key }); } } else { var arpPitch = curArpeggio[beat16Index % curArpeggio.length]; if (arpPitch != undefined && arpPitch.beats > 0) { playNote(arpPitch, curTune.instrumentB, { channel: bitsy.SOUND2, beatLen: beat16 }); } } } if (maxBeatCount != null && beat16Index >= (maxBeatCount – 1)) { // stop playback early curTune = null; } } } this.update = function(dt) { updateSfx(dt); if (!isTunePaused && !isMusicPausedForBlip) { updateTune(dt); } }; this.playTune = function(tune, options) { curTune = tune; beat16Timer = 0; beat16Index = -1; barIndex = 0; isLooping = false; isMelodyMuted = false; maxBeatCount = null; // special options for the editor if (options != undefined) { if (options.barIndex != undefined) { barIndex = options.barIndex; } if (options.loop != undefined) { isLooping = options.loop; } if (options.melody != undefined) { isMelodyMuted = !options.melody; } if (options.beatCount != undefined) { maxBeatCount = options.beatCount; } } // update tempo beat16 = tempos[curTune.tempo]; if (curTune.arpeggioPattern != ArpeggioPattern.OFF && curTune.key != null) { curArpeggio = arpeggiateBar(curTune.harmony[barIndex], curTune.key, curTune.arpeggioPattern); } }; this.isTunePlaying = function() { return curTune != null; }; this.getCurTuneId = function() { if (curTune) { return curTune.id; } return null; }; this.stopTune = function() { curTune = null; }; this.pauseTune = function() { isTunePaused = true; }; this.resumeTune = function() { isTunePaused = false; }; this.getBeat = function() { if (curTune == null) { return null; } return { bar : barIndex, beat : beat16Index, }; }; this.getBlipState = function() { return activeSfx; }; this.playNote = function(pitch, instrument, channel, key) { beat16 = tempos[Tempo.SLW]; muteTimer = beat16; playNote(pitch, instrument, { channel: channel, key: key }); }; this.setTempo = function(tempo) { beat16 = tempos[tempo]; }; this.setLooping = function(looping) { isLooping = looping; }; /* SOUND EFFECTS */ var sfxPeakVolume = 10; // todo : is this a good value? var activeSfx = null; var isMusicPausedForBlip = false; function createSfxState(blip, pitch, isPitchRandomized) { // bitsy.log(“init sfx blip: ” + blip.id); var sfxState = { blip : blip, pitchIndex : -1, frequencies : [], timer : 0, duration : 0, }; // is it weird to track this both in the system *AND* the engine? sfxState.duration = (blip.envelope.attack + blip.envelope.decay + blip.envelope.length + blip.envelope.release); // adjust starting pitch var step = 0; if (pitch != null) { step = pitchDistance(blip.pitchA, pitch); } else if (isPitchRandomized > 0) { step = Math.floor(Math.random() * 6); } if (blip.pitchA.beats > 0) { sfxState.frequencies.push(makePitchFrequency(adjustPitch(blip.pitchA, step))); } if (blip.pitchB.beats > 0) { sfxState.frequencies.push(makePitchFrequency(adjustPitch(blip.pitchB, step))); } if (blip.pitchC.beats > 0) { sfxState.frequencies.push(makePitchFrequency(adjustPitch(blip.pitchC, step))); } return sfxState; } function playBlip(blip, options) { // default to pausing music while the blip plays (except when playing a blip as *part* of music) isMusicPausedForBlip = (options === undefined || options.interruptMusic === undefined) ? true : options.interruptMusic; // always play blips on channel 1 var channel = bitsy.SOUND1; // other options var pitch = (options === undefined || options.pitch === undefined) ? null : options.pitch; var isPitchRandomized = (options === undefined || options.isPitchRandomized === undefined) ? false : options.isPitchRandomized; var key = (options != undefined && options.key != undefined) ? options.key : null; activeSfx = createSfxState(blip, pitchToChromatic(pitch, key), isPitchRandomized); bitsy.log(“play blip: ” + activeSfx.frequencies); bitsy.sound( channel, activeSfx.duration * 10, // HACK : mult by 10 is to avoid accidentally turning off early activeSfx.frequencies.length > 0 ? (activeSfx.frequencies[0] * 100) : 0, 0, // volume activeSfx.blip.instrument); }; this.playBlip = playBlip; this.isBlipPlaying = function() { return isMusicPausedForBlip; // todo : rename this variable? }; // todo : should any of this stuff be moved into the tool code? this.sampleBlip = function(blip, sampleCount) { var sfx = createSfxState(blip, null, false); var minFreq = makePitchFrequency({ note: Note.C, octave: Octave[2] }); var maxFreq = makePitchFrequency({ note: Note.B, octave: Octave[5] }); // sample the frequency of the sound var frequencySamples = []; for (var i = 0; i 0) { var t = Math.floor((i / sampleCount) * sfx.duration); // get frequency at time var freq = sfxFrequencyAtTime(sfx, t); // normalize the sample freq = freq / (maxFreq – minFreq); frequencySamples.push(freq); } else { frequencySamples.push(0); } } // sample the volume envelope var amplitudeSamples = []; for (var i = 0; i < sampleCount; i++) { var t = Math.floor((i / sampleCount) * sfx.duration); amplitudeSamples.push(sfxVolumeAtTime(sfx, t) / maxVolume); } return { frequencies: frequencySamples, amplitudes: amplitudeSamples }; }; } /* TODO: – can I simplify this more now that I’ve removed the external resources stuff? */ function FontManager(packagedFontNames) { var self = this; var fontExtension = “.bitsyfont”; this.GetExtension = function() { return fontExtension; } // place to store font data var fontResources = {}; // load fonts from the editor if (packagedFontNames != undefined && packagedFontNames != null && packagedFontNames.length > 0 && Resources != undefined && Resources != null) { for (var i = 0; i < packagedFontNames.length; i++) { var filename = packagedFontNames[i]; fontResources[filename] = Resources[filename]; } } // manually add resource this.AddResource = function(filename, fontdata) { fontResources[filename] = fontdata; } this.ContainsResource = function(filename) { return fontResources[filename] != null; } function GetData(fontName) { return fontResources[fontName + fontExtension]; } this.GetData = GetData; function Create(fontData) { return new Font(fontData); } this.Create = Create; this.Get = function(fontName) { var fontData = self.GetData(fontName); return self.Create(fontData); } function Font(fontData) { bitsy.log("create font"); var name = "unknown"; var width = 6; // default size so if you have NO font or an invalid font it displays boxes var height = 8; var chardata = {}; // create invalid char data at default size in case the font is missing var invalidCharData = {}; updateInvalidCharData(); this.getName = function() { return name; } this.getData = function() { return chardata; } this.getWidth = function() { return width; } this.getHeight = function() { return height; } this.hasChar = function(char) { var codepoint = char.charCodeAt(0); return chardata[codepoint] != null; } this.getChar = function(char) { var codepoint = char.charCodeAt(0); if (chardata[codepoint] != null) { return chardata[codepoint]; } else { return invalidCharData; } } this.allCharCodes = function() { var codeList = []; for (var code in chardata) { codeList.push(code); } return codeList; } function createCharData() { return { width: width, height: height, offset: { x: 0, y: 0 }, spacing: width, data: [], }; } function updateInvalidCharData() { invalidCharData = createCharData(); for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { if (x < width-1 && y < height-1) { invalidCharData.data.push(1); } else { invalidCharData.data.push(0); } } } } function parseFont(fontData) { if (fontData == null) { return; } bitsy.log("split font lines"); // NOTE: this is where we run out of memory – split creates a lot of memory issues // var lines = fontData.split("\n"); bitsy.log("after split lines"); var isReadingChar = false; var isReadingCharProperties = false; var curCharLineCount = 0; var curCharCode = 0; var lineStart = 0; var lineEnd = fontData.indexOf("\n", lineStart) != -1 ? fontData.indexOf("\n", lineStart) : fontData.length; // for (var i = 0; i < lines.length; i++) { // var line = lines[i]; while (lineStart < fontData.length) { var line = fontData.substring(lineStart, lineEnd); // bitsy.log("parse font xx " + line); if (line[0] === "#") { // skip comment lines } else if (!isReadingChar) { // READING NON CHARACTER DATA LINE var args = line.split(" "); if (args[0] == "FONT") { name = args[1]; } else if (args[0] == "SIZE") { width = parseInt(args[1]); height = parseInt(args[2]); } else if (args[0] == "CHAR") { isReadingChar = true; isReadingCharProperties = true; curCharLineCount = 0; curCharCode = parseInt(args[1]); chardata[curCharCode] = createCharData(); } } else { // CHAR PROPERTIES if (isReadingCharProperties) { var args = line.split(" "); if (args[0].indexOf("CHAR_") == 0) { // Sub-properties start with "CHAR_" if (args[0] == "CHAR_SIZE") { // Custom character size – overrides the default character size for the font chardata[curCharCode].width = parseInt(args[1]); chardata[curCharCode].height = parseInt(args[2]); chardata[curCharCode].spacing = parseInt(args[1]); // HACK : assumes CHAR_SIZE is always declared first } else if (args[0] == "CHAR_OFFSET") { // Character offset – shift the origin of the character on the X or Y axis chardata[curCharCode].offset.x = parseInt(args[1]); chardata[curCharCode].offset.y = parseInt(args[2]); } else if (args[0] == "CHAR_SPACING") { // Character spacing: // specify total horizontal space taken up by the character // lets chars take up more or less space on a line than its bitmap does chardata[curCharCode].spacing = parseInt(args[1]); } } else { isReadingCharProperties = false; } } // CHAR DATA if (!isReadingCharProperties) { // READING CHARACTER DATA LINE for (var j = 0; j = chardata[curCharCode].height) { isReadingChar = false; } } } lineStart = lineEnd + 1; lineEnd = fontData.indexOf(“\n”, lineStart) != -1 ? fontData.indexOf(“\n”, lineStart) : fontData.length; } // re-init invalid character box at the actual font size once it’s loaded updateInvalidCharData(); } bitsy.log(“parse font”); parseFont(fontData); bitsy.log(“create font”); } } // FontManager var TransitionManager = function() { var transitionStart = null; var transitionEnd = null; var isTransitioning = false; var transitionTime = 0; // milliseconds var minStepTime = 125; // cap the frame rate var curStep = 0; this.BeginTransition = function(startRoom, startX, startY, endRoom, endX, endY, effectName) { bitsy.log(“— START ROOM TRANSITION —“); curEffect = effectName; var tmpRoom = player().room; var tmpX = player().x; var tmpY = player().y; if (transitionEffects[curEffect].showPlayerStart) { player().room = startRoom; player().x = startX; player().y = startY; } else { player().room = “_transition_none”; // kind of hacky!! } var startRoomPixels = createRoomPixelBuffer(room[startRoom]); var startPalette = getPal(room[startRoom].pal); var startImage = new PostProcessImage(startRoomPixels); transitionStart = new TransitionInfo(startImage, startPalette, startX, startY); if (transitionEffects[curEffect].showPlayerEnd) { player().room = endRoom; player().x = endX; player().y = endY; } else { player().room = “_transition_none”; } var endRoomPixels = createRoomPixelBuffer(room[endRoom]); var endPalette = getPal(room[endRoom].pal); var endImage = new PostProcessImage(endRoomPixels); transitionEnd = new TransitionInfo(endImage, endPalette, endX, endY); isTransitioning = true; transitionTime = 0; curStep = 0; player().room = endRoom; player().x = endX; player().y = endY; bitsy.graphicsMode(bitsy.GFX_VIDEO); } this.UpdateTransition = function(dt) { if (!isTransitioning) { return; } transitionTime += dt; var maxStep = transitionEffects[curEffect].stepCount; if (transitionTime >= minStepTime) { curStep++; var step = curStep; bitsy.log(“transition step ” + step); if (transitionEffects[curEffect].paletteEffectFunc) { var colors = transitionEffects[curEffect].paletteEffectFunc(transitionStart, transitionEnd, (step / maxStep)); updatePaletteWithTileColors(colors); } bitsy.fill(bitsy.VIDEO, tileColorStartIndex); for (var y = 0; y < bitsy.VIDEO_SIZE; y++) { for (var x = 0; x = (maxStep – 1)) { isTransitioning = false; transitionTime = 0; transitionStart = null; transitionEnd = null; curStep = 0; if (transitionCompleteCallback != null) { transitionCompleteCallback(); } transitionCompleteCallback = null; bitsy.graphicsMode(bitsy.GFX_MAP); } } this.IsTransitionActive = function() { return isTransitioning; } // todo : should this be part of the constructor? var transitionCompleteCallback = null; this.OnTransitionComplete = function(callback) { if (isTransitioning) { // TODO : safety check necessary? transitionCompleteCallback = callback; } } var transitionEffects = {}; var curEffect = “none”; this.RegisterTransitionEffect = function(name, effect) { transitionEffects[name] = effect; } this.RegisterTransitionEffect(“none”, { showPlayerStart : false, showPlayerEnd : false, paletteEffectFunc : function() {}, pixelEffectFunc : function() {}, }); this.RegisterTransitionEffect(“fade_w”, { // TODO : have it linger on full white briefly? showPlayerStart : false, showPlayerEnd : true, stepCount : 6, pixelEffectFunc : function(start, end, pixelX, pixelY, delta) { return delta < 0.5 ? start.Image.GetPixel(pixelX, pixelY) : end.Image.GetPixel(pixelX, pixelY); }, paletteEffectFunc : function(start, end, delta) { var colors = []; if (delta < 0.5) { delta = delta / 0.5; for (var i = 0; i < start.Palette.length; i++) { colors.push(lerpColor(start.Palette[i], [255, 255, 255], delta)); } } else { delta = ((delta – 0.5) / 0.5); for (var i = 0; i < end.Palette.length; i++) { colors.push(lerpColor([255, 255, 255], end.Palette[i], delta)); } } return colors; }, }); this.RegisterTransitionEffect("fade_b", { showPlayerStart : false, showPlayerEnd : true, stepCount : 6, pixelEffectFunc : function(start, end, pixelX, pixelY, delta) { return delta < 0.5 ? start.Image.GetPixel(pixelX, pixelY) : end.Image.GetPixel(pixelX, pixelY); }, paletteEffectFunc : function(start, end, delta) { var colors = []; if (delta < 0.5) { delta = delta / 0.5; for (var i = 0; i < start.Palette.length; i++) { colors.push(lerpColor(start.Palette[i], [0, 0, 0], delta)); } } else { delta = ((delta – 0.5) / 0.5); for (var i = 0; i < end.Palette.length; i++) { colors.push(lerpColor([0, 0, 0], end.Palette[i], delta)); } } return colors; }, }); this.RegisterTransitionEffect("wave", { showPlayerStart : true, showPlayerEnd : true, stepCount : 12, pixelEffectFunc : function(start, end, pixelX, pixelY, delta) { var waveDelta = delta < 0.5 ? delta / 0.5 : 1 – ((delta – 0.5) / 0.5); var offset = (pixelY + (waveDelta * waveDelta * 0.2 * start.Image.Height)); var freq = 4; var size = 2 + (14 * waveDelta); pixelX += Math.floor(Math.sin(offset / freq) * size); if (pixelX = start.Image.Width) { pixelX -= start.Image.Width; } var curImage = delta < 0.5 ? start.Image : end.Image; return curImage.GetPixel(pixelX, pixelY); }, paletteEffectFunc : function(start, end, delta) { return delta < 0.5 ? start.Palette : end.Palette; }, }); this.RegisterTransitionEffect("tunnel", { showPlayerStart : true, showPlayerEnd : true, stepCount : 12, pixelEffectFunc : function(start, end, pixelX, pixelY, delta) { if (delta start.Image.Width * tunnelDelta) { return 0; } else { return start.Image.GetPixel(pixelX, pixelY); } } else if (delta end.Image.Width * tunnelDelta) { return 0; } else { return end.Image.GetPixel(pixelX, pixelY); } } }, paletteEffectFunc : function(start, end, delta) { return delta end.Palette.length) ? start.Palette.length : end.Palette.length; for (var i = 0; i < maxLength; i++) { if (i < start.Palette.length && i < end.Palette.length) { colors.push(lerpColor(start.Palette[i], end.Palette[i], delta)); } else if (i < start.Palette.length) { colors.push(lerpColor( start.Palette[i], end.Palette[end.Palette.length – 1], delta)); } else if (i = 0) { return start.Image.GetPixel(pixelX, slidePixelY); } else { slidePixelY += start.Image.Height; return end.Image.GetPixel(pixelX, slidePixelY); } }, paletteEffectFunc : lerpPalettes, }); this.RegisterTransitionEffect(“slide_d”, { showPlayerStart : false, showPlayerEnd : true, stepCount : 8, pixelEffectFunc : function(start, end, pixelX, pixelY, delta) { var pixelOffset = Math.floor(start.Image.Height * delta); var slidePixelY = pixelY + pixelOffset; if (slidePixelY = 0) { return start.Image.GetPixel(slidePixelX, pixelY); } else { slidePixelX += start.Image.Width; return end.Image.GetPixel(slidePixelX, pixelY); } }, paletteEffectFunc : lerpPalettes, }); this.RegisterTransitionEffect(“slide_r”, { showPlayerStart : false, showPlayerEnd : true, stepCount : 8, pixelEffectFunc : function(start, end, pixelX, pixelY, delta) { var pixelOffset = Math.floor(start.Image.Width * delta); var slidePixelX = pixelX + pixelOffset; if (slidePixelX < start.Image.Width) { return start.Image.GetPixel(slidePixelX, pixelY); } else { slidePixelX -= start.Image.Width; return end.Image.GetPixel(slidePixelX, pixelY); } }, paletteEffectFunc : lerpPalettes, }); // todo : move to Renderer()? function createRoomPixelBuffer(room) { var pixelBuffer = []; for (var i = 0; i < bitsy.VIDEO_SIZE * bitsy.VIDEO_SIZE; i++) { pixelBuffer.push(tileColorStartIndex); } var drawTileInPixelBuffer = function(sourceData, frameIndex, colorIndex, tx, ty, pixelBuffer) { var frameData = sourceData[frameIndex]; for (var y = 0; y < bitsy.TILE_SIZE; y++) { for (var x = 0; x < bitsy.TILE_SIZE; x++) { var color = tileColorStartIndex + (frameData[y][x] === 1 ? colorIndex : 0); pixelBuffer[(((ty * bitsy.TILE_SIZE) + y) * bitsy.VIDEO_SIZE) + ((tx * bitsy.TILE_SIZE) + x)] = color; } } } //draw tiles for (i in room.tilemap) { for (j in room.tilemap[i]) { var id = room.tilemap[i][j]; var x = parseInt(j); var y = parseInt(i); if (id != "0" && tile[id] != null) { drawTileInPixelBuffer( renderer.GetDrawingSource(tile[id].drw), tile[id].animation.frameIndex, tile[id].col, x, y, pixelBuffer); } } } //draw items for (var i = 0; i < room.items.length; i++) { var itm = room.items[i]; drawTileInPixelBuffer( renderer.GetDrawingSource(item[itm.id].drw), item[itm.id].animation.frameIndex, item[itm.id].col, itm.x, itm.y, pixelBuffer); } //draw sprites for (id in sprite) { var spr = sprite[id]; if (spr.room === room.id) { drawTileInPixelBuffer( renderer.GetDrawingSource(spr.drw), spr.animation.frameIndex, spr.col, spr.x, spr.y, pixelBuffer); } } return pixelBuffer; } function lerpColor(colorA, colorB, t) { return [ colorA[0] + ((colorB[0] – colorA[0]) * t), colorA[1] + ((colorB[1] – colorA[1]) * t), colorA[2] + ((colorB[2] – colorA[2]) * t), ]; }; }; // TransitionManager() // todo : is this wrapper still useful? var PostProcessImage = function(imageData) { this.Width = bitsy.VIDEO_SIZE; this.Height = bitsy.VIDEO_SIZE; this.GetPixel = function(x, y) { return imageData[(y * bitsy.VIDEO_SIZE) + x]; }; this.GetData = function() { return imageData; }; }; var TransitionInfo = function(image, palette, playerX, playerY) { this.Image = image; this.Palette = palette; this.PlayerTilePos = { x: playerX, y: playerY }; this.PlayerCenter = { x: Math.floor((playerX * bitsy.TILE_SIZE) + (bitsy.TILE_SIZE / 2)), y: Math.floor((playerY * bitsy.TILE_SIZE) + (bitsy.TILE_SIZE / 2)) }; }; function Script() { this.CreateInterpreter = function() { return new Interpreter(); }; this.CreateUtils = function() { return new Utils(); }; var Interpreter = function() { var env = new Environment(); var parser = new Parser( env ); this.SetDialogBuffer = function(buffer) { env.SetDialogBuffer( buffer ); }; // TODO — maybe this should return a string instead othe actual script?? this.Compile = function(scriptName, scriptStr) { var script = parser.Parse(scriptStr, scriptName); env.SetScript(scriptName, script); } this.Run = function(scriptName, exitHandler, objectContext) { // Runs pre-compiled script var localEnv = new LocalEnvironment(env); if (objectContext) { localEnv.SetObject(objectContext); // PROTO : should this be folded into the constructor? } var script = env.GetScript(scriptName); script.Eval( localEnv, function(result) { OnScriptReturn(localEnv, exitHandler); } ); } this.Interpret = function(scriptStr, exitHandler, objectContext) { // Compiles and runs code immediately // bitsy.log(“INTERPRET”); var localEnv = new LocalEnvironment(env); if (objectContext) { localEnv.SetObject(objectContext); // PROTO : should this be folded into the constructor? } var script = parser.Parse(scriptStr, “anonymous”); script.Eval( localEnv, function(result) { OnScriptReturn(localEnv, exitHandler); } ); } this.HasScript = function(name) { return env.HasScript(name); }; this.ResetEnvironment = function() { env = new Environment(); parser = new Parser( env ); } this.Parse = function(scriptStr, rootId) { // parses a script but doesn’t save it return parser.Parse(scriptStr, rootId); } this.Eval = function(scriptTree, exitHandler) { // runs a script stored externally var localEnv = new LocalEnvironment(env); // TODO : does this need an object context? scriptTree.Eval( localEnv, function(result) { OnScriptReturn(result, exitHandler); }); } function OnScriptReturn(result, exitHandler) { if (exitHandler != null) { exitHandler(result); } } this.CreateExpression = function(expStr) { return parser.CreateExpression(expStr); } this.SetVariable = function(name,value,useHandler) { env.SetVariable(name,value,useHandler); } this.DeleteVariable = function(name,useHandler) { env.DeleteVariable(name,useHandler); } this.HasVariable = function(name) { return env.HasVariable(name); } this.SetOnVariableChangeHandler = function(onVariableChange) { env.SetOnVariableChangeHandler(onVariableChange); } this.GetVariableNames = function() { return env.GetVariableNames(); } this.GetVariable = function(name) { return env.GetVariable(name); } function DebugVisualizeScriptTree(scriptTree) { var printVisitor = { Visit : function(node,depth) { bitsy.log(“-“.repeat(depth) + “- ” + node.ToString()); }, }; scriptTree.VisitAll( printVisitor ); } this.DebugVisualizeScriptTree = DebugVisualizeScriptTree; this.DebugVisualizeScript = function(scriptName) { DebugVisualizeScriptTree(env.GetScript(scriptName)); } } var Utils = function() { // for editor ui this.CreateDialogBlock = function(children,doIndentFirstLine) { if (doIndentFirstLine === undefined) { doIndentFirstLine = true; } var block = new DialogBlockNode(doIndentFirstLine); for (var i = 0; i < children.length; i++) { block.AddChild(children[i]); } return block; } this.CreateOptionBlock = function() { var block = new DialogBlockNode(false); block.AddChild(new FuncNode("say", [new LiteralNode(" ")])); return block; } this.CreateItemConditionPair = function() { var itemFunc = this.CreateFunctionBlock("item", ["0"]); var condition = new ExpNode("==", itemFunc, new LiteralNode(1)); var result = new DialogBlockNode(true); result.AddChild(new FuncNode("say", [new LiteralNode(" ")])); var conditionPair = new ConditionPairNode(condition, result); return conditionPair; } this.CreateVariableConditionPair = function() { var varNode = this.CreateVariableNode("a"); var condition = new ExpNode("==", varNode, new LiteralNode(1)); var result = new DialogBlockNode(true); result.AddChild(new FuncNode("say", [new LiteralNode(" ")])); var conditionPair = new ConditionPairNode(condition, result); return conditionPair; } this.CreateDefaultConditionPair = function() { var condition = this.CreateElseNode(); var result = new DialogBlockNode(true); result.AddChild(new FuncNode("say", [new LiteralNode(" ")])); var conditionPair = new ConditionPairNode(condition, result); return conditionPair; } this.CreateEmptySayFunc = function() { return new FuncNode("say", [new LiteralNode("…")]); } this.CreateFunctionBlock = function(name, initParamValues) { var parameters = []; for (var i = 0; i -1) { dialogStr = Sym.DialogOpen + “\n” + dialogStr + “\n” + Sym.DialogClose; } return dialogStr; } this.RemoveDialogBlockFormat = function(source) { var sourceLines = source.split(“\n”); var dialogStr = “”; if(sourceLines[0] === Sym.DialogOpen) { // multi line var i = 1; while (i < sourceLines.length && sourceLines[i] != Sym.DialogClose) { dialogStr += sourceLines[i] + (sourceLines[i+1] != Sym.DialogClose ? '\n' : ''); i++; } } else { // single line dialogStr = source; } return dialogStr; } this.SerializeDialogNodeList = function(nodeList) { var tempBlock = new DialogBlockNode(false); // set children directly to avoid breaking the parenting chain for this temp operation tempBlock.children = nodeList; return tempBlock.Serialize(); } this.GetOperatorList = function() { return [Sym.Set].concat(Sym.Operators); } this.IsInlineCode = function(node) { return isInlineCode(node); } } /* BUILT-IN FUNCTIONS */ // TODO: better way to encapsulate these? function deprecatedFunc(environment,parameters,onReturn) { bitsy.log("BITSY SCRIPT WARNING: Tried to use deprecated function"); onReturn(null); } function sayFunc(environment, parameters, onReturn) { if (parameters[0] != undefined && parameters[0] != null) { var textStr = "" + parameters[0]; environment.GetDialogBuffer().AddText(textStr); environment.GetDialogBuffer().AddScriptReturn(function() { onReturn(null); }); } else { onReturn(null); } } function linebreakFunc(environment, parameters, onReturn) { // bitsy.log("LINEBREAK FUNC"); environment.GetDialogBuffer().AddLinebreak(); environment.GetDialogBuffer().AddScriptReturn(function() { onReturn(null); }); } function pagebreakFunc(environment, parameters, onReturn) { environment.GetDialogBuffer().AddPagebreak(function() { onReturn(null); }); } function drawFunc(environment, parameters, onReturn) { var drawingId = parameters[0]; environment.GetDialogBuffer().AddDrawing(drawingId); environment.GetDialogBuffer().AddScriptReturn(function() { onReturn(null); }); } function drawSpriteFunc(environment, parameters, onReturn) { var spriteId = parameters[0]; // check if id parameter is actually a name if (names.sprite[spriteId] != undefined) { spriteId = names.sprite[spriteId]; } var drawingId = sprite[spriteId].drw; drawFunc(environment, [drawingId], onReturn); } function drawTileFunc(environment, parameters, onReturn) { var tileId = parameters[0]; // check if id parameter is actually a name if (names.tile[tileId] != undefined) { tileId = names.tile[tileId]; } var drawingId = tile[tileId].drw; drawFunc(environment, [drawingId], onReturn); } function drawItemFunc(environment, parameters, onReturn) { var itemId = parameters[0]; // check if id parameter is actually a name if (names.item[itemId] != undefined) { itemId = names.item[itemId]; } var drawingId = item[itemId].drw; drawFunc(environment, [drawingId], onReturn); } function printFontFunc(environment, parameters, onReturn) { var allCharacters = ""; var font = fontManager.Get(fontName); var codeList = font.allCharCodes(); for (var i = 0; i 1) { // TODO : is it a good idea to force inventory to be >= 0? player().inventory[itemId] = Math.max(0, parseInt(parameters[1])); curItemCount = player().inventory[itemId]; if (onInventoryChanged != null) { onInventoryChanged(itemId); } } onReturn(curItemCount); } function toggleTextEffect(environment, name) { if (environment.GetDialogBuffer().hasTextEffect(name)) { environment.GetDialogBuffer().popTextEffect(name); } else { environment.GetDialogBuffer().pushTextEffect(name, []); } } function color1Func(environment, parameters, onReturn) { toggleTextEffect(environment, “clr1”); onReturn(null); } function color2Func(environment, parameters, onReturn) { toggleTextEffect(environment, “clr2”); onReturn(null); } function color3Func(environment, parameters, onReturn) { toggleTextEffect(environment, “clr3”); onReturn(null); } function colorFunc(environment, parameters, onReturn) { environment.GetDialogBuffer().pushTextEffect(“clr”, parameters); onReturn(null); } function colorPopFunc(environment, parameters, onReturn) { if (environment.GetDialogBuffer().hasTextEffect(“clr”)) { environment.GetDialogBuffer().popTextEffect(“clr”); } onReturn(null); } function rainbowFunc(environment, parameters, onReturn) { toggleTextEffect(environment, “rbw”); onReturn(null); } function rainbowPopFunc(environment, parameters, onReturn) { if (environment.GetDialogBuffer().hasTextEffect(“rbw”)) { environment.GetDialogBuffer().popTextEffect(“rbw”); } onReturn(null); } function wavyFunc(environment, parameters, onReturn) { toggleTextEffect(environment, “wvy”); onReturn(null); } function wavyPopFunc(environment, parameters, onReturn) { if (environment.GetDialogBuffer().hasTextEffect(“wvy”)) { environment.GetDialogBuffer().popTextEffect(“wvy”); } onReturn(null); } function shakyFunc(environment, parameters, onReturn) { toggleTextEffect(environment, “shk”); onReturn(null); } function shakyPopFunc(environment, parameters, onReturn) { if (environment.GetDialogBuffer().hasTextEffect(“shk”)) { environment.GetDialogBuffer().popTextEffect(“shk”); } onReturn(null); } function propertyFunc(environment, parameters, onReturn) { var outValue = null; if (parameters.length > 0 && parameters[0]) { var propertyName = parameters[0]; if (environment.HasProperty(propertyName)) { // TODO : in a future update I can handle the case of initializing a new property // after which we can move this block outside the HasProperty check if (parameters.length > 1) { var inValue = parameters[1]; environment.SetProperty(propertyName, inValue); } outValue = environment.GetProperty(propertyName); } } bitsy.log(“PROPERTY! ” + propertyName + ” ” + outValue); onReturn(outValue); } function endFunc(environment,parameters,onReturn) { isEnding = true; isNarrating = true; dialogRenderer.SetCentered(true); dialogRenderer.DrawTextbox(); onReturn(null); } function exitFunc(environment, parameters, onReturn) { var destRoom; var destX; var destY; if (parameters.length >= 1) { destRoom = parameters[0]; // is it a name? if (names.room[destRoom] != undefined) { destRoom = names.room[destRoom]; } } if (parameters.length >= 3) { destX = parseInt(parameters[1]); destY = parseInt(parameters[2]); } if (parameters.length >= 4) { var transitionEffect = parameters[3]; transition.BeginTransition( player().room, player().x, player().y, destRoom, destX, destY, transitionEffect); transition.UpdateTransition(0); } var movePlayerAndResumeScript = function() { if (destRoom != undefined && destX != undefined && destY != undefined) { // update world state player().room = destRoom; player().x = destX; player().y = destY; state.room = destRoom; // update game state initRoom(state.room); } if (dialogRenderer) { dialogRenderer.updateTextboxPosition(); } // resume dialog script onReturn(state.room); }; // TODO : this doesn’t play nice with pagebreak because it thinks the dialog is finished! if (transition.IsTransitionActive()) { transition.OnTransitionComplete(movePlayerAndResumeScript); } else { movePlayerAndResumeScript(); } } function tuneFunc(environment, parameters, onReturn) { if (parameters.length > 0) { var tuneId = parameters[0]; // check if id parameter is actually a name if (names.tune[tuneId] != undefined) { tuneId = names.tune[tuneId]; } if (soundPlayer) { if (tuneId === “0”) { soundPlayer.stopTune(); } else if (state.tune != tuneId) { soundPlayer.playTune(tune[tuneId]); } } state.tune = tuneId; } onReturn(state.tune); } function blipFunc(environment, parameters, onReturn) { if (parameters.length > 0) { var blipId = parameters[0]; // check if id parameter is actually a name if (names.blip[blipId] != undefined) { blipId = names.blip[blipId]; } soundPlayer.playBlip(blip[blipId]); } // if a dialog skip is happening, stop it and force a redraw of the textbox if (dialogBuffer) { if (dialogBuffer.tryInterruptSkip()) { dialogRenderer.Draw(dialogBuffer, 0, true /* disableOnPrint */); } } onReturn(null); } /* // TODO : use later? function yakFunc(environment, parameters, onReturn) { if (parameters.length > 0) { var blipId = parameters[0]; // check if id parameter is actually a name if (names.blip[blipId] != undefined) { blipId = names.blip[blipId]; } environment.GetDialogBuffer().pushTextEffect(“yak”, [blipId]); } onReturn(null); } function yakPopFunc(environment, parameters, onReturn) { if (environment.GetDialogBuffer().hasTextEffect(“yak”)) { environment.GetDialogBuffer().popTextEffect(“yak”); } onReturn(null); } */ function paletteFunc(environment, parameters, onReturn) { if (parameters.length > 0) { var palId = parameters[0]; // check if id parameter is actually a name if (names.palette[palId] != undefined) { palId = names.palette[palId]; } updatePalette(palId); } onReturn(state.pal); } function avatarFunc(environment, parameters, onReturn) { if (parameters.length > 0) { var sprId = parameters[0]; // check if id parameter is actually a name if (names.sprite[sprId] != undefined) { sprId = names.sprite[sprId]; } // override the avatar’s current appearance state.ava = sprId; // redraw the avatar with its new appearance drawRoom(room[state.room], { redrawAvatar: true }); } onReturn(state.ava); } /* BUILT-IN OPERATORS */ function setExp(environment,left,right,onReturn) { // bitsy.log(“SET ” + left.name); if(left.type != “variable”) { // not a variable! return null and hope for the best D: onReturn( null ); return; } right.Eval(environment,function(rVal) { environment.SetVariable( left.name, rVal ); // bitsy.log(“VAL ” + environment.GetVariable( left.name ) ); left.Eval(environment,function(lVal) { onReturn( lVal ); }); }); } function equalExp(environment,left,right,onReturn) { // bitsy.log(“EVAL EQUAL”); // bitsy.log(left); // bitsy.log(right); right.Eval(environment,function(rVal){ left.Eval(environment,function(lVal){ onReturn( lVal === rVal ); }); }); } function greaterExp(environment,left,right,onReturn) { right.Eval(environment,function(rVal){ left.Eval(environment,function(lVal){ onReturn( lVal > rVal ); }); }); } function lessExp(environment,left,right,onReturn) { right.Eval(environment,function(rVal){ left.Eval(environment,function(lVal){ onReturn( lVal = rVal ); }); }); } function lessEqExp(environment,left,right,onReturn) { right.Eval(environment,function(rVal){ left.Eval(environment,function(lVal){ onReturn( lVal “] = greaterExp; operatorMap[“=”] = greaterEqExp; operatorMap[“<="] = lessEqExp; operatorMap["*"] = multExp; operatorMap["/"] = divExp; operatorMap["+"] = addExp; operatorMap["-"] = subExp; this.HasOperator = function(sym) { return operatorMap[sym] != undefined; }; this.EvalOperator = function(sym,left,right,onReturn) { operatorMap[ sym ]( this, left, right, onReturn ); } var scriptMap = {}; this.HasScript = function(name) { return scriptMap[name] != undefined; }; this.GetScript = function(name) { return scriptMap[name]; }; this.SetScript = function(name,script) { scriptMap[name] = script; }; var onVariableChangeHandler = null; this.SetOnVariableChangeHandler = function(onVariableChange) { onVariableChangeHandler = onVariableChange; } this.GetVariableNames = function() { var variableNames = []; for (var key in variableMap) { variableNames.push(key); } return variableNames; } } // Local environment for a single run of a script: knows local context var LocalEnvironment = function(parentEnvironment) { // this.SetDialogBuffer // not allowed in local environment? this.GetDialogBuffer = function() { return parentEnvironment.GetDialogBuffer(); }; this.HasFunction = function(name) { return parentEnvironment.HasFunction(name); }; this.EvalFunction = function(name,parameters,onReturn,env) { if (env == undefined || env == null) { env = this; } parentEnvironment.EvalFunction(name,parameters,onReturn,env); } this.HasVariable = function(name) { return parentEnvironment.HasVariable(name); }; this.GetVariable = function(name) { return parentEnvironment.GetVariable(name); }; this.SetVariable = function(name,value,useHandler) { parentEnvironment.SetVariable(name,value,useHandler); }; // this.DeleteVariable // not needed in local environment? this.HasOperator = function(sym) { return parentEnvironment.HasOperator(sym); }; this.EvalOperator = function(sym,left,right,onReturn,env) { if (env == undefined || env == null) { env = this; } parentEnvironment.EvalOperator(sym,left,right,onReturn,env); }; // TODO : I don't *think* any of this is required by the local environment // this.HasScript // this.GetScript // this.SetScript // TODO : pretty sure these debug methods aren't required by the local environment either // this.SetOnVariableChangeHandler // this.GetVariableNames /* Here's where specific local context data goes: * this includes access to the object running the script * and any properties it may have (so far only "locked") */ // The local environment knows what object called it — currently only used to access properties var curObject = null; this.HasObject = function() { return curObject != undefined && curObject != null; } this.SetObject = function(object) { curObject = object; } this.GetObject = function() { return curObject; } // accessors for properties of the object that's running the script this.HasProperty = function(name) { if (curObject && curObject.property && curObject.property.hasOwnProperty(name)) { return true; } else { return false; } }; this.GetProperty = function(name) { if (curObject && curObject.property && curObject.property.hasOwnProperty(name)) { return curObject.property[name]; // TODO : should these be getters and setters instead? } else { return null; } }; this.SetProperty = function(name, value) { // NOTE : for now, we need to gaurd against creating new properties if (curObject && curObject.property && curObject.property.hasOwnProperty(name)) { curObject.property[name] = value; } }; } function leadingWhitespace(depth) { var str = ""; for(var i = 0; i < depth; i++) { str += " "; // two spaces per indent } // bitsy.log("WHITESPACE " + depth + " ::" + str + "::"); return str; } /* NODES */ var TreeRelationship = function() { this.parent = null; this.children = []; this.AddChild = function(node) { this.children.push(node); node.parent = this; }; this.AddChildren = function(nodeList) { for (var i = 0; i < nodeList.length; i++) { this.AddChild(nodeList[i]); } }; this.SetChildren = function(nodeList) { this.children = []; this.AddChildren(nodeList); }; this.VisitAll = function(visitor, depth) { if (depth == undefined || depth == null) { depth = 0; } visitor.Visit(this, depth); for (var i = 0; i < this.children.length; i++) { this.children[i].VisitAll( visitor, depth + 1 ); } }; this.rootId = null; // for debugging this.GetId = function() { // bitsy.log(this); if (this.rootId != null) { return this.rootId; } else if (this.parent != null) { var parentId = this.parent.GetId(); if (parentId != null) { return parentId + "_" + this.parent.children.indexOf(this); } } else { return null; } } } function DialogBlockNode(doIndentFirstLine) { TreeRelationship.call(this); this.type = "dialog_block"; this.Eval = function(environment, onReturn) { // bitsy.log("EVAL BLOCK " + this.children.length); if (isPlayerEmbeddedInEditor && events != undefined && events != null) { events.Raise("script_node_enter", { id: this.GetId() }); } var lastVal = null; var i = 0; function evalChildren(children, done) { if (i > CHILD ” + i); children[i].Eval(environment, function(val) { // bitsy.log(“<< CHILD " + i); lastVal = val; i++; evalChildren(children,done); }); } else { done(); } }; var self = this; evalChildren(this.children, function() { if (isPlayerEmbeddedInEditor && events != undefined && events != null) { events.Raise("script_node_exit", { id: self.GetId() }); } onReturn(lastVal); }); } if (doIndentFirstLine === undefined) { doIndentFirstLine = true; // This is just for serialization } this.Serialize = function(depth) { if (depth === undefined) { depth = 0; } var str = ""; var lastNode = null; for (var i = 0; i < this.children.length; i++) { var curNode = this.children[i]; var shouldIndentFirstLine = (i == 0 && doIndentFirstLine); var shouldIndentAfterLinebreak = (lastNode && lastNode.type === "function" && lastNode.name === "br"); if (shouldIndentFirstLine || shouldIndentAfterLinebreak) { str += leadingWhitespace(depth); } str += curNode.Serialize(depth); lastNode = curNode; } return str; } this.ToString = function() { return this.type + " " + this.GetId(); }; } function CodeBlockNode() { TreeRelationship.call(this); this.type = "code_block"; this.Eval = function(environment, onReturn) { // bitsy.log("EVAL BLOCK " + this.children.length); if (isPlayerEmbeddedInEditor && events != undefined && events != null) { events.Raise("script_node_enter", { id: this.GetId() }); } var lastVal = null; var i = 0; function evalChildren(children, done) { if (i > CHILD ” + i); children[i].Eval(environment, function(val) { // bitsy.log(“<< CHILD " + i); lastVal = val; i++; evalChildren(children,done); }); } else { done(); } }; var self = this; evalChildren(this.children, function() { if (isPlayerEmbeddedInEditor && events != undefined && events != null) { events.Raise("script_node_exit", { id: self.GetId() }); } onReturn(lastVal); }); } this.Serialize = function(depth) { if(depth === undefined) { depth = 0; } // bitsy.log("SERIALIZE BLOCK!!!"); // bitsy.log(depth); // bitsy.log(doIndentFirstLine); var str = "{"; // todo: increase scope of Sym? // TODO : do code blocks ever have more than one child anymore???? for (var i = 0; i 0 && node.children[0].type === “undefined”; } var textEffectBlockNames = [“clr1”, “clr2”, “clr3”, “wvy”, “shk”, “rbw”, “printSprite”, “printItem”, “printTile”, “print”, “say”, “br”]; function isTextEffectBlock(node) { if (node.type === “code_block”) { if (node.children.length > 0 && node.children[0].type === “function”) { var func = node.children[0]; return textEffectBlockNames.indexOf(func.name) != -1; } } return false; } var listBlockTypes = [“sequence”, “cycle”, “shuffle”, “if”]; function isMultilineListBlock(node) { if (node.type === “code_block”) { if (node.children.length > 0) { var child = node.children[0]; return listBlockTypes.indexOf(child.type) != -1; } } return false; } // for round-tripping undefined code through the parser (useful for hacks!) function UndefinedNode(sourceStr) { TreeRelationship.call(this); this.type = “undefined”; this.source = sourceStr; this.Eval = function(environment,onReturn) { toggleTextEffect(environment, “_debug_highlight”); sayFunc(environment, [“{” + sourceStr + “}”], function() { onReturn(null); }); toggleTextEffect(environment, “_debug_highlight”); } this.Serialize = function(depth) { return this.source; } this.ToString = function() { return “undefined” + ” ” + this.GetId(); } } function FuncNode(name, args) { TreeRelationship.call(this); this.type = “function”; this.name = name; this.args = args; this.Eval = function(environment,onReturn) { if (isPlayerEmbeddedInEditor && events != undefined && events != null) { events.Raise(“script_node_enter”, { id: this.GetId() }); } var self = this; // hack to deal with scope (TODO : move up higher?) var argumentValues = []; var i = 0; function evalArgs(args, done) { // TODO : really hacky way to make we get the first // symbol’s NAME instead of its variable value // if we are trying to do something with a property if (self.name === “property” && i === 0 && i < args.length) { if (args[i].type === "variable") { argumentValues.push(args[i].name); i++; } else { // first argument for a property MUST be a variable symbol // — so skip everything if it's not! i = args.length; } } if (i < args.length) { // Evaluate each argument args[i].Eval( environment, function(val) { argumentValues.push(val); i++; evalArgs(args, done); }); } else { done(); } }; evalArgs( this.args, function() { if (isPlayerEmbeddedInEditor && events != undefined && events != null) { events.Raise("script_node_exit", { id: self.GetId() }); } environment.EvalFunction(self.name, argumentValues, onReturn); }); } this.Serialize = function(depth) { var isDialogBlock = this.parent.type === "dialog_block"; if (isDialogBlock && this.name === "say") { // TODO this could cause problems with "real" print functions return this.args[0].value; // first argument should be the text of the {print} func } else if (isDialogBlock && this.name === "br") { return "\n"; } else { var str = ""; str += this.name; for(var i = 0; i < this.args.length; i++) { str += " "; str += this.args[i].Serialize(depth); } return str; } } this.ToString = function() { return this.type + " " + this.name + " " + this.GetId(); }; } function LiteralNode(value) { TreeRelationship.call(this); this.type = "literal"; this.value = value; this.Eval = function(environment,onReturn) { onReturn(this.value); }; this.Serialize = function(depth) { var str = ""; if (this.value === null) { return str; } if (typeof this.value === "string") { str += '"'; } str += this.value; if (typeof this.value === "string") { str += '"'; } return str; }; this.ToString = function() { return this.type + " " + this.value + " " + this.GetId(); }; } function VarNode(name) { TreeRelationship.call(this); this.type = "variable"; this.name = name; this.Eval = function(environment,onReturn) { // bitsy.log("EVAL " + this.name + " " + environment.HasVariable(this.name) + " " + environment.GetVariable(this.name)); if( environment.HasVariable(this.name) ) onReturn( environment.GetVariable( this.name ) ); else onReturn(null); // not a valid variable — return null and hope that's ok } // TODO: might want to store nodes in the variableMap instead of values??? this.Serialize = function(depth) { var str = "" + this.name; return str; } this.ToString = function() { return this.type + " " + this.name + " " + this.GetId(); }; } function ExpNode(operator, left, right) { TreeRelationship.call(this); this.type = "operator"; this.operator = operator; this.left = left; this.right = right; this.Eval = function(environment,onReturn) { // bitsy.log("EVAL " + this.operator); var self = this; // hack to deal with scope environment.EvalOperator( this.operator, this.left, this.right, function(val){ // bitsy.log("EVAL EXP " + self.operator + " " + val); onReturn(val); } ); // NOTE : sadly this pushes a lot of complexity down onto the actual operator methods }; this.Serialize = function(depth) { var isNegativeNumber = this.operator === "-" && this.left.type === "literal" && this.left.value === null; if (!isNegativeNumber) { var str = ""; if (this.left != undefined && this.left != null) { str += this.left.Serialize(depth) + " "; } str += this.operator; if (this.right != undefined && this.right != null) { str += " " + this.right.Serialize(depth); } return str; } else { return this.operator + this.right.Serialize(depth); // hacky but seems to work } }; this.VisitAll = function(visitor, depth) { if (depth == undefined || depth == null) { depth = 0; } visitor.Visit( this, depth ); if(this.left != null) this.left.VisitAll( visitor, depth + 1 ); if(this.right != null) this.right.VisitAll( visitor, depth + 1 ); }; this.ToString = function() { return this.type + " " + this.operator + " " + this.GetId(); }; } function SequenceBase() { TreeRelationship.call(this); this.Serialize = function(depth) { var str = ""; str += this.type + "\n"; for (var i = 0; i < this.children.length; i++) { str += leadingWhitespace(depth + 1) + Sym.List + " "; str += this.children[i].Serialize(depth + 2); str += "\n"; } str += leadingWhitespace(depth); return str; }; this.VisitAll = function(visitor, depth) { if (depth == undefined || depth == null) { depth = 0; } visitor.Visit(this, depth); for (var i = 0; i < this.children.length; i++) { this.children[i].VisitAll( visitor, depth + 1 ); } }; this.ToString = function() { return this.type + " " + this.GetId(); }; } function SequenceNode(options) { SequenceBase.call(this); this.type = "sequence"; this.AddChildren(options); var index = 0; this.Eval = function(environment, onReturn) { // bitsy.log("SEQUENCE " + index); this.children[index].Eval(environment, onReturn); var next = index + 1; if (next < this.children.length) { index = next; } } } function CycleNode(options) { SequenceBase.call(this); this.type = "cycle"; this.AddChildren(options); var index = 0; this.Eval = function(environment, onReturn) { // bitsy.log("CYCLE " + index); this.children[index].Eval(environment, onReturn); var next = index + 1; if (next 0) { var i = Math.floor(Math.random() * optionsUnshuffled.length); optionsShuffled.push(optionsUnshuffled.splice(i,1)[0]); } } shuffle(this.children); var index = 0; this.Eval = function(environment, onReturn) { optionsShuffled[index].Eval(environment, onReturn); index++; if (index >= this.children.length) { shuffle(this.children); index = 0; } } } // TODO : rename? ConditionalNode? function IfNode(conditions, results, isSingleLine) { TreeRelationship.call(this); this.type = “if”; for (var i = 0; i < conditions.length; i++) { this.AddChild(new ConditionPairNode(conditions[i], results[i])); } var self = this; this.Eval = function(environment, onReturn) { // bitsy.log("EVAL IF"); var i = 0; function TestCondition() { self.children[i].Eval(environment, function(result) { if (result.conditionValue == true) { onReturn(result.resultValue); } else if (i+1 1 && this.children[1].children[0].type === Sym.Else) { str += ” ” + Sym.ElseExp + ” ” + this.children[1].children[1].Serialize(); } } else { str += “\n”; for (var i = 0; i < this.children.length; i++) { str += this.children[i].Serialize(depth); } str += leadingWhitespace(depth); } return str; }; this.IsSingleLine = function() { return isSingleLine; }; this.VisitAll = function(visitor, depth) { if (depth == undefined || depth == null) { depth = 0; } visitor.Visit(this, depth); for (var i = 0; i < this.children.length; i++) { this.children[i].VisitAll(visitor, depth + 1); } }; this.ToString = function() { return this.type + " " + this.mode + " " + this.GetId(); }; } function ConditionPairNode(condition, result) { TreeRelationship.call(this); this.type = "condition_pair"; this.AddChild(condition); this.AddChild(result); var self = this; this.Eval = function(environment, onReturn) { self.children[0].Eval(environment, function(conditionSuccess) { if (conditionSuccess) { self.children[1].Eval(environment, function(resultValue) { onReturn({ conditionValue:true, resultValue:resultValue }); }); } else { onReturn({ conditionValue:false }); } }); }; this.Serialize = function(depth) { var str = ""; str += leadingWhitespace(depth + 1); str += Sym.List + " " + this.children[0].Serialize(depth) + " " + Sym.ConditionEnd + Sym.Linebreak; str += this.children[1].Serialize(depth + 2) + Sym.Linebreak; return str; }; this.VisitAll = function(visitor, depth) { if (depth == undefined || depth == null) { depth = 0; } visitor.Visit(this, depth); for (var i = 0; i =”, “”, “= sourceStr.length; }; this.Char = function() { return sourceStr[i]; }; this.Step = function(n) { if(n===undefined) n=1; i += n; }; this.MatchAhead = function(str) { // bitsy.log(str); str = “” + str; // hack to turn single chars into strings // bitsy.log(str); // bitsy.log(str.length); for (var j = 0; j = sourceStr.length) { return false; } else if (str[j] != sourceStr[i+j]) { return false; } } return true; } this.Peak = function(end) { var str = “”; var j = i; // bitsy.log(j); while (j 0 && !this.Done()) { if (this.MatchAhead(close)) { matchCount–; this.Step( close.length ); } else if (this.MatchAhead(open)) { matchCount++; this.Step(open.length); } else { this.Step(); } } if (includeSymbols) { return sourceStr.slice(startIndex, i); } else { return sourceStr.slice(startIndex + open.length, i – close.length); } } this.Print = function() { bitsy.log(sourceStr); }; this.Source = function() { return sourceStr; }; }; /* ParseDialog(): This function adds {print} nodes and linebreak {br} nodes to display text, interleaved with bracketed code nodes for functions and flow control, such as text effects {shk} {wvy} or sequences like {cycle} and {shuffle}. The parsing of those code blocks is handled by ParseCode. Note on parsing newline characters: – there should be an implicit linebreak {br} after each dialog line – a “dialog line” is defined as any line that either: – 1) contains dialog text (any text outside of a code block) – 2) is entirely empty (no text, no code) – *or* 3) contains a list block (sequence, cycle, shuffle, or conditional) – lines *only* containing {code} blocks are not dialog lines NOTE TO SELF: all the state I’m storing in here feels like evidence that the parsing system kind of broke down at this point 🙁 Maybe it would feel better if I move into the “state” object */ function ParseDialog(state) { var curLineNodeList = []; var curText = “”; var curLineIsEmpty = true; var curLineContainsDialogText = false; var prevLineIsDialogLine = false; var curLineIsDialogLine = function() { return curLineContainsDialogText || curLineIsEmpty; } var resetLineStateForNewLine = function() { prevLineIsDialogLine = curLineIsDialogLine(); curLineContainsDialogText = false; curLineIsEmpty = true; curText = “”; curLineNodeList = []; } var tryAddTextNodeToList = function() { if (curText.length > 0) { var sayNode = new FuncNode(“say”, [new LiteralNode(curText)]); curLineNodeList.push(sayNode); curText = “”; curLineIsEmpty = false; curLineContainsDialogText = true; } } var addCodeNodeToList = function() { var codeSource = state.ConsumeBlock(Sym.CodeOpen, Sym.CodeClose); var codeState = new ParserState(new CodeBlockNode(), codeSource); codeState = ParseCode(codeState); var codeBlockNode = codeState.rootNode; curLineNodeList.push(codeBlockNode); curLineIsEmpty = false; // lists count as dialog text, because they can contain it if (isMultilineListBlock(codeBlockNode)) { curLineContainsDialogText = true; } } var tryAddLinebreakNodeToList = function() { if (prevLineIsDialogLine) { var linebreakNode = new FuncNode(“br”, []); curLineNodeList.unshift(linebreakNode); } } var addLineNodesToParent = function() { for (var i = 0; i = requiredLeadingWhitespace) { var trimmedText = trimLeadingWhitespace(lineResults.text, requiredLeadingWhitespace); if (lineResults.isNewCondition) { conditionStrings[curIndex] += trimmedText; } else { resultStrings[curIndex] += trimmedText + Sym.Linebreak; } } } // hack: cut off the trailing newlines from all the result strings resultStrings = resultStrings.map(function(result) { return result.slice(0,-1); }); var conditions = []; for (var i = 0; i < conditionStrings.length; i++) { var str = conditionStrings[i].trim(); if (str === Sym.Else) { conditions.push(new ElseNode()); } else { var exp = CreateExpression(str); conditions.push(exp); } } var results = []; for (var i = 0; i = requiredLeadingWhitespace) { var trimmedText = trimLeadingWhitespace(lineResults.text, requiredLeadingWhitespace); itemStrings[curItemIndex] += trimmedText + Sym.Linebreak; } } // a bit hacky: cut off the trailing newlines from all the items itemStrings = itemStrings.map(function(item) { return item.slice(0,-1); }); var options = []; for (var i = 0; i 0) { OnSymbolEnd(); } else { curSymbol += state.Char(); } state.Step(); } if(curSymbol.length > 0) { OnSymbolEnd(); } state.curNode.AddChild( new FuncNode( funcName, args ) ); return state; } function IsValidVariableName(str) { var reg = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; var isValid = reg.test(str); // bitsy.log(“VALID variable??? ” + isValid); return isValid; } function StringToValue(valStr) { if(valStr[0] === Sym.CodeOpen) { // CODE BLOCK!!! var codeStr = (new ParserState( null, valStr )).ConsumeBlock(Sym.CodeOpen, Sym.CodeClose); //hacky var codeBlockState = new ParserState(new CodeBlockNode(), codeStr); codeBlockState = ParseCode( codeBlockState ); return codeBlockState.rootNode; } else if(valStr[0] === Sym.String) { // STRING!! // bitsy.log(“STRING”); var str = “”; var i = 1; while (i < valStr.length && valStr[i] != Sym.String) { str += valStr[i]; i++; } // bitsy.log(str); return new LiteralNode( str ); } else if(valStr === "true") { // BOOL return new LiteralNode( true ); } else if(valStr === "false") { // BOOL return new LiteralNode( false ); } else if( !isNaN(parseFloat(valStr)) ) { // NUMBER!! // bitsy.log("NUMBER!!! " + valStr); return new LiteralNode( parseFloat(valStr) ); } else if(IsValidVariableName(valStr)) { // VARIABLE!! // bitsy.log("VARIABLE"); return new VarNode(valStr); // TODO : check for valid potential variables } else { // uh oh return new LiteralNode(null); } } function CreateExpression(expStr) { expStr = expStr.trim(); function IsInsideString(index) { var inString = false; for(var i = 0; i < expStr.length; i++) { if(expStr[i] === Sym.String) inString = !inString; if(index === i) return inString; } return false; } function IsInsideCode(index) { var count = 0; for(var i = 0; i 0; } return false; } var operator = null; // set is special because other operator can look like it, and it has to go first in the order of operations var setIndex = expStr.indexOf(Sym.Set); if( setIndex > -1 && !IsInsideString(setIndex) && !IsInsideCode(setIndex) ) { // it might be a set operator if( expStr[setIndex+1] != “=” && expStr[setIndex-1] != “>” && expStr[setIndex-1] != “=, or -1 && !IsInsideString(ifIndex) && !IsInsideCode(ifIndex) ) { operator = Sym.ConditionEnd; var conditionStr = expStr.substring(0,ifIndex).trim(); var conditions = [ CreateExpression(conditionStr) ]; var resultStr = expStr.substring(ifIndex+Sym.ConditionEnd.length); var results = []; function AddResult(str) { var dialogBlockState = new ParserState(new DialogBlockNode(), str); dialogBlockState = ParseDialog( dialogBlockState ); var dialogBlock = dialogBlockState.rootNode; results.push( dialogBlock ); } var elseIndex = resultStr.indexOf(Sym.ElseExp); // does this need to test for strings? if(elseIndex > -1) { conditions.push( new ElseNode() ); var elseStr = resultStr.substring(elseIndex+Sym.ElseExp.length); var resultStr = resultStr.substring(0,elseIndex); AddResult( resultStr.trim() ); AddResult( elseStr.trim() ); } else { AddResult( resultStr.trim() ); } return new IfNode( conditions, results, true /*isSingleLine*/ ); } for( var i = 0; (operator == null) && (i -1 && !IsInsideString(opIndex) && !IsInsideCode(opIndex) ) { operator = opSym; var left = CreateExpression( expStr.substring(0,opIndex) ); var right = CreateExpression( expStr.substring(opIndex+opSym.length) ); var exp = new ExpNode( operator, left, right ); return exp; } } if( operator == null ) { return StringToValue(expStr); } } this.CreateExpression = CreateExpression; function IsWhitespace(str) { return ( str === ” ” || str === “\t” || str === “\n” ); } function IsExpression(str) { var tempState = new ParserState(null, str); // hacky var textOutsideCodeBlocks = “”; while (!tempState.Done()) { if (tempState.MatchAhead(Sym.CodeOpen)) { tempState.ConsumeBlock(Sym.CodeOpen, Sym.CodeClose); } else { textOutsideCodeBlocks += tempState.Char(); tempState.Step(); } } var containsAnyExpressionOperators = (textOutsideCodeBlocks.indexOf(Sym.ConditionEnd) != -1) || (textOutsideCodeBlocks.indexOf(Sym.Set) != -1) || (Sym.Operators.some(function(opSym) { return textOutsideCodeBlocks.indexOf(opSym) != -1; })); return containsAnyExpressionOperators; } function IsLiteral(str) { var isBool = str === “true” || str === “false”; var isNum = !isNaN(parseFloat(str)); var isStr = str[0] === ‘”‘ && str[str.length-1] === ‘”‘; var isVar = IsValidVariableName(str); var isEmpty = str.length === 0; return isBool || isNum || isStr || isVar || isEmpty; } function ParseExpression(state) { var line = state.Source(); // state.Peak( [Sym.Linebreak] ); // TODO : remove the linebreak thing // bitsy.log(“EXPRESSION ” + line); var exp = CreateExpression(line); // bitsy.log(exp); state.curNode.AddChild(exp); state.Step(line.length); return state; } function IsConditionalBlock(state) { var peakToFirstListSymbol = state.Peak([Sym.List]); var foundListSymbol = peakToFirstListSymbol < state.Source().length; var areAllCharsBeforeListWhitespace = true; for (var i = 0; i < peakToFirstListSymbol.length; i++) { if (!IsWhitespace(peakToFirstListSymbol[i])) { areAllCharsBeforeListWhitespace = false; } } var peakToFirstConditionSymbol = state.Peak([Sym.ConditionEnd]); peakToFirstConditionSymbol = peakToFirstConditionSymbol.slice(peakToFirstListSymbol.length); var hasNoLinebreakBetweenListAndConditionEnd = peakToFirstConditionSymbol.indexOf(Sym.Linebreak) == -1; return foundListSymbol && areAllCharsBeforeListWhitespace && hasNoLinebreakBetweenListAndConditionEnd; } function ParseCode(state) { if (IsConditionalBlock(state)) { state = ParseConditional(state); } else if (environment.HasFunction(state.Peak([" "]))) { // TODO — what about newlines??? var funcName = state.Peak([" "]); state.Step(funcName.length); state = ParseFunction(state, funcName); } else if (IsSequence(state.Peak([" ", Sym.Linebreak]))) { var sequenceType = state.Peak([" ", Sym.Linebreak]); state.Step(sequenceType.length); state = ParseSequence(state, sequenceType); } else if (IsLiteral(state.Source()) || IsExpression(state.Source())) { state = ParseExpression(state); } else { var undefinedSrc = state.Peak([]); var undefinedNode = new UndefinedNode(undefinedSrc); state.curNode.AddChild(undefinedNode); } // just go to the end now while (!state.Done()) { state.Step(); } return state; } function ParseCodeBlock(state) { var codeStr = state.ConsumeBlock( Sym.CodeOpen, Sym.CodeClose ); var codeState = new ParserState(new CodeBlockNode(), codeStr); codeState = ParseCode( codeState ); state.curNode.AddChild( codeState.rootNode ); return state; } } } // Script() function Dialog() { this.CreateRenderer = function() { return new DialogRenderer(); }; this.CreateBuffer = function() { return new DialogBuffer(); }; var DialogRenderer = function() { // TODO : refactor this eventually? remove everything from struct.. avoid the defaults? var textboxInfo = { width : 104, height : 8+4+2+5, //8 for text, 4 for top-bottom padding, 2 for line padding, 5 for arrow top : 12, left : 12, bottom : 12, //for drawing it from the bottom padding_vert : 2, padding_horz : 4, arrow_height : 5, }; var font = null; this.SetFont = function(f) { font = f; textboxInfo.height = (textboxInfo.padding_vert * 3) + (relativeFontHeight() * 2) + textboxInfo.arrow_height; // todo : clean up all the scale stuff var textboxScaleW = textboxInfo.width * getTextScale(); var textboxScaleH = textboxInfo.height * getTextScale(); bitsy.textbox(false, 0, 0, textboxScaleW, textboxScaleH); } this.GetPixelsPerRow = function() { return (textboxInfo.width – (textboxInfo.padding_horz * 2)) * getTextScale(); } // todo : cache this value? it shouldn’t really change in the middle of a game function getTextScale() { return bitsy.textMode() === bitsy.TXT_LOREZ ? 1 : 2; } function relativeFontWidth() { return Math.ceil(font.getWidth() / getTextScale()); } function relativeFontHeight() { return Math.ceil(font.getHeight() / getTextScale()); } this.ClearTextbox = function() { bitsy.fill(bitsy.TEXTBOX, textBackgroundIndex); }; var isCentered = false; this.SetCentered = function(centered) { isCentered = centered; }; // todo : I can stop doing this every frame right? this.DrawTextbox = function() { if (isCentered) { // todo : will the height calculations always work? bitsy.textbox(true, textboxInfo.left, ((bitsy.VIDEO_SIZE / 2) – (textboxInfo.height / 2))); } else if (player().y < (bitsy.MAP_SIZE / 2)) { // bottom bitsy.textbox(true, textboxInfo.left, (bitsy.VIDEO_SIZE – textboxInfo.bottom – textboxInfo.height)); } else { // top bitsy.textbox(true, textboxInfo.left, textboxInfo.top); } }; var arrowdata = [ 1,1,1,1,1, 0,1,1,1,0, 0,0,1,0,0 ]; this.DrawNextArrow = function() { // bitsy.log("draw arrow!"); var text_scale = getTextScale(); var textboxScaleW = textboxInfo.width * text_scale; var textboxScaleH = textboxInfo.height * text_scale; var top = (textboxInfo.height – 5) * text_scale; var left = (textboxInfo.width – (5 + 4)) * text_scale; if (textDirection === TextDirection.RightToLeft) { // RTL hack left = 4 * text_scale; } for (var y = 0; y < 3; y++) { for (var x = 0; x < 5; x++) { var i = (y * 5) + x; if (arrowdata[i] == 1) { //scaling nonsense for (var sy = 0; sy < text_scale; sy++) { for (var sx = 0; sx < text_scale; sx++) { var px = left + (x * text_scale) + sx; var py = top + (y * text_scale) + sy; bitsy.set(bitsy.TEXTBOX, (py * textboxScaleW) + px, textArrowIndex); } } } } } }; function drawCharData(charData, textScale, top, left, width, height, color) { for (var y = 0; y < height; y++) { for (var x = 0; x 0) { char.redraw = true; } // skip characters that are already drawn and don’t need to be updated if (!char.redraw) { return; } char.redraw = false; var text_scale = getTextScale(); var charData = char.bitmap; var top; var left; if (char.effectList.length > 0) { // clear the pixels from the previous frame top = (4 * text_scale) + (row * 2 * text_scale) + (row * font.getHeight()) + Math.floor(char.offset.y); left = (4 * text_scale) + leftPos + Math.floor(char.offset.x); drawCharData(charData, text_scale, top, left, char.width, char.height, textBackgroundIndex); } // compute render offset *every* frame char.offset = { x: char.base_offset.x, y: char.base_offset.y }; char.SetPosition(row, col); char.ApplyEffects(effectTime); top = (4 * text_scale) + (row * 2 * text_scale) + (row * font.getHeight()) + Math.floor(char.offset.y); left = (4 * text_scale) + leftPos + Math.floor(char.offset.x); drawCharData(charData, text_scale, top, left, char.width, char.height, char.color); // TODO : consider for a future update? /* if (soundPlayer && char.blip && char.hasPlayedBlip != true) { soundPlayer.playBlip(blip[char.blip], { isPitchRandomized: true }); char.hasPlayedBlip = true; } */ // call printHandler for character if (!disableOnPrintHandlers) { char.OnPrint(); } }; var effectTime = 0; // TODO this variable should live somewhere better var shouldUpdateTextboxSettings = true; var shouldClearTextbox = true; var shouldDrawArrow = true; var disableOnPrintHandlers = false; this.Draw = function(buffer, dt, disableOnPrint) { disableOnPrintHandlers = (disableOnPrint === true); // bitsy.log(“draw dialog”); if (buffer.DidFlipPageThisFrame()) { shouldClearTextbox = true; shouldDrawArrow = true; } effectTime += dt; if (shouldUpdateTextboxSettings) { bitsy.log(“draw textbox”); this.DrawTextbox(); // todo : rename to something more accurate shouldUpdateTextboxSettings = false; } if (shouldClearTextbox) { // bitsy.log(“clear textbox”); this.ClearTextbox(); shouldClearTextbox = false; } // bitsy.log(“draw chars”); buffer.ForEachActiveChar(this.DrawChar); if (buffer.CanContinue() && shouldDrawArrow) { // bitsy.log(“draw next arrow”); this.DrawNextArrow(); shouldDrawArrow = false; } if (buffer.DidPageFinishThisFrame() && onPageFinish != null) { bitsy.log(“page finished”); onPageFinish(); } // bitsy.log(“draw dialog end”); }; /* this is a hook for GIF rendering */ var onPageFinish = null; this.SetPageFinishHandler = function(handler) { onPageFinish = handler; }; this.Reset = function() { effectTime = 0; // TODO – anything else? shouldUpdateTextboxSettings = true; shouldClearTextbox = true; shouldDrawArrow = true; } this.updateTextboxPosition = function() { shouldUpdateTextboxSettings = true; }; // this.CharsPerRow = function() { // return textboxInfo.charsPerRow; // } } var DialogBuffer = function() { var buffer = [[[]]]; // holds dialog in an array buffer var pageIndex = 0; var rowIndex = 0; var charIndex = 0; var nextCharTimer = 0; var nextCharMaxTime = 50; // in milliseconds var isDialogReadyToContinue = false; var activeTextEffects = []; var activeTextEffectParameters = []; var font = null; var arabicHandler = new ArabicHandler(); var onDialogEndCallbacks = []; this.SetFont = function(f) { font = f; }; this.SetPixelsPerRow = function(n) { pixelsPerRow = n; }; this.CurPage = function() { return buffer[ pageIndex ]; }; this.CurRow = function() { return this.CurPage()[ rowIndex ]; }; this.CurChar = function() { return this.CurRow()[ charIndex ]; }; this.CurPageCount = function() { return buffer.length; }; this.CurRowCount = function() { return this.CurPage().length; }; this.CurCharCount = function() { return this.CurRow().length; }; this.ForEachActiveChar = function(handler) { // Iterates over visible characters on the active page var rowCount = rowIndex + 1; for (var i = 0; i < rowCount; i++) { var row = this.CurPage()[i]; var charCount = (i == rowIndex) ? charIndex+1 : row.length; // bitsy.log(charCount); var leftPos = 0; if (textDirection === TextDirection.RightToLeft) { leftPos = 24 * 8; // hack — I think this is correct? } for(var j = 0; j < charCount; j++) { var char = row[j]; if(char) { if (textDirection === TextDirection.RightToLeft) { leftPos -= char.spacing; } // bitsy.log(j + " " + leftPos); // handler( char, i /*rowIndex*/, j /*colIndex*/ ); handler(char, i /*rowIndex*/, j /*colIndex*/, leftPos) if (textDirection === TextDirection.LeftToRight) { leftPos += char.spacing; } } } } } this.Reset = function() { buffer = [[[]]]; pageIndex = 0; rowIndex = 0; charIndex = 0; isDialogReadyToContinue = false; afterManualPagebreak = false; activeTextEffects = []; onDialogEndCallbacks = []; isActive = false; }; this.DoNextChar = function() { nextCharTimer = 0; //reset timer //time to update characters if (charIndex + 1 < this.CurCharCount()) { //add char to current row charIndex++; } else if (rowIndex + 1 nextCharMaxTime) { this.DoNextChar(); } }; var isSkipping = false; this.Skip = function() { bitsy.log(“SKIPPP”); isSkipping = true; didPageFinishThisFrame = false; didFlipPageThisFrame = false; // add new characters until you get to the end of the current line of dialog while (rowIndex < this.CurRowCount() && isSkipping) { this.DoNextChar(); if (isDialogReadyToContinue) { //make sure to push the rowIndex past the end to break out of the loop rowIndex++; charIndex = 0; } } if (isSkipping) { rowIndex = this.CurRowCount() – 1; charIndex = this.CurCharCount() – 1; } isSkipping = false; }; this.tryInterruptSkip = function() { if (isSkipping) { isSkipping = false; return true; } return false; }; this.FlipPage = function() { didFlipPageThisFrame = true; isDialogReadyToContinue = false; pageIndex++; rowIndex = 0; charIndex = 0; } this.EndDialog = function() { isActive = false; // no more text to show… this should be a sign to stop rendering dialog for (var i = 0; i < onDialogEndCallbacks.length; i++) { onDialogEndCallbacks[i](); } } var afterManualPagebreak = false; // is it bad to track this state like this? this.Continue = function() { bitsy.log("CONTINUE"); // if we used a page break character to continue we need // to run whatever is in the script afterwards! // TODO : make this comment better if (this.CurChar().isPageBreak) { // hacky: always treat a page break as the end of dialog // if there's more dialog later we re-activate the dialog buffer this.EndDialog(); afterManualPagebreak = true; this.CurChar().OnContinue(); return false; } if (pageIndex + 1 < this.CurPageCount()) { bitsy.log("FLIP PAGE!"); //start next page this.FlipPage(); return true; /* hasMoreDialog */ } else { bitsy.log("END DIALOG!"); bitsy.textbox(false); //end dialog mode this.EndDialog(); return false; /* hasMoreDialog */ } }; var isActive = false; this.IsActive = function() { return isActive; }; this.OnDialogEnd = function(callback) { if (!isActive) { callback(); } else { onDialogEndCallbacks.push(callback); } } this.CanContinue = function() { return isDialogReadyToContinue; }; function DialogChar() { this.redraw = true; this.effectList = []; this.effectParameterList = []; this.color = textColorIndex; // white this.offset = { x:0, y:0 }; // in pixels (screen pixels?) this.col = 0; this.row = 0; this.SetPosition = function(row,col) { // bitsy.log("SET POS"); // bitsy.log(this); this.row = row; this.col = col; }; this.ApplyEffects = function(time) { // bitsy.log("APPLY EFFECTS! " + time); for (var i = 0; i < this.effectList.length; i++) { var effectName = this.effectList[i]; // bitsy.log("FX " + effectName); TextEffects[effectName].doEffect(this, time, this.effectParameterList[i]); } }; var printHandler = null; // optional function to be called once on printing character this.SetPrintHandler = function(handler) { printHandler = handler; }; this.OnPrint = function() { if (printHandler != null) { // bitsy.log("PRINT HANDLER —- DIALOG BUFFER"); printHandler(); printHandler = null; // only call handler once (hacky) } }; this.bitmap = []; this.width = 0; this.height = 0; this.base_offset = { // hacky name x: 0, y: 0 }; this.spacing = 0; } function DialogFontChar(font, char, effectList, effectParameterList) { DialogChar.call(this); this.effectList = effectList.slice(); // clone effect list (since it can change between chars) this.effectParameterList = effectParameterList.slice(); var charData = font.getChar(char); this.char = char; this.bitmap = charData.data; this.width = charData.width; this.height = charData.height; this.base_offset.x = charData.offset.x; this.base_offset.y = charData.offset.y; this.spacing = charData.spacing; this.blip = null; this.hasPlayedBlip = false; } function DialogDrawingChar(drawingId, effectList, effectParameterList) { DialogChar.call(this); this.effectList = effectList.slice(); // clone effect list (since it can change between chars) this.effectParameterList = effectParameterList.slice(); // get the first frame of the drawing and flatten it var drawingData = renderer.GetDrawingSource(drawingId)[0]; var drawingDataFlat = []; for (var i = 0; i < drawingData.length; i++) { drawingDataFlat = drawingDataFlat.concat(drawingData[i]); } this.bitmap = drawingDataFlat; this.width = 8; this.height = 8; this.spacing = 8; } function DialogScriptControlChar() { DialogChar.call(this); this.width = 0; this.height = 0; this.spacing = 0; } // is a control character really the best way to handle page breaks? function DialogPageBreakChar() { DialogChar.call(this); this.width = 0; this.height = 0; this.spacing = 0; this.isPageBreak = true; var continueHandler = null; this.SetContinueHandler = function(handler) { continueHandler = handler; }; this.OnContinue = function() { if (continueHandler) { continueHandler(); } }; } function AddWordToCharArray(charArray, word, effectList, effectParameterList) { // bitsy.log("add char array"); for (var i = 0; i < word.length; i++) { charArray.push(new DialogFontChar(font, word[i], effectList, effectParameterList)); } // bitsy.log("add char array end"); return charArray; } function GetCharArrayWidth(charArray) { var width = 0; for(var i = 0; i < charArray.length; i++) { width += charArray[i].spacing; } return width; } function GetStringWidth(str) { var width = 0; for (var i = 0; i < str.length; i++) { var charData = font.getChar(str[i]); width += charData.spacing; } return width; } var pixelsPerRow = 192; // hard-coded fun times!!! this.AddScriptReturn = function(onReturnHandler) { var curPageIndex = buffer.length – 1; var curRowIndex = buffer[curPageIndex].length – 1; var curRowArr = buffer[curPageIndex][curRowIndex]; var controlChar = new DialogScriptControlChar(); controlChar.SetPrintHandler(onReturnHandler); curRowArr.push(controlChar); isActive = true; } this.AddDrawing = function(drawingId) { // bitsy.log("DRAWING ID " + drawingId); var curPageIndex = buffer.length – 1; var curRowIndex = buffer[curPageIndex].length – 1; var curRowArr = buffer[curPageIndex][curRowIndex]; var drawingChar = new DialogDrawingChar(drawingId, activeTextEffects, activeTextEffectParameters); var rowLength = GetCharArrayWidth(curRowArr); // TODO : clean up copy-pasted code here :/ if (afterManualPagebreak) { this.FlipPage(); // hacky buffer[curPageIndex][curRowIndex] = curRowArr; buffer.push([]); curPageIndex++; buffer[curPageIndex].push([]); curRowIndex = 0; curRowArr = buffer[curPageIndex][curRowIndex]; curRowArr.push(drawingChar); afterManualPagebreak = false; } else if (rowLength + drawingChar.spacing <= pixelsPerRow || rowLength <= 0) { //stay on same row curRowArr.push(drawingChar); } else if (curRowIndex == 0) { //start next row buffer[curPageIndex][curRowIndex] = curRowArr; buffer[curPageIndex].push([]); curRowIndex++; curRowArr = buffer[curPageIndex][curRowIndex]; curRowArr.push(drawingChar); } else { //start next page buffer[curPageIndex][curRowIndex] = curRowArr; buffer.push([]); curPageIndex++; buffer[curPageIndex].push([]); curRowIndex = 0; curRowArr = buffer[curPageIndex][curRowIndex]; curRowArr.push(drawingChar); } isActive = true; // this feels like a bad way to do this??? } // TODO : convert this into something that takes DialogChar arrays this.AddText = function(textStr) { bitsy.log("ADD TEXT " + textStr); //process dialog so it's easier to display var words = textStr.split(" "); // var curPageIndex = this.CurPageCount() – 1; // var curRowIndex = this.CurRowCount() – 1; // var curRowArr = this.CurRow(); var curPageIndex = buffer.length – 1; var curRowIndex = buffer[curPageIndex].length – 1; var curRowArr = buffer[curPageIndex][curRowIndex]; for (var i = 0; i < words.length; i++) { var word = words[i]; if (arabicHandler.ContainsArabicCharacters(word)) { word = arabicHandler.ShapeArabicCharacters(word); } var wordWithPrecedingSpace = ((i == 0) ? "" : " ") + word; var wordLength = GetStringWidth(wordWithPrecedingSpace); var rowLength = GetCharArrayWidth(curRowArr); if (afterManualPagebreak) { this.FlipPage(); // hacky copied bit for page breaks buffer[curPageIndex][curRowIndex] = curRowArr; buffer.push([]); curPageIndex++; buffer[curPageIndex].push([]); curRowIndex = 0; curRowArr = buffer[curPageIndex][curRowIndex]; curRowArr = AddWordToCharArray(curRowArr, word, activeTextEffects, activeTextEffectParameters); afterManualPagebreak = false; } else if (rowLength + wordLength <= pixelsPerRow || rowLength 0) { var lastChar = lastRow[lastRow.length-1]; } // bitsy.log(buffer); bitsy.log(“add text finished”); isActive = true; }; this.AddLinebreak = function() { var lastPage = buffer[buffer.length-1]; if (lastPage.length = arabicCharStart && code <= arabicCharEnd); } function ContainsArabicCharacters(word) { for (var i = 0; i < word.length; i++) { if (IsArabicCharacter(word[i])) { return true; } } return false; } function IsDisconnectedCharacter(char) { var code = char.charCodeAt(0); return disconnectedCharacters.indexOf(code) != -1; } function ShapeArabicCharacters(word) { var shapedWord = ""; for (var i = 0; i = 0 && IsArabicCharacter(word[i-1]) && !IsDisconnectedCharacter(word[i-1]); var connectedToNextChar = i+1 0) { char.color = tileColorStartIndex + parameters[0]; } else { char.color = tileColorStartIndex + index; } }; } TextEffects[“clr”] = new ColorEffect(); TextEffects[“clr1”] = new ColorEffect(0); TextEffects[“clr2”] = new ColorEffect(1); TextEffects[“clr3”] = new ColorEffect(2); function WavyEffect() { this.doEffect = function(char, time, parameters) { char.offset.y += Math.sin((time / 250) – (char.col / 2)) * 2; }; } TextEffects[“wvy”] = new WavyEffect(); function ShakyEffect() { function disturb(func, time, offset, mult1, mult2) { return func((time * mult1) – (offset * mult2)); } this.doEffect = function(char, time, parameters) { char.offset.y += 1.5 * disturb(Math.sin, time, char.col, 0.1, 0.5) * disturb(Math.cos, time, char.col, 0.3, 0.2) * disturb(Math.sin, time, char.row, 2.0, 1.0); char.offset.x += 1.5 * disturb(Math.cos, time, char.row, 0.1, 1.0) * disturb(Math.sin, time, char.col, 3.0, 0.7) * disturb(Math.cos, time, char.col, 0.2, 0.3); }; } TextEffects[“shk”] = new ShakyEffect(); /* // TODO : maybe use this in a future update? function YakEffect() { this.doEffect = function(char, time, parameters) { if (char.char != ” “) { char.blip = parameters[0]; } }; } TextEffects[“yak”] = new YakEffect(); */ var DebugHighlightEffect = function() { this.doEffect = function(char, time, parameters) { char.color = tileColorStartIndex; }; } TextEffects[“_debug_highlight”] = new DebugHighlightEffect(); } // Dialog() function TileRenderer() { bitsy.log(“!!!!! NEW TILE RENDERER”); var drawingCache = { source: {}, render: {}, }; // var debugRenderCount = 0; function createRenderCacheId(drawingId, colorIndex) { return drawingId + “_” + colorIndex; } function renderDrawing(drawing) { // debugRenderCount++; // bitsy.log(“RENDER COUNT ” + debugRenderCount); var col = drawing.col; var bgc = drawing.bgc; var drwId = drawing.drw; var drawingFrames = drawingCache.source[drwId]; // initialize render cache entry var cacheId = createRenderCacheId(drwId, col); if (drawingCache.render[cacheId] === undefined) { // initialize array of frames for drawing drawingCache.render[cacheId] = []; } for (var i = 0; i < drawingFrames.length; i++) { var frameData = drawingFrames[i]; var frameTileId = renderTileFromDrawingData(frameData, col, bgc); drawingCache.render[cacheId].push(frameTileId); } } function renderTileFromDrawingData(drawingData, col, bgc) { var tileId = bitsy.tile(); var backgroundColor = tileColorStartIndex + bgc; var foregroundColor = tileColorStartIndex + col; bitsy.fill(tileId, backgroundColor); for (var y = 0; y < bitsy.TILE_SIZE; y++) { for (var x = 0; x < bitsy.TILE_SIZE; x++) { var px = drawingData[y][x]; if (px === 1) { bitsy.set(tileId, (y * bitsy.TILE_SIZE) + x, foregroundColor); } } } return tileId; } // TODO : move into core function undefinedOrNull(x) { return x === undefined || x === null; } function isDrawingRendered(drawing) { var cacheId = createRenderCacheId(drawing.drw, drawing.col); return drawingCache.render[cacheId] != undefined; } function getRenderedDrawingFrames(drawing) { var cacheId = createRenderCacheId(drawing.drw, drawing.col); return drawingCache.render[cacheId]; } function getDrawingFrameTileId(drawing, frameOverride) { var frameIndex = 0; if (drawing != null && drawing.animation.isAnimated) { if (frameOverride != undefined && frameOverride != null) { frameIndex = frameOverride; } else { frameIndex = drawing.animation.frameIndex; } } return getRenderedDrawingFrames(drawing)[frameIndex]; } function getOrRenderDrawingFrame(drawing, frameOverride) { // bitsy.log("frame render: " + drawing.type + " " + drawing.id + " f:" + frameOverride); if (!isDrawingRendered(drawing)) { bitsy.log("frame render: doesn't exist " + drawing.id); renderDrawing(drawing); } return getDrawingFrameTileId(drawing, frameOverride); } function deleteRenders(drawingId) { for (var cacheId in drawingCache.render) { if (cacheId.indexOf(drawingId) === 0) { var tiles = drawingCache.render[cacheId]; for (var i = 0; i < tiles.length; i++) { bitsy.delete(tiles[i]); } delete drawingCache.render[cacheId]; } } } /* PUBLIC INTERFACE */ this.GetDrawingFrame = getOrRenderDrawingFrame; // todo : leave individual get and set stuff for now – should I remove later? // todo : better name for function? this.SetDrawings = function(drawingSource) { drawingCache.source = drawingSource; // need to reset entire render cache when all the drawings are changed drawingCache.render = {}; }; this.SetDrawingSource = function(drawingId, drawingData) { deleteRenders(drawingId); drawingCache.source[drawingId] = drawingData; }; this.GetDrawingSource = function(drawingId) { return drawingCache.source[drawingId]; }; this.GetFrameCount = function(drawingId) { return drawingCache.source[drawingId].length; }; // todo : forceReset option is hacky? this.ClearCache = function(forceReset) { if (forceReset === undefined || forceReset === true) { for (var cacheId in drawingCache.render) { var tiles = drawingCache.render[cacheId]; for (var i = 0; i < tiles.length; i++) { bitsy.delete(tiles[i]); } } } drawingCache.render = {}; }; this.deleteDrawing = deleteRenders; } // Renderer() /* WORLD DATA */ var room = {}; var tile = {}; var sprite = {}; var item = {}; var dialog = {}; var end = {}; // for backwards compatibility var palette = { // start off with a default palette “default” : { name : “default”, colors : [[0,0,0],[255,255,255],[255,255,255]] } }; var variable = {}; // these are starting variable values — they don’t update (or I don’t think they will) var tune = {}; var blip = {}; var playerId = “A”; var fontName = defaultFontName; var textDirection = TextDirection.LeftToRight; /* NAME-TO-ID MAPS */ var names = { room : {}, tile : {}, sprite : {}, item : {}, dialog : {}, palette : {}, tune : {}, blip : {}, }; // todo : this is basically a copy of the one in world.js – can I remove it? function updateNamesFromCurData() { function createNameMap(objectStore) { var map = {}; for (id in objectStore) { if (objectStore[id].name != undefined && objectStore[id].name != null) { map[objectStore[id].name] = id; } } return map; } names.room = createNameMap(room); names.tile = createNameMap(tile); names.sprite = createNameMap(sprite); names.item = createNameMap(item); names.dialog = createNameMap(dialog); names.palette = createNameMap(palette); names.tune = createNameMap(tune); names.blip = createNameMap(blip); } /* GAME STATE */ var state = {} function resetGameState() { state.room = “0”; state.ava = playerId; // avatar appearance override state.pal = “0”; // current palette id state.tune = “0”; // current tune id (“0” === off) } // title helper functions function getTitle() { return dialog[titleDialogId].src; } function setTitle(titleSrc) { dialog[titleDialogId] = { src:titleSrc, name:null }; } /* FLAGS */ var flags = createDefaultFlags(); // feature flags for testing purposes var engineFeatureFlags = { isSoundEnabled : true, isFontEnabled : true, isTransitionEnabled : true, isScriptEnabled : true, isDialogEnabled : true, isRendererEnabled : true, }; function clearGameData() { room = {}; tile = {}; sprite = {}; item = {}; dialog = {}; palette = { //start off with a default palette “default” : { name : “default”, colors : [[0,0,0],[255,255,255],[255,255,255]] } }; isEnding = false; //todo – correct place for this? variable = {}; updateNamesFromCurData(); fontName = defaultFontName; // TODO : reset font manager too? textDirection = TextDirection.LeftToRight; resetGameState(); isGameLoaded = false; isGameOver = false; } // engine event hooks for the editor var onInventoryChanged = null; var onVariableChanged = null; var onGameReset = null; var onInitRoom = null; var isPlayerEmbeddedInEditor = false; var renderer; if (engineFeatureFlags.isRendererEnabled) { renderer = new TileRenderer(); } var curGameData = null; var curDefaultFontData = null; var isGameLoaded = false; var isGameOver = false; function load_game(gameData, defaultFontData, startWithTitle) { // bitsy.log(“game data in: \n” + gameData); curGameData = gameData; //remember the current game (used to reset the game) if (dialogBuffer) { dialogBuffer.Reset(); } if (scriptInterpreter) { scriptInterpreter.ResetEnvironment(); // ensures variables are reset — is this the best way? } loadWorldFromGameData(gameData); bitsy.log(“world loaded”); if (fontManager && !isPlayerEmbeddedInEditor && defaultFontData) { bitsy.log(“load font”); curDefaultFontData = defaultFontData; // store for resetting game // todo : consider replacing this with a more general system for requesting resources from the system? // hack to ensure default font is available fontManager.AddResource(defaultFontName + fontManager.GetExtension(), defaultFontData); bitsy.log(“load font end”); } // request text mode if (flags.TXT_MODE === 1) { bitsy.textMode(bitsy.TXT_LOREZ); } else { // default to 2x scale for text rendering bitsy.textMode(bitsy.TXT_HIREZ); } if (fontManager && dialogBuffer) { bitsy.log(“get font”); var font = fontManager.Get( fontName ); dialogBuffer.SetFont(font); dialogRenderer.SetFont(font); bitsy.log(“get font end”); } if (dialogBuffer) { // this feels a little silly to me – oh well?? dialogBuffer.SetPixelsPerRow(dialogRenderer.GetPixelsPerRow()); } setInitialVariables(); bitsy.log(“ready”); onready(startWithTitle); isGameLoaded = true; } function loadWorldFromGameData(gameData) { bitsy.log(“load world from game data”); var world = parseWorld(gameData); bitsy.log(“parse world done”); // move world data into global scope palette = world.palette; room = world.room; tile = world.tile; sprite = world.sprite; item = world.item; dialog = world.dialog; end = world.end; // back compat endings variable = world.variable; fontName = world.fontName; textDirection = world.textDirection; tune = world.tune; blip = world.blip; flags = world.flags; names = world.names; if (renderer) { renderer.SetDrawings(world.drawings); } // find starting room and initialize it var roomIds = Object.keys(room); if (player() != undefined && player().room != null && roomIds.indexOf(player().room) != -1) { // player has valid room state.room = player().room; } else if (roomIds.length > 0) { // player not in any room! what the heck state.room = roomIds[0]; } else { // uh oh there are no rooms I guess??? state.room = null; } if (state.room != null) { bitsy.log(“INIT ROOM ” + state.room); initRoom(state.room); } } function reset_cur_game() { if (curGameData == null) { return; //can’t reset if we don’t have the game data } stopGame(); clearGameData(); if (isPlayerEmbeddedInEditor && onGameReset != null) { onGameReset(); } } function onready(startWithTitle) { bitsy.log(“game ready!”); if (startWithTitle === undefined || startWithTitle === null) { startWithTitle = true; } if (startWithTitle) { // used by editor startNarrating(getTitle()); } } function setInitialVariables() { if (!scriptInterpreter) { return; } for(id in variable) { var value = variable[id]; // default to string if(value === “true”) { value = true; } else if(value === “false”) { value = false; } else if(!isNaN(parseFloat(value))) { value = parseFloat(value); } scriptInterpreter.SetVariable(id,value); } scriptInterpreter.SetOnVariableChangeHandler( onVariableChanged ); } function getOffset(evt) { var offset = { x:0, y:0 }; var el = evt.target; var rect = el.getBoundingClientRect(); offset.x += rect.left + el.scrollLeft; offset.y += rect.top + el.scrollTop; offset.x = evt.clientX – offset.x; offset.y = evt.clientY – offset.y; return offset; } function stopGame() { if (soundPlayer) { soundPlayer.stopTune(); } bitsy.log(“stop GAME!”); } function update(dt) { if (!isGameLoaded) { load_game(bitsy.getGameData(), bitsy.getFontData()); } if (state.room == null) { // in the special case where there is no valid room, end the game startNarrating( “”, true /*isEnding*/ ); } if (!transition || !transition.IsTransitionActive()) { updateInput(); } if (transition && transition.IsTransitionActive()) { // transition animation takes over everything! transition.UpdateTransition(dt); } else { if (bitsy.graphicsMode() != bitsy.GFX_MAP) { bitsy.graphicsMode(bitsy.GFX_MAP); } if (soundPlayer) { soundPlayer.update(dt); } if (!isNarrating && !isEnding) { // draw world if game has begun var didAnimate = updateAnimation(dt); // test whether player moved so we can redraw just the avatar playerCurX = player().x; playerCurY = player().y; var didPlayerMove = (playerPrevX != playerCurX) || (playerPrevY != playerCurY); drawRoom(room[state.room], { redrawAnimated: didAnimate, redrawAvatar: didPlayerMove }); // store player’s position for next frame playerPrevX = playerCurX; playerPrevY = playerCurY; } else { clearRoom(); } if (dialogBuffer && dialogBuffer.IsActive() && !(soundPlayer && soundPlayer.isBlipPlaying())) { // bitsy.log(“update dialog”); // bitsy.log(“renderer”); dialogRenderer.Draw(dialogBuffer, dt); // bitsy.log(“buffer”); dialogBuffer.Update(dt); // bitsy.log(“update dialog end”); } // keep moving avatar if player holds down button if ((!dialogBuffer || !dialogBuffer.IsActive()) && !isEnding) { if (curPlayerDirection != Direction.None) { playerHoldToMoveTimer -= dt; if (playerHoldToMoveTimer = animationTime) { // animate sprites for (id in sprite) { var spr = sprite[id]; if (spr.animation.isAnimated) { spr.animation.frameIndex = (spr.animation.frameIndex + 1) % spr.animation.frameCount; } } // animate tiles for (id in tile) { var til = tile[id]; if (til.animation.isAnimated) { til.animation.frameIndex = (til.animation.frameIndex + 1) % til.animation.frameCount; } } // animate items for (id in item) { var itm = item[id]; if (itm.animation.isAnimated) { itm.animation.frameIndex = (itm.animation.frameIndex + 1) % itm.animation.frameCount; } } // reset counter animationCounter = 0; // updated animations this frame return true; } // did *not* update animations this frame return false; } function resetAllAnimations() { for (id in sprite) { var spr = sprite[id]; if (spr.animation.isAnimated) { spr.animation.frameIndex = 0; } } for (id in tile) { var til = tile[id]; if (til.animation.isAnimated) { til.animation.frameIndex = 0; } } for (id in item) { var itm = item[id]; if (itm.animation.isAnimated) { itm.animation.frameIndex = 0; } } } function getSpriteAt(x, y, roomId) { if (roomId === undefined) { roomId = state.room; } for (id in sprite) { var spr = sprite[id]; if (spr.room === roomId) { if (spr.x == x && spr.y == y) { return id; } } } return null; } var Direction = { None : -1, Up : 0, Down : 1, Left : 2, Right : 3 }; var curPlayerDirection = Direction.None; var playerHoldToMoveTimer = 0; var playerPrevX = 0; var playerPrevY = 0; function movePlayer(direction, isFirstMove) { didPlayerMove = false; var roomIds = Object.keys(room); if (player().room == null || roomIds.indexOf(player().room) -1) { var itm = room[player().room].items[itmIndex]; var itemRoom = player().room; // play sound on pitck up item if (item[itm.id].blip != null) { blipId = item[itm.id].blip; } startItemDialog(itm.id, function() { // remove item from room room[itemRoom].items.splice(itmIndex, 1); // update player inventory if (player().inventory[itm.id]) { player().inventory[itm.id] += 1; } else { player().inventory[itm.id] = 1; } // show inventory change in UI if (onInventoryChanged != null) { onInventoryChanged(itm.id); } }); } if (end) { startEndingDialog(end); } else if (ext) { movePlayerThroughExit(ext); } else if (spr) { // play sound on greet sprite if (sprite[spr].blip != null) { blipId = sprite[spr].blip; } startSpriteDialog(spr /*spriteId*/); } // TODO : maybe add in a future update? /* // play sound when player moves (if no other sound selected) if (isFirstMove && blipId === null && sprite[state.ava].blip != null) { blipId = sprite[state.ava].blip; randomizeBlip = true; blipChannel = bitsy.SOUND2; // play walking sfx *under* the tune melody } */ if (soundPlayer && blipId != null && blip[blipId]) { soundPlayer.playBlip(blip[blipId]); } } var transition; if (engineFeatureFlags.isTransitionEnabled) { transition = new TransitionManager(); } function movePlayerThroughExit(ext) { var GoToDest = function() { if (transition && ext.transition_effect != null) { transition.BeginTransition( player().room, player().x, player().y, ext.dest.room, ext.dest.x, ext.dest.y, ext.transition_effect); transition.UpdateTransition(0); transition.OnTransitionComplete(function() { player().room = ext.dest.room; player().x = ext.dest.x; player().y = ext.dest.y; state.room = ext.dest.room; initRoom(state.room); }); } else { player().room = ext.dest.room; player().x = ext.dest.x; player().y = ext.dest.y; state.room = ext.dest.room; initRoom(state.room); } }; if (ext.dlg != undefined && ext.dlg != null) { // TODO : I need to simplify dialog code, // so I don’t have to get the ID and the source str // every time! startDialog( dialog[ext.dlg].src, ext.dlg, function(result) { var isLocked = ext.property && ext.property.locked === true; if (!isLocked) { GoToDest(); } }, ext); } else { GoToDest(); } } /* PALETTE INDICES */ var backgroundIndex = 0; var textBackgroundIndex = 1; var textArrowIndex = 2; var textColorIndex = 3; // precalculated rainbow colors var rainbowColorStartIndex = 4; var rainbowColorCount = 10; var rainbowColors = [ [255,0,0], [255,217,0], [78,255,0], [0,255,125], [0,192,255], [0,18,255], [136,0,255], [255,0,242], [255,0,138], [255,0,61], ]; function updatePaletteWithTileColors(tileColors) { // the screen background color should match the first tile color if (tileColors.length > 0) { var color = tileColors[0]; bitsy.color(backgroundIndex, color[0], color[1], color[2]); } else { // as a fallback, use black as the background bitsy.log(“no tile colors!”); bitsy.color(backgroundIndex, 0, 0, 0); } // textbox colors bitsy.color(textBackgroundIndex, 0, 0, 0); // black bitsy.color(textArrowIndex, 255, 255, 255); // white bitsy.color(textColorIndex, 255, 255, 255); // white // rainbow colors for (var i = 0; i < rainbowColorCount; i++) { var color = rainbowColors[i]; bitsy.color(rainbowColorStartIndex + i, color[0], color[1], color[2]); } // tile colors for (var i = 0; i < tileColors.length; i++) { var color = tileColors[i]; bitsy.color(tileColorStartIndex + i, color[0], color[1], color[2]); } } function updatePalette(palId) { state.pal = palId; var pal = palette[state.pal]; updatePaletteWithTileColors(pal.colors); } function initRoom(roomId) { bitsy.log("init room " + roomId); updatePalette(getRoomPal(roomId)); // update avatar appearance state.ava = (room[roomId].ava != null) ? room[roomId].ava : playerId; if (renderer) { renderer.ClearCache(); } // init exit properties for (var i = 0; i < room[roomId].exits.length; i++) { room[roomId].exits[i].property = { locked:false }; } // init ending properties for (var i = 0; i < room[roomId].endings.length; i++) { room[roomId].endings[i].property = { locked:false }; } if (soundPlayer) { if (!room[roomId].tune || room[roomId].tune === "0" || !tune[room[roomId].tune]) { // stop music state.tune = "0"; soundPlayer.stopTune(); } else if (room[roomId].tune != state.tune) { // start music state.tune = room[roomId].tune; soundPlayer.playTune(tune[state.tune]); } } var drawArgs = { redrawAll: true }; drawRoom(room[roomId], drawArgs); if (onInitRoom) { onInitRoom(roomId); } } function getItemIndex( roomId, x, y ) { for( var i = 0; i < room[roomId].items.length; i++ ) { var itm = room[roomId].items[i]; if ( itm.x == x && itm.y == y) return i; } return -1; } function getSpriteLeft() { //repetitive? return getSpriteAt( player().x – 1, player().y ); } function getSpriteRight() { return getSpriteAt( player().x + 1, player().y ); } function getSpriteUp() { return getSpriteAt( player().x, player().y – 1 ); } function getSpriteDown() { return getSpriteAt( player().x, player().y + 1 ); } function isWallLeft() { return (player().x – 1 = bitsy.MAP_SIZE) || isWall(player().x + 1, player().y); } function isWallUp() { return (player().y – 1 = bitsy.MAP_SIZE) || isWall(player().x, player().y + 1); } function isWall(x, y, roomId) { if (roomId == undefined || roomId == null) { roomId = state.room; } var tileId = getTile(x, y, roomId); if (tileId === ‘0’) { return false; // Blank spaces aren’t walls, ya doofus } if (tile[tileId].isWall === undefined || tile[tileId].isWall === null) { // No wall-state defined: check room-specific walls var i = room[roomId].walls.indexOf(getTile(x, y, roomId)); return (i > -1); } // Otherwise, use the tile’s own wall-state return tile[tileId].isWall; } function getItem(roomId,x,y) { for (i in room[roomId].items) { var item = room[roomId].items[i]; if (x == item.x && y == item.y) { return item; } } return null; } function getExit(roomId,x,y) { for (i in room[roomId].exits) { var e = room[roomId].exits[i]; if (x == e.x && y == e.y) { return e; } } return null; } function getEnding(roomId,x,y) { for (i in room[roomId].endings) { var e = room[roomId].endings[i]; if (x == e.x && y == e.y) { return e; } } return null; } function getTile(x, y, roomId) { // bitsy.log(x + ” ” + y); var t = getRoom(roomId).tilemap[y][x]; return t; } function player() { return sprite[playerId]; } // Sort of a hack for legacy palette code (when it was just an array) function getPal(id) { if (palette[id] === undefined) { id = “default”; } return palette[ id ].colors; } function getRoom(id) { return room[id === undefined ? state.room : id]; } function isSpriteOffstage(id) { return sprite[id].room == null; } function serializeNote(note, key, useFriendlyName) { var isSolfa = (key != undefined && key != null); var noteType = (isSolfa === true) ? Solfa : Note; if (isSolfa && key.scale.indexOf(note) === -1) { // no matching note in key return null; } if (isSolfa && useFriendlyName != true) { for (var name in Solfa) { if (Solfa[name] === note) { return name.toLowerCase(); } } // no solfa note found return null; } // for a solfa note’s “friendly name” convert to the chromatic equivalent if (isSolfa && useFriendlyName === true) { note = key.notes[note]; } // from this point on, we know the note we’re looking for is chromatic for (var name in Note) { if (Note[name] === note) { name = name.replace(“_SHARP”, “#”); if (useFriendlyName === true && name === “H”) { name = “C”; } return name; } } // no note found return symbol; } function serializeOctave(octave) { for (var symbol in Octave) { if (Octave[symbol] === octave) { return symbol; } } // default to middle octave return “4”; } //TODO this is in progress and doesn’t support all features function serializeWorld(skipFonts) { if (skipFonts === undefined || skipFonts === null) { skipFonts = false; } // update version flags flags.VER_MAJ = version.major; flags.VER_MIN = version.minor; var worldStr = “”; /* TITLE */ worldStr += getTitle() + “\n”; worldStr += “\n”; /* VERSION */ worldStr += “# BITSY VERSION ” + getEngineVersion() + “\n”; // add version as a comment for debugging purposes if (version.devBuildPhase != “RELEASE”) { worldStr += “# DEVELOPMENT BUILD — ” + version.devBuildPhase; } worldStr += “\n”; /* FLAGS */ for (f in flags) { worldStr += “! ” + f + ” ” + flags[f] + “\n”; } worldStr += “\n” /* FONT */ if (fontName != defaultFontName) { worldStr += “DEFAULT_FONT ” + fontName + “\n”; worldStr += “\n” } if (textDirection != TextDirection.LeftToRight) { worldStr += “TEXT_DIRECTION ” + textDirection + “\n”; worldStr += “\n” } /* PALETTE */ for (id in palette) { if (id != “default”) { worldStr += “PAL ” + id + “\n”; for (i in getPal(id)) { for (j in getPal(id)[i]) { worldStr += getPal(id)[i][j]; if (j < 2) worldStr += ","; } worldStr += "\n"; } if (palette[id].name != null) { worldStr += "NAME " + palette[id].name + "\n"; } worldStr += "\n"; } } /* ROOM */ for (id in room) { worldStr += "ROOM " + id + "\n"; if ( flags.ROOM_FORMAT == 0 ) { // old non-comma separated format for (i in room[id].tilemap) { for (j in room[id].tilemap[i]) { worldStr += room[id].tilemap[i][j]; } worldStr += "\n"; } } else if ( flags.ROOM_FORMAT == 1 ) { // new comma separated format for (i in room[id].tilemap) { for (j in room[id].tilemap[i]) { worldStr += room[id].tilemap[i][j]; if (j 0) { /* WALLS */ worldStr += “WAL “; for (j in room[id].walls) { worldStr += room[id].walls[j]; if (j 0) { /* ITEMS */ for (j in room[id].items) { var itm = room[id].items[j]; worldStr += “ITM ” + itm.id + ” ” + itm.x + “,” + itm.y; worldStr += “\n”; } } if (room[id].exits.length > 0) { /* EXITS */ for (j in room[id].exits) { var e = room[id].exits[j]; if ( isExitValid(e) ) { worldStr += “EXT ” + e.x + “,” + e.y + ” ” + e.dest.room + ” ” + e.dest.x + “,” + e.dest.y; if (e.transition_effect != undefined && e.transition_effect != null) { worldStr += ” FX ” + e.transition_effect; } if (e.dlg != undefined && e.dlg != null) { worldStr += ” DLG ” + e.dlg; } worldStr += “\n”; } } } if (room[id].endings.length > 0) { /* ENDINGS */ for (j in room[id].endings) { var e = room[id].endings[j]; // todo isEndingValid worldStr += “END ” + e.id + ” ” + e.x + “,” + e.y; worldStr += “\n”; } } if (room[id].pal != null && room[id].pal != “default”) { /* PALETTE */ worldStr += “PAL ” + room[id].pal + “\n”; } if (room[id].ava != null) { /* AVATAR SPRITE */ worldStr += “AVA ” + room[id].ava + “\n”; } if (room[id].tune != null && room[id].tune != “0”) { /* TUNE */ worldStr += “TUNE ” + room[id].tune + “\n”; } worldStr += “\n”; } /* TILES */ for (id in tile) { worldStr += “TIL ” + id + “\n”; worldStr += serializeDrawing( “TIL_” + id ); if (tile[id].name != null && tile[id].name != undefined) { /* NAME */ worldStr += “NAME ” + tile[id].name + “\n”; } if (tile[id].isWall != null && tile[id].isWall != undefined) { /* WALL */ worldStr += “WAL ” + tile[id].isWall + “\n”; } if (tile[id].col != null && tile[id].col != undefined && tile[id].col != 1) { /* COLOR OVERRIDE */ worldStr += “COL ” + tile[id].col + “\n”; } if (tile[id].bgc != null && tile[id].bgc != undefined && tile[id].bgc != 0) { /* BACKGROUND COLOR OVERRIDE */ if (tile[id].bgc < 0) { // transparent background worldStr += "BGC *\n"; } else { worldStr += "BGC " + tile[id].bgc + "\n"; } } worldStr += "\n"; } /* SPRITES */ for (id in sprite) { worldStr += "SPR " + id + "\n"; worldStr += serializeDrawing( "SPR_" + id ); if (sprite[id].name != null && sprite[id].name != undefined) { /* NAME */ worldStr += "NAME " + sprite[id].name + "\n"; } if (sprite[id].dlg != null) { worldStr += "DLG " + sprite[id].dlg + "\n"; } if (sprite[id].room != null) { /* SPRITE POSITION */ worldStr += "POS " + sprite[id].room + " " + sprite[id].x + "," + sprite[id].y + "\n"; } if (sprite[id].inventory != null) { for(itemId in sprite[id].inventory) { worldStr += "ITM " + itemId + " " + sprite[id].inventory[itemId] + "\n"; } } if (sprite[id].col != null && sprite[id].col != undefined && sprite[id].col != 2) { /* COLOR OVERRIDE */ worldStr += "COL " + sprite[id].col + "\n"; } if (sprite[id].bgc != null && sprite[id].bgc != undefined && sprite[id].bgc != 0) { /* BACKGROUND COLOR OVERRIDE */ if (sprite[id].bgc < 0) { // transparent background worldStr += "BGC *\n"; } else { worldStr += "BGC " + sprite[id].bgc + "\n"; } } if (sprite[id].blip != null && sprite[id].blip != undefined) { /* BLIP */ worldStr += "BLIP " + sprite[id].blip + "\n"; } worldStr += "\n"; } /* ITEMS */ for (id in item) { worldStr += "ITM " + id + "\n"; worldStr += serializeDrawing( "ITM_" + id ); if (item[id].name != null && item[id].name != undefined) { /* NAME */ worldStr += "NAME " + item[id].name + "\n"; } if (item[id].dlg != null) { worldStr += "DLG " + item[id].dlg + "\n"; } if (item[id].col != null && item[id].col != undefined && item[id].col != 2) { /* COLOR OVERRIDE */ worldStr += "COL " + item[id].col + "\n"; } if (item[id].bgc != null && item[id].bgc != undefined && item[id].bgc != 0) { /* BACKGROUND COLOR OVERRIDE */ if (item[id].bgc < 0) { // transparent background worldStr += "BGC *\n"; } else { worldStr += "BGC " + item[id].bgc + "\n"; } } if (item[id].blip != null && item[id].blip != undefined) { /* BLIP */ worldStr += "BLIP " + item[id].blip + "\n"; } worldStr += "\n"; } /* DIALOG */ for (id in dialog) { if (id != titleDialogId) { worldStr += "DLG " + id + "\n"; worldStr += dialog[id].src + "\n"; if (dialog[id].name != null) { worldStr += "NAME " + dialog[id].name + "\n"; } worldStr += "\n"; } } /* ENDINGS (for backwards compability only) */ for (id in end) { worldStr += "END " + id + "\n"; worldStr += end[id].src + "\n"; worldStr += "\n"; } /* VARIABLES */ for (id in variable) { worldStr += "VAR " + id + "\n"; worldStr += variable[id] + "\n"; worldStr += "\n"; } /* TUNES */ for (id in tune) { if (id === "0") { continue; } worldStr += "TUNE " + id + "\n"; for (var i = 0; i < maxTuneLength && i < tune[id].melody.length; i++) { // MELODY for (var j = 0; j 0) { worldStr += noteStr; } if (tune[id].melody[i][j].beats > 0 && tune[id].melody[i][j].octave != Octave[4]) { worldStr += serializeOctave(tune[id].melody[i][j].octave); } if (tune[id].melody[i][j].beats > 0 && tune[id].melody[i][j].blip != undefined) { // todo : create constant for the blip separator? worldStr += “~” + tune[id].melody[i][j].blip; } if (j < 15) { worldStr += ","; } } worldStr += "\n"; // HARMONY // todo : lots of copy-pasting – I could probably make some helper functions to simplify this for (var j = 0; j 0) { worldStr += noteStr; } if (tune[id].harmony[i][j].beats > 0 && tune[id].harmony[i][j].octave != Octave[4]) { worldStr += serializeOctave(tune[id].harmony[i][j].octave); } if (tune[id].harmony[i][j].beats > 0 && tune[id].harmony[i][j].blip != undefined) { worldStr += “~” + tune[id].harmony[i][j].blip; } if (j < 15) { worldStr += ","; } } worldStr += "\n"; if (i “; worldStr += “\n”; } } if (tune[id].name != null) { /* NAME */ worldStr += “NAME ” + tune[id].name + “\n”; } if (tune[id].key != undefined && tune[id].key != null) { worldStr += “KEY “; for (var i = 0; i < Solfa.COUNT; i++) { worldStr += serializeNote(tune[id].key.notes[i]); if (i < Solfa.COUNT – 1) { worldStr += ","; } } worldStr += " "; for (var i = 0; i < tune[id].key.scale.length; i++) { worldStr += serializeNote(tune[id].key.scale[i], tune[id].key); if (i 0) { worldStr += serializeNote(blip[id].pitchA.note); if (blip[id].pitchA.octave != Octave[4]) { worldStr += serializeOctave(blip[id].pitchA.octave); } } else { worldStr += blip[id].pitchA.beats; } worldStr += “,”; if (blip[id].pitchB.beats > 0) { worldStr += serializeNote(blip[id].pitchB.note); if (blip[id].pitchB.octave != Octave[4]) { worldStr += serializeOctave(blip[id].pitchB.octave); } } else { worldStr += blip[id].pitchB.beats; } worldStr += “,”; if (blip[id].pitchC.beats > 0) { worldStr += serializeNote(blip[id].pitchC.note); if (blip[id].pitchC.octave != Octave[4]) { worldStr += serializeOctave(blip[id].pitchC.octave); } } else { worldStr += blip[id].pitchC.beats; } worldStr += “\n”; if (blip[id].name != null) { /* NAME */ worldStr += “NAME ” + blip[id].name + “\n”; } // envelope worldStr += “ENV ” + blip[id].envelope.attack + ” ” + blip[id].envelope.decay + ” ” + blip[id].envelope.sustain + ” ” + blip[id].envelope.length + ” ” + blip[id].envelope.release + “\n”; // beat worldStr += “BEAT ” + blip[id].beat.time + ” ” + blip[id].beat.delay + “\n”; // instrument (square wave type) worldStr += “SQR “; switch (blip[id].instrument) { case SquareWave.P8: worldStr += “P8”; break; case SquareWave.P4: worldStr += “P4”; break; case SquareWave.P2: worldStr += “P2”; break; } worldStr += “\n”; // other parameters if (blip[id].doRepeat === true) { worldStr += “RPT 1\n”; } // TODO : consider for future update // if (blip[id].doSlide === true) { // worldStr += “SLD 1\n”; // } worldStr += “\n”; } /* FONT */ // TODO : support multiple fonts if (fontManager && fontName != defaultFontName && !skipFonts) { worldStr += fontManager.GetData(fontName); } return worldStr; } function serializeDrawing(drwId) { if (!renderer) { return “”; } var drawingData = renderer.GetDrawingSource(drwId); var drwStr = “”; for (f in drawingData) { for (y in drawingData[f]) { var rowStr = “”; for (x in drawingData[f][y]) { rowStr += drawingData[f][y][x]; } drwStr += rowStr + “\n”; } if (f \n”; } return drwStr; } function isExitValid(e) { var hasValidStartPos = e.x >= 0 && e.x = 0 && e.y = 0 && e.dest.x = 0 && e.dest.y < bitsy.MAP_SIZE); return hasValidStartPos && hasDest && hasValidRoomDest; } function setTile(mapId, x, y, tileId) { bitsy.set(mapId, (y * bitsy.MAP_SIZE) + x, tileId); } function drawTile(tileId, x, y) { setTile(bitsy.MAP1, x, y, tileId); } function drawSprite(tileId, x, y) { setTile(bitsy.MAP2, x, y, tileId); } function drawItem(tileId, x, y) { setTile(bitsy.MAP2, x, y, tileId); } // var debugLastRoomDrawn = "0"; function clearRoom() { var paletteId = "default"; if (room === undefined) { // protect against invalid rooms return; } if (room.pal != null && palette[paletteId] != undefined) { paletteId = room.pal; } // clear background & foreground bitsy.fill(bitsy.MAP1, 0); bitsy.fill(bitsy.MAP2, 0); } function drawRoomBackground(room, frameIndex, redrawAnimatedOnly) { if (!redrawAnimatedOnly) { // clear background map bitsy.fill(bitsy.MAP1, 0); } // NOTE: interestingly the slowest part of this is iterating over all the tiles, not actually drawing them for (var y = 0; y < bitsy.MAP_SIZE; y++) { for (var x = 0; x < bitsy.MAP_SIZE; x++) { var id = room.tilemap[y][x]; if (id != "0" && tile[id] == null) { // hack-around to avoid corrupting files (not a solution though!) id = "0"; room.tilemap[y][x] = id; } if (id != "0" && (!redrawAnimatedOnly || tile[id].animation.isAnimated)) { drawTile(getTileFrame(tile[id], frameIndex), x, y); } } } } function drawRoomForeground(room, frameIndex, redrawAnimatedOnly) { if (!redrawAnimatedOnly) { // clear foreground map bitsy.fill(bitsy.MAP2, 0); } // draw items for (var i = 0; i < room.items.length; i++) { var itm = room.items[i]; if (!redrawAnimatedOnly || item[itm.id].animation.isAnimated) { drawItem(getItemFrame(item[itm.id], frameIndex), itm.x, itm.y); } } // draw sprites for (id in sprite) { var spr = sprite[id]; if (id != playerId && spr.room === room.id && (!redrawAnimatedOnly || spr.animation.isAnimated)) { drawSprite(getSpriteFrame(spr, frameIndex), spr.x, spr.y); } } } function drawRoomForegroundTile(room, frameIndex, x, y) { // draw items for (var i = 0; i < room.items.length; i++) { var itm = room.items[i]; if (itm.x === x && itm.y === y) { drawItem(getItemFrame(item[itm.id], frameIndex), itm.x, itm.y); } } // draw sprites for (id in sprite) { var spr = sprite[id]; if (id != playerId && spr.room === room.id && spr.x === x && spr.y === y) { drawSprite(getSpriteFrame(spr, frameIndex), spr.x, spr.y); } } } function drawRoom(room, args) { if (room === undefined || isNarrating) { // protect against invalid rooms return; } var redrawAll = args && (args.redrawAll === true); var redrawAnimated = args && (args.redrawAnimated === true); var redrawAvatar = args && (args.redrawAvatar === true); var frameIndex = args ? args.frameIndex : undefined; // if *only* redrawing the avatar, first clear its previous position if (redrawAvatar) { setTile(bitsy.MAP2, playerPrevX, playerPrevY, 0); // also redraw any sprite or item that might be "under" the player (todo: possible perf issue?) drawRoomForegroundTile(room, frameIndex, playerPrevX, playerPrevY); } // draw background & foreground tiles if (redrawAll || redrawAnimated) { // draw tiles drawRoomBackground(room, frameIndex, redrawAnimated); // draw sprites & items drawRoomForeground(room, frameIndex, redrawAnimated); } // draw the player's avatar at its current position if ((redrawAll || redrawAnimated || redrawAvatar) && sprite[playerId] && sprite[playerId].room === room.id) { var spr = sprite[playerId]; var x = spr.x; var y = spr.y; // get the avatar override sprite (if there is one) if (state.ava && state.ava != playerId && sprite[state.ava]) { spr = sprite[state.ava]; } drawSprite(getSpriteFrame(spr, frameIndex), x, y); } } // TODO : remove these get*Image methods function getTileFrame(t, frameIndex) { if (!renderer) { return null; } return renderer.GetDrawingFrame(t, frameIndex); } function getSpriteFrame(s, frameIndex) { if (!renderer) { return null; } return renderer.GetDrawingFrame(s, frameIndex); } function getItemFrame(itm, frameIndex) { if (!renderer) { return null; } return renderer.GetDrawingFrame(itm, frameIndex); } function curDefaultPal() { return getRoomPal(state.room); } function getRoomPal(roomId) { var defaultId = "default"; if (roomId == null) { return defaultId; } else if (room[roomId].pal != null) { //a specific palette was chosen return room[roomId].pal; } else { if (roomId in palette) { //there is a palette matching the name of the room return roomId; } else { //use the default palette return defaultId; } } return defaultId; } var isDialogMode = false; var isNarrating = false; var isEnding = false; var dialogModule; var dialogRenderer; var dialogBuffer; if (engineFeatureFlags.isDialogEnabled) { dialogModule = new Dialog(); dialogRenderer = dialogModule.CreateRenderer(); dialogBuffer = dialogModule.CreateBuffer(); } var fontManager; if (engineFeatureFlags.isFontEnabled) { fontManager = new FontManager(); } // TODO : is this scriptResult thing being used anywhere??? function onExitDialog(scriptResult, dialogCallback) { isDialogMode = false; bitsy.textbox(false); if (isNarrating) { isNarrating = false; // redraw the room drawRoom(room[state.room], { redrawAll: true }); } if (isDialogPreview) { isDialogPreview = false; if (onDialogPreviewEnd != null) { onDialogPreviewEnd(); } } if (dialogCallback != undefined && dialogCallback != null) { dialogCallback(scriptResult); } if (soundPlayer) { soundPlayer.resumeTune(); } } /* TODO – titles and endings should also take advantage of the script pre-compilation if possible?? – could there be a namespace collision? – what about dialog NAMEs vs IDs? – what about a special script block separate from DLG? */ function startNarrating(dialogStr, end) { bitsy.log("NARRATE " + dialogStr); if(end === undefined) { end = false; } isNarrating = true; isEnding = end; if (isEnding && soundPlayer) { soundPlayer.stopTune(); } // clear the room tiles before narrating bitsy.fill(bitsy.MAP1, 0); bitsy.fill(bitsy.MAP2, 0); startDialog(dialogStr); } function startEndingDialog(ending) { isNarrating = true; isEnding = true; var endingScriptId = ending.id; var endingDialogStr = dialog[ending.id].src; // compatibility with pre-7.0 endings if (flags.DLG_COMPAT === 1 && end[ending.id]) { endingScriptId = "end_compat_" + ending.id; endingDialogStr = end[ending.id].src; } var tmpTuneId = null; if (isEnding && soundPlayer) { tmpTuneId = soundPlayer.getCurTuneId(); soundPlayer.stopTune(); } startDialog( endingDialogStr, endingScriptId, function() { var isLocked = ending.property && ending.property.locked === true; if (isLocked) { isEnding = false; // if the ending was cancelled, restart the music // todo : should it resume from where it started? (right now it starts over) if (tmpTuneId && soundPlayer && !soundPlayer.isTunePlaying()) { soundPlayer.playTune(tune[tmpTuneId]); } } }, ending); } function startItemDialog(itemId, dialogCallback) { var dialogId = item[itemId].dlg; // bitsy.log("START ITEM DIALOG " + dialogId); if (dialog[dialogId]) { var dialogStr = dialog[dialogId].src; startDialog(dialogStr, dialogId, dialogCallback); } else { dialogCallback(); } } function startSpriteDialog(spriteId) { var spr = sprite[spriteId]; var dialogId = spr.dlg; // back compat for when dialog IDs were implicitly the same as sprite IDs if (flags.DLG_COMPAT === 1 && (dialogId === undefined || dialogId === null)) { dialogId = spr.id; } // bitsy.log("START SPRITE DIALOG " + dialogId); if (dialog[dialogId]){ var dialogStr = dialog[dialogId].src; startDialog(dialogStr, dialogId); } } function startDialog(dialogStr, scriptId, dialogCallback, objectContext) { bitsy.log("START DIALOG"); if (soundPlayer) { soundPlayer.pauseTune(); } if (dialogStr.length <= 0) { onExitDialog(null, dialogCallback); return; } if (!dialogBuffer) { bitsy.log(dialogStr); onExitDialog(null, dialogCallback); return; } if (!scriptInterpreter) { dialogRenderer.Reset(); dialogRenderer.SetCentered(isNarrating /*centered*/); dialogBuffer.Reset(); dialogBuffer.AddText(dialogStr); dialogBuffer.OnDialogEnd(function() { onExitDialog(null, dialogCallback); }); bitsy.log("dialog start end"); return; }; isDialogMode = true; dialogRenderer.Reset(); dialogRenderer.SetCentered(isNarrating /*centered*/); dialogBuffer.Reset(); scriptInterpreter.SetDialogBuffer(dialogBuffer); var onScriptEnd = function(scriptResult) { dialogBuffer.OnDialogEnd(function() { onExitDialog(scriptResult, dialogCallback); }); }; if (scriptId === undefined) { // TODO : what's this for again? scriptInterpreter.Interpret(dialogStr, onScriptEnd); } else { if (!scriptInterpreter.HasScript(scriptId)) { scriptInterpreter.Compile(scriptId, dialogStr); } // scriptInterpreter.DebugVisualizeScript(scriptId); scriptInterpreter.Run(scriptId, onScriptEnd, objectContext); } } var isDialogPreview = false; function startPreviewDialog(script, dialogCallback) { if (!scriptInterpreter || !dialogBuffer) { return; } isNarrating = true; isDialogMode = true; isDialogPreview = true; dialogRenderer.Reset(); dialogRenderer.SetCentered(true); dialogBuffer.Reset(); scriptInterpreter.SetDialogBuffer(dialogBuffer); // TODO : do I really need a seperate callback for this debug mode?? onDialogPreviewEnd = dialogCallback; var onScriptEndCallback = function(scriptResult) { dialogBuffer.OnDialogEnd(function() { onExitDialog(scriptResult, null); }); }; scriptInterpreter.Eval(script, onScriptEndCallback); } /* NEW SCRIPT STUFF */ var scriptModule; var scriptInterpreter; var scriptUtils; // scriptInterpreter.SetDialogBuffer( dialogBuffer ); if (engineFeatureFlags.isScriptEnabled) { bitsy.log("init script module"); scriptModule = new Script(); bitsy.log("init interpreter"); scriptInterpreter = scriptModule.CreateInterpreter(); bitsy.log("init utils"); scriptUtils = scriptModule.CreateUtils(); // TODO: move to editor.js? bitsy.log("init script module end"); } /* SOUND */ var soundPlayer; if (engineFeatureFlags.isSoundEnabled) { soundPlayer = new SoundPlayer(); } /* EVENTS */ bitsy.loop(update); FONT ascii_small SIZE 6 8 CHAR 0 000000 000000 000000 000000 000000 000000 000000 000000 CHAR 32 000000 000000 000000 000000 000000 000000 000000 000000 CHAR 33 000100 001110 001110 000100 000100 000000 000100 000000 CHAR 34 011011 011011 010010 000000 000000 000000 000000 000000 CHAR 8220 011011 011011 010010 000000 000000 000000 000000 000000 CHAR 8221 011011 011011 010010 000000 000000 000000 000000 000000 CHAR 35 000000 001010 011111 001010 001010 011111 001010 000000 CHAR 36 001000 001110 010000 001100 000010 011100 000100 000000 CHAR 37 011001 011001 000010 000100 001000 010011 010011 000000 CHAR 38 001000 010100 010100 001000 010101 010010 001101 000000 CHAR 39 001100 001100 001000 000000 000000 000000 000000 000000 CHAR 8216 001100 001100 001000 000000 000000 000000 000000 000000 CHAR 8217 001100 001100 001000 000000 000000 000000 000000 000000 CHAR 40 000100 001000 001000 001000 001000 001000 000100 000000 CHAR 41 001000 000100 000100 000100 000100 000100 001000 000000 CHAR 42 000000 001010 001110 011111 001110 001010 000000 000000 CHAR 43 000000 000100 000100 011111 000100 000100 000000 000000 CHAR 44 000000 000000 000000 000000 000000 001100 001100 001000 CHAR 45 000000 000000 000000 011111 000000 000000 000000 000000 CHAR 46 000000 000000 000000 000000 000000 001100 001100 000000 CHAR 47 000000 000001 000010 000100 001000 010000 000000 000000 CHAR 48 001110 010001 010011 010101 011001 010001 001110 000000 CHAR 49 000100 001100 000100 000100 000100 000100 001110 000000 CHAR 50 001110 010001 000001 000110 001000 010000 011111 000000 CHAR 51 001110 010001 000001 001110 000001 010001 001110 000000 CHAR 52 000010 000110 001010 010010 011111 000010 000010 000000 CHAR 53 011111 010000 010000 011110 000001 010001 001110 000000 CHAR 54 000110 001000 010000 011110 010001 010001 001110 000000 CHAR 55 011111 000001 000010 000100 001000 001000 001000 000000 CHAR 56 001110 010001 010001 001110 010001 010001 001110 000000 CHAR 57 001110 010001 010001 001111 000001 000010 001100 000000 CHAR 58 000000 000000 001100 001100 000000 001100 001100 000000 CHAR 59 000000 000000 001100 001100 000000 001100 001100 001000 CHAR 60 000010 000100 001000 010000 001000 000100 000010 000000 CHAR 61 000000 000000 011111 000000 000000 011111 000000 000000 CHAR 62 001000 000100 000010 000001 000010 000100 001000 000000 CHAR 63 001110 010001 000001 000110 000100 000000 000100 000000 CHAR 64 001110 010001 010111 010101 010111 010000 001110 000000 CHAR 65 001110 010001 010001 010001 011111 010001 010001 000000 CHAR 66 011110 010001 010001 011110 010001 010001 011110 000000 CHAR 67 001110 010001 010000 010000 010000 010001 001110 000000 CHAR 68 011110 010001 010001 010001 010001 010001 011110 000000 CHAR 69 011111 010000 010000 011110 010000 010000 011111 000000 CHAR 70 011111 010000 010000 011110 010000 010000 010000 000000 CHAR 71 001110 010001 010000 010111 010001 010001 001111 000000 CHAR 72 010001 010001 010001 011111 010001 010001 010001 000000 CHAR 73 001110 000100 000100 000100 000100 000100 001110 000000 CHAR 74 000001 000001 000001 000001 010001 010001 001110 000000 CHAR 75 010001 010010 010100 011000 010100 010010 010001 000000 CHAR 76 010000 010000 010000 010000 010000 010000 011111 000000 CHAR 77 010001 011011 010101 010001 010001 010001 010001 000000 CHAR 78 010001 011001 010101 010011 010001 010001 010001 000000 CHAR 79 001110 010001 010001 010001 010001 010001 001110 000000 CHAR 80 011110 010001 010001 011110 010000 010000 010000 000000 CHAR 81 001110 010001 010001 010001 010101 010010 001101 000000 CHAR 82 011110 010001 010001 011110 010010 010001 010001 000000 CHAR 83 001110 010001 010000 001110 000001 010001 001110 000000 CHAR 84 011111 000100 000100 000100 000100 000100 000100 000000 CHAR 85 010001 010001 010001 010001 010001 010001 001110 000000 CHAR 86 010001 010001 010001 010001 010001 001010 000100 000000 CHAR 87 010001 010001 010101 010101 010101 010101 001010 000000 CHAR 88 010001 010001 001010 000100 001010 010001 010001 000000 CHAR 89 010001 010001 010001 001010 000100 000100 000100 000000 CHAR 90 011110 000010 000100 001000 010000 010000 011110 000000 CHAR 91 001110 001000 001000 001000 001000 001000 001110 000000 CHAR 92 000000 010000 001000 000100 000010 000001 000000 000000 CHAR 93 001110 000010 000010 000010 000010 000010 001110 000000 CHAR 94 000100 001010 010001 000000 000000 000000 000000 000000 CHAR 95 000000 000000 000000 000000 000000 000000 000000 111111 CHAR 96 001100 001100 000100 000000 000000 000000 000000 000000 CHAR 97 000000 000000 001110 000001 001111 010001 001111 000000 CHAR 98 010000 010000 011110 010001 010001 010001 011110 000000 CHAR 99 000000 000000 001110 010001 010000 010001 001110 000000 CHAR 100 000001 000001 001111 010001 010001 010001 001111 000000 CHAR 101 000000 000000 001110 010001 011110 010000 001110 000000 CHAR 102 000110 001000 001000 011110 001000 001000 001000 000000 CHAR 103 000000 000000 001111 010001 010001 001111 000001 001110 CHAR 104 010000 010000 011100 010010 010010 010010 010010 000000 CHAR 105 000100 000000 000100 000100 000100 000100 000110 000000 CHAR 106 000010 000000 000110 000010 000010 000010 010010 001100 CHAR 107 010000 010000 010010 010100 011000 010100 010010 000000 CHAR 108 000100 000100 000100 000100 000100 000100 000110 000000 CHAR 109 000000 000000 011010 010101 010101 010001 010001 000000 CHAR 110 000000 000000 011100 010010 010010 010010 010010 000000 CHAR 111 000000 000000 001110 010001 010001 010001 001110 000000 CHAR 112 000000 000000 011110 010001 010001 010001 011110 010000 CHAR 113 000000 000000 001111 010001 010001 010001 001111 000001 CHAR 114 000000 000000 010110 001001 001000 001000 011100 000000 CHAR 115 000000 000000 001110 010000 001110 000001 001110 000000 CHAR 116 000000 001000 011110 001000 001000 001010 000100 000000 CHAR 117 000000 000000 010010 010010 010010 010110 001010 000000 CHAR 118 000000 000000 010001 010001 010001 001010 000100 000000 CHAR 119 000000 000000 010001 010001 010101 011111 001010 000000 CHAR 120 000000 000000 010010 010010 001100 010010 010010 000000 CHAR 121 000000 000000 010010 010010 010010 001110 000100 011000 CHAR 122 000000 000000 011110 000010 001100 010000 011110 000000 CHAR 123 000110 001000 001000 011000 001000 001000 000110 000000 CHAR 124 000100 000100 000100 000100 000100 000100 000100 000100 CHAR 125 001100 000010 000010 000011 000010 000010 001100 000000 CHAR 126 000000 000000 000000 001010 010100 000000 000000 000000 CHAR 160 000000 000000 000000 000000 000000 000000 000000 000000 CHAR 161 000100 000000 000100 000100 001110 001110 000100 000000 CHAR 162 000000 000100 001110 010000 010000 001110 000100 000000 CHAR 163 000110 001001 001000 011110 001000 001001 010111 000000 CHAR 165 010001 001010 000100 011111 000100 011111 000100 000000 CHAR 167 001110 010001 001100 001010 000110 010001 001110 000000 CHAR 171 000000 000000 001001 010010 001001 000000 000000 000000 CHAR 172 000000 000000 111111 000001 000001 000000 000000 000000 CHAR 177 000000 000100 001110 000100 000000 001110 000000 000000 CHAR 178 011000 000100 001000 011100 000000 000000 000000 000000 CHAR 181 000000 000000 010010 010010 010010 011100 010000 010000 CHAR 182 001111 010101 010101 001101 000101 000101 000101 000000 CHAR 187 000000 000000 010010 001001 010010 000000 000000 000000 CHAR 188 010000 010010 010100 001011 010101 000111 000001 000000 CHAR 189 010000 010010 010100 001110 010001 000010 000111 000000 CHAR 191 000100 000000 000100 001100 010000 010001 001110 000000 CHAR 196 001010 000000 000100 001010 010001 011111 010001 000000 CHAR 197 001110 001010 001110 011011 010001 011111 010001 000000 CHAR 198 001111 010100 010100 011111 010100 010100 010111 000000 CHAR 199 001110 010001 010000 010000 010001 001110 000100 001100 CHAR 201 000011 000000 011111 010000 011110 010000 011111 000000 CHAR 209 001010 010100 000000 010010 011010 010110 010010 000000 CHAR 214 010010 001100 010010 010010 010010 010010 001100 000000 CHAR 220 001010 000000 010010 010010 010010 010010 001100 000000 CHAR 223 000000 011100 010010 011100 010010 010010 011100 010000 CHAR 224 001100 000000 001110 000001 001111 010001 001111 000000 CHAR 225 000110 000000 001110 000001 001111 010001 001111 000000 CHAR 226 001110 000000 001110 000001 001111 010001 001111 000000 CHAR 228 001010 000000 001110 000001 001111 010001 001111 000000 CHAR 229 001110 001010 001110 000001 001111 010001 001111 000000 CHAR 230 000000 000000 011110 000101 011111 010100 001111 000000 CHAR 231 000000 001110 010001 010000 010001 001110 000100 001100 CHAR 232 001100 000000 001110 010001 011110 010000 001110 000000 CHAR 233 000011 000000 001110 010001 011110 010000 001110 000000 CHAR 234 001110 000000 001110 010001 011110 010000 001110 000000 CHAR 235 001010 000000 001110 010001 011110 010000 001110 000000 CHAR 236 001000 000000 000100 000100 000100 000100 000110 000000 CHAR 237 000110 000000 000100 000100 000100 000100 000110 000000 CHAR 238 000100 001010 000000 000100 000100 000100 000110 000000 CHAR 239 001010 000000 000100 000100 000100 000100 000110 000000 CHAR 241 001010 010100 000000 011100 010010 010010 010010 000000 CHAR 242 011000 000000 001100 010010 010010 010010 001100 000000 CHAR 243 000110 000000 001100 010010 010010 010010 001100 000000 CHAR 244 001110 000000 001100 010010 010010 010010 001100 000000 CHAR 246 001010 000000 001100 010010 010010 010010 001100 000000 CHAR 247 001010 000000 001110 010001 010001 010001 001110 000000 CHAR 249 011000 000000 010010 010010 010010 010110 001010 000000 CHAR 250 000110 000000 010010 010010 010010 010110 001010 000000 CHAR 251 001110 000000 010010 010010 010010 010110 001010 000000 CHAR 252 010010 000000 010010 010010 010010 010110 001010 000000 CHAR 255 001010 000000 010010 010010 010010 001110 000100 011000 CHAR 402 000010 000101 000100 001110 000100 000100 010100 001000 CHAR 915 011110 010010 010000 010000 010000 010000 010000 000000 CHAR 920 001100 010010 010010 011110 010010 010010 001100 000000 CHAR 931 011111 010000 001000 000100 001000 010000 011111 000000 CHAR 934 001110 000100 001110 010001 001110 000100 001110 000000 CHAR 937 000000 001110 010001 010001 001010 001010 011011 000000 CHAR 948 001100 010000 001000 000100 001110 010010 001100 000000 CHAR 949 000000 001110 010000 011110 010000 001110 000000 000000 CHAR 960 000000 011111 001010 001010 001010 001010 001010 000000 CHAR 963 000000 000000 001111 010010 010010 001100 000000 000000 CHAR 964 000000 000000 001010 010100 000100 000100 000100 000000 CHAR 966 000000 000100 001110 010101 010101 001110 000100 000000 CHAR 8226 000000 000000 000000 001100 001100 000000 000000 000000 CHAR 8252 001010 001010 001010 001010 001010 000000 001010 000000 CHAR 8592 000000 000100 001100 011111 001100 000100 000000 000000 CHAR 8593 000100 001110 011111 000100 000100 000100 000100 000000 CHAR 8594 000000 000100 000110 011111 000110 000100 000000 000000 CHAR 8595 000100 000100 000100 000100 011111 001110 000100 000000 CHAR 8734 000000 000000 001010 010101 010101 001010 000000 000000 CHAR 8735 000000 000000 000000 010000 010000 010000 011111 000000 CHAR 8801 000000 011110 000000 011110 000000 011110 000000 000000 CHAR 8804 000010 001100 010000 001100 000010 000000 011110 000000 CHAR 8805 010000 001100 000010 001100 010000 000000 011110 000000 CHAR 8962 000100 001110 011011 010001 010001 011111 000000 000000 CHAR 8976 000000 000000 011111 010000 010000 010000 000000 000000 CHAR 9472 000000 000000 000000 111111 000000 000000 000000 000000 CHAR 9474 000100 000100 000100 000100 000100 000100 000100 000100 CHAR 9488 000000 000000 000000 111100 000100 000100 000100 000100 CHAR 9492 000100 000100 000100 000111 000000 000000 000000 000000 CHAR 9500 000100 000100 000100 000111 000100 000100 000100 000100 CHAR 9508 000100 000100 000100 111100 000100 000100 000100 000100 CHAR 9516 000000 000000 000000 111111 000100 000100 000100 000100 CHAR 9524 000100 000100 000100 111111 000000 000000 000000 000000 CHAR 9532 000100 000100 000100 111111 000100 000100 000100 000100 CHAR 9552 000000 111111 000000 111111 000000 000000 000000 000000 CHAR 9553 010100 010100 010100 010100 010100 010100 010100 010100 CHAR 9556 000000 011111 010000 010111 010100 010100 010100 010100 CHAR 9557 000000 111100 000100 111100 000100 000100 000100 000100 CHAR 9558 000000 000000 000000 111100 010100 010100 010100 010100 CHAR 9559 000000 111100 000100 110100 010100 010100 010100 010100 CHAR 9561 010100 010100 010100 011111 000000 000000 000000 000000 CHAR 9562 010100 010111 010000 011111 000000 000000 000000 000000 CHAR 9563 000100 111100 000100 111100 000000 000000 000000 000000 CHAR 9564 010100 010100 010100 111100 000000 000000 000000 000000 CHAR 9565 010100 110100 000100 111100 000000 000000 000000 000000 CHAR 9566 000100 000111 000100 000111 000100 000100 000100 000100 CHAR 9567 010100 010100 010100 010111 010100 010100 010100 010100 CHAR 9568 010100 010111 010000 010111 010100 010100 010100 010100 CHAR 9569 000000 000000 010010 010010 010010 011100 010000 010000 CHAR 9570 010100 010100 010100 110100 010100 010100 010100 010100 CHAR 9571 010100 110100 000100 110100 010100 010100 010100 010100 CHAR 9572 000000 111111 000000 111111 000100 000100 000100 000100 CHAR 9573 000000 000000 000000 111111 010100 010100 010100 010100 CHAR 9574 000000 111111 000000 110111 010100 010100 010100 010100 CHAR 9575 000100 111111 000000 111111 000000 000000 000000 000000 CHAR 9576 010100 010100 010100 111111 000000 000000 000000 000000 CHAR 9577 010100 110111 000000 111111 000000 000000 000000 000000 CHAR 9580 010100 110111 000000 110111 010100 010100 010100 010100 CHAR 9601 000000 000000 000000 000000 000000 000000 000000 111111 CHAR 9602 000000 000000 000000 000000 000000 000000 111111 111111 CHAR 9603 000000 000000 000000 000000 000000 111111 111111 111111 CHAR 9604 000000 000000 000000 000000 111111 111111 111111 111111 CHAR 9605 000000 000000 000000 111111 111111 111111 111111 111111 CHAR 9606 000000 000000 111111 111111 111111 111111 111111 111111 CHAR 9607 000000 111111 111111 111111 111111 111111 111111 111111 CHAR 9608 111111 111111 111111 111111 111111 111111 111111 111111 CHAR 9609 111100 111100 111100 111100 111100 111100 111100 111100 CHAR 9610 111100 111100 111100 111100 111100 111100 111100 111100 CHAR 9611 111000 111000 111000 111000 111000 111000 111000 111000 CHAR 9613 110000 110000 110000 110000 110000 110000 110000 110000 CHAR 9615 100000 100000 100000 100000 100000 100000 100000 100000 CHAR 9617 010101 000000 101010 000000 010101 000000 101010 000000 CHAR 9618 010101 101010 010101 101010 010101 101010 010101 101010 CHAR 9619 101010 111111 010101 111111 101010 111111 010101 111111 CHAR 9644 000000 000000 000000 000000 000000 011110 011110 000000 CHAR 9650 000100 000100 001110 001110 011111 011111 000000 000000 CHAR 9658 001000 001100 001110 001111 001110 001100 001000 000000 CHAR 9660 011111 011111 001110 001110 000100 000100 000000 000000 CHAR 9668 000010 000110 001110 011110 001110 000110 000010 000000 CHAR 9675 000000 000000 011110 010010 010010 011110 000000 000000 CHAR 9688 111111 111111 111111 110011 110011 111111 111111 111111 CHAR 9689 111111 111111 100001 101101 101101 100001 111111 111111 CHAR 9786 001110 010001 011011 010001 010101 010001 001110 000000 CHAR 9787 001110 011111 010101 011111 010001 011111 001110 000000 CHAR 9788 000000 010101 001110 011011 001110 010101 000000 000000 CHAR 9792 001110 010001 010001 001110 000100 001110 000100 000000 CHAR 9794 000000 000111 000011 001101 010010 010010 001100 000000 CHAR 9824 000000 000100 001110 011111 011111 000100 001110 000000 CHAR 9827 000100 001110 001110 000100 011111 011111 000100 000000 CHAR 9829 000000 001010 011111 011111 011111 001110 000100 000000 CHAR 9830 000000 000100 001110 011111 011111 001110 000100 000000 CHAR 9834 000100 000110 000101 000100 001100 011100 011000 000000 CHAR 9835 000011 001101 001011 001101 001011 011011 011000 000000

1 reply on “Hello world!”

Leave a Reply

Your email address will not be published. Required fields are marked *