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@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
keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack

@ -489,6 +489,24 @@ bool isHunter(df::unit* unit)
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
// (don't try to pasture/slaughter random untame animals)
bool isOwnCiv(df::unit* unit)
@ -503,6 +521,8 @@ bool isOwnRace(df::unit* unit)
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)
{
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];
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)
{
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
int raceId;
// target amounts
int fk; // max female kids
int mk; // max male kids
int fa; // max female 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
// that way there's no need for the 4 ugly process methods
vector <df::unit*> fk_ptr;
@ -2758,6 +2795,7 @@ public:
mk = _mk;
fa = _fa;
ma = _ma;
fk_prot = fa_prot = mk_prot = ma_prot = 0;
}
~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()
{
fk_prot = fa_prot = mk_prot = ma_prot = 0;
fk_ptr.clear();
mk_ptr.clear();
fa_ptr.clear();
@ -2833,7 +2890,7 @@ public:
int ProcessUnits_fk()
{
int subcount = 0;
while(fk_ptr.size() > fk)
while(fk_ptr.size() && (fk_ptr.size() + fk_prot > fk) )
{
df::unit* unit = fk_ptr.back();
doMarkForSlaughter(unit);
@ -2846,7 +2903,7 @@ public:
int ProcessUnits_mk()
{
int subcount = 0;
while(mk_ptr.size() > mk)
while(mk_ptr.size() && (mk_ptr.size() + mk_prot > mk) )
{
df::unit* unit = mk_ptr.back();
doMarkForSlaughter(unit);
@ -2859,7 +2916,7 @@ public:
int ProcessUnits_fa()
{
int subcount = 0;
while(fa_ptr.size() > fa)
while(fa_ptr.size() && (fa_ptr.size() + fa_prot > fa) )
{
df::unit* unit = fa_ptr.back();
doMarkForSlaughter(unit);
@ -2872,7 +2929,7 @@ public:
int ProcessUnits_ma()
{
int subcount = 0;
while(ma_ptr.size() > ma)
while(ma_ptr.size() && (ma_ptr.size() + ma_prot > ma) )
{
df::unit* unit = ma_ptr.back();
doMarkForSlaughter(unit);
@ -2902,8 +2959,8 @@ std::vector<WatchedRace*> watched_races;
// helper for sorting the watchlist alphabetically
bool compareRaceNames(WatchedRace* i, WatchedRace* j)
{
string name_i = getRaceName(i->raceId);
string name_j = getRaceName(j->raceId);
string name_i = getRaceNamePlural(i->raceId);
string name_j = getRaceNamePlural(j->raceId);
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++)
{
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)
|| isUndead(unit)
|| isMarkedForSlaughter(unit)
@ -3354,12 +3417,6 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
|| 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()
|| unit->name.has_name
)
continue;
@ -3368,26 +3425,40 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
if(!isContainedInItem(unit) && !hasValidMapPos(unit))
continue;
WatchedRace * w = NULL;
int watched_index = getWatchedIndex(unit->race);
if(watched_index != -1)
{
WatchedRace * w = watched_races[watched_index];
if(w->isWatched)
w->PushUnit(unit);
w = watched_races[watched_index];
}
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);
watched_races.push_back(w);
w->PushUnit(unit);
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);
//out << announce << endl;
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;
@ -3401,12 +3472,10 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
stringstream ss;
ss << slaughter_subcount;
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);
//out << announce << endl;
}
}
//out << slaughter_count << " units total marked for slaughter." << endl;
return CR_OK;
}
@ -3490,7 +3559,6 @@ command_result init_autobutcher(color_ostream &out)
//out << " mk: " << p->ival(3) << endl;
//out << " fa: " << p->ival(4) << 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));
w->rconfig = *p;
watched_races.push_back(w);
@ -3562,6 +3630,199 @@ command_result cleanup_autonestbox(color_ostream &out)
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
@ -3615,135 +3876,48 @@ static void autowatch_setEnabled(color_ostream &out, bool enable)
}
}
static size_t autobutcher_getWatchListSize()
{
return watched_races.size();
}
// 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)
// set all data for a watchlist race in one go
// if race is not already on watchlist it will be added
// 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)
{
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())
return;
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())
return;
WatchedRace * w = watched_races[idx];
w->fa = value;
w->UpdateConfig(out);
}
// set MK for a watchlist index
static void autobutcher_setWatchListRaceMK(color_ostream &out, size_t idx, size_t value)
{
if(idx >= watched_races.size())
return;
WatchedRace * w = watched_races[idx];
w->mk = value;
w->UpdateConfig(out);
}
// 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];
w->ma = value;
w->UpdateConfig(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;
int watched_index = getWatchedIndex(id);
if(watched_index != -1)
{
out << "updating watchlist entry" << endl;
WatchedRace * w = watched_races[watched_index];
w->fk = fk;
w->mk = mk;
w->fa = fa;
w->ma = ma;
w->isWatched = watched;
w->UpdateConfig(out);
}
else
{
out << "creating new watchlist entry" << endl;
WatchedRace * w = new WatchedRace(watched, id, fk, mk, fa, ma); //default_fk, default_mk, default_fa, default_ma);
w->UpdateConfig(out);
watched_races.push_back(w);
WatchedRace * w = watched_races[idx];
w->isWatched = watched;
w->UpdateConfig(out);
string announce;
announce = "New race added to autobutcher watchlist: " + getRaceNamePlural(w->raceId);
Gui::showAnnouncement(announce, 2, false);
autobutcher_sortWatchList(out);
}
}
// 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())
return;
WatchedRace * w = watched_races[idx];
w->RemoveConfig(out);
watched_races.erase(watched_races.begin()+idx);
int watched_index = getWatchedIndex(id);
if(watched_index != -1)
{
out << "updating watchlist entry" << endl;
WatchedRace * w = watched_races[watched_index];
w->RemoveConfig(out);
watched_races.erase(watched_races.begin() + watched_index);
}
}
// sort watchlist alphabetically
@ -3752,24 +3926,6 @@ static void autobutcher_sortWatchList(color_ostream &out)
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
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);
}
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;
return getRaceName(i);
color_ostream &out = *Lua::GetOutput(L);
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_LUA_FUNCTION(autobutcher_isEnabled),
@ -3816,34 +4045,19 @@ DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(autowatch_setEnabled),
DFHACK_LUA_FUNCTION(autobutcher_getSleep),
DFHACK_LUA_FUNCTION(autobutcher_setSleep),
DFHACK_LUA_FUNCTION(autobutcher_getWatchListSize),
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_setWatchListRace),
DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetNew),
DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetAll),
DFHACK_LUA_FUNCTION(autobutcher_butcherRace),
DFHACK_LUA_FUNCTION(autobutcher_unbutcherRace),
DFHACK_LUA_FUNCTION(autobutcher_removeFromWatchList),
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
};
@ -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_items;
using df::global::ui_building_in_assign;
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)
@ -3894,7 +4110,7 @@ public:
}
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;
initialized = true;
@ -3952,6 +4168,22 @@ public:
if (!show_pastured && isAssignedToZone(curr_unit))
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())
{
string desc = Translation::TranslateName(
@ -4018,16 +4250,36 @@ public:
}
}
// 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;
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;
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))
{
// Hotkey pressed, enter typing mode
@ -4076,7 +4328,7 @@ public:
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;
y += 2;
@ -4084,11 +4336,57 @@ public:
OutputString(COLOR_WHITE, x, y, ": ");
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;
++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");
}
// 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;
bool initialized;
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<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, ())
{
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_is_marked && ui_building_assign_items &&
ui_building_assign_type->size() == ui_building_assign_units->size() &&
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))
filter.initialize(ui->main.mode);

