GP Tech Lightbulb Idea

I don't know if it's true or not. The function that gives techs to a team has a "bFirst" parameter, and I assume that if you set it to False you won't found a religion or get a great person. This function is what WorldBuilder uses. So an easy test would be to open up WB, give yourself Theology, and see if you found Christianity.

That being said, I still prefer doing it the way Impaler suggests (see my original post). The info is available via TechInfo and could be put into a data structure the first time the screen is opened and used from then on. Let me see what I can come up with. Creating the lists of preferences like those posted in the GPTechPref thread should be pretty easy. It's what to do with that list and the current known techs that is a little trickier.
 
I've completed the first part. The following code builds a data structure that holds the various GP tech preferenes and the tech prerequisites. This should be enough for you to build the screen.

To use it, import it and create it.

Code:
import TechPrefs # or whatever you save the file as

tp = TechPrefs.TechPrefs()

Next, tell it to remove all known techs.

Code:
tp.removeKnownTechs()

With that done, you can get the next tech for each GP type. Here's an example for a Great Scientist. *

Code:
pTech = tp.getNextResearchableFlavorTech(TechPrefs.FLAVOR_SCIENCE)
pInfo = pTech.getInfo()
# now use pInfo to get button and place on screen

Use the following method to get all the techs the player can research:

Code:
sCanResearch = tp.getResearchableTechs() # a set
for pTech in sCanResearch:
    # use like pTech above

Finally, to test which tech a GP would give once another tech has been researched, use this:

Code:
for pTech in sCanResearch:
    sTechs = set()
    sTechs.add(pTech)
    pNextTech = tp.getNextResearchableWithFlavorTech(iFlavor, sTechs)
    # use as above

The code below has some samples for using the other methods in it. I tried to put in comments as much as possible, but I know some parts won't be entirely obvious.

Basically, the Tech class encapsulates a single technology along with references to which techs it requires and which it leads to. The TechPrefs class holds all the Techs and builds the lists of GP prefs. You'll access Techs using the single TechPrefs you create in interfaceScreen().

Let me know if there are other methods I could add to be helpful.

* Note that the great people types are hard coded, so you'll need to use the constants defined at the top of my file. Actually, CyUnitType does have a getFlavorValue(int) that returns 1 if the passed in constant is its flavor type. So you should get this:

Code:
pUnitInfo = gc.getUnitUnit(gc.getInfoTypeForString("UNIT_SCIENTIST"))
if (pUnitInfo.getFlavorValue(TechPrefs.FLAVOR_SCIENCE) == 1):
    # should be true

Anyway, here's the code. Enjoy!

Code:
## TechPrefs
## Builds ordered lists of techs as preferred by the various Great People
## Copyright 2007 EmperorFool @ civfanatics.com

from CvPythonExtensions import *

gc = CyGlobalContext()

# see gc.getNumFlavorTypes() and gc.getFlavorTypes()
# not available via gc.getInfoTypeForString(), thus the hard-coding here :(
FLAVOR_MILITARY = 0
FLAVOR_RELIGION = 1
FLAVOR_PRODUCTION = 2
FLAVOR_GOLD = 3
FLAVOR_SCIENCE = 4
FLAVOR_CULTURE = 5
FLAVOR_GROWTH = 6
FLAVOR_ESPIONAGE = 7

NUM_FLAVORS = gc.getNumFlavorTypes()
FLAVORS = [ "Military", "Religion", "Production",
			"Gold", "Science", "Culture",
			"Growth", "Espionage" ]

NUM_TECHS = gc.getNumTechInfos()
NUM_AND_PREREQS = gc.getDefineINT("NUM_AND_TECH_PREREQS")
NUM_OR_PREREQS = gc.getDefineINT("NUM_OR_TECH_PREREQS")

