Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ Thumbs.db
/Project Files/Boost-1.32.0
/Project Files/Python24
.idea
.claude
/Assets/EditorCache.xml
/Assets/EditorSettings.xml
65 changes: 53 additions & 12 deletions Assets/Python/Screens/CvTradeRoutesAdvisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ def __init__(self):
self.NO_YIELD = -1
self.EUROPE_CITY = -1
self.NO_CITY = -2
self.AFRICA_CITY = -3
self.PORT_ROYAL_CITY = -4
self.OFF_MAP_CITIES = [self.EUROPE_CITY, self.AFRICA_CITY, self.PORT_ROYAL_CITY]

# Button ids
self.YIELD_TABLE_ID = 0
Expand All @@ -79,6 +82,21 @@ def __init__(self):
#R&R mod, vetiarvind, trade groups - END


def isOffMapCity(self, iCityId):
return iCityId in self.OFF_MAP_CITIES

def isRealCity(self, iCityId):
return iCityId != self.NO_CITY and not self.isOffMapCity(iCityId)

def getOffMapCityName(self, iCityId):
if iCityId == self.EUROPE_CITY:
return localText.getText("TXT_KEY_CONCEPT_EUROPE", ())
elif iCityId == self.AFRICA_CITY:
return localText.getText("TXT_KEY_CONCEPT_AFRICA", ())
elif iCityId == self.PORT_ROYAL_CITY:
return localText.getText("TXT_KEY_CONCEPT_PORT_ROYAL", ())
return u""

def interfaceScreen (self):
screen = self.getScreen()
if screen.isActive():
Expand Down Expand Up @@ -346,9 +364,9 @@ def sortByDestinationCities(pRoute):

def getColor(self, pRoute):
szColor = u"<color=255,255,255>"
if pRoute.getDestinationCity().iID == self.EUROPE_CITY:
if self.isOffMapCity(pRoute.getDestinationCity().iID):
szColor = u"<color=170,170,170>"

return szColor

def updateRoutes(self):
Expand Down Expand Up @@ -384,10 +402,10 @@ def appendBuilderRow(self):
if self.iExport != self.NO_CITY:
szExport = u"%s" % self.player.getCity(self.iExport).getName()
szImport = localText.getText("TXT_KEY_TRADE_ROUTES_MISSING_CITY", ())
if self.iImport > self.EUROPE_CITY:
if self.isRealCity(self.iImport):
szImport = u"%s" % self.player.getCity(self.iImport).getName()
elif self.iImport == self.EUROPE_CITY:
szImport = localText.getText("TXT_KEY_CONCEPT_EUROPE", ())
elif self.isOffMapCity(self.iImport):
szImport = self.getOffMapCityName(self.iImport)

