module DFHack class << self # return a map block by tile coordinates # you can also use find_map_block(cursor) or anything that respond to x/y/z def map_block_at(x, y=nil, z=nil) x = x.pos if x.respond_to?(:pos) x, y, z = x.x, x.y, x.z if x.respond_to?(:x) if x >= 0 and x < world.map.x_count and y >= 0 and y < world.map.y_count and z >= 0 and z < world.map.z_count world.map.block_index[x/16][y/16][z] end end def map_designation_at(x, y=nil, z=nil) x = x.pos if x.respond_to?(:pos) x, y, z = x.x, x.y, x.z if x.respond_to?(:x) if b = map_block_at(x, y, z) b.designation[x%16][y%16] end end def map_occupancy_at(x, y=nil, z=nil) x = x.pos if x.respond_to?(:pos) x, y, z = x.x, x.y, x.z if x.respond_to?(:x) if b = map_block_at(x, y, z) b.occupancy[x%16][y%16] end end def map_tile_at(x=df.cursor, y=nil, z=nil) x = x.pos if x.respond_to?(:pos) x, y, z = x.x, x.y, x.z if x.respond_to?(:x) b = map_block_at(x, y, z) MapTile.new(b, x, y, z) if b end # yields every map block def each_map_block (0...world.map.x_count_block).each { |xb| xl = world.map.block_index[xb] (0...world.map.y_count_block).each { |yb| yl = xl[yb] (0...world.map.z_count_block).each { |z| p = yl[z] yield p if p } } } end # yields every map block for a given z level def each_map_block_z(z) (0...world.map.x_count_block).each { |xb| xl = world.map.block_index[xb] (0...world.map.y_count_block).each { |yb| p = xl[yb][z] yield p if p } } end end class MapTile attr_accessor :x, :y, :z, :dx, :dy, :mapblock def initialize(b, x, y, z) @x, @y, @z = x, y, z @dx, @dy = @x&15, @y&15 @mapblock = b end def offset(dx, dy=nil, dz=0) if dx.respond_to?(:x) dz = dx.z if dx.respond_to?(:z) dx, dy = dx.x, dx.y end df.map_tile_at(@x+dx, @y+dy, @z+dz) end def designation @mapblock.designation[@dx][@dy] end def occupancy @mapblock.occupancy[@dx][@dy] end def tiletype @mapblock.tiletype[@dx][@dy] end def tiletype=(t) @mapblock.tiletype[@dx][@dy] = t end def caption Tiletype::Caption[tiletype] end def shape Tiletype::Shape[tiletype] end def tilemat Tiletype::Material[tiletype] end def variant Tiletype::Variant[tiletype] end def special Tiletype::Special[tiletype] end def direction Tiletype::Direction[tiletype] end def shape_caption TiletypeShape::Caption[shape] end def shape_basic TiletypeShape::BasicShape[shape] end def shape_passablelow TiletypeShape::PassableLow[shape] end def shape_passablehigh TiletypeShape::PassableHigh[shape] end def shape_passableflow TiletypeShape::PassableFlow[shape] end def shape_walkable TiletypeShape::Walkable[shape] end # return all veins for current mapblock def all_veins mapblock.block_events.grep(BlockSquareEventMineralst) end # return the vein applicable to current tile def vein # last vein wins all_veins.reverse.find { |v| v.tile_bitmask.bits[@dy][@dx] > 0 } end # return the first BlockBurrow this tile is in (nil if none) def burrow mapblock.block_burrows.find { |b| b.tile_bitmask.bits[@dy][@dx] > 0 } end # return the array of BlockBurrow this tile is in def all_burrows mapblock.block_burrows.find_all { |b| b.tile_bitmask.bits[@dy][@dx] > 0 } end # return the mat_index for the current tile (if in vein) def mat_index_vein v = vein v.inorganic_mat if v end # return the RegionMapEntry (from designation.biome) def region_map_entry b = mapblock.region_offset[designation.biome] wd = df.world.world_data # region coords + [[-1, -1], [0, -1], ..., [1, 1]][b] # clipped to world dimensions rx = df.world.map.region_x/16 rx -= 1 if b % 3 == 0 and rx > 0 rx += 1 if b % 3 == 2 and rx < wd.world_width-1 ry = df.world.map.region_y/16 ry -= 1 if b < 3 and ry > 0 ry += 1 if b > 5 and ry < wd.world_height-1 wd.region_map[rx][ry] end # return the world_data.geo_biome for current tile def geo_biome df.world.world_data.geo_biomes[ region_map_entry.geo_index ] end # return the world_data.geo_biome.layer for current tile def stone_layer geo_biome.layers[designation.geolayer_index] end # MaterialInfo: token for current tile, based on tilemat (vein, soil, plant, lava_stone...) def mat_info case tilemat when :SOIL base = stone_layer if !df.world.raws.inorganics[base.mat_index].flags[:SOIL_ANY] base = geo_biome.layers.find_all { |l| df.world.raws.inorganics[l.mat_index].flags[:SOIL_ANY] }.last end mat_index = (base ? base.mat_index : rand(df.world.raws.inorganics.length)) MaterialInfo.new(0, mat_index) when :STONE base = stone_layer if df.world.raws.inorganics[base.mat_index].flags[:SOIL_ANY] base = geo_biome.layers.find { |l| !df.world.raws.inorganics[l.mat_index].flags[:SOIL_ANY] } end mat_index = (base ? base.mat_index : rand(df.world.raws.inorganics.length)) MaterialInfo.new(0, mat_index) when :MINERAL mat_index = (mat_index_vein || stone_layer.mat_index) MaterialInfo.new(0, mat_index) when :LAVA_STONE # XXX this is wrong # maybe should search world.region_details.pos == biome_region_pos ? idx = mapblock.region_offset[designation.biome] mat_index = df.world.world_data.region_details[idx].lava_stone MaterialInfo.new(0, mat_index) when :FEATURE if designation.feature_local mx = mapblock.region_pos.x my = mapblock.region_pos.y df.decode_mat(df.world.world_data.feature_map[mx/16][my/16].features.feature_init[mx%16][my%16][mapblock.local_feature]) elsif designation.feature_global df.decode_mat(df.world.world_data.underground_regions[mapblock.global_feature].feature_init) else MaterialInfo.new(-1, -1) end when :FROZEN_LIQUID MaterialInfo.new('WATER') # TODO #when :PLANT #when :GRASS_DARK, :GRASS_DEAD, :GRASS_DRY, :GRASS_LIGHT #when :CONSTRUCTION else # AIR ASHES BROOK CAMPFIRE DRIFTWOOD FIRE HFS MAGMA POOL RIVER MaterialInfo.new(-1, -1) end end def mat_type mat_info.mat_type end def mat_index mat_info.mat_index end def inspect "#" end def dig(mode=:Default) if mode == :Smooth if (tilemat == :STONE or tilemat == :MINERAL) and caption !~ /smooth|pillar|fortification/i and # XXX caption.. designation.smooth == 0 and (designation.hidden or not df.world.jobs.list.find { |j| # the game removes 'smooth' designation as soon as it assigns a job, if we # re-set it the game may queue another :DetailWall that will carve a fortification (j.job_type == :DetailWall or j.job_type == :DetailFloor) and df.same_pos?(j, self) }) designation.dig = :No designation.smooth = 1 mapblock.flags.designated = true end else return if mode != :No and designation.dig == :No and not designation.hidden and df.world.jobs.list.find { |j| # someone already enroute to dig here, avoid 'Inappropriate dig square' spam JobType::Type[j.job_type] == :Digging and df.same_pos?(j, self) } designation.dig = mode mapblock.flags.designated = true if mode != :No end end def spawn_liquid(quantity, is_magma=false, flowing=true) designation.flow_size = quantity designation.liquid_type = (is_magma ? :Magma : :Water) designation.flow_forbid = true if is_magma or quantity >= 4 if flowing mapblock.flags.update_liquid = true mapblock.flags.update_liquid_twice = true zf = df.world.map_extras.z_level_flags[z] zf.update = true zf.update_twice = true end end def spawn_water(quantity=7) spawn_liquid(quantity) end def spawn_magma(quantity=7) spawn_liquid(quantity, true) end # yield a serie of tiles until the block returns true, returns the matching tile # the yielded tiles form a (squared) spiral centered here in the current zlevel # eg for radius 4, yields (-4, -4), (-4, -3), .., (-4, 3), # (-4, 4), (-3, 4), .., (4, 4), .., (4, -4), .., (-3, -4) # then move on to radius 5 def spiral_search(maxradius=100, minradius=0, step=1) if minradius == 0 return self if yield self minradius += step end sides = [[0, 1], [1, 0], [0, -1], [-1, 0]] (minradius..maxradius).step(step) { |r| sides.length.times { |s| dxr, dyr = sides[(s-1) % sides.length] dx, dy = sides[s] (-r...r).step(step) { |v| t = offset(dxr*r + dx*v, dyr*r + dy*v) return t if t and yield t } } } nil end # returns dx^2+dy^2+dz^2 def distance_to(ot) (x-ot.x)**2 + (y-ot.y)**2 + (z-ot.z)**2 end end end