Merge remote-tracking branch 'jjyg/master'
commit
6f4f3039e1
@ -1 +1 @@
|
||||
Subproject commit ad38c5e96b05fedf16114fd16bd463e933f13582
|
||||
Subproject commit eb6f706d702367ea3121272670e603000bbdd42a
|
@ -0,0 +1,305 @@
|
||||
module DFHack
|
||||
class << self
|
||||
def building_find(what=:selected, y=nil, z=nil)
|
||||
if what == :selected
|
||||
case ui.main.mode
|
||||
when :LookAround
|
||||
k = ui_look_list.items[ui_look_cursor]
|
||||
k.building if k.type == :Building
|
||||
when :BuildingItems, :QueryBuilding
|
||||
world.selected_building
|
||||
end
|
||||
|
||||
elsif what.kind_of?(Integer)
|
||||
# search by building.id
|
||||
return world.buildings.all.binsearch(what) if not z
|
||||
|
||||
# search by coordinates
|
||||
x = what
|
||||
world.buildings.all.find { |b|
|
||||
b.z == z and
|
||||
if b.room.extents
|
||||
dx = x - b.room.x
|
||||
dy = y - b.room.y
|
||||
dx >= 0 and dx <= b.room.width and
|
||||
dy >= 0 and dy <= b.room.height and
|
||||
b.room.extents[ dy*b.room.width + dx ] > 0
|
||||
else
|
||||
b.x1 <= x and b.x2 >= x and
|
||||
b.y1 <= y and b.y2 >= y
|
||||
end
|
||||
}
|
||||
|
||||
elsif what.respond_to?(:x) or what.respond_to?(:pos)
|
||||
# find the building at the same position
|
||||
what = what.pos if what.respond_to?(:pos)
|
||||
building_find(what.x, what.y, what.z)
|
||||
|
||||
else
|
||||
raise "what what?"
|
||||
end
|
||||
end
|
||||
|
||||
# allocate a new building object
|
||||
def building_alloc(type, subtype=-1, custom=-1)
|
||||
cls = rtti_n2c[BuildingType::Classname[type].to_sym]
|
||||
raise "invalid building type #{type.inspect}" if not cls
|
||||
bld = cls.cpp_new
|
||||
bld.race = ui.race_id
|
||||
bld.setSubtype(subtype) if subtype != -1
|
||||
bld.setCustomType(custom) if custom != -1
|
||||
case type
|
||||
when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0
|
||||
when :Coffin; bld.initBurialFlags
|
||||
when :Trap; bld.unk_cc = 500 if bld.trap_type == :PressurePlate
|
||||
end
|
||||
bld
|
||||
end
|
||||
|
||||
# used by building_setsize
|
||||
def building_check_bridge_support(bld)
|
||||
x1 = bld.x1-1
|
||||
x2 = bld.x2+1
|
||||
y1 = bld.y1-1
|
||||
y2 = bld.y2+1
|
||||
z = bld.z
|
||||
(x1..x2).each { |x|
|
||||
(y1..y2).each { |y|
|
||||
next if ((x == x1 or x == x2) and
|
||||
(y == y1 or y == y2))
|
||||
if mb = map_block_at(x, y, z) and tile = mb.tiletype[x%16][y%16] and TiletypeShape::BasicShape[Tiletype::Shape[tile]] != :Open
|
||||
bld.gate_flags.has_support = true
|
||||
return
|
||||
end
|
||||
}
|
||||
}
|
||||
bld.gate_flags.has_support = false
|
||||
end
|
||||
|
||||
# sets x2/centerx/y2/centery from x1/y1/bldtype
|
||||
# x2/y2 preserved for :FarmPlot etc
|
||||
def building_setsize(bld)
|
||||
bld.x2 = bld.x1 if bld.x1 > bld.x2
|
||||
bld.y2 = bld.y1 if bld.y1 > bld.y2
|
||||
case bld.getType
|
||||
when :Bridge
|
||||
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||
building_check_bridge_support(bld)
|
||||
when :FarmPlot, :RoadDirt, :RoadPaved, :Stockpile, :Civzone
|
||||
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||
when :TradeDepot, :Shop
|
||||
bld.x2 = bld.x1+4
|
||||
bld.y2 = bld.y1+4
|
||||
bld.centerx = bld.x1+2
|
||||
bld.centery = bld.y1+2
|
||||
when :SiegeEngine, :Windmill, :Wagon
|
||||
bld.x2 = bld.x1+2
|
||||
bld.y2 = bld.y1+2
|
||||
bld.centerx = bld.x1+1
|
||||
bld.centery = bld.y1+1
|
||||
when :AxleHorizontal
|
||||
if bld.is_vertical == 1
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||
else
|
||||
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
end
|
||||
when :WaterWheel
|
||||
if bld.is_vertical == 1
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.y1+2
|
||||
bld.centery = bld.y1+1
|
||||
else
|
||||
bld.x2 = bld.x1+2
|
||||
bld.centerx = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
end
|
||||
when :Workshop, :Furnace
|
||||
# Furnace = Custom or default case only
|
||||
case bld.type
|
||||
when :Quern, :Millstone, :Tool
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
when :Siege, :Kennels
|
||||
bld.x2 = bld.x1+4
|
||||
bld.y2 = bld.y1+4
|
||||
bld.centerx = bld.x1+2
|
||||
bld.centery = bld.y1+2
|
||||
when :Custom
|
||||
if bdef = world.raws.buildings.all.binsearch(bld.getCustomType)
|
||||
bld.x2 = bld.x1 + bdef.dim_x - 1
|
||||
bld.y2 = bld.y1 + bdef.dim_y - 1
|
||||
bld.centerx = bld.x1 + bdef.workloc_x
|
||||
bld.centery = bld.y1 + bdef.workloc_y
|
||||
end
|
||||
else
|
||||
bld.x2 = bld.x1+2
|
||||
bld.y2 = bld.y1+2
|
||||
bld.centerx = bld.x1+1
|
||||
bld.centery = bld.y1+1
|
||||
end
|
||||
when :ScrewPump
|
||||
case bld.direction
|
||||
when :FromEast
|
||||
bld.x2 = bld.centerx = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
when :FromSouth
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1+1
|
||||
when :FromWest
|
||||
bld.x2 = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
bld.centerx = bld.x1
|
||||
else
|
||||
bld.x2 = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
bld.centerx = bld.x1
|
||||
end
|
||||
when :Well
|
||||
bld.bucket_z = bld.z
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
when :Construction
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
bld.setMaterialAmount(1)
|
||||
return
|
||||
else
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
end
|
||||
bld.setMaterialAmount((bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1)
|
||||
end
|
||||
|
||||
# set building at position, with optional width/height
|
||||
def building_position(bld, pos, w=nil, h=nil)
|
||||
bld.x1 = pos.x
|
||||
bld.y1 = pos.y
|
||||
bld.z = pos.z
|
||||
bld.x2 = bld.x1+w-1 if w
|
||||
bld.y2 = bld.y1+h-1 if h
|
||||
building_setsize(bld)
|
||||
end
|
||||
|
||||
# set map occupancy/stockpile/etc for a building
|
||||
def building_setoccupancy(bld)
|
||||
stockpile = (bld.getType == :Stockpile)
|
||||
complete = (bld.getBuildStage >= bld.getMaxBuildStage)
|
||||
extents = (bld.room.extents and bld.isExtentShaped)
|
||||
|
||||
z = bld.z
|
||||
(bld.x1..bld.x2).each { |x|
|
||||
(bld.y1..bld.y2).each { |y|
|
||||
next if !extents or bld.room.extents[bld.room.width*(y-bld.room.y)+(x-bld.room.x)] == 0
|
||||
next if not mb = map_block_at(x, y, z)
|
||||
des = mb.designation[x%16][y%16]
|
||||
des.pile = stockpile
|
||||
des.dig = :No
|
||||
if complete
|
||||
bld.updateOccupancy(x, y)
|
||||
else
|
||||
mb.occupancy[x%16][y%16].building = :Planned
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# link bld into other rooms if it is inside their extents
|
||||
def building_linkrooms(bld)
|
||||
didstuff = false
|
||||
world.buildings.other[:ANY_FREE].each { |ob|
|
||||
next if !ob.is_room or ob.z != bld.z
|
||||
next if !ob.room.extents or !ob.isExtentShaped or ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)] == 0
|
||||
didstuff = true
|
||||
ob.children << bld
|
||||
bld.parents << ob
|
||||
}
|
||||
ui.equipment.update.buildings = true if didstuff
|
||||
end
|
||||
|
||||
# link the building into the world, set map data, link rooms, bld.id
|
||||
def building_link(bld)
|
||||
bld.id = df.building_next_id
|
||||
df.building_next_id += 1
|
||||
|
||||
world.buildings.all << bld
|
||||
bld.categorize(true)
|
||||
building_setoccupancy(bld) if bld.isSettingOccupancy
|
||||
building_linkrooms(bld)
|
||||
end
|
||||
|
||||
# set a design for the building
|
||||
def building_createdesign(bld, rough=true)
|
||||
job = bld.jobs[0]
|
||||
job.mat_type = bld.mat_type
|
||||
job.mat_index = bld.mat_index
|
||||
if bld.needsDesign
|
||||
bld.design = BuildingDesign.cpp_new
|
||||
bld.design.flags.rough = rough
|
||||
end
|
||||
end
|
||||
|
||||
# creates a job to build bld, return it
|
||||
def building_linkforconstruct(bld)
|
||||
building_link bld
|
||||
ref = GeneralRefBuildingHolderst.cpp_new
|
||||
ref.building_id = bld.id
|
||||
job = Job.cpp_new
|
||||
job.job_type = :ConstructBuilding
|
||||
job.pos = [bld.centerx, bld.centery, bld.z]
|
||||
job.references << ref
|
||||
bld.jobs << job
|
||||
job_link job
|
||||
job
|
||||
end
|
||||
|
||||
# construct a building with items or JobItems
|
||||
def building_construct(bld, items)
|
||||
job = building_linkforconstruct(bld)
|
||||
rough = false
|
||||
items.each { |item|
|
||||
if items.kind_of?(JobItem)
|
||||
item.quantity = (bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1 if item.quantity < 0
|
||||
job.job_items << item
|
||||
else
|
||||
job_attachitem(job, item, :Hauled)
|
||||
end
|
||||
rough = true if item.getType == :BOULDER
|
||||
bld.mat_type = item.getMaterial if bld.mat_type == -1
|
||||
bld.mat_index = item.getMaterialIndex if bld.mat_index == -1
|
||||
}
|
||||
building_createdesign(bld, rough)
|
||||
end
|
||||
|
||||
# creates a job to deconstruct the building
|
||||
def building_deconstruct(bld)
|
||||
job = Job.cpp_new
|
||||
refbuildingholder = GeneralRefBuildingHolderst.cpp_new
|
||||
job.job_type = :DestroyBuilding
|
||||
refbuildingholder.building_id = building.id
|
||||
job.references << refbuildingholder
|
||||
building.jobs << job
|
||||
job_link job
|
||||
job
|
||||
end
|
||||
|
||||
# exemple usage
|
||||
def buildbed(pos=cursor)
|
||||
raise 'where to ?' if pos.x < 0
|
||||
|
||||
item = world.items.all.find { |i|
|
||||
i.kind_of?(ItemBedst) and
|
||||
i.itemrefs.empty? and
|
||||
!i.flags.in_job
|
||||
}
|
||||
raise 'no free bed, build more !' if not item
|
||||
|
||||
bld = building_alloc(:Bed)
|
||||
building_position(bld, pos)
|
||||
building_construct(bld, [item])
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,44 @@
|
||||
module DFHack
|
||||
class << self
|
||||
# return an Item
|
||||
# arg similar to unit.rb/unit_find; no arg = 'k' menu
|
||||
def item_find(what=:selected, y=nil, z=nil)
|
||||
if what == :selected
|
||||
if curview._rtti_classname == :viewscreen_itemst
|
||||
ref = curview.entry_ref[curview.cursor_pos]
|
||||
ref.item_tg if ref.kind_of?(GeneralRefItem)
|
||||
else
|
||||
case ui.main.mode
|
||||
when :LookAround
|
||||
k = ui_look_list.items[ui_look_cursor]
|
||||
case k.type
|
||||
when :Item
|
||||
k.item
|
||||
when :Building
|
||||
# hilight a constructed bed/coffer
|
||||
mats = k.building.contained_items.find_all { |i| i.use_mode == 2 }
|
||||
mats[0].item if mats.length == 1
|
||||
end
|
||||
when :BuildingItems
|
||||
bld = world.selected_building
|
||||
bld.contained_items[ui_building_item_cursor].item if bld
|
||||
when :ViewUnits
|
||||
u = world.units.active[ui_selected_unit]
|
||||
u.inventory[ui_look_cursor].item if u and u.pos.z == cursor.z and
|
||||
ui_unit_view_mode.value == :Inventory and u.inventory[ui_look_cursor]
|
||||
end
|
||||
end
|
||||
elsif what.kind_of?(Integer)
|
||||
# search by id
|
||||
return world.items.all.binsearch(what) if not z
|
||||
# search by position
|
||||
x = what
|
||||
world.items.all.find { |i| i.pos.x == x and i.pos.y == y and i.pos.z == z }
|
||||
elsif what.respond_to?(:x) or what.respond_to?(:pos)
|
||||
world.items.all.find { |i| same_pos?(what, i) }
|
||||
else
|
||||
raise "what what?"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,35 @@
|
||||
module DFHack
|
||||
class << self
|
||||
# link a job to the world
|
||||
# allocate & set job.id, allocate a JobListLink, link to job & world.job_list
|
||||
def job_link(job)
|
||||
lastjob = world.job_list
|
||||
lastjob = lastjob.next while lastjob.next
|
||||
joblink = JobListLink.cpp_new
|
||||
joblink.prev = lastjob
|
||||
joblink.item = job
|
||||
job.list_link = joblink
|
||||
job.id = df.job_next_id
|
||||
df.job_next_id += 1
|
||||
lastjob.next = joblink
|
||||
end
|
||||
|
||||
# attach an item to a job, flag item in_job
|
||||
def job_attachitem(job, item, role=:Hauled, filter_idx=-1)
|
||||
if role != :TargetContainer
|
||||
item.flags.in_job = true
|
||||
end
|
||||
|
||||
itemlink = SpecificRef.cpp_new
|
||||
itemlink.type = :JOB
|
||||
itemlink.job = job
|
||||
item.specific_refs << itemlink
|
||||
|
||||
joblink = JobItemRef.cpp_new
|
||||
joblink.item = item
|
||||
joblink.role = role
|
||||
joblink.job_item_idx = filter_idx
|
||||
job.items << joblink
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,166 @@
|
||||
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, 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 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
|
||||
|
||||
# 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] & (1 << @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 world_data.geo_biome for current tile
|
||||
def geo_biome
|
||||
b = 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.geo_biomes[ wd.region_map[rx][ry].geo_index ]
|
||||
end
|
||||
|
||||
# return the world_data.geo_biome.layer for current tile
|
||||
def stone_layer
|
||||
geo_biome.layers[designation.geolayer_index]
|
||||
end
|
||||
|
||||
# current tile mat_index (vein if applicable, or base material)
|
||||
def mat_index
|
||||
mat_index_vein or stone_layer.mat_index
|
||||
end
|
||||
|
||||
# MaterialInfo: inorganic token for current tile
|
||||
def mat_info
|
||||
MaterialInfo.new(0, mat_index)
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<MapTile pos=[#@x, #@y, #@z] shape=#{shape} tilemat=#{tilemat} material=#{mat_info.token}>"
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,199 @@
|
||||
module DFHack
|
||||
class MaterialInfo
|
||||
attr_accessor :mat_type, :mat_index
|
||||
attr_accessor :mode, :material, :creature, :figure, :plant, :inorganic
|
||||
def initialize(what, idx=nil)
|
||||
case what
|
||||
when Integer
|
||||
@mat_type, @mat_index = what, idx
|
||||
decode_type_index
|
||||
when String
|
||||
decode_string(what)
|
||||
else
|
||||
@mat_type, @mat_index = what.mat_type, what.mat_index
|
||||
decode_type_index
|
||||
end
|
||||
end
|
||||
|
||||
CREATURE_BASE = 19
|
||||
FIGURE_BASE = CREATURE_BASE+200
|
||||
PLANT_BASE = FIGURE_BASE+200
|
||||
END_BASE = PLANT_BASE+200
|
||||
|
||||
# interpret the mat_type and mat_index fields
|
||||
def decode_type_index
|
||||
if @mat_index < 0 or @mat_type >= END_BASE
|
||||
@mode = :Builtin
|
||||
@material = df.world.raws.mat_table.builtin[@mat_type]
|
||||
|
||||
elsif @mat_type >= PLANT_BASE
|
||||
@mode = :Plant
|
||||
@plant = df.world.raws.plants.all[@mat_index]
|
||||
@material = @plant.material[@mat_type-PLANT_BASE] if @plant
|
||||
|
||||
elsif @mat_type >= FIGURE_BASE
|
||||
@mode = :Figure
|
||||
@figure = df.world.history.figures.binsearch(@mat_index)
|
||||
@creature = df.world.raws.creatures.all[@figure.race] if @figure
|
||||
@material = @creature.material[@mat_type-FIGURE_BASE] if @creature
|
||||
|
||||
elsif @mat_type >= CREATURE_BASE
|
||||
@mode = :Creature
|
||||
@creature = df.world.raws.creatures.all[@mat_index]
|
||||
@material = @creature.material[@mat_type-CREATURE_BASE] if @creature
|
||||
|
||||
elsif @mat_type > 0
|
||||
@mode = :Builtin
|
||||
@material = df.world.raws.mat_table.builtin[@mat_type]
|
||||
|
||||
elsif @mat_type == 0
|
||||
@mode = :Inorganic
|
||||
@inorganic = df.world.raws.inorganics[@mat_index]
|
||||
@material = @inorganic.material if @inorganic
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string(str)
|
||||
parts = str.split(':')
|
||||
case parts[0].chomp('_MAT')
|
||||
when 'INORGANIC', 'STONE', 'METAL'
|
||||
decode_string_inorganic(parts)
|
||||
when 'PLANT'
|
||||
decode_string_plant(parts)
|
||||
when 'CREATURE'
|
||||
if parts[3] and parts[3] != 'NONE'
|
||||
decode_string_figure(parts)
|
||||
else
|
||||
decode_string_creature(parts)
|
||||
end
|
||||
when 'INVALID'
|
||||
@mat_type = parts[1].to_i
|
||||
@mat_index = parts[2].to_i
|
||||
else
|
||||
decode_string_builtin(parts)
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string_inorganic(parts)
|
||||
@@inorganics_index ||= (0...df.world.raws.inorganics.length).inject({}) { |h, i| h.update df.world.raws.inorganics[i].id => i }
|
||||
|
||||
@mode = :Inorganic
|
||||
@mat_type = 0
|
||||
|
||||
if parts[1] and parts[1] != 'NONE'
|
||||
@mat_index = @@inorganics_index[parts[1]]
|
||||
raise "invalid inorganic token #{parts.join(':').inspect}" if not @mat_index
|
||||
@inorganic = df.world.raws.inorganics[@mat_index]
|
||||
@material = @inorganic.material
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string_builtin(parts)
|
||||
@@builtins_index ||= (1...df.world.raws.mat_table.builtin.length).inject({}) { |h, i| b = df.world.raws.mat_table.builtin[i] ; b ? h.update(b.id => i) : h }
|
||||
|
||||
@mode = :Builtin
|
||||
@mat_index = -1
|
||||
@mat_type = @@builtins_index[parts[0]]
|
||||
raise "invalid builtin token #{parts.join(':').inspect}" if not @mat_type
|
||||
@material = df.world.raws.mat_table.builtin[@mat_type]
|
||||
|
||||
if parts[0] == 'COAL' and parts[1]
|
||||
@mat_index = ['COKE', 'CHARCOAL'].index(parts[1]) || -1
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string_creature(parts)
|
||||
@@creatures_index ||= (0...df.world.raws.creatures.all.length).inject({}) { |h, i| h.update df.world.raws.creatures.all[i].creature_id => i }
|
||||
|
||||
@mode = :Creature
|
||||
|
||||
if parts[1] and parts[1] != 'NONE'
|
||||
@mat_index = @@creatures_index[parts[1]]
|
||||
raise "invalid creature token #{parts.join(':').inspect}" if not @mat_index
|
||||
@creature = df.world.raws.creatures.all[@mat_index]
|
||||
end
|
||||
|
||||
if @creature and parts[2] and parts[2] != 'NONE'
|
||||
@mat_type = @creature.material.index { |m| m.id == parts[2] }
|
||||
@material = @creature.material[@mat_type]
|
||||
@mat_type += CREATURE_BASE
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string_figure(parts)
|
||||
@mode = :Figure
|
||||
@mat_index = parts[3].to_i
|
||||
@figure = df.world.history.figures.binsearch(@mat_index)
|
||||
raise "invalid creature histfig #{parts.join(':').inspect}" if not @figure
|
||||
|
||||
@creature = df.world.raws.creatures.all[@figure.race]
|
||||
if parts[1] and parts[1] != 'NONE'
|
||||
raise "invalid histfig race #{parts.join(':').inspect}" if @creature.creature_id != parts[1]
|
||||
end
|
||||
|
||||
if @creature and parts[2] and parts[2] != 'NONE'
|
||||
@mat_type = @creature.material.index { |m| m.id == parts[2] }
|
||||
@material = @creature.material[@mat_type]
|
||||
@mat_type += FIGURE_BASE
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string_plant(parts)
|
||||
@@plants_index ||= (0...df.world.raws.plants.all.length).inject({}) { |h, i| h.update df.world.raws.plants.all[i].id => i }
|
||||
|
||||
@mode = :Plant
|
||||
|
||||
if parts[1] and parts[1] != 'NONE'
|
||||
@mat_index = @@plants_index[parts[1]]
|
||||
raise "invalid plant token #{parts.join(':').inspect}" if not @mat_index
|
||||
@plant = df.world.raws.plants.all[@mat_index]
|
||||
end
|
||||
|
||||
if @plant and parts[2] and parts[2] != 'NONE'
|
||||
@mat_type = @plant.material.index { |m| m.id == parts[2] }
|
||||
raise "invalid plant type #{parts.join(':').inspect}" if not @mat_type
|
||||
@material = @plant.material[@mat_type]
|
||||
@mat_type += PLANT_BASE
|
||||
end
|
||||
end
|
||||
|
||||
# delete the caches of raws id => index used in decode_string
|
||||
def self.flush_raws_cache
|
||||
@@inorganics_index = @@plants_index = @@creatures_index = @@builtins_index = nil
|
||||
end
|
||||
|
||||
def token
|
||||
out = []
|
||||
case @mode
|
||||
when :Builtin
|
||||
out << (@material ? @material.id : 'NONE')
|
||||
out << (['COKE', 'CHARCOAL'][@mat_index] || 'NONE') if @material and @material.id == 'COAL' and @mat_index >= 0
|
||||
when :Inorganic
|
||||
out << 'INORGANIC'
|
||||
out << @inorganic.id if @inorganic
|
||||
when :Plant
|
||||
out << 'PLANT_MAT'
|
||||
out << @plant.id if @plant
|
||||
out << @material.id if @plant and @material
|
||||
when :Creature, :Figure
|
||||
out << 'CREATURE_MAT'
|
||||
out << @creature.creature_id if @creature
|
||||
out << @material.id if @creature and @material
|
||||
out << @figure.id.to_s if @creature and @material and @figure
|
||||
else
|
||||
out << 'INVALID'
|
||||
out << @mat_type.to_s
|
||||
out << @mat_index.to_s
|
||||
end
|
||||
out.join(':')
|
||||
end
|
||||
|
||||
def to_s ; token ; end
|
||||
end
|
||||
|
||||
class << self
|
||||
def decode_mat(what, idx=nil)
|
||||
MaterialInfo.new(what, idx)
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,111 @@
|
||||
module DFHack
|
||||
class << self
|
||||
# return a Plant
|
||||
# arg similar to unit.rb/unit_find, no menu
|
||||
def plant_find(what=cursor)
|
||||
if what.kind_of?(Integer)
|
||||
world.items.all.binsearch(what)
|
||||
elsif what.respond_to?(:x) or what.respond_to?(:pos)
|
||||
world.plants.all.find { |p| same_pos?(what, p) }
|
||||
else
|
||||
raise "what what?"
|
||||
end
|
||||
end
|
||||
|
||||
def each_tree(material=:any)
|
||||
@raws_tree_name ||= {}
|
||||
if @raws_tree_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_tree_name[idx] = p.id if p.flags[:TREE]
|
||||
}
|
||||
end
|
||||
|
||||
if material != :any
|
||||
mat = match_rawname(material, @raws_tree_name.values)
|
||||
unless wantmat = @raws_tree_name.index(mat)
|
||||
raise "invalid tree material #{material}"
|
||||
end
|
||||
end
|
||||
|
||||
world.plants.all.each { |plant|
|
||||
next if not @raws_tree_name[plant.material]
|
||||
next if wantmat and plant.material != wantmat
|
||||
yield plant
|
||||
}
|
||||
end
|
||||
|
||||
def each_shrub(material=:any)
|
||||
@raws_shrub_name ||= {}
|
||||
if @raws_tree_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_shrub_name[idx] = p.id if not p.flags[:GRASS] and not p.flags[:TREE]
|
||||
}
|
||||
end
|
||||
|
||||
if material != :any
|
||||
mat = match_rawname(material, @raws_shrub_name.values)
|
||||
unless wantmat = @raws_shrub_name.index(mat)
|
||||
raise "invalid shrub material #{material}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SaplingToTreeAge = 120960
|
||||
def cuttrees(material=nil, count_max=100)
|
||||
if !material
|
||||
# list trees
|
||||
cnt = Hash.new(0)
|
||||
each_tree { |plant|
|
||||
next if plant.grow_counter < SaplingToTreeAge
|
||||
next if map_designation_at(plant).hidden
|
||||
cnt[plant.material] += 1
|
||||
}
|
||||
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = @raws_tree_name[mat]
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
else
|
||||
cnt = 0
|
||||
each_tree(material) { |plant|
|
||||
next if plant.grow_counter < SaplingToTreeAge
|
||||
b = map_block_at(plant)
|
||||
d = b.designation[plant.pos.x%16][plant.pos.y%16]
|
||||
next if d.hidden
|
||||
if d.dig == :No
|
||||
d.dig = :Default
|
||||
b.flags.designated = true
|
||||
cnt += 1
|
||||
break if cnt == count_max
|
||||
end
|
||||
}
|
||||
puts "Updated #{cnt} plant designations"
|
||||
end
|
||||
end
|
||||
|
||||
def growtrees(material=nil, count_max=100)
|
||||
if !material
|
||||
# list plants
|
||||
cnt = Hash.new(0)
|
||||
each_tree { |plant|
|
||||
next if plant.grow_counter >= SaplingToTreeAge
|
||||
next if map_designation_at(plant).hidden
|
||||
cnt[plant.material] += 1
|
||||
}
|
||||
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = @raws_tree_name[mat]
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
else
|
||||
cnt = 0
|
||||
each_tree(material) { |plant|
|
||||
next if plant.grow_counter >= SaplingToTreeAge
|
||||
next if map_designation_at(plant).hidden
|
||||
plant.grow_counter = SaplingToTreeAge
|
||||
cnt += 1
|
||||
break if cnt == count_max
|
||||
}
|
||||
puts "Grown #{cnt} saplings"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,266 +0,0 @@
|
||||
module DFHack
|
||||
|
||||
# allocate a new building object
|
||||
def self.building_alloc(type, subtype=-1, custom=-1)
|
||||
cls = rtti_n2c[BuildingType::Classname[type].to_sym]
|
||||
raise "invalid building type #{type.inspect}" if not cls
|
||||
bld = cls.cpp_new
|
||||
bld.race = ui.race_id
|
||||
bld.setSubtype(subtype) if subtype != -1
|
||||
bld.setCustomType(custom) if custom != -1
|
||||
case type
|
||||
when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0
|
||||
when :Coffin; bld.initBurialFlags
|
||||
when :Trap; bld.unk_cc = 500 if bld.trap_type == :PressurePlate
|
||||
end
|
||||
bld
|
||||
end
|
||||
|
||||
# used by building_setsize
|
||||
def self.building_check_bridge_support(bld)
|
||||
x1 = bld.x1-1
|
||||
x2 = bld.x2+1
|
||||
y1 = bld.y1-1
|
||||
y2 = bld.y2+1
|
||||
z = bld.z
|
||||
(x1..x2).each { |x|
|
||||
(y1..y2).each { |y|
|
||||
next if ((x == x1 or x == x2) and
|
||||
(y == y1 or y == y2))
|
||||
if mb = map_block_at(x, y, z) and tile = mb.tiletype[x%16][y%16] and TiletypeShape::BasicShape[Tiletype::Shape[tile]] == :Open
|
||||
bld.gate_flags.has_support = true
|
||||
return
|
||||
end
|
||||
}
|
||||
}
|
||||
bld.gate_flags.has_support = false
|
||||
end
|
||||
|
||||
# sets x2/centerx/y2/centery from x1/y1/bldtype
|
||||
# x2/y2 preserved for :FarmPlot etc
|
||||
def self.building_setsize(bld)
|
||||
bld.x2 = bld.x1 if bld.x1 > bld.x2
|
||||
bld.y2 = bld.y1 if bld.y1 > bld.y2
|
||||
case bld.getType
|
||||
when :Bridge
|
||||
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||
building_check_bridge_support(bld)
|
||||
when :FarmPlot, :RoadDirt, :RoadPaved, :Stockpile, :Civzone
|
||||
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||
when :TradeDepot, :Shop
|
||||
bld.x2 = bld.x1+4
|
||||
bld.y2 = bld.y1+4
|
||||
bld.centerx = bld.x1+2
|
||||
bld.centery = bld.y1+2
|
||||
when :SiegeEngine, :Windmill, :Wagon
|
||||
bld.x2 = bld.x1+2
|
||||
bld.y2 = bld.y1+2
|
||||
bld.centerx = bld.x1+1
|
||||
bld.centery = bld.y1+1
|
||||
when :AxleHorizontal
|
||||
if bld.is_vertical == 1
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||
else
|
||||
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
end
|
||||
when :WaterWheel
|
||||
if bld.is_vertical == 1
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.y1+2
|
||||
bld.centery = bld.y1+1
|
||||
else
|
||||
bld.x2 = bld.x1+2
|
||||
bld.centerx = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
end
|
||||
when :Workshop, :Furnace
|
||||
# Furnace = Custom or default case only
|
||||
case bld.type
|
||||
when :Quern, :Millstone, :Tool
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
when :Siege, :Kennels
|
||||
bld.x2 = bld.x1+4
|
||||
bld.y2 = bld.y1+4
|
||||
bld.centerx = bld.x1+2
|
||||
bld.centery = bld.y1+2
|
||||
when :Custom
|
||||
if bdef = world.raws.buildings.all.binsearch(bld.getCustomType)
|
||||
bld.x2 = bld.x1 + bdef.dim_x - 1
|
||||
bld.y2 = bld.y1 + bdef.dim_y - 1
|
||||
bld.centerx = bld.x1 + bdef.workloc_x
|
||||
bld.centery = bld.y1 + bdef.workloc_y
|
||||
end
|
||||
else
|
||||
bld.x2 = bld.x1+2
|
||||
bld.y2 = bld.y1+2
|
||||
bld.centerx = bld.x1+1
|
||||
bld.centery = bld.y1+1
|
||||
end
|
||||
when :ScrewPump
|
||||
case bld.direction
|
||||
when :FromEast
|
||||
bld.x2 = bld.centerx = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
when :FromSouth
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1+1
|
||||
when :FromWest
|
||||
bld.x2 = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
bld.centerx = bld.x1
|
||||
else
|
||||
bld.x2 = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
bld.centerx = bld.x1
|
||||
end
|
||||
when :Well
|
||||
bld.bucket_z = bld.z
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
when :Construction
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
bld.setMaterialAmount(1)
|
||||
return
|
||||
else
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
end
|
||||
bld.setMaterialAmount((bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1)
|
||||
end
|
||||
|
||||
# set building at position, with optional width/height
|
||||
def self.building_position(bld, pos, w=nil, h=nil)
|
||||
bld.x1 = pos.x
|
||||
bld.y1 = pos.y
|
||||
bld.z = pos.z
|
||||
bld.x2 = bld.x1+w-1 if w
|
||||
bld.y2 = bld.y1+h-1 if h
|
||||
building_setsize(bld)
|
||||
end
|
||||
|
||||
# set map occupancy/stockpile/etc for a building
|
||||
def self.building_setoccupancy(bld)
|
||||
stockpile = (bld.getType == :Stockpile)
|
||||
complete = (bld.getBuildStage >= bld.getMaxBuildStage)
|
||||
extents = (bld.room.extents and bld.isExtentShaped)
|
||||
|
||||
z = bld.z
|
||||
(bld.x1..bld.x2).each { |x|
|
||||
(bld.y1..bld.y2).each { |y|
|
||||
next if !extents or bld.room.extents[bld.room.width*(y-bld.room.y)+(x-bld.room.x)] == 0
|
||||
next if not mb = map_block_at(x, y, z)
|
||||
des = mb.designation[x%16][y%16]
|
||||
des.pile = stockpile
|
||||
des.dig = :No
|
||||
if complete
|
||||
bld.updateOccupancy(x, y)
|
||||
else
|
||||
mb.occupancy[x%16][y%16].building = :Planned
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# link bld into other rooms if it is inside their extents
|
||||
def self.building_linkrooms(bld)
|
||||
didstuff = false
|
||||
world.buildings.other[:ANY_FREE].each { |ob|
|
||||
next if !ob.is_room or ob.z != bld.z
|
||||
next if !ob.room.extents or !ob.isExtentShaped or ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)] == 0
|
||||
didstuff = true
|
||||
ob.children << bld
|
||||
bld.parents << ob
|
||||
}
|
||||
ui.equipment.update.buildings = true if didstuff
|
||||
end
|
||||
|
||||
# link the building into the world, set map data, link rooms, bld.id
|
||||
def self.building_link(bld)
|
||||
bld.id = df.building_next_id
|
||||
df.building_next_id += 1
|
||||
|
||||
world.buildings.all << bld
|
||||
bld.categorize(true)
|
||||
building_setoccupancy(bld) if bld.isSettingOccupancy
|
||||
building_linkrooms(bld)
|
||||
end
|
||||
|
||||
# set a design for the building
|
||||
def self.building_createdesign(bld, rough=true)
|
||||
job = bld.jobs[0]
|
||||
job.mat_type = bld.mat_type
|
||||
job.mat_index = bld.mat_index
|
||||
if bld.needsDesign
|
||||
bld.design = BuildingDesign.cpp_new
|
||||
bld.design.flags.rough = rough
|
||||
end
|
||||
end
|
||||
|
||||
# creates a job to build bld, return it
|
||||
def self.building_linkforconstruct(bld)
|
||||
building_link bld
|
||||
ref = GeneralRefBuildingHolderst.cpp_new
|
||||
ref.building_id = bld.id
|
||||
job = Job.cpp_new
|
||||
job.job_type = :ConstructBuilding
|
||||
job.pos = [bld.centerx, bld.centery, bld.z]
|
||||
job.references << ref
|
||||
bld.jobs << job
|
||||
job_link job
|
||||
job
|
||||
end
|
||||
|
||||
# construct a building with items or JobItems
|
||||
def self.building_construct(bld, items)
|
||||
job = building_linkforconstruct(bld)
|
||||
rough = false
|
||||
items.each { |item|
|
||||
if items.kind_of?(JobItem)
|
||||
item.quantity = (bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1 if item.quantity < 0
|
||||
job.job_items << item
|
||||
else
|
||||
job_attachitem(job, item, :Hauled)
|
||||
end
|
||||
rough = true if item.getType == :BOULDER
|
||||
bld.mat_type = item.getMaterial if bld.mat_type == -1
|
||||
bld.mat_index = item.getMaterialIndex if bld.mat_index == -1
|
||||
}
|
||||
building_createdesign(bld, rough)
|
||||
end
|
||||
|
||||
# creates a job to deconstruct the building
|
||||
def self.building_deconstruct(bld)
|
||||
job = Job.cpp_new
|
||||
refbuildingholder = GeneralRefBuildingHolderst.cpp_new
|
||||
job.job_type = :DestroyBuilding
|
||||
refbuildingholder.building_id = building.id
|
||||
job.references << refbuildingholder
|
||||
building.jobs << job
|
||||
job_link job
|
||||
job
|
||||
end
|
||||
|
||||
# exemple usage
|
||||
def self.buildbed(pos=cursor)
|
||||
suspend {
|
||||
raise 'where to ?' if pos.x < 0
|
||||
|
||||
item = world.items.all.find { |i|
|
||||
i.kind_of?(ItemBedst) and
|
||||
i.itemrefs.empty? and
|
||||
!i.flags.in_job
|
||||
}
|
||||
raise 'no free bed, build more !' if not item
|
||||
|
||||
bld = building_alloc(:Bed)
|
||||
building_position(bld, pos)
|
||||
building_construct(bld, [item])
|
||||
}
|
||||
end
|
||||
end
|
@ -1,152 +0,0 @@
|
||||
module DFHack
|
||||
def self.each_tree(material=:any)
|
||||
@raws_tree_name ||= {}
|
||||
if @raws_tree_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_tree_name[idx] = p.id if p.flags[:TREE]
|
||||
}
|
||||
end
|
||||
|
||||
if material != :any
|
||||
mat = match_rawname(material, @raws_tree_name.values)
|
||||
unless wantmat = @raws_tree_name.index(mat)
|
||||
raise "invalid tree material #{material}"
|
||||
end
|
||||
end
|
||||
|
||||
world.plants.all.each { |plant|
|
||||
next if not @raws_tree_name[plant.material]
|
||||
next if wantmat and plant.material != wantmat
|
||||
yield plant
|
||||
}
|
||||
end
|
||||
|
||||
def self.each_shrub(material=:any)
|
||||
@raws_shrub_name ||= {}
|
||||
if @raws_tree_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_shrub_name[idx] = p.id if not p.flags[:GRASS] and not p.flags[:TREE]
|
||||
}
|
||||
end
|
||||
|
||||
if material != :any
|
||||
mat = match_rawname(material, @raws_shrub_name.values)
|
||||
unless wantmat = @raws_shrub_name.index(mat)
|
||||
raise "invalid shrub material #{material}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SaplingToTreeAge = 120960
|
||||
def self.cuttrees(material=nil, count_max=100)
|
||||
if !material
|
||||
# list trees
|
||||
cnt = Hash.new(0)
|
||||
suspend {
|
||||
each_tree { |plant|
|
||||
next if plant.grow_counter < SaplingToTreeAge
|
||||
next if map_designation_at(plant).hidden
|
||||
cnt[plant.material] += 1
|
||||
}
|
||||
}
|
||||
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = @raws_tree_name[mat]
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
else
|
||||
cnt = 0
|
||||
suspend {
|
||||
each_tree(material) { |plant|
|
||||
next if plant.grow_counter < SaplingToTreeAge
|
||||
b = map_block_at(plant)
|
||||
d = b.designation[plant.pos.x%16][plant.pos.y%16]
|
||||
next if d.hidden
|
||||
if d.dig == :No
|
||||
d.dig = :Default
|
||||
b.flags.designated = true
|
||||
cnt += 1
|
||||
break if cnt == count_max
|
||||
end
|
||||
}
|
||||
}
|
||||
puts "Updated #{cnt} plant designations"
|
||||
end
|
||||
end
|
||||
|
||||
def self.growtrees(material=nil, count_max=100)
|
||||
if !material
|
||||
# list plants
|
||||
cnt = Hash.new(0)
|
||||
suspend {
|
||||
each_tree { |plant|
|
||||
next if plant.grow_counter >= SaplingToTreeAge
|
||||
next if map_designation_at(plant).hidden
|
||||
cnt[plant.material] += 1
|
||||
}
|
||||
}
|
||||
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = @raws_tree_name[mat]
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
else
|
||||
cnt = 0
|
||||
suspend {
|
||||
each_tree(material) { |plant|
|
||||
next if plant.grow_counter >= SaplingToTreeAge
|
||||
next if map_designation_at(plant).hidden
|
||||
plant.grow_counter = SaplingToTreeAge
|
||||
cnt += 1
|
||||
break if cnt == count_max
|
||||
}
|
||||
}
|
||||
puts "Grown #{cnt} saplings"
|
||||
end
|
||||
end
|
||||
|
||||
def self.growcrops(material=nil, count_max=100)
|
||||
@raws_plant_name ||= {}
|
||||
@raws_plant_growdur ||= {}
|
||||
if @raws_plant_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_plant_name[idx] = p.id
|
||||
@raws_plant_growdur[idx] = p.growdur
|
||||
}
|
||||
end
|
||||
|
||||
if !material
|
||||
cnt = Hash.new(0)
|
||||
suspend {
|
||||
world.items.other[:SEEDS].each { |seed|
|
||||
next if not seed.flags.in_building
|
||||
next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||
cnt[seed.mat_index] += 1
|
||||
}
|
||||
}
|
||||
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = world.raws.plants.all[mat].id
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
else
|
||||
if material != :any
|
||||
mat = match_rawname(material, @raws_plant_name.values)
|
||||
unless wantmat = @raws_plant_name.index(mat)
|
||||
raise "invalid plant material #{material}"
|
||||
end
|
||||
end
|
||||
|
||||
cnt = 0
|
||||
suspend {
|
||||
world.items.other[:SEEDS].each { |seed|
|
||||
next if wantmat and seed.mat_index != wantmat
|
||||
next if not seed.flags.in_building
|
||||
next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
|
||||
cnt += 1
|
||||
}
|
||||
}
|
||||
puts "Grown #{cnt} crops"
|
||||
end
|
||||
end
|
||||
end
|
@ -1,52 +0,0 @@
|
||||
module DFHack
|
||||
# returns an Array of all units that are current fort citizen (dwarves, on map, not hostile)
|
||||
def self.unit_citizens
|
||||
race = ui.race_id
|
||||
civ = ui.civ_id
|
||||
world.units.active.find_all { |u|
|
||||
u.race == race and u.civ_id == civ and !u.flags1.dead and !u.flags1.merchant and
|
||||
!u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and
|
||||
!u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and
|
||||
u.mood != :Berserk
|
||||
# TODO check curse ; currently this should keep vampires, but may include werebeasts
|
||||
}
|
||||
end
|
||||
|
||||
# list workers (citizen, not crazy / child / inmood / noble)
|
||||
def self.unit_workers
|
||||
unit_citizens.find_all { |u|
|
||||
u.mood == :None and
|
||||
u.profession != :CHILD and
|
||||
u.profession != :BABY and
|
||||
# TODO MENIAL_WORK_EXEMPTION_SPOUSE
|
||||
!unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] }
|
||||
}
|
||||
end
|
||||
|
||||
# list currently idle workers
|
||||
def self.unit_idlers
|
||||
unit_workers.find_all { |u|
|
||||
# current_job includes eat/drink/sleep/pickupequip
|
||||
!u.job.current_job._getv and
|
||||
# filter 'attend meeting'
|
||||
u.meetings.length == 0 and
|
||||
# filter soldiers (TODO check schedule)
|
||||
u.military.squad_index == -1 and
|
||||
# filter 'on break'
|
||||
!u.status.misc_traits.find { |t| id == :OnBreak }
|
||||
}
|
||||
end
|
||||
|
||||
def self.unit_entitypositions(unit)
|
||||
list = []
|
||||
return list if not hf = world.history.figures.binsearch(unit.hist_figure_id)
|
||||
hf.entity_links.each { |el|
|
||||
next if el._rtti_classname != :histfig_entity_link_positionst
|
||||
next if not ent = world.entities.all.binsearch(el.entity_id)
|
||||
next if not pa = ent.positions.assignments.binsearch(el.assignment_id)
|
||||
next if not pos = ent.positions.own.binsearch(pa.position_id)
|
||||
list << pos
|
||||
}
|
||||
list
|
||||
end
|
||||
end
|
@ -0,0 +1,753 @@
|
||||
# definition of classes used by ruby-autogen
|
||||
module DFHack
|
||||
module MemHack
|
||||
INSPECT_SIZE_LIMIT=16384
|
||||
class MemStruct
|
||||
attr_accessor :_memaddr
|
||||
def _at(addr) ; d = dup ; d._memaddr = addr ; d ; end
|
||||
def _get ; self ; end
|
||||
def _cpp_init ; end
|
||||
end
|
||||
|
||||
class Compound < MemStruct
|
||||
class << self
|
||||
attr_accessor :_fields, :_rtti_classname, :_sizeof
|
||||
def field(name, offset)
|
||||
struct = yield
|
||||
return if not struct
|
||||
@_fields ||= []
|
||||
@_fields << [name, offset, struct]
|
||||
define_method(name) { struct._at(@_memaddr+offset)._get }
|
||||
define_method("#{name}=") { |v| struct._at(@_memaddr+offset)._set(v) }
|
||||
end
|
||||
def _fields_ancestors
|
||||
if superclass.respond_to?(:_fields_ancestors)
|
||||
superclass._fields_ancestors + _fields.to_a
|
||||
else
|
||||
_fields.to_a
|
||||
end
|
||||
end
|
||||
|
||||
def number(bits, signed, initvalue=nil, enum=nil)
|
||||
Number.new(bits, signed, initvalue, enum)
|
||||
end
|
||||
def float
|
||||
Float.new
|
||||
end
|
||||
def bit(shift)
|
||||
BitField.new(shift, 1)
|
||||
end
|
||||
def bits(shift, len, enum=nil)
|
||||
BitField.new(shift, len, enum)
|
||||
end
|
||||
def pointer
|
||||
Pointer.new((yield if block_given?))
|
||||
end
|
||||
def pointer_ary(tglen)
|
||||
PointerAry.new(tglen, yield)
|
||||
end
|
||||
def static_array(len, tglen, indexenum=nil)
|
||||
StaticArray.new(tglen, len, indexenum, yield)
|
||||
end
|
||||
def static_string(len)
|
||||
StaticString.new(len)
|
||||
end
|
||||
|
||||
def stl_vector(tglen=nil)
|
||||
tg = yield if tglen
|
||||
case tglen
|
||||
when 1; StlVector8.new(tg)
|
||||
when 2; StlVector16.new(tg)
|
||||
else StlVector32.new(tg)
|
||||
end
|
||||
end
|
||||
def stl_string
|
||||
StlString.new
|
||||
end
|
||||
def stl_bit_vector
|
||||
StlBitVector.new
|
||||
end
|
||||
def stl_deque(tglen)
|
||||
StlDeque.new(tglen, yield)
|
||||
end
|
||||
|
||||
def df_flagarray(indexenum=nil)
|
||||
DfFlagarray.new(indexenum)
|
||||
end
|
||||
def df_array(tglen)
|
||||
DfArray.new(tglen, yield)
|
||||
end
|
||||
def df_linked_list
|
||||
DfLinkedList.new(yield)
|
||||
end
|
||||
|
||||
def global(glob)
|
||||
Global.new(glob)
|
||||
end
|
||||
def compound(name=nil, &b)
|
||||
m = Class.new(Compound)
|
||||
DFHack.const_set(name, m) if name
|
||||
m.class_eval(&b)
|
||||
m.new
|
||||
end
|
||||
def rtti_classname(n)
|
||||
DFHack.rtti_register(n, self)
|
||||
@_rtti_classname = n
|
||||
end
|
||||
def sizeof(n)
|
||||
@_sizeof = n
|
||||
end
|
||||
|
||||
# allocate a new c++ object, return its ruby wrapper
|
||||
def cpp_new(init=nil)
|
||||
ptr = DFHack.malloc(_sizeof)
|
||||
if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname)
|
||||
DFHack.memory_write_int32(ptr, vt)
|
||||
# TODO call constructor
|
||||
end
|
||||
o = new._at(ptr)
|
||||
o._cpp_init
|
||||
o._set(init) if init
|
||||
o
|
||||
end
|
||||
end
|
||||
def _cpp_init
|
||||
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
|
||||
end
|
||||
def _set(h)
|
||||
case h
|
||||
when Hash; h.each { |k, v| send("#{k}=", v) }
|
||||
when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) }
|
||||
when Compound; _field_names.each { |n| send("#{n}=", h.send(n)) }
|
||||
else raise 'wut?'
|
||||
end
|
||||
end
|
||||
def _fields ; self.class._fields.to_a ; end
|
||||
def _fields_ancestors ; self.class._fields_ancestors.to_a ; end
|
||||
def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end
|
||||
def _rtti_classname ; self.class._rtti_classname ; end
|
||||
def _sizeof ; self.class._sizeof ; end
|
||||
@@inspecting = {} # avoid infinite recursion on mutually-referenced objects
|
||||
def inspect
|
||||
cn = self.class.name.sub(/^DFHack::/, '')
|
||||
cn << ' @' << ('0x%X' % _memaddr) if cn != ''
|
||||
out = "#<#{cn}"
|
||||
return out << ' ...>' if @@inspecting[_memaddr]
|
||||
@@inspecting[_memaddr] = true
|
||||
_fields_ancestors.each { |n, o, s|
|
||||
out << ' ' if out.length != 0 and out[-1, 1] != ' '
|
||||
if out.length > INSPECT_SIZE_LIMIT
|
||||
out << '...'
|
||||
break
|
||||
end
|
||||
out << inspect_field(n, o, s)
|
||||
}
|
||||
out.chomp!(' ')
|
||||
@@inspecting.delete _memaddr
|
||||
out << '>'
|
||||
end
|
||||
def inspect_field(n, o, s)
|
||||
if s.kind_of?(BitField) and s._len == 1
|
||||
send(n) ? n.to_s : ''
|
||||
elsif s.kind_of?(Pointer)
|
||||
"#{n}=#{s._at(@_memaddr+o).inspect}"
|
||||
elsif n == :_whole
|
||||
"_whole=0x#{_whole.to_s(16)}"
|
||||
else
|
||||
v = send(n).inspect
|
||||
"#{n}=#{v}"
|
||||
end
|
||||
rescue
|
||||
"#{n}=ERR(#{$!})"
|
||||
end
|
||||
end
|
||||
|
||||
class Enum
|
||||
# number -> symbol
|
||||
def self.enum
|
||||
# ruby weirdness, needed to make the constants 'virtual'
|
||||
@enum ||= const_get(:ENUM)
|
||||
end
|
||||
# symbol -> number
|
||||
def self.nume
|
||||
@nume ||= const_get(:NUME)
|
||||
end
|
||||
|
||||
def self.int(i)
|
||||
nume[i] || i
|
||||
end
|
||||
def self.sym(i)
|
||||
enum[i] || i
|
||||
end
|
||||
end
|
||||
|
||||
class Number < MemStruct
|
||||
attr_accessor :_bits, :_signed, :_initvalue, :_enum
|
||||
def initialize(bits, signed, initvalue, enum)
|
||||
@_bits = bits
|
||||
@_signed = signed
|
||||
@_initvalue = initvalue
|
||||
@_enum = enum
|
||||
end
|
||||
|
||||
def _get
|
||||
v = case @_bits
|
||||
when 32; DFHack.memory_read_int32(@_memaddr)
|
||||
when 16; DFHack.memory_read_int16(@_memaddr)
|
||||
when 8; DFHack.memory_read_int8( @_memaddr)
|
||||
when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32)
|
||||
end
|
||||
v &= (1 << @_bits) - 1 if not @_signed
|
||||
v = @_enum.sym(v) if @_enum
|
||||
v
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
v = @_enum.int(v) if @_enum
|
||||
case @_bits
|
||||
when 32; DFHack.memory_write_int32(@_memaddr, v)
|
||||
when 16; DFHack.memory_write_int16(@_memaddr, v)
|
||||
when 8; DFHack.memory_write_int8( @_memaddr, v)
|
||||
when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32)
|
||||
end
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
_set(@_initvalue) if @_initvalue
|
||||
end
|
||||
end
|
||||
class Float < MemStruct
|
||||
def _get
|
||||
DFHack.memory_read_float(@_memaddr)
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
DFHack.memory_write_float(@_memaddr, v)
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
_set(0.0)
|
||||
end
|
||||
end
|
||||
class BitField < MemStruct
|
||||
attr_accessor :_shift, :_len, :_enum
|
||||
def initialize(shift, len, enum=nil)
|
||||
@_shift = shift
|
||||
@_len = len
|
||||
@_enum = enum
|
||||
end
|
||||
def _mask
|
||||
(1 << @_len) - 1
|
||||
end
|
||||
|
||||
def _get
|
||||
v = DFHack.memory_read_int32(@_memaddr) >> @_shift
|
||||
if @_len == 1
|
||||
((v & 1) == 0) ? false : true
|
||||
else
|
||||
v &= _mask
|
||||
v = @_enum.sym(v) if @_enum
|
||||
v
|
||||
end
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
if @_len == 1
|
||||
# allow 'bit = 0'
|
||||
v = (v && v != 0 ? 1 : 0)
|
||||
end
|
||||
v = @_enum.int(v) if @_enum
|
||||
v = (v & _mask) << @_shift
|
||||
|
||||
ori = DFHack.memory_read_int32(@_memaddr) & 0xffffffff
|
||||
DFHack.memory_write_int32(@_memaddr, ori - (ori & ((-1 & _mask) << @_shift)) + v)
|
||||
end
|
||||
end
|
||||
|
||||
class Pointer < MemStruct
|
||||
attr_accessor :_tg
|
||||
def initialize(tg)
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
def _getp
|
||||
DFHack.memory_read_int32(@_memaddr) & 0xffffffff
|
||||
end
|
||||
|
||||
def _get
|
||||
addr = _getp
|
||||
return if addr == 0
|
||||
return addr if not @_tg
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
# XXX shaky...
|
||||
def _set(v)
|
||||
if v.kind_of?(Pointer)
|
||||
DFHack.memory_write_int32(@_memaddr, v._getp)
|
||||
elsif v.kind_of?(MemStruct)
|
||||
DFHack.memory_write_int32(@_memaddr, v._memaddr)
|
||||
else
|
||||
_get._set(v)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
ptr = _getp
|
||||
if ptr == 0
|
||||
'NULL'
|
||||
else
|
||||
cn = ''
|
||||
cn = @_tg.class.name.sub(/^DFHack::/, '').sub(/^MemHack::/, '') if @_tg
|
||||
cn = @_tg._glob if cn == 'Global'
|
||||
"#<Pointer #{cn} #{'0x%X' % _getp}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
class PointerAry < MemStruct
|
||||
attr_accessor :_tglen, :_tg
|
||||
def initialize(tglen, tg)
|
||||
@_tglen = tglen
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
def _getp(i=0)
|
||||
delta = (i != 0 ? i*@_tglen : 0)
|
||||
(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta
|
||||
end
|
||||
|
||||
def _get
|
||||
addr = _getp
|
||||
return if addr == 0
|
||||
self
|
||||
end
|
||||
|
||||
def [](i)
|
||||
addr = _getp(i)
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
def []=(i, v)
|
||||
addr = _getp(i)
|
||||
raise 'null pointer' if addr == 0
|
||||
@_tg._at(addr)._set(v)
|
||||
end
|
||||
|
||||
def inspect ; ptr = _getp ; (ptr == 0) ? 'NULL' : "#<PointerAry #{'0x%X' % ptr}>" ; end
|
||||
end
|
||||
module Enumerable
|
||||
include ::Enumerable
|
||||
attr_accessor :_indexenum
|
||||
def each ; (0...length).each { |i| yield self[i] } ; end
|
||||
def inspect
|
||||
out = '['
|
||||
each_with_index { |e, idx|
|
||||
out << ', ' if out.length > 1
|
||||
if out.length > INSPECT_SIZE_LIMIT
|
||||
out << '...'
|
||||
break
|
||||
end
|
||||
out << "#{_indexenum.sym(idx)}=" if _indexenum
|
||||
out << e.inspect
|
||||
}
|
||||
out << ']'
|
||||
end
|
||||
def empty? ; length == 0 ; end
|
||||
def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end
|
||||
def index(elem=nil, &b) ; (0...length).find { |i| b ? b[self[i]] : self[i] == elem } ; end
|
||||
end
|
||||
class StaticArray < MemStruct
|
||||
attr_accessor :_tglen, :_length, :_indexenum, :_tg
|
||||
def initialize(tglen, length, indexenum, tg)
|
||||
@_tglen = tglen
|
||||
@_length = length
|
||||
@_indexenum = indexenum
|
||||
@_tg = tg
|
||||
end
|
||||
def _set(a)
|
||||
a.each_with_index { |v, i| self[i] = v }
|
||||
end
|
||||
def _cpp_init
|
||||
_length.times { |i| _tgat(i)._cpp_init }
|
||||
end
|
||||
alias length _length
|
||||
alias size _length
|
||||
def _tgat(i)
|
||||
@_tg._at(@_memaddr + i*@_tglen) if i >= 0 and i < @_length
|
||||
end
|
||||
def [](i)
|
||||
i = _indexenum.int(i) if _indexenum
|
||||
i += @_length if i < 0
|
||||
_tgat(i)._get
|
||||
end
|
||||
def []=(i, v)
|
||||
i = _indexenum.int(i) if _indexenum
|
||||
i += @_length if i < 0
|
||||
_tgat(i)._set(v)
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
end
|
||||
class StaticString < MemStruct
|
||||
attr_accessor :_length
|
||||
def initialize(length)
|
||||
@_length = length
|
||||
end
|
||||
def _get
|
||||
DFHack.memory_read(@_memaddr, @_length)
|
||||
end
|
||||
def _set(v)
|
||||
DFHack.memory_write(@_memaddr, v[0, @_length])
|
||||
end
|
||||
end
|
||||
|
||||
class StlVector32 < MemStruct
|
||||
attr_accessor :_tg
|
||||
def initialize(tg)
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
def length
|
||||
DFHack.memory_vector32_length(@_memaddr)
|
||||
end
|
||||
def size ; length ; end # alias wouldnt work for subclasses
|
||||
def valueptr_at(idx)
|
||||
DFHack.memory_vector32_ptrat(@_memaddr, idx)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vector32_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vector32_delete(@_memaddr, idx)
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
delete_at(length-1) while length > v.length # match lengthes
|
||||
v.each_with_index { |e, i| self[i] = e } # patch entries
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
DFHack.memory_vector_init(@_memaddr)
|
||||
end
|
||||
|
||||
def clear
|
||||
delete_at(length-1) while length > 0
|
||||
end
|
||||
def [](idx)
|
||||
idx += length if idx < 0
|
||||
@_tg._at(valueptr_at(idx))._get if idx >= 0 and idx < length
|
||||
end
|
||||
def []=(idx, v)
|
||||
idx += length if idx < 0
|
||||
if idx >= length
|
||||
insert_at(idx, 0)
|
||||
elsif idx < 0
|
||||
raise 'invalid idx'
|
||||
end
|
||||
@_tg._at(valueptr_at(idx))._set(v)
|
||||
end
|
||||
def push(v)
|
||||
self[length] = v
|
||||
self
|
||||
end
|
||||
def <<(v) ; push(v) ; end
|
||||
def pop
|
||||
l = length
|
||||
if l > 0
|
||||
v = self[l-1]
|
||||
delete_at(l-1)
|
||||
end
|
||||
v
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
# do a binary search in an ordered vector for a specific target attribute
|
||||
# ex: world.history.figures.binsearch(unit.hist_figure_id)
|
||||
def binsearch(target, field=:id)
|
||||
o_start = 0
|
||||
o_end = length - 1
|
||||
while o_end >= o_start
|
||||
o_half = o_start + (o_end-o_start)/2
|
||||
obj = self[o_half]
|
||||
oval = obj.send(field)
|
||||
if oval == target
|
||||
return obj
|
||||
elsif oval < target
|
||||
o_start = o_half+1
|
||||
else
|
||||
o_end = o_half-1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
class StlVector16 < StlVector32
|
||||
def length
|
||||
DFHack.memory_vector16_length(@_memaddr)
|
||||
end
|
||||
def valueptr_at(idx)
|
||||
DFHack.memory_vector16_ptrat(@_memaddr, idx)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vector16_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vector16_delete(@_memaddr, idx)
|
||||
end
|
||||
end
|
||||
class StlVector8 < StlVector32
|
||||
def length
|
||||
DFHack.memory_vector8_length(@_memaddr)
|
||||
end
|
||||
def valueptr_at(idx)
|
||||
DFHack.memory_vector8_ptrat(@_memaddr, idx)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vector8_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vector8_delete(@_memaddr, idx)
|
||||
end
|
||||
end
|
||||
class StlBitVector < StlVector32
|
||||
def initialize ; end
|
||||
def length
|
||||
DFHack.memory_vectorbool_length(@_memaddr)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vectorbool_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vectorbool_delete(@_memaddr, idx)
|
||||
end
|
||||
def [](idx)
|
||||
idx += length if idx < 0
|
||||
DFHack.memory_vectorbool_at(@_memaddr, idx) if idx >= 0 and idx < length
|
||||
end
|
||||
def []=(idx, v)
|
||||
idx += length if idx < 0
|
||||
if idx >= length
|
||||
insert_at(idx, v)
|
||||
elsif idx < 0
|
||||
raise 'invalid idx'
|
||||
else
|
||||
DFHack.memory_vectorbool_setat(@_memaddr, idx, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
class StlString < MemStruct
|
||||
def _get
|
||||
DFHack.memory_read_stlstring(@_memaddr)
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
DFHack.memory_write_stlstring(@_memaddr, v)
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
DFHack.memory_stlstring_init(@_memaddr)
|
||||
end
|
||||
end
|
||||
class StlDeque < MemStruct
|
||||
attr_accessor :_tglen, :_tg
|
||||
def initialize(tglen, tg)
|
||||
@_tglen = tglen
|
||||
@_tg = tg
|
||||
end
|
||||
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every
|
||||
# possible struct size, like for StlVector. Just ignore it for now, deque are rare enough.
|
||||
def inspect ; "#<StlDeque>" ; end
|
||||
end
|
||||
|
||||
class DfFlagarray < MemStruct
|
||||
attr_accessor :_indexenum
|
||||
def initialize(indexenum)
|
||||
@_indexenum = indexenum
|
||||
end
|
||||
def length
|
||||
DFHack.memory_bitarray_length(@_memaddr)
|
||||
end
|
||||
# TODO _cpp_init
|
||||
def size ; length ; end
|
||||
def resize(len)
|
||||
DFHack.memory_bitarray_resize(@_memaddr, len)
|
||||
end
|
||||
def [](idx)
|
||||
idx = _indexenum.int(idx) if _indexenum
|
||||
idx += length if idx < 0
|
||||
DFHack.memory_bitarray_isset(@_memaddr, idx) if idx >= 0 and idx < length
|
||||
end
|
||||
def []=(idx, v)
|
||||
idx = _indexenum.int(idx) if _indexenum
|
||||
idx += length if idx < 0
|
||||
if idx >= length or idx < 0
|
||||
raise 'invalid idx'
|
||||
else
|
||||
DFHack.memory_bitarray_set(@_memaddr, idx, v)
|
||||
end
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
end
|
||||
class DfArray < Compound
|
||||
attr_accessor :_tglen, :_tg
|
||||
def initialize(tglen, tg)
|
||||
@_tglen = tglen
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
field(:_ptr, 0) { number 32, false }
|
||||
field(:_length, 4) { number 16, false }
|
||||
|
||||
def length ; _length ; end
|
||||
def size ; _length ; end
|
||||
# TODO _cpp_init
|
||||
def _tgat(i)
|
||||
@_tg._at(_ptr + i*@_tglen) if i >= 0 and i < _length
|
||||
end
|
||||
def [](i)
|
||||
i += _length if i < 0
|
||||
_tgat(i)._get
|
||||
end
|
||||
def []=(i, v)
|
||||
i += _length if i < 0
|
||||
_tgat(i)._set(v)
|
||||
end
|
||||
def _set(a)
|
||||
a.each_with_index { |v, i| self[i] = v }
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
end
|
||||
class DfLinkedList < Compound
|
||||
attr_accessor :_tg
|
||||
def initialize(tg)
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
field(:_ptr, 0) { number 32, false }
|
||||
field(:_prev, 4) { number 32, false }
|
||||
field(:_next, 8) { number 32, false }
|
||||
|
||||
def item
|
||||
# With the current xml structure, currently _tg designate
|
||||
# the type of the 'next' and 'prev' fields, not 'item'.
|
||||
# List head has item == NULL, so we can safely return nil.
|
||||
|
||||
#addr = _ptr
|
||||
#return if addr == 0
|
||||
#@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
def item=(v)
|
||||
#addr = _ptr
|
||||
#raise 'null pointer' if addr == 0
|
||||
#@_tg.at(addr)._set(v)
|
||||
raise 'null pointer'
|
||||
end
|
||||
|
||||
def prev
|
||||
addr = _prev
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
def next
|
||||
addr = _next
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
def each
|
||||
o = self
|
||||
while o
|
||||
yield o.item if o.item
|
||||
o = o.next
|
||||
end
|
||||
end
|
||||
def inspect ; "#<DfLinkedList #{item.inspect} prev=#{'0x%X' % _prev} next=#{'0x%X' % _next}>" ; end
|
||||
end
|
||||
|
||||
class Global < MemStruct
|
||||
attr_accessor :_glob
|
||||
def initialize(glob)
|
||||
@_glob = glob
|
||||
end
|
||||
def _at(addr)
|
||||
g = DFHack.const_get(@_glob)
|
||||
g = DFHack.rtti_getclassat(g, addr)
|
||||
g.new._at(addr)
|
||||
end
|
||||
def inspect ; "#<#{@_glob}>" ; end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class BooleanEnum
|
||||
def self.int(v) ; ((v == true) || (v == 1)) ? 1 : 0 ; end
|
||||
def self.sym(v) ; (!v || (v == 0)) ? false : true ; end
|
||||
end
|
||||
|
||||
# cpp rtti name -> rb class
|
||||
@rtti_n2c = {}
|
||||
@rtti_c2n = {}
|
||||
|
||||
# cpp rtti name -> vtable ptr
|
||||
@rtti_n2v = {}
|
||||
@rtti_v2n = {}
|
||||
|
||||
def self.rtti_n2c ; @rtti_n2c ; end
|
||||
def self.rtti_c2n ; @rtti_c2n ; end
|
||||
def self.rtti_n2v ; @rtti_n2v ; end
|
||||
def self.rtti_v2n ; @rtti_v2n ; end
|
||||
|
||||
# register a ruby class with a cpp rtti class name
|
||||
def self.rtti_register(cppname, cls)
|
||||
@rtti_n2c[cppname] = cls
|
||||
@rtti_c2n[cls] = cppname
|
||||
end
|
||||
|
||||
# return the ruby class to use for the cpp object at address if rtti info is available
|
||||
def self.rtti_getclassat(cls, addr)
|
||||
if addr != 0 and @rtti_c2n[cls]
|
||||
# rtti info exist for class => cpp object has a vtable
|
||||
@rtti_n2c[rtti_readclassname(get_vtable_ptr(addr))] || cls
|
||||
else
|
||||
cls
|
||||
end
|
||||
end
|
||||
|
||||
# try to read the rtti classname from an object vtable pointer
|
||||
def self.rtti_readclassname(vptr)
|
||||
unless n = @rtti_v2n[vptr]
|
||||
n = @rtti_v2n[vptr] = get_rtti_classname(vptr).to_sym
|
||||
@rtti_n2v[n] = vptr
|
||||
end
|
||||
n
|
||||
end
|
||||
|
||||
# return the vtable pointer from the cpp rtti name
|
||||
def self.rtti_getvtable(cppname)
|
||||
unless v = @rtti_n2v[cppname]
|
||||
v = get_vtable(cppname.to_s)
|
||||
@rtti_n2v[cppname] = v
|
||||
@rtti_v2n[v] = cppname if v != 0
|
||||
end
|
||||
v if v != 0
|
||||
end
|
||||
|
||||
def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0)
|
||||
vmethod_do_call(obj._memaddr, voff, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3))
|
||||
end
|
||||
|
||||
def self.vmethod_arg(arg)
|
||||
case arg
|
||||
when nil, false; 0
|
||||
when true; 1
|
||||
when Integer; arg
|
||||
#when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer
|
||||
when MemHack::Compound; arg._memaddr
|
||||
else raise "bad vmethod arg #{arg.class}"
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,72 @@
|
||||
# df user-interface related methods
|
||||
module DFHack
|
||||
class << self
|
||||
# returns the current active viewscreen
|
||||
def curview
|
||||
ret = gview.view
|
||||
ret = ret.child while ret.child
|
||||
ret
|
||||
end
|
||||
|
||||
# center the DF screen on something
|
||||
# updates the cursor position if visible
|
||||
def center_viewscreen(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)
|
||||
|
||||
# compute screen 'map' size (tiles)
|
||||
menuwidth = ui_menu_width
|
||||
# ui_menu_width shows only the 'tab' status
|
||||
menuwidth = 1 if menuwidth == 2 and ui_area_map_width == 2 and cursor.x != -30000
|
||||
menuwidth = 2 if menuwidth == 3 and cursor.x != -30000
|
||||
w_w = gps.dimx - 2
|
||||
w_h = gps.dimy - 2
|
||||
case menuwidth
|
||||
when 1; w_w -= 55
|
||||
when 2; w_w -= (ui_area_map_width == 2 ? 24 : 31)
|
||||
end
|
||||
|
||||
# center view
|
||||
w_x = x - w_w/2
|
||||
w_y = y - w_h/2
|
||||
w_z = z
|
||||
# round view coordinates (optional)
|
||||
#w_x -= w_x % 10
|
||||
#w_y -= w_y % 10
|
||||
# crop to map limits
|
||||
w_x = [[w_x, world.map.x_count - w_w].min, 0].max
|
||||
w_y = [[w_y, world.map.y_count - w_h].min, 0].max
|
||||
|
||||
self.window_x = w_x
|
||||
self.window_y = w_y
|
||||
self.window_z = w_z
|
||||
|
||||
if cursor.x != -30000
|
||||
cursor.x, cursor.y, cursor.z = x, y, z
|
||||
end
|
||||
end
|
||||
|
||||
# add an announcement
|
||||
# color = integer, bright = bool
|
||||
def add_announcement(str, color=nil, bright=nil)
|
||||
cont = false
|
||||
while str.length > 0
|
||||
rep = Report.cpp_new
|
||||
rep.color = color if color
|
||||
rep.bright = ((bright && bright != 0) ? 1 : 0) if bright != nil
|
||||
rep.year = cur_year
|
||||
rep.time = cur_year_tick
|
||||
rep.flags.continuation = cont
|
||||
cont = true
|
||||
rep.flags.announcement = true
|
||||
rep.text = str[0, 73]
|
||||
str = str[73..-1].to_s
|
||||
rep.id = world.status.next_report_id
|
||||
world.status.next_report_id += 1
|
||||
world.status.reports << rep
|
||||
world.status.announcements << rep
|
||||
world.status.display_timer = 2000
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,93 @@
|
||||
module DFHack
|
||||
class << self
|
||||
# return an Unit
|
||||
# with no arg, return currently selected unit in df UI ('v' or 'k' menu)
|
||||
# with numeric arg, search unit by unit.id
|
||||
# with an argument that respond to x/y/z (eg cursor), find first unit at this position
|
||||
def unit_find(what=:selected, y=nil, z=nil)
|
||||
if what == :selected
|
||||
if curview._rtti_classname == :viewscreen_itemst
|
||||
ref = curview.entry_ref[curview.cursor_pos]
|
||||
ref.unit_tg if ref.kind_of?(GeneralRefUnit)
|
||||
else
|
||||
case ui.main.mode
|
||||
when :ViewUnits
|
||||
# nobody selected => idx == 0
|
||||
v = world.units.active[ui_selected_unit]
|
||||
v if v and v.pos.z == cursor.z
|
||||
when :LookAround
|
||||
k = ui_look_list.items[ui_look_cursor]
|
||||
k.unit if k.type == :Unit
|
||||
end
|
||||
end
|
||||
elsif what.kind_of?(Integer)
|
||||
# search by id
|
||||
return world.units.all.binsearch(what) if not z
|
||||
# search by coords
|
||||
x = what
|
||||
world.units.all.find { |u| u.pos.x == x and u.pos.y == y and u.pos.z == z }
|
||||
elsif what.respond_to?(:x) or what.respond_to?(:pos)
|
||||
world.units.all.find { |u| same_pos?(what, u) }
|
||||
else
|
||||
raise "what what?"
|
||||
end
|
||||
end
|
||||
|
||||
# returns an Array of all units that are current fort citizen (dwarves, on map, not hostile)
|
||||
def unit_citizens
|
||||
race = ui.race_id
|
||||
civ = ui.civ_id
|
||||
world.units.active.find_all { |u|
|
||||
u.race == race and u.civ_id == civ and !u.flags1.dead and !u.flags1.merchant and
|
||||
!u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and
|
||||
!u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and
|
||||
u.mood != :Berserk
|
||||
# TODO check curse ; currently this should keep vampires, but may include werebeasts
|
||||
}
|
||||
end
|
||||
|
||||
# list workers (citizen, not crazy / child / inmood / noble)
|
||||
def unit_workers
|
||||
unit_citizens.find_all { |u|
|
||||
u.mood == :None and
|
||||
u.profession != :CHILD and
|
||||
u.profession != :BABY and
|
||||
# TODO MENIAL_WORK_EXEMPTION_SPOUSE
|
||||
!unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] }
|
||||
}
|
||||
end
|
||||
|
||||
# list currently idle workers
|
||||
def unit_idlers
|
||||
unit_workers.find_all { |u|
|
||||
# current_job includes eat/drink/sleep/pickupequip
|
||||
!u.job.current_job and
|
||||
# filter 'attend meeting'
|
||||
u.meetings.length == 0 and
|
||||
# filter soldiers (TODO check schedule)
|
||||
u.military.squad_index == -1 and
|
||||
# filter 'on break'
|
||||
!u.status.misc_traits.find { |t| id == :OnBreak }
|
||||
}
|
||||
end
|
||||
|
||||
def unit_entitypositions(unit)
|
||||
list = []
|
||||
return list if not hf = world.history.figures.binsearch(unit.hist_figure_id)
|
||||
hf.entity_links.each { |el|
|
||||
next if el._rtti_classname != :histfig_entity_link_positionst
|
||||
next if not ent = world.entities.all.binsearch(el.entity_id)
|
||||
next if not pa = ent.positions.assignments.binsearch(el.assignment_id)
|
||||
next if not pos = ent.positions.own.binsearch(pa.position_id)
|
||||
list << pos
|
||||
}
|
||||
list
|
||||
end
|
||||
end
|
||||
|
||||
class LanguageName
|
||||
def to_s(english=true)
|
||||
df.translate_name(self, english)
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,20 @@
|
||||
# fix doors that are frozen in 'open' state
|
||||
|
||||
# door is stuck in open state if the map occupancy flag incorrectly indicates
|
||||
# that an unit is present (and creatures will prone to pass through)
|
||||
|
||||
count = 0
|
||||
df.world.buildings.all.each { |bld|
|
||||
# for all doors
|
||||
next if bld._rtti_classname != :building_doorst
|
||||
# check if it is open
|
||||
next if bld.close_timer == 0
|
||||
# check if occupancy is set
|
||||
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
|
||||
next if not occ.unit
|
||||
# check if an unit is present
|
||||
next if df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z }
|
||||
count += 1
|
||||
occ.unit = false
|
||||
}
|
||||
puts "unstuck #{count} doors"
|
@ -0,0 +1,49 @@
|
||||
# grow crops in farm plots. ex: growcrops helmet_plump 20
|
||||
|
||||
material = $script_args[0]
|
||||
count_max = $script_args[1].to_i
|
||||
count_max = 100 if count_max == 0
|
||||
|
||||
# cache information from the raws
|
||||
@raws_plant_name ||= {}
|
||||
@raws_plant_growdur ||= {}
|
||||
if @raws_plant_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_plant_name[idx] = p.id
|
||||
@raws_plant_growdur[idx] = p.growdur
|
||||
}
|
||||
end
|
||||
|
||||
inventory = Hash.new(0)
|
||||
df.world.items.other[:SEEDS].each { |seed|
|
||||
next if not seed.flags.in_building
|
||||
next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||
inventory[seed.mat_index] += 1
|
||||
}
|
||||
|
||||
if !material or material == 'help' or material == 'list'
|
||||
# show a list of available crop types
|
||||
inventory.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = df.world.raws.plants.all[mat].id
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
|
||||
else
|
||||
|
||||
mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] })
|
||||
unless wantmat = @raws_plant_name.index(mat)
|
||||
raise "invalid plant material #{material}"
|
||||
end
|
||||
|
||||
count = 0
|
||||
df.world.items.other[:SEEDS].each { |seed|
|
||||
next if seed.mat_index != wantmat
|
||||
next if not seed.flags.in_building
|
||||
next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
|
||||
count += 1
|
||||
}
|
||||
puts "Grown #{count} #{mat}"
|
||||
end
|
@ -0,0 +1,27 @@
|
||||
# remove bad thoughts for the selected unit or the whole fort
|
||||
|
||||
# with removebadthoughts -v, dump the bad thoughts types we removed
|
||||
verbose = $script_args.delete('-v')
|
||||
|
||||
if u = df.unit_find(:selected)
|
||||
targets = [u]
|
||||
else
|
||||
targets = df.unit_citizens
|
||||
end
|
||||
|
||||
seenbad = Hash.new(0)
|
||||
|
||||
targets.each { |u|
|
||||
u.status.recent_events.each { |e|
|
||||
next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-'
|
||||
seenbad[e.type] += 1
|
||||
e.age = 0x1000_0000
|
||||
}
|
||||
}
|
||||
|
||||
if verbose
|
||||
seenbad.sort_by { |k, v| v }.each { |k, v| puts " #{v} #{k}" }
|
||||
end
|
||||
|
||||
count = seenbad.values.inject(0) { |s, v| s+v }
|
||||
puts "removed #{count} bad thought#{'s' if count != 1}"
|
@ -0,0 +1,33 @@
|
||||
# slay all creatures of a given race
|
||||
|
||||
race = $script_args[0]
|
||||
|
||||
checkunit = lambda { |u|
|
||||
u.body.blood_count != 0 and
|
||||
not u.flags1.dead and
|
||||
not u.flags1.caged and
|
||||
not df.map_designation_at(u).hidden
|
||||
}
|
||||
|
||||
all_races = df.world.units.active.map { |u|
|
||||
u.race_tg.creature_id if checkunit[u]
|
||||
}.compact.uniq.sort
|
||||
|
||||
if !race
|
||||
puts all_races
|
||||
else
|
||||
raw_race = df.match_rawname(race, all_races)
|
||||
raise 'invalid race' if not raw_race
|
||||
|
||||
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race }
|
||||
|
||||
count = 0
|
||||
df.world.units.active.each { |u|
|
||||
if u.race == race_nr and checkunit[u]
|
||||
u.body.blood_count = 0
|
||||
count += 1
|
||||
end
|
||||
}
|
||||
|
||||
puts "slain #{count} #{raw_race}"
|
||||
end
|
Loading…
Reference in New Issue