Petr Mrázek 2013-04-24 16:21:59 +02:00
commit 0efbc74ac2
9 changed files with 181 additions and 65 deletions

@ -3,6 +3,10 @@ DFHack future
New commands: New commands:
- restrictliquid - Restrict traffic on every visible square with liquid. - restrictliquid - Restrict traffic on every visible square with liquid.
- restrictice - Restrict traffic on squares above visible ice. - restrictice - Restrict traffic on squares above visible ice.
New scripts:
- masspit: designate caged creatures in a zone for pitting
Misc improvements:
- exterminate: renamed from slayrace, add help message
DFHack v0.34.11-r3 DFHack v0.34.11-r3

@ -1882,8 +1882,8 @@ Internals: the thoughts are set to be very old, so that the game remove them
quickly after you unpause. quickly after you unpause.
slayrace exterminate
======== ===========
Kills any unit of a given race. Kills any unit of a given race.
With no argument, lists the available races and count eligible targets. With no argument, lists the available races and count eligible targets.
@ -1906,15 +1906,15 @@ Will target any unit on a revealed tile of the map, including ambushers.
Ex:: Ex::
slayrace gob exterminate gob
To kill a single creature, select the unit with the 'v' cursor and:: To kill a single creature, select the unit with the 'v' cursor and::
slayrace him exterminate him
To purify all elves on the map with fire (may have side-effects):: To purify all elves on the map with fire (may have side-effects)::
slayrace elve magma exterminate elve magma
magmasource magmasource
@ -1936,6 +1936,13 @@ To remove all placed sources, call ``magmasource stop``.
With no argument, this command shows an help message and list existing sources. With no argument, this command shows an help message and list existing sources.
masspit
=======
Designate all creatures in cages on top of a pit/pond activity zone for pitting.
Works best with an animal stockpile on top of the zone.
Works with a zone number as argument (eg ``Activity Zone #6`` -> ``masspit 6``)
or with the game cursor on top of the area.
digfort digfort
======= =======

@ -46,18 +46,23 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK; return CR_OK;
} }
bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool glove2 = false) bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_item = false)
{ {
vector<df::item *> out_items; vector<df::item *> out_items;
vector<df::reaction_reagent *> in_reag; vector<df::reaction_reagent *> in_reag;
vector<df::item *> in_items; vector<df::item *> in_items;
bool is_gloves = (prod->item_type == df::item_type::GLOVES); bool is_gloves = (prod->item_type == df::item_type::GLOVES);
bool is_shoes = (prod->item_type == df::item_type::SHOES);
prod->produce(unit, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE, prod->produce(unit, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE,
df::historical_entity::find(unit->civ_id), df::historical_entity::find(unit->civ_id),
((*gametype == df::game_type::DWARF_MAIN) || (*gametype == df::game_type::DWARF_RECLAIM)) ? df::world_site::find(ui->site_id) : NULL); ((*gametype == df::game_type::DWARF_MAIN) || (*gametype == df::game_type::DWARF_RECLAIM)) ? df::world_site::find(ui->site_id) : NULL);
if (!out_items.size()) if (!out_items.size())
return false; return false;
// if we asked to make shoes and we got twice as many as we asked, then we're okay
// otherwise, make a second set because shoes are normally made in pairs
if (is_shoes && out_items.size() == prod->count * 2)
is_shoes = false;
for (size_t i = 0; i < out_items.size(); i++) for (size_t i = 0; i < out_items.size(); i++)
{ {
out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z); out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z);
@ -67,10 +72,10 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool glove2 =
if (out_items[i]->getGloveHandedness() > 0) if (out_items[i]->getGloveHandedness() > 0)
is_gloves = false; is_gloves = false;
else else
out_items[i]->setGloveHandedness(glove2 ? 2 : 1); out_items[i]->setGloveHandedness(second_item ? 2 : 1);
} }
} }
if (is_gloves && !glove2) if ((is_gloves || is_shoes) && !second_item)
return makeItem(prod, unit, true); return makeItem(prod, unit, true);
return true; return true;
@ -248,7 +253,9 @@ command_result df_createitem (color_ostream &out, vector <string> & parameters)
break; break;
} }
if (!makeItem(prod, unit)) bool result = makeItem(prod, unit);
delete prod;
if (!result)
{ {
out.printerr("Failed to create item!\n"); out.printerr("Failed to create item!\n");
return CR_FAILURE; return CR_FAILURE;

@ -422,12 +422,17 @@ protected:
virtual bool can_init(S *screen) virtual bool can_init(S *screen)
{ {
auto list = getLayerList(screen); auto list = getLayerList(screen);
if (!list->active) if (!is_list_valid(screen) || !list->active)
return false; return false;
return true; return true;
} }
virtual bool is_list_valid(S*)
{
return true;
}
virtual void do_search() virtual void do_search()
{ {
search_generic<S,T>::do_search(); search_generic<S,T>::do_search();
@ -444,8 +449,12 @@ protected:
virtual void clear_search() virtual void clear_search()
{ {
search_generic<S,T>::clear_search(); search_generic<S,T>::clear_search();
auto list = getLayerList(this->viewscreen);
list->num_entries = this->get_primary_list()->size(); if (is_list_valid(this->viewscreen))
{
auto list = getLayerList(this->viewscreen);
list->num_entries = this->get_primary_list()->size();
}
} }
private: private:
@ -1208,12 +1217,14 @@ public:
return 'q'; return 'q';
} }
bool can_init(df::viewscreen_layer_militaryst *screen) // When not on the positions page, this list is used for something
// else entirely, so screwing with it seriously breaks stuff.
bool is_list_valid(df::viewscreen_layer_militaryst *screen)
{ {
if (screen->page != df::viewscreen_layer_militaryst::Positions) if (screen->page != df::viewscreen_layer_militaryst::Positions)
return false; return false;
return military_search_base::can_init(screen); return true;
} }
vector<df::unit *> *get_primary_list() vector<df::unit *> *get_primary_list()

@ -6,10 +6,12 @@ class AutoFarm
end end
def setthreshold(id, v) def setthreshold(id, v)
if df.world.raws.plants.all.find { |r| r.id == id } list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id }
@thresholds[id] = v.to_i if tok = df.match_rawname(id, list)
@thresholds[tok] = v.to_i
else else
puts "No plant with id #{id}" puts "No plant with id #{id}, try one of " +
list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ')
end end
end end
@ -17,11 +19,11 @@ class AutoFarm
@thresholds.default = v.to_i @thresholds.default = v.to_i
end end
def is_plantable (plant) def is_plantable(plant)
has_seed = plant.flags[:SEED] has_seed = plant.flags[:SEED]
season = df.cur_season season = df.cur_season
harvest = df.cur_season_tick + plant.growdur * 10 harvest = df.cur_season_tick + plant.growdur * 10
will_finish = harvest < 10080 will_finish = harvest < 10080
can_plant = has_seed && plant.flags[season] can_plant = has_seed && plant.flags[season]
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4]) can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
can_plant can_plant
@ -36,7 +38,7 @@ class AutoFarm
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction && !i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact) !i.flags.artifact)
counts[i.mat_index] = counts[i.mat_index] + i.stack_size counts[i.mat_index] += i.stack_size
end end
} }
@ -53,7 +55,7 @@ class AutoFarm
return plantable return plantable
end end
def set_farms( plants, farms) def set_farms(plants, farms)
return if farms.length == 0 return if farms.length == 0
if plants.length == 0 if plants.length == 0
plants = [-1] plants = [-1]
@ -61,41 +63,36 @@ class AutoFarm
season = df.cur_season season = df.cur_season
idx = 0 farms.each_with_index { |f, idx|
f.plant_id[season] = plants[idx % plants.length]
farms.each { |f|
f.plant_id[season] = plants[idx]
idx = (idx + 1) % plants.length
} }
end end
def process def process
return false unless @running
plantable = find_plantable_plants plantable = find_plantable_plants
counts = Hash.new(0) @lastcounts = Hash.new(0)
df.world.items.other[:PLANT].each { |i| df.world.items.other[:PLANT].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction && !i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact && plantable.has_key?(i.mat_index)) !i.flags.artifact && plantable.has_key?(i.mat_index))
counts[i.mat_index] = counts[i.mat_index] + i.stack_size id = df.world.raws.plants.all[i.mat_index].id
@lastcounts[id] += i.stack_size
end end
} }
return unless @running
plants_s = [] plants_s = []
plants_u = [] plants_u = []
@lastcounts.clear
plantable.each_key { |k| plantable.each_key { |k|
plant = df.world.raws.plants.all[k] plant = df.world.raws.plants.all[k]
if (counts[k] < @thresholds[plant.id]) if (@lastcounts[plant.id] < @thresholds[plant.id])
plants_s.push(k) if plantable[k] == :Surface plants_s.push(k) if plantable[k] == :Surface
plants_u.push(k) if plantable[k] == :Underground plants_u.push(k) if plantable[k] == :Underground
end end
@lastcounts[plant.id] = counts[k]
} }
farms_s = [] farms_s = []
@ -110,11 +107,11 @@ class AutoFarm
set_farms(plants_s, farms_s) set_farms(plants_s, farms_s)
set_farms(plants_u, farms_u) set_farms(plants_u, farms_u)
end end
def start def start
@onupdate = df.onupdate_register('autofarm', 100) { process } return if @running
@onupdate = df.onupdate_register('autofarm', 1200) { process }
@running = true @running = true
end end
@ -125,23 +122,28 @@ class AutoFarm
def status def status
stat = @running ? "Running." : "Stopped." stat = @running ? "Running." : "Stopped."
@lastcounts.each { |k,v|
stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}"
}
@thresholds.each { |k,v| @thresholds.each { |k,v|
stat += "\n#{k} limit #{v} current #{@lastcounts[k]}" stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k)
} }
stat += "\nDefault: #{@thresholds.default}" stat << "\nDefault: #{@thresholds.default}"
stat stat
end end
end end
$AutoFarm = AutoFarm.new unless $AutoFarm $AutoFarm ||= AutoFarm.new
case $script_args[0] case $script_args[0]
when 'start' when 'start'
$AutoFarm.start $AutoFarm.start
puts $AutoFarm.status
when 'end', 'stop' when 'end', 'stop', 'disable'
$AutoFarm.stop $AutoFarm.stop
puts 'Stopped.'
when 'default' when 'default'
$AutoFarm.setdefault($script_args[1]) $AutoFarm.setdefault($script_args[1])
@ -156,10 +158,19 @@ when 'delete'
$AutoFarm.stop $AutoFarm.stop
$AutoFarm = nil $AutoFarm = nil
when 'help', '?'
puts <<EOS
Automatically handle crop selection in farm plots based on current plant stocks.
Selects a crop for planting if current stock is below a threshold.
Selected crops are dispatched on all farmplots.
Usage:
autofarm start
autofarm default 30
autofarm threshold 150 helmet_plump tail_pig
EOS
else else
if $AutoFarm $AutoFarm.process
puts $AutoFarm.status puts $AutoFarm.status
else
puts "AI not started"
end
end end

