/* 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 "scumm/he/intern_he.h" #include "scumm/he/basketball/basketball.h" #include "scumm/he/basketball/collision/bball_collision.h" #include "scumm/he/basketball/geo_translations.h" namespace Scumm { int LogicHEBasketball::u32_userInitCourt(int courtID) { static const char *courtNames[] = { "", "Dobbaguchi", "Jocindas", "SandyFlats", "Queens", "Park", "Scheffler", "Polk", "McMillan", "CrownHill", "Memorial", "TechState", "Garden", "Moon", "Barn" }; // Make sure nothing on the court is currently initialized. _vm->_basketball->_court->_objectTree.~CCollisionObjectTree(); _vm->_basketball->_court->_objectList.clear(); _vm->_basketball->_court->_homePlayerList.clear(); _vm->_basketball->_court->_awayPlayerList.clear(); // Initialize the shot spots _vm->_basketball->_court->_shotSpot[LEFT_BASKET].center.x = BASKET_X; _vm->_basketball->_court->_shotSpot[LEFT_BASKET].center.y = BASKET_Y; _vm->_basketball->_court->_shotSpot[LEFT_BASKET].center.z = BASKET_Z; _vm->_basketball->_court->_shotSpot[LEFT_BASKET].radius = SHOT_SPOT_RADIUS; _vm->_basketball->_court->_shotSpot[RIGHT_BASKET].center.x = MAX_WORLD_X - BASKET_X; _vm->_basketball->_court->_shotSpot[RIGHT_BASKET].center.y = BASKET_Y; _vm->_basketball->_court->_shotSpot[RIGHT_BASKET].center.z = BASKET_Z; _vm->_basketball->_court->_shotSpot[RIGHT_BASKET].radius = SHOT_SPOT_RADIUS; // Get the name and object file for this court. _vm->_basketball->_court->_name = Common::String(courtNames[courtID]); // Put together to relative path and filename. Common::Path objectFileName = Common::Path(Common::String::format("data/courts/%s.cof", courtNames[courtID])); // Create a file stream to the collision object file Common::File objectFile; if (!objectFile.open(objectFileName)) error("LogicHEBasketball::u32_userInitCourt(): Could not open file '%s'", objectFileName.toString(Common::Path::kNativeSeparator).c_str()); // Read in the object file version char fileVersion[32]; int versionStringLength = objectFile.readUint32LE(); if (versionStringLength <= 0 || versionStringLength > ARRAYSIZE(fileVersion) - 1) error("LogicHEBasketball::u32_userInitCourt(): Read from stream did not read the version string length correctly."); objectFile.read(fileVersion, versionStringLength); fileVersion[versionStringLength] = '\0'; if (strcmp(fileVersion, "01.05")) error("LogicHEBasketball::u32_userInitCourt(): Invalid court version field: %s", fileVersion); // Read in the total number of objects _vm->_basketball->_court->_objectCount = objectFile.readUint32LE(); _vm->_basketball->_court->_objectList.resize(_vm->_basketball->_court->_objectCount); // Keep a list of pointers to the court objects... CCollisionObjectVector objectPtrList; objectPtrList.resize(_vm->_basketball->_court->_objectCount); // Read in each court object... for (int i = 0; i < _vm->_basketball->_court->_objectCount; i++) { CCollisionBox *currentObject = &_vm->_basketball->_court->_objectList[i]; // Read in this object's description... int descriptionStringLength = objectFile.readUint32LE(); char *tmp = (char *)malloc(descriptionStringLength + 1); assert(tmp); objectFile.read(tmp, descriptionStringLength); tmp[descriptionStringLength] = '\0'; Common::String tmp2(tmp); _vm->_basketball->_court->_objectList[i]._description = Common::move(tmp2); free(tmp); // Read in all other object attributes... currentObject->_objectType = (EObjectType)objectFile.readUint32LE(); currentObject->_collisionEfficiency = objectFile.readFloatLE(); currentObject->_friction = objectFile.readFloatLE(); currentObject->_soundNumber = objectFile.readUint32LE(); currentObject->_objectID = objectFile.readUint32LE(); currentObject->minPoint.x = objectFile.readUint32LE(); currentObject->minPoint.y = objectFile.readUint32LE(); currentObject->minPoint.z = objectFile.readUint32LE(); currentObject->maxPoint.x = objectFile.readUint32LE(); currentObject->maxPoint.y = objectFile.readUint32LE(); currentObject->maxPoint.z = objectFile.readUint32LE(); objectPtrList[i] = currentObject; // Decide if this is a backboard, and keep track of it if it is... if (currentObject->_objectType == kBackboard) { // See which backboard it is... if (((currentObject->minPoint.x + currentObject->maxPoint.x) / 2) < (MAX_WORLD_X / 2)) { _vm->_basketball->_court->_backboardIndex[LEFT_BASKET] = i; } else { _vm->_basketball->_court->_backboardIndex[RIGHT_BASKET] = i; } } } _vm->_basketball->_court->_objectTree.initialize(objectPtrList); // Lower all the shields... u32_userLowerShields(ALL_SHIELD_ID); return 1; } int LogicHEBasketball::u32_userDeinitCourt() { // Make sure the collision object tree has been cleared... _vm->_basketball->_court->_objectTree.~CCollisionObjectTree(); // Lower all the shields... u32_userLowerShields(ALL_SHIELD_ID); return 1; } int LogicHEBasketball::u32_userInitBall(U32FltPoint3D &ballLocation, U32FltVector3D &bellVelocity, int radius, int ballID) { _vm->_basketball->_court->_basketBall._description = "Basketball"; _vm->_basketball->_court->_basketBall._objectType = kBall; _vm->_basketball->_court->_basketBall._objectID = ballID; _vm->_basketball->_court->_basketBall.center = ballLocation; _vm->_basketball->_court->_basketBall._velocity = bellVelocity; _vm->_basketball->_court->_basketBall.radius = radius; _vm->_basketball->_court->_basketBall._collisionEfficiency = 1.0F; _vm->_basketball->_court->_basketBall._friction = 0.0F; _vm->_basketball->_court->_basketBall._ignore = false; _vm->_basketball->_court->_basketBall.save(); return 1; } int LogicHEBasketball::u32_userInitVirtualBall(U32FltPoint3D &ballLocation, U32FltVector3D &bellVelocity, int radius, int ballID) { _vm->_basketball->_court->_virtualBall._description = "Virtual Basketball"; _vm->_basketball->_court->_virtualBall._objectType = kBall; _vm->_basketball->_court->_virtualBall._objectID = ballID; _vm->_basketball->_court->_virtualBall.center = ballLocation; _vm->_basketball->_court->_virtualBall._velocity = bellVelocity; _vm->_basketball->_court->_virtualBall.radius = radius; _vm->_basketball->_court->_virtualBall._collisionEfficiency = 1.0F; _vm->_basketball->_court->_virtualBall._friction = 0.0F; _vm->_basketball->_court->_virtualBall._ignore = false; _vm->_basketball->_court->_virtualBall.save(); return 1; } int LogicHEBasketball::u32_userDeinitBall() { _vm->_basketball->_court->_basketBall._ignore = true; return 1; } int LogicHEBasketball::u32_userDeinitVirtualBall() { _vm->_basketball->_court->_virtualBall._ignore = true; return 1; } int LogicHEBasketball::u32_userInitPlayer(int playerID, U32FltPoint3D &playerLocation, int height, int radius, bool playerIsInGame) { if (!((FIRST_PLAYER <= playerID) && (playerID <= LAST_PLAYER))) error("LogicHEBasketball::u32_userInitPlayer(): Passed in invalid player ID"); // Cycle through all of the player slots until an empty one is found... Common::Array *playerList = _vm->_basketball->_court->getPlayerListPtr(playerID); if (playerList->size() < MAX_PLAYERS_ON_TEAM) { CCollisionPlayer newPlayer; newPlayer._objectType = kPlayer; newPlayer._objectID = playerID; newPlayer.height = height; newPlayer._catchHeight = PLAYER_CATCH_HEIGHT; newPlayer.radius = radius; newPlayer.center = playerLocation; newPlayer.center.z = playerLocation.z + (height / 2); newPlayer._collisionEfficiency = 0.5F; newPlayer._friction = 0.5F; newPlayer._playerIsInGame = playerIsInGame; newPlayer.save(); playerList->push_back(newPlayer); return 1; } else { warning("LogicHEBasketball::u32_userInitPlayer(): There were no empty player slots. You can't initialize a new player until you deinit one."); return 0; } } int LogicHEBasketball::u32_userDeinitPlayer(int playerID) { if (!((FIRST_PLAYER <= playerID) && (playerID <= LAST_PLAYER))) error("LogicHEBasketball::u32_userDeinitPlayer(): Passed in invalid player ID"); int index = _vm->_basketball->_court->getPlayerIndex(playerID); Common::Array *playerList = _vm->_basketball->_court->getPlayerListPtr(playerID); playerList->remove_at(index); return 1; } int LogicHEBasketball::u32_userPlayerOff(int playerID) { if (!((FIRST_PLAYER <= playerID) && (playerID <= LAST_PLAYER))) error("LogicHEBasketball::u32_userPlayerOff(): Passed in invalid player ID"); _vm->_basketball->_court->getPlayerPtr(playerID)->_ignore = true; return 1; } int LogicHEBasketball::u32_userPlayerOn(int playerID) { if (!((FIRST_PLAYER <= playerID) && (playerID <= LAST_PLAYER))) error("LogicHEBasketball::u32_userPlayerOn(): Passed in invalid player ID"); _vm->_basketball->_court->getPlayerPtr(playerID)->_ignore = false; return 1; } static void trackCollisionObject(const ICollisionObject &sourceObject, const ICollisionObject &targetObject, CCollisionObjectVector *objectVector) { float currentDist = sourceObject.getObjectDistance(targetObject); // As an object moves backwards along its velocity vector and new collisions // are detected, older collisions may become invalid. Here, we go through prior // collisions, and see which ones are invalidated by this new collision. for (CCollisionObjectVector::const_iterator objectIt = objectVector->begin(); objectIt != objectVector->end(); ++objectIt) { float pastDist = sourceObject.getObjectDistance(**objectIt); // If the distance between the source object and the current target object // is less than or equal to the distance between the source object and the last // target object, then the current target object is stored along side the last // target object. Otherwise, the current object replaces the last object. if ((fabs(pastDist - currentDist) < COLLISION_EPSILON) || (!sourceObject.isCollisionHandled(targetObject)) || (!sourceObject.isCollisionHandled(**objectIt))) { break; } } // Make sure that we aren't keeping track of the same object twice... if (!objectVector->contains(targetObject)) { objectVector->push_back(&targetObject); } } int LogicHEBasketball::u32_userDetectBallCollision(U32FltPoint3D &ballLocation, U32FltVector3D &ballVector, int recordCollision, int ballID) { bool ballIsClear = false; // Flag that indicates if the ball collided with any objects on its current vector bool errorOccurred = false; U32Distance3D distance; // The distance between the ball and a collision object candidate CCollisionObjectVector targetList; // All potential collision candidates CCollisionObjectVector collisionVector; // All objects that have been collided with CCollisionObjectVector rollingVector; // All objects that have been rolled on int collisionOccurred = 0; int rollingHappened = 0; // Determine which ball we're dealing with... CCollisionBasketball *sourceBall = _vm->_basketball->_court->getBallPtr(ballID); // Update the position and vector of the basketball... sourceBall->center = ballLocation; sourceBall->_velocity = ballVector; // Clear the ball's collision stack... sourceBall->_objectCollisionHistory.clear(); sourceBall->_objectRollingHistory.clear(); // Find out who our potential collision candidates are... _vm->_basketball->fillBallTargetList(sourceBall, &targetList); // See if there was an error while traversing the object tree, // if there was put the player in the last known safe position... if (_vm->_basketball->_court->_objectTree.checkErrors()) { sourceBall->restore(); } for (int i = 0; (i < MAX_BALL_COLLISION_PASSES) && (!ballIsClear); i++) { float totalTime = 0.0F; // The time it takes to back out of all objects we have intersected while on the current vector ballIsClear = true; // Go through all of the collision candidates.... for (size_t j = 0; j < targetList.size(); ++j) { const ICollisionObject *targetObject = targetList[j]; assert(targetObject); // See if we intersect the current object... bool intersectionResult = sourceBall->testObjectIntersection(*targetObject, &distance); if (intersectionResult) { // If we are intersecting a moving object, make sure that we actually // ran into them, and they didn't just run into us... if (sourceBall->validateCollision(*targetObject, &distance)) { // If we are intersecting the object, back out of it... if (sourceBall->backOutOfObject(*targetObject, &distance, &totalTime)) { // Move in to the exact point of collision... if (sourceBall->nudgeObject(*targetObject, &distance, &totalTime)) { // Keep track of this object so we can respond to the collision later... trackCollisionObject(*sourceBall, *targetObject, &sourceBall->_objectCollisionHistory); if (sourceBall->isCollisionHandled(*targetObject)) { trackCollisionObject(*sourceBall, *targetObject, &collisionVector); ballIsClear = false; } collisionOccurred = 1; } else { errorOccurred = true; } } else { errorOccurred = true; } } } else { // See if we are passing over a player... if (sourceBall->testCatch(*targetObject, &distance, _vm->_basketball->_court)) { trackCollisionObject(*sourceBall, *targetObject, &sourceBall->_objectCollisionHistory); collisionOccurred = true; } } // See if we are rolling on the current object... if (sourceBall->isOnObject(*targetObject, distance)) { rollingHappened = 1; if (!intersectionResult) { // This is not really a collision, but the ball is rolling, so we want to slow it down... trackCollisionObject(*sourceBall, *targetObject, &rollingVector); trackCollisionObject(*sourceBall, *targetObject, &sourceBall->_objectRollingHistory); } } } // Adjust the ball's velocity and position due to any collisions... sourceBall->handleCollisions(&rollingVector, &totalTime, false); sourceBall->handleCollisions(&collisionVector, &totalTime, true); } // Keep track of how long we've been rolling... if (rollingHappened) { ++sourceBall->_rollingCount; } else { sourceBall->_rollingCount = 0; } // If there were no errors this frame, save the position... if (!errorOccurred) { sourceBall->save(); } writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32FloatToInt(sourceBall->center.x)); writeScummVar(_vm1->VAR_U32_USER_VAR_B, _vm->_basketball->u32FloatToInt(sourceBall->center.y)); writeScummVar(_vm1->VAR_U32_USER_VAR_C, _vm->_basketball->u32FloatToInt(sourceBall->center.z)); writeScummVar(_vm1->VAR_U32_USER_VAR_D, _vm->_basketball->u32FloatToInt(sourceBall->_velocity.x)); writeScummVar(_vm1->VAR_U32_USER_VAR_E, _vm->_basketball->u32FloatToInt(sourceBall->_velocity.y)); writeScummVar(_vm1->VAR_U32_USER_VAR_F, _vm->_basketball->u32FloatToInt(sourceBall->_velocity.z)); writeScummVar(_vm1->VAR_U32_USER_VAR_G, collisionOccurred); writeScummVar(_vm1->VAR_U32_USER_VAR_H, rollingHappened != 0 ? 0 : 1); return 1; } int LogicHEBasketball::u32_userDetectPlayerCollision(int playerID, U32FltPoint3D &playerLocation, U32FltVector3D &playerVector, bool playerHasBall) { U32Distance3D distance; // The distance between the ball and a collision object candidate CCollisionObjectVector collisionVector; // All objects that have been collided with int playerIsOnObject = 0; int collisionOccurred = 0; bool playerIsClear = false; bool errorOccurred = false; float totalTime = 0.0F; // The time it takes to back out of all objects we have intersected on this frame if (!((FIRST_PLAYER <= playerID) && (playerID <= LAST_PLAYER))) error("LogicHEBasketball::u32_userDetectPlayerCollision(): Passed in invalid player ID"); // Get the player who is being tested... CCollisionPlayer *sourcePlayer = _vm->_basketball->_court->getPlayerPtr(playerID); // Update the player's status... sourcePlayer->_playerHasBall = playerHasBall; // In SCUMM code, the center of a player in the z dimension is at their feet. // In U32 code, it is in the middle of the cylinder, so make the translation... playerLocation.z += (sourcePlayer->height / 2); // Update the player's position and velocity... sourcePlayer->center = playerLocation; sourcePlayer->_velocity = playerVector; sourcePlayer->_movementType = kStraight; // Clear the player's collision stack... sourcePlayer->_objectCollisionHistory.clear(); sourcePlayer->_objectRollingHistory.clear(); // Find out who our potential collision candidates are... CCollisionObjectVector targetList; _vm->_basketball->fillPlayerTargetList(sourcePlayer, &targetList); // See if there was an error while traversing the object tree, // if there was put the player in the last known safe position... if (_vm->_basketball->_court->_objectTree.checkErrors()) { sourcePlayer->restore(); } for (int i = 0; (i < MAX_PLAYER_COLLISION_PASSES) && (!playerIsClear); i++) { playerIsClear = 1; // Check all of the collision candidates... for (size_t j = 0; j < targetList.size(); ++j) { const ICollisionObject *targetObject = targetList[j]; assert(targetObject); // See if we intersect the current object... bool intersectionResult = sourcePlayer->testObjectIntersection(*targetObject, &distance); if (intersectionResult) { // If we are intersecting a moving object, make sure that we actually // ran into them, and they didn't just run into us... if (sourcePlayer->validateCollision(*targetObject, &distance)) { // If we are intersecting an object, back out to the exact point of collision... if (sourcePlayer->backOutOfObject(*targetObject, &distance, &totalTime)) { // Move in to the exact point of collision... if (sourcePlayer->nudgeObject(*targetObject, &distance, &totalTime)) { trackCollisionObject(*sourcePlayer, *targetObject, &sourcePlayer->_objectCollisionHistory); collisionOccurred = true; if (sourcePlayer->isCollisionHandled(*targetObject)) { trackCollisionObject(*sourcePlayer, *targetObject, &collisionVector); playerIsClear = false; } } else { errorOccurred = true; } } else { errorOccurred = true; } } } else { // See if the virtual ball is passing over us... if (sourcePlayer->testCatch(*targetObject, &distance, _vm->_basketball->_court)) { trackCollisionObject(*sourcePlayer, *targetObject, &sourcePlayer->_objectCollisionHistory); collisionOccurred = true; } } // See if we are standing on the current object... if (sourcePlayer->isOnObject(*targetObject, distance)) { playerIsOnObject = true; } } sourcePlayer->handleCollisions(&collisionVector, &totalTime, true); } // If there were no errors this frame, save the position... if (!errorOccurred) { sourcePlayer->save(); } writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32FloatToInt(sourcePlayer->center.x)); writeScummVar(_vm1->VAR_U32_USER_VAR_B, _vm->_basketball->u32FloatToInt(sourcePlayer->center.y)); writeScummVar(_vm1->VAR_U32_USER_VAR_C, _vm->_basketball->u32FloatToInt(sourcePlayer->center.z - (sourcePlayer->height / 2))); writeScummVar(_vm1->VAR_U32_USER_VAR_D, _vm->_basketball->u32FloatToInt(sourcePlayer->_velocity.x)); writeScummVar(_vm1->VAR_U32_USER_VAR_E, _vm->_basketball->u32FloatToInt(sourcePlayer->_velocity.y)); writeScummVar(_vm1->VAR_U32_USER_VAR_F, _vm->_basketball->u32FloatToInt(sourcePlayer->_velocity.z)); writeScummVar(_vm1->VAR_U32_USER_VAR_G, collisionOccurred); writeScummVar(_vm1->VAR_U32_USER_VAR_H, playerIsOnObject != 0 ? 0 : 1); return 1; } int LogicHEBasketball::u32_userGetLastBallCollision(int ballID) { EObjectType lastObjectType = kNoObjectType; int objectID = 0; // Determine which ball we're dealing with CCollisionSphere *sourceBall; if (ballID == _vm->_basketball->_court->_basketBall._objectID) { sourceBall = (CCollisionSphere *)&_vm->_basketball->_court->_basketBall; } else if (ballID == _vm->_basketball->_court->_virtualBall._objectID) { sourceBall = (CCollisionSphere *)&_vm->_basketball->_court->_virtualBall; } else { warning("LogicHEBasketball::u32_userGetLastBallCollision(): Invalid ball ID %d.", ballID); sourceBall = (CCollisionSphere *)&_vm->_basketball->_court->_basketBall; } if (!sourceBall->_objectCollisionHistory.empty()) { lastObjectType = sourceBall->_objectCollisionHistory.back()->_objectType; objectID = sourceBall->_objectCollisionHistory.back()->_objectID; sourceBall->_objectCollisionHistory.pop_back(); } else if (!sourceBall->_objectRollingHistory.empty()) { lastObjectType = sourceBall->_objectRollingHistory.back()->_objectType; objectID = sourceBall->_objectRollingHistory.back()->_objectID; sourceBall->_objectRollingHistory.pop_back(); } writeScummVar(_vm1->VAR_U32_USER_VAR_A, lastObjectType); writeScummVar(_vm1->VAR_U32_USER_VAR_B, objectID); return 1; } int LogicHEBasketball::u32_userGetLastPlayerCollision(int playerID) { EObjectType lastObjectType = kNoObjectType; int objectID = 0; bool playerIsOnObject = false; CCollisionPlayer *pPlayer = _vm->_basketball->_court->getPlayerPtr(playerID); if (!pPlayer->_objectCollisionHistory.empty()) { const ICollisionObject *targetObject = pPlayer->_objectCollisionHistory.back(); lastObjectType = targetObject->_objectType; objectID = targetObject->_objectID; // See if we are standing on the current object U32Distance3D distance; pPlayer->testObjectIntersection(*targetObject, &distance); if (pPlayer->isOnObject(*targetObject, distance)) { playerIsOnObject = true; } pPlayer->_objectCollisionHistory.pop_back(); } writeScummVar(_vm1->VAR_U32_USER_VAR_A, lastObjectType); writeScummVar(_vm1->VAR_U32_USER_VAR_B, objectID); writeScummVar(_vm1->VAR_U32_USER_VAR_C, playerIsOnObject); return 1; } } // End of namespace Scumm