Files
scummvm/engines/twine/scene/animations.cpp
T

831 lines
29 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/animations.h"
#include "common/endian.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "common/util.h"
#include "twine/audio/sound.h"
#include "twine/debugger/debug_scene.h"
#include "twine/parser/anim.h"
#include "twine/parser/entity.h"
#include "twine/renderer/renderer.h"
#include "twine/resources/resources.h"
#include "twine/scene/collision.h"
#include "twine/scene/extra.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
namespace TwinE {
static const int32 magicLevelStrengthOfHit[] = {
MagicballStrengthType::kNoBallStrength,
MagicballStrengthType::kYellowBallStrength,
MagicballStrengthType::kGreenBallStrength,
MagicballStrengthType::kRedBallStrength,
MagicballStrengthType::kFireBallStrength,
0};
Animations::Animations(TwinEEngine *engine) : _engine(engine) {
}
int32 Animations::searchAnim(AnimationTypes animIdx, int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const int32 bodyAnimIndex = actor->_entityDataPtr->getAnimIndex(animIdx);
if (bodyAnimIndex != -1) {
_currentActorAnimExtraPtr = animIdx;
}
return bodyAnimIndex;
}
int16 Animations::applyAnimStepRotation(int32 deltaTime, int32 keyFrameLength, int16 newAngle1, int16 lastAngle1) const {
const int16 lastAngle = ClampAngle(lastAngle1);
const int16 newAngle = ClampAngle(newAngle1);
int16 angleDiff = newAngle - lastAngle;
int16 computedAngle;
if (angleDiff) {
if (angleDiff < -LBAAngles::ANGLE_180) {
angleDiff += LBAAngles::ANGLE_360;
} else if (angleDiff > LBAAngles::ANGLE_180) {
angleDiff -= LBAAngles::ANGLE_360;
}
computedAngle = lastAngle + (angleDiff * deltaTime) / keyFrameLength;
} else {
computedAngle = lastAngle;
}
return ClampAngle(computedAngle);
}
int16 Animations::applyAnimStepTranslation(int32 deltaTime, int32 keyFrameLength, int16 newPos, int16 lastPos) const {
int16 distance = newPos - lastPos;
int16 computedPos;
if (distance) {
computedPos = lastPos + (distance * deltaTime) / keyFrameLength;
} else {
computedPos = lastPos;
}
return computedPos;
}
bool Animations::setModelAnimation(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
if (!bodyData.isAnimated()) {
return false;
}
const KeyFrame *keyFrame = animData.getKeyframe(keyframeIdx);
_currentStep.x = keyFrame->x;
_currentStep.y = keyFrame->y;
_currentStep.z = keyFrame->z;
_processRotationByAnim = keyFrame->boneframes[0].type;
_processLastRotationAngle = ToAngle(keyFrame->boneframes[0].y);
const int16 numBones = bodyData.getNumBones();
int32 numOfBonesInAnim = animData.getNumBoneframes();
if (numOfBonesInAnim > numBones) {
numOfBonesInAnim = numBones;
}
const int32 keyFrameLength = keyFrame->length;
const KeyFrame *lastKeyFramePtr = animTimerDataPtr->ptr;
int32 remainingFrameTime = animTimerDataPtr->time;
if (lastKeyFramePtr == nullptr) {
lastKeyFramePtr = keyFrame;
remainingFrameTime = keyFrameLength;
}
const int32 deltaTime = _engine->_lbaTime - remainingFrameTime;
if (deltaTime >= keyFrameLength) {
copyKeyFrameToState(keyFrame, bodyData, numOfBonesInAnim);
animTimerDataPtr->ptr = keyFrame;
animTimerDataPtr->time = _engine->_lbaTime;
return true;
}
_processLastRotationAngle = (_processLastRotationAngle * deltaTime) / keyFrameLength;
if (numOfBonesInAnim <= 1) {
return false;
}
int16 boneIdx = 1;
int16 tmpNumOfPoints = MIN<int16>(lastKeyFramePtr->boneframes.size() - 1, numOfBonesInAnim - 1);
do {
BoneFrame *boneState = bodyData.getBoneState(boneIdx);
const BoneFrame &boneFrame = keyFrame->boneframes[boneIdx];
const BoneFrame &lastBoneFrame = lastKeyFramePtr->boneframes[boneIdx];
boneState->type = boneFrame.type;
switch (boneFrame.type) {
case 0:
boneState->x = applyAnimStepRotation(deltaTime, keyFrameLength, boneFrame.x, lastBoneFrame.x);
boneState->y = applyAnimStepRotation(deltaTime, keyFrameLength, boneFrame.y, lastBoneFrame.y);
boneState->z = applyAnimStepRotation(deltaTime, keyFrameLength, boneFrame.z, lastBoneFrame.z);
break;
case 1:
case 2:
boneState->x = applyAnimStepTranslation(deltaTime, keyFrameLength, boneFrame.x, lastBoneFrame.x);
boneState->y = applyAnimStepTranslation(deltaTime, keyFrameLength, boneFrame.y, lastBoneFrame.y);
boneState->z = applyAnimStepTranslation(deltaTime, keyFrameLength, boneFrame.z, lastBoneFrame.z);
break;
default:
error("Unsupported animation rotation mode %d", boneFrame.type);
}
++boneIdx;
} while (--tmpNumOfPoints);
return false;
}
void Animations::setAnimObjet(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
if (!bodyData.isAnimated()) {
return;
}
const int32 numOfKeyframeInAnim = animData.getKeyframes().size();
if (keyframeIdx < 0 || keyframeIdx >= numOfKeyframeInAnim) {
return;
}
const KeyFrame *keyFrame = animData.getKeyframe(keyframeIdx);
_currentStep.x = keyFrame->x;
_currentStep.y = keyFrame->y;
_currentStep.z = keyFrame->z;
_processRotationByAnim = keyFrame->boneframes[0].type;
_processLastRotationAngle = ToAngle(keyFrame->boneframes[0].y);
animTimerDataPtr->ptr = animData.getKeyframe(keyframeIdx);
animTimerDataPtr->time = _engine->_lbaTime;
const int16 numBones = bodyData.getNumBones();
int16 numOfBonesInAnim = animData.getNumBoneframes();
if (numOfBonesInAnim > numBones) {
numOfBonesInAnim = numBones;
}
copyKeyFrameToState(keyFrame, bodyData, numOfBonesInAnim);
}
void Animations::stockInterAnim(const BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
if (!bodyData.isAnimated()) {
return;
}
if (_animKeyframeBufIdx >= ARRAYSIZE(_animKeyframeBuf)) {
_animKeyframeBufIdx = 0;
}
animTimerDataPtr->time = _engine->_lbaTime;
KeyFrame *keyframe = &_animKeyframeBuf[_animKeyframeBufIdx++];
animTimerDataPtr->ptr = keyframe;
copyStateToKeyFrame(keyframe, bodyData);
}
void Animations::copyStateToKeyFrame(KeyFrame *keyframe, const BodyData &bodyData) const {
const int32 numBones = bodyData.getNumBones();
keyframe->boneframes.clear();
keyframe->boneframes.reserve(numBones);
for (int32 i = 0; i < numBones; ++i) {
const BoneFrame *boneState = bodyData.getBoneState(i);
keyframe->boneframes.push_back(*boneState);
}
}
void Animations::copyKeyFrameToState(const KeyFrame *keyframe, BodyData &bodyData, int32 numBones) const {
for (int32 i = 0; i < numBones; ++i) {
BoneFrame *boneState = bodyData.getBoneState(i);
*boneState = keyframe->boneframes[i];
}
}
bool Animations::verifyAnimAtKeyframe(int32 keyframeIdx, const AnimData &animData, AnimTimerDataStruct *animTimerDataPtr) {
const KeyFrame *keyFrame = animData.getKeyframe(keyframeIdx);
const int32 keyFrameLength = keyFrame->length;
int32 remainingFrameTime = animTimerDataPtr->time;
if (animTimerDataPtr->ptr == nullptr) {
remainingFrameTime = keyFrameLength;
}
const int32 deltaTime = _engine->_lbaTime - remainingFrameTime;
_currentStep.x = keyFrame->x;
_currentStep.y = keyFrame->y;
_currentStep.z = keyFrame->z;
const BoneFrame &boneFrame = keyFrame->boneframes[0];
_processRotationByAnim = boneFrame.type;
_processLastRotationAngle = ToAngle(boneFrame.y);
if (deltaTime >= keyFrameLength) {
animTimerDataPtr->ptr = animData.getKeyframe(keyframeIdx);
animTimerDataPtr->time = _engine->_lbaTime;
return true;
}
_processLastRotationAngle = (_processLastRotationAngle * deltaTime) / keyFrameLength;
_currentStep.x = (_currentStep.x * deltaTime) / keyFrameLength;
_currentStep.y = (_currentStep.y * deltaTime) / keyFrameLength;
_currentStep.z = (_currentStep.z * deltaTime) / keyFrameLength;
return false;
}
void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_entityDataPtr == nullptr || actor->_ptrAnimAction == AnimationTypes::kAnimNone) {
return;
}
const Common::Array<EntityAnim::Action> *actions = actor->_entityDataPtr->getActions(actor->_ptrAnimAction);
if (actions == nullptr) {
return;
}
for (const EntityAnim::Action &action : *actions) {
switch (action.type) {
case ActionType::ACTION_HITTING:
if (action.animFrame - 1 == actor->_frame) {
actor->_strengthOfHit = action.strength;
actor->_dynamicFlags.bIsHitting = 1;
}
break;
case ActionType::ACTION_SAMPLE:
case ActionType::ACTION_SAMPLE_FREQ:
if (action.animFrame == actor->_frame) {
_engine->_sound->playSample(action.sampleIndex, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_THROW_EXTRA_BONUS:
if (action.animFrame == actor->_frame) {
_engine->_extra->throwExtra(actorIdx, actor->_pos.x, actor->_pos.y + action.yHeight, actor->_pos.z, action.spriteIndex, action.xAngle, actor->_beta + action.yAngle, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_MAGIC_BALL:
if (_engine->_gameState->_magicBall == -1 && action.animFrame == actor->_frame) {
_engine->_extra->addExtraThrowMagicball(actor->_pos.x, actor->_pos.y + action.yHeight, actor->_pos.z, action.xAngle, actor->_beta + action.yAngle, action.xRotPoint, action.extraAngle);
}
break;
case ActionType::ACTION_SAMPLE_REPEAT:
if (action.animFrame == actor->_frame) {
_engine->_sound->playSample(action.sampleIndex, action.repeat, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_THROW_SEARCH:
if (action.animFrame == actor->_frame) {
_engine->_extra->addExtraAiming(actorIdx, actor->_pos.x, actor->_pos.y + action.yHeight, actor->_pos.z, action.spriteIndex, action.targetActor, action.finalAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_ALPHA:
if (action.animFrame == actor->_frame) {
_engine->_extra->throwExtra(actorIdx, actor->_pos.x, actor->_pos.y + action.yHeight, actor->_pos.z, action.spriteIndex, action.xAngle, actor->_beta + action.yAngle, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_SAMPLE_STOP:
if (action.animFrame == actor->_frame) {
_engine->_sound->stopSample(action.sampleIndex);
}
break;
case ActionType::ACTION_LEFT_STEP:
if (action.animFrame == actor->_frame && (actor->_brickSound & 0xF0U) != 0xF0U) {
const int16 sampleIdx = (actor->_brickSound & 0x0FU) + Samples::WalkFloorBegin;
_engine->_sound->playSample(sampleIdx, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_RIGHT_STEP:
if (action.animFrame == actor->_frame && (actor->_brickSound & 0xF0U) != 0xF0U) {
const int16 sampleIdx = (actor->_brickSound & 0x0FU) + Samples::WalkFloorRightBegin;
_engine->_sound->playSample(sampleIdx, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_HERO_HITTING:
if (action.animFrame - 1 == actor->_frame) {
actor->_strengthOfHit = magicLevelStrengthOfHit[_engine->_gameState->_magicLevelIdx];
actor->_dynamicFlags.bIsHitting = 1;
}
break;
case ActionType::ACTION_THROW_3D:
if (action.animFrame == actor->_frame) {
const IVec3 &destPos = _engine->_movements->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 throwX = destPos.x + actor->_pos.x;
const int32 throwY = action.distanceY + actor->_pos.y;
const int32 throwZ = destPos.z + actor->_pos.z;
_engine->_extra->throwExtra(actorIdx, throwX, throwY, throwZ, action.spriteIndex,
action.xAngle, action.yAngle + actor->_beta, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_3D_ALPHA:
if (action.animFrame == actor->_frame) {
const int32 distance = getDistance2D(actor->posObj(), _engine->_scene->_sceneHero->posObj());
const int32 newAngle = _engine->_movements->getAngleAndSetTargetActorDistance(actor->_pos.y, 0, _engine->_scene->_sceneHero->_pos.y, distance);
const IVec3 &destPos = _engine->_movements->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 throwX = destPos.x + actor->_pos.x;
const int32 throwY = action.distanceY + actor->_pos.y;
const int32 throwZ = destPos.z + actor->_pos.z;
_engine->_extra->throwExtra(actorIdx, throwX, throwY, throwZ, action.spriteIndex,
action.xAngle + newAngle, action.yAngle + actor->_beta, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_3D_SEARCH:
if (action.animFrame == actor->_frame) {
const IVec3 &destPos = _engine->_movements->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 x = actor->_pos.x + destPos.x;
const int32 y = actor->_pos.y + action.distanceY;
const int32 z = actor->_pos.z + destPos.z;
_engine->_extra->addExtraAiming(actorIdx, x, y, z, action.spriteIndex,
action.targetActor, action.finalAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_3D_MAGIC:
if (_engine->_gameState->_magicBall == -1 && action.animFrame == actor->_frame) {
const IVec3 &destPos = _engine->_movements->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 x = actor->_pos.x + destPos.x;
const int32 y = actor->_pos.y + action.distanceY;
const int32 z = actor->_pos.z + destPos.z;
_engine->_extra->addExtraThrowMagicball(x, y, z, action.xAngle, actor->_beta, action.yAngle, action.finalAngle);
}
break;
case ActionType::ACTION_ZV:
default:
break;
}
}
}
bool Animations::initAnim(AnimationTypes newAnim, AnimType flag, AnimationTypes genNextAnim, int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_body == -1) {
return false;
}
if (actor->_staticFlags.bIsSpriteActor) {
return false;
}
if (newAnim == actor->_genAnim && actor->_anim != -1) {
return true;
}
if (genNextAnim == AnimationTypes::kAnimInvalid && actor->_flagAnim != AnimType::kAnimationAllThen) {
genNextAnim = actor->_genAnim;
}
int32 newanim = searchAnim(newAnim, actorIdx);
if (newanim == -1) {
newanim = searchAnim(AnimationTypes::kStanding, actorIdx);
if (newanim == -1) {
error("Could not find anim index for 'standing' (actor %i)", actorIdx);
}
}
if (flag != AnimType::kAnimationSet && actor->_flagAnim == AnimType::kAnimationAllThen) {
actor->_nextGenAnim = newAnim;
return false;
}
if (flag == AnimType::kAnimationInsert) {
flag = AnimType::kAnimationAllThen;
genNextAnim = actor->_genAnim;
if (genNextAnim == AnimationTypes::kThrowBall || genNextAnim == AnimationTypes::kFall || genNextAnim == AnimationTypes::kLanding || genNextAnim == AnimationTypes::kLandingHit) {
genNextAnim = AnimationTypes::kStanding;
}
}
if (flag == AnimType::kAnimationSet) {
flag = AnimType::kAnimationAllThen;
}
if (actor->_anim == -1) {
// if no previous animation
setAnimObjet(0, _engine->_resources->_animData[newanim], _engine->_resources->_bodyData[actor->_body], &actor->_animTimerData);
} else {
// interpolation between animations
stockInterAnim(_engine->_resources->_bodyData[actor->_body], &actor->_animTimerData);
}
actor->_anim = newanim;
actor->_genAnim = newAnim;
actor->_nextGenAnim = genNextAnim;
actor->_ptrAnimAction = _currentActorAnimExtraPtr;
actor->_flagAnim = flag;
actor->_frame = 0;
actor->_dynamicFlags.bIsHitting = 0;
actor->_dynamicFlags.bAnimEnded = 0;
actor->_dynamicFlags.bAnimNewFrame = 1;
processAnimActions(actorIdx);
actor->_animStepBeta = LBAAngles::LBAAngles::ANGLE_0;
actor->_animStep = IVec3();
return true;
}
void Animations::doAnim(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
_engine->_actor->_processActorPtr = actor;
if (actor->_body == -1) {
return;
}
IVec3 &previousActor = actor->_previousActor;
previousActor = actor->_oldPos;
IVec3 &processActor = actor->_processActor;
if (actor->_staticFlags.bIsSpriteActor) {
if (actor->_strengthOfHit) {
actor->_dynamicFlags.bIsHitting = 1;
}
processActor = actor->posObj();
if (!actor->_dynamicFlags.bIsFalling) {
if (actor->_speed) {
int32 xAxisRotation = actor->_moveAngle.getRealValue(_engine->_lbaTime);
if (!xAxisRotation) {
if (actor->_moveAngle.to > 0) {
xAxisRotation = 1;
} else {
xAxisRotation = -1;
}
}
const IVec3 xRotPos = _engine->_movements->rotate(xAxisRotation, 0, actor->_spriteActorRotation);
processActor.y = actor->_pos.y - xRotPos.z;
const IVec3 destPos = _engine->_movements->rotate(0, xRotPos.x, actor->_beta);
processActor.x = actor->_pos.x + destPos.x;
processActor.z = actor->_pos.z + destPos.z;
_engine->_movements->setActorAngle(LBAAngles::LBAAngles::ANGLE_0, actor->_speed, LBAAngles::LBAAngles::ANGLE_17, &actor->_moveAngle);
if (actor->_dynamicFlags.bIsSpriteMoving) {
if (actor->_doorWidth) { // open door
if (getDistance2D(processActor.x, processActor.z, actor->_animStep.x, actor->_animStep.z) >= actor->_doorWidth) {
if (actor->_beta == LBAAngles::LBAAngles::ANGLE_0) { // down
processActor.z = actor->_animStep.z + actor->_doorWidth;
} else if (actor->_beta == LBAAngles::LBAAngles::ANGLE_90) { // right
processActor.x = actor->_animStep.x + actor->_doorWidth;
} else if (actor->_beta == LBAAngles::LBAAngles::ANGLE_180) { // up
processActor.z = actor->_animStep.z - actor->_doorWidth;
} else if (actor->_beta == LBAAngles::LBAAngles::ANGLE_270) { // left
processActor.x = actor->_animStep.x - actor->_doorWidth;
}
actor->_dynamicFlags.bIsSpriteMoving = 0;
actor->_speed = 0;
}
} else { // close door
bool updatePos = false;
if (actor->_beta == LBAAngles::LBAAngles::ANGLE_0) { // down
if (processActor.z <= actor->_animStep.z) {
updatePos = true;
}
} else if (actor->_beta == LBAAngles::LBAAngles::ANGLE_90) { // right
if (processActor.x <= actor->_animStep.x) {
updatePos = true;
}
} else if (actor->_beta == LBAAngles::LBAAngles::ANGLE_180) { // up
if (processActor.z >= actor->_animStep.z) {
updatePos = true;
}
} else if (actor->_beta == LBAAngles::LBAAngles::ANGLE_270) { // left
if (processActor.x >= actor->_animStep.x) {
updatePos = true;
}
}
if (updatePos) {
processActor = actor->_animStep;
actor->_dynamicFlags.bIsSpriteMoving = 0;
actor->_speed = 0;
}
}
}
}
if (actor->_staticFlags.bCanBePushed) {
processActor += actor->_animStep;
if (actor->_staticFlags.bUseMiniZv) {
processActor.x = ((processActor.x / (SIZE_BRICK_XZ / 4)) * (SIZE_BRICK_XZ / 4));
processActor.z = ((processActor.z / (SIZE_BRICK_XZ / 4)) * (SIZE_BRICK_XZ / 4));
}
actor->_animStep = IVec3();
}
}
} else { // 3D actor
if (actor->_anim != -1) {
const AnimData &animData = _engine->_resources->_animData[actor->_anim];
bool keyFramePassed = false;
if (_engine->_resources->_bodyData[actor->_body].isAnimated()) {
keyFramePassed = verifyAnimAtKeyframe(actor->_frame, animData, &actor->_animTimerData);
}
if (_processRotationByAnim) {
actor->_dynamicFlags.bIsRotationByAnim = 1;
} else {
actor->_dynamicFlags.bIsRotationByAnim = 0;
}
actor->_beta = ClampAngle(actor->_beta + _processLastRotationAngle - actor->_animStepBeta);
actor->_animStepBeta = _processLastRotationAngle;
const IVec3 &destPos = _engine->_movements->rotate(_currentStep.x, _currentStep.z, actor->_beta);
_currentStep.x = destPos.x;
_currentStep.z = destPos.z;
processActor = actor->posObj() + _currentStep - actor->_animStep;
actor->_animStep = _currentStep;
actor->_dynamicFlags.bAnimEnded = 0;
actor->_dynamicFlags.bAnimNewFrame = 0;
if (keyFramePassed) {
actor->_frame++;
actor->_dynamicFlags.bAnimNewFrame = 1;
// if actor have animation actions to process
processAnimActions(actorIdx);
int16 numKeyframe = actor->_frame;
if (numKeyframe == (int16)animData.getNumKeyframes()) {
actor->_dynamicFlags.bIsHitting = 0;
if (actor->_flagAnim == AnimType::kAnimationTypeLoop) {
actor->_frame = animData.getLoopFrame();
} else {
actor->_genAnim = actor->_nextGenAnim;
actor->_anim = searchAnim(actor->_genAnim, actorIdx);
if (actor->_anim == -1) {
actor->_anim = searchAnim(AnimationTypes::kStanding, actorIdx);
actor->_genAnim = AnimationTypes::kStanding;
}
actor->_ptrAnimAction = _currentActorAnimExtraPtr;
actor->_flagAnim = AnimType::kAnimationTypeLoop;
actor->_frame = 0;
actor->_strengthOfHit = 0;
}
processAnimActions(actorIdx);
actor->_dynamicFlags.bAnimEnded = 1;
}
actor->_animStepBeta = LBAAngles::LBAAngles::ANGLE_0;
actor->_animStep = IVec3();
}
}
}
Collision* collision = _engine->_collision;
// actor standing on another actor
if (actor->_carryBy != -1) {
const ActorStruct *standOnActor = _engine->_scene->getActor(actor->_carryBy);
processActor -= standOnActor->_oldPos;
processActor += standOnActor->posObj();
if (!collision->checkZvOnZv(actorIdx, actor->_carryBy)) {
actor->_carryBy = -1; // no longer standing on other actor
}
}
// actor falling Y speed
if (actor->_dynamicFlags.bIsFalling) {
processActor = previousActor;
processActor.y += _engine->_loopActorStep; // add step to fall
}
// actor collisions with bricks
if (actor->_staticFlags.bComputeCollisionWithBricks) {
ShapeType brickShape = _engine->_grid->worldColBrick(previousActor);
if (brickShape != ShapeType::kNone) {
if (brickShape == ShapeType::kSolid) {
actor->_pos.y = processActor.y = (processActor.y / SIZE_BRICK_Y) * SIZE_BRICK_Y + SIZE_BRICK_Y; // go upper
} else {
collision->reajustPos(processActor, brickShape);
}
}
if (actor->_staticFlags.bComputeCollisionWithObj) {
collision->checkObjCol(actorIdx);
}
if (actor->_carryBy != -1 && actor->_dynamicFlags.bIsFalling) {
collision->receptionObj(actorIdx);
}
collision->_causeActorDamage = 0;
// TODO: hack to fix tank-not-moving bug https://bugs.scummvm.org/ticket/13177
// remove processActorSave
const IVec3 processActorSave = processActor;
collision->setCollisionPos(processActor);
if (IS_HERO(actorIdx) && !actor->_staticFlags.bComputeLowCollision) {
// check hero collisions with bricks
collision->doCornerReajustTwinkel(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 1);
collision->doCornerReajustTwinkel(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 2);
collision->doCornerReajustTwinkel(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 4);
collision->doCornerReajustTwinkel(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 8);
} else {
// check other actors collisions with bricks
collision->doCornerReajust(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 1);
collision->doCornerReajust(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 2);
collision->doCornerReajust(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 4);
collision->doCornerReajust(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 8);
}
// TODO: hack to fix tank-not-moving bug https://bugs.scummvm.org/ticket/13177
if (actorIdx == 1 && _engine->_scene->_currentSceneIdx == LBA1SceneId::Hamalayi_Mountains_2nd_fighting_scene) {
processActor = processActorSave;
}
// process wall hit while running
if (collision->_causeActorDamage && !actor->_dynamicFlags.bIsFalling && IS_HERO(actorIdx) && _engine->_actor->_heroBehaviour == HeroBehaviourType::kAthletic && actor->_genAnim == AnimationTypes::kForward) {
IVec3 destPos = _engine->_movements->rotate(actor->_boundingBox.mins.x, actor->_boundingBox.mins.z, actor->_beta + LBAAngles::LBAAngles::ANGLE_360 + LBAAngles::LBAAngles::ANGLE_135);
destPos.x += processActor.x;
destPos.z += processActor.z;
if (destPos.x >= 0 && destPos.z >= 0 && destPos.x <= SCENE_SIZE_MAX && destPos.z <= SCENE_SIZE_MAX) {
if (_engine->_grid->worldColBrick(destPos.x, processActor.y + SIZE_BRICK_Y, destPos.z) != ShapeType::kNone && _engine->_cfgfile.WallCollision) { // avoid wall hit damage
_engine->_extra->initSpecial(actor->_pos.x, actor->_pos.y + 1000, actor->_pos.z, ExtraSpecialType::kHitStars);
initAnim(AnimationTypes::kBigHit, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx);
if (IS_HERO(actorIdx)) {
_engine->_movements->_lastJoyFlag = true;
}
actor->addLife(-1);
}
}
}
brickShape = _engine->_grid->worldColBrick(processActor);
actor->setBrickShape(brickShape);
if (brickShape != ShapeType::kNone) {
if (brickShape == ShapeType::kSolid) {
if (actor->_dynamicFlags.bIsFalling) {
collision->receptionObj(actorIdx);
processActor.y = (collision->_collision.y * SIZE_BRICK_Y) + SIZE_BRICK_Y;
} else {
if (IS_HERO(actorIdx) && _engine->_actor->_heroBehaviour == HeroBehaviourType::kAthletic && actor->_genAnim == AnimationTypes::kForward && _engine->_cfgfile.WallCollision) { // avoid wall hit damage
_engine->_extra->initSpecial(actor->_pos.x, actor->_pos.y + 1000, actor->_pos.z, ExtraSpecialType::kHitStars);
initAnim(AnimationTypes::kBigHit, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx);
_engine->_movements->_lastJoyFlag = true;
actor->addLife(-1);
}
// no Z coordinate issue
if (_engine->_grid->worldColBrick(processActor.x, processActor.y, previousActor.z) != ShapeType::kNone) {
if (_engine->_grid->worldColBrick(previousActor.x, processActor.y, processActor.z) != ShapeType::kNone) {
return;
} else {
processActor.x = previousActor.x;
}
} else {
processActor.z = previousActor.z;
}
}
} else {
if (actor->_dynamicFlags.bIsFalling) {
collision->receptionObj(actorIdx);
}
collision->reajustPos(processActor, brickShape);
}
actor->_dynamicFlags.bIsFalling = 0;
} else {
if (actor->_staticFlags.bCanFall && actor->_carryBy == -1) {
brickShape = _engine->_grid->worldColBrick(processActor.x, processActor.y - 1, processActor.z);
if (brickShape != ShapeType::kNone) {
if (actor->_dynamicFlags.bIsFalling) {
collision->receptionObj(actorIdx);
}
collision->reajustPos(processActor, brickShape);
} else {
if (!actor->_dynamicFlags.bIsRotationByAnim) {
actor->_dynamicFlags.bIsFalling = 1;
if (IS_HERO(actorIdx) && _engine->_scene->_startYFalling == 0) {
_engine->_scene->_startYFalling = processActor.y;
int32 y = processActor.y - 1 - SIZE_BRICK_Y;
while (y > 0 && ShapeType::kNone == _engine->_grid->worldColBrick(processActor.x, y, processActor.z)) {
y -= SIZE_BRICK_Y;
}
y = (y + SIZE_BRICK_Y) & ~(SIZE_BRICK_Y - 1);
int32 fallHeight = processActor.y - y;
if (fallHeight <= (2 * SIZE_BRICK_Y) && actor->_genAnim == AnimationTypes::kForward) {
actor->_dynamicFlags.bWasWalkingBeforeFalling = 1;
} else {
initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeLoop, AnimationTypes::kAnimInvalid, actorIdx);
}
} else {
initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeLoop, AnimationTypes::kAnimInvalid, actorIdx);
}
}
}
}
}
// if under the map, than die
if (collision->_collision.y == -1) {
actor->setLife(0);
}
} else {
if (actor->_staticFlags.bComputeCollisionWithObj) {
collision->checkObjCol(actorIdx);
}
}
if (collision->_causeActorDamage) {
actor->setBrickCausesDamage();
}
// check and fix actor bounding position
if (processActor.x < 0) {
processActor.x = 0;
}
if (processActor.y < 0) {
processActor.y = 0;
}
if (processActor.z < 0) {
processActor.z = 0;
}
if (processActor.x > SCENE_SIZE_MAX) {
processActor.x = SCENE_SIZE_MAX;
}
if (processActor.z > SCENE_SIZE_MAX) {
processActor.z = SCENE_SIZE_MAX;
}
actor->_pos = processActor;
}
} // namespace TwinE