@ -1,31 +1,31 @@
# create arbitrary items under cursor # create first necessity items under cursor
category = $script_args[0] || 'help' category = $script_args[0] || 'help'
mat_raw = $script_args[1] || 'list' mat_raw = $script_args[1] || 'list'
count = $script_args[2] count = $script_args[2]
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs']) || 'help' category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help'
if category == 'help' if category == 'help'
puts <<EOS puts <<EOS
Create items under the cursor. Create first necessity items under the cursor.
Usage: Usage:
create [category] [raws token] [number] create-items [category] [raws token] [number]
Item categories: Item categories:
bars, boulders, plants, logs, web bars, boulders, plants, logs, webs, anvils
Raw token: Raw token:
either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only Either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
(the missing part is autocompleted depending on the item category) (the missing part is autocompleted depending on the item category)
use 'list' to show all possibilities Use 'list' to show all possibilities
Exemples: Exemples:
create boulders hematite 30 create-items boulders hematite 30
create bars CREATURE_MAT:CAT:SOAP 10 create-items bars CREATURE_MAT:CAT:SOAP 10
create web cave_giant create-items web cave_giant
create plants list create-items plants list
EOS EOS
throw :script_finished throw :script_finished
@ -129,6 +129,18 @@ when 'webs'
item.dimension = 15000 # XXX may depend on creature (this is for GCS) item.dimension = 15000 # XXX may depend on creature (this is for GCS)
} }
when 'anvils'
cls = DFHack::ItemAnvilst
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
count ||= 1
end end
@ -156,6 +168,10 @@ count.to_i.times {
# move game view, so that the ui menu updates # move game view, so that the ui menu updates
df.curview.feed_keys(:CURSOR_UP_Z) if df.cursor.z > 5
df.curview.feed_keys(:CURSOR_DOWN_Z) df.curview.feed_keys(:CURSOR_DOWN_Z)
df.curview.feed_keys(:CURSOR_UP_Z)
else
df.curview.feed_keys(:CURSOR_UP_Z)
df.curview.feed_keys(:CURSOR_DOWN_Z)
end

@ -1,4 +1,4 @@
# slay all creatures of a given race # exterminate creatures
# race = name of the race to eradicate, use 'him' to target only the selected creature # race = name of the race to eradicate, use 'him' to target only the selected creature
# use 'undead' to target all undeads # use 'undead' to target all undeads
@ -24,7 +24,7 @@ slayit = lambda { |u|
else else
# it's getting hot around here # it's getting hot around here
# !!WARNING!! do not call on a magma-safe creature # !!WARNING!! do not call on a magma-safe creature
ouh = df.onupdate_register("slayrace ensure #{u.id}", 1) { ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) {
if u.flags1.dead if u.flags1.dead
df.onupdate_unregister(ouh) df.onupdate_unregister(ouh)
else else
@ -55,7 +55,23 @@ case race
when nil when nil
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" } all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
when 'him' when 'help', '?'
puts <<EOS
Kills all creatures of a given race.
With no argument, lists possible targets with their head count.
With the special argument 'him' or 'her', kill only the currently selected creature.
With the special argument 'undead', kill all undead creatures/thralls.
The targets will bleed out on the next game tick, or if they are immune to that, will vanish in a puff of smoke.
The special final argument 'magma' will make magma rain on the targets instead.
Ex: exterminate gob
exterminate elve magma
exterminate him
EOS
when 'him', 'her'
if him = df.unit_find if him = df.unit_find
slayit[him] slayit[him]
else else
@ -77,7 +93,10 @@ when /^undead/i
else else
raw_race = df.match_rawname(race, all_races.keys) raw_race = df.match_rawname(race, all_races.keys)
raise 'invalid race' if not raw_race if not raw_race
puts "Invalid race, use one of #{all_races.keys.sort.join(' ')}"
throw :script_finished
end
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race } race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race }

@ -0,0 +1,40 @@
# pit all caged creatures in a zone
case $script_args[0]
when '?', 'help'
puts <<EOS
Run this script with the cursor on top of a pit/pond activity zone, or with a zone identifier as argument.
It will mark all caged creatures on tiles covered by the zone to be dumped.
Works best with an animal stockpile on top of the pit/pond zone.
EOS
throw :script_finished
when /(\d+)/
nr = $1.to_i
bld = df.world.buildings.other[:ACTIVITY_ZONE].find { |zone| zone.zone_num == nr }
else
bld = df.world.buildings.other[:ACTIVITY_ZONE].find { |zone|
zone.zone_flags.pit_pond and zone.z == df.cursor.z and
zone.x1 <= df.cursor.x and zone.x2 >= df.cursor.x and zone.y1 <= df.cursor.y and zone.y2 >= df.cursor.y
}
end
if not bld
puts "Please select a pit/pond zone"
throw :script_finished
end
found = 0
df.world.items.other[:CAGE].each { |cg|
next if not cg.flags.on_ground
next if cg.pos.z != bld.z or cg.pos.x < bld.x1 or cg.pos.x > bld.x2 or cg.pos.y < bld.y1 or cg.pos.y > bld.y2
next if not uref = cg.general_refs.grep(DFHack::GeneralRefContainsUnitst).first
found += 1
u = uref.unit_tg
puts "Pitting #{u.race_tg.name[0]} #{u.id} #{u.name}"
u.general_refs << DFHack::GeneralRefBuildingCivzoneAssignedst.cpp_new(:building_id => bld.id)
bld.assigned_creature << u.id
}
puts "No creature available for pitting" if found == 0

@ -1,6 +1,7 @@
-- On map load writes the current season to gamelog.txt -- On map load writes the current season to gamelog.txt
local seasons = { local seasons = {
[-1] = 'Nothing', -- worldgen
[0] = 'Spring', [0] = 'Spring',
[1] = 'Summer', [1] = 'Summer',
[2] = 'Autumn', [2] = 'Autumn',