Help: Urban Planner Unit for BTS

smeagolheart

Monarch
Joined
Sep 4, 2007
Messages
924
Location
Phoenix, AZ
Hey I'm a python pre-noob. I came across the Urban Planner unit from Genetic Era for Warlords (and ViSa Modpack). Urban planner being a Call to Power unit that is like an upgraded settler that spawns a couple buildings and a slightly increased city size. Neat deal, so I've been trying to get it to work in my mod.

I added the unit xml fine, unit shows up in game. XML looks like this:
Spoiler :

Code:
		<UnitInfo>
			<Class>UNITCLASS_URBAN_PLANNER</Class>
			<Type>UNIT_URBAN_PLANNER</Type>
			<UniqueNames/>
			<Special>NONE</Special>
			<Capture>UNITCLASS_WORKER</Capture>
			<Combat>NONE</Combat>
			<Domain>DOMAIN_LAND</Domain>
			<DefaultUnitAI>UNITAI_SETTLE</DefaultUnitAI>
			<Invisible>NONE</Invisible>
			<SeeInvisible>NONE</SeeInvisible>
			<Description>TXT_KEY_UNIT_URBAN_PLANNER</Description>
			<Civilopedia>TXT_KEY_UNIT_URBAN_PLANNER_PEDIA</Civilopedia>
			<Strategy>TXT_KEY_UNIT_URBAN_PLANNER_STRATEGY</Strategy>
			<Advisor>ADVISOR_GROWTH</Advisor>
			<bAnimal>0</bAnimal>
			<bFood>1</bFood>
			<bNoBadGoodies>0</bNoBadGoodies>
			<bOnlyDefensive>0</bOnlyDefensive>
			<bNoCapture>0</bNoCapture>
			<bQuickCombat>0</bQuickCombat>
			<bRivalTerritory>0</bRivalTerritory>
			<bMilitaryHappiness>0</bMilitaryHappiness>
			<bMilitarySupport>0</bMilitarySupport>
			<bMilitaryProduction>0</bMilitaryProduction>
			<bPillage>0</bPillage>
			<bSpy>0</bSpy>
			<bSabotage>0</bSabotage>
			<bDestroy>0</bDestroy>
			<bStealPlans>0</bStealPlans>
			<bInvestigate>0</bInvestigate>
			<bCounterSpy>0</bCounterSpy>
			<bFound>1</bFound>
			<bGoldenAge>0</bGoldenAge>
			<bInvisible>0</bInvisible>
			<bFirstStrikeImmune>0</bFirstStrikeImmune>
			<bNoDefensiveBonus>0</bNoDefensiveBonus>
			<bIgnoreBuildingDefense>0</bIgnoreBuildingDefense>
			<bCanMoveImpassable>0</bCanMoveImpassable>
			<bCanMoveAllTerrain>0</bCanMoveAllTerrain>
			<bFlatMovementCost>0</bFlatMovementCost>
			<bIgnoreTerrainCost>0</bIgnoreTerrainCost>
			<bNukeImmune>0</bNukeImmune>
			<bPrereqBonuses>0</bPrereqBonuses>
			<bPrereqReligion>0</bPrereqReligion>
			<bMechanized>0</bMechanized>
			<bSuicide>0</bSuicide>
			<bHiddenNationality>0</bHiddenNationality>
			<bAlwaysHostile>0</bAlwaysHostile>
			<UnitClassUpgrades/>
			<UnitClassTargets/>
			<UnitCombatTargets/>
			<UnitClassDefenders/>
			<UnitCombatDefenders/>
			<FlankingStrikes/>
			<UnitAIs>
				<UnitAI>
					<UnitAIType>UNITAI_SETTLE</UnitAIType>
					<bUnitAI>1</bUnitAI>
				</UnitAI>
			</UnitAIs>
			<NotUnitAIs/>
			<Builds/>
			<ReligionSpreads/>
			<CorporationSpreads/>
			<GreatPeoples/>
			<Buildings>
				<Building>
					<BuildingType>BUILDING_GRANARY</BuildingType>
					<bBuilding>1</bBuilding>
				</Building>
				<Building>
					<BuildingType>BUILDING_FORGE</BuildingType>
					<bBuilding>1</bBuilding>
				</Building>
				<Building>
					<BuildingType>BUILDING_BARRACKS</BuildingType>
					<bBuilding>1</bBuilding>
				</Building>
				<Building>
					<BuildingType>BUILDING_STABLE</BuildingType>
					<bBuilding>1</bBuilding>
				</Building>
				<Building>
					<BuildingType>BUILDING_COURTHOUSE</BuildingType>
					<bBuilding>1</bBuilding>
				</Building>
				<Building>
					<BuildingType>BUILDING_MARKET</BuildingType>
					<bBuilding>1</bBuilding>
				</Building>
			</Buildings>
			<ForceBuildings/>
			<HolyCity>NONE</HolyCity>
			<ReligionType>NONE</ReligionType>
			<StateReligion>NONE</StateReligion>
			<PrereqReligion>NONE</PrereqReligion>
			<PrereqCorporation>NONE</PrereqCorporation>
			<PrereqBuilding>NONE</PrereqBuilding>
			<PrereqTech>NONE</PrereqTech>
			<TechTypes/>
			<BonusType>NONE</BonusType>
			<PrereqBonuses/>
			<ProductionTraits>
				<ProductionTrait>
					<ProductionTraitType>TRAIT_IMPERIALIST</ProductionTraitType>
					<iProductionTrait>50</iProductionTrait>
				</ProductionTrait>
			</ProductionTraits>
			<Flavors/>
			<iAIWeight>0</iAIWeight>
			<iCost>100</iCost>
			<iHurryCostModifier>0</iHurryCostModifier>
			<iAdvancedStartCost>100</iAdvancedStartCost>
			<iAdvancedStartCostIncrease>0</iAdvancedStartCostIncrease>
			<iMinAreaSize>-1</iMinAreaSize>
			<iMoves>2</iMoves>
			<bNoRevealMap>0</bNoRevealMap>
			<iAirRange>0</iAirRange>
			<iAirUnitCap>0</iAirUnitCap>
			<iDropRange>0</iDropRange>
			<iNukeRange>-1</iNukeRange>
			<iWorkRate>0</iWorkRate>
			<iBaseDiscover>0</iBaseDiscover>
			<iDiscoverMultiplier>0</iDiscoverMultiplier>
			<iBaseHurry>0</iBaseHurry>
			<iHurryMultiplier>0</iHurryMultiplier>
			<iBaseTrade>0</iBaseTrade>
			<iTradeMultiplier>0</iTradeMultiplier>
			<iGreatWorkCulture>0</iGreatWorkCulture>
			<iEspionagePoints>0</iEspionagePoints>
			<TerrainImpassables/>
			<FeatureImpassables/>
			<TerrainPassableTechs/>
			<FeaturePassableTechs/>
			<iCombat>0</iCombat>
			<iCombatLimit>0</iCombatLimit>
			<iAirCombat>0</iAirCombat>
			<iAirCombatLimit>0</iAirCombatLimit>
			<iXPValueAttack>0</iXPValueAttack>
			<iXPValueDefense>0</iXPValueDefense>
			<iFirstStrikes>0</iFirstStrikes>
			<iChanceFirstStrikes>0</iChanceFirstStrikes>
			<iInterceptionProbability>0</iInterceptionProbability>
			<iEvasionProbability>0</iEvasionProbability>
			<iWithdrawalProb>0</iWithdrawalProb>
			<iCollateralDamage>0</iCollateralDamage>
			<iCollateralDamageLimit>0</iCollateralDamageLimit>
			<iCollateralDamageMaxUnits>0</iCollateralDamageMaxUnits>
			<iCityAttack>0</iCityAttack>
			<iCityDefense>0</iCityDefense>
			<iAnimalCombat>0</iAnimalCombat>
			<iHillsAttack>0</iHillsAttack>
			<iHillsDefense>0</iHillsDefense>
			<TerrainNatives/>
			<FeatureNatives/>
			<TerrainAttacks/>
			<TerrainDefenses/>
			<FeatureAttacks/>
			<FeatureDefenses/>
			<UnitClassAttackMods/>
			<UnitClassDefenseMods/>
			<UnitCombatMods/>
			<UnitCombatCollateralImmunes/>
			<DomainMods/>
			<BonusProductionModifiers/>
			<iBombRate>0</iBombRate>
			<iBombardRate>0</iBombardRate>
			<SpecialCargo>NONE</SpecialCargo>
			<DomainCargo>NONE</DomainCargo>
			<iCargo>0</iCargo>
			<iConscription>0</iConscription>
			<iCultureGarrison>0</iCultureGarrison>
			<iExtraCost>0</iExtraCost>
			<iAsset>1</iAsset>
			<iPower>0</iPower>
			<UnitMeshGroups>
				<iGroupSize>2</iGroupSize>
				<fMaxSpeed>1.75</fMaxSpeed>
				<fPadTime>1</fPadTime>
				<iMeleeWaveSize>2</iMeleeWaveSize>
				<iRangedWaveSize>0</iRangedWaveSize>
				<UnitMeshGroup>
					<iRequired>1</iRequired>
					<EarlyArtDefineTag>ART_DEF_UNIT_URBAN_PLANNER</EarlyArtDefineTag>
				</UnitMeshGroup>
			</UnitMeshGroups>
			<FormationType>FORMATION_TYPE_DEFAULT</FormationType>
			<HotKey/>
			<bAltDown>0</bAltDown>
			<bShiftDown>0</bShiftDown>
			<bCtrlDown>0</bCtrlDown>
			<iHotKeyPriority>0</iHotKeyPriority>
			<FreePromotions/>
			<LeaderPromotion>NONE</LeaderPromotion>
			<iLeaderExperience>0</iLeaderExperience>
		</UnitInfo>


