how to move unit towards destination, in python?

davidlallen

Deity
Joined
Apr 28, 2008
Messages
4,743
Location
California
I have worked out a mechanism for units that use fuel. It is simple and specific for a "precious fuel" application (see my sig). In this mechanism, fuel trucks are produced in cities with refineries, and when a gas powered unit is low on fuel, you can "use up" a gas truck to refuel it.

For the human player, this is working fine. For the AI, not so much. The AI will build gas trucks and gas powered units. Then it sends out the gas powered units to attack while leaving the gas trucks at home. The attacking units run out of gas and get stuck.

I am not using SDK and I don't want to get involved in it. I know there are mods, like Final Frontier, which do quite a lot of the AI in python. I have a few hundred lines of python already and I am using the onBeginTurn / onEndTurn hooks for some other, simple AI behavior. I have designed the algorithm to look at gas truck locations vs gas powered unit locations and decide which truck to move where.

The one critical thing I am stuck on is, how can I tell a unit to move "towards" a location in python? Depending on terrain, roads, etc I cannot easily tell how many plots it can actually traverse. I just want to give it the destination and have the engine work out moving it there. I could set the x,y location but then if the path is visible to the player, the truck "teleports". This is undesirable; for example, it could teleport past player units.

Is there any way to give a destination to a unit in python?
 
Yea, this doesn't work like you would first expect. CyUnits don't every accually move. Move orders are passed to the unit's selectiong group and then the group moves.

So, say you have pUnit set to a CyUnit object and a pPlot that is the CyPlot you want the unit to move to.
Code:
pSelectionGroup = pUnit.getGroup()
pSelectionGroup.pushMoveToMission(pPlot.getX(), pPlot.getY())

That will cause it to move to the new tile using all the built in mechanics of promotions, terrain movement costs, unit's move points, ect.
 
Cool. I guess this is like "players" and "teams", something you expect to be stored on the player is actually stored on a team. I can see the documentation for CySelectionGroup but I would not have made the connection. I will try that out tonight.

Suppose I want my fuel truck to stay together with a specific gas powered unit, say a tank, and they are currently in the same plot. Perhaps I can simply find out the group that this tank belongs to, and add my fuel truck to the same group. Then I hope the fuel truck would permanently stay with the tank, and if one moved slower than the other, the group would stay together.

But, offhand I cannot find a way to *add* a unit to a group, neither in CyUnit nor in CySelectionGroup. Is there a way, and would it work as I described above?
 
Huh, your right, there isn't any addUnit or removeUnit in the SelectionGroup, weird. Nevering having had a reason to use it, I just hadn't noticed.

So, no, unfortunetly, I don't know how to add another unit to a group, but you might want to check GlobalContext, CvPlot, and hell maybe CvPlayer or CvTeam. Some of these things can be hidden in weird places.
And one idea that popped into my head, you might want to check and see if two units in the same square with the same owner are in the same group without manually puting them together. Probly not, but somthing to try.

Now assuming you can get them into the same SelectionGroup and you've trained the AI to want to move them together (or knock on wood it automagically figures it out on its own), then yes what you described should work as you described it. The the faster of the two will only move as far as the slower one.
 
That should be possible to group some units with :

if the unit is not at the same plot of the target :
pGroup.pushMission(MissionTypes.MISSION_MOVE_TO_UNIT, pTargetUnit.getOwner(), pTargetUnit.getID(), 0, False, True, MissionAITypes.MISSIONAI_GROUP, None, pTargetUnit)

(None or pTargetUnit.plot()), idon't know if python can use None in the call.

That need some tests , i don't know if that works if the unit isnot at the same plot. You should take a look at the sdk.

Tcho !
 
Last night I got the details of the move algorithm right. This morning I tried calling pushMoveToMission(pPlot.getX(), pPlot.getY()) and it is getting ignored. I have created a stripped-down testcase, 25 lines of python, to demonstrate. Here it is:

Code:
# Test ability to move unit in python

import CvEventManager
from CvPythonExtensions import *

class CvMoveTestEvents(CvEventManager.CvEventManager):

   def __init__(self):
      self.parent = CvEventManager.CvEventManager
      self.parent.__init__(self)
      self.gc = CyGlobalContext()

   # Print a message to the transient screen display
   def myPrint(self, text):
      CyInterface().addMessage(CyGame().getActivePlayer(),True,10,\
         text,'',1,'',ColorTypes(8),-1,-1,True,True)

   def onGameStart(self, argsList):
      self.parent.onGameStart(self, argsList)
      self.myPrint("MoveTest example active")

   # Find AI warriors and tell them to move one plot east
   def onBeginPlayerTurn(self, argsList):
      iTurn, iPlay = argsList
      pPlay = self.gc.getPlayer(iPlay)
      if (not pPlay.isAlive()) or (pPlay.isHuman()): return
      iWar  = self.gc.getInfoTypeForString("UNIT_WARRIOR")
      for iUnit in range(pPlay.getNumUnits()):
         pUnit = pPlay.getUnit(iUnit)
         iType = pUnit.getUnitType()
         if iType != iWar: continue
         iX = pUnit.getX() + 1 ; iY = pUnit.getY()
         pGroup = pUnit.getGroup()
         iID = pGroup.getID()
         pGroup.clearMissionQueue()
         pGroup.pushMoveToMission(iX, iY)
         self.myPrint("MoveTest sent group %d to %d,%d" % (iID, iX, iY))

The MoveTest print statement displays; I can see in ctrl-z debug mode that the coordinates and group number are right; but the warrior ignores the order and does whatever else he wants. Usually in a beginning game he has UNITAI_EXPLORE and goes one plot in a random direction.

I have tried setting the UnitAI to -1 and to "NO_UNITAI". At least in ctrl-z mode, this actually causes the GUI to fail. It is trying to display the help string for this AI type, and there isn't one. So when I mouse-over the warrior, I get an unhandled C exception and I can't recover.

I suspect that the AI code is deciding my mission isn't exciting enough so it pops my mission and pushes its own.

Maybe only one little bit is missing, can you help me find what is wrong?
 
In the code block you show, I so see this error
Code:
iX = pUnit.getX() + 1 ; iY = pUnit.getY()
should be
Code:
iX = pUnit.getX() + 1
iY = pUnit.getY()

Other then that, I don't see anything that jumps out at me.

Just to ask some of the basic questions.
Do you have python exception popups enabled in your civ4 ini file?
Are you ending your turn, cause the unit won't do the mission on its own until you end your turn?.. or I think you can also hit Ctrl + R to make units do the turns qued missions.
 
The semicolon is an optional separator in python. The iy statement is being executed correctly since it shows up in the print statement. So, no error.

Yes, I have python exceptions enabled. I know the pushMission statement is being executed because my print statement comes afterwards and the print is appearing. I have done several turns in a row, and the warriors continue to wander around on their own. So it cannot be due to only performing a partial turn.

I have also looked into the BTS Final Frontier mod more closely, which does appear to move units on its own in python. I see it calls pUnit.finishMoves() right after pushing the mission. I added that, but the warriors still ignore the call to move east.

I'd really like to be able to control unit movement from python. Can anybody help by suggesting what I may have missed?
 
You should test pGroup.canAllMove() , pGroup.canMoveInto(CyMap().plot(iX+1,iY), False) , pGroup.isAutomated() (should prevent the mission to start if true) , pGroup.readyToMove(True) (if this return False , you should try your function CvGameUtils.AI_unitUpdate).

You shouldn't need to clear the mission queue. Since NULL is called for bAppend, the mission should made clear the others and start this one.

Hope this help , Tcho !
 
Thanks for noting the additional checks. The problem wasn't any additional calls needed; I was trying to do it in the wrong place. I traced out how Final Frontiers does it. It overrides CvGameUtils.AI_unitUpdate, which is called for each unit. If the function returns true, then the game skips any AI for that unit. So, I called pushMission within AI_unitUpdate and now my warriors are happily marching off to the east!
 
Be careful , this function is not called for each unit but for the head unit of each group .

Tcho !
 
Thanks for the warning. I cannot find any way to add, change, modify groups in python anyway, although it would be nice. As long as I am only moving units which I created in python, I can be sure they are not in a group.
 
Sweet, glad you figured it out davidlallen. Good luck and let us know if you run into any more problems. :)
 
Thanks for the warning.

I've forgotten to say also that if you do nothing for this unit you should finishMove . Or i you don't take decision and let the default impl, the update may be called several times. If another group is forced to separate , the process restart the update for each group.

I cannot find any way to add, change, modify groups in python anyway, although it would be nice.

see post 5 below. But i've never used this function through python , so i don't know how to handle the arguments. That should require some tests with arguments. And find a mission if the unit is at the same plot of the group you want to merge (not sure the move_to unit mission will start in this case).

As long as I am only moving units which I created in python, I can be sure they are not in a group.

Every unit is in a group , even alone. And more it becomes to have units in the games , more they group with each others.(Edit : even if you have created the unit. Another may merge with it)

Tcho !
 
Back
Top Bottom