/* 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 . * */ #include "twine/scene/animations.h" #include "common/util.h" #include "twine/audio/sound.h" #include "twine/debugger/debug_state.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::patchInterAngle(int32 deltaTime, int32 keyFrameLength, int16 newAngle1, int16 lastAngle1) const { const int16 lastAngle = ClampAngle(lastAngle1); const int16 nextAngle = ClampAngle(newAngle1); int16 angleDiff = nextAngle - 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::patchInterStep(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::doSetInterAnimObjet(int32 framedest, const AnimData &animData, BodyData &pBody, AnimTimerDataStruct *ptranimdest, bool global) { if (!pBody.isAnimated()) { return false; } const KeyFrame *keyFrame = animData.getKeyframe(framedest); const int16 numBones = pBody.getNumBones(); int32 numOfBonesInAnim = animData.getNumBoneframes(); if (numOfBonesInAnim > numBones) { numOfBonesInAnim = numBones; } const int32 timeDest = keyFrame->length; const KeyFrame *lastKeyFramePtr = ptranimdest->ptr; int32 remainingFrameTime = ptranimdest->time; if (lastKeyFramePtr == nullptr) { lastKeyFramePtr = keyFrame; remainingFrameTime = timeDest; } const int32 time = _engine->timerRef - remainingFrameTime; if (time >= timeDest) { ptranimdest->ptr = keyFrame; if (global) { ptranimdest->time = _engine->timerRef; _animStep.x = keyFrame->x; _animStep.y = keyFrame->y; _animStep.z = keyFrame->z; _animMasterRot = keyFrame->animMasterRot; _animStepAlpha = ToAngle(keyFrame->animStepAlpha); _animStepBeta = ToAngle(keyFrame->animStepBeta); _animStepGamma = ToAngle(keyFrame->animStepGamma); } copyKeyFrameToState(keyFrame, pBody, numOfBonesInAnim); return true; } if (global) { _animStep.x = keyFrame->x; _animStep.y = keyFrame->y; _animStep.z = keyFrame->z; _animMasterRot = keyFrame->animMasterRot; _animStepAlpha = (keyFrame->animStepAlpha * time) / timeDest; _animStepBeta = (keyFrame->animStepBeta * time) / timeDest; _animStepGamma = (keyFrame->animStepGamma * time) / timeDest; } if (numOfBonesInAnim <= 1) { return false; } int16 boneIdx = 1; int16 tmpNumOfPoints = MIN(lastKeyFramePtr->boneframes.size() - 1, numOfBonesInAnim - 1); do { BoneFrame *boneState = pBody.getBoneState(boneIdx); const BoneFrame &boneFrame = keyFrame->boneframes[boneIdx]; const BoneFrame &lastBoneFrame = lastKeyFramePtr->boneframes[boneIdx]; boneState->type = boneFrame.type; switch (boneFrame.type) { case BoneType::TYPE_ROTATE: boneState->x = patchInterAngle(time, timeDest, boneFrame.x, lastBoneFrame.x); boneState->y = patchInterAngle(time, timeDest, boneFrame.y, lastBoneFrame.y); boneState->z = patchInterAngle(time, timeDest, boneFrame.z, lastBoneFrame.z); break; case BoneType::TYPE_TRANSLATE: case BoneType::TYPE_ZOOM: boneState->x = patchInterStep(time, timeDest, boneFrame.x, lastBoneFrame.x); boneState->y = patchInterStep(time, timeDest, boneFrame.y, lastBoneFrame.y); boneState->z = patchInterStep(time, timeDest, 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); _animStep.x = keyFrame->x; _animStep.y = keyFrame->y; _animStep.z = keyFrame->z; _animMasterRot = keyFrame->animMasterRot; _animStepBeta = ToAngle(keyFrame->animStepBeta); animTimerDataPtr->ptr = animData.getKeyframe(keyframeIdx); animTimerDataPtr->time = _engine->timerRef; 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->timerRef; 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::setInterDepObjet(int32 keyframeIdx, const AnimData &animData, AnimTimerDataStruct *animTimerDataPtr) { const KeyFrame *keyFrame = animData.getKeyframe(keyframeIdx); const int32 timeDest = keyFrame->length; int32 remainingFrameTime = animTimerDataPtr->time; if (animTimerDataPtr->ptr == nullptr) { remainingFrameTime = timeDest; } const int32 time = _engine->timerRef - remainingFrameTime; _animMasterRot = keyFrame->animMasterRot; if (time >= timeDest) { _animStep.x = keyFrame->x; _animStep.y = keyFrame->y; _animStep.z = keyFrame->z; _animStepAlpha = ToAngle(keyFrame->animStepAlpha); _animStepBeta = ToAngle(keyFrame->animStepBeta); _animStepGamma = ToAngle(keyFrame->animStepGamma); animTimerDataPtr->ptr = animData.getKeyframe(keyframeIdx); animTimerDataPtr->time = _engine->timerRef; return true; // finished animation } _animStep.x = (keyFrame->x * time) / timeDest; _animStep.y = (keyFrame->y * time) / timeDest; _animStep.z = (keyFrame->z * time) / timeDest; _animStepAlpha = ToAngle((keyFrame->animStepAlpha * time) / timeDest); _animStepBeta = ToAngle((keyFrame->animStepBeta * time) / timeDest); _animStepGamma = ToAngle((keyFrame->animStepGamma * time) / timeDest); 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 *actions = actor->_entityDataPtr->getActions(actor->_ptrAnimAction); if (actions == nullptr) { return; } for (const EntityAnim::Action &action : *actions) { debugC(1, TwinE::kDebugAnimation, "Execute animation action %d for actor %d", (int)action.type, actorIdx); switch (action.type) { case ActionType::ACTION_HITTING: if (action.animFrame - 1 == actor->_frame) { actor->_hitForce = action.strength; actor->_workFlags.bIsHitting = 1; } break; case ActionType::ACTION_SAMPLE: if (action.animFrame == actor->_frame) { _engine->_sound->mixSample3D(action.sampleIndex, 0x1000, 1, actor->posObj(), actorIdx); } break; case ActionType::ACTION_SAMPLE_FREQ: if (action.animFrame == actor->_frame) { const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(action.frequency) - (action.frequency / 2); _engine->_sound->mixSample3D(action.sampleIndex, pitchBend, 1, actor->posObj(), actorIdx); } break; case ActionType::ACTION_THROW_EXTRA_BONUS: if (action.animFrame == actor->_frame) { _engine->_extra->throwExtra(actorIdx, actor->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.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->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.z, action.xAngle, actor->_beta + action.yAngle, action.xRotPoint, action.extraAngle); } break; case ActionType::ACTION_SAMPLE_REPEAT: if (action.animFrame == actor->_frame) { _engine->_sound->mixSample3D(action.sampleIndex, 0x1000, action.repeat, actor->posObj(), actorIdx); } break; case ActionType::ACTION_THROW_SEARCH: if (action.animFrame == actor->_frame) { _engine->_extra->addExtraAiming(actorIdx, actor->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.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->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.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; const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(1000) - 500; _engine->_sound->mixSample3D(sampleIdx, pitchBend, 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; const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(1000) - 500; _engine->_sound->mixSample3D(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx); } break; case ActionType::ACTION_HERO_HITTING: if (action.animFrame - 1 == actor->_frame) { actor->_hitForce = magicLevelStrengthOfHit[_engine->_gameState->_magicLevelIdx]; actor->_workFlags.bIsHitting = 1; } break; case ActionType::ACTION_THROW_3D: if (action.animFrame == actor->_frame) { const IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta); const int32 throwX = destPos.x + actor->_posObj.x; const int32 throwY = action.distanceY + actor->_posObj.y; const int32 throwZ = destPos.y + actor->_posObj.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->getAngle(actor->_posObj.y, 0, _engine->_scene->_sceneHero->_posObj.y, distance); const IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta); const int32 throwX = destPos.x + actor->_posObj.x; const int32 throwY = action.distanceY + actor->_posObj.y; const int32 throwZ = destPos.y + actor->_posObj.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 IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta); const int32 x = actor->_posObj.x + destPos.x; const int32 y = actor->_posObj.y + action.distanceY; const int32 z = actor->_posObj.z + destPos.y; _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 IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta); const int32 x = actor->_posObj.x + destPos.x; const int32 y = actor->_posObj.y + action.distanceY; const int32 z = actor->_posObj.z + destPos.y; _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 genNewAnim, AnimType flag, AnimationTypes genNextAnim, int32 actorIdx) { ActorStruct *actor = _engine->_scene->getActor(actorIdx); if (actor->_body == -1) { return false; } if (actor->_flags.bSprite3D) { return false; } if (genNewAnim == actor->_genAnim && actor->_anim != -1) { return true; } if (genNextAnim == AnimationTypes::kNoAnim && actor->_flagAnim != AnimType::kAnimationAllThen) { genNextAnim = actor->_genAnim; } int32 newanim = searchAnim(genNewAnim, 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 = genNewAnim; 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; } BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body); if (actor->_anim == -1) { // if no previous animation setAnimObjet(0, _engine->_resources->_animData[newanim], bodyData, &bodyData._animTimerData); } else { // interpolation between animations stockInterAnim(bodyData, &bodyData._animTimerData); } actor->_anim = newanim; actor->_genAnim = genNewAnim; actor->_nextGenAnim = genNextAnim; actor->_ptrAnimAction = _currentActorAnimExtraPtr; actor->_flagAnim = flag; actor->_frame = 0; actor->_workFlags.bIsHitting = 0; actor->_workFlags.bAnimEnded = 0; actor->_workFlags.bAnimNewFrame = 1; processAnimActions(actorIdx); actor->_animStepBeta = LBAAngles::ANGLE_0; actor->_animStep = IVec3(); debugC(1, TwinE::kDebugAnimation, "Change animation for actor %d to %d", actorIdx, newanim); return true; } void Animations::doAnim(int32 actorIdx) { ActorStruct *actor = _engine->_scene->getActor(actorIdx); if (actor->_body == -1) { return; } const IVec3 &oldPos = actor->_oldPos; IVec3 &processActor = actor->_processActor; if (actor->_flags.bSprite3D) { if (actor->_hitForce) { actor->_workFlags.bIsHitting = 1; } processActor = actor->posObj(); if (!actor->_workFlags.bIsFalling) { if (actor->_srot) { int32 xAxisRotation = actor->realAngle.getRealValueFromTime(_engine->timerRef); if (!xAxisRotation) { if (actor->realAngle.endValue > 0) { xAxisRotation = 1; } else { xAxisRotation = -1; } } const IVec2 xRotPos = _engine->_renderer->rotate(xAxisRotation, 0, actor->_spriteActorRotation); processActor.y = actor->_posObj.y - xRotPos.y; const IVec2 destPos = _engine->_renderer->rotate(0, xRotPos.x, actor->_beta); processActor.x = actor->_posObj.x + destPos.x; processActor.z = actor->_posObj.z + destPos.y; _engine->_movements->initRealValue(LBAAngles::ANGLE_0, actor->_srot, LBAAngles::ANGLE_17, &actor->realAngle); if (actor->_workFlags.bIsSpriteMoving) { if (actor->_doorWidth) { // open door if (getDistance2D(processActor.x, processActor.z, actor->_animStep.x, actor->_animStep.z) >= actor->_doorWidth) { if (actor->_beta == LBAAngles::ANGLE_0) { // down processActor.z = actor->_animStep.z + actor->_doorWidth; } else if (actor->_beta == LBAAngles::ANGLE_90) { // right processActor.x = actor->_animStep.x + actor->_doorWidth; } else if (actor->_beta == LBAAngles::ANGLE_180) { // up processActor.z = actor->_animStep.z - actor->_doorWidth; } else if (actor->_beta == LBAAngles::ANGLE_270) { // left processActor.x = actor->_animStep.x - actor->_doorWidth; } actor->_workFlags.bIsSpriteMoving = 0; actor->_srot = 0; } } else { // close door bool updatePos = false; if (actor->_beta == LBAAngles::ANGLE_0) { // down if (processActor.z <= actor->_animStep.z) { updatePos = true; } } else if (actor->_beta == LBAAngles::ANGLE_90) { // right if (processActor.x <= actor->_animStep.x) { updatePos = true; } } else if (actor->_beta == LBAAngles::ANGLE_180) { // up if (processActor.z >= actor->_animStep.z) { updatePos = true; } } else if (actor->_beta == LBAAngles::ANGLE_270) { // left if (processActor.x >= actor->_animStep.x) { updatePos = true; } } if (updatePos) { processActor = actor->_animStep; actor->_workFlags.bIsSpriteMoving = 0; actor->_srot = 0; } } } } if (actor->_flags.bCanBePushed) { processActor += actor->_animStep; if (actor->_flags.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; BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body); if (bodyData.isAnimated()) { keyFramePassed = setInterDepObjet(actor->_frame, animData, &bodyData._animTimerData); } if (_animMasterRot) { actor->_workFlags.bIsRotationByAnim = 1; } else { actor->_workFlags.bIsRotationByAnim = 0; } actor->_beta = ClampAngle(actor->_beta + _animStepBeta - actor->_animStepBeta); actor->_animStepBeta = _animStepBeta; const IVec2 &destPos = _engine->_renderer->rotate(_animStep.x, _animStep.z, actor->_beta); _animStep.x = destPos.x; _animStep.z = destPos.y; processActor = actor->posObj() + _animStep - actor->_animStep; actor->_animStep = _animStep; actor->_workFlags.bAnimEnded = 0; actor->_workFlags.bAnimNewFrame = 0; if (keyFramePassed) { actor->_frame++; actor->_workFlags.bAnimNewFrame = 1; // if actor have animation actions to process processAnimActions(actorIdx); int16 numKeyframe = actor->_frame; if (numKeyframe == (int16)animData.getNbFramesAnim()) { actor->_workFlags.bIsHitting = 0; if (actor->_flagAnim == AnimType::kAnimationTypeRepeat) { 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::kAnimationTypeRepeat; actor->_frame = 0; actor->_hitForce = 0; } processAnimActions(actorIdx); actor->_workFlags.bAnimEnded = 1; } actor->_animStepBeta = 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->_workFlags.bIsFalling) { processActor = oldPos; processActor.y += _engine->_stepFalling; // add step to fall } // actor collisions with bricks uint32 col1 = 0; /** Cause damage in current processed actor */ if (actor->_flags.bComputeCollisionWithBricks) { ShapeType col = _engine->_grid->worldColBrick(oldPos); if (col != ShapeType::kNone) { if (col == ShapeType::kSolid) { processActor.y = (processActor.y / SIZE_BRICK_Y) * SIZE_BRICK_Y + SIZE_BRICK_Y; // go upper actor->_posObj.y = processActor.y; } else { collision->reajustPos(processActor, col); } } if (actor->_flags.bComputeCollisionWithObj) { collision->checkObjCol(actorIdx); } if (actor->_carryBy != -1 && actor->_workFlags.bIsFalling) { collision->receptionObj(actorIdx); } collision->setCollisionPos(processActor); if (IS_HERO(actorIdx) && !actor->_flags.bComputeLowCollision) { // check hero collisions with bricks col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 1); col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 2); col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 4); col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 8); } else { // check other actors collisions with bricks col1 |= collision->doCornerReajust(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 1); col1 |= collision->doCornerReajust(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 2); col1 |= collision->doCornerReajust(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 4); col1 |= collision->doCornerReajust(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 8); } // process wall hit while running if (col1 && !actor->_workFlags.bIsFalling && IS_HERO(actorIdx) && _engine->_actor->_heroBehaviour == HeroBehaviourType::kAthletic && actor->_genAnim == AnimationTypes::kForward) { IVec2 destPos = _engine->_renderer->rotate(actor->_boundingBox.mins.x, actor->_boundingBox.mins.z, actor->_beta + LBAAngles::ANGLE_315 + LBAAngles::ANGLE_180); destPos.x += processActor.x; destPos.y += processActor.z; if (destPos.x >= 0 && destPos.y >= 0 && destPos.x <= SCENE_SIZE_MAX && destPos.y <= SCENE_SIZE_MAX) { if (_engine->_grid->worldColBrick(destPos.x, processActor.y + SIZE_BRICK_Y, destPos.y) != ShapeType::kNone && _engine->_cfgfile.WallCollision) { // avoid wall hit damage _engine->_extra->initSpecial(actor->_posObj.x, actor->_posObj.y + 1000, actor->_posObj.z, ExtraSpecialType::kHitStars); initAnim(AnimationTypes::kBigHit, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx); if (IS_HERO(actorIdx)) { _engine->_movements->_lastJoyFlag = true; } actor->addLife(-1); } } } col = _engine->_grid->worldColBrick(processActor); actor->setCollision(col); if (col != ShapeType::kNone) { if (col == ShapeType::kSolid) { if (actor->_workFlags.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->_posObj.x, actor->_posObj.y + 1000, actor->_posObj.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, oldPos.z) != ShapeType::kNone) { if (_engine->_grid->worldColBrick(oldPos.x, processActor.y, processActor.z) != ShapeType::kNone) { return; } else { processActor.x = oldPos.x; } } else { processActor.z = oldPos.z; } } } else { if (actor->_workFlags.bIsFalling) { collision->receptionObj(actorIdx); } collision->reajustPos(processActor, col); } if (actor->_workFlags.bIsFalling) { debugC(1, TwinE::kDebugCollision, "Actor %d reset falling", actorIdx); } actor->_workFlags.bIsFalling = 0; } else { if (actor->_flags.bObjFallable && actor->_carryBy == -1) { col = _engine->_grid->worldColBrick(processActor.x, processActor.y - 1, processActor.z); if (col != ShapeType::kNone) { if (actor->_workFlags.bIsFalling) { collision->receptionObj(actorIdx); } collision->reajustPos(processActor, col); } else { if (!actor->_workFlags.bIsRotationByAnim) { debugC(1, TwinE::kDebugCollision, "Actor %d is falling", actorIdx); actor->_workFlags.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->_workFlags.bWasWalkingBeforeFalling = 1; } else { initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx); } } else { initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx); } } } } } // if under the map, than die if (collision->_collision.y == -1) { actor->setLife(0); } } else { if (actor->_flags.bComputeCollisionWithObj) { collision->checkObjCol(actorIdx); } } if (col1) { 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->_posObj = processActor; } } // namespace TwinE