class TechPrefs:

	def __init__(self):
		self.mTechs = {}
		self.lTechsByFlavor = []
		for iFlavor in range(NUM_FLAVORS):
			self.lTechsByFlavor.append([])
		
		# build a list of all techs and a list of techs for each flavor
		for iTech in range(NUM_TECHS):
			pTechInfo = gc.getTechInfo(iTech)
			pTech = self.getTech(iTech)
			for iFlavor in range(NUM_FLAVORS):
				iFlavorValue = pTechInfo.getFlavorValue(iFlavor)
				if (iFlavorValue > 0):
					pTech.setFlavorValue(iFlavor, iFlavorValue)
					self.lTechsByFlavor[iFlavor].append((-iFlavorValue, iTech, pTech))
					bHasFlavor = True
			
			# hook up prereq techs
			for i in range(NUM_AND_PREREQS):
				pPrereqTech = pTechInfo.getPrereqAndTechs(i)
				if (pPrereqTech != -1):
					pTech.addAndPrereq(self.getTech(pPrereqTech))
			for i in range(NUM_OR_PREREQS):
				pPrereqTech = pTechInfo.getPrereqOrTechs(i)
				if (pPrereqTech != -1):
					pTech.addOrPrereq(self.getTech(pPrereqTech))
		
		# sort each flavor's list of techs by decreasing preference: reverse flavor value, tech number
		for iFlavor in range(NUM_FLAVORS):
			lTechs = self.lTechsByFlavor[iFlavor]
##			print "%s has %d techs" % (FLAVORS[iFlavor], len(lTechs))
			lTechs.sort()
			self.lTechsByFlavor[iFlavor] = [ pTech for _, _, pTech in lTechs ]
		
##		print "---- Techs with Flavor ----"
##		for pTech in self.mTechs.values():
##			print "%2d: %s" % (pTech.iTech, pTech.getName())
##		for iFlavor in range(NUM_FLAVORS):
##			print "---- %d Techs with Flavor %s ----" % (len(self.lTechsByFlavor[iFlavor]), FLAVORS[iFlavor])
##			for pTech in self.lTechsByFlavor[iFlavor]:
##				print "%2d-%2d: %s" % (pTech.getFlavorValue(iFlavor), pTech.iTech, pTech.getName())
		
##		pOptics = self.getTech(gc.getInfoTypeForString("TECH_OPTICS"))
##		print pOptics
##		pAstronomy = self.getTech(gc.getInfoTypeForString("TECH_ASTRONOMY"))
##		print pAstronomy
##		pPhysics = self.getTech(gc.getInfoTypeForString("TECH_PHYSICS"))
##		print pPhysics
##		pAstronomy.removeFromTree()
##		print pOptics
##		print pAstronomy
##		print pPhysics
##		
##		self.removeKnownTechs()
##		print self.getNextResearchableFlavorTech(FLAVOR_RELIGION)
##		print self.getNextResearchableFlavorTech(FLAVOR_SCIENCE)
##		print self.getNextResearchableFlavorTech(FLAVOR_GOLD)
##		techs = set()
##		techs.add(self.getTechStr("TECH_THE_WHEEL"))
##		techs.add(self.getTechStr("TECH_MYSTICISM"))
##		print self.getNextResearchableWithFlavorTech(FLAVOR_RELIGION, techs)
##		print self.getNextResearchableWithFlavorTech(FLAVOR_SCIENCE, techs)
##		print self.getNextResearchableWithFlavorTech(FLAVOR_GOLD, techs)
##		techs.add(self.getTechStr("TECH_IRON_WORKING"))
##		techs.add(self.getTechStr("TECH_IRON_POTTERY"))
##		techs.add(self.getTechStr("TECH_MEDITATION"))
##		print self.getNextResearchableWithFlavorTech(FLAVOR_RELIGION, techs)
##		print self.getNextResearchableWithFlavorTech(FLAVOR_SCIENCE, techs)
##		print self.getNextResearchableWithFlavorTech(FLAVOR_GOLD, techs)#


	def getTech(self, iTech):
		if iTech not in self.mTechs:
			self.mTechs[iTech] = Tech(iTech)
		return self.mTechs[iTech]

	def getTechStr(self, sTech):
		iTech = gc.getInfoTypeForString(sTech)
		if iTech in self.mTechs:
			return self.mTechs[iTech]
		return None

	def removeTech(self, iTech):
		"""Removes the given tech, usually because it has been researched."""
		if (iTech in self.mTechs):
			pTech = self.mTechs[iTech]
			del self.mTechs[iTech]
			for iFlavor in range(NUM_FLAVORS):
				if pTech in self.lTechsByFlavor[iFlavor]:
					self.lTechsByFlavor[iFlavor].remove(pTech)
			pTech.removeFromTree()

	def removeKnownTechs(self):
		"""Removes the techs known to the current team."""
		pTeam = gc.getTeam(gc.getActivePlayer().getTeam())
		for iTech in range(NUM_TECHS):
			if (pTeam.isHasTech(iTech)):
				self.removeTech(iTech)


	def getResearchableTechs(self):
		"""Returns a set of all techs that can be researched now."""
		sCan = set()
		for pTech in self.mTechs.values():
			if (pTech.canResearch()):
				sCan.add(pTech)
		return sCan

	def getResearchableWithTechs(self, sTechs):
		"""Returns a set of all techs that can be researched once the given techs have been researched."""
		sCan = set()
		for pTech in self.mTechs.values():
			if (pTech not in sTechs and pTech.canResearchWith(sTechs)):
				sCan.add(pTech)
		return sCan


	def getNextFlavorTech(self, iFlavor):
		"""Returns the next tech in the flavor's list or None."""
		if (len(self.lTechsByFlavor[iFlavor]) > 0):
			return self.lTechsByFlavor[iFlavor][0]
		else:
			return None

	def getNextResearchableFlavorTech(self, iFlavor):
		"""Returns the next tech in the flavor's list that is researchable now or None."""
		for pTech in self.lTechsByFlavor[iFlavor]:
			if (pTech.canResearch()):
				return pTech
		return None

	def getNextResearchableWithFlavorTech(self, iFlavor, sTechs):
		"""Returns the next tech in the flavor's list that is researchable once the given techs are researched or None."""
		for pTech in self.lTechsByFlavor[iFlavor]:
			if (pTech not in sTechs and pTech.canResearchWith(sTechs)):
				return pTech
		return None


	def printFlavorTechs(self, iFlavor):
		"""Prints the techs in the flavor's list."""
		for pTech in self.lTechsByFlavor[iFlavor]:
			print pTech

	def printResearchableFlavorTechs(self, iFlavor):
		"""Prints the techs in the flavor's list."""
		for pTech in self.lTechsByFlavor[iFlavor]:
			if pTech.canResearch():
				print pTech

	def printResearchableWithFlavorTechs(self, iFlavor, sTechs):
		"""Prints the techs in the flavor's list."""
		for pTech in self.lTechsByFlavor[iFlavor]:
			if pTech.canResearchWith(sTechs):
				print pTech


class Tech:
	
	def __init__(self, iTech):
		self.iTech = iTech
		self.lFlavorValues = [0] * NUM_FLAVORS
		self.lFlavorPref = [0] * NUM_FLAVORS
		self.sAndPrereqs = set()
		self.sOrPrereqs = set()
		self.iNumAndPrereqs = 0
		self.iNumOrPrereqs = 0
		self.sLeadsTo = set()

	def getID(self):
		return self.iTech

	def getInfo(self):
		return gc.getTechInfo(self.iTech)

	def getName(self):
		return self.getInfo().getDescription()


	def setFlavorValue(self, iFlavor, iValue):
		self.lFlavorValues[iFlavor] = iValue

	def getFlavorValue(self, iFlavor):
		return self.lFlavorValues[iFlavor]

	def setFlavorPref(self, iFlavor, iPref):
		self.lFlavorPref[iFlavor] = iPref

	def getFlavorPref(self, iFlavor):
		return self.lFlavorPref[iFlavor]


	def addAndPrereq(self, pTech):
		if pTech not in self.sAndPrereqs:
			self.iNumAndPrereqs += 1
			self.sAndPrereqs.add(pTech)
			pTech.addLeadsTo(self)

	def addOrPrereq(self, pTech):
		if pTech not in self.sOrPrereqs:
			self.iNumOrPrereqs += 1
			self.sOrPrereqs.add(pTech)
			pTech.addLeadsTo(self)

	def removePrereq(self, pTech):
		self.sAndPrereqs.discard(pTech)
		self.sOrPrereqs.discard(pTech)


	def getNumTechsNeeded(self):
		"""Returns the minimum number of techs that must be researched to be able to research this tech."""
		return self.getNumAndTechsNeeded() + self.getNumOrTechsNeeded()

	def getNumAndTechsNeeded(self):
		return len(self.sAndPrereqs)

	def getNumOrTechsNeeded(self):
		if (len(self.sOrPrereqs) == 0 or len(self.sOrPrereqs) < self.iNumOrPrereqs):
			return 0
		return 1

	def getTechsNeeded(self):
		"""
		Returns two sets of techs that are needed to make this tech researchable.
		
		The first set are all missing And prereqs.
		The second set is all Or prereqs or an empty set if at least one has already been researched.
		"""
		andSet = self.sAndPrereqs.copy()
		if (len(self.sOrPrereqs) == 0 or len(self.sOrPrereqs) < self.iNumOrPrereqs):
			orSet = set()
		else:
			orSet = self.sOrPrereqs.copy()
		return andSet, orSet

	def canResearch(self):
		"""Returns True if this tech has met all And prereqs and at least one Or prereq."""
		return self.getNumTechsNeeded() == 0

	def canResearchWith(self, sTechs):
		"""Returns True if this tech can be researched once the given tech(s) have been researched."""
		if (len(sTechs) == 0):
			return self.canResearch()
		sAnds = self.sAndPrereqs.difference(sTechs)
		sOrs = self.sOrPrereqs.difference(sTechs)
		return (len(sOrs) == 0 or len(sOrs) < self.iNumOrPrereqs) and len(sAnds) == 0


	def addLeadsTo(self, pTech):
		self.sLeadsTo.add(pTech)

	def removeLeadsTo(self, pTech):
		self.sLeadsTo.discard(pTech)

	def removeFromTree(self):
		"""Removes this tech from the prereq lists of the techs it leads to and the leads to lists of its prereqs."""
		for pTech in self.sAndPrereqs:
			pTech.removeLeadsTo(self)
		for pTech in self.sOrPrereqs:
			pTech.removeLeadsTo(self)
		for pTech in self.sLeadsTo:
			pTech.removePrereq(self)


	def __str__(self):
		str = self.getName()
		bFirst = True
		if (len(self.sAndPrereqs) > 0 or len(self.sOrPrereqs) > 0):
			str += " requires "
		if (len(self.sAndPrereqs) > 0):
			for pTech in self.sAndPrereqs:
				if bFirst:
					bFirst = False
				else:
					str += " and "
				str += pTech.getName()
		if (len(self.sOrPrereqs) > 0):
			if not bFirst:
				str += " and "
				bFirst = True
			for pTech in self.sOrPrereqs:
				if bFirst:
					bFirst = False
				else:
					str += " or "
				str += pTech.getName()
		if (len(self.sLeadsTo) > 0):
			str += ", leads to "
			bFirst = True
			for pTech in self.sLeadsTo:
				if bFirst:
					bFirst = False
				else:
					str += ", "
				str += pTech.getName()
		return str
 
An extension of your idea for the screen -- why make it too easy? ;) -- would be to turn each of the tech buttons along the top into a toggle button (CheckBoxGFC).

As you have described it now, for a given GP and tech it shows the tech that GP will pop if the column's tech is researched. The extension is that it would also assume all other "checked" techs along the top are researched.

Say you have possible techs A, B and C with techs X, Y and Z listed below them. X is what the GP will pop if you research A next.

I'm suggesting allowing the user to click on (and thus check) tech B at the top. Now X shows what the GP will pop if you research A and B and Z shows what the GP will pop if you research B and C. Y still only assumes you research X because neither A nor C are checked. Does that make sense?

In any case, the code I posted above will support this pretty easily. Both TechPrefs.getResearchableWithTechs(set) and TechPrefs.getNextResearchableWithFlavorTechs(int, set) accept a set of Techs rather than just a single tech (though you can pass in a set containing a single Tech as I demonstrated above).

This is obviously a bit trickier in the interface, but I think entirely doable once you get the original versional working. I'm really excited to see how this pans out.

Another idea would be to add more tech columns than you can currently research. You can find all the techs you could research if you were to research all the techs you can research now. Add those as columns and then grey them out unless you check the techs that are prereqs.

So for example if you haven't researched Mysticism yet, both Meditation and Polytheism would be additional disabled columns. If you check Mystycism at the top, those two columns enable and act as normal columns. You could theoretically put the whole list of techs on it, but that's probably too overwhelming.
 
