/ld26

To get this branch, use:
bzr branch /bzr/ld26
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
-- Class: Animation
-- An animation displays a sequence of frames. If you do not specify
-- a width and height for the sprite, it will size itself so that
-- it is a square, where each side is as tall as the source image's height.
--
--
-- Event: onEndSequence
-- Called whenever an animation sequence ends. It is passed the name
-- of the sequence that just ended.
-- 
-- Extends:
--		<Sprite>

Animation = Sprite:extend{
	-- Property: paused
	-- Set this to true to freeze the animation on the current frame.
	paused = false,

	-- Property: sequences
	-- A lookup table of sequences. Each one is stored by name and has
	-- the following properties:
	-- * name - string name for the sequence.
	-- * frames - table of frames to display. The first frame in the sheet is at index 1.
	-- * fps - frames per second.
	-- * loops - does the animation loop? defaults to true
	sequences = {},

	-- Property: image
	-- A string filename to the image to use as a sprite strip. A sprite
	-- strip can have multiple rows of frames.

	-- Property: currentSequence
	-- A reference to the current animation sequence table.

	-- Property: currentName
	-- The name of the current animation sequence.

	-- Property: currentFrame
	-- The current frame being displayed; starts at 1.

	-- Property: frameIndex
	-- Numeric index of the current frame in the current sequence; starts at 1.

	-- Property: frameTimer
	-- Time left before the animation changes to the next frame in seconds.
	-- Normally you shouldn't need to change this directly.

	-- private property: used to check whether the source image
	-- for our quad is up-to-date
	_set = {},

	-- private property imageObj: actual Image instance used to draw
	-- this is normally set via the image property, but you may set it directly
	-- so long as you never change that image property afterwards.

	new = function (self, obj)
		obj = obj or {}
		self:extend(obj)
		obj:updateQuad()

		if obj.onNew then obj:onNew() end
		return obj
	end,

	-- Method: play 
	-- Begins playing an animation in the sprite's library.
	-- If the animation is already playing, this has no effect.
	--
	-- Arguments:
	--		name - name of the animation
	--
	-- Returns:
	--		nothing

	play = function (self, name)
		if self.currentName == name and not self.paused then
			return
		end
		
		assert(self.sequences[name], 'no animation sequence named "' .. name .. '"')
		
		self.currentName = name
		self.currentSequence = self.sequences[name]
		self.frameIndex = 0
		self.frameTimer = 0
		self.paused = false
	end,

	-- Method: freeze
	-- Freezes the animation on the specified frame.
	--
	-- Arguments:
	--		* index - integer frame index relative to the entire sprite sheet,
	--				  starts at 1. If omitted, this freezes the current frame.
	--				  If there is no current frame, this freezes on the first frame.
	--
	-- Returns:
	--		nothing

	freeze = function (self, index)
		if self.currentSequence then
			index = index or self.currentSequence[self.frameIndex]
		end
		
		index = index or self.currentFrame or 1

		if self._set.image ~= self.image then
			self:updateQuad()
		end

		self.currentFrame = index
		self:updateFrame(index)
		self.paused = true
	end,

	-- private method: updateQuad
	-- sets up the sprite's quad property based on the image;
	-- needs to be called whenever the sprite's image property changes.
	--
	-- Arguments:
	--		none
	--
	-- Returns:
	--		nothing

	updateQuad = function (self)
		if self.image then 
			self._imageObj = Cached:image(self.image)
			if not self.width then self.width = self._imageObj:getHeight() end
			if not self.height then self.height = self.width end

			self._quad = love.graphics.newQuad(0, 0, self.width, self.height,
											  self._imageObj:getWidth(), self._imageObj:getHeight())
			self._imageWidth = self._imageObj:getWidth()
			self._set.image = self.image
		end
	end,

	-- private method: updateFrame
	-- changes the sprite's quad property based on the current frame;
	-- needs to be called whenever the sprite's currentFrame property changes.
	--
	-- Arguments:
	--		none
	--
	-- Returns:
	--		nothing

	updateFrame = function (self)
		assert(type(self.currentFrame) == 'number', "current frame is not a number")
		assert(self.image, "asked to set the frame of a nil image")

		if self._set.image ~= self.image then
			self:updateQuad()
		end

		local frameX = (self.currentFrame - 1) * self.width
		local viewportX = frameX % self._imageWidth
		local viewportY = self.height * math.floor(frameX / self._imageWidth)
		self._quad:setViewport(viewportX, viewportY, self.width, self.height)
	end,

	update = function (self, elapsed)
		-- move the animation frame forward

		if self.currentSequence and not self.paused then
			self.frameTimer = self.frameTimer - elapsed
			
			if self.frameTimer <= 0 then
				self.frameIndex = self.frameIndex + 1

				if self.frameIndex > #self.currentSequence.frames then
					if self.onEndSequence then self:onEndSequence(self.currentName) end

					if self.currentSequence.loops ~= false then
						self.frameIndex = 1
					else
						self.frameIndex = self.frameIndex - 1
						self.paused = true
					end
				end

				self.currentFrame = self.currentSequence.frames[self.frameIndex]
				self:updateFrame()
				self.frameTimer = 1 / self.currentSequence.fps
			end
		end

		Sprite.update(self, elapsed)
	end,

	draw = function (self, x, y)
		x = math.floor(x or self.x)
		y = math.floor(y or self.y)

		if STRICT then
			assert(type(x) == 'number', 'visible animation does not have a numeric x property')
			assert(type(y) == 'number', 'visible animation does not have a numeric y property')
			assert(type(self.width) == 'number', 'visible animation does not have a numeric width property')
			assert(type(self.height) == 'number', 'visible animation does not have a numeric height property')
		end

		if not self.visible or not self.image or self.alpha <= 0 then return end
		
		-- if our image changed, update the quad
		
		if self._set.image ~= self.image then
			self:updateQuad()
		end
		
		-- set color if needed

		local colored = self.alpha ~= 1 or self.tint[1] ~= 1 or self.tint[2] ~= 1 or self.tint[3] ~= 1

		if colored then
			love.graphics.setColor(self.tint[1] * 255, self.tint[2] * 255, self.tint[3] * 255, self.alpha * 255)
		end

		-- draw the quad

		local scaleX = self.scale * self.distort.x
		local scaleY = self.scale * self.distort.y

		if self.flipX then scaleX = scaleX * -1 end
		if self.flipY then scaleY = scaleY * -1 end
			
		love.graphics.drawq(self._imageObj, self._quad, x + self.width / 2, y + self.height / 2, self.rotation,
							scaleX, scaleY, self.width / 2, self.height / 2)
		
		-- reset color
		
		if colored then
			love.graphics.setColor(255, 255, 255, 255)
		end
	end,

	__tostring = function (self)
		local result = 'Animation (x: ' .. self.x .. ', y: ' .. self.y ..
					   ', w: ' .. self.width .. ', h: ' .. self.height .. ', '

		if self.currentName then
			result = result .. 'playing ' .. self.currentName .. ', '
		end

		result = result .. ' frame ' .. self.currentFrame .. ', '

		if self.active then
			result = result .. 'active, '
		else
			result = result .. 'inactive, '
		end

		if self.visible then
			result = result .. 'visible, '
		else
			result = result .. 'invisible, '
		end

		if self.solid then
			result = result .. 'solid'
		else
			result = result .. 'not solid'
		end

		return result .. ')'
	end
}