Claim Flags from Dungeon Adventure

Lutefisk Mafia

Inscrutable Enforcer
Joined
Jun 15, 2007
Messages
544
Location
Minnesota
Want to be able to claim territory without having to build a city? Well now you can! Introducing the Lutefisk Mafia Claim Flags for FfH2!

Claim flags are a new unit that creates a zone of persistent culture around them, just like a city. This makes it possible to link far away improved resources into your civ, or to just claim territory to keep other civs out.

Here is a video clip of a claim flag being used:

Claim function = http://www.youtube.com/watch?v=0OI1BAPtwDA

The method and code examples are discussed in the Dungeon Adventure mod thread here: http://forums.civfanatics.com/showpost.php?p=6915331&postcount=502

And also reproduced in full below:

************

As I said earlier, I am going to release some of the individual items I came up with for this mod. My hope is that they will benefit the community in some way -- since it will be a while before a DA rebuild gets done.

And so, allow me to introduce "DA mod giblet 1: Claim flags."

By request, I am posting this first. Claim flags create a zone of persistent culture around them, without needing a city! This is great for claiming, and being able to get the benefits of, improved resources outside of ones normal cultural boundaries.

I created claim flags by modding the starbase code from Final Frontier. The essentials are:

1. A new unit, which I called "STARBASE_I" just to avoid more edits in the modded python code.
2. Changes and additions to the CvEventManager.py file.
3. Some method of getting the new unit into the game. In Final Frontier, starbases are built by constructor ships. In my mod, the ability to plant claim flags is restricted to a special unit called a "Dungeoneer," and required researching a special tech. The basis of this is a new spell called "stake claim." More on that later.

First, the new unit. I literally just ripped the starbase unitclass and unit XML from FF, as well as the unit arts. You could get away with any new unit actually, so long as you call it "STARBASE_I" or whatever name you use consistently in the modded python. Because I wanted the look of a planted flag, I just kept the starbase art, but scaled the NIF for the actual unit down to zero. That makes the unit itself invisible, but still shows the player flag. (Sneaky, huh?) The flag is what is important to see anyway.

Second, you must change CvEventManager.py file. Find the onEndGameTurn def and change the code block to match the stuff below. Also, add in all of the "starbase" related defs. Looks like this:

Code:
	def onEndGameTurn(self, argsList):
		'Called at the end of the end of each turn'
		
		iGameTurn = argsList[0]
		self.updateAllStarbases()

	def doMakeStarbase(self, iPlayer, iX, iY):
		
		pPlayer = gc.getPlayer(iPlayer)
		pPlot = CyMap().plot(iX, iY)
		# Create Starbase Unit
		iUnitStarbaseID = CvUtil.findInfoTypeNum(gc.getUnitInfo,gc.getNumUnitInfos(),'UNIT_STARBASE_I')
		pPlayer.initUnit(iUnitStarbaseID, iX, iY, UnitAITypes.UNITAI_ATTACK, DirectionTypes.NO_DIRECTION)
		self.updateStarbaseCulture(iPlayer, iX, iY)
		
	def updateStarbaseCulture(self, iPlayer, iX, iY):
		
		# Create culture around unit
		for iXLoop in range(iX-2, iX+3):
			for iYLoop in range(iY-2, iY+3):
				iActiveX = iXLoop
				iActiveY = iYLoop
				if (iActiveX < 0):
					iActiveX = CyMap().getGridWidth() + iActiveX
				if (iActiveY < 0):
					iActiveY = CyMap().getGridHeight() + iActiveY
				pLoopPlot = CyMap().plot(iActiveX, iActiveY)
				if (pLoopPlot.getOwner() == -1):
					pLoopPlot.setOwner(iPlayer)
					pLoopPlot.changeCulture(iPlayer, 10, False)

	def updateAllStarbases(self):
		
		# Update Starbase culture
		iUnitStarbaseID = CvUtil.findInfoTypeNum(gc.getUnitInfo,gc.getNumUnitInfos(),'UNIT_STARBASE_I')
		
		# List made to preserve culture of units built first
		aaiStarbaseList = []
		
		for iPlayerLoop in range(gc.getMAX_CIV_PLAYERS()):
			pPlayer = gc.getPlayer(iPlayerLoop)
			pTeam = gc.getTeam(pPlayer.getTeam())
			pyPlayer = PyPlayer(iPlayerLoop)
			apUnitList = pyPlayer.getUnitList()
			for pUnitLoop in apUnitList:
				if (pUnitLoop.getUnitType() == iUnitStarbaseID):
					aaiStarbaseList.append([pUnitLoop.getGameTurnCreated(), iPlayerLoop, pUnitLoop.getX(), pUnitLoop.getY()])
			
		if (len(aaiStarbaseList) > 0):
			
			# Make order such that units built first get culture preference
			aaiStarbaseList.sort()
#			aaiStarbaseList.reverse()
			for iStarbaseLoop in range(len(aaiStarbaseList)):
				self.updateStarbaseCulture(aaiStarbaseList[iStarbaseLoop][1], aaiStarbaseList[iStarbaseLoop][2], aaiStarbaseList[iStarbaseLoop][3])

Finally, you will need a way to get the unit into the game. Keep in mind that the unit must be DOMAIN_IMMOBILE or things will get buggy on you quick. That is because the python creates an array that keeps track of where flags were planted and when. Earlier flags take precedence over later ones, and this is tied to position. Having mobile flags makes things buggy.

So given that the flags must be immobile, you need a way get them into the game that creates them at the spot they are intended to claim.

I created a spell called "stake claim" (internally called "crumbs"). Oddly enough, simply treating it like a permanent summon of a flag unit didn't seem to work. At least I couldn't make that work, which is a shame because it would have been the easiest way.

Anyway, here is the spell infos xml:

Code:
        <SpellInfo>
            <Type>SPELL_CRUMBS</Type>
            <Description>TXT_KEY_SPELL_CLAIM</Description>
            <Civilopedia>TXT_KEY_SPELL_PLACEHOLDER_PEDIA</Civilopedia>
            <Strategy>NONE</Strategy>
            <Help>TXT_KEY_SPELL_CLAIM_HELP</Help>
            <PromotionPrereq1>NONE</PromotionPrereq1>
            <PromotionPrereq2>NONE</PromotionPrereq2>
            <UnitPrereq>NONE</UnitPrereq>
            <UnitClassPrereq>NONE</UnitClassPrereq>
            <UnitCombatPrereq>NONE</UnitCombatPrereq>
            <UnitInStackPrereq>NONE</UnitInStackPrereq>
            <CivilizationPrereq>NONE</CivilizationPrereq>
            <ReligionPrereq>NONE</ReligionPrereq>
            <StateReligionPrereq>NONE</StateReligionPrereq>
            <TechPrereq>TECH_CLAIM_STAKING</TechPrereq>
            <bAllowAI>1</bAllowAI>
            <bAdjacentToWaterOnly>0</bAdjacentToWaterOnly>
            <bCausesWar>0</bCausesWar>
            <bGlobal>0</bGlobal>
            <bInBordersOnly>0</bInBordersOnly>
            <bInCityOnly>0</bInCityOnly>
            <iAIWeight>0</iAIWeight>
            <bDisplayWhenDisabled>1</bDisplayWhenDisabled>
            <bHasCasted>1</bHasCasted>
            <bIgnoreHasCasted>0</bIgnoreHasCasted>
            <bResistable>0</bResistable>
            <iRange>0</iRange>
            <iResistModify>0</iResistModify>
            <iDamage>0</iDamage>
            <iDamageLimit>0</iDamageLimit>
            <DamageType>NONE</DamageType>
            <AddPromotionType1>NONE</AddPromotionType1>
            <AddPromotionType2>NONE</AddPromotionType2>
            <AddPromotionType3>NONE</AddPromotionType3>
            <RemovePromotionType1>NONE</RemovePromotionType1>
            <RemovePromotionType2>NONE</RemovePromotionType2>
            <RemovePromotionType3>NONE</RemovePromotionType3>
            <bBuffCasterOnly>0</bBuffCasterOnly>
            <ConvertUnitType>NONE</ConvertUnitType>
            <CreateBuildingType>NONE</CreateBuildingType>
            <CreateFeatureType>NONE</CreateFeatureType>
            <CreateImprovementType>NONE</CreateImprovementType>
            <SpreadReligion>NONE</SpreadReligion>
            <CreateUnitType>NONE</CreateUnitType>
            <iCreateUnitNum>0</iCreateUnitNum>
            <bPermanentUnitCreate>0</bPermanentUnitCreate>
            <CreateUnitPromotion>NONE</CreateUnitPromotion>
            <bImmuneTeam>0</bImmuneTeam>
            <bImmuneNeutral>0</bImmuneNeutral>
            <bImmuneEnemy>0</bImmuneEnemy>
            <bImmuneFlying>0</bImmuneFlying>
            <bImmuneNotAlive>0</bImmuneNotAlive>
            <bDispel>0</bDispel>
            <bPush>0</bPush>
            <bRemoveHasCasted>0</bRemoveHasCasted>
            <bSacrificeCaster>0</bSacrificeCaster>
            <iChangePopulation>0</iChangePopulation>
            <iCost>50</iCost>
            <iImmobileTurns>0</iImmobileTurns>
            <iMiscastChance>0</iMiscastChance>
            <PyMiscast></PyMiscast>
            <PyResult>spellCrumbs(pCaster)</PyResult>
            <PyRequirement>reqCrumbs(pCaster)</PyRequirement>
            <Effect>EFFECT_SPELL1</Effect>
            <Sound></Sound>
            <HotKey></HotKey>
            <bAltDown>0</bAltDown>
            <bShiftDown>0</bShiftDown>
            <bCtrlDown>0</bCtrlDown>
            <bGraphicalOnly>0</bGraphicalOnly>
            <iHotKeyPriority>0</iHotKeyPriority>
            <Button>Art/Interface/Buttons/techtree/fanaticism.dds</Button>
        </SpellInfo>

and here is the CvSpellInterface.py entry:

Code:
def reqCrumbs(caster):
	pPlot = caster.plot()
	if pPlot.getOwner() == caster.getOwner():
		return False
	if pPlot.isWater():
		return false
	if pPlot.isCity():
		return False
	return True

def spellCrumbs(caster):
	pPlayer = gc.getPlayer(caster.getOwner())
	newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNIT_STARBASE_I'), caster.getX(), caster.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
	newUnit.finishMoves()
	newUnit.setHasCasted(True)

Finally, I have posted a snippet of the CvEventManager.py code for people to download:

http://www.atomicgamer.com/file.php?id=70011

It wouldn't be too hard to change the code to run off of an improvement rather than a unit. You can also change it so that the cultural zone will not penetrate certain terrains or features. For example, in my DA mod, I made it so the claim flag culture would not penetrate dungeon walls, but just the halls and rooms.


Anyway, enjoy! Let me know if it works, or you have additional questions.
 
This is good, so it could be adapted so that you could build remote outposts and gain access to resources, it makes a no settler game more playable.

Awesome... I remember in Civ3 they had the ability of building resources outside of your cultural territory.
 
I would rather see this be an improvement rather than a unit. Could work with a unit that dies upon building it, for a cheaper settler of a sort. Would work nicely with the kurioates
 
I would rather see this be an improvement rather than a unit. Could work with a unit that dies upon building it, for a cheaper settler of a sort. Would work nicely with the kurioates

An easy way to implement this as an improvement would be to let each civ have a unique improvement version of the same thing. E.g., BANNOR_CLAIM, SHEAIM_CLAIM, etc. So you would need to add code to the onImprovementBuilt or other section that would check for the players civ and then place the appropriate civ specific version of the improvement when it is built or created. Then the claim flag code in the onGameEndTurn def could look for that unique improvement and correctly associate it with the owning civ for the purposes of generating the cultural zone. This would address the problem that currently, improvements do not have an "owner" other than whoever's cultural borders current contain the improvement.

You could probably reuse the doMakeStarbase def and insert a lot of "if the player is X, then make an X_CLAIM improvement" contingencies. The only downside of this is that you would need to create multiple versions of the improvement -- one for each potential civ.
 
Awesome! This is EXACTLY what I've been looking for! I've been waiting forever for someone to duplicate the Starbase mechanic so I could tweak it to make a Claim Mana Node spell... and here you post the code with barely any tweaking required! :)

Most excellent, thank you muchly. One question, though, for you more-python-inclined-than-I-am types (sorry if this is thread hijacking): How do you check the bonus class of a bonus on a tile in python? That way I can just take the claim code, throw a check for BONUSCLASS_MANA into the requirement, add a gold cost and a delay, and BOOM, my spell is complete. :)
 
@ MaxAstro:

If it were me, I would just do a direct check for each mana resource in the spell req python, with a positive result generating a "return true."

I mean, there's only so many types of mana, right? So something like:

Code:
def reqClaimMana(caster):
	pPlot = caster.plot()
	if pPlot.getBonusType() == gc.getInfoTypeForString('BONUS_MANA_AIR'):
		return True
	return False

But with an "if pPlot has X mana, return true" for each type of mana.

The other possibility would be to try:

if pPlot.getBonusClassType() == gc.getInfoTypeForString('BONUSCLASS_MANA'):

and see what happens. I don't know if that callout even exists.

Good luck! Let me know if it works for you.
 
do you think there would be an easyer way to do this with improvements rather than having a unique version for each one? i ask because i want to add the culture expanding code to the fortress tile improvement in warhammer...
 
Just an idle thought -

Do you suppose that it is the arrays in certain mods that are giving Vista fits?

Don't the arrays write data to a file somewhere? Otherwise, how could the array data be passed along with savegames?

Anyhow, if indeed arrays write and read from a dedicated file, it could be that the permissions on this dedicated file get screwed up. Since the file is created by the game and not from within the usual Vista UI, it may be creating them with default settings, which do not always play nicely with mods.

If anyone wanted to test this, it would help the community out a lot.
 
Arrays write to the same savefile. They just chunk out each element as a char set and write them individually (since it is of a fixed size this works fine for reloading as well).

Unless of course I am fuzzy on what an array is (often the case, I fit lingo to where I think it works until proven wrong).

Taking Affinity as an Array example:

pStream->Read(GC.getNumBonusInfos(), m_paiBonusAffinity);
&
pStream->Write(GC.getNumBonusInfos(), m_paiBonusAffinity);
 
Well, the claim flag code (based on FF starbase code) stores the flag data in an array. Among other things, it keeps track of "turn created" to help sort out which flags have precedence over claimed territory. Each Claim Flag unit has an individual entry in the array.

So, unless I am interpreting this incorrectly, the claim flags array is dynamic in both content and SIZE. That is, the file will grow larger as more claim flag units get built.

So the question becomes - does the claim flag array data get saved as a part of the regular savegame file? If not, where does it go?
 
Arrays are specifically static size in C++ from what I am understanding. Lists or Maps have to be used for a variable size. Had to research that stuff for my Slave/Master work recently, so I may have overlooked a method for variable length arrays.

The workaround was to create an INT value which tracks the size of the list, then do a simple loop to write the data, and again to read it.

Code:
    pStream->Read(&m_iNumSlaves);
    if (m_iNumSlaves != 0)
        {for (int iI = 0; iI < m_iNumSlaves; iI++)
            {int iID = 0;
            pStream->Read(&iID);
            m_pSlaveUnitList.push_back(iID);}}


    pStream->Write(m_iNumSlaves);
    if (m_iNumSlaves != 0)
        for (std::list<int>::const_iterator iter = m_pSlaveUnitList.begin(); iter != m_pSlaveUnitList.end(); ++iter)
            pStream->Write(*iter);

But I don't have any feedback on if this causes issues in Vista or not. How were you doing the Read/Write for your Flags? As I recall you were doing quite a bit in Python, and had to learn about "Pickles", which from what I gather is a data storage method. If that was the case, you are probably doing a seperate file somewhere/somehow. But if you use the SDK ->Read ->Write, then it all goes to the standard savefile.
 
My claim flags only use python and xml. (see 1st post in this thread).

Now, these may link up with something in the SDK that is present in the BTS SDK, but not vanilla Civ.

The "array" is created in the python code, written to and accessed from there. Although I notice that it is coded as a "list."

The list is created by doing this:

aaiStarbaseList = []

and new claim flags are added to the list:

aaiStarbaseList.append([pUnitLoop.getGameTurnCreated(), iPlayerLoop, pUnitLoop.getX(), pUnitLoop.getY()])
 
Ah, helps when I actually look at your code. No, you don't save anything at all with that code.

One item of note: oddly enough, while you account for being along the top or left of the map (if X < 0, Y < 0) you do not account for being along the bottom of right of it (X > width, y > width).

Anyway, all that you are doing is looping through every unit that the player owns and checking to see if they are a Starbase Type I. If they are, then you toss them into a completely new list (new list made every turn and on game load). Then you sort that list by the turn each unit was created (variable tracked already in the C++) and use that to set a priority for updating their culture values.
 
Thank you, that is very useful to know.

So, apparently, savegames already include "turn created" data for every unit currently in play?

Whoah. I have tons of ideas for this already.

...As in, "better find those eggs sooner than 10 turns after they were laid, Adventurers! Who knows what might hatch?" [insert evil chortle]

I think I will use this idea for my spawner units. Instead of having them be invisible units that constantly refresh the dungeon with new low-level units they could be...

...Visible units that run around laying eggs that hatch a certain number of turns after being laid. The Adventurers have an incentive to find and destroy these eggs before they hatch. Also, there would be a great deal of satisfaction derived from tracking down and destroying an egg-layer.

Shoot. This would be EASY. I think I'll post about this in the main DA thread.
 
Tons of nice things are tracked already in the code, and it's getting easier daily for me to make the game jump through hoops :) I'm hoping that sometime soon the AI movement decision process clicks in my brain so I can make the multiple layer maps a reality, then your DA will be a part of ever City Ruins, Burrow & Ancient Tower on the map ;)

Is most of your work Python only? I never got a chance to play around with what you had done thus far, but I imagine that much of the work I have done lately would be nicely lined up with what you are aiming at in DA. I do attempt to keep my code commented so that a novice can understand what is going on and develop an understanding of how to take things further. So if you don't do C++ yet, it may be worth peeking at my codebase for FF.

Or of course just coming up with awesome ideas and letting me have the joy of making the code work for it ;)
 
Is most of your work Python only? I never got a chance to play around with what you had done thus far, but I imagine that much of the work I have done lately would be nicely lined up with what you are aiming at in DA. I do attempt to keep my code commented so that a novice can understand what is going on and develop an understanding of how to take things further. So if you don't do C++ yet, it may be worth peeking at my codebase for FF.

I'd second that (though obviously I may be biased :D) - the work Xienwolf has done in the brief time he's had with the FF DLL has been amazing - there's probably more (and better implemented) features added since FF040 than there was in all the previous versions combined.

Better still - he's actually organized about it - with all added code being well marked and commented. What it basically adds is a lot more flexibility and options to what you can actually implement (especially with regards to promotions - a *lot* of new options there).
 
Top Bottom