Suggestions for how to create a lure?

Bad Player

Deity
Joined
Oct 31, 2005
Messages
3,534
Location
(Bris)Vegas!
I'd like to cause all units with the undead promotion to move towards a lure (either a fixed tile or an item). Is it possible to code this? I can't think of any code already done that I could easily modify (maybe someone can think of some similar code?).

EDIT: are the Lurchuip AI attracted to pieces of Barnaxis?


EDIT2: A slow way to do this (because it would cycle through all units each turn) might be:

def onturnbegin:
Get lure x coordinate and name it LurePosX
Get lure y coordinate and name it LurePosY
If lure exists
... Cycle through all units (maybe using the for loop) on map and if a unit has the undead promotion
...... Get unit's x coordinate and name it UndeadUnitX
...... Get unit's y coordinate and name it UndeadUnitY
...... MoveUnitX = 0
...... MoveUnitY = 0
...... If UndeadUnitX < LurePosX
......... MoveUnitX = 1
...... If UndeadUnitX > LurePosX
......... MoveUnitX = -1
...... If UndeadUnitY < LurePosY
......... MoveUnitY = 1
...... If UndeadUnitY > LurePosY
......... MoveUnitY = -1
...... Change the unit's (x,y) position by adding MoveUnitX and MoveUnitY to its position (maybe check if it is a valid tile for the unit - not water/mountain, not owned by another nation)
...... Subtract one movement point from the unit
 
I'd like to cause all units with the undead promotion to move towards a lure (either a fixed tile or an item). Is it possible to code this? I can't think of any code already done that I could easily modify (maybe someone can think of some similar code?).

EDIT: are the Lurchuip AI attracted to pieces of Barnaxis?


EDIT2: A slow way to do this (because it would cycle through all units each turn) might be:

def onturnbegin:
Get lure x coordinate and name it LurePosX
Get lure y coordinate and name it LurePosY
If lure exists
... Cycle through all units (maybe using the for loop) on map and if a unit has the undead promotion
...... Get unit's x coordinate and name it UndeadUnitX
...... Get unit's y coordinate and name it UndeadUnitY
...... MoveUnitX = 0
...... MoveUnitY = 0
...... If UndeadUnitX < LurePosX
......... MoveUnitX = 1
...... If UndeadUnitX > LurePosX
......... MoveUnitX = -1
...... If UndeadUnitY < LurePosY
......... MoveUnitY = 1
...... If UndeadUnitY > LurePosY
......... MoveUnitY = -1
...... Change the unit's (x,y) position by adding MoveUnitX and MoveUnitY to its position (maybe check if it is a valid tile for the unit - not water/mountain, not owned by another nation)
...... Subtract one movement point from the unit
I think thats the only way to do it in python. In SDK it would be faster and probably also be easy to get the undead units to circumvent obstacles.

I don't think that it would have a big performance impact as long as you have only one lure, since the code in the loop really doesn't do much.
 
(I've been a bit distracted/lazy and haven't made the code yet). I'm thinking of the best way to make a lure work (not slowing the program down too much and being easy to code, etc) - should it be:
1) An item which can be picked up and made into a promotion (similar to Orthus Axe, Healing Salve)
2) A unique unit
3) Something else?



Note to self - code to select all undead:
File CvEventManager.py:
Code:
			iUndead = gc.getInfoTypeForString('PROMOTION_UNDEAD')
			for iLoopPlayer in range(gc.getMAX_PLAYERS()):
				pLoopPlayer = gc.getPlayer(iLoopPlayer)
				player = PyPlayer(iLoopPlayer)
				if pLoopPlayer.isAlive():
					apUnitList = player.getUnitList()
					for pUnit in apUnitList:
[COLOR="DimGray"]						if (pUnit.isHasPromotion(iDemon) or pUnit.isHasPromotion(iUndead)):
							pUnit.kill(False, iPlayer)[/COLOR]
Because I want to move the undead units during their owner's turns, I think I will have to remove the code which cycles through each player. Instead I think that this code should be written in the section def onBeginGameTurn (in CvEventManager.py). Do I need to check if the player is alive since it only would go to def onBeginGameTurn if that player is alive?
Code:
			iUndead = gc.getInfoTypeForString('PROMOTION_UNDEAD')
			for iLoopPlayer in range(gc.getMAX_PLAYERS()):
				pLoopPlayer = gc.getPlayer(iLoopPlayer)
				player = PyPlayer(iLoopPlayer)
				if pLoopPlayer.isAlive():
					apUnitList = player.getUnitList()
					for pUnit in apUnitList:
						if pUnit.isHasPromotion(iUndead)):
My idea on how to search for a player's undead units in CvEventManager def onBeginGameTurn:
Code:
                        iUndead = gc.getInfoTypeForString('PROMOTION_UNDEAD')
                        player = gc.getPlayer(iPlayer)
                        apUnitList = player.getUnitList()
                        for pUnit in apUnitList:
                                if pUnit.isHasPromotion(iUndead)):



Code pieces which might find a unit's (x,y) position:
Code:
			if iImprovement == gc.getInfoTypeForString('IMPROVEMENT_RING_OF_CARCER'):
				pPlot.setMinLevel(15)
				bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
[COLOR="DarkSlateGray"]				bPlayer.initUnit(gc.getInfoTypeForString('UNIT_BRIGIT_HELD'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)[/COLOR]

OR MAYBE THIS:

		pPlot,pUnit,pOldPlot = argsList
		player = PyPlayer(pUnit.getOwner())
		unitInfo = PyInfo.UnitInfo(pUnit.getUnitType())

		if (not self.__LOG_MOVEMENT):
			return
		if player and unitInfo:
			CvUtil.pyPrint('Player %d Civilization %s unit %s is moving to %d, %d' 
				%(player.getID(), player.getCivilizationName(), unitInfo.getDescription(), 
				pUnit.getX(), pUnit.getY()))


OR MAYBE THIS:

	def onUnitKilled(self, argsList):
		'Unit Killed'
		unit, iAttacker = argsList
		iPlayer = unit.getOwner()
		player = PyPlayer(iPlayer)
		attacker = PyPlayer(iAttacker)
		pPlayer = gc.getPlayer(iPlayer)
		iX = unit.getX()
		iY = unit.getY()
		pPlot = CyMap().plot(iX,iY)








Note to self mostly:
I need to write code that makes the owner of an undead unit declare war on whomever their units walk into the lands of (if no open border agreement)

This code could be used to make Barbarian units follow Lucian (so they will try to get in Lucian's tile and then you could remove the slow promotion once they are in his tile so that they can attack outwards from Lucian's stack - useful in getting Barbarian support if you are attacking a city).

Or the code could be used thus: An event can lure dragons to a city ("dragon fanatics summoned them") which they attack unless you have made peace with the dragons (through building temples and sacrificing population occasionally and paying tribute possibly. This event requires the cult to be present in the city.

Lure code so far:
Code:
	def onBeginGameTurn(self, argsList):
		
		[If lure exists]
			[Get lure x coordinate and name it LurePosX]
			[Get lure y coordinate and name it LurePosY]
				iUndead = gc.getInfoTypeForString('PROMOTION_UNDEAD')
				player = gc.getPlayer(iPlayer)
				apUnitList = player.getUnitList()
				for pUnit in apUnitList:
					if pUnit.isHasPromotion(iUndead)):
						pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_SLOW'),True)
						
						[Get unit's x coordinate and name it UndeadUnitX]
						[Get unit's y coordinate and name it UndeadUnitY]
						MoveUnitX = 0
						MoveUnitY = 0
						If UndeadUnitX < LurePosX
							MoveUnitX = 1
						If UndeadUnitX > LurePosX
							MoveUnitX = -1
						If UndeadUnitY < LurePosY
							MoveUnitY = 1
						If UndeadUnitY > LurePosY
							MoveUnitY = -1
						UndeadUnitX = UndeadUnitX + MoveUnitX
						UndeadUnitY = UndeadUnitY + MoveUnitY
						
						[If (UndeadUnitX, UndeadUnitY) is a mountain then:]
							[If (UndeadUnitX, UndeadUnitY - MoveUnitY) is not a mountain then:]
								[UndeadUnitY = UndeadUnitY - MoveUnitY]
							#If changing the y coordinate avoids a mountain then the next line will not run#
							[Else If (UndeadUnitX - MoveUnitX, UndeadUnitY) is not a mountain then:]
								[UndeadUnitX = UndeadUnitX - MoveUnitX]
							#If changing the x coordinate avoids a mountain then the next line will not run#
							#The next line keeps the unit in the original (x,y) coordinate if it can't move without going onto a mountain#
							Else:
								[UndeadUnitY = UndeadUnitY - MoveUnitY]
								[UndeadUnitX = UndeadUnitX - MoveUnitX]

						[Move the unit to (UndeadUnitX, UndeadUnitY)]
						[If (UndeadUnitX, UndeadUnitY) is water then kill the unit]
						[If (UndeadUnitX, UndeadUnitY) is in another nation's territory then:]
							[Check if player does not have open borders with other nation]
								[Player declares war on other nation]
*Check I used the Else: statement correctly
 
Code so far:

Code:
BeginPlayerTurn(self, argsList):
		
		[If lure exists]
			[Get lure x coordinate and name it LurePosX]
			[Get lure y coordinate and name it LurePosY]
				iUndead = gc.getInfoTypeForString('PROMOTION_UNDEAD')
				player = gc.getPlayer(iPlayer)
				apUnitList = player.getUnitList()
				for pUnit in apUnitList:
					if pUnit.isHasPromotion(iUndead)):
						#This removes any undead units from a group (bGroup is set to False) they are in before moving them so that the whole group is not moved as well#
						pUnit.setXY(pUnit.getX(), pUnit.getY(), False, False, False)
						#This uses the AI's pathnoding to move the group (since the function only works for a group) towards a certain tile#
						pUnit.getGroup().pushMoveToMission(LurePosX, LurePosY)

- Code for how promotions work (e.g. Aeron's chosen) is all in the CIV4PromotionInfos.xml file.
- Technically the lure itself does nothing. The lure's position is found by finding the unit with the promotion and then finding the (x,y) of the unit.
- All I need to write into CIV4PromotionInfos.xml is the artwork.
- Make the event which gives the lure a unique event (perhaps copy the Aeron's chosen event which is unique).
- How can I tell if a promotion exists? Is the best way to do copy the way the program searches for any undead promotion units?
 
well, the best way would probably be to do something like this:
Code:
lUndeadUnits = [];
iLureX = -1;
iLureY = -1;
iUndead = gc.getInfoTypeForString('PROMOTION_UNDEAD');
iLure = gc.getInfoTypeForString('PROMOTION_LURE');
player = gc.getPlayer(iPlayer);
apUnitList = player.getUnitList();
for pUnit in apUnitList:
     if pUnit.isHasPromotion(iUndead):
          lUndeadUnits.append(pUnit);
     if pUnit.isHasPromotion(iLure):
          iLureX = pUnit.getX();
          iLureY = pUnit.getY();

if (iLureX != -1):
     for pUnit in lUndeadUnits:
          pUnit.setXY(pUnit.getX(), pUnit.getY(), False, False, False)
          pUnit.getGroup().pushMoveToMission(iLureX, iLureY)

ref: http://docs.python.org/2/tutorial/datastructures.html

edit: corrected pushMoveToMission(iLureX, iLureY)
 
Anw I'm trying to find you on chat to ask you about this but I've got a couple of questions about using lists for this.

1) What's the advantage of having a list of undead units (lUndeadUnits) since it gets remade every turn?
2) Is the last line meant to be pUnit.getGroup().pushMoveToMission(iLureX , iLureY) ?
3) Will the last paragraph cause the unit with the lure promotion to try to move to its own (x,y)? Does this matter?
 
Anw I'm trying to find you on chat to ask you about this but I've got a couple of questions about using lists for this.

1) What's the advantage of having a list of undead units (lUndeadUnits) since it gets remade every turn?
2) Is the last line meant to be pUnit.getGroup().pushMoveToMission(iLureX , iLureY) ?
3) Will the last paragraph cause the unit with the lure promotion to try to move to its own (x,y)? Does this matter?

1) the difference is between two full 'for' cycles - one for checking whether lure exists + its location, second one for actually moving undead units - this way you only do one full 'for' loop, and in the second one you only go through undead units

2) yes, it should be "pUnit.getGroup().pushMoveToMission(iLureX, iLureY)"

3) well, if the unit has lure promotion AND the same unit is undead, then the code will give the unit a mission to move towards its own location, which shouldn't do anything, so no problem here
 
re:1) Would it be more efficient (faster) to only run the code that moves the undead if the lure actually exists rather than making a list of all undead units every turn regardless?


EDIT: anw said it won't work on a different civ if they don't have the lure
you would have to use for iPlayer in xrange(gc.getMAX_PLAYERS()): and for pUnit in PyPlayer(iPlayer).getUnitList():
look for "def spellDivineRetribution(caster):" in Assets/python/entrypoints/CvSpellInterface.py

def onBeginGameTurn() - Called at the beginning of the end of each turn
def onBeginPlayerTurn() - Called at the beginning of a player's turn

anw's suggestion:
Code:
    iLureX = -1;
    iLureY = -1;
    iUndead = gc.getInfoTypeForString('PROMOTION_UNDEAD');
    iLure = gc.getInfoTypeForString('PROMOTION_LURE');
    for iPlayer in xrange(gc.getMAX_PLAYERS()):
         if gc.getPlayer(iPlayer).isAlive():
              player = gc.getPlayer(iPlayer);
              apUnitList = player.getUnitList();
              for pUnit in apUnitList:
                   if pUnit.isHasPromotion(iLure):
                        iLureX = pUnit.getX();
                        iLureY = pUnit.getY();
     
    if (iLureX != -1):
         for iPlayer in xrange(gc.getMAX_PLAYERS()):
              if gc.getPlayer(iPlayer).isAlive():
                   player = gc.getPlayer(iPlayer);
                   apUnitList = player.getUnitList();
                   for pUnit in apUnitList:
                        if pUnit.isHasPromotion(iUndead):
                             pUnit.setXY(pUnit.getX(), pUnit.getY(), False, False, False)
                             pUnit.getGroup().pushMoveToMission(iLureX, iLureY)
 
re:1) Would it be more efficient (faster) to only run the code that moves the undead if the lure actually exists rather than making a list of all undead units every turn regardless?

making the list will have better performance if the lure exists
not making the list (and using second 'for' loop through all units) will have better performance if the lure does not exist

it's up to you to pick which one you will go for
 
One efficient way to do it is make the unit with the lure promotion cast a spell each turn and then get the (x,y) of that unit. Probably use PyPerTurn because that is called every turn (see Crown of Brilliance for a working example). effectCrownOfBrillance is defined in CvSpellInterface.py (and Crown of Brilliance is of course a promotion in CIV4PromotionInfos.xml).

Promotion (xml) -> python function -> spell (xml)

From CIV4PromotionsInfos.xml (N.B. misspelling of brilliance):
Code:
        <PromotionInfo>
            <Type>PROMOTION_CROWN_OF_BRILLANCE</Type>
            <Description>TXT_KEY_PROMOTION_CROWN_OF_BRILLANCE</Description>
            <Help>TXT_KEY_PROMOTION_CROWN_OF_BRILLANCE_PEDIA</Help>
            <Sound>AS2D_IF_LEVELUP</Sound>
            <TechPrereq>TECH_NEVER</TechPrereq>
            <Button>Art/Interface/Buttons/Promotions/CrownofBrillance.dds</Button>
            <bDispellable>1</bDispellable>
            <bSeeInvisible>1</bSeeInvisible>
            <iAIWeight>150</iAIWeight>
            <iExpireChance>25</iExpireChance>
            <iMinLevel>-1</iMinLevel>
            <PyPerTurn>effectCrownOfBrillance(pCaster)</PyPerTurn>
        </PromotionInfo>

From CvSpellInterface.py:
Code:
def effectCrownOfBrillance(caster):
	caster.cast(gc.getInfoTypeForString('SPELL_CROWN_OF_BRILLANCE'))

def reqCrownOfBrillance(caster):
	if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CROWN_OF_BRILLANCE')):
		return False
	return True

From CIV4SpellsInfos.xml:
Code:
        <SpellInfo>
            <Type>SPELL_CROWN_OF_BRILLANCE</Type>
            <Description>TXT_KEY_SPELL_CROWN_OF_BRILLANCE</Description>
            <Civilopedia>TXT_KEY_SPELL_PLACEHOLDER_PEDIA</Civilopedia>
            <PromotionPrereq1>PROMOTION_CHANNELING3</PromotionPrereq1>
            <PromotionPrereq2>PROMOTION_DIVINE</PromotionPrereq2>
            <ReligionPrereq>RELIGION_THE_EMPYREAN</ReligionPrereq>
            <bAllowAI>1</bAllowAI>
            <bDisplayWhenDisabled>1</bDisplayWhenDisabled>
            <bHasCasted>1</bHasCasted>
            <iRange>1</iRange>
            <iDamage>30</iDamage>
            <iDamageLimit>60</iDamageLimit>
            <DamageType>DAMAGE_HOLY</DamageType>
            <AddPromotionType1>PROMOTION_CROWN_OF_BRILLANCE</AddPromotionType1>
            <bBuffCasterOnly>1</bBuffCasterOnly>
            <bImmuneTeam>1</bImmuneTeam>
            <bImmuneNeutral>1</bImmuneNeutral>
            <PyRequirement>reqCrownOfBrillance(pCaster)</PyRequirement>
            <Effect>EFFECT_SPELL1</Effect>
            <Sound>AS3D_SPELL_EARTHQUAKE</Sound>
            <Button>Art/Interface/Buttons/Promotions/CrownofBrillance.dds</Button>
        </SpellInfo>



