Bay 12 Games Forum

Please login or register.

Login with username, password and session length
Advanced search  

Author Topic: [DFHack] Move the embark wagon to another position/Embarking underground?  (Read 3651 times)

LargeSnail

  • Bay Watcher
  • Sluggish Modder
    • View Profile

I was developing a script to embark right into the caverns, but an unpleasant issue appeared: While I can actually teleport all the seven dwarves and the animals of the embark to the underground, the embark supplies in the wagon (and the wagon itself) cannot be moved.

Is there some workaround? Using dfhack.buildings.deconstruct(wagon) doesn't destroy the wagon, just marks it to be dismantled by the carpenter. Changing the position and flags of the items also doesn't work.

Spoiler: The script (click to show/hide)

Are my dreams of a Drow mod condemned? :(

Orbotosh

  • Bay Watcher
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #1 on: November 10, 2019, 04:03:00 am »

IIRC all you need to do is stick the raw tag for underground species on the race.
Logged
It's a buyers market. The author writes what sells, catering to the lowly demands of uneducated masses. In this case, apparently, charioteers.
Can we get migrant sponge cooks, a squid for bookkeeper, a starfish peasants, squirrel mechanics, vermin invaders, and cook roast crab meats? 

Roses

  • Bay Watcher
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #2 on: November 10, 2019, 11:36:19 am »

You should be able to just move the wagon by changing it's coordinates.
Logged

LargeSnail

  • Bay Watcher
  • Sluggish Modder
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #3 on: November 11, 2019, 01:57:44 pm »

IIRC all you need to do is stick the raw tag for underground species on the race.

You say LAYER_LINKED? It doesn't work very well with playable entities.

You should be able to just move the wagon by changing it's coordinates.

Nope. I receive the following error on DFhack:
...ktop\Testing DF/hack/scripts/largesnail/cavernEmbark.lua:80: Cannot write field building_wagonst.pos: not found.
stack traceback:
        [C]: in metamethod '__newindex'
        ...ktop\Testing DF/hack/scripts/largesnail/cavernEmbark.lua:80: in global 'moveUnderground'
        ...ktop\Testing DF/hack/scripts/largesnail/cavernEmbark.lua:100: in global 'checkEmbark'
        ...ktop\Testing DF/hack/scripts/largesnail/cavernEmbark.lua:106: in local 'script_code'
        C:\Users\user\Desktop\Testing DF\hack\lua\dfhack.lua:680: in function 'dfhack.run_script_with_env'
        (...tail calls...)


Clément

  • Bay Watcher
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #4 on: November 11, 2019, 03:17:04 pm »

You are trying to write a field that does not exist. The coordinates of a building are x1, y1, centerx, x2, y2, centery, z.
Logged

Roses

  • Bay Watcher
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #5 on: November 11, 2019, 03:24:17 pm »

You are trying to write a field that does not exist. The coordinates of a building are x1, y1, centerx, x2, y2, centery, z.

Like Clement said, buildings don't have a pos field. You will have to change the x1, x2, centerx, y1, y2, centery, and z. I believe there is also a virtual method to move buildings, but it takes a delta x,y,z instead of a specific x,y,z
Logged

Orbotosh

  • Bay Watcher
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #6 on: November 11, 2019, 05:11:57 pm »

IIRC all you need to do is stick the raw tag for underground species on the race.

You say LAYER_LINKED? It doesn't work very well with playable entities.
What's the problem?
Logged
It's a buyers market. The author writes what sells, catering to the lowly demands of uneducated masses. In this case, apparently, charioteers.
Can we get migrant sponge cooks, a squid for bookkeeper, a starfish peasants, squirrel mechanics, vermin invaders, and cook roast crab meats? 

LargeSnail

  • Bay Watcher
  • Sluggish Modder
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #7 on: November 12, 2019, 01:10:43 am »

You are trying to write a field that does not exist. The coordinates of a building are x1, y1, centerx, x2, y2, centery, z.

Changing the x1,x2,y1,y2,centerx,centery and z of the wagon worked. Thanks you!

Clément

  • Bay Watcher
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #8 on: November 12, 2019, 05:41:35 am »

You are trying to write a field that does not exist. The coordinates of a building are x1, y1, centerx, x2, y2, centery, z.

Like Clement said, buildings don't have a pos field. You will have to change the x1, x2, centerx, y1, y2, centery, and z. I believe there is also a virtual method to move buildings, but it takes a delta x,y,z instead of a specific x,y,z

If there is a method, it may be best to use it. I checked the assembly it modifies room.x and room.y too, not only the fields listed above. And since it is virtual some building types may do other special stuff (wagon_buildingst doesn't).
Logged

Roses

  • Bay Watcher
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #9 on: November 12, 2019, 10:37:06 am »

You are trying to write a field that does not exist. The coordinates of a building are x1, y1, centerx, x2, y2, centery, z.

Like Clement said, buildings don't have a pos field. You will have to change the x1, x2, centerx, y1, y2, centery, and z. I believe there is also a virtual method to move buildings, but it takes a delta x,y,z instead of a specific x,y,z

If there is a method, it may be best to use it. I checked the assembly it modifies room.x and room.y too, not only the fields listed above. And since it is virtual some building types may do other special stuff (wagon_buildingst doesn't).

True, for most uses I would say you should definitely use the method. But the wagon should be fine to move as is
Logged

Atomic Chicken

  • Bay Watcher
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #10 on: November 16, 2019, 04:19:52 am »

I swear this is a coincidence, but I finished writing a script with this exact objective earlier this week following a request by Meph:

Code: (deep-embark.lua) [Select]
-- embark underground
-- author: Atomic Chicken

--@ module = true

local usage = [====[

deep-embark
===========
Moves the starting units and equipment to
a specific underground region upon embarking.

To use, create a file called "onMapLoad.init"
in the DF raw folder and enter within it the name of
this script followed by any of the args listed below.

example:
    modtools/deep-embark -depth CAVERN_2

Usage::

    -depth X
        (obligatory)
        replace "X" with one of the following:
            CAVERN_1
            CAVERN_2
            CAVERN_3
            UNDERWORLD

    -atReclaim
        including this arg will enable deep embarking
        when reclaiming sites too

    -blockDemons
        including this arg will prevent demon surges
        in the context of breached underworld spires
        (intended mainly for UNDERWORLD embarks)
        ('wildlife' demon spawning will be unaffected)

]====]

local utils = require 'utils'
local teleport = reqscript("teleport").teleport

function getFeatureID(cavernType)
  local features = df.global.world.features
  local map_features = features.map_features
  if cavernType == 'CAVERN_1' then
    for i, feature in ipairs(map_features) do
      if feature._type == df.feature_init_subterranean_from_layerst
      and feature.start_depth == 0 then
        return features.feature_global_idx[i]
      end
    end
  elseif cavernType == 'CAVERN_2' then
    for i, feature in ipairs(map_features) do
      if feature._type == df.feature_init_subterranean_from_layerst
      and feature.start_depth == 1 then
        return features.feature_global_idx[i]
      end
    end
  elseif cavernType == 'CAVERN_3' then
    for i, feature in ipairs(map_features) do
      if feature._type == df.feature_init_subterranean_from_layerst
      and feature.start_depth == 2 then
        return features.feature_global_idx[i]
      end
    end
  elseif cavernType == 'UNDERWORLD' then
    for i, feature in ipairs(map_features) do
      if feature._type == df.feature_init_underworld_from_layerst
      and feature.start_depth == 4 then
        return features.feature_global_idx[i]
      end
    end
  end
end

function getFeatureBlocks(featureID)
  local featureBlocks = {}
  for i,block in ipairs(df.global.world.map.map_blocks) do
    if block.global_feature == featureID and block.local_feature == -1 then
      table.insert(featureBlocks, i)
    end
  end
  return featureBlocks
end

function isValidTiletype(tiletype)
  local tiletype = df.tiletype[tiletype]
  local tiletypeAttrs = df.tiletype.attrs[tiletype]
  local material = tiletypeAttrs.material
  local forbiddenMaterials = {
    df.tiletype_material.TREE, -- so as not to embark stranded on top of a tree
    df.tiletype_material.MUSHROOM,
    df.tiletype_material.FIRE,
    df.tiletype_material.CAMPFIRE
  }
  for _,forbidden in ipairs(forbiddenMaterials) do
    if material == forbidden then
      return false
    end
  end
  local shapeAttrs = df.tiletype_shape.attrs[tiletypeAttrs.shape]
  if shapeAttrs.walkable and shapeAttrs.basic_shape ~= df.tiletype_shape_basic.Open then -- downward ramps are walkable but open; units placed here would fall
    return true
  else
    return false
  end
end

function getValidEmbarkTiles(block)
  local validTiles = {}
  for xi = 0,15 do
    for yi = 0,15 do
      if block.designation[xi][yi].flow_size == 0
      and isValidTiletype(block.tiletype[xi][yi]) then
        table.insert(validTiles, {x = block.map_pos.x + xi, y = block.map_pos.y + yi, z = block.map_pos.z})
      end
    end
  end
  return validTiles
end

function blockGlowingBarrierAnnouncements(recenter)
--  temporarily disables the "glowing barrier has disappeared" announcement
--  announcement settings are restored after 1 tick
--  setting recenter to true enables recentering of game view to the announcement position
  local announcementFlags = df.global.d_init.announcements.flags.ENDGAME_EVENT_1 -- glowing barrier disappearance announcement
  local oldFlags = df.global.d_init.announcements.flags.ENDGAME_EVENT_1:new()
  announcementFlags.DO_MEGA = false
  announcementFlags.PAUSE = false
  announcementFlags.RECENTER = recenter and true or false
  announcementFlags.A_DISPLAY = false
  announcementFlags.D_DISPLAY = recenter and true or false -- required to recenter or pause
  dfhack.timeout(1,'ticks', function() -- barrier disappears after 1 tick
    announcementFlags:assign(oldFlags) -- restore announcement settings
    if recenter then
--    Remove glowing barrier notifications:
      local status = df.global.world.status
      local announcements = status.announcements
      for i = #announcements-1, 0, -1 do
        if string.find(announcements[i].text,"glowing barrier has disappeared") then
          announcements:erase(i)
          break
        end
      end
      local reports = status.reports
      for i = #reports-1, 0, -1 do
        if string.find(reports[i].text,"glowing barrier has disappeared") then
          reports:erase(i)
          break
        end
      end
      status.display_timer = 0 -- to avoid displaying other announcements
    end
  end)
end

function reveal(pos)
-- creates an unbound glowing barrier at the target location
-- so as to trigger tile revelation when it disappears 1 tick later (fortress mode only)
-- should be run in conjunction with blockGlowingBarrierAnnouncements()
  local x,y,z = pos2xyz(pos)
  local block = dfhack.maps.getTileBlock(x,y,z)
  local tiletype = block.tiletype[x%16][y%16]
  if tiletype ~= df.tiletype.GlowingBarrier then -- to avoid multiple instances
    block.tiletype[x%16][y%16] = df.tiletype.GlowingBarrier
    local barriers = df.global.world.glowing_barriers
    local barrier = df.glowing_barrier:new()
    barrier.buildings:insert('#',-1) -- being unbound to a building makes the barrier disappear immediately
    barrier.pos:assign(pos)
    barriers:insert('#',barrier)
    local hfs = df.glowing_barrier:new()
    hfs.triggered = 1 -- UPDATE THIS -- prevents HFS events which could otherwise be triggered by the barrier disappearing
    barriers:insert('#',hfs)
    dfhack.timeout(1,'ticks', function() -- barrier tiletype disappears after 1 tick
      block.tiletype[x%16][y%16] = tiletype -- restore old tiletype
      barriers:erase(#barriers-1) -- remove hfs blocker
      barriers:erase(#barriers-1) -- remove revelation barrier
    end)
  end
end

function moveEmbarkStuff(selectedBlock, embarkTiles)
  local spawnPosCentre
  for _, hotkey in ipairs(df.global.ui.main.hotkeys) do
    if hotkey.name == "Gate" then -- the preset hotkey is centred around the spawn point
      spawnPosCentre = xyz2pos(hotkey.x, hotkey.y, hotkey.z)
      hotkey:assign(embarkTiles[math.random(1, #embarkTiles)])
      break
    end
  end

-- only target things within this zone to help avoid teleporting non-embark stuff:
-- the following values might need to be modified
  local x1 = spawnPosCentre.x - 15
  local x2 = spawnPosCentre.x + 15
  local y1 = spawnPosCentre.y - 15
  local y2 = spawnPosCentre.y + 15
  local z1 = spawnPosCentre.z - 3 -- units can be spread across multiple z-levels when embarking on a mountain
  local z2 = spawnPosCentre.z + 3

-- Move citizens and pets:
  local unitsAtSpawn = dfhack.units.getUnitsInBox(x1,y1,z1,x2,y2,z2)
  local movedUnit = false
  for i, unit in ipairs(unitsAtSpawn) do
    if unit.civ_id == df.global.ui.civ_id and not unit.flags1.inactive and not unit.flags2.killed then
      local pos = embarkTiles[math.random(1, #embarkTiles)]
      teleport(unit, pos)
      reveal(pos)
      movedUnit = true
    end
  end
  if movedUnit then
    blockGlowingBarrierAnnouncements(true) -- this is separate from the reveal() function as it only needs to be called once per tick, regardless of how many times reveal() has been run
  end

-- Move wagon contents:
  local wagonFound = false
  for _, wagon in ipairs(df.global.world.buildings.other.WAGON) do
    if wagon.age == 0 then -- just in case there's an older wagon present for some reason
      local contained = wagon.contained_items
      for i = #contained-1, 0, -1 do
        if contained[i].use_mode == 0 then -- actual contents (as opposed to building components)
          local item = contained[i].item
--        dfhack.items.moveToGround() does not handle items within buildings, so do this manually:
          contained:erase(i)
          for k = #item.general_refs-1, 0, -1 do
            if item.general_refs[k]._type == df.general_ref_building_holderst then
              item.general_refs:erase(k)
            end
          end
          item.flags.in_building = false
          item.flags.on_ground = true
          local pos = embarkTiles[math.random(1, #embarkTiles)]
          item.pos:assign(pos)
          selectedBlock.items:insert('#', item.id)
          selectedBlock.occupancy[pos.x%16][pos.y%16].item = true
        end
      end
    end
    dfhack.buildings.deconstruct(wagon)
    wagon.flags.almost_deleted = true -- wagon vanishes a tick later
    wagonFound = true
    break
  end

-- Move items scattered around the spawn point if there's no wagon:
  if not wagonFound then
    for _, item in ipairs(df.global.world.items.other.IN_PLAY) do
      local flags = item.flags
      if item.age == 0 -- embark equipment consists of newly created items
      and item.pos.x >= x1 and item.pos.x <= x2
      and item.pos.y >= y1 and item.pos.y <= y2
      and item.pos.z >= z1 and item.pos.z <= z2
      and flags.on_ground
      and not flags.in_inventory
      and not flags.in_building
      and not flags.in_chest
      and not flags.construction
      and not flags.spider_web
      and not flags.encased then
        dfhack.items.moveToGround(item, embarkTiles[math.random(1, #embarkTiles)])
      end
    end
  end
end

function deepEmbark(cavernType)
  local cavernBlocks = getFeatureBlocks(getFeatureID(cavernType))
  if #cavernBlocks == 0 then
    qerror(cavernType .. " not found!")
  end

  local moved = false
  for n = 1, #cavernBlocks do
    local i = math.random(1, #cavernBlocks)
    local selectedBlock = df.global.world.map.map_blocks[cavernBlocks[i]]
    local embarkTiles = getValidEmbarkTiles(selectedBlock)
    if #embarkTiles >= 20 then -- value chosen arbitrarily; might want to increase/decrease (determines how cramped the embark spot is allowed to be)
      moveEmbarkStuff(selectedBlock, embarkTiles)
      moved = true
      break
    end
    table.remove(cavernBlocks, i)
  end
  if not moved then
    qerror('Insufficient space at ' .. cavernType)
  end
end

function disableSpireDemons()
--  marks underworld spires on the map as having been breached already, preventing HFS events
  for _, spire in ipairs(df.global.world.deep_vein_hollows) do
    spire.anon_1 = 1 -- UPDATE THIS
  end
end

local validArgs = utils.invert({
  'depth',
  'atReclaim',
  'blockDemons',
  'help'
})
local args = utils.processArgs({...}, validArgs)

if moduleMode then
  return
end

if args.help then
  print(usage)
  return
end

if df.global.gamemode ~= df.game_mode.DWARF then -- no need to run in adventure/legends mode
  return
end
local gametype = df.global.gametype
if gametype == df.game_type.DWARF_ARENA or gametype == DWARF_UNRETIRE then -- because unretiring forts ~= embarking
  return
end
if gametype == df.game_type.DWARF_RECLAIM and not args.atReclaim then -- some might prefer being able to reclaim sites in a cavern-raiding style, so this is an option
  return
end

if df.global.ui.fortress_age > 0 then -- reclaimed fortresses also start at fortress_age 0
  return
end

if not args.depth then
  qerror('Depth not specified! Enter "deep-embark -help" for more information.')
end

local validDepths = {
  ["CAVERN_1"] = true,
  ["CAVERN_2"] = true,
  ["CAVERN_3"] = true,
  ["UNDERWORLD"] = true
}

if not validDepths[args.depth] then
  qerror("Invalid depth: " .. tostring(args.depth))
end

if args.blockDemons then
  disableSpireDemons()
end

deepEmbark(tostring(args.depth))
« Last Edit: November 23, 2019, 02:47:41 am by Atomic Chicken »
Logged
As mentioned in the previous turn, the most exciting field of battle this year will be in the Arstotzkan capitol, with plenty of close-quarter fighting and siege warfare.  Arstotzka, accordingly, spent their design phase developing a high-altitude tactical bomber. 

Quietust

  • Bay Watcher
  • Does not suffer fools gladly
    • View Profile
    • QMT Productions
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #11 on: November 22, 2019, 10:07:52 pm »

If there is a method, it may be best to use it. I checked the assembly it modifies room.x and room.y too, not only the fields listed above. And since it is virtual some building types may do other special stuff (wagon_buildingst doesn't).
Actually, I think that method is only used in Adventurer mode when approaching or leaving a site from the north and/or west (because the building's coordinates are relative to the northwest edge of the currently loaded area - as additional map blocks are loaded/unloaded, the relative locations of all loaded buildings need to get adjusted).

Whatever method you use to relocate the wagon, you'll probably also need to ensure that the map tile occupancy values are recalculated, otherwise your dwarves will act as if there's still a building present in the original location (and potentially allow strange things like building workshops directly on top of the new wagon).
Logged
P.S. If you don't get this note, let me know and I'll write you another.
It's amazing how dwarves can make a stack of bones completely waterproof and magmaproof.
It's amazing how they can make an entire floodgate out of the bones of 2 cats.

LargeSnail

  • Bay Watcher
  • Sluggish Modder
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #12 on: November 22, 2019, 11:23:06 pm »

I swear this is a coincidence, but I finished writing a script with this exact objective earlier this week following a request by Meph:

Code: (deep-embark.lua) [Select]
-- embark underground
-- author: Atomic Chicken

local usage = [====[

deep-embark
===========
Moves the starting units and equipment to
a specific underground region upon embarking.

To use, create a file called "onMapLoad.init"
in the DF raw folder and enter within it the name of
this script followed by any of the args listed below.

example:
    deep-embark -depth CAVERN_2

Usage::

    -depth X
        (obligatory)
        replace "X" with one of the following:
            CAVERN_1
            CAVERN_2
            CAVERN_3
            UNDERWORLD

    -atReclaim
        including this arg will enable deep embarking
        when reclaiming sites too

]====]

local utils = require 'utils'
local teleport = reqscript("teleport").teleport

function getFeatureID(cavernType)
  local features = df.global.world.features
  local map_features = features.map_features
  if cavernType == 'CAVERN_1' then
    for i, feature in ipairs(map_features) do
      if feature._type == df.feature_init_subterranean_from_layerst
      and feature.start_depth == 0 then
        return features.feature_global_idx[i]
      end
    end
  elseif cavernType == 'CAVERN_2' then
    for i, feature in ipairs(map_features) do
      if feature._type == df.feature_init_subterranean_from_layerst
      and feature.start_depth == 1 then
        return features.feature_global_idx[i]
      end
    end
  elseif cavernType == 'CAVERN_3' then
    for i, feature in ipairs(map_features) do
      if feature._type == df.feature_init_subterranean_from_layerst
      and feature.start_depth == 2 then
        return features.feature_global_idx[i]
      end
    end
  elseif cavernType == 'UNDERWORLD' then
    for i, feature in ipairs(map_features) do
      if feature._type == df.feature_init_underworld_from_layerst
      and feature.start_depth == 4 then
        return features.feature_global_idx[i]
      end
    end
  end
end

function getFeatureBlocks(featureID)
  local featureBlocks = {}
  for i,block in ipairs(df.global.world.map.map_blocks) do
    if block.global_feature == featureID and block.local_feature == -1 then
      table.insert(featureBlocks, i)
    end
  end
  return featureBlocks
end

function isValidTiletype(tiletype)
  local tiletype = df.tiletype[tiletype]
  local tiletypeAttrs = df.tiletype.attrs[tiletype]
  local material = tiletypeAttrs.material
  local forbiddenMaterials = {
    df.tiletype_material.TREE, -- so as not to embark stranded on top of a tree
    df.tiletype_material.MUSHROOM,
    df.tiletype_material.FIRE,
    df.tiletype_material.CAMPFIRE
  }
  for _,forbidden in ipairs(forbiddenMaterials) do
    if material == forbidden then
      return false
    end
  end
  local shape = tiletypeAttrs.shape
  local shapeAttrs = df.tiletype_shape.attrs[shape]
  if shapeAttrs.walkable and shapeAttrs.basic_shape ~= df.tiletype_shape_basic.Open then -- downward ramps are walkable but open; units placed here would fall
    return true
  else
    return false
  end
end

function getValidEmbarkTiles(block)
  local validTiles = {}
  for xi = 0,15 do
    for yi = 0,15 do
      if block.designation[xi][yi].flow_size == 0
      and isValidTiletype(block.tiletype[xi][yi]) then
        table.insert(validTiles, {x = block.map_pos.x + xi, y = block.map_pos.y + yi, z = block.map_pos.z})
      end
    end
  end
  return validTiles
end

function reveal(pos, recenter)
-- creates an unbound glowing barrier at the target location to trigger proper tile revelation when it disappears (fortress mode only)
-- if recenter is true, shifts the view to the target location
  local x,y,z = pos2xyz(pos)
  local block = dfhack.maps.getTileBlock(x,y,z)
  local tiletype = block.tiletype[x%16][y%16]
  if tiletype ~= df.tiletype.GlowingBarrier then -- to avoid multiple instances
    block.tiletype[x%16][y%16] = df.tiletype.GlowingBarrier
    local barriers = df.global.world.glowing_barriers
    local barrier = df.glowing_barrier:new()
    barrier.buildings:insert('#',-1) -- being unbound to a building makes the barrier disappear immediately
    barrier.pos:assign(pos)
    barriers:insert('#',barrier)
    local hfs = df.glowing_barrier:new()
    hfs.triggered = 1 -- this prevents hfs events (which can otherwise be triggered by the barrier disappearing)
    barriers:insert('#',hfs)
    local announcementFlags = df.global.d_init.announcements.flags.ENDGAME_EVENT_1 -- glowing barrier disappearance announcement
    local oldDO_MEGA = announcementFlags.DO_MEGA
    announcementFlags.DO_MEGA = false -- turn off popup announcement
    local oldPAUSE = announcementFlags.PAUSE
    announcementFlags.PAUSE = false
    local oldRECENTER = announcementFlags.RECENTER
    announcementFlags.RECENTER = recenter and true or false
    local oldA_DISPLAY = announcementFlags.A_DISPLAY
    announcementFlags.A_DISPLAY = false
    local oldD_DISPLAY = announcementFlags.D_DISPLAY
    announcementFlags.D_DISPLAY = recenter and true or false -- won't recenter without an actual announcement
    dfhack.timeout(1,'ticks', function() -- barrier disappears after 1 tick
      block.tiletype[x%16][y%16] = tiletype -- restore old tiletype
      barriers:erase(#barriers-1) -- remove hfs blocker
      barriers:erase(#barriers-1) -- remove revealer
      announcementFlags.RECENTER = oldRECENTER -- restore announcement settings
      announcementFlags.A_DISPLAY = oldA_DISPLAY
      announcementFlags.D_DISPLAY = oldD_DISPLAY
      announcementFlags.DO_MEGA = oldDO_MEGA
      announcementFlags.PAUSE = oldPAUSE
      if recenter then
--      Remove glowing barrier notifications:
        local status = df.global.world.status
        local announcements = status.announcements
        for i = #announcements-1, 0, -1 do
          if string.find(announcements[i].text, "glowing barrier has disappeared") then
            announcements:erase(i)
            break
          end
        end
        local reports = status.reports
        for i = #reports-1, 0, -1 do
          if string.find(reports[i].text, "glowing barrier has disappeared") then
            reports:erase(i)
            break
          end
        end
        status.display_timer = 0 -- otherwise an older announcement could be displayed
      end
    end)
  end
end

function moveEmbarkStuff(selectedBlock, embarkTiles)
  local spawnPosCentre
  for _, hotkey in ipairs(df.global.ui.main.hotkeys) do
    if hotkey.name == "Gate" then -- the preset hotkey is centred around the spawn point
      spawnPosCentre = xyz2pos(hotkey.x, hotkey.y, hotkey.z)
      break
    end
  end

-- only target things within this zone to help avoid teleporting non-embark stuff:
-- the following values might need to be modified
  local x1 = spawnPosCentre.x - 15
  local x2 = spawnPosCentre.x + 15
  local y1 = spawnPosCentre.y - 15
  local y2 = spawnPosCentre.y + 15
  local z1 = spawnPosCentre.z - 3 -- units can be spread across multiple z-levels when embarking on a mountain
  local z2 = spawnPosCentre.z + 3

-- Move citizens and pets:
  local unitsAtSpawn = dfhack.units.getUnitsInBox(x1,y1,z1,x2,y2,z2)
  local last = #unitsAtSpawn
  for i, unit in ipairs(unitsAtSpawn) do
    if unit.civ_id == df.global.ui.civ_id and not unit.flags1.inactive and not unit.flags2.killed then
      local pos = embarkTiles[math.random(1, #embarkTiles)]
      teleport(unit, pos)
      reveal(pos, i == last and true or false)
    end
  end

-- Move wagon contents:
  local wagonFound = false
  for _, wagon in ipairs(df.global.world.buildings.other.WAGON) do
    if wagon.age == 0 then -- just in case there's an older wagon present for some reason
      local contained = wagon.contained_items
      for i = #contained-1, 0, -1 do
        if contained[i].use_mode == 0 then -- actual contents (as opposed to building components)
          local item = contained[i].item
          contained:erase(i)
          for k = #item.general_refs-1, 0, -1 do
            if item.general_refs[k]._type == df.general_ref_building_holderst then
              item.general_refs:erase(k)
            end
          end
          item.flags.in_building = false
          item.flags.on_ground = true
          local pos = embarkTiles[math.random(1, #embarkTiles)]
          item.pos:assign(pos)
          selectedBlock.items:insert('#', item.id)
          selectedBlock.occupancy[pos.x%16][pos.y%16].item = true
        end
      end
    end
    dfhack.buildings.deconstruct(wagon)
    wagon.flags.almost_deleted = true -- wagon vanishes a tick later
    wagonFound = true
    break
  end

-- Move items scattered around the spawn point if there's no wagon:
  if not wagonFound then
    for _, item in ipairs(df.global.world.items.other.IN_PLAY) do
      local flags = item.flags
      if item.age == 0 -- embark equipment consists of newly created items
      and item.pos.x >= x1 and item.pos.x <= x2
      and item.pos.y >= y1 and item.pos.y <= y2
      and item.pos.z >= z1 and item.pos.z <= z2
      and flags.on_ground
      and not flags.in_inventory
      and not flags.in_building
      and not flags.in_chest
      and not flags.construction
      and not flags.spider_web
      and not flags.encased then
        dfhack.items.moveToGround(item, embarkTiles[math.random(1, #embarkTiles)])
      end
    end
  end
end

function deepEmbark(cavernType)
  local cavernBlocks = getFeatureBlocks(getFeatureID(cavernType))
  if #cavernBlocks == 0 then
    qerror(cavernType .. " not found!")
  end

  local moved = false
  for n = 1, #cavernBlocks do
    local i = math.random(1, #cavernBlocks)
    local selectedBlock = df.global.world.map.map_blocks[cavernBlocks[i]]
    local embarkTiles = getValidEmbarkTiles(selectedBlock)
    if #embarkTiles >= 20 then -- value chosen arbitrarily; might want to increase/decrease (determines how cramped the embark spot is allowed to be)
      moveEmbarkStuff(selectedBlock, embarkTiles)
      moved = true
      break
    end
    table.remove(cavernBlocks, i)
  end
  if not moved then
    qerror('Insufficient space at ' .. cavernType)
  end
end

local validArgs = utils.invert({
  'depth',
  'atReclaim',
  'help'
})
local args = utils.processArgs({...}, validArgs)

if args.help then
  print(usage)
  return
end

if df.global.gamemode ~= df.game_mode.DWARF then -- no need to run in adventure/legends mode
  return
end
local gametype = df.global.gametype
if gametype == df.game_type.DWARF_ARENA or gametype == DWARF_UNRETIRE then -- because unretiring forts ~= embarking
  return
end
if gametype == df.game_type.DWARF_RECLAIM and not args.atReclaim then -- some might prefer being able to reclaim sites in a cavern-raiding style, so this is an option
  return
end

if df.global.ui.fortress_age > 0 then -- reclaimed fortresses also start at fortress_age 0
  return
end

if not args.depth then
  qerror('Depth not specified! Enter "deep-embark -help" for more information.')
end

local validDepths = {
  ["CAVERN_1"] = true,
  ["CAVERN_2"] = true,
  ["CAVERN_3"] = true,
  ["UNDERWORLD"] = true
}

if not validDepths[args.depth] then
  qerror("Invalid depth: " .. tostring(args.depth))
end

deepEmbark(tostring(args.depth))

Your script looks way better than mine.
Can I employ it in future mods? I'll give you credit of course.

Atomic Chicken

  • Bay Watcher
    • View Profile
Re: [DFHack] Move the embark wagon to another position/Embarking underground?
« Reply #13 on: November 23, 2019, 02:04:03 am »

Your script looks way better than mine.
Can I employ it in future mods? I'll give you credit of course.

Sure! I'm pushing it to the DFHack repository (with a few updates which I've edited into the previous post) specifically to make it easily accessible to modders, so this is fine.
Logged
As mentioned in the previous turn, the most exciting field of battle this year will be in the Arstotzkan capitol, with plenty of close-quarter fighting and siege warfare.  Arstotzka, accordingly, spent their design phase developing a high-altitude tactical bomber.