RandomMap_mst.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. #
  2. # FILE: RandomMap_mst.py
  3. # AUTHOR: Terkhen (based on Sansimap by sansi)
  4. # PURPOSE: Generate random maps following certain map options by randomly selecting a MapScript from a list.
  5. # DEPENDENCY: Needs MapScriptTools.py
  6. #-----------------------------------------------------------------------------
  7. # CHANGELOG
  8. #-----------------------------------------------------------------------------
  9. #
  10. # 1.1_mst Terkhen 12.Sep.2016
  11. # Added, compatibility with Pangaea.
  12. # Added, compatibility with Archipelago.
  13. # Added, RandomMap no longer requires all compatible mapscripts to work. If some are missing, it will only list the existing ones.
  14. # Fixed, RandomMap will use default grid sizes if the mapscript misses the getGridSize method.
  15. #
  16. # 1.0_mst Terkhen 08.Dic.2014 ( uses MapScriptTools )
  17. # Added, RandomMap will try to avoid launching exceptions when the chosen mapscript lacks a certain method.
  18. # Added, save/load map options.
  19. # Added, weighted randomization of scenarios.
  20. # Added, support for Earth3, Erebus, Fractured World, Inland Sea, Medium and Small, Planetfall, Sea Highlands and Tectonics.
  21. # Added, initial version.
  22. #-----------------------------------------------------------------------------
  23. from CvPythonExtensions import *
  24. import CvUtil
  25. import CvMapGeneratorUtil
  26. # Universal constants
  27. map = CyMap()
  28. # Helper class which tests if supported MapScripts are present and stores them.
  29. class RMSupportedMapScripts(object):
  30. def __init__(self):
  31. # Includes one tuple for each supported MapScript, in the format name, txtId, module, weight.
  32. self.__mapscripts = list()
  33. def addMapScript(self, txt, module, weight):
  34. # Try to get the module.
  35. importedModule = None
  36. try:
  37. importedModule = __import__(module, globals(), locals(), [])
  38. self.__mapscripts.append([module, txt, importedModule, weight])
  39. except ImportError:
  40. print "[RandomMap] Warning: Module for supported MapScript %s not found." % module
  41. def getNumMapScripts(self):
  42. return len(self.__mapscripts)
  43. def getName(self, index):
  44. return self.__mapscripts[index][0]
  45. def getTxt(self, index):
  46. return self.__mapscripts[index][1]
  47. def getModule(self, index):
  48. return self.__mapscripts[index][2]
  49. def getWeight(self, index):
  50. return self.__mapscripts[index][3]
  51. def setWeight(self, index, weight):
  52. self.__mapscripts[index][3] = weight
  53. supportedMapscripts = RMSupportedMapScripts()
  54. # Append any supported MapScripts here, in the format [textID, moduleName, defaultWeight]
  55. supportedMapscripts.addMapScript("TXT_KEY_MAP_SCRIPT_RANDOM_ARCHIPELAGO", "Archipelago_mst", 2)
  56. supportedMapscripts.addMapScript("TXT_KEY_MAP_SCRIPT_RANDOM_EARTH3", "Earth3_mst", 1)
  57. supportedMapscripts.addMapScript("TXT_KEY_MAP_SCRIPT_RANDOM_EREBUS", "Erebus_mst", 2)
  58. supportedMapscripts.addMapScript("TXT_KEY_MAP_SCRIPT_RANDOM_FRACTURED_WORLD", "FracturedWorld_mst", 2)
  59. supportedMapscripts.addMapScript("TXT_KEY_MAP_SCRIPT_RANDOM_INLAND_SEA", "Inland_Sea_mst", 2)
  60. supportedMapscripts.addMapScript("TXT_KEY_MAP_SCRIPT_RANDOM_MEDIUM_AND_SMALL", "Medium_and_Small_mst", 2)
  61. supportedMapscripts.addMapScript("TXT_KEY_MAP_SCRIPT_RANDOM_PANGAEA", "Pangaea_mst", 2)
  62. supportedMapscripts.addMapScript("TXT_KEY_MAP_SCRIPT_RANDOM_PLANETFALL", "PlanetFall_mst", 2)
  63. supportedMapscripts.addMapScript("TXT_KEY_MAP_SCRIPT_RANDOM_SEA_HIGHLANDS", "Sea_Highlands_mst", 2)
  64. supportedMapscripts.addMapScript("TXT_KEY_MAP_SCRIPT_RANDOM_TECTONICS", "Tectonics_mst", 2)
  65. # MapScriptTools Interface by Temudjin.
  66. import MapScriptTools as mst
  67. # Text information about the map script.
  68. def getVersion():
  69. """ Returns the version of the map script """
  70. return "1.1_mst"
  71. def getDescription():
  72. """ Returns the description of the map script """
  73. return "TXT_KEY_MAP_SCRIPT_RANDOM_DESCR"
  74. # Methods called while we are still choosing the map options prior to map generation.
  75. ################################################################
  76. ## Custom Map Option Interface by Temudjin START
  77. ################################################################
  78. def setCustomOptions():
  79. """ Loads custom map options from a cfg file (when possible) and configures all of them in a single place. """
  80. global op # { optionID: Name, OptionList, Default, RandomOpt }
  81. # Initialize options to the default values.
  82. lMapOptions = list()
  83. # World wrap.
  84. lMapOptions.append(1)
  85. # Resources.
  86. lMapOptions.append(0)
  87. # Weights for all present MapScripts.
  88. for i in range(0, supportedMapscripts.getNumMapScripts()):
  89. lMapOptions.append(supportedMapscripts.getWeight(i))
  90. # Coasts / team start
  91. lMapOptions.append(0)
  92. # Mars theme / team start
  93. lMapOptions.append(0)
  94. # Try to read map options from the cfgFile.
  95. mst.mapOptionsStorage.initialize(lMapOptions, 'RandomMap')
  96. lMapOptions = mst.mapOptionsStorage.readConfig()
  97. # Weight option used for all supported MapScripts.
  98. optionWeights = [ "TXT_KEY_MAP_SCRIPT_RANDOM_NO", "1", "2", "3", "4" ]
  99. op = dict()
  100. # World wrap.
  101. op[0] = ["TXT_KEY_MAP_SCRIPT_WORLD_WRAP", ["TXT_KEY_MAP_SCRIPT_WORLD_WRAP_FLAT", "TXT_KEY_MAP_SCRIPT_WORLD_WRAP_CYLINDRICAL", "TXT_KEY_MAP_SCRIPT_WORLD_WRAP_TUBULAR", "TXT_KEY_MAP_SCRIPT_WORLD_WRAP_TOROIDAL"], lMapOptions[0], True]
  102. # Resources.
  103. op[1] = ["TXT_KEY_MAP_RESOURCES", ["TXT_KEY_MAP_RESOURCES_STANDARD", "TXT_KEY_MAP_RESOURCES_BALANCED"], lMapOptions[1], True]
  104. # All present MapScripts.
  105. for i in range(0, supportedMapscripts.getNumMapScripts()):
  106. op[i + 2] = [supportedMapscripts.getTxt(i), optionWeights, lMapOptions[i + 2], True]
  107. # Coasts / team start
  108. if mst.bPfall:
  109. op[supportedMapscripts.getNumMapScripts() + 2] = [
  110. "TXT_KEY_MAP_TEAM_START", ["TXT_KEY_MAP_TEAM_START_NEIGHBORS", "TXT_KEY_MAP_TEAM_START_SEPARATED", "TXT_KEY_MAP_TEAM_START_RANDOM"], lMapOptions[10], False
  111. ]
  112. else:
  113. op[supportedMapscripts.getNumMapScripts() + 2] = [
  114. "TXT_KEY_MAP_COASTS", ["TXT_KEY_MAP_COASTS_STANDARD", "TXT_KEY_MAP_COASTS_EXPANDED"], lMapOptions[10], False
  115. ]
  116. # Mars theme / team start
  117. if mst.bMars:
  118. op[supportedMapscripts.getNumMapScripts() + 3] = [
  119. "TXT_KEY_MAP_MARS_THEME", ["TXT_KEY_MAP_MARS_THEME_SANDS_OF_MARS", "TXT_KEY_MAP_MARS_THEME_TERRAFORMED_MARS"], lMapOptions[11], False
  120. ]
  121. elif not mst.bPfall:
  122. op[supportedMapscripts.getNumMapScripts() + 3] = [
  123. "TXT_KEY_MAP_TEAM_START", ["TXT_KEY_MAP_TEAM_START_NEIGHBORS", "TXT_KEY_MAP_TEAM_START_SEPARATED", "TXT_KEY_MAP_TEAM_START_RANDOM"], lMapOptions[10], False
  124. ]
  125. op["Hidden"] = 10
  126. mst.printDict(op,"Random Map Options:")
  127. def isAdvancedMap():
  128. """ This map should not show up in simple mode """
  129. return 1
  130. # first function to be called by the map building process
  131. def getNumHiddenCustomMapOptions():
  132. """ Default is used for the last n custom-options in 'Play Now' mode. """
  133. setCustomOptions() # Define Options
  134. return op["Hidden"]
  135. def getNumCustomMapOptions():
  136. """ Number of different user-defined options for this map """
  137. return len( op ) - 1
  138. def getCustomMapOptionName(argsList):
  139. """ Returns name of specified option """
  140. optionID = argsList[0]
  141. translated_text = unicode(CyTranslator().getText(op[optionID][0], ()))
  142. return translated_text
  143. def getNumCustomMapOptionValues(argsList):
  144. """ Number of different choices for a particular setting """
  145. optionID = argsList[0]
  146. return len( op[optionID][1] )
  147. def getCustomMapOptionDescAt(argsList):
  148. """ Returns name of value of option at specified row """
  149. optionID = argsList[0]
  150. valueID = argsList[1]
  151. translated_text = unicode(CyTranslator().getText(op[optionID][1][valueID], ()))
  152. return translated_text
  153. def getCustomMapOptionDefault(argsList):
  154. """ Returns default value of specified option """
  155. optionID = argsList[0]
  156. return op[optionID][2]
  157. def isRandomCustomMapOption(argsList):
  158. """ Returns a flag indicating whether a random option should be provided """
  159. optionID = argsList[0]
  160. return op[optionID][3]
  161. ################################################################
  162. ## Interfaces by Temudjin END
  163. ################################################################
  164. def isClimateMap():
  165. """ Indicates if this map script uses the Climate options. """
  166. # At this point of map generation we still don't know if the map script to be chosen randomly
  167. # will require this option, so we always request it.
  168. return True
  169. def isSeaLevelMap():
  170. """ Indicates if this map script uses the Sea Level options. """
  171. # At this point of map generation we still don't know if the map script to be chosen randomly
  172. # will require this option, so we always request it.
  173. return True
  174. # Methods called before map generation is initialized.
  175. def beforeInit():
  176. """ Obtains a map chosen randomly from the weights given by the user and initializes it. """
  177. # Map script to be chosen randomly.
  178. global cms
  179. # Calculate weights for each map
  180. pMapScripts = []
  181. print "[RandomMap] Calculating map script weights..."
  182. iTotalWeight = 0
  183. for i in range(0, supportedMapscripts.getNumMapScripts()):
  184. iWeight = map.getCustomMapOption(i + 2)
  185. if iWeight > 0:
  186. pMapScripts.append((iWeight, i))
  187. iTotalWeight += iWeight
  188. print "[RandomMap] %s weight: %i (Total weight: %i)" % (supportedMapscripts.getName(i), iWeight, iTotalWeight)
  189. # If the user chose no map scripts, we will choose a random one between all supported map scripts.
  190. if len(pMapScripts) == 0:
  191. print "[RandomMap] No MapScripts selected. Using default list..."
  192. for i in range(0, supportedMapscripts.getNumMapScripts()):
  193. iWeight = supportedMapscripts.getWeight(i)
  194. pMapScripts.append((iWeight, i))
  195. iTotalWeight += iWeight
  196. # Choose randomly between all supported mapscripts, taking into account their weights.
  197. iRand = CyGlobalContext().getGame().getMapRand().get(iTotalWeight, "RandomMap.beforeInit()")
  198. iCurrentWeight = 0
  199. iChosenMapScript = -1
  200. for index in range( len(pMapScripts) ):
  201. iWeight, iMapScriptIndex = pMapScripts[index]
  202. iCurrentWeight = iCurrentWeight + iWeight
  203. sMapScript = supportedMapscripts.getName(iMapScriptIndex)
  204. print "[RandomMap] %s current weight: %i (Random value: %i)" % (sMapScript, iCurrentWeight, iRand)
  205. if iRand < iCurrentWeight:
  206. iChosenMapScript = iMapScriptIndex
  207. print "[RandomMap] %s has been chosen" % sMapScript
  208. break
  209. # Initialize the chosen map script.
  210. cms = supportedMapscripts.getModule(iChosenMapScript)
  211. # Map options to be transferred to the chosen map script.
  212. mapOptionWorldShape = map.getCustomMapOption(0)
  213. mapOptionResources = map.getCustomMapOption(1)
  214. mapOptionCoastalWaters = mst.iif(mst.bPfall, None, map.getCustomMapOption(10))
  215. mapOptionTeamStart = mst.iif(mst.bMars, None, map.getCustomMapOption( mst.iif(mst.bPfall, 10, 11) ))
  216. mapOptionMarsTheme = mst.iif(mst.bMars, map.getCustomMapOption(11), None)
  217. # The chosen map script will initialize its map options with these values. All other map options will be set randomly.
  218. try:
  219. cmsMethod = getattr(cms, "randomMapBeforeInit")
  220. except AttributeError:
  221. print "[RandomMap] Fatal error: %s is not compatible with RandomMap" % sChosenMapScript
  222. else:
  223. cmsMethod(mapOptionWorldShape, mapOptionResources, mapOptionCoastalWaters, mapOptionTeamStart, mapOptionMarsTheme)
  224. def getGridSize(argsList):
  225. """ Returns the chosen grid size. """
  226. if (argsList[0] == -1): # (-1,) is passed to function on loads
  227. return []
  228. try:
  229. cmsMethod = getattr(cms, "method")
  230. except AttributeError:
  231. [eWorldSize] = argsList
  232. return CyGlobalContext().getWorldInfo(eWorldSize).getGridWidth(),\
  233. CyGlobalContext().getWorldInfo(eWorldSize).getGridHeight()
  234. else:
  235. return cmsMethod(argsList)
  236. def getTopLatitude():
  237. """ Returns the maximum latitude. This value must be in a [-90, 90] interval. """
  238. try:
  239. cmsMethod = getattr(cms, "getTopLatitude")
  240. except AttributeError:
  241. return 90
  242. else:
  243. return cmsMethod()
  244. def getBottomLatitude():
  245. """ Returns the minimum latitude. This value must be in a [-90, 90] interval. """
  246. try:
  247. cmsMethod = getattr(cms, "getBottomLatitude")
  248. except AttributeError:
  249. return -90
  250. else:
  251. return cmsMethod()
  252. def getWrapX():
  253. """ Returns if the map should wrap in the X axis. """
  254. try:
  255. cmsMethod = getattr(cms, "getWrapX")
  256. except AttributeError:
  257. return False
  258. else:
  259. return cmsMethod()
  260. def getWrapY():
  261. """ Returns if the map should wrap in the Y axis. """
  262. try:
  263. cmsMethod = getattr(cms, "getWrapY")
  264. except AttributeError:
  265. return False
  266. else:
  267. return cmsMethod()
  268. # Methods called before map generation.
  269. def beforeGeneration():
  270. """ Saves the map options used for this generation and calls beforeGeneration() in the chosen map script. """
  271. # Obtain the map options in use by RandomMap.
  272. lMapOptions = []
  273. for opt in range( getNumCustomMapOptions() ):
  274. iValue = 0 + map.getCustomMapOption( opt )
  275. lMapOptions.append( iValue )
  276. # Save used RandomMap map options.
  277. mst.mapOptionsStorage.writeConfig(lMapOptions)
  278. # Call beforeGeneration() in the chosen map script.
  279. try:
  280. cmsMethod = getattr(cms, "beforeGeneration")
  281. except AttributeError:
  282. return False
  283. else:
  284. return cmsMethod()
  285. # Methods called during map generation.
  286. def generatePlotTypes():
  287. """ FIRST stage in map generation. Calls generatePlotTypes() in the chosen map script. """
  288. try:
  289. cmsMethod = getattr(cms, "generatePlotTypes")
  290. except AttributeError:
  291. return CyPythonMgr().allowDefaultImpl()
  292. else:
  293. return cmsMethod()
  294. def generateTerrainTypes():
  295. """ SECOND stage in map generation. Calls generateTerrainTypes() in the chosen map script. """
  296. try:
  297. cmsMethod = getattr(cms, "generateTerrainTypes")
  298. except AttributeError:
  299. return CyPythonMgr().allowDefaultImpl()
  300. else:
  301. return cmsMethod()
  302. def addRivers():
  303. """ THIRD stage in map generation. Calls addRivers() in the chosen map script. """
  304. try:
  305. cmsMethod = getattr(cms, "addRivers")
  306. except AttributeError:
  307. CyPythonMgr().allowDefaultImpl()
  308. else:
  309. cmsMethod()
  310. def addLakes():
  311. """ FOURTH stage in map generation. Calls addLakes() in the chosen map script. """
  312. try:
  313. cmsMethod = getattr(cms, "addLakes")
  314. except AttributeError:
  315. CyPythonMgr().allowDefaultImpl()
  316. else:
  317. cmsMethod()
  318. def addFeatures():
  319. """ FIFTH stage in map generation. Calls addFeatures() in the chosen map script. """
  320. try:
  321. cmsMethod = getattr(cms, "addFeatures")
  322. except AttributeError:
  323. CyPythonMgr().allowDefaultImpl()
  324. else:
  325. cmsMethod()
  326. def addBonuses():
  327. """ SIXTH stage in map generation. Calls addBonuses() in the chosen map script. """
  328. try:
  329. cmsMethod = getattr(cms, "addBonuses")
  330. except AttributeError:
  331. CyPythonMgr().allowDefaultImpl()
  332. else:
  333. cmsMethod()
  334. def isBonusIgnoreLatitude():
  335. """ Defines if bonus placement should ignore map latitude. Used by the default implementation of addBonuses(), normalizeAddFoodBonuses() and normalizeAddExtras(). Calls isBonusIgnoreLatitude() in the chosen map script. """
  336. try:
  337. cmsMethod = getattr(cms, "isBonusIgnoreLatitude")
  338. except AttributeError:
  339. return False
  340. else:
  341. return cmsMethod()
  342. def addGoodies():
  343. """ SEVENTH stage in map generation. Calls addGoodies() in the chosen map script. """
  344. try:
  345. cmsMethod = getattr(cms, "addGoodies")
  346. except AttributeError:
  347. CyPythonMgr().allowDefaultImpl()
  348. else:
  349. cmsMethod()
  350. # Methods called during starting plot normalization.
  351. def normalizeStartingPlotLocations():
  352. """ FIRST stage in the normalization of starting plots. Calls normalizeStartingPlotLocations() in the chosen map script. """
  353. try:
  354. cmsMethod = getattr(cms, "normalizeStartingPlotLocations")
  355. except AttributeError:
  356. CyPythonMgr().allowDefaultImpl()
  357. else:
  358. cmsMethod()
  359. def normalizeAddRiver():
  360. """ SECOND stage in the normalization of starting plots. Calls normalizeAddRiver() in the chosen map script. """
  361. try:
  362. cmsMethod = getattr(cms, "normalizeAddRiver")
  363. except AttributeError:
  364. CyPythonMgr().allowDefaultImpl()
  365. else:
  366. cmsMethod()
  367. def normalizeRemovePeaks():
  368. """ THIRD stage in the normalization of starting plots. Calls normalizeRemovePeaks() in the chosen map script. """
  369. try:
  370. cmsMethod = getattr(cms, "normalizeRemovePeaks")
  371. except AttributeError:
  372. return CyPythonMgr().allowDefaultImpl()
  373. else:
  374. return cmsMethod()
  375. def normalizeAddLakes():
  376. """ FOURTH stage in the normalization of starting plots. Calls normalizeAddLakes() in the chosen map script. """
  377. try:
  378. cmsMethod = getattr(cms, "normalizeAddLakes")
  379. except AttributeError:
  380. CyPythonMgr().allowDefaultImpl()
  381. else:
  382. cmsMethod()
  383. def normalizeRemoveBadFeatures():
  384. """ FIFTH stage in the normalization of starting plots. Calls normalizeRemoveBadFeatures() in the chosen map script. """
  385. try:
  386. cmsMethod = getattr(cms, "normalizeRemoveBadFeatures")
  387. except AttributeError:
  388. return CyPythonMgr().allowDefaultImpl()
  389. else:
  390. return cmsMethod()
  391. def normalizeRemoveBadTerrain():
  392. """ SIXTH stage in the normalization of starting plots. Calls normalizeRemoveBadTerrain() in the chosen map script. """
  393. try:
  394. cmsMethod = getattr(cms, "normalizeRemoveBadTerrain")
  395. except AttributeError:
  396. CyPythonMgr().allowDefaultImpl()
  397. else:
  398. cmsMethod()
  399. def normalizeAddFoodBonuses():
  400. """ SEVENTH stage in the normalization of starting plots. Calls normalizeAddFoodBonuses() in the chosen map script. """
  401. try:
  402. cmsMethod = getattr(cms, "normalizeAddFoodBonuses")
  403. except AttributeError:
  404. CyPythonMgr().allowDefaultImpl()
  405. else:
  406. cmsMethod()
  407. def normalizeAddGoodTerrain():
  408. """ EIGHT stage in the normalization of starting plots. Calls normalizeAddGoodTerrain() in the chosen map script. """
  409. try:
  410. cmsMethod = getattr(cms, "normalizeAddGoodTerrain")
  411. except AttributeError:
  412. CyPythonMgr().allowDefaultImpl()
  413. else:
  414. cmsMethod()
  415. def normalizeAddExtras():
  416. """ NINTH and last stage in the normalization of starting plots. Calls normalizeAddExtras() in the chosen map script. """
  417. try:
  418. cmsMethod = getattr(cms, "normalizeAddExtras")
  419. except AttributeError:
  420. CyPythonMgr().allowDefaultImpl()
  421. else:
  422. cmsMethod()
  423. def minStartingDistanceModifier():
  424. """ FIRST stage in the selection of starting plots. Calls minStartingDistanceModifier() in the chosen map script. """
  425. try:
  426. cmsMethod = getattr(cms, "minStartingDistanceModifier")
  427. except AttributeError:
  428. if mst.bPfall: return -25
  429. if mst.bMars: return -15
  430. return 0
  431. else:
  432. return cmsMethod()
  433. ##########