 // lazyNES descend demo, 'Lazycow 2020
// (graphics from Lazycow, music from Cyborgjeff, for use in this demo only)

#include "lazynes.h"
#include "famitone2.h"

 // data
//
extern const ubyte chrPal[];    // background color palettes
extern const ubyte sprPal[];   // sprite color palettes
extern const ubyte sprTab[];  // meta data of all sprites
extern const uword sprOff[]; // offsets for each sprite into the metadata
extern const ubyte musicData[]; // music data, generated by text2data


enum {
	F=4,
	fadeBlock=2,
	endTime=60*40,
	endDelay=120,
};

 // (use const to place the tables into ROM)
//
const ubyte
	fTab[]={ // block fading types: 0==noFade, 2==blue, 8==lightBlue, 14==yellow
		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,
		8,14,14,14,14,14,14,8,
		8,14,14,14,14,14,14,8,
		8,14,8,14,14,8,14,8,
		8,14,8,14,14,8,14,8,
		0,0,2,8,8,2,0,0,
		0,0,0,2,2,0,0,0,
		0,2,8,8,8,8,2,0,
		0,2,2,8,8,2,2,0,
		2,8,8,8,8,8,8,2,
		2,8,8,8,8,8,8,2,
		8,8,8,8,8,8,8,8,
		8,8,8,8,8,8,8,8
	};


 // construct 32 blocks for one line, select blocks pseudo randomly
//
void GetMapData(ubyte* field, ubyte line) {
	static const ubyte
		l1Tab[]={64,68,96,100},    r1Tab[]={76,72,108,104}, // start pos of segments
		l2Tab[]={128,160,192,224}, r2Tab[]={136,168,200,232},
		// map of segment parts
		l1Map[]={0,2,1,2,3,1,3,2,0,3,2,0,1,0,1,0,3,0,1,2,0,1,0,3}, // 24
		r1Map[]={1,2,3,2,0,1,2,0,1,2,3,0,1,3,2,0,1,2,0,3}, // 20
		l2Map[]={0,1,2,3,3,2,0,1,1,0,0,0,2,1,1,0,0,2,3,3,2,0,0,0,2,1,2,1}, // 28
		r2Map[]={1,0,1,2,3,2,1,0,0,0,1,2,3,3,2,1,0,0,1,0,1,0,0,0,2,3,3,2,1,0}; // 30
	static ubyte ml1=0, ml2=0, mr1=0, mr2=0;
	static ubyte oldLine=255;
	ubyte x, yAdd;

	if (line!=oldLine) { // avoid multi call
		oldLine=line;
		if (line&1) yAdd=16; else { // handle upper half or lower half?
			yAdd=0;
			if (++ml1>=24) ml1=0;
			if (++mr1>=20) mr1=0;
			if (++ml2>=28) ml2=0;
			if (++mr2>=30) mr2=0;
		}

		for (x=0;x<8;x+=1) {
			field[0+x]=l2Tab[l2Map[ml2]]+yAdd+x; // outer parts
			field[24+x]=r2Tab[r2Map[mr2]]+yAdd+x;
			if (x<4) { // inner parts
				field[8+x]=l1Tab[l1Map[ml1]]+yAdd+x;
				field[20+x]=r1Tab[r1Map[mr1]]+yAdd+x;
			}
			if (0==(line&3)) field[12+x]=48+x; else field[12+x]=32+x; // middle part
		}
	}
}


 //
