Added some more commands to autobutcher UI script.

Changed autobutcher behavior a bit:
now protected units count towards the target quota,
units available for adoption are considered as protected.
Added filters for caged, male and female to assignement UI
for cages, pastures and pits.
develop
Robert Heinrich 2013-04-06 15:10:13 +02:00 committed by Anuradha Dissanayake
parent fd265b37d6
commit 024a3d766b
3 changed files with 1005 additions and 535 deletions

@ -93,6 +93,9 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job
keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
keybinding add Alt-W@overallstatus "gui/workflow status" keybinding add Alt-W@overallstatus "gui/workflow status"
# autobutcher front-end
keybinding add Shift-B@pet/List/Unit "gui/autobutcher"
# assign weapon racks to squads so that they can be used # assign weapon racks to squads so that they can be used
keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack

@ -489,6 +489,24 @@ bool isHunter(df::unit* unit)
return false; return false;
} }
// check if unit is marked as available for adoption
bool isAvailableForAdoption(df::unit* unit)
{
auto refs = unit->specific_refs;
for(int i=0; i<refs.size(); i++)
{
auto ref = refs[i];
auto reftype = ref->type;
if( reftype == df::specific_ref_type::PETINFO_PET )
{
//df::pet_info* pet = ref->pet;
return true;
}
}
return false;
}
// check if creature belongs to the player's civilization // check if creature belongs to the player's civilization
// (don't try to pasture/slaughter random untame animals) // (don't try to pasture/slaughter random untame animals)
bool isOwnCiv(df::unit* unit) bool isOwnCiv(df::unit* unit)
@ -503,6 +521,8 @@ bool isOwnRace(df::unit* unit)
return unit->race == ui->race_id; return unit->race == ui->race_id;
} }
// get race name by id or unit pointer
// todo: rename these two functions to "getRaceToken" since the output is more of a token
string getRaceName(int32_t id) string getRaceName(int32_t id)
{ {
df::creature_raw *raw = df::global::world->raws.creatures.all[id]; df::creature_raw *raw = df::global::world->raws.creatures.all[id];
@ -513,6 +533,15 @@ string getRaceName(df::unit* unit)
df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
return raw->creature_id; return raw->creature_id;
} }
// get plural of race name (used for display in autobutcher UI and for sorting the watchlist)
string getRaceNamePlural(int32_t id)
{
//WatchedRace * w = watched_races[idx];
df::creature_raw *raw = df::global::world->raws.creatures.all[id];
return raw->name[1]; // second field is plural of race name
}
string getRaceBabyName(df::unit* unit) string getRaceBabyName(df::unit* unit)
{ {
df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
@ -2738,11 +2767,19 @@ public:
bool isWatched; // if true, autobutcher will process this race bool isWatched; // if true, autobutcher will process this race
int raceId; int raceId;
// target amounts
int fk; // max female kids int fk; // max female kids
int mk; // max male kids int mk; // max male kids
int fa; // max female adults int fa; // max female adults
int ma; // max male adults int ma; // max male adults
// amounts of protected (not butcherable) units
int fk_prot;
int fa_prot;
int mk_prot;
int ma_prot;
// bah, this should better be an array of 4 vectors // bah, this should better be an array of 4 vectors
// that way there's no need for the 4 ugly process methods // that way there's no need for the 4 ugly process methods
vector <df::unit*> fk_ptr; vector <df::unit*> fk_ptr;
@ -2758,6 +2795,7 @@ public:
mk = _mk; mk = _mk;
fa = _fa; fa = _fa;
ma = _ma; ma = _ma;
fk_prot = fa_prot = mk_prot = ma_prot = 0;
} }
~WatchedRace() ~WatchedRace()
@ -2822,8 +2860,27 @@ public:
} }
} }
void PushProtectedUnit(df::unit * unit)
{
if(isFemale(unit))
{
if(isBaby(unit) || isChild(unit))
fk_prot++;
else
fa_prot++;
}
else //treat sex n/a like it was male
{
if(isBaby(unit) || isChild(unit))
mk_prot++;
else
ma_prot++;
}
}
void ClearUnits() void ClearUnits()
{ {
fk_prot = fa_prot = mk_prot = ma_prot = 0;
fk_ptr.clear(); fk_ptr.clear();
mk_ptr.clear(); mk_ptr.clear();
fa_ptr.clear(); fa_ptr.clear();
@ -2833,7 +2890,7 @@ public:
int ProcessUnits_fk() int ProcessUnits_fk()
{ {
int subcount = 0; int subcount = 0;
while(fk_ptr.size() > fk) while(fk_ptr.size() && (fk_ptr.size() + fk_prot > fk) )
{ {
df::unit* unit = fk_ptr.back(); df::unit* unit = fk_ptr.back();
doMarkForSlaughter(unit); doMarkForSlaughter(unit);
@ -2846,7 +2903,7 @@ public:
int ProcessUnits_mk() int ProcessUnits_mk()
{ {
int subcount = 0; int subcount = 0;
while(mk_ptr.size() > mk) while(mk_ptr.size() && (mk_ptr.size() + mk_prot > mk) )
{ {
df::unit* unit = mk_ptr.back(); df::unit* unit = mk_ptr.back();
doMarkForSlaughter(unit); doMarkForSlaughter(unit);
@ -2859,7 +2916,7 @@ public:
int ProcessUnits_fa() int ProcessUnits_fa()
{ {
int subcount = 0; int subcount = 0;
while(fa_ptr.size() > fa) while(fa_ptr.size() && (fa_ptr.size() + fa_prot > fa) )
{ {
df::unit* unit = fa_ptr.back(); df::unit* unit = fa_ptr.back();
doMarkForSlaughter(unit); doMarkForSlaughter(unit);
@ -2872,7 +2929,7 @@ public:
int ProcessUnits_ma() int ProcessUnits_ma()
{ {
int subcount = 0; int subcount = 0;
while(ma_ptr.size() > ma) while(ma_ptr.size() && (ma_ptr.size() + ma_prot > ma) )
{ {
df::unit* unit = ma_ptr.back(); df::unit* unit = ma_ptr.back();
doMarkForSlaughter(unit); doMarkForSlaughter(unit);
@ -2902,8 +2959,8 @@ std::vector<WatchedRace*> watched_races;
// helper for sorting the watchlist alphabetically // helper for sorting the watchlist alphabetically
bool compareRaceNames(WatchedRace* i, WatchedRace* j) bool compareRaceNames(WatchedRace* i, WatchedRace* j)
{ {
string name_i = getRaceName(i->raceId); string name_i = getRaceNamePlural(i->raceId);
string name_j = getRaceName(j->raceId); string name_j = getRaceNamePlural(j->raceId);
return (name_i < name_j); return (name_i < name_j);
} }
@ -3347,6 +3404,12 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
for(size_t i=0; i<world->units.all.size(); i++) for(size_t i=0; i<world->units.all.size(); i++)
{ {
df::unit * unit = world->units.all[i]; df::unit * unit = world->units.all[i];
// this check is now divided into two steps, squeezed autowatch into the middle
// first one ignores completely inappropriate units (dead, undead, not belonging to the fort, ...)
// then let autowatch add units to the watchlist which will probably start breeding (owned pets, war animals, ...)
// then process units counting those which can't be butchered (war animals, named pets, ...)
// so that they are treated as "own stock" as well and count towards the target quota
if( isDead(unit) if( isDead(unit)
|| isUndead(unit) || isUndead(unit)
|| isMarkedForSlaughter(unit) || isMarkedForSlaughter(unit)
@ -3354,12 +3417,6 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
|| isForest(unit) // ignore merchants' caged animals || isForest(unit) // ignore merchants' caged animals
|| !isOwnCiv(unit) || !isOwnCiv(unit)
|| !isTame(unit) || !isTame(unit)
|| isWar(unit) // ignore war dogs etc
|| isHunter(unit) // ignore hunting dogs etc
// ignore creatures in built cages which are defined as rooms to leave zoos alone
// (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher)
|| (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom()
|| unit->name.has_name
) )
continue; continue;
@ -3368,26 +3425,40 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
if(!isContainedInItem(unit) && !hasValidMapPos(unit)) if(!isContainedInItem(unit) && !hasValidMapPos(unit))
continue; continue;
WatchedRace * w = NULL;
int watched_index = getWatchedIndex(unit->race); int watched_index = getWatchedIndex(unit->race);
if(watched_index != -1) if(watched_index != -1)
{ {
WatchedRace * w = watched_races[watched_index]; w = watched_races[watched_index];
if(w->isWatched)
w->PushUnit(unit);
} }
else if(enable_autobutcher_autowatch) else if(enable_autobutcher_autowatch)
{ {
WatchedRace * w = new WatchedRace(true, unit->race, default_fk, default_mk, default_fa, default_ma); w = new WatchedRace(true, unit->race, default_fk, default_mk, default_fa, default_ma);
w->UpdateConfig(out); w->UpdateConfig(out);
watched_races.push_back(w); watched_races.push_back(w);
w->PushUnit(unit);
string announce; string announce;
announce = "New race added to autobutcher watchlist: " + getRaceName(w->raceId); announce = "New race added to autobutcher watchlist: " + getRaceNamePlural(w->raceId);
Gui::showAnnouncement(announce, 2, false); Gui::showAnnouncement(announce, 2, false);
//out << announce << endl;
autobutcher_sortWatchList(out); autobutcher_sortWatchList(out);
} }
if(w && w->isWatched)
{
// don't butcher protected units, but count them as stock as well
// this way they count towards target quota, so if you order that you want 1 female adult cat
// and have 2 cats, one of them being a pet, the other gets butchered
if( isWar(unit) // ignore war dogs etc
|| isHunter(unit) // ignore hunting dogs etc
// ignore creatures in built cages which are defined as rooms to leave zoos alone
// (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher)
|| (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom()
|| isAvailableForAdoption(unit)
|| unit->name.has_name )
w->PushProtectedUnit(unit);
else
w->PushUnit(unit);
}
} }
int slaughter_count = 0; int slaughter_count = 0;
@ -3401,12 +3472,10 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
stringstream ss; stringstream ss;
ss << slaughter_subcount; ss << slaughter_subcount;
string announce; string announce;
announce = getRaceName(w->raceId) + " marked for slaughter: " + ss.str(); announce = getRaceNamePlural(w->raceId) + " marked for slaughter: " + ss.str();
Gui::showAnnouncement(announce, 2, false); Gui::showAnnouncement(announce, 2, false);
//out << announce << endl;
} }
} }
//out << slaughter_count << " units total marked for slaughter." << endl;
return CR_OK; return CR_OK;
} }
@ -3490,7 +3559,6 @@ command_result init_autobutcher(color_ostream &out)
//out << " mk: " << p->ival(3) << endl; //out << " mk: " << p->ival(3) << endl;
//out << " fa: " << p->ival(4) << endl; //out << " fa: " << p->ival(4) << endl;
//out << " ma: " << p->ival(5) << endl; //out << " ma: " << p->ival(5) << endl;
WatchedRace * w = new WatchedRace(p->ival(1), p->ival(0), p->ival(2), p->ival(3),p->ival(4),p->ival(5)); WatchedRace * w = new WatchedRace(p->ival(1), p->ival(0), p->ival(2), p->ival(3),p->ival(4),p->ival(5));
w->rconfig = *p; w->rconfig = *p;
watched_races.push_back(w); watched_races.push_back(w);
@ -3562,6 +3630,199 @@ command_result cleanup_autonestbox(color_ostream &out)
return CR_OK; return CR_OK;
} }
// abuse WatchedRace struct for counting stocks (since it sorts by gender and age)
// calling method must delete pointer!
WatchedRace * checkRaceStocksTotal(int race)
{
WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma);
for(size_t i=0; i<world->units.all.size(); i++)
{
df::unit * unit = world->units.all[i];
if(unit->race != race)
continue;
if( isDead(unit)
|| isUndead(unit)
|| isMerchant(unit) // ignore merchants' draught animals
|| isForest(unit) // ignore merchants' caged animals
|| !isOwnCiv(unit)
)
continue;
// found a bugged unit which had invalid coordinates but was not in a cage.
// marking it for slaughter didn't seem to have negative effects, but you never know...
if(!isContainedInItem(unit) && !hasValidMapPos(unit))
continue;
w->PushUnit(unit);
}
return w;
}
WatchedRace * checkRaceStocksProtected(int race)
{
WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma);
for(size_t i=0; i<world->units.all.size(); i++)
{
df::unit * unit = world->units.all[i];
if(unit->race != race)
continue;
if( isDead(unit)
|| isUndead(unit)
|| isMerchant(unit) // ignore merchants' draught animals
|| isForest(unit) // ignore merchants' caged animals
|| !isOwnCiv(unit)
)
continue;
// found a bugged unit which had invalid coordinates but was not in a cage.
// marking it for slaughter didn't seem to have negative effects, but you never know...
if(!isContainedInItem(unit) && !hasValidMapPos(unit))
continue;
if( !isTame(unit)
|| isWar(unit) // ignore war dogs etc
|| isHunter(unit) // ignore hunting dogs etc
// ignore creatures in built cages which are defined as rooms to leave zoos alone
// (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher)
|| (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom()
|| isAvailableForAdoption(unit)
|| unit->name.has_name )
w->PushUnit(unit);
}
return w;
}
WatchedRace * checkRaceStocksButcherable(int race)
{
WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma);
for(size_t i=0; i<world->units.all.size(); i++)
{
df::unit * unit = world->units.all[i];
if(unit->race != race)
continue;
if( isDead(unit)
|| isUndead(unit)
|| isMerchant(unit) // ignore merchants' draught animals
|| isForest(unit) // ignore merchants' caged animals
|| !isOwnCiv(unit)
|| !isTame(unit)
|| isWar(unit) // ignore war dogs etc
|| isHunter(unit) // ignore hunting dogs etc
// ignore creatures in built cages which are defined as rooms to leave zoos alone
// (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher)
|| (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom()
|| isAvailableForAdoption(unit)
|| unit->name.has_name
)
continue;
// found a bugged unit which had invalid coordinates but was not in a cage.
// marking it for slaughter didn't seem to have negative effects, but you never know...
if(!isContainedInItem(unit) && !hasValidMapPos(unit))
continue;
w->PushUnit(unit);
}
return w;
}
WatchedRace * checkRaceStocksButcherFlag(int race)
{
WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma);
for(size_t i=0; i<world->units.all.size(); i++)
{
df::unit * unit = world->units.all[i];
if(unit->race != race)
continue;
if( isDead(unit)
|| isUndead(unit)
|| isMerchant(unit) // ignore merchants' draught animals
|| isForest(unit) // ignore merchants' caged animals
|| !isOwnCiv(unit)
)
continue;
// found a bugged unit which had invalid coordinates but was not in a cage.
// marking it for slaughter didn't seem to have negative effects, but you never know...
if(!isContainedInItem(unit) && !hasValidMapPos(unit))
continue;
if(isMarkedForSlaughter(unit))
w->PushUnit(unit);
}
return w;
}
void butcherRace(int race)
{
for(size_t i=0; i<world->units.all.size(); i++)
{
df::unit * unit = world->units.all[i];
if(unit->race != race)
continue;
if( isDead(unit)
|| isUndead(unit)
|| isMerchant(unit) // ignore merchants' draught animals
|| isForest(unit) // ignore merchants' caged animals
|| !isOwnCiv(unit)
|| !isTame(unit)
|| isWar(unit) // ignore war dogs etc
|| isHunter(unit) // ignore hunting dogs etc
// ignore creatures in built cages which are defined as rooms to leave zoos alone
// (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher)
|| (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom()
|| isAvailableForAdoption(unit)
|| unit->name.has_name
)
continue;
// found a bugged unit which had invalid coordinates but was not in a cage.
// marking it for slaughter didn't seem to have negative effects, but you never know...
if(!isContainedInItem(unit) && !hasValidMapPos(unit))
continue;
unit->flags2.bits.slaughter = true;
}
}
// remove butcher flag for all units of a given race
void unbutcherRace(int race)
{
for(size_t i=0; i<world->units.all.size(); i++)
{
df::unit * unit = world->units.all[i];
if(unit->race != race)
continue;
if( isDead(unit)
|| isUndead(unit)
|| !isMarkedForSlaughter(unit)
)
continue;
if(!isContainedInItem(unit) && !hasValidMapPos(unit))
continue;
unit->flags2.bits.slaughter = false;
}
}
///////////////////////////////////// /////////////////////////////////////
// API functions to control autobutcher with a lua script // API functions to control autobutcher with a lua script
@ -3615,135 +3876,48 @@ static void autowatch_setEnabled(color_ostream &out, bool enable)
} }
} }
static size_t autobutcher_getWatchListSize() // set all data for a watchlist race in one go
{ // if race is not already on watchlist it will be added
return watched_races.size(); // params: (id, fk, mk, fa, ma, watched)
} static void autobutcher_setWatchListRace(color_ostream &out, size_t id, size_t fk, size_t mk, size_t fa, size_t ma, bool watched)
// get race name for a watchlist index
static std::string autobutcher_getWatchListRace(color_ostream &out, size_t idx)
{
if(idx >= watched_races.size())
return "INVALID";
WatchedRace * w = watched_races[idx];
return getRaceName(w->raceId);
}
// get FK for a watchlist index
static size_t autobutcher_getWatchListRaceFK(color_ostream &out, size_t idx)
{
if(idx >= watched_races.size())
return -1;
WatchedRace * w = watched_races[idx];
return w->fk;
}
// get FA for a watchlist index
static size_t autobutcher_getWatchListRaceFA(color_ostream &out, size_t idx)
{
if(idx >= watched_races.size())
return -1;
WatchedRace * w = watched_races[idx];
return w->fa;
}
// get MK for a watchlist index
static size_t autobutcher_getWatchListRaceMK(color_ostream &out, size_t idx)
{
if(idx >= watched_races.size())
return -1;
WatchedRace * w = watched_races[idx];
return w->mk;
}
// get MA for a watchlist index
static size_t autobutcher_getWatchListRaceMA(color_ostream &out, size_t idx)
{
if(idx >= watched_races.size())
return -1;
WatchedRace * w = watched_races[idx];
return w->ma;
}
// set FK for a watchlist index
static void autobutcher_setWatchListRaceFK(color_ostream &out, size_t idx, size_t value)
{ {
if(idx >= watched_races.size()) int watched_index = getWatchedIndex(id);
return; if(watched_index != -1)
WatchedRace * w = watched_races[idx];
w->fk = value;
w->UpdateConfig(out);
}
// set FA for a watchlist index
static void autobutcher_setWatchListRaceFA(color_ostream &out, size_t idx, size_t value)
{ {
if(idx >= watched_races.size()) out << "updating watchlist entry" << endl;
return; WatchedRace * w = watched_races[watched_index];
w->fk = fk;
WatchedRace * w = watched_races[idx]; w->mk = mk;
w->fa = value; w->fa = fa;
w->ma = ma;
w->isWatched = watched;
w->UpdateConfig(out); w->UpdateConfig(out);
} }
else
// set MK for a watchlist index
static void autobutcher_setWatchListRaceMK(color_ostream &out, size_t idx, size_t value)
{ {
if(idx >= watched_races.size()) out << "creating new watchlist entry" << endl;
return; WatchedRace * w = new WatchedRace(watched, id, fk, mk, fa, ma); //default_fk, default_mk, default_fa, default_ma);
WatchedRace * w = watched_races[idx];
w->mk = value;
w->UpdateConfig(out); w->UpdateConfig(out);
} watched_races.push_back(w);
// set MA for a watchlist index
static void autobutcher_setWatchListRaceMA(color_ostream &out, size_t idx, size_t value)
{
if(idx >= watched_races.size())
return;
WatchedRace * w = watched_races[idx]; string announce;
w->ma = value; announce = "New race added to autobutcher watchlist: " + getRaceNamePlural(w->raceId);
w->UpdateConfig(out); Gui::showAnnouncement(announce, 2, false);
autobutcher_sortWatchList(out);
} }
// check if "watch" is enabled for watchlist index
static bool autobutcher_isWatchListRaceWatched(color_ostream &out, size_t idx)
{
if(idx >= watched_races.size())
return false;
WatchedRace * w = watched_races[idx];
return w->isWatched;
}
// set "watched" status for a watchlist index
static void autobutcher_setWatchListRaceWatched(color_ostream &out, size_t idx, bool watched)
{
if(idx >= watched_races.size())
return;
WatchedRace * w = watched_races[idx];
w->isWatched = watched;
w->UpdateConfig(out);
} }
// remove entry from watchlist // remove entry from watchlist
static void autobutcher_removeFromWatchList(color_ostream &out, size_t idx) static void autobutcher_removeFromWatchList(color_ostream &out, size_t id)
{ {
if(idx >= watched_races.size()) int watched_index = getWatchedIndex(id);
return; if(watched_index != -1)
{
WatchedRace * w = watched_races[idx]; out << "updating watchlist entry" << endl;
WatchedRace * w = watched_races[watched_index];
w->RemoveConfig(out); w->RemoveConfig(out);
watched_races.erase(watched_races.begin()+idx); watched_races.erase(watched_races.begin() + watched_index);
}
} }
// sort watchlist alphabetically // sort watchlist alphabetically
@ -3752,24 +3926,6 @@ static void autobutcher_sortWatchList(color_ostream &out)
sort(watched_races.begin(), watched_races.end(), compareRaceNames); sort(watched_races.begin(), watched_races.end(), compareRaceNames);
} }
// get default target values for new races
static size_t autobutcher_getDefaultMK(color_ostream &out)
{
return default_mk;
}
static size_t autobutcher_getDefaultMA(color_ostream &out)
{
return default_ma;
}
static size_t autobutcher_getDefaultFK(color_ostream &out)
{
return default_fk;
}
static size_t autobutcher_getDefaultFA(color_ostream &out)
{
return default_fa;
}
// set default target values for new races // set default target values for new races
static void autobutcher_setDefaultTargetNew(color_ostream &out, size_t fk, size_t mk, size_t fa, size_t ma) static void autobutcher_setDefaultTargetNew(color_ostream &out, size_t fk, size_t mk, size_t fa, size_t ma)
{ {
@ -3801,13 +3957,86 @@ static void autobutcher_setDefaultTargetAll(color_ostream &out, size_t fk, size_
autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma); autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma);
} }
static std::string testString(color_ostream &out, size_t i) static void autobutcher_butcherRace(color_ostream &out, size_t id)
{
butcherRace(id);
}
static void autobutcher_unbutcherRace(color_ostream &out, size_t id)
{
unbutcherRace(id);
}
// push autobutcher settings on lua stack
static int autobutcher_getSettings(lua_State *L)
{ {
out << "where will this be written?" << std::endl; color_ostream &out = *Lua::GetOutput(L);
return getRaceName(i); lua_newtable(L);
int ctable = lua_gettop(L);
Lua::SetField(L, enable_autobutcher, ctable, "enable_autobutcher");
Lua::SetField(L, enable_autobutcher_autowatch, ctable, "enable_autowatch");
Lua::SetField(L, default_fk, ctable, "fk");
Lua::SetField(L, default_mk, ctable, "mk");
Lua::SetField(L, default_fa, ctable, "fa");
Lua::SetField(L, default_ma, ctable, "ma");
Lua::SetField(L, sleep_autobutcher, ctable, "sleep");
return 1;
} }
// DFHACK_LUA_FUNCTION(autobutcher_setEnabled), // push the watchlist vector as nested table on the lua stack
static int autobutcher_getWatchList(lua_State *L)
{
color_ostream &out = *Lua::GetOutput(L);
lua_newtable(L);
for(size_t i=0; i<watched_races.size(); i++)
{
lua_newtable(L);
int ctable = lua_gettop(L);
WatchedRace * w = watched_races[i];
Lua::SetField(L, w->raceId, ctable, "id");
Lua::SetField(L, w->isWatched, ctable, "watched");
Lua::SetField(L, getRaceNamePlural(w->raceId), ctable, "name");
Lua::SetField(L, w->fk, ctable, "fk");
Lua::SetField(L, w->mk, ctable, "mk");
Lua::SetField(L, w->fa, ctable, "fa");
Lua::SetField(L, w->ma, ctable, "ma");
int id = w->raceId;
w = checkRaceStocksTotal(id);
Lua::SetField(L, w->fk_ptr.size(), ctable, "fk_total");
Lua::SetField(L, w->mk_ptr.size(), ctable, "mk_total");
Lua::SetField(L, w->fa_ptr.size(), ctable, "fa_total");
Lua::SetField(L, w->ma_ptr.size(), ctable, "ma_total");
delete w;
w = checkRaceStocksProtected(id);
Lua::SetField(L, w->fk_ptr.size(), ctable, "fk_protected");
Lua::SetField(L, w->mk_ptr.size(), ctable, "mk_protected");
Lua::SetField(L, w->fa_ptr.size(), ctable, "fa_protected");
Lua::SetField(L, w->ma_ptr.size(), ctable, "ma_protected");
delete w;
w = checkRaceStocksButcherable(id);
Lua::SetField(L, w->fk_ptr.size(), ctable, "fk_butcherable");
Lua::SetField(L, w->mk_ptr.size(), ctable, "mk_butcherable");
Lua::SetField(L, w->fa_ptr.size(), ctable, "fa_butcherable");
Lua::SetField(L, w->ma_ptr.size(), ctable, "ma_butcherable");
delete w;
w = checkRaceStocksButcherFlag(id);
Lua::SetField(L, w->fk_ptr.size(), ctable, "fk_butcherflag");
Lua::SetField(L, w->mk_ptr.size(), ctable, "mk_butcherflag");
Lua::SetField(L, w->fa_ptr.size(), ctable, "fa_butcherflag");
Lua::SetField(L, w->ma_ptr.size(), ctable, "ma_butcherflag");
delete w;
lua_rawseti(L, -2, i+1);
}
return 1;
}
DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(autobutcher_isEnabled), DFHACK_LUA_FUNCTION(autobutcher_isEnabled),
@ -3816,34 +4045,19 @@ DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(autowatch_setEnabled), DFHACK_LUA_FUNCTION(autowatch_setEnabled),
DFHACK_LUA_FUNCTION(autobutcher_getSleep), DFHACK_LUA_FUNCTION(autobutcher_getSleep),
DFHACK_LUA_FUNCTION(autobutcher_setSleep), DFHACK_LUA_FUNCTION(autobutcher_setSleep),
DFHACK_LUA_FUNCTION(autobutcher_getWatchListSize), DFHACK_LUA_FUNCTION(autobutcher_setWatchListRace),
DFHACK_LUA_FUNCTION(autobutcher_getWatchListRace),
DFHACK_LUA_FUNCTION(autobutcher_isWatchListRaceWatched),
DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceWatched),
DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceFK),
DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceFA),
DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceMK),
DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceMA),
DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceFK),
DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceFA),
DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceMK),
DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceMA),
DFHACK_LUA_FUNCTION(autobutcher_getDefaultFK),
DFHACK_LUA_FUNCTION(autobutcher_getDefaultFA),
DFHACK_LUA_FUNCTION(autobutcher_getDefaultMK),
DFHACK_LUA_FUNCTION(autobutcher_getDefaultMA),
DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetNew), DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetNew),
DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetAll), DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetAll),
DFHACK_LUA_FUNCTION(autobutcher_butcherRace),
DFHACK_LUA_FUNCTION(autobutcher_unbutcherRace),
DFHACK_LUA_FUNCTION(autobutcher_removeFromWatchList), DFHACK_LUA_FUNCTION(autobutcher_removeFromWatchList),
DFHACK_LUA_FUNCTION(autobutcher_sortWatchList), DFHACK_LUA_FUNCTION(autobutcher_sortWatchList),
DFHACK_LUA_END
};
DFHACK_LUA_FUNCTION(testString), DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(autobutcher_getSettings),
DFHACK_LUA_COMMAND(autobutcher_getWatchList),
DFHACK_LUA_END DFHACK_LUA_END
}; };
@ -3858,6 +4072,8 @@ using df::global::ui_building_assign_is_marked;
using df::global::ui_building_assign_units; using df::global::ui_building_assign_units;
using df::global::ui_building_assign_items; using df::global::ui_building_assign_items;
using df::global::ui_building_in_assign;
static const int ascii_to_enum_offset = interface_key::STRING_A048 - '0'; static const int ascii_to_enum_offset = interface_key::STRING_A048 - '0';
void OutputString(int8_t color, int &x, int y, const std::string &text) void OutputString(int8_t color, int &x, int y, const std::string &text)
@ -3894,7 +4110,7 @@ public:
} }
search_string.clear(); search_string.clear();
show_non_grazers = show_pastured = show_other_zones = true; show_non_grazers = show_pastured = show_noncaged = show_male = show_female = show_other_zones = true;
entry_mode = false; entry_mode = false;
initialized = true; initialized = true;
@ -3952,6 +4168,22 @@ public:
if (!show_pastured && isAssignedToZone(curr_unit)) if (!show_pastured && isAssignedToZone(curr_unit))
continue; continue;
if (!show_noncaged)
{
// must be in a container
if(!isContainedInItem(curr_unit))
continue;
// but exclude built cages (zoos, traps, ...) to avoid "accidental" pitting of creatures you'd prefer to keep
if (isInBuiltCage(curr_unit))
continue;
}
if (!show_male && isMale(curr_unit))
continue;
if (!show_female && isFemale(curr_unit))
continue;
if (!search_string_l.empty()) if (!search_string_l.empty())
{ {
string desc = Translation::TranslateName( string desc = Translation::TranslateName(
@ -4018,16 +4250,36 @@ public:
} }
} }
// Not in query typing mode // Not in query typing mode
else if (input->count(interface_key::CUSTOM_G) && mode == ui_sidebar_mode::ZonesPenInfo) else if (input->count(interface_key::CUSTOM_G) &&
(mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::QueryBuilding))
{ {
show_non_grazers = !show_non_grazers; show_non_grazers = !show_non_grazers;
apply_filters(); apply_filters();
} }
else if (input->count(interface_key::CUSTOM_P) && mode == ui_sidebar_mode::ZonesPenInfo) else if (input->count(interface_key::CUSTOM_C) &&
(mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding))
{
show_noncaged = !show_noncaged;
apply_filters();
}
else if (input->count(interface_key::CUSTOM_P) &&
(mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding))
{ {
show_pastured = !show_pastured; show_pastured = !show_pastured;
apply_filters(); apply_filters();
} }
else if (input->count(interface_key::CUSTOM_M) &&
(mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding))
{
show_male = !show_male;
apply_filters();
}
else if (input->count(interface_key::CUSTOM_F) &&
(mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding))
{
show_female = !show_female;
apply_filters();
}
else if (input->count(interface_key::CUSTOM_S)) else if (input->count(interface_key::CUSTOM_S))
{ {
// Hotkey pressed, enter typing mode // Hotkey pressed, enter typing mode
@ -4076,7 +4328,7 @@ public:
OutputString(COLOR_LIGHTGREEN, x, y, "_"); OutputString(COLOR_LIGHTGREEN, x, y, "_");
} }
if (mode == ui_sidebar_mode::ZonesPenInfo) if (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::QueryBuilding)
{ {
x = left_margin; x = left_margin;
y += 2; y += 2;
@ -4084,11 +4336,57 @@ public:
OutputString(COLOR_WHITE, x, y, ": "); OutputString(COLOR_WHITE, x, y, ": ");
OutputString((show_non_grazers) ? COLOR_WHITE : COLOR_GREY, x, y, "Non-Grazing"); OutputString((show_non_grazers) ? COLOR_WHITE : COLOR_GREY, x, y, "Non-Grazing");
x = left_margin;
++y;
OutputString(COLOR_LIGHTGREEN, x, y, "c");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString((show_noncaged) ? COLOR_WHITE : COLOR_GREY, x, y, "Not Caged");
x = left_margin; x = left_margin;
++y; ++y;
OutputString(COLOR_LIGHTGREEN, x, y, "p"); OutputString(COLOR_LIGHTGREEN, x, y, "p");
OutputString(COLOR_WHITE, x, y, ": "); OutputString(COLOR_WHITE, x, y, ": ");
OutputString((show_pastured) ? COLOR_WHITE : COLOR_GREY, x, y, "Currently Pastured"); OutputString((show_pastured) ? COLOR_WHITE : COLOR_GREY, x, y, "Currently Pastured");
x = left_margin;
++y;
OutputString(COLOR_LIGHTGREEN, x, y, "f");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString((show_female) ? COLOR_WHITE : COLOR_GREY, x, y, "Female");
x = left_margin;
++y;
OutputString(COLOR_LIGHTGREEN, x, y, "m");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString((show_male) ? COLOR_WHITE : COLOR_GREY, x, y, "Male");
}
// pits don't have grazer filter because it seems pointless
if (mode == ui_sidebar_mode::ZonesPitInfo)
{
x = left_margin;
y += 2;
OutputString(COLOR_LIGHTGREEN, x, y, "c");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString((show_noncaged) ? COLOR_WHITE : COLOR_GREY, x, y, "Not Caged");
x = left_margin;
++y;
OutputString(COLOR_LIGHTGREEN, x, y, "p");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString((show_pastured) ? COLOR_WHITE : COLOR_GREY, x, y, "Currently Pastured");
x = left_margin;
++y;
OutputString(COLOR_LIGHTGREEN, x, y, "f");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString((show_female) ? COLOR_WHITE : COLOR_GREY, x, y, "Female");
x = left_margin;
++y;
OutputString(COLOR_LIGHTGREEN, x, y, "m");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString((show_male) ? COLOR_WHITE : COLOR_GREY, x, y, "Male");
} }
} }
@ -4097,7 +4395,7 @@ private:
string search_string; string search_string;
bool initialized; bool initialized;
bool entry_mode; bool entry_mode;
bool show_non_grazers, show_pastured, show_other_zones; bool show_non_grazers, show_pastured, show_noncaged, show_male, show_female, show_other_zones;
std::vector<int8_t> saved_ui_building_assign_type; std::vector<int8_t> saved_ui_building_assign_type;
std::vector<df::unit*> saved_ui_building_assign_units, reference_list; std::vector<df::unit*> saved_ui_building_assign_units, reference_list;
@ -4121,11 +4419,20 @@ struct zone_hook : public df::viewscreen_dwarfmodest
DEFINE_VMETHOD_INTERPOSE(void, render, ()) DEFINE_VMETHOD_INTERPOSE(void, render, ())
{ {
if ((ui->main.mode == ui_sidebar_mode::ZonesPenInfo || ui->main.mode == ui_sidebar_mode::ZonesPitInfo) && if ( ( (ui->main.mode == ui_sidebar_mode::ZonesPenInfo || ui->main.mode == ui_sidebar_mode::ZonesPitInfo) &&
ui_building_assign_type && ui_building_assign_units && ui_building_assign_type && ui_building_assign_units &&
ui_building_assign_is_marked && ui_building_assign_items && ui_building_assign_is_marked && ui_building_assign_items &&
ui_building_assign_type->size() == ui_building_assign_units->size() && ui_building_assign_type->size() == ui_building_assign_units->size() &&
ui_building_item_cursor) ui_building_item_cursor)
// allow mode QueryBuilding, but only for cages (bedrooms will crash DF with this code, chains don't work either etc)
||
( ui->main.mode == ui_sidebar_mode::QueryBuilding &&
ui_building_in_assign && *ui_building_in_assign &&
ui_building_assign_type && ui_building_assign_units &&
ui_building_assign_type->size() == ui_building_assign_units->size() &&
ui_building_item_cursor &&
world->selected_building && isCage(world->selected_building) )
)
{ {
if (vector_get(*ui_building_assign_units, *ui_building_item_cursor)) if (vector_get(*ui_building_assign_units, *ui_building_item_cursor))
filter.initialize(ui->main.mode); filter.initialize(ui->main.mode);

@ -1,38 +1,4 @@
-- A GUI front-end for the autobutcher plugin. -- A GUI front-end for the autobutcher plugin.
-- requires to be called from the stock screen (z)
--[[
API overview (zone/autobutcher plugin functions which can be used by this lua script):
autobutcher_isEnabled() - returns true if autobutcher is running
autowatch_isEnabled() - returns true if autowatch is running
autobutcher_getSleep() - get sleep timer in ticks
autobutcher_setSleep(int) - set sleep timer in ticks
autobutcher_getWatchListSize() - return size of watchlist
autobutcher_getWatchListRace(idx) - return race name for this watchlist index
autobutcher_isWatchListRaceWatched(idx) - true if this watchlist index is watched
autobutcher_setWatchListRaceWatched(idx, bool) - set watchlist index to watched/unwatched
autobutcher_getWatchListRaceFK() - get target fk
autobutcher_getWatchListRaceFA() - get target fa
autobutcher_getWatchListRaceMK() - get target mk
autobutcher_getWatchListRaceMA() - get target ma
autobutcher_setWatchListRaceFK(value) - set fk
autobutcher_setWatchListRaceFA(value) - set fa
autobutcher_setWatchListRaceMK(value) - set mk
autobutcher_setWatchListRaceMA(value) - set ma
autobutcher_removeFromWatchList(idx) - remove watchlist entry
autobutcher_sortWatchList() - sort the watchlist alphabetically
testString(id) - returns race name for this race id (gonna be removed soon)
--]]
local gui = require 'gui' local gui = require 'gui'
local utils = require 'utils' local utils = require 'utils'
@ -55,16 +21,16 @@ local racewidth = 25
function nextAutowatchState() function nextAutowatchState()
if(plugin.autowatch_isEnabled()) then if(plugin.autowatch_isEnabled()) then
return 'stop' return 'Stop '
end end
return 'start' return 'Start'
end end
function nextAutobutcherState() function nextAutobutcherState()
if(plugin.autobutcher_isEnabled()) then if(plugin.autobutcher_isEnabled()) then
return 'stop' return 'Stop '
end end
return 'start' return 'Start'
end end
function getSleepTimer() function getSleepTimer()
@ -76,6 +42,7 @@ function setSleepTimer(ticks)
end end
function WatchList:init(args) function WatchList:init(args)
local colwidth = 7
self:addviews{ self:addviews{
widgets.Panel{ widgets.Panel{
frame = { l = 0, r = 0 }, frame = { l = 0, r = 0 },
@ -86,27 +53,30 @@ function WatchList:init(args)
text_pen = COLOR_CYAN, text_pen = COLOR_CYAN,
text = { text = {
{ text = 'Race', width = racewidth }, ' ', { text = 'Race', width = racewidth }, ' ',
{ text = 'female', width = 6 }, ' ', { text = 'female', width = colwidth }, ' ',
{ text = ' male', width = 6 }, ' ', { text = ' male', width = colwidth }, ' ',
{ text = 'Female', width = 6 }, ' ', { text = 'Female', width = colwidth }, ' ',
{ text = ' Male', width = 6 }, ' ', { text = ' Male', width = colwidth }, ' ',
{ text = 'watching?' }, { text = 'watch? ' },
{ text = ' butchering' },
NEWLINE, NEWLINE,
{ text = '', width = racewidth }, ' ', { text = '', width = racewidth }, ' ',
{ text = ' kids', width = 6 }, ' ', { text = ' kids', width = colwidth }, ' ',
{ text = ' kids', width = 6 }, ' ', { text = ' kids', width = colwidth }, ' ',
{ text = 'adults', width = 6 }, ' ', { text = 'adults', width = colwidth }, ' ',
{ text = 'adults', width = 6 }, ' ', { text = 'adults', width = colwidth }, ' ',
{ text = ' ' },
{ text = ' ordered' },
} }
}, },
widgets.List{ widgets.List{
view_id = 'list', view_id = 'list',
frame = { t = 3, b = 3 }, frame = { t = 3, b = 5 },
not_found_label = 'Watchlist is empty.', not_found_label = 'Watchlist is empty.',
edit_pen = COLOR_LIGHTCYAN, edit_pen = COLOR_LIGHTCYAN,
text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK },
cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN },
--on_select = self:callback('onSelectConstraint'), --on_select = self:callback('onSelectEntry'),
}, },
widgets.Label{ widgets.Label{
view_id = 'bottom_ui', view_id = 'bottom_ui',
@ -121,10 +91,25 @@ function WatchList:init(args)
self:updateBottom() self:updateBottom()
end end
-- change the viewmode for stock data displayed in left section of columns
local viewmodes = { 'total stock', 'protected stock', 'butcherable', 'butchering ordered' }
local viewmode = 1
function WatchList:onToggleView()
if viewmode < #viewmodes then
viewmode = viewmode + 1
else
viewmode = 1
end
self:initListChoices()
self:updateBottom()
end
-- update the bottom part of the UI (after sleep timer changed etc) -- update the bottom part of the UI (after sleep timer changed etc)
function WatchList:updateBottom() function WatchList:updateBottom()
self.subviews.bottom_ui:setText( self.subviews.bottom_ui:setText(
{ {
{ key = 'CUSTOM_SHIFT_V', text = ': View in colums shows: '..viewmodes[viewmode]..' / target max',
on_activate = self:callback('onToggleView') }, NEWLINE,
{ key = 'CUSTOM_F', text = ': f kids', { key = 'CUSTOM_F', text = ': f kids',
on_activate = self:callback('onEditFK') }, ', ', on_activate = self:callback('onEditFK') }, ', ',
{ key = 'CUSTOM_M', text = ': m kids', { key = 'CUSTOM_M', text = ': m kids',
@ -132,38 +117,63 @@ function WatchList:updateBottom()
{ key = 'CUSTOM_SHIFT_F', text = ': f adults', { key = 'CUSTOM_SHIFT_F', text = ': f adults',
on_activate = self:callback('onEditFA') }, ', ', on_activate = self:callback('onEditFA') }, ', ',
{ key = 'CUSTOM_SHIFT_M', text = ': m adults', { key = 'CUSTOM_SHIFT_M', text = ': m adults',
on_activate = self:callback('onEditMA') }, ', ', on_activate = self:callback('onEditMA') }, '. ',
{ key = 'CUSTOM_SHIFT_X', text = ': Delete', { key = 'CUSTOM_W', text = ': Toggle watch',
on_activate = self:callback('onDeleteConstraint') }, ', ', on_activate = self:callback('onToggleWatching') }, '. ',
{ key = 'CUSTOM_W', text = ': toggle watch', { key = 'CUSTOM_X', text = ': Delete',
on_activate = self:callback('onToggleWatching') }, ', ', NEWLINE, on_activate = self:callback('onDeleteEntry') }, '. ', NEWLINE,
--{ key = 'CUSTOM_A', text = ': Add race',
-- on_activate = self:callback('onAddRace') }, ', ',
{ key = 'CUSTOM_SHIFT_R', text = ': Set whole row',
on_activate = self:callback('onSetRow') }, '. ',
{ key = 'CUSTOM_B', text = ': Remove butcher orders',
on_activate = self:callback('onUnbutcherRace') }, '. ',
{ key = 'CUSTOM_SHIFT_B', text = ': Butcher race',
on_activate = self:callback('onButcherRace') }, '. ', NEWLINE,
{ key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher', { key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher',
on_activate = self:callback('onToggleAutobutcher') }, ', ', on_activate = self:callback('onToggleAutobutcher') }, '. ',
{ key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch', { key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch',
on_activate = self:callback('onToggleAutowatch') }, ', ', on_activate = self:callback('onToggleAutowatch') }, '. ',
{ key = 'CUSTOM_SHIFT_S', text = ': sleep timer ('..getSleepTimer()..' ticks)', { key = 'CUSTOM_SHIFT_S', text = ': Sleep ('..getSleepTimer()..' ticks)',
on_activate = self:callback('onEditSleepTimer') }, ', ', on_activate = self:callback('onEditSleepTimer') }, '. ',
}) })
end end
function stringify(number)
-- cap displayed number to 3 digits
-- after population of 50 per race is reached pets stop breeding anyways
-- so probably this could safely be reduced to 99
local max = 999
if number > max then number = max end
return tostring(number)
end
function WatchList:initListChoices() function WatchList:initListChoices()
local choices = {} local choices = {}
-- first two rows are for "edit all races" and "edit new races" -- first two rows are for "edit all races" and "edit new races"
local fk = plugin.autobutcher_getDefaultFK() local settings = plugin.autobutcher_getSettings()
local fa = plugin.autobutcher_getDefaultFA() local fk = stringify(settings.fk)
local mk = plugin.autobutcher_getDefaultMK() local fa = stringify(settings.fa)
local ma = plugin.autobutcher_getDefaultMA() local mk = stringify(settings.mk)
local watched = '---' local ma = stringify(settings.ma)
local watched = ''
local colwidth = 7
table.insert (choices, { table.insert (choices, {
text = { text = {
{ text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ', { text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ',
{ text = tostring(fk), width = 7, rjustify = true }, { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = tostring(mk), width = 7, rjustify = true }, { text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = tostring(fa), width = 7, rjustify = true }, { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = tostring(ma), width = 7, rjustify = true }, { text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = watched, width = 6, rjustify = true } { text = watched, width = 6, rjustify = true }
} }
}) })
@ -171,35 +181,85 @@ function WatchList:initListChoices()
table.insert (choices, { table.insert (choices, {
text = { text = {
{ text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ', { text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ',
{ text = tostring(fk), width = 7, rjustify = true }, { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = tostring(mk), width = 7, rjustify = true }, { text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = tostring(fa), width = 7, rjustify = true }, { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = tostring(ma), width = 7, rjustify = true }, { text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = watched, width = 6, rjustify = true } { text = watched, width = 6, rjustify = true }
} }
}) })
-- fill with watchlist local watchlist = plugin.autobutcher_getWatchList()
for i=0, plugin.autobutcher_getWatchListSize()-1 do
local racestr = plugin.autobutcher_getWatchListRace(i) for i,entry in ipairs(watchlist) do
fk = plugin.autobutcher_getWatchListRaceFK(i) fk = stringify(entry.fk)
fa = plugin.autobutcher_getWatchListRaceFA(i) fa = stringify(entry.fa)
mk = plugin.autobutcher_getWatchListRaceMK(i) mk = stringify(entry.mk)
ma = plugin.autobutcher_getWatchListRaceMA(i) ma = stringify(entry.ma)
if viewmode == 1 then
fkc = stringify(entry.fk_total)
fac = stringify(entry.fa_total)
mkc = stringify(entry.mk_total)
mac = stringify(entry.ma_total)
end
if viewmode == 2 then
fkc = stringify(entry.fk_protected)
fac = stringify(entry.fa_protected)
mkc = stringify(entry.mk_protected)
mac = stringify(entry.ma_protected)
end
if viewmode == 3 then
fkc = stringify(entry.fk_butcherable)
fac = stringify(entry.fa_butcherable)
mkc = stringify(entry.mk_butcherable)
mac = stringify(entry.ma_butcherable)
end
if viewmode == 4 then
fkc = stringify(entry.fk_butcherflag)
fac = stringify(entry.fa_butcherflag)
mkc = stringify(entry.mk_butcherflag)
mac = stringify(entry.ma_butcherflag)
end
local butcher_ordered = entry.fk_butcherflag + entry.fa_butcherflag + entry.mk_butcherflag + entry.ma_butcherflag
local bo = ' '
if butcher_ordered > 0 then bo = stringify(butcher_ordered) end
local watched = 'no' local watched = 'no'
if plugin.autobutcher_isWatchListRaceWatched(i) then if entry.watched then watched = 'yes' end
watched = 'yes'
end local racestr = entry.name
-- highlight entries where the target quota can't be met because too many are protected
bad_pen = COLOR_LIGHTRED
good_pen = NONE -- this is stupid, but it works. sue me
fk_pen = good_pen
fa_pen = good_pen
mk_pen = good_pen
ma_pen = good_pen
if entry.fk_protected > entry.fk then fk_pen = bad_pen end
if entry.fa_protected > entry.fa then fa_pen = bad_pen end
if entry.mk_protected > entry.mk then mk_pen = bad_pen end
if entry.ma_protected > entry.ma then ma_pen = bad_pen end
table.insert (choices, { table.insert (choices, {
text = { text = {
{ text = racestr, width = racewidth, pad_char = ' ' }, --' ', { text = racestr, width = racewidth, pad_char = ' ' }, --' ',
{ text = tostring(fk), width = 7, rjustify = true }, { text = fkc, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = tostring(mk), width = 7, rjustify = true }, { text = fk, width = 3, rjustify = false, pad_char = ' ', pen = fk_pen }, ' ',
{ text = tostring(fa), width = 7, rjustify = true }, { text = mkc, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = tostring(ma), width = 7, rjustify = true }, { text = mk, width = 3, rjustify = false, pad_char = ' ', pen = mk_pen }, ' ',
{ text = watched, width = 6, rjustify = true } { text = fac, width = 3, rjustify = true, pad_char = ' ' }, '/',
} { text = fa, width = 3, rjustify = false, pad_char = ' ', pen = fa_pen }, ' ',
{ text = mac, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = ma, width = 3, rjustify = false, pad_char = ' ', pen = ma_pen }, ' ',
{ text = watched, width = 6, rjustify = true, pad_char = ' ' }, ' ',
{ text = bo, width = 8, rjustify = true, pad_char = ' ' }
},
obj = entry,
}) })
end end
@ -245,20 +305,28 @@ end
function WatchList:onEditFK() function WatchList:onEditFK()
local selidx,selobj = self.subviews.list:getSelected() local selidx,selobj = self.subviews.list:getSelected()
local fk = plugin.autobutcher_getDefaultFK(); local settings = plugin.autobutcher_getSettings()
local mk = plugin.autobutcher_getDefaultMK(); local fk = settings.fk
local fa = plugin.autobutcher_getDefaultFA(); local mk = settings.mk
local ma = plugin.autobutcher_getDefaultMA(); local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW' local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then if selidx == 2 then
race = 'ONLY NEW RACES' race = 'ONLY NEW RACES'
end end
local watchindex = selidx - 3
if selidx > 2 then if selidx > 2 then
fk = plugin.autobutcher_getWatchListRaceFK(watchindex) local entry = selobj.obj
race = plugin.autobutcher_getWatchListRace(watchindex) fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end end
dlg.showInputPrompt( dlg.showInputPrompt(
@ -277,7 +345,7 @@ function WatchList:onEditFK()
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end end
if selidx > 2 then if selidx > 2 then
plugin.autobutcher_setWatchListRaceFK(watchindex, fk) plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end end
self:initListChoices() self:initListChoices()
end end
@ -286,21 +354,29 @@ function WatchList:onEditFK()
end end
function WatchList:onEditMK() function WatchList:onEditMK()
local selidx = self.subviews.list:getSelected() local selidx,selobj = self.subviews.list:getSelected()
local fk = plugin.autobutcher_getDefaultFK(); local settings = plugin.autobutcher_getSettings()
local mk = plugin.autobutcher_getDefaultMK(); local fk = settings.fk
local fa = plugin.autobutcher_getDefaultFA(); local mk = settings.mk
local ma = plugin.autobutcher_getDefaultMA(); local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW' local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then if selidx == 2 then
race = 'ONLY NEW RACES' race = 'ONLY NEW RACES'
end end
local watchindex = selidx - 3
if selidx > 2 then if selidx > 2 then
mk = plugin.autobutcher_getWatchListRaceMK(watchindex) local entry = selobj.obj
race = plugin.autobutcher_getWatchListRace(watchindex) fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end end
dlg.showInputPrompt( dlg.showInputPrompt(
@ -319,7 +395,7 @@ function WatchList:onEditMK()
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end end
if selidx > 2 then if selidx > 2 then
plugin.autobutcher_setWatchListRaceMK(watchindex, mk) plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end end
self:initListChoices() self:initListChoices()
end end
@ -328,21 +404,29 @@ function WatchList:onEditMK()
end end
function WatchList:onEditFA() function WatchList:onEditFA()
local selidx = self.subviews.list:getSelected() local selidx,selobj = self.subviews.list:getSelected()
local fk = plugin.autobutcher_getDefaultFK(); local settings = plugin.autobutcher_getSettings()
local mk = plugin.autobutcher_getDefaultMK(); local fk = settings.fk
local fa = plugin.autobutcher_getDefaultFA(); local mk = settings.mk
local ma = plugin.autobutcher_getDefaultMA(); local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW' local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then if selidx == 2 then
race = 'ONLY NEW RACES' race = 'ONLY NEW RACES'
end end
local watchindex = selidx - 3
if selidx > 2 then if selidx > 2 then
fa = plugin.autobutcher_getWatchListRaceFA(watchindex) local entry = selobj.obj
race = plugin.autobutcher_getWatchListRace(watchindex) fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end end
dlg.showInputPrompt( dlg.showInputPrompt(
@ -361,7 +445,7 @@ function WatchList:onEditFA()
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end end
if selidx > 2 then if selidx > 2 then
plugin.autobutcher_setWatchListRaceFA(watchindex, fa) plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end end
self:initListChoices() self:initListChoices()
end end
@ -370,21 +454,29 @@ function WatchList:onEditFA()
end end
function WatchList:onEditMA() function WatchList:onEditMA()
local selidx = self.subviews.list:getSelected() local selidx,selobj = self.subviews.list:getSelected()
local fk = plugin.autobutcher_getDefaultFK(); local settings = plugin.autobutcher_getSettings()
local mk = plugin.autobutcher_getDefaultMK(); local fk = settings.fk
local fa = plugin.autobutcher_getDefaultFA(); local mk = settings.mk
local ma = plugin.autobutcher_getDefaultMA(); local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW' local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then if selidx == 2 then
race = 'ONLY NEW RACES' race = 'ONLY NEW RACES'
end end
local watchindex = selidx - 3
if selidx > 2 then if selidx > 2 then
ma = plugin.autobutcher_getWatchListRaceMA(watchindex) local entry = selobj.obj
race = plugin.autobutcher_getWatchListRace(watchindex) fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end end
dlg.showInputPrompt( dlg.showInputPrompt(
@ -403,7 +495,7 @@ function WatchList:onEditMA()
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end end
if selidx > 2 then if selidx > 2 then
plugin.autobutcher_setWatchListRaceMA(watchindex, ma) plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end end
self:initListChoices() self:initListChoices()
end end
@ -430,46 +522,100 @@ function WatchList:onEditSleepTimer()
end end
function WatchList:onToggleWatching() function WatchList:onToggleWatching()
local selidx = self.subviews.list:getSelected() local selidx,selobj = self.subviews.list:getSelected()
if(selidx == 0) then
--print('special handling for zero - list empty?')
end
if (selidx == 1) then
--print('special handling for first row - ALL animals')
end
if (selidx == 2) then
--print('special handling for second row - NEW animals')
end
if selidx > 2 then if selidx > 2 then
--print('handling for single animal on watchlist') local entry = selobj.obj
local idx = selidx - 3 plugin.autobutcher_setWatchListRace(entry.id, entry.fk, entry.mk, entry.fa, entry.ma, not entry.watched)
if plugin.autobutcher_isWatchListRaceWatched(idx) then
plugin.autobutcher_setWatchListRaceWatched(idx, false)
else
plugin.autobutcher_setWatchListRaceWatched(idx, true)
end
end end
self:initListChoices() self:initListChoices()
end end
function WatchList:onDeleteConstraint() function WatchList:onDeleteEntry()
local selidx,selobj = self.subviews.list:getSelected() local selidx,selobj = self.subviews.list:getSelected()
if(selidx < 3) then if(selidx < 3 or selobj == nil) then
-- print('cannot delete this entry')
return return
end end
local idx = selidx - 3
dlg.showYesNoPrompt( dlg.showYesNoPrompt(
'Delete from Watchlist', 'Delete from Watchlist',
'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)', 'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)',
COLOR_YELLOW, COLOR_YELLOW,
function() function()
plugin.autobutcher_removeFromWatchList(idx) plugin.autobutcher_removeFromWatchList(selobj.obj.id)
self:initListChoices() self:initListChoices()
end end
) )
end end
function WatchList:onAddRace()
print('onAddRace - not implemented yet')
end
function WatchList:onUnbutcherRace()
local selidx,selobj = self.subviews.list:getSelected()
if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end
if selidx > 2 then
local entry = selobj.obj
local race = entry.name
plugin.autobutcher_unbutcherRace(entry.id)
self:initListChoices()
self:updateBottom()
end
end
function WatchList:onButcherRace()
local selidx,selobj = self.subviews.list:getSelected()
if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end
if selidx > 2 then
local entry = selobj.obj
local race = entry.name
plugin.autobutcher_butcherRace(entry.id)
self:initListChoices()
self:updateBottom()
end
end
-- set whole row (fk, mk, fa, ma) to one value
function WatchList:onSetRow()
local selidx,selobj = self.subviews.list:getSelected()
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
local watchindex = selidx - 3
if selidx > 2 then
local entry = selobj.obj
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Set whole row for '..race,
'Enter desired maximum for all subtypes:',
COLOR_WHITE,
' ',
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( count, count, count, count )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( count, count, count, count )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, count, count, count, count, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onToggleAutobutcher() function WatchList:onToggleAutobutcher()
if(plugin.autobutcher_isEnabled()) then if(plugin.autobutcher_isEnabled()) then
plugin.autobutcher_setEnabled(false) plugin.autobutcher_setEnabled(false)
@ -491,6 +637,20 @@ function WatchList:onToggleAutowatch()
self:updateBottom() self:updateBottom()
end end
if not dfhack.isMapLoaded() then
qerror('Map is not loaded.')
end
if string.match(dfhack.gui.getCurFocus(), '^dfhack/lua') then
qerror("This script must not be called while other lua gui stuff is running.")
end
-- maybe this is too strict, there is not really a reason why it can only be called from the status screen
-- (other than the hotkey might overlap with other scripts)
if (not string.match(dfhack.gui.getCurFocus(), '^overallstatus') and not string.match(dfhack.gui.getCurFocus(), '^pet/List/Unit')) then
qerror("This script must either be called from the overall status screen or the animal list screen.")
end
local screen = WatchList{ } local screen = WatchList{ }
screen:show() screen:show()