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:
- restrictliquid - Restrict traffic on every visible square with liquid.
- 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

@ -1882,8 +1882,8 @@ Internals: the thoughts are set to be very old, so that the game remove them
quickly after you unpause.
slayrace
========
exterminate
===========
Kills any unit of a given race.
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::
slayrace gob
exterminate gob
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)::
slayrace elve magma
exterminate elve magma
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.
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
=======

@ -46,18 +46,23 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
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::reaction_reagent *> in_reag;
vector<df::item *> in_items;
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,
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);
if (!out_items.size())
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++)
{
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)
is_gloves = false;
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 true;
@ -248,7 +253,9 @@ command_result df_createitem (color_ostream &out, vector <string> & parameters)
break;
}
if (!makeItem(prod, unit))
bool result = makeItem(prod, unit);
delete prod;
if (!result)
{
out.printerr("Failed to create item!\n");
return CR_FAILURE;

@ -422,12 +422,17 @@ protected:
virtual bool can_init(S *screen)
{
auto list = getLayerList(screen);
if (!list->active)
if (!is_list_valid(screen) || !list->active)
return false;
return true;
}
virtual bool is_list_valid(S*)
{
return true;
}
virtual void do_search()
{
search_generic<S,T>::do_search();
@ -444,8 +449,12 @@ protected:
virtual void 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:
@ -1208,12 +1217,14 @@ public:
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)
return false;
return military_search_base::can_init(screen);
return true;
}
vector<df::unit *> *get_primary_list()

@ -6,10 +6,12 @@ class AutoFarm
end
def setthreshold(id, v)
if df.world.raws.plants.all.find { |r| r.id == id }
@thresholds[id] = v.to_i
list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id }
if tok = df.match_rawname(id, list)
@thresholds[tok] = v.to_i
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
@ -17,11 +19,11 @@ class AutoFarm
@thresholds.default = v.to_i
end
def is_plantable (plant)
def is_plantable(plant)
has_seed = plant.flags[:SEED]
season = df.cur_season
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 = can_plant && (will_finish || plant.flags[(season+1)%4])
can_plant
@ -36,7 +38,7 @@ class AutoFarm
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact)
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
counts[i.mat_index] += i.stack_size
end
}
@ -53,7 +55,7 @@ class AutoFarm
return plantable
end
def set_farms( plants, farms)
def set_farms(plants, farms)
return if farms.length == 0
if plants.length == 0
plants = [-1]
@ -61,41 +63,36 @@ class AutoFarm
season = df.cur_season
idx = 0
farms.each { |f|
f.plant_id[season] = plants[idx]
idx = (idx + 1) % plants.length
farms.each_with_index { |f, idx|
f.plant_id[season] = plants[idx % plants.length]
}
end
def process
return false unless @running
plantable = find_plantable_plants
counts = Hash.new(0)
@lastcounts = Hash.new(0)
df.world.items.other[:PLANT].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!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
}
return unless @running
plants_s = []
plants_u = []
@lastcounts.clear
plantable.each_key { |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_u.push(k) if plantable[k] == :Underground
end
@lastcounts[plant.id] = counts[k]
}
farms_s = []
@ -110,11 +107,11 @@ class AutoFarm
set_farms(plants_s, farms_s)
set_farms(plants_u, farms_u)
end
def start
@onupdate = df.onupdate_register('autofarm', 100) { process }
return if @running
@onupdate = df.onupdate_register('autofarm', 1200) { process }
@running = true
end
@ -125,23 +122,28 @@ class AutoFarm
def status
stat = @running ? "Running." : "Stopped."
@lastcounts.each { |k,v|
stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{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
end
end
$AutoFarm = AutoFarm.new unless $AutoFarm
$AutoFarm ||= AutoFarm.new
case $script_args[0]
when 'start'
$AutoFarm.start
$AutoFarm.start
puts $AutoFarm.status
when 'end', 'stop'
$AutoFarm.stop
when 'end', 'stop', 'disable'
$AutoFarm.stop
puts 'Stopped.'
when 'default'
$AutoFarm.setdefault($script_args[1])
@ -156,10 +158,19 @@ when 'delete'
$AutoFarm.stop
$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
if $AutoFarm
puts $AutoFarm.status
else
puts "AI not started"
end
$AutoFarm.process
puts $AutoFarm.status
end

@ -1,31 +1,31 @@
# create arbitrary items under cursor
# create first necessity 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'
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help'
if category == 'help'
puts <<EOS
Create items under the cursor.
Create first necessity items under the cursor.
Usage:
create [category] [raws token] [number]
create-items [category] [raws token] [number]
Item categories:
bars, boulders, plants, logs, web
bars, boulders, plants, logs, webs, anvils
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)
use 'list' to show all possibilities
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
create-items boulders hematite 30
create-items bars CREATURE_MAT:CAT:SOAP 10
create-items web cave_giant
create-items plants list
EOS
throw :script_finished
@ -129,6 +129,18 @@ when 'webs'
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
@ -156,6 +168,10 @@ count.to_i.times {
# move game view, so that the ui menu updates
df.curview.feed_keys(:CURSOR_UP_Z)
df.curview.feed_keys(:CURSOR_DOWN_Z)
if df.cursor.z > 5
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
# use 'undead' to target all undeads
@ -24,7 +24,7 @@ slayit = lambda { |u|
else
# it's getting hot around here
# !!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
df.onupdate_unregister(ouh)
else
@ -55,7 +55,23 @@ case race
when nil
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
slayit[him]
else
@ -77,7 +93,10 @@ when /^undead/i
else
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 }

@ -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
local seasons = {
[-1] = 'Nothing', -- worldgen
[0] = 'Spring',
[1] = 'Summer',
[2] = 'Autumn',