Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ----------------------------------------------------------------
- --[[ extendedRinkas.lua v1.0 by KBM-Quine ]]--
- --[[ with code help/advice from: Enjl ]]--
- --[[ Based on moarrinkas by aristocrat (and Enjl's rewrite) ]]--
- --[[ and chucks.lua by snoruntpyro ]]--
- ----------------------------------------------------------------
- local npcManager = require("npcManager")
- local npcutils = require("npcs/npcutils")
- local extendedRinkas = {
- extendedBlockDrawing = true,
- rinkaIDList = {},
- rinkaInfo = {},
- blockIDList = {},
- blockInfo = {}
- }
- local rinkaBlockSpawnedNPCs = {}
- local rinkaBlockSpawnedNPCsCount = 0
- local sfx_shoot = (Misc.resolveSoundFile("rinkygun-shoot"));
- local gfx_block = Graphics.loadImageResolved("rinka_spawner.png")
- function extendedRinkas.registerRinkaBlock(npcID, args)
- npcManager.registerEvent(npcID,extendedRinkas,"onTickEndNPC","onTickEndBlock")
- npcManager.registerEvent(npcID,extendedRinkas,"onDrawNPC","onDrawBlock")
- extendedRinkas.blockIDList[npcID] = true
- extendedRinkas.blockInfo[npcID] = args or {}
- end
- function extendedRinkas.registerRinka(npcID, args)
- npcManager.registerEvent(npcID,extendedRinkas,"onTickEndNPC","onTickEndRinka")
- extendedRinkas.rinkaIDList[npcID] = true
- extendedRinkas.rinkaInfo[npcID] = args or {}
- end
- -- auto add the vanilla things
- extendedRinkas.registerRinka(210, {isVanilla = true, noDeathFunction = true})
- extendedRinkas.registerRinkaBlock(211, {
- isVanilla = true,
- deathFunction = function(v) -- because it has a death noise but no effect apparently
- Effect.spawn(108,v.x+(v.width/2),v.y+(v.height/2))
- end
- })
- function extendedRinkas.onInitAPI() -- looked messy when there was one onKill, so more it is.
- registerEvent(extendedRinkas,"onTickEnd", "onHeldNPCTickEnd")
- registerEvent(extendedRinkas,"onDraw","onDrawHeldNPC")
- registerEvent(extendedRinkas,"onNPCKill", "onRinkaKill")
- registerEvent(extendedRinkas,"onNPCKill", "onBlockKill")
- end
- function extendedRinkas.standardDeathFunction(v)
- Effect.spawn((NPC.config[v.id].deatheffect or 108),v.x+(v.width/2),v.y+(v.height/2))
- SFX.play(65)
- end
- function extendedRinkas.onBlockKill(eventObj,v,reason)
- if not extendedRinkas.blockInfo[v.id] or extendedRinkas.blockInfo[v.id].noDeathFunction or (reason == HARM_TYPE_OFFSCREEN) then return end
- local deathFunc = extendedRinkas.blockInfo[v.id].deathFunction or extendedRinkas.standardDeathFunction
- deathFunc(v)
- end
- function extendedRinkas.onRinkaKill(eventObj,v,reason)
- if not extendedRinkas.rinkaIDList[v.id] or extendedRinkas.rinkaInfo[v.id].noDeathFunction or (reason == HARM_TYPE_OFFSCREEN) then return end
- local deathFunc = extendedRinkas.rinkaInfo[v.id].deathFunction or extendedRinkas.standardDeathFunction
- deathFunc(v)
- end
- function extendedRinkas.rinkaSourceHoming(v, speed) -- taken from https://github.com/smbx/smbx-legacy-source/blob/master/modNPC.bas#L9823
- local localeX = 0
- local localeY = 0
- local localeSqrd = 0
- local targetPlayer
- for i=1,Player.count() do
- if not Player(i):mem(0x13C, FIELD_BOOL) and Player(i).section == v.section then
- if localeX == 0 or math.abs(v.x + v.width*0.5 - (Player(i).x + Player(i).width*0.5)) < localeX then
- localeX = math.abs(v.x + v.width*0.5 - (Player(i).x + Player(i).width*0.5))
- targetPlayer = Player(i)
- end
- end
- end
- if targetPlayer == nil then return end -- fixes an error upon level ending
- localeX = (v.x + v.width*0.5) - (targetPlayer.x + targetPlayer.width*0.5)
- localeY = (v.y + v.height*0.5) - (targetPlayer.y + targetPlayer.height*0.5)
- localeSqrd = math.sqrt(localeX^2 + localeY^2)
- localeX = -localeX / localeSqrd
- localeY = -localeY / localeSqrd
- v.speedX = localeX * speed
- v.speedY = localeY * speed
- end
- function extendedRinkas.rinkaAdvancedHoming(v, speed, angleOffset)
- local targetPlayer = npcutils.getNearestPlayer(v)
- local toPlayerVect = vector(targetPlayer.x+0.5*targetPlayer.width - v.x, targetPlayer.y+targetPlayer.height*0.5 - v.y)
- local speedVect = toPlayerVect:normalise() * speed
- local rotatedVect = speedVect:rotate(angleOffset)
- v.speedX = rotatedVect.x
- v.speedY = rotatedVect.y
- end
- function extendedRinkas.defaultBlockSpawning(v) -- taken from https://github.com/smbx/smbx-legacy-source/blob/master/modNPC.bas#L9823
- local s = NPC.spawn(v.data._settings.projectile, v.x + (v.width*0.5), v.y + (v.height*0.5), v.section, false, true)
- npcutils.faceNearestPlayer(s)
- s.friendly = v.friendly
- s.layerName = "Spawned NPCs"
- s.data.speed = v.data._settings.projectileSpeed
- if v.data._settings.npcHoldTimerMax ~= 80 and extendedRinkas.rinkaIDList[s.id] then
- s.ai2 = (80 - v.data._settings.npcHoldTimerMax)
- end
- if v.data._settings.noWaiting and extendedRinkas.rinkaIDList[s.id] then
- s.ai2 = 80 + 30
- end
- if not extendedRinkas.rinkaIDList[s.id] then
- s:mem(0x138, FIELD_WORD, 4)
- s:mem(0x156, FIELD_WORD, 10)
- s.data.npcHoldTimerMax = v.data._settings.npcHoldTimerMax
- s.data.noWaiting = v.data._settings.noWaiting
- s.data.parent = v
- s.data.state = 0
- s.data.holdTimer = 0
- s.data.notColliding = false
- if v.data._settings.npcHoldTimerMax ~= 80 then
- s.data.holdTimer = (80 - v.data._settings.npcHoldTimerMax)
- end
- if v.data._settings.noWaiting then
- s.data.holdTimer = 80 + 30
- end
- rinkaBlockSpawnedNPCsCount = rinkaBlockSpawnedNPCsCount + 1
- table.insert(rinkaBlockSpawnedNPCs, rinkaBlockSpawnedNPCsCount, s)
- s.data.rinkaBlockSpawnedNPCsID = rinkaBlockSpawnedNPCsCount
- end
- return s
- end
- function extendedRinkas.drawNPC(npcobject, args) -- lifted from npcutils, solely for making heldNPCs render over rinka blocks
- args = args or {}
- if npcobject.__type ~= "NPC" then
- error("Must pass a NPC object to draw. Example: drawNPC(myNPC)")
- end
- local spawnedbygenerator = {
- [1] = true,
- [3] = true,
- [4] = true,
- }
- local frame = args.frame or npcobject.animationFrame
- local afs = args.applyFrameStyle
- if afs == nil then afs = true end
- local cfg = NPC.config[npcobject.id]
- --gfxwidth/gfxheight can be unreliable
- local trueWidth = cfg.gfxwidth
- if trueWidth == 0 then trueWidth = npcobject.width end
- local trueHeight = cfg.gfxheight
- if trueHeight == 0 then trueHeight = npcobject.height end
- --drawing position isn't always exactly hitbox position
- local x = args.x or npcobject.x + 0.5 * npcobject.width - 0.5 * trueWidth + cfg.gfxoffsetx + (args.xOffset or 0)
- local y = args.x or npcobject.y + npcobject.height - trueHeight + cfg.gfxoffsety + (args.yOffset or 0)
- --cutting off our sprite might be nice for piranha plants and the likes
- local w = args.width or trueWidth
- local h = args.height or trueHeight
- local o = args.opacity or 1
- --the bane of the checklist's existence
- local po = args.priorityOverride
- if po == nil then po = true end
- local p = args.priority or -45
- if po == true then -- literally just needed to tweak this part so my hack would work
- if cfg.foreground then
- p = -15
- end
- if spawnedbygenerator[npcobject:mem(0x138, FIELD_WORD)] then
- p = -75
- end
- end
- local sourceX = args.sourceX or 0
- local sourceY = args.sourceY or 0
- --framestyle is a weird thing...
- local frames = args.frames or cfg.frames
- local f = frame or 0
- --but only if we actually pass a custom frame...
- if args.frame and afs and cfg.framestyle > 0 then
- if cfg.framestyle == 2 then
- if npcobject:mem(0x12C, FIELD_WORD) > 0 or npcobject:mem(0x132, FIELD_WORD) > 0 then
- f = f + 2 * frames
- end
- end
- if npcobject.direction == 1 then
- f = f + frames
- end
- end
- Graphics.drawImageToSceneWP(args.texture or Graphics.sprites.npc[npcobject.id].img, x, y, sourceX, sourceY + trueHeight * f, w, h, o, p)
- end
- -- frame junk for blocks with the rinka below it (used as a reference while deving, not entirely relevent) https://github.com/smbx/smbx-legacy-source/blob/master/modNPC.bas#L7507
- function extendedRinkas.animateNPC(v) -- not perfectly accurate but does it's job
- v.data.animationTimer = v.data.animationTimer + 1
- if v.data.animationTimer%NPC.config[v.id].framespeed == 0 then
- v.data.frame = v.data.frame + (1*v.data.animationDirection)
- if v.data.frame == (NPC.config[v.id].frames - 1) then
- v.data.animationDirection = -1
- end
- if v.data.frame == 0 then
- v.data.animationDirection = 1
- end
- end
- v.animationFrame = npcutils.getFrameByFramestyle(v, {frame=v.data.frame})
- end
- function extendedRinkas.onTickEndRinka(v)
- if Defines.levelFreeze then return end
- if v:mem(0x138, FIELD_WORD) > 0 then return end
- local data = v.data
- if not extendedRinkas.rinkaInfo[v.id].isVanilla then
- if not data.initialized then
- if extendedRinkas.rinkaInfo[v.id].initFunction ~= nil then
- extendedRinkas.rinkaInfo[v.id].initFunction(v)
- end
- data.animationTimer = 0
- data.animationDirection = 1
- data.frame = 0
- data.initialized = true
- end
- if v.despawnTimer <= 0 then
- --Reset our properties, if necessary
- data.animationTimer = 0
- data.animationDirection = 1
- data.frame = 0
- data.initialized = false
- return
- end
- data.animationTimer = data.animationTimer + 1
- if not v:mem(0x136, FIELD_BOOL) and v:mem(0x12C, FIELD_WORD) == 0 then --if not a projectile or being held...
- if v.ai1 == 0 then
- v.ai2 = v.ai2 + 1
- if v.ai2 >= (80 + RNG.randomInt(0,20)) then
- v.ai1 = 1
- extendedRinkas.rinkaSourceHoming(v, data.speed or NPC.config[v.id].speed)
- end
- end
- if extendedRinkas.rinkaInfo[v.id].tickFunction ~= nil then
- extendedRinkas.rinkaInfo[v.id].tickFunction(v)
- end
- end
- end
- if extendedRinkas.rinkaInfo[v.id].isVanilla and v.ai1 == 1 and not data.fired and data.speed ~= nil then -- allow vanilla rinkas to inherit the speed from the block
- data.fired = true
- extendedRinkas.rinkaSourceHoming(v, data.speed or NPC.config[v.id].speed)
- end
- if not Defines.levelFreeze and not extendedRinkas.rinkaInfo[v.id].isVanilla then
- extendedRinkas.animateNPC(v)
- end
- end
- function extendedRinkas.onTickEndBlock(v) -- based around https://github.com/smbx/smbx-legacy-source/blob/master/modNPC.bas#L9848
- if v:mem(0x136, FIELD_BOOL) then -- keep the 1.3 thing from happening where projectile'd/thrown blocks kill their generated npcs
- v.speedX = 0
- v.speedY = 0
- v:mem(0x136, FIELD_BOOL, false)
- end
- if v:mem(0x12E, FIELD_WORD) > 0 and v:mem(0x12C, FIELD_WORD) == 0 then -- basically The Suck type mechanics
- v:kill(HARM_TYPE_HELD)
- SFX.play(65)
- end
- local settings = v.data._settings
- local data = v.data
- if not data.initialized then
- if extendedRinkas.blockInfo[v.id].initFunction ~= nil then
- extendedRinkas.blockInfo[v.id].initFunction(v)
- end
- settings.generationTimerMax = settings.generationTimerMax or 200 -- shoooould handle being spawned in... maybe
- settings.noWaiting = settings.noWaiting or false
- settings.projectile = settings.projectile or 210
- settings.npcHoldTimerMax = settings.npcHoldTimerMax or 80
- settings.projectileSpeed = settings.projectileSpeed or 3
- data.animationDirection = 1
- data.animationTimer = 0
- data.frame = 0
- data.initialized = true
- end
- if v.despawnTimer <= 0 then
- data.initialized = false
- return
- end
- if Defines.levelFreeze and v:mem(0x12C, FIELD_WORD) == 0 then return end
- if v:mem(0x12C, FIELD_WORD) == 0 then -- if not held, do stuff...
- if extendedRinkas.blockInfo[v.id].isVanilla then -- clamp the timer for the vanilla rinka block so we can override behaviour
- v.ai1 = 0
- end
- v.ai2 = v.ai2 + 1 + RNG.random()
- if v.ai2 >= settings.generationTimerMax + RNG.random() * settings.generationTimerMax then
- v.ai2 = 0
- if (settings.projectile ~= 0) then
- extendedRinkas.defaultBlockSpawning(v)
- end
- end
- if extendedRinkas.blockInfo[v.id].tickFunction ~= nil then
- extendedRinkas.blockInfo[v.id].tickFunction(v)
- end
- else -- other wise, act like a rinky gun
- if v.ai2 >= 60 then -- was going to make configuable but became a headache
- SFX.play(sfx_shoot)
- local rink = NPC.spawn(667, v.x + v.width/2, v.y + v.height/2, v:mem(0x146, FIELD_WORD), false, true)
- local data = rink.data._basegame
- rink.data._basegame.triggered = false
- rink.layerName = "Spawned NPCs"
- rink.data._basegame.thrownBy = v:mem(0x12E, FIELD_WORD)
- rink.direction = v.direction
- v.ai2 = 0
- else
- v.ai2 = v.ai2 + RNG.random(1, 2)
- end
- end
- if not Defines.levelFreeze and not extendedRinkas.blockInfo[v.id].isVanilla then -- stops blocks from moving/animating when the levels' frozen or it's a vanilla block
- extendedRinkas.animateNPC(v)
- npcutils.applyLayerMovement(v)
- end
- end
- function extendedRinkas.onHeldNPCTickEnd() -- for rinka-like behavior in default npcs
- if #rinkaBlockSpawnedNPCs == 0 then return end
- for k,v in pairs(rinkaBlockSpawnedNPCs) do
- if v.isValid then
- local data = v.data
- data.holdTimer = data.holdTimer + 1
- if data.state == 0 then
- v:mem(0x138, FIELD_WORD, 4)
- v:mem(0x156, FIELD_WORD, 10)
- v.despawnTimer = 50
- end
- if (data.holdTimer >= data.npcHoldTimerMax + RNG.random() * 20 and data.state == 0) or data.noWaiting then
- v:mem(0x138, FIELD_WORD, 0)
- v:mem(0x156, FIELD_WORD, 10)
- extendedRinkas.rinkaSourceHoming(v, data.speed)
- data.holdTimer = 0
- data.state = 1
- end
- if data.parent and Colliders.collide(data.parent, v) then
- v.noblockcollision = true
- data.notColliding = false
- else
- v.noblockcollision = false
- data.notColliding = true
- end
- if data.state == 1 and data.notColliding then
- table.remove(rinkaBlockSpawnedNPCs, k)
- rinkaBlockSpawnedNPCsCount = rinkaBlockSpawnedNPCsCount - 1
- data.rinkaBlockSpawnedNPCsID = nil
- data.parent = nil
- data.holdTimer = nil
- data.npcHoldTimerMax = nil
- data.noWaiting = nil
- data.state = nil
- data.notColliding = nil
- end
- else
- table.remove(rinkaBlockSpawnedNPCs, k)
- rinkaBlockSpawnedNPCsCount = rinkaBlockSpawnedNPCsCount - 1
- end
- end
- end
- function extendedRinkas.onDrawBlock(v)
- if v.despawnTimer <= 0 then return end
- if extendedRinkas.extendedBlockDrawing and v.data._settings.horizontalFrame ~= 0 then -- a carry over from moarrinkas
- local trueWidth = NPC.config[v.id].gfxwidth
- if trueWidth == 0 then trueWidth = v.width end
- npcutils.drawNPC(v, {texture = gfx_block,sourceX = 0 + trueWidth * (v.data._settings.horizontalFrame or 0)})
- npcutils.hideNPC(v)
- end
- end
- function extendedRinkas.onDrawHeldNPC()
- if #rinkaBlockSpawnedNPCs == 0 then return end
- for k,v in ipairs(rinkaBlockSpawnedNPCs) do
- if v.isValid then -- solely used to insure that held npcs render above the block like rinkas do
- extendedRinkas.drawNPC(v, {priorityOverride = false, priority = -15})
- npcutils.hideNPC(v)
- end
- end
- end
- return extendedRinkas
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement