Bay 12 Games Forum

Please login or register.

Login with username, password and session length
Advanced search  
Pages: [1] 2

Author Topic: Getting the material of a tile from a script: Lua module provided  (Read 5323 times)

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile

I finally cracked the problem of getting a tile's layer material, and the other possibilities fell soon after, so I am proud to present a working tile material finder.

This is a Lua pseudo module, install it to "raw/dfhack" with the name "libs_dfhack_get_tile_material.mod.lua" (it MUST have that name!).
You will need to install the Rubble script loader.

Code: [Select]

local _ENV = rubble.mkmodule("libs_dfhack_get_tile_material")

--[[
This module contains functions for finding the material of a tile.

There is a function that will find the material of the tile based on it's type (in other words
it will return the material DF is using for that tile), and there are functions that will attempt
to return only a certain class of materials.

Most users will be most interested in the generic "GetTileMat" function, but the other functions
should be useful in certain cases. For example "GetLayerMat" will always return the material of
the stone (or soil) in the current layer, ignoring any veins or other inclusions.

Some tile types/materials have special behavior with the "GetTileMat" function.

* Open space and other "material-less" tiles (such as semi-molten rock or eerie glowing pits)
  will return nil.
* Ice will return the hard-coded water material ("WATER:NONE").
* Grass is ignored.

The specialized functions will return nil if a material of their type is not possible for a tile.
For example calling "GetVeinMat" for a tile that does not have (and has never had) a mineral vein
will always return nil.

There are two functions for dealing with constructions, one to get the material of the construction
and one that gets the material of the tile the construction was built over.

I am not sure how caved in tiles are handled, but after some quick testing it appears that the
game creates mineral veins for them. I am not 100% sure if these functions will reliably work
with all caved in tiles, but I can confirm that they do in at least some cases...
]]

