/* 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