//---------------------------------------------------------------------------- // // Project : Call To Power 2 // File type : C++ source // Description : CTP API File // Author : Ushhan Gundevia // Continued By : Chad Hogg, Joe Souto // //---------------------------------------------------------------------------- // // - CPT API file. This file contains all the API functions that will be used // to control the units in the games. Each function has a small description // about its usage, the values it accepts and a description on its implementation //---------------------------------------------------------------------------- #include "c3.h" #include "profileai.h" #include "gfx_options.h" #include "Diplomat.h" #include "Scheduler.h" #include "ctpagent.h" #include "ctpgoal.h" #include "ctpai.h" #include "GoalRecord.h" #include "squad.h" #include "mapanalysis.h" #include "governor.h" #include "AgreementMatrix.h" #include "Civilisation.h" #include "DiplomacyProposalRecord.h" #include "robotastar2.h" #include "gaiacontroller.h" #include "OrderRecord.h" #include "AdvanceListRecord.h" #include "AdvanceRecord.h" #include "UnitRecord.h" #include "EndGameObjectRecord.h" //#include "DiffDB.h" #include "profileDB.h" #include "RegardEvent.h" #include "reactevent.h" #include "ProposalResponseEvent.h" #include "CounterResponseEvent.h" #include "ThreatResponseEvent.h" #include "RejectResponseEvent.h" #include "MotivationEvent.h" #include "NProposalEvent.h" #include "ResponseEvent.h" #include "SStateEvent.h" #include "DStateEvent.h" #include "player.h" #include "newturncount.h" #include "CTPDatabase.h" #include "Army.h" #include "ArmyData.h" #include "ArmyPool.h" #include "Unit.h" #include "UnitRecord.h" #include "UnitData.h" #include "UnitPool.h" #include "World.h" #include "time.h" #include "Cell.h" #include "gold.h" #include "RandGen.h" #include "GameSettings.h" #include "SelItem.h" #include "network.h" #include "director.h" #include "SlicEngine.h" #include "tiledmap.h" #include "radarmap.h" #include "civapp.h" extern SelectedItem *g_selected_item; extern Network g_network; extern Director *g_director; extern RadarMap *g_radarMap; extern UnitAstar *g_theUnitAstar; #include "MoveFlags.h" #include enum READINESS_LEVEL; #include "gamefile.h" //for SaveGame function #include "Events.h" #include "GameEventUser.h" #include "Unit.h" #include "settlemap.h" #include "ctpaidebug.h" #include "TurnCnt.h" //Added by Martin Gühmann to access the ConstDB #include "ConstDB.h" //Added by Ushhan #include "SlicFunc.h" #include "slicif_sym.h" #include "SlicSymbol.h" #include "Order.h" #include "CTP_API.h" extern CivApp *g_civApp; //for loadgame extern SaveInfo *g_savedGameRequest; //for savegame extern TurnCount *g_turn; extern SlicEngine *g_slicEngine; extern CTPDatabase *g_theGoalDB; extern ArmyPool *g_theArmyPool; extern World *g_theWorld; extern "C" int SendMessageToTIELT( const char* ); using namespace std; namespace { // Settings for periodic actions // These should be > 0 (otherwise % will crash). // The original ACTIVISION values are 5 for all period. size_t const PERIOD_COMPUTE_ROADS = 2; // From 5 size_t const PERIOD_COMPUTE_TILE_IMPROVEMENTS = 5; size_t const EXPLORE_RESOLUTION = 5; } // namespace /* This function is used to move an army in the direction specified. If movement is not possible in that direction, it chooses the next one till its possible. In CTP2, all units are referenced by adding them to armys. Here armyId is the id of the army which has to be moved. dir is the direction its supposed to move in. player_ptr is a pointer to the player whose army has to be moved. It returns the direction the unit moves in. The direction can have values from 0 - 7 where 0 is North and moving clockwise. */ CTP_API::API_Direction CTP_API::MoveArmy( CTP_API::API_Army p_aArmy, const CTP_API::API_Direction p_iDirection ) { MapPoint pos; Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); army.GetPos( pos ); for( int dir = 0; dir < NOWHERE; ++dir ) { MapPoint dest; int newDir; // Start from the direction that was specified newDir = ( dir + p_iDirection ) % NOWHERE; // Get the neighbouring position in that direction if( pos.GetNeighborPosition( static_cast(newDir), dest ) && dest.IsValid() && army.CanEnter(dest) ) { Path * tmpPath = new Path; tmpPath->SetStart( pos ); tmpPath->AddDir( static_cast(newDir) ); tmpPath->Start( pos ); g_gevManager->AddEvent( GEV_INSERT_Tail, GEV_MoveOrder, GEA_Army, army, GEA_Path, tmpPath, GEA_MapPoint, dest, GEA_Int, FALSE, GEA_End ); return CTP_API::DirFromInt( newDir ); } } // Signifies no movement return API_DIR_INVALID; } /* This function is used to settle a city Here armyId is the id of the settler and player_ptr is a pointer to the player. It returns true or false. */ bool CTP_API::Settle( CTP_API::API_Army p_aArmy ) { Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); if( army->CanSettle() ) { g_gevManager->AddEvent( GEV_INSERT_Tail, GEV_SettleOrder, GEA_Army, army, GEA_End ); return true; } else return false; } /* This function is used to build a unit from a city. Here cityId is the id of the city, unitType is the unit type that has to be built (refer to Units.txt in ctp2_data\default\gamedata\) and player_ptr is a pointer to the player. It returns true or false. */ bool CTP_API::CityBuild( CTP_API::API_City p_cCity, const CTP_API::API_UnitType unitType ) { UnitDynamicArray *city_list = p_cCity.m_pPlayer.GetInternalPlayerPtr()->m_all_cities; if( !g_theUnitPool->IsValid( city_list->Access( p_cCity.m_iCityId ) ) ) return false; CityData *city = city_list->Access( p_cCity.m_iCityId )->GetCityData(); if( city->GetBuildQueue()->GetLen() <= 0 ) { // Nothing in the build queue bool insert_ok = false; if( city->CanBuildUnit( unitType ) ) { insert_ok = city->BuildUnit( unitType ); } return( insert_ok ); } else return false; } /* This function is used to build an improvement in a city. Here cityId is the id of the city, improveType is the improvement type that has to be built (refer to Buildings.txt in ctp2_data\default\gamedata\) and player_ptr is a pointer to the player. It returns true or false. */ bool CTP_API::CityImprove( CTP_API::API_City p_cCity, const CTP_API::API_CityImprovementType p_iImproveType ) { UnitDynamicArray *city_list = p_cCity.m_pPlayer.GetInternalPlayerPtr()->m_all_cities; if( !g_theUnitPool->IsValid( city_list->Access( p_cCity.m_iCityId ) ) ) return false; CityData *city = city_list->Access( p_cCity.m_iCityId )->GetCityData(); // Nothing in the build queue BOOL insert_ok = false; if( city->CanBuildBuilding( p_iImproveType ) ) { insert_ok = city->BuildImprovement( p_iImproveType ); } if( insert_ok ) return true; else return false; } /* This function is used to move an army to the specified point. Here armyId is the id of the army which has to be moved. newPos is the position to move to. It returns if the movement was possible. */ bool CTP_API::MoveArmyTo( CTP_API::API_Army p_aArmy, const CTP_API::API_Location p_lDestination ) { MapPoint pos; Path good_path, bad_path; float total_cost; sint32 is_broken_path; Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); army.GetPos( pos ); if( !p_lDestination.GetMapPoint().IsValid() ) return false; /*********************** bool found = CTPAgent::FindPath(army, newPos, true, path); fprintf( stderr, "Trying to find a path\n"); if (found) { fprintf( stderr, "Path found :)\n"); Path *tmpPath = new Path(path); MapPoint target_pos = tmpPath->GetEnd(); fprintf( stderr, "Path found for army %d\n", armyId); g_gevManager->AddEvent(GEV_INSERT_Tail, GEV_MoveOrder, GEA_Army, army, GEA_Path, tmpPath, GEA_MapPoint, target_pos, GEA_Int, FALSE, GEA_End ); } else { fprintf( stderr, "Path not found :(\n"); } return found; */ /**********************************/ /* double trans_max_r = 0.8; float total_cost; if (! RobotAstar2::s_aiPathing.FindPath(RobotAstar2::PATH_TYPE_DEFAULT, army, army->GetMovementType(), pos, newPos, true, g_theWorld->GetContinent(newPos), trans_max_r, path, total_cost)) { fprintf( stderr, "Path not found :(\n"); return false; } fprintf( stderr, "Path found :)\n"); Path *tmpPath = new Path(path); MapPoint target_pos = tmpPath->GetEnd(); g_gevManager->AddEvent(GEV_INSERT_Tail, GEV_MoveOrder, GEA_Army, army, GEA_Path, tmpPath, GEA_MapPoint, target_pos, GEA_Int, FALSE, GEA_End); return true; ***************************/ Assert( g_theUnitAstar ); fprintf( stderr, "Trying to find path\n" ); // Using the Unit's internal A Star path finding algo // 1 is the player index, which is 1 for human player sint32 r = g_theUnitAstar->FindPath( army, pos, 1, p_lDestination.GetMapPoint(), good_path, is_broken_path, bad_path, total_cost ); Assert( r ); if( is_broken_path ) { fprintf( stderr, "Path not found :( \n" ); return false; } else { fprintf( stderr, "Path found :)\n" ); Path *tmpPath = new Path( good_path ); MapPoint target_pos = tmpPath->GetEnd(); g_gevManager->AddEvent( GEV_INSERT_Tail, GEV_MoveOrder, GEA_Army, army, GEA_Path, tmpPath, GEA_MapPoint, target_pos, GEA_Int, FALSE, GEA_End ); return true; } } /* This function is used to attack a position which has an enemy unit with an army. Here armyId is the id of the army which has to be moved. pos is the position to attack. It returns if the attack was possible. */ CTP_API::API_AttackResult CTP_API::AttackEnemyPosWithArmy( CTP_API::API_Army p_aArmy, const CTP_API::API_Location p_lDestination ) { MapPoint currPos; Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); army.GetPos( currPos ); if( !p_lDestination.GetMapPoint().IsValid() ) return API_ATK_NOT_POSSIBLE; sint32 dist = currPos.NormalizedDistance( p_lDestination.GetMapPoint() ); CellUnitList defender; g_theWorld->GetArmy( p_lDestination.GetMapPoint(), defender ); if( defender.Num() > 0 ) { PLAYER_INDEX defense_owner = defender.GetOwner(); fprintf( stderr, "Position: x = %d, y = %d defended by army of player %d\n", p_lDestination.GetMapPoint().x, p_lDestination.GetMapPoint().y, defense_owner ); if( army.IsEnemy( defense_owner ) && !g_player[1]->WillViolateCeaseFire( defense_owner ) && !g_player[1]->WillViolatePact( defense_owner ) && army.CanFight( defender ) ) { if( dist < 2 ) { fprintf( stderr, "Position: x = %d, y = %d can be attacked by army\n", p_lDestination.GetMapPoint().x, p_lDestination.GetMapPoint().y ); army.Fight( defender ); return API_ATK_HIT_AND_WON; //CH Figure out how to check if API_ATK_HIT_AND_DIED is correct. } else { fprintf( stderr, "Position: x = %d, y = %d too far away from army: moving\n", p_lDestination.GetMapPoint().x, p_lDestination.GetMapPoint().y ); return ( MoveArmyTo( p_aArmy, p_lDestination ) ? API_ATK_MOVED_TOWARDS : API_ATK_NOT_POSSIBLE ); } } else { fprintf( stderr, "Position: x = %d, y = %d occupied by friend or cant be attacked\n", p_lDestination.GetMapPoint().x, p_lDestination.GetMapPoint().y ); } } else { fprintf( stderr, "Position: x = %d, y = %d not occupied by enemy\n", p_lDestination.GetMapPoint().x, p_lDestination.GetMapPoint().y ); } return API_ATK_NOT_POSSIBLE; } /* This function is used to attack a position which has an enemy city with an army. Here armyId is the id of the army which has to be moved. pos is the position to attack. It returns if the attack was possible. */ CTP_API::API_AttackResult CTP_API::AttackCityPosWithArmy( CTP_API::API_Army p_aArmy, const CTP_API::API_Location p_lDestination ) { /* Declare a MapPoint object */ MapPoint currPos; /* Get the army referred to by the argument */ Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); /* Obtain its position */ army.GetPos( currPos ); /* Assert destination is valid */ if( !p_lDestination.GetMapPoint().IsValid() ) return API_ATK_NOT_POSSIBLE; /* Get distance between the unit and the destination */ sint32 dist = currPos.NormalizedDistance( p_lDestination.GetMapPoint() ); /* Declare city object */ CellUnitList cityList; /* Get city on destination location */ Unit city = g_theWorld->GetCity( p_lDestination.GetMapPoint() ); /* If there is a city on that location (ie: if 'city' points to a valid location) */ if( city != Unit( 0 ) ) { cityList.Insert( city ); PLAYER_INDEX city_owner = city.GetOwner(); /* If you're attacking an enemy city, not viloating a treaty, and the unit can fight */ if( army.IsEnemy( city_owner ) && !g_player[1]->WillViolateCeaseFire( city_owner ) && !g_player[1]->WillViolatePact( city_owner ) && army.CanFight( cityList ) ) { /* If within striking distance */ if( dist < 2 ) { /* This tells the unit to fight that city, but you don't know what the result is */ army.Fight( cityList ); /* JS: Note to self: check Fight function in Army.cpp */ return API_ATK_HIT_AND_WON; //CH Figure out how to check if API_ATK_HIT_AND_DIED is correct. } /* Distance too great */ else { fprintf( stderr, "Position: x = %d, y = %d too far away from army: moving\n", p_lDestination.GetMapPoint().x, p_lDestination.GetMapPoint().y ); return ( MoveArmyTo( p_aArmy, p_lDestination.GetMapPoint() ) ? API_ATK_MOVED_TOWARDS : API_ATK_NOT_POSSIBLE ); } } else { fprintf( stderr, "Position: x = %d, y = %d has a friendly city or cant be attacked\n", p_lDestination.GetMapPoint().x, p_lDestination.GetMapPoint().y ); } } else { fprintf( stderr, "Position: x = %d, y = %d has no city\n", p_lDestination.GetMapPoint().x, p_lDestination.GetMapPoint().y ); } /* Attack wasn't possible for various reasons */ return API_ATK_NOT_POSSIBLE; } /* This function is used to assign an army with a defensive stance. Here armyId is the id of the army which has to be assigned. It returns if the action was possible. */ /****** NOTE: Bug: CanEntrench() function only tells if a unit is capable of performing a garrison. So if the unit is not on one of your cities and you issue this query, it will falsely return true. This function should be changed later to indicate whether the unit can garrison its CURRENT location. ******/ bool CTP_API::ArmyToDefend( CTP_API::API_Army p_aArmy ) { MapPoint currPos; Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); ArmyData *aData = army.AccessData(); /* If the unit can garrison, and it is not already garrisoning return true */ if( aData->CanEntrench() && !aData->IsEntrenching() && !aData->IsEntrenched() ) aData->Entrench(); else return false; return true; } /* This is a private function that is used to find whether there is an enemy unit visible from one of your units on the map. If there are any units visible, return an array of all the visible units. The range of the unit can be between 1 - 4. */ bool CTP_API::FindEnemyUnit( CTP_API::API_Army p_aArmy, const int p_iVisionRange, DynamicArray * p_pEnemyList ) { MapPoint look; CellUnitList defender; MapPoint pos; bool found = false; Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); army.GetPos( pos ); p_pEnemyList->Clear(); /* This part was used to test whether we could get units that could not be seen. We can......hence we need to make sure not to look into points that cant be seen, we need exceptions. look.x = 11; look.y = 87; g_theWorld->GetArmy(look, defender); if(defender.Num() > 0) { PLAYER_INDEX defense_owner = defender.GetOwner(); if (army.IsEnemy(defense_owner) && !g_player[1]->WillViolateCeaseFire(defense_owner) && !g_player[1]->WillViolatePact(defense_owner)) { fprintf( stderr, "Hidden Position: x = %d, y = %d defended by army of enemy %d\n", look.x, look.y, defense_owner); //return true; } } */ for( int i = -p_iVisionRange; i < p_iVisionRange + 1; i++ ) { for( int j = -p_iVisionRange - i; j < p_iVisionRange + 1 - i; j++ ) { /* Used for excluding the special blocks that are not visible This is required because the visible area is not a exact square I have only type 2 implemented, you might have to implement exceptions for type 3 and 4 To check this, it helps to draw the grid on a sheet of paper with all the cells numbered. We consider the current home units cell as (0,0) and then draw a box aroud it. Look at the API Doc for an example. */ if( p_iVisionRange == 2 ) { if( i == -2 && ( j == 0 || j == 4 ) ) continue; else if( i == 2 && ( j == 0 || j == -4 ) ) continue; } look.x = pos.x + i; look.y = pos.y + j; if( look.IsValid() ) { g_theWorld->GetArmy( look, defender ); if( defender.Num() > 0 ) { PLAYER_INDEX defense_owner = defender.GetOwner(); if( army.IsEnemy( defense_owner ) && !g_player[1]->WillViolateCeaseFire( defense_owner ) && !g_player[1]->WillViolatePact( defense_owner ) ) { fprintf( stderr, "Position: x = %d, y = %d defended by army of enemy %d\n", look.x, look.y, defense_owner ); for( int k = 0; k < defender.Num(); k++ ) { p_pEnemyList->Insert( defender[k] ); } found = true; } } } } } return found; } /* This is a private function that is used to find whether there is an enemy city visible from one of your units on the map. If there are any cities visible, return an array of all the visible cities. The range of the unit can be between 1 - 4. */ bool CTP_API::FindEnemyCity( CTP_API::API_Army p_aArmy, const int p_iVisionRange, DynamicArray * p_pCityList ) { MapPoint look; Unit city; MapPoint pos; bool found = false; Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); army.GetPos( pos ); p_pCityList->Clear(); for( int i = -p_iVisionRange; i < p_iVisionRange + 1; i++ ) { for( int j = -p_iVisionRange - i; j < p_iVisionRange + 1 - i; j++ ) { if( i == -2 && ( j == 0 || j == 4 ) ) continue; else if( i == 2 && ( j == 0 || j == -4 ) ) continue; look.x = pos.x + i; look.y = pos.y + j; if( look.IsValid() ) { city = g_theWorld->GetCity( look ); //Unit(0) is the default Unit returned if a city is not found if( city != Unit(0) ) { PLAYER_INDEX city_owner = city.GetOwner(); if( army.IsEnemy( city_owner ) && !g_player[1]->WillViolateCeaseFire( city_owner ) && !g_player[1]->WillViolatePact( city_owner ) ) { fprintf( stderr, "Enemy %d has City at: x = %d, y = %d \n", city_owner, look.x, look.y ); p_pCityList->Insert( city ); found = true; } } } } } return found; } bool CTP_API::FindUnexplored( const CTP_API::API_Army p_aArmy, CTP_API::API_Location & p_lUnexplored ) { MapPoint look; MapPoint pos; MapPoint prev; Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); army.GetPos( pos ); // This should search in concentric boxes around the unit. for( int boxnum = 1; boxnum < 100; boxnum++ ) { // Set initial box to check look.x = pos.x + boxnum; look.y = pos.y - 2 * boxnum; // Go around each side of the box for( int side = 0; side < 4; side ++ ) { // Check each tile in that side of the box for( int check = 0; check < 2 * boxnum; check++ ) { // Check each unexplored tile found if( look.IsValid() && !p_aArmy.m_pPlayer.GetInternalPlayerPtr()->IsExplored( look ) ) { // Check each tile surrounding the unexplored area to see if we can move there for( int neighbor = 0; neighbor < 8; neighbor++ ) { switch( neighbor ) { case 0: prev.x = look.x + 1; prev.y = look.y - 2; break; case 1: prev.x = look.x + 1; prev.y = look.y - 1; break; case 2: prev.x = look.x + 1; prev.y = look.y; break; case 3: prev.x = look.x; prev.y = look.y + 1; break; case 4: prev.x = look.x - 1; prev.y = look.y + 2; break; case 5: prev.x = look.x - 1; prev.y = look.y + 1; break; case 6: prev.x = look.x - 1; prev.y = look.y; break; case 7: prev.x = look.x; prev.y = look.y - 1; break; } if( prev.IsValid() && prev != pos ) { if( army.CanEnter( prev ) && p_aArmy.m_pPlayer.GetInternalPlayerPtr()->IsExplored( prev ) && CTP_API::ArmyCanReach( p_aArmy, CTP_API::API_Location( prev ) ) ) { fprintf( stderr, "Army %i at (%i,%i) is moving to unexplored (%i,%i) by way of (%i,%i)\n", p_aArmy.m_iArmyId, pos.x, pos.y, look.x, look.y, prev.x, prev.y ); p_lUnexplored = CTP_API::API_Location( prev.x, prev.y ); return true; } } } fprintf( stderr, "Army %i at (%i,%i) cannot move to unexplored (%i,%i)\n", p_aArmy.m_iArmyId, pos.x, pos.y, look.x, look.y ); } // Check the next tile along that side of the box switch( side ) { case 0: look.y = look.y + 1; break; case 1: look.x = look.x - 1; look.y = look.y + 1; break; case 2: look.y = look.y - 1; break; case 3: look.x = look.x + 1; look.y = look.y - 1; break; } } } } return false; } bool CTP_API::ArmyCanReach( const CTP_API::API_Army p_aArmy, const CTP_API::API_Location p_lCheck ) { MapPoint pos; Path good_path, bad_path; float total_cost; sint32 is_broken_path; Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); army.GetPos( pos ); Assert( g_theUnitAstar ); if( !p_lCheck.GetMapPoint().IsValid() ) return false; //Using the Unit's internal A Star path finding algo //1 is the player index, which is 1 for human player sint32 r = g_theUnitAstar->FindPath( army, pos, 1, p_lCheck.GetMapPoint(), good_path, is_broken_path, bad_path, total_cost ); Assert( r ); if( is_broken_path ) return false; else return true; } CTP_API::API_Player::API_Player( const PLAYER_INDEX p_iIndex ) { m_iIndex = p_iIndex; } CTP_API::API_Player::API_Player( const API_Player & p_pOther ) { m_iIndex = p_pOther.m_iIndex; } CTP_API::API_Player::~API_Player() { } sint32 CTP_API::API_Player::GetGold() const { return g_player[ m_iIndex ]->GetGold(); } const Player * CTP_API::API_Player::GetInternalPlayerPtr() const { return g_player[ m_iIndex ]; } PLAYER_INDEX CTP_API::API_Player::GetIndex() const { return m_iIndex; } CTP_API::API_Army::API_Army( const CTP_API::API_Player p_pPlayer, const sint32 p_iArmyId ) : m_pPlayer( p_pPlayer ) { m_iArmyId = p_iArmyId; unit_type = -1; } /** * Constructor that also allows the unit type to be set. * NOTE: nothing currently uses this constructor * @param p_pPlayer [in] The player who owns this army. * @param p_iArmyId [in] An index into that player's army list. * @param p_iUnitType [in] The integer type of this army */ CTP_API::API_Army::API_Army( const API_Player p_pPlayer, const sint32 p_iArmyId, const int p_iUnitType ) : m_pPlayer( p_pPlayer ) { m_iArmyId = p_iArmyId; unit_type = p_iUnitType; } CTP_API::API_Army::~API_Army() { } CTP_API::API_Location CTP_API::API_Army::GetLocation() const { MapPoint pos; m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( m_iArmyId ).GetPos( pos ); return CTP_API::API_Location( pos ); } sint32 CTP_API::API_Army::GetIndex() const { return m_iArmyId; } CTP_API::API_City::API_City( const CTP_API::API_Player p_pPlayer, const sint32 p_iCityId ) : m_pPlayer( p_pPlayer ) { m_iCityId = p_iCityId; } CTP_API::API_City::~API_City() { } CTP_API::API_Location CTP_API::API_City::GetLocation() const { return CTP_API::API_Location( m_pPlayer.GetInternalPlayerPtr()->m_all_cities->Access( m_iCityId )->GetPos() ); } sint32 CTP_API::API_City::GetIndex() const { return m_iCityId; } CTP_API::API_Location::API_Location( const MapPoint p_pPoint ) { m_iXCoord = p_pPoint.x; m_iYCoord = p_pPoint.y; } CTP_API::API_Location::API_Location( const sint16 p_iXCoord, const sint16 p_iYCoord ) { m_iXCoord = p_iXCoord; m_iYCoord = p_iYCoord; } CTP_API::API_Location::API_Location( const API_Location & p_lOther ) { m_iXCoord = p_lOther.m_iXCoord; m_iYCoord = p_lOther.m_iYCoord; } CTP_API::API_Location::~API_Location() { } sint16 CTP_API::API_Location::GetXCoord() { return m_iXCoord; } sint16 CTP_API::API_Location::GetYCoord() { return m_iYCoord; } MapPoint CTP_API::API_Location::GetMapPoint() const { return MapPoint( m_iXCoord, m_iYCoord ); } CTP_API::API_Direction CTP_API::DirFromInt( const int p_iDirection ) { switch( p_iDirection ) { case 0: return API_DIR_NORTH; case 1: return API_DIR_NORTHEAST; case 2: return API_DIR_EAST; case 3: return API_DIR_SOUTHEAST; case 4: return API_DIR_SOUTH; case 5: return API_DIR_SOUTHWEST; case 6: return API_DIR_WEST; case 7: return API_DIR_NORTHWEST; default: return API_DIR_INVALID; } } /** * Instruct an army to stop fortifying its current location * @param p_aArmy [in] The army to detrench * @return Whether or not it was possible to detrench the unit. */ bool CTP_API::StopDefending( CTP_API::API_Army p_aArmy ) { /* Access this army */ Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); ArmyData *aData = army.AccessData(); /* If the unit is already garrisoning, detrench and return true */ if( aData->IsEntrenching() || aData->IsEntrenched() ) aData->Detrench(); else return false; return true; } /** * Query if a unit can build a city at its current location * @param p_aArmy [in] The army including a settler unit. * @return Whether or not it was possible to settle. */ bool CTP_API::QueryCityBuildable( const CTP_API::API_Army p_aArmy) { /* Get the army referred to by p_aArmy -JS */ Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); /* If this unit can settle, return true, otherwise return false -JS */ if( army->CanSettle() ) { return true; } else { return false; } return true; } /** * Query if a unit can move to a given location on the map * @param p_aArmy [in] The army to move to another location * @param p_lLocation [in] The desired location to move the army to * @return Whether or not it is possible to move the army towards the destination */ bool CTP_API::QueryMoveable( CTP_API::API_Army p_aArmy, const CTP_API::API_Location p_lLocation ) { /* Variables */ MapPoint pos; Path good_path, bad_path; float total_cost; sint32 is_broken_path; /* Get the right unit */ Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); army.GetPos( pos ); /* Assert the destination is valid */ if( !p_lLocation.GetMapPoint().IsValid() ) return false; Assert( g_theUnitAstar ); // Using the Unit's internal A Star path finding algo // 1 is the player index, which is 1 for human player sint32 r = g_theUnitAstar->FindPath( army, pos, 1, p_lLocation.GetMapPoint(), good_path, is_broken_path, bad_path, total_cost ); Assert( r ); /* If the path exists, return true. If not, return false */ if( is_broken_path ) { return false; } else { return true; } } /** * Query if a unit can be produced in a given city you control * There are currently 5 types of units that can be built: * Settler = 54 --builds cities * Warrior = 70 --basic military unit * Hoplite = 30 --defensive unit * Archer = 2 --ranged military unit, requires ballistics, agriculture, and tool making advances * Diplomat = 20 --diplomatic unit, requires beauracracy advance (which requires a lot of other advances, see the Great Library in the game) * @param unit_type [in] the integer representation of the unit type you wish to build * @param p_cCity [in] the city to produce the new unit * @return Whether or not it is possible to produce that unit type in that city */ bool CTP_API::QueryUnitBuildable( int unit_type, API_City p_cCity ) { /* Access this city */ UnitDynamicArray *city_list = p_cCity.m_pPlayer.GetInternalPlayerPtr()->m_all_cities; /* Check the city is valid */ if( !g_theUnitPool->IsValid( city_list->Access( p_cCity.m_iCityId ) ) ) return false; /* Access the city data */ CityData *city = city_list->Access( p_cCity.m_iCityId )->GetCityData(); /* Can only add a unit to the queue if it is empty */ if( (city->GetBuildQueue()->GetLen() <= 0) && (city->CanBuildUnit(unit_type)) ) { /* If the queue is empty and the unit can be built, return true */ return true; } /* Otherwise return false */ else { return false; } } /** * Query if an improvement can be built in a given city you control * There are currently 4 types of city improvements that can be built: * Ballista Towers = 7 --defensive improvement, requires tool making, agriculture, and ballistics advances * Bazaar = 11 --gives gold bonus, requries writing, jurisprudence, agriculture, and trade advances * Granary = 30 --gives food bonus * Shrine = 47 --gives happiness bonus * @param improvement_type [in] the integer representation of the improvement type you wish to build * @param p_cCity [in] the city to build the improvement * @return Whether or not it is possible to build that improvement in that city */ bool CTP_API::QueryImprovementBuildable( int improvement_type, API_City p_cCity ) { /* Access the city */ UnitDynamicArray *city_list = p_cCity.m_pPlayer.GetInternalPlayerPtr()->m_all_cities; /* Check that the city is valid */ if( !g_theUnitPool->IsValid( city_list->Access( p_cCity.m_iCityId ) ) ) return false; /* Access city data */ CityData *city = city_list->Access( p_cCity.m_iCityId )->GetCityData(); /* If the improvement can be built return true, otherwise return false */ if( city->CanBuildBuilding( improvement_type ) ) { return true; } else { return false; } } /** * Query if a unit can garrison its current location * @param p_aArmy [in] the unit to garrison its current location * @return Whether or not it is possible to garrison at the current location */ /****** NOTE: Bug: CanEntrench() function only tells if a unit is capable of performing a garrison. So if the unit is not on one of your cities and you issue this query, it will falsely return true. This function and ArmyToDefend should be changed later to indicate whether the unit can garrison its CURRENT location. ******/ bool CTP_API::QueryGarrison( API_Army p_aArmy ) { MapPoint currPos; Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); ArmyData *aData = army.AccessData(); /* If the unit can garrison, and it is not already garrisoning return true */ if( aData->CanEntrench() && !aData->IsEntrenching() && !aData->IsEntrenched() ) return true; else return false; } /** * Query if a unit can garrison its current location * @param p_aArmy [in] the unit to garrison its current location * @return Whether or not it is possible to garrison at the current location */ bool CTP_API::QueryUngarrison( API_Army p_aArmy ) { /* Access this army */ Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); ArmyData *aData = army.AccessData(); /* If the unit is already garrisoning, detrench and return true */ if( aData->IsEntrenching() || aData->IsEntrenched() ) return true; else return false; /* Should never reach here */ return true; } /** * Query if a unit can attack an enemy unit on a given location * @param p_aArmy [in] the unit to perform the attack * @param p_lLocation [in] the location with the unit you want to attack * @return Whether or not it is possible to attack that unit */ bool CTP_API::QueryUnitAttackable( API_Army p_aArmy, API_Location p_lDestination ) { MapPoint currPos; Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); army.GetPos( currPos ); if( !p_lDestination.GetMapPoint().IsValid() ) return false; sint32 dist = currPos.NormalizedDistance( p_lDestination.GetMapPoint() ); CellUnitList defender; g_theWorld->GetArmy( p_lDestination.GetMapPoint(), defender ); if( defender.Num() > 0 ) { PLAYER_INDEX defense_owner = defender.GetOwner(); if( army.IsEnemy( defense_owner ) && !g_player[1]->WillViolateCeaseFire( defense_owner ) && !g_player[1]->WillViolatePact( defense_owner ) && army.CanFight( defender ) ) { if( dist < 2 ) { return true; } else { /* JS: Note that this could be redesigned so that the function returns more than just true/false to inform the agent of why the attack can't be performed. When that is done, this should make a call to MoveArmyTo (see commented line below) and return something to TIELT like "too far, but moved closer" */ /* Position too far away from army */ return false; //return ( MoveArmyTo( p_aArmy, p_lDestination ) ? API_ATK_MOVED_TOWARDS : API_ATK_NOT_POSSIBLE ); } } else { /* Position occupied by friend or cant be attacked */ return false; } } else { /* Position not occupied by enemy */ return false; } return false; } /** * Query if a unit can attack an enemy city on a given location * @param p_aArmy [in] the unit to perform the attack * @param p_lLocation [in] the location with the city you want to attack * @return Whether or not it is possible to attack that city */ bool CTP_API::QueryCityAttackable( API_Army p_aArmy, API_Location p_lDestination ) { /* Declare a MapPoint object */ MapPoint currPos; /* Get the army referred to by the argument */ Army army = p_aArmy.m_pPlayer.GetInternalPlayerPtr()->m_all_armies->Access( p_aArmy.m_iArmyId ); Assert( army ); /* Obtain its position */ army.GetPos( currPos ); /* Assert destination is valid */ if( !p_lDestination.GetMapPoint().IsValid() ) return false; /* Get distance between the unit and the destination */ sint32 dist = currPos.NormalizedDistance( p_lDestination.GetMapPoint() ); /* Declare city object */ CellUnitList cityList; /* Get city on destination location */ Unit city = g_theWorld->GetCity( p_lDestination.GetMapPoint() ); /* If unit there is a city */ if( city != Unit( 0 ) ) { cityList.Insert( city ); PLAYER_INDEX city_owner = city.GetOwner(); /* If you're attacking an enemy city, not viloating a treaty, and the unit can fight */ if( army.IsEnemy( city_owner ) && !g_player[1]->WillViolateCeaseFire( city_owner ) && !g_player[1]->WillViolatePact( city_owner ) && army.CanFight( cityList ) ) { /* If within striking distance */ if( dist < 2 ) { return true; } /* Distance too great */ else { /* JS: Note that this could be redesigned so that the function returns more than just true/false to inform the agent of why the attack can't be performed. When that is done, this should make a call to MoveArmyTo (see commented line below) and return something to TIELT like "too far, but moved closer" */ /* Position too far away from army */ return false; //return ( MoveArmyTo( p_aArmy, p_lDestination.GetMapPoint() ) ? API_ATK_MOVED_TOWARDS : API_ATK_NOT_POSSIBLE ); } } else { /* Position has a friendly city or cant be attacked */ return false; } } else { /* Position has no city */ return false; } /* Attack wasn't possible for various reasons */ return false; } /** * Load a saved game * Note that this calls g_slicEngine's SetLoadGame, which eventaully triggers the * LoadSavedGame function defined in civapp.cpp * @param name [in] name of the game to be loaded * @returns true */ bool CTP_API::LoadGame(char name[]) { /* LoadSavedGame expects an MBCHAR*, so we need to convert name */ MBCHAR* game; game = new MBCHAR[1024]; sprintf(game, "%s", name); /* Set the name of the game we want loaded */ g_slicEngine->SetLoadGame(game); //g_slicEngine->SetLoadGame("C:\\devel\\~myProjs\\CTP2\\ctp2_code\\ctp\\save\\games\\Julius\\qwerty"); /* Return */ return true; } /** * Save the current game. * Note that this calls the Save function defined in fileio\GameFile.cpp * @param name [in] name of the game to save * @return 0 on success, >0 on failure */ int CTP_API::SaveGame(char name[]) { /* Make a new file */ GameFile * game = new GameFile; /* Call Save function */ if (game->Save(name, g_savedGameRequest) == GAMEFILE_ERR_STORE_OK) { delete game; return 0; } /* Save function failed, return 1 */ else { delete game; return 1; } /* g_savedGameRequest is invalid, return 2 */ return 2; } PLAYER_INDEX CTP_API::g_Me; bool CTP_API::g_bTIELTShouldListen;