Need Help with Worker AI

MegalodonShark

Chieftain
Joined
Oct 3, 2010
Messages
39
Location
Land of Oz
In a mod I'm making, I have a worker unit called a "nanite" which is supposed to remain stationary on the plot it's placed, and perpetually improve it once set to automate.

That's the idea anyway.

I successfully made the unit DOMAIN_IMMOBILE and gave it the ability to paradrop, which considering what I'm up against now is pretty trivial. While I succeeded in that, if I automate the nanite worker, it just sits on the tile and does nothing. It'll improve the plot if I tell it to do something specific, of course, but otherwise it does nothing.

Could someone familiar with the worker AI code tell me where I should look in the SDK for what to change, or what might be going on? I'm guessing it's getting "stuck" because it's trying to go to other tiles to improve and can't, but I'm not certain. Any pointers would be appreciated :)
 
I'm not an expert on this, but maybe this will help:

In CvUnitAI.cpp, You have the method CvUnitAI::AI_update() which determines what the unit should do.
Assuming it's automated, it'll go into the 'switch (getGroup()->getAutomateType())'.

You should have your unit AI set to UNITAI_WORKER or you might need to create a new AI for this unit. I'm not sure which other implications it has. (You can set the default AI behavior for the unit in the XML).

And then AI_workerMove() is called.
Try debugging this method. You should probably add some code there to reflect the immobility of the the nanite.
 
I'm not an expert on this, but maybe this will help:

In CvUnitAI.cpp, You have the method CvUnitAI::AI_update() which determines what the unit should do.
Assuming it's automated, it'll go into the 'switch (getGroup()->getAutomateType())'.

You should have your unit AI set to UNITAI_WORKER or you might need to create a new AI for this unit. I'm not sure which other implications it has. (You can set the default AI behavior for the unit in the XML).

And then AI_workerMove() is called.
Try debugging this method. You should probably add some code there to reflect the immobility of the the nanite.

Hehe, great idea - actually, I had just finished doing that very thing when I came back to the forum to check and see if someone had replied. Unfortunately, what I have doesn't work.

Basically, I had it check a boolean tag I had created to tell the program whether or not the unit could ignore its domain for a paradrop: bIgnoreDomainParadrop.

So, what I did was:

Code:
if (AI_getUnitAIType() == UNITAI_WORKER)
{
	if (!isIgnoreDomainParadrop())
	{
		AI_workerMove();
	} else {
		//Put normal automated improvement function here
		AI_improveLocalPlot(0,NULL);
		//Hopefully this means improve this plot.
	}
}

AI_improveLocalPlot takes two parameters, the first is a range (for the range of tiles to check around the unit I think) and the second is a city parameter. In the code it seems you have either the choice to define a city, or ignore the city. Since I wanted it to ignore the city and be relative to the worker, and I only wanted it to improve the plot it's on, I put NULL for city, and set the range to 0.

Unfortunately, it still just sits there. So my search continues. I thought if maybe I could intercept the point where the program actually tells the unit what to do when a user clicks a command button, I could redirect it to do a specific thing. I think I'm not fully grasping the flow of the program here.

