From 772c6b1cbba496cf5653a20b0719853b77dca290 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 2 Apr 2012 16:07:23 +0200 Subject: [PATCH 01/28] Added plugin 'zone'. Helps with assigning units to pens/pastures and pits. --- README.rst | 19 + plugins/CMakeLists.txt | 1 + plugins/dig.cpp | 6 + plugins/zone.cpp | 895 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 921 insertions(+) create mode 100644 plugins/zone.cpp diff --git a/README.rst b/README.rst index 95c7775da..3c751251e 100644 --- a/README.rst +++ b/README.rst @@ -798,3 +798,22 @@ Export the current loaded map as a file. This will be eventually usable with vis dwarfexport =========== Export dwarves to RuneSmith-compatible XML. + +zone +==== +Helps a bit with managing activity zones (pens, pastures and pits). + +Options: +-------- +:set: Set zone under cursor as default for future assigns. +:assign: Assign selected unit to the pen or pit marked with the 'set' command. + Can also be followed by a valid zone id which will be set instead. +:unassign: Unassign selected creature from it's zone. +:uinfo: Print info about the selected unit. +:zinfo: Print info about zone(s) under the cursor (also lists chains and cages). +:all: In combination with 'zinfo' or 'cinfo': print info about all zones/units on the map. +:verbose: Print some more info. + +Usage +----- +One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units on the map (in 'v' or 'k' mode), in the unit list or from inside cages and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way. Some more features are planned for the future. diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4fbf4b51b..6c2a537d1 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -96,6 +96,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(tweak tweak.cpp) DFHACK_PLUGIN(feature feature.cpp) DFHACK_PLUGIN(lair lair.cpp) + DFHACK_PLUGIN(zone zone.cpp) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() diff --git a/plugins/dig.cpp b/plugins/dig.cpp index e8987f6ef..91d963fc3 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -1302,6 +1302,9 @@ command_result digl (color_ostream &out, vector & parameters) des_minus.bits.dig = tile_dig_designation::UpDownStair; else des_minus.bits.dig = tile_dig_designation::UpStair; + // undo mode: clear designation + if(undo) + des_minus.bits.dig = tile_dig_designation::No; MCache->setDesignationAt(current-1,des_minus); des.bits.dig = tile_dig_designation::DownStair; @@ -1314,6 +1317,9 @@ command_result digl (color_ostream &out, vector & parameters) des_plus.bits.dig = tile_dig_designation::UpDownStair; else des_plus.bits.dig = tile_dig_designation::DownStair; + // undo mode: clear designation + if(undo) + des_plus.bits.dig = tile_dig_designation::No; MCache->setDesignationAt(current+1,des_plus); if(des.bits.dig == tile_dig_designation::DownStair) diff --git a/plugins/zone.cpp b/plugins/zone.cpp new file mode 100644 index 000000000..eb9a1e807 --- /dev/null +++ b/plugins/zone.cpp @@ -0,0 +1,895 @@ +// Intention: help with activity zone management (auto-pasture animals, auto-pit goblins, ...) +// +// the following things would be nice: +// - dump info about pastures, pastured animals, count non-pastured tame animals, print gender info +// - help finding caged dwarves? (maybe even allow to build their cages for fast release) +// - dump info about caged goblins, animals, ... +// - full automation of handling mini-pastures over nestboxes: +// go through all pens, check if they are empty and placed over a nestbox +// find female tame egg-layer who is not assigned to another pen (allowing to grab them from cages would be nice) +// maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds +// assign to that pasture +// - allow to mark old animals for slaughter automatically? +// that should include a check to ensure that at least one male and one female remain for breeding +// allow some fine-tuning like how many males/females should per race should be left alive +// and at what age they are being marked for slaughter. don't slaughter pregnant females? +// - count grass tiles on pastures, move grazers to new pasture if old pasture is empty +// move hungry unpastured grazers to pasture with grass +// +// What is working so far: +// - print detailed info about activity zone and units under cursor (mostly for checking refs and stuff) +// - mark a zone which is used for future assignment commands +// - assign single selected creature to a zone +// - unassign single creature under cursor from current zone +// - pitting own dwarves :) + +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/Units.h" +#include "modules/Maps.h" +#include "modules/Gui.h" +#include "modules/Materials.h" +#include "modules/MapCache.h" +#include "modules/Buildings.h" +#include "MiscUtils.h" + +#include +#include "df/world.h" +#include "df/world_raws.h" +#include "df/building_def.h" +#include "df/building_civzonest.h" +#include "df/building_cagest.h" +#include "df/building_chainst.h" +#include "df/general_ref_building_civzone_assignedst.h" +#include + +using std::vector; +using std::string; +using namespace DFHack; +using namespace df::enums; +using df::global::world; +using df::global::cursor; +using df::global::ui; + +using namespace DFHack::Gui; + +struct genref : df::general_ref_building_civzone_assignedst {}; + +command_result df_zone (color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("zone"); + +const string zone_help = + "Allows easier management of pens/pastures and pits.\n" + "Options:\n" + " set - set zone under cursor as default for future assigns\n" + " assign - assign selected creature to a pen or pit\n" + " (can be followed by valid zone id which will then be set)\n" + " unassign - unassign selected creature from it's zone\n" + " uinfo - print info about selected unit\n" + " zinfo - print info about zone(s) under cursor\n" + //" cinfo - print info about (built) cage under cursor\n" + //" rinfo - print info about restraint under cursor\n" + " all - in combination with 'zinfo' or 'cinfo': prints all zones/units\n" + " verbose - print some more info, mostly useless debug stuff\n" + "Example:\n" + " (ingame) move cursor to a pen/pasture or pit zone\n" + " (dfhack) 'zone set' to use this zone for future assignments\n" + " (dfhack) map 'zone assign' to a hotkey of your choice\n" + " (ingame) select a unit with 'v', 'k' or in the unit list or inside a cage\n" + " (ingame) press hotkey to assign unit to it's new home (or pit)\n" + ; + + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "zone", "manage activity zones.", + df_zone, false, + zone_help.c_str() + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + + +/////////////// +// Various small tool functions +// +int32_t getCreatureAge(df::unit* unit); +bool isTame(df::unit* unit); +bool isTrained(df::unit* unit); +bool isTrained(df::unit* creature); +bool isWar(df::unit* creature); +bool isHunter(df::unit* creature); +bool isOwnCiv(df::unit* creature); +bool isActivityZone(df::building * building); +bool isPenPasture(df::building * building); +bool isPit(df::building * building); +bool isActive(df::building * building); +int32_t findBuildingIndexById(int32_t id); +int32_t findPenPitAtCursor(); +int32_t findCageAtCursor(); +int32_t findChainAtCursor(); + +df::general_ref_building_civzone_assignedst * createCivzoneRef(); +bool unassignUnitFromZone(df::unit* unit); +command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose); +void unitInfo(color_ostream & out, df::unit* creature, bool list_refs); +void zoneInfo(color_ostream & out, df::building* building, bool list_refs); +void cageInfo(color_ostream & out, df::building* building, bool list_refs); +void chainInfo(color_ostream & out, df::building* building, bool list_refs); + + +int32_t getUnitAge(df::unit* unit) +{ + // If the birthday this year has not yet passed, subtract one year. + // ASSUMPTION: birth_time is on the same scale as cur_year_tick + int32_t yearDifference = *df::global::cur_year - unit->relations.birth_year; + if (unit->relations.birth_time >= *df::global::cur_year_tick) + yearDifference--; + return yearDifference; +} + +bool isTame(df::unit* creature) +{ + bool tame = false; + if(creature->flags1.bits.tame) + { + switch (creature->training_level) + { + case df::animal_training_level::Trained: + case df::animal_training_level::WellTrained: + case df::animal_training_level::SkilfullyTrained: + case df::animal_training_level::ExpertlyTrained: + case df::animal_training_level::ExceptionallyTrained: + case df::animal_training_level::MasterfullyTrained: + case df::animal_training_level::Domesticated: + tame=true; + break; + case df::animal_training_level::SemiWild: //?? + case df::animal_training_level::Unk8: //?? + case df::animal_training_level::WildUntamed: + default: + tame=false; + break; + } + } + return tame; +} + +// check if trained (might be useful if pasturing war dogs etc) +bool isTrained(df::unit* creature) +{ + bool trained = false; + if(creature->flags1.bits.tame) + { + switch (creature->training_level) + { + case df::animal_training_level::Trained: + case df::animal_training_level::WellTrained: + case df::animal_training_level::SkilfullyTrained: + case df::animal_training_level::ExpertlyTrained: + case df::animal_training_level::ExceptionallyTrained: + case df::animal_training_level::MasterfullyTrained: + //case df::animal_training_level::Domesticated: + trained = true; + break; + default: + break; + } + } + return trained; +} + +// check for profession "war creature" +bool isWar(df::unit* creature) +{ + if( creature->profession == df::profession::TRAINED_WAR + || creature->profession2 == df::profession::TRAINED_WAR) + return true; + else + return false; +} + +// check for profession "hunting creature" +bool isHunter(df::unit* creature) +{ + if( creature->profession == df::profession::TRAINED_HUNTER + || creature->profession2 == df::profession::TRAINED_HUNTER) + return true; + else + return false; +} + +// check if creature belongs to the player's civilization +// (don't try to pasture/slaughter random untame animals) +bool isOwnCiv(df::unit* creature) +{ + if(creature->civ_id == ui->civ_id) + return true; + else + return false; +} + +// dump some unit info +void unitInfo(color_ostream & out, df::unit* unit, bool list_refs = false) +{ + out.print("Unit %d", unit->id); //race %d, civ %d,", creature->race, creature->civ_id + if(unit->name.has_name) + out << ", name: " << unit->name.first_name << " " << unit->name.nickname; + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + out << " " << raw->creature_id << " ("; + switch(unit->sex) + { + case -1: + out << "n/a"; + break; + case 0: + out << "female"; + break; + case 1: + out << "male"; + break; + default: + out << "INVALID!"; + break; + } + out << ")"; + out << ", age: " << getUnitAge(unit); + + if(isTame(unit)) + out << ", tame"; + if(isOwnCiv(unit)) + out << ", owned"; + if(isWar(unit)) + out << ", war"; + if(isHunter(unit)) + out << ", hunter"; + + out << endl; + + if(!list_refs) + return; + + //out << "number of refs: " << creature->refs.size() << endl; + for(size_t r = 0; rrefs.size(); r++) + { + df::general_ref* ref = unit->refs.at(r); + df::general_ref_type refType = ref->getType(); + out << " ref#" << r <<" refType#" << refType << " "; //endl; + switch(refType) + { + case df::general_ref_type::BUILDING_CIVZONE_ASSIGNED: + { + out << "assigned to zone"; + df::building_civzonest * civAss = (df::building_civzonest *) ref->getBuilding(); + out << " #" << civAss->id; + } + break; + case df::general_ref_type::CONTAINED_IN_ITEM: + out << "contained in item"; + break; + case df::general_ref_type::BUILDING_CAGED: + out << "caged"; + break; + case df::general_ref_type::BUILDING_CHAIN: + out << "chained"; + break; + default: + //out << "unhandled reftype"; + break; + } + out << endl; + } +} + +bool isActivityZone(df::building * building) +{ + if( building->getType() == building_type::Civzone + && building->getSubtype() == civzone_type::ActivityZone) + return true; + else + return false; +} + +bool isPenPasture(df::building * building) +{ + if(!isActivityZone(building)) + return false; + + df::building_civzonest * civ = (df::building_civzonest *) building; + + if(civ->zone_flags.bits.pen_pasture) + return true; + else + return false; +} + +bool isPit(df::building * building) +{ + if(!isActivityZone(building)) + return false; + + df::building_civzonest * civ = (df::building_civzonest *) building; + + if(civ->zone_flags.bits.pit_pond && civ->pit_flags==0) + return true; + else + return false; +} + +bool isCage(df::building * building) +{ + if(building->getType() == building_type::Cage) + return true; + else + return false; +} + +bool isChain(df::building * building) +{ + if(building->getType() == building_type::Chain) + return true; + else + return false; +} + +bool isActive(df::building * building) +{ + if(!isActivityZone(building)) + return false; + + df::building_civzonest * civ = (df::building_civzonest *) building; + if(civ->zone_flags.bits.active) + return true; + else + return false; +} + +int32_t findBuildingIndexById(int32_t id) +{ + for (size_t b = 0; b < world->buildings.all.size(); b++) + { + if(world->buildings.all.at(b)->id == id) + return b; + } + return -1; +} + +// returns id of pen/pit at cursor position (-1 if nothing found) +int32_t findPenPitAtCursor() +{ + int32_t foundID = -1; + + if(cursor->x == -30000) + return -1; + + for (size_t b = 0; b < world->buildings.all.size(); b++) + { + df::building* building = world->buildings.all[b]; + + // find zone under cursor + if (!(building->x1 <= cursor->x && cursor->x <= building->x2 && + building->y1 <= cursor->y && cursor->y <= building->y2 && + building->z == cursor->z)) + continue; + + if(isPenPasture(building) || isPit(building)) + { + foundID = building->id; + break; + } + } + return foundID; +} + +// returns id of cage at cursor position (-1 if nothing found) +int32_t findCageAtCursor() +{ + int32_t foundID = -1; + + if(cursor->x == -30000) + return -1; + + for (size_t b = 0; b < world->buildings.all.size(); b++) + { + df::building* building = world->buildings.all[b]; + + if (!(building->x1 <= cursor->x && cursor->x <= building->x2 && + building->y1 <= cursor->y && cursor->y <= building->y2 && + building->z == cursor->z)) + continue; + + if(isCage(building)) + { + foundID = building->id; + break; + } + } + return foundID; +} + +int32_t findChainAtCursor() +{ + int32_t foundID = -1; + + if(cursor->x == -30000) + return -1; + + for (size_t b = 0; b < world->buildings.all.size(); b++) + { + df::building* building = world->buildings.all[b]; + + // find zone under cursor + if (!(building->x1 <= cursor->x && cursor->x <= building->x2 && + building->y1 <= cursor->y && cursor->y <= building->y2 && + building->z == cursor->z)) + continue; + + if(isChain(building)) + { + foundID = building->id; + break; + } + } + return foundID; +} + +df::general_ref_building_civzone_assignedst * createCivzoneRef() +{ + static bool vt_initialized = false; + df::general_ref_building_civzone_assignedst* newref = NULL; + + // after having run successfully for the first time it's safe to simply create the object + if(vt_initialized) + { + newref = (df::general_ref_building_civzone_assignedst*) + df::general_ref_building_civzone_assignedst::_identity.instantiate(); + return newref; + } + + // being called for the first time, need to initialize the vtable + for(size_t i = 0; i < world->units.all.size(); i++) + { + df::unit * creature = world->units.all[i]; + for(size_t r = 0; rrefs.size(); r++) + { + df::general_ref* ref; + ref = creature->refs.at(r); + if(ref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) + { + if (strict_virtual_cast(ref)) + { + // !! calling new() doesn't work, need _identity.instantiate() instead !! + newref = (df::general_ref_building_civzone_assignedst*) + df::general_ref_building_civzone_assignedst::_identity.instantiate(); + vt_initialized = true; + break; + } + } + } + if(vt_initialized) + break; + } + return newref; +} + +// check if unit is already assigned to a zone, remove that ref from unit and old zone +// returns false if no pasture information was found +// helps as workaround for http://www.bay12games.com/dwarves/mantisbt/view.php?id=4475 by the way +// (pastured animals assigned to chains will get hauled back and forth because the pasture ref is not deleted) +bool unassignUnitFromZone(df::unit* unit) +{ + bool success = false; + for (size_t or = 0; or < unit->refs.size(); or++) + { + df::general_ref * oldref = unit->refs[or]; + if(oldref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) + { + unit->refs.erase(unit->refs.begin() + or); + df::building_civzonest * oldciv = (df::building_civzonest *) oldref->getBuilding(); + for(size_t oc=0; ocassigned_creature.size(); oc++) + { + if(oldciv->assigned_creature[oc] == unit->id) + { + oldciv->assigned_creature.erase(oldciv->assigned_creature.begin() + oc); + break; + } + } + delete oldref; + success = true; + break; + } + } + return success; +} + +// assign to pen or pit +command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) +{ + //if(!isOwnCiv(unit) || !isTame(unit)) + //{ + // out << "Creature must be tame and your own." << endl; + // return CR_WRONG_USAGE; + //} + + // building must be a pen/pasture or pit + //df::building * building = world->buildings.all.at(index); + if(!isPenPasture(building) && !isPit(building)) + { + out << "Invalid building type. This is not a pen/pasture or pit." << endl; + //target_zone = -1; + return CR_WRONG_USAGE; + } + + // try to get a fresh civzone ref + df::general_ref_building_civzone_assignedst * ref = createCivzoneRef(); + if(!ref) + { + out << "Could not find a clonable activity zone reference" << endl + << "You need to pen/pasture/pit at least one creature" << endl + << "before using 'assign' for the first time." << endl; + return CR_WRONG_USAGE; + } + + // check if unit is already pastured, remove that ref from unit and old pasture + if(verbose) + { + if(unassignUnitFromZone(unit)) + out << "old zone info cleared."; + else + out << "no old zone info found."; + } + + ref->building_id = building->id; + unit->refs.push_back(ref); + + df::building_civzonest * civz = (df::building_civzonest *) building; + civz->assigned_creature.push_back(unit->id); + + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + out << "Unit " << unit->id + << "(" << raw->creature_id << ")" + << " assigned to zone " << building->id; + if(isPit(building)) + out << " (pit)."; + if(isPenPasture(building)) + out << " (pen/pasture)."; + out << endl; + + return CR_OK; +} + + +// dump some zone info +void zoneInfo(color_ostream & out, df::building* building, bool list_refs = false) +{ + if(building->getType()!= building_type::Civzone) + return; + + if(building->getSubtype() != civzone_type::ActivityZone) + return; + + string name; + building->getName(&name); + out.print("Building %i - \"%s\" - type %s (%i)", + building->id, + name.c_str(), + ENUM_KEY_STR(building_type, building->getType()).c_str(), + building->getType()); + out.print(", subtype %s (%i)", + ENUM_KEY_STR(civzone_type, (df::civzone_type)building->getSubtype()).c_str(), + building->getSubtype()); + out.print("\n"); + + df::building_civzonest * civ = (df::building_civzonest *) building; + if(civ->zone_flags.bits.active) + out << "active"; + else + out << "not active"; + + // look at zone flags, ignore fishing, water, hospital, ... + // in fact, only deal with pits and pens + if(civ->zone_flags.bits.pen_pasture) + out << ", pen/pasture"; + else if (civ->zone_flags.bits.pit_pond && civ->pit_flags==0) + out << ", pit"; + else + return; + out << endl; + + int32_t creaturecount = civ->assigned_creature.size(); + out << "Creatures in this zone: " << creaturecount << endl; + for(size_t c = 0; c < creaturecount; c++) + { + int32_t cindex = civ->assigned_creature.at(c); + + // print list of all units assigned to that zone + for(size_t i = 0; i < world->units.all.size(); i++) + { + df::unit * creature = world->units.all[i]; + if(creature->id != cindex) + continue; + + unitInfo(out, creature, false); + } + } +} + +// dump some cage info +void cageInfo(color_ostream & out, df::building* building, bool list_refs = false) +{ + if(!isCage(building)) + return; + + string name; + building->getName(&name); + out.print("Building %i - \"%s\" - type %s (%i)", + building->id, + name.c_str(), + ENUM_KEY_STR(building_type, building->getType()).c_str(), + building->getType()); + out.print("\n"); + + df::building_cagest * cage = (df::building_cagest*) building; + int32_t creaturecount = cage->assigned_creature.size(); + out << "Creatures in this cage: " << creaturecount << endl; + for(size_t c = 0; c < creaturecount; c++) + { + int32_t cindex = cage->assigned_creature.at(c); + + // print list of all units assigned to that cage + for(size_t i = 0; i < world->units.all.size(); i++) + { + df::unit * creature = world->units.all[i]; + if(creature->id != cindex) + continue; + + unitInfo(out, creature, false); + } + } +} + + +// dump some chain/restraint info +void chainInfo(color_ostream & out, df::building* building, bool list_refs = false) +{ + if(!isChain(building)) + return; + + string name; + building->getName(&name); + out.print("Building %i - \"%s\" - type %s (%i)", + building->id, + name.c_str(), + ENUM_KEY_STR(building_type, building->getType()).c_str(), + building->getType()); + out.print("\n"); + + df::building_chainst* chain = (df::building_chainst*) building; + if(chain->assigned) + { + out << "assigned: "; + unitInfo(out, chain->assigned, true); + } + if(chain->chained) + { + out << "chained: "; + unitInfo(out, chain->chained, true); + } +} + +command_result df_zone (color_ostream &out, vector & parameters) +{ + CoreSuspender suspend; + + bool need_cursor = false; // for zone_info, zone_assign, ... + bool unit_info = false; + bool zone_info = false; + //bool cage_info = false; + //bool chain_info = false; + + bool zone_assign = false; + bool zone_unassign = false; + bool zone_set = false; + bool verbose = false; + bool all = false; + static int target_zone = -1; + + for (size_t i = 0; i < parameters.size(); i++) + { + string & p = parameters[i]; + + if (p == "help" || p == "?") + { + out << zone_help << endl; + return CR_OK; + } + else if(p == "zinfo") + { + zone_info = true; + } + else if(p == "uinfo") + { + unit_info = true; + } + //else if(p == "cinfo") + //{ + // cage_info = true; + //} + //else if(p == "rinfo") + //{ + // chain_info = true; + //} + else if(p == "verbose") + { + verbose = true; + } + else if(p == "unassign") + { + zone_unassign = true; + } + else if(p == "assign") + { + if(i == parameters.size()-1) + { + if(target_zone == -1) + { + out.printerr("No zone id specified and current one is invalid!"); + return CR_WRONG_USAGE; + } + else + { + out << "No zone id specified. Will try to use #" << target_zone << endl; + zone_assign = true; + } + } + else + { + stringstream ss(parameters[i+1]); + i++; + ss >> target_zone; + out << "Assign selected unit to zone #" << target_zone <x == -30000) + { + out.printerr("No cursor; place cursor over activity zone.\n"); + return CR_FAILURE; + } + + // give info on unit(s) + if(unit_info) + { + if(all) + { + // todo: this should really be filtered somehow (prints even dead units etc) + for(size_t c=0; cunits.all.size(); c++) + { + df::unit *unit = world->units.all[c]; + unitInfo(out, unit, verbose); + } + } + else + { + df::unit *unit = getSelectedUnit(out); + if (!unit) + return CR_FAILURE; + unitInfo(out, unit, verbose); + } + return CR_OK; + } + + // give info on zone(s), chain or cage under cursor + // (doesn't use the findXyzAtCursor() methods because zones might overlap and contain a cage or chain) + if(zone_info) // || chain_info || cage_info) + { + for (size_t b = 0; b < world->buildings.all.size(); b++) + { + df::building * building = world->buildings.all[b]; + + // find building under cursor + if (!all && + !(building->x1 <= cursor->x && cursor->x <= building->x2 && + building->y1 <= cursor->y && cursor->y <= building->y2 && + building->z == cursor->z)) + continue; + + zoneInfo(out, building, verbose); + chainInfo(out, building, verbose); + cageInfo(out, building, verbose); + } + return CR_OK; + } + + // set building at cursor position to be new target zone + if(zone_set) + { + target_zone = findPenPitAtCursor(); + if(target_zone==-1) + { + out << "No pen/pasture or pit under cursor!" << endl; + return CR_WRONG_USAGE; + } + out << "Current zone set to #" << target_zone << endl; + return CR_OK; + } + + // de-assign from pen or pit + if(zone_unassign) + { + // must have unit selected + df::unit *unit = getSelectedUnit(out); + if (!unit) + { + out << "No unit selected." << endl; + return CR_WRONG_USAGE; + } + + // remove assignment reference from unit and old zone + if(unassignUnitFromZone(unit)) + out << "Unit unassigned." << endl; + else + out << "Unit is not assigned to a zone!" << endl; + return CR_OK; + } + + // assign to pen or pit + if(zone_assign) + { + // must have unit selected + df::unit *unit = getSelectedUnit(out); + if (!unit) + { + out << "No unit selected." << endl; + return CR_WRONG_USAGE; + } + + // try to get building index from the id + int32_t index = findBuildingIndexById(target_zone); + if(index == -1) + { + out << "Invalid building id." << endl; + target_zone = -1; + return CR_WRONG_USAGE; + } + + df::building * building = world->buildings.all.at(index); + return assignUnitToZone(out, unit, building, verbose); + } + + return CR_OK; +} From 0f7e380e1b1524e19ab7efd3af488fa15e664b52 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 2 Apr 2012 16:08:30 +0200 Subject: [PATCH 02/28] minor: readme.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 3c751251e..1ee00cc6b 100644 --- a/README.rst +++ b/README.rst @@ -816,4 +816,4 @@ Options: Usage ----- -One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units on the map (in 'v' or 'k' mode), in the unit list or from inside cages and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way. Some more features are planned for the future. +One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units on the map (in 'v' or 'k' mode), in the unit list or from inside cages and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way. From 9cc2040a6bcd001bb028f74b25a146fa40ed914d Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 2 Apr 2012 16:58:12 +0200 Subject: [PATCH 03/28] minor fix in zone tool: ignore dead units --- plugins/zone.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index eb9a1e807..03c38b101 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -111,7 +111,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) /////////////// // Various small tool functions -// +// probably many of these should be moved to Unit.h and Building.h sometime later... int32_t getCreatureAge(df::unit* unit); bool isTame(df::unit* unit); bool isTrained(df::unit* unit); @@ -147,6 +147,14 @@ int32_t getUnitAge(df::unit* unit) return yearDifference; } +bool isDead(df::unit* unit) +{ + if(unit->flags1.bits.dead) + return true; + else + return false; +} + bool isTame(df::unit* creature) { bool tame = false; @@ -231,6 +239,9 @@ bool isOwnCiv(df::unit* creature) // dump some unit info void unitInfo(color_ostream & out, df::unit* unit, bool list_refs = false) { + if(isDead(unit)) + return; + out.print("Unit %d", unit->id); //race %d, civ %d,", creature->race, creature->civ_id if(unit->name.has_name) out << ", name: " << unit->name.first_name << " " << unit->name.nickname; From a8fe0eccb4ab03231da0692f06b9e4bbb27dc5c2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 2 Apr 2012 19:10:57 +0400 Subject: [PATCH 04/28] Add functions for checking validity of lua wrapper objects. --- library/LuaWrapper.cpp | 106 +++++++++++++++++++++++++++++++++++++ library/include/LuaTools.h | 23 ++++++++ 2 files changed, 129 insertions(+) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 1148d3fcd..818e6b9e1 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -36,6 +36,7 @@ distribution. #include "DataDefs.h" #include "DataIdentity.h" #include "LuaWrapper.h" +#include "LuaTools.h" #include "MiscUtils.h" @@ -402,6 +403,46 @@ static bool is_valid_metatable(lua_State *state, int objidx, int metaidx) return ok; } +bool Lua::IsDFNull(lua_State *state, int val_index) +{ + if (lua_isnil(state, val_index)) + return true; + if (lua_islightuserdata(state, val_index)) + return lua_touserdata(state, val_index) == NULL; + return false; +} + +Lua::ObjectClass Lua::IsDFObject(lua_State *state, int val_index) +{ + if (lua_isnil(state, val_index)) + return Lua::OBJ_NULL; + if (lua_islightuserdata(state, val_index)) + return lua_touserdata(state, val_index) ? Lua::OBJ_VOIDPTR : OBJ_NULL; + + Lua::ObjectClass cls; + + if (lua_istable(state, val_index)) + { + cls = Lua::OBJ_TYPE; + lua_pushvalue(state, val_index); + LookupInTable(state, &DFHACK_TYPEID_TABLE_TOKEN); + } + else if (lua_isuserdata(state, val_index)) + { + if (!lua_getmetatable(state, val_index)) + return Lua::OBJ_INVALID; + cls = Lua::OBJ_REF; + LookupInTable(state, &DFHACK_TYPETABLE_TOKEN); + } + else + return Lua::OBJ_INVALID; + + bool ok = !lua_isnil(state, -1); + lua_pop(state, 1); + + return ok ? cls : Lua::OBJ_INVALID; +} + /** * Given a DF object reference or type, safely retrieve its identity pointer. */ @@ -865,6 +906,52 @@ static int meta_enum_attr_index(lua_State *state) return 1; } +/** + * Metamethod: df.isvalid(obj[,allow_null]) + */ +static int meta_isvalid(lua_State *state) +{ + luaL_checkany(state, 1); + + switch (Lua::IsDFObject(state, 1)) + { + case Lua::OBJ_NULL: + lua_settop(state, 2); + if (lua_toboolean(state, 2)) + lua_pushvalue(state, lua_upvalueindex(1)); + else + lua_pushnil(state); + return 1; + + case Lua::OBJ_TYPE: + lua_pushvalue(state, lua_upvalueindex(2)); + return 1; + + case Lua::OBJ_VOIDPTR: + lua_pushvalue(state, lua_upvalueindex(3)); + return 1; + + case Lua::OBJ_REF: + lua_pushvalue(state, lua_upvalueindex(4)); + return 1; + + case Lua::OBJ_INVALID: + default: + lua_pushnil(state); + return 1; + } +} + +/** + * Metamethod: df.isnull(obj) + */ +static int meta_isnull(lua_State *state) +{ + luaL_checkany(state, 1); + lua_pushboolean(state, Lua::IsDFNull(state, 1)); + return 1; +} + static int meta_nodata(lua_State *state) { return 0; @@ -1221,9 +1308,15 @@ static void RenderType(lua_State *state, compound_identity *node) break; case IDTYPE_GLOBAL: + lua_pushstring(state, "global"); + lua_setfield(state, ftable, "_kind"); + { RenderTypeChildren(state, node->getScopeChildren()); + lua_pushlightuserdata(state, node); + lua_setfield(state, ftable, "_identity"); + BuildTypeMetatable(state, node); lua_dup(state); @@ -1244,6 +1337,9 @@ static void RenderType(lua_State *state, compound_identity *node) RenderTypeChildren(state, node->getScopeChildren()); + lua_pushlightuserdata(state, node); + lua_setfield(state, ftable, "_identity"); + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); lua_setfield(state, ftable, "sizeof"); @@ -1341,6 +1437,16 @@ static int DoAttach(lua_State *state) lua_pushlightuserdata(state, NULL); lua_setglobal(state, "NULL"); + lua_pushstring(state, "null"); + lua_pushstring(state, "type"); + lua_pushstring(state, "voidptr"); + lua_pushstring(state, "ref"); + lua_pushcclosure(state, meta_isvalid, 4); + lua_setfield(state, -2, "isvalid"); + + lua_pushcfunction(state, meta_isnull); + lua_setfield(state, -2, "isnull"); + freeze_table(state, false, "df"); } diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 32e352010..6f95e7f6e 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -46,6 +46,29 @@ namespace DFHack { namespace Lua { DFHACK_EXPORT bool Require(color_ostream &out, lua_State *state, const std::string &module, bool setglobal = false); + /** + * Check if the object at the given index is NIL or NULL. + */ + DFHACK_EXPORT bool IsDFNull(lua_State *state, int val_index); + + enum ObjectClass { + /** Not a DF wrapper object */ + OBJ_INVALID = 0, + /** NIL or NULL */ + OBJ_NULL, + /** A named type identity object */ + OBJ_TYPE, + /** A void* reference, i.e. non-null lightuserdata */ + OBJ_VOIDPTR, + /** A typed object reference */ + OBJ_REF + }; + + /** + * Check if the object at the given index is a valid wrapper object. + */ + DFHACK_EXPORT ObjectClass IsDFObject(lua_State *state, int val_index); + /** * Push the pointer onto the stack as a wrapped DF object of the given type. */ From 59d1971df163ffeb97873b9290e3fc6864d5bf50 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 2 Apr 2012 22:00:38 +0400 Subject: [PATCH 05/28] Add docs for the lua wrapper. --- LUA_API.rst | 299 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 LUA_API.rst diff --git a/LUA_API.rst b/LUA_API.rst new file mode 100644 index 000000000..12b25bac8 --- /dev/null +++ b/LUA_API.rst @@ -0,0 +1,299 @@ +############## +DFHack Lua API +############## + +.. contents:: + +==================== +DF structure wrapper +==================== + +DF structures described by the xml files in library/xml are exported +to lua code as a tree of objects and functions under the ``df`` global, +which broadly maps to the ``df`` namespace in C++. + +**WARNING**: The wrapper provides almost raw access to the memory +of the game, so mistakes in manipulating objects are as likely to +crash the game as equivalent plain C++ code would be. E.g. NULL +pointer access is safely detected, but dangling pointers aren't. + +Objects managed by the wrapper can be broadly classified into the following groups: + +1. Typed object pointers (references). + + References represent objects in DF memory with a known type. + + In addition to fields and methods defined by the wrapped type, + every reference has some built-in properties and methods. + +2. Untyped pointers + + Represented as lightuserdata. + + In assignment to a pointer NULL can be represented either as + ``nil``, or a NULL lightuserdata; reading a NULL pointer field + returns ``nil``. + +3. Named types + + Objects in the ``df`` tree that represent identity of struct, class, + enum and bitfield types. They host nested named types, static + methods, builtin properties & methods, and, for enums and bitfields, + the bi-directional mapping between key names and values. + +4. The ``global`` object + + ``df.global`` corresponds to the ``df::global`` namespace, and + behaves as a mix between a named type and a reference, containing + both nested types and fields corresponding to global symbols. + +In addition to the ``global`` object and top-level types the ``df`` +global also contains a few global builtin utility functions. + +Typed object references +======================= + +The underlying primitive lua object is userdata with a metatable. +Every structured field access produces a new userdata instance. + +All typed objects have the following built-in features: + +* ``ref1 == ref2``, ``tostring(ref)`` + + References implement equality by type & pointer value, and string conversion. + +* ``pairs(ref)`` + + Returns an iterator for the sequence of actual C++ field names + and values. Fields are enumerated in memory order. Methods and + lua wrapper properties are not included in the iteration. + +* ``ref._kind`` + + Returns one of: ``primitive``, ``struct``, ``container``, + or ``bitfield``, as appropriate for the referenced object. + +* ``ref._type`` + + Returns the named type object or a string that represents + the referenced object type. + +* ``ref:sizeof()`` + + Returns *size, address* + +* ``ref:new()`` + + Allocates a new instance of the same type, and copies data + from the current object. + +* ``ref:delete()`` + + Destroys the object with the C++ ``delete`` operator. + If destructor is not available, returns *false*. + + **WARNING**: the lua reference object remains as a dangling + pointer, like a raw C++ pointer would. + +* ``ref:assign(object)`` + + Assigns data from object to ref. Object must either be another + ref of a compatible type, or a lua table; in the latter case + special recursive assignment rules are applied. + +* ``ref:_displace(index[,step])`` + + Returns a new reference with the pointer adjusted by index*step. + Step defaults to the natural object size. + +Primitive references +-------------------- + +References of the *_kind* ``'primitive'`` are used for objects +that don't fit any of the other reference types. Such +references can only appear as a value of a pointer field, +or as a result of calling the ``_field()`` method. + +They behave as structs with one field ``value`` of the right type. + +Struct references +----------------- + +Struct references are used for class and struct objects. + +They implement the following features: + +* ``ref.field``, ``ref.field = value`` + + Valid fields of the structure may be accessed by subscript. + + In case of inheritance, *superclass* fields have precedence + over the subclass, but fields shadowed in this way can still + be accessed as ``ref['subclasstype.field']``. + + This shadowing order is necessary because vtable-based classes + are automatically exposed in their exact type, and the reverse + rule would make access to superclass fields unreliable. + +* ``ref._field(field)`` + + Returns a reference to a valid field. That is, unlike regular + subscript, it returns a pointer reference even for primitive + typed fields. + +* ``ref:vmethod(args...)`` + + Named virtual methods are also exposed, subject to the same + shadowing rules. + +* ``pairs(ref)`` + + Enumerates all real fields (but not methods) in memory + (= declaration) order. + +Container references +-------------------- + +Containers represent vectors and arrays, possibly resizable. + +A container field can associate an enum to the container +reference, which allows accessing elements using string keys +instead of numerical indices. + +Implemented features: + +* ``ref._enum`` + + If the container has an associated enum, returns the matching + named type object. + +* ``#ref`` + + Returns the *length* of the container. + +* ``ref[index]`` + + Accesses the container element, using either a *0-based* numerical + index, or, if an enum is associated, a valid enum key string. + + Accessing an invalid index is an error, but some container types + may return a default value, or auto-resize instead for convenience. + Currently this relaxed mode is implemented by df-flagarray aka BitArray. + +* ``ref._field(index)`` + + Like with structs, returns a pointer to the array element, if possible. + Flag and bit arrays cannot return such pointer, so it fails with an error. + +* ``pairs(ref)``, ``ipairs(ref)`` + + If the container has no associated enum, both behave identically, + iterating over numerical indices in order. Otherwise, ipairs still + uses numbers, while pairs tries to substitute enum keys whenever + possible. + +* ``ref:resize(new_size)`` + + Resizes the container if supported, or fails with an error. + +* ``ref:insert(index,item)`` + + Inserts a new item at the specified index. To add at the end, + use ``#ref`` as index. + +* ``ref:erase(index)`` + + Removes the element at the given valid index. + +Bitfield references +------------------- + +Bitfields behave like special fixed-size containers. +The ``_enum`` property points to the bitfield type. + +Numerical indices correspond to the shift value, +and if a subfield occupies multiple bits, the +``ipairs`` order would have a gap. + +Named types +=========== + +Named types are exposed in the ``df`` tree with names identical +to the C++ version, except for the ``::`` vs ``.`` difference. + +All types and the global object have the following features: + +* ``type._kind`` + + Evaluates to one of ``struct-type``, ``class-type``, ``enum-type``, + ``bitfield-type`` or ``global``. + +* ``type._identity`` + + Contains a lightuserdata pointing to the underlying + DFHack::type_instance object. + +Types excluding the global object also support: + +* ``type:sizeof()`` + + Returns the size of an object of the type. + +* ``type:new()`` + + Creates a new instance of an object of the type. + +* ``type:is_instance(object)`` + + Returns true if object is same or subclass type, or a reference + to an object of same or subclass type. It is permissible to pass + nil, NULL or non-wrapper value as object; in this case the + method returns nil. + +In addition to this, enum and bitfield types contain a +bi-directional mapping between key strings and values, and +also map ``_first_item`` and ``_last_item`` to the min and +max values. + +Struct and class types with instance-vector attribute in the +xml have a ``type.find(key)`` function that wraps the find +method provided in C++. + +Global functions +================ + +The ``df`` table itself contains the following functions and values: + +* ``NULL``, ``df.NULL`` + + Contains the NULL lightuserdata. + +* ``df.isnull(obj)`` + + Evaluates to true if obj is nil or NULL; false otherwise. + +* ``df.isvalid(obj[,allow_null])`` + + For supported objects returns one of ``type``, ``voidptr``, ``ref``. + + If *allow_null* is true, and obj is nil or NULL, returns ``null``. + + Otherwise returns *nil*. + +* ``df.sizeof(obj)`` + + For types and refs identical to ``obj:sizeof()``. + For lightuserdata returns *nil, address* + +* ``df.new(obj)``, ``df.delete(obj)``, ``df.assign(obj, obj2)`` + + Equivalent to using the matching methods of obj. + +* ``df._displace(obj,index[,step])`` + + For refs equivalent to the method, but also works with + lightuserdata (step is mandatory then). + +* ``df.is_instance(type,obj)`` + + Equivalent to the method, but also allows a reference as proxy for its type. From 444377f9db0e5443433468cc13904bed5b343d40 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 3 Apr 2012 13:13:44 +0400 Subject: [PATCH 06/28] Finish documenting the DFHack core lua api existing so far. --- LUA_API.rst | 181 ++++++++++++++++++++++++++++++++++++++++++- library/LuaTools.cpp | 25 +++++- 2 files changed, 200 insertions(+), 6 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 12b25bac8..fd05e5448 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -127,10 +127,15 @@ They implement the following features: Valid fields of the structure may be accessed by subscript. - In case of inheritance, *superclass* fields have precedence + Primitive typed fields, i.e. numbers & strings, are converted + to/from matching lua values. The value of a pointer is a reference + to the target, or nil/NULL. Complex types are represented by + a reference to the field within the structure; unless recursive + lua table assignment is used, such fields can only be read. + + **NOTE:** In case of inheritance, *superclass* fields have precedence over the subclass, but fields shadowed in this way can still be accessed as ``ref['subclasstype.field']``. - This shadowing order is necessary because vtable-based classes are automatically exposed in their exact type, and the reverse rule would make access to superclass fields unreliable. @@ -138,8 +143,8 @@ They implement the following features: * ``ref._field(field)`` Returns a reference to a valid field. That is, unlike regular - subscript, it returns a pointer reference even for primitive - typed fields. + subscript, it returns a reference to the field within the structure + even for primitive typed fields and pointers. * ``ref:vmethod(args...)`` @@ -297,3 +302,171 @@ The ``df`` table itself contains the following functions and values: * ``df.is_instance(type,obj)`` Equivalent to the method, but also allows a reference as proxy for its type. + +Recursive table assignment +========================== + +Recursive assignment is invoked when a lua table is assigned +to a C++ object or field, i.e. one of: + +* ``ref:assign{...}`` +* ``ref.field = {...}`` + +The general mode of operation is that all fields of the table +are assigned to the fields of the target structure, roughly +emulating the following code:: + + function rec_assign(ref,table) + for key,value in pairs(table) do + ref[key] = value + end + end + +Since assigning a table to a field using = invokes the same +process, it is recursive. + +There are however some variations to this process depending +on the type of the field being assigned to: + +1. If the table contains an ``assign`` field, it is + applied first, using the ``ref:assign(value)`` method. + It is never assigned as a usual field. + +2. When a table is assigned to a non-NULL pointer field + using the ``ref.field = {...}`` syntax, it is applied + to the target of the pointer instead. + + If the pointer is NULL, the table is checked for a ``new`` field: + + a. If it is *nil* or *false*, assignment fails with an error. + + b. If it is *true*, the pointer is initialized with a newly + allocated object of the declared target type of the pointer. + + c. Otherwise, ``table.new`` must be a named type, or an + object of a type compatible with the pointer. The pointer + is initialized with the result of calling ``table.new:new()``. + + After this auto-vivification process, assignment proceeds + as if the pointer wasn't NULL. + + Obviously, the ``new`` field inside the table is always skipped + during the actual per-field assignment processing. + +3. If the target of the assignment is a container, a separate + rule set is used: + + a. If the table contains neither ``assign`` nor ``resize`` + fields, it is interpreted as an ordinary *1-based* lua + array. The container is resized to the #-size of the + table, and elements are assigned in numeric order:: + + ref:resize(#table); + for i=1,#table do ref[i-1] = table[i] end + + b. Otherwise, ``resize`` must be *true*, *false*, or + an explicit number. If it is not false, the container + is resized. After that the usual struct-like 'pairs' + assignment is performed. + + In case ``resize`` is *true*, the size is computed + by scanning the table for the largest numeric key. + + This means that in order to reassign only one element of + a container using this system, it is necessary to use:: + + { resize=false, [idx]=value } + +Since nil inside a table is indistinguishable from missing key, +it is necessary to use ``df.NULL`` as a null pointer value. + +This system is intended as a way to define a nested object +tree using pure lua data structures, and then materialize it in +C++ memory in one go. Note that if pointer auto-vivification +is used, an error in the middle of the recursive walk would +not destroy any objects allocated in this way, so the user +should be prepared to catch the error and do the necessary +cleanup. + +================ +DFHack utilities +================ + +DFHack utility functions are placed in the ``dfhack`` global tree. + +Currently it defines the following features: + +* ``dfhack.print(args...)`` + + Output tab-separated args as standard lua print would do, + but without a newline. + +* ``print(args...)``, ``dfhack.println(args...)`` + + A replacement of the standard library print function that + works with DFHack output infrastructure. + +* ``dfhack.printerr(args...)`` + + Same as println; intended for errors. Uses red color and logs to stderr.log. + +* ``dfhack.interpreter([prompt[,env[,history_filename]]])`` + + Starts an interactive lua interpreter, using the specified prompt + string, global environment and command-line history file. + + If the interactive console is not accessible, returns *nil, error*. + +* ``dfhack.with_suspend(f[,args...])`` + + Calls ``f`` with arguments after grabbing the DF core suspend lock. + Suspending is necessary for accessing a consistent state of DF memory. + + Returned values and errors are propagated through after releasing + the lock. It is safe to nest suspends. + + Every thread is allowed only one suspend per DF frame, so it is best + to group operations together in one big critical section. A plugin + can choose to run all lua code inside a C++-side suspend lock. + +Persistent configuration storage +================================ + +This api is intended for storing configuration options in the world itself. +It probably should be restricted to data that is world-dependent. + +Entries are identified by a string ``key``, but it is also possible to manage +multiple entries with the same key; their identity is determined by ``entry_id``. +Every entry has a mutable string ``value``, and an array of 7 mutable ``ints``. + +* ``dfhack.persistent.get(key)``, ``entry:get()`` + + Retrieves a persistent config record with the given string key, + or refreshes an already retrieved entry. If there are multiple + entries with the same key, it is undefined which one is retrieved + by the first version of the call. + + Returns entry, or *nil* if not found. + +* ``dfhack.persistent.delete(key)``, ``entry:delete()`` + + Removes an existing entry. Returns *true* if succeeded. + +* ``dfhack.persistent.get_all(key[,match_prefix])`` + + Retrieves all entries with the same key, or starting with key..'/'. + Calling ``get_all('',true)`` will match all entries. + + If none found, returns nil; otherwise returns an array of entries. + +* ``dfhack.persistent.save({key=str1, ...}[,new])``, ``entry:save([new])`` + + Saves changes in an entry, or creates a new one. Passing true as + new forces creation of a new entry even if one already exists; + otherwise the existing one is simply updated. + Returns *entry, did_create_new* + +Since the data is hidden in data structures owned by the DF world, +and automatically stored in the save game, these save and retrieval +functions can just copy values in memory without doing any actual I/O. +However, currently every entry has a 180+-byte dead-weight overhead. diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index f81396f95..1477b16d5 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -240,6 +240,8 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, // Make a proxy global environment. lua_newtable(state); + int base = lua_gettop(state); + lua_newtable(state); if (env) lua_pushvalue(state, env); @@ -249,7 +251,6 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, lua_setmetatable(state, -2); // Main interactive loop - int base = lua_gettop(state); int vcnt = 1; string curline; string prompt_str = "[" + string(prompt) + "]# "; @@ -324,8 +325,20 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, static int lua_dfhack_interpreter(lua_State *state) { color_ostream *pstream = Lua::GetOutput(state); + if (!pstream) - luaL_error(state, "Cannot use dfhack.interpreter() without output."); + { + lua_pushnil(state); + lua_pushstring(state, "no output stream"); + return 2; + } + + if (!pstream->is_console()) + { + lua_pushnil(state); + lua_pushstring(state, "not an interactive console"); + return 2; + } int argc = lua_gettop(state); @@ -459,6 +472,8 @@ static PersistentDataItem get_persistent(lua_State *state) static int dfhack_persistent_get(lua_State *state) { + CoreSuspender suspend; + auto ref = get_persistent(state); return read_persistent(state, ref, !lua_istable(state, 1)); @@ -466,6 +481,8 @@ static int dfhack_persistent_get(lua_State *state) static int dfhack_persistent_delete(lua_State *state) { + CoreSuspender suspend; + auto ref = get_persistent(state); bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); @@ -476,6 +493,8 @@ static int dfhack_persistent_delete(lua_State *state) static int dfhack_persistent_get_all(lua_State *state) { + CoreSuspender suspend; + const char *str = luaL_checkstring(state, 1); bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); @@ -501,6 +520,8 @@ static int dfhack_persistent_get_all(lua_State *state) static int dfhack_persistent_save(lua_State *state) { + CoreSuspender suspend; + lua_settop(state, 2); luaL_checktype(state, 1, LUA_TTABLE); bool add = lua_toboolean(state, 2); From 42a9b0a59271f80d512dde78407d3a07681b3421 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 3 Apr 2012 13:29:59 +0400 Subject: [PATCH 07/28] Make Core::Suspend safe in plugin_onupdate by pretending to hold the lock. It is in essence true that OnUpdate owns the suspend lock, so expose it officially to the recursive suspend lock mechanics. --- library/Core.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index ffc174721..b3c91034f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -868,6 +868,15 @@ int Core::Update() color_ostream_proxy out(con); + // Pretend this thread has suspended the core in the usual way + { + lock_guard lock(d->AccessMutex); + + assert(d->df_suspend_depth == 0); + d->df_suspend_thread = this_thread::get_id(); + d->df_suspend_depth = 1000; + } + // detect if the game was loaded or unloaded in the meantime void *new_wdata = NULL; void *new_mapdata = NULL; @@ -922,6 +931,14 @@ int Core::Update() // notify all the plugins that a game tick is finished plug_mgr->OnUpdate(out); + // Release the fake suspend lock + { + lock_guard lock(d->AccessMutex); + + assert(d->df_suspend_depth == 1000); + d->df_suspend_depth = 0; + } + out << std::flush; // wake waiting tools From 8e1d26ff97d6f709cb95387c91d6dc581472fd4a Mon Sep 17 00:00:00 2001 From: Warmist Date: Tue, 3 Apr 2012 16:17:43 +0300 Subject: [PATCH 08/28] Added ability to call dfuse as a hotkey. E.g. calling dfuse "tools.empregnate()" now works. --- plugins/Dfusion/include/lua_Misc.h | 1 + plugins/Dfusion/luafiles/init.lua | 10 +++++++++- .../Dfusion/luafiles/onfunction/locations.lua | 3 ++- plugins/Dfusion/luafiles/onfunction/plugin.lua | 5 +++-- plugins/Dfusion/src/lua_Misc.cpp | 16 +++++++++++++--- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/plugins/Dfusion/include/lua_Misc.h b/plugins/Dfusion/include/lua_Misc.h index 4df1f3629..b39d60214 100644 --- a/plugins/Dfusion/include/lua_Misc.h +++ b/plugins/Dfusion/include/lua_Misc.h @@ -8,6 +8,7 @@ #include "luamain.h" #include "OutFile.h" #include "functioncall.h" +#include "LuaTools.h" namespace lua { diff --git a/plugins/Dfusion/luafiles/init.lua b/plugins/Dfusion/luafiles/init.lua index 0cab4c31d..57014e510 100644 --- a/plugins/Dfusion/luafiles/init.lua +++ b/plugins/Dfusion/luafiles/init.lua @@ -84,7 +84,15 @@ table.insert(plugins,{"saves","run current worlds's init.lua",RunSaved}) loadall(plugins) dofile_silent("dfusion/initcustom.lua") - +local args={...} +for k,v in pairs(args) do + local f,err=load(v) + if f then + f() + else + Console.printerr(err) + end +end if not INIT then mainmenu(plugins) end diff --git a/plugins/Dfusion/luafiles/onfunction/locations.lua b/plugins/Dfusion/luafiles/onfunction/locations.lua index 7849fc45d..362bfd7ab 100644 --- a/plugins/Dfusion/luafiles/onfunction/locations.lua +++ b/plugins/Dfusion/luafiles/onfunction/locations.lua @@ -8,7 +8,8 @@ if WINDOWS then --windows function defintions onfunction.AddFunction(0x5af826+offsets.base(),"Hurt",{target="esi",attacker={off=0x74,rtype=DWORD,reg="esp"}}) onfunction.AddFunction(0x3D5886+offsets.base(),"Flip",{building="esi"}) onfunction.AddFunction(0x35E340+offsets.base(),"ItemCreate")--]=] - onfunction.AddFunction(4B34B6+offsets.base(),"ReactionFinish") --esp item. Ecx creature, edx? + --onfunction.AddFunction(0x4B34B6+offsets.base(),"ReactionFinish") --esp item. Ecx creature, edx? 0.34.07 + onfunction.AddFunction(0x72aB6+offsets.base(),"Die",{creature="edi"}) --0.34.07 else --linux --[=[onfunction.AddFunction(0x899befe+offsets.base(),"Move") -- found out by attaching watch... onfunction.AddFunction(0x850eecd+offsets.base(),"Die",{creature="ebx"}) -- same--]=] diff --git a/plugins/Dfusion/luafiles/onfunction/plugin.lua b/plugins/Dfusion/luafiles/onfunction/plugin.lua index 7c76af122..60360817c 100644 --- a/plugins/Dfusion/luafiles/onfunction/plugin.lua +++ b/plugins/Dfusion/luafiles/onfunction/plugin.lua @@ -1,8 +1,9 @@ mypos=engine.getmod("functions") function DeathMsg(values) local name - name=engine.peek(values[onfunction.hints["Die"].creature],ptt_dfstring) - print(name:getval().." died") + local u=engine.cast(df.unit,values[onfunction.hints["Die"].creature]) + + print(u.name.first_name.." died") end if mypos then print("Onfunction already installed") diff --git a/plugins/Dfusion/src/lua_Misc.cpp b/plugins/Dfusion/src/lua_Misc.cpp index 0e1bfac0d..b58efc7ac 100644 --- a/plugins/Dfusion/src/lua_Misc.cpp +++ b/plugins/Dfusion/src/lua_Misc.cpp @@ -151,8 +151,6 @@ static size_t __stdcall PushValue(size_t ret,uint32_t eax,uint32_t ebx,uint32_t #endif { lua::state st=lua::glua::Get(); - st.getglobal("err"); - int perr=st.gettop(); st.getglobal("OnFunction"); if(st.is()) return 0; @@ -175,7 +173,7 @@ static size_t __stdcall PushValue(size_t ret,uint32_t eax,uint32_t ebx,uint32_t st.setfield("ebp"); st.push(ret); st.setfield("ret"); - st.pcall(1,1,perr); + DFHack::Lua::SafeCall(DFHack::Core::getInstance().getConsole(),st,1,1); return st.as(); } static int Get_PushValue(lua_State *L) @@ -210,6 +208,17 @@ static int Resume_Df(lua_State *L) DFHack::Core::getInstance().Resume(); return 0; } +static int Cast(lua_State *L) +{ + lua::state st(L); + if(DFHack::Lua::IsDFObject(st,1)!=DFHack::Lua::OBJ_TYPE) + st.error("First argument must be df type!"); + if(!st.is(2)) //todo maybe lightuserdata? + st.error("Second argument must be pointer as a number!"); + st.getfield("_identity",1); + DFHack::Lua::PushDFObject(st,(DFHack::type_identity*)lua_touserdata(st,-1),(void*)st.as(2)); + return 1; +} const luaL_Reg lua_misc_func[]= { {"alloc",lua_malloc}, @@ -224,6 +233,7 @@ const luaL_Reg lua_misc_func[]= {"calldf",Call_Df}, {"suspend",Suspend_Df}, {"resume",Resume_Df}, + {"cast",Cast}, {NULL,NULL} }; void lua::RegisterMisc(lua::state &st) From 2d4af4ac3e42ae1140fecc90b3beb02d0dd84f4a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 3 Apr 2012 20:02:01 +0400 Subject: [PATCH 09/28] A few more utility functions for lua. --- LUA_API.rst | 4 ++ library/LuaTools.cpp | 99 +++++++++++++++++++++++++++++++------- library/include/LuaTools.h | 23 +++++++++ library/lua/dfhack.lua | 2 + 4 files changed, 110 insertions(+), 18 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index fd05e5448..6a169e009 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -410,6 +410,10 @@ Currently it defines the following features: Same as println; intended for errors. Uses red color and logs to stderr.log. +* ``safecall(f[,args...])``, ``dfhack.safecall(f[,args...])`` + + Just like pcall, but prints the error with traceback using printerr. + * ``dfhack.interpreter([prompt[,env[,history_filename]]])`` Starts an interactive lua interpreter, using the specified prompt diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 1477b16d5..caeed3a36 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -141,6 +141,49 @@ static int traceback (lua_State *L) { return 1; } +static int finish_dfhack_safecall (lua_State *L, bool success) +{ + if (!lua_checkstack(L, 1)) + { + lua_settop(L, 0); /* create space for return values */ + lua_pushboolean(L, 0); + lua_pushstring(L, "stack overflow in dfhack.safecall()"); + success = false; + } + else + { + lua_pushboolean(L, success); + lua_replace(L, 1); /* put first result in first slot */ + } + + if (!success) + { + const char *msg = lua_tostring(L, -1); + if (!msg) msg = "In dfhack.safecall: error message is not a string."; + if (color_ostream *out = Lua::GetOutput(L)) + out->printerr("%s\n", msg); + else + Core::printerr("%s\n", msg); + } + + return lua_gettop(L); +} + +static int safecall_cont (lua_State *L) +{ + int status = lua_getctx(L, NULL); + return finish_dfhack_safecall(L, (status == LUA_YIELD)); +} + +static int lua_dfhack_safecall (lua_State *L) +{ + luaL_checkany(L, 1); + lua_pushcfunction(L, traceback); + lua_insert(L, 1); + int status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 1, 0, safecall_cont); + return finish_dfhack_safecall(L, (status == LUA_OK)); +} + static void report_error(color_ostream &out, lua_State *L) { const char *msg = lua_tostring(L, -1); @@ -189,24 +232,48 @@ bool DFHack::Lua::Require(color_ostream &out, lua_State *state, return true; } -static bool load_with_env(color_ostream &out, lua_State *state, const std::string &code, int eidx) +bool DFHack::Lua::AssignDFObject(color_ostream &out, lua_State *state, + type_identity *type, void *target, int val_index, bool perr) { - if (luaL_loadbuffer(state, code.data(), code.size(), "=(interactive)") != LUA_OK) + val_index = lua_absindex(state, val_index); + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME); + PushDFObject(state, type, target); + lua_pushvalue(state, val_index); + return Lua::SafeCall(out, state, 2, 0, perr); +} + +bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std::string &code, + int nargs, int nres, bool perr, + const char *debug_tag, int env_idx) +{ + if (!debug_tag) + debug_tag = code.c_str(); + if (env_idx) + env_idx = lua_absindex(state, env_idx); + + int base = lua_gettop(state); + + // Parse the code + if (luaL_loadbuffer(state, code.data(), code.size(), debug_tag) != LUA_OK) { - report_error(out, state); + if (perr) + report_error(out, state); + return false; } // Replace _ENV - lua_pushvalue(state, eidx); - - if (!lua_setupvalue(state, -2, 1)) + if (env_idx) { - out.printerr("No _ENV upvalue.\n"); - return false; + lua_pushvalue(state, env_idx); + lua_setupvalue(state, -2, 1); + assert(lua_gettop(state) == base+1); } - return true; + if (nargs > 0) + lua_insert(state, -1-nargs); + + return Lua::SafeCall(out, state, nargs, nres, perr); } bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, @@ -273,9 +340,7 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, { curline = "return " + curline.substr(1); - if (!load_with_env(out, state, curline, base)) - continue; - if (!SafeCall(out, state, 0, LUA_MULTRET)) + if (!Lua::SafeCallString(out, state, curline, 0, LUA_MULTRET, true, "=(interactive)", base)) continue; int numret = lua_gettop(state) - base; @@ -309,9 +374,7 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, } else { - if (!load_with_env(out, state, curline, base)) - continue; - if (!SafeCall(out, state, 0, 0)) + if (!Lua::SafeCallString(out, state, curline, 0, LUA_MULTRET, true, "=(interactive)", base)) continue; } } @@ -352,8 +415,7 @@ static int lua_dfhack_interpreter(lua_State *state) static int lua_dfhack_with_suspend(lua_State *L) { - int ctx; - int rv = lua_getctx(L, &ctx); + int rv = lua_getctx(L, NULL); // Non-resume entry point: if (rv == LUA_OK) @@ -385,7 +447,8 @@ static const luaL_Reg dfhack_funcs[] = { { "print", lua_dfhack_print }, { "println", lua_dfhack_println }, { "printerr", lua_dfhack_printerr }, - { "traceback", traceback }, + { "safecall", lua_dfhack_safecall }, + { "onerror", traceback }, { "interpreter", lua_dfhack_interpreter }, { "with_suspend", lua_dfhack_with_suspend }, { NULL, NULL } diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 6f95e7f6e..7e3ed1683 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -79,6 +79,13 @@ namespace DFHack { namespace Lua { */ DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false); + /** + * Assign the value at val_index to the target of given identity using df.assign(). + * Return behavior is of SafeCall below. + */ + DFHACK_EXPORT bool AssignDFObject(color_ostream &out, lua_State *state, + type_identity *type, void *target, int val_index, bool perr = true); + /** * Push the pointer onto the stack as a wrapped DF object of a specific type. */ @@ -95,12 +102,28 @@ namespace DFHack { namespace Lua { return (T*)GetDFObject(state, df::identity_traits::get(), val_index, exact_type); } + /** + * Assign the value at val_index to the target using df.assign(). + */ + template + bool AssignDFObject(color_ostream &out, lua_State *state, T *target, int val_index, bool perr = true) { + return AssignDFObject(out, state, df::identity_traits::get(), target, val_index, perr); + } + /** * Invoke lua function via pcall. Returns true if success. * If an error is signalled, and perr is true, it is printed and popped from the stack. */ DFHACK_EXPORT bool SafeCall(color_ostream &out, lua_State *state, int nargs, int nres, bool perr = true); + /** + * Parse code from string with debug_tag and env_idx, then call it using SafeCall. + * In case of error, it is either left on the stack, or printed like SafeCall does. + */ + DFHACK_EXPORT bool SafeCallString(color_ostream &out, lua_State *state, const std::string &code, + int nargs, int nres, bool perr = true, + const char *debug_tag = NULL, int env_idx = 0); + /** * Returns the ostream passed to SafeCall. */ diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index b7052b2ea..9435b393c 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -1,6 +1,8 @@ -- Common startup file for all dfhack plugins with lua support -- The global dfhack table is already created by C++ init code. +safecall = dfhack.safecall + function mkmodule(module,env) local pkg = package.loaded[module] if pkg == nil then From 59d7b6faabdfbf662ca8a71b66b3893542d0ec45 Mon Sep 17 00:00:00 2001 From: Will Rogers Date: Tue, 3 Apr 2012 14:03:06 -0400 Subject: [PATCH 10/28] Add ListJobSkills. --- library/RemoteTools.cpp | 22 ++++++++++++++++++++++ library/include/RemoteTools.h | 4 ++++ library/proto/Basic.proto | 11 +++++++++++ library/proto/BasicApi.proto | 5 +++++ 4 files changed, 42 insertions(+) mode change 100644 => 100755 library/proto/Basic.proto diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 0ac4d5808..4ff7ce695 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -338,6 +338,21 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, info->add_burrows(unit->burrows[i]); } +void DFHack::describeJobSkills(RepeatedPtrField *pf) +{ + FOR_ENUM_ITEMS(job_skill, skill) + { + auto item = pf->Add(); + + item->set_id(skill); + item->set_caption(ENUM_ATTR_STR(job_skill, caption, skill)); + item->set_caption_noun(ENUM_ATTR_STR(job_skill, caption_noun, skill)); + item->set_profession(ENUM_ATTR(job_skill, profession, skill)); + item->set_labor(ENUM_ATTR(job_skill, labor, skill)); + item->set_type(ENUM_ATTR(job_skill, type, skill)); + } +} + static command_result GetVersion(color_ostream &stream, const EmptyMessage *, StringMessage *out) { @@ -445,6 +460,12 @@ static command_result ListEnums(color_ostream &stream, #undef BITFIELD } +static command_result ListJobSkills(color_ostream &stream, const EmptyMessage *, ListJobSkillsOut *out) +{ + describeJobSkills(out->mutable_value()); + return CR_OK; +} + static void listMaterial(ListMaterialsOut *out, int type, int index, const BasicMaterialInfoMask *mask) { MaterialInfo info(type, index); @@ -590,6 +611,7 @@ CoreService::CoreService() { addFunction("GetWorldInfo", GetWorldInfo); addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND); + addFunction("ListJobSkills", ListJobSkills, SF_CALLED_ONCE | SF_DONT_SUSPEND); addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE); addFunction("ListUnits", ListUnits); diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h index bccbb5e62..b72b83036 100644 --- a/library/include/RemoteTools.h +++ b/library/include/RemoteTools.h @@ -127,6 +127,10 @@ namespace DFHack DFHACK_EXPORT void describeUnit(BasicUnitInfo *info, df::unit *unit, const BasicUnitInfoMask *mask = NULL); + using dfproto::JobSkillInfo; + + DFHACK_EXPORT void describeJobSkills(RepeatedPtrField *pf); + ///// class CoreService : public RPCService { diff --git a/library/proto/Basic.proto b/library/proto/Basic.proto old mode 100644 new mode 100755 index ead41cd81..1d22ea11c --- a/library/proto/Basic.proto +++ b/library/proto/Basic.proto @@ -69,6 +69,17 @@ message BasicMaterialInfoMask { optional bool reaction = 3 [default = false]; }; +message JobSkillInfo { + required int32 id = 1; + + optional string caption = 2; + optional string caption_noun = 3; + + optional int32 profession = 4; + optional int32 labor = 5; + optional int32 type = 6; +}; + message NameInfo { optional string first_name = 1; optional string nickname = 2; diff --git a/library/proto/BasicApi.proto b/library/proto/BasicApi.proto index 137f25726..bfbb076fa 100644 --- a/library/proto/BasicApi.proto +++ b/library/proto/BasicApi.proto @@ -51,6 +51,11 @@ message ListEnumsOut { repeated EnumItemName profession = 11; }; +// RPC ListJobSkills : EmptyMessage -> ListJobSkillsOut +message ListJobSkillsOut { + repeated JobSkillInfo value = 1; +}; + // RPC ListMaterials : ListMaterialsIn -> ListMaterialsOut message ListMaterialsIn { optional BasicMaterialInfoMask mask = 1; From 7e896277ec4076c0c441d9b3cda766493eb82d15 Mon Sep 17 00:00:00 2001 From: Will Rogers Date: Tue, 3 Apr 2012 14:22:46 -0400 Subject: [PATCH 11/28] Don't export describeJobSkills. --- library/RemoteTools.cpp | 30 ++++++++++++++---------------- library/include/RemoteTools.h | 4 ---- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 4ff7ce695..5103b7561 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -338,21 +338,6 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, info->add_burrows(unit->burrows[i]); } -void DFHack::describeJobSkills(RepeatedPtrField *pf) -{ - FOR_ENUM_ITEMS(job_skill, skill) - { - auto item = pf->Add(); - - item->set_id(skill); - item->set_caption(ENUM_ATTR_STR(job_skill, caption, skill)); - item->set_caption_noun(ENUM_ATTR_STR(job_skill, caption_noun, skill)); - item->set_profession(ENUM_ATTR(job_skill, profession, skill)); - item->set_labor(ENUM_ATTR(job_skill, labor, skill)); - item->set_type(ENUM_ATTR(job_skill, type, skill)); - } -} - static command_result GetVersion(color_ostream &stream, const EmptyMessage *, StringMessage *out) { @@ -462,7 +447,20 @@ static command_result ListEnums(color_ostream &stream, static command_result ListJobSkills(color_ostream &stream, const EmptyMessage *, ListJobSkillsOut *out) { - describeJobSkills(out->mutable_value()); + auto pf = out->mutable_value(); + + FOR_ENUM_ITEMS(job_skill, skill) + { + auto item = pf->Add(); + + item->set_id(skill); + item->set_caption(ENUM_ATTR_STR(job_skill, caption, skill)); + item->set_caption_noun(ENUM_ATTR_STR(job_skill, caption_noun, skill)); + item->set_profession(ENUM_ATTR(job_skill, profession, skill)); + item->set_labor(ENUM_ATTR(job_skill, labor, skill)); + item->set_type(ENUM_ATTR(job_skill, type, skill)); + } + return CR_OK; } diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h index b72b83036..bccbb5e62 100644 --- a/library/include/RemoteTools.h +++ b/library/include/RemoteTools.h @@ -127,10 +127,6 @@ namespace DFHack DFHACK_EXPORT void describeUnit(BasicUnitInfo *info, df::unit *unit, const BasicUnitInfoMask *mask = NULL); - using dfproto::JobSkillInfo; - - DFHACK_EXPORT void describeJobSkills(RepeatedPtrField *pf); - ///// class CoreService : public RPCService { From 3a0bd73315c8ac2e9c6ce2fc90d770193f2ae50c Mon Sep 17 00:00:00 2001 From: Warmist Date: Tue, 3 Apr 2012 21:47:54 +0300 Subject: [PATCH 12/28] Fix for getSelectedCreature in adv mode. --- plugins/Dfusion/luafiles/common.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index 43f95abd8..7cdced5ac 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -468,6 +468,9 @@ function ParseNames(path) return ret end function getSelectedUnit() + if df.global.ui.main.mode~=23 then + return nil + end local unit_indx=df.global.ui_selected_unit if unit_indx<#df.global.world.units.other[0]-1 then return df.global.world.units.other[0][unit_indx] From 6d4ef1fd3807d23eb4c5afcb2b9b73c34521a9a3 Mon Sep 17 00:00:00 2001 From: Warmist Date: Tue, 3 Apr 2012 23:16:29 +0300 Subject: [PATCH 13/28] Started rework lua files to be more hotkey friendly and adv/fort mode independant. --- plugins/Dfusion/luafiles/adv_tools/init.lua | 1 + plugins/Dfusion/luafiles/common.lua | 34 ++++++--- plugins/Dfusion/luafiles/offsets_misc.lua | 7 +- plugins/Dfusion/luafiles/tools/init.lua | 77 ++++++++++----------- 4 files changed, 66 insertions(+), 53 deletions(-) diff --git a/plugins/Dfusion/luafiles/adv_tools/init.lua b/plugins/Dfusion/luafiles/adv_tools/init.lua index 3810f0364..4550f837d 100644 --- a/plugins/Dfusion/luafiles/adv_tools/init.lua +++ b/plugins/Dfusion/luafiles/adv_tools/init.lua @@ -1,5 +1,6 @@ adv_tools=adv_tools or {} adv_tools.menu=adv_tools.menu or MakeMenu() +--TODO make every tool generic (work for both modes) function adv_tools.ressurect() v2=engine.peek(vector:getval(indx),ptr_Creature.hurt1) diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index 7cdced5ac..cf07cb53b 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -496,10 +496,34 @@ function getCreatureAtPos(x,y,z) -- gets the creature index @ x,y,z coord return vector[i] --return index end end - print("Creature not found!") + --print("Creature not found!") return nil end +function getCreatureAtPointer() + return getCreatureAtPos(getxyz()) +end +function getCreature() + local unit=getSelectedUnit() + if unit==nil then + unit=getCreatureAtPointer() + end + --any other selection methods... + return unit +end +function getNemesisId(unit) + for k,v in pairs(unit.refs) do + if tostring(v._type)=="" then + return v.nemesis_id + end + end +end +function getNemesis(unit) + local id=getNemesisId(unit) + if id then + return df.global.world.nemesis.all[id] + end +end function Allocate(size) local ptr=engine.getmod('General_Space') if ptr==nil then @@ -512,14 +536,6 @@ function Allocate(size) engine.poked(ptr,curptr) return curptr-size+ptr end -function initType(object,...) - local m=getmetatable(object) - if m~=nil and m.__setup~=nil then - m.__setup(object,...) - else - error("This object does not have __setup function") - end -end dofile("dfusion/patterns.lua") dofile("dfusion/patterns2.lua") dofile("dfusion/itempatterns.lua") diff --git a/plugins/Dfusion/luafiles/offsets_misc.lua b/plugins/Dfusion/luafiles/offsets_misc.lua index 5a35abd71..8bca50000 100644 --- a/plugins/Dfusion/luafiles/offsets_misc.lua +++ b/plugins/Dfusion/luafiles/offsets_misc.lua @@ -1,7 +1,7 @@ offsets=offsets or {} function offsets.find(startoffset,...) - local endadr=GetTextRegion()["end"]; - --[=[if startoffset== 0 then + -- [=[ + if startoffset== 0 then local text=GetTextRegion() --print("searching in:"..text.name) startoffset=text.start @@ -14,7 +14,8 @@ function offsets.find(startoffset,...) return 0 end endadr=reg["end"] - end--]=] + end + --]=] --print(string.format("Searching (%x->%x)",startoffset,endadr)) local h=hexsearch(startoffset,endadr,...) local pos=h:find() diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index a84472a60..54cb62a1f 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -1,20 +1,28 @@ tools={} tools.menu=MakeMenu() -function tools.setrace() +function tools.setrace(name) RaceTable=BuildNameTable() print("Your current race is:"..GetRaceToken(df.global.ui.race_id)) - print("Type new race's token name in full caps (q to quit):") - repeat - entry=getline() - if entry=="q" then - return + local id + if name == nil then + print("Type new race's token name in full caps (q to quit):") + repeat + entry=getline() + if entry=="q" then + return + end + id=RaceTable[entry] + until id~=nil + else + id=RaceTable[name] + if id==nil then + error("Name not found!") end - id=RaceTable[entry] - until id~=nil + end df.global.ui.race_id=id end tools.menu:add("Set current race",tools.setrace) -function tools.GiveSentience(names) --TODO make pattern... +function tools.GiveSentience(names) RaceTable=RaceTable or BuildNameTable() --slow.If loaded don't load again if names ==nil then ids={} @@ -63,23 +71,7 @@ function tools.embark() end end tools.menu:add("Embark anywhere",tools.embark) -function tools.getlegendsid(croff) - local vec=engine.peek(croff,ptr_Creature.legends) - if vec:size()==0 then - return 0 - end - for i =0,vector:size()-1 do - --if engine.peekd(vec:getval(i))~=0 then - -- print(string.format("%x",engine.peekd(vec:getval(i))-offsets.base())) - --end - if(engine.peekd(vec:getval(i))==offsets.getEx("vtableLegends")) then --easy to get.. just copy from player's-base - return engine.peekd(vec:getval(i)+4) - end - end - return 0 -end -function tools.getCreatureId(vector) - +function tools.getCreatureId(vector) --redo it to getcreature by name tnames={} rnames={} --[[print("vector1 size:"..vector:size()) @@ -137,22 +129,25 @@ function tools.change_adv() end tools.menu:add("Change Adventurer",tools.change_adv) -function tools.MakeFollow() - myoff=offsets.getEx("AdvCreatureVec") - vector=engine.peek(myoff,ptr_vector) - indx=tools.getCreatureId(vector) - print(string.format("current creature:%x",vector:getval(indx))) +function tools.MakeFollow(unit,trgunit) - trgid=engine.peek(vector:getval(0)+ptr_Creature.ID.off,DWORD) - lfollow=engine.peek(vector:getval(indx)+ptr_Creature.followID.off,DWORD) - if lfollow ~=0xFFFFFFFF then - print("Already following, unfollow? y/N") - r=getline() - if r== "y" then - engine.poke(vector:getval(indx)+ptr_Creature.followID.off,DWORD,0) - end - else - engine.poke(vector:getval(indx)+ptr_Creature.followID.off,DWORD,trgid) + if unit == nil then + unit=getCreature() + end + if unit== nil then + error("Invalid creature") + end + if trgunit==nil then + trgunit=df.global.world.units.other[0][0] + end + unit.relations.group_leader_id=trgunit.id + local u_nem=getNemesis(unit) + local t_nem=getNemesis(trgunit) + if u_nem then + u_nem.group_leader_id=t_nem.id + end + if t_nem and u_nem then + t_nem.companions:insert(#t_nem.companions,u_nem.id) end end tools.menu:add("Make creature follow",tools.MakeFollow) From 26f5e0dfb591d56bf1b2977ae4618e23a9cba9f0 Mon Sep 17 00:00:00 2001 From: Warmist Date: Tue, 3 Apr 2012 23:51:54 +0300 Subject: [PATCH 14/28] Fixed a lot of bugs, mostly in dfusion->tools. Embark anywhere now works (in windows atleast), all other tools should work in both linux and windows. TODO: should move embark anywhere somewhere else (as it is code changing tool) --- plugins/Dfusion/luafiles/common.lua | 4 +- plugins/Dfusion/luafiles/tools/init.lua | 57 ++++++++++++++++--------- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index cf07cb53b..1ed25679e 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -254,11 +254,11 @@ function it_menu:display() if r=='q' then return end ans=tonumber(r) - if ans==nil or not(ans<=table.maxn(self.items) and ans>0) then + if ans==nil or not(ans<=#self.items and ans>0) then print("incorrect choice") end - until ans~=nil and (ans<=table.maxn(self.items) and ans>0) + until ans~=nil and (ans<=#self.items and ans>0) self.items[ans][1]() end function MakeMenu() diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index 54cb62a1f..adc376e5a 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -71,7 +71,7 @@ function tools.embark() end end tools.menu:add("Embark anywhere",tools.embark) -function tools.getCreatureId(vector) --redo it to getcreature by name +function tools.getCreatureId(vector) --redo it to getcreature by name/id or something tnames={} rnames={} --[[print("vector1 size:"..vector:size()) @@ -103,28 +103,43 @@ function tools.getCreatureId(vector) --redo it to getcreature by name end return indx end -function tools.change_adv() - myoff=offsets.getEx("AdvCreatureVec") - vector=engine.peek(myoff,ptr_vector) - indx=tools.getCreatureId(vector) - print("Swaping, press enter when done or 'q' to stay, 's' to stay with legends id change") - tval=vector:getval(0) - vector:setval(0,vector:getval(indx)) - vector:setval(indx,tval) - r=getline() - if r=='q' then - return +function tools.change_adv(unit,nemesis) + if nemesis==nil then + nemesis=true --default value is nemesis switch too. end - if r~='s' then - tval=vector:getval(0) - vector:setval(0,vector:getval(indx)) - vector:setval(indx,tval) + if unit==nil then + unit=getCreatureAtPointer() end - local lid=tools.getlegendsid(vector:getval(0)) - if lid~=0 then - engine.poked(offsets.getEx("PlayerLegend"),lid) - else - print("Warning target does not have a valid legends id!") + if unit==nil then + error("Invalid unit!") + end + local other=df.global.world.units.other[0] + local unit_indx + for k,v in pairs(other) do + if v==unit then + unit_indx=k + break + end + end + if unit_indx==nil then + error("Unit not found in array?!") --should not happen + end + other[unit_indx]=other[0] + other[0]=unit + if nemesis then --basicly copied from advtools plugin... + local nem=getNemesis(unit) + local other_nem=getNemesis(other[unit_indx]) + if other_nem then + other_nem.flags[0]=false + other_nem.flags[1]=true + end + if nem then + nem.flags[1]=true + nem.flags[2]=true + df.global.ui_advmode.player_id=nem.id + else + error("Current unit does not have nemesis record, further working not guaranteed") + end end end tools.menu:add("Change Adventurer",tools.change_adv) From 6a6ca516c37243081f107518b167424e79a75bcc Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 4 Apr 2012 00:18:40 +0300 Subject: [PATCH 15/28] Stupid bug with nemesis... --- plugins/Dfusion/luafiles/common.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index 1ed25679e..b74c6aa8d 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -521,7 +521,11 @@ end function getNemesis(unit) local id=getNemesisId(unit) if id then - return df.global.world.nemesis.all[id] + for k,v in pairs(df.global.world.nemesis.all) do + if id==v.id then + return v + end + end end end function Allocate(size) From 179c6e4025c019f15ac59ff7c84922a0a57a0a1c Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 4 Apr 2012 02:06:35 +0300 Subject: [PATCH 16/28] Revived adv_tools, with new reincarnate command. --- plugins/Dfusion/luafiles/adv_tools/init.lua | 58 ++++++++++++++++++- plugins/Dfusion/luafiles/adv_tools/plugin.lua | 3 + plugins/Dfusion/luafiles/init.lua | 3 +- plugins/Dfusion/luafiles/tools/init.lua | 2 +- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/plugins/Dfusion/luafiles/adv_tools/init.lua b/plugins/Dfusion/luafiles/adv_tools/init.lua index 4550f837d..ae0947ed5 100644 --- a/plugins/Dfusion/luafiles/adv_tools/init.lua +++ b/plugins/Dfusion/luafiles/adv_tools/init.lua @@ -1,6 +1,60 @@ -adv_tools=adv_tools or {} -adv_tools.menu=adv_tools.menu or MakeMenu() +adv_tools= {} +adv_tools.menu=MakeMenu() --TODO make every tool generic (work for both modes) +function adv_tools.reincarnate(swap_soul) --only for adventurer i guess, TODO soul swap... + if swap_soul==nil then + swap_soul=true + end + local adv=df.global.world.units.other[0][0] + if adv.flags1.dead==false then + error("You are not dead (yet)!") + end + local hist_fig=getNemesis(adv).figure + if hist_fig==nil then + error("No historical figure for adventurer...") + end + local events=df.global.world.history.events + local trg_hist_fig + for i=#events-1,0,-1 do + if df.history_event_hist_figure_diedst:is_instance(events[i]) then + --print("is instance:"..i) + if events[i].hfid==hist_fig.id then + --print("Is same id:"..i) + trg_hist_fig=events[i].slayer + break + end + end + end + if trg_hist_fig ==nil then + error("Slayer not found") + end + local trg_unit + for k,v in pairs(df.global.world.history.figures) do --maybe getting it by [] would be enought? + if v.id==trg_hist_fig then + trg_unit=v.unit_id + break + end + end + if trg_unit==nil then + error("Unit id not found!") + end + local trg_unit_final + for k,v in pairs(df.global.world.units.all) do + if v.id==trg_unit then + trg_unit_final=v + break + end + end + tools.change_adv(trg_unit_final) + if swap_souls then --actually add a soul... + t_soul=adv.status.cur_soul + adv.status.cur_soul=df.NULL + adv.status.souls:resize(0) + trg_unit_final.status.cur_soul=t_soul + trg_unit_final.status.souls:insert(#trg_unit_final.status.souls,t_soul) + end +end +adv_tools.menu:add("Reincarnate",adv_tools.reincarnate) function adv_tools.ressurect() v2=engine.peek(vector:getval(indx),ptr_Creature.hurt1) diff --git a/plugins/Dfusion/luafiles/adv_tools/plugin.lua b/plugins/Dfusion/luafiles/adv_tools/plugin.lua index e69de29bb..cdd81e06f 100644 --- a/plugins/Dfusion/luafiles/adv_tools/plugin.lua +++ b/plugins/Dfusion/luafiles/adv_tools/plugin.lua @@ -0,0 +1,3 @@ +if not(FILE) then + adv_tools.menu:display() +end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/init.lua b/plugins/Dfusion/luafiles/init.lua index 57014e510..358f93d00 100644 --- a/plugins/Dfusion/luafiles/init.lua +++ b/plugins/Dfusion/luafiles/init.lua @@ -73,7 +73,7 @@ table.insert(plugins,{"friendship","Multi race fort enabler"}) table.insert(plugins,{"offsets","Find all offsets"}) table.insert(plugins,{"friendship_civ","Multi civ fort enabler"}) -table.insert(plugins,{"adv_tools","some tools for (mainly) advneturer hacking"}) + table.insert(plugins,{"triggers","a function calling plug (discontinued...)"}) table.insert(plugins,{"migrants","multi race imigrations"}) @@ -81,6 +81,7 @@ table.insert(plugins,{"migrants","multi race imigrations"}) --table.insert(plugins,{"onfunction","run lua on some df function"}) --table.insert(plugins,{"editor","edit internals of df",EditDF}) table.insert(plugins,{"saves","run current worlds's init.lua",RunSaved}) +table.insert(plugins,{"adv_tools","some tools for (mainly) advneturer hacking"}) loadall(plugins) dofile_silent("dfusion/initcustom.lua") diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index adc376e5a..27624e0a9 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -134,7 +134,7 @@ function tools.change_adv(unit,nemesis) other_nem.flags[1]=true end if nem then - nem.flags[1]=true + nem.flags[0]=true nem.flags[2]=true df.global.ui_advmode.player_id=nem.id else From 9707939f2c9c6edf7314a33c790e475046da22a7 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 4 Apr 2012 02:13:48 +0300 Subject: [PATCH 17/28] whoops, now it actually swaps souls :) --- plugins/Dfusion/luafiles/adv_tools/init.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Dfusion/luafiles/adv_tools/init.lua b/plugins/Dfusion/luafiles/adv_tools/init.lua index ae0947ed5..c6917a767 100644 --- a/plugins/Dfusion/luafiles/adv_tools/init.lua +++ b/plugins/Dfusion/luafiles/adv_tools/init.lua @@ -46,11 +46,11 @@ function adv_tools.reincarnate(swap_soul) --only for adventurer i guess, TODO so end end tools.change_adv(trg_unit_final) - if swap_souls then --actually add a soul... - t_soul=adv.status.cur_soul - adv.status.cur_soul=df.NULL + if swap_soul then --actually add a soul... + t_soul=adv.status.current_soul + adv.status.current_soul=df.NULL adv.status.souls:resize(0) - trg_unit_final.status.cur_soul=t_soul + trg_unit_final.status.current_soul=t_soul trg_unit_final.status.souls:insert(#trg_unit_final.status.souls,t_soul) end end From 81fb57a8536fed5957f9a8c5e4cecbc71746951f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 4 Apr 2012 10:40:33 +0400 Subject: [PATCH 18/28] Add color output and input prompt support to core lua api. --- LUA_API.rst | 17 ++++++++- library/LuaTools.cpp | 90 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 91 insertions(+), 16 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 6a169e009..30b1c638b 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -410,9 +410,18 @@ Currently it defines the following features: Same as println; intended for errors. Uses red color and logs to stderr.log. -* ``safecall(f[,args...])``, ``dfhack.safecall(f[,args...])`` +* ``dfhack.color([color])`` - Just like pcall, but prints the error with traceback using printerr. + Sets the current output color. If color is *nil* or *-1*, resets to default. + +* ``dfhack.is_interactive()`` + + Checks if the thread can access the interactive console and returns *true* or *false*. + +* ``dfhack.lineedit([prompt[,history_filename]])`` + + If the thread owns the interactive console, shows a prompt + and returns the entered string. Otherwise returns *nil, error*. * ``dfhack.interpreter([prompt[,env[,history_filename]]])`` @@ -421,6 +430,10 @@ Currently it defines the following features: If the interactive console is not accessible, returns *nil, error*. +* ``safecall(f[,args...])``, ``dfhack.safecall(f[,args...])`` + + Just like pcall, but prints the error with traceback using printerr. + * ``dfhack.with_suspend(f[,args...])`` Calls ``f`` with arguments after grabbing the DF core suspend lock. diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index caeed3a36..a2b672034 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -76,6 +76,27 @@ static void set_dfhack_output(lua_State *L, color_ostream *p) lua_rawsetp(L, LUA_REGISTRYINDEX, &DFHACK_OSTREAM_TOKEN); } +static Console *get_console(lua_State *state) +{ + color_ostream *pstream = Lua::GetOutput(state); + + if (!pstream) + { + lua_pushnil(state); + lua_pushstring(state, "no output stream"); + return NULL; + } + + if (!pstream->is_console()) + { + lua_pushnil(state); + lua_pushstring(state, "not an interactive console"); + return NULL; + } + + return static_cast(pstream); +} + static std::string lua_print_fmt(lua_State *L) { /* Copied from lua source to fully replicate builtin print */ @@ -130,6 +151,56 @@ static int lua_dfhack_printerr(lua_State *S) return 0; } +static int lua_dfhack_color(lua_State *S) +{ + int cv = luaL_optint(S, 1, -1); + + if (cv < -1 || cv > color_ostream::COLOR_MAX) + luaL_argerror(S, 1, "invalid color value"); + + color_ostream *out = Lua::GetOutput(S); + if (out) + out->color(color_ostream::color_value(cv)); + return 0; +} + +static int lua_dfhack_is_interactive(lua_State *S) +{ + lua_pushboolean(S, get_console(S) != NULL); + return 1; +} + +static int lua_dfhack_lineedit(lua_State *S) +{ + const char *prompt = luaL_optstring(S, 1, ">> "); + const char *hfile = luaL_optstring(S, 2, NULL); + + Console *pstream = get_console(S); + if (!pstream) + return 2; + + DFHack::CommandHistory hist; + if (hfile) + hist.load(hfile); + + std::string ret; + int rv = pstream->lineedit(prompt, ret, hist); + + if (rv < 0) + { + lua_pushnil(S); + lua_pushstring(S, "input error"); + return 2; + } + else + { + if (hfile) + hist.save(hfile); + lua_pushlstring(S, ret.data(), ret.size()); + return 1; + } +} + static int traceback (lua_State *L) { const char *msg = lua_tostring(L, 1); if (msg) @@ -387,21 +458,9 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, static int lua_dfhack_interpreter(lua_State *state) { - color_ostream *pstream = Lua::GetOutput(state); - + Console *pstream = get_console(state); if (!pstream) - { - lua_pushnil(state); - lua_pushstring(state, "no output stream"); - return 2; - } - - if (!pstream->is_console()) - { - lua_pushnil(state); - lua_pushstring(state, "not an interactive console"); return 2; - } int argc = lua_gettop(state); @@ -447,9 +506,12 @@ static const luaL_Reg dfhack_funcs[] = { { "print", lua_dfhack_print }, { "println", lua_dfhack_println }, { "printerr", lua_dfhack_printerr }, + { "color", lua_dfhack_color }, + { "is_interactive", lua_dfhack_is_interactive }, + { "lineedit", lua_dfhack_lineedit }, + { "interpreter", lua_dfhack_interpreter }, { "safecall", lua_dfhack_safecall }, { "onerror", traceback }, - { "interpreter", lua_dfhack_interpreter }, { "with_suspend", lua_dfhack_with_suspend }, { NULL, NULL } }; From 7efbd798ce30eb598763bfceeb5afc9e5ddffda6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 4 Apr 2012 13:34:07 +0400 Subject: [PATCH 19/28] Upgrade lua errors to structures when attaching stack trace. This allows detecting when it's re-thrown and avoiding attaching the stack twice, and so on. Would also be useful if debugging is added. --- LUA_API.rst | 12 ++- library/LuaTools.cpp | 193 +++++++++++++++++++++++++++++++++-------- library/lua/dfhack.lua | 4 + 3 files changed, 171 insertions(+), 38 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 30b1c638b..bf6c81ec0 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -430,9 +430,19 @@ Currently it defines the following features: If the interactive console is not accessible, returns *nil, error*. +* ``dfhack.pcall(f[,args...])`` + + Invokes f via xpcall, using an error function that attaches + a stack trace to the error. The same function is used by SafeCall + in C++, and dfhack.safecall. + + The returned error is a table with separate ``message`` and + ``stacktrace`` string fields; it implements ``__tostring``. + * ``safecall(f[,args...])``, ``dfhack.safecall(f[,args...])`` - Just like pcall, but prints the error with traceback using printerr. + Just like pcall, but also prints the error using printerr before + returning. Intended as a convenience function. * ``dfhack.with_suspend(f[,args...])`` diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index a2b672034..5a50d0dd2 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -141,13 +141,18 @@ static int lua_dfhack_println(lua_State *S) return 0; } -static int lua_dfhack_printerr(lua_State *S) +static void dfhack_printerr(lua_State *S, const std::string &str) { - std::string str = lua_print_fmt(S); if (color_ostream *out = Lua::GetOutput(S)) out->printerr("%s\n", str.c_str()); else Core::printerr("%s\n", str.c_str()); +} + +static int lua_dfhack_printerr(lua_State *S) +{ + std::string str = lua_print_fmt(S); + dfhack_printerr(S, str); return 0; } @@ -201,20 +206,135 @@ static int lua_dfhack_lineedit(lua_State *S) } } -static int traceback (lua_State *L) { - const char *msg = lua_tostring(L, 1); - if (msg) - luaL_traceback(L, L, msg, 1); - else if (!lua_isnoneornil(L, 1)) { /* is there an error object? */ - if (!luaL_callmeta(L, 1, "__tostring")) /* try its 'tostring' metamethod */ - lua_pushliteral(L, "(no error message)"); +static int DFHACK_EXCEPTION_META_TOKEN = 0; + +static void error_tostring(lua_State *L) +{ + lua_getglobal(L, "tostring"); + lua_pushvalue(L, -2); + bool ok = lua_pcall(L, 1, 1, 0) == LUA_OK; + + const char *msg = lua_tostring(L, -1); + if (!msg) + { + msg = "tostring didn't return a string"; + ok = false; + } + + if (!ok) + { + lua_pushfstring(L, "(invalid error: %s)", msg); + lua_remove(L, -2); + } +} + +static void report_error(lua_State *L, color_ostream *out = NULL) +{ + lua_dup(L); + error_tostring(L); + + const char *msg = lua_tostring(L, -1); + assert(msg); + + if (out) + out->printerr("%s\n", msg); + else + dfhack_printerr(L, msg); + + lua_pop(L, 1); +} + +static bool convert_to_exception(lua_State *L) +{ + int base = lua_gettop(L); + + bool force_unknown = false; + + if (lua_istable(L, base) && lua_getmetatable(L, base)) + { + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN); + bool is_exception = lua_rawequal(L, -1, -2); + lua_settop(L, base); + + // If it is an exception, return as is + if (is_exception) + return false; + + force_unknown = true; } + + if (!lua_istable(L, base) || force_unknown) + { + lua_newtable(L); + lua_swap(L); + + if (lua_isstring(L, -1)) + lua_setfield(L, base, "message"); + else + { + error_tostring(L); + lua_setfield(L, base, "message"); + lua_setfield(L, base, "object"); + } + } + else + { + lua_getfield(L, base, "message"); + + if (!lua_isstring(L, -1)) + { + error_tostring(L); + lua_setfield(L, base, "message"); + } + + lua_settop(L, base); + } + + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN); + lua_setmetatable(L, base); + return true; +} + +static int dfhack_onerror(lua_State *L) +{ + luaL_checkany(L, 1); + lua_settop(L, 1); + + bool changed = convert_to_exception(L); + if (!changed) + return 1; + + luaL_traceback(L, L, NULL, 1); + lua_setfield(L, 1, "stacktrace"); + + return 1; +} + +static int dfhack_exception_tostring(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TTABLE); + + int base = lua_gettop(L); + + lua_getfield(L, 1, "message"); + if (!lua_isstring(L, -1)) + { + lua_pop(L, 1); + lua_pushstring(L, "(error message is not a string)"); + } + + lua_pushstring(L, "\n"); + lua_getfield(L, 1, "stacktrace"); + if (!lua_isstring(L, -1)) + lua_pop(L, 2); + + lua_concat(L, lua_gettop(L) - base); return 1; } static int finish_dfhack_safecall (lua_State *L, bool success) { - if (!lua_checkstack(L, 1)) + if (!lua_checkstack(L, 2)) { lua_settop(L, 0); /* create space for return values */ lua_pushboolean(L, 0); @@ -228,14 +348,7 @@ static int finish_dfhack_safecall (lua_State *L, bool success) } if (!success) - { - const char *msg = lua_tostring(L, -1); - if (!msg) msg = "In dfhack.safecall: error message is not a string."; - if (color_ostream *out = Lua::GetOutput(L)) - out->printerr("%s\n", msg); - else - Core::printerr("%s\n", msg); - } + report_error(L); return lua_gettop(L); } @@ -249,22 +362,12 @@ static int safecall_cont (lua_State *L) static int lua_dfhack_safecall (lua_State *L) { luaL_checkany(L, 1); - lua_pushcfunction(L, traceback); + lua_pushcfunction(L, dfhack_onerror); lua_insert(L, 1); int status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 1, 0, safecall_cont); return finish_dfhack_safecall(L, (status == LUA_OK)); } -static void report_error(color_ostream &out, lua_State *L) -{ - const char *msg = lua_tostring(L, -1); - if (msg) - out.printerr("%s\n", msg); - else - out.printerr("In Lua::SafeCall: error message is not a string.\n", msg); - lua_pop(L, 1); -} - bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres, bool perr) { int base = lua_gettop(L) - nargs; @@ -272,17 +375,20 @@ bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres color_ostream *cur_out = Lua::GetOutput(L); set_dfhack_output(L, &out); - lua_pushcfunction(L, traceback); + lua_pushcfunction(L, dfhack_onerror); lua_insert(L, base); bool ok = lua_pcall(L, nargs, nres, base) == LUA_OK; + if (!ok && perr) + { + report_error(L, &out); + lua_pop(L, 1); + } + lua_remove(L, base); set_dfhack_output(L, cur_out); - if (!ok && perr) - report_error(out, L); - return ok; } @@ -328,7 +434,10 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std if (luaL_loadbuffer(state, code.data(), code.size(), debug_tag) != LUA_OK) { if (perr) - report_error(out, state); + { + report_error(state, &out); + lua_pop(state, 1); + } return false; } @@ -485,7 +594,7 @@ static int lua_dfhack_with_suspend(lua_State *L) Core::getInstance().Suspend(); - lua_pushcfunction(L, traceback); + lua_pushcfunction(L, dfhack_onerror); lua_insert(L, 1); rv = lua_pcallk(L, nargs-1, LUA_MULTRET, 1, 0, lua_dfhack_with_suspend); @@ -511,7 +620,7 @@ static const luaL_Reg dfhack_funcs[] = { { "lineedit", lua_dfhack_lineedit }, { "interpreter", lua_dfhack_interpreter }, { "safecall", lua_dfhack_safecall }, - { "onerror", traceback }, + { "onerror", dfhack_onerror }, { "with_suspend", lua_dfhack_with_suspend }, { NULL, NULL } }; @@ -743,8 +852,18 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_pushcfunction(state, lua_dfhack_println); lua_setglobal(state, "print"); - // Create and initialize the dfhack global + // Create the dfhack global + lua_newtable(state); + + // Create the metatable for exceptions lua_newtable(state); + lua_pushcfunction(state, dfhack_exception_tostring); + lua_setfield(state, -2, "__tostring"); + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN); + lua_setfield(state, -2, "exception"); + + // Initialize the dfhack global luaL_setfuncs(state, dfhack_funcs, 0); OpenPersistent(state); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 9435b393c..67652ff15 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -3,6 +3,10 @@ safecall = dfhack.safecall +function dfhack.pcall(f, ...) + return xpcall(f, dfhack.onerror, ...) +end + function mkmodule(module,env) local pkg = package.loaded[module] if pkg == nil then From 5ce0e7dbf268cca19e4f5360147ff3d208cd12ca Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Wed, 4 Apr 2012 14:58:53 +0200 Subject: [PATCH 20/28] zone tool: added lots of filters, added mass-assigning of nestboxes --- plugins/zone.cpp | 775 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 694 insertions(+), 81 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 03c38b101..657b2bd3e 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -4,11 +4,6 @@ // - dump info about pastures, pastured animals, count non-pastured tame animals, print gender info // - help finding caged dwarves? (maybe even allow to build their cages for fast release) // - dump info about caged goblins, animals, ... -// - full automation of handling mini-pastures over nestboxes: -// go through all pens, check if they are empty and placed over a nestbox -// find female tame egg-layer who is not assigned to another pen (allowing to grab them from cages would be nice) -// maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds -// assign to that pasture // - allow to mark old animals for slaughter automatically? // that should include a check to ensure that at least one male and one female remain for breeding // allow some fine-tuning like how many males/females should per race should be left alive @@ -22,6 +17,10 @@ // - assign single selected creature to a zone // - unassign single creature under cursor from current zone // - pitting own dwarves :) +// - full automation of handling mini-pastures over nestboxes: +// go through all pens, check if they are empty and placed over a nestbox +// find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture +// maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds #include #include @@ -54,6 +53,7 @@ using namespace std; #include "df/building_chainst.h" #include "df/general_ref_building_civzone_assignedst.h" #include +#include using std::vector; using std::string; @@ -75,22 +75,66 @@ const string zone_help = "Allows easier management of pens/pastures and pits.\n" "Options:\n" " set - set zone under cursor as default for future assigns\n" - " assign - assign selected creature to a pen or pit\n" - " (can be followed by valid zone id which will then be set)\n" + " assign - assign creature(s) to a pen or pit\n" + " if no filters are used, a single unit must be selected.\n" + " can be followed by valid zone id which will then be set.\n" " unassign - unassign selected creature from it's zone\n" + " autonestbox - assign unpastured female egg-layers to nestbox zones\n" + " requires you to create 1-tile pastures above nestboxes\n" + " filters (except count) will be ignored currently\n" " uinfo - print info about selected unit\n" " zinfo - print info about zone(s) under cursor\n" - //" cinfo - print info about (built) cage under cursor\n" - //" rinfo - print info about restraint under cursor\n" - " all - in combination with 'zinfo' or 'cinfo': prints all zones/units\n" " verbose - print some more info, mostly useless debug stuff\n" - "Example:\n" + " filters - print list of supported filters\n" + " examples - print some usage examples\n"; + +const string zone_help_filters = + "Filters (to be used in combination with 'all' or 'count'):\n" + " all - in combinations with zinfo/uinfo: print all zones/units\n" + " in combination with assign: process all units\n" + " should be used in combination with further filters\n" + " count - must be followed by number. process X units\n" + " should be used in combination with further filters\n" + " race - must be followed by a race raw id (e.g. BIRD_TURKEY)\n" + " unassigned - not assigned to zone, chain or built cage\n" + " caged - in a built cage\n" + " uncaged - not in cage\n" + " foreign - not of own civilization (i.e. own fortress)\n" + " own - from own civilization (fortress)\n" + " war - trained war creature\n" + " tamed - tamed\n" + " trained - obvious\n" + " untrained - obvious\n" + " male - obvious\n" + " female - obvious\n" + " egglayer - race lays eggs (use together with 'female')\n" + " grazer - obvious\n" + " milkable - race is milkable (use together with 'female')\n" + " minage - minimum age. must be followed by number\n" + " maxage - maximum age. must be followed by number\n"; + +const string zone_help_examples = + "Example for assigning single units:\n" " (ingame) move cursor to a pen/pasture or pit zone\n" " (dfhack) 'zone set' to use this zone for future assignments\n" " (dfhack) map 'zone assign' to a hotkey of your choice\n" - " (ingame) select a unit with 'v', 'k' or in the unit list or inside a cage\n" + " (ingame) select unit with 'v', 'k' or from unit list or inside a cage\n" " (ingame) press hotkey to assign unit to it's new home (or pit)\n" - ; + "Examples for assigning with filters:\n" + " (this assumes you have already set up a target zone)\n" + " zone assign all own grazer maxage 10\n" + " zone assign count 5 own female milkable\n" + " zone assign all own race DWARF maxage 2\n" + " throw all useless kids into a pit :)\n" + "Notes:\n" + " Assigning per filters ignores built cages and chains currently.\n" + " Usually you should always use the filter 'own' (which implies tame)\n" + " unless you want to use the zone tool for pitting hostiles.\n" + " 'own' ignores own dwarves unless you specify 'race DWARF'\n" + " (so it's safe to use 'assign all own' to one big pasture\n" + " if you want to have all your animals at the same place).\n" + " 'egglayer' and 'milkable' should be used together with 'female'\n" + " unless you have a mod with egg-laying male elves who give milk.\n"; DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) @@ -112,7 +156,8 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) /////////////// // Various small tool functions // probably many of these should be moved to Unit.h and Building.h sometime later... -int32_t getCreatureAge(df::unit* unit); + +int32_t getUnitAge(df::unit* unit); bool isTame(df::unit* unit); bool isTrained(df::unit* unit); bool isTrained(df::unit* creature); @@ -123,6 +168,7 @@ bool isActivityZone(df::building * building); bool isPenPasture(df::building * building); bool isPit(df::building * building); bool isActive(df::building * building); + int32_t findBuildingIndexById(int32_t id); int32_t findPenPitAtCursor(); int32_t findCageAtCursor(); @@ -135,7 +181,7 @@ void unitInfo(color_ostream & out, df::unit* creature, bool list_refs); void zoneInfo(color_ostream & out, df::building* building, bool list_refs); void cageInfo(color_ostream & out, df::building* building, bool list_refs); void chainInfo(color_ostream & out, df::building* building, bool list_refs); - +bool isBuiltCageAtPos(df::coord pos); int32_t getUnitAge(df::unit* unit) { @@ -236,30 +282,109 @@ bool isOwnCiv(df::unit* creature) return false; } +// check if creature belongs to the player's race +// (in combination with check for civ helps to filter out own dwarves) +bool isOwnRace(df::unit* creature) +{ + if(creature->race == ui->race_id) + return true; + else + return false; +} + +string getRaceName(df::unit* unit) +{ + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + return raw->creature_id; +} + +bool isEggLayer(df::unit* unit) +{ + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + size_t sizecas = raw->caste.size(); + for (size_t j = 0; j < sizecas;j++) + { + df::caste_raw *caste = raw->caste[j]; + if( caste->flags.is_set(caste_raw_flags::LAYS_EGGS) + || caste->flags.is_set(caste_raw_flags::LAYS_UNUSUAL_EGGS)) + return true; + } + return false; +} + +bool isGrazer(df::unit* unit) +{ + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + size_t sizecas = raw->caste.size(); + for (size_t j = 0; j < sizecas;j++) + { + df::caste_raw *caste = raw->caste[j]; + if(caste->flags.is_set(caste_raw_flags::GRAZER)) + return true; + } + return false; +} + +bool isMilkable(df::unit* unit) +{ + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + size_t sizecas = raw->caste.size(); + for (size_t j = 0; j < sizecas;j++) + { + df::caste_raw *caste = raw->caste[j]; + if(caste->flags.is_set(caste_raw_flags::MILKABLE)) + return true; + } + return false; +} + +bool isMale(df::unit* unit) +{ + if(unit->sex==1) + return true; + else + return false; +} +bool isFemale(df::unit* unit) +{ + if(unit->sex==0) + return true; + else + return false; +} + // dump some unit info -void unitInfo(color_ostream & out, df::unit* unit, bool list_refs = false) +void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) { if(isDead(unit)) return; - out.print("Unit %d", unit->id); //race %d, civ %d,", creature->race, creature->civ_id + out.print("Unit %d ", unit->id); //race %d, civ %d,", creature->race, creature->civ_id if(unit->name.has_name) - out << ", name: " << unit->name.first_name << " " << unit->name.nickname; - df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; - out << " " << raw->creature_id << " ("; + { + // units given a nick with the rename tool might not have a first name (animals etc) + string firstname = unit->name.first_name; + if(firstname.size() > 0) + { + firstname[0] = toupper(firstname[0]); + out << "Name: " << firstname; + } + if(unit->name.nickname.size() > 0) + out << " '" << unit->name.nickname << "'"; + out << ", "; + } + out << getRaceName(unit) << " ("; switch(unit->sex) { - case -1: - out << "n/a"; - break; case 0: out << "female"; break; case 1: out << "male"; break; + case -1: default: - out << "INVALID!"; + out << "n/a"; break; } out << ")"; @@ -274,9 +399,19 @@ void unitInfo(color_ostream & out, df::unit* unit, bool list_refs = false) if(isHunter(unit)) out << ", hunter"; + if(verbose) + { + //out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ")" << endl; + if(isEggLayer(unit)) + out << ", egglayer"; + if(isGrazer(unit)) + out << ", grazer"; + if(isMilkable(unit)) + out << ", milkable"; + } out << endl; - if(!list_refs) + if(!verbose) return; //out << "number of refs: " << creature->refs.size() << endl; @@ -502,6 +637,161 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef() return newref; } +// check if assigned to pen, pit, (built) cage or chain +// note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence) +// animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead +// removing them from cages on stockpiles is no problem even without clearing the ref +// and usually it will be desired behavior to do so. +bool isAssigned(df::unit* unit) +{ + bool assigned = false; + for (size_t r=0; r < unit->refs.size(); r++) + { + df::general_ref * ref = unit->refs[r]; + auto rtype = ref->getType(); + if( rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED + || rtype == df::general_ref_type::BUILDING_CAGED + || rtype == df::general_ref_type::BUILDING_CHAIN + || (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isBuiltCageAtPos(unit->pos)) + ) + { + assigned = true; + break; + } + } + return assigned; +} + + +// check if assigned to a chain or built cage +// (need to check if the ref needs to be removed, until then touching them is forbidden) +bool isChained(df::unit* unit) +{ + bool contained = false; + for (size_t r=0; r < unit->refs.size(); r++) + { + df::general_ref * ref = unit->refs[r]; + auto rtype = ref->getType(); + if(rtype == df::general_ref_type::BUILDING_CHAIN) + { + contained = true; + break; + } + } + return contained; +} + + + +// check if contained in item (e.g. animals in cages) +bool isContainedInItem(df::unit* unit) +{ + bool contained = false; + for (size_t r=0; r < unit->refs.size(); r++) + { + df::general_ref * ref = unit->refs[r]; + auto rtype = ref->getType(); + if(rtype == df::general_ref_type::CONTAINED_IN_ITEM) + { + contained = true; + break; + } + } + return contained; +} + +// check a map position for a built cage +// animals in cages are CONTAINED_IN_ITEM, no matter if they are on a stockpile or inside a built cage +// if they are on animal stockpiles they should count as unassigned to allow pasturing them +// if they are inside built cages they should be ignored in case the cage is a zoo or linked to a lever or whatever +bool isBuiltCageAtPos(df::coord pos) +{ + bool cage = false; + for (size_t b=0; b < world->buildings.all.size(); b++) + { + df::building* building = world->buildings.all[b]; + if( building->getType() == building_type::Cage + && building->x1 == pos.x + && building->y1 == pos.y + && building->z == pos.z ) + { + cage = true; + break; + } + } + return cage; +} + +bool isNestboxAtPos(int32_t x, int32_t y, int32_t z) +{ + bool found = false; + for (size_t b=0; b < world->buildings.all.size(); b++) + { + df::building* building = world->buildings.all[b]; + if( building->getType() == building_type::NestBox + && building->x1 == x + && building->y1 == y + && building->z == z ) + { + found = true; + break; + } + } + return found; +} + +bool isEmptyPasture(df::building* building) +{ + if(!isPenPasture(building)) + return false; + df::building_civzonest * civ = (df::building_civzonest *) building; + if(civ->assigned_creature.size() == 0) + return true; + else + return false; +} + +df::building* findFreeNestboxZone() +{ + df::building * free_building = NULL; + + //df::unit * free_unit = findFreeEgglayer(); + + bool cage = false; + for (size_t b=0; b < world->buildings.all.size(); b++) + { + df::building* building = world->buildings.all[b]; + if( isEmptyPasture(building) && + isNestboxAtPos(building->x1, building->y1, building->z)) + { + free_building = building; + break; + } + } + return free_building; +} + +df::unit * findFreeEgglayer() +{ + df::unit* free_unit = NULL; + for (size_t i=0; i < world->units.all.size(); i++) + { + df::unit* unit = world->units.all[i]; + if( isFemale(unit) + && isTame(unit) + && isOwnCiv(unit) + && isEggLayer(unit) + && !isAssigned(unit) + ) + { + free_unit = unit; + break; + } + } + return free_unit; +} + + // check if unit is already assigned to a zone, remove that ref from unit and old zone // returns false if no pasture information was found // helps as workaround for http://www.bay12games.com/dwarves/mantisbt/view.php?id=4475 by the way @@ -561,9 +851,14 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building } // check if unit is already pastured, remove that ref from unit and old pasture + // testing showed that this only seems to be necessary for pastured creatures + // if they are in cages on stockpiles the game unassigns them automatically + // (need to check if that is also true for chains and built cages) + bool cleared_old = unassignUnitFromZone(unit); + if(verbose) { - if(unassignUnitFromZone(unit)) + if(cleared_old) out << "old zone info cleared."; else out << "no old zone info found."; @@ -588,9 +883,36 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building return CR_OK; } +command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) +{ + out << "sorry. assigning to cages is not possible yet." << endl; + return CR_WRONG_USAGE; +} + +command_result assignUnitToChain(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) +{ + out << "sorry. assigning to chains is not possible yet." << endl; + return CR_WRONG_USAGE; +} + +command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) +{ + command_result result = CR_WRONG_USAGE; + + if(isActivityZone(building)) + result = assignUnitToZone(out, unit, building, verbose); + else if(isCage(building)) + result = assignUnitToCage(out, unit, building, verbose); + else if(isChain(building)) + result = assignUnitToChain(out, unit, building, verbose); + else + out << "Cannot assign units to this type of building!" << endl; + + return result; +} // dump some zone info -void zoneInfo(color_ostream & out, df::building* building, bool list_refs = false) +void zoneInfo(color_ostream & out, df::building* building, bool verbose = false) { if(building->getType()!= building_type::Civzone) return; @@ -616,8 +938,6 @@ void zoneInfo(color_ostream & out, df::building* building, bool list_refs = fals else out << "not active"; - // look at zone flags, ignore fishing, water, hospital, ... - // in fact, only deal with pits and pens if(civ->zone_flags.bits.pen_pasture) out << ", pen/pasture"; else if (civ->zone_flags.bits.pit_pond && civ->pit_flags==0) @@ -625,6 +945,12 @@ void zoneInfo(color_ostream & out, df::building* building, bool list_refs = fals else return; out << endl; + out << "x1:" <x1 + << " x2:" <x2 + << " y1:" <y1 + << " y2:" <y2 + << " z:" <z + << endl; int32_t creaturecount = civ->assigned_creature.size(); out << "Creatures in this zone: " << creaturecount << endl; @@ -639,13 +965,13 @@ void zoneInfo(color_ostream & out, df::building* building, bool list_refs = fals if(creature->id != cindex) continue; - unitInfo(out, creature, false); + unitInfo(out, creature, verbose); } } } // dump some cage info -void cageInfo(color_ostream & out, df::building* building, bool list_refs = false) +void cageInfo(color_ostream & out, df::building* building, bool verbose = false) { if(!isCage(building)) return; @@ -659,6 +985,11 @@ void cageInfo(color_ostream & out, df::building* building, bool list_refs = fals building->getType()); out.print("\n"); + out << "x:" << building->x1 + << " y:" << building->y1 + << " z:" << building->z + << endl; + df::building_cagest * cage = (df::building_cagest*) building; int32_t creaturecount = cage->assigned_creature.size(); out << "Creatures in this cage: " << creaturecount << endl; @@ -673,12 +1004,11 @@ void cageInfo(color_ostream & out, df::building* building, bool list_refs = fals if(creature->id != cindex) continue; - unitInfo(out, creature, false); + unitInfo(out, creature, verbose); } } } - // dump some chain/restraint info void chainInfo(color_ostream & out, df::building* building, bool list_refs = false) { @@ -716,12 +1046,39 @@ command_result df_zone (color_ostream &out, vector & parameters) bool zone_info = false; //bool cage_info = false; //bool chain_info = false; + + bool find_unassigned = false; + bool find_caged = false; + bool find_uncaged = false; + bool find_foreign = false; + bool find_untrained = false; + //bool find_trained = false; + bool find_war = false; + bool find_own = false; + bool find_tame = false; + bool find_male = false; + bool find_female = false; + bool find_egglayer = false; + bool find_grazer = false; + bool find_milkable = false; + + bool find_agemin = false; + bool find_agemax = false; + int target_agemin = 0; + int target_agemax = 0; + + bool find_count = false; + size_t target_count = 0; + + bool find_race = false; + string target_race = ""; bool zone_assign = false; bool zone_unassign = false; bool zone_set = false; bool verbose = false; bool all = false; + bool auto_nestbox = false; static int target_zone = -1; for (size_t i = 0; i < parameters.size(); i++) @@ -733,6 +1090,16 @@ command_result df_zone (color_ostream &out, vector & parameters) out << zone_help << endl; return CR_OK; } + if (p == "filters") + { + out << zone_help_filters << endl; + return CR_OK; + } + if (p == "examples") + { + out << zone_help_examples << endl; + return CR_OK; + } else if(p == "zinfo") { zone_info = true; @@ -741,14 +1108,6 @@ command_result df_zone (color_ostream &out, vector & parameters) { unit_info = true; } - //else if(p == "cinfo") - //{ - // cage_info = true; - //} - //else if(p == "rinfo") - //{ - // chain_info = true; - //} else if(p == "verbose") { verbose = true; @@ -781,6 +1140,158 @@ command_result df_zone (color_ostream &out, vector & parameters) zone_assign = true; } } + else if(p == "race") + { + if(i == parameters.size()-1) + { + out.printerr("No race id specified!"); + return CR_WRONG_USAGE; + } + else + { + target_race = parameters[i+1]; + i++; + out << "Filter by race " << target_race << endl; + find_race = true; + } + } + else if(p == "foreign") + { + out << "Filter by 'foreign' (i.e. not from the fortress civ, can be a dwarf)." << endl; + find_foreign = true; + } + else if(p == "unassigned") + { + out << "Filter by 'unassigned'." << endl; + find_unassigned = true; + } + else if(p == "caged") + { + out << "Filter by 'caged' (ignores built cages)." << endl; + find_caged = true; + } + else if(p == "uncaged") + { + out << "Filter by 'uncaged'." << endl; + find_uncaged = true; + } + else if(p == "untrained") + { + out << "Filter by 'untrained'." << endl; + find_untrained = true; + } + else if(p == "war") + { + out << "Filter by 'trained war creature'." << endl; + find_war = true; + } + else if(p == "own") + { + out << "Filter by 'own civilization'." << endl; + find_own = true; + } + else if(p == "tame") + { + out << "Filter by 'tame'." << endl; + find_tame = true; + } + else if(p == "autonestbox") + { + out << "Auto-assign female tame owned egg-layers to free nestboxes." << endl; + auto_nestbox = true; + } + else if(p == "count") + { + if(i == parameters.size()-1) + { + out.printerr("No count specified!"); + return CR_WRONG_USAGE; + } + else + { + stringstream ss(parameters[i+1]); + i++; + ss >> target_count; + if(target_count <= 0) + { + out.printerr("Invalid count specified (must be > 0)!"); + return CR_WRONG_USAGE; + } + find_count = true; + out << "Process up to " << target_count << " units." << endl; + } + } + else if(p == "age") + { + if(i == parameters.size()-1) + { + out.printerr("No age specified!"); + return CR_WRONG_USAGE; + } + else + { + stringstream ss(parameters[i+1]); + i++; + ss >> target_agemin; + ss >> target_agemax; + find_agemin = true; + find_agemax = true; + out << "Filter by exact age of " << target_agemin << endl; + } + } + else if(p == "minage") + { + if(i == parameters.size()-1) + { + out.printerr("No age specified!"); + return CR_WRONG_USAGE; + } + else + { + stringstream ss(parameters[i+1]); + i++; + ss >> target_agemin; + find_agemin = true; + out << "Filter by minimum age of " << target_agemin << endl; + } + } + else if(p == "maxage") + { + if(i == parameters.size()-1) + { + out.printerr("No age specified!"); + return CR_WRONG_USAGE; + } + else + { + stringstream ss(parameters[i+1]); + i++; + ss >> target_agemax; + find_agemax = true; + out << "Filter by maximum age of " << target_agemax << endl; + } + } + else if(p == "male") + { + find_male = true; + } + else if(p == "female") + { + find_female = true; + } + else if(p == "egglayer") + { + find_egglayer = true; + } + else if(p == "grazer") + { + find_grazer = true; + } + else if(p == "milkable") + { + find_milkable = true; + } + else if(p == "set") { zone_set = true; @@ -790,7 +1301,10 @@ command_result df_zone (color_ostream &out, vector & parameters) all = true; } else + { + out << "Unknown command: " << p << endl; return CR_WRONG_USAGE; + } } if((zone_info && !all) || zone_set) @@ -802,25 +1316,54 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_FAILURE; } - // give info on unit(s) - if(unit_info) + // search for male and female is exclusive, so drop the flags if both are specified + if(find_male && find_female) { - if(all) + find_male=false; + find_female=false; + } + + // try to cope with user dumbness + if(target_agemin > target_agemax || target_agemax < target_agemin) + { + out << "Invalid values for minage/maxage specified! I'll swap them." << endl; + int oldmin = target_agemin; + target_agemin = target_agemax; + target_agemax = oldmin; + } + + // auto-assign to empty nestboxes. this requires an empty 1x1 pen/pasture zone placed over a nestbox + // currently it will not be checked if the nestbox is already claimed by another egglayer + if(auto_nestbox) + { + bool stop = false; + size_t processed = 0; + do { - // todo: this should really be filtered somehow (prints even dead units etc) - for(size_t c=0; cunits.all.size(); c++) + df::building * free_building = findFreeNestboxZone(); + df::unit * free_unit = findFreeEgglayer(); + if(free_building && free_unit) { - df::unit *unit = world->units.all[c]; - unitInfo(out, unit, verbose); + command_result result = assignUnitToBuilding(out, free_unit, free_building, verbose); + if(result != CR_OK) + return result; + processed ++; + if(find_count && processed >= target_count) + stop = true; } - } - else - { - df::unit *unit = getSelectedUnit(out); - if (!unit) - return CR_FAILURE; - unitInfo(out, unit, verbose); - } + else + { + stop = true; + if(free_unit && !free_building) + { + out << "Not enough free nestbox zones found!" << endl; + out << "You can check how many more you need with:" << endl; + out << "'zone uinfo all unassigned own female egglayer'" << endl; + out << "Or simply build some more and use 'zone autonestbox' again." << endl; + } + } + } while (!stop); + out << processed << " units assigned to their new nestboxes." << endl; return CR_OK; } @@ -859,27 +1402,102 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } - // de-assign from pen or pit - if(zone_unassign) + // assign to pen or pit + if(zone_assign || unit_info) { - // must have unit selected - df::unit *unit = getSelectedUnit(out); - if (!unit) + df::building * building; + if(zone_assign) { - out << "No unit selected." << endl; - return CR_WRONG_USAGE; + // try to get building index from the id + int32_t index = findBuildingIndexById(target_zone); + if(index == -1) + { + out << "Invalid building id." << endl; + target_zone = -1; + return CR_WRONG_USAGE; + } + building = world->buildings.all.at(index); } - // remove assignment reference from unit and old zone - if(unassignUnitFromZone(unit)) - out << "Unit unassigned." << endl; + if(all || find_count) + { + size_t count = 0; + for(size_t c = 0; c < world->units.all.size(); c++) + { + df::unit *unit = world->units.all[c]; + if(find_race && getRaceName(unit) != target_race) + continue; + // ignore own dwarves by default + if(isOwnCiv(unit) && isOwnRace(unit)) + { + if(!find_race) + { + continue; + } + } + + if( (find_unassigned && isAssigned(unit)) + || (isContainedInItem(unit) && (find_uncaged || isBuiltCageAtPos(unit->pos))) + || (isChained(unit)) + || (find_caged && !isContainedInItem(unit)) + || (find_own && !isOwnCiv(unit)) + || (find_foreign && isOwnCiv(unit)) + || (find_tame && !isTame(unit)) + || (find_untrained && isTrained(unit)) + || (find_war && !isWar(unit)) + || (find_agemin && getUnitAge(unit)target_agemax) + || (find_grazer && !isGrazer(unit)) + || (find_egglayer && !isEggLayer(unit)) + || (find_milkable && !isMilkable(unit)) + || (find_male && !isMale(unit)) + || (find_female && !isFemale(unit)) + ) + continue; + + if(unit_info) + { + if(unit->pos.x <0 || unit->pos.y<0 || unit->pos.z<0) + out << "invalid unit pos" << endl; + unitInfo(out, unit, verbose); + } + else + { + command_result result = assignUnitToBuilding(out, unit, building, verbose); + if(result != CR_OK) + return result; + } + count++; + if(find_count && count >= target_count) + break; + } + out << "Processed creatures: " << count << endl; + } else - out << "Unit is not assigned to a zone!" << endl; - return CR_OK; + { + // must have unit selected + df::unit *unit = getSelectedUnit(out); + if (!unit) + { + out << "No unit selected." << endl; + return CR_WRONG_USAGE; + } + + if(unit_info) + { + unitInfo(out, unit, verbose); + return CR_OK; + } + + return assignUnitToBuilding(out, unit, building, verbose); + } } - // assign to pen or pit - if(zone_assign) + // de-assign from pen or pit + // using the zone tool to free creatures from cages or chains + // is pointless imo since that is already quite easy using the ingame UI. + // but it's easy to implement so I might as well add it later + if(zone_unassign) { // must have unit selected df::unit *unit = getSelectedUnit(out); @@ -889,17 +1507,12 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_WRONG_USAGE; } - // try to get building index from the id - int32_t index = findBuildingIndexById(target_zone); - if(index == -1) - { - out << "Invalid building id." << endl; - target_zone = -1; - return CR_WRONG_USAGE; - } - - df::building * building = world->buildings.all.at(index); - return assignUnitToZone(out, unit, building, verbose); + // remove assignment reference from unit and old zone + if(unassignUnitFromZone(unit)) + out << "Unit unassigned." << endl; + else + out << "Unit is not assigned to an activity zone!" << endl; + return CR_OK; } return CR_OK; From 8fbdb5fd223bd29cdfad68dd7bc39130ce0cf8bc Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Wed, 4 Apr 2012 15:37:14 +0200 Subject: [PATCH 21/28] updated readme.rst with new filters/commands for the zone plugin --- README.rst | 55 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 1ee00cc6b..4f1b3355d 100644 --- a/README.rst +++ b/README.rst @@ -805,15 +805,50 @@ Helps a bit with managing activity zones (pens, pastures and pits). Options: -------- -:set: Set zone under cursor as default for future assigns. -:assign: Assign selected unit to the pen or pit marked with the 'set' command. - Can also be followed by a valid zone id which will be set instead. -:unassign: Unassign selected creature from it's zone. -:uinfo: Print info about the selected unit. -:zinfo: Print info about zone(s) under the cursor (also lists chains and cages). -:all: In combination with 'zinfo' or 'cinfo': print info about all zones/units on the map. -:verbose: Print some more info. +:set: Set zone under cursor as default for future assigns. +:assign: Assign unit(s) to the pen or pit marked with the 'set' command. If no filters are set a unit must be selected in the in-game ui. Can also be followed by a valid zone id which will be set instead. +:unassign: Unassign selected creature from it's zone. +:autonestbox: Assign all (unless count is specified) unpastured female egg-layers to empty pens which contain a nestbox. If the pen is bigger than 1x1 the nestbox must be placed at the top left corner to be recognized. Only 1 unit will be assigned per pen. +:uinfo: Print info about the selected unit. +:zinfo: Print info about zone(s) under the cursor (also lists chains and cages). +:all: In combination with 'zinfo' or 'cinfo': print info about all zones/units on the map. +:verbose: Print some more info. +:filters: Print list of valid filter options. +:examples: Print some usage examples. -Usage ------ +Filters: +-------- +:all: Process all units (to be used with additional filters). +:count: Must be followed by a number. Process only n units (to be used with additional filters). +:race: Must be followed by a race raw id (e.g. BIRD_TURKEY, ALPACA etc). +:unassigned: Not assigned to zone, chain or built cage. +:caged: In a built cage. +:uncaged: Not in a cage (in case you want your stockpiles to be left alone). +:foreign: Not of own civilization (i.e. own fortress). +:own: From own civilization (i.e. own fortress). +:war: Trained war creature. +:tamed: Creature is tame. +:trained: Creature is trained. +:untrained: Creature is untrained. +:male: Creature is male. +:female: Creature is female. +:egglayer: Race lays eggs. +:grazer: Race is a grazer. +:milkable: Race is milkable. +:minage: Minimum age. Must be followed by number. +:maxage: Maximum age. Must be followed by number. + +Usage with single units +----------------------- One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units on the map (in 'v' or 'k' mode), in the unit list or from inside cages and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way. + +Usage with filters +------------------ +All filters can be used together with the 'assign' command. The only restriction is that it's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. Usually you should always use the filter 'own' (which implies tame) unless you want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you specify 'race DWARF' (so it's safe to use 'assign all own' to one big pasture if you want to have all your animals at the same place). 'egglayer' and 'milkable' should be used together with 'female' unless you have a mod with egg-laying male elves who give milk or whatever. + +``zone assign all own grazer maxage 10`` + Assign all own grazers who are up to 10 years old to the selected pasture. +``zone assign count 5 own female milkable`` + Assign up to 5 own female milkable creatures to the selected pasture. +``zone assign all own race DWARF maxage 2`` + Throw all useless kids into a pit :) From b8421c4e1abe8eeb05ecb4908ed0e4f9d5318be3 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Wed, 4 Apr 2012 15:56:14 +0200 Subject: [PATCH 22/28] readme.rst: removed duplicate line --- README.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 4f1b3355d..8c450e287 100644 --- a/README.rst +++ b/README.rst @@ -809,9 +809,8 @@ Options: :assign: Assign unit(s) to the pen or pit marked with the 'set' command. If no filters are set a unit must be selected in the in-game ui. Can also be followed by a valid zone id which will be set instead. :unassign: Unassign selected creature from it's zone. :autonestbox: Assign all (unless count is specified) unpastured female egg-layers to empty pens which contain a nestbox. If the pen is bigger than 1x1 the nestbox must be placed at the top left corner to be recognized. Only 1 unit will be assigned per pen. -:uinfo: Print info about the selected unit. -:zinfo: Print info about zone(s) under the cursor (also lists chains and cages). -:all: In combination with 'zinfo' or 'cinfo': print info about all zones/units on the map. +:uinfo: Print info about unit(s). If no filters are set a unit must be selected in the in-game ui. +:zinfo: Print info about zone(s). If no filters are set zones under the cursor are listed. :verbose: Print some more info. :filters: Print list of valid filter options. :examples: Print some usage examples. From 796bc18fb07e3a44252dd7d56307ea453e3ed943 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Wed, 4 Apr 2012 16:48:22 +0200 Subject: [PATCH 23/28] zone plugin: fixed bug when parsing options --- README.rst | 6 ++++-- plugins/zone.cpp | 30 ++++++++++++++++-------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index 8c450e287..4ede446bb 100644 --- a/README.rst +++ b/README.rst @@ -845,8 +845,10 @@ Usage with filters ------------------ All filters can be used together with the 'assign' command. The only restriction is that it's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. Usually you should always use the filter 'own' (which implies tame) unless you want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you specify 'race DWARF' (so it's safe to use 'assign all own' to one big pasture if you want to have all your animals at the same place). 'egglayer' and 'milkable' should be used together with 'female' unless you have a mod with egg-laying male elves who give milk or whatever. -``zone assign all own grazer maxage 10`` - Assign all own grazers who are up to 10 years old to the selected pasture. +``zone assign all own ALPACA minage 3 maxage 10`` + Assign all own alpacas who are between 3 and 10 years old to the selected pasture. +``zone assign all own caged grazer`` + Assign all own grazers who are sitting in cages on stockpiles (e.g. after buying them from merchants) to the selected pasture. ``zone assign count 5 own female milkable`` Assign up to 5 own female milkable creatures to the selected pasture. ``zone assign all own race DWARF maxage 2`` diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 657b2bd3e..5e712510c 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -1118,25 +1118,27 @@ command_result df_zone (color_ostream &out, vector & parameters) } else if(p == "assign") { - if(i == parameters.size()-1) + // if followed by another parameter, check if it's numeric + if(i < parameters.size()-1) { - if(target_zone == -1) - { - out.printerr("No zone id specified and current one is invalid!"); - return CR_WRONG_USAGE; - } - else + stringstream ss(parameters[i+1]); + int new_zone = -1; + ss >> new_zone; + if(new_zone != -1) { - out << "No zone id specified. Will try to use #" << target_zone << endl; - zone_assign = true; + i++; + target_zone = new_zone; + out << "Assign selected unit(s) to zone #" << target_zone <> target_zone; - out << "Assign selected unit to zone #" << target_zone < & parameters) { find_milkable = true; } - else if(p == "set") { zone_set = true; } else if(p == "all") { + out << "Filter: all" << endl; all = true; } else From 1b6f5f3f343d572be72a58ce49a11818986c52a4 Mon Sep 17 00:00:00 2001 From: Will Rogers Date: Wed, 4 Apr 2012 11:53:47 -0400 Subject: [PATCH 24/28] Add additional related data to ListJobSkills. Add the attributes for profession and unit_labor, and (re)name the protobuf messages JobSkillAttr, ProfessionAttr, and UnitLaborAttr to better reflect their content and distinguish them from e.g. the SkillInfo message included in BasicUnitInfo. --- library/RemoteTools.cpp | 31 +++++++++++++++++++++++++++---- library/proto/Basic.proto | 31 +++++++++++++++++++++++++------ library/proto/BasicApi.proto | 4 +++- 3 files changed, 55 insertions(+), 11 deletions(-) mode change 100644 => 100755 library/proto/BasicApi.proto diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 5103b7561..229e5a70c 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -447,18 +447,41 @@ static command_result ListEnums(color_ostream &stream, static command_result ListJobSkills(color_ostream &stream, const EmptyMessage *, ListJobSkillsOut *out) { - auto pf = out->mutable_value(); - + auto pf_skill = out->mutable_skill(); FOR_ENUM_ITEMS(job_skill, skill) { - auto item = pf->Add(); + auto item = pf_skill->Add(); item->set_id(skill); + item->set_key(ENUM_KEY_STR(job_skill, skill)); item->set_caption(ENUM_ATTR_STR(job_skill, caption, skill)); item->set_caption_noun(ENUM_ATTR_STR(job_skill, caption_noun, skill)); item->set_profession(ENUM_ATTR(job_skill, profession, skill)); item->set_labor(ENUM_ATTR(job_skill, labor, skill)); - item->set_type(ENUM_ATTR(job_skill, type, skill)); + item->set_type(ENUM_KEY_STR(job_skill_class, ENUM_ATTR(job_skill, type, skill))); + } + + auto pf_profession = out->mutable_profession(); + FOR_ENUM_ITEMS(profession, p) + { + auto item = pf_profession->Add(); + + item->set_id(p); + item->set_key(ENUM_KEY_STR(profession, p)); + item->set_caption(ENUM_ATTR_STR(profession, caption, p)); + item->set_military(ENUM_ATTR(profession, military, p)); + item->set_can_assign_labor(ENUM_ATTR(profession, can_assign_labor, p)); + item->set_parent(ENUM_ATTR(profession, parent, p)); + } + + auto pf_labor = out->mutable_labor(); + FOR_ENUM_ITEMS(unit_labor, labor) + { + auto item = pf_labor->Add(); + + item->set_id(labor); + item->set_key(ENUM_KEY_STR(unit_labor, labor)); + item->set_caption(ENUM_ATTR_STR(unit_labor, caption, labor)); } return CR_OK; diff --git a/library/proto/Basic.proto b/library/proto/Basic.proto index 1d22ea11c..246fba22e 100755 --- a/library/proto/Basic.proto +++ b/library/proto/Basic.proto @@ -69,15 +69,34 @@ message BasicMaterialInfoMask { optional bool reaction = 3 [default = false]; }; -message JobSkillInfo { +message JobSkillAttr { required int32 id = 1; + required string key = 2; - optional string caption = 2; - optional string caption_noun = 3; + optional string caption = 3; + optional string caption_noun = 4; - optional int32 profession = 4; - optional int32 labor = 5; - optional int32 type = 6; + optional int32 profession = 5; + optional int32 labor = 6; + optional string type = 7; +}; + +message ProfessionAttr { + required int32 id = 1; + required string key = 2; + + optional string caption = 3; + optional bool military = 4; + optional bool can_assign_labor = 5; + + optional int32 parent = 6; +}; + +message UnitLaborAttr { + required int32 id = 1; + required string key = 2; + + optional string caption = 3; }; message NameInfo { diff --git a/library/proto/BasicApi.proto b/library/proto/BasicApi.proto old mode 100644 new mode 100755 index bfbb076fa..3072f9cad --- a/library/proto/BasicApi.proto +++ b/library/proto/BasicApi.proto @@ -53,7 +53,9 @@ message ListEnumsOut { // RPC ListJobSkills : EmptyMessage -> ListJobSkillsOut message ListJobSkillsOut { - repeated JobSkillInfo value = 1; + repeated JobSkillAttr skill = 1; + repeated ProfessionAttr profession = 2; + repeated UnitLaborAttr labor = 3; }; // RPC ListMaterials : ListMaterialsIn -> ListMaterialsOut From aaffdd56d8c2684667082372ea4effd02d3f4b2a Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 4 Apr 2012 19:06:56 +0300 Subject: [PATCH 25/28] Fixes to all of the new stuff. --- plugins/Dfusion/luafiles/adv_tools/init.lua | 25 ++++++++------------- plugins/Dfusion/luafiles/common.lua | 8 ++----- plugins/Dfusion/luafiles/tools/init.lua | 6 ++++- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/plugins/Dfusion/luafiles/adv_tools/init.lua b/plugins/Dfusion/luafiles/adv_tools/init.lua index c6917a767..f5fd9a473 100644 --- a/plugins/Dfusion/luafiles/adv_tools/init.lua +++ b/plugins/Dfusion/luafiles/adv_tools/init.lua @@ -1,7 +1,7 @@ adv_tools= {} adv_tools.menu=MakeMenu() --TODO make every tool generic (work for both modes) -function adv_tools.reincarnate(swap_soul) --only for adventurer i guess, TODO soul swap... +function adv_tools.reincarnate(swap_soul) --only for adventurer i guess if swap_soul==nil then swap_soul=true end @@ -15,12 +15,15 @@ function adv_tools.reincarnate(swap_soul) --only for adventurer i guess, TODO so end local events=df.global.world.history.events local trg_hist_fig - for i=#events-1,0,-1 do + for i=#events-1,0,-1 do -- reverse search because almost always it will be last entry if df.history_event_hist_figure_diedst:is_instance(events[i]) then --print("is instance:"..i) if events[i].hfid==hist_fig.id then --print("Is same id:"..i) trg_hist_fig=events[i].slayer + if trg_hist_fig then + trg_hist_fig=df.historical_figure.find(trg_hist_fig) + end break end end @@ -28,23 +31,13 @@ function adv_tools.reincarnate(swap_soul) --only for adventurer i guess, TODO so if trg_hist_fig ==nil then error("Slayer not found") end - local trg_unit - for k,v in pairs(df.global.world.history.figures) do --maybe getting it by [] would be enought? - if v.id==trg_hist_fig then - trg_unit=v.unit_id - break - end - end + + local trg_unit=trg_hist_fig.unit_id if trg_unit==nil then error("Unit id not found!") end - local trg_unit_final - for k,v in pairs(df.global.world.units.all) do - if v.id==trg_unit then - trg_unit_final=v - break - end - end + local trg_unit_final=df.unit.find(trg_unit) + tools.change_adv(trg_unit_final) if swap_soul then --actually add a soul... t_soul=adv.status.current_soul diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index b74c6aa8d..7e41dc4e5 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -513,7 +513,7 @@ function getCreature() end function getNemesisId(unit) for k,v in pairs(unit.refs) do - if tostring(v._type)=="" then + if df.general_ref_is_nemesisst:is_instance(v) then return v.nemesis_id end end @@ -521,11 +521,7 @@ end function getNemesis(unit) local id=getNemesisId(unit) if id then - for k,v in pairs(df.global.world.nemesis.all) do - if id==v.id then - return v - end - end + return df.nemesis_record.find(id) end end function Allocate(size) diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index 27624e0a9..e690e9412 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -136,7 +136,11 @@ function tools.change_adv(unit,nemesis) if nem then nem.flags[0]=true nem.flags[2]=true - df.global.ui_advmode.player_id=nem.id + for k,v in pairs(df.global.world.nemesis.all) do + if v.id==nem.id then + df.global.ui_advmode.player_id=k + end + end else error("Current unit does not have nemesis record, further working not guaranteed") end From 7fa4ffeb9eec9924698560669c24d44bac790938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 5 Apr 2012 02:39:38 +0200 Subject: [PATCH 26/28] Add a html version of the LUA API doc, package it. --- CMakeLists.txt | 2 +- Lua API.html | 580 +++++++++++++++++++++++++++++++++++++++++++++++++ Readme.html | 94 ++++---- fixTexts.sh | 1 + 4 files changed, 634 insertions(+), 43 deletions(-) create mode 100644 Lua API.html diff --git a/CMakeLists.txt b/CMakeLists.txt index 60286c925..3cbbe6a1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,7 +140,7 @@ add_subdirectory(depends) IF(BUILD_LIBRARY) add_subdirectory (library) ## install the default documentation files - install(FILES LICENSE Readme.html Compile.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) + install(FILES LICENSE Readme.html Compile.html Lua\ API.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) endif() #build the plugins diff --git a/Lua API.html b/Lua API.html new file mode 100644 index 000000000..86b9c00c8 --- /dev/null +++ b/Lua API.html @@ -0,0 +1,580 @@ + + + + + + +DFHack Lua API + + + +
+

