Volcanoes

TrippedOnACloud

Warlord
Joined
Jun 28, 2012
Messages
294
I've noticed that in every one of my games for quite some time, volcanoes fail to persist much beyond the late Prehistoric era, if that. The events linked to the Volcano - Active and Volcano - Dormant features have been triggering properly as far as I can tell, with multiple eruptions and cycling between Active and Dormant throughout the early Prehistoric. However, every single volcano on the map has been replaced with Rocky Hills (often with Obsidian or Sulfur) by mid to late Prehistoric, and I haven't seen the event that triggers a re-eruption of a fully extinct volcano for a long time. Now, I know that several versions ago, there was a time when said re-eruption event could trigger even if a city was founded on the former volcano plot, resulting in a city that would be inaccessible but still functional, but I don't recall if that was ever addressed in any way. In any case, the complete inability to build any of the volcano-dependent buildings has been bugging me for a while now.

Anyway, I was looking for feedback from others as to whether or not they are having a similar experience, and if not, what settings they're using. It's remotely possible that the odds of the eruption are just not favoring me, but I believe that's unlikely to have happened over dozens of games.

Also, where in the game assets are the volcano events located? I haven't gone on a serious search of the files, because of the sheer scale of things, but if one of the team could point me at the right folder I can take a look for myself.
 
According to a grepWin-search, the files would be

  • Assets\XML\Events\CIV4EventInfos.xml
  • Assets\XML\Events\CIV4EventTriggerInfos.xml
  • Assets\Python\EntryPoints\CvRandomEventInterface.py (for Python)
 
As tmv has posted those should be the files. The 1st 2 xml files I could not find anything that is the actual "trigger". Dancing Huskold would need to point out what does that. I would assume the python file.

I have not paid much attention to when the volcano events stop. I have other things on my plate for my test games. But I will say I'm kind of glad when they do stop.:clap: :p

JosEPh
 
I believe that volcanoes are rigged to the amount of time between events, not the amount of turns. Either that or it very much is era dependant. It's been this way since they were introduced and it kinda makes sense as later eruptions would be much less frequent.
 
The volcano events and triggers are in the XML/Events folder. It is implemented in the python Python/Events/CvRandomEventInterface.py

There is no time limit on volcano t is just later there are a lot more events so they become less and less likely. Also we may have a bug there as all volcanoes that go extinct will get a sulphur or obsidian resource on them which may stop any new eruptions.

Known bugs
  1. The player triggering the event is getting the message when only those that can see the volcano should be. I think I turned that on when testing some fixes and forgot to turn it off.
  2. Eruptions are currently destroying all improvements and harming all units on the 9 plots surrounding the volcano (cities are not damaged) when it should only be selecting 3 plots (neighbouring each other).
  3. extension to be done
    terrain features
    forest or old forest become burnt forest
    savanna or bamboo forest are removed

A check needs to be made each turn to turn burnt forest into forest or savanna before we can do the extension
 
  1. extension to be done
    terrain features
    forest or old forest become burnt forest
    savanna or bamboo forest are removed
A check needs to be made each turn to turn burnt forest into forest or savanna before we can do the extension
I've always wanted to do a terrain/feature morphism modification so we could more easily setup terrain and feature change rules. The map should be somewhat mutable imo. That check to have the burnt forest recover could be a part of that.

I'm not saying that would be anytime soon though. Way too much I want to accomplish between now and then. Just saying that it would be very nice to have some more dynamic map stuff happening someday.
 
  1. extension to be done
    terrain features
    forest or old forest become burnt forest
    savanna or bamboo forest are removed

A check needs to be made each turn to turn burnt forest into forest or savanna before we can do the extension

Surely, that would be very time consuming and slow game turns a lot. Checking every tile, every turn. Maybe checking every X turns would be more acceptable. X being 10, 20 turns etc.

Unless it is an addition to an already per turn check that is being done.
 
The volcano events and triggers are in the XML/Events folder. It is implemented in the python Python/Events/CvRandomEventInterface.py

There is no time limit on volcano t is just later there are a lot more events so they become less and less likely. Also we may have a bug there as all volcanoes that go extinct will get a sulphur or obsidian resource on them which may stop any new eruptions.
I think the latter is what I've been running into. I don't have visibility of Sulfur yet in my current game, and I haven't checked in WB, but that sounds like what's happened to all volcanoes. From a gameplay perspective, Obsidian is provided by the Lava Rock Quarry that the volcano feature allows, so replacing a Rocky/Hills/Obsidian with an active volcano does not remove access to it. Also, a quick peek at the Python for the volcanoes is rather confusing. That said, there's what appears to be some random lines of code commented out. I'll have to look at it more carefully to see if that's meant to be the case, or if it's changing intended functionality.

I'm fairly confident I can take a stab at fixing the all-9-tiles eruption damage when I have the time, though. I'm assuming it's meant to represent direction of flow for the eruption to one of the eight surrounding tiles, and its two neighbors?

edit: Ok, after looking at the Python some more, I can definitely throw together some (possibly inefficient) code to make an erupting volcano pick a direction to erupt in, then damage units/improvements in that plot and the two neighboring plots. It would mean a fairly significant change to the way the eruption code is currently set up though; at present it seems to be intended to damage units in all surrounding plots, and destroy improvements in X (currently 3) random plots, though with only a 50% chance of destroying the Xth.

Also, currently, it looks like once a volcano goes extinct, it's guaranteed to produce either Obsidian or Sulfur at equal odds.

Thus, the overall issue seems to be that all volcanoes eventually will go extinct (event trigger is 100% of games, 40% chance of triggering on Dormant), but no new volcanoes are forming for any reason. Cycling between Active/Dormant, and associated eruptions, seem to be working fine. I'm wondering if maybe sidestepping the issue by creating a new Volcano - Extinct feature that allows construction of the volcano vicinity buildings would be simpler. After all, an extinct volcano site should be just as capable of being mined for Pumice, Obsidian, and such.

The main reason I first noticed this was the fairly recent addition of Waterproof Cement, but my complete inability to acquire it due to lack of volcanoes, anyway.

edit2: Ok, looking at the event triggers for the New Volcano events, it looks like the problem is indeed the presence of Obsidian or Sulfur on extinct volcano plots. Specifically, in def canDoNewVolcano in the Python file, there's a check that returns False if the plot in question is a city, has a bonus, or is already a volcano. I believe that the following code will allow extinct volcanoes to re-erupt, and will be testing the change in my own game shortly:

