--Super Mario Bros. - Drag and Drop
--Written by XKeeper
--Allows you to use the mouse to pick up enemies and movie them around!

debugmodes	= {
	enabled			= false;	-- CHANGE THIS AT YOUR PERIL
	showenemydata	= false;
	drawmouse		= false;
	locktimer		= false;
	invincible		= false;
	ver				= "03/29 15:00:00";
	};

control	= {};	-- must come first.
				-- basically, will contain all the "interface" things,
				-- buttons, menus, etc.
				-- Easier to organize, I guess.

require "x_functions";
require "x_interface";
if debugmodes['enabled'] and false then
	require "x_smb1enemylist";		-- used for summoning and other things... not finished
end;

if not x_requires then
	-- Sanity check. If they require a newer version, let them know.
	timer	= 1;
	while (true) do
		timer = timer + 1;
		for i = 0, 32 do
			gui.drawbox( 6, 28 + i, 250, 92 - i, "#000000");
		end;
		gui.text( 10, 32, string.format("This Lua script requires the x_functions library."));
		gui.text( 53, 42, string.format("It appears you do not have it."));
		gui.text( 39, 58, "Please get the x_functions library at");
		gui.text( 14, 69, "http://xkeeper.shacknet.nu:5/");
		gui.text(114, 78, "emu/nes/lua/x_functions.lua");

		warningboxcolor	= string.format("%02X", math.floor(math.abs(30 - math.fmod(timer, 60)) / 30 * 0xFF));
		gui.drawbox(7, 29, 249, 91, "#ff" .. warningboxcolor .. warningboxcolor);

		FCEU.frameadvance();
	end;

else
	x_requires(6);
end;


-- ****************************************************************************
-- * drawmouse(xpos, ypos, click)
-- * Draws a crude mouse pointer at the location; mostly good for screenshots and debugging.
-- ****************************************************************************
function drawmouse(x, y, click)

	if click then
		fill	= "#cccccc";
	else
		fill	= "#ffffff";
	end;

	y	= y + 1;

	for i = 0, 6 do
		if i ~= 6 then 
			line(x + i, y + i, x + i, y + 8 - math.floor(i / 2), fill);
			pixel(x + i, y + 8 - math.floor(i / 2), "#000000");
		end;
		pixel(x + i, y + i - 1, "#000000");
	end;
	pixel(x + 1, y +  0, "#000000");

	line(x    , y     , x     , y +  9, "#000000");
--	line(x + 1, y +  1, x + 6 , y +  6, "#000000");
--	line(x    , y + 11, x + 7 , y +  7, "#000000");

end;


-- ****************************************************************************
-- * smbpx2ram( screen-x, screen-y )
-- * Returns the offset that represents the tile under screenx/screeny.
-- ****************************************************************************
function smbpx2ram(px, py)

		local py		= math.floor(py) - 0x20;
		local px		= math.floor(px);
		if px < 0 or px > 400 or py < 0x00 or py > (240 - 0x20) then
			return false;
		end;
		
		local oy		= math.floor(py / 0x10);
		local ox		= math.fmod(math.floor((px + smbdata['screenpos']) / 0x10), 0x20);

		offset	= 0x500 + math.fmod(oy * 0x10 + math.floor(ox / 0x10) * 0xC0 + math.fmod(ox, 0xD0), 0x1A0);
		return offset;

end;


-- ****************************************************************************
-- * smbram2px( memory offset )
-- * Gives the current top-left pixel of the tile the offset represents.
-- ****************************************************************************
function smbram2px(offset)
	
	offset		= offset - 0x500;
	if offset < 0 or offset >= 0x1A0 then
		return false;
	end;

	local px	= (math.fmod(offset, 0x10) + math.floor(offset / 0xD0) * 0x10) * 0x10;
	px			= px - math.fmod(smbdata['screenpos'], 0x200);
	if px < 0 then
		px		= px + 0x200;
	end;

	local py	= math.floor(math.fmod(offset, 0xD0) / 0x10);
	returnval	= {x = px, y = py};
	return returnval;

end;


-- ****************************************************************************
-- * smbmoveenemy( Enemy number, xpos, ypos, x accell, y accell )
-- * moves enemies to given point. auto-sets facing direction, as well
-- ****************************************************************************
function smbmoveenemy(n, x, y, ax, ay)

	local x1	= math.fmod(x, 0x100);
	local x2	= math.floor(x / 0x100);
	local y1	= math.fmod(y, 0x100);
	local y2	= math.floor(y / 0x100);
	local ax	= math.max(-128, math.min(ax, 0x7F));
	local ay	= math.max(-128, math.min(ay, 0x7F));

	memory.writebyte(0x006D + n, x2);
	memory.writebyte(0x0086 + n, x1);
	memory.writebyte(0x00B5 + n, y2);
	memory.writebyte(0x00CE + n, y1);
	memory.writebyte(0x0057 + n, ax);
	memory.writebyte(0x009F + n, ay);
	
	if ax > 0 then
		memory.writebyte(0x0045 + n, 1);
	elseif ax < 0 then
		memory.writebyte(0x0045 + n, 2);
	end;

end;


-- ****************************************************************************
-- * inputaverage()
-- * Mouse movement averages (something unique to this...).
-- ****************************************************************************
function inputaverage()

	local tempx	= 0;
	local tempy	= 0;
	for temp = 1, 2 do
		tempx				= tempx	+ avgmove[temp]['xmouse'];
		tempy				= tempy	+ avgmove[temp]['ymouse'];
		avgmove[temp]		= avgmove[temp + 1];
	end;
	avgmove[3]['xmouse']			= inpt['xmouse'] - last['xmouse'];
	avgmove[3]['ymouse']			= inpt['ymouse'] - last['ymouse'];
	avgmove['calc']['xmouse']	= (tempx + avgmove[3]['xmouse']) / 3;
	avgmove['calc']['ymouse']	= (tempy + avgmove[3]['ymouse']) / 3;

end;


-- ****************************************************************************
-- * tileview()
-- * This does all the "what tile is here" shit for you (me)
-- ****************************************************************************
function tileview ()
	local ramval		= smbpx2ram(inpt['xmouse'], inpt['ymouse']);
	if ramval then
		local ret	= smbram2px(ramval);
		local c	= "#ffffff";
		if math.fmod(timer, 4) < 2 then
			c	= "#cccccc";
		end;
		if ret then
			local tx1	= math.max(0, ret['x'] - 1);
			local tx2	= math.min(0xFF, ret['x'] + 0x10);
			local ty1	= math.max(ret['y'] * 0x10 + 0x1F, 0);
			local ty2	= math.min(244, ret['y'] * 0x10 + 0x30);
			box(tx1, ty1, tx2, ty2, c);
		end;

		local textx	= inpt['xmouse'] + 10;
		local texty	= inpt['ymouse'] -  4;
		
		if textx > 229 then
			textx	= textx - 42;
		end;
		texty	= math.min(214, texty);

		text(textx, texty, string.format("%04X", ramval));
		text(textx, texty + 8, string.format(" %02X ", memory.readbyte(ramval)));
	end;
end;


-- ****************************************************************************
-- * Generic test function, really. Does nothing of use.
-- * Incidentally, most of the times this shows up, it doesn't
-- ****************************************************************************
function test(arg)
	
	text(50, 100, "IT WORKS");

end;


-- ****************************************************************************
-- * spawnsetup(arg)
-- * Prepares spawning of an enemy.
-- * Perhaps should create a dialog box in the middle of the screen?..
-- ****************************************************************************
function spawnsetup(args)
	
	if mode ~= 2 then
		spawndata['lmode']	= mode;
		mode				= 2;
		spawndata['enum']	= 0x00;
		spawndata['ename']	= "Green Koopa";
		spawndata['etype']	= 0;
		spawndata['exs']	= 16;
		spawndata['eys']	= 24;
		-- etype:
		--- 0: normal enemy (one slot)
		--- 1: big enemy (two slots, takes latter but fills both)
		--- 2: powerup (takes slot 6)
	end;
end;