MagisterCuultum wrote:
"The only parameter passed to PyPerTurn is pCaster, but you can get the location with pCaster.getPlot() pretty easily. "

"PyPerTurn effects happen once per turn, which could be before or after moving. The player has no real way of controlling exactly when it will happen. If you want it to be called whenever the unit moves then you either need to make SDK changes to re-enable the onMove calls, or use the python prereq as a spell."

"The problem with that is the PyPerTurn effect doesn't always happen at the start of the turn"

PyPerTurn is used for promotions


Useful pieces of code:
- pPlot = pCaster.getPlot()
- LurePosX = pCaster.getX()
- LurePosY = pCaster.getY()




--------------------------------------------------------------------------------------------
Another efficient way to do it is to save the unit ID and player ID with the sdToolKit.
So that it doesn't slow down the program searching through every unit every turn, Snarko thinks it will require saving the unit ID and the player ID of the lure using the SD toolkit.
 
1) CIV4PromotionInfos.xml:
Code:
            <PromotionInfo>
                <Type>PROMOTION_UNDEAD_LURE</Type>
                <Description></Description>
                <Help></Help>
                <Sound></Sound>
                <TechPrereq>TECH_NEVER</TechPrereq>
                <Button>Art/Interface/Buttons/Promotions/CrownofBrillance.dds</Button>
                <bDispellable>1</bDispellable>
                <bSeeInvisible>1</bSeeInvisible>
                <iAIWeight>150</iAIWeight>
                <iExpireChance>0</iExpireChance>
                <iMinLevel>-1</iMinLevel>
                <PyPerTurn>effectUndeadLure(pCaster)</PyPerTurn>
            </PromotionInfo>

2) CvSpellInterface.py:
Code:
    def effectUndeadLure(caster):
            caster.cast(gc.getInfoTypeForString('SPELL_UNDEAD_LURE'))
            LurePosX = pCaster.getX()
            LurePosY = pCaster.getY()
     
    def reqUndeadLure(caster):
            if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_UNDEAD_LURE')):
                    return False
            return True

3) CIV4SpellsInfo.xml:
Code:
            <SpellInfo>
                <Type>SPELL_UNDEAD_LURE</Type>
                <Description></Description>
                <Civilopedia></Civilopedia>
                <bAllowAI>1</bAllowAI>
                <bDisplayWhenDisabled>1</bDisplayWhenDisabled>
                <bHasCasted>1</bHasCasted>
                <iRange>1</iRange>
                <iDamage>0</iDamage>
                <iDamageLimit>0</iDamageLimit>
                <DamageType>DAMAGE_HOLY</DamageType>
                <AddPromotionType1>PROMOTION_UNDEAD_LURE</AddPromotionType1>
                <bBuffCasterOnly>1</bBuffCasterOnly>
                <bImmuneTeam>1</bImmuneTeam>
                <bImmuneNeutral>1</bImmuneNeutral>
                <PyRequirement>reqUndeadLure(pCaster)</PyRequirement>
                <Effect></Effect>
                <Sound></Sound>
                <Button></Button>
            </SpellInfo>
 
N.B. Double check the pushMission command if I come back to fix this at some stage. The correct command might be something like: pUnit.getGroup().pushMission(MissionTypes.MISSION_MOVE_TO, CX, CY, 0, False, False, MissionAITypes.NO_MISSIONAI, pUnit.plot(), pUnit)

Although after checking http://civ4bug.sourceforge.net/PythonAPI/index.html I found that the command is correct: VOID pushMoveToMission (INT iX, INT iY)
 
Top Bottom