commit
9b94a86035
@ -1,31 +0,0 @@
|
||||
.. _rb:
|
||||
|
||||
ruby
|
||||
====
|
||||
|
||||
.. dfhack-tool::
|
||||
:summary: Allow Ruby scripts to be executed as DFHack commands.
|
||||
:tags: dev
|
||||
:no-command:
|
||||
|
||||
.. dfhack-command:: rb
|
||||
:summary: Eval() a ruby string.
|
||||
|
||||
.. dfhack-command:: rb_eval
|
||||
:summary: Eval() a ruby string.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
::
|
||||
|
||||
enable ruby
|
||||
rb "ruby expression"
|
||||
rb_eval "ruby expression"
|
||||
:rb ruby expression
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
``:rb puts df.unit_find(:selected).name``
|
||||
Print the name of the selected unit.
|
@ -1,93 +0,0 @@
|
||||
# Allow build system to turn off downloading of libruby.so.
|
||||
option(DOWNLOAD_RUBY "Download prebuilt libruby.so for ruby plugin." ON)
|
||||
|
||||
if(DOWNLOAD_RUBY)
|
||||
|
||||
if(APPLE)
|
||||
set(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/osx${DFHACK_BUILD_ARCH}/libruby.dylib)
|
||||
set(RUBYLIB_INSTALL_NAME "libruby.dylib")
|
||||
if(${DFHACK_BUILD_ARCH} STREQUAL 64)
|
||||
# message("No ruby lib for 64-bit OS X yet")
|
||||
else()
|
||||
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx32-libruby187.dylib.gz"
|
||||
"gz"
|
||||
${RUBYLIB}.gz
|
||||
"e9bc4263557e652121b055a46abb4f97"
|
||||
${RUBYLIB}
|
||||
"3ee5356759f764a440be5b5b44649826")
|
||||
endif()
|
||||
elseif(UNIX)
|
||||
set(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/linux${DFHACK_BUILD_ARCH}/libruby.so)
|
||||
set(RUBYLIB_INSTALL_NAME "libruby.so")
|
||||
if(${DFHACK_BUILD_ARCH} STREQUAL 64)
|
||||
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/linux64-libruby187.so.gz"
|
||||
"gz"
|
||||
${RUBYLIB}.gz
|
||||
"8eb757bb9ada08608914d8ca8906c427"
|
||||
${RUBYLIB}
|
||||
"e8c36a06f031cfbf02def28169bb5f1f")
|
||||
else()
|
||||
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/linux32-libruby187.so.gz"
|
||||
"gz"
|
||||
${RUBYLIB}.gz
|
||||
"2d06f5069ff07ea934ecd40db55a4ac5"
|
||||
${RUBYLIB}
|
||||
"b00d8d7086cb39f6fde793f9d89cb2d7")
|
||||
endif()
|
||||
else()
|
||||
set(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/win${DFHACK_BUILD_ARCH}/libruby.dll)
|
||||
set(RUBYLIB_INSTALL_NAME "libruby.dll")
|
||||
if(${DFHACK_BUILD_ARCH} STREQUAL 64)
|
||||
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-libruby200.dll.gz"
|
||||
"gz"
|
||||
${RUBYLIB}.gz
|
||||
"81db54a8b8b3090c94c6ae2147d30b8f"
|
||||
${RUBYLIB}
|
||||
"8a8564418aebddef3dfee1e96690e713")
|
||||
else()
|
||||
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-libruby187.dll.gz"
|
||||
"gz"
|
||||
${RUBYLIB}.gz
|
||||
"ffc0f1b5b33748e2a36128e90c97f6b2"
|
||||
${RUBYLIB}
|
||||
"482c1c418f4ee1a5f04203eee1cda0ef")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
if(APPLE OR UNIX)
|
||||
set(RUBYAUTOGEN ruby-autogen-gcc.rb)
|
||||
else(APPLE OR UNIX)
|
||||
set(RUBYAUTOGEN ruby-autogen-win.rb)
|
||||
endif(APPLE OR UNIX)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN}
|
||||
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} ${CMAKE_SYSTEM_NAME} ${DFHACK_BUILD_ARCH}
|
||||
# cmake quirk: depending on codegen.out.xml or generate_headers only is not enough, needs both
|
||||
# test by manually patching any library/xml/moo.xml, run make ruby-autogen-rb -j2, and check build/plugins/ruby/ruby-autogen.rb for patched xml data
|
||||
DEPENDS generate_headers ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl
|
||||
COMMENT ${RUBYAUTOGEN}
|
||||
)
|
||||
add_custom_target(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN})
|
||||
|
||||
include_directories("${dfhack_SOURCE_DIR}/depends/tthread")
|
||||
|
||||
dfhack_plugin(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
|
||||
add_dependencies(ruby ruby-autogen-rb)
|
||||
|
||||
if(EXISTS ${RUBYLIB})
|
||||
install(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION} RENAME ${RUBYLIB_INSTALL_NAME})
|
||||
else()
|
||||
# Only fire this warning if DOWNLOAD_RUBY was set.
|
||||
if(NOT(APPLE AND ${DFHACK_BUILD_ARCH} STREQUAL 64) AND DOWNLOAD_RUBY)
|
||||
message(WARNING "Ruby library not found at ${RUBYLIB} - will not be installed")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(DIRECTORY .
|
||||
DESTINATION hack/ruby
|
||||
FILES_MATCHING PATTERN "*.rb")
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} DESTINATION hack/ruby)
|
@ -1,279 +0,0 @@
|
||||
This plugins embeds a ruby interpreter inside DFHack (ie inside Dwarf Fortress).
|
||||
|
||||
The plugin maps all the structures available in library/xml/ to ruby objects.
|
||||
|
||||
These objects are described in ruby-autogen.rb, they are all in the DFHack
|
||||
module. The toplevel 'df' method is a shortcut to the DFHack module.
|
||||
|
||||
The plugin does *not* map most of dfhack methods (MapCache, ...) ; only direct
|
||||
access to the raw DF data structures in memory is provided.
|
||||
|
||||
Some library methods are stored in the various .rb file, e.g. shortcuts to read
|
||||
a map block, find an unit or an item, etc.
|
||||
|
||||
Global dfhack objects are accessible through the 'df' accessor (eg 'df.world').
|
||||
|
||||
DFHack structures are renamed in CamelCase in the ruby namespace.
|
||||
|
||||
For a list of the structures and their methods, grep the ruby-autogen.rb file.
|
||||
|
||||
All ruby code runs while the main DF process and other plugins are suspended.
|
||||
|
||||
|
||||
DFHack console
|
||||
--------------
|
||||
|
||||
The ruby plugin defines one new dfhack console command:
|
||||
rb_eval <ruby expression> ; evaluate a ruby expression and show the result in
|
||||
the console. Ex: rb_eval df.unit_find().name.first_name
|
||||
You can use single-quotes for strings ; avoid double-quotes that are parsed
|
||||
and removed by the dfhack console code.
|
||||
|
||||
Text output from ruby code, through the standard 'puts', 'p' or 'raise' are
|
||||
redirected to the dfhack console window.
|
||||
|
||||
If dfhack reports 'rb_eval is not a recognized command', check stderr.log. You
|
||||
need a valid 32-bit ruby library to work, and ruby1.8 is prefered (ruby1.9 may
|
||||
crash DF on startup for now). Install the library in the df root folder (or
|
||||
df/hack/ on linux), the library should be named 'libruby.dll' (.so on linux).
|
||||
You can download a tested version at http://github.com/jjyg/dfhack/downloads/
|
||||
|
||||
|
||||
Ruby scripts
|
||||
------------
|
||||
|
||||
The ruby plugin allows the creation of '.rb' scripts in df/hack/scripts/.
|
||||
|
||||
If you create such a script, e.g. 'test.rb', that will add a new dfhack console
|
||||
command 'test'.
|
||||
The script can access the console command arguments through the global variable
|
||||
'$script_args', which is an array of ruby Strings.
|
||||
To exit early from a script, use 'throw :script_finished'
|
||||
|
||||
The help string displayed in dfhack 'ls' command is the first line of the
|
||||
script, if it is a comment (ie starts with '# ').
|
||||
|
||||
|
||||
Calling dfhack commands
|
||||
-----------------------
|
||||
|
||||
The ruby plugin allows the calling of arbitrary dfhack commands, as if typed
|
||||
directly on the dfhack prompt.
|
||||
However due to locks and stuff, the dfhack command is delayed until the current
|
||||
ruby command is finished, so it is restricted to interactive uses.
|
||||
It is possible to call the method many times, this will queue dfhack commands
|
||||
to be run in order.
|
||||
|
||||
df.dfhack_run "reveal"
|
||||
|
||||
|
||||
Ruby helper functions
|
||||
---------------------
|
||||
|
||||
This is an excerpt of the functions defined in dfhack/plugins/ruby/*.rb. Check
|
||||
the files and the comments for a complete list.
|
||||
|
||||
df.same_pos?(obj1, obj2)
|
||||
Returns true if both objects are at the same game coordinates.
|
||||
obj1 and 2 should respond to #pos and #x #y #z.
|
||||
|
||||
df.map_block_at(pos) / map_block_at(x, y, z)
|
||||
Returns the MapBlock for the coordinates or nil.
|
||||
|
||||
df.map_tile_at(pos)
|
||||
Returns a MapTile, holding all informations wrt the map tile (read&write).
|
||||
This class is a ruby specific extention, to facilitate interaction with the
|
||||
DF map data. Check out hack/ruby/map.rb.
|
||||
|
||||
df.each_map_block { |b| }
|
||||
df.each_map_block_z(zlevel) { |b| }
|
||||
Iterates over every map block (opt. on a single z-level).
|
||||
|
||||
df.center_viewscreen(coords)
|
||||
Centers the DF view on the given coordinates. Accepts x/y/z arguments, or a
|
||||
single argument responding to pos/x/y/z, eg an Unit, Item, ...
|
||||
|
||||
df.unit_find(arg)
|
||||
Returns an Unit.
|
||||
With no arg, returns the currently selected unit (through the (v) or (k) menus)
|
||||
With a number, returns the unit with this ID
|
||||
With something else, returns the first unit at the same game coordinates
|
||||
|
||||
df.unit_workers
|
||||
Returns a list of worker citizen: units of your race & civilization, adults,
|
||||
not dead, crazy, ghosts or nobles exempted of work.
|
||||
|
||||
df.unit_entitypositions(unit)
|
||||
Returns the list of EntityPosition occupied by the unit.
|
||||
Check the 'code' field for a readable name (MANAGER, CHIEF_MEDICAL_DWARF, ...)
|
||||
|
||||
df.match_rawname(name, list)
|
||||
String fuzzy matching. Returns the list entry most similar to 'name'.
|
||||
First searches for an exact match, then for a case-insensitive match, and
|
||||
finally for a case-insensitive substring.
|
||||
Returns the element from list if there is only one match, or nil.
|
||||
Most useful to allow the user to specify a raw-defined name,
|
||||
eg 'gob' for 'GOBLIN' or 'coal' for 'COAL_BITUMINOUS', hence the name.
|
||||
|
||||
df.building_alloc(type, subtype, customtype)
|
||||
df.building_position(bld, pos, w, h)
|
||||
df.building_construct(bld, item_list)
|
||||
Allocates a new building in DF memory, define its position / dimensions, and
|
||||
create a dwarf job to construct it from the given list of items.
|
||||
See buildings.rb/buildbed for an example.
|
||||
|
||||
df.each_tree(material) { |t| }
|
||||
Iterates over every tree of the given material (eg 'maple').
|
||||
|
||||
df.translate_name(name, in_english=true, only_lastpart=false)
|
||||
Decode the LanguageName structure as a String as displayed in the game UI.
|
||||
A shortcut is available through name.to_s
|
||||
|
||||
df.decode_mat(obj)
|
||||
Returns a MaterialInfo definition for the given object, using its mat_type
|
||||
and mat_index fields. Also works with a token string argument ('STONE:DOLOMITE')
|
||||
|
||||
|
||||
DFHack callbacks
|
||||
----------------
|
||||
|
||||
The plugin interfaces with dfhack 'onupdate' hook.
|
||||
To register ruby code to be run every graphic frame, use:
|
||||
handle = df.onupdate_register('log') { puts 'i love flooding the console' }
|
||||
You can also rate-limit when your callback is called to a number of game ticks:
|
||||
handle = df.onupdate_register('myname', 10) { puts '10 more in-game ticks elapsed' }
|
||||
In this case, the callback is called immediately, and then every X in-game
|
||||
ticks (advances only when the game is unpaused).
|
||||
To stop being called, use:
|
||||
df.onupdate_unregister handle
|
||||
|
||||
The same mechanism is available for 'onstatechange', but the
|
||||
SC_BEGIN_UNLOAD event is not propagated to the ruby handler.
|
||||
|
||||
Available states:
|
||||
:WORLD_LOADED, :WORLD_UNLOADED, :MAP_LOADED, :MAP_UNLOADED,
|
||||
:VIEWSCREEN_CHANGED, :CORE_INITIALIZED, :PAUSED, :UNPAUSED
|
||||
|
||||
|
||||
C++ object manipulation
|
||||
-----------------------
|
||||
|
||||
The ruby classes defined in ruby-autogen.rb are accessors to the underlying
|
||||
df C++ objects in-memory. To allocate a new C++ object for use in DF, use the
|
||||
RubyClass.cpp_new method (see buildings.rb for examples), works for Compounds
|
||||
only.
|
||||
A special Compound DFHack::StlString is available for allocating a single c++
|
||||
stl::string, so that you can call vmethods that take a string pointer argument
|
||||
(eg getName).
|
||||
ex: s = DFHack::StlString.cpp_new ; df.building_find.getName(s) ; p s.str
|
||||
|
||||
Deallocation may work, using the compound method _cpp_delete. Use with caution,
|
||||
may crash your DF session. It may be simpler to just leak the memory.
|
||||
_cpp_delete will try to free all memory directly used by the compound, eg
|
||||
strings and vectors. It will *not* call the class destructor, and will not free
|
||||
stuff behind pointers.
|
||||
|
||||
C++ std::string fields may be directly re-allocated using standard ruby strings,
|
||||
e.g. some_unit.name.nickname = 'moo'
|
||||
More subtle string manipulation, e.g. changing a single character, are not
|
||||
supported. Read the whole string, manipulate it in ruby, and re-assign it
|
||||
instead.
|
||||
|
||||
C++ std::vector<> can be iterated as standard ruby Enumerable objects, using
|
||||
each/map/etc.
|
||||
To append data to a vector, use vector << newelement or vector.push(newelement)
|
||||
To insert at a given pos, vector.insert_at(index, value)
|
||||
To delete an element, vector.delete_at(index)
|
||||
|
||||
You can binary search an element in a vector for a given numeric field value:
|
||||
df.world.unit.all.binsearch(42, :id)
|
||||
will find the entry whose 'id' field is 42 (needs the vector to be initially
|
||||
sorted by this field). The binsearch 2nd argument defaults to :id.
|
||||
|
||||
Any numeric field defined as being an enum value will be converted to a ruby
|
||||
Symbol. This works for array indexes too.
|
||||
ex: df.unit_find(:selected).status.labors[:HAUL_FOOD] = true
|
||||
df.map_tile_at(df.cursor).designation.liquid_type = :Water
|
||||
|
||||
Virtual method calls are supported for C++ objects, with a maximum of 6
|
||||
arguments. Arguments / return value are interpreted as Compound/Enums as
|
||||
specified in the vmethod definition in the xmls.
|
||||
|
||||
Pointer fields are automatically dereferenced ; so a vector of pointer to
|
||||
Units will yield Units directly. NULL pointers yield the 'nil' value.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
For more complex examples, check the dfhack/scripts/*.rb files.
|
||||
|
||||
Show info on the currently selected unit ('v' or 'k' DF menu)
|
||||
p df.unit_find.flags1
|
||||
|
||||
Set a custom nickname to unit with id '123'
|
||||
df.unit_find(123).name.nickname = 'moo'
|
||||
|
||||
Show current unit profession
|
||||
p df.unit_find.profession
|
||||
|
||||
Change current unit profession
|
||||
df.unit_find.profession = :MASON
|
||||
|
||||
Center the screen on unit ID '123'
|
||||
df.center_viewscreen(df.unit_find(123))
|
||||
|
||||
Find an item under the game cursor and show its C++ classname
|
||||
p df.item_find(df.cursor)._rtti_classname
|
||||
|
||||
Find the raws name of the plant under cursor
|
||||
plant = df.world.plants.all.find { |plt| df.at_cursor?(plt) }
|
||||
p df.world.raws.plants.all[plant.mat_index].id
|
||||
|
||||
Dig a channel under the cursor
|
||||
df.map_tile_at(df.cursor).dig(:Channel)
|
||||
|
||||
Spawn 2/7 magma on the tile of the dwarf nicknamed 'hotfeet'
|
||||
hot = df.unit_citizens.find { |u| u.name.nickname == 'hotfeet' }
|
||||
df.map_tile_at(hot).spawn_magma(2)
|
||||
|
||||
|
||||
Plugin compilation
|
||||
------------------
|
||||
|
||||
The plugin consists of the main ruby.cpp native plugin and the *.rb files.
|
||||
|
||||
The native plugin handles only low-level ruby-to-df interaction (eg raw memory
|
||||
read/write, and dfhack integration), and the .rb files hold end-user helper
|
||||
functions.
|
||||
|
||||
On dfhack start, the native plugin will initialize the ruby interpreter, and
|
||||
load hack/ruby/ruby.rb. This one then loads all other .rb files.
|
||||
|
||||
The DF internal structures are described in ruby-autogen.rb .
|
||||
It is output by ruby/codegen.pl, from dfhack/library/include/df/codegen.out.xml
|
||||
It contains architecture-specific data (eg DF internal structures field offsets,
|
||||
which differ between Windows and Linux. Linux and Macosx are the same, as they
|
||||
both use gcc).
|
||||
It is stored inside the build directory (eg build/plugins/ruby/ruby-autogen.rb)
|
||||
|
||||
For example,
|
||||
<ld:global-type ld:meta="struct-type" type-name="unit">
|
||||
<ld:field type-name="language_name" name="name" ld:meta="global"/>
|
||||
<ld:field name="custom_profession" ld:meta="primitive" ld:subtype="stl-string"/>
|
||||
<ld:field ld:subtype="enum" base-type="int16_t" name="profession" type-name="profession" ld:meta="global"/>
|
||||
|
||||
Will generate
|
||||
class Unit < MemHack::Compound
|
||||
field(:name, 0) { global :LanguageName }
|
||||
field(:custom_profession, 60) { stl_string }
|
||||
field(:profession, 64) { number 16, true }
|
||||
|
||||
The syntax for the 'field' method in ruby-autogen.rb is:
|
||||
1st argument = name of the method
|
||||
2nd argument = offset of this field from the beginning of the current struct.
|
||||
This field depends on the compiler used by Toady to generate DF.
|
||||
The block argument describes the type of the field: uint32, ptr to global...
|
||||
|
||||
Primitive type access is done through native methods from ruby.cpp (vector length,
|
||||
raw memory access, etc)
|
@ -1,368 +0,0 @@
|
||||
module DFHack
|
||||
class << self
|
||||
def building_find(what=:selected, y=nil, z=nil)
|
||||
if what == :selected
|
||||
return world.buildings.all.binsearch(df.get_selected_building_id)
|
||||
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
|
||||
subtype = ConstructionType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Construction
|
||||
subtype = SiegeengineType.int(subtype) if subtype.kind_of?(::Symbol) and type == :SiegeEngine
|
||||
subtype = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop
|
||||
subtype = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace
|
||||
subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone
|
||||
subtype = TrapType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Trap
|
||||
bld.setSubtype(subtype)
|
||||
bld.setCustomType(custom)
|
||||
case type
|
||||
when :Well; bld.bucket_z = bld.z
|
||||
when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0
|
||||
when :Coffin; bld.initBurialFlags
|
||||
when :Trap; bld.ready_timeout = 500 if bld.trap_type == :PressurePlate
|
||||
when :Floodgate; bld.gate_flags.closed = true
|
||||
when :GrateWall; bld.gate_flags.closed = true
|
||||
when :GrateFloor; bld.gate_flags.closed = true
|
||||
when :BarsVertical; bld.gate_flags.closed = true
|
||||
when :BarsFloor; bld.gate_flags.closed = true
|
||||
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)
|
||||
if pos.respond_to?(:x1)
|
||||
x, y, z = pos.x1, pos.y1, pos.z
|
||||
w ||= pos.x2-pos.x1+1 if pos.respond_to?(:x2)
|
||||
h ||= pos.y2-pos.y1+1 if pos.respond_to?(:y2)
|
||||
elsif pos.respond_to?(:x)
|
||||
x, y, z = pos.x, pos.y, pos.z
|
||||
else
|
||||
x, y, z = pos
|
||||
end
|
||||
w ||= pos.w if pos.respond_to?(:w)
|
||||
h ||= pos.h if pos.respond_to?(:h)
|
||||
bld.x1 = x
|
||||
bld.y1 = y
|
||||
bld.z = 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 and 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 or vice versa
|
||||
def building_linkrooms(bld)
|
||||
world.buildings.other[:IN_PLAY].each { |ob|
|
||||
next if ob.z != bld.z
|
||||
if bld.is_room and bld.room.extents
|
||||
next if ob.is_room or ob.x1 < bld.room.x or ob.x1 >= bld.room.x+bld.room.width or ob.y1 < bld.room.y or ob.y1 >= bld.room.y+bld.room.height
|
||||
next if bld.room.extents[bld.room.width*(ob.y1-bld.room.y)+(ob.x1-bld.room.x)] == 0
|
||||
ui.equipment.update.buildings = true
|
||||
bld.children << ob
|
||||
ob.parents << bld
|
||||
elsif ob.is_room and ob.room.extents
|
||||
next if bld.is_room or bld.x1 < ob.room.x or bld.x1 >= ob.room.x+ob.room.width or bld.y1 < ob.room.y or bld.y1 >= ob.room.y+ob.room.height
|
||||
next if ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)].to_i == 0
|
||||
ui.equipment.update.buildings = true
|
||||
ob.children << bld
|
||||
bld.parents << ob
|
||||
end
|
||||
}
|
||||
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.general_refs << 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
|
||||
|
||||
# construct an abstract building (stockpile, farmplot, ...)
|
||||
def building_construct_abstract(bld)
|
||||
case bld.getType
|
||||
when :Stockpile
|
||||
max = df.world.buildings.other[:STOCKPILE].map { |s| s.stockpile_number }.max
|
||||
bld.stockpile_number = max.to_i + 1
|
||||
when :Civzone
|
||||
max = df.world.buildings.other[:ANY_ZONE].map { |z| z.zone_num }.max
|
||||
bld.zone_num = max.to_i + 1
|
||||
end
|
||||
building_link bld
|
||||
if !bld.flags.exists
|
||||
bld.flags.exists = true
|
||||
bld.initFarmSeasons
|
||||
end
|
||||
end
|
||||
|
||||
def building_setowner(bld, unit)
|
||||
return unless bld.is_room
|
||||
return if bld.owner == unit
|
||||
|
||||
if bld.owner
|
||||
if idx = bld.owner.owned_buildings.index { |ob| ob.id == bld.id }
|
||||
bld.owner.owned_buildings.delete_at(idx)
|
||||
end
|
||||
if spouse = bld.owner.relations.spouse_tg and
|
||||
idx = spouse.owned_buildings.index { |ob| ob.id == bld.id }
|
||||
spouse.owned_buildings.delete_at(idx)
|
||||
end
|
||||
end
|
||||
bld.owner = unit
|
||||
if unit
|
||||
unit.owned_buildings << bld
|
||||
if spouse = bld.owner.relations.spouse_tg and
|
||||
!spouse.owned_buildings.index { |ob| ob.id == bld.id } and
|
||||
bld.canUseSpouseRoom
|
||||
spouse.owned_buildings << bld
|
||||
end
|
||||
end
|
||||
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 = bld.id
|
||||
job.general_refs << refbuildingholder
|
||||
bld.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
|
||||
item_isfree(i)
|
||||
}
|
||||
raise 'no free bed, build more !' if not item
|
||||
|
||||
bld = building_alloc(:Bed)
|
||||
building_position(bld, pos)
|
||||
building_construct(bld, [item])
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
@ -1,44 +0,0 @@
|
||||
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
|
||||
return world.items.all.binsearch(df.get_selected_item_id)
|
||||
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
|
||||
|
||||
# check item flags to see if it is suitable for use as a job input material
|
||||
def item_isfree(i, check_empty=true)
|
||||
!i.flags.trader and
|
||||
!i.flags.in_job and
|
||||
!i.flags.construction and
|
||||
!i.flags.removed and
|
||||
!i.flags.forbid and
|
||||
!i.flags.dump and
|
||||
!i.flags.owned and
|
||||
!i.flags.in_chest and # used as hospital supply ?
|
||||
(!i.flags.container or not check_empty or
|
||||
!i.general_refs.find { |ir| ir.kind_of?(DFHack::GeneralRefContainsItemst) }) and
|
||||
(!i.flags.in_inventory or
|
||||
(!i.general_refs.find { |ir| ir.kind_of?(DFHack::GeneralRefUnitHolderst) and # allow hauled items TODO check if holder is a thief
|
||||
ir.unit_tg.inventory.find { |ii| ii.item == i and ii.mode != :Hauled } } and
|
||||
!i.general_refs.find { |ir| ir.kind_of?(DFHack::GeneralRefContainedInItemst) and
|
||||
!item_isfree(ir.item_tg, false) })) and
|
||||
(!i.flags.in_building or
|
||||
!i.general_refs.find { |ir| ir.kind_of?(DFHack::GeneralRefBuildingHolderst) and
|
||||
ir.building_tg.contained_items.find { |bi| bi.use_mode == 2 and bi.item == i } }) and
|
||||
(!i.flags.on_ground or !df.map_tile_at(i).designation.hidden) # i.flags.unk11?
|
||||
end
|
||||
end
|
||||
end
|
@ -1,35 +0,0 @@
|
||||
module DFHack
|
||||
class << self
|
||||
# link a job to the world
|
||||
# allocate & set job.id, allocate a JobListLink, link to job & world.jobs.list
|
||||
def job_link(job)
|
||||
lastjob = world.jobs.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
|
@ -1 +0,0 @@
|
||||
libruby*
|
@ -1 +0,0 @@
|
||||
libruby*
|
@ -1,344 +0,0 @@
|
||||
module DFHack
|
||||
class << self
|
||||
# return a map block by tile coordinates
|
||||
# you can also use find_map_block(cursor) or anything that respond to x/y/z
|
||||
def map_block_at(x, y=nil, z=nil)
|
||||
x = x.pos if x.respond_to?(:pos)
|
||||
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
|
||||
if x >= 0 and x < world.map.x_count and y >= 0 and y < world.map.y_count and z >= 0 and z < world.map.z_count
|
||||
world.map.block_index[x/16][y/16][z]
|
||||
end
|
||||
end
|
||||
|
||||
def map_designation_at(x, y=nil, z=nil)
|
||||
x = x.pos if x.respond_to?(:pos)
|
||||
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
|
||||
if b = map_block_at(x, y, z)
|
||||
b.designation[x%16][y%16]
|
||||
end
|
||||
end
|
||||
|
||||
def map_occupancy_at(x, y=nil, z=nil)
|
||||
x = x.pos if x.respond_to?(:pos)
|
||||
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
|
||||
if b = map_block_at(x, y, z)
|
||||
b.occupancy[x%16][y%16]
|
||||
end
|
||||
end
|
||||
|
||||
def map_tile_at(x=df.cursor, y=nil, z=nil)
|
||||
x = x.pos if x.respond_to?(:pos)
|
||||
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
|
||||
b = map_block_at(x, y, z)
|
||||
MapTile.new(b, x, y, z) if b
|
||||
end
|
||||
|
||||
# yields every map block
|
||||
def each_map_block
|
||||
(0...world.map.x_count_block).each { |xb|
|
||||
xl = world.map.block_index[xb]
|
||||
(0...world.map.y_count_block).each { |yb|
|
||||
yl = xl[yb]
|
||||
(0...world.map.z_count_block).each { |z|
|
||||
p = yl[z]
|
||||
yield p if p
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# yields every map block for a given z level
|
||||
def each_map_block_z(z)
|
||||
(0...world.map.x_count_block).each { |xb|
|
||||
xl = world.map.block_index[xb]
|
||||
(0...world.map.y_count_block).each { |yb|
|
||||
p = xl[yb][z]
|
||||
yield p if p
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class MapTile
|
||||
attr_accessor :x, :y, :z, :dx, :dy, :mapblock
|
||||
def initialize(b, x, y, z)
|
||||
@x, @y, @z = x, y, z
|
||||
@dx, @dy = @x&15, @y&15
|
||||
@mapblock = b
|
||||
end
|
||||
|
||||
def offset(dx, dy=nil, dz=0)
|
||||
if dx.respond_to?(:x)
|
||||
dz = dx.z if dx.respond_to?(:z)
|
||||
dx, dy = dx.x, dx.y
|
||||
end
|
||||
df.map_tile_at(@x+dx, @y+dy, @z+dz)
|
||||
end
|
||||
|
||||
def designation
|
||||
@mapblock.designation[@dx][@dy]
|
||||
end
|
||||
|
||||
def occupancy
|
||||
@mapblock.occupancy[@dx][@dy]
|
||||
end
|
||||
|
||||
def tiletype
|
||||
@mapblock.tiletype[@dx][@dy]
|
||||
end
|
||||
|
||||
def tiletype=(t)
|
||||
@mapblock.tiletype[@dx][@dy] = t
|
||||
end
|
||||
|
||||
def caption
|
||||
Tiletype::Caption[tiletype]
|
||||
end
|
||||
|
||||
def shape
|
||||
Tiletype::Shape[tiletype]
|
||||
end
|
||||
|
||||
def tilemat
|
||||
Tiletype::Material[tiletype]
|
||||
end
|
||||
|
||||
def variant
|
||||
Tiletype::Variant[tiletype]
|
||||
end
|
||||
|
||||
def special
|
||||
Tiletype::Special[tiletype]
|
||||
end
|
||||
|
||||
def direction
|
||||
Tiletype::Direction[tiletype]
|
||||
end
|
||||
|
||||
def shape_caption
|
||||
TiletypeShape::Caption[shape]
|
||||
end
|
||||
|
||||
def shape_basic
|
||||
TiletypeShape::BasicShape[shape]
|
||||
end
|
||||
|
||||
def shape_passablelow
|
||||
TiletypeShape::PassableLow[shape]
|
||||
end
|
||||
|
||||
def shape_passablehigh
|
||||
TiletypeShape::PassableHigh[shape]
|
||||
end
|
||||
|
||||
def shape_passableflow
|
||||
TiletypeShape::PassableFlow[shape]
|
||||
end
|
||||
|
||||
def shape_walkable
|
||||
TiletypeShape::Walkable[shape]
|
||||
end
|
||||
|
||||
|
||||
# return all veins for current mapblock
|
||||
def all_veins
|
||||
mapblock.block_events.grep(BlockSquareEventMineralst)
|
||||
end
|
||||
|
||||
# return the vein applicable to current tile
|
||||
def vein
|
||||
# last vein wins
|
||||
all_veins.reverse.find { |v|
|
||||
v.tile_bitmask.bits[@dy][@dx] > 0
|
||||
}
|
||||
end
|
||||
|
||||
# return the first BlockBurrow this tile is in (nil if none)
|
||||
def burrow
|
||||
mapblock.block_burrows.find { |b|
|
||||
b.tile_bitmask.bits[@dy][@dx] > 0
|
||||
}
|
||||
end
|
||||
|
||||
# return the array of BlockBurrow this tile is in
|
||||
def all_burrows
|
||||
mapblock.block_burrows.find_all { |b|
|
||||
b.tile_bitmask.bits[@dy][@dx] > 0
|
||||
}
|
||||
end
|
||||
|
||||
# return the mat_index for the current tile (if in vein)
|
||||
def mat_index_vein
|
||||
v = vein
|
||||
v.inorganic_mat if v
|
||||
end
|
||||
|
||||
# return the RegionMapEntry (from designation.biome)
|
||||
def region_map_entry
|
||||
b = mapblock.region_offset[designation.biome]
|
||||
wd = df.world.world_data
|
||||
|
||||
# region coords + [[-1, -1], [0, -1], ..., [1, 1]][b]
|
||||
# clipped to world dimensions
|
||||
rx = df.world.map.region_x/16
|
||||
rx -= 1 if b % 3 == 0 and rx > 0
|
||||
rx += 1 if b % 3 == 2 and rx < wd.world_width-1
|
||||
|
||||
ry = df.world.map.region_y/16
|
||||
ry -= 1 if b < 3 and ry > 0
|
||||
ry += 1 if b > 5 and ry < wd.world_height-1
|
||||
|
||||
wd.region_map[rx][ry]
|
||||
end
|
||||
|
||||
# return the world_data.geo_biome for current tile
|
||||
def geo_biome
|
||||
df.world.world_data.geo_biomes[ region_map_entry.geo_index ]
|
||||
end
|
||||
|
||||
# return the world_data.geo_biome.layer for current tile
|
||||
def stone_layer
|
||||
geo_biome.layers[designation.geolayer_index]
|
||||
end
|
||||
|
||||
# MaterialInfo: token for current tile, based on tilemat (vein, soil, plant, lava_stone...)
|
||||
def mat_info
|
||||
case tilemat
|
||||
when :SOIL
|
||||
base = stone_layer
|
||||
if !df.world.raws.inorganics[base.mat_index].flags[:SOIL_ANY]
|
||||
base = geo_biome.layers.find_all { |l| df.world.raws.inorganics[l.mat_index].flags[:SOIL_ANY] }.last
|
||||
end
|
||||
mat_index = (base ? base.mat_index : rand(df.world.raws.inorganics.length))
|
||||
MaterialInfo.new(0, mat_index)
|
||||
|
||||
when :STONE
|
||||
base = stone_layer
|
||||
if df.world.raws.inorganics[base.mat_index].flags[:SOIL_ANY]
|
||||
base = geo_biome.layers.find { |l| !df.world.raws.inorganics[l.mat_index].flags[:SOIL_ANY] }
|
||||
end
|
||||
mat_index = (base ? base.mat_index : rand(df.world.raws.inorganics.length))
|
||||
MaterialInfo.new(0, mat_index)
|
||||
|
||||
when :MINERAL
|
||||
mat_index = (mat_index_vein || stone_layer.mat_index)
|
||||
MaterialInfo.new(0, mat_index)
|
||||
|
||||
when :LAVA_STONE
|
||||
# XXX this is wrong
|
||||
# maybe should search world.region_details.pos == biome_region_pos ?
|
||||
idx = mapblock.region_offset[designation.biome]
|
||||
mat_index = df.world.world_data.region_details[idx].lava_stone
|
||||
MaterialInfo.new(0, mat_index)
|
||||
|
||||
when :FEATURE
|
||||
if designation.feature_local
|
||||
mx = mapblock.region_pos.x
|
||||
my = mapblock.region_pos.y
|
||||
df.decode_mat(df.world.world_data.feature_map[mx/16][my/16].features.feature_init[mx%16][my%16][mapblock.local_feature])
|
||||
elsif designation.feature_global
|
||||
df.decode_mat(df.world.world_data.underground_regions[mapblock.global_feature].feature_init)
|
||||
else
|
||||
MaterialInfo.new(-1, -1)
|
||||
end
|
||||
|
||||
when :FROZEN_LIQUID
|
||||
MaterialInfo.new('WATER')
|
||||
|
||||
# TODO
|
||||
#when :PLANT
|
||||
#when :GRASS_DARK, :GRASS_DEAD, :GRASS_DRY, :GRASS_LIGHT
|
||||
#when :CONSTRUCTION
|
||||
else # AIR ASHES BROOK CAMPFIRE DRIFTWOOD FIRE HFS MAGMA POOL RIVER
|
||||
MaterialInfo.new(-1, -1)
|
||||
end
|
||||
end
|
||||
|
||||
def mat_type
|
||||
mat_info.mat_type
|
||||
end
|
||||
|
||||
def mat_index
|
||||
mat_info.mat_index
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<MapTile pos=[#@x, #@y, #@z] shape=#{shape} tilemat=#{tilemat} material=#{mat_info.token}>"
|
||||
end
|
||||
|
||||
def dig(mode=:Default)
|
||||
if mode == :Smooth
|
||||
if (tilemat == :STONE or tilemat == :MINERAL) and caption !~ /smooth|pillar|fortification/i and # XXX caption..
|
||||
designation.smooth == 0 and (designation.hidden or not df.world.jobs.list.find { |j|
|
||||
# the game removes 'smooth' designation as soon as it assigns a job, if we
|
||||
# re-set it the game may queue another :DetailWall that will carve a fortification
|
||||
(j.job_type == :DetailWall or j.job_type == :DetailFloor) and df.same_pos?(j, self)
|
||||
})
|
||||
designation.dig = :No
|
||||
designation.smooth = 1
|
||||
mapblock.flags.designated = true
|
||||
end
|
||||
else
|
||||
return if mode != :No and designation.dig == :No and not designation.hidden and df.world.jobs.list.find { |j|
|
||||
# someone already enroute to dig here, avoid 'Inappropriate dig square' spam
|
||||
JobType::Type[j.job_type] == :Digging and df.same_pos?(j, self)
|
||||
}
|
||||
designation.dig = mode
|
||||
mapblock.flags.designated = true if mode != :No
|
||||
end
|
||||
end
|
||||
|
||||
def spawn_liquid(quantity, is_magma=false, flowing=true)
|
||||
designation.flow_size = quantity
|
||||
designation.liquid_type = (is_magma ? :Magma : :Water)
|
||||
designation.flow_forbid = true if is_magma or quantity >= 4
|
||||
|
||||
if flowing
|
||||
mapblock.flags.update_liquid = true
|
||||
mapblock.flags.update_liquid_twice = true
|
||||
|
||||
zf = df.world.map_extras.z_level_flags[z]
|
||||
zf.update = true
|
||||
zf.update_twice = true
|
||||
end
|
||||
end
|
||||
|
||||
def spawn_water(quantity=7)
|
||||
spawn_liquid(quantity)
|
||||
end
|
||||
|
||||
def spawn_magma(quantity=7)
|
||||
spawn_liquid(quantity, true)
|
||||
end
|
||||
|
||||
# yield a serie of tiles until the block returns true, returns the matching tile
|
||||
# the yielded tiles form a (squared) spiral centered here in the current zlevel
|
||||
# eg for radius 4, yields (-4, -4), (-4, -3), .., (-4, 3),
|
||||
# (-4, 4), (-3, 4), .., (4, 4), .., (4, -4), .., (-3, -4)
|
||||
# then move on to radius 5
|
||||
def spiral_search(maxradius=100, minradius=0, step=1)
|
||||
if minradius == 0
|
||||
return self if yield self
|
||||
minradius += step
|
||||
end
|
||||
|
||||
sides = [[0, 1], [1, 0], [0, -1], [-1, 0]]
|
||||
(minradius..maxradius).step(step) { |r|
|
||||
sides.length.times { |s|
|
||||
dxr, dyr = sides[(s-1) % sides.length]
|
||||
dx, dy = sides[s]
|
||||
(-r...r).step(step) { |v|
|
||||
t = offset(dxr*r + dx*v, dyr*r + dy*v)
|
||||
return t if t and yield t
|
||||
}
|
||||
}
|
||||
}
|
||||
nil
|
||||
end
|
||||
|
||||
# returns dx^2+dy^2+dz^2
|
||||
def distance_to(ot)
|
||||
(x-ot.x)**2 + (y-ot.y)**2 + (z-ot.z)**2
|
||||
end
|
||||
end
|
||||
end
|
@ -1,203 +0,0 @@
|
||||
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
|
||||
|
||||
def ===(other)
|
||||
other.mat_index == mat_index and other.mat_type == mat_type
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def decode_mat(what, idx=nil)
|
||||
MaterialInfo.new(what, idx)
|
||||
end
|
||||
end
|
||||
end
|
@ -1 +0,0 @@
|
||||
libruby*
|
@ -1 +0,0 @@
|
||||
libruby*
|
@ -1,111 +0,0 @@
|
||||
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, quiet=false)
|
||||
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}" unless quiet
|
||||
}
|
||||
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" unless quiet
|
||||
end
|
||||
end
|
||||
|
||||
def growtrees(material=nil, count_max=100, quiet=false)
|
||||
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}" unless quiet
|
||||
}
|
||||
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" unless quiet
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,258 +0,0 @@
|
||||
# redefine standard i/o methods to use the dfhack console
|
||||
module Kernel
|
||||
def puts(*a)
|
||||
a.flatten.each { |l|
|
||||
# XXX looks like print_str crashes with strings longer than 4096... maybe not nullterminated ?
|
||||
# this workaround fixes it
|
||||
s = l.to_s.chomp + "\n"
|
||||
while s.length > 0
|
||||
DFHack.print_str(s[0, 4000])
|
||||
s[0, 4000] = ''
|
||||
end
|
||||
}
|
||||
nil
|
||||
end
|
||||
|
||||
def puts_err(*a)
|
||||
a.flatten.each { |l|
|
||||
s = l.to_s.chomp + "\n"
|
||||
while s.length > 0
|
||||
DFHack.print_err(s[0, 4000])
|
||||
s[0, 4000] = ''
|
||||
end
|
||||
}
|
||||
nil
|
||||
end
|
||||
|
||||
def p(*a)
|
||||
a.each { |e|
|
||||
puts_err e.inspect
|
||||
}
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
module DFHack
|
||||
VERSION = version
|
||||
|
||||
class OnupdateCallback
|
||||
attr_accessor :callback, :timelimit, :minyear, :minyeartick, :description
|
||||
def initialize(descr, cb, tl, initdelay=0)
|
||||
@description = descr
|
||||
@callback = cb
|
||||
@ticklimit = tl
|
||||
@minyear = (tl ? df.cur_year : 0)
|
||||
@minyeartick = (tl ? df.cur_year_tick+initdelay : 0)
|
||||
end
|
||||
|
||||
# run callback if timedout
|
||||
def check_run(year, yeartick, yearlen)
|
||||
if @ticklimit
|
||||
return unless year > @minyear or (year == @minyear and yeartick >= @minyeartick)
|
||||
@minyear = year
|
||||
@minyeartick = yeartick + @ticklimit
|
||||
if @minyeartick > yearlen
|
||||
@minyear += 1
|
||||
@minyeartick -= yearlen
|
||||
end
|
||||
end
|
||||
# t0 = Time.now
|
||||
@callback.call
|
||||
# dt = Time.now - t0 ; puts "rb cb #@description took #{'%.02f' % dt}s" if dt > 0.1
|
||||
rescue Exception
|
||||
df.onupdate_unregister self
|
||||
puts_err "onupdate #@description unregistered: #$!", $!.backtrace
|
||||
end
|
||||
|
||||
def <=>(o)
|
||||
[@minyear, @minyeartick] <=> [o.minyear, o.minyeartick]
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
attr_accessor :onupdate_list, :onstatechange_list
|
||||
|
||||
# register a callback to be called every gframe or more
|
||||
# ex: DFHack.onupdate_register('fastdwarf') { DFHack.world.units[0].counters.job_counter = 0 }
|
||||
# if ticklimit is given, do not call unless this much game ticks have passed. Handles advmode time stretching.
|
||||
def onupdate_register(descr, ticklimit=nil, initialtickdelay=0, &b)
|
||||
raise ArgumentError, 'need a description as 1st arg' unless descr.kind_of?(::String)
|
||||
@onupdate_list ||= []
|
||||
@onupdate_list << OnupdateCallback.new(descr, b, ticklimit, initialtickdelay)
|
||||
DFHack.onupdate_active = true
|
||||
if onext = @onupdate_list.min
|
||||
DFHack.onupdate_minyear = onext.minyear
|
||||
DFHack.onupdate_minyeartick = onext.minyeartick
|
||||
end
|
||||
@onupdate_list.last
|
||||
end
|
||||
|
||||
# delete the callback for onupdate ; use the value returned by onupdate_register or the description
|
||||
def onupdate_unregister(b)
|
||||
b = @onupdate_list.find { |bb| bb.description == b } if b.kind_of?(String)
|
||||
@onupdate_list.delete b
|
||||
if @onupdate_list.empty?
|
||||
DFHack.onupdate_active = false
|
||||
DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = DFHack.onupdate_minyeartickadv = -1
|
||||
end
|
||||
end
|
||||
|
||||
# same as onupdate_register, but remove the callback once it returns true
|
||||
def onupdate_register_once(*a)
|
||||
handle = onupdate_register(*a) {
|
||||
onupdate_unregister(handle) if yield
|
||||
}
|
||||
end
|
||||
|
||||
TICKS_PER_YEAR = 1200*28*12
|
||||
# this method is called by ruby.cpp if df.onupdate_active is true
|
||||
def onupdate
|
||||
@onupdate_list ||= []
|
||||
|
||||
y = yt = 0
|
||||
y = cur_year rescue 0
|
||||
ytmax = TICKS_PER_YEAR
|
||||
if df.gamemode == :ADVENTURE and df.respond_to?(:cur_year_tick_advmode)
|
||||
yt = cur_year_tick_advmode
|
||||
ytmax *= 144
|
||||
else
|
||||
yt = cur_year_tick rescue 0
|
||||
end
|
||||
|
||||
@onupdate_list.each { |o|
|
||||
o.check_run(y, yt, ytmax)
|
||||
}
|
||||
|
||||
if onext = @onupdate_list.min
|
||||
DFHack.onupdate_minyear = onext.minyear
|
||||
if ytmax > TICKS_PER_YEAR
|
||||
DFHack.onupdate_minyeartick = -1
|
||||
DFHack.onupdate_minyeartickadv = onext.minyeartick
|
||||
else
|
||||
DFHack.onupdate_minyeartick = onext.minyeartick
|
||||
DFHack.onupdate_minyeartickadv = -1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# register a callback to be called every gframe or more
|
||||
# ex: DFHack.onstatechange_register { |newstate| puts "state changed to #{newstate}" }
|
||||
def onstatechange_register(&b)
|
||||
@onstatechange_list ||= []
|
||||
@onstatechange_list << b
|
||||
@onstatechange_list.last
|
||||
end
|
||||
|
||||
# delete the callback for onstatechange ; use the value returned by onstatechange_register
|
||||
def onstatechange_unregister(b)
|
||||
@onstatechange_list.delete b
|
||||
end
|
||||
|
||||
# same as onstatechange_register, but auto-unregisters if the block returns true
|
||||
def onstatechange_register_once
|
||||
handle = onstatechange_register { |st|
|
||||
onstatechange_unregister(handle) if yield(st)
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
# this method is called by dfhack every 'onstatechange'
|
||||
def onstatechange(newstate)
|
||||
@onstatechange_list ||= []
|
||||
@onstatechange_list.each { |cb| cb.call(newstate) }
|
||||
end
|
||||
|
||||
# return true if the argument is under the cursor
|
||||
def at_cursor?(obj)
|
||||
same_pos?(obj, cursor)
|
||||
end
|
||||
|
||||
# returns true if both arguments are at the same x/y/z
|
||||
def same_pos?(pos1, pos2)
|
||||
pos1 = pos1.pos if pos1.respond_to?(:pos)
|
||||
pos2 = pos2.pos if pos2.respond_to?(:pos)
|
||||
pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z
|
||||
end
|
||||
|
||||
# try to match a user-specified name to one from the raws
|
||||
# uses case-switching and substring matching
|
||||
# eg match_rawname('coal', ['COAL_BITUMINOUS', 'BAUXITE']) => 'COAL_BITUMINOUS'
|
||||
def match_rawname(name, rawlist)
|
||||
rawlist.each { |r| return r if name == r }
|
||||
rawlist.each { |r| return r if name.downcase == r.downcase }
|
||||
may = rawlist.find_all { |r| r.downcase.index(name.downcase) }
|
||||
may.first if may.length == 1
|
||||
end
|
||||
|
||||
def translate_name(name, english=true, onlylastpart=false)
|
||||
out = []
|
||||
|
||||
if not onlylastpart
|
||||
out << name.first_name if name.first_name != ''
|
||||
if name.nickname != ''
|
||||
case respond_to?(:d_init) && d_init.nickname[gametype]
|
||||
when :REPLACE_ALL; return "`#{name.nickname}'"
|
||||
when :REPLACE_FIRST; out.pop
|
||||
end
|
||||
out << "`#{name.nickname}'"
|
||||
end
|
||||
end
|
||||
return out.join(' ') unless name.words.find { |w| w >= 0 }
|
||||
|
||||
if not english
|
||||
tsl = world.raws.language.translations[name.language]
|
||||
if name.words[0] >= 0 or name.words[1] >= 0
|
||||
out << ''
|
||||
out.last << tsl.words[name.words[0]] if name.words[0] >= 0
|
||||
out.last << tsl.words[name.words[1]] if name.words[1] >= 0
|
||||
end
|
||||
if name.words[5] >= 0
|
||||
out << ''
|
||||
(2..5).each { |i| out.last << tsl.words[name.words[i]] if name.words[i] >= 0 }
|
||||
end
|
||||
if name.words[6] >= 0
|
||||
out << tsl.words[name.words[6]]
|
||||
end
|
||||
else
|
||||
wl = world.raws.language
|
||||
if name.words[0] >= 0 or name.words[1] >= 0
|
||||
out << ''
|
||||
out.last << wl.words[name.words[0]].forms[name.parts_of_speech[0]] if name.words[0] >= 0
|
||||
out.last << wl.words[name.words[1]].forms[name.parts_of_speech[1]] if name.words[1] >= 0
|
||||
end
|
||||
if name.words[5] >= 0
|
||||
out << 'the'
|
||||
out.last.capitalize! if out.length == 1
|
||||
out << wl.words[name.words[2]].forms[name.parts_of_speech[2]] if name.words[2] >= 0
|
||||
out << wl.words[name.words[3]].forms[name.parts_of_speech[3]] if name.words[3] >= 0
|
||||
if name.words[4] >= 0
|
||||
out << wl.words[name.words[4]].forms[name.parts_of_speech[4]]
|
||||
out.last << '-'
|
||||
else
|
||||
out << ''
|
||||
end
|
||||
out.last << wl.words[name.words[5]].forms[name.parts_of_speech[5]]
|
||||
end
|
||||
if name.words[6] >= 0
|
||||
out << 'of'
|
||||
out.last.capitalize! if out.length == 1
|
||||
out << wl.words[name.words[6]].forms[name.parts_of_speech[6]]
|
||||
end
|
||||
end
|
||||
|
||||
out.join(' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# global alias so we can write 'df.world.units.all[0]'
|
||||
def df
|
||||
DFHack
|
||||
end
|
||||
|
||||
# load autogenned file
|
||||
require './hack/ruby/ruby-autogen-defs'
|
||||
require(RUBY_PLATFORM =~ /mswin|mingw|cygwin/i ? './hack/ruby/ruby-autogen-win' : './hack/ruby/ruby-autogen-gcc')
|
||||
|
||||
# load all modules
|
||||
Dir['./hack/ruby/*.rb'].each { |m| require m.chomp('.rb') if m !~ /ruby-autogen/ }
|
@ -1,91 +0,0 @@
|
||||
# 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[0]
|
||||
# ui_menu_width shows only the 'tab' status
|
||||
menuwidth = 1 if menuwidth == 2 and ui_menu_width[1] == 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_menu_width[1] == 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
|
||||
yield rep if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
# add an announcement to display in a game popup message
|
||||
# (eg "the megabeast foobar arrived")
|
||||
def popup_announcement(str, color=nil, bright=nil)
|
||||
pop = PopupMessage.cpp_new(:text => str)
|
||||
pop.color = color if color
|
||||
pop.bright = bright if bright
|
||||
world.status.popups << pop
|
||||
end
|
||||
end
|
||||
|
||||
class Viewscreen
|
||||
def feed_keys(*keys)
|
||||
keyset = StlSet.cpp_new(keys, InterfaceKey)
|
||||
ret = feed(keyset)
|
||||
keyset._cpp_delete
|
||||
ret
|
||||
end
|
||||
end
|
||||
end
|
@ -1,280 +0,0 @@
|
||||
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
|
||||
return world.units.all.binsearch(df.get_selected_unit_id)
|
||||
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
|
||||
world.units.active.find_all { |u|
|
||||
unit_iscitizen(u)
|
||||
}
|
||||
end
|
||||
|
||||
def unit_testflagcurse(u, flag)
|
||||
return false if u.curse.rem_tags1.send(flag)
|
||||
return true if u.curse.add_tags1.send(flag)
|
||||
return false if u.caste < 0
|
||||
u.race_tg.caste[u.caste].flags[flag]
|
||||
end
|
||||
|
||||
def unit_isfortmember(u)
|
||||
# RE from viewscreen_unitlistst ctor
|
||||
return false if df.gamemode != :DWARF or
|
||||
u.mood == :Berserk or
|
||||
unit_testflagcurse(u, :CRAZED) or
|
||||
unit_testflagcurse(u, :OPPOSED_TO_LIFE) or
|
||||
u.enemy.undead or
|
||||
u.flags3.ghostly or
|
||||
u.flags1.marauder or u.flags1.active_invader or u.flags1.invader_origin or
|
||||
u.flags1.forest or
|
||||
u.flags1.merchant or u.flags1.diplomat
|
||||
return true if u.flags1.tame
|
||||
return false if u.flags2.underworld or u.flags2.resident or
|
||||
u.flags2.visitor_uninvited or u.flags2.visitor or
|
||||
u.civ_id == -1 or
|
||||
u.civ_id != df.ui.civ_id
|
||||
true
|
||||
end
|
||||
|
||||
# return the page in viewscreen_unitlist where the unit would appear
|
||||
def unit_category(u)
|
||||
return if u.flags1.left or u.flags1.incoming
|
||||
# return if hostile & unit_invisible(u) (hidden_in_ambush or caged+mapblock.hidden or caged+holder.ambush
|
||||
return :Dead if u.flags2.killed
|
||||
return :Dead if u.flags3.ghostly # hostile ?
|
||||
return if u.flags1.inactive
|
||||
return :Others if !unit_isfortmember(u)
|
||||
casteflags = u.race_tg.caste[u.caste].flags if u.caste >= 0
|
||||
return :Livestock if casteflags and (casteflags[:PET] or casteflags[:PET_EXOTIC])
|
||||
return :Citizens if unit_testflagcurse(u, :CAN_SPEAK)
|
||||
:Livestock
|
||||
# some other stuff with ui.race_id ? (jobs only?)
|
||||
end
|
||||
|
||||
# merchant: df.ui.caravans.find { |cv| cv.entity == u.civ_id }
|
||||
# diplomat: df.ui.dip_meeting_info.find { |m| m.diplomat_id == u.hist_figure_id or m.diplomat_id2 == u.hist_figure_id }
|
||||
|
||||
|
||||
def unit_nemesis(u)
|
||||
if ref = u.general_refs.find { |r| r.kind_of?(DFHack::GeneralRefIsNemesisst) }
|
||||
ref.nemesis_tg
|
||||
end
|
||||
end
|
||||
|
||||
# return the subcategory for :Others (from vs_unitlist)
|
||||
def unit_other_category(u)
|
||||
# comment is actual code returned by the df function
|
||||
return :Berserk if u.mood == :Berserk # 5
|
||||
return :Berserk if unit_testflagcurse(u, :CRAZED) # 14
|
||||
return :Undead if unit_testflagcurse(u, :OPPOSED_TO_LIFE) # 1
|
||||
return :Undead if u.flags3.ghostly # 15
|
||||
|
||||
if df.gamemode == :ADVENTURE
|
||||
return :Hostile if u.civ_id == -1 # 2
|
||||
if u.animal.population.region_x == -1
|
||||
return :Wild if u.flags2.roaming_wilderness_population_source_not_a_map_feature # 0
|
||||
else
|
||||
return :Hostile if u.flags2.important_historical_figure and n = unit_nemesis(u) and n.flags[:ACTIVE_ADVENTURER] # 2
|
||||
end
|
||||
return :Hostile if u.flags2.resident # 3
|
||||
return :Hostile # 4
|
||||
end
|
||||
|
||||
return :Invader if u.flags1.active_invader or u.flags1.invader_origin # 6
|
||||
return :Friendly if u.flags1.forest or u.flags1.merchant or u.flags1.diplomat # 8
|
||||
return :Hostile if u.flags1.tame # 7
|
||||
|
||||
if u.civ_id != -1
|
||||
return :Unsure if u.civ_id != df.ui.civ_id or u.flags1.resident or u.flags1.visitor or u.flags1.visitor_uninvited # 10
|
||||
return :Hostile # 7
|
||||
|
||||
elsif u.animal.population.region_x == -1
|
||||
return :Friendly if u.flags2.visitor # 8
|
||||
return :Uninvited if u.flags2.visitor_uninvited # 12
|
||||
return :Underworld if r = u.race_tg and r.underground_layer_min == 5 # 9
|
||||
return :Resident if u.flags2.resident # 13
|
||||
return :Friendly # 8
|
||||
|
||||
else
|
||||
return :Friendly if u.flags2.visitor # 8
|
||||
return :Underworld if r = u.race_tg and r.underground_layer_min == 5 # 9
|
||||
return :Wild if u.animal.population.feature_idx == -1 and u.animal.population.cave_id == -1 # 0
|
||||
return :Wild # 11
|
||||
end
|
||||
end
|
||||
|
||||
def unit_iscitizen(u)
|
||||
unit_category(u) == :Citizens
|
||||
end
|
||||
|
||||
def unit_hostiles
|
||||
world.units.active.find_all { |u|
|
||||
unit_ishostile(u)
|
||||
}
|
||||
end
|
||||
|
||||
# returns if an unit is openly hostile
|
||||
# does not include ghosts / wildlife
|
||||
def unit_ishostile(u)
|
||||
# return true if u.flags3.ghostly and not u.flags1.inactive
|
||||
return false unless unit_category(u) == :Others
|
||||
|
||||
case unit_other_category(u)
|
||||
when :Berserk, :Undead, :Hostile, :Invader, :Underworld
|
||||
# XXX :Resident, :Uninvited?
|
||||
true
|
||||
|
||||
when :Unsure
|
||||
# from df code, with removed duplicate checks already in other_category
|
||||
return true if u.enemy.undead or u.flags3.ghostly or u.flags1.marauder
|
||||
return false if u.flags1.forest or u.flags1.merchant or u.flags1.diplomat or u.flags2.visitor
|
||||
return true if u.flags1.tame or u.flags2.underworld
|
||||
|
||||
if histfig = u.hist_figure_tg
|
||||
group = df.ui.group_tg
|
||||
case unit_checkdiplomacy_hf_ent(histfig, group)
|
||||
when 4, 5
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
elsif diplo = u.civ_tg.unknown1b.diplomacy.binsearch(df.ui.group_id, :group_id)
|
||||
diplo.relation != 1 and diplo.relation != 5
|
||||
|
||||
else
|
||||
u.animal.population.region_x != -1 or u.flags2.resident or u.flags2.visitor_uninvited
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unit_checkdiplomacy_hf_ent(histfig, group)
|
||||
var_3d = var_3e = var_45 = var_46 = var_47 = var_48 = var_49 = nil
|
||||
|
||||
var_3d = 1 if group.type == :Outcast or group.type == :NomadicGroup or
|
||||
(group.type == :Civilization and group.entity_raw.flags[:LOCAL_BANDITRY])
|
||||
|
||||
histfig.entity_links.each { |link|
|
||||
if link.entity_id == group.id
|
||||
case link.getType
|
||||
when :MEMBER, :MERCENARY, :SLAVE, :PRISONER, :POSITION, :HERO
|
||||
var_47 = 1
|
||||
when :FORMER_MEMBER, :FORMER_MERCENARY, :FORMER_SLAVE, :FORMER_PRISONER
|
||||
var_48 = 1
|
||||
when :ENEMY
|
||||
var_49 = 1
|
||||
when :CRIMINAL
|
||||
var_45 = 1
|
||||
end
|
||||
else
|
||||
case link.getType
|
||||
when :MEMBER, :MERCENARY, :SLAVE
|
||||
if link_entity = link.entity_tg
|
||||
diplo = group.unknown1b.diplomacy.binsearch(link.entity_id, :group_id)
|
||||
case diplo.relation
|
||||
when 0, 3, 4
|
||||
var_48 = 1
|
||||
when 1, 5
|
||||
var_46 = 1
|
||||
end
|
||||
|
||||
var_3e = 1 if link_entity.type == :Outcast or link_entity.type == :NomadicGroup or
|
||||
(link_entity.type == :Civilization and link_entity.entity_raw.flags[:LOCAL_BANDITRY])
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
if var_49
|
||||
4
|
||||
elsif var_46
|
||||
5
|
||||
elsif !var_47 and group.resources.ethic[:KILL_NEUTRAL] == 16
|
||||
4
|
||||
elsif df.gamemode == :ADVENTURE and !var_47 and (var_3e or !var_3d)
|
||||
4
|
||||
elsif var_45
|
||||
3
|
||||
elsif var_47
|
||||
2
|
||||
elsif var_48
|
||||
1
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# list workers (citizen, not crazy / child / inmood / noble)
|
||||
def unit_workers
|
||||
world.units.active.find_all { |u|
|
||||
unit_isworker(u)
|
||||
}
|
||||
end
|
||||
|
||||
def unit_isworker(u)
|
||||
unit_iscitizen(u) and
|
||||
u.race == df.ui.race_id and
|
||||
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
|
||||
world.units.active.find_all { |u|
|
||||
unit_isidler(u)
|
||||
}
|
||||
end
|
||||
|
||||
def unit_isidler(u)
|
||||
unit_isworker(u) and
|
||||
# current_job includes eat/drink/sleep/pickupequip
|
||||
!u.job.current_job and
|
||||
# filter 'attend meeting'
|
||||
not u.specific_refs.find { |s| s.type == :ACTIVITY } and
|
||||
# filter soldiers (TODO check schedule)
|
||||
u.military.squad_id == -1 and
|
||||
# filter incoming migrants
|
||||
not u.status.misc_traits.find { |t| t.id == :Migrant }
|
||||
end
|
||||
|
||||
def unit_entitypositions(unit)
|
||||
list = []
|
||||
return list if not histfig = unit.hist_figure_tg
|
||||
histfig.entity_links.each { |el|
|
||||
next if el._rtti_classname != :histfig_entity_link_positionst
|
||||
next if not ent = el.entity_tg
|
||||
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=false)
|
||||
df.translate_name(self, english)
|
||||
end
|
||||
end
|
||||
end
|
@ -1 +0,0 @@
|
||||
libruby*
|
@ -1 +0,0 @@
|
||||
libruby*
|
Loading…
Reference in New Issue