--[[ Copyright (c) 2013 Julius Riecke This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ]] SCRIPT_TITLE = "Mega Man 2 LASER EYES" SCRIPT_VERSION = "1.0" require "m_utils" m_require("m_utils",0) --[[ Mega Man 2 LASER EYES version 1.0 by miau Visit http://morphcat.de/lua/ for the most recent version and other scripts. Controls Press select to fire a laser from Mega Man's eyes aimed at the closest object. Left-click to drag and drop objects. Middle-clicking instantly moves Mega Man to the cursor position. Supported roms Rockman 2 - Dr Wily no Nazo (J), Mega Man 2 (U), Mega Man 2 (E) Known bugs - shooting lasers at bosses is glitchy, do it at your own risk - eyes might turn black for a short period of time after screen transition - some objects have duplicate IDs which will result in wrong names being shown for them -]] --configurable vars local show_info = false --show sprite info on startup local show_cursor = false --set this to true when recording a video --sound effects to use for various script events, nil to disable local SFX_LASER = 0x25 local SFX_EXPLOSION = 0x2B local SFX_TARGET = nil --0x39 --0x21 -------------------------------------------------------------------------------------------- --List of sound effects --0x21 press --0x22 peeewwwwwww --0x23 metal blade --0x24 mega buster --0x25 sniper armor shot? --0x26 hit1 --0x27 air shooter --0x28 energy --0x29 touch ground --0x2A wily thing --0x2B destroy1 --0x2C ?? --0x2D reflected shot --0x2E ?? (metal blade-like noise channel usage, short) --0x2F menu selector --0x30 mega man appear --0x31 leaf shield --0x32 menu open --0x33 ?? (short, low noise) --0x34 gate open --0x35 atomic fire charge 1 --0x36 atomic fire charge 2 --0x37 atomic fire charge 2 --0x38 atomic fire shot ? --0x39 prop-top leap --0x3A mega man disappearing --0x3B diving into water --0x3C appearing platform --0x3D wily stage lava drops --0x3E wily stage lava drops --0x3F ?? (similar to 0x27) --0x40 ?? (short noise) --0x41 death --0x42 e-tank/1up --0x43 ?? (short noise in fixed intervals) local sprdb = {} --enemy names source: http://megaman.wikia.com sprdb[0x00] = {name="Squirt"} sprdb[0x01] = {name="Squirt"} --spawned by Lantern Fish sprdb[0x04] = {name="M-445"} sprdb[0x07] = {name="Snapper Controller",invincible=true} sprdb[0x08] = {name="Snapper"} sprdb[0x0A] = {name="Crabbot"} sprdb[0x0C] = {name="Croaker"} sprdb[0x0D] = {name="Croaker (tiny)"} sprdb[0x0E] = {name="",invincible=true} --bubble...underwater mega man sprdb[0x0F] = {name="Lantern Fish"} sprdb[0x13] = {name="Platform"} sprdb[0x15] = {name="Beam"} sprdb[0x16] = {name="Bubble Bat"} sprdb[0x17] = {name="Robo-Rabbit"} sprdb[0x18] = {name="Carrot"} --or mega man being hit sprdb[0x1D] = {name="Mecha Monkey"} sprdb[0x1E] = {name="Atomic Chicken Controller",invincible=true} sprdb[0x1F] = {name="Atomic Chicken"} --sprdb[0x1B] = {name="Climbing Mega Man"} --or hot dog fire --sprdb[0x1C] = {name=""} --or hot dog fire controller? sprdb[0x21] = {name="Telly Spawn Point",invincible=true} sprdb[0x22] = {name="Telly"} sprdb[0x23] = {name="Hothead"} --or Mega Buster sprdb[0x24] = {name="Tackle Fire"} --sprdb[0x27] = {name="Light Control"} --sprdb[0x28] = {name="Light Control"} sprdb[0x29] = {name="Cogwheel"} sprdb[0x2A] = {name="Pierobot"} sprdb[0x2B] = {name="Prop-Top Controller",invincible=true} sprdb[0x2C] = {name="Prop-Top"} sprdb[0x2E] = {name="Hot Dog"} sprdb[0x2F] = {name="Gate"} sprdb[0x30] = {name="Press"} sprdb[0x31] = {name="Blocky"} sprdb[0x32] = {name="Big Fish Controller",invincible=true} --sprdb[0x33] = {name=""} --Bubble Lead sprdb[0x34] = {name="Neo Met"} sprdb[0x35] = {name="Bullet"} --Sniper Armor/Sniper Joe sprdb[0x36] = {name="Fan Fiend"} --or Metal Blade sprdb[0x37] = {name="Pipi Controller",invincible=true} --or Flash weapon (Time Stopper) controller(?) sprdb[0x38] = {name="Pipi"} sprdb[0x3A] = {name="Pipi Egg"} --or Item-3(?) sprdb[0x3B] = {name="Pipi Egg Shell"} sprdb[0x3C] = {name="Child Pipi"} sprdb[0x3D] = {name="Lightning Lord"} sprdb[0x3E] = {name="Thunder Chariot"} --sprdb[0x3F] = {name="Lightning Bolt"} --or "splash"... (mega man diving into water) sprdb[0x3F] = {invincible=true} sprdb[0x40] = {name="Air Tikki"} sprdb[0x41] = sprdb[0x40] sprdb[0x44] = {name="Air Tikki Horn"} sprdb[0x45] = {name="Gremlin"} sprdb[0x46] = {name="Spring Head"} sprdb[0x47] = {name="Mole Controller",invincible=true} sprdb[0x48] = {name="Mole (rising)"} sprdb[0x49] = {name="Mole (falling)"} sprdb[0x4B] = {name="Crazy Cannon"} sprdb[0x4D] = {name="Bullet (Crazy Cannon)"} sprdb[0x4E] = {name="Sniper Armor"} sprdb[0x4F] = {name="Sniper Joe"} sprdb[0x50] = {name="Squirm"} sprdb[0x51] = {name="Squirm Pipe"} sprdb[0x5C] = {name="Metal Blade"} sprdb[0x5D] = {name="Air Shooter"} sprdb[0x5E] = {name="Crash Bomb"} --[[sprdb[0x60] = {name="Bubble Man"} sprdb[0x61] = sprdb[0x60] sprdb[0x62] = sprdb[0x60] sprdb[0x63] = {name="Metal Man"} --or Falling Blocks (Dragon) (id2:80) sprdb[0x64] = sprdb[0x63] --or Falling Blocks (Dragon).. Dragon Controller? (id2:80) sprdb[0x65] = sprdb[0x63] --or Dragon Body (id2:8B) sprdb[0x66] = {name="Air Man"} --or Dragon Bottom (id2:8B) sprdb[0x67] = sprdb[0x66] sprdb[0x68] = sprdb[0x66] sprdb[0x69] = {name="Crash Man"} sprdb[0x6A] = sprdb[0x69] --running or wily boss no.2 (id2:8B/CB/83/C3/80) sprdb[0x6B] = sprdb[0x69] --jumping sprdb[0x70] = {name="Dragon"} --(id2:8B) sprdb[0x71] = {name="Big Fish"} --Wily Boss 3 (id2:83) --]] sprdb[0x76] = {name="Large Life Energy",invincible=true} sprdb[0x77] = {name="Small Life Energy",invincible=true} sprdb[0x78] = {name="Large Weapon Energy",invincible=true} sprdb[0x79] = {name="Small Weapon Energy",invincible=true} sprdb[0x7A] = {name="E-Tank",invincible=true} sprdb[0x7B] = {name="1UP",invincible=true} local eyes = {} eyes.open = { {0,0,0,0,0,1,1,0,1,0,0,0,0}, {0,0,0,0,0,1,1,0,1,0,0,0,0}, } eyes.running = { {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,1,1,0,1,0,0,0,0}, {0,0,0,0,0,1,1,0,1,0,0,0,0}, } eyes.shooting = { {0,0,0,0,0,0,0,0,0,1,1,0,1}, {0,0,0,0,0,0,0,0,0,1,1,0,1}, } eyes.ladder = { {0,0,0,0,0,0,0,1,1,0,0,0,0}, {0,0,0,0,0,0,0,1,1,0,0,0,0}, } eyes.closed = { {0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,1,1,1,1,0,1,1,0,0,0}, } eyes.none = {} local mmeyes = eyes.open local timer = 0 local spr = {} local last_inp = {} local inp = {} local laser = {timer=0} local last_laser = 0 --very limited and hacky particle engine following local P_MAX = 150 local particle = {p={}} function particle.laserai(pi) local function checkcollision(px,py,rx,ry) if(px>=rx-8 and px<=rx+8 and py>=ry-8 and py<=ry+8) then return true else return false end end for i=1,31 do if(spr[i].a and is_enemy(i)) then local px = particle.p[pi].x local py = particle.p[pi].y local px2 = particle.p[pi].x+particle.p[pi].vx*6 local py2 = particle.p[pi].y+particle.p[pi].vy*6 local rx = spr[i].x+spr[i].hx*256 local ry = spr[i].y if(checkcollision(px,py,rx,ry) or checkcollision(px2,py2,rx,ry)) then --if(spr[i].hp>5) then -- memory.writebyte(0x06C0+i,spr[i].hp-5) --else memory.writebyte(0x06C0+i,1) play_sfx(SFX_EXPLOSION) particle.explosion(spr[i].x+spr[i].hx*256,spr[i].y) destroy_sprite(i) --end end end end end function particle.create(new) for i=0,P_MAX-1 do if(particle.p[i]==nil) then particle.p[i]=new if(particle.p[i].x==nil) then particle.p[i].x=0 end if(particle.p[i].y==nil) then particle.p[i].y=0 end if(particle.p[i].vx==nil) then particle.p[i].vx=0 end if(particle.p[i].vy==nil) then particle.p[i].vy=0 end if(particle.p[i].timer==nil) then particle.p[i].timer=0 end if(particle.p[i].lspan==nil) then particle.p[i].lspan=60 end if(particle.p[i].color==nil) then particle.p[i].color="#FFFFFF" end return i end end return nil end function particle.explosion(x,y) --faster than using particle.create local j = 0 for i=0,P_MAX-1 do if(particle.p[i]==nil) then local vx = math.random(-50,50)/30 local vy = math.random(-50,50)/30 local color = "#FF0000" particle.p[i] = {} particle.p[i].x = x particle.p[i].y = y particle.p[i].vx = vx particle.p[i].vy = vy particle.p[i].color = color particle.p[i].lspan = math.random(20,40) particle.p[i].timer=0 if(j==40) then return end j = j + 1 end end end function particle.destroy(i) particle.p[i] = nil end function particle.update() for i=0,P_MAX-1 do if(particle.p[i]) then if(particle.p[i].timer >= particle.p[i].lspan) then particle.destroy(i) else if(particle.p[i].aifunc) then particle.p[i].aifunc(i) end if(particle.p[i]) then particle.p[i].x = particle.p[i].x + particle.p[i].vx particle.p[i].y = particle.p[i].y + particle.p[i].vy particle.p[i].timer = particle.p[i].timer + 1 end end end end end function particle.render(xdisp,ydisp) local j=0 for i=0,P_MAX-1 do if(particle.p[i]) then bcline2(particle.p[i].x-xdisp,particle.p[i].y-ydisp, particle.p[i].x-xdisp+particle.p[i].vx*6,particle.p[i].y+particle.p[i].vy*6-ydisp, particle.p[i].color) if(particle.p[i].aifunc) then bcline2(particle.p[i].x-xdisp,particle.p[i].y+1-ydisp, particle.p[i].x-xdisp+particle.p[i].vx*6,particle.p[i].y+particle.p[i].vy*6+1-ydisp, particle.p[i].color) end j = j + 1 end end end function initialize() for i=0,31 do spr[i] = { a=0, id=0, id2=0, x=0, y=0, hp=0, --timer=-1, } end end function unselect_all() for i=0,31 do spr[i].selected=false spr[i].moving = false end end function getx16(s) return s.hx*256+s.x end function get_target() --closest target local closest = nil local dmin = 9999 for i=1,31 do if(spr[i].a and is_enemy(i) and spr[i].seltimer>=10 and ( (spr[i].sx>=megaman.sx and megaman.dir==1) or (spr[i].sx<=megaman.sx and megaman.dir==-1) or megaman.dir==0)) then local d = getdistance(megaman.sx,megaman.sy,spr[i].sx,spr[i].sy) if(d=0x80) if(spr[i].a==false) then spr[i].seltimer=nil end if(spr[i].a) then if(inp.xmouse>=spr[i].sx-8 and inp.xmouse<=spr[i].sx+8 and inp.ymouse>=spr[i].sy-8 and inp.ymouse<=spr[i].sy+8) then spr[i].hover = true spr[i].clicked = inp.leftclick if(inp.leftclick and last_inp.leftclick==nil) then unselect_all() spr[i].selected=true end else spr[i].hover = false if(inp.leftclick==nil) then spr[i].clicked = false end end --auto-target if(is_enemy(i)) then if(spr[i].seltimer==nil) then spr[i].seltimer=1 else spr[i].seltimer = spr[i].seltimer + 1 end end if(spr[i].selected) then --[[if(inp.delete) then destroy_sprite(i) elseif(inp.pagedown) then memory.writebyte(0x0400+i,spr[i].id-1) elseif(inp.pageup) then memory.writebyte(0x0400+i,spr[i].id+1) end-]] if(spr[i].clicked) then if(inp.xmouse~=last_inp.xmouse or inp.ymouse~=last_inp.ymouse) then spr[i].moving = true end else spr[i].moving = false end end if(spr[i].moving) then local x = inp.xmouse+scroll_x memory.writebyte(0x0460+i,inp.xmouse+scroll_x) memory.writebyte(0x04A0+i,inp.ymouse) memory.writebyte(0x0440+i,scroll_hx+math.floor(x/256)) end end --end end megaman = spr[0] if(megaman.id2==0x80) then megaman.dir=-1 else megaman.dir=1 end if(megaman.id==0x1B) then megaman.dir = 0 end if(inp.middleclick) then --change mega man's coordinates local x = inp.xmouse+scroll_x memory.writebyte(0x0460,x) memory.writebyte(0x04A0,inp.ymouse) memory.writebyte(0x0440,scroll_hx+math.floor(x/256)) end --LASER!!! jp = joypad.read(1) if(jp.select and (last_jp.select==nil or timer-last_laser>22)) then local t = get_target() local vx,vy play_sfx(SFX_LASER) if(t) then vx,vy = getvdir(megaman.sx,megaman.sy-3,spr[t].sx,spr[t].sy) vx = vx * 10 vy = vy * 10 else if(megaman.dir==0) then vx = 0 vy = -10 else vx = megaman.dir*10 vy = 0 end end last_laser = timer particle.create({x=getx16(megaman),y=megaman.y-3,vx=vx,vy=vy,color="#FF0000",aifunc=particle.laserai}) end if(inp.xmouse<83 and inp.ymouse<18) then if(inp.leftclick and last_inp.leftclick==nil) then if(show_info) then show_info = false else show_info = true end end end last_jp = jp last_inp = inp end function render() if(show_info) then gui.text(0,8,"Sprite Info: ON") else gui.text(0,8,"Sprite Info: OFF") end if(inp.xmouse<83 and inp.ymouse<18) then if(show_info) then gui.drawbox(0,9,77,17,"red") else gui.drawbox(0,9,83,17,"red") end end particle.render(scroll_x+scroll_hx*256,0) --LASER EYES!!! --TODO: prevent play_sfx from playing sounds during title screen/stage select if(memory.readbyte(0x580) == 0x0D) then --title screen tune is being played megaman.sx=211 megaman.sy=131 megaman.dir = -1 mmeyes = eyes.open --lasers during title screen if(AND(timer,7)==0 and math.random(0,1)==0) then --play_sfx(SFX_LASER) local vx,vy=getvdir(megaman.sx,megaman.sy-3,0,math.random(30,220)) vx=vx*10 vy=vy*10 particle.create({x=megaman.sx+6,y=megaman.sy-3,vx=vx,vy=vy,color="#FF0000",aifunc=LaserAI}) mmeyes = eyes.none end elseif(megaman.a) then --sprite active local f = memory.readbyte(0x06A0)*256 + memory.readbyte(0x0680) local frame = memory.readbyte(0x06A0) local ftimer = memory.readbyte(0x0680) if(memory.readbyte(0x69)~=0x0E) then --menu opened or "stage intro" tune mmeyes = eyes.none elseif(megaman.id==0x00) then --BLINK! change occurs on next frame if(f >= 0xA01) then mmeyes = eyes.closed elseif(f>=0x001) then mmeyes = eyes.open end elseif(megaman.id==0x01 or megaman.id==0x03 or megaman.id==0x05 or megaman.id==0x07 or megaman.id==0x0B or megaman.id==0x11 or megaman.id==0x13) then mmeyes = eyes.shooting elseif(megaman.id==0x04 or megaman.id==0x0C or megaman.id==0x0D or megaman.id==0x10) then mmeyes = eyes.open elseif(megaman.id==0x08 or megaman.id==0x09 or megaman.id==0x14) then if((f>=0x201 and f<=0x300) or (f>=0x001 and f<=0x100)) then mmeyes = eyes.running else mmeyes = eyes.open end elseif(megaman.id==0x18) then --getting hurt mmeyes = eyes.closed elseif(megaman.id==0x1C or megaman.id==0x1E) then --mega buster, other weapons mmeyes = eyes.ladder else--if(megaman.id==0x1A or megaman.id==0x1B) then --respawning after death, climbing a ladder mmeyes = eyes.none end --if(megaman.hp==0) then -- mmeyes = eyes.none --end else mmeyes = eyes.none end if(mmeyes ~= eyes.none) then local eyecolor = "#FF0000" if(timer-last_laser<5) then eyecolor = "#FFFFFF" end if(megaman.dir==-1) then for y=1,4 do if(mmeyes[y]) then for x=1,14 do if(mmeyes[y][15-x]==1) then bcpixel(megaman.sx-9+x,megaman.sy-5+y,eyecolor) end end end end else for y=1,4 do if(mmeyes[y]) then for x=1,14 do if(mmeyes[y][x]==1) then bcpixel(megaman.sx-5+x,megaman.sy-5+y,eyecolor) end end end end end end for i=0,31 do if(spr[i].a) then local x,y local boxcolor if(spr[i].clicked) then boxcolor="#AA5500" elseif(spr[i].hover) then boxcolor="#FFCCAA" else boxcolor=nil end x = spr[i].sx y = spr[i].sy if(boxcolor) then bcbox(x-9,y-9,x+9,y+9,boxcolor) end if(spr[i].seltimer) then if(spr[i].seltimer<30) then local b=30-spr[i].seltimer local c = (30-spr[i].seltimer)*8 bcbox(x-9-b,y-9-b,x+9+b,y+9+b,string.format("#CC%02X%02X",c,c)) if(spr[i].seltimer==23) then play_sfx(SFX_TARGET) end elseif(spr[i].seltimer>90 or AND(timer,7)>2) then bcbox(x-9,y-9,x+9,y+9,"red") end end if(show_info and (spr[i].hover or spr[i].clicked or spr[i].seltimer)) then --bctext(AND(spr[i].x+255-scroll_x,255), spr[i].y, i); if(i==0) then bctext(x, y, "Blue Bomber") bctext(x, y+8, "("..spr[i].sx..","..spr[i].sy..")") bctext(x, y+16, "HP "..spr[i].hp) elseif(i>4) then if(sprdb[spr[i].id] and sprdb[spr[i].id].name --[[and i>4--]]) then bctext(x, y, sprdb[spr[i].id].name) else bctext(x, y, "("..spr[i].id..")") end bctext(x, y+8, "("..spr[i].sx..","..spr[i].sy..")") if(spr[i].hp~=0) then bctext(x, y+16, "HP "..spr[i].hp) end end end end end if(show_cursor) then local col2 if(inp.leftclick) then col2 = "#FFAA00" elseif(inp.rightclick) then col2 = "#0099EE" elseif(inp.middleclick) then col2 = "#AACC00" else col2 = "white" end drawcursor(inp.xmouse,inp.ymouse,"black",col2) end end initialize() while(true) do update_vars() render() EMU.frameadvance() timer = timer + 1 end