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

494 lines
16 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/movements.h"
#include "common/textconsole.h"
#include "twine/input.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/shadeangletab.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/scene.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
Movements::Movements(TwinEEngine *engine) : _engine(engine) {}
IVec3 Movements::getShadowPosition(const IVec3 &pos) { // GetShadow
IVec3 shadowCoord;
const uint8 *ptr = _engine->_grid->getBlockBufferGround(pos, shadowCoord.y);
shadowCoord.x = pos.x;
shadowCoord.z = pos.z;
ShapeType shadowCollisionType;
const int32 blockIdx = *ptr;
if (blockIdx) {
const int32 brickIdx = *(ptr + 1);
const BlockDataEntry *blockPtr = _engine->_grid->getBlockPointer(blockIdx, brickIdx);
shadowCollisionType = (ShapeType)blockPtr->brickShape;
} else {
shadowCollisionType = ShapeType::kNone;
}
_engine->_collision->reajustPos(shadowCoord, shadowCollisionType);
return shadowCoord;
}
void Movements::initRealAngle(int16 startAngle, int16 endAngle, int16 stepAngle, ActorMoveStruct *movePtr) {
movePtr->from = ClampAngle(startAngle);
movePtr->to = ClampAngle(endAngle);
movePtr->numOfStep = ClampAngle(stepAngle);
movePtr->timeOfChange = _engine->_lbaTime;
}
void Movements::clearRealAngle(ActorStruct *actorPtr) {
initRealAngle(actorPtr->_beta, actorPtr->_beta, LBAAngles::ANGLE_0, &actorPtr->_moveAngle);
}
void Movements::setActorAngle(int16 startAngle, int16 endAngle, int16 stepAngle, ActorMoveStruct *movePtr) {
movePtr->from = startAngle;
movePtr->to = endAngle;
movePtr->numOfStep = stepAngle;
movePtr->timeOfChange = _engine->_lbaTime;
}
int32 Movements::getAngleAndSetTargetActorDistance(int32 x1, int32 z1, int32 x2, int32 z2) {
/*
//Pythagoras
targetActorDistance = (int32)sqrt((float)(((z2 - z1)*(z2 - z1) + (x2 - x1)*(x2 - x1))));
if (targetActorDistance == 0)
return 0;
//given two points, we calculate its arc-tangent in radians
//Then we convert from radians (360 degrees == 2*M_PI) to a 10bit value (360 degrees == 1024) and invert the rotation direction
//Then we add an offset of 90 degrees (256) and limit it to the 10bit value range.
return (256 + ((int32)floor((-1024 * atan2((float)(z2-z1), (int32)(x2-x1))) / (2*M_PI)))) % 1024;
*/
int32 difZ = z2 - z1;
const int32 newZ = difZ * difZ;
int32 difX = x2 - x1;
const int32 newX = difX * difX;
bool flag;
// Exchange X and Z
if (newX < newZ) {
const int32 tmpEx = difX;
difX = difZ;
difZ = tmpEx;
flag = true;
} else {
flag = false;
}
_targetActorDistance = (int32)sqrt((float)(newX + newZ));
if (!_targetActorDistance) {
return 0;
}
const int32 destAngle = (difZ * SCENE_SIZE_HALF) / _targetActorDistance;
int32 startAngle = LBAAngles::ANGLE_0;
// stopAngle = LBAAngles::ANGLE_90;
const int16 *shadeAngleTab3(&lba1ShadeAngleTable[LBAAngles::ANGLE_135]);
while (shadeAngleTab3[startAngle] > destAngle) {
startAngle++;
}
if (shadeAngleTab3[startAngle] != destAngle) {
if ((shadeAngleTab3[startAngle - 1] + shadeAngleTab3[startAngle]) / 2 <= destAngle) {
startAngle--;
}
}
int32 finalAngle = LBAAngles::ANGLE_45 + startAngle;
if (difX <= 0) {
finalAngle = -finalAngle;
}
if (flag) {
finalAngle = -finalAngle + LBAAngles::ANGLE_90;
}
return ClampAngle(finalAngle);
}
IVec3 Movements::rotate(int32 x, int32 z, int32 angle) {
if (angle) {
const double radians = AngleToRadians(angle);
const int32 vx = (int32)(x * cos(radians) + z * sin(radians));
const int32 vz = (int32)(z * cos(radians) - x * sin(radians));
return IVec3(vx, 0, vz);
}
return IVec3(x, 0, z);
}
void Movements::initRealAngleConst(int32 start, int32 end, int32 duration, ActorMoveStruct *movePtr) const { // ManualRealAngle
const int16 cstart = ClampAngle(start);
const int16 cend = ClampAngle(end);
movePtr->from = cstart;
movePtr->to = cend;
const int16 numOfStep = (cstart - cend) * 64;
int32 numOfStepInt = ABS(numOfStep);
numOfStepInt /= 64;
numOfStepInt *= duration;
numOfStepInt /= 256;
movePtr->numOfStep = (int16)numOfStepInt;
movePtr->timeOfChange = _engine->_lbaTime;
}
void Movements::ChangedCursorKeys::update(TwinEEngine *engine) {
if (engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
leftChange = leftDown == 0;
leftDown = 1;
} else {
leftChange = leftDown;
leftDown = 0;
}
if (engine->_input->isActionActive(TwinEActionType::TurnRight)) {
rightChange = rightDown == 0;
rightDown = 1;
} else {
rightChange = rightDown;
rightDown = 0;
}
if (engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
backwardChange = backwardDown == 0;
backwardDown = 1;
} else {
backwardChange = backwardDown;
backwardDown = 0;
}
if (engine->_input->isActionActive(TwinEActionType::MoveForward)) {
forwardChange = forwardDown == 0;
forwardDown = 1;
} else {
forwardChange = forwardDown;
forwardDown = 0;
}
}
void Movements::update() {
_previousChangedCursorKeys = _changedCursorKeys;
_previousLoopActionKey = _heroActionKey;
_heroActionKey = _engine->_input->isHeroActionActive();
_changedCursorKeys.update(_engine);
}
bool Movements::processBehaviourExecution(int actorIdx) {
bool executeAction = false;
if (_engine->_input->toggleActionIfActive(TwinEActionType::SpecialAction)) {
executeAction = true;
}
switch (_engine->_actor->_heroBehaviour) {
case HeroBehaviourType::kNormal:
executeAction = true;
break;
case HeroBehaviourType::kAthletic:
_engine->_animations->initAnim(AnimationTypes::kJump, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
case HeroBehaviourType::kAggressive:
if (_engine->_actor->_combatAuto) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
_lastJoyFlag = true;
actor->_beta = actor->_moveAngle.getRealAngle(_engine->_lbaTime);
// TODO: previousLoopActionKey must be handled properly
if (!_previousLoopActionKey || actor->_genAnim == AnimationTypes::kStanding) {
const int32 aggresiveMode = _engine->getRandomNumber(3);
switch (aggresiveMode) {
case 0:
_engine->_animations->initAnim(AnimationTypes::kKick, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
case 1:
_engine->_animations->initAnim(AnimationTypes::kRightPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
case 2:
_engine->_animations->initAnim(AnimationTypes::kLeftPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
}
}
} else {
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
_engine->_animations->initAnim(AnimationTypes::kLeftPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
_lastJoyFlag = true;
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
_engine->_animations->initAnim(AnimationTypes::kRightPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
_lastJoyFlag = true;
} else if (_engine->_input->isActionActive(TwinEActionType::MoveForward)) {
_engine->_animations->initAnim(AnimationTypes::kKick, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
_lastJoyFlag = true;
}
}
break;
case HeroBehaviourType::kDiscrete:
_engine->_animations->initAnim(AnimationTypes::kHide, AnimType::kAnimationTypeLoop, AnimationTypes::kAnimInvalid, actorIdx);
break;
case HeroBehaviourType::kProtoPack:
break;
}
return executeAction;
}
bool Movements::processAttackExecution(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (!_engine->_gameState->_usingSabre) {
// Use Magic Ball
if (_engine->_gameState->hasItem(InventoryItems::kiMagicBall)) {
if (_engine->_gameState->_magicBall == -1) {
_engine->_animations->initAnim(AnimationTypes::kThrowBall, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
}
actor->_beta = actor->_moveAngle.getRealAngle(_engine->_lbaTime);
return true;
}
} else if (_engine->_gameState->hasItem(InventoryItems::kiUseSabre)) {
if (actor->_genBody != BodyType::btSabre) {
_engine->_actor->initBody(BodyType::btSabre, actorIdx);
}
_engine->_animations->initAnim(AnimationTypes::kSabreAttack, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
actor->_beta = actor->_moveAngle.getRealAngle(_engine->_lbaTime);
return true;
}
return false;
}
void Movements::processManualMovementExecution(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->isAttackAnimationActive()) {
return;
}
if (actor->isJumpAnimationActive()) {
return;
}
if (actor->isAttackWeaponAnimationActive()) {
return;
}
if (!_changedCursorKeys || _actionNormal) {
// if walking should get stopped
if (!_engine->_input->isActionActive(TwinEActionType::MoveForward) && !_engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
if (_lastJoyFlag && (_heroActionKey != _previousLoopActionKey || _changedCursorKeys != _previousChangedCursorKeys)) {
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeLoop, AnimationTypes::kAnimInvalid, actorIdx);
}
}
_lastJoyFlag = false;
if (_engine->_input->isActionActive(TwinEActionType::MoveForward)) {
if (!_engine->_scene->_currentActorInZone) {
_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeLoop, AnimationTypes::kAnimInvalid, actorIdx);
}
_lastJoyFlag = true;
} else if (_engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
_engine->_animations->initAnim(AnimationTypes::kBackward, AnimType::kAnimationTypeLoop, AnimationTypes::kAnimInvalid, actorIdx);
_lastJoyFlag = true;
}
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
if (actor->_genAnim == AnimationTypes::kStanding) {
_engine->_animations->initAnim(AnimationTypes::kTurnLeft, AnimType::kAnimationTypeLoop, AnimationTypes::kAnimInvalid, actorIdx);
} else {
if (!actor->_dynamicFlags.bIsRotationByAnim) {
actor->_beta = actor->_moveAngle.getRealAngle(_engine->_lbaTime);
}
}
_lastJoyFlag = true;
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
if (actor->_genAnim == AnimationTypes::kStanding) {
_engine->_animations->initAnim(AnimationTypes::kTurnRight, AnimType::kAnimationTypeLoop, AnimationTypes::kAnimInvalid, actorIdx);
} else {
if (!actor->_dynamicFlags.bIsRotationByAnim) {
actor->_beta = actor->_moveAngle.getRealAngle(_engine->_lbaTime);
}
}
_lastJoyFlag = true;
}
}
}
void Movements::processManualRotationExecution(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (!_engine->_actor->_combatAuto && actor->isAttackAnimationActive()) {
// it is allowed to rotate in auto aggressive mode - but not in manual mode.
return;
}
if (actor->isJumpAnimationActive()) {
return;
}
int16 tempAngle;
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
tempAngle = LBAAngles::ANGLE_90;
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
tempAngle = -LBAAngles::ANGLE_90;
} else {
tempAngle = LBAAngles::ANGLE_0;
}
initRealAngleConst(actor->_beta, actor->_beta + tempAngle, actor->_speed, &actor->_moveAngle);
}
void Movements::processManualAction(int actorIdx) {
if (IS_HERO(actorIdx)) {
_actionNormal = false;
if (_engine->_input->isHeroActionActive()) {
_actionNormal = processBehaviourExecution(actorIdx);
}
}
if (_engine->_input->isActionActive(TwinEActionType::ThrowMagicBall) && !_engine->_gameState->inventoryDisabled()) {
if (processAttackExecution(actorIdx)) {
_lastJoyFlag = true;
}
}
processManualMovementExecution(actorIdx);
processManualRotationExecution(actorIdx);
}
void Movements::processFollowAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const ActorStruct *followedActor = _engine->_scene->getActor(actor->_followedActor);
int32 newAngle = getAngleAndSetTargetActorDistance(actor->posObj(), followedActor->posObj());
if (actor->_staticFlags.bIsSpriteActor) {
actor->_beta = newAngle;
} else {
initRealAngleConst(actor->_beta, newAngle, actor->_speed, &actor->_moveAngle);
}
}
void Movements::processRandomAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_dynamicFlags.bIsRotationByAnim) {
return;
}
if (actor->brickCausesDamage()) {
const int32 angle = ClampAngle(actor->_beta + (_engine->getRandomNumber() & (LBAAngles::ANGLE_180 - 1)) - LBAAngles::ANGLE_90 + LBAAngles::ANGLE_180);
initRealAngleConst(actor->_beta, angle, actor->_speed, &actor->_moveAngle);
actor->_delayInMillis = _engine->getRandomNumber(300) + _engine->_lbaTime + 300;
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeLoop, AnimationTypes::kAnimInvalid, actorIdx);
}
if (!actor->_moveAngle.numOfStep) {
_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeLoop, AnimationTypes::kAnimInvalid, actorIdx);
if (_engine->_lbaTime > actor->_delayInMillis) {
const int32 angle = ClampAngle(actor->_beta + (_engine->getRandomNumber() & (LBAAngles::ANGLE_180 - 1)) - LBAAngles::ANGLE_90);
initRealAngleConst(actor->_beta, angle, actor->_speed, &actor->_moveAngle);
actor->_delayInMillis = _engine->getRandomNumber(300) + _engine->_lbaTime + 300;
}
}
}
void Movements::processTrackAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_offsetTrack == -1) {
actor->_offsetTrack = 0;
}
}
void Movements::processSameXZAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const ActorStruct *followedActor = _engine->_scene->getActor(actor->_followedActor);
actor->_pos.x = followedActor->_pos.x;
actor->_pos.z = followedActor->_pos.z;
}
void Movements::doDir(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_body == -1) {
return;
}
if (actor->_dynamicFlags.bIsFalling) {
if (actor->_controlMode != ControlMode::kManual) {
return;
}
int16 tempAngle = LBAAngles::ANGLE_0;
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
tempAngle = LBAAngles::ANGLE_90;
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
tempAngle = -LBAAngles::ANGLE_90;
}
initRealAngleConst(actor->_beta, actor->_beta + tempAngle, actor->_speed, &actor->_moveAngle);
return;
}
if (!actor->_staticFlags.bIsSpriteActor) {
if (actor->_controlMode != ControlMode::kManual) {
actor->_beta = actor->_moveAngle.getRealAngle(_engine->_lbaTime);
}
}
switch (actor->_controlMode) {
/**
* The Actor's Track Script is stopped. Track Script execution may be started with Life Script of
* the Actor or other Actors (with SET_TRACK(_OBJ) command). This mode does not mean the Actor
* will literally not move, but rather that it's Track Script (also called Move Script) is
* initially stopped. The Actor may move if it is assigned a moving animation.
*/
case ControlMode::kNoMove:
case ControlMode::kFollow2: // unused
case ControlMode::kTrackAttack: // unused
break;
case ControlMode::kManual:
processManualAction(actorIdx);
break;
case ControlMode::kFollow:
processFollowAction(actorIdx);
break;
case ControlMode::kTrack:
processTrackAction(actorIdx);
break;
case ControlMode::kSameXZ:
// TODO: see lSET_DIRMODE and lSET_DIRMODE_OBJ opcodes
processSameXZAction(actorIdx);
break;
case ControlMode::kRandom:
processRandomAction(actorIdx);
break;
default:
warning("Unknown control mode %d", (int)actor->_controlMode);
break;
}
}
} // namespace TwinE