DFHack Lua API

+ + +
+

DF structure wrapper

+

DF structures described by the xml files in library/xml are exported +to lua code as a tree of objects and functions under the df global, +which broadly maps to the df namespace in C++.

+

WARNING: The wrapper provides almost raw access to the memory +of the game, so mistakes in manipulating objects are as likely to +crash the game as equivalent plain C++ code would be. E.g. NULL +pointer access is safely detected, but dangling pointers aren't.

+

Objects managed by the wrapper can be broadly classified into the following groups:

+
    +
  1. Typed object pointers (references).

    +

    References represent objects in DF memory with a known type.

    +

    In addition to fields and methods defined by the wrapped type, +every reference has some built-in properties and methods.

    +
  2. +
  3. Untyped pointers

    +

    Represented as lightuserdata.

    +

    In assignment to a pointer NULL can be represented either as +nil, or a NULL lightuserdata; reading a NULL pointer field +returns nil.

    +
  4. +
  5. Named types

    +

    Objects in the df tree that represent identity of struct, class, +enum and bitfield types. They host nested named types, static +methods, builtin properties & methods, and, for enums and bitfields, +the bi-directional mapping between key names and values.

    +
  6. +
  7. The global object

    +

    df.global corresponds to the df::global namespace, and +behaves as a mix between a named type and a reference, containing +both nested types and fields corresponding to global symbols.

    +
  8. +
