/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "scumm/he/intern_he.h" #include "scumm/he/moonbase/moonbase.h" #include "scumm/he/moonbase/ai_targetacquisition.h" #include "scumm/he/moonbase/ai_main.h" #include "scumm/he/moonbase/ai_weapon.h" namespace Scumm { int Sortie::_sSourceX = 0; int Sortie::_sSourceY = 0; int Sortie::_sTargetX = 0; int Sortie::_sTargetY = 0; Sortie::~Sortie() { for (Common::Array::iterator k = _enemyDefenses.begin(); k != _enemyDefenses.end(); k++) { delete *k; } } void Sortie::setEnemyDefenses(int enemyDefensesScummArray, int defendX, int defendY) { DefenseUnit *thisUnit; int currentPlayer = _ai->getCurrentPlayer(); for (int i = 0; i < 200; i++) { int thisElement = _ai->_vm->_moonbase->readFromArray(enemyDefensesScummArray, 0, i); if (thisElement) { if (_ai->getBuildingOwner(thisElement)) { if (_ai->getPlayerTeam(currentPlayer) != _ai->getBuildingTeam(thisElement)) { int type = _ai->getBuildingType(thisElement); switch (type) { case BUILDING_ANTI_AIR: thisUnit = new AntiAirUnit(_ai); break; case BUILDING_SHIELD: thisUnit = new ShieldUnit(_ai); break; case BUILDING_EXPLOSIVE_MINE: if (_ai->getDistance(_ai->getHubX(thisElement), _ai->getHubY(thisElement), defendX, defendY) < 90) thisUnit = new MineUnit(_ai); else thisUnit = NULL; break; case BUILDING_CRAWLER: thisUnit = NULL; break; default: thisUnit = NULL; break; } if (thisUnit != NULL) { thisUnit->setID(thisElement); thisUnit->setPos(_ai->getHubX(thisElement), _ai->getHubY(thisElement)); if (_ai->getBuildingState(thisElement)) thisUnit->setState(DUS_OFF); _enemyDefenses.push_back(thisUnit); } } } } else { i = 200; } } } int *Sortie::getShotPos() const { int *retVal = new int[2]; retVal[0] = _shotPosX; retVal[1] = _shotPosY; return retVal; } int Sortie::numChildrenToGen() { int retVal = MAX(_enemyDefenses.size(), 1) * NUM_SHOT_POSITIONS * NUM_WEAPONS; return retVal; } IContainedObject *Sortie::createChildObj(int index, int &completionFlag) { float thisDamage; Sortie *retSortie = new Sortie(_ai); int activeDefenses = 0; Common::Array thisEnemyDefenses; // Copy the defensive unit list from the parent for (Common::Array::iterator k = _enemyDefenses.begin(); k != _enemyDefenses.end(); k++) { DefenseUnit *temp; switch ((*k)->getType()) { case DUT_ANTI_AIR: temp = new AntiAirUnit(*k, _ai); break; case DUT_SHIELD: temp = new ShieldUnit(*k, _ai); break; case DUT_MINE: temp = new MineUnit(*k, _ai); break; case DUT_CRAWLER: temp = new CrawlerUnit(*k, _ai); break; default: temp = new ShieldUnit(*k, _ai); break; } thisEnemyDefenses.push_back(temp); } // Calculate the current target from the index DefenseUnit *currentTarget = *(thisEnemyDefenses.begin() + static_cast(index / (NUM_WEAPONS * NUM_SHOT_POSITIONS))); assert(currentTarget); // Pick correct weapon according to index Weapon *currentWeapon = new Weapon(currentTarget->selectWeapon(index % NUM_WEAPONS)); retSortie->setUnitType(currentWeapon->getTypeID()); // Calculate distance from target to source hub int distance = _ai->getDistance(currentTarget->getPosX(), currentTarget->getPosY(), getSourcePosX(), getSourcePosY()); // Pick correct shot position according to index Common::Point *targetCoords; targetCoords = currentTarget->createTargetPos((static_cast(index / NUM_WEAPONS) % NUM_SHOT_POSITIONS), distance, currentWeapon->getTypeID(), getSourcePosX(), getSourcePosY()); retSortie->setShotPos(targetCoords->x, targetCoords->y); // Set the g value based on cost of the weapon retSortie->setValueG(getG() + currentWeapon->getCost()); int AAcounter = 3; // Loop through defensive units, toggling anti-air units and deciding if this weapon will land safely for (Common::Array::iterator i = thisEnemyDefenses.begin(); i != thisEnemyDefenses.end(); i++) { distance = _ai->getDistance((*i)->getPosX(), (*i)->getPosY(), targetCoords->x, targetCoords->y); // Check to see if we're within an active defense's radius if ((distance < (*i)->getRadius()) && ((*i)->getState() == DUS_ON)) { activeDefenses++; // Turn off this anti-air and drop the coverage count if (((*i)->getType() == DUT_ANTI_AIR)) { (*i)->setState(DUS_OFF); if (currentWeapon->getTypeID() == ITEM_CLUSTER) AAcounter--; else AAcounter = 0; } // Essentially disable this weapon choice, due to its impact with a shield, or untriggered anti-air if (((*i)->getType() == DUT_SHIELD) || !AAcounter) { retSortie->setValueG(1000); i = thisEnemyDefenses.end() - 1; } } else { // Turn on any anti-airs that were off the previous turn if (((*i)->getType() == DUT_ANTI_AIR) && ((*i)->getState() == DUS_OFF)) (*i)->setState(DUS_ON); } } // Turn on all the non-anti-air units in preparation for emp's and the next turn for (Common::Array::iterator i = thisEnemyDefenses.begin(); i != thisEnemyDefenses.end(); i++) { if ((*i)->getType() != DUT_ANTI_AIR) { (*i)->setState(DUS_ON); } } // If this weapon is still valid if (retSortie->getValueG() < 1000) { // Apply emp effects and damage to all units in range of weapon for (Common::Array::iterator i = thisEnemyDefenses.begin(); i != thisEnemyDefenses.end(); ) { // Special simulated crawler detonation location used, since it walks a bit if (currentWeapon->getTypeID() == ITEM_CRAWLER) distance = _ai->getDistance((*i)->getPosX(), (*i)->getPosY(), currentTarget->getPosX(), currentTarget->getPosY()); // Normal detonation location used here else { distance = _ai->getDistance((*i)->getPosX(), (*i)->getPosY(), targetCoords->x, targetCoords->y); } if (distance < currentWeapon->getRadius()) { // Apply damage thisDamage = currentWeapon->getDamage(); if ((AAcounter != 3) && (currentWeapon->getTypeID() == ITEM_CLUSTER)) thisDamage = 0; if (!_ai->_vm->_rnd.getRandomNumber(4)) currentWeapon->setTypeID(ITEM_MINE); (*i)->setDamage((int)thisDamage); // Apply emp effect if (currentWeapon->getTypeID() == ITEM_EMP) { (*i)->setState(DUS_OFF); } // Remove destroyed defenses if ((*i)->getArmor() <= 0) { delete *i; i = thisEnemyDefenses.erase(i); } else { i++; } } else { i++; } } } retSortie->setEnemyDefenses(thisEnemyDefenses); delete targetCoords; delete currentWeapon; return retSortie; } float Sortie::calcH() { float retValue = 0; Common::Array thisEnemyDefenses = getEnemyDefenses(); for (Common::Array::iterator i = thisEnemyDefenses.begin(); i != thisEnemyDefenses.end(); i++) { if ((*i)->getState() == DUS_ON) { switch ((*i)->getType()) { case DUT_ANTI_AIR: retValue += 1; // Is it bug in the original? Fixing it may break replay compatibility // fall through case DUT_MINE: retValue += 1; break; case DUT_SHIELD: retValue += 1; break; default: break; } } } return retValue; } int Sortie::checkSuccess() { if (!_enemyDefenses.size()) return SUCCESS; int targetX = getTargetPosX(); int targetY = getTargetPosY(); int targetCheck = 0; for (Common::Array::iterator i = _enemyDefenses.begin(); i != _enemyDefenses.end(); i++) { if (((*i)->getState() == DUS_ON) && ((*i)->getType() != DUT_HUB)) { return 0; } if (((*i)->getPosX() == targetX) && ((*i)->getPosY() == targetY)) targetCheck = 1; } if (!targetCheck) return SUCCESS; // If shot pos == target pos return SUCCESS; if ((targetX == getShotPosX()) && (getTargetPosY() == getShotPosY())) { return SUCCESS; } return 0; } float Sortie::calcT() { return (checkSuccess() != SUCCESS) ? (getG() + calcH()) : SUCCESS; } IContainedObject *Sortie::duplicate() { return this; } void Sortie::printEnemyDefenses() { for (Common::Array::iterator i = _enemyDefenses.begin(); i != _enemyDefenses.end(); i++) { debugC(DEBUG_MOONBASE_AI, "Unit %d - Type: %d, Armor: %d, Status: %d", (*i)->getID(), (*i)->getType(), static_cast((*i)->getArmor()), (*i)->getState()); } } Defender::Defender(AI *ai) : _ai(ai) { _sourceX = _sourceY = 0; _targetX = _targetY = 0; _sourceUnit = 0; _power = 0; _angle = 0; _unit = 0; } int Defender::calculateDefenseUnitPosition(int targetX, int targetY, int index) { int currentPlayer = _ai->getCurrentPlayer(); //get list of near hubs int unitsArray = _ai->getUnitsWithinRadius(targetX + 5, targetY, 480); const int NUM_HUBS = 10; //Order on dist int hubArray[NUM_HUBS] = { 0 }; int hubIndex = 0; for (int i = 0; i < 200; i++) { int thisUnit = _ai->_vm->_moonbase->readFromArray(unitsArray, 0, i); if (thisUnit) { if (((_ai->getBuildingType(thisUnit) == BUILDING_MAIN_BASE) || (_ai->getBuildingType(thisUnit) == BUILDING_OFFENSIVE_LAUNCHER)) && (_ai->getBuildingOwner(thisUnit) == currentPlayer)) { for (int j = 0; j < NUM_HUBS; j++) { if (hubArray[j]) { int distCurrent = _ai->getDistance(targetX, targetY, _ai->getHubX(thisUnit), _ai->getHubY(thisUnit)); int distSaved = _ai->getDistance(targetX, targetY, _ai->getHubX(hubArray[j]), _ai->getHubY(hubArray[j])); if (distCurrent < distSaved) { hubArray[hubIndex] = hubArray[j]; hubArray[j] = thisUnit; hubIndex++; j = 100; } } else { hubArray[j] = thisUnit; hubIndex++; j = 100; } } } } if (hubIndex >= NUM_HUBS) { hubIndex = NUM_HUBS; i = 200; } } _ai->_vm->_moonbase->deallocateArray(unitsArray); //Check if repair is needed int targetUnit = _ai->getClosestUnit(targetX + 5, targetY, 15, currentPlayer, 1, 0, 0, 0); if (targetUnit && (targetUnit != BUILDING_CRAWLER) && (_ai->getBuildingTeam(targetUnit) == _ai->getPlayerTeam(currentPlayer))) { int armor = _ai->getBuildingArmor(targetUnit); if (armor < _ai->getBuildingMaxArmor(targetUnit)) { unitsArray = _ai->getUnitsWithinRadius(targetX + 5, targetY, 170); int defCount = 0; for (int i = 0; i < 200; i++) { int thisUnit = _ai->_vm->_moonbase->readFromArray(unitsArray, 0, i); if (thisUnit) { if (((_ai->getBuildingType(thisUnit) == BUILDING_SHIELD) || (_ai->getBuildingType(thisUnit) == BUILDING_ANTI_AIR)) && (_ai->getBuildingOwner(thisUnit) == currentPlayer) && (_ai->getBuildingState(thisUnit) == 0)) { defCount++; i = 200; } } } _ai->_vm->_moonbase->deallocateArray(unitsArray); if (defCount) { //repair int hubUnit = _ai->getClosestUnit(targetX, targetY, 480, currentPlayer, 1, BUILDING_MAIN_BASE, 1, 110); if (hubUnit && (hubUnit != targetUnit)) { int powAngle = abs(_ai->getPowerAngleFromPoint(_ai->getHubX(hubUnit), _ai->getHubY(hubUnit), targetX, targetY, 20)); int power = powAngle / 360; int angle = powAngle - (power * 360); setTargetX(targetX); setTargetY(targetY); setSourceUnit(hubUnit); setUnit(ITEM_REPAIR); setPower(power); setAngle(angle); return 1; } } } } //For each hub for (int i = 0; i < MIN(NUM_HUBS, hubIndex); i++) { int hubX = _ai->getHubX(hubArray[i]); int hubY = _ai->getHubY(hubArray[i]); //get angle to hub int directAngleToHub = 0; //If this hub is the target if ((hubX == targetX) && (hubY == targetY)) { //make the angle seed point at the closest enemy int enemyUnit = _ai->getClosestUnit(hubX, hubY, _ai->getMaxX(), currentPlayer, 0, 0, 0); directAngleToHub = _ai->calcAngle(targetX, targetY, _ai->getHubX(enemyUnit), _ai->getHubY(enemyUnit)); } else { directAngleToHub = _ai->calcAngle(targetX, targetY, hubX, hubY); } //Number of random chances to land for (int j = 0; j < 3; j++) { //Pick random angle and dist within semicircle (-90 to +90) and (40 to 150) int randAngle = directAngleToHub + _ai->_vm->_rnd.getRandomNumber(179) - 90; int randDist = _ai->_vm->_rnd.getRandomNumber(109) + 40; int x = (int)(targetX + randDist * cos(_ai->degToRad(randAngle))); int y = (int)(targetY + randDist * sin(_ai->degToRad(randAngle))); int powAngle = _ai->getPowerAngleFromPoint(hubX, hubY, x, y, 20); if (powAngle < 0) continue; int power = powAngle / 360; int angle = powAngle - (power * 360); int coords = 0; coords = _ai->simulateBuildingLaunch(hubX, hubY, power, angle, 100, 0); //if valid, return if (coords > 0) { //debugC(DEBUG_MOONBASE_AI, "The prospective launching hub for this defensive unit is: %d", hubArray[i]); setSourceX(hubX); setSourceY(hubY); setTargetX((x + _ai->getMaxX()) % _ai->getMaxX()); setTargetY((y + _ai->getMaxY()) % _ai->getMaxY()); setSourceUnit(hubArray[i]); int unitsArray2 = _ai->getUnitsWithinRadius(targetX + 5, targetY, 200); int shieldCount = 0; for (int k = 0; k < 200; k++) { int thisUnit = _ai->_vm->_moonbase->readFromArray(unitsArray2, 0, k); if (thisUnit) { if ((_ai->getBuildingType(thisUnit) == BUILDING_SHIELD) && (_ai->getBuildingOwner(thisUnit) == currentPlayer)) shieldCount++; if ((_ai->getBuildingType(thisUnit) == BUILDING_BRIDGE) && (_ai->getBuildingOwner(thisUnit) == currentPlayer)) { shieldCount--; shieldCount = MAX(-1, shieldCount); } } } if ((_ai->_vm->_rnd.getRandomNumber((int)pow(3.0f, shieldCount + 1) - 1) == 0) && (_ai->getPlayerEnergy() > 6)) setUnit(ITEM_SHIELD); else setUnit(ITEM_ANTIAIR); setPower(power); setAngle(angle); _ai->_vm->_moonbase->deallocateArray(unitsArray2); return 1; } if (coords < 0) { //drop a bridge for the cord int yCoord = -coords / _ai->getMaxX(); int xCoord = -coords - (yCoord * _ai->getMaxX()); if (_ai->checkIfWaterState(xCoord, yCoord)) { int terrainSquareSize = _ai->getTerrainSquareSize(); xCoord = ((xCoord / terrainSquareSize * terrainSquareSize) + (terrainSquareSize / 2)); yCoord = ((yCoord / terrainSquareSize * terrainSquareSize) + (terrainSquareSize / 2)); int xDist = xCoord - x; int yDist = yCoord - y; x = (int)(xCoord + (terrainSquareSize * 1.414 * (xDist / (abs(xDist) + 1)))); y = (int)(yCoord + (terrainSquareSize * 1.414 * (yDist / (abs(yDist) + 1)))); setTargetX(x); setTargetY(y); int nextUnit = _ai->getClosestUnit(x, y, 480, _ai->getCurrentPlayer(), 1, BUILDING_MAIN_BASE, 1, 120); powAngle = _ai->getPowerAngleFromPoint(_ai->getHubX(nextUnit), _ai->getHubY(nextUnit), x, y, 15); powAngle = abs(powAngle); power = powAngle / 360; angle = powAngle - (power * 360); setSourceUnit(nextUnit); setUnit(ITEM_BRIDGE); setPower(power); setAngle(angle); return 1; } } } } // Else create new hub int count = 0; int coords = 0; if (hubIndex == 0) return -3; do { int sourceHub = hubArray[_ai->_vm->_rnd.getRandomNumber(hubIndex - 1)]; setSourceX(_ai->getHubX(sourceHub)); setSourceY(_ai->getHubY(sourceHub)); setSourceUnit(sourceHub); setUnit(ITEM_HUB); setPower(_ai->_vm->_rnd.getRandomNumber(299) + 200); setAngle(_ai->_vm->_rnd.getRandomNumber(359)); count++; if (count > (NUM_HUBS * 3)) break; coords = _ai->simulateBuildingLaunch(getSourceX(), getSourceY(), getPower(), getAngle(), 100, 0); } while (coords <= 0); if (coords > 0) { setTargetX(coords % _ai->getMaxX()); setTargetY(coords / _ai->getMaxX()); } else { setTargetX(0); setTargetY(0); } return -1; } } // End of namespace Scumm