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!”
Hi, this is a comment.
To get started with moderating, editing, and deleting comments, please visit the Comments screen in the dashboard.
Commenter avatars come from Gravatar.