
wgen <- null

class WorldGenerator extends WorldElement
{
  topo=null
  geo=null

  avgh=0
  rndh=10
  erosion=null
  doErosion=true

  grassMask=null
  seaLevel=null

  groups=null

  worldTags=null


  testTopo=null
  testMask=null
  testMaskName="mask"


  constructor()
  {
    wgen=this

    topo=[]
    geo=[]
    groups={}
    worldTags=[]

    erosion=
    {
      droplets=90<<10,
      Kq=0.1, Kr=0.9, Kd=0.9,
      periodStep=10<<10,
    }
  }


  function getGroup(name)
  {
    if (!(name in groups)) groups[name]<-[]
    return groups[name];
  }


  function addElem(e, grp)
  {
    if (e==null) return;
    if (typeof e=="array")
      foreach (s in e) addElem(s, grp)
    else
      getGroup(grp).append(e)
  }


  function addGeo(e)
  {
    if (e==null) return;
    if (typeof e=="array")
      foreach (s in e) addGeo(s)
    else
      geo.append(e)
  }


  function generate()
  {
    show_splash("building topology", -1)
    foreach (t in topo) t.makeMask()

    geo.sort(@(a, b) a.priority<=>b.priority)
    foreach (i,g in geo)
    {
      show_splash("building terrain shape", i/geo.len().tofloat())
      g.startGen()
    }

    local minmap=HmapMask(avgh-rndh)
    local maxmap=HmapMask(avgh+rndh)
    foreach (g in geo) g.applyMinMax(minmap, maxmap)

    show_splash("generating terrain", -1)
    local nmap=HmapMask.fromSimplexNoise(1, 4, 6, null, 0.5)

    //== mix noise map from multiple maps by element

    local nlim=nmap.calcMinMax()
    local nscl=1.0/(nlim[1]-nlim[0])
    local nofs=-nlim[0]*nscl

    local hmap=HmapMask(function(x, z)
    {
      local mi=minmap.getraw(x, z)
      local ma=maxmap.getraw(x, z)
      local n=nmap.getraw(x, z)
      n=n*nscl+nofs
      return (ma-mi)*n+mi;
    })

    apply_height_mask(hmap, null)

    // erosion
    chem_layers_by_height(irnd()%3)
    foreach (g in geo) g.beforeErosion()

    if (doErosion)
    {
      local ep=clone erosion
      ep.onPeriod <- function()
      {
        foreach (g in geo) g.duringErosion()
        apply_height_mask(minmap, @(h, m) h<m ? m:h)
        apply_height_mask(maxmap, @(h, m) h>m ? m:h)
      }.bindenv(this)

      common_erosion(ep.droplets, ep)

      ep.onPeriod()
    }

    foreach (g in geo) g.afterErosion()

    recolor_chem_layers()

    if (seaLevel!=null)
    {
      local level=seaLevel
      apply_water_mask(HmapMask.fromHeight(), @(w, h) h<level ? level-h : 0)
    }

    add_grass_by_slope(get_pal_color(cur_world_pal, "grass"),
      rndf(0.7, 0.85), rndf(0.85, 0.99), 0.2, grassMask?grassMask.mask:null)

    recenter_hmap()
    mark_water(0.1, MARK_WATER, 0)

    if (testTopo)
      testMask=testTopo[testMaskName].shiftedCopy(testTopo.ox, testTopo.oz)

    if (testMask)
    {
      local m=testMask
      apply_height_mask(HmapMask(@(x, z) m.get(x, z)?2:0),
        @(h, v) v)
    }
  }
}


//ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ//


class PillarsWorldShaper extends WorldElement
{
  tags="WorldShaper pillars generic"

  function shapeWorld(wg)
  {
    local high=[], low=[]
    foreach (t in wg.getGroup("topTopo"))
      if (frnd()<0.3) low.append(t)
      else high.append(t)

    for (local n=irnd()%5+3; n>0 || high.len()<12; --n)
    {
      local at=0
      if (high.len()) at=irnd()%high.len()
      high.insert(at, TopoIsland(TopoCircle(rndf(3, 20)), { extraSteps=5 }))
    }

    local pillars=TopoScatter(high)
    local lowPlaces=TopoScatter(low)

    wg.avgh=0
    wg.rndh=10
    wg.erosion.Kr=0.5
    wg.erosion.Kd=0.5
    wg.topo.append(TopoAppend([pillars, lowPlaces], { border=5 }))

    local strata=[[], [], [], []]

    foreach (t in high) strata[irnd()&3].append(t)

    foreach (si,s in strata)
    {
      if (!s.len()) continue;
      local t=TopoBitwise(s, 14)
      wg.topo.append(t)
      wg.addGeo(GeoSkeleton(t,
        { avgh=(si+1+srnd()*0.25)*10, blendMin=-0.5, priority=0 }))
    }

    local filler=TopoErode(TopoInverse(pillars), { iterations=12 })
    filler=TopoBitwise([filler, lowPlaces], 2)
    wg.topo.append(filler)
    WELEM("!Filler")({ targetTopo=filler }).addToWGen(wg, "fill")

    local noGrass=[]
    foreach (t in high) if (frnd()<0.5) noGrass.append(t)
    wg.grassMask=TopoErode(TopoInverse(TopoBitwise(noGrass, 14)), { iterations=5 })
    wg.topo.append(wg.grassMask)

    wg.generateShaped()
  }
}


//ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ//