+

In addition to the global object and top-level types the df +global also contains a few global builtin utility functions.

+
+

Typed object references

+

The underlying primitive lua object is userdata with a metatable. +Every structured field access produces a new userdata instance.

+

All typed objects have the following built-in features:

+
    +
  • ref1 == ref2, tostring(ref)

    +

    References implement equality by type & pointer value, and string conversion.

    +
  • +
  • pairs(ref)

    +

    Returns an iterator for the sequence of actual C++ field names +and values. Fields are enumerated in memory order. Methods and +lua wrapper properties are not included in the iteration.

    +
  • +
  • ref._kind

    +

    Returns one of: primitive, struct, container, +or bitfield, as appropriate for the referenced object.

    +
  • +
  • ref._type

    +

    Returns the named type object or a string that represents +the referenced object type.

    +
  • +
  • ref:sizeof()

    +

    Returns size, address

    +
  • +
  • ref:new()

    +

    Allocates a new instance of the same type, and copies data +from the current object.

    +
  • +
  • ref:delete()

    +

    Destroys the object with the C++ delete operator. +If destructor is not available, returns false.

    +

    WARNING: the lua reference object remains as a dangling +pointer, like a raw C++ pointer would.

    +
  • +
  • ref:assign(object)

    +

    Assigns data from object to ref. Object must either be another +ref of a compatible type, or a lua table; in the latter case +special recursive assignment rules are applied.

    +
  • +
  • ref:_displace(index[,step])

    +

    Returns a new reference with the pointer adjusted by index*step. +Step defaults to the natural object size.

    +
  • +
