Coding - Having issues selecting unit form python (domestic advisor)

WombatCoder

Chieftain
Joined
Sep 10, 2023
Messages
4
Since this is my first post I have the urge to say thanks to everyone that have contributed to this great mod :)

Are dabbling with a few hacks to the trade system.
One of them is adding a page in the domestic advisor that list trade units, with the ability to select a unit when clicking it. I'm trying to use the selectUnit in CvGame.cpp, but can not get it to work. Nothing seems to happen, incl. no errors.

C++:
void CvUnit::select(bool val1, bool val2, bool val3) {
    gDLL->getInterfaceIFace()->selectUnit(this, val1, val2, val3);
    gDLL->getInterfaceIFace()->lookAtSelectionPlot();
//    GC.getGame().selectUnit(this, val1, val2, val3);
//    GC.getGame().lookAtSelectionPlot();
}

C++:
void CyUnit::select(bool val1, bool val2, bool val3)
{
    if (m_pUnit)
        m_pUnit->select(val1, val2, val3);
}

CvUnit.h and CyUnit.h have been update too.

CyUnitInterface2.cpp:
C++:
        .def("select", &CyUnit::select, "void select(bool val1, bool val2, bool val3)")

New page in the domestic advisor:
Python:
player = gc.getPlayer(gc.getGame().getActivePlayer())
unit = player.getUnit(unitId)
unit.select(True, False, False)

Are there any existing code that can give some hints or any other advice will be appreciated.

My next step is getting debugging up and running, so I can pry further into what is happening.

Havn't had the time to get a good idea of the ins and out of the code base yet and Python and C++ is not my main language. Have so fare managed with trail an error for getting things to work, so have almost no idea of what I'm doing :) (originally I was just trying to do some quick fixes)

Are this kind of question better suited for Discord?
 
Are this kind of question better suited for Discord?
Better "suited" is hard to say.

Generally in discord, there is more activity of modders in these days.
So chances of one of them seeing your question and answering is higher.

In the old days, most of the technical discussions happened here though.
As a thread in the forum allows to stay focussed on topics a lot better.

So for the moment, maybe leave a ping here, so that a modder may see it.

Then discussion could of course also continue here. :thumbsup:

-------

Generally if you are motivated to work on something it might even be a good idea to get access to the "internal area" for the WTP team and supporters.
As everybody that is creating something is welcome to discuss and show what he does and ask for help ... if he wants to contribute it or not is then up to him.

Thus maybe contact @Nightinggale to give you access to the WTP modding channels.
Just so you may more easily get in contact with the few remaining active modders and also see what they are currently up to.

But again, it is also fine to continue discussion here.
 
Last edited:
Since this is my first post I have the urge to say thanks to everyone that have contributed to this great mod :)
Thanks and nice to see that there are still a few people around that try to mod themselves. :thumbsup:
 
Another comment:
There is a difference between "selecting" and actually "jumping to it".
Those are 2 steps necessary if you actually want to "focus on the unit" on the screen.

