Advertisement
Guest User

flipperino.lua 2.1

a guest
Aug 16th, 2022
150
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 20.50 KB | None | 0 0
  1. --[[
  2.  
  3.     flipperino.lua v2.1
  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.     Changelog:
  9.     -Blocks don't need to have a flipped graphic anymore (sizeables do... for now)
  10.     -Backgrounds don't need to have a flipped graphic anymore
  11.         -However, a txt file with the following settings is recommended:
  12.             [BG]
  13.             img="background2-x.png"
  14.             name="BG"
  15.             parallaxX=1
  16.             parallaxY=1
  17.             alignY=CENTRE
  18.             alignX=CENTRE
  19.             repeatX = true
  20.             repeatY = true
  21.  
  22. ]]
  23.  
  24. local cb = Graphics.CaptureBuffer(800, 600);
  25. local bgcb = Graphics.CaptureBuffer(800, 600);
  26. local blockmanager = require("blockmanager");
  27. local blockutils = require("blocks/blockutils");
  28. local cp = require("blocks/ai/clearpipe");
  29.  
  30.  
  31. local flipperino = {}
  32.  
  33. local cpOdb = cp.onDrawBlock;
  34. function flipperino.cpOdb(b)
  35.     if (not flipperino.flipped) then
  36.         cpOdb(b);
  37.     end
  38. end
  39.  
  40. cp.onDrawBlock = flipperino.cpOdb;
  41.  
  42. function flipperino.onInitAPI()
  43.     registerEvent(flipperino, "onStart", "onStart", false);
  44.     registerEvent(flipperino, "onTick", "onTick", false);
  45.     registerEvent(flipperino, "onDraw", "onDraw", false);
  46.     registerEvent(flipperino, "onDrawEnd", "onDrawEnd", false);
  47.     registerEvent(flipperino, "onEvent", "onEvent", false);
  48.     registerEvent(flipperino, "onCameraUpdate", "onCameraUpdate", false);
  49. end
  50.  
  51.  
  52. --Init stuff
  53. flipperino.flipped = false;      --If the level is flipped or not.
  54. 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.
  55. flipperino.showMirrored = true;  --If set to false, the camera doesn't flip while the level is upside-down.
  56. flipperino.flipBgos = {};        --When the player touches one of these BGOs, the level will turn upside-down.
  57. flipperino.unflipBgos = {};      --When the player touches one of these BGOS, the level will return to normal.
  58. flipperino.flipSound = 13;       --The sound that plays when the level flips.
  59.  
  60. function flipperino.onStart()
  61.  
  62.     --Stretch and Ceiling Stretch. There's no situation where you wouldn't want these to turn into each other. Mainly because they'd break.
  63.     flipperino.flipNpcs[323] = 324;
  64.     flipperino.flipNpcs[324] = 323;
  65.  
  66.     --Search for all blocks in the level and store their flipped graphic, if it exists.
  67.     flipperino.flippedBlocks = {};
  68.     for k,v in ipairs(Block.get()) do
  69.         if (flipperino.flippedBlocks[v.id] == nil) then
  70.             flipperino.flippedBlocks[v.id] = Graphics.loadImage("flipped/block-"..v.id..".png") ;
  71.         end
  72.         --In order to make upside-down sizeables work, all sizeables now have bottom collision as well as top collision.
  73.         if (Block.config[v.id].sizeable) then
  74.             blockmanager.registerEvent(v.id, flipperino, "onTickEndBlock");
  75.         end
  76.     end
  77.    
  78.     --Search for all npcs in the level and store their flipped graphic, if it exists.
  79.     flipperino.flippedNpcs = {};
  80.     for k,v in ipairs(NPC.get()) do
  81.         if (flipperino.flippedNpcs[v.id] == nil) then
  82.             flipperino.flippedNpcs[v.id] = Graphics.loadImage("flipped/npc-"..v.id..".png");
  83.         end
  84.     end
  85.    
  86.     --Search for all bgos in the level and, of course, store their flipped graphic, if it exists.
  87.     flipperino.flippedBgos = {};
  88.     for k,v in ipairs(BGO.get()) do
  89.         if (flipperino.flippedBgos[v.id] == nil) then
  90.             flipperino.flippedBgos[v.id] = Graphics.loadImage("flipped/background-"..v.id..".png");
  91.         end
  92.     end
  93.  
  94. end
  95.  
  96. function flipperino.onCameraUpdate(camIdx)
  97.     --Horizontal splitscreen fix
  98.     if (flipperino.flipped and flipperino.showMirrored and camera2 ~= nil and camera2.isSplit and camera.height == 300) then
  99.         if (Player(1).y > Player(2).y) then
  100.             camera.renderY = -0.5;
  101.             camera2.renderY = 301;
  102.         else
  103.             camera2.renderY = -0.5;
  104.             camera.renderY = 301;
  105.         end
  106.     end
  107. end
  108.  
  109. function drawFlipped()
  110.    
  111.     --If the level is flipped and showMirrored is true, then draw the camera upside-down.
  112.     if (flipperino.flipped and flipperino.showMirrored) then
  113.         cb:captureAt(-5);
  114.         bgcb:captureAt(-100);
  115.        
  116.         if (camera2 ~= nil  and camera2.isSplit and camera.height == 300) then
  117.             Graphics.glDraw {
  118.                 vertexCoords = {
  119.                     0,   -300,
  120.                     800, -300,
  121.                     0,   300,
  122.                     0,   300,
  123.                     800, 300,
  124.                     800, -300
  125.                 },
  126.                 texture = cb,
  127.                 textureCoords = {0,1, 1,1, 0,0, 0,0, 1,0, 1,1},
  128.                 primitive = Graphics.GL_TRIANGLE,
  129.                 priority = -5
  130.             };
  131.             --Not perfect, idk if it's fixable.
  132.             --It's been 7 months since I wrote that comment, not sure what I was referring to.
  133.             Graphics.glDraw {
  134.                 vertexCoords = {
  135.                     0,   -300,
  136.                     800, -300,
  137.                     0,   300,
  138.                     0,   300,
  139.                     800, 300,
  140.                     800, -300
  141.                 },
  142.                 texture = bgcb,
  143.                 textureCoords = {0,1, 1,1, 0,0, 0,0, 1,0, 1,1},
  144.                 primitive = Graphics.GL_TRIANGLE,
  145.                 priority = -100
  146.             };
  147.         else
  148.             Graphics.glDraw {
  149.                 vertexCoords = {
  150.                     0,   0,
  151.                     800, 0,
  152.                     0,   600,
  153.                     0,   600,
  154.                     800, 600,
  155.                     800, 0
  156.                 },
  157.                 texture = cb,
  158.                 textureCoords = {0,1, 1,1, 0,0, 0,0, 1,0, 1,1},
  159.                 primitive = Graphics.GL_TRIANGLE,
  160.                 priority = -5
  161.             };
  162.  
  163.             Graphics.glDraw{
  164.                 vertexCoords = {
  165.                     0,   0,
  166.                     800, 0,
  167.                     0,   600,
  168.                     0,   600,
  169.                     800, 600,
  170.                     800, 0
  171.                 },
  172.                 texture = bgcb,
  173.                 textureCoords = {0,1, 1,1, 0,0, 0,0, 1,0, 1,1},
  174.                 primitive = Graphics.GL_TRIANGLE,
  175.                 priority = -100;
  176.             };
  177.         end
  178.     end
  179. end
  180.  
  181. function flipperino.onDraw()
  182.  
  183.     drawFlipped();
  184.  
  185.     redrawBlocks();
  186.    
  187.    
  188. end
  189.  
  190. function redrawBlocks();
  191.     flipperino.visibleBlocks = {};
  192.     for _,c in ipairs(Camera.get()) do
  193.         c = camera;
  194.         if (flipperino.flipped and flipperino.showMirrored) then
  195.  
  196.             for k,v in ipairs(Block.getIntersecting(c.x, c.y, c.x+c.width, c.y+c.height)) do
  197.                 if (not v.invisible and not Block.config[v.id].sizeable) then
  198.                     flipperino.visibleBlocks[#flipperino.visibleBlocks+1] = v;
  199.                     v.invisible = true;
  200.  
  201.                     frame = blockutils.getBlockFrame(v.id);
  202.                     priority = -65;
  203.                     if (Block.config[v.id].sizeable) then --not working (for now)
  204.                         priority = -90;
  205.                     elseif (Block.config[v.id].lava) then
  206.                         priority = -10;
  207.                     elseif (cp.PIPES[v.id] ~= nil) then
  208.                         priority = -22.5;
  209.                     end
  210.  
  211.                     x0 = v.x-c.x;
  212.                     x1 = v.x+v.width-c.x;
  213.                     y0 = v.y-c.y;
  214.                     y1 = v.y+v.height-c.y;
  215.  
  216.                     h0 = v.height * frame / Graphics.sprites.block[v.id].img.height;
  217.                     h1 = h0 + (v.height / Graphics.sprites.block[v.id].img.height);
  218.  
  219.  
  220.                     Graphics.glDraw {
  221.                         vertexCoords = {
  222.                             x0, y0,
  223.                             x1, y0,
  224.                             x0, y1,
  225.                             x0, y1,
  226.                             x1, y1,
  227.                             x1, y0
  228.                         },
  229.                         texture = Graphics.sprites.block[v.id].img,
  230.                         textureCoords = {0,h1, 1,h1, 0,h0, 0,h0, 1,h0, 1,h1},
  231.                         primitive = Graphics.GL_TRIANGLE,
  232.                         priority = priority
  233.                     };
  234.  
  235.                 end
  236.             end
  237.         end
  238.     end
  239. end
  240.  
  241. function flipperino.onDrawEnd()
  242.     if (flipperino.flipped and flipperino.showMirrored) then
  243.         for k,v in ipairs(flipperino.visibleBlocks) do
  244.             v.invisible = false;
  245.         end
  246.     end
  247. end
  248.  
  249. function flipperino.onTick()
  250.  
  251.     itemCaught = false; --Without this, only the last player can pick up items from the item box.
  252.     fSections = {}; --There is some code that shouldn't run twice if both players are on the same section.
  253.  
  254.     for _,p in ipairs(Player.get()) do
  255.  
  256.         if (Camera(p.idx).isSplit) then
  257.             bounds = Camera(p.idx).bounds;
  258.         else
  259.             bounds = camera.bounds;
  260.         end
  261.        
  262.         for k,n in ipairs(NPC.getIntersecting(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  263.  
  264.             --This code handles the item box while upside-down.
  265.             --The item is set to friendly until it visually reaches the point where the player should be, and then teleports to the appropriate position.
  266.             --Way easier than trying to modify the item's actual position, trust me.
  267.             if (flipperino.flipped and n:mem(0x138, FIELD_WORD) == 2 and not itemCaught) then
  268.  
  269.                 midpoint = (bounds.top+bounds.bottom)/2 - 16;
  270.                 py = midpoint + (midpoint - (p.y + hOffset));
  271.  
  272.                 n.friendly = true;
  273.  
  274.                 pBox = Colliders.Box(p.x, py - p.height / 2, p.width, p.height);
  275.                 nBox = Colliders.Box(n.x, n.y, n.width, n.height);
  276.  
  277.                 if (Colliders.collide(pBox, nBox)) then
  278.                     n:mem(0x13C,FIELD_DFLOAT, 2);
  279.                     n.y = p.y;
  280.                     n.friendly = false;
  281.                     itemCaught = true;
  282.                 end
  283.  
  284.             elseif (n.friendly and n:mem(0x138, FIELD_WORD) == 2) then
  285.                 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.
  286.             end
  287.  
  288.             --Items affected by gravity should stick to the ground when the level is flipped.
  289.             --Some vanilla NPCs, like Rippers, have nogravity set to false, so this makes them drop to the floor.
  290.             --This also happens to coins, shockingly. That's why the isCoin check is there.
  291.             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
  292.                 n.speedY = n.speedY - 0.26;
  293.  
  294.                 if (n.collidesBlockUp) then
  295.                     n:mem(0x136, FIELD_BOOL, false); --This makes thrown items believe they are touching the ground, when they are in fact touching the ceiling.
  296.                 end
  297.             end
  298.         end
  299.  
  300.         for _,b in ipairs(BGO.getIntersecting(p.x, p.y, p.x+p.width, p.y+p.height)) do
  301.             if (flipperino.flipBgos[b.id] and not flipperino.flipped) then
  302.                 flipperino.performFlip();
  303.             elseif (flipperino.unflipBgos[b.id] and flipperino.flipped) then
  304.                 flipperino.performFlip();
  305.             end
  306.         end
  307.  
  308.         fSections[p.section] = true;
  309.     end
  310. end
  311.  
  312. function flipperino.performFlip()
  313.  
  314.     --Play sound effect.
  315.     SFX.play(flipperino.flipSound);
  316.  
  317.     --Flip the screen and change npc gravity.
  318.     flipperino.flipped = not flipperino.flipped;
  319.     Defines.npc_grav = flipperino.flipped and 0 or 0.26;
  320.  
  321.     --Iterate through all sections and flip them. I'm sure this won't be a problem on huge levels.
  322.     for i=0,20,1 do
  323.  
  324.         sec = Section(i);
  325.  
  326.         --Switch backgrounds
  327.         if (sec.background ~= nil) then
  328.             for k,v in ipairs(sec.background:get()) do
  329.                 if (v.parallaxY ~= nil) then
  330.                     v.parallaxY = -v.parallaxY;
  331.                 end
  332.             end
  333.         end
  334.  
  335.         bounds = sec.boundary;
  336.         midpoint = (bounds.top+bounds.bottom)/2 - 16;
  337.  
  338.         --Change block positions.
  339.         for k,v in ipairs(Block.getIntersecting(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  340.             hOffset = v.height - 32;
  341.             v.y = midpoint + (midpoint - (v.y + hOffset));
  342.         end
  343.  
  344.         --Change block sprites.
  345.         for v in pairs(flipperino.flippedBlocks) do
  346.             if (flipperino.flipped) then
  347.                 Graphics.sprites.block[v].img = flipperino.flippedBlocks[v];
  348.             else
  349.                 Graphics.sprites.block[v].img = nil;
  350.             end
  351.            
  352.             --Slopes are flipped here, too.
  353.             temp = -Block.config[v].floorslope;
  354.             Block.config[v].floorslope = -Block.config[v].ceilingslope;
  355.             Block.config[v].ceilingslope = temp;
  356.         end
  357.  
  358.         --Flip clearpipes.
  359.         for k, v in pairs(cp.PIPES) do
  360.             cp.PIPES[k] = {v[2], v[1], v[3], v[4]};
  361.         end
  362.  
  363.         for k,v in pairs(cp.JUNCS) do
  364.             if (v == 1 or v == 6) then
  365.                 cp.JUNCS[k] = v + 1;
  366.             elseif (v == 2 or v == 7) then
  367.                 cp.JUNCS[k] = v - 1;
  368.             end
  369.         end
  370.  
  371.         for k,v in pairs(cp.ELBS) do
  372.             cp.ELBS[k] = -v;
  373.         end
  374.  
  375.         --Change NPC sprites.
  376.         for v in pairs(flipperino.flippedNpcs) do
  377.             if (flipperino.flipped) then
  378.                 Graphics.sprites.npc[v].img = flipperino.flippedNpcs[v];
  379.             else
  380.                 Graphics.sprites.npc[v].img = nil;
  381.             end
  382.         end
  383.  
  384.         --Change BGO positions.
  385.         for k,v in ipairs(BGO.getIntersecting(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  386.             hOffset = v.height - 32;
  387.             v.y = midpoint + (midpoint - (v.y + hOffset));
  388.         end
  389.  
  390.         --Change BGO sprites.
  391.         for v in pairs(flipperino.flippedBgos) do
  392.             if (flipperino.flipped) then
  393.                 Graphics.sprites.background[v].img = flipperino.flippedBgos[v];
  394.             else
  395.                 Graphics.sprites.background[v].img = nil;
  396.             end
  397.         end
  398.  
  399.         --Hide interaction hardcoded icon when flipped
  400.         if (flipperino.flipped) then
  401.             Graphics.sprites.hardcoded[43].img = Graphics.loadImage("flipped/hardcoded-43.png");
  402.         else
  403.             Graphics.sprites.hardcoded[43].img = nil;
  404.         end
  405.  
  406.         --Change liquid positions.
  407.         for k,v in ipairs(Liquid.getIntersecting(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  408.             hOffset = v.height - 32;
  409.             v.y = midpoint + (midpoint - (v.y + hOffset));
  410.         end
  411.        
  412.         --Change warp positions.
  413.         for k,v in ipairs(Warp.getIntersectingEntrance(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  414.             hOffset = v.entranceHeight - 32;
  415.             v.entranceY = midpoint + (midpoint - (v.entranceY + hOffset)) + (32 * ((v.warpType == 2 or (v.entranceDirection == 2 or v.entranceDirection == 4)) and 1 or 0));
  416.  
  417.             if (v.warpType ~= 2) then
  418.                 if     (v.entranceDirection == 1) then v.entranceDirection = 3;
  419.                 elseif (v.entranceDirection == 3) then v.entranceDirection = 1;
  420.                 end
  421.             end
  422.         end
  423.  
  424.         for k,v in ipairs(Warp.getIntersectingExit(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  425.             hOffset = v.exitHeight - 32;
  426.             v.exitY = midpoint + (midpoint - (v.exitY + hOffset)) + (32 * ((v.warpType == 2 or (v.exitDirection == 2 or v.exitDirection == 4)) and 1 or 0));
  427.  
  428.             if (v.warpType ~= 2) then
  429.                 if     (v.exitDirection == 1) then v.exitDirection = 3;
  430.                 elseif (v.exitDirection == 3) then v.exitDirection = 1;
  431.                 end
  432.             end
  433.         end
  434.  
  435.         --Change player position
  436.         for _,p in ipairs(Player.get()) do
  437.             if (p.section == i) then
  438.                 hOffset = p.height - 32;
  439.                 p.y = midpoint + (midpoint - (p.y + hOffset));
  440.                 p.speedY = -p.speedY;
  441.             end
  442.         end
  443.  
  444.         --Change NPC position.
  445.         for k,v in ipairs(NPC.getIntersecting(bounds.left, bounds.top, bounds.right, bounds.bottom)) do
  446.             if (v:mem(0x138, FIELD_WORD) ~= 2) then
  447.  
  448.                 hOffset = v.height - 32;
  449.                 v.y = midpoint + (midpoint - (v.y + hOffset));
  450.                 v.speedY = -v.speedY;
  451.                
  452.                 if (flipperino.flipNpcs[v.id] ~= nil) then
  453.                     v.id = flipperino.flipNpcs[v.id];
  454.                     v.spawnId = v.id; --Really important, or else npcs would revert when they respawned.
  455.                 end
  456.  
  457.                 spawnY = midpoint + (midpoint - (v.spawnY + hOffset));
  458.                 --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.
  459.                 v.spawnY = spawnY;
  460.  
  461.                 --Thwomps.
  462.                 if (v.id == 432 or v.id == 435 or v.id == 437) then
  463.  
  464.                     v.data._basegame.direction = -v.data._basegame.direction;
  465.                     v.data._basegame.previous.speedY = -v.data._basegame.previous.speedY;
  466.  
  467.                     if (v.data._basegame.state == 3) then v.data._basegame.state = 6;
  468.                     elseif (v.data._basegame.state == 6) then v.data._basegame.state = 3; end
  469.  
  470.                     if (v.data._basegame.state == 2) then v.data._basegame.state = 5;
  471.                     elseif (v.data._basegame.state == 5) then v.data._basegame.state = 2; end
  472.  
  473.                     if (v.data._basegame.state == 1) then v.data._basegame.state = 4;
  474.                     elseif (v.data._basegame.state == 4) then v.data._basegame.state = 1; end
  475.  
  476.                 --Wall crawlers. They kinda work but sometimes they don't. Mainly when colliding when quicksand. Or water. Or sizeables. Or slopes.
  477.                 elseif ( v.id == 205 or v.id == 206 or v.id == 207) then
  478.  
  479.                     if (v.ai1 == 1) then v.ai1 = 3;
  480.                     elseif (v.ai1 == 2) then v.ai1 = 4;
  481.                     elseif (v.ai1 == 3) then v.ai1 = 1;
  482.                     elseif (v.ai1 == 4) then v.ai1 = 2;
  483.                     end
  484.  
  485.                 end
  486.             end
  487.         end
  488.  
  489.     end
  490.  
  491. end
  492.  
  493. --In order to flip the level, you call an event with the name "flip". That's it.
  494. function flipperino.onEvent(eventName)
  495.     if (eventName == "flip") then
  496.         flipperino.performFlip();
  497.     elseif (eventName == "flipUpsideDown" and not flipperino.flipped) then
  498.         flipperino.performFlip();
  499.     elseif (eventName == "flipNormal" and flipperino.flipped) then
  500.         flipperino.performFlip();
  501.     end
  502. end
  503.  
  504. --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.
  505.  
  506. local function objectHasCollision(v) -- Get whether a player/NPC has collision. Obviously not perfect, but I guess it does the job
  507.     if type(v) == "Player" then
  508.         return ((v.forcedState == 0 and v.deathTimer == 0 and not v:mem(0x13C,FIELD_BOOL)) and not Defines.cheat_shadowmario)
  509.     elseif type(v) == "NPC" then
  510.         local config = NPC.config[v.id]
  511.         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)
  512.     end
  513. end
  514.  
  515. local function pushObjectOut(v, w)
  516.     if (w.y-w.speedY) >= (v.y+v.height-v.speedY) and (w.speedY < 0) then
  517.         w.y = v.y+v.height
  518.         w.speedY = 0
  519.  
  520.         if type(w) == "Player" then
  521.             w:mem(0x11C,FIELD_WORD,0) -- Jump force
  522.             w:mem(0x14A,FIELD_WORD,2)
  523.         elseif type(w) == "NPC" then
  524.             w.collidesBlockUp = true
  525.         end
  526.     end
  527. end
  528.  
  529. function flipperino.onTickEndBlock(v)
  530.     if v.isHidden or v:mem(0x5A, FIELD_BOOL) then return end
  531.  
  532.     -- Players
  533.     for _,w in ipairs(Player.get()) do
  534.         if objectHasCollision(w) and v:collidesWith(w) > 0 then
  535.             pushObjectOut(v,w)
  536.         end
  537.     end
  538.     -- NPCs
  539.     local hitbox = blockutils.getHitbox(v,0.3)
  540.  
  541.     for _,w in ipairs(Colliders.getColliding{a = hitbox,btype = Colliders.NPC,filter = objectHasCollision}) do
  542.         pushObjectOut(v,w)
  543.     end
  544.  
  545. end
  546.  
  547. return flipperino;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement