Case Study: How to make multi-domain units

Kael

Deity
Joined
May 6, 2002
Messages
17,401
Location
Ohio
Note, this is no longer required with BtS, all you have to do is set the bCanMoveAllTerrain attribute on the unit to do this

This was one of the first changes I made with the SDK. I wanted units that were able to travel over land and water tiles without being air units. I thought it would be a simple change, but I came to realize that it is easy to change things with the SDK and really hard to get the AI to use those changes.

For specific implementation I decided to use the bCanMoveImpassable attribute, if this attribute is set the unit would be able to travel over all land and water tiles. Then I could block the terrains I didn't want with the bTerrainImpassable attribute. I don't know if its nessesarily the best way to do it, if I was going to start again I might make a new attribute to control it, or trigger it off of a promotion. I would hope that this writeup makes it clear enough that you are able to implement multi-domain units in your own mods in whatever specific implementation works best for you.


Step 1: Give units with bCanMoveImpassable the ability to cross water tiles

Woohoo, this seems so easy. There is a function that controls what plots a unit is allowed to enter. If it is a water tile and the unit isn't getting into cargo it returns false. I add an additional check to see if the unit is canMoveImpassable and I am done, right? Unfortunatly no, but its a start.

CvUnit.cpp CvUnit::canMoveInto:

Original code:
Code:
	case DOMAIN_LAND:
		if (pPlot->isWater())
		{
			if (bIgnoreLoad || !isHuman() || plot()->isWater() || !canLoad(pPlot))
			{
				return false;
			}
		}
		break;

Changed to:
Code:
	case DOMAIN_LAND:
		if (pPlot->isWater()[b] && !canMoveImpassable()[/b])
		{
			if (bIgnoreLoad || !isHuman() || plot()->isWater() || !canLoad(pPlot))
			{
				return false;
			}
		}
		break;


Step 2: Teaching an old dog new tricks

Okay, now the units have the ability to travel across water tiles but the AI refused to use the ability. Units dumped in the middle of the ocean would just sit there. Barbarians wouldn't cross water tiles to attack undefended cities or defend their own.

Turns out there is a seperate function that the AI uses to decide what plots it can enter, its AI_plotValid(). As far as I can tell area() means its a land mass, the change means that instead of just checking to make sure the target plot is land, it returns true if the plot is land or the unit has bCanMoveImpassable.

CvUnitAI.cpp CvUnitAI::AI_plotValid():

Original Code:
Code:
	case DOMAIN_LAND:
		if (pPlot->area() == area())
		{
			return true;
		}
		break;

Changed to:
Code:
	case DOMAIN_LAND:
		if (pPlot->area() == area()[b] || canMoveImpassable()[/b])
		{
			return true;
		}
		break;

Now the AI kinda used water tiles. It would cross them if I dropped it on a water tile, but it still wouldn't cross water to get to stuff. Back into the SDK and there is another function that controls what missions are passed to units, AI_Update.

CvUnitAI.cpp CvUnitAI::AI_Update():

Original Code:
Code:
		if (plot()->isWater())
		{
			getGroup()->pushMission(MISSION_SKIP);
			return;
		}

Changed to:
Code:
		if (plot()->isWater()[b] && !canMoveImpassable()[/b])
		{
			getGroup()->pushMission(MISSION_SKIP);
			return;
		}


Step 3: My Dog has fleas!

Now it all works perfectly... almost. Testing showed (despite the experience of people that play FfH I actually do test, quit laughing, yes I mean you Woodelf) that everything was working as planned except the units were losing all of their movement points whenever they crossed a water tile. So no matter how many movement points they had they could only move 1. I actually don't know what the below does, I just noticed that DOMAIN_LAND and DOMAIN_IMMOBILE seemed to do the same thing on water tiles so I gave DOMAIN_LAND its own break and it fixed it.

CvPlot.cpp CvPlot::isValidDomainForAction():

Original Code:
Code:
	case DOMAIN_LAND:
	case DOMAIN_IMMOBILE:
		return !(isWater());
		break;

Changed to:
Code:
	case DOMAIN_LAND:
		[b]return true;
		break;[/b]

	case DOMAIN_IMMOBILE:
		return !(isWater());
		break;
 
Yay! Now my Quarren can move on land and water!

Any Idea on how to change animations when they are in water?

Can you do the same thing with air?
 
Civmansam said:
Yay! Now my Quarren can move on land and water!

Any Idea on how to change animations when they are in water?

Can you do the same thing with air?