-- GetLayerMat returns the layer material for the given tile.
-- AFAIK this will never return nil.
function GetLayerMat(x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

local region_info = dfhack.maps.getRegionBiome(dfhack.maps.getTileBiomeRgn(pos))
local map_block = dfhack.maps.ensureTileBlock(pos)

local biome = df.world_geo_biome.find(region_info.geo_index)

local layer_index = map_block.designation[pos.x%16][pos.y%16].geolayer_index
local layer_mat_index = biome.layers[layer_index].mat_index

return dfhack.matinfo.decode(0, layer_mat_index)
end

-- GetLavaStone returns the biome lava stone material (generally obsidian).
function GetLavaStone(x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

local regions = df.global.world.world_data.region_details

local rx, ry = dfhack.maps.getTileBiomeRgn(pos)

for _, region in ipairs(regions) do
if region.pos.x == rx and region.pos.y == ry then
return dfhack.matinfo.decode(0, region.lava_stone)
end
end
return nil
end

-- GetVeinMat returns the vein material of the given tile or nil if the tile has no veins.
-- Multiple veins in one tile should be handled properly (smallest vein type, last in the list wins,
-- which seems to be the rule DF uses).
function GetVeinMat(x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

local region_info = dfhack.maps.getRegionBiome(dfhack.maps.getTileBiomeRgn(pos))
local map_block = dfhack.maps.ensureTileBlock(pos)

local events = {}
for _, event in ipairs(map_block.block_events) do
if getmetatable(event) == "block_square_event_mineralst" then
if dfhack.maps.getTileAssignment(event.tile_bitmask, pos.x, pos.y) then
table.insert(events, event)
end
end
end

if #events == 0 then
return nil
end

local event_priority = function(event)
if event.flags.cluster then
return 1
elseif event.flags.vein then
return 2
elseif event.flags.cluster_small then
return 3
elseif event.flags.cluster_one then
return 4
else
return 5
end
end

local priority = events[1]
for _, event in ipairs(events) do
if event_priority(event) >= event_priority(priority) then
priority = event
end
end

return dfhack.matinfo.decode(0, priority.inorganic_mat)
end

-- GetConstructionMat returns the material of the construction at the given tile or nil if the tile
-- has no construction.
function GetConstructionMat(x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

for _, construction in ipairs(df.global.world.constructions) do
if construction.pos.x == pos.x and construction.pos.y == pos.y and construction.pos.z == pos.z then
return dfhack.matinfo.decode(construction)
end
end
return nil
end

-- GetConstructOriginalTileMat returns the material of the tile under the construction at the given
-- tile or nil if the tile has no construction.
function GetConstructOriginalTileMat(x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

for _, construction in ipairs(df.global.world.constructions) do
if construction.pos.x == pos.x and construction.pos.y == pos.y and construction.pos.z == pos.z then
return GetTileTypeMat(construction.original_tile, pos)
end
end
return nil
end

-- GetTreeMat returns the material of the tree at the given tile or nil if the tile does not have a
-- tree or giant mushroom.
-- Currently roots are ignored.
function GetTreeMat(x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

local function coordInTree(pos, tree)
local x1 = tree.pos.x - math.floor(tree.tree_info.dim_x / 2)
local x2 = tree.pos.x + math.floor(tree.tree_info.dim_x / 2)
local y1 = tree.pos.y - math.floor(tree.tree_info.dim_y / 2)
local y2 = tree.pos.y + math.floor(tree.tree_info.dim_y / 2)
local z1 = tree.pos.z
local z2 = tree.pos.z + tree.tree_info.body_height

if not ((pos.x >= x1 and pos.x <= x2) and (pos.y >= y1 and pos.y <= y2) and (pos.z >= z1 and pos.z <= z2)) then
return false
end

return not tree.tree_info.body[pos.z - tree.pos.z]:_displace((pos.y - y1) * tree.tree_info.dim_x + (pos.x - x1)).blocked
end

for _, tree in ipairs(df.global.world.plants.all) do
if tree.tree_info ~= nil then
if coordInTree(pos, tree) then
return dfhack.matinfo.decode(419, tree.material)
end
end
end
return nil
end

-- GetShrubMat returns the material of the shrub at the given tile or nil if the tile does not
-- contain a shrub or sapling.
function GetShrubMat(x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

for _, shrub in ipairs(df.global.world.plants.all) do
if shrub.tree_info == nil then
if shrub.pos.x == pos.x and shrub.pos.y == pos.y and shrub.pos.z == pos.z then
return dfhack.matinfo.decode(419, shrub.material)
end
end
end
return nil
end

-- GetFeatureMat returns the material of the feature (adamantine tube, underworld surface, etc) at
-- the given tile or nil if the tile is not made of a feature stone.
function GetFeatureMat(x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

local map_block = dfhack.maps.ensureTileBlock(pos)

if df.tiletype.attrs[map_block.tiletype[pos.x%16][pos.y%16]].material ~= df.tiletype_material.FEATURE then
return nil
end

if map_block.designation[pos.x%16][pos.y%16].feature_local then
-- adamantine tube, etc
for id, idx in ipairs(df.global.world.features.feature_local_idx) do
if idx == map_block.local_feature then
return dfhack.matinfo.decode(df.global.world.features.map_features[id])
end
end
elseif map_block.designation[pos.x%16][pos.y%16].feature_global then
-- cavern, magma sea, underworld, etc
for id, idx in ipairs(df.global.world.features.feature_global_idx) do
if idx == map_block.global_feature then
return dfhack.matinfo.decode(df.global.world.features.map_features[id])
end
end
end

return nil
end

local function fixedMat(id)
local mat = dfhack.matinfo.find(id)
return function(x, y, z)
return mat
end
end

-- GetTileMat will return the material of the specified tile as determined by its tile type and the
-- world geology data, etc.
-- The returned material should exactly match the material reported by DF except in cases where is
-- is impossible to get a material.
function GetTileMat(x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

local typ = dfhack.maps.getTileType(pos)
if typ == nil then
return nil
end

return GetTileTypeMat(typ, pos)
end

-- GetTileTypeMat is exactly like GetTileMat except it allows you to specify the notional type for
-- the tile. This allows you to see what the tile would be made of it it was a certain type.
-- Unless the tile could be the given type this function will probably return nil.
function GetTileTypeMat(typ, x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

local type_mat = df.tiletype.attrs[typ].material

local mat_actions = {
[df.tiletype_material.AIR] = nil, -- Empty
[df.tiletype_material.SOIL] = GetLayerMat,
[df.tiletype_material.STONE] = GetLayerMat,
[df.tiletype_material.FEATURE] = GetFeatureMat,
[df.tiletype_material.LAVA_STONE] = GetLavaStone,
[df.tiletype_material.MINERAL] = GetVeinMat,
[df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"),
[df.tiletype_material.CONSTRUCTION] = GetConstructionMat,
[df.tiletype_material.GRASS_LIGHT] = GetLayerMat,
[df.tiletype_material.GRASS_DARK] = GetLayerMat,
[df.tiletype_material.GRASS_DRY] = GetLayerMat,
[df.tiletype_material.GRASS_DEAD] = GetLayerMat,
[df.tiletype_material.PLANT] = GetShrubMat,
[df.tiletype_material.HFS] = nil, -- Eerie Glowing Pit
[df.tiletype_material.CAMPFIRE] = GetLayerMat,
[df.tiletype_material.FIRE] = GetLayerMat,
[df.tiletype_material.ASHES] = GetLayerMat,
[df.tiletype_material.MAGMA] = nil, -- SMR
[df.tiletype_material.DRIFTWOOD] = GetLayerMat,
[df.tiletype_material.POOL] = GetLayerMat,
[df.tiletype_material.BROOK] = GetLayerMat,
[df.tiletype_material.ROOT] = GetLayerMat,
[df.tiletype_material.TREE] = GetTreeMat,
[df.tiletype_material.MUSHROOM] = GetTreeMat,
[df.tiletype_material.UNDERWORLD_GATE] = nil, -- I guess this is for the gates found in vaults?
}

local mat_getter = mat_actions[type_mat]
if mat_getter == nil then
return nil
end
return mat_getter(pos)
end

return _ENV
« Last Edit: July 13, 2015, 12:10:49 pm by milo christiansen »
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

Quietust

  • Bay Watcher
  • Does not suffer fools gladly
    • View Profile
    • QMT Productions
Re: Getting the material of a tile from a script: Lua module provided
« Reply #1 on: May 16, 2015, 09:17:34 pm »

Just so you know, nearly all of this functionality has been part of DFHack itself for many years - after all, the Prospect command pretty much relies on it.

As for trees, you've got most of the logic - coordInTree also needs to check tree.body[z][x_and_y] (either x*dim_y+y or y*dim_x+x, I forget which) and make sure the "blocked" flag is not set.
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.

jason0320

  • Bay Watcher
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #2 on: May 17, 2015, 03:03:46 am »

Sweet, can this used in a workshop reaction?
Logged

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #3 on: May 29, 2015, 10:37:53 am »

Sweet, can this used in a workshop reaction?

Not without some more Lua code, no. This is a library for use by other DFHack modders.

Just so you know, nearly all of this functionality has been part of DFHack itself for many years - after all, the Prospect command pretty much relies on it.

As for trees, you've got most of the logic - coordInTree also needs to check tree.body[z][x_and_y] (either x*dim_y+y or y*dim_x+x, I forget which) and make sure the "blocked" flag is not set.

Yes, you are right all of this was possible long ago. For people writing plugins in C++. Modders writing Lua scripts were out of luck because the wrapper API used for C++ plugins was not exported. Basically I just wrote a simplified Lua version of that wrapper, except I figured it all out by experimentation.

Thanks for the stuff about trees, I'll need to try that to make sure it works.
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #4 on: June 01, 2015, 12:25:38 pm »

@Quietust: That information on tree structure? It may work with C++, but Lua insists on seeing tree_data.body as an array of tree tile pointers, not an array of arrays of tree tile values. If that is really how the tree data is stored it is impossible to access from a Lua script.
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

Quietust

  • Bay Watcher
  • Does not suffer fools gladly
    • View Profile
    • QMT Productions
Re: Getting the material of a tile from a script: Lua module provided
« Reply #5 on: June 01, 2015, 07:02:18 pm »

It should still be possible by doing "pointer._displace(N)".
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.

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #6 on: June 03, 2015, 03:16:56 pm »

I'll try that, thanks.
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #7 on: June 20, 2015, 03:21:39 pm »

I got trees working now! But not in the copy posted here, see the Rubble version for that. (I really need to take the time to port the Rubble version so it can be used without Rubble...)
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #8 on: July 13, 2015, 12:14:43 pm »

Finally added the full, everything working, version.

You will need to install the script loader linked from the first page, but otherwise usage is more-or-less the same.
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

Roses

  • Bay Watcher
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #9 on: September 10, 2015, 07:34:09 pm »

So I am just getting around to looking at this and am wondering if you can change the material of a tile/shrub/tree with some minor modifications?

For instance
Code: [Select]
function GetVeinMat(x, y, z)
local pos = nil
if y == nil and z == nil then
pos = x
else
pos = {x = x, y = y, z = z}
end

local region_info = dfhack.maps.getRegionBiome(dfhack.maps.getTileBiomeRgn(pos))
local map_block = dfhack.maps.ensureTileBlock(pos)

local events = {}
for _, event in ipairs(map_block.block_events) do
if getmetatable(event) == "block_square_event_mineralst" then
if dfhack.maps.getTileAssignment(event.tile_bitmask, pos.x, pos.y) then
table.insert(events, event)
end
end
end

if #events == 0 then
return nil
end

local event_priority = function(event)
if event.flags.cluster then
return 1
elseif event.flags.vein then
return 2
elseif event.flags.cluster_small then
return 3
elseif event.flags.cluster_one then
return 4
else
return 5
end
end

local priority = events[1]
for _, event in ipairs(events) do
if event_priority(event) >= event_priority(priority) then
priority = event
end
end

return dfhack.matinfo.decode(0, priority.inorganic_mat)
end

Is changing the mat as simple as saying priority.inorganic_mat = X?
Logged

Max™

  • Bay Watcher
  • [CULL:SQUARE]
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #10 on: September 10, 2015, 08:04:32 pm »

Dunno, but setting a fire on a tile is as simple as saying "there is a fire here", how does changevein/changelayer do it?
Logged

Quietust

  • Bay Watcher
  • Does not suffer fools gladly
    • View Profile
    • QMT Productions
Re: Getting the material of a tile from a script: Lua module provided
« Reply #11 on: September 11, 2015, 06:34:12 am »

block_square_event_mineralst is only used for mineral veins/clusters, and I believe the DFHack "tiletypes" plugin already has the ability to set tiles to arbitrary materials by inserting 1-tile mineral inclusions.

Trees, however, use a completely different system, where the best you can do is mark that a tile is made of "branches" and "twigs" instead of "trunk" and "thick branches 1" or simply change the "race" of the entire tree/shrub into something else (e.g. turn "oak" into "pine", though that could have some interesting side effects on the tree's structure).
« Last Edit: September 11, 2015, 06:36:29 am by Quietust »
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.

Roses

  • Bay Watcher
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #12 on: September 11, 2015, 05:12:18 pm »

I currently use this to change individual tiles, but sometimes it doesn't work, not sure if there is a better way to do it.

Code: [Select]
function changeInorganic(x,y,z,inorganic,dur)
 if y == nil and z == nil then
  pos = x
 else
  pos = {x = x, y = y, z = z}
 end

 local block=dfhack.maps.ensureTileBlock(pos)
 for k = #block.block_events-1,0,-1 do
   if df.block_square_event_mineralst:is_instance(block.block_events[k]) then
    b.block_events:erase(k)
   end
 end
 if inorganic == 'clear' then
  return
 else
  if tonumber(inorganic) then
   inorganic = tonumber(inorganic)
  else
   inorganic = dfhack.matinfo.find(inorganic).index
  end   
  ev=df.block_square_event_mineralst:new()
  ev.inorganic_mat=inorganic
  ev.flags.vein=true
  block.block_events:insert("#",ev)
  dfhack.maps.setTileAssignment(ev.tile_bitmask,math.fmod(pos.x,16),math.fmod(pos.y,16),true)   
 end
end

milo's stuff seems much more robust and capable of handling different types of tiles
Logged

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #13 on: September 16, 2015, 01:46:12 pm »

I've never tried to change tile materials, so I can't help, frankly it's been a while since I wrote the tree material code, so I don't remember how it all worked...
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

Roses

  • Bay Watcher
    • View Profile
Re: Getting the material of a tile from a script: Lua module provided
« Reply #14 on: December 02, 2016, 01:02:04 am »

So I have just started playing around with this and just had a couple questions. When trying to get the material of a tile with grass on it the df.tiletype_material is GRASS_DARK but the material it gives me back is SILTY_CLAY (which is what the grass is growing in). Just checking that that is how it is supposed to be? Is there a way to get the grass material itself?
Logged
Pages: [1] 2