//
int main() {

	uword
		ev,    // elevator velocity
		yOff; // current scroll position [0..65535]
	word
		ey, // elevator y pos
		tics=0;
	ubyte
		ntsc,
		previous,
		yVel,
		sOff,   // current scroll offset [0..239]
		fOff;  // fading offset
	byte
		step;

	// To avoid glitches, always write color palettes immediately after lnSync()!
	ntsc=lnSync(lfBlank)&128; // switch to blank mode (needed below to use lnPush)
	lnPush(lnBackCol,8,chrPal);
	lnPush(lnSprPal0,3,&sprPal[1]);
	lnPush(lnSprPal1,3,&sprPal[5]);
	lnPush(lnSprPal2,3,&sprPal[9]);

	ftInit(musicData,ntsc); // init famitone2

	   // preset attribute table
	{ //
		ubyte y;
		static const ubyte aTab[]={0,0,0,1+4+16+64,1+4+16+64,0,0,0};
		for (y=0;y<8;y+=1) lnPush(lnAttrTab0+(y<<3),8,aTab);
	}

	 // main loop
	//
	for (;;) {
		static ubyte updateList[3+32+3+32+1], // 32 bytes + 32 bytes + endmarker
			mapLine[32]; // temporary buffer for map data, because it's needed twice
		uword vramOff=0;
		ubyte* p=updateList, i; // set pointer p to beginning of the update list

		if (0==tics) {
			ey=1<<F;  ev=4;  yOff=yVel=sOff=fOff=step=0;  previous=255;
			ftMusicPlay(0);
		}

		 // scroll
		//
		if (0==(tics&3)) { // scroll only every 2nd frame
			if (tics>=endTime+endDelay) {
				if (fOff>=30) fOff=0; // init fadeout sequence?
			} else if (tics<endTime
				&& ((tics>=320 && 0==(tics&7)) || (tics>=520 && 0==(tics&3)))) {
				yOff+=1; // screen movement
				sOff+=1;  if (sOff>=240) sOff-=240; // 2nd counter to avoid div
			}
			// elevator movement
			if (0==(tics&7)) { ev+=1;  if (ev>16) ev=16; }
			ey+=ev;
		}

		 // show fade effect?
		//
		if (fOff<30) {
			if (0==(tics&1)) {
				ubyte x, passes=1;  if (0==step && tics<endTime) passes+=1;
				for (i=0;i<passes;i+=1) {
					byte y=fOff-i;  if (!i) GetMapData(mapLine,y);
					y+=(sOff>>3);  if (y>=30) y-=30;
					if (0<=y && y<30) { // inside nametab?
						vramOff=lnNameTab0+(y<<5); // calculate nametable start
						*p++=(vramOff>>8)|lfHor;  *p++=vramOff&255; // hibyte/lobyte of start
						*p++=32; // 32 bytes will follow
						if (i) for (x=0;x<32;++x) *p++=mapLine[x]; else for (x=0;x<16;++x) {
							ubyte fadeType=fTab[(mapLine[x<<1])>>1];
							if (step<0) fadeType=0; else if (fadeType) fadeType+=step<<1;
							*p++=fadeType;  *p++=fadeType+1;
						}
					}
				}
				if (tics<endTime) { // fade-in?
					step+=1;  if (step>=3) { fOff+=1;  step=0; }
				} else { // fade-out!
					step-=1;  if (step<-1) { fOff+=1;  step=2; }
					if (fOff>=30) tics=-1; // restart
				}
			}
		} else {

			 // scrolling
			//
			if (0==(sOff&7) && previous!=sOff && tics<endTime+endDelay) {
				// clear line at the end of rotating nametable
				ubyte y=sOff>>3;  previous=sOff;  vramOff=0x2000+(y<<5);
				*p++=(vramOff>>8)|lfHor;  *p++=vramOff&255; // hibyte/lobyte of start
				*p++=32;  for (i=0;i<32;i+=1) *p++=0; // clear
				// update last line with data at the beginning of rotating nametable
				if (0==y) y=29; else y-=1;
				vramOff=0x2000+(y<<5);
				*p++=(vramOff>>8)|lfHor;  *p++=vramOff&255; // hibyte/lobyte of start
				*p++=32; // 32 bytes will follow
				GetMapData(mapLine,(yOff>>3)+30);
				for (i=0;i<32;i+=1) *p++=mapLine[i];
			}

		}
		if (vramOff) { *p++=lfEnd;  lnList(updateList); } // endmarker, activate

		// show hero and elevator (it's one sprite)
		lnAddSpr(sprTab,96,(ey>>F)-yOff);

		tics+=1;
		lnScroll(0,sOff);
		lnSync(0); // sync with vertical blank
	}

	return 0;
}
