Merge branch 'master' of git://github.com/jjyg/dfhack

develop
Warmist 2012-12-12 18:58:11 +02:00
commit ddceabbfb7
13 changed files with 308 additions and 35 deletions

@ -24,6 +24,7 @@ DFHack future
- lever: list and pull fort levers from the dfhack console.
- stripcaged: mark items inside cages for dumping, eg caged goblin weapons.
- soundsense-season: writes the correct season to gamelog.txt on world load.
- devel/create-items: spawn items
New GUI scripts:
- gui/guide-path: displays the cached path for minecart Guide orders.
- gui/workshop-job: displays inputs of a workshop job and allows tweaking them.

@ -316,7 +316,7 @@ static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr,
rbcmd += "'" + args[i] + "', ";
rbcmd += "]\n";
rbcmd += "load './hack/scripts/" + name + ".rb'";
rbcmd += "catch(:script_finished) { load './hack/scripts/" + name + ".rb' }";
return plug_mgr->eval_ruby(out, rbcmd.c_str());
}

@ -785,10 +785,10 @@ bool DFHack::Units::isSane(df::unit *unit)
if (unit->flags1.bits.dead ||
unit->flags3.bits.ghostly ||
isOpposedToLife(unit) ||
unit->unknown8.unk2)
unit->enemy.undead)
return false;
if (unit->unknown8.normal_race == unit->unknown8.were_race && isCrazed(unit))
if (unit->enemy.normal_race == unit->enemy.were_race && isCrazed(unit))
return false;
switch (unit->mood)
@ -839,7 +839,7 @@ bool DFHack::Units::isDwarf(df::unit *unit)
CHECK_NULL_POINTER(unit);
return unit->race == ui->race_id ||
unit->unknown8.normal_race == ui->race_id;
unit->enemy.normal_race == ui->race_id;
}
double DFHack::Units::getAge(df::unit *unit, bool true_age)
@ -1225,12 +1225,12 @@ int Units::computeMovementSpeed(df::unit *unit)
// Stance
if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2)
if (!unit->flags1.bits.on_ground && unit->status2.limbs_stand_max > 2)
{
// WTF
int as = unit->status2.able_stand;
int as = unit->status2.limbs_stand_max;
int x = (as-1) - (as>>1);
int y = as - unit->status2.able_stand_impair;
int y = as - unit->status2.limbs_stand_count;
if (unit->flags3.bits.on_crutch) y--;
y = y * 500 / x;
if (y > 0) speed += y;

@ -189,10 +189,10 @@ function healunit(unit)
unit.body.wounds:resize(0) -- memory leak here :/
unit.body.blood_count=unit.body.blood_max
--set flags for standing and grasping...
unit.status2.able_stand=4
unit.status2.able_stand_impair=4
unit.status2.able_grasp=4
unit.status2.able_grasp_impair=4
unit.status2.limbs_stand_max=4
unit.status2.limbs_stand_count=4
unit.status2.limbs_grasp_max=4
unit.status2.limbs_grasp_count=4
--should also set temperatures, and flags for breath etc...
unit.flags1.dead=false
unit.flags2.calculated_bodyparts=false
@ -240,4 +240,4 @@ function powerup(unit,labor_rating,military_rating,skills)
end
menu:add("Power up",powerup)
return _ENV
return _ENV

@ -48,6 +48,7 @@ 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 '# ').