+
+

Primitive references

+

References of the _kind 'primitive' are used for objects +that don't fit any of the other reference types. Such +references can only appear as a value of a pointer field, +or as a result of calling the _field() method.

+

They behave as structs with one field value of the right type.

+
+
+

Struct references

+

Struct references are used for class and struct objects.

+

They implement the following features:

+
    +
  • ref.field, ref.field = value

    +

    Valid fields of the structure may be accessed by subscript.

    +

    In case of inheritance, superclass fields have precedence +over the subclass, but fields shadowed in this way can still +be accessed as ref['subclasstype.field'].

    +

    This shadowing order is necessary because vtable-based classes +are automatically exposed in their exact type, and the reverse +rule would make access to superclass fields unreliable.

    +
  • +
  • ref._field(field)

    +

    Returns a reference to a valid field. That is, unlike regular +subscript, it returns a pointer reference even for primitive +typed fields.

    +
  • +
  • ref:vmethod(args...)

    +

    Named virtual methods are also exposed, subject to the same +shadowing rules.

    +
  • +
  • pairs(ref)

    +

    Enumerates all real fields (but not methods) in memory +(= declaration) order.

    +
  • +
+
+
+

Container references

+

Containers represent vectors and arrays, possibly resizable.

+

A container field can associate an enum to the container +reference, which allows accessing elements using string keys +instead of numerical indices.