What about a screen with GPs and their associated lightbulb techs down the right and ALL techs to the left. Yellow highlighted techs are techs that you already have (and these drive the lightbulb'ed techs) and (some other colour highlight - red?) are techs that you can research. You can click on the red techs - turning them orange (mixture or yellow and red) and this will update the GPs lightbulb techs. Non coloured techs are techs that you cannot research yet. Clicking on a red tech updates some other techs to red too.

You could use large icons for the GPs and lightbulb techs and small icons for the grid of techs. If you have seen the tech grid in WB, then that is sort of what I am talking about.
 
I've completed the first part. The following code builds a data structure that holds the various GP tech preferenes and the tech prerequisites. This should be enough for you to build the screen.
Thx - screen building is stuff that I haven't done - will have to dig into another similar screen and steal the code.
 
Here's another thought. On the existing tech screen you can already select multiple techs to research. How about either tagging the techs that the GPs will pop or putting the icons on the bottom (or somewhere else) of the screen?

It might be information overload, but it also might fit nicely. I'll have to load up a game later and see if that would even fit. Some techs will have multiple GPs on them, so it might be a tight squeeze. But it would probably be a lot easier than designing and coding a whole new screen.
 
It might be information overload, but it also might fit nicely. I'll have to load up a game later and see if that would even fit. Some techs will have multiple GPs on them, so it might be a tight squeeze. But it would probably be a lot easier than designing and coding a whole new screen.

I'm thinking we add a tab to the current tech screen. F4 has multiple tabs, why not F6!
 
I think a full tab would be great as you could show much more information as your sample screenshot shows. Until then, here's what I came up with. It adds a row of images at the bottom of the normal tech chooser screen, one block of three per GP type (5 types): GP image, tech the GP will pop now, and tech the GP would pop if you researched all the techs in your queue. The latter image updates as you click/shift-click on techs in the tree above.

Here is the ZIP containing the two files. TechPrefs.py goes into CustomAssets/Python and CvTechChooser.py goes into CustomAssets/Python/screens. It should give you a good sample of how to use TechPrefs in your screen and how to add the images.
 
I'm trying to do a cut of .04 of the unaltered that has some stuff added in.

This mod, being one of them... But what happens when I add the two files in
Is before Dawn Of Man, the Tech Screen Pops, and once its closed Dawn of Man is there and passing through dawn of man gives us a blank interface.
 
Yes something funky is going on for me as well. I made some minor changes (locally only -- not in 0.4 of BUG or what I have posted above) to deal with Advanced Start and can't seem to get it to work at all. :(

Basically, it appears that all the screens are built when the game starts and then hidden. The TechChooser is throwing an exception, halting this process. The version I have posted here, however, was working when I posted it. Are you opening a game or starting a new game? If the latter, are you doing AdvStart?

Finally, can you try this mod with just the two files above? I'll do the same on my end. I've been :wallbash: for a while now with no luck. :(
 
I just tested it with only the above two files in my CustomAssets, after clearing the cache, and it's borked. It was working so well when I uploaded it, I don't see how the same code could be failing now. :cry:

The whole import of TechPrefs is failing. None of its global variables are set when the module loads. I don't see how that's possible since the GC exists at that time. If I remove the TechChooser and import the module in the console, it works just fine. Any ideas?
 
I have had trouble like this before. Somtimes the def __init__ function is called before the SDK has exposed everything to python and code like this "NUM_FLAVORS = gc.getNumFlavorTypes()" which is outside of the class can also cause this. If this is the problem you're having it could explain why it works from the console (The game has completely loaded).
 
I found the error. L A M E is all I can say about this one. CyGlobalContext.getFlavorTypes() apparently halts execution if it's called where I have it in TechPrefs. No exception is generated -- the module just stops executing.

In TechPrefs replace it with 8

Code:
NUM_FLAVORS = 8

near the top of the file, and everything works. WTFever. Notice that gc.getNumTechInfos() works just fine a few lines down. :crazyeye:

I'll update the archive above once I've tested my changes to handle Advanced Start.
 
code like this "NUM_FLAVORS = gc.getNumFlavorTypes()" which is outside of the class can also cause this.

Damn, I wish I had posted this an hour ago. I'm actually rather annoyed that I changed the file and then posted without testing the change. I'm not usually that careless. Apologies, Ket, for spinning you in circles.

The truly odd part is that the flavors are hard-coded (not defined in XML as far as I can tell) and not available via getInfoTypeForString(). Ah, I bet they are built from the techs, and the techs aren't processed until you call getNumTechInfos(). Total assumption, but good to know.

Normally I'd avoid executing code at the global level like this -- let alone use global variables -- but all the Firaxis code does it. Anyway, thanks Zebra9 for the info.
 
I've uploaded version 0.2 of this mod, working so far as I can tell.

It handles Advanced Start correctly and also displays a "N/A" button (like no state religion) when a GP can't research any techs.
 
Flavors are defined in XML on the GlobalTypes.xml directly under the XML directory
 
Ah okay, that explains why they're not available through getInfoTypeForString. That and they dont have a FlavorInfo class I suppose. Thanks!
 
Can you post the unadjusted techchooser file so that I can extract your changes and put it into Vanilla and Warlords?
 
Back
Top Bottom