1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "neverhood/sprite.h"
24 #include "neverhood/screen.h"
25 
26 namespace Neverhood {
27 
28 // Sprite
29 
Sprite(NeverhoodEngine * vm,int objectPriority)30 Sprite::Sprite(NeverhoodEngine *vm, int objectPriority)
31 	: Entity(vm, objectPriority), _x(0), _y(0), _spriteUpdateCb(NULL), _filterXCb(NULL), _filterYCb(NULL),
32 	_dataResource(vm), _doDeltaX(false), _doDeltaY(false), _needRefresh(false), _flags(0), _surface(NULL) {
33 
34 	_drawOffset.x = 0;
35 	_drawOffset.y = 0;
36 	_drawOffset.width = 0;
37 	_drawOffset.height = 0;
38 	_collisionBounds.x1 = 0;
39 	_collisionBounds.y1 = 0;
40 	_collisionBounds.x2 = 0;
41 	_collisionBounds.y2 = 0;
42 	_collisionBoundsOffset.x = 0;
43 	_collisionBoundsOffset.y = 0;
44 	_collisionBoundsOffset.width = 0;
45 	_collisionBoundsOffset.height = 0;
46 
47 	SetMessageHandler(&Sprite::handleMessage);
48 }
49 
~Sprite()50 Sprite::~Sprite() {
51 	delete _surface;
52 }
53 
updateBounds()54 void Sprite::updateBounds() {
55 	if (_doDeltaX) {
56 		_collisionBounds.x1 = _x - _collisionBoundsOffset.x - _collisionBoundsOffset.width + 1;
57 		_collisionBounds.x2 = _x - _collisionBoundsOffset.x;
58 	} else {
59 		_collisionBounds.x1 = _x + _collisionBoundsOffset.x;
60 		_collisionBounds.x2 = _x + _collisionBoundsOffset.x + _collisionBoundsOffset.width - 1;
61 	}
62 	if (_doDeltaY) {
63 		_collisionBounds.y1 = _y - _collisionBoundsOffset.y - _collisionBoundsOffset.height + 1;
64 		_collisionBounds.y2 = _y - _collisionBoundsOffset.y;
65 	} else {
66 		_collisionBounds.y1 = _y + _collisionBoundsOffset.y;
67 		_collisionBounds.y2 = _y + _collisionBoundsOffset.y + _collisionBoundsOffset.height - 1;
68 	}
69 }
70 
setDoDeltaX(int type)71 void Sprite::setDoDeltaX(int type) {
72 	// Clear, set or toggle
73 	_doDeltaX = type == 2 ? !_doDeltaX : type == 1;
74 }
75 
setDoDeltaY(int type)76 void Sprite::setDoDeltaY(int type) {
77 	// Clear, set or toggle
78 	_doDeltaY = type == 2 ? !_doDeltaY : type == 1;
79 }
80 
isPointInside(int16 x,int16 y)81 bool Sprite::isPointInside(int16 x, int16 y) {
82 	return x >= _collisionBounds.x1 && x <= _collisionBounds.x2 && y >= _collisionBounds.y1 && y <= _collisionBounds.y2;
83 }
84 
checkCollision(NRect & rect)85 bool Sprite::checkCollision(NRect &rect) {
86 	return (_collisionBounds.x1 < rect.x2) && (rect.x1 < _collisionBounds.x2) && (_collisionBounds.y1 < rect.y2) && (rect.y1 < _collisionBounds.y2);
87 }
88 
handleMessage(int messageNum,const MessageParam & param,Entity * sender)89 uint32 Sprite::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
90 	return 0;
91 }
92 
loadDataResource(uint32 fileHash)93 void Sprite::loadDataResource(uint32 fileHash) {
94 	_dataResource.load(fileHash);
95 }
96 
createSurface(int surfacePriority,int16 width,int16 height)97 void Sprite::createSurface(int surfacePriority, int16 width, int16 height) {
98 	_surface = new BaseSurface(_vm, surfacePriority, width, height, "sprite");
99 }
100 
defFilterY(int16 y)101 int16 Sprite::defFilterY(int16 y) {
102 	return y - _vm->_screen->getYOffset();
103 }
104 
setClipRect(int16 x1,int16 y1,int16 x2,int16 y2)105 void Sprite::setClipRect(int16 x1, int16 y1, int16 x2, int16 y2) {
106 	NRect &clipRect = _surface->getClipRect();
107 	clipRect.x1 = x1;
108 	clipRect.y1 = y1;
109 	clipRect.x2 = x2;
110 	clipRect.y2 = y2;
111 }
112 
setClipRect(NRect & clipRect)113 void Sprite::setClipRect(NRect& clipRect) {
114 	_surface->getClipRect() = clipRect;
115 }
116 
setClipRect(NDrawRect & drawRect)117 void Sprite::setClipRect(NDrawRect& drawRect) {
118 	setClipRect(drawRect.x, drawRect.y, drawRect.x2(), drawRect.y2());
119 }
120 
121 // StaticSprite
122 
StaticSprite(NeverhoodEngine * vm,int objectPriority)123 StaticSprite::StaticSprite(NeverhoodEngine *vm, int objectPriority)
124 	: Sprite(vm, objectPriority), _spriteResource(vm) {
125 
126 }
127 
StaticSprite(NeverhoodEngine * vm,uint32 fileHash,int surfacePriority,int16 x,int16 y)128 StaticSprite::StaticSprite(NeverhoodEngine *vm, uint32 fileHash, int surfacePriority, int16 x, int16 y)
129 	: Sprite(vm, 0), _spriteResource(vm) {
130 
131 	_spriteResource.load(fileHash, true);
132 	createSurface(surfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height);
133 	_x = x == kDefPosition ? _spriteResource.getPosition().x : x;
134 	_y = y == kDefPosition ? _spriteResource.getPosition().y : y;
135 	_drawOffset.set(0, 0, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height);
136 	_needRefresh = true;
137 	updatePosition();
138 }
139 
loadSprite(uint32 fileHash,uint flags,int surfacePriority,int16 x,int16 y)140 void StaticSprite::loadSprite(uint32 fileHash, uint flags, int surfacePriority, int16 x, int16 y) {
141 	_spriteResource.load(fileHash, true);
142 	if (!_surface)
143 		createSurface(surfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height);
144 	if (flags & kSLFDefDrawOffset)
145 		_drawOffset.set(0, 0, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height);
146 	else if (flags & kSLFCenteredDrawOffset)
147 		_drawOffset.set(-(_spriteResource.getDimensions().width / 2), -(_spriteResource.getDimensions().height / 2),
148 			_spriteResource.getDimensions().width, _spriteResource.getDimensions().height);
149 	if (flags & kSLFDefPosition) {
150 		_x = _spriteResource.getPosition().x;
151 		_y = _spriteResource.getPosition().y;
152 	} else if (flags & kSLFSetPosition) {
153 		_x = x;
154 		_y = y;
155 	}
156 	if (flags & kSLFDefCollisionBoundsOffset) {
157 		_collisionBoundsOffset = _drawOffset;
158 		updateBounds();
159 	}
160 	_needRefresh = true;
161 	updatePosition();
162 }
163 
updatePosition()164 void StaticSprite::updatePosition() {
165 
166 	if (!_surface)
167 		return;
168 
169 	if (_doDeltaX) {
170 		_surface->getDrawRect().x = filterX(_x - _drawOffset.x - _drawOffset.width + 1);
171 	} else {
172 		_surface->getDrawRect().x = filterX(_x + _drawOffset.x);
173 	}
174 
175 	if (_doDeltaY) {
176 		_surface->getDrawRect().y = filterY(_y - _drawOffset.y - _drawOffset.height + 1);
177 	} else {
178 		_surface->getDrawRect().y = filterY(_y + _drawOffset.y);
179 	}
180 
181 	if (_needRefresh) {
182 		_surface->drawSpriteResourceEx(_spriteResource, _doDeltaX, _doDeltaY, _drawOffset.width, _drawOffset.height);
183 		_needRefresh = false;
184 	}
185 
186 }
187 
188 // AnimatedSprite
189 
AnimatedSprite(NeverhoodEngine * vm,int objectPriority)190 AnimatedSprite::AnimatedSprite(NeverhoodEngine *vm, int objectPriority)
191 	: Sprite(vm, objectPriority), _animResource(vm) {
192 
193 	init();
194 }
195 
AnimatedSprite(NeverhoodEngine * vm,uint32 fileHash,int surfacePriority,int16 x,int16 y)196 AnimatedSprite::AnimatedSprite(NeverhoodEngine *vm, uint32 fileHash, int surfacePriority, int16 x, int16 y)
197 	: Sprite(vm, 1100), _animResource(vm) {
198 
199 	init();
200 	SetUpdateHandler(&AnimatedSprite::update);
201 	createSurface1(fileHash, surfacePriority);
202 	_x = x;
203 	_y = y;
204 	startAnimation(fileHash, 0, -1);
205 }
206 
init()207 void AnimatedSprite::init() {
208 	_currFrameTicks = 0;
209 	_newAnimFileHash = 0;
210 	_deltaX = 0;
211 	_deltaY = 0;
212 	_nextAnimFileHash = 0;
213 	_plFirstFrameIndex = 0;
214 	_currFrameIndex = 0;
215 	_currStickFrameIndex = -1;
216 	_finalizeStateCb = NULL;
217 	_currStateCb = NULL;
218 	_nextStateCb = NULL;
219 	_newStickFrameIndex = -1;
220 	_newStickFrameHash = 0;
221 	_frameChanged = false;
222 	_replOldColor = 0;
223 	_replNewColor = 0;
224 	_animResource.setReplEnabled(false);
225 	_playBackwards = false;
226 	_currAnimFileHash = 0;
227 	_lastFrameIndex = 0;
228 	_plLastFrameIndex = 0;
229 	_plFirstFrameHash = 0;
230 	_plLastFrameHash = 0;
231 	_animStatus = 0;
232 }
233 
update()234 void AnimatedSprite::update() {
235 	updateAnim();
236 	handleSpriteUpdate();
237 	updatePosition();
238 }
239 
updateDeltaXY()240 void AnimatedSprite::updateDeltaXY() {
241 	if (_doDeltaX) {
242 		_x -= _deltaX;
243 	} else {
244 		_x += _deltaX;
245 	}
246 	if (_doDeltaY) {
247 		_y -= _deltaY;
248 	} else {
249 		_y += _deltaY;
250 	}
251 	_deltaX = 0;
252 	_deltaY = 0;
253 	updateBounds();
254 }
255 
setRepl(byte oldColor,byte newColor)256 void AnimatedSprite::setRepl(byte oldColor, byte newColor) {
257 	_replOldColor = oldColor;
258 	_replNewColor = newColor;
259 	_animResource.setReplEnabled(true);
260 }
261 
clearRepl()262 void AnimatedSprite::clearRepl() {
263 	_replOldColor = 0;
264 	_replNewColor = 0;
265 	_animResource.setReplEnabled(false);
266 }
267 
updateAnim()268 void AnimatedSprite::updateAnim() {
269 
270 	_frameChanged = false;
271 
272 	if (_newAnimFileHash == 0) {
273 		if (_newStickFrameIndex != -1) {
274 			_currStickFrameIndex = _newStickFrameIndex == STICK_LAST_FRAME ? _animResource.getFrameCount() - 1 : _newStickFrameIndex;
275 			_newStickFrameIndex = -1;
276 		} else if (_newStickFrameHash != 0) {
277 			_currStickFrameIndex = MAX<int16>(0, _animResource.getFrameIndex(_newStickFrameHash));
278 			_newStickFrameHash = 0;
279 		}
280 		if (_newAnimFileHash == 0 && _currFrameIndex != _currStickFrameIndex) {
281 			if (_currFrameTicks != 0 && (--_currFrameTicks == 0) && _animResource.getFrameCount() != 0) {
282 
283 				if (_nextAnimFileHash != 0) {
284 					if (_animResource.load(_nextAnimFileHash)) {
285 						_currAnimFileHash = _nextAnimFileHash;
286 					} else {
287 						_animResource.load(calcHash("sqDefault"));
288 						_currAnimFileHash = 0;
289 					}
290 					if (_replOldColor != _replNewColor) {
291 						_animResource.setRepl(_replOldColor, _replNewColor);
292 					}
293 					_nextAnimFileHash = 0;
294 					if (_animStatus != 0) {
295 						_currFrameIndex = _plFirstFrameHash != 0 ? MAX<int16>(0, _animResource.getFrameIndex(_plFirstFrameHash)) : 0;
296 						_lastFrameIndex = _plLastFrameHash != 0 ? MAX<int16>(0, _animResource.getFrameIndex(_plLastFrameHash)) : _animResource.getFrameCount() - 1;
297 					} else {
298 						_currFrameIndex = _plFirstFrameIndex != -1 ? _plFirstFrameIndex : _animResource.getFrameCount() - 1;
299 						_lastFrameIndex = _plLastFrameIndex != -1 ? _plLastFrameIndex : _animResource.getFrameCount() - 1;
300 					}
301 				} else {
302 					updateFrameIndex();
303 				}
304 				if (_newAnimFileHash == 0)
305 					updateFrameInfo();
306 			}
307 		}
308 	}
309 
310 	if (_newAnimFileHash != 0) {
311 		if (_animStatus == 2) {
312 			_currStickFrameIndex = _currFrameIndex;
313 		} else {
314 			if (_animStatus == 1) {
315 				if (_animResource.load(_newAnimFileHash)) {
316 					_currAnimFileHash = _newAnimFileHash;
317 				} else {
318 					_animResource.load(calcHash("sqDefault"));
319 					_currAnimFileHash = 0;
320 				}
321 				if (_replOldColor != _replNewColor) {
322 					_animResource.setRepl(_replOldColor, _replNewColor);
323 				}
324 				_newAnimFileHash = 0;
325 				_currFrameIndex = _plFirstFrameHash != 0 ? MAX<int16>(0, _animResource.getFrameIndex(_plFirstFrameHash)) : 0;
326 				_lastFrameIndex = _plLastFrameHash != 0 ? MAX<int16>(0, _animResource.getFrameIndex(_plLastFrameHash)) : _animResource.getFrameCount() - 1;
327 			} else {
328 				if (_animResource.load(_newAnimFileHash)) {
329 					_currAnimFileHash = _newAnimFileHash;
330 				} else {
331 					_animResource.load(calcHash("sqDefault"));
332 					_currAnimFileHash = 0;
333 				}
334 				if (_replOldColor != _replNewColor) {
335 					_animResource.setRepl(_replOldColor, _replNewColor);
336 				}
337 				_newAnimFileHash = 0;
338 				_currFrameIndex = _plFirstFrameIndex != -1 ? _plFirstFrameIndex : _animResource.getFrameCount() - 1;
339 				_lastFrameIndex = _plLastFrameIndex != -1 ? _plLastFrameIndex : _animResource.getFrameCount() - 1;
340 			}
341 			updateFrameInfo();
342 		}
343 
344 		if (_newStickFrameIndex != -1) {
345 			_currStickFrameIndex = _newStickFrameIndex == STICK_LAST_FRAME ? _animResource.getFrameCount() - 1 : _newStickFrameIndex;
346 			_newStickFrameIndex = -1;
347 		} else if (_newStickFrameHash != 0) {
348 			_currStickFrameIndex = MAX<int16>(0, _animResource.getFrameIndex(_newStickFrameHash));
349 			_newStickFrameHash = 0;
350 		}
351 
352 	}
353 
354 }
355 
updatePosition()356 void AnimatedSprite::updatePosition() {
357 
358 	if (!_surface)
359 		return;
360 
361 	if (_doDeltaX) {
362 		_surface->getDrawRect().x = filterX(_x - _drawOffset.x - _drawOffset.width + 1);
363 	} else {
364 		_surface->getDrawRect().x = filterX(_x + _drawOffset.x);
365 	}
366 
367 	if (_doDeltaY) {
368 		_surface->getDrawRect().y = filterY(_y - _drawOffset.y - _drawOffset.height + 1);
369 	} else {
370 		_surface->getDrawRect().y = filterY(_y + _drawOffset.y);
371 	}
372 
373 	if (_needRefresh) {
374 		_surface->drawAnimResource(_animResource, _currFrameIndex, _doDeltaX, _doDeltaY, _drawOffset.width, _drawOffset.height);
375 		_needRefresh = false;
376 	}
377 
378 }
379 
updateFrameIndex()380 void AnimatedSprite::updateFrameIndex() {
381 	if (!_playBackwards) {
382 		if (_currFrameIndex < _lastFrameIndex) {
383 			_currFrameIndex++;
384 		} else {
385 			// Inform self about end of current animation
386 			// The caller can then e.g. set a new animation fileHash
387 			sendMessage(this, NM_ANIMATION_STOP, 0);
388 			if (_newAnimFileHash == 0)
389 				_currFrameIndex = 0;
390 		}
391 	} else {
392 		if (_currFrameIndex > 0) {
393 			_currFrameIndex--;
394 		} else {
395 			sendMessage(this, NM_ANIMATION_STOP, 0);
396 			if (_newAnimFileHash == 0)
397 				_currFrameIndex = _lastFrameIndex;
398 		}
399 	}
400 }
401 
updateFrameInfo()402 void AnimatedSprite::updateFrameInfo() {
403 	debug(8, "AnimatedSprite::updateFrameInfo()");
404 	const AnimFrameInfo &frameInfo = _animResource.getFrameInfo(_currFrameIndex);
405 	_frameChanged = true;
406 	_drawOffset = frameInfo.drawOffset;
407 	_deltaX = frameInfo.deltaX;
408 	_deltaY = frameInfo.deltaY;
409 	_collisionBoundsOffset = frameInfo.collisionBoundsOffset;
410 	_currFrameTicks = frameInfo.counter;
411 	updateBounds();
412 	_needRefresh = true;
413 	if (frameInfo.frameHash != 0)
414 		sendMessage(this, NM_ANIMATION_START, frameInfo.frameHash);
415 }
416 
createSurface1(uint32 fileHash,int surfacePriority)417 void AnimatedSprite::createSurface1(uint32 fileHash, int surfacePriority) {
418 	NDimensions dimensions = _animResource.loadSpriteDimensions(fileHash);
419 	_surface = new BaseSurface(_vm, surfacePriority, dimensions.width, dimensions.height, "animated sprite");
420 }
421 
createShadowSurface1(BaseSurface * shadowSurface,uint32 fileHash,int surfacePriority)422 void AnimatedSprite::createShadowSurface1(BaseSurface *shadowSurface, uint32 fileHash, int surfacePriority) {
423 	NDimensions dimensions = _animResource.loadSpriteDimensions(fileHash);
424 	_surface = new ShadowSurface(_vm, surfacePriority, dimensions.width, dimensions.height, shadowSurface);
425 }
426 
createShadowSurface(BaseSurface * shadowSurface,int16 width,int16 height,int surfacePriority)427 void AnimatedSprite::createShadowSurface(BaseSurface *shadowSurface, int16 width, int16 height, int surfacePriority) {
428 	_surface = new ShadowSurface(_vm, surfacePriority, width, height, shadowSurface);
429 }
430 
startAnimation(uint32 fileHash,int16 plFirstFrameIndex,int16 plLastFrameIndex)431 void AnimatedSprite::startAnimation(uint32 fileHash, int16 plFirstFrameIndex, int16 plLastFrameIndex) {
432 	debug(2, "AnimatedSprite::startAnimation(%08X, %d, %d)", fileHash, plFirstFrameIndex, plLastFrameIndex);
433 	_newAnimFileHash = fileHash;
434 	_plFirstFrameIndex = plFirstFrameIndex;
435 	_plLastFrameIndex = plLastFrameIndex;
436 	_newStickFrameHash = 0;
437 	_animStatus = 0;
438 	_playBackwards = false;
439 	_newStickFrameIndex = -1;
440 	_currStickFrameIndex = -1;
441 }
442 
stopAnimation()443 void AnimatedSprite::stopAnimation() {
444 	_newAnimFileHash = 1;
445 	_animStatus = 2;
446 }
447 
startAnimationByHash(uint32 fileHash,uint32 plFirstFrameHash,uint32 plLastFrameHash)448 void AnimatedSprite::startAnimationByHash(uint32 fileHash, uint32 plFirstFrameHash, uint32 plLastFrameHash) {
449 	debug(2, "AnimatedSprite::startAnimationByHash(%08X, %08X, %08X)", fileHash, plFirstFrameHash, plLastFrameHash);
450 	_newAnimFileHash = fileHash;
451 	_plFirstFrameHash = plFirstFrameHash;
452 	_plLastFrameHash = plLastFrameHash;
453 	_newStickFrameHash = 0;
454 	_animStatus = 1;
455 	_playBackwards = false;
456 	_newStickFrameIndex = -1;
457 	_currStickFrameIndex = -1;
458 }
459 
nextAnimationByHash(uint32 fileHash2,uint32 plFirstFrameHash,uint32 plLastFrameHash)460 void AnimatedSprite::nextAnimationByHash(uint32 fileHash2, uint32 plFirstFrameHash, uint32 plLastFrameHash) {
461 	_nextAnimFileHash = fileHash2;
462 	_plFirstFrameHash = plFirstFrameHash;
463 	_plLastFrameHash = plLastFrameHash;
464 	_newStickFrameHash = 0;
465 	_animStatus = 1;
466 	_playBackwards = false;
467 	_newStickFrameIndex = -1;
468 	_currStickFrameIndex = -1;
469 }
470 
setFinalizeState(AnimationCb finalizeStateCb)471 void AnimatedSprite::setFinalizeState(AnimationCb finalizeStateCb) {
472 	if (_finalizeStateCb)
473 		(this->*_finalizeStateCb)();
474 	_finalizeStateCb = finalizeStateCb;
475 }
476 
gotoState(AnimationCb currStateCb)477 void AnimatedSprite::gotoState(AnimationCb currStateCb) {
478 	if (_finalizeStateCb) {
479 		AnimationCb cb = _finalizeStateCb;
480 		_finalizeStateCb = NULL;
481 		(this->*cb)();
482 	}
483 	_nextStateCb = NULL;
484 	_currStateCb = currStateCb;
485 	if (_currStateCb)
486 		(this->*_currStateCb)();
487 }
488 
gotoNextState()489 void AnimatedSprite::gotoNextState() {
490 	if (_finalizeStateCb) {
491 		AnimationCb cb = _finalizeStateCb;
492 		_finalizeStateCb = NULL;
493 		(this->*cb)();
494 	}
495 	if (_nextStateCb) {
496 		_currStateCb = _nextStateCb;
497 		_nextStateCb = NULL;
498 		(this->*_currStateCb)();
499 	} else {
500 		_currStateCb = NULL;
501 	}
502 }
503 
504 } // End of namespace Neverhood
505