Advertisement
KBM-Quine

extendedRinkas.lua prerelease 1

Mar 14th, 2022
1,142
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.46 KB | None | 0 0
  1. ----------------------------------------------------------------
  2. --[[         extendedRinkas.lua v1.0 by KBM-Quine           ]]--
  3. --[[            with code help/advice from: Enjl            ]]--
  4. --[[ Based on moarrinkas by aristocrat (and Enjl's rewrite) ]]--
  5. --[[              and chucks.lua by snoruntpyro             ]]--
  6. ----------------------------------------------------------------
  7. local npcManager = require("npcManager")
  8. local npcutils = require("npcs/npcutils")
  9.  
  10. local extendedRinkas = {
  11.     extendedBlockDrawing = true,
  12.     rinkaIDList = {},
  13.     rinkaInfo = {},
  14.     blockIDList = {},
  15.     blockInfo = {}
  16. }
  17.  
  18. local rinkaBlockSpawnedNPCs = {}
  19. local rinkaBlockSpawnedNPCsCount = 0
  20.  
  21. local sfx_shoot = (Misc.resolveSoundFile("rinkygun-shoot"));
  22. local gfx_block = Graphics.loadImageResolved("rinka_spawner.png")
  23.  
  24. function extendedRinkas.registerRinkaBlock(npcID, args)
  25.     npcManager.registerEvent(npcID,extendedRinkas,"onTickEndNPC","onTickEndBlock")
  26.     npcManager.registerEvent(npcID,extendedRinkas,"onDrawNPC","onDrawBlock")
  27.     extendedRinkas.blockIDList[npcID] = true
  28.     extendedRinkas.blockInfo[npcID] = args or {}
  29. end
  30.  
  31. function extendedRinkas.registerRinka(npcID, args)
  32.     npcManager.registerEvent(npcID,extendedRinkas,"onTickEndNPC","onTickEndRinka")
  33.     extendedRinkas.rinkaIDList[npcID] = true
  34.     extendedRinkas.rinkaInfo[npcID] = args or {}
  35. end
  36.  
  37. -- auto add the vanilla things
  38. extendedRinkas.registerRinka(210, {isVanilla = true, noDeathFunction = true})
  39. extendedRinkas.registerRinkaBlock(211, {
  40.     isVanilla = true,
  41.     deathFunction = function(v) -- because it has a death noise but no effect apparently
  42.         Effect.spawn(108,v.x+(v.width/2),v.y+(v.height/2))
  43.     end
  44. })
  45.  
  46. function extendedRinkas.onInitAPI() -- looked messy when there was one onKill, so more it is.
  47.     registerEvent(extendedRinkas,"onTickEnd", "onHeldNPCTickEnd")
  48.     registerEvent(extendedRinkas,"onDraw","onDrawHeldNPC")
  49.     registerEvent(extendedRinkas,"onNPCKill", "onRinkaKill")
  50.     registerEvent(extendedRinkas,"onNPCKill", "onBlockKill")
  51. end
  52.  
  53. function extendedRinkas.standardDeathFunction(v)
  54.     Effect.spawn((NPC.config[v.id].deatheffect or 108),v.x+(v.width/2),v.y+(v.height/2))
  55.     SFX.play(65)
  56. end
  57.  
  58. function extendedRinkas.onBlockKill(eventObj,v,reason)
  59.     if not extendedRinkas.blockInfo[v.id] or extendedRinkas.blockInfo[v.id].noDeathFunction or (reason == HARM_TYPE_OFFSCREEN) then return end
  60.  
  61.     local deathFunc = extendedRinkas.blockInfo[v.id].deathFunction or extendedRinkas.standardDeathFunction
  62.     deathFunc(v)
  63. end
  64.  
  65. function extendedRinkas.onRinkaKill(eventObj,v,reason)
  66.     if not extendedRinkas.rinkaIDList[v.id] or extendedRinkas.rinkaInfo[v.id].noDeathFunction or (reason == HARM_TYPE_OFFSCREEN) then return end
  67.  
  68.     local deathFunc = extendedRinkas.rinkaInfo[v.id].deathFunction or extendedRinkas.standardDeathFunction
  69.     deathFunc(v)
  70. end
  71.  
  72. function extendedRinkas.rinkaSourceHoming(v, speed) -- taken from https://github.com/smbx/smbx-legacy-source/blob/master/modNPC.bas#L9823
  73.     local localeX = 0
  74.     local localeY = 0
  75.     local localeSqrd = 0
  76.     local targetPlayer
  77.     for  i=1,Player.count() do
  78.         if not Player(i):mem(0x13C, FIELD_BOOL) and Player(i).section == v.section then
  79.             if localeX == 0 or math.abs(v.x + v.width*0.5 - (Player(i).x + Player(i).width*0.5)) < localeX then
  80.                 localeX = math.abs(v.x + v.width*0.5 - (Player(i).x + Player(i).width*0.5))
  81.                 targetPlayer = Player(i)
  82.             end
  83.         end
  84.     end
  85.     if targetPlayer == nil then return end -- fixes an error upon level ending
  86.     localeX = (v.x + v.width*0.5) - (targetPlayer.x + targetPlayer.width*0.5)
  87.     localeY = (v.y + v.height*0.5) - (targetPlayer.y + targetPlayer.height*0.5)
  88.     localeSqrd = math.sqrt(localeX^2 + localeY^2)
  89.     localeX = -localeX / localeSqrd
  90.     localeY = -localeY / localeSqrd
  91.     v.speedX = localeX * speed
  92.     v.speedY = localeY * speed
  93. end
  94.  
  95. function extendedRinkas.rinkaAdvancedHoming(v, speed, angleOffset)
  96.     local targetPlayer = npcutils.getNearestPlayer(v)
  97.     local toPlayerVect = vector(targetPlayer.x+0.5*targetPlayer.width - v.x, targetPlayer.y+targetPlayer.height*0.5 - v.y)
  98.     local speedVect = toPlayerVect:normalise() * speed
  99.     local rotatedVect = speedVect:rotate(angleOffset)
  100.     v.speedX = rotatedVect.x
  101.     v.speedY = rotatedVect.y
  102. end
  103.  
  104. function extendedRinkas.defaultBlockSpawning(v) -- taken from https://github.com/smbx/smbx-legacy-source/blob/master/modNPC.bas#L9823
  105.     local s = NPC.spawn(v.data._settings.projectile, v.x + (v.width*0.5), v.y + (v.height*0.5), v.section, false, true)
  106.     npcutils.faceNearestPlayer(s)
  107.     s.friendly = v.friendly
  108.     s.layerName = "Spawned NPCs"
  109.     s.data.speed = v.data._settings.projectileSpeed
  110.     if v.data._settings.npcHoldTimerMax ~= 80 and extendedRinkas.rinkaIDList[s.id] then
  111.         s.ai2 = (80 - v.data._settings.npcHoldTimerMax)
  112.     end
  113.     if v.data._settings.noWaiting and extendedRinkas.rinkaIDList[s.id] then
  114.         s.ai2 = 80 + 30
  115.     end
  116.     if not extendedRinkas.rinkaIDList[s.id] then
  117.         s:mem(0x138, FIELD_WORD, 4)
  118.         s:mem(0x156, FIELD_WORD, 10)
  119.         s.data.npcHoldTimerMax = v.data._settings.npcHoldTimerMax
  120.         s.data.noWaiting = v.data._settings.noWaiting
  121.         s.data.parent = v
  122.         s.data.state = 0
  123.         s.data.holdTimer = 0
  124.         s.data.notColliding = false
  125.         if v.data._settings.npcHoldTimerMax ~= 80 then
  126.             s.data.holdTimer = (80 - v.data._settings.npcHoldTimerMax)
  127.         end
  128.         if v.data._settings.noWaiting then
  129.             s.data.holdTimer = 80 + 30
  130.         end
  131.         rinkaBlockSpawnedNPCsCount = rinkaBlockSpawnedNPCsCount + 1
  132.         table.insert(rinkaBlockSpawnedNPCs, rinkaBlockSpawnedNPCsCount, s)
  133.         s.data.rinkaBlockSpawnedNPCsID = rinkaBlockSpawnedNPCsCount
  134.     end
  135.     return s
  136. end
  137.  
  138. function extendedRinkas.drawNPC(npcobject, args) -- lifted from npcutils, solely for making heldNPCs render over rinka blocks
  139.     args = args or {}
  140.     if npcobject.__type ~= "NPC" then
  141.         error("Must pass a NPC object to draw. Example: drawNPC(myNPC)")
  142.     end
  143.     local spawnedbygenerator = {
  144.         [1] = true,
  145.         [3] = true,
  146.         [4] = true,
  147.     }
  148.     local frame = args.frame or npcobject.animationFrame
  149.  
  150.     local afs = args.applyFrameStyle
  151.     if afs == nil then afs = true end
  152.  
  153.     local cfg = NPC.config[npcobject.id]
  154.    
  155.     --gfxwidth/gfxheight can be unreliable
  156.     local trueWidth = cfg.gfxwidth
  157.     if trueWidth == 0 then trueWidth = npcobject.width end
  158.  
  159.     local trueHeight = cfg.gfxheight
  160.     if trueHeight == 0 then trueHeight = npcobject.height end
  161.  
  162.     --drawing position isn't always exactly hitbox position
  163.     local x = args.x or npcobject.x + 0.5 * npcobject.width - 0.5 * trueWidth + cfg.gfxoffsetx + (args.xOffset or 0)
  164.     local y = args.x or npcobject.y + npcobject.height - trueHeight + cfg.gfxoffsety + (args.yOffset or 0)
  165.  
  166.     --cutting off our sprite might be nice for piranha plants and the likes
  167.     local w = args.width or trueWidth
  168.     local h = args.height or trueHeight
  169.  
  170.     local o = args.opacity or 1
  171.  
  172.     --the bane of the checklist's existence
  173.     local po = args.priorityOverride
  174.     if po == nil then po = true end
  175.     local p = args.priority or -45
  176.     if po == true then -- literally just needed to tweak this part so my hack would work
  177.         if cfg.foreground then
  178.             p = -15
  179.         end
  180.  
  181.         if spawnedbygenerator[npcobject:mem(0x138, FIELD_WORD)] then
  182.             p = -75
  183.         end
  184.     end
  185.    
  186.  
  187.     local sourceX = args.sourceX or 0
  188.     local sourceY = args.sourceY or 0
  189.  
  190.     --framestyle is a weird thing...
  191.  
  192.     local frames = args.frames or cfg.frames
  193.     local f = frame or 0
  194.     --but only if we actually pass a custom frame...
  195.     if args.frame and afs and cfg.framestyle > 0 then
  196.         if cfg.framestyle == 2 then
  197.             if npcobject:mem(0x12C, FIELD_WORD) > 0 or npcobject:mem(0x132, FIELD_WORD) > 0 then
  198.                 f = f + 2 * frames
  199.             end
  200.         end
  201.         if npcobject.direction == 1 then
  202.             f = f + frames
  203.         end
  204.     end
  205.  
  206.     Graphics.drawImageToSceneWP(args.texture or Graphics.sprites.npc[npcobject.id].img, x, y, sourceX, sourceY + trueHeight * f, w, h, o, p)
  207. end
  208.  
  209. -- 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
  210. function extendedRinkas.animateNPC(v) -- not perfectly accurate but does it's job
  211.     v.data.animationTimer = v.data.animationTimer + 1
  212.     if v.data.animationTimer%NPC.config[v.id].framespeed == 0 then
  213.         v.data.frame = v.data.frame + (1*v.data.animationDirection)
  214.         if v.data.frame == (NPC.config[v.id].frames - 1) then
  215.             v.data.animationDirection = -1
  216.         end
  217.         if v.data.frame == 0 then
  218.             v.data.animationDirection = 1
  219.         end
  220.     end
  221.     v.animationFrame = npcutils.getFrameByFramestyle(v, {frame=v.data.frame})
  222. end
  223.  
  224. function extendedRinkas.onTickEndRinka(v)
  225.     if Defines.levelFreeze then return end
  226.     if v:mem(0x138, FIELD_WORD) > 0 then return end
  227.     local data = v.data
  228.    
  229.     if not extendedRinkas.rinkaInfo[v.id].isVanilla then
  230.         if not data.initialized then
  231.             if extendedRinkas.rinkaInfo[v.id].initFunction ~= nil then
  232.                 extendedRinkas.rinkaInfo[v.id].initFunction(v)
  233.             end
  234.             data.animationTimer = 0
  235.             data.animationDirection = 1
  236.             data.frame = 0
  237.             data.initialized = true
  238.         end
  239.  
  240.         if v.despawnTimer <= 0 then
  241.             --Reset our properties, if necessary
  242.             data.animationTimer = 0
  243.             data.animationDirection = 1
  244.             data.frame = 0
  245.             data.initialized = false
  246.             return
  247.         end
  248.  
  249.         data.animationTimer = data.animationTimer + 1
  250.        
  251.         if not v:mem(0x136, FIELD_BOOL) and v:mem(0x12C, FIELD_WORD) == 0 then --if not a projectile or being held...
  252.  
  253.             if v.ai1 == 0 then
  254.                 v.ai2 = v.ai2 + 1
  255.                 if v.ai2 >= (80 + RNG.randomInt(0,20)) then
  256.                     v.ai1 = 1
  257.                     extendedRinkas.rinkaSourceHoming(v, data.speed or NPC.config[v.id].speed)
  258.                 end
  259.             end
  260.  
  261.             if extendedRinkas.rinkaInfo[v.id].tickFunction ~= nil then
  262.                 extendedRinkas.rinkaInfo[v.id].tickFunction(v)
  263.             end
  264.         end
  265.     end
  266.    
  267.     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
  268.         data.fired = true
  269.         extendedRinkas.rinkaSourceHoming(v, data.speed or NPC.config[v.id].speed)
  270.     end
  271.  
  272.     if not Defines.levelFreeze and not extendedRinkas.rinkaInfo[v.id].isVanilla then
  273.         extendedRinkas.animateNPC(v)
  274.     end
  275. end
  276.  
  277. function extendedRinkas.onTickEndBlock(v) -- based around https://github.com/smbx/smbx-legacy-source/blob/master/modNPC.bas#L9848
  278.  
  279.     if v:mem(0x136, FIELD_BOOL) then -- keep the 1.3 thing from happening where projectile'd/thrown blocks kill their generated npcs
  280.         v.speedX = 0
  281.         v.speedY = 0
  282.         v:mem(0x136, FIELD_BOOL, false)
  283.     end
  284.  
  285.     if v:mem(0x12E, FIELD_WORD) > 0 and v:mem(0x12C, FIELD_WORD) == 0 then -- basically The Suck type mechanics
  286.         v:kill(HARM_TYPE_HELD)
  287.         SFX.play(65)
  288.     end
  289.    
  290.     local settings = v.data._settings
  291.     local data = v.data
  292.  
  293.     if not data.initialized then
  294.         if extendedRinkas.blockInfo[v.id].initFunction ~= nil then
  295.             extendedRinkas.blockInfo[v.id].initFunction(v)
  296.         end
  297.         settings.generationTimerMax = settings.generationTimerMax or 200 -- shoooould handle being spawned in... maybe
  298.         settings.noWaiting = settings.noWaiting or false
  299.         settings.projectile = settings.projectile or 210
  300.         settings.npcHoldTimerMax = settings.npcHoldTimerMax or 80
  301.         settings.projectileSpeed = settings.projectileSpeed or 3
  302.         data.animationDirection = 1
  303.         data.animationTimer = 0
  304.         data.frame = 0
  305.         data.initialized = true
  306.     end
  307.  
  308.     if v.despawnTimer <= 0 then
  309.         data.initialized = false
  310.         return
  311.     end
  312.  
  313.     if Defines.levelFreeze and v:mem(0x12C, FIELD_WORD) == 0 then return end
  314.  
  315.     if v:mem(0x12C, FIELD_WORD) == 0 then -- if not held, do stuff...
  316.  
  317.         if extendedRinkas.blockInfo[v.id].isVanilla then -- clamp the timer for the vanilla rinka block so we can override behaviour
  318.             v.ai1 = 0
  319.         end
  320.  
  321.         v.ai2 = v.ai2 + 1 + RNG.random()
  322.  
  323.         if v.ai2 >= settings.generationTimerMax + RNG.random() * settings.generationTimerMax then
  324.             v.ai2 = 0
  325.             if (settings.projectile ~= 0) then
  326.                 extendedRinkas.defaultBlockSpawning(v)
  327.             end
  328.         end
  329.  
  330.         if extendedRinkas.blockInfo[v.id].tickFunction ~= nil then
  331.             extendedRinkas.blockInfo[v.id].tickFunction(v)
  332.         end
  333.     else -- other wise, act like a rinky gun
  334.         if v.ai2 >= 60 then -- was going to make configuable but became a headache
  335.             SFX.play(sfx_shoot)
  336.             local rink = NPC.spawn(667, v.x + v.width/2, v.y + v.height/2, v:mem(0x146, FIELD_WORD), false, true)
  337.             local data = rink.data._basegame
  338.             rink.data._basegame.triggered = false
  339.             rink.layerName = "Spawned NPCs"
  340.             rink.data._basegame.thrownBy = v:mem(0x12E, FIELD_WORD)
  341.             rink.direction = v.direction
  342.             v.ai2 = 0
  343.         else
  344.             v.ai2 = v.ai2 + RNG.random(1, 2)
  345.         end
  346.     end
  347.  
  348.     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
  349.         extendedRinkas.animateNPC(v)
  350.         npcutils.applyLayerMovement(v)
  351.     end
  352.  
  353.    
  354. end
  355.  
  356. function extendedRinkas.onHeldNPCTickEnd() -- for rinka-like behavior in default npcs
  357.     if #rinkaBlockSpawnedNPCs == 0 then return end
  358.     for k,v in pairs(rinkaBlockSpawnedNPCs) do
  359.         if v.isValid then
  360.             local data = v.data
  361.             data.holdTimer = data.holdTimer + 1
  362.            
  363.             if data.state == 0 then
  364.                 v:mem(0x138, FIELD_WORD, 4)
  365.                 v:mem(0x156, FIELD_WORD, 10)
  366.                 v.despawnTimer = 50
  367.             end
  368.  
  369.             if (data.holdTimer >= data.npcHoldTimerMax + RNG.random() * 20 and data.state == 0) or data.noWaiting then
  370.                 v:mem(0x138, FIELD_WORD, 0)
  371.                 v:mem(0x156, FIELD_WORD, 10)
  372.                 extendedRinkas.rinkaSourceHoming(v, data.speed)
  373.                 data.holdTimer = 0
  374.                 data.state = 1
  375.             end
  376.  
  377.             if data.parent and Colliders.collide(data.parent, v) then
  378.                 v.noblockcollision = true
  379.                 data.notColliding = false
  380.             else
  381.                 v.noblockcollision = false
  382.                 data.notColliding = true
  383.             end
  384.  
  385.             if data.state == 1 and data.notColliding then
  386.                 table.remove(rinkaBlockSpawnedNPCs, k)
  387.                 rinkaBlockSpawnedNPCsCount = rinkaBlockSpawnedNPCsCount - 1
  388.                 data.rinkaBlockSpawnedNPCsID = nil
  389.                 data.parent = nil
  390.                 data.holdTimer = nil
  391.                 data.npcHoldTimerMax = nil
  392.                 data.noWaiting = nil
  393.                 data.state = nil
  394.                 data.notColliding = nil
  395.             end
  396.         else
  397.             table.remove(rinkaBlockSpawnedNPCs, k)
  398.             rinkaBlockSpawnedNPCsCount = rinkaBlockSpawnedNPCsCount - 1
  399.         end
  400.     end
  401. end
  402.  
  403. function extendedRinkas.onDrawBlock(v)
  404.     if v.despawnTimer <= 0 then return end
  405.  
  406.     if extendedRinkas.extendedBlockDrawing and v.data._settings.horizontalFrame ~= 0 then -- a carry over from moarrinkas
  407.         local trueWidth = NPC.config[v.id].gfxwidth
  408.         if trueWidth == 0 then trueWidth = v.width end
  409.         npcutils.drawNPC(v, {texture = gfx_block,sourceX = 0 + trueWidth * (v.data._settings.horizontalFrame or 0)})
  410.         npcutils.hideNPC(v)
  411.     end
  412. end
  413.  
  414. function extendedRinkas.onDrawHeldNPC()
  415.     if #rinkaBlockSpawnedNPCs == 0 then return end
  416.     for k,v in ipairs(rinkaBlockSpawnedNPCs) do
  417.         if v.isValid then -- solely used to insure that held npcs render above the block like rinkas do
  418.             extendedRinkas.drawNPC(v, {priorityOverride = false, priority = -15})
  419.             npcutils.hideNPC(v)
  420.         end
  421.     end
  422. end
  423.  
  424. return extendedRinkas
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement