Can map script tell what trait a player has?

Astax

Prince
Joined
Oct 27, 2005
Messages
556
Hi I'm wondering how much a map script knows about the player. Is there a function I can call to get info about the player? Ideally traits, or leader name. I wish to see if I can make a map script that modifies the starting area based on your traits, as to give a leg up to some less desirable traits. If I can get traits or leader tied to player Id, I know I can do this.

update: I know CyPlayer has hasTrait function, but I don't know if I can call this using a map script.
 
I have never heard of a *mapscript* making decisions based on the player. I suppose it is possible. However, you may want to put this code inside onGameStart instead. A lot of mapscripts do this. Are you using any other python mods? If so then you probably already have an event manager as a starting point.
 
Well I rather it be compatible with the stock version of the game. Can I put onGameStart in the map script?
 
Perhaps I don't understand what you are trying to do. If you are adding a mapscript, you are already making something which is not compatible with the stock version of the game.
 
Yes but a map script is automatically distributed in MP. and the other players do not need to have it.

Anyway I am able to get CyPlayer, but I am not certain if it has a trait info. So far my attempts at giving a Ind player stone at his starting location have turned out a bit weird. He ends up tossed out of the whole process where an acceptable starting plot is found, and ends ups tarting in a random location. I will keep trying thou as it may be my poor coding that's at fault!
 
Update: OK I tried placing something other than stone (I used hit singles), and it works(Sort of). It added few more hit singles than i expected :/

I think there is some sort of another normalization routine that wont let me start on stone, and moves the settler. Which is weird. Anyone got a clue as to what it is?

Also because it placed so many resources I think I placed the call to my function in the wrong place. I placed it right before isValid (part of findStartingPlot) returns true. So I think it might be the game gets not one starting plot but a list and then chooses one fo those. So it gets a bit more complicated.

Also self.gc.getPlayer(i).getStartingPlot() does not seem to work properly. This could be because there is no call for setStartingPlot in my script. How does the game setStartingPlot otherwise? If I have it setStartingPlot, I could cut out the extra resources by only palcing them near plot that gets returned with getStartingPlot().
 
All of the mapscripts are different. Please indicate which mapscript you are starting with, and please post the segments of changes you have made.
 
I am using Team Battleground script. I cant post all changes, they are extensive. But this is the function I wrote for adding somethign absed os trait. Right now it is jsut prototype:

Code:
def addExtraGoodies(x, y, iPlayerID):
	map = CyMap()
	xPlayer = CyGlobalContext().getPlayer(iPlayerID)
	pPlot = map.plot(x,y)
	
	if xPlayer.hasTrait(4):
		pPlot.setBonusType(-1)
		pPlot.setPlotType(2, true, true)
		pPlot.setTerrainType(1, true, true)
		pPlot.setFeatureType(1, 1)
		pPlot.setBonusType(33)
		
	return true

Now this is the changes I put in normalizeStartingPlotLocations. Cause I figured this would be called once the starting plots were established:

Code:
def normalizeStartingPlotLocations():
	numTeams = CyGlobalContext().getGame().countCivTeamsAlive()
	userInputProximity = CyMap().getCustomMapOption(1)
	
	gc = CyGlobalContext()
	map = CyMap()
	seaCheck = map.getSeaLevel()
	tvbCheck = map.getCustomMapOption(0) 
		
	if (seaCheck == 0) and (tvbCheck == 1):
		for i in range(self.gc.getMAX_CIV_PLAYERS()):
			if (self.gc.getPlayer(i).isAlive()):
				start_plot = self.gc.getPlayer(i).getStartingPlot() # returns a CyPlot
				startx, starty = start_plot.getX(), start_plot.getY()
				addExtraGoodies(startx, starty, i)



	
	if (numTeams > 4 or numTeams < 2) and userInputProximity == 0:
		CyPythonMgr().allowDefaultImpl()
	else:
		return None

This however totally does not work since I don't think that the starting plots are set anywhere.

Alternately when I put the calls inside isValid, I ended up with the game placing multiples of the resource at different places. Some of which would not have resolved as valid. This was confusing since I put the calls directly before return true. So I assumed when these were executed, the plot would have been valid, and would have been picked.

Code:
	def isValid(playerID, x, y):
#		BugUtil.debug("Team_Battleground: isValid")
		map = CyMap()
		numTeams = CyGlobalContext().getGame().countCivTeamsAlive()
		if numTeams > 4 or numTeams < 2: # Put em anywhere, and let the normalizer sort em out.
			return true
		userInputProximity = map.getCustomMapOption(1)
		if userInputProximity == 2: # Start anywhere!
			return true

		global shuffle
		global shuffledTeams
		global team_num
		global shuffledPlayers
		global player_num

		thisTeamID = CyGlobalContext().getPlayer(playerID).getTeam()
		teamID = team_num[thisTeamID]
		userInputPlots = map.getCustomMapOption(0)
		iW = map.getGridWidth()
		iH = map.getGridHeight()

#		BugUtil.debug("Team_Battleground: isValid teams %i, prox %i", numTeams, userInputProximity)

		# Two Teams, Start Together
		if numTeams == 2 and userInputProximity == 0: # Two teams, Start Together
			if userInputPlots == 1: # TvB
				
				seaCheck = map.getSeaLevel()
	
				if (seaCheck == 0):
					checkFood = doesBFCHaveFood(x, y)
					if not checkFood:
						return false
								
				gc = CyGlobalContext()

				if (CyMap().getCustomMapOption(3) == 1):
					check20Copper = isResourceInRange(x, y, 2, 2)
					check40Copper = isResourceInRange(x, y, 2, 3)
					check20Iron = isResourceInRange(x, y, 4, 2)
					check20Horse = isResourceInRange(x, y, 3, 2)
					check40Horse = isResourceInRange(x, y, 3, 3)
					if check20Copper or check20Horse:
						return false
					if not (check40Copper or check40Horse or check20Iron):
						return false
				
				if teamID == 0 and shuffle and y >= iH * 0.6:
					addExtraGoodies(x, y, playerID)
					return true
				if teamID == 1 and not shuffle and y >= iH * 0.6:
					addExtraGoodies(x, y, playerID)
					return true
				if teamID == 0 and not shuffle and y <= iH * 0.4:
					addExtraGoodies(x, y, playerID)
					return true
				if teamID == 1 and shuffle and y <= iH * 0.4:
					addExtraGoodies(x, y, playerID)
					return true
				return false

			elif (userInputPlots == 0   # LvR
			or    userInputPlots == 3): # LvR with land bridge
				if teamID == 0 and shuffle and x >= iW * 0.6:
					return true
				if teamID == 1 and not shuffle and x >= iW * 0.6:
					return true
				if teamID == 0 and not shuffle and x <= iW * 0.4:
					return true
				if teamID == 1 and shuffle and x <= iW * 0.4:
					return true
				return false

			else: # 4C
				corner = shuffledTeams[teamID]
				if corner == 0 and x <= iW * 0.4 and y <= iH * 0.4:
					return true
				if corner == 1 and x >= iW * 0.6 and y <= iH * 0.4:
					return true
				if corner == 2 and x <= iW * 0.4 and y >= iH * 0.6:
					return true
				if corner == 3 and x >= iW * 0.6 and y >= iH * 0.6:
					return true
				return false

		# Three or Four Teams
		elif (numTeams == 3 or numTeams == 4) and userInputProximity == 0: # 3 or 4 teams, Start Together
			corner = shuffledTeams[teamID]
			if corner == 0 and x <= iW * 0.4 and y <= iH * 0.4:
				return true
			if corner == 1 and x >= iW * 0.6 and y <= iH * 0.4:
				return true
			if corner == 2 and x <= iW * 0.4 and y >= iH * 0.6:
				return true
			if corner == 3 and x >= iW * 0.6 and y >= iH * 0.6:
				return true
			return false
		elif (numTeams == 3 or numTeams == 4) and userInputProximity == 1: # 3 or 4 teams, Start Separated
			corner = shuffledTeams[teamID] + assignedPlayers[teamID]
			while corner >= 4:
				corner -= 4
			if corner == 0 and x <= iW * 0.4 and y <= iH * 0.4:
				return true
			if corner == 1 and x >= iW * 0.6 and y <= iH * 0.4:
				return true
			if corner == 2 and x <= iW * 0.4 and y >= iH * 0.6:
				return true
			if corner == 3 and x >= iW * 0.6 and y >= iH * 0.6:
				return true
			return false

		# Two Teams, Start Separated
		elif numTeams == 2 and userInputProximity == 1: # Two teams, Start Separated
			if (shuffle and teamID == 0) or (not shuffle and teamID == 1):
				side = assignedPlayers[teamID]
			else:
				side = 1 + assignedPlayers[teamID]
			while side >= 2:
				side -= 2
			if userInputPlots == 1: # TvB
				
				seaCheck = map.getSeaLevel()
	
				if (seaCheck == 0):
					checkFood = doesBFCHaveFood(x, y)
					if not checkFood:
						return false
					
						
				gc = CyGlobalContext()

				if (CyMap().getCustomMapOption(3) == 1):
					check20Copper = isResourceInRange(x, y, 2, 2)
					check40Copper = isResourceInRange(x, y, 2, 3)
					check20Iron = isResourceInRange(x, y, 4, 2)
					check20Horse = isResourceInRange(x, y, 3, 2)
					check40Horse = isResourceInRange(x, y, 3, 3)
					if check20Copper or check20Horse:
						return false
					if not (check40Copper or check40Horse or check20Iron):
						return false
								
				if teamID == 0 and side and y >= iH * 0.625 and y <= iH * 0.825:
					addExtraGoodies(x, y, playerID)
					return true
				if teamID == 1 and not side and y >= iH * 0.625 and y <= iH * 0.825:
					addExtraGoodies(x, y, playerID)
					return true
				if teamID == 0 and not side and y <= iH * 0.375 and y >= iH * 0.175:
					addExtraGoodies(x, y, playerID)
					return true
				if teamID == 1 and side and y <= iH * 0.375 and y >= iH * 0.175:
					addExtraGoodies(x, y, playerID)
					return true
					
				return false

			elif (userInputPlots == 0   # LvR
			or    userInputPlots == 3): # LvR with land bridge
				if teamID == 0 and side and x >= iW * 0.6:
					return true
				if teamID == 1 and not side and x >= iW * 0.6:
					return true
				if teamID == 0 and not side and x <= iW * 0.4:
					return true
				if teamID == 1 and side and x <= iW * 0.4:
					return true
				return false

			else: # 4C
				corner = shuffledTeams[side]
				if corner == 0 and x <= iW * 0.4 and y <= iH * 0.4:
					return true
				if corner == 1 and x >= iW * 0.6 and y <= iH * 0.4:
					return true
				if corner == 2 and x <= iW * 0.4 and y >= iH * 0.6:
					return true
				if corner == 3 and x >= iW * 0.6 and y >= iH * 0.6:
					return true
				return false

		# All conditions have failed? Wow. Is that even possible? :)
		return true
		
	return CvMapGeneratorUtil.findStartingPlot(playerID, isValid)

Note that I am not concerned with anything that's not Top vs Bottom, Start together or Start Separated. And for now limited to low sea level. I only have the legacy code in there cause its a pain to trip it out, but I plan on getting rid of unsupported options.
 
I am not sure of the exact calling sequence of the various "normalize" routines. However, python functions like "normalizeXYZ" exist to override sdk functions with the same names. Even if you do not know C++, it may still be helpful to look in the sdk files, see the "cvgamecoredll" directory under your BTS installation. The comment for normalizeStartingPlotLocations says:

// Swaps starting locations until we have reached the optimal closeness between teams


So my guess is that when your python function is called, your code is executed instead of the sdk code for this function. That means the starting positions may exist but they may not be good ones. This function, and perhaps some other functions after this, may move the start locations.

It may be helpful for you to find out more about the calling sequence, and then move your code much later in the sequence. You may find it helpful to look at CvGame::normalizeStartingPlots. I am not sure if there is some existing document or thread which describes more about this.

You may also find it helpful to add a print statement which loops over the players and prints the starting locations returned by getStartingPlot. Then you can compare this to the actual start locations you see on the map. This may build up confidence that getStartingPlot is returning the right values.
 
Yeah it seems it keeps finding starting plot over and over. I'm gonna look into it a lot more. I have to find a place in the script where I am sure it has finalized the starting position. Then I can do my thing. But I am confident I can make modifications based on what trait a player has.
 