szTable = self.TableNames[self.CURRENT_TABLE]
szColor = u"<color=255,200,50>"
Expand Down Expand Up @@ -493,13 +511,34 @@ def cityTable(self, bImport):
#Europe
screen.appendTableRow(szTable)
screen.setTableRowHeight(szTable, iI, self.ROW_HIGHT)
screen.setTableText(szTable, 0, iI, u"-1", "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
screen.setTableText(szTable, 0, iI, u"%d" % self.EUROPE_CITY, "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
screen.setTableText(szTable, 1, iI, u"%c" % CyGame().getSymbolID(FontSymbols.ANCHOR_EUROPE_CHAR), "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
screen.setTableText(szTable, 2, iI, localText.getText("TXT_KEY_CONCEPT_EUROPE", ()), "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_LEFT_JUSTIFY)
screen.setTableText(szTable, 3, iI, u"-", "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
screen.setTableText(szTable, 4, iI, u"%c" % CyGame().getSymbolID(FontSymbols.IMPORT_CHAR), "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)

iI += 1

#Africa
if self.pTransport.canSailToAfrica():
screen.appendTableRow(szTable)
screen.setTableRowHeight(szTable, iI, self.ROW_HIGHT)
screen.setTableText(szTable, 0, iI, u"%d" % self.AFRICA_CITY, "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
screen.setTableText(szTable, 1, iI, u"%c" % CyGame().getSymbolID(FontSymbols.ANCHOR_EUROPE_CHAR), "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
screen.setTableText(szTable, 2, iI, localText.getText("TXT_KEY_CONCEPT_AFRICA", ()), "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_LEFT_JUSTIFY)
screen.setTableText(szTable, 3, iI, u"-", "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
screen.setTableText(szTable, 4, iI, u"%c" % CyGame().getSymbolID(FontSymbols.IMPORT_CHAR), "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
iI += 1

#Port Royal
if self.pTransport.canSailToPortRoyal():
screen.appendTableRow(szTable)
screen.setTableRowHeight(szTable, iI, self.ROW_HIGHT)
screen.setTableText(szTable, 0, iI, u"%d" % self.PORT_ROYAL_CITY, "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
screen.setTableText(szTable, 1, iI, u"%c" % CyGame().getSymbolID(FontSymbols.ANCHOR_EUROPE_CHAR), "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
screen.setTableText(szTable, 2, iI, localText.getText("TXT_KEY_CONCEPT_PORT_ROYAL", ()), "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_LEFT_JUSTIFY)
screen.setTableText(szTable, 3, iI, u"-", "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
screen.setTableText(szTable, 4, iI, u"%c" % CyGame().getSymbolID(FontSymbols.IMPORT_CHAR), "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_CENTER_JUSTIFY)
iI += 1

for city in self.CityList:
screen.appendTableRow(szTable)
Expand Down Expand Up @@ -723,7 +762,7 @@ def updatePreview(self):

CURRENT_X += self.PREVIEW_WIDTH + self.STANDARD_MARGIN / 2

if self.iImport > self.EUROPE_CITY:
if self.isRealCity(self.iImport):
if self.iImport != self.iImportPreview:
self.iImportPreview = self.iImport
screen.show(self.szPreviewImport + "Banner")
Expand All @@ -735,11 +774,13 @@ def updatePreview(self):
screen.setLabelAt(self.szPreviewImport + "Label", self.szPreviewImport + "Banner", u"<font=2>" + u"%s" % self.player.getCity(self.iImport).getName() + u"</font>", CvUtil.FONT_CENTER_JUSTIFY, (self.PREVIEW_WIDTH - 20) / 2, 10, 0, FontTypes.GAME_FONT, WidgetTypes.WIDGET_GENERAL, -1, -1)
screen.addPlotGraphicGFC(self.szPreviewImport, CURRENT_X + 6, self.PREVIEW_Y + 6, self.PREVIEW_WIDTH - 12, self.PREVIEW_HEIGHT - 12, self.player.getCity(self.iImport).plot(), 350, True, WidgetTypes.WIDGET_GENERAL, -1, -1)
screen.moveToFront(self.szPreviewImport + "Border" + str(0))
elif self.iImport == self.EUROPE_CITY:
elif self.isOffMapCity(self.iImport):
screen.show(self.szPreviewImport + "Banner")
screen.show(self.szPreviewImport + "Cancel")
screen.setLabelAt(self.szPreviewImport + "Label", self.szPreviewImport + "Banner", u"<font=2>" + localText.getText("TXT_KEY_CONCEPT_EUROPE", ()) + u"</font>", CvUtil.FONT_CENTER_JUSTIFY, (self.PREVIEW_WIDTH - 10) / 2, 10, 0, FontTypes.GAME_FONT, WidgetTypes.WIDGET_GENERAL, -1, -1)
screen.addDDSGFC(self.szPreviewImport, "Art/Interface/Screens/TradeRoutes/EuropePreview.dds", CURRENT_X + 6, self.PREVIEW_Y + 6, self.PREVIEW_WIDTH - 12, self.PREVIEW_HEIGHT - 12, WidgetTypes.WIDGET_GENERAL, -1, -1 )
szLocationName = self.getOffMapCityName(self.iImport)
szPreviewArt = "Art/Interface/Screens/TradeRoutes/EuropePreview.dds"
screen.setLabelAt(self.szPreviewImport + "Label", self.szPreviewImport + "Banner", u"<font=2>" + szLocationName + u"</font>", CvUtil.FONT_CENTER_JUSTIFY, (self.PREVIEW_WIDTH - 10) / 2, 10, 0, FontTypes.GAME_FONT, WidgetTypes.WIDGET_GENERAL, -1, -1)
screen.addDDSGFC(self.szPreviewImport, szPreviewArt, CURRENT_X + 6, self.PREVIEW_Y + 6, self.PREVIEW_WIDTH - 12, self.PREVIEW_HEIGHT - 12, WidgetTypes.WIDGET_GENERAL, -1, -1 )
screen.moveToFront(self.szPreviewImport + "Border" + str(0))
else:
screen.hide(self.szPreviewImport)
Expand Down Expand Up @@ -797,7 +838,7 @@ def addSelection(self):
return

CyMessageControl().sendDoTask(self.iExport, TaskTypes.TASK_YIELD_EXPORT, self.iYields, True, False, False, False, False)
if self.iImport > self.EUROPE_CITY:
if self.isRealCity(self.iImport):
CyMessageControl().sendDoTask(self.iImport, TaskTypes.TASK_YIELD_IMPORT, self.iYields, True, False, False, False, False)


Expand Down
172 changes: 172 additions & 0 deletions Assets/Python/_DebugTools/WTPTests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
## WTP Test Suite - Python Tests (Tier 3)
## Callable from the in-game debug console or via CvEventManager hook.
## Tests exercise the Python-DLL bridge and validate XML data integrity.
##
## Usage: from the debug console or Python event hook, call:
## import WTPTests
## WTPTests.runAllTests()

from CvPythonExtensions import *
import CvUtil

gc = CyGlobalContext()

# ============================================================================
# Test Infrastructure
# ============================================================================

class WTPTestRunner:
def __init__(self):
self.tests_run = 0
self.tests_passed = 0
self.tests_failed = 0
self.failures = []

def reset(self):
self.tests_run = 0
self.tests_passed = 0
self.tests_failed = 0
self.failures = []

def runTest(self, name, testFunc):
self.tests_run = self.tests_run + 1
errors = testFunc()
if len(errors) == 0:
self.tests_passed = self.tests_passed + 1
else:
self.tests_failed = self.tests_failed + 1
for e in errors:
self.failures.append("%s: %s" % (name, e))

def report(self):
if self.tests_failed == 0:
msg = "[PASS] WTPTests.py: %d/%d tests passed" % (self.tests_passed, self.tests_run)
CvUtil.pyPrint(msg)
CyInterface().addImmediateMessage(msg, "")
else:
msg = "[FAIL] WTPTests.py: %d/%d tests failed" % (self.tests_failed, self.tests_run)
CvUtil.pyPrint(msg)
CyInterface().addImmediateMessage(msg, "")
for f in self.failures:
CvUtil.pyPrint(" " + f)
CyInterface().addImmediateMessage(f, "")

g_runner = WTPTestRunner()

# ============================================================================
# Test: Profession Yields Valid
# ============================================================================

def testProfessionYieldsValid():
errors = []
numYields = gc.getNumYieldInfos()
for i in range(gc.getNumProfessionInfos()):
info = gc.getProfessionInfo(i)
for j in range(info.getNumYieldsProduced()):
eYield = info.getYieldsProduced(j)
if eYield != -1 and (eYield < 0 or eYield >= numYields):
errors.append("Profession %d has invalid produced yield %d" % (i, eYield))
for j in range(info.getNumYieldsConsumed()):
eYield = info.getYieldsConsumed(j)
if eYield != -1 and (eYield < 0 or eYield >= numYields):
errors.append("Profession %d has invalid consumed yield %d" % (i, eYield))
return errors

# ============================================================================
# Test: Building Classes Valid
# ============================================================================

def testBuildingClassesValid():
errors = []
numClasses = gc.getNumBuildingClassInfos()
for i in range(gc.getNumBuildingInfos()):
info = gc.getBuildingInfo(i)
iClass = info.getBuildingClassType()
if iClass < 0 or iClass >= numClasses:
errors.append("Building %d has invalid class %d" % (i, iClass))
return errors

# ============================================================================
# Test: Unit Default Professions Valid
# ============================================================================

def testUnitDefaultProfessions():
errors = []
numProfessions = gc.getNumProfessionInfos()
for i in range(gc.getNumUnitInfos()):
info = gc.getUnitInfo(i)
eProfession = info.getDefaultProfession()
if eProfession != -1 and (eProfession < 0 or eProfession >= numProfessions):
errors.append("Unit %d has invalid default profession %d" % (i, eProfession))
return errors

# ============================================================================
# Test: Father Categories Valid
# ============================================================================

def testFatherCategoriesValid():
errors = []
numCategories = gc.getNumFatherCategoryInfos()
for i in range(gc.getNumFatherInfos()):
info = gc.getFatherInfo(i)
eCategory = info.getFatherCategory()
if eCategory < 0 or eCategory >= numCategories:
errors.append("Father %d has invalid category %d" % (i, eCategory))
return errors

# ============================================================================
# Test: Yield Infos Cargo Flag Consistency
# ============================================================================

def testYieldCargoConsistency():
errors = []
for i in range(gc.getNumYieldInfos()):
info = gc.getYieldInfo(i)
# Every yield should have a valid type string
typeName = info.getType()
if typeName is None or len(typeName) == 0:
errors.append("Yield %d has empty type string" % i)
return errors

# ============================================================================
# Test: Terrain Yield Access
# ============================================================================

def testTerrainYieldAccess():
errors = []
numYields = gc.getNumYieldInfos()
for i in range(gc.getNumTerrainInfos()):
info = gc.getTerrainInfo(i)
for j in range(numYields):
# Should not crash
val = info.getYield(j)
return errors

# ============================================================================
# Test: Civilization Info Consistency
# ============================================================================

def testCivilizationInfoConsistency():
errors = []
for i in range(gc.getNumCivilizationInfos()):
info = gc.getCivilizationInfo(i)
typeName = info.getType()
if typeName is None or len(typeName) == 0:
errors.append("Civilization %d has empty type string" % i)
return errors

# ============================================================================
# Test Runner
# ============================================================================

def runAllTests():
g_runner.reset()
g_runner.runTest("ProfessionYieldsValid", testProfessionYieldsValid)
g_runner.runTest("BuildingClassesValid", testBuildingClassesValid)
g_runner.runTest("UnitDefaultProfessions", testUnitDefaultProfessions)
g_runner.runTest("FatherCategoriesValid", testFatherCategoriesValid)
g_runner.runTest("YieldCargoConsistency", testYieldCargoConsistency)
g_runner.runTest("TerrainYieldAccess", testTerrainYieldAccess)
g_runner.runTest("CivilizationInfoConsistency", testCivilizationInfoConsistency)
g_runner.report()
return g_runner.failures
16 changes: 16 additions & 0 deletions Project Files/DLLSources/CvCity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12234,6 +12234,22 @@ void CvCity::addExport(YieldTypes eYield, bool bUpdateRoutes)
kRoutePlayer.addTradeRoute(getIDInfo(), IDInfo((PlayerTypes) iRoutePlayer, CvTradeRoute::EUROPE_CITY_ID), eYield);
}
}

if (kRoutePlayer.isYieldAfricaTradable(eYield) && kRoutePlayer.canTradeWithAfrica())
{
if (isHuman() || isBestPortCity())
{
kRoutePlayer.addTradeRoute(getIDInfo(), IDInfo((PlayerTypes) iRoutePlayer, CvTradeRoute::AFRICA_CITY_ID), eYield);
}
}

if (kRoutePlayer.isYieldPortRoyalTradable(eYield) && kRoutePlayer.canTradeWithPortRoyal())
{
if (isHuman() || isBestPortCity())
{
kRoutePlayer.addTradeRoute(getIDInfo(), IDInfo((PlayerTypes) iRoutePlayer, CvTradeRoute::PORT_ROYAL_CITY_ID), eYield);
}
}
}
}
}
Expand Down
11 changes: 7 additions & 4 deletions Project Files/DLLSources/CvDLLButtonPopup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1087,11 +1087,14 @@ void CvDLLButtonPopup::OnOkClicked(CvPopup* pPopup, PopupReturn *pPopupReturn, C
std::vector<std::string> routeDetails = split(tradeRouteStr, ' ');
int srcId = atoi(routeDetails[0].c_str());
int destId = atoi(routeDetails[1].c_str());
IDInfo europeCity((PlayerTypes)GC.getGameINLINE().getActivePlayer(),CvTradeRoute::EUROPE_CITY_ID);
PlayerTypes eActivePlayer = (PlayerTypes)GC.getGameINLINE().getActivePlayer();

addedTradeGroup->addRoute(
srcId != CvTradeRoute::EUROPE_CITY_ID ? player.getCity(srcId)->getIDInfo() : europeCity,
destId != CvTradeRoute::EUROPE_CITY_ID ? player.getCity(destId)->getIDInfo() : europeCity,
IDInfo srcInfo = CvTradeRoute::isOffMapTradeLocation(srcId) ?
IDInfo(eActivePlayer, srcId) : player.getCity(srcId)->getIDInfo();
IDInfo destInfo = CvTradeRoute::isOffMapTradeLocation(destId) ?
IDInfo(eActivePlayer, destId) : player.getCity(destId)->getIDInfo();

addedTradeGroup->addRoute(srcInfo, destInfo,
(YieldTypes) atoi(routeDetails[2].c_str()));
}

Expand Down
2 changes: 1 addition & 1 deletion Project Files/DLLSources/CvGameTextMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9344,7 +9344,7 @@ int CvGameTextMgr::setCityYieldModifierString(CvWStringBuffer& szBuffer, YieldTy

// WTP, ray, trying to fix Rebel Rate Modifier on Happiness for Balancing - START
// just if condition added
if (eYieldType != YIELD_HAPPINESS && eYieldType != YIELD_UNHAPPINESS && eYieldType != YIELD_CRIME)
if (eYieldType != YIELD_HAPPINESS && eYieldType != YIELD_UNHAPPINESS && eYieldType != YIELD_LAW && eYieldType != YIELD_CRIME)
{
int iRebelMod = kCity.getRebelPercent() * GC.getMAX_REBEL_YIELD_MODIFIER() / 100;
if (0 != iRebelMod)
Expand Down
8 changes: 8 additions & 0 deletions Project Files/DLLSources/CvGlobalsEnumSetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "autogenerated/AutoXmlDeclare.h"

void TestEnumMap();
void RunAllWTPTests();


static void DisplayXMLmissingError(bool bSuccess, const char* szName)
Expand Down Expand Up @@ -112,6 +113,13 @@ void CvGlobals::postXMLLoad(bool bFirst)
TestEnumMap();
}
#endif

#ifdef FASSERT_ENABLE
if (!bFirst)
{
RunAllWTPTests();
}
#endif
}


Expand Down
Loading