Announcement

Collapse
No announcement yet.

Project: AI Settling

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Project: AI Settling

    I thought there was a thread but couldn't find it (I'll link if I do)

    I hit the issues I think here (and add any I missed as per request)

    1) AI settling only where it Can, meaning that if mountains are excluded it doesn't try or if the CanSettleOn code is used


    2) Supporting distance: The Ai should settle where it can connect its empire or atleast planning to put forts or other connectos there.

    3) Settling distances. I know its a variable but not sure how it relates. Maybe it should be adjusted to reflect road movebonuses so it builds thing is less than one turn distances when roads are available (default const when not) and settles farther with railroads. Long term stuff
    Formerly known as "E" on Apolyton

    See me at Civfanatics.com

  • #2
    For issue 1 I think going in here:

    Code:
    [b]Settlemap.cpp[/b]
    
    bool SettleMap::CanSettlePos(const MapPoint & rc_pos) const
    {
        return !m_invalidCells.Get(rc_pos.x, rc_pos.y);
    }
    
    
    void SettleMap::SetCanSettlePos(const MapPoint & rc_pos, const bool can_settle)
    {
        m_invalidCells.Set(rc_pos.x, rc_pos.y, !can_settle);
    }
    and avoiding the affecting of score is best. But I think it may be a bug/defect that the CanSettlePos function here doesn't correlate to this function:

    Code:
    [b]unitdata.cpp[/b]
    
    BOOL UnitData::CanSettle(const MapPoint &pos) const 
    {
    	return  UDUnitTypeCanSettle(m_type, pos);
    }
    
    BOOL UDUnitTypeCanSettle(sint32 unit_type, const MapPoint &pos) 
    {
    	sint32 i;
    	const UnitRecord *rec = g_theUnitDB->Get(unit_type);   
    	sint32 t = rec->GetSettleCityTypeIndex();
    	if (t < 0) {
    		return FALSE;      
    	}
    	if (g_theUnitDB->Get(t)->GetHasPopAndCanBuild() == FALSE) {
    		return FALSE;                               
    	}
    	if (g_theWorld->HasCity(pos)) 
    		return FALSE;
    //EMOD
    	if (rec->GetNumCanSettleOn() > 0){
    		for(i = 0; i < rec->GetNumCanSettleOn(); i++) {
    			if(rec->GetCanSettleOnIndex(i) == g_theWorld->GetCell(pos)->GetTerrain()) {
    				return TRUE;
    			}
    		}
    		return FALSE;
    	}
    // end EMOD
    
    	if (rec->GetSettleLand() && g_theWorld->IsLand(pos))
    		return TRUE; 
    	else if (rec->GetSettleMountain() && g_theWorld->IsMountain(pos))
    		return TRUE; 
    	else if (rec->GetSettleWater() && g_theWorld->IsWater(pos))
    		return TRUE; 
    	else if (rec->GetSettleSpace() && g_theWorld->IsSpace(pos))
    		return TRUE;
    	
    	return FALSE;
    }
    I inverted those functions just to highlight that there is a mappoint CanSettle function in unitdata and this should be used by the AI since the AI settlemap only looks at invalid cells. From my 'research' the source of invalid cells only comes from handle cit growth which sets cells inside a city radius as invalid ...

    Code:
    void SettleMap::HandleCityGrowth(const Unit & city)
    {
    	
    	MapPoint pos = city.RetPos();
    	CityData *citydata = city.GetCityData();
    	Assert(citydata);
    	PLAYER_INDEX playerId = city.GetOwner();
    
    	
    	sint32 radius = g_theCitySizeDB->Get(citydata->GetSizeIndex())->GetIntRadius();
    	radius += radius + 1;
    	
    	RadiusIterator it(pos, radius);
    	for (it.Start(); !it.End(); it.Next()) 
    	{
    		pos = it.Pos();
    		m_invalidCells.Set(pos.x, pos.y, TRUE);
    	}
    
    ...more code... (but no invalidcells reference)
    So I think its a defect. SO my question is since the bool for settlemap::CanSettlePos is a little different than the bools I handled for canbuildunit because it returns a statement that may be true or false instead of just true or false (sorry little confused why different) should I just change it to

    Code:
    bool SettleMap::CanSettlePos(const MapPoint & rc_pos) const
    {
        return UnitData::CanSettle((rc_pos);
    }
    or how should I use it? And bear in mind I think these map targets are done at the beginning of the game so how does one have it based off the unit the AI can currently build and then if a more advanced settle comes around have it look for new settle targets?

    thanks...
    Formerly known as "E" on Apolyton

    See me at Civfanatics.com

    Comment


    • #3
      In my opinion 1) should be adressed in the source code, while 2) and 3) are questionable and should be adressed by modders rather than in the source code: Modding folks might have different ideas about if and under which circumstances the AI should settle in a compact way / connect their cities or spread their empire / colonize other parts of the map etc. In my own modding project I do basically prefer the AI trying to build a compact empire, but if this fails (because they are stuck on a small island or surrounded by competitors) they shall be allowed to settle wherever they can . But other players/modders may have a different concept in mind -- the same goes for forts, villages, roads, outposts, colonies ... and possibly other aspects. Therefore I would prefer a hardcoded logic that provides a working framework of values/flags/parameters that can be tweaked and does not over-determine the AI's behaviour.

      BTW: Meanwhile my BetterSettling SLIC is one of the few parts of my modding project that work really well -- even on ultra-gigantic maps .

      But there is another issue I would really like to see treated by the brave guys working on the sourcecode : Player placement at gamestart. There are some parameters in const.txt like NICE_RADIUS, MIN_ABSOLUTE_START_DISTANCE etc. I have tried to improve player placement by playing with these values -- with no real success (which means I sometimes have the impression that modifying these values does have some impact though it is not convincing). With the tools currently available there is actually nothing a Mod can do about player placement (apart from providing a map that provides a lot of "good" terrain making player placement easier for the program).
      The modding knowledgebase: CTP2 Bureau (with CTP2 AE Modding Wiki). Modern Times Mod (work in progress): MoT-Mod for CTP2.

      Comment


      • #4
        Originally posted by E
        Code:
        bool SettleMap::CanSettlePos(const MapPoint & rc_pos) const
        {
            return UnitData::CanSettle((rc_pos);
        }
        E, that's nonesense the SettleMap is tarrain dependent only. It isn't unit related. It just contains a list of good placed to settle, whether they are in sea or on land doesn't matter.

        The problem is in the AI logic, the AI assigns settlers to targets that can't be settled by at least some of the units.

        -Martin
        Civ2 military advisor: "No complaints, Sir!"

        Comment


        • #5
          Well this one seems to address units

          Code:
          void CtpAi::AddSettleTargets(const PLAYER_INDEX playerId)
          {
          	
          	Scheduler & scheduler = Scheduler::GetScheduler(playerId);
          	const StrategyRecord & strategy = 
          		Diplomat::GetDiplomat(playerId).GetCurrentStrategy();
          
          	Player *player_ptr = g_player[playerId];
          	Assert(player_ptr);
          	if (player_ptr == NULL)
          		return;
          
          	
          	bool settle_water = false;
          	Unit unit;
          	list weapon_list;
          	Assert(player_ptr->m_all_units);
          	for(sint32 i = 0; i < player_ptr->m_all_units->Num(); i++) {
          		
          		unit = player_ptr->m_all_units->Access(i);
          		if (unit.GetDBRec()->GetSettleWater())
          		{
          			settle_water = true;
          			break;
          		}
          	}
          
          	
          	SettleMap::SettleTargetList targets;
          	SettleMap::s_settleMap.GetSettleTargets(playerId, settle_water, targets);
          
          	
          	SettleMap::SettleTarget settle_target;
          	CTPGoal_ptr goal_ptr;
          
          	SettleMap::SettleTargetList::iterator iter;
          	sint32 desired_goals;
          	sint32 max_desired_goals;
          	uint8 magnitude;
          	char buf[10];
          
          	
          	GOAL_TYPE goal_type;
          	const StrategyRecord::GoalElement *goal_element_ptr;
          	sint16 goal_element;
          	for (goal_element = 0; goal_element < strategy.GetNumGoalElement(); goal_element++) 
          		{
          			goal_element_ptr = strategy.GetGoalElement(goal_element);
          			goal_type = goal_element_ptr->GetGoalIndex();
          
          			
          			Assert(goal_type >= 0);
          
          			
          			if ( !g_theGoalDB->Get(goal_type)->GetTargetTypeSettleLand() &&
          				 !g_theGoalDB->Get(goal_type)->GetTargetTypeSettleSea() )
          				continue;
          
          			max_desired_goals = goal_element_ptr->GetMaxEval() -
          				scheduler.CountGoalsOfType(goal_type);
          
          			desired_goals = max_desired_goals;
          
          			
          			for (iter = targets.begin(); 
          				 iter != targets.end() && (desired_goals > 0); 
          				 iter++)
          				{
          					settle_target = *iter;
          
          					
          					if ( (g_theWorld->IsWater(settle_target.m_pos) == FALSE) && 
          						 (g_theGoalDB->Get(goal_type)->GetTargetTypeSettleLand()) ||
          						 (g_theWorld->IsWater(settle_target.m_pos) == TRUE) && 
          						 (g_theGoalDB->Get(goal_type)->GetTargetTypeSettleSea()))
          						{
          							goal_ptr = new CTPGoal();
          							goal_ptr->Set_Type( goal_type );
          							goal_ptr->Set_Player_Index( playerId );
          							goal_ptr->Set_Target_Pos( settle_target.m_pos );
          
          							scheduler.Add_New_Goal( goal_ptr );
          
          							magnitude = (uint8) (((max_desired_goals - desired_goals)* 255.0) / max_desired_goals);
          							sprintf(buf,"%4.0f",settle_target.m_value);
          							g_graphicsOptions->AddTextToCell(settle_target.m_pos, buf, magnitude);
          							
          							
          							desired_goals--;
          						}
          				}
          	}
          }
          I just have to figure out how to add the unitdata bool CanSettle here.

          or should it be added here, which seems to be the terrain component

          Code:
          void SettleMap::GetSettleTargets(const PLAYER_INDEX &playerId, 
          								 const bool & settle_water,
          								 SettleMap::SettleTargetList & targets) const
          {
          	
          	
          	BoundingRect rect =	
          		MapAnalysis::GetMapAnalysis().GetBoundingRectangle(playerId);
          
          	if (!rect.IsValid())
          		return;
          
          	
          	rect.Expand( 10 );
          
          	MapPoint xy_pos(0,0);
          	MapPoint rc_pos(0,0);
          	SettleTarget settle_target;
          
          	sint16 rows = rect.GetMaxRows();
          	sint16 cols = rect.GetMaxCols();
          
          	
          	sint32 settle_threshold = 0;
          	(void) Diplomat::GetDiplomat(playerId).GetCurrentStrategy().GetMinSettleScore(settle_threshold);
          
          	for (sint32 i = 0; rect.Get(i, xy_pos, rows, cols); i++)
          	{
          		rc_pos.xy2rc(xy_pos, *g_theWorld->GetSize());
          
          		settle_target.m_value = m_settleValues.GetGridValue(rc_pos);
          		settle_target.m_pos = rc_pos;
          
          #ifdef _DEBUG
          		if (g_graphicsOptions->IsCellTextOn()) {
          			char buf[16];
          			sprintf(buf,"*%4.0f*",settle_target.m_value );
          			g_graphicsOptions->AddTextToCell(rc_pos, buf, 255);
          		}
          #endif _DEBUG
          
          		
          		if (!CanSettlePos(rc_pos))
          			continue;
          
          		if (settle_target.m_value <= settle_threshold)
          		{
          
          #ifdef _DEBUG
          			if (g_graphicsOptions->IsCellTextOn()) {
          				char buf[16];
          				sprintf(buf,"(%4.0f)",settle_target.m_value );
          				g_graphicsOptions->AddTextToCell(rc_pos, buf, 255);
          			}
          #endif _DEBUG
          
          			continue;
          		}
          
          		
          		if (!settle_water && g_theWorld->IsWater(rc_pos))
          			continue;
          
          #ifdef _DEBUG
          		if (g_graphicsOptions->IsCellTextOn()) {
          			char buf[16];
          			sprintf(buf,"%4.0f",settle_target.m_value );
          			g_graphicsOptions->AddTextToCell(rc_pos, buf, 255);
          		}
          #endif _DEBUG
          
          		targets.push_back(settle_target);
          	}
          
              Assert(!targets.empty());
              if (targets.empty())
              {
                  return;
              }
          
          	targets.sort(std::greater());
          
          	
          	sint16 max_water_cont = g_theWorld->GetMaxWaterContinent() - g_theWorld->GetMinWaterContinent();
          	sint16 max_land_cont = g_theWorld->GetMaxLandContinent() - g_theWorld->GetMinLandContinent();
          
          	
          	std::vector water_continent_count(max_water_cont, 0);
          	std::vector land_continent_count(max_land_cont, 0);
          	BOOL is_land;
          	sint32 cont;
          
          	
          	const StrategyRecord & strategy = Diplomat::GetDiplomat(playerId).GetCurrentStrategy();
          	sint32 min_settle_distance = 0;
          	(void) strategy.GetMinSettleDistance(min_settle_distance);
          
          	
          	SettleMap::SettleTargetList::iterator iter  = targets.begin();
          	SettleMap::SettleTargetList::iterator tmp_iter;
          
          	settle_target = *iter;  // non-emptyness verified before
          	++iter;
          
          	while (iter != targets.end())
          	{
          		g_theWorld->GetContinent(iter->m_pos, cont, is_land);
          
          #ifdef _DEBUG
                            if (is_land) {Assert(cont < max_land_cont);}
                            else {Assert(cont < max_water_cont);}
          #endif _DEBUG
          		Assert(cont >= 0);
          
          		if ((is_land && (land_continent_count[cont] >= k_targets_per_continent)) ||  
          			(!is_land && (water_continent_count[cont] >= k_targets_per_continent)))
          		{
          			iter = targets.erase(iter);
          		}
          		else
          		{
          			for (tmp_iter = targets.begin(); tmp_iter != iter; ++tmp_iter)
          			{
                         if (MapPoint::GetSquaredDistance(iter->m_pos, tmp_iter->m_pos) <
                             (min_settle_distance * min_settle_distance) 
                            )
          			   {
          				   break;
          			   }
          			}
          
          			if (tmp_iter == iter)
                      {
          				++iter;
                      }
          			else 
                      {
          				iter = targets.erase(iter);
          				continue;
          			}
          
          			if (is_land)
          			{
          				if (land_continent_count[cont] == 0)
          				{
          					iter->m_value += 2;
          				}
          				land_continent_count[cont]++;
          			}
          			else
          			{
          				if (water_continent_count[cont] == 0)
          				{
          					iter->m_value += 2;
          				}
          				water_continent_count[cont]++;
          			}
          		}
          	}
          
          	targets.sort(std::greater());
          }

          EDIT: I added a settle_mountain bool in GetSettleTargets. basically a cut and paste of the settle_water in addtargets and just added it as another check. It doesn't incrporate CanSettleOn but I'll see if this works first.
          Last edited by Ekmek; May 16, 2006, 19:02.
          Formerly known as "E" on Apolyton

          See me at Civfanatics.com

          Comment


          • #6
            Originally posted by E
            EDIT: I added a settle_mountain bool in GetSettleTargets. basically a cut and paste of the settle_water in addtargets and just added it as another check. It doesn't incrporate CanSettleOn but I'll see if this works first.
            Actually you should wait, until I commit my next revision, I already modified those two methods. And it also take your CanSettleOn stuff into account.

            So far the code doesn't crash and assigns settle targets, arccording to the units available, with the consequence that no settle goals are assigned if there are no settlers. But I don't think that harms the AI.

            Unfortunately I think there is another problem: If the AI has more than one settle unit class - for instance a mountain settler and a grassland settler, both can only settler on their terrain - what keeps the AI in this case from assigning mountain settlers to grassland locations. I think there is no such limitation since Wes reported that he had to remove mountain movement from settlers so that those non mountain settlers don't try to settle at mountains.

            -Martin
            Civ2 military advisor: "No complaints, Sir!"

            Comment


            • #7
              thanks Martin,

              I committed my settle code before you posted so you can have a look.

              Not sure if the problems you mentioned relate to the code your working on, or the code I posted. I'm assuming my code. In that case I think it assigns based on the unit. I haven't thoroughly tested although what I did test was working way better than before.
              Formerly known as "E" on Apolyton

              See me at Civfanatics.com

              Comment


              • #8
                Originally posted by E
                Not sure if the problems you mentioned relate to the code your working on, or the code I posted.
                The code you posted deals finding settle targets, and its touches the problem whether the AI has units for the target only slightly. In fact the original code always adds land settle whether you have the appropiate units or not. And it does not distinquish between mountain and non-mountain areas.

                Originally posted by E
                I'm assuming my code. In that case I think it assigns based on the unit. I haven't thoroughly tested although what I did test was working way better than before.
                It is rather based on land/sea opposition. And I guess the code that assigns the units to the goals just checks whether the unit in question can move there and can settle in principle, but not whether it can settle at the target position.

                Unfortuantely I have no idea where the goals get their units. Already these pieces of code weren't so easy to find.

                -Martin
                Civ2 military advisor: "No complaints, Sir!"

                Comment


                • #9
                  Martin, my code seems to be working atleast for my no-mountain settlers. The AI settlers aren't entrenching or hanging-out any where. Either in SettleMap::GetSettleTargets or CtpAi::AddSettleTargets the Ai is establishing those goals based on the unit (I think)
                  Formerly known as "E" on Apolyton

                  See me at Civfanatics.com

                  Comment


                  • #10
                    Originally posted by E
                    Martin, my code seems to be working atleast for my no-mountain settlers. The AI settlers aren't entrenching or hanging-out any where. Either in SettleMap::GetSettleTargets or CtpAi::AddSettleTargets the Ai is establishing those goals based on the unit (I think)
                    Well so far what I have seen is that you put a for loop into another for loop without the need to do it. But anyway I did my own code including the CanSettleOn stuff, but so far it seems that it does not work as intended. However I have now another priority.

                    And a note about this stuff setting the settle value of a tile to zero if the tile itsself has a value of zero. I already removed it, and you add it again. For instance if there is an ice land near the coast there is a good then I don't plant a city on that good without coast access, so I use one of the zero tiles. I expect that the AI settles at places where I settle. Everything else is stupid.

                    -Martin
                    Civ2 military advisor: "No complaints, Sir!"

                    Comment


                    • #11
                      I thought I got rid of that...

                      my extra for loop code does work though, should we keep it there until you get back to this?

                      (what is the new priority if I may ask?)
                      Formerly known as "E" on Apolyton

                      See me at Civfanatics.com

                      Comment


                      • #12
                        Originally posted by E
                        my extra for loop code does work though, should we keep it there until you get back to this?
                        Move the inner for loop just outside before the outer for loop as far as I have seen it, it does nothing that depends on the counter of the outer for loop, therefore it has nothing to do there, because it does always the same in each iteration in the result is the same and therefore you only need to do it once.

                        Originally posted by E
                        (what is the new priority if I may ask?)
                        The new priority is to release a new playtest build, compiled in MSVS .NET 2005 with the VC 8 compiler of course with all the compiler optimizations enabled.

                        -Martin
                        Civ2 military advisor: "No complaints, Sir!"

                        Comment


                        • #13
                          Originally posted by Martin Gühmann


                          Move the inner for loop just outside before the outer for loop as far as I have seen it, it does nothing that depends on the counter of the outer for loop, therefore it has nothing to do there, because it does always the same in each iteration in the result is the same and therefore you only need to do it once.
                          ok will do


                          The new priority is to release a new playtest build, compiled in MSVS .NET 2005 with the VC 8 compiler of course with all the compiler optimizations enabled.

                          -Martin
                          Great. the new builds are stable but let me tweak the slaver war code and the barbarian thing first, i think they are the only two "defects" out right now.

                          EDIT: also i need to update the bomber move id deduct move and the non-ownership pillage bug.
                          Formerly known as "E" on Apolyton

                          See me at Civfanatics.com

                          Comment

                          Working...
                          X