@ -39,7 +39,7 @@ static color_ostream *r_console; // color_ostream given as argument, if NU
static const char *r_command;
static tthread::thread *r_thread;
static int onupdate_active;
static int onupdate_minyear, onupdate_minyeartick;
static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1;
static color_ostream_proxy *console_proxy;
@ -180,10 +180,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (!onupdate_active)
return CR_OK;
if (*df::global::cur_year < onupdate_minyear)
if (df::global::cur_year && (*df::global::cur_year < onupdate_minyear))
return CR_OK;
if (*df::global::cur_year == onupdate_minyear &&
*df::global::cur_year_tick < onupdate_minyeartick)
if (df::global::cur_year_tick && onupdate_minyeartick >= 0 &&
(*df::global::cur_year == onupdate_minyear &&
*df::global::cur_year_tick < onupdate_minyeartick))
return CR_OK;
if (df::global::cur_year_tick_advmode && onupdate_minyeartickadv >= 0 &&
(*df::global::cur_year == onupdate_minyear &&
*df::global::cur_year_tick_advmode < onupdate_minyeartickadv))
return CR_OK;
return plugin_eval_ruby(out, "DFHack.onupdate");
@ -481,6 +486,17 @@ static VALUE rb_dfonupdate_minyeartick_set(VALUE self, VALUE val)
return Qtrue;
}
static VALUE rb_dfonupdate_minyeartickadv(VALUE self)
{
return rb_uint2inum(onupdate_minyeartickadv);
}
static VALUE rb_dfonupdate_minyeartickadv_set(VALUE self, VALUE val)
{
onupdate_minyeartickadv = rb_num2ulong(val);
return Qtrue;
}
static VALUE rb_dfprint_str(VALUE self, VALUE s)
{
if (r_console)
@ -531,6 +547,23 @@ static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr)
return rb_uint2inum(*(uint32_t*)rb_num2ulong(objptr));
}
// run a dfhack command, as if typed from the dfhack console
static VALUE rb_dfhack_run(VALUE self, VALUE cmd)
{
if (!r_console) // XXX
return Qnil;
std::string s;
int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0));
s.assign(rb_string_value_ptr(&cmd), strlen);
// allow the target command to suspend
// FIXME
//CoreSuspendClaimer suspend(true);
Core::getInstance().runCommand(*r_console, s);
return Qtrue;
}
@ -663,6 +696,49 @@ static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw)
return ret ? Qtrue : Qfalse;
}
// allocate memory pages
static VALUE rb_dfmemory_pagealloc(VALUE self, VALUE len)
{
void *ret = Core::getInstance().p->memAlloc(rb_num2ulong(len));
return (ret == (void*)-1) ? Qnil : rb_uint2inum((uint32_t)ret);
}
// free memory from pagealloc
static VALUE rb_dfmemory_pagedealloc(VALUE self, VALUE ptr, VALUE len)
{
int ret = Core::getInstance().p->memDealloc((void*)rb_num2ulong(ptr), rb_num2ulong(len));
return ret ? Qfalse : Qtrue;
}
// change memory page permissions
// ptr must be page-aligned
// prot is a String, eg 'rwx', 'r', 'x'
static VALUE rb_dfmemory_pageprotect(VALUE self, VALUE ptr, VALUE len, VALUE prot_str)
{
int ret, prot=0;
char *prot_p = rb_string_value_ptr(&prot_str);
if (*prot_p == 'r') {
prot |= Process::MemProt::READ;
++prot_p;
}
if (*prot_p == 'w') {
prot |= Process::MemProt::WRITE;
++prot_p;
}
if (*prot_p == 'x') {
prot |= Process::MemProt::EXEC;
++prot_p;
}
Core::printerr("pageprot %x %x %x\n", rb_num2ulong(ptr), rb_num2ulong(len), prot);
ret = Core::getInstance().p->memProtect((void*)rb_num2ulong(ptr), rb_num2ulong(len), prot);
return ret ? Qfalse : Qtrue;
}
// stl::string
static VALUE rb_dfmemory_stlstring_new(VALUE self)
@ -963,14 +1039,20 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "onupdate_minyear=", RUBY_METHOD_FUNC(rb_dfonupdate_minyear_set), 1);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick), 0);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick_set), 1);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartickadv", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartickadv), 0);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartickadv=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartickadv_set), 1);
rb_define_singleton_method(rb_cDFHack, "get_global_address", RUBY_METHOD_FUNC(rb_dfget_global_address), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1);
rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
//rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1);
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1);
rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1);
rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);
rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1);
rb_define_singleton_method(rb_cDFHack, "pagealloc", RUBY_METHOD_FUNC(rb_dfmemory_pagealloc), 1);
rb_define_singleton_method(rb_cDFHack, "pagedealloc", RUBY_METHOD_FUNC(rb_dfmemory_pagedealloc), 2);
rb_define_singleton_method(rb_cDFHack, "pageprotect", RUBY_METHOD_FUNC(rb_dfmemory_pageprotect), 3);
rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8);
rb_define_singleton_method(rb_cDFHack, "version", RUBY_METHOD_FUNC(rb_dfversion), 0);

@ -64,6 +64,7 @@ module DFHack
# 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 ||= []
@ -82,7 +83,7 @@ module DFHack
@onupdate_list.delete b
if @onupdate_list.empty?
DFHack.onupdate_active = false
DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = 0
DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = DFHack.onupdate_minyeartickadv = -1
end
end
@ -94,20 +95,32 @@ module DFHack
end
TICKS_PER_YEAR = 1200*28*12
# this method is called by dfhack every 'onupdate' if onupdate_active is true
# this method is called by ruby.cpp if df.onupdate_active is true
def onupdate
@onupdate_list ||= []
ticks_per_year = TICKS_PER_YEAR
ticks_per_year *= 72 if gametype == :ADVENTURE_MAIN or gametype == :ADVENTURE_ARENA
y = cur_year
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
end
@onupdate_list.each { |o|
o.check_run(cur_year, cur_year_tick, ticks_per_year)
o.check_run(y, yt, ytmax)
}
if onext = @onupdate_list.sort.first
DFHack.onupdate_minyear = onext.minyear
DFHack.onupdate_minyeartick = onext.minyeartick
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

