-- v1.3.0 local flame = {} local redstone = require("redstone") local npcManager = require("npcManager") local deg, atan2 = math.deg, math.atan2 local append, unmap = table.append, table.unmap flame.name = "flame" flame.id = NPC_ID flame.order = 0.62 flame.onRedPower = function(n, c, power, dir, hitbox) redstone.setEnergy(n, power) end flame.config = npcManager.setNpcSettings({ id = flame.id, width = 32, height = 32, gfxheight = 32, gfxwidth = 32, gfxoffsetx = 0, gfxoffsety = 0, frames = 4, framespeed = 2, framestyle = 0, invisible = false, nogravity = 1, jumphurt = 1, nofireball = 1, noiceball = 0, grabside = 0, grabtop = 0, noyoshi = 1, playerblock = 0, spinjumpsafe = true, notcointransformable = true, nogravity = true, noblockcollision = true, ishot = true, disabledespawn = false, firespeed = 4, -- The speed of the fireball in pixel/frame bouncetype = 0 -- The different flame's AIs when colliding with a wall: 0: Bounce, 1: Destroy, 2: Pass-through }) -- A table of block IDs and the "melting" behavior local iceBlock = {} -- Frozen coin block turns into a SMB3 coin iceBlock[620] = function(b, n) NPC.spawn(10, b.x, b.y, n.section) redstone.spawnEffect(10, b) b:remove() end -- Frozen muncher block turns into a muncher iceBlock[621] = function(b, n) b.id = 109 redstone.spawnEffect(10, b) end -- Ice block melts iceBlock[633] = function(b, n) b:remove() redstone.spawnEffect(10, b) end -- Large ice blocks takes two flame hits to melt iceBlock[634] = function(b, n) if b.data.flame_metling then b:remove() redstone.spawnEffect(10, b) else b.data.flame_metling = true end end -- List of ice blocks to scan local iceBlock_MAP = unmap(iceBlock) flame.iceBlock, flame.iceBlock_MAP = iceBlock, iceBlock_MAP local TYPE_BOUNCE = 0 local TYPE_HIT = 1 local TYPE_NOCOLL = 2 function flame.prime(n) local data = n.data data.animFrame = data.animFrame or 0 data.animTimer = data.animTimer or 0 data.frameX = data.frameX or 0 data.frameY = data.frameY or 0 data.angle = data.angle or data._settings.angle or 0 data.vector = vector.v2(flame.config.firespeed, 0):rotate(-data.angle) data.disabledespawn = true data.redhitbox = Colliders.Circle(0, 0, 0.5*n.width + 6) end -- Determines if a block is bouncable local blockfilter = function(n, d) return function(v) if v.isHidden or Block.NONSOLID_MAP[v.id] then return false elseif Block.SEMISOLID_MAP[v.id] then local m = vector(n.x + 0.5*n.width, n.y + 0.5*n.height) return d.y > 0 and Colliders.raycast(m, d, Colliders.Box(v.x, v.y, v.width, 2)) end return true end end -- Determines if an NPC is bouncable local npcfilter = function(n, d) return function(v) local c = NPC.config[v.id] if n.data.immuneparent and v == n.data.parent then return false elseif c.blocknpc or c.playerblock or c.npcblock then return true elseif c.npcblocktop or c.playerblocktop then local m = vector(n.x + 0.5*n.width, n.y + 0.5*n.height) return d.y > 0 and Colliders.raycast(m, 10*d:normalize(), Colliders.Box(v.x, v.y, v.width, 2)) end end end -- Does the actual bounce calculation local function doBounce(n) local cx, cy = n.x + 0.5*n.width, n.y + 0.5*n.height local data = n.data local iscollision, point, normal, crashcollider local blockList = Colliders.getColliding{a = data.redhitbox, b = Block.ALL, btype = Colliders.BLOCK, filter = blockfilter(n, data.vector)} local npcList = Colliders.getColliding{a = data.redhitbox, b = NPC.ALL, btype = Colliders.NPC, filter = npcfilter(n, data.vector)} local collList = append(blockList, npcList) if collList[1] then local startpoint = vector.v2(cx, cy) local rayLen = vector.v2(0.5*n.width + 6, 0):rotate(-data.angle) iscollision, point, normal, crashcollider = Colliders.raycast(startpoint, rayLen, collList) end if iscollision then if flame.config.bouncetype == TYPE_BOUNCE then data.vector = -2*(data.vector .. normal)*normal + data.vector data.angle = deg(atan2(-data.vector.y, data.vector.x)) elseif flame.config.bouncetype == TYPE_HIT then local e = Effect.spawn(10, n.x + 0.5*n.width, n.y + 0.5*n.height) e.x, e.y = e.x - e.width*0.5, e.y - e.height*0.5 n:kill() end end end function flame.onRedTick(n) local data = n.data -- Get center of NPC local cx, cy = n.x + 0.5*n.width, n.y + 0.5*n.height if data.angle > 360 or data.angle < 0 then data.angle = data.angle%360 end -- Create vector speed and update collider box data.vector = vector(flame.config.firespeed, 0):rotate(-data.angle) data.redhitbox.x, data.redhitbox.y = cx, cy -- Melt any ice blocks that are in the way local iceList = Colliders.getColliding{a = data.redhitbox, b = iceBlock_MAP, btype = Colliders.BLOCK, filter = redstone.nothidden} if iceList[1] then local b = iceList[1] -- Get the first block in the list iceBlock[b.id](b, n) -- Do the given action of the block n:kill() end -- If set to bounce or hit, then do the nessesary calculations if flame.config.bouncetype ~= TYPE_NOCOLL then -- Prevent the flame from colliding with the flamethrower it spawned on if data.immuneparent and data.parent.isValid and not Colliders.collide(n, data.parent) then data.immuneparent = false end doBounce(n) end -- Apply speed n.speedX = data.vector.x n.speedY = data.vector.y end flame.onRedDraw = redstone.drawNPC redstone.register(flame) return flame