Edit:
void CvUnit::select(bool val1, bool val2, bool val3) {
Ah ok, I just saw that this does both ... it selects and also jumps to the plot.
 
Her is the full code of my new domestic advisor page:
Python:
from CvPythonExtensions import *
import CvUtil
import ScreenInput
import CvScreenEnums

import DomesticAdvisorTable
import BaseAdvisorWindow

gc = CyGlobalContext()
ArtFileMgr = CyArtFileMgr()
localText = CyTranslator()

class TradeRouteExtAdvisor(BaseAdvisorWindow.BaseAdvisorWindow):
    def __init__(self, parent):
        BaseAdvisorWindow.BaseAdvisorWindow.__init__(self, parent, "TradeRouteExtStateClass")

    def drawTableContent(self):
        player = gc.getPlayer(gc.getGame().getActivePlayer())

        self.numUnits = 0

        (unit, iter) = player.firstUnit()
        while (unit):
            if (not unit.isCargo() and not unit.isDelayedDeath() and unit.cargoSpace() > 0):
                self.numUnits = self.numUnits + 1
            (unit, iter) = player.nextUnit(iter)

        self.tableManager.setNumRows(self.numUnits)

        (unit, iter) = player.firstUnit()
        while (unit):
            if (not unit.isCargo() and not unit.isDelayedDeath() and unit.cargoSpace() > 0):
                self.drawTradeUnitLine(unit)
            (unit, iter) = player.nextUnit(iter)
    
    def drawTradeUnitLine(self, unit):
        self.tableManager.addPanelButton(unit.getButton(), WidgetTypes.WIDGET_GENERAL, unit.getID(), -1)
        self.tableManager.addInt(unit.getName(), unit.getID(), -1, WidgetTypes.WIDGET_GENERAL)

    def setDirty(self):
        self.dirty = True

    def createTableHeader(self):
        self.tableManager.addHeaderButton()
        self.tableManager.addHeaderTxt("Unit name", 400)

    def handleInput (self, inputClass):
        if (inputClass.getNotifyCode() == NotifyCode.NOTIFY_LISTBOX_ITEM_SELECTED and inputClass.getButtonType() == 0):
            unitId = inputClass.getData1()

            if (unitId >= 0):
                player = gc.getPlayer(gc.getGame().getActivePlayer())
                unit = player.getUnit(unitId)

                unit.select(True, False, False)
                return 0
        return -1
 
Edit:
This was wrong guessing.
Spoiler :

unitId = inputClass.getData1()
It is sometimes unclear where which data is hidden in which Widget.
(Got me confused a couple of times in the past as well.)

You might thus just also give this here a try:
unitId = inputClass.getData2()

As I saw that you put the ID in the 2nd parameter in the Widget, see here:
self.tableManager.addInt(unit.getName(), unit.getID(), -1, WidgetTypes.WIDGET_GENERAL)

-------

It is pure guessing though, but I have otherwise not really found an actual error in your code in first review. :dunno:
Maybe one of the other modders sees something.


@Nightinggale is much better in Python (and programming in general) than myself so maybe he finds something.
(Just not sure how often he is still around here in the forum.)

@f1rpo Is also a very good source of information on programming. :)
(Most likely he will get notified by this ping.)
 