+

Implemented features:

+
    +
  • ref._enum

    +

    If the container has an associated enum, returns the matching +named type object.

    +
  • +
  • #ref

    +

    Returns the length of the container.

    +
  • +
  • ref[index]

    +

    Accesses the container element, using either a 0-based numerical +index, or, if an enum is associated, a valid enum key string.

    +

    Accessing an invalid index is an error, but some container types +may return a default value, or auto-resize instead for convenience. +Currently this relaxed mode is implemented by df-flagarray aka BitArray.

    +
  • +
  • ref._field(index)

    +

    Like with structs, returns a pointer to the array element, if possible. +Flag and bit arrays cannot return such pointer, so it fails with an error.

    +
  • +
  • pairs(ref), ipairs(ref)

    +

    If the container has no associated enum, both behave identically, +iterating over numerical indices in order. Otherwise, ipairs still +uses numbers, while pairs tries to substitute enum keys whenever +possible.

    +
  • +
  • ref:resize(new_size)

    +

    Resizes the container if supported, or fails with an error.

    +
  • +
  • ref:insert(index,item)

    +

    Inserts a new item at the specified index. To add at the end, +use #ref as index.

    +
  • +
  • ref:erase(index)

    +

    Removes the element at the given valid index.

    +
  • +
+
+
+

