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 ¶m, 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