[PYTHON] Action Buttons Utilities

talchas

Prince
Joined
Jan 3, 2006
Messages
428
Edit:
This is now (sort of) obseleted by a new version that uses the SDK. Look in my sig for the link.

This set of utilities allows you to add buttons to the list of actions in the bottom center of the main interface. However, you cannot add any sort of tooltip to your button. AFAIK it is hardcoded with the relevant linkups in Units/CIV4MissionInfos.xml and the text in one of the Text/* xml files. It works like this:


All of this code would have to be in the same file, and should be at a place that is run at the beginning of the game. This would include any file imported by one of the EntryPonts/Cv*Interface classes.
Make your handlers - what python code you want to execute when a button is pushed:
Code:
import ActionButtons
gc = CyGlobalContext()
...
def hurtUnit(unit):
    unit.changeDamage(10,gc.getGame().getActivePlayer())
def healUnit(unit):
    unit.changeDamage(-10,gc.getGame().getActivePlayer())

Make your buttons:
Code:
...
hurt = ActionButtons.ActionButton("Art/Interface/Buttons/Actions/SplitUnitGroup.dds",hurtUnit)
heal = ActionButtons.ActionButton("Art/Interface/Buttons/Actions/CreateUnitGroup.dds",healUnit)

Make a function that returns a list of buttons you want to appear for a given unit. Use setEnabled(false) to make the button appear greyed-out. If you do, you will have to call setEnabled(true) when you want it to be enabled again.
Code:
...
def buttons(unit):
    heal.setEnabled(unit.getDamage()>20) # you can only heal up to 80%
    if(unit.firstStrikes()>0):
        return [heal,hurt]
    return [heal]
This would let you heal units up to 80%, and hurt units that have first strikes. The heal ability would always appear and just change being enabled/disabled, but the hurt one would only appear if that unit had first strikes.

Then tell this mod's code about it:
Code:
ActionButtons.setButtonsFunction(buttons)

The mod using this would also have to have all the python files in its Assets/Python folder. If you need to edit CvScreensInterface, edit mine instead or change yours to import and use CvActionButtonsMainInterface instead of CvMainInterface. The only other thing is CvGameInterface, which you could do anything you want with as long as you copy into your CvGameUtils subclass my cannotHandleAction function and the "import ActionButtons" statement. If you need to edit CvMainInterface, mine has markers everywhere I changed the original (v. 1.52).

The zip file contains a Python folder with all the .py files.

This stuff is just the modularized version of what I was using in my Magic/Square Selection demo.

Edit:
1.01: Updated version that repaints the ui.
1.02: Only repaints the necessary parts of the ui. Also put in a possible workaround for the occasional MemoryError/Unidentified C++ Exception thing.
 

Attachments

Missed this one. Managed to catch a link from another thread.

Hmmm - I suppose you could set mouseover info if it was of a set type, could you not? For example, if I wanted to put an upgrade button in so that a unit could upgrade where it could not normally upgrade could I not set the widget to the new unit widget?

EDIT: Another question -

About the code you've shown as an example. Are you saying that you have to put it in a file which is ran by one of the EntryPoints files? So if I make a new file called "CustomButtons.py", and copy-paste that code you've put there (as well as sort out all the files) will I get the hurt/heal buttons?

EDIT 2: Okies - I tried to get it to work and failed... bah! Any chance you could post an example file with working code which I can base my fiddlings off?
 
The Great Apple said:
Hmmm - I suppose you could set mouseover info if it was of a set type, could you not? For example, if I wanted to put an upgrade button in so that a unit could upgrade where it could not normally upgrade could I not set the widget to the new unit widget?
However, if you did that, then you couldn't respond to it in the python. The python function is only called if the game can't handle the button itself.

The Great Apple said:
About the code you've shown as an example. Are you saying that you have to put it in a file which is ran by one of the EntryPoints files? So if I make a new file called "CustomButtons.py", and copy-paste that code you've put there (as well as sort out all the files) will I get the hurt/heal buttons?
Yes, that should work. For example, stick it in the bottom of the CvActionButtonsGameUtils.py (not indented) and it should work.

There was also a bug with the buttons function in my example.
So CvActionButtonsGameUtils.py would be:
Code:
import CvUtil
from CvPythonExtensions import *
import CvGameUtils
import CvEventInterface
import ActionButtons
# globals
gc = CyGlobalContext()

class CvActionButtonsGameUtils(CvGameUtils.CvGameUtils):
	def cannotHandleAction(self,argsList): # this is the only place I've found that you can respond to the action buttons
		pPlot = argsList[0]
		iAction = argsList[1] # iAction is data1 from the appendMultiListButton command - it is always negative so that civ4 doesn't deal with it itself
		bTestVisible = argsList[2]
		
		button = ActionButtons._getButtonFromAction(iAction)
		if(button==None):
			return False
		button.getHandler()(CyInterface().getHeadSelectedUnit())
		return True
def hurtUnit(unit):
    unit.changeDamage(10,gc.getGame().getActivePlayer())
def healUnit(unit):
    unit.changeDamage(-10,gc.getGame().getActivePlayer())
hurt = ActionButtons.ActionButton("Art/Interface/Buttons/Actions/SplitUnitGroup.dds",hurtUnit)
heal = ActionButtons.ActionButton("Art/Interface/Buttons/Actions/CreateUnitGroup.dds",healUnit)
def buttons(unit):
    heal.setEnabled(unit.getDamage()>20) # you can only heal up to 80%
    if(unit.firstStrikes()>0):
        return [heal,hurt]
    return [heal]
ActionButtons.setButtonsFunction(buttons)
 
Well, it works :)

It doesn't however update the iterface after you press the button, which is a bit weird, and the button stays enabled until you flick between units. I turned the heal to hurt for testing purposes (and flipped the > to a < on the damage check), and was able to kill my own units because the button didn't grey out until I flicked back to the units. Also the damage didn't show up in the interface (although the multi-units had people disappearing). Basically, I doesn't seem that the interface is getting updated until you select a new object.

Thinking about it the interface *does* get updated when you select a promotion. Hmmm.
 
I just uploaded a new version, but I haven't had time to test it. It calls CyInterface().makeInterfaceDirty() to make it repaint, but I'm not sure that that is enough for the enabled/disabled thing. Could you check it out?
 
talchas said:
I just uploaded a new version, but I haven't had time to test it. It calls CyInterface().makeInterfaceDirty() to make it repaint, but I'm not sure that that is enough for the enabled/disabled thing. Could you check it out?
It works fine, however the game has a brief pause while the whole interface is redrawn, which is a bit annoying.

After a bit of testing, it seems that it's only a change in damage that doesn't update the screens (rather odd). I've tried changing movement, and promotions, and both seem to update both the info panel on the left, and the bit in the middle, without any slowdown.
 
I expect that it is only needs to be repainting part of the ui instead of the whole screen. When I get home I will take a look.
 
Ok, I changed it to just mark the buttons and the info pane as dirty, and it works fine.
 
Thanks Talchas, this will help me alot :)

def hurtUnit(unit):
unit.changeDamage(10,gc.getGame().getActivePlayer())
def healUnit(unit):
unit.changeDamage(-10,gc.getGame().getActivePlayer())

This is nice, but what if I wanted to hurt or heal other units on the same plot? For example, if I had a "medic" unit that can automatically give a damaged unit some strength back. Or, more particularly, if I had a unit that could only heal other units/unit types?
 
Shqype said:
Thanks Talchas, this will help me alot :)
This is nice, but what if I wanted to hurt or heal other units on the same plot? For example, if I had a "medic" unit that can automatically give a damaged unit some strength back. Or, more particularly, if I had a unit that could only heal other units/unit types?
Well, to hurt/heal all units on a plot do this:
Code:
for i in range(targetPlot.getNumUnits()):
	targetPlot.getUnit(i).changeDamage(amount,gc.getGame().getActivePlayer())
where targetPlot is the probably the plot of the selected unit (unit.plot()), amount is the amount of damage to deal, negative to heal and gc is the global context.

If you wanted to only do stuff to certain types of units you would do something like this:
Code:
for i in range(targetPlot.getNumUnits()):
	unit =targetPlot.getUnit(i)
	if(gc.getUnitInfo(unit.getUnitType()).getType()=="MY_SPECIAL_UNIT"):
		unit.changeDamage(amount,gc.getGame().getActivePlayer())
If you wanted a whole unitclass then replace getUnitInfo/getUnitType with getUnitClassInfo/getUnitClassType.
 
Thanks, I made the changes, but what this does is make that particular button/action available to all units. I only want to make it available to one unit, like a Medic. What code would accomplish this?
 
Shqype said:
Thanks, I made the changes, but what this does is make that particular button/action available to all units. I only want to make it available to one unit, like a Medic. What code would accomplish this?
Oh, right. Instead of
Code:
def buttons(unit):
    heal.setEnabled(unit.getDamage()>20) # you can only heal up to 80%
    return [heal]
Do this:
Code:
def buttons(unit):
    heal.setEnabled(unit.getDamage()>20) # you can only heal up to 80%
    if(gc.getUnitInfo(unit.getUnitType()).getType()=="MY_SPECIAL_UNIT"):
        return [heal]
    return []
Of course you probably would want to do a more complex check for heal.setEnabled - something like looping through all the units on that square and only disabling if none of the units can be healed. Something like this:
Code:
def buttons(unit):
    heal.setEnabled(false)
    targetPlot=unit.plot()
    for i in range(targetPlot.getNumUnits()):
	unit =targetPlot.getUnit(i)
	if(unit.getDamage()>20):
            heal.setEnabled(true)
            break
    if(gc.getUnitInfo(unit.getUnitType()).getType()=="MY_SPECIAL_UNIT"):
        return [heal]
    return []
Also you would then need to check each unit in the heal function, something like this:
Code:
for i in range(targetPlot.getNumUnits()):
	unit =targetPlot.getUnit(i)
	if(unit.getDamage()>20):
		unit.changeDamage(-10,gc.getGame().getActivePlayer())
 
Alright, I have the following code, and have observed a few bugs.
Code:
Code:
def healUnit(unit):
    for i in range(targetPlot.getNumUnits()):
	unit = targetPlot.getUnit(i)
	if(gc.getUnitInfo(unit.getUnitType()).getType()=="UNIT_INJURED"):
		unit.changeDamage(10,gc.getGame().getActivePlayer())
		
hurt = ActionButtons.ActionButton("Art/Interface/Buttons/Actions/SplitUnitGroup.dds",hurtUnit)
heal = ActionButtons.ActionButton("Art/Interface/Buttons/Actions/CreateUnitGroup.dds",healUnit)
def buttons(unit):
    heal.setEnabled(false)
    targetPlot=unit.plot()
    for i in range(targetPlot.getNumUnits()):
	unit =targetPlot.getUnit(i)
	if(unit.getDamage()>0):
            heal.setEnabled(true)
            break
    if(gc.getUnitInfo(unit.getUnitType()).getType()=="UNIT_MEDIC"):
        return [heal]
    return []

Bugs:
-Each turn the injured unit heals 10 strength by itself, without any button actions being pushed/performed.
-The Medic unit, supposed to be the only one to heal, has a grayed out button when on any plot NOT containing the unit it is supposed to be able to heal. Once you step on a plot containing the injured unit, the button simply disappears.

What I need is for there to be 2 units: Medic and Injured. The medic unit should be the ONLY unit with the "heal" button. Also, the ONLY unit it can heal is the unit "Injured," ONLY when it has less than 100% strength.
 
Yeah, that code is buggy. You've used "unit" to define two separate things. Change this:
Code:
    for i in range(targetPlot.getNumUnits()):
	unit =targetPlot.getUnit(i)
	if(unit.getDamage()>0):
            heal.setEnabled(true)
            break
to this:
Code:
    for i in range(targetPlot.getNumUnits()):
	unit2 =targetPlot.getUnit(i)
	if(unit2.getDamage()>0):
            heal.setEnabled(true)
            break
and I think it should work.

About the unit healing itself - are you sure this isn't just how the game works? Units healing at the end of turns? Maybe you have a medic promotion?
 
Oops, I forgot that unit was being used already when I posted that code.
 
Thanks TGA. I made the change and got the button to appear at the correct time, however there are still bugs:

-Apparently each unit will heal automatically at the end of each turn, even if you skip its turn by pressing [space]. I feel that makes the "Fortify Until Healed" button redundant. In any case, there were 3 INJURED units, one at full strength (100), one at 55% strength, and another at 61% strength. I skipped to the next turn and their new strengths were 100, 75, and 81, respectively. It seems there is 10 strength gain from the injured unit (once) and then there is a natural 10% strength gain that all units recieve from the game at the end of the turn/beginning of next turn.
-When I used the "medic" to heal the injured unit, pressing the newly created button gives me the following error:

pythonhealerror2cu.jpg


I believe the problem has to do with this code:
Code:
if(gc.getUnitInfo(unit.getUnitType()).getType()=="UNIT_INJURED"):
		unit.changeDamage(10,gc.getGame().getActivePlayer())

As it is it gives the INJURED unit the ability to heal itself IF it is damaged. Not only should this not happen, but I also want to remove the INJURED unit's ability to even be healed completely (except for the medic). In other words, the method that heals each injured unit after each turn needs to be overriden so that all INJURED units do not get healed. The only way they should be healed is by the medic.
 
Okies, your new error comes from you having not defined targetplot. Now, as targetplot is going to be the plot of the unit, you can simply change this:
Code:
def healUnit(unit):
    for i in range(targetPlot.getNumUnits()):
	unit = targetPlot.getUnit(i)
	if(gc.getUnitInfo(unit.getUnitType()).getType()=="UNIT_INJURED"):
		unit.changeDamage(10,gc.getGame().getActivePlayer())
to this:
Code:
def healUnit(unit):
    targetplot = unit.plot()
    for i in range(targetPlot.getNumUnits()):
	unit = targetPlot.getUnit(i)
	if(gc.getUnitInfo(unit.getUnitType()).getType()=="UNIT_INJURED"):
		unit.changeDamage(10,gc.getGame().getActivePlayer())
This is still pretty poor code, as you have unit used twice for two separate things, but it'll work...

To remove the ability of units to heal normally, just edit GlobalDefines.xml - change all the heal rates (city/friendly/neutral/enemy) to 0.

Just to warn you the AI won't know how to heal units if you do this.
 
Alright, 1 problem down, 1 to go.

The units no longer heal themselves, the only healing is the kind that occurs each turn as defined in GlobalDefines.xml. By the way, couldn't this be overriden in python by something like:
Code:
if (pPlayer.isHuman()):
	#All heal rates set to 0 for INJURED
Sure, the computer will get a slight advantage, being able to heal INJURED units 15% on its own for free (without medics), but I guess it's fair since we have an advantage over the computer called conscious intelligence :p

Now, the problem that still persists:
pythonhealerror21vi.jpg


It says targetPlot is not defined.
 
Make that targetPlot -python is case-sensitive
Code:
def healUnit(unit):
    targetPlot = unit.plot()
    for i in range(targetPlot.getNumUnits()):
	unit = targetPlot.getUnit(i)
	if(gc.getUnitInfo(unit.getUnitType()).getType()=="UNIT_INJURED"):
		unit.changeDamage(10,gc.getGame().getActivePlayer())


Of course you probably want to just heal all units on your team, so then do:
Code:
def healUnit(healer):
    targetPlot = healer.plot()
    for i in range(targetPlot.getNumUnits()):
	unit = targetPlot.getUnit(i)
	if(unit.getTeam()==healer.getTeam()):
		unit.changeDamage(10,gc.getGame().getActivePlayer())
 
Thanks Talchas. Thank you and TGA both for helping me. Now I have a better idea of how it works.

The only other bug I've got to report is that you can heal with injured unit until it regains all its health. There is no restriction on how many times the button can be pressed. Would it be easier to only allow the button to function if the unit has 1+ movement left?

PS- somehow throughout all this code the heal was 10 instead of -10 ... so I actually pressed it repeatedly and killed my units, haha, until I went back and changed it.
 
Back
Top Bottom