Files
scummvm/engines/twine/animations.cpp
T

1121 lines
36 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "twine/animations.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "twine/actor.h"
#include "twine/collision.h"
#include "twine/gamestate.h"
#include "twine/grid.h"
#include "twine/movements.h"
#include "twine/renderer.h"
#include "twine/resources.h"
#include "twine/scene.h"
#include "twine/sound.h"
#include "twine/twine.h"
namespace TwinE {
static const int32 magicLevelStrengthOfHit[] = {
kNoBallStrength,
kYellowBallStrength,
kGreenBallStrength,
kRedBallStrength,
kFireBallStrength,
0
};
enum ActionType {
ACTION_HITTING = 0,
ACTION_SAMPLE = 1,
ACTION_SAMPLE_FREQ = 2,
ACTION_THROW_EXTRA_BONUS = 3,
ACTION_THROW_MAGIC_BALL = 4,
ACTION_SAMPLE_REPEAT = 5,
ACTION_UNKNOWN_6 = 6,
ACTION_UNKNOWN_7 = 7,
ACTION_SAMPLE_STOP = 8,
ACTION_UNKNOWN_9 = 9, // unused
ACTION_SAMPLE_BRICK_1 = 10,
ACTION_SAMPLE_BRICK_2 = 11,
ACTION_HERO_HITTING = 12,
ACTION_UNKNOWN_13 = 13,
ACTION_UNKNOWN_14 = 14,
ACTION_UNKNOWN_15 = 15,
ACTION_LAST
};
Animations::Animations(TwinEEngine *engine) : _engine(engine) {
animBuffer1 = animBuffer2 = (uint8 *)malloc(5000 * sizeof(uint8));
}
Animations::~Animations() {
free(animBuffer1);
}
int32 Animations::setAnimAtKeyframe(int32 keyframeIdx, uint8 *animPtr, uint8 *bodyPtr, AnimTimerDataStruct *animTimerDataPtr) {
const int16 numOfKeyframeInAnim = READ_LE_INT16(animPtr);
if (keyframeIdx >= numOfKeyframeInAnim) {
return numOfKeyframeInAnim;
}
int16 numOfBonesInAnim = READ_LE_INT16(animPtr + 2);
const uint8 *ptrToData = (const uint8 *)((numOfBonesInAnim * 8 + 8) * keyframeIdx + animPtr + 8);
const int16 bodyHeader = READ_LE_INT16(bodyPtr);
if (!(bodyHeader & 2)) {
return 0;
}
uint8 *ptrToBodyData = bodyPtr + 14;
animTimerDataPtr->ptr = ptrToData;
animTimerDataPtr->time = _engine->lbaTime;
ptrToBodyData = ptrToBodyData + READ_LE_INT16(ptrToBodyData) + 2;
const int16 numOfElementInBody = READ_LE_INT16(ptrToBodyData);
ptrToBodyData = ptrToBodyData + numOfElementInBody * 6 + 12;
const int16 numOfPointInBody = READ_LE_INT16(ptrToBodyData - 10); // num elements
if (numOfBonesInAnim > numOfPointInBody) {
numOfBonesInAnim = numOfPointInBody;
}
const uint8 *ptrToDataBackup = ptrToData;
ptrToData += 8;
do {
for (int32 i = 0; i < 8; i++) {
*(ptrToBodyData++) = *(ptrToData++);
}
ptrToBodyData += 30;
} while (--numOfBonesInAnim);
ptrToData = ptrToDataBackup + 2;
currentStepX = READ_LE_INT16(ptrToData);
currentStepY = READ_LE_INT16(ptrToData + 2);
currentStepZ = READ_LE_INT16(ptrToData + 4);
processRotationByAnim = READ_LE_INT16(ptrToData + 6);
processLastRotationAngle = ToAngle(READ_LE_INT16(ptrToData + 10));
return 1;
}
int32 Animations::getNumKeyframes(const uint8 *animPtr) {
return READ_LE_INT16(animPtr);
}
int32 Animations::getStartKeyframe(const uint8 *animPtr) {
return READ_LE_INT16(animPtr + 4);
}
void Animations::applyAnimStepRotation(uint8 **ptr, int32 bp, int32 bx, const uint8 **keyFramePtr, const uint8 **lastKeyFramePtr) {
const int16 lastAngle = ClampAngle(READ_LE_INT16(*lastKeyFramePtr));
*lastKeyFramePtr += 2;
const int16 newAngle = ClampAngle(READ_LE_INT16(*keyFramePtr));
*keyFramePtr += 2;
int16 angleDiff = newAngle - lastAngle;
int16 computedAngle;
if (angleDiff) {
if (angleDiff < -ANGLE_180) {
angleDiff += ANGLE_360;
} else if (angleDiff > ANGLE_180) {
angleDiff -= ANGLE_360;
}
computedAngle = lastAngle + (angleDiff * bp) / bx;
} else {
computedAngle = lastAngle;
}
int16 *dest = (int16 *)*(ptr);
*dest = ClampAngle(computedAngle);
*(ptr) = *(ptr) + 2;
}
void Animations::applyAnimStep(uint8 **ptr, int32 bp, int32 bx, const uint8 **keyFramePtr, const uint8 **lastKeyFramePtr) {
int16 lastAngle = READ_LE_INT16(*lastKeyFramePtr);
*lastKeyFramePtr += 2;
int16 newAngle = READ_LE_INT16(*keyFramePtr);
*keyFramePtr += 2;
int16 angleDif = newAngle - lastAngle;
int16 computedAngle;
if (angleDif) {
computedAngle = lastAngle + (angleDif * bp) / bx;
} else {
computedAngle = lastAngle;
}
int16 *dest = (int16 *)*(ptr);
*dest = computedAngle;
*(ptr) = *(ptr) + 2;
}
int32 Animations::getAnimMode(uint8 **ptr, const uint8 **keyFramePtr, const uint8 **lastKeyFramePtr) {
int16 *lptr = (int16 *)*ptr;
int16 opcode = READ_LE_INT16(*keyFramePtr);
*(int16 *)(lptr) = opcode;
*keyFramePtr += 2;
*(ptr) = *(ptr) + 2;
*lastKeyFramePtr += 2;
return opcode;
}
bool Animations::setModelAnimation(int32 animState, const uint8 *animPtr, uint8 *bodyPtr, AnimTimerDataStruct *animTimerDataPtr) {
int32 numOfPointInAnim = READ_LE_INT16(animPtr + 2);
const uint8* keyFramePtr = ((numOfPointInAnim * 8 + 8) * animState) + animPtr + 8;
int32 keyFrameLength = READ_LE_INT16(keyFramePtr);
int16 bodyHeader = READ_LE_INT16(bodyPtr);
if (!(bodyHeader & 2)) {
return false;
}
uint8 *edi = bodyPtr + 16;
const uint8 *ebx = animTimerDataPtr->ptr;
int32 ebp = animTimerDataPtr->time;
if (!ebx) {
ebx = keyFramePtr;
ebp = keyFrameLength;
}
const uint8* lastKeyFramePtr = ebx;
int32 eax = READ_LE_INT16(edi - 2);
edi += eax;
eax = READ_LE_INT16(edi);
eax = eax + eax * 2;
edi = edi + eax * 2 + 12;
int32 numOfPointInBody = READ_LE_INT16(edi - 10);
if (numOfPointInAnim > numOfPointInBody) {
numOfPointInAnim = numOfPointInBody;
}
eax = _engine->lbaTime - ebp;
if (eax >= keyFrameLength) {
const int32 *sourcePtr = (const int32 *)(keyFramePtr + 8);
int32 *destPtr = (int32 *)edi; // keyframe
do {
*(destPtr++) = *(sourcePtr++);
*(destPtr++) = *(sourcePtr++);
destPtr = (int32 *)(((int8 *)destPtr) + 30);
} while (--numOfPointInAnim);
animTimerDataPtr->ptr = keyFramePtr;
animTimerDataPtr->time = _engine->lbaTime;
currentStepX = READ_LE_INT16(keyFramePtr + 2);
currentStepY = READ_LE_INT16(keyFramePtr + 4);
currentStepZ = READ_LE_INT16(keyFramePtr + 6);
processRotationByAnim = READ_LE_INT16(keyFramePtr + 8);
processLastRotationAngle = ToAngle(READ_LE_INT16(keyFramePtr + 12));
return true;
}
const uint8 *keyFramePtrOld = keyFramePtr;
lastKeyFramePtr += 8;
keyFramePtr += 8;
processRotationByAnim = READ_LE_INT16(keyFramePtr);
processLastRotationAngle = ToAngle((READ_LE_INT16(keyFramePtr + 4) * eax) / keyFrameLength);
lastKeyFramePtr += 8;
keyFramePtr += 8;
edi += 38;
if (--numOfPointInAnim) {
int16 tmpNumOfPoints = numOfPointInAnim;
do {
int16 animOpcode = getAnimMode(&edi, &keyFramePtr, &lastKeyFramePtr);
switch (animOpcode) {
case 0: // allow global rotate
applyAnimStepRotation(&edi, eax, keyFrameLength, &keyFramePtr, &lastKeyFramePtr);
applyAnimStepRotation(&edi, eax, keyFrameLength, &keyFramePtr, &lastKeyFramePtr);
applyAnimStepRotation(&edi, eax, keyFrameLength, &keyFramePtr, &lastKeyFramePtr);
break;
case 1: // dissallow global rotate
case 2: // dissallow global rotate + hide
applyAnimStep(&edi, eax, keyFrameLength, &keyFramePtr, &lastKeyFramePtr);
applyAnimStep(&edi, eax, keyFrameLength, &keyFramePtr, &lastKeyFramePtr);
applyAnimStep(&edi, eax, keyFrameLength, &keyFramePtr, &lastKeyFramePtr);
break;
default:
error("Unsupported animation rotation mode %d", animOpcode);
}
edi += 30;
} while (--tmpNumOfPoints);
}
currentStepX = (READ_LE_INT16(keyFramePtrOld + 2) * eax) / keyFrameLength;
currentStepY = (READ_LE_INT16(keyFramePtrOld + 4) * eax) / keyFrameLength;
currentStepZ = (READ_LE_INT16(keyFramePtrOld + 6) * eax) / keyFrameLength;
return false;
}
int32 Animations::getBodyAnimIndex(AnimationTypes animIdx, int32 actorIdx) {
uint8 *costumePtr = nullptr;
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
uint8 *bodyPtr = actor->entityDataPtr;
do {
int8 type = *(bodyPtr++);
if (type == -1) {
currentActorAnimExtraPtr = nullptr;
return -1;
}
uint8 *ptr = (bodyPtr + 1);
if (type == 3) {
if (animIdx == (AnimationTypes)*bodyPtr) {
ptr++;
uint16 realAnimIdx = READ_LE_INT16(ptr);
ptr += 2;
uint8 *ptr2 = ptr;
ptr++;
if (*ptr2 != 0) {
costumePtr = ptr - 1;
}
currentActorAnimExtraPtr = costumePtr;
return realAnimIdx;
}
}
bodyPtr = *ptr + ptr;
} while (1);
return 0;
}
int32 Animations::stockAnimation(uint8 *bodyPtr, AnimTimerDataStruct *animTimerDataPtr) {
uint8 *animPtr = animBuffer2;
int32 playAnim = READ_LE_INT16(bodyPtr);
if (!(playAnim & 2)) {
return 0;
}
const uint8 *ptr = (bodyPtr + 0x10);
animTimerDataPtr->time = _engine->lbaTime;
animTimerDataPtr->ptr = animPtr;
int32 var0 = READ_LE_INT16(ptr - 2);
ptr = ptr + var0;
int32 var1 = READ_LE_INT16(ptr);
var1 = var1 + var1 * 2;
ptr = ptr + var1 * 2 + 2;
int32 var2 = READ_LE_INT16(ptr);
int32 counter = var2;
var2 = (var2 * 8) + 8;
int32 *edi = (int32 *)(animPtr + 8);
const int32 *esi = (const int32 *)(ptr + 10);
do {
*(edi++) = *(esi++);
*(edi++) = *(esi++);
esi = (const int32 *)(((const int8 *)esi) + 30);
} while (counter--);
animBuffer2 += var2;
if (animBuffer1 + 4488 < animBuffer2) {
animBuffer2 = animBuffer1;
}
return var2;
}
int32 Animations::verifyAnimAtKeyframe(int32 animIdx, uint8 *animPtr, uint8 *bodyPtr, AnimTimerDataStruct *animTimerDataPtr) {
const int32 numOfPointInAnim = READ_LE_INT16(animPtr + 2);
const uint8 *keyFramePtr = ((numOfPointInAnim * 8 + 8) * animIdx) + animPtr + 8;
const int32 keyFrameLength = READ_LE_INT16(keyFramePtr);
const int16 bodyHeader = READ_LE_INT16(bodyPtr);
if (!(bodyHeader & 2)) {
return 0;
}
const uint8 *ebx = animTimerDataPtr->ptr;
int32 ebp = animTimerDataPtr->time;
if (!ebx) {
ebx = keyFramePtr;
ebp = keyFrameLength;
}
const uint8* lastKeyFramePtr = ebx;
const int32 eax = _engine->lbaTime - ebp;
if (eax >= keyFrameLength) {
animTimerDataPtr->ptr = keyFramePtr;
animTimerDataPtr->time = _engine->lbaTime;
currentStepX = READ_LE_INT16(keyFramePtr + 2);
currentStepY = READ_LE_INT16(keyFramePtr + 4);
currentStepZ = READ_LE_INT16(keyFramePtr + 6);
processRotationByAnim = READ_LE_INT16(keyFramePtr + 8);
processLastRotationAngle = ToAngle(READ_LE_INT16(keyFramePtr + 12));
return 1;
}
const uint8 *keyFramePtrOld = keyFramePtr;
lastKeyFramePtr += 8;
keyFramePtr += 8;
processRotationByAnim = READ_LE_INT16(keyFramePtr);
processLastRotationAngle = ToAngle((READ_LE_INT16(keyFramePtr + 4) * eax) / keyFrameLength);
lastKeyFramePtr += 8;
keyFramePtr += 8;
currentStepX = (READ_LE_INT16(keyFramePtrOld + 2) * eax) / keyFrameLength;
currentStepY = (READ_LE_INT16(keyFramePtrOld + 4) * eax) / keyFrameLength;
currentStepZ = (READ_LE_INT16(keyFramePtrOld + 6) * eax) / keyFrameLength;
return 0;
}
void Animations::processAnimActions(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->animExtraPtr == nullptr) {
return; // avoid null pointers
}
Common::MemoryReadStream stream(actor->animExtraPtr, 1000000);
int32 index = 0;
const int32 endAnimEntityIdx = stream.readByte();
while (index++ < endAnimEntityIdx) {
const int32 actionType = stream.readByte() - 5;
if (actionType >= ACTION_LAST) {
return;
}
switch (actionType) {
case ACTION_HITTING: {
const int32 animPos = stream.readByte() - 1;
const int32 strength = stream.readByte();
if (animPos == actor->animPosition) {
actor->strengthOfHit = strength;
actor->dynamicFlags.bIsHitting = 1;
}
break;
}
case ACTION_SAMPLE: {
const int32 animPos = stream.readByte();
const int16 sampleIdx = stream.readSint16LE();
if (animPos == actor->animPosition) {
_engine->_sound->playSample(sampleIdx, 1, actor->x, actor->y, actor->z, actorIdx);
}
break;
}
case ACTION_SAMPLE_FREQ: {
const int32 animPos = stream.readByte();
const int16 sampleIdx = stream.readSint16LE();
/*int16 frequency = */stream.readSint16LE();
if (animPos == actor->animPosition) {
_engine->_sound->playSample(sampleIdx, 1, actor->x, actor->y, actor->z, actorIdx);
}
break;
}
case ACTION_THROW_EXTRA_BONUS: {
const int32 animPos = stream.readByte();
const int32 yHeight = stream.readSint16LE();
const int32 sprite = stream.readByte();
const int32 xAngle = ToAngle(stream.readSint16LE());
const int32 yAngle = actor->angle + ToAngle(stream.readSint16LE());
const int32 xRotPoint = stream.readSint16LE();
const int32 extraAngle = ToAngle(stream.readByte());
const int32 strengthOfHit = stream.readByte();
if (animPos == actor->animPosition) {
_engine->_extra->addExtraThrow(actorIdx, actor->x, actor->y + yHeight, actor->z, sprite, xAngle, yAngle, xRotPoint, extraAngle, strengthOfHit);
}
break;
}
case ACTION_THROW_MAGIC_BALL: {
const int32 animPos = stream.readByte();
const int32 yOffset = stream.readSint16LE();
const int32 xAngle = ToAngle(stream.readSint16LE());
const int32 xRotPoint = stream.readSint16LE();
const int32 extraAngle = stream.readByte();
if (_engine->_gameState->magicBallIdx == -1 && animPos == actor->animPosition) {
_engine->_extra->addExtraThrowMagicball(actor->x, actor->y + yOffset, actor->z, xAngle, actor->angle, xRotPoint, extraAngle);
}
break;
}
case ACTION_SAMPLE_REPEAT: {
const int32 animPos = stream.readByte();
const int16 sampleIdx = stream.readSint16LE();
const int16 repeat = stream.readSint16LE();
if (animPos == actor->animPosition) {
_engine->_sound->playSample(sampleIdx, repeat, actor->x, actor->y, actor->z, actorIdx);
}
break;
}
case ACTION_UNKNOWN_6: {
const int32 animPos = stream.readByte();
if (animPos == actor->animPosition) {
// TODO: The folowing fetches 7 bytes, but the else block skips only 6 bytes.
// Please check if that's correct.
const int32 yOffset = stream.readSint16LE();
const int32 spriteIdx = stream.readByte();
const int32 targetActorIdx = stream.readByte();
const int32 maxSpeed = stream.readSint16LE();
const int32 strengthOfHit = stream.readByte();
_engine->_extra->addExtraAiming(actorIdx, actor->x, actor->y + yOffset, actor->z, spriteIdx, targetActorIdx, maxSpeed, strengthOfHit);
} else {
stream.skip(6);
}
break;
}
case ACTION_UNKNOWN_7: {
const int32 animPos = stream.readByte();
const int32 yHeight = stream.readSint16LE();
const int32 spriteIdx = stream.readByte();
const int32 xAngle = ToAngle(stream.readSint16LE());
const int32 yAngle = actor->angle + ToAngle(stream.readSint16LE());
const int32 xRotPoint = stream.readSint16LE();
const int32 extraAngle = ToAngle(stream.readByte());
const int32 strengthOfHit = stream.readByte();
if (animPos == actor->animPosition) {
_engine->_extra->addExtraThrow(actorIdx, actor->x, actor->y + yHeight, actor->z, spriteIdx, xAngle, yAngle, xRotPoint, extraAngle, strengthOfHit);
}
break;
}
case ACTION_SAMPLE_STOP: {
const int32 animPos = stream.readByte();
const int32 sampleIdx = stream.readByte(); //why is it reading a byte but saving it in a 32bit variable?
stream.skip(1); // TODO what is the meaning of this extra byte?
if (animPos == actor->animPosition) {
_engine->_sound->stopSample(sampleIdx);
}
break;
}
case ACTION_SAMPLE_BRICK_1: {
const int32 animPos = stream.readByte();
if (animPos == actor->animPosition && (actor->brickSound & 0x0F0) != 0x0F0) {
const int16 sampleIdx = (actor->brickSound & 0x0F) + Samples::WalkFloorBegin;
_engine->_sound->playSample(sampleIdx, 1, actor->x, actor->y, actor->z, actorIdx);
}
break;
}
case ACTION_SAMPLE_BRICK_2: {
const int32 animPos = stream.readByte();
if (animPos == actor->animPosition && (actor->brickSound & 0x0F0) != 0x0F0) {
const int16 sampleIdx = (actor->brickSound & 0x0F) + Samples::WalkFloorBegin;
_engine->_sound->playSample(sampleIdx, 1, actor->x, actor->y, actor->z, actorIdx);
}
break;
}
case ACTION_HERO_HITTING: {
const int32 animPos = stream.readByte() - 1;
if (animPos == actor->animPosition) {
actor->strengthOfHit = magicLevelStrengthOfHit[_engine->_gameState->magicLevelIdx];
actor->dynamicFlags.bIsHitting = 1;
}
break;
}
case ACTION_UNKNOWN_13: {
const int32 animPos = stream.readByte();
const int32 distanceX = stream.readSint16LE();
const int32 distanceY = stream.readSint16LE();
const int32 distanceZ = stream.readSint16LE();
const int32 spriteIdx = stream.readByte();
const int32 xAngle = ToAngle(stream.readSint16LE());
const int32 yAngle = ToAngle(stream.readSint16LE());
const int32 xRotPoint = stream.readSint16LE();
const int32 extraAngle = ToAngle(stream.readByte());
const int32 strength = stream.readByte();
if (animPos == actor->animPosition) {
_engine->_movements->rotateActor(distanceX, distanceZ, actor->angle);
const int32 throwX = _engine->_renderer->destX + actor->x;
const int32 throwY = distanceY + actor->y;
const int32 throwZ = _engine->_renderer->destZ + actor->z;
_engine->_extra->addExtraThrow(actorIdx, throwX, throwY, throwZ, spriteIdx,
xAngle, yAngle + actor->angle, xRotPoint, extraAngle, strength);
}
break;
}
case ACTION_UNKNOWN_14: {
const int32 animPos = stream.readByte();
const int32 distanceX = stream.readSint16LE();
const int32 distanceY = stream.readSint16LE();
const int32 distanceZ = stream.readSint16LE();
const int32 spriteIdx = stream.readByte();
const int32 xAngle = ToAngle(stream.readSint16LE());
const int32 yAngle = ToAngle(stream.readSint16LE());
const int32 xRotPoint = stream.readSint16LE();
const int32 extraAngle = ToAngle(stream.readByte());
const int32 strength = stream.readByte();
if (animPos == actor->animPosition) {
const int32 newAngle = _engine->_movements->getAngleAndSetTargetActorDistance(actor->y, 0, _engine->_scene->sceneHero->y, _engine->_movements->getDistance2D(actor->x, actor->z, _engine->_scene->sceneHero->x, _engine->_scene->sceneHero->z));
_engine->_movements->rotateActor(distanceX, distanceZ, actor->angle);
const int32 throwX = _engine->_renderer->destX + actor->x;
const int32 throwY = distanceY + actor->y;
const int32 throwZ = _engine->_renderer->destZ + actor->z;
_engine->_extra->addExtraThrow(actorIdx, throwX, throwY, throwZ, spriteIdx,
xAngle + newAngle, yAngle + actor->angle, xRotPoint, extraAngle, strength);
}
break;
}
case ACTION_UNKNOWN_15: {
const int32 animPos = stream.readByte();
const int32 distanceX = stream.readSint16LE();
const int32 distanceY = stream.readSint16LE();
const int32 distanceZ = stream.readSint16LE();
const int32 spriteIdx = stream.readByte();
const int32 targetActor = stream.readByte();
const int32 finalAngle = ToAngle(stream.readSint16LE());
const int32 strengthOfHit = stream.readByte();
if (animPos == actor->animPosition) {
_engine->_movements->rotateActor(distanceX, distanceZ, actor->angle);
_engine->_extra->addExtraAiming(actorIdx, actor->x + _engine->_renderer->destX, actor->y + distanceY, actor->z + distanceZ, spriteIdx,
targetActor, finalAngle, strengthOfHit);
}
break;
}
case ACTION_UNKNOWN_9:
break;
default:
break;
}
}
}
bool Animations::initAnim(AnimationTypes newAnim, int16 animType, AnimationTypes animExtra, int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->entity == -1) {
return false;
}
if (actor->staticFlags.bIsSpriteActor) {
return false;
}
if (newAnim == actor->anim && actor->previousAnimIdx != -1) {
return true;
}
if (animExtra == AnimationTypes::kAnimInvalid && actor->animType != 2) {
animExtra = actor->anim;
}
int32 animIndex = getBodyAnimIndex(newAnim, actorIdx);
if (animIndex == -1) {
animIndex = getBodyAnimIndex(AnimationTypes::kStanding, actorIdx);
}
if (animType != 4 && actor->animType == 2) {
actor->animExtra = newAnim;
return false;
}
if (animType == 3) {
animType = 2;
animExtra = actor->anim;
if (animExtra == AnimationTypes::kThrowBall || animExtra == AnimationTypes::kFall || animExtra == AnimationTypes::kLanding || animExtra == AnimationTypes::kLandingHit) {
animExtra = AnimationTypes::kStanding;
}
}
if (animType == 4) {
animType = 2;
}
if (actor->previousAnimIdx == -1) { // if no previous animation
setAnimAtKeyframe(0, _engine->_resources->animTable[animIndex], _engine->_actor->bodyTable[actor->entity], &actor->animTimerData);
} else { // interpolation between animations
animBuffer2 += stockAnimation(_engine->_actor->bodyTable[actor->entity], &actor->animTimerData);
if (animBuffer1 + 4488 < animBuffer2) {
animBuffer2 = animBuffer1;
}
}
actor->previousAnimIdx = animIndex;
actor->anim = newAnim;
actor->animExtra = animExtra;
actor->animExtraPtr = currentActorAnimExtraPtr;
actor->animType = animType;
actor->animPosition = 0;
actor->dynamicFlags.bIsHitting = 0;
actor->dynamicFlags.bAnimEnded = 0;
actor->dynamicFlags.bAnimFrameReached = 1;
if (actor->animExtraPtr) {
processAnimActions(actorIdx);
}
actor->lastRotationAngle = 0;
actor->lastX = 0;
actor->lastY = 0;
actor->lastZ = 0;
return true;
}
void Animations::processActorAnimations(int32 actorIdx) { // DoAnim
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
currentlyProcessedActorIdx = actorIdx;
_engine->_actor->processActorPtr = actor;
if (actor->entity == -1) {
return;
}
_engine->_movements->previousActorX = actor->collisionX;
_engine->_movements->previousActorY = actor->collisionY;
_engine->_movements->previousActorZ = actor->collisionZ;
if (actor->staticFlags.bIsSpriteActor) { // is sprite actor
if (actor->strengthOfHit) {
actor->dynamicFlags.bIsHitting = 1;
}
_engine->_movements->processActorX = actor->x;
_engine->_movements->processActorY = actor->y;
_engine->_movements->processActorZ = actor->z;
if (!actor->dynamicFlags.bIsFalling) {
if (actor->speed) {
int32 xAxisRotation = actor->move.getRealValue(_engine->lbaTime);
if (!xAxisRotation) {
if (actor->move.to > 0) {
xAxisRotation = 1;
} else {
xAxisRotation = -1;
}
}
_engine->_movements->rotateActor(xAxisRotation, 0, actor->animType);
_engine->_movements->processActorY = actor->y - _engine->_renderer->destZ;
_engine->_movements->rotateActor(0, _engine->_renderer->destX, actor->angle);
_engine->_movements->processActorX = actor->x + _engine->_renderer->destX;
_engine->_movements->processActorZ = actor->z + _engine->_renderer->destZ;
_engine->_movements->setActorAngle(ANGLE_0, actor->speed, 50, &actor->move);
if (actor->dynamicFlags.bIsSpriteMoving) {
if (actor->doorStatus) { // open door
if (_engine->_movements->getDistance2D(_engine->_movements->processActorX, _engine->_movements->processActorZ, actor->lastX, actor->lastZ) >= actor->doorStatus) {
if (actor->angle == ANGLE_0) {
_engine->_movements->processActorZ = actor->lastZ + actor->doorStatus;
} else if (actor->angle == ANGLE_90) {
_engine->_movements->processActorX = actor->lastX + actor->doorStatus;
} else if (actor->angle == ANGLE_180) {
_engine->_movements->processActorZ = actor->lastZ - actor->doorStatus;
} else if (actor->angle == ANGLE_270) {
_engine->_movements->processActorX = actor->lastX - actor->doorStatus;
}
actor->dynamicFlags.bIsSpriteMoving = 0;
actor->speed = 0;
}
} else { // close door
bool updatePos = false;
if (actor->angle == ANGLE_0) {
if (_engine->_movements->processActorZ <= actor->lastZ) {
updatePos = true;
}
} else if (actor->angle == ANGLE_90) {
if (_engine->_movements->processActorX <= actor->lastX) {
updatePos = true;
}
} else if (actor->angle == ANGLE_180) {
if (_engine->_movements->processActorZ >= actor->lastZ) {
updatePos = true;
}
} else if (actor->angle == ANGLE_270) {
if (_engine->_movements->processActorX >= actor->lastX) {
updatePos = true;
}
}
if (updatePos) {
_engine->_movements->processActorX = actor->lastX;
_engine->_movements->processActorY = actor->lastY;
_engine->_movements->processActorZ = actor->lastZ;
actor->dynamicFlags.bIsSpriteMoving = 0;
actor->speed = 0;
}
}
}
}
if (actor->staticFlags.bCanBePushed) {
_engine->_movements->processActorX += actor->lastX;
_engine->_movements->processActorY += actor->lastY;
_engine->_movements->processActorZ += actor->lastZ;
if (actor->staticFlags.bUseMiniZv) {
_engine->_movements->processActorX = ((_engine->_movements->processActorX / 128) * 128);
_engine->_movements->processActorZ = ((_engine->_movements->processActorZ / 128) * 128);
}
actor->lastX = 0;
actor->lastY = 0;
actor->lastZ = 0;
}
}
} else { // 3D actor
if (actor->previousAnimIdx != -1) {
uint8 *animPtr = _engine->_resources->animTable[actor->previousAnimIdx];
int32 keyFramePassed = verifyAnimAtKeyframe(actor->animPosition, animPtr, _engine->_actor->bodyTable[actor->entity], &actor->animTimerData);
if (processRotationByAnim) {
actor->dynamicFlags.bIsRotationByAnim = 1;
} else {
actor->dynamicFlags.bIsRotationByAnim = 0;
}
actor->angle = ClampAngle(actor->angle + processLastRotationAngle - actor->lastRotationAngle);
actor->lastRotationAngle = processLastRotationAngle;
_engine->_movements->rotateActor(currentStepX, currentStepZ, actor->angle);
currentStepX = _engine->_renderer->destX;
currentStepZ = _engine->_renderer->destZ;
_engine->_movements->processActorX = actor->x + currentStepX - actor->lastX;
_engine->_movements->processActorY = actor->y + currentStepY - actor->lastY;
_engine->_movements->processActorZ = actor->z + currentStepZ - actor->lastZ;
actor->lastX = currentStepX;
actor->lastY = currentStepY;
actor->lastZ = currentStepZ;
actor->dynamicFlags.bAnimEnded = 0;
actor->dynamicFlags.bAnimFrameReached = 0;
if (keyFramePassed) {
actor->animPosition++;
// if actor have animation actions to process
if (actor->animExtraPtr) {
processAnimActions(actorIdx);
}
int16 numKeyframe = actor->animPosition;
if (numKeyframe == getNumKeyframes(animPtr)) {
actor->dynamicFlags.bIsHitting = 0;
if (actor->animType == 0) {
actor->animPosition = getStartKeyframe(animPtr);
} else {
actor->anim = (AnimationTypes)actor->animExtra;
actor->previousAnimIdx = getBodyAnimIndex(actor->anim, actorIdx);
if (actor->previousAnimIdx == -1) {
actor->previousAnimIdx = getBodyAnimIndex(AnimationTypes::kStanding, actorIdx);
actor->anim = AnimationTypes::kStanding;
}
actor->animExtraPtr = currentActorAnimExtraPtr;
actor->animType = 0;
actor->animPosition = 0;
actor->strengthOfHit = 0;
}
if (actor->animExtraPtr) {
processAnimActions(actorIdx);
}
actor->dynamicFlags.bAnimEnded = 1;
}
actor->lastRotationAngle = 0;
actor->lastX = 0;
actor->lastY = 0;
actor->lastZ = 0;
}
}
}
// actor standing on another actor
if (actor->standOn != -1) {
_engine->_movements->processActorX -= _engine->_scene->getActor(actor->standOn)->collisionX;
_engine->_movements->processActorY -= _engine->_scene->getActor(actor->standOn)->collisionY;
_engine->_movements->processActorZ -= _engine->_scene->getActor(actor->standOn)->collisionZ;
_engine->_movements->processActorX += _engine->_scene->getActor(actor->standOn)->x;
_engine->_movements->processActorY += _engine->_scene->getActor(actor->standOn)->y;
_engine->_movements->processActorZ += _engine->_scene->getActor(actor->standOn)->z;
if (!_engine->_collision->standingOnActor(actorIdx, actor->standOn)) {
actor->standOn = -1; // no longer standing on other actor
}
}
// actor falling Y speed
if (actor->dynamicFlags.bIsFalling) {
_engine->_movements->processActorX = _engine->_movements->previousActorX;
_engine->_movements->processActorY = _engine->_movements->previousActorY + _engine->loopActorStep; // add step to fall
_engine->_movements->processActorZ = _engine->_movements->previousActorZ;
}
// actor collisions with bricks
if (actor->staticFlags.bComputeCollisionWithBricks) {
_engine->_collision->collisionY = 0;
ShapeType brickShape = _engine->_grid->getBrickShape(_engine->_movements->previousActorX, _engine->_movements->previousActorY, _engine->_movements->previousActorZ);
if (brickShape != ShapeType::kNone) {
if (brickShape != ShapeType::kSolid) {
_engine->_collision->reajustActorPosition(brickShape);
} /*else { // this shouldn't happen (collision should avoid it)
actor->y = processActorY = (processActorY / 256) * 256 + 256; // go upper
}*/
}
if (actor->staticFlags.bComputeCollisionWithObj) {
_engine->_collision->checkCollisionWithActors(actorIdx);
}
if (actor->standOn != -1 && actor->dynamicFlags.bIsFalling) {
_engine->_collision->stopFalling();
}
_engine->_collision->causeActorDamage = 0;
_engine->_collision->processCollisionX = _engine->_movements->processActorX;
_engine->_collision->processCollisionY = _engine->_movements->processActorY;
_engine->_collision->processCollisionZ = _engine->_movements->processActorZ;
if (IS_HERO(actorIdx) && !actor->staticFlags.bComputeLowCollision) {
// check hero collisions with bricks
_engine->_collision->checkHeroCollisionWithBricks(actor->boudingBox.x.bottomLeft, actor->boudingBox.y.bottomLeft, actor->boudingBox.z.bottomLeft, 1);
_engine->_collision->checkHeroCollisionWithBricks(actor->boudingBox.x.topRight, actor->boudingBox.y.bottomLeft, actor->boudingBox.z.bottomLeft, 2);
_engine->_collision->checkHeroCollisionWithBricks(actor->boudingBox.x.topRight, actor->boudingBox.y.bottomLeft, actor->boudingBox.z.topRight, 4);
_engine->_collision->checkHeroCollisionWithBricks(actor->boudingBox.x.bottomLeft, actor->boudingBox.y.bottomLeft, actor->boudingBox.z.topRight, 8);
} else {
// check other actors collisions with bricks
_engine->_collision->checkActorCollisionWithBricks(actor->boudingBox.x.bottomLeft, actor->boudingBox.y.bottomLeft, actor->boudingBox.z.bottomLeft, 1);
_engine->_collision->checkActorCollisionWithBricks(actor->boudingBox.x.topRight, actor->boudingBox.y.bottomLeft, actor->boudingBox.z.bottomLeft, 2);
_engine->_collision->checkActorCollisionWithBricks(actor->boudingBox.x.topRight, actor->boudingBox.y.bottomLeft, actor->boudingBox.z.topRight, 4);
_engine->_collision->checkActorCollisionWithBricks(actor->boudingBox.x.bottomLeft, actor->boudingBox.y.bottomLeft, actor->boudingBox.z.topRight, 8);
}
// process wall hit while running
if (_engine->_collision->causeActorDamage && !actor->dynamicFlags.bIsFalling && !currentlyProcessedActorIdx && _engine->_actor->heroBehaviour == HeroBehaviourType::kAthletic && actor->anim == AnimationTypes::kForward) {
_engine->_movements->rotateActor(actor->boudingBox.x.bottomLeft, actor->boudingBox.z.bottomLeft, actor->angle + ANGLE_360 + ANGLE_135);
_engine->_renderer->destX += _engine->_movements->processActorX;
_engine->_renderer->destZ += _engine->_movements->processActorZ;
if (_engine->_renderer->destX >= 0 && _engine->_renderer->destZ >= 0 && _engine->_renderer->destX <= 0x7E00 && _engine->_renderer->destZ <= 0x7E00) {
if (_engine->_grid->getBrickShape(_engine->_renderer->destX, _engine->_movements->processActorY + 256, _engine->_renderer->destZ) != ShapeType::kNone && _engine->cfgfile.WallCollision) { // avoid wall hit damage
_engine->_extra->addExtraSpecial(actor->x, actor->y + 1000, actor->z, ExtraSpecialType::kHitStars);
initAnim(AnimationTypes::kBigHit, 2, AnimationTypes::kStanding, currentlyProcessedActorIdx);
if (IS_HERO(currentlyProcessedActorIdx)) {
_engine->_movements->heroMoved = true;
}
actor->life--;
}
}
}
brickShape = _engine->_grid->getBrickShape(_engine->_movements->processActorX, _engine->_movements->processActorY, _engine->_movements->processActorZ);
actor->setBrickShape(brickShape);
if (brickShape != ShapeType::kNone) {
if (brickShape == ShapeType::kSolid) {
if (actor->dynamicFlags.bIsFalling) {
_engine->_collision->stopFalling();
_engine->_movements->processActorY = (_engine->_collision->collisionY << 8) + 0x100;
} else {
if (IS_HERO(actorIdx) && _engine->_actor->heroBehaviour == HeroBehaviourType::kAthletic && actor->anim == AnimationTypes::kForward && _engine->cfgfile.WallCollision) { // avoid wall hit damage
_engine->_extra->addExtraSpecial(actor->x, actor->y + 1000, actor->z, ExtraSpecialType::kHitStars);
initAnim(AnimationTypes::kBigHit, 2, AnimationTypes::kStanding, currentlyProcessedActorIdx);
_engine->_movements->heroMoved = true;
actor->life--;
}
// no Z coordinate issue
if (_engine->_grid->getBrickShape(_engine->_movements->processActorX, _engine->_movements->processActorY, _engine->_movements->previousActorZ) == ShapeType::kNone) {
_engine->_movements->processActorZ = _engine->_movements->previousActorZ;
}
// no X coordinate issue
if (_engine->_grid->getBrickShape(_engine->_movements->previousActorX, _engine->_movements->processActorY, _engine->_movements->processActorZ) == ShapeType::kNone) {
_engine->_movements->processActorX = _engine->_movements->previousActorX;
}
// X and Z with issue, no move
if (_engine->_grid->getBrickShape(_engine->_movements->processActorX, _engine->_movements->processActorY, _engine->_movements->previousActorZ) != ShapeType::kNone &&
_engine->_grid->getBrickShape(_engine->_movements->previousActorX, _engine->_movements->processActorY, _engine->_movements->processActorZ) != ShapeType::kNone) {
return;
}
}
} else {
if (actor->dynamicFlags.bIsFalling) {
_engine->_collision->stopFalling();
}
_engine->_collision->reajustActorPosition(brickShape);
}
actor->dynamicFlags.bIsFalling = 0;
} else {
if (actor->staticFlags.bCanFall && actor->standOn == -1) {
brickShape = _engine->_grid->getBrickShape(_engine->_movements->processActorX, _engine->_movements->processActorY - 1, _engine->_movements->processActorZ);
if (brickShape != ShapeType::kNone) {
if (actor->dynamicFlags.bIsFalling) {
_engine->_collision->stopFalling();
}
_engine->_collision->reajustActorPosition(brickShape);
} else {
if (!actor->dynamicFlags.bIsRotationByAnim) {
actor->dynamicFlags.bIsFalling = 1;
if (IS_HERO(actorIdx) && _engine->_scene->heroYBeforeFall == 0) {
_engine->_scene->heroYBeforeFall = _engine->_movements->processActorY;
}
initAnim(AnimationTypes::kFall, 0, AnimationTypes::kAnimInvalid, actorIdx);
}
}
}
}
// if under the map, than die
if (_engine->_collision->collisionY == -1) {
actor->life = 0;
}
} else {
if (actor->staticFlags.bComputeCollisionWithObj) {
_engine->_collision->checkCollisionWithActors(actorIdx);
}
}
if (_engine->_collision->causeActorDamage) {
actor->setBrickCausesDamage();
}
// check and fix actor bounding position
if (_engine->_movements->processActorX < 0) {
_engine->_movements->processActorX = 0;
}
if (_engine->_movements->processActorY < 0) {
_engine->_movements->processActorY = 0;
}
if (_engine->_movements->processActorZ < 0) {
_engine->_movements->processActorZ = 0;
}
if (_engine->_movements->processActorX > 0x7E00) {
_engine->_movements->processActorX = 0x7E00;
}
if (_engine->_movements->processActorZ > 0x7E00) {
_engine->_movements->processActorZ = 0x7E00;
}
actor->x = _engine->_movements->processActorX;
actor->y = _engine->_movements->processActorY;
actor->z = _engine->_movements->processActorZ;
}
} // namespace TwinE