AI basing squadrons on starbases

God-Emperor

Deity
Joined
Jul 18, 2009
Messages
3,551
Location
Texas
I have managed to get the AI to base a squadron on a starbase. I say "a squadron", because so far can verify only exactly one, but that is one more than it ever has before. I didn't even have to resort to finding a squadron and forcing it to be on the station (which I though I might need to do).

In this screenshot, you can see an omega fighter squadron on intercept duty at a starbase. On the previous turn it had shot down a delta bomber squadron of mine that was attacking the battleship that you can see, which is how I realized that it was there.

How did I do it? Just a tiny bit of Python in the loop that processes starbases each turn for missile production, culture, and ranged attacks, to force every starbase to use UNITAI_CARRIER_SEA. That is it. Currently it is forcing the Sensor Station to the same unit AI type, but I'll find a better one since they can't actually carry a squadron - perhaps UNITAI_SPY_SEA. I am selecting the sea type unit AIs for this so they are not counted as any of the types it can actually build, so it doesn't think it has more of them that it can use than it really does. I can't confirm it for sure, but around when I added this, the same AI started to get a significant increase in its military power which is possibly due to the sudden decrease in units with UNITAI_ATTACK causing it to build more. Shortly thereafter they also recaptured a system the pirates had taken from them. So far that is not significant since the game is so random anyway, but it could be a sign that it improved more than just the squadron basing.

After a bit more tweaking and testing I will commit the code change to Sourceforge and also post it here for the mod-moders to get it quickly.
 

Attachments

  • Civ4ScreenShot0066.JPG
    Civ4ScreenShot0066.JPG
    184.3 KB · Views: 317
Replying to my own just posted message...

With a little more thinking and some checking, I think this can be fixed in the DLL. That would make the constant fiddling with the unit AI in Python unnecessary. Currently, when the improvement that the construction ship builds is done the unit that it creates for this is specifically set to have UNITAI_ATTACK (line 6662 of CvUnit.cpp). Changing this to NO_UNITAI would make it use the unit type's unit info's default unit AI type setting as per the DefaultUnitAI tag in the XML. I checked the code and when creating the unit it does not attempt to do any sort of validation (like checking to see if it is a DOMAIN_SEA type unit when a sea type unit AI is specified) which would prevent it from using whatever it is set to in the XML. All of that sort of checking is done by the AI when it is trying to decode what to build, eliminating things from consideration if it doesn't like the combination, but the actual unit initialization will use whatever is in the XML.

This is a much more general fix and would allow for it to be defined in the XML for each unit type that is created via building an improvement. Being both more general and not adding the little bit of processing time to the Python starbase processing loop means that this is a better fix.
 
I was going to fiddle with it some more, but since determining that the adjustment is better made in the DLL this Python adjustment is not going to be used, so no more fiddling seems necessary.

However, some of you may want to give it a shot, and it is much easier than buildnig a new DLL, so here it is. It is not efficient it actually checks the current unit AI type and only changes it if it is not the desired one (the more efficient version would just always set it since checking it and setting it should take about the same amount of time as each requires going through the Python-DLL interface which is slow). It checks to see if it is a Starabse or "OtherStation", although in FFP the one "OtherStation" unit type is also flagged to be a Starbase so it is redundant for regular FFP.

Anyhow, the change is in the event manager Python file. Post 1.7 this is called FinalFrontierEvents.py, earlier (pre BUG) versions used CvFinalFrontierEvents.py. In this file is a function called updateAllStarbases. In the function is code that looks like this (lines 468-471 in v1.73):
Code:
			apUnitList = pyPlayer.getUnitList()
			for pUnitLoop in apUnitList:
				if (pUnitLoop.isStarbase() and (not pUnitLoop.isOtherStation())):
					aaiStarbaseList.append([pUnitLoop.getGameTurnCreated(), iPlayerLoop, pUnitLoop.getX(), pUnitLoop.getY()])

I inserted some code inside the for loop before that first if statement, like so:
Code:
			apUnitList = pyPlayer.getUnitList()
			for pUnitLoop in apUnitList:
