321 lines
11 KiB
C++
321 lines
11 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 "scumm/he/intern_he.h"
|
|
#include "scumm/he/basketball/court.h"
|
|
#include "scumm/he/basketball/passing.h"
|
|
|
|
namespace Scumm {
|
|
|
|
static float getBallImpactTime(CCollisionSphere *ball, int gravity, int height) {
|
|
// Solve the equation:
|
|
//
|
|
// z + vz * t - 0.5 * g * t * t = height
|
|
//
|
|
// to find out how long before the ball hits the ground...
|
|
float a = -.5 * gravity;
|
|
float b = ball->_velocity.z;
|
|
float c = ball->center.z - ball->radius - height;
|
|
|
|
double tFinal;
|
|
if (((b * b) < (4 * a * c)) || (a == 0)) {
|
|
tFinal = 0;
|
|
} else {
|
|
// See how long before the ball hits the ground...
|
|
tFinal = (-b - sqrt(b * b - 4 * a * c)) / (2 * a);
|
|
tFinal = MAX(0.0, tFinal);
|
|
}
|
|
|
|
return tFinal;
|
|
}
|
|
|
|
int LogicHEBasketball::u32_userComputeAngleOfPass(int velocity, int hDist, int vDist, int gravity) {
|
|
assert(hDist > 0);
|
|
|
|
double theta = _vm->_basketball->getLaunchAngle(velocity, hDist, vDist, gravity);
|
|
|
|
writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32DoubleToInt(theta));
|
|
|
|
return 1;
|
|
}
|
|
|
|
int LogicHEBasketball::u32_userComputeAngleOfBouncePass(int velocity, int hDist, int currentZ, int destZ, int gravity) {
|
|
double theta;
|
|
int vDist;
|
|
|
|
assert(hDist > 0);
|
|
|
|
// Aim the ball at the floor 2/3 the distance between you and your target...
|
|
hDist = (hDist * 2) / 3;
|
|
vDist = 0 - currentZ;
|
|
theta = _vm->_basketball->getLaunchAngle(velocity, hDist, vDist, gravity);
|
|
|
|
writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32DoubleToInt(theta));
|
|
|
|
return 1;
|
|
}
|
|
|
|
int LogicHEBasketball::u32_userHitMovingTarget(U32FltPoint2D sourcePlayer, U32FltPoint2D targetPlayer, U32FltVector2D targetVelocity, int passSpeed) {
|
|
// Calculate the xy speed of the pass. To do this, we need to estimate
|
|
// the elevation angle of the pass. This is because the actual angle
|
|
// will be calculated based on the target location, which is what we
|
|
// are currently getting...
|
|
float xyPassSpeed = passSpeed * cos((PASS_ANGLE * BBALL_M_PI) / 180);
|
|
|
|
// The distance between the target player at future time t and and the
|
|
// passing player is equal to the pass speed times t. Solve for t...
|
|
float a = (targetVelocity.x * targetVelocity.x) +
|
|
(targetVelocity.y * targetVelocity.y) -
|
|
(xyPassSpeed * xyPassSpeed);
|
|
|
|
float b = (2 * targetPlayer.x * targetVelocity.x) +
|
|
(2 * targetPlayer.y * targetVelocity.y) -
|
|
(2 * sourcePlayer.x * targetVelocity.x) -
|
|
(2 * sourcePlayer.y * targetVelocity.y);
|
|
|
|
float c = (targetPlayer.x * targetPlayer.x) +
|
|
(targetPlayer.y * targetPlayer.y) +
|
|
(sourcePlayer.x * sourcePlayer.x) +
|
|
(sourcePlayer.y * sourcePlayer.y) -
|
|
(2 * targetPlayer.x * sourcePlayer.x) -
|
|
(2 * targetPlayer.y * sourcePlayer.y);
|
|
|
|
// Now we have two answer candidates. We want the smallest of the two that is
|
|
// greater than 0...
|
|
double tFinal;
|
|
if (((b * b) < (4 * a * c)) || (a == 0)) {
|
|
tFinal = 0.0;
|
|
} else {
|
|
double t1 = (-b + sqrt(b * b - 4 * a * c)) / (2 * a);
|
|
double t2 = (-b - sqrt(b * b - 4 * a * c)) / (2 * a);
|
|
tFinal = MIN_GREATER_THAN_ZERO(t1, t2);
|
|
tFinal -= BALL_LEAD_TIME; // Put the player at the target spot a few frames
|
|
// before the ball to give them a chance to get ready
|
|
// to catch it...
|
|
tFinal = MAX(0.0, tFinal);
|
|
}
|
|
|
|
assert(tFinal < 50.0);
|
|
|
|
U32FltPoint2D targetPoint;
|
|
targetPoint.x = targetPlayer.x + (targetVelocity.x * tFinal);
|
|
targetPoint.y = targetPlayer.y + (targetVelocity.y * tFinal);
|
|
|
|
writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32FloatToInt(targetPoint.x));
|
|
writeScummVar(_vm1->VAR_U32_USER_VAR_B, _vm->_basketball->u32FloatToInt(targetPoint.y));
|
|
writeScummVar(_vm1->VAR_U32_USER_VAR_C, _vm->_basketball->u32DoubleToInt(tFinal));
|
|
|
|
return 1;
|
|
}
|
|
|
|
int LogicHEBasketball::u32_userGetPassTarget(int playerID, const U32FltVector3D &aimVector) {
|
|
U32Ray3D passerRay;
|
|
|
|
// Get our target candidates...
|
|
Common::Array<CCollisionPlayer> *targetList = (_vm->_basketball->_court->getPlayerListPtr(playerID));
|
|
|
|
// Get the passer...
|
|
CCollisionPlayer *passer = _vm->_basketball->_court->getPlayerPtr(playerID);
|
|
|
|
// Set up a ray from the passer, pointing in the direction of the aim vector...
|
|
passerRay._origin = passer->center;
|
|
passerRay._direction = aimVector.normalize() * MAX_PASSING_DIST;
|
|
|
|
// Get the distance from the passers aim vector to each of the remaining target
|
|
// candidates...
|
|
int passTargetID = NO_PLAYER;
|
|
float leastDist = MAX_PASSING_DIST;
|
|
|
|
for (size_t i = 0; i < targetList->size(); ++i) {
|
|
// Get the current target candidate...
|
|
CCollisionPlayer *target = &(*targetList)[i];
|
|
|
|
// Make sure the target is in the game...
|
|
if (target->_playerIsInGame) {
|
|
// Make sure that the passing player is not a target candidate...
|
|
if (passer->_objectID != target->_objectID) {
|
|
// Point-line distance formula.
|
|
U32FltPoint3D *targetCenter = &target->center;
|
|
|
|
float u = (((targetCenter->x - passerRay._origin.x) * passerRay._direction.x) +
|
|
((targetCenter->y - passerRay._origin.y) * passerRay._direction.y)) /
|
|
(passerRay._direction.magnitude() * passerRay._direction.magnitude());
|
|
|
|
if (u >= 0) {
|
|
U32FltVector3D distance;
|
|
distance.x = targetCenter->x - (passerRay._origin.x + (u * passerRay._direction.x));
|
|
distance.y = targetCenter->y - (passerRay._origin.y + (u * passerRay._direction.y));
|
|
float totalDist = distance.magnitude();
|
|
if (totalDist < leastDist) {
|
|
leastDist = totalDist;
|
|
passTargetID = target->_objectID;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
writeScummVar(_vm1->VAR_U32_USER_VAR_A, passTargetID);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int LogicHEBasketball::u32_userDetectPassBlocker(int playerID, const U32FltPoint3D &targetPoint) {
|
|
int blockerPresent = 0;
|
|
|
|
// Get our blocker candidates....
|
|
Common::Array<CCollisionPlayer> *targetList = (_vm->_basketball->_court->getOpponentListPtr(playerID));
|
|
|
|
// Get the passer and catcher...
|
|
CCollisionPlayer *passer = _vm->_basketball->_court->getPlayerPtr(playerID);
|
|
|
|
// Set up a ray from the passer, pointing in the direction of the aim vector...
|
|
U32FltVector2D passVector = targetPoint - passer->center;
|
|
|
|
// Get the distance from the passers aim vector to each of the remaining target
|
|
// candidates...
|
|
for (size_t i = 0; i < targetList->size(); ++i) {
|
|
CCollisionPlayer *blocker = &(*targetList)[i];
|
|
|
|
if (blocker->_playerIsInGame) {
|
|
// --- Ray -> Circle intersection test ---
|
|
// Get a vector from the passer to the potential blocker...
|
|
U32FltVector2D enemyVector = blocker->center - passer->center;
|
|
|
|
// Project that vector onto the pass vector
|
|
float enemyDistance = enemyVector.projectScalar(passVector);
|
|
|
|
// Test to see if the blocker is behind the pass or behind the pass target...
|
|
float maxBlockerDistance = passVector.magnitude();
|
|
if ((0 > enemyDistance) || (enemyDistance > maxBlockerDistance)) {
|
|
continue;
|
|
}
|
|
|
|
// Find the distance between the blocker and the passVector...
|
|
float mSquared = (enemyVector.magnitude() * enemyVector.magnitude()) -
|
|
(enemyDistance * enemyDistance);
|
|
|
|
// If that distance is less than the blocker's radius, we are done...
|
|
if (mSquared <= ((blocker->radius + _vm->_basketball->_court->_basketBall.radius) * (blocker->radius + _vm->_basketball->_court->_basketBall.radius))) {
|
|
blockerPresent = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
writeScummVar(_vm1->VAR_U32_USER_VAR_A, blockerPresent);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int LogicHEBasketball::u32_userGetBallIntercept(int playerID, int ballID, int playerSpeed, int gravity) {
|
|
double tFinal = 0.0;
|
|
|
|
CCollisionSphere *ball = _vm->_basketball->_court->getBallPtr(ballID);
|
|
CCollisionPlayer *player = _vm->_basketball->_court->getPlayerPtr(playerID);
|
|
|
|
assert(!ball->_ignore);
|
|
|
|
U32Circle playerMarker;
|
|
playerMarker.center = player->center;
|
|
playerMarker.radius = player->radius + ball->radius;
|
|
|
|
U32Ray2D playerRay;
|
|
playerRay._origin = player->center;
|
|
playerRay._direction = player->_velocity;
|
|
|
|
U32Ray2D ballRay;
|
|
ballRay._origin = ball->center;
|
|
ballRay._direction = ball->_velocity;
|
|
|
|
// See if the ball is standing still...
|
|
if (ballRay._direction.magnitude() == 0) {
|
|
tFinal = 0.0;
|
|
} else {
|
|
// See when the ball is going to fall to the player's height...
|
|
float tFall = getBallImpactTime(ball, gravity, (player->height / 2));
|
|
|
|
// See if the ball is headed straight for us...
|
|
U32FltPoint2D intersection;
|
|
if (ballRay.nearIntersection(playerMarker, &intersection)) {
|
|
// Get the time till the ball reaches the player...
|
|
float tPlayerImpact = (ball->center - player->center).xyMagnitude() / ball->_velocity.xyMagnitude();
|
|
|
|
// Decide if it's best to stay put, or run back where the ball will fall to...
|
|
tFinal = MAX(tPlayerImpact, tFall);
|
|
} else {
|
|
// The distance between the target player at future time t and and the
|
|
// ball is equal to the playerSpeed times t. Solve for t...
|
|
float a = (ball->_velocity.x * ball->_velocity.x) +
|
|
(ball->_velocity.y * ball->_velocity.y) -
|
|
(playerSpeed * playerSpeed);
|
|
|
|
float b = (2 * ball->center.x * ball->_velocity.x) +
|
|
(2 * ball->center.y * ball->_velocity.y) -
|
|
(2 * player->center.x * ball->_velocity.x) -
|
|
(2 * player->center.y * ball->_velocity.y);
|
|
|
|
float c = (ball->center.x * ball->center.x) +
|
|
(ball->center.y * ball->center.y) +
|
|
(player->center.x * player->center.x) +
|
|
(player->center.y * player->center.y) -
|
|
(2 * ball->center.x * player->center.x) -
|
|
(2 * ball->center.y * player->center.y);
|
|
|
|
if (((b * b) < (4 * a * c)) || (a == 0)) {
|
|
// Now see if we can get away with just going the way we were...
|
|
if (playerRay.intersection(ballRay, &intersection)) {
|
|
tFinal = (ball->center - intersection).xyMagnitude() / ball->_velocity.xyMagnitude();
|
|
}
|
|
} else {
|
|
// Find the closest place we could intercept the ball...
|
|
tFinal = (-b - sqrt(b * b - 4 * a * c)) / (2 * a);
|
|
if (tFinal < 0) {
|
|
tFinal = (-b + sqrt(b * b - 4 * a * c)) / (2 * a);
|
|
}
|
|
|
|
// See if the ball will come down low enough to catch by that time...
|
|
tFinal = MAX((double)tFall, tFinal);
|
|
}
|
|
|
|
// Now see if we're just better off just going the way we were...
|
|
if (playerRay.intersection(ballRay, &intersection)) {
|
|
float tFutureImpact = (ball->center - intersection).xyMagnitude() / ball->_velocity.xyMagnitude();
|
|
tFinal = MAX((double)tFutureImpact, tFinal);
|
|
}
|
|
}
|
|
|
|
// Now see if we're better off just chasing the ball...
|
|
tFinal = MAX(0.0, tFinal);
|
|
}
|
|
|
|
U32FltPoint2D targetPoint;
|
|
targetPoint.x = ball->center.x + (ball->_velocity.x * tFinal);
|
|
targetPoint.y = ball->center.y + (ball->_velocity.y * tFinal);
|
|
|
|
writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32FloatToInt(targetPoint.x));
|
|
writeScummVar(_vm1->VAR_U32_USER_VAR_B, _vm->_basketball->u32FloatToInt(targetPoint.y));
|
|
|
|
return 1;
|
|
}
|
|
|
|
} // End of namespace Scumm
|