Advertisement
Guest User

flipperino.lua

a guest
Dec 31st, 2021
226
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.23 KB | None | 0 0
  1. --[[
  2.  
  3.     flipperino.lua
  4.  
  5.     Written by Zeus guy
  6.     A few lines of code taken from MrDoubleA's One-Way Walls and maybe from the default libraries.
  7.  
  8.     Known issues:
  9.     -In vertical sections, dropped items move wildly while jumping. Not much I can do there, at least it still works.
  10.     -When interacting with an NPC, the icon appears under the npc instead of above it. Again, not much I can do there.
  11.     -Effects aren't flipped.
  12. ]]
  13.  
  14. local cb = Graphics.CaptureBuffer(800, 600);
  15. local blockmanager = require("blockmanager");
  16. local blockutils = require("blocks/blockutils");
  17. local cp = require("blocks/ai/clearpipe");
  18.  
  19. local flipperino = {}
  20.  
  21. function flipperino.onInitAPI()
  22.     registerEvent(flipperino, "onStart", "onStart", false);
  23.     registerEvent(flipperino, "onTick", "onTick", false);
  24.     registerEvent(flipperino, "onDraw", "onDraw", false);
  25.     registerEvent(flipperino, "onEvent", "onEvent", false);
  26.     registerEvent(flipperino, "onCameraUpdate", "onCameraUpdate", false);
  27. end
  28.  
  29. --Init stuff
  30. flipperino.flipped = false;      --If the level is flipped or not.
  31. flipperino.flipNpcs = {};        --Used for when an NPC should turn into another when the level is flipped. Values can be added in your level file if needed.
  32. flipperino.parallaxFlipBgs = {}; --Used for storing the background layers that will be shown when the level is flipped.
  33. flipperino.showMirrored = true;  --If set to false, the camera doesn't flip while the level is upside-down.
  34. flipperino.flipBgos = {};        --When the player touches one of these BGOs, the level will turn upside-down.
  35. flipperino.unflipBgos = {};      --When the player touches one of these BGOS, the level will return to normal.
  36. flipperino.flipSound = 13;       --The sound that plays when the level flips.
  37.  
  38. function flipperino.onStart()
  39.  
  40.     --Stretch and Ceiling Stretch. There's no situation where you wouldn't want these to turn into each other. Mainly because they'd break.
  41.     flipperino.flipNpcs[323] = 324;
  42.     flipperino.flipNpcs[324] = 323;
  43.  
  44.     --Background layer "FLIP" should only appear when the level is flipped. You can add more layers on your luna.lua file.
  45.     flipperino.parallaxFlipBgs["FLIP"] = true;
  46.  
  47.     --Search for all blocks in the level and store their flipped graphic, if it exists.
  48.     flipperino.flippedBlocks = {};
  49.     for k,v in ipairs(Block.get()) do
  50.         if (flipperino.flippedBlocks[v.id] == nil) then
  51.             flipperino.flippedBlocks[v.id] = Graphics.loadImage("flipped/block-"..v.id..".png") ;
  52.         end
  53.         --In order to make upside-down sizeables work, all sizeables now have bottom collision as well as top collision.
  54.         if (Block.config[v.id].sizeable) then
  55.             blockmanager.registerEvent(v.id, flipperino, "onTickEndBlock");
  56.         end
  57.     end
  58.    
  59.     --Search for all npcs in the level and store their flipped graphic, if it exists.
  60.     flipperino.flippedNpcs = {};
  61.     for k,v in ipairs(NPC.get()) do
  62.         if (flipperino.flippedNpcs[v.id] == nil) then
  63.             flipperino.flippedNpcs[v.id] = Graphics.loadImage("flipped/npc-"..v.id..".png");
  64.         end
  65.     end
  66.    
  67.     --Search for all bgos in the level and, of course, store their flipped graphic, if it exists.
  68.     flipperino.flippedBgos = {};
  69.     for k,v in ipairs(BGO.get()) do
  70.         if (flipperino.flippedBgos[v.id] == nil) then
  71.             flipperino.flippedBgos[v.id] = Graphics.loadImage("flipped/background-"..v.id..".png");
  72.         end
  73.     end
  74.  
  75. end
  76.  
  77. function flipperino.onCameraUpdate(camIdx)
  78.     --Horizontal splitscreen fix
  79.     if (flipperino.flipped and flipperino.showMirrored and camera2 ~= nil and camera2.isSplit and camera.height == 300) then
  80.         if (Player(1).y > Player(2).y) then
  81.             camera.renderY = -0.5;
  82.             camera2.renderY = 301;
  83.         else
  84.             camera2.renderY = -0.5;
  85.             camera.renderY = 301;
  86.         end
  87.     end
  88. end
  89.  
  90. function drawFlipped()
  91.    
  92.     --If the level is flipped and showMirrored is true, then draw the camera upside-down.
  93.     if (flipperino.flipped and flipperino.showMirrored) then
  94.         cb:captureAt(-5);
  95.        
  96.         if (camera2 ~= nil  and camera2.isSplit and camera.height == 300) then
  97.             Graphics.glDraw {
  98.                 vertexCoords = {
  99.                     0,   -300,
  100.                     800, -300,
  101.                     0,   300,
  102.                     0,   300,
  103.                     800, 300,
  104.                     800, -300
  105.                 },
  106.                 texture = cb,
  107.                 textureCoords = {0,1, 1,1, 0,0, 0,0, 1,0, 1,1},
  108.                 primitive = Graphics.GL_TRIANGLE,
  109.                 priority = -5
  110.             };
  111.         else
  112.             Graphics.glDraw {
  113.                 vertexCoords = {
  114.                     0,   0,
  115.                     800, 0,
  116.                     0,   600,
  117.                     0,   600,
  118.                     800, 600,
  119.                     800, 0
  120.                 },
  121.                 texture = cb,
  122.                 textureCoords = {0,1, 1,1, 0,0, 0,0, 1,0, 1,1},
  123.                 primitive = Graphics.GL_TRIANGLE,
  124.                 priority = -5
  125.             };
  126.         end
  127.     end
  128. end
  129.  
  130. function flipperino.onDraw()
  131.     drawFlipped();
  132. end
  133.  
  134. function flipperino.onTick()
  135.  
  136.     itemCaught = false; --Without this, only the last player can pick up items from the item box.
  137.     fSections = {}; --There is some code that shouldn't run twice if both players are on the same section.
  138.  
  139.     for _,p in ipairs(Player.get()) do
  140.  
  141.         if (Camera(p.idx).isSplit) then
  142.             bounds = Camera(p.idx).bounds;
  143.         else
  144.             bounds = camera.bounds;
  145.         end
  146.        
  147.         for k,n in ipairs(NPC.getIntersecting(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  148.  
  149.             --This code handles the item box while upside-down.
  150.             --The item is set to friendly until it visually reaches the point where the player should be, and then teleports to the appropriate position.
  151.             --Way easier than trying to modify the item's actual position, trust me.
  152.             if (flipperino.flipped and n:mem(0x138, FIELD_WORD) == 2 and not itemCaught) then
  153.  
  154.                 midpoint = (bounds.top+bounds.bottom)/2 - 16;
  155.                 py = midpoint + (midpoint - (p.y + hOffset));
  156.  
  157.                 n.friendly = true;
  158.  
  159.                 pBox = Colliders.Box(p.x, py - p.height / 2, p.width, p.height);
  160.                 nBox = Colliders.Box(n.x, n.y, n.width, n.height);
  161.  
  162.                 Text.print("PY:  "..py, 20, 20);
  163.                 Text.print("APY: "..py - p.height / 2, 20, 40);
  164.                 Text.print("IY:  "..n.y, 20, 60);
  165.  
  166.                 if (Colliders.collide(pBox, nBox)) then
  167.                     n:mem(0x13C,FIELD_DFLOAT, 2);
  168.                     n.y = p.y;
  169.                     n.friendly = false;
  170.                     itemCaught = true;
  171.                 end
  172.  
  173.             elseif (n.friendly and n:mem(0x138, FIELD_WORD) == 2) then
  174.                 n.friendly = false; --You should still be able to collect the item properly if you flip the level while the item is on its way.
  175.             end
  176.  
  177.             --Items affected by gravity should stick to the ground when the level is flipped.
  178.             --Some vanilla NPCs, like Rippers, have nogravity set to false, so this makes them drop to the floor.
  179.             --This also happens to coins, shockingly. That's why the isCoin check is there.
  180.             if (flipperino.flipped and not NPC.config[n.id].isCoin and not NPC.config[n.id].nogravity and not fSections[p.section] and n.despawnTimer > 0) then
  181.                 n.speedY = n.speedY - 0.26;
  182.  
  183.                 if (n.collidesBlockUp) then
  184.                     n:mem(0x136, FIELD_BOOL, false); --This makes thrown items believe they are touching the ground, when they are in fact touching the ceiling.
  185.                 end
  186.             end
  187.         end
  188.  
  189.         for _,b in ipairs(BGO.getIntersecting(p.x, p.y, p.x+p.width, p.y+p.height)) do
  190.             if (flipperino.flipBgos[b.id] and not flipperino.flipped) then
  191.                 flipperino.performFlip();
  192.             elseif (flipperino.unflipBgos[b.id] and flipperino.flipped) then
  193.                 flipperino.performFlip();
  194.             end
  195.         end
  196.  
  197.         fSections[p.section] = true;
  198.     end
  199. end
  200.  
  201. function flipperino.performFlip()
  202.  
  203.     --Play sound effect.
  204.     SFX.play(flipperino.flipSound);
  205.  
  206.     --Flip the screen and change npc gravity.
  207.     flipperino.flipped = not flipperino.flipped;
  208.     Defines.npc_grav = flipperino.flipped and 0 or 0.26;
  209.  
  210.     --Iterate through all sections and flip them. I'm sure this won't be a problem on huge levels.
  211.     for i=0,20,1 do
  212.  
  213.         sec = Section(i);
  214.  
  215.         --Switch backgrounds
  216.         for v in pairs(flipperino.parallaxFlipBgs) do
  217.             if (sec.background ~= nil and sec.background:get(v) ~= nil) then
  218.                 sec.background:get(v).opacity = flipperino.flipped and 1 or 0;
  219.             end
  220.         end
  221.  
  222.         bounds = sec.boundary;
  223.         midpoint = (bounds.top+bounds.bottom)/2 - 16;
  224.  
  225.         --Change block positions.
  226.         for k,v in ipairs(Block.getIntersecting(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  227.             hOffset = v.height - 32;
  228.             v.y = midpoint + (midpoint - (v.y + hOffset));
  229.         end
  230.  
  231.         --Change block sprites.
  232.         for v in pairs(flipperino.flippedBlocks) do
  233.             if (flipperino.flipped) then
  234.                 Graphics.sprites.block[v].img = flipperino.flippedBlocks[v];
  235.             else
  236.                 Graphics.sprites.block[v].img = nil;
  237.             end
  238.            
  239.             --Slopes are flipped here, too.
  240.             temp = -Block.config[v].floorslope;
  241.             Block.config[v].floorslope = -Block.config[v].ceilingslope;
  242.             Block.config[v].ceilingslope = temp;
  243.         end
  244.  
  245.         --Flip clearpipes.
  246.         for k, v in pairs(cp.PIPES) do
  247.             cp.PIPES[k] = {v[2], v[1], v[3], v[4]};
  248.         end
  249.  
  250.         for k,v in pairs(cp.JUNCS) do
  251.             if (v == 1 or v == 6) then
  252.                 cp.JUNCS[k] = v + 1;
  253.             elseif (v == 2 or v == 7) then
  254.                 cp.JUNCS[k] = v - 1;
  255.             end
  256.         end
  257.  
  258.         for k,v in pairs(cp.ELBS) do
  259.             cp.ELBS[k] = -v;
  260.         end
  261.  
  262.         --Change NPC sprites.
  263.         for _,v in pairs(flipperino.flippedNpcs) do
  264.             if (flipperino.flipped) then
  265.                 Graphics.sprites.npc[v].img = flipperino.flippedNpcs[v];
  266.             else
  267.                 Graphics.sprites.npc[v].img = nil;
  268.             end
  269.         end
  270.  
  271.         --Change BGO positions.
  272.         for k,v in ipairs(BGO.getIntersecting(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  273.             hOffset = v.height - 32;
  274.             v.y = midpoint + (midpoint - (v.y + hOffset));
  275.         end
  276.  
  277.         --Change BGO sprites.
  278.         for v in pairs(flipperino.flippedBgos) do
  279.             if (flipperino.flipped) then
  280.                 Graphics.sprites.background[v].img = flipperino.flippedBgos[v];
  281.             else
  282.                 Graphics.sprites.background[v].img = nil;
  283.             end
  284.         end
  285.  
  286.         --Hide interaction hardcoded icon when flipped
  287.         if (flipperino.flipped) then
  288.             Graphics.sprites.hardcoded[43].img = Graphics.loadImage("flipped/hardcoded-43.png");
  289.         else
  290.             Graphics.sprites.hardcoded[43].img = nil;
  291.         end
  292.  
  293.         --Change liquid positions.
  294.         for k,v in ipairs(Liquid.getIntersecting(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  295.             hOffset = v.height - 32;
  296.             v.y = midpoint + (midpoint - (v.y + hOffset));
  297.         end
  298.        
  299.         --Change warp positions.
  300.         for k,v in ipairs(Warp.getIntersectingEntrance(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  301.             hOffset = v.entranceHeight - 32;
  302.             v.entranceY = midpoint + (midpoint - (v.entranceY + hOffset)) + (32 * ((v.warpType == 2 or (v.entranceDirection == 2 or v.entranceDirection == 4)) and 1 or 0));
  303.  
  304.             if (v.warpType ~= 2) then
  305.                 if     (v.entranceDirection == 1) then v.entranceDirection = 3;
  306.                 elseif (v.entranceDirection == 3) then v.entranceDirection = 1;
  307.                 end
  308.             end
  309.         end
  310.  
  311.         for k,v in ipairs(Warp.getIntersectingExit(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  312.             hOffset = v.exitHeight - 32;
  313.             v.exitY = midpoint + (midpoint - (v.exitY + hOffset)) + (32 * ((v.warpType == 2 or (v.exitDirection == 2 or v.exitDirection == 4)) and 1 or 0));
  314.  
  315.             if (v.warpType ~= 2) then
  316.                 if     (v.exitDirection == 1) then v.exitDirection = 3;
  317.                 elseif (v.exitDirection == 3) then v.exitDirection = 1;
  318.                 end
  319.             end
  320.         end
  321.  
  322.         --Change player position
  323.         for _,p in ipairs(Player.get()) do
  324.             if (p.section == i) then
  325.                 hOffset = p.height - 32;
  326.                 p.y = midpoint + (midpoint - (p.y + hOffset));
  327.                 p.speedY = -p.speedY;
  328.             end
  329.         end
  330.  
  331.         --Change NPC position.
  332.         for k,v in ipairs(NPC.getIntersecting(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  333.             if (v:mem(0x138, FIELD_WORD) ~= 2) then
  334.  
  335.                 hOffset = v.height - 32;
  336.                 v.y = midpoint + (midpoint - (v.y + hOffset));
  337.                 v.speedY = -v.speedY;
  338.                
  339.                 if (flipperino.flipNpcs[v.id] ~= nil) then
  340.                     v.id = flipperino.flipNpcs[v.id];
  341.                     v.spawnId = v.id; --Really important, or else npcs would revert when they respawned.
  342.                 end
  343.  
  344.                 spawnY = midpoint + (midpoint - (v.spawnY + hOffset));
  345.                 --v:mem(0xB0, FIELD_DFLOAT, spawnY); --Pretty sure this is just v.spawnY? I'll leave this here just in case it's not and something breaks.
  346.                 v.spawnY = spawnY;
  347.  
  348.                 --Thwomps.
  349.                 if (v.id == 432 or v.id == 435 or v.id == 437) then
  350.  
  351.                     v.data._basegame.direction = -v.data._basegame.direction;
  352.                     v.data._basegame.previous.speedY = -v.data._basegame.previous.speedY;
  353.  
  354.                     if (v.data._basegame.state == 3) then v.data._basegame.state = 6;
  355.                     elseif (v.data._basegame.state == 6) then v.data._basegame.state = 3; end
  356.  
  357.                     if (v.data._basegame.state == 2) then v.data._basegame.state = 5;
  358.                     elseif (v.data._basegame.state == 5) then v.data._basegame.state = 2; end
  359.  
  360.                     if (v.data._basegame.state == 1) then v.data._basegame.state = 4;
  361.                     elseif (v.data._basegame.state == 4) then v.data._basegame.state = 1; end
  362.  
  363.                 --Wall crawlers. They kinda work but sometimes they don't. Mainly when colliding when quicksand. Or water. Or sizeables. Or slopes.
  364.                 elseif ( v.id == 205 or v.id == 206 or v.id == 207) then
  365.  
  366.                     if (v.ai1 == 1) then v.ai1 = 3;
  367.                     elseif (v.ai1 == 2) then v.ai1 = 4;
  368.                     elseif (v.ai1 == 3) then v.ai1 = 1;
  369.                     elseif (v.ai1 == 4) then v.ai1 = 2;
  370.                     end
  371.  
  372.                 end
  373.             end
  374.         end
  375.  
  376.     end
  377.  
  378. end
  379.  
  380. --In order to flip the level, you call an event with the name "flip". That's it.
  381. function flipperino.onEvent(eventName)
  382.     if (eventName == "flip") then
  383.         flipperino.performFlip();
  384.     elseif (eventName == "flipUpsideDown" and not flipperino.flipped) then
  385.         flipperino.performFlip();
  386.     elseif (eventName == "flipNormal" and flipperino.flipped) then
  387.         flipperino.performFlip();
  388.     end
  389. end
  390.  
  391. --The following functions have been "borrowed" from MrDoubleA's One-Way Walls. Wanted to rewrite it but I ran out of time, so I'll do that later.
  392.  
  393. local function objectHasCollision(v) -- Get whether a player/NPC has collision. Obviously not perfect, but I guess it does the job
  394.     if type(v) == "Player" then
  395.         return ((v.forcedState == 0 and v.deathTimer == 0 and not v:mem(0x13C,FIELD_BOOL)) and not Defines.cheat_shadowmario)
  396.     elseif type(v) == "NPC" then
  397.         local config = NPC.config[v.id]
  398.         return ((v.despawnTimer > 0 and not v.isGenerator and not v.isHidden) and not v.noblockcollision and (not config or not config.noblockcollision) and v.id ~= 266 and v:mem(0x12C,FIELD_WORD) == 0 and v:mem(0x138,FIELD_WORD) == 0)
  399.     end
  400. end
  401.  
  402. local function pushObjectOut(v, w)
  403.     if (w.y-w.speedY) >= (v.y+v.height-v.speedY) and (w.speedY < 0) then
  404.         w.y = v.y+v.height
  405.         w.speedY = 0
  406.  
  407.         if type(w) == "Player" then
  408.             w:mem(0x11C,FIELD_WORD,0) -- Jump force
  409.             w:mem(0x14A,FIELD_WORD,2)
  410.         elseif type(w) == "NPC" then
  411.             w.collidesBlockUp = true
  412.         end
  413.     end
  414. end
  415.  
  416. function flipperino.onTickEndBlock(v)
  417.     if v.isHidden or v:mem(0x5A, FIELD_BOOL) then return end
  418.  
  419.     -- Players
  420.     for _,w in ipairs(Player.get()) do
  421.         if objectHasCollision(w) and v:collidesWith(w) > 0 then
  422.             pushObjectOut(v,w)
  423.         end
  424.     end
  425.     -- NPCs
  426.     local hitbox = blockutils.getHitbox(v,0.3)
  427.  
  428.     for _,w in ipairs(Colliders.getColliding{a = hitbox,btype = Colliders.NPC,filter = objectHasCollision}) do
  429.         pushObjectOut(v,w)
  430.     end
  431.  
  432. end
  433.  
  434. return flipperino;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement