[BUG, 2.7.1beta2] Customs house will not accept to export food, lumber and stone

ShadesOfTime

Chieftain
Joined
May 3, 2012
Messages
31
Latest observed in 2.7.1beta2

Steps to reproduce:
- Open customs house dialog
- Chose to export food at a quantity above 0
- Close dialog
- Re-open dialog
 
According to the source code, those 3 are intended to not be sold. Also it seems that the code is prepared to not draw them in the popup menu. Looking at CvDLLButtonPopup::launchCustomHousePopup(), the code looks like: (starting on line 3177)
PHP:
            if (eYield == YIELD_FOOD || eYield == YIELD_LUMBER || eYield == YIELD_STONE)
            {
                gDLL->getInterfaceIFace()->popupStartHLayout(pPopup, 0);
                gDLL->getInterfaceIFace()->popupAddGenericButton(pPopup, L"", kYield.getButton(), -1, WIDGET_HELP_YIELD, iYield);
                gDLL->getInterfaceIFace()->popupCreateCheckBoxes(pPopup, 1, iYield, WIDGET_GENERAL, POPUP_LAYOUT_TOP);
                gDLL->getInterfaceIFace()->popupSetCheckBoxText(pPopup, 0, gDLL->getText("TXT_KEY_POPUP_NEVER_SELL"), iYield);
                // R&R, ray, finishing Custom House Screen
                gDLL->getInterfaceIFace()->popupSetCheckBoxState(pPopup, 0, true, iYield);
                gDLL->getInterfaceIFace()->popupCreateSpinBox(pPopup, iYield, L"", pCity->getCustomHouseSellThreshold(eYield), 10, 999, 0);
                gDLL->getInterfaceIFace()->popupEndLayout(pPopup);
            }
            else
            {
                gDLL->getInterfaceIFace()->popupStartHLayout(pPopup, 0);
                gDLL->getInterfaceIFace()->popupAddGenericButton(pPopup, L"", kYield.getButton(), -1, WIDGET_HELP_YIELD, iYield);
                gDLL->getInterfaceIFace()->popupCreateCheckBoxes(pPopup, 1, iYield, WIDGET_GENERAL, POPUP_LAYOUT_TOP);
                gDLL->getInterfaceIFace()->popupSetCheckBoxText(pPopup, 0, gDLL->getText("TXT_KEY_POPUP_NEVER_SELL"), iYield);
                // R&R, ray, finishing Custom House Screen
                gDLL->getInterfaceIFace()->popupSetCheckBoxState(pPopup, 0, pCity->isCustomHouseNeverSell(eYield), iYield);
                gDLL->getInterfaceIFace()->popupCreateSpinBox(pPopup, iYield, L"", pCity->getCustomHouseSellThreshold(eYield), 10, 999, 0);
                gDLL->getInterfaceIFace()->popupEndLayout(pPopup);
            }
It looks like the problem is fixed by simply removing the true case, but if it's that simple, why haven't it been done when the code was written in the first place? I fear this could be concealing some other bug. In other words this requires somebody who is willing to spend a while testing if the fix will cause other issues.
 
Additionally I found conditions for all 3 in CvDLLButtonPopup.cpp on line 928, where the closed customs house popup is handled:

PHP:
					if (GC.getYieldInfo((YieldTypes) iYield).isCargo() && (eYield != YIELD_FOOD) && (eYield != YIELD_LUMBER) && (eYield != YIELD_STONE))
					{
						bool bNeverSell = (pPopupReturn->getCheckboxBitfield(iYield) & 0x01);
						int iLevel = pPopupReturn->getSpinnerWidgetValue(iYield);
						
						if (bNeverSell != pCity->isCustomHouseNeverSell(eYield) || iLevel != pCity->getCustomHouseSellThreshold(eYield))
						{
							gDLL->sendDoTask(info.getData1(), TASK_CHANGE_CUSTOM_HOUSE_SETTINGS, iYield, iLevel, bNeverSell, false, false, false);
						}
					}

and conditions for stone and lumber in CvCity.cpp on line 6915, in the customs house and overflow selling algorithm itself:

PHP:
			if (GC.getYieldInfo(eYield).isCargo() && eYield != YIELD_LUMBER && eYield != YIELD_STONE) // we do not sell YIELD_LUMBER and Stone to Overflow or Custom House

So some1 wanted to make sure those 3 are not sold from the customs house for real. :-D
 
Thinking about what you wrote, that this code is there for a purpose, the need to protect the AI from selling crucial goods, comes to my mind as the only logical reason. So the thing I pursue next is to find out, if the AI uses the customs house and if it makes changes to the "never sell" flag.

Another thing that comes to my mind is, that we also could approach the issue from the perspective of what we want it to be, not from the perspective of what some1 else intended. Admittedly, this would open a pandora's box, as IMO distributing and selling goods is already part of the grand strategy. Maybe worth it.
 
Last edited:
the need to protect the AI from selling crucial goods, comes to my mind as the only logical reason.
Sounds reasonable, except for the fact that this is GUI code and the AI doesn't use the GUI. Also the AI is unaware of the custom house settings, meaning it ignores the custom house entirely.

I was more thinking like what happens if some yields are skipped while drawing the code. The drawing code should be fine, but when ok is cliched, yields are looped and what happens if a yield isn't drawn, yet the code requests the button status? Is there a risk that enabling selling cigars will get an offset error and sell something else? At first glance I wouldn't think so, but the fact that it draws all the yields despite being able to skip some worries me. It could be a solution to an offset issue.
 
I followed down the road you proposed and checked for any offset issues.

- The creation of the customs house popup and the handling of the closed popup show exactly the same itteration. No additions or subtractions to or from the itterator. Code looks clean.
- The same is true for the yield handling in CvCity::doYields, case default .
- The arrays, where the data (neverSell, threshhold) is stored, are just in time arrays, but which get initialized for all NUM_YIELD_TYPES yields, once used the first time.

I don't see any offset problems.

Additionally I have seen that the AI uses its own city logic (CvCityAI). Whatever that logic is, the conditions we found should not affect the AI.

I will work on a fix.

Questions:
-) How do I enable logging? It is initialized to false ( CvGlobals.cpp:42 ) but no where reset.
-) Should lumber and stone also be sold from the overflow (once the needed warehouse expansion was built)? Currently they are sold neither from the customs house nor from the overflow. I think I will just let them be sold from the overflow as well. The important tools do get sold too.
 
I found out how to enable logging. Via the file "Documents\My Games\Sid Meier's Civilization IV Colonization\CivilizationIV.ini".

I don't like it, when I can not see from the code how things come together.
 
I found out how to enable logging. Via the file "Documents\My Games\Sid Meier's Civilization IV Colonization\CivilizationIV.ini".

I don't like it, when I can not see from the code how things come together.
I was writing that just now. I guess I wrote too slow :sad:

There are many interesting settings in this file.
  • HidePythonExceptions = 0 (you don't want it to silently fail)
  • AutoSaveInterval = 1 (if you encounter a bug, then you want a savegame from last turn)
  • MaxAutoSaves can be whatever you want. Default is 5, but that's with a savegame every 4th turn, meaning saving every turn requires 20 saves to go equally far back
  • DisableFileCaching = 1 (the cache is unstable when switching mods or modifying the mod)
  • DisableCaching = 1 (same reason)
  • LoggingEnabled = 1 (you usually want that one)
  • CheatCode = chipotle (enables a bunch of world builder features)
You can also use the debugger to figure out what goes on at runtime. However the default settings will make MSVC try to execute the DLL directly, which won't work. To set it up, you select the project (RaR), then project->properties. Here you select configuration properties->debugging. You need to fill in 3 lines here.
First you select the target. I recommend "all configurations". You then fill in the following:
  • Command: this is the Colonization exe (full path)
  • Command Arguments: mod="\Religion-and-Revolution" (the folder name of the mod to start)
  • Working directory: Same as command, except remove the filename. It's just the dir
Save the project and you will be able to start debugging from the debug menu.
Don't debug in full screen mode!!!
You risk hitting a breakpoint, assert or crash and the screen will freeze. The only way out is control-alt-delete if that happens because it disables alt-tab while in the debugger. There is also the option of just starting the game and then attach to a running process, but it's more hassle. It should also be noted that steam crash the game if you attach the debugger, but starting the steam from the exe directly like this is actually untested.

The debugger is very powerful if you figure out how to use it correctly. It's not just to identify crashes. I use it occasionally to test my newly added code before committing. Insert a breakpoint at the start, step through the function and see if the variables contains what is expected. This can be very useful if it's not something, which is easy to read on the screen ingame.
 
Top Bottom