View difference between Paste ID: qypL4BkR and LVP1mWJG
SHOW: | | - or go back to the newest paste.
1
--[[			GameUtils
2-
	GameUtil
2+
3-
	An API for drawing sprites and animations made in NPaintPro
3+
	A simple API for creating optimized, graphically intensive games by Nitrogen Fingers
4-
	By NitrogenFingers
4+
5
	Version: 0.3a
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()
323+
324
		file:close()
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.."]")
376+
377
				anim.frames[cFrame].bounds = {
378-
				print(leftX," ",rightX," ",topY," ",botY)
378+
379
					y = topY,
380
					width = rightX - leftX + 1,
381
					height = botY - topY + 1
382
				}
383
				anim.frames[cFrame].dimensions = {
384
					width = rightX,
385
					height = botY
386
				}
387
				table.insert(anim.frames, { })
388
				leftX, rightX = math.huge, 0
389
				topY, botY = nil,nil
390
				lcount = 0
391
			else
392
				table.insert(anim.frames[cFrame], {})
393
				for i=1,#line do
394
					if string.sub(line, i, i) ~= " " then
395
						leftX = math.min(leftX, i)
396
						rightX = math.max(rightX, i)
397
						if not topY then topY = lcount end
398
						botY = lcount
399
					end
400
					anim.frames[cFrame][#anim.frames[cFrame]] [i] = getColourOf(string.sub(line,i,i))
401
				end
402
			end
403
		end
404
		file:close()
405
		local cFrame = #anim.frames
406
		anim.frames[cFrame].bounds = {
407
			x = leftX,
408
			y = topY,
409
			width = rightX - leftX + 1,
410
			height = botY - topY + 1
411
		}
412
		
413
		if not currentFrame or type(currentFrame) ~= "number" or currentFrame < 1 or 
414
				currentFrame > #anim.frames then 
415
			anim.currentFrame = 1 
416
		end
417
	
418
		anim.timerID = nil
419
		anim.lowerBound = 1
420-
		print("continue")
420+
421-
		os.pullEvent("key")
421+
422
	
423
		anim.repaint = repaintA
424
		anim.rCollidesWith = rCollidesWith
425
		anim.pCollidesWith = pCollidesWith
426
		anim.draw = drawA
427
		anim.update = updateA
428
		anim.next = nextA
429
		anim.previous = previousA
430
		return anim
431
	else
432
		error(path.." not found!")
433
	end
434
end