@ -1,45 +1,11 @@
-- 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 utils = require 'utils'
local widgets = require 'gui.widgets'
local dlg = require 'gui.dialogs'
local plugin = require 'plugins.zone'
local plugin = require 'plugins.zone'
WatchList = defclass(WatchList, gui.FramedScreen)
@ -54,28 +20,29 @@ WatchList.ATTRS {
local racewidth = 25
function nextAutowatchState()
if(plugin.autowatch_isEnabled()) then
return 'stop'
end
return 'start'
if(plugin.autowatch_isEnabled()) then
return 'Stop '
end
return 'Start'
end
function nextAutobutcherState()
if(plugin.autobutcher_isEnabled()) then
return 'stop'
end
return 'start'
if(plugin.autobutcher_isEnabled()) then
return 'Stop '
end
return 'Start'
end
function getSleepTimer()
return plugin.autobutcher_getSleep()
return plugin.autobutcher_getSleep()
end
function setSleepTimer(ticks)
plugin.autobutcher_setSleep(ticks)
plugin.autobutcher_setSleep(ticks)
end
function WatchList:init(args)
local colwidth = 7
self:addviews{
widgets.Panel{
frame = { l = 0, r = 0 },
@ -86,30 +53,33 @@ function WatchList:init(args)
text_pen = COLOR_CYAN,
text = {
{ text = 'Race', width = racewidth }, ' ',
{ text = 'female', width = 6 }, ' ',
{ text = ' male', width = 6 }, ' ',
{ text = 'Female', width = 6 }, ' ',
{ text = ' Male', width = 6 }, ' ',
{ text = 'watching?' },
NEWLINE,
{ text = 'female', width = colwidth }, ' ',
{ text = ' male', width = colwidth }, ' ',
{ text = 'Female', width = colwidth }, ' ',
{ text = ' Male', width = colwidth }, ' ',
{ text = 'watch? ' },
{ text = ' butchering' },
NEWLINE,
{ text = '', width = racewidth }, ' ',
{ text = ' kids', width = 6 }, ' ',
{ text = ' kids', width = 6 }, ' ',
{ text = 'adults', width = 6 }, ' ',
{ text = 'adults', width = 6 }, ' ',
{ text = ' kids', width = colwidth }, ' ',
{ text = ' kids', width = colwidth }, ' ',
{ text = 'adults', width = colwidth }, ' ',
{ text = 'adults', width = colwidth }, ' ',
{ text = ' ' },
{ text = ' ordered' },
}
},
widgets.List{
view_id = 'list',
frame = { t = 3, b = 3 },
frame = { t = 3, b = 5 },
not_found_label = 'Watchlist is empty.',
edit_pen = COLOR_LIGHTCYAN,
text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK },
cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN },
--on_select = self:callback('onSelectConstraint'),
--on_select = self:callback('onSelectEntry'),
},
widgets.Label{
view_id = 'bottom_ui',
widgets.Label{
view_id = 'bottom_ui',
frame = { b = 0, h = 1 },
text = 'filled by updateBottom()'
}
@ -117,92 +87,182 @@ function WatchList:init(args)
},
}
self:initListChoices()
self:updateBottom()
self:initListChoices()
self:updateBottom()
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)
function WatchList:updateBottom()
self.subviews.bottom_ui:setText(
{
{ key = 'CUSTOM_F', text = ': f kids',
on_activate = self:callback('onEditFK') }, ', ',
{ key = 'CUSTOM_M', text = ': m kids',
on_activate = self:callback('onEditMK') }, ', ',
{ key = 'CUSTOM_SHIFT_F', text = ': f adults',
on_activate = self:callback('onEditFA') }, ', ',
{ key = 'CUSTOM_SHIFT_M', text = ': m adults',
on_activate = self:callback('onEditMA') }, ', ',
{ key = 'CUSTOM_SHIFT_X', text = ': Delete',
on_activate = self:callback('onDeleteConstraint') }, ', ',
{ key = 'CUSTOM_W', text = ': toggle watch',
on_activate = self:callback('onToggleWatching') }, ', ', NEWLINE,
{ key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher',
on_activate = self:callback('onToggleAutobutcher') }, ', ',
{ key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch',
on_activate = self:callback('onToggleAutowatch') }, ', ',
{ key = 'CUSTOM_SHIFT_S', text = ': sleep timer ('..getSleepTimer()..' ticks)',
on_activate = self:callback('onEditSleepTimer') }, ', ',
})
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',
on_activate = self:callback('onEditFK') }, ', ',
{ key = 'CUSTOM_M', text = ': m kids',
on_activate = self:callback('onEditMK') }, ', ',
{ key = 'CUSTOM_SHIFT_F', text = ': f adults',
on_activate = self:callback('onEditFA') }, ', ',
{ key = 'CUSTOM_SHIFT_M', text = ': m adults',
on_activate = self:callback('onEditMA') }, '. ',
{ key = 'CUSTOM_W', text = ': Toggle watch',
on_activate = self:callback('onToggleWatching') }, '. ',
{ key = 'CUSTOM_X', text = ': Delete',
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',
on_activate = self:callback('onToggleAutobutcher') }, '. ',
{ key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch',
on_activate = self:callback('onToggleAutowatch') }, '. ',
{ key = 'CUSTOM_SHIFT_S', text = ': Sleep ('..getSleepTimer()..' ticks)',
on_activate = self:callback('onEditSleepTimer') }, '. ',
})
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()
local choices = {}
-- first two rows are for "edit all races" and "edit new races"
local fk = plugin.autobutcher_getDefaultFK()
local fa = plugin.autobutcher_getDefaultFA()
local mk = plugin.autobutcher_getDefaultMK()
local ma = plugin.autobutcher_getDefaultMA()
local watched = '---'
table.insert (choices, {
text = {
{ text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ',
{ text = tostring(fk), width = 7, rjustify = true },
{ text = tostring(mk), width = 7, rjustify = true },
{ text = tostring(fa), width = 7, rjustify = true },
{ text = tostring(ma), width = 7, rjustify = true },
{ text = watched, width = 6, rjustify = true }
}
})
table.insert (choices, {
text = {
{ text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ',
{ text = tostring(fk), width = 7, rjustify = true },
{ text = tostring(mk), width = 7, rjustify = true },
{ text = tostring(fa), width = 7, rjustify = true },
{ text = tostring(ma), width = 7, rjustify = true },
{ text = watched, width = 6, rjustify = true }
}
})
-- fill with watchlist
for i=0, plugin.autobutcher_getWatchListSize()-1 do
local racestr = plugin.autobutcher_getWatchListRace(i)
fk = plugin.autobutcher_getWatchListRaceFK(i)
fa = plugin.autobutcher_getWatchListRaceFA(i)
mk = plugin.autobutcher_getWatchListRaceMK(i)
ma = plugin.autobutcher_getWatchListRaceMA(i)
local watched = 'no'
if plugin.autobutcher_isWatchListRaceWatched(i) then
watched = 'yes'
end
table.insert (choices, {
text = {
local choices = {}
-- first two rows are for "edit all races" and "edit new races"
local settings = plugin.autobutcher_getSettings()
local fk = stringify(settings.fk)
local fa = stringify(settings.fa)
local mk = stringify(settings.mk)
local ma = stringify(settings.ma)
local watched = ''
local colwidth = 7
table.insert (choices, {
text = {
{ text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ 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 }
}
})
table.insert (choices, {
text = {
{ text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ 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 }
}
})
local watchlist = plugin.autobutcher_getWatchList()
for i,entry in ipairs(watchlist) do
fk = stringify(entry.fk)
fa = stringify(entry.fa)
mk = stringify(entry.mk)
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'
if entry.watched then 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, {
text = {
{ text = racestr, width = racewidth, pad_char = ' ' }, --' ',
{ text = tostring(fk), width = 7, rjustify = true },
{ text = tostring(mk), width = 7, rjustify = true },
{ text = tostring(fa), width = 7, rjustify = true },
{ text = tostring(ma), width = 7, rjustify = true },
{ text = watched, width = 6, rjustify = true }
}
})
end
{ text = fkc, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = fk, width = 3, rjustify = false, pad_char = ' ', pen = fk_pen }, ' ',
{ text = mkc, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = mk, width = 3, rjustify = false, pad_char = ' ', pen = mk_pen }, ' ',
{ 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
local list = self.subviews.list
list:setChoices(choices)
end
@ -210,285 +270,385 @@ end
function WatchList:onInput(keys)
if keys.LEAVESCREEN then
self:dismiss()
else
WatchList.super.onInput(self, keys)
else
WatchList.super.onInput(self, keys)
end
end
-- check the user input for target population values
function WatchList:checkUserInput(count, text)
if count == nil then
dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED)
return false
end
if count < 0 then
dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED)
return false
end
return true
if count == nil then
dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED)
return false
end
if count < 0 then
dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED)
return false
end
return true
end
-- check the user input for sleep timer
function WatchList:checkUserInputSleep(count, text)
if count == nil then
dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED)
return false
end
if count < 1000 then
dlg.showMessage('Invalid Number',
'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!',
COLOR_LIGHTRED)
return false
end
return true
if count == nil then
dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED)
return false
end
if count < 1000 then
dlg.showMessage('Invalid Number',
'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!',
COLOR_LIGHTRED)
return false
end
return true
end
function WatchList:onEditFK()
local selidx,selobj = self.subviews.list:getSelected()
local fk = plugin.autobutcher_getDefaultFK();
local mk = plugin.autobutcher_getDefaultMK();
local fa = plugin.autobutcher_getDefaultFA();
local ma = plugin.autobutcher_getDefaultMA();
local race = 'ALL RACES PLUS NEW'
if selidx == 2 then
race = 'ONLY NEW RACES'
end
local watchindex = selidx - 3
if selidx > 2 then
fk = plugin.autobutcher_getWatchListRaceFK(watchindex)
race = plugin.autobutcher_getWatchListRace(watchindex)
end
dlg.showInputPrompt(
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of female kids:',
COLOR_WHITE,
' '..fk,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
fk = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRaceFK(watchindex, fk)
end
self:initListChoices()
end
local count = tonumber(text)
if self:checkUserInput(count, text) then
fk = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditMK()
local selidx = self.subviews.list:getSelected()
local fk = plugin.autobutcher_getDefaultFK();
local mk = plugin.autobutcher_getDefaultMK();
local fa = plugin.autobutcher_getDefaultFA();
local ma = plugin.autobutcher_getDefaultMA();
local race = 'ALL RACES PLUS NEW'
if selidx == 2 then
race = 'ONLY NEW RACES'
end
local watchindex = selidx - 3
if selidx > 2 then
mk = plugin.autobutcher_getWatchListRaceMK(watchindex)
race = plugin.autobutcher_getWatchListRace(watchindex)
end
dlg.showInputPrompt(
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of male kids:',
COLOR_WHITE,
' '..mk,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
mk = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRaceMK(watchindex, mk)
end
self:initListChoices()
end
local count = tonumber(text)
if self:checkUserInput(count, text) then
mk = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditFA()
local selidx = self.subviews.list:getSelected()
local fk = plugin.autobutcher_getDefaultFK();
local mk = plugin.autobutcher_getDefaultMK();
local fa = plugin.autobutcher_getDefaultFA();
local ma = plugin.autobutcher_getDefaultMA();
local race = 'ALL RACES PLUS NEW'
if selidx == 2 then
race = 'ONLY NEW RACES'
end
local watchindex = selidx - 3
if selidx > 2 then
fa = plugin.autobutcher_getWatchListRaceFA(watchindex)
race = plugin.autobutcher_getWatchListRace(watchindex)
end
dlg.showInputPrompt(
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of female adults:',
COLOR_WHITE,
' '..fa,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
fa = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRaceFA(watchindex, fa)
end
self:initListChoices()
end
local count = tonumber(text)
if self:checkUserInput(count, text) then
fa = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditMA()
local selidx = self.subviews.list:getSelected()
local fk = plugin.autobutcher_getDefaultFK();
local mk = plugin.autobutcher_getDefaultMK();
local fa = plugin.autobutcher_getDefaultFA();
local ma = plugin.autobutcher_getDefaultMA();
local race = 'ALL RACES PLUS NEW'
if selidx == 2 then
race = 'ONLY NEW RACES'
end
local watchindex = selidx - 3
if selidx > 2 then
ma = plugin.autobutcher_getWatchListRaceMA(watchindex)
race = plugin.autobutcher_getWatchListRace(watchindex)
end
dlg.showInputPrompt(
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of male adults:',
COLOR_WHITE,
' '..ma,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
ma = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRaceMA(watchindex, ma)
end
self:initListChoices()
end
local count = tonumber(text)
if self:checkUserInput(count, text) then
ma = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditSleepTimer()
local sleep = getSleepTimer()
dlg.showInputPrompt(
local sleep = getSleepTimer()
dlg.showInputPrompt(
'Edit Sleep Timer',
'Enter new sleep timer in ticks:'..NEWLINE..'(1 ingame day equals 1200 ticks)',
COLOR_WHITE,
' '..sleep,
function(text)
local count = tonumber(text)
if self:checkUserInputSleep(count, text) then
sleep = count
setSleepTimer(sleep)
self:updateBottom()
end
local count = tonumber(text)
if self:checkUserInputSleep(count, text) then
sleep = count
setSleepTimer(sleep)
self:updateBottom()
end
end
)
end
function WatchList:onToggleWatching()
local selidx = 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
--print('handling for single animal on watchlist')
local idx = selidx - 3
if plugin.autobutcher_isWatchListRaceWatched(idx) then
plugin.autobutcher_setWatchListRaceWatched(idx, false)
else
plugin.autobutcher_setWatchListRaceWatched(idx, true)
end
end
self:initListChoices()
local selidx,selobj = self.subviews.list:getSelected()
if selidx > 2 then
local entry = selobj.obj
plugin.autobutcher_setWatchListRace(entry.id, entry.fk, entry.mk, entry.fa, entry.ma, not entry.watched)
end
self:initListChoices()
end
function WatchList:onDeleteConstraint()
local selidx,selobj = self.subviews.list:getSelected()
if(selidx < 3) then
-- print('cannot delete this entry')
return
end
local idx = selidx - 3
function WatchList:onDeleteEntry()
local selidx,selobj = self.subviews.list:getSelected()
if(selidx < 3 or selobj == nil) then
return
end
dlg.showYesNoPrompt(
'Delete from Watchlist',
'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)',
COLOR_YELLOW,
function()
plugin.autobutcher_removeFromWatchList(idx)
plugin.autobutcher_removeFromWatchList(selobj.obj.id)
self:initListChoices()
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()
if(plugin.autobutcher_isEnabled()) then
plugin.autobutcher_setEnabled(false)
plugin.autobutcher_sortWatchList()
else
plugin.autobutcher_setEnabled(true)
end
self:initListChoices()
self:updateBottom()
if(plugin.autobutcher_isEnabled()) then
plugin.autobutcher_setEnabled(false)
plugin.autobutcher_sortWatchList()
else
plugin.autobutcher_setEnabled(true)
end
self:initListChoices()
self:updateBottom()
end
function WatchList:onToggleAutowatch()
if(plugin.autowatch_isEnabled()) then
plugin.autowatch_setEnabled(false)
else
plugin.autowatch_setEnabled(true)
end
self:initListChoices()
self:updateBottom()
if(plugin.autowatch_isEnabled()) then
plugin.autowatch_setEnabled(false)
else
plugin.autowatch_setEnabled(true)
end
self:initListChoices()
self:updateBottom()
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