-- ****************************************************************************
-- * spawnsetup(arg)
-- * Prepares spawning of an enemy.
-- * Perhaps should create a dialog box in the middle of the screen?..
-- ****************************************************************************
function spawnenemy(args)
	
	local c	= "#ffffff";
	if math.fmod(timer, 4) < 2 then
		c	= "#888888";
	end;

	local freespace	= 0;

	if debugmodes['showenemydata'] then
		for i = 1, 6 do 
			text(8, 8 + 8 * i, string.format("%d %02X", i, memory.readbyte(0x000E+i)));
		end;
	end;
	for i = 1, 6 do 
		if ((spawndata['etype'] <= 1 and i <= 5) or (spawndata['etype'] == 2 and i == 6)) and memory.readbyte(0x000E+i) == 0 then
			if debugmodes['showenemydata'] then
				text(8, 8 + 8 * i, string.format("%d %02X *", i, memory.readbyte(0x000E+i)));
			end;
			if (spawndata['etype'] == 1 and memory.readbyte(0x000E + i - 1) == 0) or spawndata['etype'] ~= 1 then
				freespace	= i;
				break;
			end;
		end;
	end;

	if freespace > 0 then
		box(inpt['xmouse'] - (spawndata['exs'] / 2), inpt['ymouse'] - (spawndata['eys'] / 2), inpt['xmouse'] + (spawndata['exs'] / 2), inpt['ymouse'] + (spawndata['eys'] / 2),  c);
		text(70, 31, string.format("Summon [%s]", spawndata['ename']));
		if debugmodes['showenemydata'] then
			text(70, 39, string.format("Enemy slot [%X]", freespace));
		end;
		local mx	= smbdata['screenpos'] + inpt['xmouse'];
		local my	= 0x100 + inpt['ymouse'];
		if inpt['leftclick'] and not last['leftclick'] then
			memory.writebyte(0x000E + freespace, 1);
			memory.writebyte(0x0015 + freespace, spawndata['enum']);
			memory.writebyte(0x0499 + freespace, 3);
			smbmoveenemy(freespace, mx - (spawndata['exs'] / 2), my - (spawndata['eys'] / 2), -1, 0);
			mode	= spawndata['lmode'];
		end;

	else
		text(70, 31, string.format("Can't summon (too many enemies)!"));
	
	end;

end;


-- ****************************************************************************
-- * modechange(arg)
-- * changes current mode (used in menu).
-- * also changes menu text to reflect new mode.
-- ****************************************************************************
function modechange(args)
	
	mode	= args[1];
	if args[1] == 0 then
		mainmenu['menu']['m001_mode']['menu']['m001_tiles']['marked']	= 1;
		mainmenu['menu']['m001_mode']['menu']['m002_enemy']['marked']	= 0;
	else
		mainmenu['menu']['m001_mode']['menu']['m001_tiles']['marked']	= 0;
		mainmenu['menu']['m001_mode']['menu']['m002_enemy']['marked']	= 1;
	end;
end;


-- ****************************************************************************
-- * debugmode(arg)
-- * changes debugmode flags
-- * useful for on-the-fly checking, I guess
-- ****************************************************************************
function debugmode(args)
	
	if debugmodes[args[2]] == false then
		debugmodes[args[2]]	= true;
		mainmenu['menu']['m999_debug']['menu'][args[1]]['marked']	= 1;
	else
		debugmodes[args[2]]	= false;
		mainmenu['menu']['m999_debug']['menu'][args[1]]['marked']	= 0;
	end;
end;





mainmenu	= {
	test	= 1;
	life	= 0;
	width	= 54;
	menu	= {
		m001_mode	= {
			label	= "Mode",
			life	= 0;
			width	= 50;
			menu	= {
				m001_tiles	= {
					label	= " Objects",
					action	= modechange,
					args	= {0},
					marked	= 0;
					},
				m002_enemy	= {
					label	= " Sprites",
					action	= modechange,
					args	= {1},
					marked	= 1;
					},
				},
			},
		m002_summon	= {
			label	= "Summon",
			action	= spawnsetup,
			args	= {1},
			},
		},
};