Code:
def canDoNewVolcano(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  ft_volcano = gc.getInfoTypeForString('FEATURE_VOLCANO2')
  ft_volcano2 = gc.getInfoTypeForString('FEATURE_VOLCANO')
  iObsidian = gc.getInfoTypeForString('BONUS_OBSIDIAN')
  iSulphur = gc.getInfoTypeForString('BONUS_SULPHUR')

  if pPlot.isCity() or ((pPlot.getBonusType() != -1) and ((pPlot.getBonusType() != iObsidian) and (pPlot.getBonusType() != iSulphur))) or (pPlot.getFeatureType() == ft_volcano) or (pPlot.getFeatureType() == ft_volcano2):
    return False
 
  return True
 
Last edited:
Surely, that would be very time consuming and slow game turns a lot. Checking every tile, every turn. Maybe checking every X turns would be more acceptable. X being 10, 20 turns etc.

Unless it is an addition to an already per turn check that is being done.
Of course there is. That is how improvements upgrade. It would just extend it to features on plots not just improvement.
 
The following code appears to work fine when substituted into Python/Events/CvRandomEventInterface.py, without throwing any Python exceptions or other errors, for allowing extinct volcanoes to re-erupt:
Code:
def canDoNewVolcano(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  ft_volcano = gc.getInfoTypeForString('FEATURE_VOLCANO2')
  ft_volcano2 = gc.getInfoTypeForString('FEATURE_VOLCANO')

  if pPlot.isCity() or ((pPlot.getBonusType(-1) != -1) and ((pPlot.getBonusType(-1) != gc.getInfoTypeForString('BONUS_OBSIDIAN')) and (pPlot.getBonusType(-1) != gc.getInfoTypeForString('BONUS_SULPHUR')))) or (pPlot.getFeatureType() == ft_volcano) or (pPlot.getFeatureType() == ft_volcano2):
    return False

  return True
The conditional is an ungodly mess of ands/ors and parentheses, but I don't know if ands can be strung together without them properly, so I erred on the side of caution.

There's some changes to doNewVolcano in the same Python file and the EventTriggers XML that I'm experimenting with to get things to come out right, as well as trying to get the directional eruption working, when I get a chance.

For now, the following change to the event trigger in the XML should limit eruptions to Obsidian/Sulfur on Rocky Hills, though iWeight needs fiddling with to adjust how often it happens. All my testing has been with it set to -1 to guarantee the event for testing.
Code:
<EventTriggerInfo>
           <Type>EVENTTRIGGER_VOLCANO_ERUPTION_EXTINCT</Type>
           <TriggerTexts>
               <TriggerText>
                   <Text>TXT_KEY_EVENT_TRIGGER_VOLCANO_EXTINCT</Text>
               </TriggerText>
           </TriggerTexts>
           <iPercentGamesActive>100</iPercentGamesActive>
           <iWeight>25</iWeight>
           <iMaxOurLandmass>-1</iMaxOurLandmass>
           <iNumPlotsRequired>1</iNumPlotsRequired>
           <iPlotType>-1</iPlotType>
           <TerrainsRequired>
               <TerrainType>TERRAIN_ROCKY</TerrainType>
           </TerrainsRequired>
           <BonusesRequired>
               <BonusType>BONUS_OBSIDIAN</BonusType>
               <BonusType>BONUS_SULPHUR</BonusType>
           </BonusesRequired>
           <Events>
               <Event>EVENT_VOLCANO_ERUPTION_1</Event>
           </Events>
           <bRecurring>1</bRecurring>
           <PythonCanDo>canDoNewVolcano</PythonCanDo>
           <PythonCallback>doVolcanoNewEruption</PythonCallback>
       </EventTriggerInfo>

The following addition to the Python changes the volcano plot to rocky flatland if it triggers on hills, to avoid the floating volcano issue. Note that the addition is just the block with 'if pPlot.isHills():', stuff before and after is just to help locate the right spot.
Code:
def doVolcanoPlot(pPlot):
  # pPlot = argsList[0]

  ## if terrain is a hill, level it by changing it to rocky flatland.
  if pPlot.isHills():
    pPlot.setPlotType(PlotTypes.PLOT_LAND, True, True)
    pPlot.setTerrainType(gc.getInfoTypeForString('TERRAIN_ROCKY'), True, True)

  ## if the terrain is not an active volcano make it so
  ft_volcano_dormant = gc.getInfoTypeForString('FEATURE_VOLCANO2')
  ft_volcano_active = gc.getInfoTypeForString('FEATURE_VOLCANO')
  iFeature = pPlot.getFeatureType
 
Last edited:
First attempt at making the volcano's eruption directional. If this works as I intend, it should pick one of the eight surrounding tiles, and destroy improvements on that tile and its two neighbors in the ring. It still damages units on all surrounding tiles, though that's easy enough to change if my (clunky) directionality code is working. I also added Suburbs to the list of improvements that leave Ruins, and Fishing Boats, Whaling Boats, and Whaling Ships to the improvements that are immune to volcanic eruptions. That said... what's the deal with various things looping coordinates from iX-1 to iX+2 when doing things to neighboring plots? Am I missing something obvious here, or is the coordinate system just weird? Alternatively, am I misinterpreting the way for loops work in Python? edit: Ok, so it's due to the way Python handles for loops. The following should do what I intended it to, and pick one of the eight directions in which to destroy improvements. There's probably more efficient or streamlined ways of doing this, but it doesn't throw any errors and seems to work as intended.
Code:
def doVolcanoNeighbouringPlots(pPlot):
  ## For each neighbouring plot
  ##   If there are units on the plot
  ##   If it is ocean change it to the correct coast
 
  ## Damage some improvements
  ## to do - start forest fire

  if pPlot == None or pPlot.isCity():
   return
 
  terrainCoast = gc.getInfoTypeForString("TERRAIN_COAST")
  terrainOcean = gc.getInfoTypeForString("TERRAIN_OCEAN")
  terrainPolarCoast = gc.getInfoTypeForString("TERRAIN_COAST_POLAR")
  terrainPolarOcean = gc.getInfoTypeForString("TERRAIN_OCEAN_POLAR")
  terrainTropicalCoast = gc.getInfoTypeForString("TERRAIN_COAST_TROPICAL")
  terrainTropicalOcean = gc.getInfoTypeForString("TERRAIN_OCEAN_TROPICAL")

  iX = pPlot.getX()
  iY = pPlot.getY()

  iMaximumImprovementsDestroyed = 3
 
  for iXLoop in range(iX-1, iX+2):
    for iYLoop in range(iY-1, iY+2):

      if not (iXLoop == iX and iYLoop == iY):
        tPlot = CyMap().plot(iXLoop, iYLoop)
       ## Change terrain
        if tPlot.isWater():
          if tPlot.getTerrainType() == terrainOcean:
            tPlot.setTerrainType(terrainCoast, True, True)
          if tPlot.getTerrainType() == terrainPolarOcean:
            tPlot.setTerrainType(terrainPolarCoast, True, True)
          if tPlot.getTerrainType() == terrainTropicalOcean:
            tPlot.setTerrainType(terrainTropicalCoast, True, True)
        
       # Damage units
        iNumberOfUnits = tPlot.getNumUnits()
        if iNumberOfUnits > 0:
         for i in range(0, iNumberOfUnits-1):
           pPlotUnit = tPlot.getUnit(i)
           if pPlotUnit.getDamage() < 50:
             pPlotUnit.setDamage(50, False)
           elif pPlotUnit.getDamage() < 75:
             pPlotUnit.setDamage(75, False)
           elif pPlotUnit.getDamage() < 90:
             pPlotUnit.setDamage(90, False)
           else:
             pPlotUnit.setDamage(99, False)

# Damage some improvements
  listRuins = [gc.getInfoTypeForString("IMPROVEMENT_COTTAGE"),
               gc.getInfoTypeForString("IMPROVEMENT_HAMLET"),
               gc.getInfoTypeForString("IMPROVEMENT_VILLAGE"),
               gc.getInfoTypeForString("IMPROVEMENT_TOWN"),
               gc.getInfoTypeForString("IMPROVEMENT_SUBURB")]
 
  iRuins = gc.getInfoTypeForString("IMPROVEMENT_CITY_RUINS")

  immuneImprovements = [gc.getInfoTypeForString("IMPROVEMENT_GRAIN_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_PINE_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_ROCK_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_SCAVENGING_CAMP"),
               gc.getInfoTypeForString("IMPROVEMENT_FRUIT_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_PLANT_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_FISHING_BOATS"),
               gc.getInfoTypeForString("IMPROVEMENT_WHALING_BOATS"),
               gc.getInfoTypeForString("IMPROVEMENT_WHALING_SHIPS")]

#Directional Volcano Eruption
  iDirection = gc.getGame().getSorenRandNum(7,"Volcano eruption direction")
  listPlots = []

  if iDirection == 0 or iDirection == 1 or iDirection == 7: #NW
    tPlot = CyMap().plot(iX-1, iY-1)
    if not tPlot.getImprovementType() in immuneImprovements:
      if tPlot.getImprovementType() != -1:
        listPlots.append(tPlot)
  if iDirection == 0 or iDirection == 1 or iDirection == 2: #N
    tPlot = CyMap().plot(iX, iY-1)
    if not tPlot.getImprovementType() in immuneImprovements:
      if tPlot.getImprovementType() != -1:
        listPlots.append(tPlot)
  if iDirection == 1 or iDirection == 2 or iDirection == 3: #NE
    tPlot = CyMap().plot(iX+1, iY-1)
    if not tPlot.getImprovementType() in immuneImprovements:
      if tPlot.getImprovementType() != -1:
        listPlots.append(tPlot)
  if iDirection == 0 or iDirection == 6 or iDirection == 7: #W
    tPlot = CyMap().plot(iX-1, iY)
    if not tPlot.getImprovementType() in immuneImprovements:
      if tPlot.getImprovementType() != -1:
        listPlots.append(tPlot)
  if iDirection == 2 or iDirection == 3 or iDirection == 4: #E
    tPlot = CyMap().plot(iX+1, iY-1)
    if not tPlot.getImprovementType() in immuneImprovements:
      if tPlot.getImprovementType() != -1:
        listPlots.append(tPlot)
  if iDirection == 5 or iDirection == 6 or iDirection == 7: #SW
    tPlot = CyMap().plot(iX-1, iY+1)
    if not tPlot.getImprovementType() in immuneImprovements:
      if tPlot.getImprovementType() != -1:
        listPlots.append(tPlot)
  if iDirection == 4 or iDirection == 5 or iDirection == 6: #S
    tPlot = CyMap().plot(iX, iY+1)
    if not tPlot.getImprovementType() in immuneImprovements:
      if tPlot.getImprovementType() != -1:
        listPlots.append(tPlot)
  if iDirection == 3 or iDirection == 4 or iDirection == 5: #SE
    tPlot = CyMap().plot(iX+1, iY+1)
    if not tPlot.getImprovementType() in immuneImprovements:
      if tPlot.getImprovementType() != -1:
        listPlots.append(tPlot)

  for i in range(len(listPlots)):
      if len(listPlots) > 0:
        plot = listPlots[gc.getGame().getSorenRandNum(len(listPlots), "Volcano event improvement destroyed")]
        iImprovement = plot.getImprovementType()
        if iPlayer > -1:
          szBuffer = localText.getText("TXT_KEY_EVENT_CITY_IMPROVEMENT_DESTROYED_NOOWNER", (gc.getImprovementInfo(iImprovement).getTextKey(), ))
          CyInterface().addMessage(iPlayer, False, gc.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_BOMBARDED", InterfaceMessageTypes.MESSAGE_TYPE_INFO, gc.getImprovementInfo(iImprovement).getButton(), gc.getInfoTypeForString("COLOR_RED"), plot.getX(), plot.getY(), True, True)
        if iImprovement in listRuins:
          plot.setImprovementType(iRuins)
        else:
          plot.setImprovementType(-1)
        listPlots.remove(plot)

#Old volcano improvement destruction code
#  listPlots = []
#  for iXLoop in range(iX-1, iX+2):
#    for iYLoop in range(iY-1, iY+2):
#      if not (iXLoop == iX and iYLoop == iY):
#        tPlot = CyMap().plot(iXLoop, iYLoop)
#        if not tPlot.getImprovementType() in immuneImprovements:
#          if tPlot.getImprovementType() != -1:
#            listPlots.append(tPlot)
#
#   iPlayer = pPlot.getOwner()
#    for i in range(iMaximumImprovementsDestroyed):
#      if len(listPlots) > 0:
#        plot = listPlots[gc.getGame().getSorenRandNum(len(listPlots), "Volcano event improvement destroyed")]
#        iImprovement = plot.getImprovementType()
#        if iPlayer > -1:
#          szBuffer = localText.getText("TXT_KEY_EVENT_CITY_IMPROVEMENT_DESTROYED_NOOWNER", (gc.getImprovementInfo(iImprovement).getTextKey(), ))
#          CyInterface().addMessage(iPlayer, False, gc.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_BOMBARDED", InterfaceMessageTypes.MESSAGE_TYPE_INFO, gc.getImprovementInfo(iImprovement).getButton(), gc.getInfoTypeForString("COLOR_RED"), plot.getX(), plot.getY(), True, True)
#        if iImprovement in listRuins:
#          plot.setImprovementType(iRuins)
#        else:
#          plot.setImprovementType(-1)
#        listPlots.remove(plot)
#  
#      if i == 1 and gc.getGame().getSorenRandNum(100, "Volcano event num improvements destroyed") < 50:
#        break
I don't suppose someone could add all of my volcano changes to the SVN for testing by a wider audience? :D
 
Last edited:
After a fair bit of reading up on Python and some assistance from the helpful folks over in the Quick Modding Questions thread, I present the rewritten, much-streamlined, hopefully bug-free Volcano code that only destroys improvements on three contiguous tiles adjacent to the volcano. edit: Nope, still buggy. Working on it. edit 2: Ok, so after trying several different methods of getting it to destroy improvements on the two tiles adjacent to the direction of the eruption, I can't seem to get it working as I want it to. I'm calling it quits for tonight, it's 2:30am and I have to be up in 4 hours for work.
Code:
##### VOLCANO C2C #####
def canDoNewVolcano(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  ft_volcano = gc.getInfoTypeForString('FEATURE_VOLCANO2')
  ft_volcano2 = gc.getInfoTypeForString('FEATURE_VOLCANO')
 
  if pPlot.isCity() or ((pPlot.getBonusType(-1) != -1) and ((pPlot.getBonusType(-1) != gc.getInfoTypeForString('BONUS_OBSIDIAN')) and (pPlot.getBonusType(-1) != gc.getInfoTypeForString('BONUS_SULPHUR')))) or (pPlot.getFeatureType() == ft_volcano) or (pPlot.getFeatureType() == ft_volcano2):
    return False

  return True
 
def canDoOldVolcano(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  ft_volcano = gc.getInfoTypeForString('FEATURE_VOLCANO2')
  ft_volcano2 = gc.getInfoTypeForString('FEATURE_VOLCANO')
 
  if pPlot.isCity() or not ((pPlot.getFeatureType() == ft_volcano) or (pPlot.getFeatureType() == ft_volcano2)):
    return False
 
  return True

def doVolcanoAdjustFertility(argsList):
  pPlot = argsList[0]
  extraFood = argsList[1]
  team = argsList[2]
  ## For each neighbouring plot
  ##   If extraFood is -1, 0 or 1 this is the amount of food to add to the plot

  if pPlot == None or pPlot.isCity():
   return
   
  if extraFood == 0:
    return
  elif extraFood < -1:
    extraFood = -1
  elif extraFood > 1:
    extraFood = 1

  iX = pPlot.getX()
  iY = pPlot.getY()

  for i in range(8):
    tPlot = CvUtil.plotDirection(iX, iY, DirectionTypes(i))
    if not tPlot.isNone():
        if not tPlot.isCity():
          CyGame().setPlotExtraYield(tPlot.getX(), tPlot.getY(), YieldTypes.YIELD_FOOD, extraFood)

def doVolcanoNeighbouringPlots(pPlot):
  ## For each neighbouring plot
  ##   If there are units on the plot
  ##   If it is ocean change it to the correct coast
 
  ## Damage some improvements
  ## to do - start forest fire

  if pPlot == None or pPlot.isCity():
   return
   
  terrainCoast = gc.getInfoTypeForString("TERRAIN_COAST")
  terrainOcean = gc.getInfoTypeForString("TERRAIN_OCEAN")
  terrainPolarCoast = gc.getInfoTypeForString("TERRAIN_COAST_POLAR")
  terrainPolarOcean = gc.getInfoTypeForString("TERRAIN_OCEAN_POLAR")
  terrainTropicalCoast = gc.getInfoTypeForString("TERRAIN_COAST_TROPICAL")
  terrainTropicalOcean = gc.getInfoTypeForString("TERRAIN_OCEAN_TROPICAL")

  iX = pPlot.getX()
  iY = pPlot.getY()

  for i in range(8):
    tPlot = CvUtil.plotDirection(iX, iY, DirectionTypes(i))
    if not tPlot.isNone():
      if tPlot.isWater():
        if tPlot.getTerrainType() == terrainOcean:
          tPlot.setTerrainType(terrainCoast, True, True)
        if tPlot.getTerrainType() == terrainPolarOcean:
          tPlot.setTerrainType(terrainPolarCoast, True, True)
        if tPlot.getTerrainType() == terrainTropicalOcean:
          tPlot.setTerrainType(terrainTropicalCoast, True, True)
           
       # Damage units
        iNumberOfUnits = tPlot.getNumUnits()
        if iNumberOfUnits > 0:
         for i in range(0, iNumberOfUnits-1):
           pPlotUnit = tPlot.getUnit(i)
           if pPlotUnit.getDamage() < 50:
             pPlotUnit.setDamage(50, False)
           elif pPlotUnit.getDamage() < 75:
             pPlotUnit.setDamage(75, False)
           elif pPlotUnit.getDamage() < 90:
             pPlotUnit.setDamage(90, False)
           else:
             pPlotUnit.setDamage(99, False)

# Directional damage to improvements
  listRuins = [gc.getInfoTypeForString("IMPROVEMENT_COTTAGE"),
               gc.getInfoTypeForString("IMPROVEMENT_HAMLET"),
               gc.getInfoTypeForString("IMPROVEMENT_VILLAGE"),
               gc.getInfoTypeForString("IMPROVEMENT_TOWN"),
               gc.getInfoTypeForString("IMPROVEMENT_SUBURB")]
  iRuins = gc.getInfoTypeForString("IMPROVEMENT_CITY_RUINS")

  immuneImprovements = [gc.getInfoTypeForString("IMPROVEMENT_GRAIN_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_PINE_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_ROCK_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_SCAVENGING_CAMP"),
               gc.getInfoTypeForString("IMPROVEMENT_FRUIT_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_PLANT_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_FISHING_BOATS"),
               gc.getInfoTypeForString("IMPROVEMENT_WHALING_BOATS"),
               gc.getInfoTypeForString("IMPROVEMENT_WHALING_SHIPS")]

  listVolcanoPlots = []
  listAdjacentPlots = []
  listAffectedPlots = []
 
  for i in range(8):
    tPlot = CvUtil.plotDirection(iX, iY, DirectionTypes(i))
    if not tPlot.isNone():
      listVolcanoPlots.append(tPlot)
 
  targetplot = listVolcanoPlots[gc.getGame().getSorenRandNum(len(listVolcanoPlots), "Volcano direction")]
  if not targetplot.getImprovementType() in immuneImprovements:
    if targetplot.getImprovementType() != -1:
      listAffectedPlots.append(targetplot)

#Missing code for targeting adjacent tiles goes here... once I figure out how to make it work right.

  for i in range(len(listAffectedPlots)):
      if len(listAffectedPlots) > 0:
        plot = listAffectedPlots[gc.getGame().getSorenRandNum(len(listAffectedPlots), "Volcano event improvement destroyed")]
        iPlayer = plot.getOwner()
        iImprovement = plot.getImprovementType()
        if iPlayer > -1:
          szBuffer = localText.getText("TXT_KEY_EVENT_CITY_IMPROVEMENT_DESTROYED_NOOWNER", (gc.getImprovementInfo(iImprovement).getTextKey(), ))
          CyInterface().addMessage(iPlayer, False, gc.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_BOMBARDED", InterfaceMessageTypes.MESSAGE_TYPE_INFO, gc.getImprovementInfo(iImprovement).getButton(), gc.getInfoTypeForString("COLOR_RED"), plot.getX(), plot.getY(), True, True)
        if iImprovement in listRuins:
          plot.setImprovementType(iRuins)
        else:
          plot.setImprovementType(-1)
        listAffectedPlots.remove(plot)


def doVolcanoPlot(pPlot):
  # pPlot = argsList[0]

  ## if terrain is a hill, level it by changing it to rocky flatland.
  if pPlot.isHills():
    pPlot.setPlotType(PlotTypes.PLOT_LAND, True, True)
    pPlot.setTerrainType(gc.getInfoTypeForString('TERRAIN_ROCKY'), True, True)

  ## if the terrain is not an active volcano make it so
  ft_volcano_dormant = gc.getInfoTypeForString('FEATURE_VOLCANO2')
  ft_volcano_active = gc.getInfoTypeForString('FEATURE_VOLCANO')
  iFeature = pPlot.getFeatureType

  ## Identify the existing feature so that the correct messages can be sent.
  if iFeature == ft_volcano_dormant:
   pPlot.setFeatureType(ft_volcano_active, 0)
  pPlot.setImprovementType(-1)
  pPlot.setBonusType(-1)

  ## Wound any units on the same plot as the volcano  
  iNumberOfUnits = pPlot.getNumUnits()
  if iNumberOfUnits > 0:
    for i in range(0, iNumberOfUnits-1):
      pPlotUnit = pPlot.getUnit(i)
      if pPlotUnit.getDamage() < 90: pPlotUnit.setDamage(90, False)
      else: pPlotUnit.setDamage(99, False)

      ## move them to safety
      iX = pPlot.getX()
      iY = pPlot.getY()
      for i in range(8):
        sPlot = CvUtil.plotDirection(iX, iY, DirectionTypes(i))
        if not sPlot.isNone():
          if pPlotUnit.canMoveInto(sPlot, False, False, True):
            pPlotUnit.setXY(sPlot.getX(), sPlot.getY(), False, True, True)

  if pPlot.isWater(): pPlot.setPlotType(PlotTypes.PLOT_LAND, True, True)
  pPlot.setFeatureType(ft_volcano_active, 0)
  pPlot.setImprovementType(-1)
  pPlot.setBonusType(-1)

  return
 
Last edited:
Keep working on it Tripped! You'll get it! I'd advise but DH would be better here since this is python. I'm starting to think you're going to need to be invited to the team here bro! As you can see, there's lots of things we need development assistance with.
 
I saw the comments on the Quick Modding thread - I knew I had forgotten to do something for C2C, add your code changes in.

I had see that different way of looping in some of Platyping's recent code but did not know how it worked.

Code:
def canDoNewVolcano(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  ft_volcano = gc.getInfoTypeForString('FEATURE_VOLCANO2')
  ft_volcano2 = gc.getInfoTypeForString('FEATURE_VOLCANO')

  if pPlot.isCity() or ((pPlot.getBonusType(-1) != -1) and ((pPlot.getBonusType(-1) != gc.getInfoTypeForString('BONUS_OBSIDIAN')) and (pPlot.getBonusType(-1) != gc.getInfoTypeForString('BONUS_SULPHUR')))) or (pPlot.getFeatureType() == ft_volcano) or (pPlot.getFeatureType() == ft_volcano2):
    return False

  return True
The conditional is an ungodly mess of ands/ors and parentheses, but I don't know if ands can be strung together without them properly, so I erred on the side of caution.


Basically return false if
  • the plot is a city or an existing volcano
  • or the plot has any bonus on it except Obsidian or Sulphur. Note: the barbarian player -1 can see all bonuses on the map and pPlot.getBonusType(player) returns the id of the bonus the player can see.
The None test on the plot needs to be added to canDoNewVolcano and canDoOldVolcano. This should not be necessary but as operating systems have progressed since 2004 (or whenever) adding the test stops a potential CTD.

For clarity the if could be broken down for future maintainers and a (very) minor speed improvement added
Code:
# Check the plot does not contain something that would stop a new volcano spawning on it
# - a city or an existing volcano of either type
  if pPlot == None or Plot.isCity() or (pPlot.getFeatureType() == ft_volcano) or (pPlot.getFeatureType() == ft_volcano2):
    return False

# Allow the volcano if there are no bonuses on the plot. 
  iBonus = pPlot.getBonusType(-1)
  if  (iBonus == -1): # no bonus on the plot
    return True
# Allow the volcano if the bonuses OBSIDIAN or SULPHUR are on the plot
  if(iBonus == gc.getInfoTypeForString('BONUS_OBSIDIAN')] or (iBonus == gc.getInfoTypeForString('BONUS_SULPHUR')) :
    return True

return False

The code was a mess when I started working on it and being a maintenance programmer back in the late 1970's means that I just comment out code until I finish working on it and get it working right. It was punched cards back then. At least I did not get to work using punched tape! Cut and Paste was actually used sometimes with the tape.:rolleyes:
 
Code:
  listVolcanoPlots = []
  listAdjacentPlots = []
  listAffectedPlots = []
 
  for i in range(8):
    tPlot = CvUtil.plotDirection(iX, iY, DirectionTypes(i))
    if not tPlot.isNone():
      listVolcanoPlots.append(tPlot)
Do we know for certain how this goes, especially on the edges, ie poles?

Is it always

123
4x5
678
or some constant variation?

Are the edges

---
1x2
345

or

---
4x5
678
?

You may need to go back to the double loop for this bit.

edit Duh! of course we know how it goes it is in the definition of DirectionTypes. As long as that does not change we can know which plots are next to each other. That is what you get when I read something and ask questions before I think on it enough (especially if it is before my morning coffee:coffee:).
 
Last edited:
For clarity the if could be broken down for future maintainers and a (very) minor speed improvement added
Oh yeah, I meant to go back through and clean it up once I got it working, and use something along the lines of if iBonus in listVolcanicBonuses to make it easier to add or remove resources volcanoes are allowed to trigger on, but got sidetracked by the directional eruptions and never got around to it.

As far as the directional eruptions goes, I'm certain I can get it to work if I use a big clunky set of conditionals, but I was hoping to come up with a more elegant solution. Unfortunately most of those ran into issues due to my not being familiar enough with Python. What I want it to do is take the four tiles cardinally adjacent to the targeted tile and grab the two that are in the 8-tile ring around the volcano itself, then do things with them, but for whatever reason if plot in listVolcanoPlots: do stuff doesn't work. Presumably there's some Python function that returns the intersection of the two lists/sets that I haven't figured out how to use yet. This code shouldn't even get called often enough for efficiency to really be an issue, but at this point I'm just trying things as part of getting a feel for C4's Python API and Python in general.
 
The other problem is that there are a number of Natural Wonders which are also volcanoes so they should be able to erupt and go dormant but should not go extinct. They should also use the damage functions.

However we don't have two graphics for when they are erupting and when not! This also makes the +/- of food difficult to identify. There is supposed to be a way to link events so it should be possible to have a set for on and off for each Natural Wonder I suppose.
 
Setting up an event to trigger the same volcanic eruptions on the appropriate Natural Wonders should be easy enough, though I think Krakatoa would take a bit of extra code to keep it from changing the water tiles around and under it. Off the top of my head I can think of a few crude ways to set up active/dormant cycling for them, for example having them plant or remove a dummy resource, to denote activity. It would still be visually indistinguisable, but honestly, I don't think it makes any difference to gameplay. The +1:food: is really more in the nature of a side benefit and compensation for the destruction of improvements than anything that really guides decision-making, IMO.

Idle additional thought, a new vicinity building for Volcanoes + Fresh Water, Hot Springs, with health and disease control effects.
 
Ok, I think I've finally got things working. Here's the relevant portion of the modified Civ4EventTriggerInfos.xml in Assets/XML/Events in order to allow extinct volcanoes (or any other Rocky/Hills tile with Obsidian or Sulfur) to erupt:
Code:
<EventTriggerInfo>
           <Type>EVENTTRIGGER_VOLCANO_ERUPTION_EXTINCT</Type>
           <TriggerTexts>
               <TriggerText>
                   <Text>TXT_KEY_EVENT_TRIGGER_VOLCANO_EXTINCT</Text>
               </TriggerText>
           </TriggerTexts>
           <iPercentGamesActive>100</iPercentGamesActive>
           <iWeight>20</iWeight>
           <iMaxOurLandmass>-1</iMaxOurLandmass>
           <iNumPlotsRequired>1</iNumPlotsRequired>
           <iPlotType>-1</iPlotType>
           <TerrainsRequired>
               <TerrainType>TERRAIN_ROCKY</TerrainType>
           </TerrainsRequired>
           <BonusesRequired>
               <BonusType>BONUS_OBSIDIAN</BonusType>
               <BonusType>BONUS_SULPHUR</BonusType>
           </BonusesRequired>
           <Events>
               <Event>EVENT_VOLCANO_ERUPTION_1</Event>
           </Events>
           <bRecurring>1</bRecurring>
           <PythonCanDo>canDoNewVolcano</PythonCanDo>
           <PythonCallback>doVolcanoNewEruption</PythonCallback>
       </EventTriggerInfo>
Here's the Python code for CvRandomEventInterface.py in Assets/Python/EntryPoints:
Code:
##### VOLCANO C2C #####
def canDoNewVolcano(argsList):
  # Checks the plot to see if a new volcano can be formed
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  if pPlot.isNone():
    return False
 
  # List of features that block new volcanoes
  listFeatures = [gc.getInfoTypeForString('FEATURE_PLATY_AUYANTEPUI'),
                  gc.getInfoTypeForString('FEATURE_PLATY_GREAT_BARRIER'),
                  gc.getInfoTypeForString('FEATURE_GREAT_BARRIER_WITH_BEACON'),
                  gc.getInfoTypeForString('FEATURE_GREAT_BARRIER_WITH_LIGHTHOUSE'),
                  gc.getInfoTypeForString('FEATURE_PLATY_EVEREST'),
                  gc.getInfoTypeForString('FEATURE_PLATY_FUJI'),
                  gc.getInfoTypeForString('FEATURE_PLATY_AURORA'),
                  gc.getInfoTypeForString('FEATURE_PLATY_BAIKAL'),
                  gc.getInfoTypeForString('FEATURE_PLATY_BARRINGER_CRATER'),
                  gc.getInfoTypeForString('FEATURE_PLATY_BASALT_ORGAN'),
                  gc.getInfoTypeForString('FEATURE_PLATY_DEVILS_TABLE'),
                  gc.getInfoTypeForString('FEATURE_PLATY_SOPKA'),
                  gc.getInfoTypeForString('FEATURE_PLATY_KRAKATOA'),
                  gc.getInfoTypeForString('FEATURE_PLATY_NGORONGORO_CRATER'),
                  gc.getInfoTypeForString('FEATURE_PLATY_NUKUORO'),
                  gc.getInfoTypeForString('FEATURE_PLATY_PAMUKKALE'),
                  gc.getInfoTypeForString('FEATURE_PLATY_PRAVCICKA_BRANA'),
                  gc.getInfoTypeForString('FEATURE_PLATY_SHARK_BAY'),
                  gc.getInfoTypeForString('FEATURE_PLATY_SINAI'),
                  gc.getInfoTypeForString('FEATURE_PLATY_SUGARLOAF'),
                  gc.getInfoTypeForString('FEATURE_PLATY_ULURU'),
                  gc.getInfoTypeForString('FEATURE_PLATY_VICTORIA_FALLS'),
                  gc.getInfoTypeForString('FEATURE_PLATY_KILIMANJARO'),
                  gc.getInfoTypeForString('FEATURE_PLATY_DEAD_SEA'),
                  gc.getInfoTypeForString('FEATURE_VOLCANO'),
                  gc.getInfoTypeForString('FEATURE_VOLCANO2')]

  # List of resources that count as volcanic, and thus do not block volcanoes.
  listVolcanicResources = [gc.getInfoTypeForString('BONUS_OBSIDIAN'),
                           gc.getInfoTypeForString('BONUS_SULPHUR')]

  if pPlot.isCity():
    return False
  if pPlot.getFeatureType() in listFeatures:
    return False
  if pPlot.getBonusType(-1) == -1:
    return True
  if pPlot.getBonusType(-1) in listVolcanicResources:
    return True

  return False
 
def canDoOldVolcano(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)

  if pPlot.isNone():
    return False

  # List of features that are volcanoes
  listVolcanoes = [gc.getInfoTypeForString('FEATURE_PLATY_FUJI'),
                  gc.getInfoTypeForString('FEATURE_PLATY_SOPKA'),
                  gc.getInfoTypeForString('FEATURE_PLATY_KRAKATOA'),
                  gc.getInfoTypeForString('FEATURE_PLATY_KILIMANJARO'),
                  gc.getInfoTypeForString('FEATURE_VOLCANO'),
                  gc.getInfoTypeForString('FEATURE_VOLCANO2')]

  if pPlot.isCity():
    return False
  elif pPlot.getFeatureType() in listVolcanoes:
    return True
 
  return False

def doVolcanoAdjustFertility(argsList):
  pPlot = argsList[0]
  extraFood = argsList[1]
  team = argsList[2]
  ## For each neighbouring plot
  ##   If extraFood is -1, 0 or 1 this is the amount of food to add to the plot

  if pPlot.isNone():
    return
 
  if extraFood == 0:
    return
  elif extraFood < -1:
    extraFood = -1
  elif extraFood > 1:
    extraFood = 1

  iX = pPlot.getX()
  iY = pPlot.getY()

  for i in range(8):
    tPlot = CvUtil.plotDirection(iX, iY, DirectionTypes(i))
    if not tPlot.isNone():
      if not tPlot.isCity():
        CyGame().setPlotExtraYield(tPlot.getX(), tPlot.getY(), YieldTypes.YIELD_FOOD, extraFood)

def doVolcanoNeighbouringPlots(pPlot):
  # Directional eruption that picks an adjacent valid plot to erupt towards.
  # It then affects that plot and its two neighbors in the ring of 8 plots surrounding the volcano
  # Affected plots have units damaged, improvements destroyed, and oceans changed to coast.
  # To do - start forest fire

  if pPlot.isNone():
    return
 
  terrainCoast = gc.getInfoTypeForString("TERRAIN_COAST")
  terrainSea = gc.getInfoTypeForString("TERRAIN_SEA")
  terrainOcean = gc.getInfoTypeForString("TERRAIN_OCEAN")
  terrainPolarCoast = gc.getInfoTypeForString("TERRAIN_COAST_POLAR")
  terrainPolarSea = gc.getInfoTypeForString("TERRAIN_SEA_POLAR")
  terrainPolarOcean = gc.getInfoTypeForString("TERRAIN_OCEAN_POLAR")
  terrainTropicalCoast = gc.getInfoTypeForString("TERRAIN_COAST_TROPICAL")
  terrainTropicalSea = gc.getInfoTypeForString("TERRAIN_SEA_TROPICAL")
  terrainTropicalOcean = gc.getInfoTypeForString("TERRAIN_OCEAN_TROPICAL")

  iX = pPlot.getX()
  iY = pPlot.getY()

  # List of improvements that leave ruins behind when destroyed
  listRuins = [gc.getInfoTypeForString("IMPROVEMENT_COTTAGE"),
               gc.getInfoTypeForString("IMPROVEMENT_HAMLET"),
               gc.getInfoTypeForString("IMPROVEMENT_VILLAGE"),
               gc.getInfoTypeForString("IMPROVEMENT_TOWN"),
               gc.getInfoTypeForString("IMPROVEMENT_SUBURB")]
  iRuins = gc.getInfoTypeForString("IMPROVEMENT_CITY_RUINS")

  # List of improvements that are unaffected by eruption
  immuneImprovements = [gc.getInfoTypeForString("IMPROVEMENT_GRAIN_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_PINE_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_ROCK_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_SCAVENGING_CAMP"),
               gc.getInfoTypeForString("IMPROVEMENT_FRUIT_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_PLANT_GATHERER"),
               gc.getInfoTypeForString("IMPROVEMENT_FISHING_BOATS"),
               gc.getInfoTypeForString("IMPROVEMENT_WHALING_BOATS"),
               gc.getInfoTypeForString("IMPROVEMENT_WHALING_SHIPS"),
               gc.getInfoTypeForString("IMPROVEMENT_CITY_RUINS")]

  listVolcanoPlots = []
  listVolcanoPlotsX = []
  listVolcanoPlotsY = []
  listAdjacentPlots = []
  listAffectedPlots = []

  # Sets up lists for plots that are adjacent to the volcano
  for i in range(8):
    plot = CvUtil.plotDirection(iX, iY, DirectionTypes(i))
    if not plot.isNone():
      listVolcanoPlots.append(plot)
      listVolcanoPlotsX.append(plot.getX())
      listVolcanoPlotsY.append(plot.getY())

  # Select a target plot
  targetplot = listVolcanoPlots[gc.getGame().getSorenRandNum(len(listVolcanoPlots), "Volcano direction")]
  listAffectedPlots.append(targetplot)

  listAdjacentPlots.append(CvUtil.plotDirection(targetplot.getX(), targetplot.getY(), DirectionTypes.DIRECTION_NORTH))
  listAdjacentPlots.append(CvUtil.plotDirection(targetplot.getX(), targetplot.getY(), DirectionTypes.DIRECTION_SOUTH))
  listAdjacentPlots.append(CvUtil.plotDirection(targetplot.getX(), targetplot.getY(), DirectionTypes.DIRECTION_EAST))
  listAdjacentPlots.append(CvUtil.plotDirection(targetplot.getX(), targetplot.getY(), DirectionTypes.DIRECTION_WEST))

  # If plot is in the ring around the volcano, add to the list of affected plots
  for i in range(len(listAdjacentPlots)):                
    plot = listAdjacentPlots[i]
    if not plot.isNone():
      if (plot.getX() in listVolcanoPlotsX) and (plot.getY() in listVolcanoPlotsY):
        if (plot.getX() != iX) and (plot.getY() != iY):
          listAffectedPlots.append(plot)

  #Loops through the list of affected plots applying eruption effects
  for i in range(len(listAffectedPlots)):
    if len(listAffectedPlots) > 0:
      plot = listAffectedPlots[gc.getGame().getSorenRandNum(len(listAffectedPlots), "Volcano event improvement destroyed")]
      iPlayer = plot.getOwner()
      iImprovement = plot.getImprovementType()

      # Destroys improvements if the plot is not a city, and if the improvement is not immune
      if not(plot.isCity()) and not(iImprovement in immuneImprovements):
        if iPlayer > -1:
          szBuffer = localText.getText("TXT_KEY_EVENT_CITY_IMPROVEMENT_DESTROYED_NOOWNER", (gc.getImprovementInfo(iImprovement).getTextKey(), ))
          CyInterface().addMessage(iPlayer, False, gc.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_BOMBARDED", InterfaceMessageTypes.MESSAGE_TYPE_INFO, gc.getImprovementInfo(iImprovement).getButton(), gc.getInfoTypeForString("COLOR_RED"), plot.getX(), plot.getY(), True, True)
        if iImprovement in listRuins:
          plot.setImprovementType(iRuins)
        else:
          plot.setImprovementType(-1)

      # Damages units
      iNumberOfUnits = plot.getNumUnits()
      if iNumberOfUnits > 0:
        for iUnit in range(iNumberOfUnits):
     pPlotUnit = plot.getUnit(iUnit)
          if pPlotUnit.getDamage() < 50:
            pPlotUnit.setDamage(50, False)
          elif pPlotUnit.getDamage() < 75:
            pPlotUnit.setDamage(75, False)
          elif pPlotUnit.getDamage() < 90:
            pPlotUnit.setDamage(90, False)
          else:
            pPlotUnit.setDamage(99, False)

      # If affected plot is Ocean or Sea, change it to Coast
      if plot.isWater():
        if plot.getTerrainType() == terrainOcean or plot.getTerrainType() == terrainSea:
          plot.setTerrainType(terrainCoast, True, True)
        if plot.getTerrainType() == terrainPolarOcean or plot.getTerrainType() == terrainPolarSea:
          plot.setTerrainType(terrainPolarCoast, True, True)
        if plot.getTerrainType() == terrainTropicalOcean or plot.getTerrainType() == terrainTropicalSea:
          plot.setTerrainType(terrainTropicalCoast, True, True)

      # Remove processed plots from list
      listAffectedPlots.remove(plot)


def doVolcanoPlot(pPlot):
  if pPlot.isNone():
     return

  # List of features that are volcanoes
  listVolcanoes = [gc.getInfoTypeForString('FEATURE_PLATY_FUJI'),
                  gc.getInfoTypeForString('FEATURE_PLATY_SOPKA'),
                  gc.getInfoTypeForString('FEATURE_PLATY_KRAKATOA'),
                  gc.getInfoTypeForString('FEATURE_PLATY_KILIMANJARO'),
                  gc.getInfoTypeForString('FEATURE_VOLCANO'),
                  gc.getInfoTypeForString('FEATURE_VOLCANO2')]
  ft_volcano_dormant = gc.getInfoTypeForString('FEATURE_VOLCANO2')
  ft_volcano_active = gc.getInfoTypeForString('FEATURE_VOLCANO')

  # if terrain is a hill, level it by changing it to rocky flatland.
  if pPlot.isHills():
    pPlot.setPlotType(PlotTypes.PLOT_LAND, True, True)
    pPlot.setTerrainType(gc.getInfoTypeForString('TERRAIN_ROCKY'), True, True)

  iFeature = pPlot.getFeatureType()
  pPlot.setImprovementType(-1)
  pPlot.setBonusType(-1)

  # if the terrain is not an active volcano make it so
  if iFeature == ft_volcano_dormant:
    pPlot.setFeatureType(ft_volcano_active, 0)
  elif not(iFeature in listVolcanoes):
    pPlot.setFeatureType(ft_volcano_active, 0)

  # Wound any units on the same plot as the volcano
  iNumberOfUnits = pPlot.getNumUnits()
  if iNumberOfUnits > 0:
    for i in range(0, iNumberOfUnits):
      pPlotUnit = pPlot.getUnit(i)
      if pPlotUnit.getDamage() < 90: pPlotUnit.setDamage(90, False)
      else: pPlotUnit.setDamage(99, False)

      # move them to safety
      iX = pPlot.getX()
      iY = pPlot.getY()
      for i in range(8):
        sPlot = CvUtil.plotDirection(iX, iY, DirectionTypes(i))
        if not sPlot.isNone():
          if pPlotUnit.canMoveInto(sPlot, False, False, True):
            pPlotUnit.setXY(sPlot.getX(), sPlot.getY(), False, True, True)

  if pPlot.isWater(): pPlot.setPlotType(PlotTypes.PLOT_LAND, True, True)

  return

def doVolcanoReport(argsList):
  pPlot = argsList[0]
  szBuffer = argsList[1]
  ft_volcano_dormant = gc.getInfoTypeForString('FEATURE_VOLCANO2')
  ft_volcano_active = gc.getInfoTypeForString('FEATURE_VOLCANO')

  # report message to any one who can see this plot
  iMaxPlayer = gc.getMAX_CIV_PLAYERS ()
  for i in xrange(iMaxPlayer):
    loopPlayer = gc.getPlayer(i)
    if loopPlayer.isHuman() and loopPlayer.isAlive() and pPlot.isVisible(loopPlayer.getTeam(), False):
      CyInterface().addMessage(loopPlayer.getID(), False, gc.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_BOMBARDED", InterfaceMessageTypes.MESSAGE_TYPE_INFO, gc.getFeatureInfo(ft_volcano_active).getButton(), gc.getInfoTypeForString("COLOR_RED"), pPlot.getX(), pPlot.getY(), True, True)
 
      if pPlot.isInViewport():
        point = pPlot.getPoint()
        CyEngine().triggerEffect(gc.getInfoTypeForString('EFFECT_ARTILLERY_SHELL_EXPLODE'),point)
        CyAudioGame().Play3DSound("AS3D_UN_GRENADE_EXPLODE",point.x,point.y,point.z)

 
def doVolcanoNewEruption(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  if pPlot.isNone():
    return
  player = gc.getPlayer(kTriggeredData.ePlayer)
  team = player.getTeam()

  doVolcanoPlot(pPlot)
  doVolcanoNeighbouringPlots(pPlot)
  doVolcanoAdjustFertility((pPlot, 1, team))
  doVolcanoReport((pPlot, BugUtil.getPlainText("TXT_KEY_EVENT_TRIGGER_VOLCANO_NEW")))

  return

def doVolcanoExistingEruption(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  if pPlot.isNone():
    return

  doVolcanoNeighbouringPlots(pPlot)
  doVolcanoReport((pPlot, BugUtil.getPlainText("TXT_KEY_EVENTTRIGGER_VOLCANO_ACTIVE")))

  return

def doVolcanoDormantEruption(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  if pPlot.isNone():
    return
  player = gc.getPlayer(kTriggeredData.ePlayer)
  team = player.getTeam()
 
  doVolcanoPlot(pPlot)
  doVolcanoNeighbouringPlots(pPlot)
  doVolcanoAdjustFertility((pPlot, 1, team))
  doVolcanoReport((pPlot, BugUtil.getPlainText("TXT_KEY_EVENT_TRIGGER_VOLCANO_EXTINCT")))

  return

def doVolcanoExtinction(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  if pPlot.isNone():
    return
  pPlot.setPlotType(PlotTypes.PLOT_HILLS, True, True)
  pPlot.setTerrainType(gc.getInfoTypeForString('TERRAIN_ROCKY'), True, True)
  player = gc.getPlayer(kTriggeredData.ePlayer)
  city = player.getCity(kTriggeredData.iCityId)

  team = player.getTeam()
  techteam = gc.getTeam(player.getTeam())
  iX = pPlot.getX()
  iY = pPlot.getY()
 
 
  if gc.getGame().getSorenRandNum(100, 'Volcanic minerals chance') < 50:
    iBonus = gc.getInfoTypeForString('BONUS_OBSIDIAN')
    pPlot.setBonusType(iBonus)
    itechresource = gc.getInfoTypeForString("TECH_STONE_TOOLS")
  else:
    iBonus = gc.getInfoTypeForString('BONUS_SULPHUR')
    pPlot.setBonusType(iBonus)
    itechresource = gc.getInfoTypeForString("TECH_ANCIENT_BALLISTICS")

  if techteam.isHasTech(itechresource) and (pPlot.isVisible(team, False)):
      doVolcanoReport((pPlot, BugUtil.getPlainText("TXT_KEY_MISC_DISCOVERED_NEW_RESOURCE_VOLCANO")))
 
def doVolcanoSleep(argsList):
  kTriggeredData = argsList[0]
  pPlot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
  pPlot.setFeatureType(gc.getInfoTypeForString('FEATURE_VOLCANO2'), 0)
  player = gc.getPlayer(kTriggeredData.ePlayer)
  team = player.getTeam()
  doVolcanoAdjustFertility((pPlot, -1, team))
  doVolcanoReport((pPlot, BugUtil.getPlainText("TXT_KEY_EVENT_TRIGGER_VOLCANO_DORMANT")))

def getHelpVolcanoEruption1(argsList):
  iEvent = argsList[0]
  kTriggeredData = argsList[1]

  szHelp = localText.getText("TXT_KEY_EVENT_VOLCANO_ERUPTION_1_HELP", ())

  return szHelp
 
def getHelpVolcanoSleep(argsList):
  iEvent = argsList[0]
  kTriggeredData = argsList[1]

  szHelp = localText.getText("TXT_KEY_EVENT_VOLCANO_SLEEP_HELP", ())

  return szHelp
 
def getHelpVolcanoExtinction(argsList):
  iEvent = argsList[0]
  kTriggeredData = argsList[1]

  szHelp = localText.getText("TXT_KEY_EVENT_VOLCANO_EXTINCTION_HELP", ())

  return szHelp
I also did a bit of preparatory work for eventual expansion to include the Natural Wonder volcanoes. Some bits of the code are still perhaps a bit clunky, but everything appears to work as intended. Also, note that the way I rewrote it, it now will damage units in cities if they happen to be one of the tiles affected. Future changes should also be somewhat easier to make with the way I set things up. My own testing so far just consisted of forcing the extinct volcano eruption event to occur every turn on any qualifying tile, then setting up a field of Rocky/Obsidian/Hills surrounded by Towns. I'd greatly appreciate any feedback on how things work out.

edit: Something went wrong with Dormant volcano code. Fiddled with it some more and it seems fixed now.
edit 2: Damn. Maybe not fixed. Will have to look at things some more tomorrow.
 
Last edited:
Back
Top Bottom