Volcanoes

Derp. Apparently while moving the improvement destruction code into the new loop I forgot to tell it to check that there's actually an improvement there. This time it's working for real, and I've forced triggering of all three current volcano conditions (active, dormant, extinct) without any problems. I'd like to get this version onto the SVN and tested by a wider group before I begin experimenting with having the Natural Wonder volcanoes erupt.

This should go into Assets/Python/EntryPoints/CvRandomEventInterface.py, replacing all of the current Volcano C2C code. The Civ4EventTriggerInfos.xml is unchanged from my previous post.
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() 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):
  # 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 iImprovement != -1:
        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 or peak, level it by changing it to rocky flatland.
  if pPlot.isHills() or pPlot.isPeak():
    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
To do list:
  • Let Natural Wonder volcanoes erupt, with an active/dormant cycle.
  • Additional effects on cities caught in eruptions? Population loss, building loss, etc.
  • Volcanic island formation?
  • Burning forests, jungles, etc.
 
Last edited:
To do list:
  • Let Natural Wonder volcanoes erupt, with an active/dormant cycle.
  • Additional effects on cities caught in eruptions? Population loss, building loss, etc.
  • Volcanic island formation?
  • Burning forests, jungles, etc.

Code now in my version for a test and then move onto SVN probably later today.

Cities being caught in volcanic eruptions is considered "unfun". We already have a number of events that do remove a single building and that is annoying. The current code does damage units in cities, it is annoying but at least on "build-ups" units heal over time now. They did not at one stage and the volcano would kill them all especially that one volcano that erupted over and over again;).
 
Cities being caught in volcanic eruptions is considered "unfun".
I think eventually we can get C2C to a place of a dynamic enough start where we can send the city back to a nomadic state so the player doesn't get completely screwed up but it does destroy, as it should, the city itself.
 
A minor point just fyi. I got one code error when I tried with this code. Incorrect indentation. Looking at what is in the code block there error is there also. Usually it is when a mixture of tabs and spaces are used for the indentation. This is because while a tab may be three spaces on one persons machine it maybe 4 spaces on another. In Python white space is part of the code if at the start of the line. In this case it is even possible that any tabs were converted to spaces by these forums when it was included.
Code:
      # 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)
 
A minor point just fyi. I got one code error when I tried with this code. Incorrect indentation. Looking at what is in the code block there error is there also. Usually it is when a mixture of tabs and spaces are used for the indentation. This is because while a tab may be three spaces on one persons machine it maybe 4 spaces on another. In Python white space is part of the code if at the start of the line. In this case it is even possible that any tabs were converted to spaces by these forums when it was included.
That looks like the result of forum formatting screwing something up, the line beginning pPlotUnit should be indented to the same level as the following if block, as the first level inside the for loop. I don't think I made any actual changes to that block of code, I just cut/pasted it from the original, and everything worked fine on my machine. That said, I'll be sure to pay attention to the spaces/tabs issues in the future; I'm fairly sure I used entirely spaces rather than tabs in everything I wrote, but copy/pasted code may well be the cause of the issue as well.

Everything to do with the actual code was running without any problems that I could tell on my machine. I did spot a couple of volcanoes that are on top of peaks in my game, but that might be the result of poorly set up event triggers or of mapscript-related issues. Far easier to fiddle with that after it's confirmed there's no bugs with the rest of the code.

Presumably this will be less of a problem if I'm committing directly to SVN rather than going through the forums.
 
Last edited:
That looks like the result of forum formatting screwing something up, the line beginning pPlotUnit should be indented to the same level as the following if block, as the first level inside the for loop. I don't think I made any actual changes to that block of code, I just cut/pasted it from the original, and everything worked fine on my machine. That said, I'll be sure to pay attention to the spaces/tabs issues in the future; I'm fairly sure I used entirely spaces rather than tabs in everything I wrote, but copy/pasted code may well be the cause of the issue as well.

Everything to do with the actual code was running without any problems that I could tell on my machine. I did spot a couple of volcanoes that are on top of peaks in my game, but that might be the result of poorly set up event triggers or of mapscript-related issues. Far easier to fiddle with that after it's confirmed there's no bugs with the rest of the code.

Presumably this will be less of a problem if I'm committing directly to SVN rather than going through the forums.
I was struggling with python work until I realized that notepad++ was causing me some serious problems with indentation. It's best to use a dedicated python program for working with py. I don't know if that has anything to do with anything here, just sharing the experience in case it's somehow helpful.
 
Top Bottom