if debugmodes['enabled'] then
	mainmenu['menu']['m999_debug']	= {
			label	= "Debug",
			life	= 0;
			width	= 90;
			menu	= {
				m000_showmouse = {
					label	= " Draw mouse",
					action	= debugmode,
					marked	= debugmodes['drawmouse'] and 1 or 0;
					args	= {"m000_showmouse", "drawmouse"},
					},
				m001_enemydata = {
					label	= " Show enemy data",
					action	= debugmode,
					marked	= debugmodes['showenemydata'] and 1 or 0;
					args	= {"m001_enemydata", "showenemydata"},
					},
				m002_locktimer = {
					label	= " Lock timer",
					action	= debugmode,
					marked	= debugmodes['locktimer'] and 1 or 0;
					args	= {"m002_locktimer", "locktimer"},
					},
				m003_invincible	= {
					label	= " Invincibility",
					action	= debugmode,
					marked	= debugmodes['invincible'] and 1 or 0;
					args	= {"m003_invincible", "invincible"},
					},
				},
			};
end;




smbdata		= {screenpos = 0};
mode		= 1;
enemyhold	= {};
avgmove		= { 
				{	xmouse = 0,	ymouse = 0	}, 
				{	xmouse = 0,	ymouse = 0	}, 
				{	xmouse = 0,	ymouse = 0	}, 
				calc = {} 
			};
spawndata	= {};

while (true) do 


	input.update();			-- updates mouse position
	inputaverage();			-- average movement (for throwing)

	smbdata['screenposold']	= smbdata['screenpos'];
	smbdata['screenpos']	= memory.readbyte(0x071a) * 0x100 + memory.readbyte(0x071c);
	smbdata['screenposchg']	= smbdata['screenpos'] - smbdata['screenposold'];
	smbdata['rendercol']	= memory.readbyte(0x06A0);
	if smbdata['screenposchg'] < 0 then
		smbdata['screenposchg'] = 0;
	end;
	timer	= timer + 1;


	if debugmodes['enabled'] then
		if control.button( 234, 15,  19, 2, "SET\n999") then
			memory.writebyte(0x07F8, 0x09);
			memory.writebyte(0x07F9, 0x09);
			memory.writebyte(0x07FA, 0x09);
		end;

		if debugmodes['locktimer'] then
			memory.writebyte(0x0787, 0x1F);
		end;

		if debugmodes['invincible'] then
			memory.writebyte(0x079E, 0x02);
		end;
	end;

	
	if mode == 0 then
		tileview();
	elseif mode == 2 then
		
		spawnenemy();
		

	else
		if debugmodes['showenemydata'] then
			text(0, 25 + 0, string.format("E# XPOS YPOS XREL YREL XA YA TY HB"));
		end;

		for i=1,6 do  
			if (memory.readbyte(0x000E+i) ~= 0) then --and memory.readbyte(0x04AC+(i*4)) ~= 0xFF) and (memory.readbyte(0x0015 + i) ~= 0x30 and memory.readbyte(0x0015 + i) ~= 0x31) then 
				if not enemyhold[i] or not inpt['leftclick'] then
					enemyhold[i]	= nil;
--					text(8, 50 + i * 8, "-");
				elseif enemyhold[i] then
--					text(8, 50 + i * 8, string.format("HOLD  %04X %04X", smbdata['screenpos'] + inpt['xmouse'] - enemyhold[i]['xmouse'], inpt['ymouse'] + 0x100 - enemyhold[i]['ymouse']));
					smbmoveenemy(i, smbdata['screenpos'] + inpt['xmouse'] - enemyhold[i]['x'], inpt['ymouse'] + 0x100 - enemyhold[i]['y'], (avgmove['calc']['xmouse']) * 8, avgmove['calc']['ymouse'] / 2.5);
				end;

				e2x1 = memory.readbyte(0x04AC+(i*4));
				e2y1 = memory.readbyte(0x04AC+(i*4)+1);
				e2x2 = memory.readbyte(0x04AC+(i*4)+2);
				e2y2 = memory.readbyte(0x04AC+(i*4)+3);
