dfhack/plugins/ruby/ruby.rb

303 lines
10 KiB
Ruby

require 'hack/ruby-autogen'
module DFHack
class << self
# update the ruby.cpp version to accept a block
def suspend
if block_given?
begin
do_suspend
yield
ensure
resume
end
else
do_suspend
end
end
module ::Kernel
def puts(*a)
a.flatten.each { |l|
DFHack.print_str(l.to_s.chomp + "\n")
}
nil
end
def puts_err(*a)
a.flatten.each { |l|
DFHack.print_err(l.to_s.chomp + "\n")
}
nil
end
def p(*a)
a.each { |e|
puts_err e.inspect
}
end
end
# register a callback to be called every gframe or more
# ex: DFHack.onupdate_register { DFHack.world.units[0].counters.job_counter = 0 }
def onupdate_register(&b)
@onupdate_list ||= []
@onupdate_list << b
DFHack.onupdate_active = true
@onupdate_list.last
end
# delete the callback for onupdate ; use the value returned by onupdate_register
def onupdate_unregister(b)
@onupdate_list.delete b
DFHack.onupdate_active = false if @onupdate_list.empty?
end
# this method is called by dfhack every 'onupdate' if onupdate_active is true
def onupdate
@onupdate_list ||= []
@onupdate_list.each { |cb| cb.call }
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
# this method is called by dfhack every 'onstatechange'
def onstatechange(newstate)
@onstatechange_list ||= []
@onstatechange_list.each { |cb| cb.call(newstate) }
end
# 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 find_unit(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)
what = what.pos if what.respond_to?(:pos)
x, y, z = what.x, what.y, what.z
world.units.all.find { |u| u.pos.x == x and u.pos.y == y and u.pos.z == z }
else
raise "what what?"
end
end
# return an Item
# arg similar to find_unit; no arg = 'k' menu
def find_item(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)
what = what.pos if what.respond_to?(:pos)
x, y, z = what.x, what.y, what.z
world.items.all.find { |i| i.pos.x == x and i.pos.y == y and i.pos.z == z }
else
raise "what what?"
end
end
# 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
# 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
# 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
# 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
# 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
# global alias so we can write 'df.world.units.all[0]'
def df
DFHack
end
# load user-specified startup file
load 'ruby_custom.rb' if File.exist?('ruby_custom.rb')