Spoiler :
Code:
bool CvUnitAI::AI_improveLocalPlot(int iRange, CvCity* pIgnoreCity)
{
	
	int iX, iY;
	
	int iBestValue = 0;
	CvPlot* pBestPlot = NULL;
	BuildTypes eBestBuild = NO_BUILD;
	
	for (iX = -iRange; iX <= iRange; iX++)
	{
		for (iY = -iRange; iY <= iRange; iY++)
		{
			CvPlot* pLoopPlot = plotXY(getX_INLINE(), getY_INLINE(), iX, iY);
			if ((pLoopPlot != NULL) && (pLoopPlot->isCityRadius()))
			{
				CvCity* pCity = pLoopPlot->getWorkingCity();
				if ((NULL != pCity) && (pCity->getOwnerINLINE() == getOwnerINLINE()))
				{
					if ((NULL == pIgnoreCity) || (pCity != pIgnoreCity))
					{
						if (AI_plotValid(pLoopPlot))
						{
							int iIndex = pCity->getCityPlotIndex(pLoopPlot);
							if (iIndex != CITY_HOME_PLOT)
							{
								if (((NULL == pIgnoreCity) || ((pCity->AI_getWorkersNeeded() > 0) && (pCity->AI_getWorkersHave() < (1 + pCity->AI_getWorkersNeeded() * 2 / 3)))) && (pCity->AI_getBestBuild(iIndex) != NO_BUILD))
								{
									if (canBuild(pLoopPlot, pCity->AI_getBestBuild(iIndex)))
									{
										bool bAllowed = true;

										if (GET_PLAYER(getOwnerINLINE()).isOption(PLAYEROPTION_SAFE_AUTOMATION))
										{
											if (pLoopPlot->getImprovementType() != NO_IMPROVEMENT && pLoopPlot->getImprovementType() != GC.getDefineINT("RUINS_IMPROVEMENT"))
											{
												bAllowed = false;
											}
										}

										if (bAllowed)
										{
											if (pLoopPlot->getImprovementType() != NO_IMPROVEMENT && GC.getBuildInfo(pCity->AI_getBestBuild(iIndex)).getImprovement() != NO_IMPROVEMENT)
											{
												bAllowed = false;
											}
										}

										if (bAllowed)
										{
											int iValue = pCity->AI_getBestBuildValue(iIndex);
											int iPathTurns;
											if (generatePath(pLoopPlot, 0, true, &iPathTurns))
											{
												int iMaxWorkers = 1;
												if (plot() == pLoopPlot)
												{
													iValue *= 3;
													iValue /= 2;
												}
												else if (getPathLastNode()->m_iData1 == 0)
												{
													iPathTurns++;
												}
												else if (iPathTurns <= 1)
												{
													iMaxWorkers = AI_calculatePlotWorkersNeeded(pLoopPlot, pCity->AI_getBestBuild(iIndex));											
												}

												if (GET_PLAYER(getOwnerINLINE()).AI_plotTargetMissionAIs(pLoopPlot, MISSIONAI_BUILD, getGroup()) < iMaxWorkers)
												{
													iValue *= 1000;
													iValue /= 1 + iPathTurns;

													if (iValue > iBestValue)
													{
														iBestValue = iValue;
														pBestPlot = pLoopPlot;
														eBestBuild = pCity->AI_getBestBuild(iIndex);											
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
	
	if (pBestPlot != NULL)
	{
	    FAssertMsg(eBestBuild != NO_BUILD, "BestBuild is not assigned a valid value");
	    FAssertMsg(eBestBuild < GC.getNumBuildInfos(), "BestBuild is assigned a corrupt value");

		FAssert(pBestPlot->getWorkingCity() != NULL);
		if (NULL != pBestPlot->getWorkingCity())
		{
			pBestPlot->getWorkingCity()->AI_changeWorkersHave(+1);

			if (plot()->getWorkingCity() != NULL)
			{
				plot()->getWorkingCity()->AI_changeWorkersHave(-1);
			}
		}
		MissionTypes eMission = MISSION_MOVE_TO;

		int iPathTurns;
		if (generatePath(pBestPlot, 0, true, &iPathTurns) && (getPathLastNode()->m_iData2 == 1) && (getPathLastNode()->m_iData1 == 0))
		{
			if (pBestPlot->getRouteType() != NO_ROUTE)
			{
				eMission = MISSION_ROUTE_TO;
			}				
		}
		else if (plot()->getRouteType() == NO_ROUTE)
		{
			int iPlotMoveCost = 0;
			iPlotMoveCost = ((plot()->getFeatureType() == NO_FEATURE) ? GC.getTerrainInfo(plot()->getTerrainType()).getMovementCost() : GC.getFeatureInfo(plot()->getFeatureType()).getMovementCost());

			if (plot()->isHills())
			{
				iPlotMoveCost += GC.getHILLS_EXTRA_MOVEMENT();
			}
			if (iPlotMoveCost > 1)
			{
				eMission = MISSION_ROUTE_TO;
			}
		}
		
		eBestBuild = AI_betterPlotBuild(pBestPlot, eBestBuild);

		getGroup()->pushMission(eMission, pBestPlot->getX_INLINE(), pBestPlot->getY_INLINE(), 0, false, false, MISSIONAI_BUILD, pBestPlot);
		getGroup()->pushMission(MISSION_BUILD, eBestBuild, -1, 0, (getGroup()->getLengthMissionQueue() > 0), false, MISSIONAI_BUILD, pBestPlot);
		return true;
	}
	
	return false;
}
 
Your use of AI_improveLocalPlot() seems correct. I don't know what's gone wrong. You should debug it :)

Once the AI decides what to do, it adds the decision to the selection group's mission queue.
This queue is queried every turn.

Most operations are started in the group level - since the entire group does the same thing (movement, fortification, worker improvement etc.).
The group then tells each unit what to do (the group might consist of a single unit).

In your case, look for CvSelectionGroup::groupBuild and CvUnit::build.

Just one thing I don't understand: Does the nanite sit in the city and paradrops to the plot it needs to improve or does it sit in the same plot all the time?

Because your automation (local plot with range 0) doesn't cover the first case.
 
Your use of AI_improveLocalPlot() seems correct. I don't know what's gone wrong. You should debug it :)

This may seem like a really dumb question, but I've only been working with C++ for a little over a day XD making the paradrop work was the first time I'd ever touched it, and that was last night.

How do I debug?:crazyeye:

In answer to your question, the nanite is created in the city, and the user paradrops it where they want the plot improved. The nanite then stays there on the plot, and doesn't move again. It stays until destroyed or deleted. The idea is that it's a "set and forget" style worker. I'll have different nanites "programmed" to do different things.

Example: You have a "farming" nanite. You paradrop it onto a tile, and it can build a farm/winery/plantation/etc based on the bonus on the tile (if any). After that's done, it stays there, and if something happens to the improvement it makes (pillaging, random volcanoes exploding, deadly thunderstorms, or nuclear attacks) it rebuilds its improvement.

This does two main things:
1) It's immersive for the game
2) Allows you to dictate what improvement will be built, and not have to manually rebuild that improvement when it's destroyed (especially when the automated AI constantly wants to put something else there).
 
For a C++ first timer you seem to be doing a good job :goodjob:

First of all - which environment do you use for compilation? I assume it's Visual Studio.
If so - debugging is quite easy and we can continue.
And which make file do you use?

Regarding the nanite - won't it get captured or killed when an enemy unit reaches its tile?
 
For a C++ first timer you seem to be doing a good job :goodjob:

First of all - which environment do you use for compilation? I assume it's Visual Studio.
If so - debugging is quite easy and we can continue.
And which make file do you use?

Regarding the nanite - won't it get captured or killed when an enemy unit reaches its tile?

VS is correct - I spent around 4 hours yesterday setting up my SDK thing, hehe. Also, I used the makefile from the wiki instructions on SDK setup. Let me find the page...

http://modiki.civfanatics.com/index.php/How_to_Install_the_SDK

Oh, yes and no. I'm still playing with nanite stats. Right now it's set to "invisible to all units" as stealth. As nanites, they won't always be easy to see. So far that's working, but I have to test how it interacts with things like enemy spies etc. Once you learn the technology to make them ,the idea is that they're fairly easy to mass produce, so they'll be cheap to remake if a unit comes along and kills them.
 
I suggest you switch to DannyDaemonic's make file. It makes the compilation much faster.
You can download it here.

To debug (I'm using VS 2005 so that's what I describe here. If you have a newer version it should be roughly the same):

First of all, make sure the game runs in window mode (not full screen), otherwise you might have problems switching between the game and VS. You can do that in 'My Games\Beyond The Sword' folder, in the main INI file (look for fullscreen, or something like that). Switch from 1 to 0.

Then, make sure you compile in debug mode (you have the current configuration in one of the tool bars in the top of your VS window). If not - switch to debug and rebuild.

In VS menu, go to 'Project -> CvGameCoreDLL Properties' (or your project's name).
In the tree on the left go to 'Configuration Properties -> Debugging'.

Make sure that the active configuration is debug (at the top of this dialog).

Set the 'command' field to the full path of the BTS executable (use double quotation marks).

In the 'command arguments' type:
Code:
-mod "Mods\<Your mod name>"
(Without the '<>', obviously). This is to tell the BTS to start with your Mod.

In the 'working directory' field enter the full path of your BTS installation folder (where the exe file sits), again with double quotation marks.

(I'm not completely sure you need the quotation, but it won't hurt).

Click 'OK', and you're good to go.

Make sure that the newly compiled debug dll is copied to the Mod's folder.

Now you can run the game from within VS, with 'Debug -> Start debugging' (or F5).

In VS, you can add breakpoints where the game execution will stop by clicking next to the source code line (to the left), or by going to this line and pressing F9 .

After breaking, check the Debug menu for your options.

They usually consist of:
F5 - continue running (until the next breakpoint or forever).
F10 - Run to the next code line.
F11 - Run to the next code line, but if a function is called step into it.
Shift-F5 - kill game.

When the game is paused, you can watch the current values of variables in the 'autos', 'locals', 'watch' windows (look around).

And that's it.
 
Do I just drop the Makefile in where I had the other, or do I have to do more setup?
(aside from changing the path values of course)

My fear is that when I debug, I won't really know what I'm looking at, heh.

------

I followed your instructions, but now when I try to rebuild, the program feeds out:

1>------ Rebuild All started: Project: CvGameCoreDLL, Configuration: Final_Release Win32 ------
1>Performing Makefile project actions
1>Microsoft (R) Program Maintenance Utility Version 9.00.21022.08
1>Copyright (C) Microsoft Corporation. All rights reserved.
1>NMAKE : fatal error U1073: don't know how to make 'clean_Final_Release'
1>Stop.
1>Microsoft (R) Program Maintenance Utility Version 9.00.21022.08
1>Copyright (C) Microsoft Corporation. All rights reserved.
1>'Final_Release' is up-to-date
1>Build log was saved at "file://D:\Program Files (x86)\Firaxis Games\Sid Meier's Civilization 4\Beyond the Sword\Mods\Dragonsward2\CvGameCoreDLL\Final_Release\BuildLog.htm"
1>CvGameCoreDLL - 1 error(s), 0 warning(s)
========== Rebuild All: 0 succeeded, 1 failed, 0 skipped ==========
 
Download the makefile+fastdep file, and extract it.
The makefile should replace your current makefile, and the bin folder with the fastdep should be next to it.

And do this in the project properties, in the NMake tab (in both the 'clean command line' and 'rebuild all command line'):

Also, I changed clean_Debug and clean_Release to Debug_clean and Release_clean, so you'll have to update your project to reflect that.
 
Use this project file (if you're using a newer version than VS2005, it will ask you to convert it).

I don't think the makefile has a Final Release configuration, so just use Debug and Release.
Specifically for debugging - use.... Debug! :eek:
 

Attachments

Took me a few minutes to figure out what you were saying, but I did it and it's working :D Rebuilding now...

Unrelated: Do you know what the pushMission function does?
 
Took me a few minutes to figure out what you were saying, but I did it and it's working :D Rebuilding now...

That's because I'm so intelligible :crazyeye:

Unrelated: Do you know what the pushMission function does?

Each selection group (a group of units selected together) has its own queue of missions. pushMission adds a mission to this queue, or replaces it completely (depending on the 'append' parameter).
 
When I try to rebuild using the Debug configuration, the rebuild breaks, whereas in the normal rebuild it doesn't :confused:

Spoiler :
Code:
1>CvPlot.cpp
1>CvPlot.cpp(2673) : error C3861: 'isIgnoreDomainParadrop': identifier not found, even with argument-dependent lookup
1>NMAKE : fatal error U1077: '"D:\Program Files (x86)\Microsoft Visual C++ Toolkit 2003\bin\cl.exe"' : return code '0x2'
1>Stop.
1>Project : error PRJ0019: A tool returned an error code from "Performing Makefile project actions"
1>Build log was saved at "file://d:\Program Files (x86)\Firaxis Games\Sid Meier's Civilization 4\Beyond the Sword\Mods\Dragonsward2\CvGameCoreDLL\Debug\BuildLog.htm"
1>CvGameCoreDLL - 3 error(s), 0 warning(s)
========== Rebuild All: 0 succeeded, 1 failed, 0 skipped ==========

I'm not sure why it's having a trouble with that file in the Debug mode and not the normal one...

And my figuring things out wasn't a reference to your intelligibility XD You explained things well and I've really really appreciated the help. It was more a reference to my n00bness.
 
And my figuring things out wasn't a reference to your intelligibility XD You explained things well and I've really really appreciated the help. It was more a reference to my n00bness.

Don't worry about it. I didn't mean anything by it.

Can you post the code in the lines with the error?
And the code where ever you defined isIgnoreDomainParadrop.
 
Sorry for the late reply - I've been doing housework. Code section:

CvPlot.cpp

Spoiler :
Code:
bool CvPlot::isValidDomainForAction(const CvUnit& unit) const
{
	switch (unit.getDomainType())
	{
	case DOMAIN_SEA:
		return (isWater() || unit.canMoveAllTerrain());
		break;

	case DOMAIN_AIR:
		return false;
		break;

	case DOMAIN_LAND:
	case DOMAIN_IMMOBILE:
		return (!isWater() || unit.canMoveAllTerrain() || unit.isIgnoreDomainParadrop());
		break;

	default:
		FAssert(false);
		break;
	}

	return false;
}
[/spoiler]

As for where I defined it... I had to paste the function in a bunch of places. I would have put it in the CvPlot.h file, but another function that was being used (canMoveAllTerrain) didn't have any definition there, so I figured I didn't need to put one for isIgnoreDomainParadrop either.
 
This thread is going a lot faster than any modding thread I've seen. I don't think a 'late reply' applies here.
It's your thread :)

isIgnoreDomainParadrop() is not supposed to be a member function of CvPlot, but of CvUnit.
You need to have it defined there (CvUnit.h, CvUnit.cpp), and you probably need it for CvUnitInfo as well (but I guess you already have that if you read this value from the XML).

Look for canMoveAllTerrain in CvUnit.

In VS you can search in all the files in the solution using "find in files" (ctrl+shift+f).
 
isIgnoreDomainParadrop() is not supposed to be a member function of CvPlot, but of CvUnit.
You need to have it defined there (CvUnit.h, CvUnit.cpp), and you probably need it for CvUnitInfo as well (but I guess you already have that if you read this value from the XML).

Yeah, I have it defined in CvUnit.ccp/.h :) I used it there a few times also. I only tried to put it in CvPlot because it looked like DOMAIN_IMMOBILE would need an exception or else it'd return false for being a valid destination. I'll try removing that and see if it breaks things.

I'm just wondering why debug errors at that file, when a normal compilation doesn't?

---

Hmm, the debug compile says it was successful, but there's no dll in the Debug folder that was created.

Additional note: I went into worldbuilder and surrounded a regular worker with normal mountains. This worker exhibited the same behavior as the nanite I made (It just sat there for the most part, even though it could have improved the land). So I think this is definitely related to the game assuming that the unit is able to move.
 
Back
Top Bottom