Advertisement
nitrogenfingers

gameutils

Dec 10th, 2012
961
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.43 KB | None | 0 0
  1. --[[
  2.     GameUtil
  3.     An API for drawing sprites and animations made in NPaintPro
  4.     By NitrogenFingers
  5. ]]--
  6.  
  7.  
  8. --The back buffer. Initialized as nil
  9. local backbuffer = nil
  10. --The bounds of the terminal the back buffer displays to
  11. local tw,th = nil, nil
  12.  
  13. --[[Constructs a new buffer. This must be done before the buffer can written to.
  14.     Params: terminal:?table = The function table to draw to a screen. By default (nil) this refers
  15.             to the native terminal, but monitor displays can be passed through as well:
  16.             local leftMonitor = peripherals.wrap("left")
  17.             initializeBuffer(leftMonitor)
  18.     Returns:boolean = True if the buffer was successfully initialized; false otherwise
  19. ]]--
  20. function initializeBuffer(terminal)
  21.     if not terminal then terminal = term end
  22.     if not terminal.getSize then
  23.         error("Parameter cannot be used to initialize the backbuffer.")
  24.     end
  25.     if not terminal.isColour() then
  26.         error("Parameter does not represent an advanced computer.")
  27.     end
  28.    
  29.     tw,th = terminal.getSize()
  30.     backbuffer = { }
  31.     for y=1,th do
  32.         backbuffer[y] = { }
  33.     end
  34.     return true
  35. end
  36.  
  37. --[[Will clear the buffer and reset to nil, or to a colour if provided
  38.     Params: colour:?number = The colour to set the back buffer to
  39.     Returns:nil
  40. ]]--
  41. function clearBuffer(colour)
  42.     if not backbuffer then
  43.         error("Back buffer not yet initialized!")
  44.     end
  45.    
  46.     for y=1,#backbuffer do
  47.         backbuffer[y] = { }
  48.         if colour then
  49.             for x=1,tw do
  50.                 backbuffer[y][x] = colour
  51.             end
  52.         end
  53.     end
  54. end
  55.  
  56. --[[Draws the given entity to the back buffer
  57.     Params: entity:table = the entity to draw to the buffer
  58.     Returns:nil
  59. ]]--
  60. function writeToBuffer(entity)
  61.     if not backbuffer then
  62.         error("Back buffer not yet initialized!")
  63.     end
  64.    
  65.     local image = nil
  66.     if entity.type == "animation" then
  67.         image = entity.frames[entity.currentFrame]
  68.     else
  69.         image = entity.image
  70.     end
  71.    
  72.     for y=1,image.dimensions.height do
  73.         for x=1,image.dimensions.width do
  74.             if image[y][x] then
  75.                 local xpos,ypos = x,y
  76.                 if entity.mirror.x then xpos = image.dimensions.width - x + 1 end
  77.                 if entity.mirror.y then ypos = image.dimensions.height - y + 1 end
  78.                
  79.                 backbuffer[entity.y + ypos - 1][entity.x + xpos - 1]
  80.                     = image[y][x]
  81.             end
  82.         end
  83.     end
  84. end
  85.  
  86. --[[Draws the contents of the buffer to the screen. This will not clear the screen or the buffer.
  87.     Params: terminal:table = the terminal to draw to
  88.     Returns:nil
  89. ]]--
  90. function drawBuffer(terminal)
  91.     if not backbuffer then
  92.         error("Back buffer not yet initialized!")
  93.     end
  94.     if not terminal then terminal = term end
  95.     if not terminal.setCursorPos or not terminal.setBackgroundColour or not terminal.write then
  96.         error("Parameter cannot be used to initialize the backbuffer.")
  97.     end
  98.     if not terminal.isColour() then
  99.         error("Parameter does not represent an advanced computer.")
  100.     end
  101.    
  102.     for y=1,math.min(#backbuffer, th) do
  103.         for x=1,tw do
  104.             if backbuffer[y][x] then
  105.                 terminal.setCursorPos(x,y)
  106.                 terminal.setBackgroundColour(backbuffer[y][x])
  107.                 terminal.write(" ")
  108.             end
  109.         end
  110.     end
  111. end
  112.  
  113. --[[Converts a hex digit into a colour value
  114.     Params: hex:?string = the hex digit to be converted
  115.     Returns:string A colour value corresponding to the hex, or nil if the character is invalid
  116. ]]--
  117. local function getColourOf(hex)
  118.     local value = tonumber(hex, 16)
  119.     if not value then return nil end
  120.     value = math.pow(2,value)
  121.     return value
  122. end
  123.  
  124. --[[Converts every pixel of one colour in a given sprite to another colour
  125.     Use for "reskinning". Uses OO function.
  126.     Params: self:sprite = the sprite to reskin
  127.             oldcol:number = the colour to replace
  128.             newcol:number = the new colour
  129.     Returns:nil
  130. ]]--
  131. local function repaintS(self, oldcol, newcol)
  132.     for y=1,self.image.bounds.height do
  133.         for x=1, self.image.bounds.width do
  134.             if self.image[y][x] == oldcol then
  135.                 self.image[y][x] = newcol
  136.             end
  137.         end
  138.     end
  139. end
  140.  
  141. --[[Converts every pixel of one colour in a given animation to another colour
  142.     Use for "reskinning". Uses OO function.
  143.     Params: self:animation = the animation to reskin
  144.             oldcol:number = the colour to replace
  145.             newcol:number = the new colour
  146.     Returns:nil
  147. ]]--
  148. local function repaintA(self, oldcol, newcol)
  149.     for f=1,#self.frames do
  150.         print(self.frames[f].bounds)
  151.         for y=1,self.frames[f].bounds.height do
  152.             for x=1, self.frames[f].bounds.width do
  153.                 if self.frames[f][y][x] == oldcol then
  154.                     self.frames[f][y][x] = newcol
  155.                 end
  156.             end
  157.         end
  158.     end
  159. end
  160.  
  161. --[[Prints the sprite on the screen
  162.     Params: self:sprite = the sprite to draw
  163.     Returns:nil
  164. ]]--
  165. local function drawS(self)
  166.     local image = self.image
  167.    
  168.     for y=1,image.dimensions.height do
  169.         for x=1,image.dimensions.width do
  170.             if image[y][x] then
  171.                 local xpos,ypos = x,y
  172.                 if self.mirror.x then xpos = image.dimensions.width - x + 1 end
  173.                 if self.mirror.y then ypos = image.dimensions.height - y + 1 end
  174.                
  175.                 term.setBackgroundColour(image[y][x])
  176.                 term.setCursorPos(self.x + xpos - 1, self.y + ypos - 1)
  177.                 term.write(" ")
  178.             end
  179.         end
  180.     end
  181. end
  182.  
  183. --[[Prints the current frame of the animation on screen
  184.     Params: self:anim = the animation to draw
  185.             frame:?number = the specific frame to draw (default self.currentFrame)
  186.     Returns:nil
  187. ]]--
  188. local function drawA(self, frame)
  189.     if not frame then frame = self.currentFrame end
  190.     local image = self.frames[frame]
  191.  
  192.     for y=1,image.dimensions.height do
  193.         for x=1,image.dimensions.width do
  194.             if image[y][x] then
  195.                 local xpos,ypos = x,y
  196.                 if self.mirror.x then xpos = image.dimensions.width - x + 1 end
  197.                 if self.mirror.y then ypos = image.dimensions.height - y + 1 end
  198.                
  199.                 term.setBackgroundColour(image[y][x])
  200.                 term.setCursorPos(self.x + xpos - 1, self.y + ypos - 1)
  201.                 term.write(" ")
  202.             end
  203.         end
  204.     end
  205. end
  206.  
  207. --[[Checks the animation timer provided to see whether or not the animation needs to be updated.
  208.     If so, it makes the necessary change.
  209.     Params: self:animation = the animation to be updated
  210.             timerID:number = the ID of the most recent timer event
  211.     Returns:bool = true if the animation was update; false otherwise
  212. ]]--
  213. local function updateA(self, timerID)
  214.     if self.timerID and timerID and self.timerID == timerID then
  215.         self.currentFrame = self.currentFrame + 1
  216.         if self.currentFrame > self.upperBound then
  217.             self.currentFrame = self.lowerBound
  218.         end
  219.         return true
  220.     else
  221.         return false
  222.     end
  223. end
  224.  
  225. --[[Moves immediately to the next frame in the sequence, as though an update had been called.
  226.     Params: self:animation = the animation to update
  227.     Returns:nil
  228. ]]--
  229. local function nextA(self)
  230.     self.currentFrame = self.currentFrame + 1
  231.     if self.currentFrame > self.upperBound then
  232.         self.currentFrame = self.lowerBound
  233.     end
  234. end
  235.  
  236. --[[Moves immediately to the previous frame in the sequence
  237.     Params: self:animation = the animation to update
  238.     Returns:nil
  239. ]]--
  240. local function previousA(self)
  241.     self.currentFrame = self.currentFrame - 1
  242.     if self.currentFrame < self.lowerBound then
  243.         self.currentFrame = self.upperBound
  244.     end
  245. end
  246.  
  247. --[[Performs a rectangle collision with another given entity. Entity can be of sprite or animation
  248.     type (also true of the self). Bases collision using a least squared approach (rectangle precision).
  249.     Params: self:sprite,animation = the object in question of the testing
  250.             other:sprite,animation = the other object tested for collision
  251.     Returns:bool = true if bounding rectangle intersect is true; false otherwse
  252. ]]--
  253. local function rCollidesWith(self, other)
  254.     error("not implemented yet...")
  255. end
  256.  
  257. --[[Performs a pixel collision test on another given entity. Either entity can be of sprite or animation
  258.     type. Bases collision on a pixel occupancy approach.
  259.     Params: self:sprite,animation = the object in question of the testing
  260.             other:sprite,animation = the other object being tested for collision
  261.     Returns:?number,?number: The X and Y position in which the collision occurred.
  262. ]]--
  263. local function pCollidesWith(self, other)
  264.     error("not implemented yet..")
  265. end
  266.  
  267. --[[
  268.     Sprites Fields:
  269. x:number = the x position of the sprite in the world
  270. y:number = the y position of the sprite in the world
  271. image:table = a table of the image. Indexed by height, a series of sub-tables, each entry being a pixel
  272.         at [y][x]. It also contains:
  273.     bounds:table =
  274.         x:number = the relative x position of the bounding rectangle
  275.         y:number = the relative y position of the bounding rectangle
  276.         width:number = the width of the bounding rectangle
  277.         height:number = the height of the bounding rectangle
  278.     dimensions:table =
  279.         width = the width of the entire image in pixels
  280.         height = the height of the entire image in pixels
  281.        
  282. mirror:table =
  283.     x:bool = whether or not the image is mirrored on the X axis
  284.     y:bool = whether or not the image is mirrored on the Y axis
  285. repaint:function = see repaintS (above)
  286. rCollidesWith:function = see rCollidesWith (above)
  287. pCollidesWith:function = see pCollidesWith (above)
  288. draw:function = see drawS (above)
  289. ]]--
  290.  
  291. --[[Loads a new sprite into a table, and returns it to the user.
  292.     Params: path:string = the absolute path to the desired sprite
  293.     x:number = the initial X position of the sprite
  294.     y:number = the initial Y position of the sprite
  295. ]]--
  296. function loadSprite(path, x, y)
  297.     local sprite = {
  298.         type = "sprite",
  299.         x = x,
  300.         y = y,
  301.         image = { },
  302.         mirror = { x = false, y = false }
  303.     }
  304.    
  305.     if fs.exists(path) then
  306.         local file = io.open(path, "r" )
  307.         local leftX, rightX = math.huge, 0
  308.         local topY, botY = nil,nil
  309.        
  310.         local lcount = 0
  311.         for line in file:lines() do
  312.             lcount = lcount+1
  313.             table.insert(sprite.image, {})
  314.             for i=1,#line do
  315.                 if string.sub(line, i, i) ~= " " then
  316.                     leftX = math.min(leftX, i)
  317.                     rightX = math.max(rightX, i)
  318.                     if not topY then topY = lcount end
  319.                     botY = lcount
  320.                 end
  321.                 sprite.image[#sprite.image][i] = getColourOf(string.sub(line,i,i))
  322.             end
  323.             file:close()
  324.         end
  325.        
  326.         sprite.image.bounds = {
  327.             x = leftX,
  328.             width = rightX - leftX + 1,
  329.             y = topY,
  330.             height = botY - topY + 1
  331.         }
  332.         sprite.image.dimensions = {
  333.             width = rightX,
  334.             height = botY
  335.         }
  336.        
  337.         sprite.repaint = repaintS
  338.         sprite.rCollidesWith = rCollidesWith
  339.         sprite.pCollidesWith = pCollidesWith
  340.         sprite.draw = drawS
  341.         return sprite
  342.     else
  343.         error(path.." not found!")
  344.     end
  345. end
  346.  
  347. --Animations contain
  348.     --Everything a sprite contains, but the image is a series of frames, not just one image
  349.     --An timerID that tracks the last animation
  350.     --An upper and lower bound on the active animation
  351.     --An update method that takes a timer event and updates the animation if necessary
  352.  
  353. --[[
  354.  
  355. ]]--
  356. function loadAnimation(path, x, y, currentFrame)
  357.     local anim = {
  358.         type = "animation",
  359.         x = x,
  360.         y = y,
  361.         frames = { },
  362.         mirror = { x = false, y = false },
  363.         currentFrame = currentFrame
  364.     }
  365.    
  366.     table.insert(anim.frames, { })
  367.     if fs.exists(path) then
  368.         local file = io.open(path, "r")
  369.         local leftX, rightX = math.huge, 0
  370.         local topY, botY = nil,nil
  371.        
  372.         local lcount = 0
  373.         for line in file:lines() do
  374.             lcount = lcount+1
  375.             local cFrame = #anim.frames
  376.             print("["..line.."]")
  377.             if line == "~" then
  378.                 print(leftX," ",rightX," ",topY," ",botY)
  379.                 anim.frames[cFrame].bounds = {
  380.                     x = leftX,
  381.                     y = topY,
  382.                     width = rightX - leftX + 1,
  383.                     height = botY - topY + 1
  384.                 }
  385.                 anim.frames[cFrame].dimensions = {
  386.                     width = rightX,
  387.                     height = botY
  388.                 }
  389.                 table.insert(anim.frames, { })
  390.                 leftX, rightX = math.huge, 0
  391.                 topY, botY = nil,nil
  392.                 lcount = 0
  393.             else
  394.                 table.insert(anim.frames[cFrame], {})
  395.                 for i=1,#line do
  396.                     if string.sub(line, i, i) ~= " " then
  397.                         leftX = math.min(leftX, i)
  398.                         rightX = math.max(rightX, i)
  399.                         if not topY then topY = lcount end
  400.                         botY = lcount
  401.                     end
  402.                     anim.frames[cFrame][#anim.frames[cFrame]] [i] = getColourOf(string.sub(line,i,i))
  403.                 end
  404.             end
  405.         end
  406.         file:close()
  407.         local cFrame = #anim.frames
  408.         anim.frames[cFrame].bounds = {
  409.             x = leftX,
  410.             y = topY,
  411.             width = rightX - leftX + 1,
  412.             height = botY - topY + 1
  413.         }
  414.        
  415.         if not currentFrame or type(currentFrame) ~= "number" or currentFrame < 1 or
  416.                 currentFrame > #anim.frames then
  417.             anim.currentFrame = 1
  418.         end
  419.        
  420.         print("continue")
  421.         os.pullEvent("key")
  422.    
  423.         anim.timerID = nil
  424.         anim.lowerBound = 1
  425.         anim.upperBound = #anim.frames
  426.         anim.updating = false
  427.    
  428.         anim.repaint = repaintA
  429.         anim.rCollidesWith = rCollidesWith
  430.         anim.pCollidesWith = pCollidesWith
  431.         anim.draw = drawA
  432.         anim.update = updateA
  433.         anim.next = nextA
  434.         anim.previous = previousA
  435.         return anim
  436.     else
  437.         error(path.." not found!")
  438.     end
  439. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement