• Civilization 7 has been announced. For more info please check the forum here .

need some help; Attribute Error

mechaerik

Tuturuu!
Joined
Oct 28, 2008
Messages
7,064
Location
Los Angeles
hello, all. What I am trying to accomlish is modify Baldyrs Rebels modcomp so that instead of spawning Barbarian units, it spawns units of the dead civ.

Spoiler :
Code:
def checkCity(cyCity) :
	bDisorder = cyCity.isDisorder()	
	iHappy = cyCity.happyLevel()
	iUnhappy = cyCity.unhappyLevel(0)
	bNeverLost = cyCity.isNeverLost()
	iForeigners = cyCity.getCulturePercentAnger()
	iOriginalOwner = cyCity.getOriginalOwner()
	iCurrentOwner = cyCity.getOwner()

	if( bDisorder == True or ((iHappy*2) < iUnhappy) ) :
		if( bNeverLost == False ) :
			if not( iOriginalOwner == iCurrentOwner ) :
				[COLOR="Red"]if not( iOriginalOwner.isAlive() ) :[/COLOR]
					iRandNum = cyGame.getSorenRandNum(100, "rebels")
					if( iRandNum <= iRebelsProb) :
						return True
	return False

The error results in the red line; the log states that the int object does not have attribute "isAlive".

I tried using iOriginalOwner.getCivilizationType().isAlive(), but it returned that it didnt have the attribute getCivilizationType. So Im confused now. I feel like its something simple that Im missing.
 
The variable iOriginalOwner is a integer value. While the isAlive() method belongs to the CyPlayer class (look in the API). So what you are doing is that you're invoking a class method on a integer value, like this:
Code:
5.isAlive()
The integer value 5 has no method called isAlive() so you get a attribute error. In fact, integer values have no methods available to them.

Instead, what you need is a CyPlayer instance to use with the isAlive() method. The iOriginalOwner value corresponds to the ID number of the player, and the way to fetch a reference to its associated CyPlayer instance is:
Code:
gc.getPlayer(iOriginalPlayer)
So this is your code, then:
Code:
iOriginalOwner = cyCity.getOriginalOwner()
pOriginalOwner = gc.getPlayer(iOriginalOwner)
bOriginalOwnerAlive = pOriginalOwner.isAlive()
if bOriginalOwnerAlive == False:
Good luck with the rest of the mod. :goodjob: (One tips would be to pass on either the iOriginalOwner or pOriginalOwner variable with the return command instead of the value True, so that you don't have to fetch the value all over again.)
Spoiler :
You could of course use this also. No variable assignment but I'm still not using the parenthesis you seem to add to every conditional statement:
Code:
if not gc.getPlayer(cyCity.getOriginalOwner()).isAlive():
 
Two things to consider:

1. When reviving a dead Civ you pretty much have to give it some Techs in order for it to be... relevant. In this context the units spawned will be the same unit type than the current conscription unit for the city owner. So for it to make any sense you pretty much need to give all Techs that the current city owner has to the revived Civ. :eek:

2. The probability change code is no good but its there because I tried to stay as close as possible to the request that prompted the mod. You probably wanna have some other way of checking for rebel cities. (See the last "lesson" in the tutorial for why the current setup isn't a good one.)

I'd actually wanna suggest something slightly different: Enable rebirth only after the discovery of Nationalism (like in RFC). To have a rising probability you could check how many Civs currently have the Tech and calculate the change from this. Like:
Code:
#@ constants
iNumPlayers = gc.getMAX_CIV_PLAYERS()
eNationalism = cg.getInfoTypeForString("TECH_NATIONALISM")

#@ checkCity()
iNumNationalism = cyGame.countKnownTechNumTeams(eNationalism)
if iNumNationalism = 0: return False
fraction = float(iNumNationalism) / iNumPlayers
iRebelsProb = int(fraction * 20)
iRandNum = cyGame.getSorenRandNum(100, "rebels")
if iRandNum <= iRebelsProb:
This would give you a 0-20% chance depending on how many Civs have discovered Nationalism. As an example.

If you put this code at the top of the checkCity() function you get better performance also, as the rest of the code isn't executed until the advent of Nationalism. And since the probability will always be less than 50% it makes sense to make the random check first, before calculating any other values or performing any other checks against these.
 
The variable iOriginalOwner is a integer value. While the isAlive() method belongs to the CyPlayer class (look in the API). So what you are doing is that you're invoking a class method on a integer value, like this:
Code:
5.isAlive()
The integer value 5 has no method called isAlive() so you get a attribute error. In fact, integer values have no methods available to them.

Instead, what you need is a CyPlayer instance to use with the isAlive() method. The iOriginalOwner value corresponds to the ID number of the player, and the way to fetch a reference to its associated CyPlayer instance is:
Code:
gc.getPlayer(iOriginalPlayer)
So this is your code, then:
Code:
iOriginalOwner = cyCity.getOriginalOwner()
pOriginalOwner = gc.getPlayer(iOriginalOwner)
bOriginalOwnerAlive = pOriginalOwner.isAlive()
if bOriginalOwnerAlive == False:
Good luck with the rest of the mod. :goodjob: (One tips would be to pass on either the iOriginalOwner or pOriginalOwner variable with the return command instead of the value True, so that you don't have to fetch the value all over again.)

I knew it was something (relatively) simple. It is now working. Thank you, sir, maam or it. :hatsoff:

Two things to consider:

1. When reviving a dead Civ you pretty much have to give it some Techs in order for it to be... relevant. In this context the units spawned will be the same unit type than the current conscription unit for the city owner. So for it to make any sense you pretty much need to give all Techs that the current city owner has to the revived Civ. :eek:

2. The probability change code is no good but its there because I tried to stay as close as possible to the request that prompted the mod. You probably wanna have some other way of checking for rebel cities. (See the last "lesson" in the tutorial for why the current setup isn't a good one.)

Yeah, im already planning on giving the respawned civ the techs of the cities owner.

Re: probability: I was already planning on changing it to something else.

I am also thinking about limiting a civs respawn zone; I might make it so that the civ will only respawn within X tiles of its starting plot (that is if getStartingPlot in CyPlayer does what i think it does).
 
I am also thinking about limiting a civs respawn zone; I might make it so that the civ will only respawn within X tiles of its starting plot (that is if getStartingPlot in CyPlayer does what i think it does).
That should work. Just holler if you need any further code for this.
Spoiler :
You basically have to fetch the X and Y coordinates from the CyPlot instance (with CyPlot.getY() and CyPlot.getX()) and compare those values to whatever limit you set. But remember that wrapped maps will produce somewhat different outcomes, as the X/Y coordinates start over from zero once your count reaches the edge of the map. So you need some additional code to take care of this.
 
And if you need code for cloning the Techs...
Spoiler :
Code:
iNumTechs = gc.getNumTechInfos()

def cloneTechs(eFrom, eTo):
	pFromTeam = gc.getTeam(gc.getPlayer(eFrom).getTeam())
	pToTeam = gc.getTeam(gc.getPlayer(eTo).getTeam())
	for eTech in range(iNumTechs):
		if pFromTeam.isHasTech(eTech):
			pToTeam.setHasTech(eTech, True, eTo, False, False)
 
Just wondering, if use a print command for debugging purposes, where exactly does it get printed to?
 
Oh Ok. Thats what i thought but i didnt see my print thing there. After going through more carefully i found it.

Incidentally, how can i make a loop to get all dead civs? I tried using this:
Code:
	for i in xrange (gc.getMAX_CIV_PLAYERS ()):
		print"second"
		pPlayer = gc.getPlayer(i)
		print "third"
		if not(pPlayer.isAlive()) :
			print "fourth"
			sCiv = pPlayer.getCivilizationShortDescription(0)
			sDeadCiv = "var.i"+sCiv
			del var.lDeadCivs[:]
			var.lDeadCivs.append(sDeadCiv)
but it did not work. I suspect it has something to do with the lines above and below "third".
 
Var stands for... Variables.py (which I imported as var). Its a file I made to house variables I would need repeatedly. As for the loop-clearingness.. :wallbash: I am an idiot.
 
Oh Ok. Thats what i thought but i didnt see my print thing there. After going through more carefully i found it.

Incidentally, how can i make a loop to get all dead civs? I tried using this:
Code:
	for i in xrange (gc.getMAX_CIV_PLAYERS ()):
		print"second"
		pPlayer = gc.getPlayer(i)
		print "third"
		if not(pPlayer.isAlive()) :
			print "fourth"
			[COLOR="Red"]sCiv = pPlayer.getCivilizationShortDescription(0)[/COLOR]
			sDeadCiv = "var.i"+sCiv
			del var.lDeadCivs[:]
			var.lDeadCivs.append(sDeadCiv)
but it did not work. I suspect it has something to do with the lines above and below "third".

I get a RuntimeError: unidentifiable C++ exception at the redded line.
 
I have no idea what the C++ exception is about but perhaps you could post the whole traceback?

Other than that, the Variables.py idea is way off. If you're trying to mirror what is happening in the RFC mod you should note that those aren't variables as such in the Consts module, but rather constants. So the values associated with those variables/names never change while the game is running.

What you want is rather global variables in your own module. You could still define the default values (like [] for lDeadCivs) in another module (like Variables), but you wanna import those to your own module's global name-space:
Code:
from Variables import lDeadCivs
or just:
Code:
from Variables import *
Now you access the variable(s) without the var. prefix. But in order to use the variable inside functions you need to make it a global variables:
Code:
global lDeadCivs
This is what your helper function could look like:
Code:
def getDeadCivList():
	lDeadCivs = []
	for ePlayer in range(gc.getMAX_CIV_PLAYERS):
		pPlayer = gc.getPlayer(ePlayer)
		if not pPlayer.isAlive():
			lDeadCivs.append(ePlayer)
	return lDeadCivs
It returns a list of PlayerTypes values (expressed as integer values) - not CyPlayer instances or name strings or anything else. Is this what you need?

You don't need a global variable or anything defined in the Variables module for this however. You simply go:
Code:
lDeadCivs = getDeadCivsList()
Furthermore, you clearly don't know what you're doing with all those parenthesis in every single if statement. Maybe you shouldn't use them, at all?

If you do add those, try to figure out why you're doing it. Otherwise it will only serve to confuse you - and others - in the end. And just because others are using parenthesis is no reason for you to do it also. There will be times when you should use a set of parenthesis to separate some expression from some others, but I haven't seen you do anything useful like that with parenthesis yet... :rolleyes: And if you just add them with no real reason you probably won't even realize when you need to use them.
 
Furthermore, you clearly don't know what you're doing with all those parenthesis in every single if statement. Maybe you shouldn't use them, at all?

If you do add those, try to figure out why you're doing it. Otherwise it will only serve to confuse you - and others - in the end. And just because others are using parenthesis is no reason for you to do it also. There will be times when you should use a set of parenthesis to separate some expression from some others, but I haven't seen you do anything useful like that with parenthesis yet... :rolleyes: And if you just add them with no real reason you probably won't even realize when you need to use them.

It's probably a C++ habit (in C++ all if conditionals must be in parenthesis).
If it's the case, (s)he won't have any problems using them when needed.
 
I get a RuntimeError: unidentifiable C++ exception at the redded line.

The problem here is, that if the civ is not alive, it does not mean that it ever has been alive.
You've maybe hit an empty slot, where never a civ has been asigned to.
There's the check isEverAlive, not sure what it does, but it's worth a try to put it in additionally to the NOT isAlive statement.
 
The_J is probably on to something here. I'll have to make a mental note of this myself...
 
The problem here is, that if the civ is not alive, it does not mean that it ever has been alive.
You've maybe hit an empty slot, where never a civ has been asigned to.
There's the check isEverAlive, not sure what it does, but it's worth a try to put it in additionally to the NOT isAlive statement.

Yep, that fixed it. And I think Ive more or less finished the most difficult part of my project, so it -should- be smooth sailing from here. Of course, I have been wrong before.
 
Alright, I managed to get my project working. I was a little wrong about the rest being smooth, but I managed to figure those parts out myself.

So the final question is, how can I time my function to see how long it takes to execute?
 
So the final question is, how can I time my function to see how long it takes to execute?
Assuming that you have this line in your CvEventManager.py file:
Code:
		Rebels.checkTurn()
Then you should be able to add this:
Code:
		import timer
		timer = time.clock()
		Rebels.checkTurn()
		print("Rebels mod timer: ", (time.clock() - timer) * 1000, " ms")
This should print out the time in the Python Debug log on every game turn.
 
Top Bottom