--bowser.lua --v2.0.0 --Created by Horikawa Otane, 2015 --Contact me at https://www.youtube.com/subscription_center?add_user=msotane --v2.0 Created by Hoeloe local colliders = require("colliders") local rng = require("rng") local pm = require("playerManager") local panim = require("playeranim") local followa = require("followa") local vectr = require("vectr") local defs = require("expandedDefines") local bowser = {} local mountlist = {}; local prevkeys = {run=false,down=false,sel=false,tanooki=false} local pressedTanooki = false; local punchTimer = 0; local punchDur = 24; local punchingActive = false; local punchStarted = false; local punchBox = pm.registerCollider(CHARACTER_BOWSER, 1, "punch", colliders.Box(0,0,24,24)); --Everything that can be hit by Bowser's punch and minions local hittables = {}; for _,v in ipairs(NPC.HITTABLE) do table.insert(hittables,v); end local minionQueue = {}; local warriorList = {}; local shield = {}; local hammerList = {}; local fireballs = {}; local iceballs = {}; bowser.MINION_GOOMBA = 0; bowser.MINION_KOOPA = 1; bowser.MINION_PARAGOOMBA = 2; bowser.MINION_PARAKOOPA = 3; bowser.MINION_CHEEPCHEEP = 4; bowser.MINION_HAMMERBRO = 5; --Minion IDs and types do --List of possible minions and their AI type bowser.minionList = {}; bowser.minionList[1] = bowser.MINION_GOOMBA; bowser.minionList[2] = bowser.MINION_GOOMBA; bowser.minionList[3] = bowser.MINION_PARAGOOMBA; bowser.minionList[4] = bowser.MINION_KOOPA; bowser.minionList[6] = bowser.MINION_KOOPA; bowser.minionList[27] = bowser.MINION_GOOMBA; bowser.minionList[28] = bowser.MINION_CHEEPCHEEP; bowser.minionList[29] = bowser.MINION_HAMMERBRO; bowser.minionList[36] = bowser.MINION_GOOMBA; bowser.minionList[48] = bowser.MINION_GOOMBA; bowser.minionList[53] = bowser.MINION_GOOMBA; bowser.minionList[54] = bowser.MINION_GOOMBA; bowser.minionList[55] = bowser.MINION_GOOMBA; bowser.minionList[71] = bowser.MINION_GOOMBA; bowser.minionList[72] = bowser.MINION_KOOPA; bowser.minionList[76] = bowser.MINION_PARAKOOPA; bowser.minionList[89] = bowser.MINION_GOOMBA; bowser.minionList[109] = bowser.MINION_KOOPA; bowser.minionList[110] = bowser.MINION_KOOPA; bowser.minionList[111] = bowser.MINION_KOOPA; bowser.minionList[112] = bowser.MINION_KOOPA; bowser.minionList[117] = bowser.MINION_GOOMBA; bowser.minionList[118] = bowser.MINION_GOOMBA; bowser.minionList[119] = bowser.MINION_GOOMBA; bowser.minionList[120] = bowser.MINION_GOOMBA; bowser.minionList[121] = bowser.MINION_PARAKOOPA; bowser.minionList[122] = bowser.MINION_PARAKOOPA; bowser.minionList[123] = bowser.MINION_PARAKOOPA; bowser.minionList[124] = bowser.MINION_PARAKOOPA; bowser.minionList[125] = bowser.MINION_GOOMBA; bowser.minionList[126] = bowser.MINION_GOOMBA; bowser.minionList[161] = bowser.MINION_PARAKOOPA; bowser.minionList[162] = bowser.MINION_GOOMBA; bowser.minionList[163] = bowser.MINION_GOOMBA; bowser.minionList[164] = bowser.MINION_GOOMBA; bowser.minionList[165] = bowser.MINION_GOOMBA; bowser.minionList[167] = bowser.MINION_PARAGOOMBA; bowser.minionList[168] = bowser.MINION_GOOMBA; bowser.minionList[173] = bowser.MINION_KOOPA; bowser.minionList[175] = bowser.MINION_KOOPA; bowser.minionList[176] = bowser.MINION_PARAKOOPA; bowser.minionList[177] = bowser.MINION_PARAKOOPA; bowser.minionList[189] = bowser.MINION_GOOMBA; bowser.minionList[207] = bowser.MINION_GOOMBA; bowser.minionList[229] = bowser.MINION_CHEEPCHEEP; bowser.minionList[230] = bowser.MINION_CHEEPCHEEP; bowser.minionList[231] = bowser.MINION_CHEEPCHEEP; bowser.minionList[232] = bowser.MINION_CHEEPCHEEP; bowser.minionList[233] = bowser.MINION_CHEEPCHEEP; bowser.minionList[234] = bowser.MINION_CHEEPCHEEP; bowser.minionList[235] = bowser.MINION_CHEEPCHEEP; bowser.minionList[236] = bowser.MINION_CHEEPCHEEP; bowser.minionList[242] = bowser.MINION_GOOMBA; bowser.minionList[243] = bowser.MINION_PARAGOOMBA; bowser.minionList[244] = bowser.MINION_PARAGOOMBA; bowser.minionList[247] = bowser.MINION_GOOMBA; bowser.minionList[285] = bowser.MINION_GOOMBA; bowser.minionList[286] = bowser.MINION_GOOMBA; bowser.minionList[296] = bowser.MINION_GOOMBA; bowser.minionList[302] = bowser.MINION_PARAGOOMBA; bowser.minionList[309] = bowser.MINION_GOOMBA; bowser.minionList[375] = bowser.MINION_GOOMBA; bowser.minionList[380] = bowser.MINION_PARAGOOMBA; bowser.minionList[382] = bowser.MINION_GOOMBA; bowser.minionList[383] = bowser.MINION_GOOMBA; bowser.minionList[392] = bowser.MINION_PARAGOOMBA; bowser.minionList[407] = bowser.MINION_GOOMBA; bowser.minionList[408] = bowser.MINION_GOOMBA; bowser.minionList[415] = bowser.MINION_GOOMBA; bowser.minionList[417] = bowser.MINION_PARAGOOMBA; bowser.minionList[578] = bowser.MINION_GOOMBA; bowser.minionList[611] = bowser.MINION_GOOMBA; bowser.minionList[612] = bowser.MINION_GOOMBA; --For Koopa and Parakoopa type minions, this determines the ID of the shell they will turn into. bowser.koopaShells = {} bowser.koopaShells[4] = 5; bowser.koopaShells[6] = 7; bowser.koopaShells[72] = 73; bowser.koopaShells[76] = 5; bowser.koopaShells[109] = 113; bowser.koopaShells[110] = 114; bowser.koopaShells[111] = 115; bowser.koopaShells[112] = 116; bowser.koopaShells[121] = 113; bowser.koopaShells[122] = 114; bowser.koopaShells[123] = 115; bowser.koopaShells[124] = 116; bowser.koopaShells[161] = 7; bowser.koopaShells[173] = 172; bowser.koopaShells[175] = 174; bowser.koopaShells[176] = 172; bowser.koopaShells[177] = 174; --Deployed Paragoomba and Parakoopa troops will ignore IDs in this list. bowser.shieldIgnoreList = {} bowser.shieldIgnoreList[5] = true bowser.shieldIgnoreList[7] = true bowser.shieldIgnoreList[73] = true bowser.shieldIgnoreList[113] = true bowser.shieldIgnoreList[114] = true bowser.shieldIgnoreList[115] = true bowser.shieldIgnoreList[116] = true bowser.shieldIgnoreList[172] = true bowser.shieldIgnoreList[174] = true end local HUD = {} HUD.npc_box = pm.registerGraphic(CHARACTER_BOWSER, "bowser-hud-npc.png"); HUD.hit_full = pm.registerGraphic(CHARACTER_BOWSER, "bowser-hud-hit.png"); HUD.hit_empty = pm.registerGraphic(CHARACTER_BOWSER, "bowser-hud-hit-empty.png"); local hp = nil; local power = 2; local powerdown = false; local stallTimer = 0; local stallFall = false; local killed = false; function bowser.setHP(v) hp = math.min(2,math.max(1,v)); end function bowser.getHP(v) return hp; end local function contains(tbl,val) for _,v in ipairs(tbl) do if(v == val) then return true; end end return false; end local function ducking_ext() -- Ignore if not holding down if not player.downKeyPressing then return false end -- Check if dead if player:mem(0x13e, FIELD_WORD) > 0 then return false end -- Check if submerged and standing on the ground if (player:mem(0x34, FIELD_WORD) ~= 0 or player:mem(0x36, FIELD_WORD) ~= 0) and not player:isGroundTouching() then return false end -- Check if on a slope and not underwater if player:mem(0x48, FIELD_WORD) ~= 0 and player:mem(0x36, FIELD_WORD) == 0 then return false end -- Check if sliding, climbing, holding an item, in statue form, or spinjumping if player:mem(0x3c, FIELD_WORD) ~= 0 or player:mem(0x40, FIELD_WORD) ~= 0 or player:mem(0x154, FIELD_WORD) > 0 or player:mem(0x4a, FIELD_WORD) == -1 or player:mem(0x50, FIELD_WORD) ~= 0 then return false end -- Check if in a forced animation if player.forcedState == 1 -- Powering up or player.forcedState == 2 -- Powering down or player.forcedState == 3 -- Entering pipe or player.forcedState == 4 -- Getting fire flower or player.forcedState == 7 -- Entering door or player.forcedState == 41 -- Getting ice flower or player.forcedState == 500 then return false end return true end --Change the ID of an NPC local function replaceNPC(npc,id) npc:transform(id); --[[ npc.id = id; local w = NPC.config[npc.id].width; local h = NPC.config[npc.id].height; npc:mem(0x90, FIELD_DFLOAT, w); npc:mem(0x88, FIELD_DFLOAT, h); if(NPC.config[npc.id].gfxwidth ~= 0) then w = NPC.config[npc.id].gfxwidth; end if(NPC.config[npc.id].gfxheight ~= 0) then h = NPC.config[npc.id].gfxheight; end npc:mem(0xB8, FIELD_DFLOAT, w); npc:mem(0xC0, FIELD_DFLOAT, h); npc:mem(0xE4, FIELD_WORD, 0); ]] end --Spawn a tiny smoke puff on the location of an object. local function spawnSmoke(k) local a = Animation.spawn(10,k.x+k.width*0.5,k.y+k.height*0.5); a.x = a.x - a.width*0.5; a.y = a.y - a.height*0.5; end --Add a minion to the queue. local function pushMinion(npc) local target = minionQueue[#minionQueue]; if(target == nil) then target = player; else target = target.npc; end npc:mem(0xDC,FIELD_WORD,0); --Prevent respawning local ftype = followa.TYPE.WALK_JUMP; if(bowser.minionList[npc.id] == bowser.MINION_CHEEPCHEEP) then ftype = followa.TYPE.FLY; end followa.Follow(npc,target,4,vectr.v2(0,0),target.width,320,ftype); table.insert(minionQueue, {id = npc.id, npc = npc}); if(bowser.minionList[npc.id] == bowser.MINION_PARAGOOMBA or bowser.minionList[npc.id] == bowser.MINION_PARAKOOPA) then minionQueue[#minionQueue].ai1 = npc.ai1; end end --Remove a minion from the queue. local function popMinion() if(#minionQueue > 0) then local npc = minionQueue[1]; table.remove(minionQueue, 1); if(#minionQueue > 0) then followa.Follow(minionQueue[1].npc,player,4,vectr.v2(0,0),player.width,320); end if(npc.npc.isValid) then return npc; end end return nil; end --Change the order of the minion queue. local function rotateMinions() if(#minionQueue > 1) then local npc = popMinion(); if(npc ~= nil) then followa.Follow(npc.npc,minionQueue[#minionQueue].npc,4,vectr.v2(0,0),minionQueue[#minionQueue].npc.width,320); table.insert(minionQueue, {id = npc.npc.id, npc = npc.npc}); end end end --Kill a minion from the queue. local function discardMinion() local npc = popMinion(); spawnSmoke(npc.npc); npc.npc:kill(9); end function bowser.onInitAPI() registerEvent(bowser, "onTick", "onTick", false) registerEvent(bowser, "onDraw", "onDraw", false) registerEvent(bowser, "onInputUpdate", "onInputUpdate", false) registerEvent(bowser, "onNPCKill", "onNPCKill", false) registerEvent(bowser, "onExitLevel", "onExitLevel", false) end function bowser.onInputUpdate() if player.character == CHARACTER_BOWSER then pm.winStateCheck() if(player:mem(0x13E,FIELD_BOOL)) then --Oops we dead. return; end --Keep track of previous key states. local prun = prevkeys.run; local psel = prevkeys.sel; local ptan = prevkeys.tanooki; prevkeys.run = player.runKeyPressing; prevkeys.sel = player.dropItemKeyPressing; prevkeys.tanooki = player.altRunKeyPressing --If we're doing Tanooki Bowser's stall-and-fall, then hijack controls early. if(stallFall) then --Force statue mode player:mem(0x4A,FIELD_BOOL,true); player:mem(0x4C,FIELD_WORD,9999); player:mem(0x4E,FIELD_WORD,1); if(stallTimer > 0) then --STALL player.speedX = 0; stallTimer = stallTimer - 1; player.speedY = -Defines.player_grav-math.max(0,(stallTimer-10)/5); else --FALL player.speedY = 24; if(player:isGroundTouching() and stallTimer == 0) then --We just hit the ground Defines.earthquake = 6; local a1 = Animation.spawn(10,player.x,player.y+player.height); local a2 = Animation.spawn(10,player.x+player.width,player.y+player.height); a1.x = a1.x-a1.width*0.5 a2.x = a2.x-a1.width*0.5 a1.y = a1.y-a1.height*0.5 a2.y = a2.y-a1.height*0.5 local _,_,cs = colliders.collideNPC(colliders.Box(player.x-32,player.y+player.height-16,player.width+32,16),hittables); for _,v in ipairs(cs) do if(not v.friendly and not v.isHidden) then v:harm(HARM_TYPE_FROMBELOW) end end playSFX(37); stallTimer = -1; end if(stallTimer < 0) then stallTimer = stallTimer - 1; end --Hold "statue" mode for a bit after we hit the ground. if(stallTimer <= -30 and not player.downKeyPressing or stallTimer <= -140) then stallFall = false; player:mem(0x4A,FIELD_BOOL,false); end end --Disable all input if we're stall-and-falling. player.upKeyPressing = false; player.downKeyPressing = false; player.leftKeyPressing = false; player.rightKeyPressing = false; player.jumpKeyPressing = false; player.altJumpKeyPressing = false; player.runKeyPressing = false; player.altRunKeyPressing = false; player.dropItemKeyPressing = false; end --Disable input if we're punching. if(punchTimer > 0 and not Misc.isPaused()) then player.upKeyPressing = false; player.downKeyPressing = prevkeys.down; player.leftKeyPressing = false; player.rightKeyPressing = false; player.jumpKeyPressing = false; player.altJumpKeyPressing = false; player.runKeyPressing = false; player.altRunKeyPressing = false; player.dropItemKeyPressing = false; end --Activate stall-and-fall if(not stallFall and player.powerup == PLAYER_TANOOKIE and prevkeys.down == false and player.downKeyPressing and not player:isGroundTouching()) then stallFall = true; playSFX(34); stallTimer = 22; end prevkeys.down = player.downKeyPressing; --Disable spinjump if(player.altJumpKeyPressing and player:mem(0x108,FIELD_WORD) == 0 --[[No mount]]) then player.jumpKeyPressing = true; player.altJumpKeyPressing = false; end --Rotate minion queue if(not psel and player.dropItemKeyPressing) then if(#minionQueue > 1) then playSFX(26); rotateMinions(); end end --Release minion if(not ptan and player.altRunKeyPressing) then if(player.powerup == PLAYER_TANOOKIE) then player.runKeyPressing = true; end --Take a minion from the front of the queue. local p = popMinion(); if(p ~= nil) then --Set the minion direction and start special behaviour p.npc.direction = player:mem(0x106,FIELD_WORD) --Ensure the punch anim happens punchTimer = punchDur; punchStarted = false; player.runKeyPressing = false; if(bowser.minionList[p.npc.id] == bowser.MINION_GOOMBA or bowser.minionList[p.npc.id] == bowser.MINION_CHEEPCHEEP) then --GOOMBA/CHEEPCHEEP RELEASE MINION TO DEFAULT AI followa.StopFollowing(p.npc); table.insert(warriorList,p.npc); elseif(bowser.minionList[p.npc.id] == bowser.MINION_KOOPA) then --KOOPACONVERT CONVERT TO SHELL AND THROW FORWARDS replaceNPC(p.npc, bowser.koopaShells[p.npc.id]); p.npc.data.isDanger = true; p.npc.data.dangerCounter = 30; p.npc.speedX = p.npc.direction*5; followa.StopFollowing(p.npc); table.insert(warriorList,p.npc) elseif(bowser.minionList[p.npc.id] == bowser.MINION_PARAGOOMBA or bowser.minionList[p.npc.id] == bowser.MINION_PARAKOOPA) then --PARAGOOMBA/PARAKOOPA ADD TO ROTATING SHIELD table.insert(warriorList,p.npc) p.npc.data.isShield = true; local t = vectr.v2(player.x+(player.width-p.npc.width)*0.5 + 128*player:mem(0x106,FIELD_WORD),player.y + (player.height-p.npc.height)*0.5); table.insert(shield,{npc=p.npc,target=t,pos=vectr.v2(p.npc.x,p.npc.y),t=90*player:mem(0x106,FIELD_WORD)}) followa.StopFollowing(p.npc); elseif(bowser.minionList[p.npc.id] == bowser.MINION_HAMMERBRO) then --HAMMERBRO ADD TO HAMMERBRO LIST AND RELEASE TO DEFAULT AI table.insert(hammerList,p.npc) followa.StopFollowing(p.npc); end end end --Disable statue form if(player.powerup == PLAYER_TANOOKIE) then player.altRunKeyPressing = false; end --Filter mounts if player:mem(0x108,FIELD_WORD) ~= 2 then player:mem(0x108,FIELD_WORD,0) end local newmounts = {} local sx = math.abs(player.speedX)*2; local sy = math.abs(player.speedY)*2; for k,v in ipairs(NPC.getIntersecting(player.x-sx,player.y-sy,player.x+player.width+sx,player.y+player.height+sy)) do if(v.id == 35 or v.id == 193 or v.id == 191 or v.id == 148 or v.id == 98 or v.id == 228 or v.id == 95 or v.id == 150 or v.id == 149 or v.id == 100 or v.id == 99) then if(mountlist[v] ~= nil) then newmounts[v] = mountlist[v]; else newmounts[v] = v.friendly; end v.friendly = true; end end for k,v in pairs(mountlist) do if(newmounts[k] == nil and k.isValid) then k.friendly = v; end end mountlist = newmounts; --Do punch if(player.runKeyPressing and not prun and player.forcedState == 0 and player:mem(0x40,FIELD_WORD) == 0) then punchTimer = punchDur; punchStarted = false; player.runKeyPressing = false; end end end local function managePowerups() local i = 1; --Track fireballs while(i <= #fireballs) do local v = fireballs[i]; if(v.isValid) then local killed = false; local b,_,cs = colliders.collideNPC(v,hittables); --Hit things with fireballs for _,w in ipairs(cs) do if(not w.friendly and not w.isHidden) then w:harm(HARM_TYPE_PROJECTILE_USED); spawnSmoke(v); playSFX(3) v:kill(4); killed = true; break; end end --Melt ice blocks if(not killed) then local b,_,cs = colliders.collideBlock(v,colliders.BLOCK_SOLID..colliders.BLOCK_LAVA..colliders.BLOCK_PLAYER); for _,w in ipairs(cs) do if(not w.isHidden) then if (w.id == 633 or w.id == 620 or w.id == 621 or w.id == 634) then -- Frozen coin if (w.id == 620) then NPC.spawn(10, w.x, w.y, player.section); end -- Large ice block if (w.id == 634) then if(v.x > w.x) then --hit right side Block.spawn(633, w.x, w.y); Block.spawn(633, w.x, w.y+w.height*0.5); Animation.spawn(10, w.x, w.y); Animation.spawn(10, w.x, w.y+w.height*0.5); else --hit left side Block.spawn(633, w.x+w.width*0.5, w.y); Block.spawn(633, w.x+w.width*0.5, w.y+w.height*0.5); Animation.spawn(10, w.x+w.width*0.5, w.y); Animation.spawn(10, w.x+w.width*0.5, w.y+w.height*0.5); end elseif (w.id == 621) then --Munch munch Block.spawn(109, w.x, w.y); else Animation.spawn(10, w.x, w.y) end w:remove(); playSFX(16); end spawnSmoke(v); playSFX(3) v:kill(4); killed = true; break; end end end i = i+1; else table.remove(fireballs,i); end end i = 1; --Track iceballs while(i <= #iceballs) do local v = iceballs[i]; if(v.isValid) then local b,_,cs = colliders.collideNPC(v,hittables); --Hit things with iceballs and freeze them for _,w in ipairs(cs) do if(not w.friendly and not w.isHidden) then w:toIce(); playSFX(3) v:kill(4); break; end end i = i+1; else table.remove(iceballs,i); end end --Change jumpheight based on powerup if(player.character == CHARACTER_BOWSER) then if(player.powerup == PLAYER_LEAF or player.powerup == PLAYER_TANOOKIE) then player:mem(0x164,FIELD_WORD,-2); if(ducking_ext()) then player:mem(0x12E,FIELD_BOOL,true); end Defines.jumpheight = 40; Defines.jumpheight_bounce = 40; player:mem(0x172,FIELD_BOOL,false); player:mem(0x168,FIELD_FLOAT,0); else Defines.jumpheight = 13; Defines.jumpheight_bounce = 13; end end end function bowser.onTick() if player.character == CHARACTER_BOWSER then player:mem(0x160,FIELD_WORD, 2); --Disable default projectiles if(not killed and player:mem(0x13E,FIELD_BOOL)) then killed = true; playSFX(5); end if(player:mem(0x122,FIELD_WORD) == 2) then --Powering down? Then update HP. if(not powerdown and hp == 1) then --This happens if player is hurt when on 1HP, so should die. killed = true; player:kill(); end powerdown = true; panim.setFrame(player,30); --Set hurt frame else powerdown = false; end if((player:mem(0x122,FIELD_WORD) == 1 or player:mem(0x122,FIELD_WORD) == 4 or player:mem(0x122,FIELD_WORD) == 5 or player:mem(0x122,FIELD_WORD) == 11 or player:mem(0x122,FIELD_WORD) == 12 or player:mem(0x122,FIELD_WORD) == 41) and hp == 1) then hp = 2; end --Lock player to "big" state, for animation purposes. if (player.powerup == 1) then hp = 1; player.powerup = 2; end --Update Bowser's hitboxes and spritesheets based on "hp" variable. This allows the "bowser-1" information to be used even though we lock him to "big" state. if(hp < 2) then Graphics.sprites.bowser[2].img = Graphics.sprites.bowser[1].img; if(power > 1) then power = 1; Misc.loadCharacterHitBoxes(2, player.powerup, pm.getHitboxPath(CHARACTER_BOWSER,1)); end else if(power < 2) then power = 2; Graphics.sprites.bowser[2].img = pm.getCostumeImage(CHARACTER_BOWSER,2); Misc.loadCharacterHitBoxes(2, player.powerup, pm.getHitboxPath(CHARACTER_BOWSER,player.powerup)); end end --No reserve powerups for you. player.reservePowerup = 0 --Update rotating shield local i = 1; while(i <= #shield) do local v = shield[i]; if(v.npc.isValid) then --Stop despawning and make rotating shield NPCs stay in the same section as Bowser v.npc:mem(0x12A, FIELD_WORD, 180); v.npc:mem(0x128, FIELD_BOOL, false); v.npc:mem(0x146, FIELD_WORD, player.section); v.npc:mem(0x74, FIELD_BOOL, false); v.target.x = player.x+(player.width-v.npc.width)*0.5 + 128*math.sin(v.t); v.target.y = player.y+(player.height-v.npc.height)*0.5 + 128*math.cos(v.t); v.t = v.t+0.05; if((v.pos-v.target).length > 800) then v.pos = v.target; end v.pos = vectr.lerp(v.pos, v.target, 0.2) v.npc.x = v.pos.x; v.npc.y = v.pos.y; i = i + 1; else table.remove(shield,i); end end --Update followers local k = 1; while(k <= #minionQueue) do v = minionQueue[k]; if(v.npc.isValid) then --Stop despawning and make followers stay in the same section as Bowser v.npc:mem(0x12A, FIELD_WORD, 180); v.npc:mem(0x128, FIELD_BOOL, false); v.npc:mem(0x146, FIELD_WORD, player.section); v.npc:mem(0x74, FIELD_BOOL, false); --Update follower behaviours if(bowser.minionList[v.id] == bowser.MINION_PARAGOOMBA or bowser.minionList[v.id] == bowser.MINION_PARAKOOPA) then v.npc.ai1 = 0; if(minionQueue[k+1] ~= nil) then followa.Update(minionQueue[k+1].npc, vectr.v2(v.npc.x+v.npc.width*0.5,player.y+player.height)); end elseif(bowser.minionList[v.id] == bowser.MINION_CHEEPCHEEP) then if(v.npc.underwater) then followa.SetBehaviour(v.npc, followa.TYPE.FLY); if(minionQueue[k+1] ~= nil) then followa.Update(minionQueue[k+1].npc, v.npc); end else followa.SetBehaviour(v.npc, followa.TYPE.WALK); if(minionQueue[k+1] ~= nil) then followa.Update(minionQueue[k+1].npc, vectr.v2(v.npc.x+v.npc.width*0.5,player.y+player.height)); end end end k = k+1; else table.remove(minionQueue,k); end end; if(not player:mem(0x13E,FIELD_BOOL)) then --We not dead. --Update punch if(punchTimer > 0) then punchTimer = punchTimer - 1; local f = 46; local yoff = 16; --Get frame and hitbox offset based on crouch state if(player:mem(0x12E, FIELD_BOOL)) then f = f + 2; yoff = 0; end --Second punching frame active if(punchTimer punchDur*0.3) then punchBox.active = true; --Set hitbox offsets local offset = -punchBox.width; if(player:mem(0x106,FIELD_WORD) == 1) then --Player is facing right offset = player.width; end punchBox.x = player.x+offset; punchBox.y = player.y+yoff; else punchBox.active = false; punchingActive = false; end end --Set animation frame panim.setFrame(player,f); --Do punch collision tests! if(punchBox.active) then local h,_,cs = colliders.collideNPC(punchBox, hittables); if(h) then for _,v in ipairs(cs) do if(not v.friendly and not v.isHidden) then v:harm(HARM_TYPE_SWORD); punchBox.active = false; end end end end else punchBox.active = false; end end --Update activated "warrior" minions local i = 1; while (i <= #warriorList) do local rem = false; --Check that the npc hasn't died or despawned if(warriorList[i].isValid and warriorList[i]:mem(0x12A,FIELD_WORD) > 0) then --Should this NPC hurt Bowser? (i.e. is a koopa shell). Make sure it's clear of his hitbox before making it unfriendly. if(warriorList[i].data.isDanger) then if(warriorList[i].data.dangerCounter > 0) then warriorList[i].data.dangerCounter = warriorList[i].data.dangerCounter - 1; elseif (not colliders.collide(warriorList[i],player)) then warriorList[i].friendly = false; end else --Do collisions manually if the NPC is a friendly one. local h,_,cs = colliders.collideNPC(warriorList[i], hittables); if(h) then for _,v in ipairs(cs) do if(not v.friendly and not v:mem(0x40,FIELD_BOOL)) then --Make sure the collision is between a rotating shield NPC and an NPC on the shield ignore list. if(warriorList[i].data.isShield ~= true or bowser.shieldIgnoreList[v.id] ~= true) then --Harm the enemy, but make sure it register being hurt by a warrior so it doesn't get added to the minion queue. if(v ~= warriorList[i]) then v.data.BOWSER_killedByMinion = true; v:harm(HARM_TYPE_HELD); if(bowser.minionList[warriorList[i].id] == bowser.MINION_PARAKOOPA) then NPC.spawn(bowser.koopaShells[warriorList[i].id],warriorList[i].x,warriorList[i].y,warriorList[i]:mem(0x146,FIELD_WORD)); warriorList[i]:kill(9); else warriorList[i].data.BOWSER_killedByMinion = true; warriorList[i]:kill(HARM_TYPE_HELD); end rem = true; break; end end end end end end else rem = true; end if(not rem) then i = i + 1; else table.remove(warriorList,i); end end --Check for newly thrown hammers from friendly hammer bros to convert them to player-type hammers. --TODO: Replace this with a more robust check (this is as robust as it gets without a reference to the NPC that threw the hammers) local i = 1; while (i <= #hammerList) do if(hammerList[i].isValid) then if(hammerList[i]:mem(0xE4,FIELD_WORD) == 2 or hammerList[i]:mem(0xE4,FIELD_WORD) == 5) then local b,_,cs = colliders.collideNPC(colliders.Box(hammerList[i].x, hammerList[i].y,1,1), 30); for _,v in ipairs(cs) do if(v.friendly and not v.isHidden and v:mem(0x124,FIELD_WORD) == -1 and v.animationFrame == 0 and v.speedX == 3*hammerList[i].direction and v.speedY == -8 and hammerList[i].x == v.x and hammerList[i].y == v.y) then replaceNPC(v,171); v.friendly = false; break; end end end i = i+1; else table.remove(hammerList,i); end end end managePowerups(); end local function drawHUD() --NPC box Graphics.draw{x=405,y=16,type=RTYPE_IMAGE,image=pm.getGraphic(CHARACTER_BOWSER,HUD.npc_box), priority=5}; if(#minionQueue > 0 and minionQueue[1].npc.isValid) then local gfxw = NPC.config[minionQueue[1].id].gfxwidth; local gfxh = NPC.config[minionQueue[1].id].gfxheight; if(gfxw == 0) then gfxw = minionQueue[1].npc.width; end if(gfxh == 0) then gfxh = minionQueue[1].npc.height; end Graphics.draw{x=433-gfxw*0.5,y=44-gfxh*0.5,type=RTYPE_IMAGE,image=Graphics.sprites.npc[minionQueue[1].id].img,sourceX=0,sourceY=0,sourceWidth=gfxw,sourceHeight=gfxh, priority=5}; end --HP box local hitimg; if(hp > 1) then hitimg = pm.getGraphic(CHARACTER_BOWSER,HUD.hit_full); else hitimg = pm.getGraphic(CHARACTER_BOWSER,HUD.hit_empty); end Graphics.draw{x=339,y=16,type=RTYPE_IMAGE,image=hitimg, priority=5}; --Life counter Graphics.draw{x=234,y=26,type=RTYPE_IMAGE,image=Graphics.sprites.hardcoded["33-3"].img, priority=5}; Graphics.draw{x=274,y=27,type=RTYPE_IMAGE,image=Graphics.sprites.hardcoded["33-1"].img, priority=5}; Text.printWP(mem(0x00B2C5AC,FIELD_FLOAT),1,296,27,5); --Coin counter Graphics.draw{x=488,y=26,type=RTYPE_IMAGE,image=Graphics.sprites.hardcoded["33-2"].img, priority=5}; Graphics.draw{x=512,y=27,type=RTYPE_IMAGE,image=Graphics.sprites.hardcoded["33-1"].img, priority=5}; Text.printWP(mem(0x00B2C5A8,FIELD_WORD),1,552-18*(#tostring(mem(0x00B2C5A8,FIELD_WORD))-1),27, 5); --Score Text.printWP(mem(0x00B2C8E4,FIELD_DWORD),1,552-18*(#tostring(mem( 0x00B2C8E4,FIELD_DWORD))-1),47, 5); --Star counter if(mem(0x00B251E0,FIELD_WORD) > 0) then Graphics.draw{x=250,y=46,type=RTYPE_IMAGE,image=Graphics.sprites.hardcoded["33-5"].img, priority=5}; Graphics.draw{x=274,y=47,type=RTYPE_IMAGE,image=Graphics.sprites.hardcoded["33-1"].img, priority=5}; Text.printWP(mem(0x00B251E0,FIELD_WORD),1,296,47,5); end end function bowser.onDraw() if(player.character == CHARACTER_BOWSER) then drawHUD(); --Update rotating shield positions before drawing them to avoid visual glitchiness when they're inside walls. for k,v in ipairs(shield) do if(v.npc.isValid) then v.npc.x = v.pos.x; v.npc.y = v.pos.y; end end end end function bowser.onNPCKill(eventObj, npc, reason) if player.character == CHARACTER_BOWSER then if(reason == 9 and (contains(NPC.POWERUP,npc.id) or npc.id == 250) and npc.id ~= 90 and npc.id ~= 186 and npc.id ~= 187 and npc.id ~= 188 and npc.id ~= 273 and colliders.collide(npc,player)) then --Register powerups to keep track of health hp = 2; elseif(reason == 9 and npc:mem(0x12A,FIELD_WORD) == -1) then --Check for despawning minions, and stop them despawning for _,v in ipairs(minionQueue) do if(v.npc == npc) then eventObj.cancelled = true; break; end end for _,v in ipairs(shield) do if(v.npc == npc) then eventObj.cancelled = true; break; end end elseif(reason <= 10 and reason ~= 9 and reason ~= 6 and reason ~= 4 and reason ~= 3 and bowser.minionList[npc.id] ~= nil and npc.data.BOWSER_killedByMinion ~= true) then --Recruit a minion! pushMinion(npc); eventObj.cancelled = true; playSFX(13); playSFX(2); end end end function bowser.onExitLevel() --Revert powerup based on health so it can be restored in the next level. if(player.character == CHARACTER_BOWSER) then if(hp == nil or hp < 2) then player.powerup = 1; end end end function bowser.initCharacter() --Set Bowser's mobility stats --TODO: Make this more stable Defines.player_walkspeed = 2; Defines.player_runspeed = 4; Defines.jumpheight = 13; Defines.jumpheight_bounce = 13; --Update powerup based on HP if the value is already set (means we've changed character to Bowser after having switched away from him). if(hp ~= nil and hp < 2) then player.powerup = 1; end --Set health based on powerup state. if(player.powerup > 1) then hp = 2; else hp = 1; end if(player.powerup < 2) then player.powerup = 2; end --TODO: Make this more stable hud(false); end function bowser.cleanupCharacter() --Revert any mounts we've set to be unusable for k,v in pairs(mountlist) do if(k.isValid) then k.friendly = v; end end --Reset Bowser's mobility stats --TODO: Make this more stable Defines.player_walkspeed = nil; Defines.player_runspeed = nil; Defines.jumpheight = nil; Defines.jumpheight_bounce = nil; --Despawn all minions while(#minionQueue > 0) do discardMinion(); end --Despawn rotating shield while(#shield > 0) do if(shield[#shield].npc.isValid) then local k = shield[#shield].npc; spawnSmoke(k); shield[#shield].npc:kill(9); end shield[#shield] = nil; end --TODO: Make this more stable hud(true); end function bowser.doGroundPound() stallFall = true stallTimer = 22 end function bowser.isGroundPounding() return stallFall end return bowser