Last edited:
(Caveat: I'm only really familiar with the Civ4BtS codebase.)
A print statement should clear up whether the select call in Python is reached. (And maybe another in __init__ to verify that print is working, i.e. printing to PythonDbg.log – if that's in doubt.)
Maybe the Advisor screen needs to be closed explicitly to allow a unit to be selected. Does not seem to be necessary when selecting a city:
CyInterface().selectCity(gc.getPlayer(inputClass.getData1()).getCity(inputClass.getData2()), true);
(That's from the original Col Domestic Advisor; seems that WtP has refactored and extended that a lot.)
Edit: The context of this code snippet (see link) could also be interesting wrt. to the semantics of Data1 and Data2. Looks like Data1 is the player ID here.
This also calls CyInterface directly, which, I think, should also work with selectUnit. (I don't know where to find the Col Python API; the one for BtS does include CyInterface.selectUnit.) Going via CyUnit in the DLL shouldn't make a difference, but, then, who knows.
 
Last edited:
Also see here:
if (self.selectedSelectionGroupHeadUnitID == inputClass.getData2()):

Honestly, I never really figured out the logic for which variable handled which input in Python.
I also most of the time used "try & error" so first trying getData1() and then if it did not work getData2().
 
CyInterface.selectUnit
Yeah, that is / was usually used.
So the new DLL code was not nessary.

But using the new "wrapper" logic written in the DLL should also work.
At least I could not find any issue in my code review of that new DLL logic.
 
As I saw that you put the ID in the 2nd parameter in the Widget, see here:
PHP:
def addInt(self, iValue, iData1 = -1, iData2 = -1, widget = WidgetTypes.WIDGET_GENERAL, justified = CvUtil.FONT_RIGHT_JUSTIFY):
First paramenter is the text to print in the spreadsheet box while the second parameter is indeed iData1 so that part looks ok.

PHP:
player = gc.getPlayer(gc.getGame().getActivePlayer())
unit = player.getUnit(unitId)

unit.select(True, False, False)
I can't find any select function in CyUnit, through selectUnit should work just fine (not that I have actually tested it).
PHP:
virtual void selectUnit(CvUnit* pUnit, bool bClear, bool bToggle = false, bool bSound = false) = 0;

This also calls CyInterface directly, which, I think, should also work with selectUnit. (I don't know where to find the Col Python API; the one for BtS does include CyInterface.selectUnit.)
Generally speaking, the API is precisely the same. Some details differs like colo lacks the function to play the nuclear detonation animation, but the more generic code appears to be identical. The biggest difference is colo adding the ability to drag and drop widgets, which is a completely undocumented feature.

Due to how identical it is, colo modders use the BTS API overview you linked to. Nobody ever made a copy, which is colo specific.

(That's from the original Col Domestic Advisor; seems that WtP has refactored and extended that a lot.)
Yeah it ended up being extended again and again and eventually it turned into a buggy mess, which was hard to add new screens to so I essentially just started over with a more OOP/modular design. It's intentionally very much not like vanilla.
 
@WombatCoder programmed this new function himself.
So it is not in our code base at the moment. (Just in his own.)
See the code in his first post.
Oops. I read it and then I read the python code and forgot about the C++ part. It looks ok. Now I wonder if it could be related to the issue I had where I had problems jumping to native settlements if the list grew too big, or rather I had problems clicking on the list if it was too big. I never figured out what the problem was, which caused that issue.
 
@WombatCoder :

After reviewing your code a second time I suspect that this red part in def handleInput (self, inputClass): might be the issue:

if (inputClass.getNotifyCode() == NotifyCode.NOTIFY_LISTBOX_ITEM_SELECTED and inputClass.getButtonType() == 0):

---------------------

Maybe just try what happens without and use this instead:

if (inputClass.getNotifyCode() == NotifyCode.NOTIFY_LISTBOX_ITEM_SELECTED):

---------------------

I am just guessing, but I really do not understand why you coded that red part.
So maybe just give it a try and see what happens. :dunno:
 
By the way:
What you are doing ("Trade Unit Advisor") - that allows to search Transports and Ships sounds like a useful addition. :thumbsup:
So if you want to contribute it to the WTP core mod, either myself or @Schmiddie could create a nice button for it.
 
Thanks for all the help and advice. And sorry for not getting back sooner.

I manage to figure out what the issue is, and in case any one else find this relevant:
The core problem was that using the WidgetTypes.WIDGET_GENERAL it seems that before the event reach the python handler data1 and data2 is set to 0 if they are out side intended range(As fare as I remember if data1 > 12 then it is set to 0). Decided to look for other widget types, and WidgetTypes.WIDGET_UNIT_MODEL seems to work fine for my purpose.
This was done on the non "beta" version from steam, so not sure if that had any thing to do with it.

@raystuttgart :
I will happily contribute to the core mod, but right now my changes are nothing more than a bunch of optimistic hacks :) Hopefully I'll soon convert my private clone in to a proper fork and clean up my commits.

Other than the wiki on GitHub and the forum thread "For Modding Beginners", are ther other info that are relevant to be familiar with? Expecially I'm having problems getting debugging up and running, I feel handicaped not having a working debugger ;)
 
Other than the wiki on GitHub and the forum thread "For Modding Beginners", are ther other info that are relevant to be familiar with? Expecially I'm having problems getting debugging up and running, I feel handicaped not having a working debugger ;)
Clone the git repository, open MSVC and close it again. Now that it should have created RaR.vcxproj.user (your personal settings, which is not part of the repository), you can run "Setup Debugger.bat". This will allow MSVC to start the mod with the debugger attached. Please remember to switch to window mode first or you might have fun with hitting breakpoints.

Version of MSVC not important. If you use 2017 or newer, double click "Convert Project to 2017.bat".

As for setting up a debugger for python code: if you figure out how to do that, then please tell because we don't know. This is one of the reasons why I prefer C++ over python.

The core problem was that using the WidgetTypes.WIDGET_GENERAL it seems that before the event reach the python handler data1 and data2 is set to 0 if they are out side intended range(As fare as I remember if data1 > 12 then it is set to 0). Decided to look for other widget types, and WidgetTypes.WIDGET_UNIT_MODEL seems to work fine for my purpose.
This was done on the non "beta" version from steam, so not sure if that had any thing to do with it.
Widgets are just a number, but some of them have special meaning, such as code triggering certain help text. In general it's a good idea to create your own widget types if you want custom ones.
 
Top Bottom