So it seems that the unit also requires python to do it's thing. Here's the code from the warlords version (I think this is all, let me know if I'm missing something).

UrbanPlanner.py
Spoiler :

Code:
## Sid Meier's Civilization 4
## Copyright Firaxis Games 2005
##
##  Extra EventHandlers by primem0ver
##

from CvPythonExtensions import *
import CvUtil
import PyHelpers
import Popup as PyPopup

# globals
gc = CyGlobalContext()
unitCity = None

class  UrbanPlannerHandler:
	def __init__(self):
		self.city = None

	def onUnitLost(self, argsList):
		#print "Running Custom Script"
		unit = argsList[0]
		handleEvent = 0
		if (unit.getUnitType() != gc.getInfoTypeForString("UNIT_URBAN_PLANNER")):
			return
		else:
			#print "Handling urban settler"
			#print self.city
			if (self.city):
				#print "Adding Buildings"
				self.city.setHasRealBuilding(gc.getInfoTypeForString("BUILDING_GRANARY"), true)
				self.city.setHasRealBuilding(gc.getInfoTypeForString("BUILDING_COURTHOUSE"), true)
				self.city.setHasRealBuilding(gc.getInfoTypeForString("BUILDING_MARKET"), true)
				self.city.setHasRealBuilding(gc.getInfoTypeForString("BUILDING_AQUEDUCT"), true)
				self.city.setPopulation(3)
				if (self.city.isCoastal(1)):
					self.city.setHasRealBuilding(gc.getInfoTypeForString("BUILDING_HARBOR"), true)
					self.city.setHasRealBuilding(gc.getInfoTypeForString("BUILDING_LIGHTHOUSE"), true)
		return
		
	def setCity(self, argsList):
		city = argsList[0]
		self.city = city
		#print "City founded: " + city.getName()


CvCustomEventManager.py
Spoiler :

Code:
## Sid Meier's Civilization 4
## Copyright Firaxis Games 2005
## 
## This class is passed an argsList from CvAppInterface.onEvent
## The argsList can contain anything from mouse location to key info
## The EVENTLIST that are being notified can be found 


from CvPythonExtensions import *
import CvUtil
import CvScreensInterface
import CvDebugTools
import CvWBPopups
import PyHelpers
import Popup as PyPopup
import CvCameraControls
import CvTopCivs
import CvEventManager
import sys
import Terraform
import TechConquest
import SettlerReligion
import Dome
import UrbanPlanner
import MigrantDestination
	
gc = CyGlobalContext()
localText = CyTranslator()
PyPlayer = PyHelpers.PyPlayer
PyInfo = PyHelpers.PyInfo

tf = Terraform.Terraform()
dm = Dome.Dome()
techConquest = TechConquest.TechConquest()
sr = SettlerReligion.SettlerReligion()

#added by primem0ver
up = UrbanPlanner.UrbanPlannerHandler()
md = MigrantDestination.MigrantDestination()


# globals
###################################################
class CvCustomEventManager(CvEventManager.CvEventManager):
	def __init__(self):
		# initialize base class
		self.parent = CvEventManager.CvEventManager
		self.parent.__init__(self)
		self.Events[7502] = ('RemoveWaterPopup', self.__eventRemoveWaterApply , self.__eventRemoveWaterBegin)

	def onImprovementBuilt(self, argsList):
		self.parent.onImprovementBuilt(self, argsList);
		tf.onImprovementBuilt(argsList)

	def onRouteBuilt(self, argsList):
		self.parent.onRouteBuilt(self, argsList);
		tf.onRouteBuilt(argsList)

	def __eventRemoveWaterApply(self, playerID, userData, popupReturn):
		tf.eventRemoveWaterApply(playerID, userData, popupReturn)
		
	def __eventRemoveWaterBegin(self, argsList):
		tf.eventRemoveWaterBegin(argsList)

	def onCityAcquired(self, argsList):
		self.parent.onCityAcquired(self, argsList)
		techConquest.onCityAcquired(argsList)


	def onUnitBuilt(self, argsList):
		self.parent.onUnitBuilt(self, argsList)
		sr.onUnitBuilt(argsList)
		md.onUnitBuilt(argsList)

	def onUnitLost(self, argsList):
		self.parent.onUnitLost(self, argsList)

		#added by primem0ver:
		md.onUnitLost(argsList)
		up.onUnitLost(argsList)
		
		#original line:
		sr.onUnitLost(argsList)
		
		

	def onCityBuilt(self, argsList):
		self.parent.onCityBuilt(self, argsList)
		sr.onCityBuilt(argsList)
		dm.onCityBuilt(argsList)

		#added by primem0ver
		up.setCity(argsList)

	def onUnitMove(self, argsList):
		self.parent.onUnitMove(self, argsList)
		md.onUnitMove(argsList)


So I've attempted to work this into my mod's CvEventManager.py but it's not working, probably because I'm a noob pythoner...
EDIT: This part is just a snippet of course corresponding to the onUnitLost area..
Spoiler :

Code:
	def onUnitLost(self, argsList):
		'Unit Lost'
		unit = argsList[0]
		player = PyPlayer(unit.getOwner())

## Urban Planner Start ##

		if (unit.getUnitType() != CyGlobalContext().getInfoTypeForString("UNIT_URBAN_PLANNER")):
			return
		else:
			if (self.city):
				self.city.setHasRealBuilding(CyGlobalContext().getInfoTypeForString("BUILDING_GRANARY"), true)
				self.city.setHasRealBuilding(CyGlobalContext().getInfoTypeForString("BUILDING_COURTHOUSE"), true)
				self.city.setHasRealBuilding(CyGlobalContext().getInfoTypeForString("BUILDING_MARKET"), true)
				self.city.setHasRealBuilding(CyGlobalContext().getInfoTypeForString("BUILDING_AQUEDUCT"), true)
				self.city.setPopulation(3)
				if (self.city.isCoastal(1)):
					self.city.setHasRealBuilding(CyGlobalContext().getInfoTypeForString("BUILDING_HARBOR"), true)
					self.city.setHasRealBuilding(CyGlobalContext().getInfoTypeForString("BUILDING_LIGHTHOUSE"), true)
		return

## Urban Planner End ##

		if (not self.__LOG_UNITLOST):
			return
		CvUtil.pyPrint('%s was lost by Player %d Civilization %s' 
			%(PyInfo.UnitInfo(unit.getUnitType()).getDescription(), player.getID(), player.getCivilizationName()))


Advices?
 
Edit your posts and put your XML and Python code between [ CODE ] ... [ /CODE ] tags (remove spaces in my tags here). You can use the # icon above the editor box (go to advanced view if you edit your post).
 
I see a couple problems. All of the following information can be referenced in the Civ4 Python API. It's very helpful in helping you figure out how to do things; that and reading the code. ;)

First, "self.city" won't check if the unit was lost on a city tile. To do that, use this:

Code:
[s][COLOR="Red"]if (self.city):[/COLOR][/s]
plot = unit.plot()
if plot.isCity():
    city = plot.getPlotCity()
    if city.getOwner() == unit.getOwner():
        ... add buildings here ...

Here's a tip: as long as the top of any file has

Code:
gc = CyGlobalContext()

you can use "gc" in place of "CyGlobalContext()" throughout the file.

Next, BTS changed buildings such that each city can have more than one building of the same type. This changes the setHasRealBuilding() function to setNumRealBuildings():

Code:
city.setNumRealBuilding(gc.getInfoTypeForString("BUILDING_GRANARY"), 1)
...

Finally, once you get this working, you might want to check that those buildings can be built in the city before building them.

In the future, it also helps to post the errors your getting. They can be found in the file Logs/PythonErr.log in the BTS My Games folder.
 
I don't want to have multiple buildings, just whatever building or unique building the empire has for that building so I think adding the building_class may be the way to go.

The Urban planner is supposed to be an advanced settler, he builds a city as a settler would and a couple buildings (ie granary) and size 3.

This code is working where the unit can add a granary, like as a great person would where he can go to the city and build it but that's not really what is intended, though it is interesting. How to make it as an advanced settler?

Code:
	def onUnitLost(self, argsList):
		'Unit Lost'
		unit = argsList[0]
		player = PyPlayer(unit.getOwner())
## Urban Planner Start ##
		plot = unit.plot()

		if (unit.getUnitType() != gc.getInfoTypeForString("UNIT_URBAN_PLANNER")):
			return
		else:
			if plot.isCity():
    				city = plot.getPlotCity()
    				if city.getOwner() == unit.getOwner():
					city.setNumRealBuilding(gc.getInfoTypeForString("BUILDING_GRANARY"), 1)
		return

## Urban Planner End ##
 
I will give this a shot, I don't want to have multiple buildings.

That's why I set the Num to 1. The old function simply doesn't exist in BTS.

just whatever building or unique building the empire has for that building so I think building_class is the way to go

That brings up a good point. You will need to use the player's CivInfo to look up the actual building specific to their civ type.
 
My Rise of Mankind mod has Colonist and Pioneer units which build cities with preset buildings and also the city size is 3 for colonist and 4 for Pioneer and they will build correct Unique buildings if the civ has one for specifig building class. The file you need to check is SettlerEventManager.py. The approach is slightly different than in UrbanPlanner.py. There's one bug in SettlerEventManager.py as it will cause OOS (Out Of Sync) errors if the it is a multiplayer game and one player uses Colonist or Pioneer.
 
@zappara - If you already know this, please disregard. The reason you are getting OOS errors is because CyInterface().getHeadSelectedUnit() will be different on each player's PC. On the PC that built the city, it will work; on the others, it will be whatever unit that player has selected.

The solution is to detect which type of unit that built the city only on the builder's PC and then send a message to all the others using CyMessageControl().sendModNetMessage(...). I think this is how it should be used:

Code:
# define mod message types at top of file, outside the eventmanager class
SETTLERS_CITY_BUILT = 1

...

def onCityBuilt(self, argsList):
	'City Built'
	city = argsList[0]
	ePlayer = gc.getGame().getActivePlayer()
	if city.getOwner() == ePlayer:
		# only runs on the PC of the player that built the city
		unit = CyInterface().getHeadSelectedUnit()
		CyMessageControl().setModNetMessage(
				SETTLERS_CITY_BUILT,
				ePlayer,
				city.getID()
				unit.getID(),
				-1)

def onModNetMessage(self, argsList):
	if argsList[0] == SETTLERS_CITY_BUILT:
		# runs on every player's PC
		_, ePlayer, iCityID, iUnitID, _ = argsList
		player = gc.getPlayer(ePlayer)
		city = player.getCity(iCityID)
		unit = player.getUnit(iUnitID)
		... add buildings based on unit class ...
 
Will this code work when the AI builds the units as well?

The code I posted for zappara will not. The problem is that the event for building a city doesn't tell you which unit built it.

Your code relies on the assumption that the unit is lost immediately after the city is built. I have no idea if that assumption is correct, though. Is your unit a replacement for Settlers or something you can build and move into an existing city?
 
Another method you can use is to loop through the units on the plot where the city is built that belong to the same player as the city's owner.

The problem is that if they have a normal Settler and an Urban Planner, use the Settler to found the city, but the UP is the first unit in the list you see, you will give the city free buildings it shouldn't get.

I think you could modify the DLL to pass the unit that founds the city along with the city itself.
 
Top Bottom