@ -76,7 +76,7 @@ module DFHack
u.mood == :Berserk or
unit_testflagcurse(u, :CRAZED) or
unit_testflagcurse(u, :OPPOSED_TO_LIFE) or
u.unknown8.unk2 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
@ -113,6 +113,9 @@ module DFHack
true
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 }
# list workers (citizen, not crazy / child / inmood / noble)
def unit_workers
world.units.active.find_all { |u|

@ -694,7 +694,7 @@ static int adjust_unit_divisor(int value) {
static bool can_spar(df::unit *unit) {
return unit->counters2.exhaustion <= 2000 && // actually 4000, but leave a gap
(unit->status2.able_grasp_impair > 0 || unit->status2.able_grasp == 0) &&
(unit->status2.limbs_grasp_count > 0 || unit->status2.limbs_grasp_max == 0) &&
(!unit->health || (unit->health->flags.whole&0x7FF) == 0) &&
(!unit->job.current_job || unit->job.current_job != job_type::Rest);
}

@ -10,6 +10,20 @@ def display_death_event(e)
puts str.chomp(',') + '.'
end
def display_death_unit(u)
death_info = u.counters.death_tg
killer = death_info.killer_tg if death_info
str = "The #{u.race_tg.name[0]}"
str << " #{u.name}" if u.name.has_name
str << " died"
str << " in year #{death_info.event_year}" if death_info
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
puts str.chomp(',') + '.'
end
item = df.item_find(:selected)
unit = df.unit_find(:selected)
@ -27,8 +41,11 @@ if not hf
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
elsif hf == -1
# TODO try to retrieve info from the unit (u = item.unit_tg)
puts "Not a historical figure, cannot death find info"
if unit ||= item.unit_tg
display_death_unit(unit)
else
puts "Not a historical figure, cannot death find info"
end
else
histfig = df.world.history.figures.binsearch(hf)

@ -0,0 +1,161 @@
# create arbitrary items under cursor
category = $script_args[0] || 'help'
mat_raw = $script_args[1] || 'list'
count = $script_args[2]
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs']) || 'help'
if category == 'help'
puts <<EOS
Create items under the cursor.
Usage:
create [category] [raws token] [number]
Item categories:
bars, boulders, plants, logs, web
Raw token:
either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
(the missing part is autocompleted depending on the item category)
use 'list' to show all possibilities
Exemples:
create boulders hematite 30
create bars CREATURE_MAT:CAT:SOAP 10
create web cave_giant
create plants list
EOS
throw :script_finished
elsif mat_raw == 'list'
# allowed with no cursor
elsif df.cursor.x == -30000
puts "Please place the game cursor somewhere"
throw :script_finished
elsif !(maptile = df.map_tile_at(df.cursor))
puts "Error: unallocated map block !"
throw :script_finished
elsif !maptile.shape_passablehigh
puts "Error: impassible tile !"
throw :script_finished
end
def match_list(tok, list)
if tok != 'list'
tok = df.match_rawname(tok, list)
if not tok
puts "Invalid raws token, use one in:"
tok = 'list'
end
end
if tok == 'list'
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
throw :script_finished
end
tok
end
case category
when 'bars'
# create metal bar, eg createbar INORGANIC:IRON
cls = DFHack::ItemBarst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
customize = lambda { |item|
item.dimension = 150
item.subtype = -1
}
when 'boulders'
cls = DFHack::ItemBoulderst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_STONE]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
when 'plants'
cls = DFHack::ItemPlantst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
puts mat_raw
end
when 'logs'
cls = DFHack::ItemWoodst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'WOOD' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
puts mat_raw
end
when 'webs'
cls = DFHack::ItemThreadst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.creatures.all.find_all { |cre|
cre.material.find { |mat| mat.id == 'SILK' }
}.map { |cre| cre.creature_id }
mat_raw = match_list(mat_raw, list)
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
puts mat_raw
end
count ||= 1
customize = lambda { |item|
item.flags.spider_web = true
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
}
end
mat = df.decode_mat mat_raw
count ||= 20
count.to_i.times {
item = cls.cpp_new
item.id = df.item_next_id
item.stack_size = 1
item.mat_type = mat.mat_type
item.mat_index = mat.mat_index
customize[item] if customize
df.item_next_id += 1
item.categorize(true)
df.world.items.all << item
item.pos = df.cursor
item.flags.on_ground = true
df.map_tile_at.mapblock.items << item.id
df.map_tile_at.occupancy.item = true
}
# move game view, so that the ui menu updates
df.curview.feed_keys(:CURSOR_UP_Z)
df.curview.feed_keys(:CURSOR_DOWN_Z)

@ -38,9 +38,9 @@ def fixunit(unit)
end
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed)
if fixed and unit.unknown8.enemy_status_slot != -1
i = unit.unknown8.enemy_status_slot
unit.unknown8.enemy_status_slot = -1
if fixed and unit.enemy.enemy_status_slot != -1
i = unit.enemy.enemy_status_slot
unit.enemy.enemy_status_slot = -1
cache = df.world.enemy_status_cache
cache.slot_used[i] = false
cache.rel_map[i].map! { -1 }

@ -8,12 +8,7 @@ when 'add'
if u = df.unit_find
$superdwarf_ids |= [u.id]
if df.gamemode == :ADVENTURE and not df.respond_to?(:cur_year_tick_advmode)
onupdate_delay = nil
else
onupdate_delay = 1
end
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', onupdate_delay) {
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) {
if $superdwarf_ids.empty?
df.onupdate_unregister($superdwarf_onupdate)
$superdwarf_onupdate = nil