-- v1.3.0 local reaper = {} local redstone = require("redstone") local npcManager = require("npcManager") local insert, remove = table.insert, table.remove reaper.name = "reaper" reaper.id = NPC_ID reaper.order = 0.46 reaper.onRedPower = function(n, c, power, dir, hitbox) if redstone.is.operator(c.id) and redstone.component.operator.operations[reaper.id] and dir == (n.data.frameX + 2)%4 then n.data.isOn = true redstone.setEnergy(n, power) else return true end end reaper.config = npcManager.setNpcSettings({ id = reaper.id, width = 32, height = 32, gfxwidth = 32, gfxheight = 32, gfxoffsetx = 0, gfxoffsety = 0, frames = 1, framespeed = 8, framestyle = 0, invisible = false, mute = false, nogravity = true, notcointransformable = true, jumphurt = true, noblockcollision = true, nohurt = true, noyoshi = true, blocknpc = true, playerblock = true, playerblocktop = true, npcblock = true, disabledespawn = false, soulspeed = 6, -- The speed at which the souls travel at disablesoulanim = false, -- Set to true to disable the soul effect animation }) local isoul = Graphics.loadImage(Misc.resolveFile("npc-"..reaper.id.."-1.png")) local sfxeat = Audio.SfxOpen(Misc.resolveFile("reaperblock-consume.ogg")) local sfxeatfast = Audio.SfxOpen(Misc.resolveFile("reaperblock-consumefast.ogg")) local ribbontail = Misc.resolveFile("npc-"..reaper.id.."-ribbon.ini") local ribbonlist = {} local function luafy(msg) return "return function(object) local npc = object "..msg.." end" end local function hasSoul(soul, reaper) local data = reaper.data local whitelistpass = true local filterpass = redstone.luaCall(data.filter, soul) local hiddenpass = not soul.isHidden local datapass = not (NPC.config[soul.id].hasnosoul or soul.data.hasNoSoul) if #table.unmap(data.whitelist) ~= 0 then whitelistpass = data.whitelist[soul.id] end if whitelistpass and filterpass and hiddenpass and datapass then return true end end local function canKill(soul, reaper) local data = reaper.data local hiddenpass = not soul.isHidden local datapass = not (NPC.config[soul.id].hasnosoul or soul.data.hasNoSoul) if hiddenpass and datapass then return true end end local function exposeSoul(n, soulNPC, dist) local data = n.data if reaper.config.disablesoulanim then data.queque = data.queque + 1 else local soul = {x = soulNPC.x + soulNPC.width*0.5, y = soulNPC.y + soulNPC.height*0.5, ribbon = Particles.Ribbon(0, 0, ribbontail)} soul.ribbon.x, soul.ribbon.y = soul.x, soul.y soul.ribbon:Emit(1) insert(data.souls, soul) end if redstone.onScreenSound(n) then if dist^2 < 160 then SFX.play(sfxeatfast) else SFX.play(sfxeat) end end end local function collisionBox(n, d) if d == 0 then return n.x - 2, n.y + 2, 2, n.height - 4 elseif d == 1 then return n.x + 2, n.y - 2, n.width - 4, 2 elseif d == 2 then return n.x + n.width, n.y + 2, 2, n.height - 4 else return n.x + 2, n.y + n.height, n.width - 4, 2 end end local function scanNPC(n) local x, y, w, h = collisionBox(n, n.data.frameX) for _, v in ipairs(NPC.getIntersecting(x, y, x + w, y + h)) do if v.isValid and not v.isHidden and canKill(v, n) then redstone.spawnEffect(10, v) v:kill() end end end local function updateSouls(n) local data = n.data local cx, cy = n.x + 0.5*n.width, n.y + 0.5*n.height for i = #data.souls, 1, -1 do local soul = data.souls[i] local v = vector.v2(cx - soul.x, cy - soul.y) if v.length < reaper.config.soulspeed then data.queque = data.queque + 1 soul.ribbon.enabled = false soul.ribbon:Break() insert(ribbonlist, soul.ribbon) remove(data.souls, i) else v = v:normalize()*reaper.config.soulspeed soul.x, soul.y = soul.x + v.x, soul.y + v.y soul.ribbon.x, soul.ribbon.y = soul.x, soul.y end end end local function passPower(n, power) local data = n.data redstone.updateDirectionalRedHitBox(n, (data.frameX + 2)%4) redstone.passDirectionEnergy{source = n, power = power, hitbox = data.redhitbox} end function reaper.prime(n) local data = n.data data.animFrame = data.animFrame or 0 data.animTimer = data.animTimer or 0 data.frameX = data._settings.dir or 0 data.frameY = data.frameY or 0 data.isOn = data.isOn or false data.prevState = data.prevState or false data.onTimer = data.onTimer or 0 data.cooldown = data.cooldown or 0 data.souls = data.souls or {} data.queque = data.queque or 0 data.whitelist = data.whitelist or redstone.parseListMAP(data._settings.whitelist) data.filter = redstone.luaParse("REAPERBLOCK", n, luafy(data._settings.filter or "return true")) data.redhitbox = redstone.basicDirectionalRedHitBox(n, (data.frameX + 2)%4) end function reaper.onRedLoad() for id = 1, NPC_MAX_ID do if NPC.config[id].hasnosoul == nil then NPC.config[id]:setDefaultProperty("hasnosoul", not NPC.HITTABLE_MAP[id]) end end end function reaper.onRedTick(n) local data = n.data data.observ = false if #data.souls > 0 then updateSouls(n) end if data.power > 0 or n.data.isOn then passPower(n, data.power) elseif data.queque > 0 then data.isOn = true if data.cooldown == 0 then data.onTimer = data.onTimer + 1 if data.onTimer > 5 then data.onTimer = 0 data.cooldown = 5 data.queque = data.queque - 1 end else data.isOn = false data.cooldown = data.cooldown - 1 end if data.isOn then passPower(n, 15) end end scanNPC(n) if not data.isOn and data.prevState then data.observ = true end if data.isOn or #data.souls > 0 then data.frameY = 1 else data.frameY = 0 end data.prevState = data.isOn data.isOn = false redstone.resetPower(n) end function reaper.onRedDraw(n) redstone.drawNPC(n) if reaper.config.invisible then return end if #n.data.souls > 0 then local hw, hh = isoul.width*0.5, isoul.height*0.5 for _, s in ipairs(n.data.souls) do Graphics.drawImageToScene(isoul, s.x - hw, s.y - hh) s.ribbon:Draw(-60) end end end function reaper.onDraw() if reaper.config.invisible then return end -- Draw all the dead ribbons for k = #ribbonlist, 1, -1 do local p = ribbonlist[k] if p:Count() > 0 then p:Draw(-60) else remove(ribbonlist, k) end end end function reaper.onPostNPCKill(soulNPC, reason) local list = Colliders.getColliding{a = Colliders.Box(soulNPC.x - 800, soulNPC.y - 600, 1600 + soulNPC.width, 1200 + soulNPC.height), b = reaper.id, btype = Colliders.NPC, filter = redstone.nofilter} if list then local closest = -1 local reaperNPC for _, v in ipairs(list) do local dist = (v.x + 0.5*v.width - soulNPC.x - 0.5*soulNPC.width)^2 + (v.y + 0.5*v.height - soulNPC.y - 0.5*soulNPC.height)^2 if (closest == -1 or dist < closest) and hasSoul(soulNPC, v) then closest = dist reaperNPC = v end end if reaperNPC then exposeSoul(reaperNPC, soulNPC, closest) end end end function reaper.onInitAPI() registerEvent(reaper, "onDraw", "onDraw") registerEvent(reaper, "onPostNPCKill", "onPostNPCKill") end redstone.register(reaper) return reaper