This function gets the list of traits for a leader:
Code:
# get list of leader traits as comma separated string
def getTraitList( player ):
	print "======== MapStats.getTraitList()"
	nTraits = gc.getNumTraitInfos()
	sTraits = ""
	pInfo = gc.getLeaderHeadInfo( player.getLeaderType() )
	bFirst = True
	for i in range( nTraits ):
		if ( pInfo.hasTrait(i) ):
			if bFirst:
				bFirst = False
			else:
				sTraits += ", "
			sTraits += gc.getTraitInfo(i).getType()[6:].capitalize()
	return sTraits
I used this in my Ringworld2.py map, which generates all kind of infos in the log.


As best as I could work it out, this is the sequence of the map-building process.

For Reference:
Code:
[FONT="Courier New"][B]-------------------------------------------------------------------
The Map Building Process according to Temudjin
    --> also see Bob Thomas in "CvMapScriptInterface.py"
    (in ..\Assets\Python\EntryPoints)
-------------------------------------------------------------------[/B]
0)     - Get Map-Options
0.1)     getNumHiddenCustomMapOptions()
0.2)     getNumCustomMapOptions()
0.3)     getCustomMapOptionDefault()
0.4)     isAdvancedMap()
0.5)     getCustomMapOptionName()
0.6)     getNumCustomMapOptionValues()
0.7)     isRandomCustomMapOption()
0.8)     getCustomMapOptionDescAt()
0.9)     - Get Map-Types
0.9.1)     isClimateMap()
0.9.2)     isSeaLevelMap()
1)     beforeInit()
2)     - Initialize Map
2.2)     getGridSize()
2.3.1)   getTopLatitude()            # always use both
2.3.2)   getBottomLatitude()         # always use both
2.4.1)   getWrapX()                  # always use both
2.4.2)   getWrapY()                  # always use both
3)     beforeGeneration()
4)     - Generate Map
4.1)     generatePlotTypes()
4.2)     generateTerrainTypes()
4.3)     addRivers()
4.4)     addLakes()
4.5)     addFeatures()
4.6)     addBonuses()
4.6.1)     isBonusIgnoreLatitude()*
4.7)     addGoodies()
5)     afterGeneration()
6)     - Select Starting-Plots
6.1)     minStartingDistanceModifier()
6.2)     assignStartingPlots()
7)     - Normalize Starting-Plots
7.1)     normalizeStartingPlotLocations()
7.2)     normalizeAddRiver()
7.3)     normalizeRemovePeaks()
7.4)     normalizeAddLakes()
7.5)     normalizeRemoveBadFeatures()
7.6)     normalizeRemoveBadTerrain()
7.7)     normalizeAddFoodBonuses()
7.7.1)     isBonusIgnoreLatitude()*
7.8)     normalizeGoodTerrain()
7.9)     normalizeAddExtras()
7.9.1)     isBonusIgnoreLatitude()*
8 )    startHumansOnSameTile()

* used by default 'CyPythonMgr().allowDefaultImpl()' in:
  addBonuses(), normalizeAddFoodBonuses(), normalizeAddExtras()
[/FONT]

In your second code-fragment - normalizeStartingPlotLocations() - you changed starting-plots before you used CyPythonMgr().allowDefaultImpl(), which may also change starting-plots. Could that be a problem?
 
Say, that is a very handy list of the order of operations. Recently all of the threads in the mapscript subforum which are not specifically mapscripts were rejected. But, could you post this order into a thread in the tutorial subforum? I think many people could use this when writing mapscripts.
 
Thnx Temudjin, very helpful list! Gives me a nice blueprint to work with. I haven't digested everything u said yet. I think my issue is that even if the starting plot is valid, the game will consider another plot if the players are not close enough together. It's a default function like davidlallen mentioned. I am actually learning about the C++ functions right now for another mod I'm helping with, so once I get the appropriate makefile I can tinker witht hat as well..
 
Oh Temudjin if you are referring to:
Code:
if (numTeams > 4 or numTeams < 2) and userInputProximity == 0:
		CyPythonMgr().allowDefaultImpl()
That's legacy code. I left it in but my map is meant for 2 teams, so that code fragment should not get called. But it seems it might be getting called! I'm working on this right now.
 
Top Bottom