[B][COLOR="DarkRed"]				# FFP - Starbase & Station UnitAI adjustment
				if pUnitLoop.isStarbase() or pUnitLoop.isOtherStation():
					printd("Base UnitAI check: Starbase = %d, OtherStation = %d, UnitAI = %d" % (pUnitLoop.isStarbase(), pUnitLoop.isOtherStation(), pUnitLoop.getUnitAIType()))
					if pUnitLoop.getUnitAIType() != UnitAITypes.UNITAI_CARRIER_SEA :
						pUnitLoop.setUnitAIType(UnitAITypes.UNITAI_CARRIER_SEA)
						printd(" --- set unit's UnitAI type to UNITAI_CARRIER_SEA")[/COLOR][/B]
				if (pUnitLoop.isStarbase() and (not pUnitLoop.isOtherStation())):
					aaiStarbaseList.append([pUnitLoop.getGameTurnCreated(), iPlayerLoop, pUnitLoop.getX(), pUnitLoop.getY()])

And now all your base are belong to us. Well, as long as "us" = UNITAI_CARRIER_SEA.
 
Maybe.

In theory, you could use the onUnitBuilt event to check the unit type and force those that are squadron carrier types to use the sea carrier unit AI. In practice, this would probably be a bad idea for any unit that also has any significant combat strength since the sea carrier unit AI is evidently not set up for regular combat too. (When is the last time you saw a carrier in BtS attack anything directly? I'm thinking "never".) Ideally, copying the basic carrier functionality from that unit AI and grafting it on to the standard land attack unit AI to get a new unit AI type (like UNITAI_ATTACK_CARRIER or something like that, which would be a land domain unit AI, so the AI could actually build them) would be the way to go for that case - that would be for units like the Battlecarrier UU in FFP which is only slightly weaker than a Battleship and can carry a squadron or two, and as I recall a lot of the regular combat ships in B5 carry squadrons.

Doing the unit AI swap like that could also mess up the AI's unit building. If it is trying to build an attack unit and unexpectedly gets a unit with the sea carrier unit AI it may just keep trying to build more attack units, and if they all get converted then it may just keep building more units since it is not getting to its target number of units of the desired unit AI. I'm not really sure exactly how all of this works, so it might not do this - but there is a good chance that it will. The basic methodology is that the AI does not decide what unit type it wants to build directly. It decides how many of each unit AI type it needs, determines which it wants to build more of, and then runs the rating system (mentioned elsewhere recently) to determine which of the types it can currently build is the best unit for that unit AI type and then builds a unit of that type. So it seems to me that if it is not getting a unit of the desired unit AI type then the entire algorithm is likely to just repeat and try again, with a good chance that it will try to build another of the exact same unit type, although the random factor in the rating system may have it try a different type (which is only helpful for this situation if that other unit type is not also converted to the carrier unit AI). So if a new hybrid unit AI type were added, the "how many of each type of unit AI do I need" system would need to be adjusted to include it.

I will also note that you could do the same for missile carrying units, like the cruisers in BtS, to give them the UNITAI_MISSILE_CARRIER_SEA type unit AI. That could work out better since that is available to the Missile Cruiser and Submarine units, which do attack as well as carry things so that unit AI might behave more like UNITAI_ATTACK_SEA at least when it has no missiles on it.

I should check the basic sea attack unit AI, UNITAI_ATTACK_SEA, to see it it has any provisions for missiles in it since it is the default unit AI for the Missile Cruiser - but I doubt it has any. I think the best that can be hoped for is that the MISSILE_CARRIER_SEA version has some attack logic in it, or at least acts like ATTACK_SEA when the unit has no missiles on it.

And finally, for units types that could both carry things or directly attack effectively you could apply it randomly so that only some percentage (half?) get the unit AI changed. That should give a mix of capabilities that is better than the current "never carries anything" situation and also be less likely to seriously mess up the AI's unit building plans.

I may try this out for missile carrying units this weekend.

Also, again, this may be better dealt with in the DLL. The problem in this case is that the issue might be complicated to fix.

For the Starbases just doing this change is an easy decision: they just sit there and are incapable of directly attacking and their ranged attack is already done in Python so changing them to the relatively combat incapable UNITAI_CARRIER_SEA has no effect on their regular capabilities. For units that can both directl fight effectivelyy and carry squadrons/missiles it is not so easy since the change could reduce (or eliminate) their direct combat abilities (as I recall hearing elsewhere from people working with the AI, sea type unit AIs are just dumber than the land versions in general, so the ATTACK_SEA unit AI is worse at it than the regular ATTACK unit AI). Another possible issue for the starbase: it may have affected how other units behave towards it, but I'm not sure. The AI might want to send units to "escort" their own starbases now. Also, enemy units may be more willing to attack starbases effectively committing suicide due to the relatively high strength - but I'm not sure of either of those effects: in my one game with this in effect for only the second half of the game (not quite done yet), it seemed like both of these might be happening. Maybe. But it could be a coincidence: I have a stabase out on the edge of the central "many features" area of a spiral galaxy map that has features that apparently tend to funnel pirates right past it - a lot of them attack it, resulting in it being level 5 now (I don't remember every having a level 5 starbase before except maybe with the Brotherhood, but I may have), so the number of pirates that attack it may, or may not, have been increased by the unit AI change (there was also one non-pirate enemy Delta Battleship that attacked it).
 
I have been doing a bit of testing with changing the unit AI type for ships that can carry missiles, making the change in the onUnitBuilt event handler.

It turns out that if you set a cruiser's unit AI type to UNITAI_MISSILE_CARRIER_SEA that the AI will actually load missiles onto it (or it did so at least once, anyway). See the attached clip from a screen shot.

I have never seen one actually launch a missile off of a ship (although the lack of missile use by the AI in general is pretty standard - this is actually due to the low strength of missiles and a default minimum value it applies to targets for an attacker that is has bSuicide set to true, as all missiles do, that the low damage potential can't overcome for many of the possible unit types they could be targeting unless there is more than one "potential attacker" that is immediately adjacent to the target unit). They seem to hang around in cities most of the time, although for most of the game I could only see one of them. This particular unit AI also seems to be interested in running away from a city that is in danger, which is not a great behavior for a cruiser. That is what this one was doing - this was the turn after New Earth capitulated after I captured a star system from him where the cruiser had been located a couple of turns before (and it had fled to that system from his capitol which the Forge had captured a few turns earlier).

In looking at the source code in CvUnitAI.cpp earlier today I noticed that missiles (any unit with UNITAI_MISSILE_AIR) will actually check units other than those with UNITAI_MISSILE_CARRIER_SEA to try an load themselves onto them. They also check units with both UNITAI_RESERVE_SEA and UNITAI_ATTACK_SEA. These additional unit types appear to be limited to not more than 2 missiles in the missile loading logic (a unit with MISSILE_CARRIER_SEA can get filled with however many it can carry), but this is not much of a problem for FFP since the only unit that can carry more is the omega cruiser (since the AI never seems to take cargo capacity increasing promotions). I have modified my test code to convert units that can carry missiles to any of the 3, with a chance of being converted from the standard land unit AI to one of them set to N/(N+2) where N is the number of missiles it can carry (I was using this for about half the last test game too, instead of always converting) and then with a 50% chance of getting ATTACK_SEA and 25% for each of the other two. I made this change late in the game and saw no units built with them (it was only about 35 turns before the game ended so my 1 test unit may have been the only cruiser built in that time and it got MISSILE_CARRIER_SEA).

So more testing to do. This time I'll make sure that some AI gets AstroTech too since their UU is the missile frigate. (They were not in the last test game, as it turned out, and I got the Brotherhood so their cruiser variant UU was not under AI control either.)

I have been looking at the DLL's code for this stuff enough that I suspect I could adjust it to load missiles onto the standard land based unit AI types without much chance of things going awry, and make other related tweaks. Of course, I'm not actually set up to build the DLL (yet), thus all the testing of behaviors for the sea unit AI types.
 

Attachments

  • Cruiser_with_missile.jpg
    Cruiser_with_missile.jpg
    42.6 KB · Views: 233
The Delta Battlecruiser shown in the attachment just wiped out all 3 extraction facilities I had on the resources controlled by this starbase using the missiles it carried (it has a capacity of 3 without any capacity increasing promotions - I think my previous post that said the capacity was 2 until Omega was wrong...). It is using UNITAI_MISSILE_CARRIER_SEA, forced on it via the onUnitBuilt event handler as described in the previous post.

So if land units are forced into the unit AI types for sea units they do function properly. It just doesn't build units with them in non-coastal cities (of which we have none).

If anyone else wants to give it a try, this is the onUnitBuilt function I am currently using. (FFP version, of course - still has the code for the Brotherhood getting extra XP - if you are using one of the variants without them you need to merge it properly).

Code:
	def onUnitBuilt(self, argsList):
		'Unit Completed'
		#self.parentClass.onUnitBuilt(self, argsList)
		pCity = argsList[0]
		pUnit = argsList[1]
		
		pPlayer = gc.getPlayer(pCity.getOwner())
		
		iTrait = gc.getInfoTypeForString('TRAIT_BROTHERHOOD')
		
		if (pPlayer.hasTrait(iTrait)):
			pUnit.changeExperience(4, 100, false, false, false)

		# FFP - Missile carrying unit UnitAI adjustment
		# Since the unit was just created it should have no cargo on it
		# so the cargoSpaceAvailable function shuold return the maximum
		# it can carry.
		# The chance to swtich unit AI to one of the sea types is
		# the number it can carry out of that number + 2.
		# So 1 in 3 if can carry 1, 2 in 4 if can carry 2, etc.
		# This should reduce the impact on the AI of swapping unit AIs (compared
		# to always switching them).
		# Unit AI types that missiels will load onto:
		#  UNITAI_MISSILE_CARRIER_SEA, UNITAI_RESERVE_SEA, UNITAI_ATTACK_SEA.
		# chance for slecting each type is 50% for ATTACK_SEA and 25% for the other two
		# Notes:
		# - UNITAI_MISSILE_CARRIER_SEA will load as many as it can carry,
		# the other two will apparently get a maximum of 2; this unit AI does not appear
		# to ever attack any other unit directly unless it is in a city (via the
		# AI_seaRetreatFromCityDanger, which seems to be misnamed as it appears
		# to be more about attacking enemies approaching a city than running away);
		# it also has additional logic for where to move to fire missiles at things
		# and will force an AI update for its cargo (which causes them to do their
		# launch logic if they haven't already) when it gets to its selected location
		# but with the other two unit AI types the carried missiles do their logic
		# whenever it gets to them in the normal order
		# - UNITAI_RESERVE_SEA will tend to protect high value resources:
		# I think this will protect the +2 happy/healthy resources and maybe the Uranium
		# once you can build Doomesday Missiles, but probably not any of the others
		# unless maybe the Industrial Complex is built (maybe not even then);
		# it can also "patrol" which ought to do anti-pirate work; its odds of
		# attacking nearby units are lower than the ATTACK version (it requires the
		# chance of winning to be higher) but it will do so; it can join an attack
		# group and go with it if it isn't doing anything else
		iMissiles = pUnit.cargoSpaceAvailable( 2, DomainTypes.DOMAIN_AIR) # Special unit type 2 is SPECIALUNIT_MISSILE
		if iMissiles > 0 :
			if iMissiles > CyGame().getSorenRandNum(iMissiles + 2, "Change missile carrying unit UNITAI type?") :
				iType = CyGame().getSorenRandNum(4, "Select new unit AI type")
				if iType == 0: # 25%
					pUnit.setUnitAIType(UnitAITypes.UNITAI_MISSILE_CARRIER_SEA)
					printd("onUnitBuilt: changed unit AI to UNITAI_MISSILE_CARRIER_SEA for %s (%s)" % (gc.getUnitInfo(pUnit.getUnitType()).getDescription().encode('unicode_escape'), pPlayer.getName()))
				elif iType == 1: # 25%
					pUnit.setUnitAIType(UnitAITypes.UNITAI_RESERVE_SEA)
					printd("onUnitBuilt: changed unit AI to UNITAI_RESERVE_SEA for %s (%s)" % (gc.getUnitInfo(pUnit.getUnitType()).getDescription().encode('unicode_escape'), pPlayer.getName()))
				else: # 50%
					pUnit.setUnitAIType(UnitAITypes.UNITAI_ATTACK_SEA)
					printd("onUnitBuilt: changed unit AI to UNITAI_ATTACK_SEA for %s (%s)" % (gc.getUnitInfo(pUnit.getUnitType()).getDescription().encode('unicode_escape'), pPlayer.getName()))
I may add something similar to check for squadron capacity and swap some of them to UNITAI_CARRIER_SEA for an additional test. Currently the AI is never building any carriers (since their squadron carrying capability is irrelevant for any unit AI type it is willing to build, making them just very expensive but very weak ships as far as it is concerned), but the Python AI override code has stuff in there that can be activated to get some.

In the long run I hope to fix this in the DLL instead of forcing it via the Python.
 

Attachments

  • BattlecruiserMissiles.jpg
    BattlecruiserMissiles.jpg
    125.5 KB · Views: 266
Just been checking over some of the things you've done with FFP and found that even though I'd added in the code above for some reason the last section was #'d out. Specifically this bit was all # out :-
Code:
iMissiles = pUnit.cargoSpaceAvailable( 2, DomainTypes.DOMAIN_AIR) # Special unit type 2 is SPECIALUNIT_MISSILE
		if iMissiles > 0 :
			if iMissiles > CyGame().getSorenRandNum(iMissiles + 2, "Change missile carrying unit UNITAI type?") :
				iType = CyGame().getSorenRandNum(4, "Select new unit AI type")
				if iType == 0: # 25%
					pUnit.setUnitAIType(UnitAITypes.UNITAI_MISSILE_CARRIER_SEA)
					printd("onUnitBuilt: changed unit AI to UNITAI_MISSILE_CARRIER_SEA for %s (%s)" % (gc.getUnitInfo(pUnit.getUnitType()).getDescription().encode('unicode_escape'), pPlayer.getName()))
				elif iType == 1: # 25%
					pUnit.setUnitAIType(UnitAITypes.UNITAI_RESERVE_SEA)
					printd("onUnitBuilt: changed unit AI to UNITAI_RESERVE_SEA for %s (%s)" % (gc.getUnitInfo(pUnit.getUnitType()).getDescription().encode('unicode_escape'), pPlayer.getName()))
				else: # 50%
					pUnit.setUnitAIType(UnitAITypes.UNITAI_ATTACK_SEA)
					printd("onUnitBuilt: changed unit AI to UNITAI_ATTACK_SEA for %s (%s)" % (gc.getUnitInfo(pUnit.getUnitType()).getDescription().encode('unicode_escape'), pPlayer.getName()))

I've now removed the # that were preventing the code from working correctly. Hoping to see more missiles and fighters in use now.
 
That code is not needed anymore as ships that can carry missiles get a proper setup to do so in the DLL now (moved into the DLL back in v1.8, I think). Some missile carrying units should be produced with the "missile carrier sea" unit AI type. Also, the missile unit AI has been modified so that it will load missiles onto units with many more unit AI types that are not "sea" types, so units that can carry missiles can have a broad range of regular "land" type unit AIs and still get a missile loaded onto them (most of them will only get 1 loaded - attack, collateral, reserve, explore, pillage, counter - but a unit with "city attack" can get up to its capacity as will the ones that end up with the "missile carrier sea" unit AI type). The setup is far from perfect, but it does get some missiles onto ships, and from time to time a ship should end up with a full load of them.

Since you are using a DLL newer than when that was done, without that code in the Python you should have seen missiles on ships that can carry them. In regular FFP it is pretty frequent, but not universal even with free (from starbases) missiles being produced. Unless you cheat (one of the modifiers, control, alt, or shift - I can never remember which is which - will add the info of what cargo is carried to the help data for the units you mouse over if you have the cheat code in the BtS .ini file, and the info is also available via the world builder) there is no way of knowing if a ship is carrying missiles or not until it uses them.
 
Top Bottom