Bitfield references

+

Bitfields behave like special fixed-size containers. +The _enum property points to the bitfield type.

+

Numerical indices correspond to the shift value, +and if a subfield occupies multiple bits, the +ipairs order would have a gap.

+
+
+
+

Named types

+

Named types are exposed in the df tree with names identical +to the C++ version, except for the :: vs . difference.

+

All types and the global object have the following features:

+
    +
  • type._kind

    +

    Evaluates to one of struct-type, class-type, enum-type, +bitfield-type or global.

    +
  • +
  • type._identity

    +

    Contains a lightuserdata pointing to the underlying +DFHack::type_instance object.

    +
  • +
+

Types excluding the global object also support:

+
    +
  • type:sizeof()

    +

    Returns the size of an object of the type.

    +
  • +
  • type:new()

    +

    Creates a new instance of an object of the type.

    +
  • +
  • type:is_instance(object)

    +

    Returns true if object is same or subclass type, or a reference +to an object of same or subclass type. It is permissible to pass +nil, NULL or non-wrapper value as object; in this case the +method returns nil.

    +
  • +
+

In addition to this, enum and bitfield types contain a +bi-directional mapping between key strings and values, and +also map _first_item and _last_item to the min and +max values.

+

Struct and class types with instance-vector attribute in the +xml have a type.find(key) function that wraps the find +method provided in C++.