The problem is that the Domain types control a lot more than the units movement abilities. I was kinda surprised when I started digging into it. I assumed they just determined what plots they could enter, but there is a ton of AI work that is dependant on the domain type too (which is why I don't have my fireballs as DOMAIN_AIR units).
 
Did I miss something, or with the above code can the unit still move through mountains?
 
Shqype said:
Did I miss something, or with the above code can the unit still move through mountains?

Yeah, bCanMoveImassable will still do everything it did, plus allow travel over water tiles.
 
That's very interesting, Kael! Thanks! :thumbsup:

I'm waiting for new Case Studies from you :)
 
NeverMind said:
That's very interesting, Kael! Thanks! :thumbsup:

I'm waiting for new Case Studies from you :)

I am more than happy to share what I have, im excited to see others posting their tips and tricks as well.
 
Is it also possbile to creat new domain?
Something like a DOMAIN_AMPHIB which combines sea and land domains. Or a DOMAIN_SUBMARINE which would allow to create a submaritime world.
It might be easier to to create some new domains, instead of combining several existing ones.
 
12monkeys said:
Is it also possbile to creat new domain?
Something like a DOMAIN_AMPHIB which combines sea and land domains. Or a DOMAIN_SUBMARINE which would allow to create a submaritime world.
It might be easier to to create some new domains, instead of combining several existing ones.

Yes, that should be possible and not too difficult. I plan on doing a DOMAIN_AMPHIBIOUS for my mod, as one of the races (lizardmen) can swim in coastal waters and there are some other units for which the trait would be useful (water elementals, pearl giants, and a few other things).

The only thing is you then have to go through the code and find wherever it references DOMAIN_LAND or DOMAIN_WATER and add in cases for DOMAIN_AMPHIBIOUS. I'm not terribly looking forward to that.
 
very good kael!!! And Put a city in a water like a "Atlanta" is possible??
 
HP_Ganesha said:
very good kael!!! And Put a city in a water like a "Atlanta" is possible??

Ive been out of america for a few weeks now, did something happen to Atlanta?!? :D

Seriously, yes. You could tie this function to a promotion instead of bMoveImpassablelike I did and give that promotion to every unit of a civ to create a "merman" civ if you wanted.
 
well I think Atlanta of American Dont Change nothing... ¬¬, but i dont be in USA if Atlanta change in a few days I the last person a know this...hehe...

Well Is only a litle question i dont realy need this mod... but tks anyway
 
I tried to achieve the same thing by adding a new DOMAIN to the xml.

There are all the spots where DOMAIN_<TYPE> are used hwere you have your own domain, but also and very importantly, you need to add the DOMAIN to CvEnums.h under enum DomainTypes.

My code works perfectly in game, but i get an XML error on launch - found where the game prints that error but i just keep diggin further into the rabbit hole and not finding where else i need to tell the game about the DOMAIN type.

Anyone else have any luck?
 
I have successfully added new domains in the XML. If you can't work out what's up I'll see if I can dig up the files and give you some pointers.
 
phatlip said:
I tried to achieve the same thing by adding a new DOMAIN to the xml.

There are all the spots where DOMAIN_<TYPE> are used hwere you have your own domain, but also and very importantly, you need to add the DOMAIN to CvEnums.h under enum DomainTypes.

My code works perfectly in game, but i get an XML error on launch - found where the game prints that error but i just keep diggin further into the rabbit hole and not finding where else i need to tell the game about the DOMAIN type.

Anyone else have any luck?

by guess is that there is a schema somewhere which needs altering.
 
The Great Apple said:
I have successfully added new domains in the XML. If you can't work out what's up I'll see if I can dig up the files and give you some pointers.

So far i have modified:

CvUnit.cpp
-> canMoveInto() // added DOMAIN to list of conditionas for move
CvEnums.h
-> enum DomainTypes // added DOMAIN as index #4
CvUnitAI.cpp
-> // edited AI to use DOMAIN units like land units even at sea
CvPlot.cpp
-> // made the plot a valid action for DOMAIN

Like i mentioned, the game works fine. Perhaps i am missing a declaration in CVInfos.cpp (or .h)

Or perhaps you need to define DOMAINS somewhere in the XML? I can't find anywhere i'm missing something yet, but the error comes from a method in XMLLoadUtility.cpp - so i'm following the function trail. It leads me to CvGlobals::getUnitInfo(DomainTypes e). Thats returns a CvInfoBase pointer. Continue searching and i find that CvInfoBase works off a hash_map for fast searching.

So now i just need to find how that has_map is populated and get my DOMAIN in there, obviously Great Apple has found this already - and since i'm at work and can't stare at my precious SDK code, can you give me a hint?
 
Kael said:
Step 3: My Dog has fleas!

Now it all works perfectly... almost. Testing showed (despite the experience of people that play FfH I actually do test, quit laughing, yes I mean you Woodelf) that everything was working as planned except the units were losing all of their movement points whenever they crossed a water tile. So no matter how many movement points they had they could only move 1. I actually don't know what the below does, I just noticed that DOMAIN_LAND and DOMAIN_IMMOBILE seemed to do the same thing on water tiles so I gave DOMAIN_LAND its own break and it fixed it.

CvPlot.cpp CvPlot::isValidDomainForAction():

Original Code:
Code:
	case DOMAIN_LAND:
	case DOMAIN_IMMOBILE:
		return !(isWater());
		break;

Changed to:
Code:
	case DOMAIN_LAND:
		[b]return true;
		break;[/b]

	case DOMAIN_IMMOBILE:
		return !(isWater());
		break;

Not entirely the correct way to do it. This will mean that using the goto (or right click) will show a water tile as a valid move for any land unit. It won't crash the game, but it isn't very 'polished'.

simple change though, just use this code instead:

Code:
	case DOMAIN_LAND:
		[b]return (!isWater() || canMoveImpassable());
		break;[/b]

	case DOMAIN_IMMOBILE:
		return !(isWater());
		break;
 
phatlip said:
Or perhaps you need to define DOMAINS somewhere in the XML? I can't find anywhere i'm missing something yet, but the error comes from a method in XMLLoadUtility.cpp - so i'm following the function trail. It leads me to CvGlobals::getUnitInfo(DomainTypes e). Thats returns a CvInfoBase pointer. Continue searching and i find that CvInfoBase works off a hash_map for fast searching.
Yes, you do. This is what I had thought you meant at the start of your first post!

CIV4BasicInfos.xml

Under the tag <DomainInfos>. They need to be the same order as the enums.

That should work.
 
Top Bottom