Merge branch 'master' of https://github.com/jjyg/dfhack
Conflicts: library/Core.cpp Starting to merge in Ruby stuffsdevelop
						commit
						c24333515b
					
				| @ -1 +1 @@ | ||||
| Subproject commit 18c1d3e3d0185d0bf80161d1e8410f08dd46d1e1 | ||||
| Subproject commit ad38c5e96b05fedf16114fd16bd463e933f13582 | ||||
| @ -0,0 +1,265 @@ | ||||
| module DFHack | ||||
|     class << self | ||||
|         # 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,21 @@ | ||||
| module DFHack | ||||
|     class << self | ||||
|         # return an Item | ||||
|         # arg similar to unit.rb/unit_find; no arg = 'k' menu | ||||
|         def item_find(what=:selected) | ||||
|             if what == :selected | ||||
|                 case ui.main.mode | ||||
|                 when :LookAround | ||||
|                     k = ui_look_list.items[ui_look_cursor] | ||||
|                     k.item if k.type == :Item | ||||
|                 end | ||||
|             elsif what.kind_of?(Integer) | ||||
|                 world.items.all.binsearch(what) | ||||
|             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,54 @@ | ||||
| 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 | ||||
| 
 | ||||
|         # 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 | ||||
| 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,752 @@ | ||||
| # 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.instance_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 | ||||
|                 @_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,65 @@ | ||||
| # df user-interface related methods | ||||
| module DFHack | ||||
|     class << self | ||||
|         # 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,78 @@ | ||||
| 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) | ||||
|             if what == :selected | ||||
|                 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 | ||||
|             elsif what.kind_of?(Integer) | ||||
|                 world.units.all.binsearch(what) | ||||
|             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 | ||||
| end | ||||
| @ -0,0 +1,71 @@ | ||||
| -- Prepare the current save for use with devel/find-offsets. | ||||
| 
 | ||||
| df.global.pause_state = true | ||||
| 
 | ||||
| --[[print('Placing anchor...') | ||||
| 
 | ||||
| do | ||||
|     local wp = df.global.ui.waypoints | ||||
| 
 | ||||
|     for _,pt in ipairs(wp.points) do | ||||
|         if pt.name == 'dfhack_anchor' then | ||||
|             print('Already placed.') | ||||
|             goto found | ||||
|         end | ||||
|     end | ||||
| 
 | ||||
|     local x,y,z = pos2xyz(df.global.cursor) | ||||
| 
 | ||||
|     if not x then | ||||
|         error("Place cursor at your preferred anchor point.") | ||||
|     end | ||||
| 
 | ||||
|     local id = wp.next_point_id | ||||
|     wp.next_point_id = id + 1 | ||||
| 
 | ||||
|     wp.points:insert('#',{ | ||||
|         new = true, id = id, name = 'dfhack_anchor', | ||||
|         comment=(x..','..y..','..z), | ||||
|         tile = string.byte('!'), fg_color = COLOR_LIGHTRED, bg_color = COLOR_BLUE, | ||||
|         pos = xyz2pos(x,y,z) | ||||
|     }) | ||||
| 
 | ||||
| ::found:: | ||||
| end]] | ||||
| 
 | ||||
| print('Nicknaming units...') | ||||
| 
 | ||||
| for i,unit in ipairs(df.global.world.units.active) do | ||||
|     dfhack.units.setNickname(unit, i..':'..unit.id) | ||||
| end | ||||
| 
 | ||||
| print('Setting weather...') | ||||
| 
 | ||||
| local wbytes = { | ||||
|     2, 1, 0, 2, 0, | ||||
|     1, 2, 1, 0, 0, | ||||
|     2, 0, 2, 1, 2, | ||||
|     1, 2, 0, 1, 1, | ||||
|     2, 0, 1, 0, 2 | ||||
| } | ||||
| 
 | ||||
| for i=0,4 do | ||||
|     for j = 0,4 do | ||||
|         df.global.current_weather[i][j] = (wbytes[i*5+j+1] or 2) | ||||
|     end | ||||
| end | ||||
| 
 | ||||
| local yearstr = df.global.cur_year..','..df.global.cur_year_tick | ||||
| 
 | ||||
| print('Cur year and tick: '..yearstr) | ||||
| 
 | ||||
| dfhack.persistent.save{ | ||||
|     key='prepare-save/cur_year', | ||||
|     value=yearstr, | ||||
|     ints={df.global.cur_year, df.global.cur_year_tick} | ||||
| } | ||||
| 
 | ||||
| -- Save | ||||
| 
 | ||||
| dfhack.run_script('quicksave') | ||||
| 
 | ||||
| @ -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