class IslandsWorldShaper extends WorldElement
{
  tags="WorldShaper islands generic main_menu"

  function shapeWorld(wg)
  {
    // build islands
    local places=[]
    foreach (t in wg.getGroup("topTopo"))
      places.append(TopoChain([t, TopoCircle(rndf(5, 20))],
        { stepRandomAngle=90 }))

    for (local n=10; n>0; --n) places.append(TopoCircle(rndf(5, 7)))

    local scatter=TopoScatter(places)
    scatter.makeMask()

    local num=places.len()/3
    if (num>10) num=10
    else if (num<1) num=1

    local islands=array(num)
    for (local i=0; i<islands.len(); ++i)
    {
      local isle=[]
      islands[i]=isle

      local pi=irnd()%places.len()
      isle.append(places[pi])
      places.remove(pi)
    }

    while (places.len())
    {
      local isle=islands[irnd()%islands.len()]

      // find nearest place
      local bi=-1, bd=1e20
      for (local i=0; i<places.len(); ++i)
        for (local j=0; j<isle.len(); ++j)
        {
          local d=isle[j].distTo(places[i])
          if (d<bd) { bd=d; bi=i; }
        }

      isle.append(places[bi])
      places.remove(bi)
    }

    // add islands to generator
    wg.avgh=-20
    wg.rndh=10
    wg.seaLevel=0

    local land=[]
    foreach (isle in islands)
    {
      if (!isle.len()) continue;
      land.append(TopoIsland(TopoBitwise(isle, 14), { extraSteps=5 }))
    }

    land=TopoBitwise(land, 14)
    wg.topo.append(land)

    local hscale=rndf(0.4, 1.5)
    wg.addGeo(GeoSkeleton(land, {
      blendMin=-2,
      minFunc=@(s, m) 0.5,
      maxFunc=@(s, m) s*hscale,
      priority=0
    }))

    // filler
    local filler=[TopoErode(land, { iterations=2 })]
    // wg.grassMask=filler[0]
    filler.extend(wg.getGroup("topTopo"))
    filler=TopoBitwise(filler, 2)
    wg.topo.append(filler)
    WELEM("!Filler")({ targetTopo=filler }).addToWGen(wg, "fill")

    wg.generateShaped()
  }
}


//ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ//


class CanyonsWorldShaper extends WorldElement
{
  tags="WorldShaper canyons generic"

  function shapeWorld(wg)
  {
    local high=[], low=[]
    foreach (t in wg.getGroup("topTopo"))
      if (frnd()<0.5) low.append(t)
      else high.append(t)

    if (low.len()==0 && high.len()!=0)
      { low.append(high[0]); high.remove(0) }

    // add extra space elements to canyon
    for (local n=3; n>0 || low.len()<12; --n)
    {
      local t=TopoCircle(rndf(10, 15))

      if (frnd()<0.5)
      {
        // add minichain
        local mc=[t]
        for (local mn=irnd()%2+2; mn; --mn)
          mc.insert(irnd()%mc.len(), TopoCircle(rndf(3, 7)))

        t=TopoChain(mc, { stepRandomAngle=90 })
      }

      local at=0
      if (low.len()) at=irnd()%low.len()
      low.insert(at, t)
    }

    local canyon=TopoIsland(
      TopoChain(low, { stepRandomAngle=90 }),
      { extraSteps=5 })

    local highPlaces=TopoScatter(high)

    wg.avgh=rndf(15, 30)
    wg.rndh=10
    wg.topo.append(TopoAppend([canyon, highPlaces]))
    wg.addGeo(GeoSkeleton(canyon, { avgh=0, rndh=10, blendMin=-0.5, priority=0 }))

    local filler=TopoErode(TopoInverse(canyon), { iterations=20 })
    wg.grassMask=filler
    filler=TopoBitwise([filler, highPlaces], 2)
    wg.topo.append(filler)
    WELEM("!Filler")({ targetTopo=filler }).addToWGen(wg, "fill")

    wg.generateShaped()
  }
}


//ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ//


class GenericWorldGen extends WorldGenerator
{
  tags="WorldGen generic"

  function generateShaped()
  {
    // doErosion=false //==
    base.generate()

    show_splash("Placing objects", -1)
    foreach (e in getGroup( "topWE")) e.placeObjects()
    foreach (e in getGroup("fillWE")) e.placeObjects()
  }

  function generate()
  {
    local numMainSpawns=2
    local numExtraSpawns=(irnd()%3)+1
    local numBonusPlaces=irnd()%3

    WELEM("!PlayerStart")().addToWGen(this, "top")

    local mainEvents=[]
    for (local i=0; i<numMainSpawns; ++i)
    {
      mainEvents.append("spawn"+i)
      WELEM("!MonsterSpawner")({ event="spawn"+i }).addToWGen(this, "top")
    }

    for (local i=0; i<numExtraSpawns; ++i)
      WELEM("!MonsterSpawner")({ event="optSpawn"+i }).addToWGen(this, "top")

    for (local i=0; i<numBonusPlaces; ++i)
      WELEM("!BonusPlace")({ event="bonus"+i }).addToWGen(this, "top")

    local portalPlace=TopoCircle(10)
    addElem(portalPlace, "topTopo")

    local shaper=WELEM("!WorldShaper")
    shaper.shapeWorld(this)

    TriggerHub({ completionEvent="complete", events=mainEvents })
    SpawnPortalListener("complete", { topo=portalPlace })
  }
}