+
+
+

Global functions

+

The df table itself contains the following functions and values:

+
    +
  • NULL, df.NULL

    +

    Contains the NULL lightuserdata.

    +
  • +
  • df.isnull(obj)

    +

    Evaluates to true if obj is nil or NULL; false otherwise.

    +
  • +
  • df.isvalid(obj[,allow_null])

    +

    For supported objects returns one of type, voidptr, ref.

    +

    If allow_null is true, and obj is nil or NULL, returns null.

    +

    Otherwise returns nil.

    +
  • +
  • df.sizeof(obj)

    +

    For types and refs identical to obj:sizeof(). +For lightuserdata returns nil, address

    +
  • +
  • df.new(obj), df.delete(obj), df.assign(obj, obj2)

    +

    Equivalent to using the matching methods of obj.

    +
  • +
  • df._displace(obj,index[,step])

    +

    For refs equivalent to the method, but also works with +lightuserdata (step is mandatory then).

    +
  • +
  • df.is_instance(type,obj)

    +

    Equivalent to the method, but also allows a reference as proxy for its type.

    +
  • +
+
+
+
+ + diff --git a/Readme.html b/Readme.html index 3fc8f27a4..4e1a06eb1 100644 --- a/Readme.html +++ b/Readme.html @@ -467,33 +467,35 @@ access DF memory and allow for easier development of new tools.

  • tubefill
  • -
  • vdig
  • -
  • vdigx
  • -
  • expdig
      -
    • Patterns:
    • -
    • Filters:
    • -
    • Examples:
    • +
    • digv
    • +
    • digvx
    • +
    • digl
    • +
    • diglx
    • +
    • digexp
    • -
    • digcircle
        -
      • Shape:
      • -
      • Action:
      • -
      • Designation types:
      • -
      • Examples:
      • +
      • digcircle
      • -
      • weather @@ -1330,21 +1332,29 @@ You can also paint only over tiles that match a set of properties (filter)

        tubefill

        Fills all the adamantine veins again. Veins that were empty will be filled in too, but might still trigger a demon invasion (this is a known bug).

        -
        -

        vdig

        +
        +

        digv

        Designates a whole vein for digging. Requires an active in-game cursor placed over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles).

        -
        -

        vdigx

        -

        A permanent alias for 'vdig x'.

        +
        +

        digvx

        +

        A permanent alias for 'digv x'.

        -
        -

        expdig

        +
        +

        digl

        +

        Designates layer stone for digging. Requires an active in-game cursor placed over a layer stone tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles). With the 'undo' option it will remove the dig designation instead (if you realize that digging out a 50 z-level deep layer was not such a good idea after all).

        +
        +
        +

        diglx

        +

        A permanent alias for 'digl x'.

        +
        +
        +

        digexp

        This command can be used for exploratory mining.

        See: http://df.magmawiki.com/index.php/DF2010:Exploratory_mining

        There are two variables that can be set: pattern and filter.

        -

        Patterns:

        +

        Patterns:

        @@ -1365,7 +1375,7 @@ You can also paint only over tiles that match a set of properties (filter)

        -

        Filters:

        +

        Filters:

        @@ -1381,7 +1391,7 @@ You can also paint only over tiles that match a set of properties (filter)

        After you have a pattern set, you can use 'expdig' to apply it again.

        -

        Examples:

        +

        Examples:

        designate the diagonal 5 patter over all hidden tiles:
          @@ -1402,11 +1412,11 @@ You can also paint only over tiles that match a set of properties (filter)

        -

        digcircle

        +

        digcircle

        A command for easy designation of filled and hollow circles. It has several types of options.

        @@ -1421,7 +1431,7 @@ It has several types of options.

        -

        Action:

        +

        Action:

        @@ -1436,7 +1446,7 @@ It has several types of options.

        -

        Designation types:

        +

        Designation types:

        @@ -1459,7 +1469,7 @@ It has several types of options.

        repeats with the last selected parameters.

        -

        Examples:

        +

        Examples:

        • 'digcircle filled 3' = Dig a filled circle with radius = 3.
        • 'digcircle' = Do it again.
        • @@ -1467,11 +1477,11 @@ repeats with the last selected parameters.

        -

        weather

        +

        weather

        Prints the current weather map by default.

        Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.

        @@ -1487,10 +1497,10 @@ repeats with the last selected parameters.

        -

        workflow

        +

        workflow

        Manage control of repeat jobs.

        -

        Usage

        +

        Usage

        workflow enable [option...], workflow disable [option...]

        If no options are specified, enables or disables the plugin. @@ -1511,7 +1521,7 @@ Otherwise, enables or disables any of the following options:

        -

        Function

        +

        Function

        When the plugin is enabled, it protects all repeat jobs from removal. If they do disappear due to any cause, they are immediately re-added to their workshop and suspended.

        @@ -1522,7 +1532,7 @@ the amount has to drop before jobs are resumed; this is intended to reduce the frequency of jobs being toggled.

        -

        Constraint examples

        +

        Constraint examples

        Keep metal bolts within 900-1000, and wood/bone within 150-200.

         workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
        @@ -1559,11 +1569,11 @@ the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material' command
         
        -

        mapexport

        +

        mapexport

        Export the current loaded map as a file. This will be eventually usable with visualizers.

        -

        dwarfexport

        +

        dwarfexport

        Export dwarves to RuneSmith-compatible XML.

        diff --git a/fixTexts.sh b/fixTexts.sh index 09689c8dc..669fa068b 100755 --- a/fixTexts.sh +++ b/fixTexts.sh @@ -2,3 +2,4 @@ rst2html README.rst > Readme.html rst2html COMPILE.rst > Compile.html rst2html DEVEL.rst > Devel.html +rst2html LUA_API.rst > Lua\ API.html From a6dd17919f9abce39113c0e271f4f8d331f02369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 5 Apr 2012 02:42:08 +0200 Subject: [PATCH 27/28] Update Lua API doc html. --- Lua API.html | 189 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 184 insertions(+), 5 deletions(-) diff --git a/Lua API.html b/Lua API.html index 86b9c00c8..25b6f06d6 100644 --- a/Lua API.html +++ b/Lua API.html @@ -330,6 +330,11 @@ ul.auto-toc {
      • Named types
      • Global functions
      • +
      • Recursive table assignment
      • + + +
      • DFHack utilities
      • @@ -430,17 +435,22 @@ or as a result of calling the _field() method.
        • ref.field, ref.field = value

          Valid fields of the structure may be accessed by subscript.

          -

          In case of inheritance, superclass fields have precedence +

          Primitive typed fields, i.e. numbers & strings, are converted +to/from matching lua values. The value of a pointer is a reference +to the target, or nil/NULL. Complex types are represented by +a reference to the field within the structure; unless recursive +lua table assignment is used, such fields can only be read.

          +

          NOTE: In case of inheritance, superclass fields have precedence over the subclass, but fields shadowed in this way can still -be accessed as ref['subclasstype.field'].

          -

          This shadowing order is necessary because vtable-based classes +be accessed as ref['subclasstype.field']. +This shadowing order is necessary because vtable-based classes are automatically exposed in their exact type, and the reverse rule would make access to superclass fields unreliable.

        • ref._field(field)

          Returns a reference to a valid field. That is, unlike regular -subscript, it returns a pointer reference even for primitive -typed fields.

          +subscript, it returns a reference to the field within the structure +even for primitive typed fields and pointers.

        • ref:vmethod(args...)

          Named virtual methods are also exposed, subject to the same @@ -574,6 +584,175 @@ lightuserdata (step is mandatory then).

        +
        +

        Recursive table assignment

        +

        Recursive assignment is invoked when a lua table is assigned +to a C++ object or field, i.e. one of:

        +
          +
        • ref:assign{...}
        • +
        • ref.field = {...}
        • +
        +

        The general mode of operation is that all fields of the table +are assigned to the fields of the target structure, roughly +emulating the following code:

        +
        +function rec_assign(ref,table)
        +    for key,value in pairs(table) do
        +        ref[key] = value
        +    end
        +end
        +
        +

        Since assigning a table to a field using = invokes the same +process, it is recursive.

        +

        There are however some variations to this process depending +on the type of the field being assigned to:

        +
          +
        1. If the table contains an assign field, it is +applied first, using the ref:assign(value) method. +It is never assigned as a usual field.

          +
        2. +
        3. When a table is assigned to a non-NULL pointer field +using the ref.field = {...} syntax, it is applied +to the target of the pointer instead.

          +

          If the pointer is NULL, the table is checked for a new field:

          +
            +
          1. If it is nil or false, assignment fails with an error.
          2. +
          3. If it is true, the pointer is initialized with a newly +allocated object of the declared target type of the pointer.
          4. +
          5. Otherwise, table.new must be a named type, or an +object of a type compatible with the pointer. The pointer +is initialized with the result of calling table.new:new().
          6. +
          +

          After this auto-vivification process, assignment proceeds +as if the pointer wasn't NULL.

          +

          Obviously, the new field inside the table is always skipped +during the actual per-field assignment processing.

          +
        4. +
        5. If the target of the assignment is a container, a separate +rule set is used:

          +
            +
          1. If the table contains neither assign nor resize +fields, it is interpreted as an ordinary 1-based lua +array. The container is resized to the #-size of the +table, and elements are assigned in numeric order:

            +
            +ref:resize(#table);
            +for i=1,#table do ref[i-1] = table[i] end
            +
            +
          2. +
          3. Otherwise, resize must be true, false, or +an explicit number. If it is not false, the container +is resized. After that the usual struct-like 'pairs' +assignment is performed.

            +

            In case resize is true, the size is computed +by scanning the table for the largest numeric key.

            +
          4. +
          +

          This means that in order to reassign only one element of +a container using this system, it is necessary to use:

          +
          +{ resize=false, [idx]=value }
          +
          +
        6. +
        +

        Since nil inside a table is indistinguishable from missing key, +it is necessary to use df.NULL as a null pointer value.

        +

        This system is intended as a way to define a nested object +tree using pure lua data structures, and then materialize it in +C++ memory in one go. Note that if pointer auto-vivification +is used, an error in the middle of the recursive walk would +not destroy any objects allocated in this way, so the user +should be prepared to catch the error and do the necessary +cleanup.

        +
        + +
        +

        DFHack utilities

        +

        DFHack utility functions are placed in the dfhack global tree.

        +

        Currently it defines the following features:

        +
          +
        • dfhack.print(args...)

          +

          Output tab-separated args as standard lua print would do, +but without a newline.

          +
        • +
        • print(args...), dfhack.println(args...)

          +

          A replacement of the standard library print function that +works with DFHack output infrastructure.

          +
        • +
        • dfhack.printerr(args...)

          +

          Same as println; intended for errors. Uses red color and logs to stderr.log.

          +
        • +
        • dfhack.color([color])

          +

          Sets the current output color. If color is nil or -1, resets to default.

          +
        • +
        • dfhack.is_interactive()

          +

          Checks if the thread can access the interactive console and returns true or false.

          +
        • +
        • dfhack.lineedit([prompt[,history_filename]])

          +

          If the thread owns the interactive console, shows a prompt +and returns the entered string. Otherwise returns nil, error.

          +
        • +
        • dfhack.interpreter([prompt[,env[,history_filename]]])

          +

          Starts an interactive lua interpreter, using the specified prompt +string, global environment and command-line history file.

          +

          If the interactive console is not accessible, returns nil, error.

          +
        • +
        • dfhack.pcall(f[,args...])

          +

          Invokes f via xpcall, using an error function that attaches +a stack trace to the error. The same function is used by SafeCall +in C++, and dfhack.safecall.

          +

          The returned error is a table with separate message and +stacktrace string fields; it implements __tostring.

          +
        • +
        • safecall(f[,args...]), dfhack.safecall(f[,args...])

          +

          Just like pcall, but also prints the error using printerr before +returning. Intended as a convenience function.

          +
        • +
        • dfhack.with_suspend(f[,args...])

          +

          Calls f with arguments after grabbing the DF core suspend lock. +Suspending is necessary for accessing a consistent state of DF memory.

          +

          Returned values and errors are propagated through after releasing +the lock. It is safe to nest suspends.

          +

          Every thread is allowed only one suspend per DF frame, so it is best +to group operations together in one big critical section. A plugin +can choose to run all lua code inside a C++-side suspend lock.

          +
        • +
        +
        +

        Persistent configuration storage

        +

        This api is intended for storing configuration options in the world itself. +It probably should be restricted to data that is world-dependent.

        +

        Entries are identified by a string key, but it is also possible to manage +multiple entries with the same key; their identity is determined by entry_id. +Every entry has a mutable string value, and an array of 7 mutable ints.

        +
          +
        • dfhack.persistent.get(key), entry:get()

          +

          Retrieves a persistent config record with the given string key, +or refreshes an already retrieved entry. If there are multiple +entries with the same key, it is undefined which one is retrieved +by the first version of the call.

          +

          Returns entry, or nil if not found.

          +
        • +
        • dfhack.persistent.delete(key), entry:delete()

          +

          Removes an existing entry. Returns true if succeeded.

          +
        • +
        • dfhack.persistent.get_all(key[,match_prefix])

          +

          Retrieves all entries with the same key, or starting with key..'/'. +Calling get_all('',true) will match all entries.

          +

          If none found, returns nil; otherwise returns an array of entries.

          +
        • +
        • dfhack.persistent.save({key=str1, ...}[,new]), entry:save([new])

          +

          Saves changes in an entry, or creates a new one. Passing true as +new forces creation of a new entry even if one already exists; +otherwise the existing one is simply updated. +Returns entry, did_create_new

          +
        • +
        +

        Since the data is hidden in data structures owned by the DF world, +and automatically stored in the save game, these save and retrieval +functions can just copy values in memory without doing any actual I/O. +However, currently every entry has a 180+-byte dead-weight overhead.

        +
        From 330118ee273f0184fc743ec9ef5a0203c7d08ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 5 Apr 2012 03:00:10 +0200 Subject: [PATCH 28/28] Fix zone tool --- plugins/zone.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 5e712510c..e39141b5b 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -799,12 +799,12 @@ df::unit * findFreeEgglayer() bool unassignUnitFromZone(df::unit* unit) { bool success = false; - for (size_t or = 0; or < unit->refs.size(); or++) + for (std::size_t idx = 0; idx < unit->refs.size(); idx++) { - df::general_ref * oldref = unit->refs[or]; + df::general_ref * oldref = unit->refs[idx]; if(oldref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) { - unit->refs.erase(unit->refs.begin() + or); + unit->refs.erase(unit->refs.begin() + idx); df::building_civzonest * oldciv = (df::building_civzonest *) oldref->getBuilding(); for(size_t oc=0; ocassigned_creature.size(); oc++) {