--				text(e2x1 - 5, e2y1 - 13, string.format("%02X", memory.readbyte(0x001E + i)));

				
				
				enemyxpos	= memory.readbytesigned(0x006D + i) * 0x0100 + memory.readbyte(0x0086 + i);
				enemyypos	= memory.readbytesigned(0x00B5 + i) * 0x100 + memory.readbyte(0x00CE + i);
				enemyxacc	= memory.readbytesigned(0x0057 + i);
				enemyyacc	= memory.readbytesigned(0x009f + i);
				enemyxposa	= enemyxpos - smbdata['screenpos'];
				enemyyposa	= enemyypos - 0x100;
				enemyyposa2	= math.fmod(enemyyposa + 0x10000, 0x100);
				enemytype	= memory.readbyte(0x0015 + i);
				enemyhitbox	= memory.readbyte(0x0499 + i);

				dead		= "";
				if enemyyposa <= -72 + 32 and enemyyacc < -3 then
					dead	= "UP";
				end;
				if math.abs(enemyxacc) >= 0x60 then
					dead	= dead .. "SIDE";
				end;
				if dead ~= "" then
					dead	= " ".. dead;
				end;

				if debugmodes['showenemydata'] then
					line(enemyxposa, enemyyposa2, enemyxposa + 16, enemyyposa2, "#ffffff");
					text(enemyxposa, enemyyposa2, memory.readbyte(0x0045 + i));
					text(1, 25 + 8 * i, string.format("E%X %04X %04X %04X %04X %02X %02X %02X %02X %s", i, 
						AND(enemyxpos, 0xFFFF), 
						AND(enemyypos, 0xFFFF), 
						AND(enemyxposa, 0xFFFF), 
						AND(enemyyposa, 0xFFFF), 
						AND(enemyxacc, 0xFF), 
						AND(enemyyacc, 0xFF), 
						AND(enemytype, 0xFF), 
						AND(enemyhitbox, 0xFF), 
						"")); -- dead));
				end;

				if hitbox(inpt['xmouse'], inpt['ymouse'], inpt['xmouse'], inpt['ymouse'], e2x1, e2y1, e2x2, e2y2, "#ffffff", "#0000ff") then
--				if hitbox(inpt['xmouse'], inpt['ymouse'], inpt['xmouse'], inpt['ymouse'], enemyxposa, enemyyposa, enemyxposa + 0xF, enemyyposa + 0x17) then
					
--					text(e2x1 - 5, e2y1 - 13, string.format("#%d %02X", i, memory.readbyte(0x0015 + i)));


					if inpt['leftclick'] then

						if not enemyhold[i] then 
							enemyhold[i]	= { x = inpt['xmouse'] - enemyxposa, y = inpt['ymouse'] - enemyyposa };
						end;


--						memory.writebyte(0x001F + i, 0xFF);
--						memory.writebyte(0x04AC + i, 0xFF);
--						memory.writebyte(0x000E + i, 0x00);
					end;  
				end;
			else
				enemyhold[i]	= nil;

			end;  
		end;  
	end;

	--[[
	zap		= zapper.read();

	box(zap['xmouse'] - 5, zap['ymouse'] - 5, zap['xmouse'] + 5, zap['ymouse'] + 5, "#ffffff");
	if zap['click'] == 1 then 
		box(zap['xmouse'] - 7, zap['ymouse'] - 7, zap['xmouse'] + 7, zap['ymouse'] + 7, "#ff0000");
	end;
	line(zap['xmouse'] - 0, zap['ymouse'] - 9, zap['xmouse'] + 0, zap['ymouse'] + 9, "#ffffff");
	line(zap['xmouse'] - 9, zap['ymouse'] - 0, zap['xmouse'] + 9, zap['ymouse'] + 0, "#ffffff");
	]]



	-- almost always
	if control.button(78, 14, 54, 1, "<Main Menu>") then
		mainmenu['life']	= 70;
	end;
	control.showmenu(78, 25, mainmenu);

	text( 20, 222, " 2009 Xkeeper - http://jul.rustedlogic.net/ ");
	line(  21, 231, 232, 231, "#000000");

	if debugmodes['enabled'] then
		text( 41, 214, " Debug version - ".. debugmodes['ver'] .." ");
	end;
	

	-- always on top
	if debugmodes['drawmouse'] then
		drawmouse(inpt['xmouse'], inpt['ymouse'], inpt['leftclick']);
	end;
	FCEU.frameadvance();

end
