From 43532e4871255d7bdb3696ce32ebe0f776844c3e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 1 Oct 2012 17:48:47 -0500 Subject: [PATCH 001/136] Progress commit of a rewrite of autolabor to draw from the jobs list and other world information. A lot still to do on this. DO NOT MERGE INTO THE MAIN BRANCH. This code is incomplete and only vaguely tested. --- plugins/autolabor.cpp | 1276 ++++++++++++++++++----------------------- 1 file changed, 560 insertions(+), 716 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index c39b126c9..7c984d35f 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -36,6 +36,14 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include + #include @@ -504,17 +512,25 @@ static const int responsibility_penalties[] = { struct dwarf_info_t { - int highest_skill; - int total_skill; - int mastery_penalty; - int assigned_jobs; + df::unit* dwarf; dwarf_state state; - bool has_exclusive_labor; - int noble_penalty; // penalty for assignment due to noble status - bool medical; // this dwarf has medical responsibility - bool trader; // this dwarf has trade responsibility - bool diplomacy; // this dwarf meets with diplomats - int single_labor; // this dwarf will be exclusively assigned to one labor (-1/NONE for none) + + bool clear_all; + + bool has_axe; + bool has_pick; + bool has_crossbow; + + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER) {} + + void set_labor(df::unit_labor labor) + { + dwarf->status.labors[labor] = true; + if ((labor == df::unit_labor::MINE && !has_pick) || + (labor == df::unit_labor::CUTWOOD && !has_axe) || + (labor == df::unit_labor::HUNT && !has_crossbow)) + dwarf->military.pickup_flags.bits.update = 1; + } }; static bool isOptionEnabled(unsigned flag) @@ -706,211 +722,570 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -// sorting objects -struct dwarfinfo_sorter + +static df::building* get_building_from_job(df::job* j) { - dwarfinfo_sorter(std::vector & info):dwarf_info(info){}; - bool operator() (int i,int j) - { - if (dwarf_info[i].state == IDLE && dwarf_info[j].state != IDLE) - return true; - if (dwarf_info[i].state != IDLE && dwarf_info[j].state == IDLE) - return false; - return dwarf_info[i].mastery_penalty > dwarf_info[j].mastery_penalty; - }; - std::vector & dwarf_info; -}; -struct laborinfo_sorter + for (auto r = j->references.begin(); r != j->references.end(); r++) + { + if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) + { + int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; + df::building* bld = binsearch_in_vector(world->buildings.all, id); + return bld; + } + } + return 0; +} + +static df::job_skill workshop_build_labor[] = { - bool operator() (int i,int j) - { - return labor_infos[i].mode() < labor_infos[j].mode(); - }; + /* Carpenters */ df::job_skill::CARPENTRY, + /* Farmers */ df::job_skill::PROCESSPLANTS, + /* Masons */ df::job_skill::MASONRY, + /* Craftsdwarfs */ df::job_skill::STONECRAFT, + /* Jewelers */ df::job_skill::CUTGEM, + /* MetalsmithsForge */ df::job_skill::METALCRAFT, + /* MagmaForge */ df::job_skill::METALCRAFT, + /* Bowyers */ df::job_skill::BOWYER, + /* Mechanics */ df::job_skill::MECHANICS, + /* Siege */ df::job_skill::SIEGECRAFT, + /* Butchers */ df::job_skill::BUTCHER, + /* Leatherworks */ df::job_skill::LEATHERWORK, + /* Tanners */ df::job_skill::TANNER, + /* Clothiers */ df::job_skill::CLOTHESMAKING, + /* Fishery */ df::job_skill::FISH, + /* Still */ df::job_skill::BREWING, + /* Loom */ df::job_skill::WEAVING, + /* Quern */ df::job_skill::MILLING, + /* Kennels */ df::job_skill::ANIMALTRAIN, + /* Kitchen */ df::job_skill::COOK, + /* Ashery */ df::job_skill::LYE_MAKING, + /* Dyers */ df::job_skill::DYER, + /* Millstone */ df::job_skill::MILLING, + /* Custom */ (df::job_skill) -1, + /* Tool */ (df::job_skill) -1 }; -struct values_sorter +static void find_job_skill_labor(df::job* j, df::job_skill &skill, df::unit_labor &labor) { - values_sorter(std::vector & values):values(values){}; - bool operator() (int i,int j) - { - return values[i] > values[j]; - }; - std::vector & values; -}; + switch (j->job_type) + { + case df::job_type::ConstructBuilding: + { + df::building* bld = get_building_from_job (j); + switch (bld->getType()) + { + case df::building_type::Workshop: + df::building_workshopst* ws = (df::building_workshopst*) bld; + skill = workshop_build_labor[ws->type]; + break; + } + } + break; + case df::job_type::CustomReaction: + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + skill = (*r)->skill; + break; + } + } + break; + default: + skill = ENUM_ATTR(job_type, skill, j->job_type); + } + + if (skill != df::job_skill::NONE) + labor = ENUM_ATTR(job_skill, labor, skill); + + if (labor == df::unit_labor::NONE) + labor = ENUM_ATTR(job_type, labor, j->job_type); + + if (labor == -1) + { + } -static void assign_labor(unit_labor::unit_labor labor, - int n_dwarfs, - std::vector& dwarf_info, - bool trader_requested, - std::vector& dwarfs, - bool has_butchers, - bool has_fishery, - color_ostream& out) -{ - df::job_skill skill = labor_to_skill[labor]; +} - if (labor_infos[labor].mode() != AUTOMATIC) - return; +class AutoLaborManager { + color_ostream& out; + +public: + AutoLaborManager(color_ostream& o) : out(o) + { + } + + ~AutoLaborManager() + { + for (std::vector::iterator i = dwarf_info.begin(); + i != dwarf_info.end(); i++) + delete (*i); + } + + dwarf_info_t* add_dwarf(df::unit* u) + { + dwarf_info_t* dwarf = new dwarf_info_t(u); + dwarf_info.push_back(dwarf); + return dwarf; + } + + +private: + bool has_butchers; + bool has_fishery; + bool trader_requested; + + int dig_count; + int tree_count; + int plant_count; + int detail_count; + int pick_count; + int axe_count; + + std::vector jobs; + std::vector dwarf_info; + std::deque idle_dwarfs; + +private: + void scan_buildings() + { + for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) + { + df::building *build = *b; + auto type = build->getType(); + if (building_type::Workshop == type) + { + df::workshop_type subType = (df::workshop_type)build->getSubtype(); + if (workshop_type::Butchers == subType) + has_butchers = true; + if (workshop_type::Fishery == subType) + has_fishery = true; + } + else if (building_type::TradeDepot == type) + { + df::building_tradedepotst* depot = (df::building_tradedepotst*) build; + trader_requested = depot->trade_flags.bits.trader_requested; + if (print_debug) + { + if (trader_requested) + out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); + else + out.print("Trade depot found but trader is not requested.\n"); + } + } + } + } + + void count_map_designations() + { + dig_count = 0; + tree_count = 0; + plant_count = 0; + detail_count = 0; + + for (int i = 0; i < world->map.map_blocks.size(); ++i) + { + df::map_block* bl = world->map.map_blocks[i]; + + if (!bl->flags.bits.designated) + continue; + + if (print_debug) + out.print ("block with designations found: %d, %d, %d\n", bl->map_pos.x, bl->map_pos.y, bl->map_pos.z); + + for (int x = 0; x < 16; x++) + for (int y = 0; y < 16; y++) + { + df::tile_dig_designation dig = bl->designation[x][y].bits.dig; + if (dig != df::enums::tile_dig_designation::No) + { + df::tiletype tt = bl->tiletype[x][y]; + df::tiletype_shape tts = ENUM_ATTR(tiletype, shape, tt); + switch (tts) + { + case df::enums::tiletype_shape::TREE: + tree_count++; break; + case df::enums::tiletype_shape::SHRUB: + plant_count++; break; + default: + dig_count++; break; + } + } + if (bl->designation[x][y].bits.smooth != 0) + detail_count++; + } + } + + if (print_debug) + out.print("Dig count = %d, Cut tree count = %d, gather plant count = %d, detail count = %d\n", dig_count, tree_count, plant_count, detail_count); + + } + + void count_tools() + { + pick_count = 0; + axe_count = 0; + + df::item_flags bad_flags; + bad_flags.whole = 0; - int best_dwarf = 0; - int best_value = -10000; +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact1); +#undef F - std::vector values(n_dwarfs); - std::vector candidates; - std::map dwarf_skill; - std::vector previously_enabled(n_dwarfs); + for (int i = 0; i < world->items.all.size(); ++i) + { + df::item* item = world->items.all[i]; + if (item->flags.whole & bad_flags.whole) + continue; + + if (!item->isWeapon()) + continue; + + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)item)->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + if (weaponsk == df::job_skill::AXE) + axe_count++; + else if (weaponsk == df::job_skill::MINING) + pick_count++; + } + + if (print_debug) + out.print("Axes = %d, picks = %d\n", axe_count, pick_count); + + } + + void collect_job_list() + { + std::vector repeating_jobs; + + + for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) + { + df::job* j = jll->item; + if (!j) + continue; + + if (j->flags.bits.suspend) + continue; + + int worker = -1; + + for (int r = 0; r < j->references.size(); ++r) + if (j->references[r]->getType() == df::general_ref_type::UNIT_WORKER) + worker = ((df::general_ref_unit_workerst *)(j->references[r]))->unit_id; + + if (worker != -1) + continue; + + if (j->flags.bits.repeat) + repeating_jobs.push_back(j); + else + jobs.push_back(j); + } + + if (print_debug) + out.print("%d repeating jobs, %d nonrepeating jobs\n", repeating_jobs.size(), jobs.size()); + + for (int i = 0; i < repeating_jobs.size(); i++) + jobs.push_back(repeating_jobs[i]); + + } + + void collect_dwarf_list() + { + + for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) + { + df::unit* cre = *u; + + if (Units::isCitizen(cre)) + { + if (cre->burrows.size() > 0) + continue; // dwarfs assigned to burrows are skipped entirely + + dwarf_info_t* dwarf = add_dwarf(cre); + + df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); + for (int i = 0; i < hf->entity_links.size(); i++) + { + df::histfig_entity_link* hfelink = hf->entity_links.at(i); + if (hfelink->getType() == df::histfig_entity_link_type::POSITION) + { + df::histfig_entity_link_positionst *epos = + (df::histfig_entity_link_positionst*) hfelink; + df::historical_entity* entity = df::historical_entity::find(epos->entity_id); + if (!entity) + continue; + df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); + if (!assignment) + continue; + df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); + if (!position) + continue; + + if (position->responsibilities[df::entity_position_responsibility::TRADE]) + if (trader_requested) + dwarf->clear_all = true; + } + + } + + // identify dwarfs who are needed for meetings and mark them for exclusion + + for (int i = 0; i < ui->activities.size(); ++i) + { + df::activity_info *act = ui->activities[i]; + if (!act) continue; + bool p1 = act->person1 == dwarf->dwarf; + bool p2 = act->person2 == dwarf->dwarf; + + if (p1 || p2) + { + dwarf->clear_all = true; + if (print_debug) + out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); + break; + } + } + + // Find the activity state for each dwarf-> + + bool is_on_break = false; + dwarf_state state = OTHER; + + for (auto p = dwarf->dwarf->status.misc_traits.begin(); p < dwarf->dwarf->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + is_on_break = true; + } + + if (dwarf->dwarf->profession == profession::BABY || + dwarf->dwarf->profession == profession::CHILD || + dwarf->dwarf->profession == profession::DRUNK) + { + state = CHILD; + } + else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) + state = MILITARY; + else if (dwarf->dwarf->job.current_job == NULL) + { + if (is_on_break) + state = OTHER; + else if (dwarf->dwarf->specific_refs.size() > 0) + state = OTHER; + else + state = IDLE; + } + else + { + int job = dwarf->dwarf->job.current_job->job_type; + if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + state = dwarf_states[job]; + else + { + out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); + state = OTHER; + } + } + + dwarf->state = state; + + if (print_debug) + out.print("Dwarf \"%s\": state %s\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state]); + + // check if dwarf has an axe, pick, or crossbow + + for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) + { + df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; + if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) + { + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; + if (weaponsk == df::job_skill::AXE) + { + dwarf->has_axe = 1; + if (state != IDLE) + axe_count--; + if (print_debug) + out.print("Dwarf \"%s\" has an axe\n", dwarf->dwarf->name.first_name.c_str()); + } + else if (weaponsk == df::job_skill::MINING) + { + dwarf->has_pick = 1; + if (state != IDLE) + pick_count--; + if (print_debug) + out.print("Dwarf \"%s\" has an pick\n", dwarf->dwarf->name.first_name.c_str()); + } + else if (rangesk == df::job_skill::CROSSBOW) + { + dwarf->has_crossbow = 1; + if (print_debug) + out.print("Dwarf \"%s\" has a crossbow\n", dwarf->dwarf->name.first_name.c_str()); + } + } + } + + // clear labors if currently idle + + if (state == IDLE || dwarf->clear_all) + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + dwarf->dwarf->status.labors[labor] = false; + } + } + + if (state == IDLE && !dwarf->clear_all) + idle_dwarfs.push_back(dwarf); + + } + + } + } + + void assign_one_dwarf (df::job_skill skill, df::unit_labor labor) + { + if (labor == df::unit_labor::NONE) + return; + + std::deque::iterator bestdwarf = idle_dwarfs.begin(); + + if (skill != df::job_skill::NONE) + { + int best_skill_level = -1; + + for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) + { + dwarf_info_t* d = (*k); + int skill_level = Units::getEffectiveSkill(d->dwarf, skill); + + if (skill_level > best_skill_level) + { + bestdwarf = k; + best_skill_level = skill_level; + } + } + } + + if (print_debug) + out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); + (*bestdwarf)->set_labor(labor); + + idle_dwarfs.erase(bestdwarf); + } - auto mode = labor_infos[labor].mode(); +public: + void process() + { + // scan for specific buildings of interest - // Find candidate dwarfs, and calculate a preference value for each dwarf - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - if (dwarf_info[dwarf].state == CHILD) - continue; - if (dwarf_info[dwarf].state == MILITARY) - continue; - if (dwarf_info[dwarf].trader && trader_requested) - continue; - if (dwarf_info[dwarf].diplomacy) - continue; + scan_buildings(); - if (labor_infos[labor].is_exclusive && dwarf_info[dwarf].has_exclusive_labor) - continue; + // count number of squares designated for dig, wood cutting, detailing, and plant harvesting - int value = dwarf_info[dwarf].mastery_penalty; + count_map_designations(); - if (skill != job_skill::NONE) - { - int skill_level = 0; - int skill_experience = 0; - - for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s < dwarfs[dwarf]->status.souls[0]->skills.end(); s++) - { - if ((*s)->id == skill) - { - skill_level = (*s)->rating; - skill_experience = (*s)->experience; - break; - } - } - - dwarf_skill[dwarf] = skill_level; - - value += skill_level * 100; - value += skill_experience / 20; - if (skill_level > 0 || skill_experience > 0) - value += 200; - if (skill_level >= 15) - value += 1000 * (skill_level - 14); - } - else - { - dwarf_skill[dwarf] = 0; - } + // count number of picks and axes available for use - if (dwarfs[dwarf]->status.labors[labor]) - { - value += 5; - if (labor_infos[labor].is_exclusive) - value += 350; - } + count_tools(); - // bias by happiness + // collect current job list - value += dwarfs[dwarf]->status.happiness; + collect_job_list(); - values[dwarf] = value; + // collect list of dwarfs - candidates.push_back(dwarf); + collect_dwarf_list(); - } + // assign jobs requiring skill to idle dwarfs - // Sort candidates by preference value - values_sorter ivs(values); - std::sort(candidates.begin(), candidates.end(), ivs); + int jobs_to_assign = jobs.size(); + if (jobs_to_assign > idle_dwarfs.size()) + jobs_to_assign = idle_dwarfs.size(); - // Disable the labor on everyone - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - if (dwarf_info[dwarf].state == CHILD) - continue; + if (print_debug) + out.print ("Assign idle (skilled): %d\n", jobs_to_assign); - previously_enabled[dwarf] = dwarfs[dwarf]->status.labors[labor]; - dwarfs[dwarf]->status.labors[labor] = false; - } + std::vector jobs2; - int min_dwarfs = labor_infos[labor].minimum_dwarfs(); - int max_dwarfs = labor_infos[labor].maximum_dwarfs(); - - // Special - don't assign hunt without a butchers, or fish without a fishery - if (unit_labor::HUNT == labor && !has_butchers) - min_dwarfs = max_dwarfs = 0; - if (unit_labor::FISH == labor && !has_fishery) - min_dwarfs = max_dwarfs = 0; - - bool want_idle_dwarf = true; - if (state_count[IDLE] < 2) - want_idle_dwarf = false; - - /* - * Assign dwarfs to this labor. We assign at least the minimum number of dwarfs, in - * order of preference, and then assign additional dwarfs that meet any of these conditions: - * - The dwarf is idle and there are no idle dwarves assigned to this labor - * - The dwarf has nonzero skill associated with the labor - * - The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. - * We stop assigning dwarfs when we reach the maximum allowed. - * Note that only idle and busy dwarfs count towards the number of dwarfs. "Other" dwarfs - * (sleeping, eating, on break, etc.) will have labors assigned, but will not be counted. - * Military and children/nobles will not have labors assigned. - * Dwarfs with the "health management" responsibility are always assigned DIAGNOSIS. - */ - for (int i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++) - { - int dwarf = candidates[i]; - - assert(dwarf >= 0); - assert(dwarf < n_dwarfs); - - bool preferred_dwarf = false; - if (want_idle_dwarf && dwarf_info[dwarf].state == IDLE) - preferred_dwarf = true; - if (dwarf_skill[dwarf] > 0) - preferred_dwarf = true; - if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive) - preferred_dwarf = true; - if (dwarf_info[dwarf].medical && labor == df::unit_labor::DIAGNOSE) - preferred_dwarf = true; - if (dwarf_info[dwarf].trader && trader_requested) - continue; - if (dwarf_info[dwarf].diplomacy) - continue; + for (int i = 0; i < jobs_to_assign; ++i) + { + df::job* j = jobs[i]; - if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf) - continue; + df::job_skill skill; + df::unit_labor labor; - if (!dwarfs[dwarf]->status.labors[labor]) - dwarf_info[dwarf].assigned_jobs++; + find_job_skill_labor(j, skill, labor); - dwarfs[dwarf]->status.labors[labor] = true; + if(print_debug) + out.print("Job skill = %d labor = %d\n", skill, labor); - if (labor_infos[labor].is_exclusive) - { - dwarf_info[dwarf].has_exclusive_labor = true; - // all the exclusive labors require equipment so this should force the dorf to reequip if needed - dwarfs[dwarf]->military.pickup_flags.bits.update = 1; - } + if (labor == -1) + out.print("Invalid labor for job (%d)\n", j->job_type); - if (print_debug) - out.print("Dwarf %i \"%s\" assigned %s: value %i %s %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), values[dwarf], dwarf_info[dwarf].trader ? "(trader)" : "", dwarf_info[dwarf].diplomacy ? "(diplomacy)" : ""); + if (skill == df::job_skill::NONE) + jobs2.push_back(j); + else + assign_one_dwarf(skill, labor); + } - if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) - labor_infos[labor].active_dwarfs++; + // assign mining jobs to idle dwarfs - if (dwarf_info[dwarf].state == IDLE) - want_idle_dwarf = false; - } -} + int dig_max = dig_count; + if (dig_max > pick_count) + dig_max = pick_count; + + jobs_to_assign = dig_max - jobs2.size(); + if (jobs_to_assign > idle_dwarfs.size()) + jobs_to_assign = idle_dwarfs.size(); + + if (print_debug) + out.print ("Assign idle (mining): %d\n", jobs_to_assign); + + for (int i = 0; i < jobs_to_assign; ++i) + { + df::job_skill skill = df::job_skill::MINING; + df::unit_labor labor = df::unit_labor::MINE; + + assign_one_dwarf(skill, labor); + } + + // now assign unskilled jobs to idle dwarfs + + jobs_to_assign = jobs2.size(); + if (jobs_to_assign > idle_dwarfs.size()) + jobs_to_assign = idle_dwarfs.size(); + + if (print_debug) + out.print ("Assign idle (unskilled): %d\n", jobs_to_assign); + + for (int i = 0; i < jobs_to_assign; ++i) + { + df::job* j = jobs2[i]; + + df::job_skill skill; + df::unit_labor labor; + + find_job_skill_labor(j, skill, labor); + assign_one_dwarf(skill, labor); + } + + print_debug = 0; + + } + +}; DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) @@ -944,339 +1319,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; step_count = 0; - uint32_t race = ui->race_id; - uint32_t civ = ui->civ_id; - - std::vector dwarfs; - - bool has_butchers = false; - bool has_fishery = false; - bool trader_requested = false; - - for (int i = 0; i < world->buildings.all.size(); ++i) - { - df::building *build = world->buildings.all[i]; - auto type = build->getType(); - if (building_type::Workshop == type) - { - df::workshop_type subType = (df::workshop_type)build->getSubtype(); - if (workshop_type::Butchers == subType) - has_butchers = true; - if (workshop_type::Fishery == subType) - has_fishery = true; - } - else if (building_type::TradeDepot == type) - { - df::building_tradedepotst* depot = (df::building_tradedepotst*) build; - trader_requested = depot->trade_flags.bits.trader_requested; - if (print_debug) - { - if (trader_requested) - out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); - else - out.print("Trade depot found but trader is not requested.\n"); - } - } - } - - for (int i = 0; i < world->units.active.size(); ++i) - { - df::unit* cre = world->units.active[i]; - if (Units::isCitizen(cre)) - { - if (cre->burrows.size() > 0) - continue; // dwarfs assigned to burrows are skipped entirely - dwarfs.push_back(cre); - } - } - - int n_dwarfs = dwarfs.size(); - - if (n_dwarfs == 0) - return CR_OK; - - std::vector dwarf_info(n_dwarfs); - - // Find total skill and highest skill for each dwarf. More skilled dwarves shouldn't be used for minor tasks. - - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - dwarf_info[dwarf].single_labor = -1; - - if (dwarfs[dwarf]->status.souls.size() <= 0) - continue; - - // compute noble penalty - - int noble_penalty = 0; - - df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id); - for (int i = 0; i < hf->entity_links.size(); i++) - { - df::histfig_entity_link* hfelink = hf->entity_links.at(i); - if (hfelink->getType() == df::histfig_entity_link_type::POSITION) - { - df::histfig_entity_link_positionst *epos = - (df::histfig_entity_link_positionst*) hfelink; - df::historical_entity* entity = df::historical_entity::find(epos->entity_id); - if (!entity) - continue; - df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); - if (!assignment) - continue; - df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); - if (!position) - continue; - - for (int n = 0; n < 25; n++) - if (position->responsibilities[n]) - noble_penalty += responsibility_penalties[n]; - - if (position->responsibilities[df::entity_position_responsibility::HEALTH_MANAGEMENT]) - dwarf_info[dwarf].medical = true; - - if (position->responsibilities[df::entity_position_responsibility::TRADE]) - dwarf_info[dwarf].trader = true; - - } - - } - - dwarf_info[dwarf].noble_penalty = noble_penalty; - - // identify dwarfs who are needed for meetings and mark them for exclusion - - for (int i = 0; i < ui->activities.size(); ++i) - { - df::activity_info *act = ui->activities[i]; - if (!act) continue; - bool p1 = act->person1 == dwarfs[dwarf]; - bool p2 = act->person2 == dwarfs[dwarf]; - - if (p1 || p2) - { - dwarf_info[dwarf].diplomacy = true; - if (print_debug) - out.print("Dwarf %i \"%s\" has a meeting, will be cleared of all labors\n", dwarf, dwarfs[dwarf]->name.first_name.c_str()); - break; - } - } - - for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s != dwarfs[dwarf]->status.souls[0]->skills.end(); s++) - { - df::job_skill skill = (*s)->id; - - df::job_skill_class skill_class = ENUM_ATTR(job_skill, type, skill); - - int skill_level = (*s)->rating; - int skill_experience = (*s)->experience; - - // Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.) - - if (skill_class != job_skill_class::Normal && skill_class != job_skill_class::Medical) - continue; - - if (dwarf_info[dwarf].highest_skill < skill_level) - dwarf_info[dwarf].highest_skill = skill_level; - dwarf_info[dwarf].total_skill += skill_level; - } - } - - // Calculate a base penalty for using each dwarf for a task he isn't good at. - - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - dwarf_info[dwarf].mastery_penalty -= 40 * dwarf_info[dwarf].highest_skill; - dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill; - dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty; - - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - if (labor_infos[labor].is_exclusive && dwarfs[dwarf]->status.labors[labor]) - dwarf_info[dwarf].mastery_penalty -= 100; - } - } - - // Find the activity state for each dwarf. It's important to get this right - a dwarf who we think is IDLE but - // can't work will gum everything up. In the future I might add code to auto-detect slacker dwarves. - - state_count.clear(); - state_count.resize(NUM_STATE); - - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - bool is_on_break = false; - - for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) - { - if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) - is_on_break = true; - } - - if (dwarfs[dwarf]->profession == profession::BABY || - dwarfs[dwarf]->profession == profession::CHILD || - dwarfs[dwarf]->profession == profession::DRUNK) - { - dwarf_info[dwarf].state = CHILD; - } - else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession)) - dwarf_info[dwarf].state = MILITARY; - else if (dwarfs[dwarf]->job.current_job == NULL) - { - if (is_on_break) - dwarf_info[dwarf].state = OTHER; - else if (dwarfs[dwarf]->specific_refs.size() > 0) - dwarf_info[dwarf].state = OTHER; - else - dwarf_info[dwarf].state = IDLE; - } - else - { - int job = dwarfs[dwarf]->job.current_job->job_type; - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) - dwarf_info[dwarf].state = dwarf_states[job]; - else - { - out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); - dwarf_info[dwarf].state = OTHER; - } - } - - state_count[dwarf_info[dwarf].state]++; - - if (print_debug) - out.print("Dwarf %i \"%s\": penalty %i, state %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), dwarf_info[dwarf].mastery_penalty, state_names[dwarf_info[dwarf].state]); - } - - std::vector labors; - - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - labor_infos[labor].active_dwarfs = 0; - - labors.push_back(labor); - } - laborinfo_sorter lasorter; - std::sort(labors.begin(), labors.end(), lasorter); - - // Handle DISABLED skills (just bookkeeping) - for (auto lp = labors.begin(); lp != labors.end(); ++lp) - { - auto labor = *lp; - - if (labor_infos[labor].mode() != DISABLE) - continue; - - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - if ((dwarf_info[dwarf].trader && trader_requested) || - dwarf_info[dwarf].diplomacy) - { - dwarfs[dwarf]->status.labors[labor] = false; - } - - if (dwarfs[dwarf]->status.labors[labor]) - { - if (labor_infos[labor].is_exclusive) - dwarf_info[dwarf].has_exclusive_labor = true; - - dwarf_info[dwarf].assigned_jobs++; - } - } - } - - // Handle all skills except those marked HAULERS - - for (auto lp = labors.begin(); lp != labors.end(); ++lp) - { - auto labor = *lp; - - assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out); - } - - // Set about 1/3 of the dwarfs as haulers. The haulers have all HAULER labors enabled. Having a lot of haulers helps - // make sure that hauling jobs are handled quickly rather than building up. - - int num_haulers = state_count[IDLE] + state_count[BUSY] * hauler_pct / 100; + AutoLaborManager alm(out); - if (num_haulers < 1) - num_haulers = 1; + alm.process(); - std::vector hauler_ids; - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - if ((dwarf_info[dwarf].trader && trader_requested) || - dwarf_info[dwarf].diplomacy) - { - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - if (labor_infos[labor].mode() != HAULERS) - continue; - dwarfs[dwarf]->status.labors[labor] = false; - } - continue; - } - - if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) - hauler_ids.push_back(dwarf); - } - dwarfinfo_sorter sorter(dwarf_info); - // Idle dwarves come first, then we sort from least-skilled to most-skilled. - std::sort(hauler_ids.begin(), hauler_ids.end(), sorter); - - // don't set any haulers if everyone is off drinking or something - if (hauler_ids.size() == 0) { - num_haulers = 0; - } - - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - if (labor_infos[labor].mode() != HAULERS) - continue; - - for (int i = 0; i < num_haulers; i++) - { - assert(i < hauler_ids.size()); - - int dwarf = hauler_ids[i]; - - assert(dwarf >= 0); - assert(dwarf < n_dwarfs); - dwarfs[dwarf]->status.labors[labor] = true; - dwarf_info[dwarf].assigned_jobs++; - - if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) - labor_infos[labor].active_dwarfs++; - - if (print_debug) - out.print("Dwarf %i \"%s\" assigned %s: hauler\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); - } - - for (int i = num_haulers; i < hauler_ids.size(); i++) - { - assert(i < hauler_ids.size()); - - int dwarf = hauler_ids[i]; - - assert(dwarf >= 0); - assert(dwarf < n_dwarfs); - - dwarfs[dwarf]->status.labors[labor] = false; - } - } - - print_debug = 0; return CR_OK; } @@ -1469,205 +1515,3 @@ command_result autolabor (color_ostream &out, std::vector & parame } } -struct StockpileInfo { - df::building_stockpilest* sp; - int size; - int free; - int x1, x2, y1, y2, z; - -public: - StockpileInfo(df::building_stockpilest *sp_) : sp(sp_) - { - MapExtras::MapCache mc; - - z = sp_->z; - x1 = sp_->room.x; - x2 = sp_->room.x + sp_->room.width; - y1 = sp_->room.y; - y2 = sp_->room.y + sp_->room.height; - int e = 0; - size = 0; - free = 0; - for (int y = y1; y < y2; y++) - for (int x = x1; x < x2; x++) - if (sp_->room.extents[e++] == 1) - { - size++; - DFCoord cursor (x,y,z); - uint32_t blockX = x / 16; - uint32_t tileX = x % 16; - uint32_t blockY = y / 16; - uint32_t tileY = y % 16; - MapExtras::Block * b = mc.BlockAt(cursor/16); - if(b && b->is_valid()) - { - auto &block = *b->getRaw(); - df::tile_occupancy &occ = block.occupancy[tileX][tileY]; - if (!occ.bits.item) - free++; - } - } - } - - bool isFull() { return free == 0; } - - bool canHold(df::item *i) - { - return false; - } - - bool inStockpile(df::item *i) - { - df::item *container = Items::getContainer(i); - if (container) - return inStockpile(container); - - if (i->pos.z != z) return false; - if (i->pos.x < x1 || i->pos.x >= x2 || - i->pos.y < y1 || i->pos.y >= y2) return false; - int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width; - return sp->room.extents[e] == 1; - } - - int getId() { return sp->id; } -}; - -static int stockcheck(color_ostream &out, vector & parameters) -{ - int count = 0; - - std::vector stockpiles; - - for (int i = 0; i < world->buildings.all.size(); ++i) - { - df::building *build = world->buildings.all[i]; - auto type = build->getType(); - if (building_type::Stockpile == type) - { - df::building_stockpilest *sp = virtual_cast(build); - StockpileInfo *spi = new StockpileInfo(sp); - stockpiles.push_back(spi); - } - - } - - std::vector &items = world->items.other[items_other_id::ANY_FREE]; - - // Precompute a bitmask with the bad flags - df::item_flags bad_flags; - bad_flags.whole = 0; - -#define F(x) bad_flags.bits.x = true; - F(dump); F(forbid); F(garbage_collect); - F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); - F(spider_web); F(owned); F(in_job); -#undef F - - for (size_t i = 0; i < items.size(); i++) - { - df::item *item = items[i]; - if (item->flags.whole & bad_flags.whole) - continue; - - // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG - - df::item_type typ = item->getType(); - if (typ != item_type::MEAT && - typ != item_type::FISH && - typ != item_type::FISH_RAW && - typ != item_type::PLANT && - typ != item_type::CHEESE && - typ != item_type::FOOD && - typ != item_type::EGG) - continue; - - df::item *container = 0; - df::unit *holder = 0; - df::building *building = 0; - - for (size_t i = 0; i < item->itemrefs.size(); i++) - { - df::general_ref *ref = item->itemrefs[i]; - - switch (ref->getType()) - { - case general_ref_type::CONTAINED_IN_ITEM: - container = ref->getItem(); - break; - - case general_ref_type::UNIT_HOLDER: - holder = ref->getUnit(); - break; - - case general_ref_type::BUILDING_HOLDER: - building = ref->getBuilding(); - break; - - default: - break; - } - } - - df::item *nextcontainer = container; - df::item *lastcontainer = 0; - - while(nextcontainer) { - df::item *thiscontainer = nextcontainer; - nextcontainer = 0; - for (size_t i = 0; i < thiscontainer->itemrefs.size(); i++) - { - df::general_ref *ref = thiscontainer->itemrefs[i]; - - switch (ref->getType()) - { - case general_ref_type::CONTAINED_IN_ITEM: - lastcontainer = nextcontainer = ref->getItem(); - break; - - case general_ref_type::UNIT_HOLDER: - holder = ref->getUnit(); - break; - - case general_ref_type::BUILDING_HOLDER: - building = ref->getBuilding(); - break; - - default: - break; - } - } - } - - if (holder) - continue; // carried items do not rot as far as i know - - if (building) { - df::building_type btype = building->getType(); - if (btype == building_type::TradeDepot || - btype == building_type::Wagon) - continue; // items in trade depot or the embark wagon do not rot - - if (typ == item_type::EGG && btype ==building_type::NestBox) - continue; // eggs in nest box do not rot - } - - int canHoldCount = 0; - StockpileInfo *current = 0; - - for (int idx = 0; idx < stockpiles.size(); idx++) - { - StockpileInfo *spi = stockpiles[idx]; - if (spi->canHold(item)) canHoldCount++; - if (spi->inStockpile(item)) current=spi; - } - - if (current) - continue; - - count++; - - } - - return count; -} From fe2257427f6495cd536513fdfb114b00ffc94810 Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 19 Nov 2012 20:17:05 -0500 Subject: [PATCH 002/136] workNow: plugin to make DF process jobs every time you pause. --- plugins/workNow.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 plugins/workNow.cpp diff --git a/plugins/workNow.cpp b/plugins/workNow.cpp new file mode 100644 index 000000000..acbb5bc30 --- /dev/null +++ b/plugins/workNow.cpp @@ -0,0 +1,43 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "DataDefs.h" +#include "modules/World.h" +#include "df/global_objects.h" + +#include +using namespace std; + +using namespace DFHack; + +DFHACK_PLUGIN("workNow"); + +static bool active = false; + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { + if ( !DFHack::Core::getInstance().getWorld()->ReadPauseState() ) + return CR_OK; + *df::global::process_jobs = true; + return CR_OK; +} + +DFhackCExport command_result workNow(color_ostream& out, vector& parameters); + +DFhackCExport command_result plugin_init(color_ostream& out, std::vector &commands) { + commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs every time you pause", workNow, false, "Full help.")); + + return CR_OK; +} + +DFhackCExport command_result workNow(color_ostream& out, vector& parameters) { + + + return CR_OK; +} + + From eecb6048002187202bff3ef2991d88d62ac6b37a Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 30 Nov 2012 11:23:58 -0600 Subject: [PATCH 003/136] Sync submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index e06438924..42e26b368 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e06438924929a8ecab751c0c233dad5767e91f7e +Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc diff --git a/plugins/stonesense b/plugins/stonesense index 75df76626..cb97cf308 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 75df766263b23182820a1e07b330e64f87d5c9b7 +Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd From 63e4c16aa17a1cd7b677468fb55acb0981a83d4e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 30 Nov 2012 20:22:29 -0600 Subject: [PATCH 004/136] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 42e26b368..22b01b80a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc +Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 From 384a667e9740d78a1895625e400d53d06b7f0c28 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 30 Nov 2012 20:22:58 -0600 Subject: [PATCH 005/136] Assorted progress on new autolabor. Still lots of issues. --- plugins/autolabor.cpp | 189 ++++++++++++++++++------------------------ 1 file changed, 79 insertions(+), 110 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 45ddda2e9..89115366a 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -8,6 +8,9 @@ #include #include +#include +#include +#include #include "modules/Units.h" #include "modules/World.h" @@ -721,7 +724,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) static df::building* get_building_from_job(df::job* j) { - for (auto r = j->references.begin(); r != j->references.end(); r++) + for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) { if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) { @@ -762,12 +765,17 @@ static df::job_skill workshop_build_labor[] = /* Tool */ (df::job_skill) -1 }; -static void find_job_skill_labor(df::job* j, df::job_skill &skill, df::unit_labor &labor) +static df::unit_labor find_job_labor(df::job* j) { + df::job_skill skill; + df::unit_labor labor; + + labor = df::unit_labor::NONE; switch (j->job_type) { case df::job_type::ConstructBuilding: + case df::job_type::DestroyBuilding: { df::building* bld = get_building_from_job (j); switch (bld->getType()) @@ -779,6 +787,7 @@ static void find_job_skill_labor(df::job* j, df::job_skill &skill, df::unit_labo } } break; + case df::job_type::CustomReaction: for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) { @@ -803,6 +812,7 @@ static void find_job_skill_labor(df::job* j, df::job_skill &skill, df::unit_labo { } + return labor; } class AutoLaborManager { @@ -840,7 +850,7 @@ private: int pick_count; int axe_count; - std::vector jobs; + std::map labor_needed; std::vector dwarf_info; std::deque idle_dwarfs; @@ -930,7 +940,7 @@ private: #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); + F(in_building); F(construction); F(artifact); #undef F for (int i = 0; i < world->items.all.size(); ++i) @@ -957,9 +967,6 @@ private: void collect_job_list() { - std::vector repeating_jobs; - - for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) { df::job* j = jll->item; @@ -971,24 +978,21 @@ private: int worker = -1; - for (int r = 0; r < j->references.size(); ++r) - if (j->references[r]->getType() == df::general_ref_type::UNIT_WORKER) - worker = ((df::general_ref_unit_workerst *)(j->references[r]))->unit_id; + for (int r = 0; r < j->general_refs.size(); ++r) + if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) + worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; if (worker != -1) continue; - if (j->flags.bits.repeat) - repeating_jobs.push_back(j); - else - jobs.push_back(j); - } + df::unit_labor labor = find_job_labor (j); - if (print_debug) - out.print("%d repeating jobs, %d nonrepeating jobs\n", repeating_jobs.size(), jobs.size()); + if (print_debug) + out.print ("Job requiring labor %d found\n", labor); - for (int i = 0; i < repeating_jobs.size(); i++) - jobs.push_back(repeating_jobs[i]); + if (labor != df::unit_labor::NONE) + labor_needed[labor]++; + } } @@ -1150,37 +1154,6 @@ private: } } - void assign_one_dwarf (df::job_skill skill, df::unit_labor labor) - { - if (labor == df::unit_labor::NONE) - return; - - std::deque::iterator bestdwarf = idle_dwarfs.begin(); - - if (skill != df::job_skill::NONE) - { - int best_skill_level = -1; - - for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) - { - dwarf_info_t* d = (*k); - int skill_level = Units::getEffectiveSkill(d->dwarf, skill); - - if (skill_level > best_skill_level) - { - bestdwarf = k; - best_skill_level = skill_level; - } - } - } - - if (print_debug) - out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); - (*bestdwarf)->set_labor(labor); - - idle_dwarfs.erase(bestdwarf); - } - public: void process() { @@ -1196,6 +1169,8 @@ public: count_tools(); + // create job entries for designation + // collect current job list collect_job_list(); @@ -1204,82 +1179,76 @@ public: collect_dwarf_list(); - // assign jobs requiring skill to idle dwarfs + // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count + // and remove the idle dwarf from the idle list - int jobs_to_assign = jobs.size(); - if (jobs_to_assign > idle_dwarfs.size()) - jobs_to_assign = idle_dwarfs.size(); - - if (print_debug) - out.print ("Assign idle (skilled): %d\n", jobs_to_assign); - - std::vector jobs2; - - for (int i = 0; i < jobs_to_assign; ++i) + for (auto i = idle_dwarfs.begin(); i != idle_dwarfs.end(); i++) { - df::job* j = jobs[i]; - - df::job_skill skill; - df::unit_labor labor; - - find_job_skill_labor(j, skill, labor); - - if(print_debug) - out.print("Job skill = %d labor = %d\n", skill, labor); - - if (labor == -1) - out.print("Invalid labor for job (%d)\n", j->job_type); - - if (skill == df::job_skill::NONE) - jobs2.push_back(j); - else - assign_one_dwarf(skill, labor); + FOR_ENUM_ITEMS(unit_labor, l) + { + if ((*i)->dwarf->status.labors[l]) + if (labor_needed[l] > 0) + { + if (print_debug) + out.print("assign \"%s\" labor %d (carried through)\n", (*i)->dwarf->name.first_name.c_str(), l); + labor_needed[l]--; + idle_dwarfs.erase(i); // remove from idle list + break; + } else { + (*i)->dwarf->status.labors[l] = false; + } + } } - // assign mining jobs to idle dwarfs - - int dig_max = dig_count; - if (dig_max > pick_count) - dig_max = pick_count; - - jobs_to_assign = dig_max - jobs2.size(); - if (jobs_to_assign > idle_dwarfs.size()) - jobs_to_assign = idle_dwarfs.size(); - - if (print_debug) - out.print ("Assign idle (mining): %d\n", jobs_to_assign); - - for (int i = 0; i < jobs_to_assign; ++i) + priority_queue> pq; + + for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - df::job_skill skill = df::job_skill::MINING; - df::unit_labor labor = df::unit_labor::MINE; - - assign_one_dwarf(skill, labor); + if (i->second > 0) + pq.push(make_pair(i->second, i->first)); } + + while (!idle_dwarfs.empty() && !pq.empty()) + { + df::unit_labor labor = pq.top().second; + int remaining = pq.top().first; + df::job_skill skill = labor_to_skill[labor]; - // now assign unskilled jobs to idle dwarfs + if (print_debug) + out.print("labor %d skill %d remaining %d\n", labor, skill, remaining); - jobs_to_assign = jobs2.size(); - if (jobs_to_assign > idle_dwarfs.size()) - jobs_to_assign = idle_dwarfs.size(); + std::deque::iterator bestdwarf = idle_dwarfs.begin(); - if (print_debug) - out.print ("Assign idle (unskilled): %d\n", jobs_to_assign); + if (skill != df::job_skill::NONE) + { + int best_skill_level = -1; - for (int i = 0; i < jobs_to_assign; ++i) - { - df::job* j = jobs2[i]; + for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) + { + dwarf_info_t* d = (*k); + int skill_level = Units::getEffectiveSkill(d->dwarf, skill); - df::job_skill skill; - df::unit_labor labor; + if (skill_level > best_skill_level) + { + bestdwarf = k; + best_skill_level = skill_level; + } + } + } - find_job_skill_labor(j, skill, labor); - assign_one_dwarf(skill, labor); + if (print_debug) + out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); + (*bestdwarf)->set_labor(labor); + + idle_dwarfs.erase(bestdwarf); + pq.pop(); + if (--remaining) + pq.push(make_pair(remaining, labor)); } print_debug = 0; - } + } }; From afb6d8ef799cef5ad493554a335a8290fbcb93cf Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 1 Dec 2012 02:26:06 -0600 Subject: [PATCH 006/136] Autolabor: improve (hopefully) the functionality for mapping jobs to labors. Still some gaps to fill, but not many. --- plugins/autolabor.cpp | 1473 +++++++++++++++++++++++++++-------------- 1 file changed, 980 insertions(+), 493 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 89115366a..5736480fb 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -45,7 +45,11 @@ #include #include #include +#include +#include #include +#include +#include #include @@ -515,27 +519,120 @@ static const int responsibility_penalties[] = { struct dwarf_info_t { - df::unit* dwarf; + df::unit* dwarf; dwarf_state state; - bool clear_all; + bool clear_all; - bool has_axe; - bool has_pick; - bool has_crossbow; + bool has_axe; + bool has_pick; + bool has_crossbow; - dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER) {} + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER) {} - void set_labor(df::unit_labor labor) - { - dwarf->status.labors[labor] = true; - if ((labor == df::unit_labor::MINE && !has_pick) || - (labor == df::unit_labor::CUTWOOD && !has_axe) || - (labor == df::unit_labor::HUNT && !has_crossbow)) - dwarf->military.pickup_flags.bits.update = 1; - } + void set_labor(df::unit_labor labor) + { + dwarf->status.labors[labor] = true; + if ((labor == df::unit_labor::MINE && !has_pick) || + (labor == df::unit_labor::CUTWOOD && !has_axe) || + (labor == df::unit_labor::HUNT && !has_crossbow)) + dwarf->military.pickup_flags.bits.update = 1; + } }; +static df::unit_labor hauling_labor_map[] = + { + df::unit_labor::HAUL_ITEM, /* BAR */ + df::unit_labor::HAUL_ITEM, /* SMALLGEM */ + df::unit_labor::HAUL_ITEM, /* BLOCKS */ + df::unit_labor::HAUL_ITEM, /* ROUGH */ + df::unit_labor::HAUL_STONE, /* BOULDER */ + df::unit_labor::HAUL_WOOD, /* WOOD */ + df::unit_labor::HAUL_FURNITURE, /* DOOR */ + df::unit_labor::HAUL_FURNITURE, /* FLOODGATE */ + df::unit_labor::HAUL_FURNITURE, /* BED */ + df::unit_labor::HAUL_FURNITURE, /* CHAIR */ + df::unit_labor::HAUL_ITEM, /* CHAIN */ + df::unit_labor::HAUL_ITEM, /* FLASK */ + df::unit_labor::HAUL_ITEM, /* GOBLET */ + df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ + df::unit_labor::HAUL_ITEM, /* TOY */ + df::unit_labor::HAUL_FURNITURE, /* WINDOW */ + df::unit_labor::HAUL_ANIMAL, /* CAGE */ + df::unit_labor::HAUL_ITEM, /* BARREL */ + df::unit_labor::HAUL_ITEM, /* BUCKET */ + df::unit_labor::HAUL_ANIMAL, /* ANIMALTRAP */ + df::unit_labor::HAUL_FURNITURE, /* TABLE */ + df::unit_labor::HAUL_FURNITURE, /* COFFIN */ + df::unit_labor::HAUL_FURNITURE, /* STATUE */ + df::unit_labor::HAUL_BODY, /* CORPSE */ + df::unit_labor::HAUL_ITEM, /* WEAPON */ + df::unit_labor::HAUL_ITEM, /* ARMOR */ + df::unit_labor::HAUL_ITEM, /* SHOES */ + df::unit_labor::HAUL_ITEM, /* SHIELD */ + df::unit_labor::HAUL_ITEM, /* HELM */ + df::unit_labor::HAUL_ITEM, /* GLOVES */ + df::unit_labor::HAUL_FURNITURE, /* BOX */ + df::unit_labor::HAUL_ITEM, /* BIN */ + df::unit_labor::HAUL_FURNITURE, /* ARMORSTAND */ + df::unit_labor::HAUL_FURNITURE, /* WEAPONRACK */ + df::unit_labor::HAUL_FURNITURE, /* CABINET */ + df::unit_labor::HAUL_ITEM, /* FIGURINE */ + df::unit_labor::HAUL_ITEM, /* AMULET */ + df::unit_labor::HAUL_ITEM, /* SCEPTER */ + df::unit_labor::HAUL_ITEM, /* AMMO */ + df::unit_labor::HAUL_ITEM, /* CROWN */ + df::unit_labor::HAUL_ITEM, /* RING */ + df::unit_labor::HAUL_ITEM, /* EARRING */ + df::unit_labor::HAUL_ITEM, /* BRACELET */ + df::unit_labor::HAUL_ITEM, /* GEM */ + df::unit_labor::HAUL_FURNITURE, /* ANVIL */ + df::unit_labor::HAUL_BODY, /* CORPSEPIECE */ + df::unit_labor::HAUL_REFUSE, /* REMAINS */ + df::unit_labor::HAUL_FOOD, /* MEAT */ + df::unit_labor::HAUL_FOOD, /* FISH */ + df::unit_labor::HAUL_FOOD, /* FISH_RAW */ + df::unit_labor::HAUL_REFUSE, /* VERMIN */ + df::unit_labor::HAUL_ITEM, /* PET */ + df::unit_labor::HAUL_FOOD, /* SEEDS */ + df::unit_labor::HAUL_FOOD, /* PLANT */ + df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ + df::unit_labor::HAUL_FOOD, /* LEAVES */ + df::unit_labor::HAUL_ITEM, /* THREAD */ + df::unit_labor::HAUL_ITEM, /* CLOTH */ + df::unit_labor::HAUL_ITEM, /* TOTEM */ + df::unit_labor::HAUL_ITEM, /* PANTS */ + df::unit_labor::HAUL_ITEM, /* BACKPACK */ + df::unit_labor::HAUL_ITEM, /* QUIVER */ + df::unit_labor::HAUL_FURNITURE, /* CATAPULTPARTS */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAPARTS */ + df::unit_labor::HAUL_FURNITURE, /* SIEGEAMMO */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAARROWHEAD */ + df::unit_labor::HAUL_FURNITURE, /* TRAPPARTS */ + df::unit_labor::HAUL_FURNITURE, /* TRAPCOMP */ + df::unit_labor::HAUL_FOOD, /* DRINK */ + df::unit_labor::HAUL_FOOD, /* POWDER_MISC */ + df::unit_labor::HAUL_FOOD, /* CHEESE */ + df::unit_labor::HAUL_FOOD, /* FOOD */ + df::unit_labor::HAUL_FOOD, /* LIQUID_MISC */ + df::unit_labor::HAUL_ITEM, /* COIN */ + df::unit_labor::HAUL_FOOD, /* GLOB */ + df::unit_labor::HAUL_STONE, /* ROCK */ + df::unit_labor::HAUL_FURNITURE, /* PIPE_SECTION */ + df::unit_labor::HAUL_FURNITURE, /* HATCH_COVER */ + df::unit_labor::HAUL_FURNITURE, /* GRATE */ + df::unit_labor::HAUL_FURNITURE, /* QUERN */ + df::unit_labor::HAUL_FURNITURE, /* MILLSTONE */ + df::unit_labor::HAUL_ITEM, /* SPLINT */ + df::unit_labor::HAUL_ITEM, /* CRUTCH */ + df::unit_labor::HAUL_FURNITURE, /* TRACTION_BENCH */ + df::unit_labor::HAUL_ITEM, /* ORTHOPEDIC_CAST */ + df::unit_labor::HAUL_ITEM, /* TOOL */ + df::unit_labor::HAUL_FURNITURE, /* SLAB */ + df::unit_labor::HAUL_FOOD, /* EGG */ + df::unit_labor::HAUL_ITEM, /* BOOK */ + }; + static bool isOptionEnabled(unsigned flag) { return config.isValid() && (config.ival(0) & flag) != 0; @@ -724,531 +821,921 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) static df::building* get_building_from_job(df::job* j) { - for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) - { - if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) - { - int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; - df::building* bld = binsearch_in_vector(world->buildings.all, id); - return bld; - } - } - return 0; + for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) + { + if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) + { + int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; + df::building* bld = binsearch_in_vector(world->buildings.all, id); + return bld; + } + } + return 0; } -static df::job_skill workshop_build_labor[] = + + +static df::unit_labor workshop_build_labor[] = +{ + /* Carpenters */ df::unit_labor::CARPENTER, + /* Farmers */ df::unit_labor::HERBALIST, + /* Masons */ df::unit_labor::MASON, + /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, + /* Jewelers */ df::unit_labor::CUT_GEM, + /* MetalsmithsForge */ df::unit_labor::METAL_CRAFT, + /* MagmaForge */ df::unit_labor::METAL_CRAFT, + /* Bowyers */ df::unit_labor::BOWYER, + /* Mechanics */ df::unit_labor::MECHANIC, + /* Siege */ df::unit_labor::SIEGECRAFT, + /* Butchers */ df::unit_labor::BUTCHER, + /* Leatherworks */ df::unit_labor::LEATHER, + /* Tanners */ df::unit_labor::TANNER, + /* Clothiers */ df::unit_labor::CLOTHESMAKER, + /* Fishery */ df::unit_labor::FISH, + /* Still */ df::unit_labor::BREWER, + /* Loom */ df::unit_labor::WEAVER, + /* Quern */ df::unit_labor::MILLER, + /* Kennels */ df::unit_labor::ANIMALTRAIN, + /* Kitchen */ df::unit_labor::COOK, + /* Ashery */ df::unit_labor::LYE_MAKING, + /* Dyers */ df::unit_labor::DYER, + /* Millstone */ df::unit_labor::MILLER, + /* Custom */ df::unit_labor::NONE, + /* Tool */ df::unit_labor::NONE +}; + +class jlfunc +{ + public: + virtual df::unit_labor get_labor(df::job* j) = 0; +}; + +class jlfunc_const : public jlfunc +{ + private: + df::unit_labor labor; + public: + df::unit_labor get_labor(df::job* j) + { + return labor; + } + jlfunc_const(df::unit_labor l) : labor(l) {}; +}; + +class jlfunc_hauling : public jlfunc { - /* Carpenters */ df::job_skill::CARPENTRY, - /* Farmers */ df::job_skill::PROCESSPLANTS, - /* Masons */ df::job_skill::MASONRY, - /* Craftsdwarfs */ df::job_skill::STONECRAFT, - /* Jewelers */ df::job_skill::CUTGEM, - /* MetalsmithsForge */ df::job_skill::METALCRAFT, - /* MagmaForge */ df::job_skill::METALCRAFT, - /* Bowyers */ df::job_skill::BOWYER, - /* Mechanics */ df::job_skill::MECHANICS, - /* Siege */ df::job_skill::SIEGECRAFT, - /* Butchers */ df::job_skill::BUTCHER, - /* Leatherworks */ df::job_skill::LEATHERWORK, - /* Tanners */ df::job_skill::TANNER, - /* Clothiers */ df::job_skill::CLOTHESMAKING, - /* Fishery */ df::job_skill::FISH, - /* Still */ df::job_skill::BREWING, - /* Loom */ df::job_skill::WEAVING, - /* Quern */ df::job_skill::MILLING, - /* Kennels */ df::job_skill::ANIMALTRAIN, - /* Kitchen */ df::job_skill::COOK, - /* Ashery */ df::job_skill::LYE_MAKING, - /* Dyers */ df::job_skill::DYER, - /* Millstone */ df::job_skill::MILLING, - /* Custom */ (df::job_skill) -1, - /* Tool */ (df::job_skill) -1 +public: + df::unit_labor get_labor(df::job* j) + { + df::item* item = j->items[0]->item; + return hauling_labor_map[item->getType()]; + } + jlfunc_hauling() {}; +}; + +class jlfunc_construct_bld : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job (j); + switch (bld->getType()) + { + case df::building_type::Workshop: + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + + break; + } + + // FIXME + return df::unit_labor::NONE; + } + jlfunc_construct_bld() {} +}; + +class jlfunc_destroy_bld : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job (j); + df::building_type type = bld->getType(); + + // FIXME + return df::unit_labor::NONE; + } + jlfunc_destroy_bld() {} }; +class jlfunc_make : public jlfunc +{ +private: + df::unit_labor metaltype; +public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job(j); + if (bld->getType() == df::building_type::Workshop) + { + df::workshop_type type = ((df::building_workshopst*)(bld))->type; + switch (type) + { + case df::workshop_type::Craftsdwarfs: + { + df::item_type jobitem = j->job_items[0]->item_type; + switch (jobitem) + { + case df::item_type::BOULDER: + return df::unit_labor::STONE_CRAFT; + case df::item_type::NONE: + if (j->material_category.bits.bone) + return df::unit_labor::BONE_CARVE; + else + return df::unit_labor::NONE; //FIXME + default: + return df::unit_labor::NONE; //FIXME + } + } + case df::workshop_type::Masons: + return df::unit_labor::MASON; + case df::workshop_type::Carpenters: + return df::unit_labor::CARPENTER; + case df::workshop_type::Leatherworks: + return df::unit_labor::LEATHER; + case df::workshop_type::Clothiers: + return df::unit_labor::CLOTHESMAKER; + case df::workshop_type::MagmaForge: + case df::workshop_type::MetalsmithsForge: + return metaltype; + default: + return df::unit_labor::NONE; // FIXME + } + } + else if (bld->getType() == df::building_type::Furnace) + { + df::furnace_type type = ((df::building_furnacest*)(bld))->type; + switch (type) + { + case df::furnace_type::MagmaGlassFurnace: + case df::furnace_type::GlassFurnace: + return df::unit_labor::GLASSMAKER; + default: + return df::unit_labor::NONE; // FIXME + } + } + + return df::unit_labor::NONE; // FIXME + } + + jlfunc_make (df::unit_labor mt) : metaltype(mt) {} +}; + +class jlfunc_custom : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + df::job_skill skill = (*r)->skill; + df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); + return labor; + } + } + return df::unit_labor::NONE; + } + jlfunc_custom() {} +}; + +static map jlf_cache; + +jlfunc* jlf_const(df::unit_labor l) { + jlfunc* jlf; + if (jlf_cache.count(l) == 0) + { + jlf = jlf_const(l); + jlf_cache[l] = jlf; + } + else + jlf = jlf_cache[l]; + + return jlf; +} + +static jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); +static jlfunc* jlf_hauling = new jlfunc_hauling(); +static jlfunc* jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); +static jlfunc* jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); +static jlfunc* jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); +static jlfunc* jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); + +static jlfunc* job_to_labor_table[] = { + jlf_const(df::unit_labor::DETAIL) /* CarveFortification */, + jlf_const(df::unit_labor::DETAIL) /* DetailWall */, + jlf_const(df::unit_labor::DETAIL) /* DetailFloor */, + jlf_const(df::unit_labor::MINE) /* Dig */, + jlf_const(df::unit_labor::MINE) /* CarveUpwardStaircase */, + jlf_const(df::unit_labor::MINE) /* CarveDownwardStaircase */, + jlf_const(df::unit_labor::MINE) /* CarveUpDownStaircase */, + jlf_const(df::unit_labor::MINE) /* CarveRamp */, + jlf_const(df::unit_labor::MINE) /* DigChannel */, + jlf_const(df::unit_labor::CUTWOOD) /* FellTree */, + jlf_const(df::unit_labor::HERBALIST) /* GatherPlants */, + jlf_no_labor /* RemoveConstruction */, + jlf_const(df::unit_labor::WEAVER) /* CollectWebs */, + jlf_no_labor /* BringItemToDepot */, + jlf_no_labor /* BringItemToShop */, + jlf_no_labor /* Eat */, + jlf_no_labor /* GetProvisions */, + jlf_no_labor /* Drink */, + jlf_no_labor /* Drink2 */, + jlf_no_labor /* FillWaterskin */, + jlf_no_labor /* FillWaterskin2 */, + jlf_no_labor /* Sleep */, + jlf_const(df::unit_labor::GLASSMAKER) /* CollectSand */, + jlf_const(df::unit_labor::FISH) /* Fish */, + jlf_const(df::unit_labor::HUNT) /* Hunt */, + jlf_no_labor /* HuntVermin */, + jlf_no_labor /* Kidnap */, + jlf_no_labor /* BeatCriminal */, + jlf_no_labor /* StartingFistFight */, + jlf_no_labor /* CollectTaxes */, + jlf_no_labor /* GuardTaxCollector */, + jlf_const(df::unit_labor::HUNT) /* CatchLiveLandAnimal */, + jlf_const(df::unit_labor::FISH) /* CatchLiveFish */, + jlf_no_labor /* ReturnKill */, + jlf_no_labor /* CheckChest */, + jlf_no_labor /* StoreOwnedItem */, + jlf_const(df::unit_labor::HAUL_BODY) /* PlaceItemInTomb */, + jlf_hauling /* StoreItemInStockpile */, + jlf_hauling /* StoreItemInBag */, + jlf_hauling /* StoreItemInHospital */, + jlf_hauling /* StoreItemInChest */, + jlf_hauling /* StoreItemInCabinet */, + jlf_hauling /* StoreWeapon */, + jlf_hauling /* StoreArmor */, + jlf_hauling /* StoreItemInBarrel */, + jlf_hauling /* StoreItemInBin */, + jlf_no_labor /* SeekArtifact */, + jlf_no_labor /* SeekInfant */, + jlf_no_labor /* AttendParty */, + jlf_no_labor /* GoShopping */, + jlf_no_labor /* GoShopping2 */, + jlf_const(df::unit_labor::CLEAN) /* Clean */, + jlf_no_labor /* Rest */, + jlf_no_labor /* PickupEquipment */, + jlf_hauling /* DumpItem */, + jlf_no_labor /* StrangeMoodCrafter */, + jlf_no_labor /* StrangeMoodJeweller */, + jlf_no_labor /* StrangeMoodForge */, + jlf_no_labor /* StrangeMoodMagmaForge */, + jlf_no_labor /* StrangeMoodBrooding */, + jlf_no_labor /* StrangeMoodFell */, + jlf_no_labor /* StrangeMoodCarpenter */, + jlf_no_labor /* StrangeMoodMason */, + jlf_no_labor /* StrangeMoodBowyer */, + jlf_no_labor /* StrangeMoodTanner */, + jlf_no_labor /* StrangeMoodWeaver */, + jlf_no_labor /* StrangeMoodGlassmaker */, + jlf_no_labor /* StrangeMoodMechanics */, + new jlfunc_construct_bld() /* ConstructBuilding */, + jlf_make_furniture /* ConstructDoor */, + jlf_make_furniture /* ConstructFloodgate */, + jlf_make_furniture /* ConstructBed */, + jlf_make_furniture /* ConstructThrone */, + jlf_make_furniture /* ConstructCoffin */, + jlf_make_furniture /* ConstructTable */, + jlf_make_furniture /* ConstructChest */, + jlf_make_furniture /* ConstructBin */, + jlf_make_furniture /* ConstructArmorStand */, + jlf_make_furniture /* ConstructWeaponRack */, + jlf_make_furniture /* ConstructCabinet */, + jlf_make_furniture /* ConstructStatue */, + jlf_make_furniture /* ConstructBlocks */, + jlf_const(df::unit_labor::GLASSMAKER) /* MakeRawGlass */, + jlf_make_object /* MakeCrafts */, + jlf_const(df::unit_labor::METAL_CRAFT) /* MintCoins */, + jlf_const(df::unit_labor::CUT_GEM) /* CutGems */, + jlf_const(df::unit_labor::CUT_GEM) /* CutGlass */, + jlf_const(df::unit_labor::ENCRUST_GEM) /* EncrustWithGems */, + jlf_const(df::unit_labor::ENCRUST_GEM) /* EncrustWithGlass */, + new jlfunc_destroy_bld() /* DestroyBuilding */, + jlf_const(df::unit_labor::SMELT) /* SmeltOre */, + jlf_const(df::unit_labor::SMELT) /* MeltMetalObject */, + jlf_const(df::unit_labor::EXTRACT_STRAND) /* ExtractMetalStrands */, + jlf_const(df::unit_labor::PLANT) /* PlantSeeds */, + jlf_const(df::unit_labor::PLANT) /* HarvestPlants */, + jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainHuntingAnimal */, + jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainWarAnimal */, + jlf_make_weapon /* MakeWeapon */, + jlf_make_furniture /* ForgeAnvil */, + jlf_const(df::unit_labor::SIEGECRAFT) /* ConstructCatapultParts */, + jlf_const(df::unit_labor::SIEGECRAFT) /* ConstructBallistaParts */, + jlf_make_armor /* MakeArmor */, + jlf_make_armor /* MakeHelm */, + jlf_make_armor /* MakePants */, + jlf_make_object /* StudWith */, + jlf_const(df::unit_labor::BUTCHER) /* ButcherAnimal */, + jlf_const(df::unit_labor::CLEAN_FISH) /* PrepareRawFish */, + jlf_const(df::unit_labor::MILLER) /* MillPlants */, + jlf_const(df::unit_labor::TRAPPER) /* BaitTrap */, + jlf_const(df::unit_labor::MILK) /* MilkCreature */, + jlf_const(df::unit_labor::MAKE_CHEESE) /* MakeCheese */, + jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlants */, + jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsBag */, + jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsVial */, + jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsBarrel */, + jlf_const(df::unit_labor::COOK) /* PrepareMeal */, + jlf_const(df::unit_labor::WEAVER) /* WeaveCloth */, + jlf_make_armor /* MakeGloves */, + jlf_make_armor /* MakeShoes */, + jlf_make_armor /* MakeShield */, + jlf_make_furniture /* MakeCage */, + jlf_make_object /* MakeChain */, + jlf_make_object /* MakeFlask */, + jlf_make_object /* MakeGoblet */, + jlf_make_object/* MakeInstrument */, + jlf_make_object/* MakeToy */, + jlf_const(df::unit_labor::TRAPPER) /* MakeAnimalTrap */, + jlf_make_furniture /* MakeBarrel */, + jlf_make_furniture /* MakeBucket */, + jlf_make_furniture /* MakeWindow */, + jlf_const(df::unit_labor::BONE_CARVE) /* MakeTotem */, + jlf_make_weapon /* MakeAmmo */, + jlf_make_object /* DecorateWith */, + jlf_make_object /* MakeBackpack */, + jlf_make_armor /* MakeQuiver */, + jlf_make_weapon /* MakeBallistaArrowHead */, + jlf_const(df::unit_labor::SIEGECRAFT) /* AssembleSiegeAmmo */, + jlf_const(df::unit_labor::SIEGEOPERATE) /* LoadCatapult */, + jlf_const(df::unit_labor::SIEGEOPERATE) /* LoadBallista */, + jlf_const(df::unit_labor::SIEGEOPERATE) /* FireCatapult */, + jlf_const(df::unit_labor::SIEGEOPERATE) /* FireBallista */, + jlf_const(df::unit_labor::MECHANIC) /* ConstructMechanisms */, + jlf_const(df::unit_labor::MECHANIC) /* MakeTrapComponent */, + jlf_const(df::unit_labor::MECHANIC) /* LoadCageTrap */, + jlf_const(df::unit_labor::MECHANIC) /* LoadStoneTrap */, + jlf_const(df::unit_labor::MECHANIC) /* LoadWeaponTrap */, + jlf_const(df::unit_labor::MECHANIC) /* CleanTrap */, + jlf_no_labor /* CastSpell */, + jlf_const(df::unit_labor::MECHANIC) /* LinkBuildingToTrigger */, + jlf_no_labor /* PullLever */, + jlf_const(df::unit_labor::BREWER) /* BrewDrink */, + jlf_const(df::unit_labor::HERBALIST) /* ExtractFromPlants */, + jlf_const(df::unit_labor::DISSECT_FISH) /* ExtractFromRawFish */, + jlf_const(df::unit_labor::DISSECT_VERMIN) /* ExtractFromLandAnimal */, + jlf_const(df::unit_labor::ANIMALTRAIN) /* TameVermin */, + jlf_const(df::unit_labor::ANIMALTRAIN) /* TameAnimal */, + jlf_no_labor /* ChainAnimal */, + jlf_no_labor /* UnchainAnimal */, + jlf_no_labor /* UnchainPet */, + jlf_no_labor /* ReleaseLargeCreature */, + jlf_no_labor /* ReleasePet */, + jlf_no_labor /* ReleaseSmallCreature */, + jlf_no_labor /* HandleSmallCreature */, + jlf_no_labor /* HandleLargeCreature */, + jlf_no_labor /* CageLargeCreature */, + jlf_no_labor /* CageSmallCreature */, + jlf_const(df::unit_labor::RECOVER_WOUNDED) /* RecoverWounded */, + jlf_const(df::unit_labor::DIAGNOSE) /* DiagnosePatient */, + jlf_const(df::unit_labor::BONE_SETTING) /* ImmobilizeBreak */, + jlf_const(df::unit_labor::DRESSING_WOUNDS) /* DressWound */, + jlf_const(df::unit_labor::CLEAN) /* CleanPatient */, + jlf_const(df::unit_labor::SURGERY) /* Surgery */, + jlf_const(df::unit_labor::SUTURING) /* Suture */, + jlf_const(df::unit_labor::BONE_SETTING) /* SetBone */, + jlf_const(df::unit_labor::BONE_SETTING) /* PlaceInTraction */, + jlf_no_labor /* DrainAquarium */, + jlf_no_labor /* FillAquarium */, + jlf_no_labor /* FillPond */, + jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) /* GiveWater */, + jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) /* GiveFood */, + jlf_no_labor /* GiveWater2 */, + jlf_no_labor /* GiveFood2 */, + jlf_no_labor /* RecoverPet */, + jlf_no_labor /* PitLargeAnimal */, + jlf_no_labor /* PitSmallAnimal */, + jlf_const(df::unit_labor::BUTCHER) /* SlaughterAnimal */, + jlf_const(df::unit_labor::BURN_WOOD) /* MakeCharcoal */, + jlf_const(df::unit_labor::BURN_WOOD) /* MakeAsh */, + jlf_const(df::unit_labor::LYE_MAKING) /* MakeLye */, + jlf_const(df::unit_labor::POTASH_MAKING) /* MakePotashFromLye */, + jlf_const(df::unit_labor::PLANT) /* FertilizeField */, + jlf_const(df::unit_labor::POTASH_MAKING) /* MakePotashFromAsh */, + jlf_const(df::unit_labor::DYER) /* DyeThread */, + jlf_const(df::unit_labor::DYER) /* DyeCloth */, + jlf_make_object /* SewImage */, + jlf_make_furniture /* MakePipeSection */, + jlf_const(df::unit_labor::OPERATE_PUMP) /* OperatePump */, + jlf_no_labor /* ManageWorkOrders */, + jlf_no_labor /* UpdateStockpileRecords */, + jlf_no_labor /* TradeAtDepot */, + jlf_make_furniture /* ConstructHatchCover */, + jlf_make_furniture /* ConstructGrate */, + jlf_const(df::unit_labor::MINE) /* RemoveStairs */, + jlf_make_furniture /* ConstructQuern */, + jlf_make_furniture /* ConstructMillstone */, + jlf_make_object /* ConstructSplint */, + jlf_make_object /* ConstructCrutch */, + jlf_const(df::unit_labor::MECHANIC) /* ConstructTractionBench */, + jlf_no_labor /* CleanSelf */, + jlf_no_labor /* BringCrutch */, + jlf_const(df::unit_labor::BONE_SETTING) /* ApplyCast */, + new jlfunc_custom() /* CustomReaction */, + jlf_make_furniture /* ConstructSlab */, + jlf_const(df::unit_labor::STONE_CRAFT) /* EngraveSlab */, + jlf_const(df::unit_labor::SHEARER) /* ShearCreature */, + jlf_const(df::unit_labor::SPINNER) /* SpinThread */, + jlf_no_labor /* PenLargeAnimal */, + jlf_no_labor /* PenSmallAnimal */, + jlf_make_furniture /* MakeTool */, + jlf_const(df::unit_labor::POTTERY) /* CollectClay */, + jlf_const(df::unit_labor::BEEKEEPING) /* InstallColonyInHive */, + jlf_const(df::unit_labor::BEEKEEPING) /* CollectHiveProducts */, + jlf_no_labor /* CauseTrouble */, + jlf_no_labor /* DrinkBlood */, + jlf_no_labor /* ReportCrime */, + jlf_no_labor /* ExecuteCriminal */, + jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainAnimal */, + jlf_const(df::unit_labor::DETAIL) /* CarveTrack */, + jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* PushTrackVehicle */, + jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* PlaceTrackVehicle */, + jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* StoreItemInVehicle */ +}; + + static df::unit_labor find_job_labor(df::job* j) { - df::job_skill skill; - df::unit_labor labor; - - labor = df::unit_labor::NONE; - - switch (j->job_type) - { - case df::job_type::ConstructBuilding: - case df::job_type::DestroyBuilding: - { - df::building* bld = get_building_from_job (j); - switch (bld->getType()) - { - case df::building_type::Workshop: - df::building_workshopst* ws = (df::building_workshopst*) bld; - skill = workshop_build_labor[ws->type]; - break; - } - } - break; - - case df::job_type::CustomReaction: - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) - { - if ((*r)->code == j->reaction_name) - { - skill = (*r)->skill; - break; - } - } - break; - default: - skill = ENUM_ATTR(job_type, skill, j->job_type); - } - - if (skill != df::job_skill::NONE) - labor = ENUM_ATTR(job_skill, labor, skill); - - if (labor == df::unit_labor::NONE) - labor = ENUM_ATTR(job_type, labor, j->job_type); - - if (labor == -1) - { - } - - return labor; + if (j->job_type == df::job_type::CustomReaction) + { + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + df::job_skill skill = (*r)->skill; + return ENUM_ATTR(job_skill, labor, skill); + } + } + return df::unit_labor::NONE; + } + + df::job_skill skill; + df::unit_labor labor; + skill = ENUM_ATTR(job_type, skill, j->job_type); + if (skill != df::job_skill::NONE) + labor = ENUM_ATTR(job_skill, labor, skill); + else + labor = ENUM_ATTR(job_type, labor, j->job_type); + + if (labor == df::unit_labor::NONE) + labor = job_to_labor_table[j->job_type]->get_labor(j); + + return labor; } class AutoLaborManager { - color_ostream& out; + color_ostream& out; public: - AutoLaborManager(color_ostream& o) : out(o) - { - } + AutoLaborManager(color_ostream& o) : out(o) + { + } - ~AutoLaborManager() - { - for (std::vector::iterator i = dwarf_info.begin(); - i != dwarf_info.end(); i++) - delete (*i); - } + ~AutoLaborManager() + { + for (std::vector::iterator i = dwarf_info.begin(); + i != dwarf_info.end(); i++) + delete (*i); + } - dwarf_info_t* add_dwarf(df::unit* u) - { - dwarf_info_t* dwarf = new dwarf_info_t(u); - dwarf_info.push_back(dwarf); - return dwarf; - } + dwarf_info_t* add_dwarf(df::unit* u) + { + dwarf_info_t* dwarf = new dwarf_info_t(u); + dwarf_info.push_back(dwarf); + return dwarf; + } private: - bool has_butchers; - bool has_fishery; - bool trader_requested; + bool has_butchers; + bool has_fishery; + bool trader_requested; - int dig_count; - int tree_count; - int plant_count; - int detail_count; - int pick_count; - int axe_count; + int dig_count; + int tree_count; + int plant_count; + int detail_count; + int pick_count; + int axe_count; - std::map labor_needed; - std::vector dwarf_info; - std::deque idle_dwarfs; + std::map labor_needed; + std::vector dwarf_info; + std::deque idle_dwarfs; private: - void scan_buildings() - { - for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) - { - df::building *build = *b; - auto type = build->getType(); - if (building_type::Workshop == type) - { - df::workshop_type subType = (df::workshop_type)build->getSubtype(); - if (workshop_type::Butchers == subType) - has_butchers = true; - if (workshop_type::Fishery == subType) - has_fishery = true; - } - else if (building_type::TradeDepot == type) - { - df::building_tradedepotst* depot = (df::building_tradedepotst*) build; - trader_requested = depot->trade_flags.bits.trader_requested; - if (print_debug) - { - if (trader_requested) - out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); - else - out.print("Trade depot found but trader is not requested.\n"); - } - } - } - } - - void count_map_designations() - { - dig_count = 0; - tree_count = 0; - plant_count = 0; - detail_count = 0; - - for (int i = 0; i < world->map.map_blocks.size(); ++i) - { - df::map_block* bl = world->map.map_blocks[i]; - - if (!bl->flags.bits.designated) - continue; - - if (print_debug) - out.print ("block with designations found: %d, %d, %d\n", bl->map_pos.x, bl->map_pos.y, bl->map_pos.z); - - for (int x = 0; x < 16; x++) - for (int y = 0; y < 16; y++) - { - df::tile_dig_designation dig = bl->designation[x][y].bits.dig; - if (dig != df::enums::tile_dig_designation::No) - { - df::tiletype tt = bl->tiletype[x][y]; - df::tiletype_shape tts = ENUM_ATTR(tiletype, shape, tt); - switch (tts) - { - case df::enums::tiletype_shape::TREE: - tree_count++; break; - case df::enums::tiletype_shape::SHRUB: - plant_count++; break; - default: - dig_count++; break; - } - } - if (bl->designation[x][y].bits.smooth != 0) - detail_count++; - } - } - - if (print_debug) - out.print("Dig count = %d, Cut tree count = %d, gather plant count = %d, detail count = %d\n", dig_count, tree_count, plant_count, detail_count); - - } - - void count_tools() - { - pick_count = 0; - axe_count = 0; - - df::item_flags bad_flags; - bad_flags.whole = 0; + void scan_buildings() + { + for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) + { + df::building *build = *b; + auto type = build->getType(); + if (building_type::Workshop == type) + { + df::workshop_type subType = (df::workshop_type)build->getSubtype(); + if (workshop_type::Butchers == subType) + has_butchers = true; + if (workshop_type::Fishery == subType) + has_fishery = true; + } + else if (building_type::TradeDepot == type) + { + df::building_tradedepotst* depot = (df::building_tradedepotst*) build; + trader_requested = depot->trade_flags.bits.trader_requested; + if (print_debug) + { + if (trader_requested) + out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); + else + out.print("Trade depot found but trader is not requested.\n"); + } + } + } + } + + void count_map_designations() + { + dig_count = 0; + tree_count = 0; + plant_count = 0; + detail_count = 0; + + for (int i = 0; i < world->map.map_blocks.size(); ++i) + { + df::map_block* bl = world->map.map_blocks[i]; + + if (!bl->flags.bits.designated) + continue; + + if (print_debug) + out.print ("block with designations found: %d, %d, %d\n", bl->map_pos.x, bl->map_pos.y, bl->map_pos.z); + + for (int x = 0; x < 16; x++) + for (int y = 0; y < 16; y++) + { + df::tile_dig_designation dig = bl->designation[x][y].bits.dig; + if (dig != df::enums::tile_dig_designation::No) + { + df::tiletype tt = bl->tiletype[x][y]; + df::tiletype_shape tts = ENUM_ATTR(tiletype, shape, tt); + switch (tts) + { + case df::enums::tiletype_shape::TREE: + tree_count++; break; + case df::enums::tiletype_shape::SHRUB: + plant_count++; break; + default: + dig_count++; break; + } + } + if (bl->designation[x][y].bits.smooth != 0) + detail_count++; + } + } + + if (print_debug) + out.print("Dig count = %d, Cut tree count = %d, gather plant count = %d, detail count = %d\n", dig_count, tree_count, plant_count, detail_count); + + } + + void count_tools() + { + pick_count = 0; + axe_count = 0; + + df::item_flags bad_flags; + bad_flags.whole = 0; #define F(x) bad_flags.bits.x = true; - F(dump); F(forbid); F(garbage_collect); - F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact); + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); #undef F - for (int i = 0; i < world->items.all.size(); ++i) - { - df::item* item = world->items.all[i]; - if (item->flags.whole & bad_flags.whole) - continue; - - if (!item->isWeapon()) - continue; - - df::itemdef_weaponst* weapondef = ((df::item_weaponst*)item)->subtype; - df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; - if (weaponsk == df::job_skill::AXE) - axe_count++; - else if (weaponsk == df::job_skill::MINING) - pick_count++; - } - - if (print_debug) - out.print("Axes = %d, picks = %d\n", axe_count, pick_count); - - } - - void collect_job_list() - { - for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) - { - df::job* j = jll->item; - if (!j) - continue; - - if (j->flags.bits.suspend) - continue; - - int worker = -1; - - for (int r = 0; r < j->general_refs.size(); ++r) - if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) - worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; - - if (worker != -1) - continue; - - df::unit_labor labor = find_job_labor (j); - - if (print_debug) - out.print ("Job requiring labor %d found\n", labor); - - if (labor != df::unit_labor::NONE) - labor_needed[labor]++; - } - - } - - void collect_dwarf_list() - { - - for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) - { - df::unit* cre = *u; - - if (Units::isCitizen(cre)) - { - if (cre->burrows.size() > 0) - continue; // dwarfs assigned to burrows are skipped entirely - - dwarf_info_t* dwarf = add_dwarf(cre); - - df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); - for (int i = 0; i < hf->entity_links.size(); i++) - { - df::histfig_entity_link* hfelink = hf->entity_links.at(i); - if (hfelink->getType() == df::histfig_entity_link_type::POSITION) - { - df::histfig_entity_link_positionst *epos = - (df::histfig_entity_link_positionst*) hfelink; - df::historical_entity* entity = df::historical_entity::find(epos->entity_id); - if (!entity) - continue; - df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); - if (!assignment) - continue; - df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); - if (!position) - continue; - - if (position->responsibilities[df::entity_position_responsibility::TRADE]) - if (trader_requested) - dwarf->clear_all = true; - } - - } - - // identify dwarfs who are needed for meetings and mark them for exclusion - - for (int i = 0; i < ui->activities.size(); ++i) - { - df::activity_info *act = ui->activities[i]; - if (!act) continue; - bool p1 = act->person1 == dwarf->dwarf; - bool p2 = act->person2 == dwarf->dwarf; - - if (p1 || p2) - { - dwarf->clear_all = true; - if (print_debug) - out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); - break; - } - } - - // Find the activity state for each dwarf-> - - bool is_on_break = false; - dwarf_state state = OTHER; - - for (auto p = dwarf->dwarf->status.misc_traits.begin(); p < dwarf->dwarf->status.misc_traits.end(); p++) - { - if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) - is_on_break = true; - } - - if (dwarf->dwarf->profession == profession::BABY || - dwarf->dwarf->profession == profession::CHILD || - dwarf->dwarf->profession == profession::DRUNK) - { - state = CHILD; - } - else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) - state = MILITARY; - else if (dwarf->dwarf->job.current_job == NULL) - { - if (is_on_break) - state = OTHER; - else if (dwarf->dwarf->specific_refs.size() > 0) - state = OTHER; - else - state = IDLE; - } - else - { - int job = dwarf->dwarf->job.current_job->job_type; - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) - state = dwarf_states[job]; - else - { - out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); - state = OTHER; - } - } - - dwarf->state = state; - - if (print_debug) - out.print("Dwarf \"%s\": state %s\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state]); - - // check if dwarf has an axe, pick, or crossbow - - for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) - { - df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; - if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) - { - df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; - df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; - df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; - if (weaponsk == df::job_skill::AXE) - { - dwarf->has_axe = 1; - if (state != IDLE) - axe_count--; - if (print_debug) - out.print("Dwarf \"%s\" has an axe\n", dwarf->dwarf->name.first_name.c_str()); - } - else if (weaponsk == df::job_skill::MINING) - { - dwarf->has_pick = 1; - if (state != IDLE) - pick_count--; - if (print_debug) - out.print("Dwarf \"%s\" has an pick\n", dwarf->dwarf->name.first_name.c_str()); - } - else if (rangesk == df::job_skill::CROSSBOW) - { - dwarf->has_crossbow = 1; - if (print_debug) - out.print("Dwarf \"%s\" has a crossbow\n", dwarf->dwarf->name.first_name.c_str()); - } - } - } - - // clear labors if currently idle - - if (state == IDLE || dwarf->clear_all) - { - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - dwarf->dwarf->status.labors[labor] = false; - } - } - - if (state == IDLE && !dwarf->clear_all) - idle_dwarfs.push_back(dwarf); - - } - - } - } + for (int i = 0; i < world->items.all.size(); ++i) + { + df::item* item = world->items.all[i]; + if (item->flags.whole & bad_flags.whole) + continue; + + if (!item->isWeapon()) + continue; + + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)item)->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + if (weaponsk == df::job_skill::AXE) + axe_count++; + else if (weaponsk == df::job_skill::MINING) + pick_count++; + } -public: - void process() - { - // scan for specific buildings of interest + if (print_debug) + out.print("Axes = %d, picks = %d\n", axe_count, pick_count); + + } + + void collect_job_list() + { + for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) + { + df::job* j = jll->item; + if (!j) + continue; + + if (j->flags.bits.suspend) + continue; + + int worker = -1; + + for (int r = 0; r < j->general_refs.size(); ++r) + if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) + worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; + + if (worker != -1) + continue; + + df::unit_labor labor = find_job_labor (j); + + if (print_debug) + out.print ("Job requiring labor %d found\n", labor); + + if (labor != df::unit_labor::NONE) + labor_needed[labor]++; + } + + } + + void collect_dwarf_list() + { + + for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) + { + df::unit* cre = *u; + + if (Units::isCitizen(cre)) + { + if (cre->burrows.size() > 0) + continue; // dwarfs assigned to burrows are skipped entirely + + dwarf_info_t* dwarf = add_dwarf(cre); + + df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); + for (int i = 0; i < hf->entity_links.size(); i++) + { + df::histfig_entity_link* hfelink = hf->entity_links.at(i); + if (hfelink->getType() == df::histfig_entity_link_type::POSITION) + { + df::histfig_entity_link_positionst *epos = + (df::histfig_entity_link_positionst*) hfelink; + df::historical_entity* entity = df::historical_entity::find(epos->entity_id); + if (!entity) + continue; + df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); + if (!assignment) + continue; + df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); + if (!position) + continue; + + if (position->responsibilities[df::entity_position_responsibility::TRADE]) + if (trader_requested) + dwarf->clear_all = true; + } + + } + + // identify dwarfs who are needed for meetings and mark them for exclusion + + for (int i = 0; i < ui->activities.size(); ++i) + { + df::activity_info *act = ui->activities[i]; + if (!act) continue; + bool p1 = act->person1 == dwarf->dwarf; + bool p2 = act->person2 == dwarf->dwarf; + + if (p1 || p2) + { + dwarf->clear_all = true; + if (print_debug) + out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); + break; + } + } + + // Find the activity state for each dwarf-> + + bool is_on_break = false; + dwarf_state state = OTHER; + + for (auto p = dwarf->dwarf->status.misc_traits.begin(); p < dwarf->dwarf->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + is_on_break = true; + } + + if (dwarf->dwarf->profession == profession::BABY || + dwarf->dwarf->profession == profession::CHILD || + dwarf->dwarf->profession == profession::DRUNK) + { + state = CHILD; + } + else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) + state = MILITARY; + else if (dwarf->dwarf->job.current_job == NULL) + { + if (is_on_break) + state = OTHER; + else if (dwarf->dwarf->specific_refs.size() > 0) + state = OTHER; + else + state = IDLE; + } + else + { + int job = dwarf->dwarf->job.current_job->job_type; + if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + state = dwarf_states[job]; + else + { + out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); + state = OTHER; + } + } + + dwarf->state = state; + + if (print_debug) + out.print("Dwarf \"%s\": state %s\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state]); + + // check if dwarf has an axe, pick, or crossbow + + for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) + { + df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; + if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) + { + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; + if (weaponsk == df::job_skill::AXE) + { + dwarf->has_axe = 1; + if (state != IDLE) + axe_count--; + if (print_debug) + out.print("Dwarf \"%s\" has an axe\n", dwarf->dwarf->name.first_name.c_str()); + } + else if (weaponsk == df::job_skill::MINING) + { + dwarf->has_pick = 1; + if (state != IDLE) + pick_count--; + if (print_debug) + out.print("Dwarf \"%s\" has an pick\n", dwarf->dwarf->name.first_name.c_str()); + } + else if (rangesk == df::job_skill::CROSSBOW) + { + dwarf->has_crossbow = 1; + if (print_debug) + out.print("Dwarf \"%s\" has a crossbow\n", dwarf->dwarf->name.first_name.c_str()); + } + } + } + + // clear labors if currently idle + + if (state == IDLE || dwarf->clear_all) + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + dwarf->dwarf->status.labors[labor] = false; + } + } + + if (state == IDLE && !dwarf->clear_all) + idle_dwarfs.push_back(dwarf); + + } - scan_buildings(); + } + } - // count number of squares designated for dig, wood cutting, detailing, and plant harvesting +public: + void process() + { + // scan for specific buildings of interest - count_map_designations(); + scan_buildings(); - // count number of picks and axes available for use + // count number of squares designated for dig, wood cutting, detailing, and plant harvesting - count_tools(); + count_map_designations(); - // create job entries for designation + // count number of picks and axes available for use - // collect current job list + count_tools(); - collect_job_list(); + // create job entries for designation - // collect list of dwarfs + // collect current job list - collect_dwarf_list(); + collect_job_list(); - // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count - // and remove the idle dwarf from the idle list + // collect list of dwarfs - for (auto i = idle_dwarfs.begin(); i != idle_dwarfs.end(); i++) - { - FOR_ENUM_ITEMS(unit_labor, l) - { - if ((*i)->dwarf->status.labors[l]) - if (labor_needed[l] > 0) - { - if (print_debug) - out.print("assign \"%s\" labor %d (carried through)\n", (*i)->dwarf->name.first_name.c_str(), l); - labor_needed[l]--; - idle_dwarfs.erase(i); // remove from idle list - break; - } else { - (*i)->dwarf->status.labors[l] = false; - } - } - } + collect_dwarf_list(); - priority_queue> pq; - - for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) - { - if (i->second > 0) - pq.push(make_pair(i->second, i->first)); - } - - while (!idle_dwarfs.empty() && !pq.empty()) - { - df::unit_labor labor = pq.top().second; - int remaining = pq.top().first; - df::job_skill skill = labor_to_skill[labor]; + // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count + // and remove the idle dwarf from the idle list - if (print_debug) - out.print("labor %d skill %d remaining %d\n", labor, skill, remaining); + for (auto i = idle_dwarfs.begin(); i != idle_dwarfs.end(); i++) + { + FOR_ENUM_ITEMS(unit_labor, l) + { + if ((*i)->dwarf->status.labors[l]) + if (labor_needed[l] > 0) + { + if (print_debug) + out.print("assign \"%s\" labor %d (carried through)\n", (*i)->dwarf->name.first_name.c_str(), l); + labor_needed[l]--; + idle_dwarfs.erase(i); // remove from idle list + break; + } else { + (*i)->dwarf->status.labors[l] = false; + } + } + } - std::deque::iterator bestdwarf = idle_dwarfs.begin(); + priority_queue> pq; + + for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) + { + if (i->second > 0) + pq.push(make_pair(i->second, i->first)); + } + + while (!idle_dwarfs.empty() && !pq.empty()) + { + df::unit_labor labor = pq.top().second; + int remaining = pq.top().first; + df::job_skill skill = labor_to_skill[labor]; - if (skill != df::job_skill::NONE) - { - int best_skill_level = -1; + if (print_debug) + out.print("labor %d skill %d remaining %d\n", labor, skill, remaining); - for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) - { - dwarf_info_t* d = (*k); - int skill_level = Units::getEffectiveSkill(d->dwarf, skill); + std::deque::iterator bestdwarf = idle_dwarfs.begin(); - if (skill_level > best_skill_level) - { - bestdwarf = k; - best_skill_level = skill_level; - } - } - } + if (skill != df::job_skill::NONE) + { + int best_skill_level = -1; + + for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) + { + dwarf_info_t* d = (*k); + int skill_level = Units::getEffectiveSkill(d->dwarf, skill); + + if (skill_level > best_skill_level) + { + bestdwarf = k; + best_skill_level = skill_level; + } + } + } - if (print_debug) - out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); - (*bestdwarf)->set_labor(labor); + if (print_debug) + out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); + (*bestdwarf)->set_labor(labor); - idle_dwarfs.erase(bestdwarf); - pq.pop(); - if (--remaining) - pq.push(make_pair(remaining, labor)); - } + idle_dwarfs.erase(bestdwarf); + pq.pop(); + if (--remaining) + pq.push(make_pair(remaining, labor)); + } - print_debug = 0; + print_debug = 0; - } + } }; @@ -1284,9 +1771,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; step_count = 0; - AutoLaborManager alm(out); + AutoLaborManager alm(out); - alm.process(); + alm.process(); return CR_OK; From 201430ed08bfa0d9d406c025e4bbe332b9aa0e1f Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 1 Dec 2012 14:08:15 -0600 Subject: [PATCH 007/136] Autolabor: add health awareness, fix initialization crash, fix idle dwarf loop crash --- plugins/autolabor.cpp | 1343 ++++++++++++++++++++++------------------- 1 file changed, 724 insertions(+), 619 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 5736480fb..e1655bba4 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -50,7 +50,8 @@ #include #include #include - +#include +#include #include @@ -67,29 +68,29 @@ using df::global::world; #define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) /* - * Autolabor module for dfhack - * - * The idea behind this module is to constantly adjust labors so that the right dwarves - * are assigned to new tasks. The key is that, for almost all labors, once a dwarf begins - * a job it will finish that job even if the associated labor is removed. Thus the - * strategy is to frequently decide, for each labor, which dwarves should possibly take - * a new job for that labor if it comes in and which shouldn't, and then set the labors - * appropriately. The updating should happen as often as can be reasonably done without - * causing lag. - * - * The obvious thing to do is to just set each labor on a single idle dwarf who is best - * suited to doing new jobs of that labor. This works in a way, but it leads to a lot - * of idle dwarves since only one dwarf will be dispatched for each labor in an update - * cycle, and dwarves that finish tasks will wait for the next update before being - * dispatched. An improvement is to also set some labors on dwarves that are currently - * doing a job, so that they will immediately take a new job when they finish. The - * details of which dwarves should have labors set is mostly a heuristic. - * - * A complication to the above simple scheme is labors that have associated equipment. - * Enabling/disabling these labors causes dwarves to change equipment, and disabling - * them in the middle of a job may cause the job to be abandoned. Those labors - * (mining, hunting, and woodcutting) need to be handled carefully to minimize churn. - */ +* Autolabor module for dfhack +* +* The idea behind this module is to constantly adjust labors so that the right dwarves +* are assigned to new tasks. The key is that, for almost all labors, once a dwarf begins +* a job it will finish that job even if the associated labor is removed. Thus the +* strategy is to frequently decide, for each labor, which dwarves should possibly take +* a new job for that labor if it comes in and which shouldn't, and then set the labors +* appropriately. The updating should happen as often as can be reasonably done without +* causing lag. +* +* The obvious thing to do is to just set each labor on a single idle dwarf who is best +* suited to doing new jobs of that labor. This works in a way, but it leads to a lot +* of idle dwarves since only one dwarf will be dispatched for each labor in an update +* cycle, and dwarves that finish tasks will wait for the next update before being +* dispatched. An improvement is to also set some labors on dwarves that are currently +* doing a job, so that they will immediately take a new job when they finish. The +* details of which dwarves should have labors set is mostly a heuristic. +* +* A complication to the above simple scheme is labors that have associated equipment. +* Enabling/disabling these labors causes dwarves to change equipment, and disabling +* them in the middle of a job may cause the job to be abandoned. Those labors +* (mining, hunting, and woodcutting) need to be handled carefully to minimize churn. +*/ static int enable_autolabor = 0; @@ -538,101 +539,611 @@ struct dwarf_info_t (labor == df::unit_labor::HUNT && !has_crossbow)) dwarf->military.pickup_flags.bits.update = 1; } + + void clear_labor(df::unit_labor labor) + { + dwarf->status.labors[labor] = false; + if ((labor == df::unit_labor::MINE && has_pick) || + (labor == df::unit_labor::CUTWOOD && has_axe) || + (labor == df::unit_labor::HUNT && has_crossbow)) + dwarf->military.pickup_flags.bits.update = 1; + } + }; static df::unit_labor hauling_labor_map[] = +{ + df::unit_labor::HAUL_ITEM, /* BAR */ + df::unit_labor::HAUL_ITEM, /* SMALLGEM */ + df::unit_labor::HAUL_ITEM, /* BLOCKS */ + df::unit_labor::HAUL_ITEM, /* ROUGH */ + df::unit_labor::HAUL_STONE, /* BOULDER */ + df::unit_labor::HAUL_WOOD, /* WOOD */ + df::unit_labor::HAUL_FURNITURE, /* DOOR */ + df::unit_labor::HAUL_FURNITURE, /* FLOODGATE */ + df::unit_labor::HAUL_FURNITURE, /* BED */ + df::unit_labor::HAUL_FURNITURE, /* CHAIR */ + df::unit_labor::HAUL_ITEM, /* CHAIN */ + df::unit_labor::HAUL_ITEM, /* FLASK */ + df::unit_labor::HAUL_ITEM, /* GOBLET */ + df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ + df::unit_labor::HAUL_ITEM, /* TOY */ + df::unit_labor::HAUL_FURNITURE, /* WINDOW */ + df::unit_labor::HAUL_ANIMAL, /* CAGE */ + df::unit_labor::HAUL_ITEM, /* BARREL */ + df::unit_labor::HAUL_ITEM, /* BUCKET */ + df::unit_labor::HAUL_ANIMAL, /* ANIMALTRAP */ + df::unit_labor::HAUL_FURNITURE, /* TABLE */ + df::unit_labor::HAUL_FURNITURE, /* COFFIN */ + df::unit_labor::HAUL_FURNITURE, /* STATUE */ + df::unit_labor::HAUL_BODY, /* CORPSE */ + df::unit_labor::HAUL_ITEM, /* WEAPON */ + df::unit_labor::HAUL_ITEM, /* ARMOR */ + df::unit_labor::HAUL_ITEM, /* SHOES */ + df::unit_labor::HAUL_ITEM, /* SHIELD */ + df::unit_labor::HAUL_ITEM, /* HELM */ + df::unit_labor::HAUL_ITEM, /* GLOVES */ + df::unit_labor::HAUL_FURNITURE, /* BOX */ + df::unit_labor::HAUL_ITEM, /* BIN */ + df::unit_labor::HAUL_FURNITURE, /* ARMORSTAND */ + df::unit_labor::HAUL_FURNITURE, /* WEAPONRACK */ + df::unit_labor::HAUL_FURNITURE, /* CABINET */ + df::unit_labor::HAUL_ITEM, /* FIGURINE */ + df::unit_labor::HAUL_ITEM, /* AMULET */ + df::unit_labor::HAUL_ITEM, /* SCEPTER */ + df::unit_labor::HAUL_ITEM, /* AMMO */ + df::unit_labor::HAUL_ITEM, /* CROWN */ + df::unit_labor::HAUL_ITEM, /* RING */ + df::unit_labor::HAUL_ITEM, /* EARRING */ + df::unit_labor::HAUL_ITEM, /* BRACELET */ + df::unit_labor::HAUL_ITEM, /* GEM */ + df::unit_labor::HAUL_FURNITURE, /* ANVIL */ + df::unit_labor::HAUL_BODY, /* CORPSEPIECE */ + df::unit_labor::HAUL_REFUSE, /* REMAINS */ + df::unit_labor::HAUL_FOOD, /* MEAT */ + df::unit_labor::HAUL_FOOD, /* FISH */ + df::unit_labor::HAUL_FOOD, /* FISH_RAW */ + df::unit_labor::HAUL_REFUSE, /* VERMIN */ + df::unit_labor::HAUL_ITEM, /* PET */ + df::unit_labor::HAUL_FOOD, /* SEEDS */ + df::unit_labor::HAUL_FOOD, /* PLANT */ + df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ + df::unit_labor::HAUL_FOOD, /* LEAVES */ + df::unit_labor::HAUL_ITEM, /* THREAD */ + df::unit_labor::HAUL_ITEM, /* CLOTH */ + df::unit_labor::HAUL_ITEM, /* TOTEM */ + df::unit_labor::HAUL_ITEM, /* PANTS */ + df::unit_labor::HAUL_ITEM, /* BACKPACK */ + df::unit_labor::HAUL_ITEM, /* QUIVER */ + df::unit_labor::HAUL_FURNITURE, /* CATAPULTPARTS */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAPARTS */ + df::unit_labor::HAUL_FURNITURE, /* SIEGEAMMO */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAARROWHEAD */ + df::unit_labor::HAUL_FURNITURE, /* TRAPPARTS */ + df::unit_labor::HAUL_FURNITURE, /* TRAPCOMP */ + df::unit_labor::HAUL_FOOD, /* DRINK */ + df::unit_labor::HAUL_FOOD, /* POWDER_MISC */ + df::unit_labor::HAUL_FOOD, /* CHEESE */ + df::unit_labor::HAUL_FOOD, /* FOOD */ + df::unit_labor::HAUL_FOOD, /* LIQUID_MISC */ + df::unit_labor::HAUL_ITEM, /* COIN */ + df::unit_labor::HAUL_FOOD, /* GLOB */ + df::unit_labor::HAUL_STONE, /* ROCK */ + df::unit_labor::HAUL_FURNITURE, /* PIPE_SECTION */ + df::unit_labor::HAUL_FURNITURE, /* HATCH_COVER */ + df::unit_labor::HAUL_FURNITURE, /* GRATE */ + df::unit_labor::HAUL_FURNITURE, /* QUERN */ + df::unit_labor::HAUL_FURNITURE, /* MILLSTONE */ + df::unit_labor::HAUL_ITEM, /* SPLINT */ + df::unit_labor::HAUL_ITEM, /* CRUTCH */ + df::unit_labor::HAUL_FURNITURE, /* TRACTION_BENCH */ + df::unit_labor::HAUL_ITEM, /* ORTHOPEDIC_CAST */ + df::unit_labor::HAUL_ITEM, /* TOOL */ + df::unit_labor::HAUL_FURNITURE, /* SLAB */ + df::unit_labor::HAUL_FOOD, /* EGG */ + df::unit_labor::HAUL_ITEM, /* BOOK */ +}; + +static df::unit_labor workshop_build_labor[] = +{ + /* Carpenters */ df::unit_labor::CARPENTER, + /* Farmers */ df::unit_labor::HERBALIST, + /* Masons */ df::unit_labor::MASON, + /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, + /* Jewelers */ df::unit_labor::CUT_GEM, + /* MetalsmithsForge */ df::unit_labor::METAL_CRAFT, + /* MagmaForge */ df::unit_labor::METAL_CRAFT, + /* Bowyers */ df::unit_labor::BOWYER, + /* Mechanics */ df::unit_labor::MECHANIC, + /* Siege */ df::unit_labor::SIEGECRAFT, + /* Butchers */ df::unit_labor::BUTCHER, + /* Leatherworks */ df::unit_labor::LEATHER, + /* Tanners */ df::unit_labor::TANNER, + /* Clothiers */ df::unit_labor::CLOTHESMAKER, + /* Fishery */ df::unit_labor::FISH, + /* Still */ df::unit_labor::BREWER, + /* Loom */ df::unit_labor::WEAVER, + /* Quern */ df::unit_labor::MILLER, + /* Kennels */ df::unit_labor::ANIMALTRAIN, + /* Kitchen */ df::unit_labor::COOK, + /* Ashery */ df::unit_labor::LYE_MAKING, + /* Dyers */ df::unit_labor::DYER, + /* Millstone */ df::unit_labor::MILLER, + /* Custom */ df::unit_labor::NONE, + /* Tool */ df::unit_labor::NONE +}; + +static df::building* get_building_from_job(df::job* j) +{ + for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) + { + if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) + { + int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; + df::building* bld = binsearch_in_vector(world->buildings.all, id); + return bld; + } + } + return 0; +} + +class JobLaborMapper { +private: + class jlfunc + { + public: + virtual df::unit_labor get_labor(df::job* j) = 0; + }; + + class jlfunc_const : public jlfunc + { + private: + df::unit_labor labor; + public: + df::unit_labor get_labor(df::job* j) + { + return labor; + } + jlfunc_const(df::unit_labor l) : labor(l) {}; + }; + + class jlfunc_hauling : public jlfunc { - df::unit_labor::HAUL_ITEM, /* BAR */ - df::unit_labor::HAUL_ITEM, /* SMALLGEM */ - df::unit_labor::HAUL_ITEM, /* BLOCKS */ - df::unit_labor::HAUL_ITEM, /* ROUGH */ - df::unit_labor::HAUL_STONE, /* BOULDER */ - df::unit_labor::HAUL_WOOD, /* WOOD */ - df::unit_labor::HAUL_FURNITURE, /* DOOR */ - df::unit_labor::HAUL_FURNITURE, /* FLOODGATE */ - df::unit_labor::HAUL_FURNITURE, /* BED */ - df::unit_labor::HAUL_FURNITURE, /* CHAIR */ - df::unit_labor::HAUL_ITEM, /* CHAIN */ - df::unit_labor::HAUL_ITEM, /* FLASK */ - df::unit_labor::HAUL_ITEM, /* GOBLET */ - df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ - df::unit_labor::HAUL_ITEM, /* TOY */ - df::unit_labor::HAUL_FURNITURE, /* WINDOW */ - df::unit_labor::HAUL_ANIMAL, /* CAGE */ - df::unit_labor::HAUL_ITEM, /* BARREL */ - df::unit_labor::HAUL_ITEM, /* BUCKET */ - df::unit_labor::HAUL_ANIMAL, /* ANIMALTRAP */ - df::unit_labor::HAUL_FURNITURE, /* TABLE */ - df::unit_labor::HAUL_FURNITURE, /* COFFIN */ - df::unit_labor::HAUL_FURNITURE, /* STATUE */ - df::unit_labor::HAUL_BODY, /* CORPSE */ - df::unit_labor::HAUL_ITEM, /* WEAPON */ - df::unit_labor::HAUL_ITEM, /* ARMOR */ - df::unit_labor::HAUL_ITEM, /* SHOES */ - df::unit_labor::HAUL_ITEM, /* SHIELD */ - df::unit_labor::HAUL_ITEM, /* HELM */ - df::unit_labor::HAUL_ITEM, /* GLOVES */ - df::unit_labor::HAUL_FURNITURE, /* BOX */ - df::unit_labor::HAUL_ITEM, /* BIN */ - df::unit_labor::HAUL_FURNITURE, /* ARMORSTAND */ - df::unit_labor::HAUL_FURNITURE, /* WEAPONRACK */ - df::unit_labor::HAUL_FURNITURE, /* CABINET */ - df::unit_labor::HAUL_ITEM, /* FIGURINE */ - df::unit_labor::HAUL_ITEM, /* AMULET */ - df::unit_labor::HAUL_ITEM, /* SCEPTER */ - df::unit_labor::HAUL_ITEM, /* AMMO */ - df::unit_labor::HAUL_ITEM, /* CROWN */ - df::unit_labor::HAUL_ITEM, /* RING */ - df::unit_labor::HAUL_ITEM, /* EARRING */ - df::unit_labor::HAUL_ITEM, /* BRACELET */ - df::unit_labor::HAUL_ITEM, /* GEM */ - df::unit_labor::HAUL_FURNITURE, /* ANVIL */ - df::unit_labor::HAUL_BODY, /* CORPSEPIECE */ - df::unit_labor::HAUL_REFUSE, /* REMAINS */ - df::unit_labor::HAUL_FOOD, /* MEAT */ - df::unit_labor::HAUL_FOOD, /* FISH */ - df::unit_labor::HAUL_FOOD, /* FISH_RAW */ - df::unit_labor::HAUL_REFUSE, /* VERMIN */ - df::unit_labor::HAUL_ITEM, /* PET */ - df::unit_labor::HAUL_FOOD, /* SEEDS */ - df::unit_labor::HAUL_FOOD, /* PLANT */ - df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ - df::unit_labor::HAUL_FOOD, /* LEAVES */ - df::unit_labor::HAUL_ITEM, /* THREAD */ - df::unit_labor::HAUL_ITEM, /* CLOTH */ - df::unit_labor::HAUL_ITEM, /* TOTEM */ - df::unit_labor::HAUL_ITEM, /* PANTS */ - df::unit_labor::HAUL_ITEM, /* BACKPACK */ - df::unit_labor::HAUL_ITEM, /* QUIVER */ - df::unit_labor::HAUL_FURNITURE, /* CATAPULTPARTS */ - df::unit_labor::HAUL_FURNITURE, /* BALLISTAPARTS */ - df::unit_labor::HAUL_FURNITURE, /* SIEGEAMMO */ - df::unit_labor::HAUL_FURNITURE, /* BALLISTAARROWHEAD */ - df::unit_labor::HAUL_FURNITURE, /* TRAPPARTS */ - df::unit_labor::HAUL_FURNITURE, /* TRAPCOMP */ - df::unit_labor::HAUL_FOOD, /* DRINK */ - df::unit_labor::HAUL_FOOD, /* POWDER_MISC */ - df::unit_labor::HAUL_FOOD, /* CHEESE */ - df::unit_labor::HAUL_FOOD, /* FOOD */ - df::unit_labor::HAUL_FOOD, /* LIQUID_MISC */ - df::unit_labor::HAUL_ITEM, /* COIN */ - df::unit_labor::HAUL_FOOD, /* GLOB */ - df::unit_labor::HAUL_STONE, /* ROCK */ - df::unit_labor::HAUL_FURNITURE, /* PIPE_SECTION */ - df::unit_labor::HAUL_FURNITURE, /* HATCH_COVER */ - df::unit_labor::HAUL_FURNITURE, /* GRATE */ - df::unit_labor::HAUL_FURNITURE, /* QUERN */ - df::unit_labor::HAUL_FURNITURE, /* MILLSTONE */ - df::unit_labor::HAUL_ITEM, /* SPLINT */ - df::unit_labor::HAUL_ITEM, /* CRUTCH */ - df::unit_labor::HAUL_FURNITURE, /* TRACTION_BENCH */ - df::unit_labor::HAUL_ITEM, /* ORTHOPEDIC_CAST */ - df::unit_labor::HAUL_ITEM, /* TOOL */ - df::unit_labor::HAUL_FURNITURE, /* SLAB */ - df::unit_labor::HAUL_FOOD, /* EGG */ - df::unit_labor::HAUL_ITEM, /* BOOK */ + public: + df::unit_labor get_labor(df::job* j) + { + df::item* item = j->items[0]->item; + return hauling_labor_map[item->getType()]; + } + jlfunc_hauling() {}; }; + class jlfunc_construct_bld : public jlfunc + { + public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job (j); + switch (bld->getType()) + { + case df::building_type::Workshop: + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + + break; + } + + // FIXME + return df::unit_labor::NONE; + } + jlfunc_construct_bld() {} + }; + + class jlfunc_destroy_bld : public jlfunc + { + public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job (j); + df::building_type type = bld->getType(); + + // FIXME + return df::unit_labor::NONE; + } + jlfunc_destroy_bld() {} + }; + + class jlfunc_make : public jlfunc + { + private: + df::unit_labor metaltype; + public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job(j); + if (bld->getType() == df::building_type::Workshop) + { + df::workshop_type type = ((df::building_workshopst*)(bld))->type; + switch (type) + { + case df::workshop_type::Craftsdwarfs: + { + df::item_type jobitem = j->job_items[0]->item_type; + switch (jobitem) + { + case df::item_type::BOULDER: + return df::unit_labor::STONE_CRAFT; + case df::item_type::NONE: + if (j->material_category.bits.bone) + return df::unit_labor::BONE_CARVE; + else + return df::unit_labor::NONE; //FIXME + default: + return df::unit_labor::NONE; //FIXME + } + } + case df::workshop_type::Masons: + return df::unit_labor::MASON; + case df::workshop_type::Carpenters: + return df::unit_labor::CARPENTER; + case df::workshop_type::Leatherworks: + return df::unit_labor::LEATHER; + case df::workshop_type::Clothiers: + return df::unit_labor::CLOTHESMAKER; + case df::workshop_type::MagmaForge: + case df::workshop_type::MetalsmithsForge: + return metaltype; + default: + return df::unit_labor::NONE; // FIXME + } + } + else if (bld->getType() == df::building_type::Furnace) + { + df::furnace_type type = ((df::building_furnacest*)(bld))->type; + switch (type) + { + case df::furnace_type::MagmaGlassFurnace: + case df::furnace_type::GlassFurnace: + return df::unit_labor::GLASSMAKER; + default: + return df::unit_labor::NONE; // FIXME + } + } + + return df::unit_labor::NONE; // FIXME + } + + jlfunc_make (df::unit_labor mt) : metaltype(mt) {} + }; + + class jlfunc_custom : public jlfunc + { + public: + df::unit_labor get_labor(df::job* j) + { + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + df::job_skill skill = (*r)->skill; + df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); + return labor; + } + } + return df::unit_labor::NONE; + } + jlfunc_custom() {} + }; + + map jlf_cache; + + jlfunc* jlf_const(df::unit_labor l) { + jlfunc* jlf; + if (jlf_cache.count(l) == 0) + { + jlf = new jlfunc_const(l); + jlf_cache[l] = jlf; + } + else + jlf = jlf_cache[l]; + + return jlf; + } +private: + jlfunc *jlf_hauling, *jlf_make_furniture, *jlf_make_object, *jlf_make_armor, *jlf_make_weapon; + jlfunc *job_to_labor_table[ENUM_LAST_ITEM(job_type)+1]; + +public: + ~JobLaborMapper() + { + delete jlf_hauling; + delete jlf_make_furniture; + delete jlf_make_object; + delete jlf_make_armor; + delete jlf_make_weapon; + } + + JobLaborMapper() + { + jlf_hauling = new jlfunc_hauling(); + jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); + jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); + jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); + jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); + + jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); + + job_to_labor_table[df::job_type::CarveFortification] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::DetailWall] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::DetailFloor] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::Dig] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveUpwardStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveDownwardStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveUpDownStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveRamp] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::DigChannel] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::FellTree] = jlf_const(df::unit_labor::CUTWOOD); + job_to_labor_table[df::job_type::GatherPlants] = jlf_const(df::unit_labor::HERBALIST); + job_to_labor_table[df::job_type::RemoveConstruction] = jlf_no_labor; + job_to_labor_table[df::job_type::CollectWebs] = jlf_const(df::unit_labor::WEAVER); + job_to_labor_table[df::job_type::BringItemToDepot] = jlf_no_labor; + job_to_labor_table[df::job_type::BringItemToShop] = jlf_no_labor; + job_to_labor_table[df::job_type::Eat] = jlf_no_labor; + job_to_labor_table[df::job_type::GetProvisions] = jlf_no_labor; + job_to_labor_table[df::job_type::Drink] = jlf_no_labor; + job_to_labor_table[df::job_type::Drink2] = jlf_no_labor; + job_to_labor_table[df::job_type::FillWaterskin] = jlf_no_labor; + job_to_labor_table[df::job_type::FillWaterskin2] = jlf_no_labor; + job_to_labor_table[df::job_type::Sleep] = jlf_no_labor; + job_to_labor_table[df::job_type::CollectSand] = jlf_const(df::unit_labor::GLASSMAKER); + job_to_labor_table[df::job_type::Fish] = jlf_const(df::unit_labor::FISH); + job_to_labor_table[df::job_type::Hunt] = jlf_const(df::unit_labor::HUNT); + job_to_labor_table[df::job_type::HuntVermin] = jlf_no_labor; + job_to_labor_table[df::job_type::Kidnap] = jlf_no_labor; + job_to_labor_table[df::job_type::BeatCriminal] = jlf_no_labor; + job_to_labor_table[df::job_type::StartingFistFight] = jlf_no_labor; + job_to_labor_table[df::job_type::CollectTaxes] = jlf_no_labor; + job_to_labor_table[df::job_type::GuardTaxCollector] = jlf_no_labor; + job_to_labor_table[df::job_type::CatchLiveLandAnimal] = jlf_const(df::unit_labor::HUNT); + job_to_labor_table[df::job_type::CatchLiveFish] = jlf_const(df::unit_labor::FISH); + job_to_labor_table[df::job_type::ReturnKill] = jlf_no_labor; + job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; + job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; + job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); + job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInChest] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInCabinet] = jlf_hauling; + job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; + job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling; + job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; + job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; + job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; + job_to_labor_table[df::job_type::GoShopping] = jlf_no_labor; + job_to_labor_table[df::job_type::GoShopping2] = jlf_no_labor; + job_to_labor_table[df::job_type::Clean] = jlf_const(df::unit_labor::CLEAN); + job_to_labor_table[df::job_type::Rest] = jlf_no_labor; + job_to_labor_table[df::job_type::PickupEquipment] = jlf_no_labor; + job_to_labor_table[df::job_type::DumpItem] = jlf_hauling; + job_to_labor_table[df::job_type::StrangeMoodCrafter] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodJeweller] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodForge] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMagmaForge] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodBrooding] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodFell] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodCarpenter] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMason] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodBowyer] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodTanner] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodWeaver] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodGlassmaker] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMechanics] = jlf_no_labor; + job_to_labor_table[df::job_type::ConstructBuilding] = new jlfunc_construct_bld(); + job_to_labor_table[df::job_type::ConstructDoor] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructFloodgate] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBed] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructThrone] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCoffin] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructTable] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructChest] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBin] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructArmorStand] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructWeaponRack] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCabinet] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructStatue] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBlocks] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeRawGlass] = jlf_const(df::unit_labor::GLASSMAKER); + job_to_labor_table[df::job_type::MakeCrafts] = jlf_make_object; + job_to_labor_table[df::job_type::MintCoins] = jlf_const(df::unit_labor::METAL_CRAFT); + job_to_labor_table[df::job_type::CutGems] = jlf_const(df::unit_labor::CUT_GEM); + job_to_labor_table[df::job_type::CutGlass] = jlf_const(df::unit_labor::CUT_GEM); + job_to_labor_table[df::job_type::EncrustWithGems] = jlf_const(df::unit_labor::ENCRUST_GEM); + job_to_labor_table[df::job_type::EncrustWithGlass] = jlf_const(df::unit_labor::ENCRUST_GEM); + job_to_labor_table[df::job_type::DestroyBuilding] = new jlfunc_destroy_bld(); + job_to_labor_table[df::job_type::SmeltOre] = jlf_const(df::unit_labor::SMELT); + job_to_labor_table[df::job_type::MeltMetalObject] = jlf_const(df::unit_labor::SMELT); + job_to_labor_table[df::job_type::ExtractMetalStrands] = jlf_const(df::unit_labor::EXTRACT_STRAND); + job_to_labor_table[df::job_type::PlantSeeds] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::HarvestPlants] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::TrainHuntingAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::TrainWarAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::MakeWeapon] = jlf_make_weapon; + job_to_labor_table[df::job_type::ForgeAnvil] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCatapultParts] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::ConstructBallistaParts] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::MakeArmor] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeHelm] = jlf_make_armor; + job_to_labor_table[df::job_type::MakePants] = jlf_make_armor; + job_to_labor_table[df::job_type::StudWith] = jlf_make_object; + job_to_labor_table[df::job_type::ButcherAnimal] = jlf_const(df::unit_labor::BUTCHER); + job_to_labor_table[df::job_type::PrepareRawFish] = jlf_const(df::unit_labor::CLEAN_FISH); + job_to_labor_table[df::job_type::MillPlants] = jlf_const(df::unit_labor::MILLER); + job_to_labor_table[df::job_type::BaitTrap] = jlf_const(df::unit_labor::TRAPPER); + job_to_labor_table[df::job_type::MilkCreature] = jlf_const(df::unit_labor::MILK); + job_to_labor_table[df::job_type::MakeCheese] = jlf_const(df::unit_labor::MAKE_CHEESE); + job_to_labor_table[df::job_type::ProcessPlants] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::ProcessPlantsBag] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::ProcessPlantsVial] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::ProcessPlantsBarrel] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::PrepareMeal] = jlf_const(df::unit_labor::COOK); + job_to_labor_table[df::job_type::WeaveCloth] = jlf_const(df::unit_labor::WEAVER); + job_to_labor_table[df::job_type::MakeGloves] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeShoes] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeShield] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeCage] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeChain] = jlf_make_object; + job_to_labor_table[df::job_type::MakeFlask] = jlf_make_object; + job_to_labor_table[df::job_type::MakeGoblet] = jlf_make_object; + job_to_labor_table[df::job_type::MakeInstrument] = jlf_make_object; + job_to_labor_table[df::job_type::MakeToy] = jlf_make_object; + job_to_labor_table[df::job_type::MakeAnimalTrap] = jlf_const(df::unit_labor::TRAPPER); + job_to_labor_table[df::job_type::MakeBarrel] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeBucket] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeWindow] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeTotem] = jlf_const(df::unit_labor::BONE_CARVE); + job_to_labor_table[df::job_type::MakeAmmo] = jlf_make_weapon; + job_to_labor_table[df::job_type::DecorateWith] = jlf_make_object; + job_to_labor_table[df::job_type::MakeBackpack] = jlf_make_object; + job_to_labor_table[df::job_type::MakeQuiver] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeBallistaArrowHead] = jlf_make_weapon; + job_to_labor_table[df::job_type::AssembleSiegeAmmo] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::LoadCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::LoadBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::FireCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::FireBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::ConstructMechanisms] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::MakeTrapComponent] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::LoadCageTrap] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::LoadStoneTrap] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::LoadWeaponTrap] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::CleanTrap] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::CastSpell] = jlf_no_labor; + job_to_labor_table[df::job_type::LinkBuildingToTrigger] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::PullLever] = jlf_no_labor; + job_to_labor_table[df::job_type::BrewDrink] = jlf_const(df::unit_labor::BREWER) ; + job_to_labor_table[df::job_type::ExtractFromPlants] = jlf_const(df::unit_labor::HERBALIST) ; + job_to_labor_table[df::job_type::ExtractFromRawFish] = jlf_const(df::unit_labor::DISSECT_FISH) ; + job_to_labor_table[df::job_type::ExtractFromLandAnimal] = jlf_const(df::unit_labor::DISSECT_VERMIN) ; + job_to_labor_table[df::job_type::TameVermin] = jlf_const(df::unit_labor::ANIMALTRAIN) ; + job_to_labor_table[df::job_type::TameAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN) ; + job_to_labor_table[df::job_type::ChainAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::UnchainAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::UnchainPet] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleasePet] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleaseSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::HandleSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::CageLargeCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::CageSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::RecoverWounded] = jlf_const(df::unit_labor::RECOVER_WOUNDED); + job_to_labor_table[df::job_type::DiagnosePatient] = jlf_const(df::unit_labor::DIAGNOSE) ; + job_to_labor_table[df::job_type::ImmobilizeBreak] = jlf_const(df::unit_labor::BONE_SETTING) ; + job_to_labor_table[df::job_type::DressWound] = jlf_const(df::unit_labor::DRESSING_WOUNDS) ; + job_to_labor_table[df::job_type::CleanPatient] = jlf_const(df::unit_labor::CLEAN) ; + job_to_labor_table[df::job_type::Surgery] = jlf_const(df::unit_labor::SURGERY) ; + job_to_labor_table[df::job_type::Suture] = jlf_const(df::unit_labor::SUTURING); + job_to_labor_table[df::job_type::SetBone] = jlf_const(df::unit_labor::BONE_SETTING) ; + job_to_labor_table[df::job_type::PlaceInTraction] = jlf_const(df::unit_labor::BONE_SETTING) ; + job_to_labor_table[df::job_type::DrainAquarium] = jlf_no_labor; + job_to_labor_table[df::job_type::FillAquarium] = jlf_no_labor; + job_to_labor_table[df::job_type::FillPond] = jlf_no_labor; + job_to_labor_table[df::job_type::GiveWater] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) ; + job_to_labor_table[df::job_type::GiveFood] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) ; + job_to_labor_table[df::job_type::GiveWater2] = jlf_no_labor; + job_to_labor_table[df::job_type::GiveFood2] = jlf_no_labor; + job_to_labor_table[df::job_type::RecoverPet] = jlf_no_labor; + job_to_labor_table[df::job_type::PitLargeAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::PitSmallAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::SlaughterAnimal] = jlf_const(df::unit_labor::BUTCHER); + job_to_labor_table[df::job_type::MakeCharcoal] = jlf_const(df::unit_labor::BURN_WOOD); + job_to_labor_table[df::job_type::MakeAsh] = jlf_const(df::unit_labor::BURN_WOOD); + job_to_labor_table[df::job_type::MakeLye] = jlf_const(df::unit_labor::LYE_MAKING); + job_to_labor_table[df::job_type::MakePotashFromLye] = jlf_const(df::unit_labor::POTASH_MAKING); + job_to_labor_table[df::job_type::FertilizeField] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::MakePotashFromAsh] = jlf_const(df::unit_labor::POTASH_MAKING); + job_to_labor_table[df::job_type::DyeThread] = jlf_const(df::unit_labor::DYER); + job_to_labor_table[df::job_type::DyeCloth] = jlf_const(df::unit_labor::DYER); + job_to_labor_table[df::job_type::SewImage] = jlf_make_object; + job_to_labor_table[df::job_type::MakePipeSection] = jlf_make_furniture; + job_to_labor_table[df::job_type::OperatePump] = jlf_const(df::unit_labor::OPERATE_PUMP); + job_to_labor_table[df::job_type::ManageWorkOrders] = jlf_no_labor; + job_to_labor_table[df::job_type::UpdateStockpileRecords] = jlf_no_labor; + job_to_labor_table[df::job_type::TradeAtDepot] = jlf_no_labor; + job_to_labor_table[df::job_type::ConstructHatchCover] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructGrate] = jlf_make_furniture; + job_to_labor_table[df::job_type::RemoveStairs] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::ConstructQuern] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructMillstone] = jlf_make_furniture ; + job_to_labor_table[df::job_type::ConstructSplint] = jlf_make_object ; + job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_object; + job_to_labor_table[df::job_type::ConstructTractionBench] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::CleanSelf] = jlf_no_labor; + job_to_labor_table[df::job_type::BringCrutch] = jlf_no_labor; + job_to_labor_table[df::job_type::ApplyCast] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::CustomReaction] = new jlfunc_custom(); + job_to_labor_table[df::job_type::ConstructSlab] = jlf_make_furniture; + job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::STONE_CRAFT); + job_to_labor_table[df::job_type::ShearCreature] = jlf_const(df::unit_labor::SHEARER); + job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); + job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::PenSmallAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::MakeTool] = jlf_make_furniture; + job_to_labor_table[df::job_type::CollectClay] = jlf_const(df::unit_labor::POTTERY); + job_to_labor_table[df::job_type::InstallColonyInHive] = jlf_const(df::unit_labor::BEEKEEPING); + job_to_labor_table[df::job_type::CollectHiveProducts] = jlf_const(df::unit_labor::BEEKEEPING); + job_to_labor_table[df::job_type::CauseTrouble] = jlf_no_labor; + job_to_labor_table[df::job_type::DrinkBlood] = jlf_no_labor; + job_to_labor_table[df::job_type::ReportCrime] = jlf_no_labor; + job_to_labor_table[df::job_type::ExecuteCriminal] = jlf_no_labor; + job_to_labor_table[df::job_type::TrainAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::CarveTrack] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); + job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); + job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); + }; + + df::unit_labor find_job_labor(df::job* j) + { + if (j->job_type == df::job_type::CustomReaction) + { + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + df::job_skill skill = (*r)->skill; + return ENUM_ATTR(job_skill, labor, skill); + } + } + return df::unit_labor::NONE; + } + + df::job_skill skill; + df::unit_labor labor; + skill = ENUM_ATTR(job_type, skill, j->job_type); + if (skill != df::job_skill::NONE) + labor = ENUM_ATTR(job_skill, labor, skill); + else + labor = ENUM_ATTR(job_type, labor, j->job_type); + + if (labor == df::unit_labor::NONE) + labor = job_to_labor_table[j->job_type]->get_labor(j); + + return labor; + } +}; + +static JobLaborMapper* labor_mapper; + static bool isOptionEnabled(unsigned flag) { return config.isValid() && (config.ival(0) & flag) != 0; @@ -652,6 +1163,11 @@ static void setOptionEnabled(ConfigFlags flag, bool on) static void cleanup_state() { labor_infos.clear(); + if (labor_mapper) + { + delete labor_mapper; + labor_mapper = 0; + } } static void reset_labor(df::unit_labor labor) @@ -717,6 +1233,9 @@ static void init_state() generate_labor_to_skill_map(); + if (!labor_mapper) + labor_mapper = new JobLaborMapper(); + } static df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; @@ -804,7 +1323,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector general_refs.begin(); r != j->general_refs.end(); r++) - { - if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) - { - int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; - df::building* bld = binsearch_in_vector(world->buildings.all, id); - return bld; - } - } - return 0; -} - - - -static df::unit_labor workshop_build_labor[] = -{ - /* Carpenters */ df::unit_labor::CARPENTER, - /* Farmers */ df::unit_labor::HERBALIST, - /* Masons */ df::unit_labor::MASON, - /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, - /* Jewelers */ df::unit_labor::CUT_GEM, - /* MetalsmithsForge */ df::unit_labor::METAL_CRAFT, - /* MagmaForge */ df::unit_labor::METAL_CRAFT, - /* Bowyers */ df::unit_labor::BOWYER, - /* Mechanics */ df::unit_labor::MECHANIC, - /* Siege */ df::unit_labor::SIEGECRAFT, - /* Butchers */ df::unit_labor::BUTCHER, - /* Leatherworks */ df::unit_labor::LEATHER, - /* Tanners */ df::unit_labor::TANNER, - /* Clothiers */ df::unit_labor::CLOTHESMAKER, - /* Fishery */ df::unit_labor::FISH, - /* Still */ df::unit_labor::BREWER, - /* Loom */ df::unit_labor::WEAVER, - /* Quern */ df::unit_labor::MILLER, - /* Kennels */ df::unit_labor::ANIMALTRAIN, - /* Kitchen */ df::unit_labor::COOK, - /* Ashery */ df::unit_labor::LYE_MAKING, - /* Dyers */ df::unit_labor::DYER, - /* Millstone */ df::unit_labor::MILLER, - /* Custom */ df::unit_labor::NONE, - /* Tool */ df::unit_labor::NONE -}; - -class jlfunc -{ - public: - virtual df::unit_labor get_labor(df::job* j) = 0; -}; - -class jlfunc_const : public jlfunc -{ - private: - df::unit_labor labor; - public: - df::unit_labor get_labor(df::job* j) - { - return labor; - } - jlfunc_const(df::unit_labor l) : labor(l) {}; -}; - -class jlfunc_hauling : public jlfunc -{ -public: - df::unit_labor get_labor(df::job* j) - { - df::item* item = j->items[0]->item; - return hauling_labor_map[item->getType()]; - } - jlfunc_hauling() {}; -}; - -class jlfunc_construct_bld : public jlfunc -{ -public: - df::unit_labor get_labor(df::job* j) - { - df::building* bld = get_building_from_job (j); - switch (bld->getType()) - { - case df::building_type::Workshop: - df::building_workshopst* ws = (df::building_workshopst*) bld; - if (ws->type == df::workshop_type::Custom) - { - df::building_def* def = df::building_def::find(ws->custom_type); - return def->build_labors[0]; - } - else - return workshop_build_labor[ws->type]; - - break; - } - - // FIXME - return df::unit_labor::NONE; - } - jlfunc_construct_bld() {} -}; - -class jlfunc_destroy_bld : public jlfunc -{ -public: - df::unit_labor get_labor(df::job* j) - { - df::building* bld = get_building_from_job (j); - df::building_type type = bld->getType(); - - // FIXME - return df::unit_labor::NONE; - } - jlfunc_destroy_bld() {} -}; - -class jlfunc_make : public jlfunc -{ -private: - df::unit_labor metaltype; -public: - df::unit_labor get_labor(df::job* j) - { - df::building* bld = get_building_from_job(j); - if (bld->getType() == df::building_type::Workshop) - { - df::workshop_type type = ((df::building_workshopst*)(bld))->type; - switch (type) - { - case df::workshop_type::Craftsdwarfs: - { - df::item_type jobitem = j->job_items[0]->item_type; - switch (jobitem) - { - case df::item_type::BOULDER: - return df::unit_labor::STONE_CRAFT; - case df::item_type::NONE: - if (j->material_category.bits.bone) - return df::unit_labor::BONE_CARVE; - else - return df::unit_labor::NONE; //FIXME - default: - return df::unit_labor::NONE; //FIXME - } - } - case df::workshop_type::Masons: - return df::unit_labor::MASON; - case df::workshop_type::Carpenters: - return df::unit_labor::CARPENTER; - case df::workshop_type::Leatherworks: - return df::unit_labor::LEATHER; - case df::workshop_type::Clothiers: - return df::unit_labor::CLOTHESMAKER; - case df::workshop_type::MagmaForge: - case df::workshop_type::MetalsmithsForge: - return metaltype; - default: - return df::unit_labor::NONE; // FIXME - } - } - else if (bld->getType() == df::building_type::Furnace) - { - df::furnace_type type = ((df::building_furnacest*)(bld))->type; - switch (type) - { - case df::furnace_type::MagmaGlassFurnace: - case df::furnace_type::GlassFurnace: - return df::unit_labor::GLASSMAKER; - default: - return df::unit_labor::NONE; // FIXME - } - } - - return df::unit_labor::NONE; // FIXME - } - - jlfunc_make (df::unit_labor mt) : metaltype(mt) {} -}; - -class jlfunc_custom : public jlfunc -{ -public: - df::unit_labor get_labor(df::job* j) - { - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) - { - if ((*r)->code == j->reaction_name) - { - df::job_skill skill = (*r)->skill; - df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); - return labor; - } - } - return df::unit_labor::NONE; - } - jlfunc_custom() {} -}; - -static map jlf_cache; - -jlfunc* jlf_const(df::unit_labor l) { - jlfunc* jlf; - if (jlf_cache.count(l) == 0) - { - jlf = jlf_const(l); - jlf_cache[l] = jlf; - } - else - jlf = jlf_cache[l]; - - return jlf; -} - -static jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); -static jlfunc* jlf_hauling = new jlfunc_hauling(); -static jlfunc* jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); -static jlfunc* jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); -static jlfunc* jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); -static jlfunc* jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); - -static jlfunc* job_to_labor_table[] = { - jlf_const(df::unit_labor::DETAIL) /* CarveFortification */, - jlf_const(df::unit_labor::DETAIL) /* DetailWall */, - jlf_const(df::unit_labor::DETAIL) /* DetailFloor */, - jlf_const(df::unit_labor::MINE) /* Dig */, - jlf_const(df::unit_labor::MINE) /* CarveUpwardStaircase */, - jlf_const(df::unit_labor::MINE) /* CarveDownwardStaircase */, - jlf_const(df::unit_labor::MINE) /* CarveUpDownStaircase */, - jlf_const(df::unit_labor::MINE) /* CarveRamp */, - jlf_const(df::unit_labor::MINE) /* DigChannel */, - jlf_const(df::unit_labor::CUTWOOD) /* FellTree */, - jlf_const(df::unit_labor::HERBALIST) /* GatherPlants */, - jlf_no_labor /* RemoveConstruction */, - jlf_const(df::unit_labor::WEAVER) /* CollectWebs */, - jlf_no_labor /* BringItemToDepot */, - jlf_no_labor /* BringItemToShop */, - jlf_no_labor /* Eat */, - jlf_no_labor /* GetProvisions */, - jlf_no_labor /* Drink */, - jlf_no_labor /* Drink2 */, - jlf_no_labor /* FillWaterskin */, - jlf_no_labor /* FillWaterskin2 */, - jlf_no_labor /* Sleep */, - jlf_const(df::unit_labor::GLASSMAKER) /* CollectSand */, - jlf_const(df::unit_labor::FISH) /* Fish */, - jlf_const(df::unit_labor::HUNT) /* Hunt */, - jlf_no_labor /* HuntVermin */, - jlf_no_labor /* Kidnap */, - jlf_no_labor /* BeatCriminal */, - jlf_no_labor /* StartingFistFight */, - jlf_no_labor /* CollectTaxes */, - jlf_no_labor /* GuardTaxCollector */, - jlf_const(df::unit_labor::HUNT) /* CatchLiveLandAnimal */, - jlf_const(df::unit_labor::FISH) /* CatchLiveFish */, - jlf_no_labor /* ReturnKill */, - jlf_no_labor /* CheckChest */, - jlf_no_labor /* StoreOwnedItem */, - jlf_const(df::unit_labor::HAUL_BODY) /* PlaceItemInTomb */, - jlf_hauling /* StoreItemInStockpile */, - jlf_hauling /* StoreItemInBag */, - jlf_hauling /* StoreItemInHospital */, - jlf_hauling /* StoreItemInChest */, - jlf_hauling /* StoreItemInCabinet */, - jlf_hauling /* StoreWeapon */, - jlf_hauling /* StoreArmor */, - jlf_hauling /* StoreItemInBarrel */, - jlf_hauling /* StoreItemInBin */, - jlf_no_labor /* SeekArtifact */, - jlf_no_labor /* SeekInfant */, - jlf_no_labor /* AttendParty */, - jlf_no_labor /* GoShopping */, - jlf_no_labor /* GoShopping2 */, - jlf_const(df::unit_labor::CLEAN) /* Clean */, - jlf_no_labor /* Rest */, - jlf_no_labor /* PickupEquipment */, - jlf_hauling /* DumpItem */, - jlf_no_labor /* StrangeMoodCrafter */, - jlf_no_labor /* StrangeMoodJeweller */, - jlf_no_labor /* StrangeMoodForge */, - jlf_no_labor /* StrangeMoodMagmaForge */, - jlf_no_labor /* StrangeMoodBrooding */, - jlf_no_labor /* StrangeMoodFell */, - jlf_no_labor /* StrangeMoodCarpenter */, - jlf_no_labor /* StrangeMoodMason */, - jlf_no_labor /* StrangeMoodBowyer */, - jlf_no_labor /* StrangeMoodTanner */, - jlf_no_labor /* StrangeMoodWeaver */, - jlf_no_labor /* StrangeMoodGlassmaker */, - jlf_no_labor /* StrangeMoodMechanics */, - new jlfunc_construct_bld() /* ConstructBuilding */, - jlf_make_furniture /* ConstructDoor */, - jlf_make_furniture /* ConstructFloodgate */, - jlf_make_furniture /* ConstructBed */, - jlf_make_furniture /* ConstructThrone */, - jlf_make_furniture /* ConstructCoffin */, - jlf_make_furniture /* ConstructTable */, - jlf_make_furniture /* ConstructChest */, - jlf_make_furniture /* ConstructBin */, - jlf_make_furniture /* ConstructArmorStand */, - jlf_make_furniture /* ConstructWeaponRack */, - jlf_make_furniture /* ConstructCabinet */, - jlf_make_furniture /* ConstructStatue */, - jlf_make_furniture /* ConstructBlocks */, - jlf_const(df::unit_labor::GLASSMAKER) /* MakeRawGlass */, - jlf_make_object /* MakeCrafts */, - jlf_const(df::unit_labor::METAL_CRAFT) /* MintCoins */, - jlf_const(df::unit_labor::CUT_GEM) /* CutGems */, - jlf_const(df::unit_labor::CUT_GEM) /* CutGlass */, - jlf_const(df::unit_labor::ENCRUST_GEM) /* EncrustWithGems */, - jlf_const(df::unit_labor::ENCRUST_GEM) /* EncrustWithGlass */, - new jlfunc_destroy_bld() /* DestroyBuilding */, - jlf_const(df::unit_labor::SMELT) /* SmeltOre */, - jlf_const(df::unit_labor::SMELT) /* MeltMetalObject */, - jlf_const(df::unit_labor::EXTRACT_STRAND) /* ExtractMetalStrands */, - jlf_const(df::unit_labor::PLANT) /* PlantSeeds */, - jlf_const(df::unit_labor::PLANT) /* HarvestPlants */, - jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainHuntingAnimal */, - jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainWarAnimal */, - jlf_make_weapon /* MakeWeapon */, - jlf_make_furniture /* ForgeAnvil */, - jlf_const(df::unit_labor::SIEGECRAFT) /* ConstructCatapultParts */, - jlf_const(df::unit_labor::SIEGECRAFT) /* ConstructBallistaParts */, - jlf_make_armor /* MakeArmor */, - jlf_make_armor /* MakeHelm */, - jlf_make_armor /* MakePants */, - jlf_make_object /* StudWith */, - jlf_const(df::unit_labor::BUTCHER) /* ButcherAnimal */, - jlf_const(df::unit_labor::CLEAN_FISH) /* PrepareRawFish */, - jlf_const(df::unit_labor::MILLER) /* MillPlants */, - jlf_const(df::unit_labor::TRAPPER) /* BaitTrap */, - jlf_const(df::unit_labor::MILK) /* MilkCreature */, - jlf_const(df::unit_labor::MAKE_CHEESE) /* MakeCheese */, - jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlants */, - jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsBag */, - jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsVial */, - jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsBarrel */, - jlf_const(df::unit_labor::COOK) /* PrepareMeal */, - jlf_const(df::unit_labor::WEAVER) /* WeaveCloth */, - jlf_make_armor /* MakeGloves */, - jlf_make_armor /* MakeShoes */, - jlf_make_armor /* MakeShield */, - jlf_make_furniture /* MakeCage */, - jlf_make_object /* MakeChain */, - jlf_make_object /* MakeFlask */, - jlf_make_object /* MakeGoblet */, - jlf_make_object/* MakeInstrument */, - jlf_make_object/* MakeToy */, - jlf_const(df::unit_labor::TRAPPER) /* MakeAnimalTrap */, - jlf_make_furniture /* MakeBarrel */, - jlf_make_furniture /* MakeBucket */, - jlf_make_furniture /* MakeWindow */, - jlf_const(df::unit_labor::BONE_CARVE) /* MakeTotem */, - jlf_make_weapon /* MakeAmmo */, - jlf_make_object /* DecorateWith */, - jlf_make_object /* MakeBackpack */, - jlf_make_armor /* MakeQuiver */, - jlf_make_weapon /* MakeBallistaArrowHead */, - jlf_const(df::unit_labor::SIEGECRAFT) /* AssembleSiegeAmmo */, - jlf_const(df::unit_labor::SIEGEOPERATE) /* LoadCatapult */, - jlf_const(df::unit_labor::SIEGEOPERATE) /* LoadBallista */, - jlf_const(df::unit_labor::SIEGEOPERATE) /* FireCatapult */, - jlf_const(df::unit_labor::SIEGEOPERATE) /* FireBallista */, - jlf_const(df::unit_labor::MECHANIC) /* ConstructMechanisms */, - jlf_const(df::unit_labor::MECHANIC) /* MakeTrapComponent */, - jlf_const(df::unit_labor::MECHANIC) /* LoadCageTrap */, - jlf_const(df::unit_labor::MECHANIC) /* LoadStoneTrap */, - jlf_const(df::unit_labor::MECHANIC) /* LoadWeaponTrap */, - jlf_const(df::unit_labor::MECHANIC) /* CleanTrap */, - jlf_no_labor /* CastSpell */, - jlf_const(df::unit_labor::MECHANIC) /* LinkBuildingToTrigger */, - jlf_no_labor /* PullLever */, - jlf_const(df::unit_labor::BREWER) /* BrewDrink */, - jlf_const(df::unit_labor::HERBALIST) /* ExtractFromPlants */, - jlf_const(df::unit_labor::DISSECT_FISH) /* ExtractFromRawFish */, - jlf_const(df::unit_labor::DISSECT_VERMIN) /* ExtractFromLandAnimal */, - jlf_const(df::unit_labor::ANIMALTRAIN) /* TameVermin */, - jlf_const(df::unit_labor::ANIMALTRAIN) /* TameAnimal */, - jlf_no_labor /* ChainAnimal */, - jlf_no_labor /* UnchainAnimal */, - jlf_no_labor /* UnchainPet */, - jlf_no_labor /* ReleaseLargeCreature */, - jlf_no_labor /* ReleasePet */, - jlf_no_labor /* ReleaseSmallCreature */, - jlf_no_labor /* HandleSmallCreature */, - jlf_no_labor /* HandleLargeCreature */, - jlf_no_labor /* CageLargeCreature */, - jlf_no_labor /* CageSmallCreature */, - jlf_const(df::unit_labor::RECOVER_WOUNDED) /* RecoverWounded */, - jlf_const(df::unit_labor::DIAGNOSE) /* DiagnosePatient */, - jlf_const(df::unit_labor::BONE_SETTING) /* ImmobilizeBreak */, - jlf_const(df::unit_labor::DRESSING_WOUNDS) /* DressWound */, - jlf_const(df::unit_labor::CLEAN) /* CleanPatient */, - jlf_const(df::unit_labor::SURGERY) /* Surgery */, - jlf_const(df::unit_labor::SUTURING) /* Suture */, - jlf_const(df::unit_labor::BONE_SETTING) /* SetBone */, - jlf_const(df::unit_labor::BONE_SETTING) /* PlaceInTraction */, - jlf_no_labor /* DrainAquarium */, - jlf_no_labor /* FillAquarium */, - jlf_no_labor /* FillPond */, - jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) /* GiveWater */, - jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) /* GiveFood */, - jlf_no_labor /* GiveWater2 */, - jlf_no_labor /* GiveFood2 */, - jlf_no_labor /* RecoverPet */, - jlf_no_labor /* PitLargeAnimal */, - jlf_no_labor /* PitSmallAnimal */, - jlf_const(df::unit_labor::BUTCHER) /* SlaughterAnimal */, - jlf_const(df::unit_labor::BURN_WOOD) /* MakeCharcoal */, - jlf_const(df::unit_labor::BURN_WOOD) /* MakeAsh */, - jlf_const(df::unit_labor::LYE_MAKING) /* MakeLye */, - jlf_const(df::unit_labor::POTASH_MAKING) /* MakePotashFromLye */, - jlf_const(df::unit_labor::PLANT) /* FertilizeField */, - jlf_const(df::unit_labor::POTASH_MAKING) /* MakePotashFromAsh */, - jlf_const(df::unit_labor::DYER) /* DyeThread */, - jlf_const(df::unit_labor::DYER) /* DyeCloth */, - jlf_make_object /* SewImage */, - jlf_make_furniture /* MakePipeSection */, - jlf_const(df::unit_labor::OPERATE_PUMP) /* OperatePump */, - jlf_no_labor /* ManageWorkOrders */, - jlf_no_labor /* UpdateStockpileRecords */, - jlf_no_labor /* TradeAtDepot */, - jlf_make_furniture /* ConstructHatchCover */, - jlf_make_furniture /* ConstructGrate */, - jlf_const(df::unit_labor::MINE) /* RemoveStairs */, - jlf_make_furniture /* ConstructQuern */, - jlf_make_furniture /* ConstructMillstone */, - jlf_make_object /* ConstructSplint */, - jlf_make_object /* ConstructCrutch */, - jlf_const(df::unit_labor::MECHANIC) /* ConstructTractionBench */, - jlf_no_labor /* CleanSelf */, - jlf_no_labor /* BringCrutch */, - jlf_const(df::unit_labor::BONE_SETTING) /* ApplyCast */, - new jlfunc_custom() /* CustomReaction */, - jlf_make_furniture /* ConstructSlab */, - jlf_const(df::unit_labor::STONE_CRAFT) /* EngraveSlab */, - jlf_const(df::unit_labor::SHEARER) /* ShearCreature */, - jlf_const(df::unit_labor::SPINNER) /* SpinThread */, - jlf_no_labor /* PenLargeAnimal */, - jlf_no_labor /* PenSmallAnimal */, - jlf_make_furniture /* MakeTool */, - jlf_const(df::unit_labor::POTTERY) /* CollectClay */, - jlf_const(df::unit_labor::BEEKEEPING) /* InstallColonyInHive */, - jlf_const(df::unit_labor::BEEKEEPING) /* CollectHiveProducts */, - jlf_no_labor /* CauseTrouble */, - jlf_no_labor /* DrinkBlood */, - jlf_no_labor /* ReportCrime */, - jlf_no_labor /* ExecuteCriminal */, - jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainAnimal */, - jlf_const(df::unit_labor::DETAIL) /* CarveTrack */, - jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* PushTrackVehicle */, - jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* PlaceTrackVehicle */, - jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* StoreItemInVehicle */ -}; - - -static df::unit_labor find_job_labor(df::job* j) -{ - if (j->job_type == df::job_type::CustomReaction) - { - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) - { - if ((*r)->code == j->reaction_name) - { - df::job_skill skill = (*r)->skill; - return ENUM_ATTR(job_skill, labor, skill); - } - } - return df::unit_labor::NONE; - } - - df::job_skill skill; - df::unit_labor labor; - skill = ENUM_ATTR(job_type, skill, j->job_type); - if (skill != df::job_skill::NONE) - labor = ENUM_ATTR(job_skill, labor, skill); - else - labor = ENUM_ATTR(job_type, labor, j->job_type); - - if (labor == df::unit_labor::NONE) - labor = job_to_labor_table[j->job_type]->get_labor(j); - - return labor; -} - class AutoLaborManager { color_ostream& out; @@ -1337,9 +1372,21 @@ private: int pick_count; int axe_count; + int cnt_recover_wounded; + int cnt_diagnosis; + int cnt_immobilize; + int cnt_dressing; + int cnt_cleaning; + int cnt_surgery; + int cnt_suture; + int cnt_setting; + int cnt_traction; + int cnt_crutch; + + std::map labor_needed; std::vector dwarf_info; - std::deque idle_dwarfs; + std::list idle_dwarfs; private: void scan_buildings() @@ -1435,7 +1482,7 @@ private: df::item* item = world->items.all[i]; if (item->flags.whole & bad_flags.whole) continue; - + if (!item->isWeapon()) continue; @@ -1472,7 +1519,7 @@ private: if (worker != -1) continue; - df::unit_labor labor = find_job_labor (j); + df::unit_labor labor = labor_mapper->find_job_labor (j); if (print_debug) out.print ("Job requiring labor %d found\n", labor); @@ -1492,9 +1539,6 @@ private: if (Units::isCitizen(cre)) { - if (cre->burrows.size() > 0) - continue; // dwarfs assigned to burrows are skipped entirely - dwarf_info_t* dwarf = add_dwarf(cre); df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); @@ -1557,7 +1601,7 @@ private: { state = CHILD; } - else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) + else if (dwarf->dwarf->military.cur_uniform != 0) state = MILITARY; else if (dwarf->dwarf->job.current_job == NULL) { @@ -1565,6 +1609,8 @@ private: state = OTHER; else if (dwarf->dwarf->specific_refs.size() > 0) state = OTHER; + else if (dwarf->dwarf->burrows.size() > 0) + state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy else state = IDLE; } @@ -1585,6 +1631,31 @@ private: if (print_debug) out.print("Dwarf \"%s\": state %s\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state]); + // determine if dwarf has medical needs + if (dwarf->dwarf->health) + { + if (dwarf->dwarf->health->flags.bits.needs_recovery) + cnt_recover_wounded++; + if (dwarf->dwarf->health->flags.bits.rq_diagnosis) + cnt_diagnosis++; + if (dwarf->dwarf->health->flags.bits.rq_immobilize) + cnt_immobilize++; + if (dwarf->dwarf->health->flags.bits.rq_dressing) + cnt_dressing++; + if (dwarf->dwarf->health->flags.bits.rq_cleaning) + cnt_cleaning++; + if (dwarf->dwarf->health->flags.bits.rq_surgery) + cnt_surgery++; + if (dwarf->dwarf->health->flags.bits.rq_suture) + cnt_suture++; + if (dwarf->dwarf->health->flags.bits.rq_setting) + cnt_setting++; + if (dwarf->dwarf->health->flags.bits.rq_traction) + cnt_traction++; + if (dwarf->dwarf->health->flags.bits.rq_crutch) + cnt_crutch++; + } + // check if dwarf has an axe, pick, or crossbow for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) @@ -1620,9 +1691,9 @@ private: } } - // clear labors if currently idle + // clear labors of dwarfs with clear_all set - if (state == IDLE || dwarf->clear_all) + if (dwarf->clear_all) { FOR_ENUM_ITEMS(unit_labor, labor) { @@ -1644,6 +1715,10 @@ private: public: void process() { + dig_count = tree_count = plant_count = detail_count = pick_count = axe_count = 0; + cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = + cnt_setting = cnt_traction = cnt_crutch = 0; + // scan for specific buildings of interest scan_buildings(); @@ -1656,8 +1731,6 @@ public: count_tools(); - // create job entries for designation - // collect current job list collect_job_list(); @@ -1666,35 +1739,67 @@ public: collect_dwarf_list(); + // add job entries for designation-related jobs + + labor_needed[df::unit_labor::MINE] += std::min(pick_count, dig_count); + labor_needed[df::unit_labor::CUTWOOD] += std::min(axe_count, tree_count); + labor_needed[df::unit_labor::DETAIL] += detail_count; + labor_needed[df::unit_labor::HERBALIST] += plant_count; + + // add job entries for health care + + labor_needed[df::unit_labor::RECOVER_WOUNDED] += cnt_recover_wounded; + labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_immobilize; + labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_dressing; + labor_needed[df::unit_labor::CLEAN] += cnt_cleaning; + labor_needed[df::unit_labor::SURGERY] += cnt_surgery; + labor_needed[df::unit_labor::SUTURING] += cnt_suture; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_setting; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; + labor_needed[df::unit_labor::HAUL_ITEM] += cnt_crutch; + + if (print_debug) + { + for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) + { + out.print ("labor_needed [%d] = %d\n", i->first, i->second); + } + } + // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count // and remove the idle dwarf from the idle list - for (auto i = idle_dwarfs.begin(); i != idle_dwarfs.end(); i++) + for (auto d = idle_dwarfs.begin(); d != idle_dwarfs.end(); d++) { FOR_ENUM_ITEMS(unit_labor, l) { - if ((*i)->dwarf->status.labors[l]) + if (l == df::unit_labor::NONE) + continue; + + if ((*d)->dwarf->status.labors[l]) if (labor_needed[l] > 0) { if (print_debug) - out.print("assign \"%s\" labor %d (carried through)\n", (*i)->dwarf->name.first_name.c_str(), l); + out.print("assign \"%s\" labor %d (carried through)\n", (*d)->dwarf->name.first_name.c_str(), l); labor_needed[l]--; - idle_dwarfs.erase(i); // remove from idle list + d = idle_dwarfs.erase(d); // remove from idle list + d--; // and go back one break; } else { - (*i)->dwarf->status.labors[l] = false; + (*d)->clear_labor(l); } } } priority_queue> pq; - + for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { if (i->second > 0) pq.push(make_pair(i->second, i->first)); } - + while (!idle_dwarfs.empty() && !pq.empty()) { df::unit_labor labor = pq.top().second; @@ -1704,13 +1809,13 @@ public: if (print_debug) out.print("labor %d skill %d remaining %d\n", labor, skill, remaining); - std::deque::iterator bestdwarf = idle_dwarfs.begin(); + std::list::iterator bestdwarf = idle_dwarfs.begin(); if (skill != df::job_skill::NONE) { int best_skill_level = -1; - for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) + for (std::list::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) { dwarf_info_t* d = (*k); int skill_level = Units::getEffectiveSkill(d->dwarf, skill); @@ -1809,7 +1914,7 @@ command_result autolabor (color_ostream &out, std::vector & parame if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "enable" || - parameters[0] == "1" || parameters[0] == "disable")) + parameters[0] == "1" || parameters[0] == "disable")) { bool enable = (parameters[0] == "1" || parameters[0] == "enable"); if (enable && !enable_autolabor) From bb2b97baa3ac528810d003ce8683387c8d10e911 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 1 Dec 2012 16:09:52 -0600 Subject: [PATCH 008/136] autolabor: exclude "item lost" jobs, exclude jobs that are not first-in-queue at workshop, improve debug messages --- plugins/autolabor.cpp | 46 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index e1655bba4..fd52d522d 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1507,18 +1507,48 @@ private: if (!j) continue; - if (j->flags.bits.suspend) + if (j->flags.bits.suspend || j->flags.bits.item_lost) continue; int worker = -1; + int bld = -1; for (int r = 0; r < j->general_refs.size(); ++r) + { if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; + if (j->general_refs[r]->getType() == df::general_ref_type::BUILDING_HOLDER) + bld = ((df::general_ref_building_holderst *)(j->general_refs[r]))->building_id; + } if (worker != -1) continue; + if (bld != -1) + { + if (print_debug) + out.print("Checking job %d for first in queue at building %d\n", j->id, bld); + df::building* b = binsearch_in_vector(world->buildings.all, bld); + int fjid = -1; + for (int jn = 0; jn < b->jobs.size(); jn++) + { + if (b->jobs[jn]->flags.bits.suspend) + continue; + fjid = b->jobs[jn]->id; + break; + } + // check if this job is the first nonsuspended job on this building; if not, ignore it + if (fjid != j->id) { + if (print_debug) + out.print("Job %d is not first in queue at building %d (%d), skipping\n", j->id, bld, fjid); + continue; + } + + if (print_debug) + out.print("Job %d is in queue at building %d\n", j->id, bld); + + } + df::unit_labor labor = labor_mapper->find_job_labor (j); if (print_debug) @@ -1763,13 +1793,16 @@ public: { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - out.print ("labor_needed [%d] = %d\n", i->first, i->second); + out.print ("labor_needed [%s] = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second); } } // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count // and remove the idle dwarf from the idle list + if (print_debug) + out.print("idle count = %d, labor need count = %d\n", idle_dwarfs.size(), labor_needed.size()); + for (auto d = idle_dwarfs.begin(); d != idle_dwarfs.end(); d++) { FOR_ENUM_ITEMS(unit_labor, l) @@ -1781,7 +1814,7 @@ public: if (labor_needed[l] > 0) { if (print_debug) - out.print("assign \"%s\" labor %d (carried through)\n", (*d)->dwarf->name.first_name.c_str(), l); + out.print("assign \"%s\" labor %s (carried through)\n", (*d)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, l).c_str()); labor_needed[l]--; d = idle_dwarfs.erase(d); // remove from idle list d--; // and go back one @@ -1800,6 +1833,9 @@ public: pq.push(make_pair(i->second, i->first)); } + if (print_debug) + out.print("idle count = %d, labor need count = %d\n", idle_dwarfs.size(), pq.size()); + while (!idle_dwarfs.empty() && !pq.empty()) { df::unit_labor labor = pq.top().second; @@ -1807,7 +1843,7 @@ public: df::job_skill skill = labor_to_skill[labor]; if (print_debug) - out.print("labor %d skill %d remaining %d\n", labor, skill, remaining); + out.print("labor %s skill %s remaining %d\n", ENUM_KEY_STR(unit_labor, labor).c_str(), ENUM_KEY_STR(job_skill, skill).c_str(), remaining); std::list::iterator bestdwarf = idle_dwarfs.begin(); @@ -1829,7 +1865,7 @@ public: } if (print_debug) - out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); + out.print("assign \"%s\" labor %s\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); (*bestdwarf)->set_labor(labor); idle_dwarfs.erase(bestdwarf); From 15f7ffa0e2a0c7d764e1d7e25bd6e6316bdccde1 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 1 Dec 2012 17:39:01 -0600 Subject: [PATCH 009/136] autolabor: add ConstructBuilding (Farm); change priority calculation --- plugins/autolabor.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index fd52d522d..5d90610d9 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -727,16 +727,19 @@ private: switch (bld->getType()) { case df::building_type::Workshop: - df::building_workshopst* ws = (df::building_workshopst*) bld; - if (ws->type == df::workshop_type::Custom) { - df::building_def* def = df::building_def::find(ws->custom_type); - return def->build_labors[0]; - } - else - return workshop_build_labor[ws->type]; - + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + } break; + case df::building_type::FarmPlot: + return df::unit_labor::PLANT; } // FIXME @@ -1830,7 +1833,7 @@ public: for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { if (i->second > 0) - pq.push(make_pair(i->second, i->first)); + pq.push(make_pair(100, i->first)); } if (print_debug) @@ -1839,11 +1842,11 @@ public: while (!idle_dwarfs.empty() && !pq.empty()) { df::unit_labor labor = pq.top().second; - int remaining = pq.top().first; + int priority = pq.top().first; df::job_skill skill = labor_to_skill[labor]; if (print_debug) - out.print("labor %s skill %s remaining %d\n", ENUM_KEY_STR(unit_labor, labor).c_str(), ENUM_KEY_STR(job_skill, skill).c_str(), remaining); + out.print("labor %s skill %s priority %d\n", ENUM_KEY_STR(unit_labor, labor).c_str(), ENUM_KEY_STR(job_skill, skill).c_str(), priority); std::list::iterator bestdwarf = idle_dwarfs.begin(); @@ -1870,8 +1873,11 @@ public: idle_dwarfs.erase(bestdwarf); pq.pop(); - if (--remaining) - pq.push(make_pair(remaining, labor)); + if (--labor_needed[labor] > 0) + { + priority /= 2; + pq.push(make_pair(priority, labor)); + } } print_debug = 0; From 45564ca0cb82d512dad4c6aa0c6dbe18b63f207e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 1 Dec 2012 23:12:41 -0600 Subject: [PATCH 010/136] Autolabor: generating haulers based on unstockpiled items (less than ideal). Fix wrong build labor for Farmer's workshop. Add build labor function for constructions (also works for furnaces and trade depots). Add architect detection. Use a different mechanism for selecting dwarfs for labors. --- plugins/autolabor.cpp | 148 +++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 60 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 5d90610d9..846ca8e64 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -529,7 +530,11 @@ struct dwarf_info_t bool has_pick; bool has_crossbow; - dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER) {} + int high_skill; + + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER), high_skill(0) + { + } void set_labor(df::unit_labor labor) { @@ -647,7 +652,7 @@ static df::unit_labor hauling_labor_map[] = static df::unit_labor workshop_build_labor[] = { /* Carpenters */ df::unit_labor::CARPENTER, - /* Farmers */ df::unit_labor::HERBALIST, + /* Farmers */ df::unit_labor::PROCESS_PLANT, /* Masons */ df::unit_labor::MASON, /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, /* Jewelers */ df::unit_labor::CUT_GEM, @@ -687,6 +692,19 @@ static df::building* get_building_from_job(df::job* j) return 0; } +static df::unit_labor construction_build_labor (df::item* i) +{ + MaterialInfo matinfo; + if (matinfo.decode(i)) + { + if (matinfo.material->flags.is_set(df::material_flags::IS_METAL)) + return df::unit_labor::METAL_CRAFT; + if (matinfo.material->flags.is_set(df::material_flags::WOOD)) + return df::unit_labor::CARPENTER; + } + return df::unit_labor::MASON; +} + class JobLaborMapper { private: class jlfunc @@ -729,6 +747,8 @@ private: case df::building_type::Workshop: { df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->design && !ws->design->flags.bits.designed) + return df::unit_labor::ARCHITECT; if (ws->type == df::workshop_type::Custom) { df::building_def* def = df::building_def::find(ws->custom_type); @@ -738,6 +758,16 @@ private: return workshop_build_labor[ws->type]; } break; + case df::building_type::Furnace: + case df::building_type::TradeDepot: + case df::building_type::Construction: + { + df::building_actual* b = (df::building_actual*) bld; + if (b->design && !b->design->flags.bits.designed) + return df::unit_labor::ARCHITECT; + return construction_build_labor(j->items[0]->item); + } + break; case df::building_type::FarmPlot: return df::unit_labor::PLANT; } @@ -1386,10 +1416,9 @@ private: int cnt_traction; int cnt_crutch; - std::map labor_needed; std::vector dwarf_info; - std::list idle_dwarfs; + std::list available_dwarfs; private: void scan_buildings() @@ -1486,6 +1515,9 @@ private: if (item->flags.whole & bad_flags.whole) continue; + if (!item->flags.bits.in_inventory && !item->isAssignedToStockpile()) + labor_needed[hauling_labor_map[item->getType()]]++; + if (!item->isWeapon()) continue; @@ -1504,6 +1536,8 @@ private: void collect_job_list() { + labor_needed.clear(); + for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) { df::job* j = jll->item; @@ -1524,9 +1558,6 @@ private: bld = ((df::general_ref_building_holderst *)(j->general_refs[r]))->building_id; } - if (worker != -1) - continue; - if (bld != -1) { if (print_debug) @@ -1617,7 +1648,7 @@ private: } } - // Find the activity state for each dwarf-> + // Find the activity state for each dwarf bool is_on_break = false; dwarf_state state = OTHER; @@ -1689,6 +1720,17 @@ private: cnt_crutch++; } + // find dwarf's highest effective skill + + int high_skill = 0; + + FOR_ENUM_ITEMS (job_skill, skill) + { + int skill_level = Units::getEffectiveSkill(dwarf->dwarf, skill); + high_skill = std::max(high_skill, skill_level); + } + + dwarf->high_skill = high_skill; // check if dwarf has an axe, pick, or crossbow for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) @@ -1733,17 +1775,17 @@ private: if (labor == unit_labor::NONE) continue; - dwarf->dwarf->status.labors[labor] = false; + dwarf->clear_labor(labor); } } - if (state == IDLE && !dwarf->clear_all) - idle_dwarfs.push_back(dwarf); + if ((state == IDLE || state == BUSY) && !dwarf->clear_all) + available_dwarfs.push_back(dwarf); } } - } + } public: void process() @@ -1760,14 +1802,14 @@ public: count_map_designations(); - // count number of picks and axes available for use - - count_tools(); - // collect current job list collect_job_list(); + // count number of picks and axes available for use + + count_tools(); + // collect list of dwarfs collect_dwarf_list(); @@ -1800,34 +1842,6 @@ public: } } - // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count - // and remove the idle dwarf from the idle list - - if (print_debug) - out.print("idle count = %d, labor need count = %d\n", idle_dwarfs.size(), labor_needed.size()); - - for (auto d = idle_dwarfs.begin(); d != idle_dwarfs.end(); d++) - { - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - - if ((*d)->dwarf->status.labors[l]) - if (labor_needed[l] > 0) - { - if (print_debug) - out.print("assign \"%s\" labor %s (carried through)\n", (*d)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, l).c_str()); - labor_needed[l]--; - d = idle_dwarfs.erase(d); // remove from idle list - d--; // and go back one - break; - } else { - (*d)->clear_labor(l); - } - } - } - priority_queue> pq; for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -1837,9 +1851,9 @@ public: } if (print_debug) - out.print("idle count = %d, labor need count = %d\n", idle_dwarfs.size(), pq.size()); + out.print("idle count = %d, labor need count = %d\n", available_dwarfs.size(), pq.size()); - while (!idle_dwarfs.empty() && !pq.empty()) + while (!available_dwarfs.empty() && !pq.empty()) { df::unit_labor labor = pq.top().second; int priority = pq.top().first; @@ -1848,30 +1862,44 @@ public: if (print_debug) out.print("labor %s skill %s priority %d\n", ENUM_KEY_STR(unit_labor, labor).c_str(), ENUM_KEY_STR(job_skill, skill).c_str(), priority); - std::list::iterator bestdwarf = idle_dwarfs.begin(); + std::list::iterator bestdwarf = available_dwarfs.begin(); - if (skill != df::job_skill::NONE) - { - int best_skill_level = -1; + int best_score = -10000; - for (std::list::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) + for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) + { + dwarf_info_t* d = (*k); + int skill_level = 0; + if (skill != df::job_skill::NONE) { - dwarf_info_t* d = (*k); int skill_level = Units::getEffectiveSkill(d->dwarf, skill); + } - if (skill_level > best_skill_level) - { - bestdwarf = k; - best_skill_level = skill_level; - } + int score = skill_level * 100 - (d->high_skill - skill) * 100; + if (d->dwarf->status.labors[labor]) + score += 300; + if ((labor == df::unit_labor::MINE && d->has_pick) || + (labor == df::unit_labor::CUTWOOD && d->has_axe) || + (labor == df::unit_labor::HUNT && d->has_crossbow)) + score += 300; + if (score > best_score) + { + bestdwarf = k; + best_score = score; } } if (print_debug) - out.print("assign \"%s\" labor %s\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); - (*bestdwarf)->set_labor(labor); + out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), best_score); + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == labor) + (*bestdwarf)->set_labor(l); + else + (*bestdwarf)->clear_labor(l); + } - idle_dwarfs.erase(bestdwarf); + available_dwarfs.erase(bestdwarf); pq.pop(); if (--labor_needed[labor] > 0) { From 799da41f70163b1bd1d29e75837233c2f8427d21 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 2 Dec 2012 02:02:16 -0600 Subject: [PATCH 011/136] Autolabor: Add debugging messages from the labor deduction module; add deduction rules for some building destroy jobs; automatically exclude handless dwarfs from labor poor; use DF's own hauling job counts to compute hauling demand (and arrange for the "hauling canary" so that this always works) --- plugins/autolabor.cpp | 117 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 10 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 846ca8e64..daa261da3 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -705,7 +705,22 @@ static df::unit_labor construction_build_labor (df::item* i) return df::unit_labor::MASON; } +color_ostream* debug_stream; + +void debug (char* fmt, ...) +{ + if (debug_stream) + { + va_list args; + va_start(args,fmt); + debug_stream->vprint(fmt, args); + va_end(args); + } +} + + class JobLaborMapper { + private: class jlfunc { @@ -772,7 +787,9 @@ private: return df::unit_labor::PLANT; } - // FIXME + debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + return df::unit_labor::NONE; } jlfunc_construct_bld() {} @@ -786,7 +803,35 @@ private: df::building* bld = get_building_from_job (j); df::building_type type = bld->getType(); - // FIXME + switch (bld->getType()) + { + case df::building_type::Workshop: + { + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + } + break; + case df::building_type::Furnace: + case df::building_type::TradeDepot: + case df::building_type::Construction: + { + df::building_actual* b = (df::building_actual*) bld; + return construction_build_labor(j->items[0]->item); + } + break; + case df::building_type::FarmPlot: + return df::unit_labor::PLANT; + } + + debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + return df::unit_labor::NONE; } jlfunc_destroy_bld() {} @@ -816,9 +861,14 @@ private: if (j->material_category.bits.bone) return df::unit_labor::BONE_CARVE; else - return df::unit_labor::NONE; //FIXME + { + debug ("AUTOLABOR: Cannot deduce labor for make crafts job (not bone)\n"); + return df::unit_labor::NONE; + } default: - return df::unit_labor::NONE; //FIXME + debug ("AUTOLABOR: Cannot deduce labor for make crafts job, item type %s\n", + ENUM_KEY_STR(item_type, jobitem).c_str()); + return df::unit_labor::NONE; } } case df::workshop_type::Masons: @@ -833,7 +883,9 @@ private: case df::workshop_type::MetalsmithsForge: return metaltype; default: - return df::unit_labor::NONE; // FIXME + debug ("AUTOLABOR: Cannot deduce labor for make job, workshop type %s\n", + ENUM_KEY_STR(workshop_type, type).c_str()); + return df::unit_labor::NONE; } } else if (bld->getType() == df::building_type::Furnace) @@ -845,11 +897,16 @@ private: case df::furnace_type::GlassFurnace: return df::unit_labor::GLASSMAKER; default: - return df::unit_labor::NONE; // FIXME + debug ("AUTOLABOR: Cannot deduce labor for make job, furnace type %s\n", + ENUM_KEY_STR(furnace_type, type).c_str()); + return df::unit_labor::NONE; } } - return df::unit_labor::NONE; // FIXME + debug ("AUTOLABOR: Cannot deduce labor for make job, building type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + + return df::unit_labor::NONE; } jlfunc_make (df::unit_labor mt) : metaltype(mt) {} @@ -1515,9 +1572,6 @@ private: if (item->flags.whole & bad_flags.whole) continue; - if (!item->flags.bits.in_inventory && !item->isAssignedToStockpile()) - labor_needed[hauling_labor_map[item->getType()]]++; - if (!item->isWeapon()) continue; @@ -1675,6 +1729,11 @@ private: state = OTHER; else if (dwarf->dwarf->burrows.size() > 0) state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy + else if (dwarf->dwarf->status2.able_grasp_impair == 0) + { + state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors + dwarf->clear_all = true; + } else state = IDLE; } @@ -1834,6 +1893,17 @@ public: labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; labor_needed[df::unit_labor::HAUL_ITEM] += cnt_crutch; + // add entries for hauling jobs + + labor_needed[df::unit_labor::HAUL_STONE] += world->stockpile.num_jobs[1]; + labor_needed[df::unit_labor::HAUL_WOOD] += world->stockpile.num_jobs[2]; + labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[3]; + labor_needed[df::unit_labor::HAUL_BODY] += world->stockpile.num_jobs[5]; + labor_needed[df::unit_labor::HAUL_FOOD] += world->stockpile.num_jobs[6]; + labor_needed[df::unit_labor::HAUL_REFUSE] += world->stockpile.num_jobs[7]; + labor_needed[df::unit_labor::HAUL_FURNITURE] += world->stockpile.num_jobs[8]; + labor_needed[df::unit_labor::HAUL_ANIMAL] += world->stockpile.num_jobs[9]; + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -1853,6 +1923,15 @@ public: if (print_debug) out.print("idle count = %d, labor need count = %d\n", available_dwarfs.size(), pq.size()); + int canary = (1 << df::unit_labor::HAUL_STONE) | + (1 << df::unit_labor::HAUL_WOOD) | + (1 << df::unit_labor::HAUL_BODY) | + (1 << df::unit_labor::HAUL_FOOD) | + (1 << df::unit_labor::HAUL_REFUSE) | + (1 << df::unit_labor::HAUL_ITEM) | + (1 << df::unit_labor::HAUL_FURNITURE) | + (1 << df::unit_labor::HAUL_ANIMAL); + while (!available_dwarfs.empty() && !pq.empty()) { df::unit_labor labor = pq.top().second; @@ -1899,6 +1978,9 @@ public: (*bestdwarf)->clear_labor(l); } + if (labor >= df::unit_labor::HAUL_STONE && labor <= df::unit_labor::HAUL_ANIMAL) + canary &= ~(1 << labor); + available_dwarfs.erase(bestdwarf); pq.pop(); if (--labor_needed[labor] > 0) @@ -1908,6 +1990,19 @@ public: } } + if (canary != 0) + { + dwarf_info_t* d = dwarf_info.front(); + FOR_ENUM_ITEMS (unit_labor, l) + { + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMAL && + canary & (1 << l)) + d->set_labor(l); + } + if (print_debug) + out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); + } + print_debug = 0; } @@ -1946,6 +2041,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; step_count = 0; + debug_stream = &out; + AutoLaborManager alm(out); alm.process(); From b743f4f42db8f217c633d3dd7b31321b2dc5aa23 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 2 Dec 2012 09:47:15 -0600 Subject: [PATCH 012/136] Autolabor: remove some debug spam, and fix an error in computing preference scoring --- plugins/autolabor.cpp | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index daa261da3..57b902ee3 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1521,9 +1521,6 @@ private: if (!bl->flags.bits.designated) continue; - if (print_debug) - out.print ("block with designations found: %d, %d, %d\n", bl->map_pos.x, bl->map_pos.y, bl->map_pos.z); - for (int x = 0; x < 16; x++) for (int y = 0; y < 16; y++) { @@ -1614,8 +1611,6 @@ private: if (bld != -1) { - if (print_debug) - out.print("Checking job %d for first in queue at building %d\n", j->id, bld); df::building* b = binsearch_in_vector(world->buildings.all, bld); int fjid = -1; for (int jn = 0; jn < b->jobs.size(); jn++) @@ -1627,21 +1622,13 @@ private: } // check if this job is the first nonsuspended job on this building; if not, ignore it if (fjid != j->id) { - if (print_debug) - out.print("Job %d is not first in queue at building %d (%d), skipping\n", j->id, bld, fjid); continue; } - if (print_debug) - out.print("Job %d is in queue at building %d\n", j->id, bld); - } df::unit_labor labor = labor_mapper->find_job_labor (j); - if (print_debug) - out.print ("Job requiring labor %d found\n", labor); - if (labor != df::unit_labor::NONE) labor_needed[labor]++; } @@ -1805,22 +1792,16 @@ private: dwarf->has_axe = 1; if (state != IDLE) axe_count--; - if (print_debug) - out.print("Dwarf \"%s\" has an axe\n", dwarf->dwarf->name.first_name.c_str()); } else if (weaponsk == df::job_skill::MINING) { dwarf->has_pick = 1; if (state != IDLE) pick_count--; - if (print_debug) - out.print("Dwarf \"%s\" has an pick\n", dwarf->dwarf->name.first_name.c_str()); } else if (rangesk == df::job_skill::CROSSBOW) { dwarf->has_crossbow = 1; - if (print_debug) - out.print("Dwarf \"%s\" has a crossbow\n", dwarf->dwarf->name.first_name.c_str()); } } } @@ -1921,7 +1902,7 @@ public: } if (print_debug) - out.print("idle count = %d, labor need count = %d\n", available_dwarfs.size(), pq.size()); + out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size()); int canary = (1 << df::unit_labor::HAUL_STONE) | (1 << df::unit_labor::HAUL_WOOD) | @@ -1954,7 +1935,7 @@ public: int skill_level = Units::getEffectiveSkill(d->dwarf, skill); } - int score = skill_level * 100 - (d->high_skill - skill) * 100; + int score = skill_level * 100 - (d->high_skill - skill_level) * 100; if (d->dwarf->status.labors[labor]) score += 300; if ((labor == df::unit_labor::MINE && d->has_pick) || From 4cd217b782625df88ee82d2e6238587e0b538cc7 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 2 Dec 2012 14:27:13 -0600 Subject: [PATCH 013/136] to new assignment algorithm. Add priority boost for labors based on how long it's been since they were last used, to avoid labor starvation. Move persistent configuration to "autolabor/2.0" to avoid conflicting with older versions. --- plugins/autolabor.cpp | 426 ++++++++++++++++++------------------------ 1 file changed, 177 insertions(+), 249 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 57b902ee3..3a88ef630 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1,6 +1,9 @@ -// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D +/* +* Autolabor 2.0 module for dfhack +* +* */ + -// some headers required for a plugin. Nothing special, just the basics. #include "Core.h" #include #include @@ -68,31 +71,6 @@ using df::global::world; #define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) -/* -* Autolabor module for dfhack -* -* The idea behind this module is to constantly adjust labors so that the right dwarves -* are assigned to new tasks. The key is that, for almost all labors, once a dwarf begins -* a job it will finish that job even if the associated labor is removed. Thus the -* strategy is to frequently decide, for each labor, which dwarves should possibly take -* a new job for that labor if it comes in and which shouldn't, and then set the labors -* appropriately. The updating should happen as often as can be reasonably done without -* causing lag. -* -* The obvious thing to do is to just set each labor on a single idle dwarf who is best -* suited to doing new jobs of that labor. This works in a way, but it leads to a lot -* of idle dwarves since only one dwarf will be dispatched for each labor in an update -* cycle, and dwarves that finish tasks will wait for the next update before being -* dispatched. An improvement is to also set some labors on dwarves that are currently -* doing a job, so that they will immediately take a new job when they finish. The -* details of which dwarves should have labors set is mostly a heuristic. -* -* A complication to the above simple scheme is labors that have associated equipment. -* Enabling/disabling these labors causes dwarves to change equipment, and disabling -* them in the middle of a job may cause the job to be abandoned. Those labors -* (mining, hunting, and woodcutting) need to be handled carefully to minimize churn. -*/ - static int enable_autolabor = 0; static bool print_debug = 0; @@ -116,12 +94,6 @@ DFHACK_PLUGIN("autolabor"); static void generate_labor_to_skill_map(); -enum labor_mode { - DISABLE, - HAULERS, - AUTOMATIC, -}; - enum dwarf_state { // Ready for a new task IDLE, @@ -387,136 +359,108 @@ struct labor_info { PersistentDataItem config; - bool is_exclusive; int active_dwarfs; - labor_mode mode() { return (labor_mode) config.ival(0); } - void set_mode(labor_mode mode) { config.ival(0) = mode; } - - int minimum_dwarfs() { return config.ival(1); } - void set_minimum_dwarfs(int minimum_dwarfs) { config.ival(1) = minimum_dwarfs; } + int priority() { return config.ival(1); } + void set_priority(int priority) { config.ival(1) = priority; } int maximum_dwarfs() { return config.ival(2); } void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; } + int time_since_last_assigned() + { + return (*df::global::cur_year - config.ival(3)) * 403200 + *df::global::cur_year_tick - config.ival(4); + } + void mark_assigned() { + config.ival(3) = (* df::global::cur_year); + config.ival(4) = (* df::global::cur_year_tick); + } + }; struct labor_default { - labor_mode mode; - bool is_exclusive; - int minimum_dwarfs; + int priority; int maximum_dwarfs; - int active_dwarfs; }; -static int hauler_pct = 33; - static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { - /* MINE */ {AUTOMATIC, true, 2, 200, 0}, - /* HAUL_STONE */ {HAULERS, false, 1, 200, 0}, - /* HAUL_WOOD */ {HAULERS, false, 1, 200, 0}, - /* HAUL_BODY */ {HAULERS, false, 1, 200, 0}, - /* HAUL_FOOD */ {HAULERS, false, 1, 200, 0}, - /* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0}, - /* HAUL_ITEM */ {HAULERS, false, 1, 200, 0}, - /* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0}, - /* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0}, - /* CLEAN */ {HAULERS, false, 1, 200, 0}, - /* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0}, - /* CARPENTER */ {AUTOMATIC, false, 1, 200, 0}, - /* DETAIL */ {AUTOMATIC, false, 1, 200, 0}, - /* MASON */ {AUTOMATIC, false, 1, 200, 0}, - /* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0}, - /* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0}, - /* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0}, - /* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0}, - /* SURGERY */ {AUTOMATIC, false, 1, 200, 0}, - /* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0}, - /* SUTURING */ {AUTOMATIC, false, 1, 200, 0}, - /* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0}, - /* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0}, - /* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0}, - /* BUTCHER */ {AUTOMATIC, false, 1, 200, 0}, - /* TRAPPER */ {AUTOMATIC, false, 1, 200, 0}, - /* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0}, - /* LEATHER */ {AUTOMATIC, false, 1, 200, 0}, - /* TANNER */ {AUTOMATIC, false, 1, 200, 0}, - /* BREWER */ {AUTOMATIC, false, 1, 200, 0}, - /* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0}, - /* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0}, - /* WEAVER */ {AUTOMATIC, false, 1, 200, 0}, - /* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0}, - /* MILLER */ {AUTOMATIC, false, 1, 200, 0}, - /* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0}, - /* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0}, - /* MILK */ {AUTOMATIC, false, 1, 200, 0}, - /* COOK */ {AUTOMATIC, false, 1, 200, 0}, - /* PLANT */ {AUTOMATIC, false, 1, 200, 0}, - /* HERBALIST */ {AUTOMATIC, false, 1, 200, 0}, - /* FISH */ {AUTOMATIC, false, 1, 1, 0}, - /* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0}, - /* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0}, - /* HUNT */ {AUTOMATIC, true, 1, 1, 0}, - /* SMELT */ {AUTOMATIC, false, 1, 200, 0}, - /* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0}, - /* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0}, - /* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0}, - /* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0}, - /* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0}, - /* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0}, - /* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0}, - /* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0}, - /* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0}, - /* BOWYER */ {AUTOMATIC, false, 1, 200, 0}, - /* MECHANIC */ {AUTOMATIC, false, 1, 200, 0}, - /* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0}, - /* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0}, - /* DYER */ {AUTOMATIC, false, 1, 200, 0}, - /* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0}, - /* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0}, - /* SHEARER */ {AUTOMATIC, false, 1, 200, 0}, - /* SPINNER */ {AUTOMATIC, false, 1, 200, 0}, - /* POTTERY */ {AUTOMATIC, false, 1, 200, 0}, - /* GLAZING */ {AUTOMATIC, false, 1, 200, 0}, - /* PRESSING */ {AUTOMATIC, false, 1, 200, 0}, - /* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) - /* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0}, - /* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0} -}; - -static const int responsibility_penalties[] = { - 0, /* LAW_MAKING */ - 0, /* LAW_ENFORCEMENT */ - 3000, /* RECEIVE_DIPLOMATS */ - 0, /* MEET_WORKERS */ - 1000, /* MANAGE_PRODUCTION */ - 3000, /* TRADE */ - 1000, /* ACCOUNTING */ - 0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */ - 0, /* MAKE_INTRODUCTIONS */ - 0, /* MAKE_PEACE_AGREEMENTS */ - 0, /* MAKE_TOPIC_AGREEMENTS */ - 0, /* COLLECT_TAXES */ - 0, /* ESCORT_TAX_COLLECTOR */ - 0, /* EXECUTIONS */ - 0, /* TAME_EXOTICS */ - 0, /* RELIGION */ - 0, /* ATTACK_ENEMIES */ - 0, /* PATROL_TERRITORY */ - 0, /* MILITARY_GOALS */ - 0, /* MILITARY_STRATEGY */ - 0, /* UPGRADE_SQUAD_EQUIPMENT */ - 0, /* EQUIPMENT_MANIFESTS */ - 0, /* SORT_AMMUNITION */ - 0, /* BUILD_MORALE */ - 5000 /* HEALTH_MANAGEMENT */ + /* MINE */ {200, 0}, + /* HAUL_STONE */ {100, 0}, + /* HAUL_WOOD */ {100, 0}, + /* HAUL_BODY */ {200, 0}, + /* HAUL_FOOD */ {300, 0}, + /* HAUL_REFUSE */ {100, 0}, + /* HAUL_ITEM */ {100, 0}, + /* HAUL_FURNITURE */ {100, 0}, + /* HAUL_ANIMAL */ {100, 0}, + /* CLEAN */ {200, 0}, + /* CUTWOOD */ {200, 0}, + /* CARPENTER */ {200, 0}, + /* DETAIL */ {200, 0}, + /* MASON */ {200, 0}, + /* ARCHITECT */ {400, 0}, + /* ANIMALTRAIN */ {200, 0}, + /* ANIMALCARE */ {200, 0}, + /* DIAGNOSE */ {1000, 0}, + /* SURGERY */ {1000, 0}, + /* BONE_SETTING */ {1000, 0}, + /* SUTURING */ {1000, 0}, + /* DRESSING_WOUNDS */ {1000, 0}, + /* FEED_WATER_CIVILIANS */ {1000, 0}, + /* RECOVER_WOUNDED */ {200, 0}, + /* BUTCHER */ {200, 0}, + /* TRAPPER */ {200, 0}, + /* DISSECT_VERMIN */ {200, 0}, + /* LEATHER */ {200, 0}, + /* TANNER */ {200, 0}, + /* BREWER */ {200, 0}, + /* ALCHEMIST */ {200, 0}, + /* SOAP_MAKER */ {200, 0}, + /* WEAVER */ {200, 0}, + /* CLOTHESMAKER */ {200, 0}, + /* MILLER */ {200, 0}, + /* PROCESS_PLANT */ {200, 0}, + /* MAKE_CHEESE */ {200, 0}, + /* MILK */ {200, 0}, + /* COOK */ {200, 0}, + /* PLANT */ {200, 0}, + /* HERBALIST */ {200, 0}, + /* FISH */ {100, 0}, + /* CLEAN_FISH */ {200, 0}, + /* DISSECT_FISH */ {200, 0}, + /* HUNT */ {100, 0}, + /* SMELT */ {200, 0}, + /* FORGE_WEAPON */ {200, 0}, + /* FORGE_ARMOR */ {200, 0}, + /* FORGE_FURNITURE */ {200, 0}, + /* METAL_CRAFT */ {200, 0}, + /* CUT_GEM */ {200, 0}, + /* ENCRUST_GEM */ {200, 0}, + /* WOOD_CRAFT */ {200, 0}, + /* STONE_CRAFT */ {200, 0}, + /* BONE_CARVE */ {200, 0}, + /* GLASSMAKER */ {200, 0}, + /* EXTRACT_STRAND */ {200, 0}, + /* SIEGECRAFT */ {200, 0}, + /* SIEGEOPERATE */ {200, 0}, + /* BOWYER */ {200, 0}, + /* MECHANIC */ {200, 0}, + /* POTASH_MAKING */ {200, 0}, + /* LYE_MAKING */ {200, 0}, + /* DYER */ {200, 0}, + /* BURN_WOOD */ {200, 0}, + /* OPERATE_PUMP */ {200, 0}, + /* SHEARER */ {200, 0}, + /* SPINNER */ {200, 0}, + /* POTTERY */ {200, 0}, + /* GLAZING */ {200, 0}, + /* PRESSING */ {200, 0}, + /* BEEKEEPING */ {200, 1}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) + /* WAX_WORKING */ {200, 0}, + /* PUSH_HAUL_VEHICLES */ {200, 0} }; struct dwarf_info_t @@ -556,6 +500,12 @@ struct dwarf_info_t }; +/* +* Here starts all the complicated stuff to try to deduce labors from jobs. +* This is all way more complicated than it really ought to be, but I have +* not found a way to make it simpler. +*/ + static df::unit_labor hauling_labor_map[] = { df::unit_labor::HAUL_ITEM, /* BAR */ @@ -1232,6 +1182,8 @@ public: } }; +/* End of labor deducer */ + static JobLaborMapper* labor_mapper; static bool isOptionEnabled(unsigned flag) @@ -1262,14 +1214,13 @@ static void cleanup_state() static void reset_labor(df::unit_labor labor) { - labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs); + labor_infos[labor].set_priority(default_labor_infos[labor].priority); labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); - labor_infos[labor].set_mode(default_labor_infos[labor].mode); } static void init_state() { - config = World::GetPersistentData("autolabor/config"); + config = World::GetPersistentData("autolabor/2.0/config"); if (config.isValid() && config.ival(0) == -1) config.ival(0) = 0; @@ -1278,30 +1229,19 @@ static void init_state() if (!enable_autolabor) return; - auto cfg_haulpct = World::GetPersistentData("autolabor/haulpct"); - if (cfg_haulpct.isValid()) - { - hauler_pct = cfg_haulpct.ival(0); - } - else - { - hauler_pct = 33; - } - // Load labors from save labor_infos.resize(ARRAY_COUNT(default_labor_infos)); std::vector items; - World::GetPersistentData(&items, "autolabor/labors/", true); + World::GetPersistentData(&items, "autolabor/2.0/labors/", true); for (auto p = items.begin(); p != items.end(); p++) { string key = p->key(); - df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); + df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/2.0/labors/")).c_str()); if (labor >= 0 && labor <= labor_infos.size()) { labor_infos[labor].config = *p; - labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive; labor_infos[labor].active_dwarfs = 0; } } @@ -1312,11 +1252,10 @@ static void init_state() continue; std::stringstream name; - name << "autolabor/labors/" << i; + name << "autolabor/2.0/labors/" << i; labor_infos[i].config = World::AddPersistentData(name.str()); - - labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive; + labor_infos[i].mark_assigned(); labor_infos[i].active_dwarfs = 0; reset_labor((df::unit_labor) i); } @@ -1358,7 +1297,7 @@ static void enable_plugin(color_ostream &out) { if (!config.isValid()) { - config = World::AddPersistentData("autolabor/config"); + config = World::AddPersistentData("autolabor/2.0/config"); config.ival(0) = 0; } @@ -1384,13 +1323,13 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector []\n" - " Set number of dwarves assigned to a labor.\n" - " autolabor haulers\n" - " Set a labor to be handled by hauler dwarves.\n" - " autolabor disable\n" - " Turn off autolabor for a specific labor.\n" - " autolabor reset\n" + " autolabor max \n" + " Set max number of dwarves assigned to a labor.\n" + " autolabor max none\n" + " Unrestrict the number of dwarves assigned to a labor.\n" + " autolabor priority \n" + " Change the assignment priority of a labor (default is 100)\n" + " autolabor reset \n" " Return a labor to the default handling.\n" " autolabor reset-all\n" " Return all labors to the default handling.\n" @@ -1400,19 +1339,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector items.all.size(); ++i) + auto& v = world->items.other[df::items_other_id::WEAPON]; + for (auto i = v.begin(); i != v.end(); i++) { - df::item* item = world->items.all[i]; + df::item* item = *i; if (item->flags.whole & bad_flags.whole) continue; @@ -1629,8 +1559,13 @@ private: df::unit_labor labor = labor_mapper->find_job_labor (j); - if (labor != df::unit_labor::NONE) + if (labor != df::unit_labor::NONE) + { labor_needed[labor]++; + + if (worker != -1) + labor_infos[labor].mark_assigned(); + } } } @@ -1897,8 +1832,16 @@ public: for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { + df::unit_labor l = i->first; + if (labor_infos[l].maximum_dwarfs() > 0 && + i->second > labor_infos[l].maximum_dwarfs()) + i->second = labor_infos[l].maximum_dwarfs(); if (i->second > 0) - pq.push(make_pair(100, i->first)); + { + int priority = labor_infos[l].priority(); + priority += labor_infos[l].time_since_last_assigned()/12; + pq.push(make_pair(priority, l)); + } } if (print_debug) @@ -2038,16 +1981,22 @@ void print_labor (df::unit_labor labor, color_ostream &out) out << labor_name << ": "; for (int i = 0; i < 20 - (int)labor_name.length(); i++) out << ' '; - if (labor_infos[labor].mode() == DISABLE) - out << "disabled" << endl; - else + out << "priority " << labor_infos[labor].priority() + << ", maximum " << labor_infos[labor].maximum_dwarfs() + << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl; +} + +df::unit_labor lookup_labor_by_name (std::string& name) +{ + df::unit_labor labor = df::unit_labor::NONE; + + FOR_ENUM_ITEMS(unit_labor, test_labor) { - if (labor_infos[labor].mode() == HAULERS) - out << "haulers"; - else - out << "minimum " << labor_infos[labor].minimum_dwarfs() << ", maximum " << labor_infos[labor].maximum_dwarfs(); - out << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl; + if (name == ENUM_KEY_STR(unit_labor, test_labor)) + labor = test_labor; } + + return labor; } @@ -2061,10 +2010,9 @@ command_result autolabor (color_ostream &out, std::vector & parame } if (parameters.size() == 1 && - (parameters[0] == "0" || parameters[0] == "enable" || - parameters[0] == "1" || parameters[0] == "disable")) + (parameters[0] == "enable" || parameters[0] == "disable")) { - bool enable = (parameters[0] == "1" || parameters[0] == "enable"); + bool enable = (parameters[0] == "enable"); if (enable && !enable_autolabor) { enable_plugin(out); @@ -2079,7 +2027,8 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_OK; } - else if (parameters.size() == 2 && parameters[0] == "haulpct") + else if (parameters.size() == 3 && + (parameters[0] == "max" || parameters[0] == "priority")) { if (!enable_autolabor) { @@ -2087,11 +2036,30 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_FAILURE; } - int pct = atoi (parameters[1].c_str()); - hauler_pct = pct; + df::unit_labor labor = lookup_labor_by_name(parameters[1]); + + if (labor == df::unit_labor::NONE) + { + out.printerr("Could not find labor %s.\n", parameters[0].c_str()); + return CR_WRONG_USAGE; + } + + int v; + + if (parameters[2] == "none") + v = 0; + else + v = atoi (parameters[2].c_str()); + + if (parameters[0] == "max") + labor_infos[labor].set_maximum_dwarfs(v); + else if (parameters[0] == "priority") + labor_infos[labor].set_priority(v); + + print_labor(labor, out); return CR_OK; } - else if (parameters.size() == 2 || parameters.size() == 3) + else if (parameters.size() == 2 && parameters[0] == "reset") { if (!enable_autolabor) { @@ -2099,55 +2067,15 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_FAILURE; } - df::unit_labor labor = unit_labor::NONE; + df::unit_labor labor = lookup_labor_by_name(parameters[1]); - FOR_ENUM_ITEMS(unit_labor, test_labor) - { - if (parameters[0] == ENUM_KEY_STR(unit_labor, test_labor)) - labor = test_labor; - } - - if (labor == unit_labor::NONE) + if (labor == df::unit_labor::NONE) { out.printerr("Could not find labor %s.\n", parameters[0].c_str()); return CR_WRONG_USAGE; } - - if (parameters[1] == "haulers") - { - labor_infos[labor].set_mode(HAULERS); - print_labor(labor, out); - return CR_OK; - } - if (parameters[1] == "disable") - { - labor_infos[labor].set_mode(DISABLE); - print_labor(labor, out); - return CR_OK; - } - if (parameters[1] == "reset") - { - reset_labor(labor); - print_labor(labor, out); - return CR_OK; - } - - int minimum = atoi (parameters[1].c_str()); - int maximum = 200; - if (parameters.size() == 3) - maximum = atoi (parameters[2].c_str()); - - if (maximum < minimum || maximum < 0 || minimum < 0) - { - out.printerr("Syntax: autolabor []\n", maximum, minimum); - return CR_WRONG_USAGE; - } - - labor_infos[labor].set_minimum_dwarfs(minimum); - labor_infos[labor].set_maximum_dwarfs(maximum); - labor_infos[labor].set_mode(AUTOMATIC); + reset_labor(labor); print_labor(labor, out); - return CR_OK; } else if (parameters.size() == 1 && parameters[0] == "reset-all") @@ -2213,8 +2141,8 @@ command_result autolabor (color_ostream &out, std::vector & parame else { out.print("Automatically assigns labors to dwarves.\n" - "Activate with 'autolabor 1', deactivate with 'autolabor 0'.\n" - "Current state: %d.\n", enable_autolabor); + "Activate with 'autolabor enable', deactivate with 'autolabor disable'.\n" + "Current state: %s.\n", enable_autolabor ? "enabled" : "disabled"); return CR_OK; } From 44bb965c97c81983f53edad71c32de52a84324d9 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 2 Dec 2012 18:41:20 -0600 Subject: [PATCH 014/136] Autolabor: add more building labors --- plugins/autolabor.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 3a88ef630..711701f78 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -735,6 +735,21 @@ private: break; case df::building_type::FarmPlot: return df::unit_labor::PLANT; + case df::building_type::Chair: + case df::building_type::Bed: + case df::building_type::Table: + case df::building_type::Coffin: + case df::building_type::Door: + case df::building_type::Floodgate: + case df::building_type::Box: + case df::building_type::Weaponrack: + case df::building_type::Armorstand: + case df::building_type::Cabinet: + case df::building_type::Statue: + case df::building_type::WindowGlass: + case df::building_type::WindowGem: + case df::building_type::Cage: + return df::unit_labor::HAUL_FURNITURE; } debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", From 9563dae5d738950b4201b87194b7860ff2b8ceb6 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 3 Dec 2012 01:41:02 -0600 Subject: [PATCH 015/136] Autolabor: add labors for construct bridge, construct nestbox, construct trap, deconstruct wagon; fix error in labor for deconstruct furnace/tradedepot/construction; actually update the "active dwarf" numbers displayed in "autolabor list"; increase assignment penalty for dwarfs using skills lower than their best skill; increase assignment bonus for continuing in the same labor and for having the right tool for the job. --- plugins/autolabor.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 711701f78..d880d3fcc 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -726,6 +726,7 @@ private: case df::building_type::Furnace: case df::building_type::TradeDepot: case df::building_type::Construction: + case df::building_type::Bridge: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) @@ -749,7 +750,10 @@ private: case df::building_type::WindowGlass: case df::building_type::WindowGem: case df::building_type::Cage: + case df::building_type::NestBox: return df::unit_labor::HAUL_FURNITURE; + case df::building_type::Trap: + return df::unit_labor::MECHANIC; } debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", @@ -785,9 +789,10 @@ private: case df::building_type::Furnace: case df::building_type::TradeDepot: case df::building_type::Construction: + case df::building_type::Wagon: { df::building_actual* b = (df::building_actual*) bld; - return construction_build_labor(j->items[0]->item); + return construction_build_labor(b->contained_items[0]->item); } break; case df::building_type::FarmPlot: @@ -1784,6 +1789,14 @@ public: cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = cnt_setting = cnt_traction = cnt_crutch = 0; + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + labor_infos[l].active_dwarfs = 0; + } + // scan for specific buildings of interest scan_buildings(); @@ -1893,13 +1906,13 @@ public: int skill_level = Units::getEffectiveSkill(d->dwarf, skill); } - int score = skill_level * 100 - (d->high_skill - skill_level) * 100; + int score = skill_level * 100 - (d->high_skill - skill_level) * 200; if (d->dwarf->status.labors[labor]) - score += 300; + score += 500; if ((labor == df::unit_labor::MINE && d->has_pick) || (labor == df::unit_labor::CUTWOOD && d->has_axe) || (labor == df::unit_labor::HUNT && d->has_crossbow)) - score += 300; + score += 500; if (score > best_score) { bestdwarf = k; @@ -1919,6 +1932,7 @@ public: if (labor >= df::unit_labor::HAUL_STONE && labor <= df::unit_labor::HAUL_ANIMAL) canary &= ~(1 << labor); + labor_infos[labor].active_dwarfs++; available_dwarfs.erase(bestdwarf); pq.pop(); From 208b9915eae8b8150fd4053c0a32b266e76b1ec3 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 3 Dec 2012 04:28:08 -0600 Subject: [PATCH 016/136] Autolabor: splints and crutches are furniture (at least at a forge); remove test that excludes pet owners from being given jobs when they're idle; add test for hungry/thirsty dwarves to trigger a feed/water civilians requirement; add a vehicle hauling requirement based on the existence of hauling routes --- plugins/autolabor.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index d880d3fcc..551e2098e 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include @@ -1143,9 +1144,9 @@ public: job_to_labor_table[df::job_type::ConstructGrate] = jlf_make_furniture; job_to_labor_table[df::job_type::RemoveStairs] = jlf_const(df::unit_labor::MINE); job_to_labor_table[df::job_type::ConstructQuern] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructMillstone] = jlf_make_furniture ; - job_to_labor_table[df::job_type::ConstructSplint] = jlf_make_object ; - job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_object; + job_to_labor_table[df::job_type::ConstructMillstone] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructSplint] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_furniture; job_to_labor_table[df::job_type::ConstructTractionBench] = jlf_const(df::unit_labor::MECHANIC); job_to_labor_table[df::job_type::CleanSelf] = jlf_no_labor; job_to_labor_table[df::job_type::BringCrutch] = jlf_no_labor; @@ -1422,6 +1423,8 @@ private: int cnt_traction; int cnt_crutch; + int need_food_water; + std::map labor_needed; std::vector dwarf_info; std::list available_dwarfs; @@ -1667,8 +1670,6 @@ private: { if (is_on_break) state = OTHER; - else if (dwarf->dwarf->specific_refs.size() > 0) - state = OTHER; else if (dwarf->dwarf->burrows.size() > 0) state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy else if (dwarf->dwarf->status2.able_grasp_impair == 0) @@ -1721,6 +1722,9 @@ private: cnt_crutch++; } + if (dwarf->dwarf->counters2.hunger_timer > 60000 || dwarf->dwarf->counters2.thirst_timer > 40000) + need_food_water++; + // find dwarf's highest effective skill int high_skill = 0; @@ -1788,6 +1792,7 @@ public: dig_count = tree_count = plant_count = detail_count = pick_count = axe_count = 0; cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = cnt_setting = cnt_traction = cnt_crutch = 0; + need_food_water = 0; FOR_ENUM_ITEMS(unit_labor, l) { @@ -1837,6 +1842,8 @@ public: labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; labor_needed[df::unit_labor::HAUL_ITEM] += cnt_crutch; + labor_needed[df::unit_labor::FEED_WATER_CIVILIANS] += need_food_water; + // add entries for hauling jobs labor_needed[df::unit_labor::HAUL_STONE] += world->stockpile.num_jobs[1]; @@ -1848,6 +1855,12 @@ public: labor_needed[df::unit_labor::HAUL_FURNITURE] += world->stockpile.num_jobs[8]; labor_needed[df::unit_labor::HAUL_ANIMAL] += world->stockpile.num_jobs[9]; + // add entries for vehicle hauling + + for (auto v = world->vehicles.all.begin(); v != world->vehicles.all.end(); v++) + if ((*v)->route_id != -1) + labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) From 287ee2bc0413fb0ccf5e5566288465479d46f23c Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 3 Dec 2012 14:20:57 -0600 Subject: [PATCH 017/136] Autolabor: allow multiple simultaneous jobs at farms. --- plugins/autolabor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 551e2098e..de4f2eb49 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1574,7 +1574,8 @@ private: break; } // check if this job is the first nonsuspended job on this building; if not, ignore it - if (fjid != j->id) { + // (except for farms) + if (fjid != j->id && b->getType() != df::building_type::FarmPlot) { continue; } From 0f1aaa6ec48d43e61f7889d97658b94f57965c67 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 4 Dec 2012 01:59:44 -0600 Subject: [PATCH 018/136] Autolabor: Items marked for dump now generate haul refuse demand. Also corrected labor for dump item jobs. --- plugins/autolabor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index de4f2eb49..c231a4cf2 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -994,7 +994,7 @@ public: job_to_labor_table[df::job_type::Clean] = jlf_const(df::unit_labor::CLEAN); job_to_labor_table[df::job_type::Rest] = jlf_no_labor; job_to_labor_table[df::job_type::PickupEquipment] = jlf_no_labor; - job_to_labor_table[df::job_type::DumpItem] = jlf_hauling; + job_to_labor_table[df::job_type::DumpItem] = jlf_const(df::unit_labor::HAUL_REFUSE); job_to_labor_table[df::job_type::StrangeMoodCrafter] = jlf_no_labor; job_to_labor_table[df::job_type::StrangeMoodJeweller] = jlf_no_labor; job_to_labor_table[df::job_type::StrangeMoodForge] = jlf_no_labor; @@ -1515,10 +1515,14 @@ private: F(in_building); F(construction); F(artifact); #undef F - auto& v = world->items.other[df::items_other_id::WEAPON]; + auto& v = world->items.all; for (auto i = v.begin(); i != v.end(); i++) { df::item* item = *i; + + if (item->flags.bits.dump) + labor_needed[df::unit_labor::HAUL_REFUSE]++; + if (item->flags.whole & bad_flags.whole) continue; From f8d6b83088c1e74d0ceef1c2582a0757191e00ce Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 4 Dec 2012 20:23:19 -0600 Subject: [PATCH 019/136] Add 'allow fishing' and 'allow hunting' config options. Protect against accidentially trying to set or unset the NONE labor or any other invalid labor value (which corrupts DF). Add traction benches. Change prioritization around quite a bit. --- plugins/autolabor.cpp | 162 +++++++++++++++++++++++++++++------------- 1 file changed, 114 insertions(+), 48 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index c231a4cf2..bad80a515 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -82,6 +82,8 @@ static PersistentDataItem config; enum ConfigFlags { CF_ENABLED = 1, + CF_ALLOW_FISHING = 2, + CF_ALLOW_HUNTING = 4, }; @@ -483,20 +485,26 @@ struct dwarf_info_t void set_labor(df::unit_labor labor) { - dwarf->status.labors[labor] = true; - if ((labor == df::unit_labor::MINE && !has_pick) || - (labor == df::unit_labor::CUTWOOD && !has_axe) || - (labor == df::unit_labor::HUNT && !has_crossbow)) - dwarf->military.pickup_flags.bits.update = 1; + if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) + { + dwarf->status.labors[labor] = true; + if ((labor == df::unit_labor::MINE && !has_pick) || + (labor == df::unit_labor::CUTWOOD && !has_axe) || + (labor == df::unit_labor::HUNT && !has_crossbow)) + dwarf->military.pickup_flags.bits.update = 1; + } } void clear_labor(df::unit_labor labor) { - dwarf->status.labors[labor] = false; - if ((labor == df::unit_labor::MINE && has_pick) || - (labor == df::unit_labor::CUTWOOD && has_axe) || - (labor == df::unit_labor::HUNT && has_crossbow)) - dwarf->military.pickup_flags.bits.update = 1; + if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) + { + dwarf->status.labors[labor] = false; + if ((labor == df::unit_labor::MINE && has_pick) || + (labor == df::unit_labor::CUTWOOD && has_axe) || + (labor == df::unit_labor::HUNT && has_crossbow)) + dwarf->military.pickup_flags.bits.update = 1; + } } }; @@ -752,6 +760,7 @@ private: case df::building_type::WindowGem: case df::building_type::Cage: case df::building_type::NestBox: + case df::building_type::TractionBench: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: return df::unit_labor::MECHANIC; @@ -1736,7 +1745,7 @@ private: FOR_ENUM_ITEMS (job_skill, skill) { - int skill_level = Units::getEffectiveSkill(dwarf->dwarf, skill); + int skill_level = Units::getNominalSkill(dwarf->dwarf, skill, false); high_skill = std::max(high_skill, skill_level); } @@ -1866,6 +1875,14 @@ public: if ((*v)->route_id != -1) labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; + // add fishing & hunting + + if (isOptionEnabled(CF_ALLOW_FISHING) && has_fishery) + labor_needed[df::unit_labor::FISH] ++; + + if (isOptionEnabled(CF_ALLOW_HUNTING) && has_butchers) + labor_needed[df::unit_labor::HUNT] ++; + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -1893,6 +1910,31 @@ public: if (print_debug) out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size()); + std::map to_assign; + + to_assign.clear(); + + int av = available_dwarfs.size(); + + while (!pq.empty() && av > 0) + { + df::unit_labor labor = pq.top().second; + int priority = pq.top().first; + to_assign[labor]++; + pq.pop(); + av--; + + if (print_debug) + out.print("Will assign: %s priority %d (%d)\n", ENUM_KEY_STR(unit_labor, labor).c_str(), priority, to_assign[labor]); + + if (--labor_needed[labor] > 0) + { + priority /= 2; + pq.push(make_pair(priority, labor)); + } + + } + int canary = (1 << df::unit_labor::HAUL_STONE) | (1 << df::unit_labor::HAUL_WOOD) | (1 << df::unit_labor::HAUL_BODY) | @@ -1902,63 +1944,65 @@ public: (1 << df::unit_labor::HAUL_FURNITURE) | (1 << df::unit_labor::HAUL_ANIMAL); - while (!available_dwarfs.empty() && !pq.empty()) + while (!available_dwarfs.empty()) { - df::unit_labor labor = pq.top().second; - int priority = pq.top().first; - df::job_skill skill = labor_to_skill[labor]; - - if (print_debug) - out.print("labor %s skill %s priority %d\n", ENUM_KEY_STR(unit_labor, labor).c_str(), ENUM_KEY_STR(job_skill, skill).c_str(), priority); - std::list::iterator bestdwarf = available_dwarfs.begin(); int best_score = -10000; + df::unit_labor best_labor = df::unit_labor::NONE; - for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) + for (auto j = to_assign.begin(); j != to_assign.end(); j++) { - dwarf_info_t* d = (*k); - int skill_level = 0; - if (skill != df::job_skill::NONE) - { - int skill_level = Units::getEffectiveSkill(d->dwarf, skill); - } + if (j->second <= 0) + continue; + + df::unit_labor labor = j->first; + df::job_skill skill = labor_to_skill[labor]; - int score = skill_level * 100 - (d->high_skill - skill_level) * 200; - if (d->dwarf->status.labors[labor]) - score += 500; - if ((labor == df::unit_labor::MINE && d->has_pick) || - (labor == df::unit_labor::CUTWOOD && d->has_axe) || - (labor == df::unit_labor::HUNT && d->has_crossbow)) - score += 500; - if (score > best_score) + for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) { - bestdwarf = k; - best_score = score; + dwarf_info_t* d = (*k); + int skill_level = 0; + if (skill != df::job_skill::NONE) + { + skill_level = Units::getEffectiveSkill(d->dwarf, skill); + } + int score = skill_level * 100 - (d->high_skill - skill_level) * 500; + if (d->dwarf->status.labors[labor]) + score += 500; + if ((labor == df::unit_labor::MINE && d->has_pick) || + (labor == df::unit_labor::CUTWOOD && d->has_axe) || + (labor == df::unit_labor::HUNT && d->has_crossbow)) + score += 500; + + if (score > best_score) + { + bestdwarf = k; + best_score = score; + best_labor = labor; + } } } if (print_debug) - out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), best_score); + out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, best_labor).c_str(), best_score); + FOR_ENUM_ITEMS(unit_labor, l) { - if (l == labor) + if (l == df::unit_labor::NONE) + continue; + + if (l == best_labor) (*bestdwarf)->set_labor(l); else (*bestdwarf)->clear_labor(l); } - if (labor >= df::unit_labor::HAUL_STONE && labor <= df::unit_labor::HAUL_ANIMAL) - canary &= ~(1 << labor); - labor_infos[labor].active_dwarfs++; - + if (best_labor >= df::unit_labor::HAUL_STONE && best_labor <= df::unit_labor::HAUL_ANIMAL) + canary &= ~(1 << best_labor); + labor_infos[best_labor].active_dwarfs++; + to_assign[best_labor]--; available_dwarfs.erase(bestdwarf); - pq.pop(); - if (--labor_needed[labor] > 0) - { - priority /= 2; - pq.push(make_pair(priority, labor)); - } } if (canary != 0) @@ -2125,6 +2169,28 @@ command_result autolabor (color_ostream &out, std::vector & parame print_labor(labor, out); return CR_OK; } + else if (parameters.size() == 1 && (parameters[0] == "allow-fishing" || parameters[0] == "forbid-fishing")) + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + setOptionEnabled(CF_ALLOW_FISHING, (parameters[0] == "allow-fishing")); + return CR_OK; + } + else if (parameters.size() == 1 && (parameters[0] == "allow-hunting" || parameters[0] == "forbid-hunting")) + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + setOptionEnabled(CF_ALLOW_HUNTING, (parameters[0] == "allow-hunting")); + return CR_OK; + } else if (parameters.size() == 1 && parameters[0] == "reset-all") { if (!enable_autolabor) From 0df60a0b4f48a9680bb53bcc7cae0a619f422efe Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 6 Dec 2012 01:38:43 -0600 Subject: [PATCH 020/136] Autolabor: slabs, animal trainers --- plugins/autolabor.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index bad80a515..56dc61111 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -57,6 +57,9 @@ #include #include #include +#include +#include +#include #include @@ -761,6 +764,7 @@ private: case df::building_type::Cage: case df::building_type::NestBox: case df::building_type::TractionBench: + case df::building_type::Slab: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: return df::unit_labor::MECHANIC; @@ -1162,7 +1166,7 @@ public: job_to_labor_table[df::job_type::ApplyCast] = jlf_const(df::unit_labor::BONE_SETTING); job_to_labor_table[df::job_type::CustomReaction] = new jlfunc_custom(); job_to_labor_table[df::job_type::ConstructSlab] = jlf_make_furniture; - job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::STONE_CRAFT); + job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::DETAIL); job_to_labor_table[df::job_type::ShearCreature] = jlf_const(df::unit_labor::SHEARER); job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_no_labor; @@ -1873,7 +1877,7 @@ public: for (auto v = world->vehicles.all.begin(); v != world->vehicles.all.end(); v++) if ((*v)->route_id != -1) - labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; + labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; // add fishing & hunting @@ -1883,6 +1887,16 @@ public: if (isOptionEnabled(CF_ALLOW_HUNTING) && has_butchers) labor_needed[df::unit_labor::HUNT] ++; + /* add animal trainers */ + for (auto a = df::global::ui->equipment.training_assignments.begin(); + a != df::global::ui->equipment.training_assignments.end(); + a++) + { + labor_needed[df::unit_labor::ANIMALTRAIN]++; + // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. + } + + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) From fa9b71adc557fe426d6efca3806256d3f8cc5e01 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 6 Dec 2012 09:39:14 -0600 Subject: [PATCH 021/136] autolabor: add archery targets, improve JobLaborMapper's destructor --- plugins/autolabor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 56dc61111..ddcd7c1ca 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -739,6 +739,7 @@ private: case df::building_type::TradeDepot: case df::building_type::Construction: case df::building_type::Bridge: + case df::building_type::ArcheryTarget: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) @@ -936,6 +937,9 @@ private: public: ~JobLaborMapper() { + for (auto i = jlf_cache.begin(); i != jlf_cache.end(); i++) + delete i->second; + delete jlf_hauling; delete jlf_make_furniture; delete jlf_make_object; From 42670f0233daff17be5151482c9895a3936cce9c Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 7 Dec 2012 15:41:39 -0600 Subject: [PATCH 022/136] Autolabor: only care about skills that are used for labors, when determining a dwarf's highest skill. --- plugins/autolabor.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index ddcd7c1ca..1bf74f558 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1751,10 +1751,14 @@ private: int high_skill = 0; - FOR_ENUM_ITEMS (job_skill, skill) + FOR_ENUM_ITEMS (unit_labor, labor) { - int skill_level = Units::getNominalSkill(dwarf->dwarf, skill, false); - high_skill = std::max(high_skill, skill_level); + df::job_skill skill = labor_to_skill[labor]; + if (skill != df::job_skill::NONE) + { + int skill_level = Units::getNominalSkill(dwarf->dwarf, skill, false); + high_skill = std::max(high_skill, skill_level); + } } dwarf->high_skill = high_skill; From e7d3fbe97ba37e32513fcf1eaaf7e821e0e4bb5c Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 02:42:22 -0600 Subject: [PATCH 023/136] Autolabor: track labors actually being used (to avoid "gone fishing" bug); fix several wrong labor map entries; add several special cases for hauling (still not all there yet); add debug warning if job deduction appears wrong; flail about mightily trying to resolve heap corruption on unload --- plugins/autolabor.cpp | 148 ++++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 50 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 1bf74f558..d3e91a31d 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include @@ -482,6 +483,8 @@ struct dwarf_info_t int high_skill; + df::unit_labor using_labor; + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER), high_skill(0) { } @@ -521,9 +524,9 @@ struct dwarf_info_t static df::unit_labor hauling_labor_map[] = { df::unit_labor::HAUL_ITEM, /* BAR */ - df::unit_labor::HAUL_ITEM, /* SMALLGEM */ + df::unit_labor::HAUL_STONE, /* SMALLGEM */ df::unit_labor::HAUL_ITEM, /* BLOCKS */ - df::unit_labor::HAUL_ITEM, /* ROUGH */ + df::unit_labor::HAUL_STONE, /* ROUGH */ df::unit_labor::HAUL_STONE, /* BOULDER */ df::unit_labor::HAUL_WOOD, /* WOOD */ df::unit_labor::HAUL_FURNITURE, /* DOOR */ @@ -543,7 +546,7 @@ static df::unit_labor hauling_labor_map[] = df::unit_labor::HAUL_FURNITURE, /* TABLE */ df::unit_labor::HAUL_FURNITURE, /* COFFIN */ df::unit_labor::HAUL_FURNITURE, /* STATUE */ - df::unit_labor::HAUL_BODY, /* CORPSE */ + df::unit_labor::HAUL_REFUSE, /* CORPSE */ df::unit_labor::HAUL_ITEM, /* WEAPON */ df::unit_labor::HAUL_ITEM, /* ARMOR */ df::unit_labor::HAUL_ITEM, /* SHOES */ @@ -565,7 +568,7 @@ static df::unit_labor hauling_labor_map[] = df::unit_labor::HAUL_ITEM, /* BRACELET */ df::unit_labor::HAUL_ITEM, /* GEM */ df::unit_labor::HAUL_FURNITURE, /* ANVIL */ - df::unit_labor::HAUL_BODY, /* CORPSEPIECE */ + df::unit_labor::HAUL_REFUSE, /* CORPSEPIECE */ df::unit_labor::HAUL_REFUSE, /* REMAINS */ df::unit_labor::HAUL_FOOD, /* MEAT */ df::unit_labor::HAUL_FOOD, /* FISH */ @@ -707,8 +710,31 @@ private: public: df::unit_labor get_labor(df::job* j) { - df::item* item = j->items[0]->item; - return hauling_labor_map[item->getType()]; + if (j->job_type == df::job_type::StoreItemInStockpile && j->item_subtype != -1) + return (df::unit_labor) j->item_subtype; + + df::item* item; +// if (j->job_type == df::job_type::StoreItemInBarrel) +// item = j->items[1]->item; +// else + item = j->items[0]->item; + + if (item->flags.bits.container && item->getType() != df::item_type::BIN) + { + for (auto a = item->general_refs.begin(); a != item->general_refs.end(); a++) + { + if ((*a)->getType() == df::general_ref_type::CONTAINS_ITEM) + { + int item_id = ((df::general_ref_contains_itemst *) (*a))->item_id; + item = binsearch_in_vector(world->items.all, item_id); + break; + } + } + } + df::unit_labor l = hauling_labor_map[item->getType()]; + if (l == df::unit_labor::HAUL_REFUSE && item->flags.bits.dead_dwarf) + l = df::unit_labor::HAUL_BODY; + return l; } jlfunc_hauling() {}; }; @@ -931,29 +957,45 @@ private: return jlf; } private: - jlfunc *jlf_hauling, *jlf_make_furniture, *jlf_make_object, *jlf_make_armor, *jlf_make_weapon; - jlfunc *job_to_labor_table[ENUM_LAST_ITEM(job_type)+1]; + std::map job_to_labor_table; public: ~JobLaborMapper() { + std::set log; + for (auto i = jlf_cache.begin(); i != jlf_cache.end(); i++) - delete i->second; + { + if (!log.count(i->second)) + { + log.insert(i->second); + delete i->second; + } + i->second = 0; + } + + FOR_ENUM_ITEMS (job_type, j) + { + if (j < 0) + continue; - delete jlf_hauling; - delete jlf_make_furniture; - delete jlf_make_object; - delete jlf_make_armor; - delete jlf_make_weapon; + jlfunc* p = job_to_labor_table[j]; + if (!log.count(p)) + { + log.insert(p); + delete p; + } + job_to_labor_table[j] = 0; + } } JobLaborMapper() { - jlf_hauling = new jlfunc_hauling(); - jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); - jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); - jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); - jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); + jlfunc* jlf_hauling = new jlfunc_hauling(); + jlfunc* jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); + jlfunc* jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); + jlfunc* jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); + jlfunc* jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); @@ -979,7 +1021,7 @@ public: job_to_labor_table[df::job_type::FillWaterskin] = jlf_no_labor; job_to_labor_table[df::job_type::FillWaterskin2] = jlf_no_labor; job_to_labor_table[df::job_type::Sleep] = jlf_no_labor; - job_to_labor_table[df::job_type::CollectSand] = jlf_const(df::unit_labor::GLASSMAKER); + job_to_labor_table[df::job_type::CollectSand] = jlf_const(df::unit_labor::HAUL_ITEM); job_to_labor_table[df::job_type::Fish] = jlf_const(df::unit_labor::FISH); job_to_labor_table[df::job_type::Hunt] = jlf_const(df::unit_labor::HUNT); job_to_labor_table[df::job_type::HuntVermin] = jlf_no_labor; @@ -1002,7 +1044,7 @@ public: job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_const(df::unit_labor::HAUL_ITEM); job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; @@ -1205,16 +1247,9 @@ public: return df::unit_labor::NONE; } - df::job_skill skill; - df::unit_labor labor; - skill = ENUM_ATTR(job_type, skill, j->job_type); - if (skill != df::job_skill::NONE) - labor = ENUM_ATTR(job_skill, labor, skill); - else - labor = ENUM_ATTR(job_type, labor, j->job_type); - if (labor == df::unit_labor::NONE) - labor = job_to_labor_table[j->job_type]->get_labor(j); + df::unit_labor labor; + labor = job_to_labor_table[j->job_type]->get_labor(j); return labor; } @@ -1222,7 +1257,7 @@ public: /* End of labor deducer */ -static JobLaborMapper* labor_mapper; +static JobLaborMapper* labor_mapper = 0; static bool isOptionEnabled(unsigned flag) { @@ -1243,11 +1278,6 @@ static void setOptionEnabled(ConfigFlags flag, bool on) static void cleanup_state() { labor_infos.clear(); - if (labor_mapper) - { - delete labor_mapper; - labor_mapper = 0; - } } static void reset_labor(df::unit_labor labor) @@ -1298,11 +1328,6 @@ static void init_state() reset_labor((df::unit_labor) i); } - generate_labor_to_skill_map(); - - if (!labor_mapper) - labor_mapper = new JobLaborMapper(); - } static df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; @@ -1382,6 +1407,10 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector ::iterator i = dwarf_info.begin(); - i != dwarf_info.end(); i++) - delete (*i); + for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) + { + delete (*d); + } } dwarf_info_t* add_dwarf(df::unit* u) @@ -1704,7 +1737,7 @@ private: } else { - int job = dwarf->dwarf->job.current_job->job_type; + df::job_type job = dwarf->dwarf->job.current_job->job_type; if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) state = dwarf_states[job]; else @@ -1712,6 +1745,20 @@ private: out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); state = OTHER; } + if (state == BUSY) + { + df::unit_labor labor = labor_mapper->find_job_labor(dwarf->dwarf->job.current_job); + if (labor != df::unit_labor::NONE) + { + labor_needed[labor]--; + if (!dwarf->dwarf->status.labors[labor]) + { + out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", + dwarf->dwarf->name.first_name.c_str(), dwarf->dwarf->id, + ENUM_KEY_STR(job_type, job).c_str(), job, ENUM_KEY_STR(unit_labor, labor).c_str(), labor); + } + } + } } dwarf->state = state; @@ -1753,6 +1800,9 @@ private: FOR_ENUM_ITEMS (unit_labor, labor) { + if (labor == df::unit_labor::NONE) + continue; + df::job_skill skill = labor_to_skill[labor]; if (skill != df::job_skill::NONE) { @@ -1804,7 +1854,7 @@ private: } } - if ((state == IDLE || state == BUSY) && !dwarf->clear_all) + if ((state == IDLE) && !dwarf->clear_all) available_dwarfs.push_back(dwarf); } @@ -1815,6 +1865,8 @@ private: public: void process() { + dwarf_info.clear(); + dig_count = tree_count = plant_count = detail_count = pick_count = axe_count = 0; cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = cnt_setting = cnt_traction = cnt_crutch = 0; @@ -1904,7 +1956,6 @@ public: // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. } - if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -2079,12 +2130,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) step_count = 0; debug_stream = &out; - AutoLaborManager alm(out); - alm.process(); - return CR_OK; } From 6ae82187d257c33252b9f3c269c2214d52ccd531 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 03:51:07 -0600 Subject: [PATCH 024/136] Autolabor: more tweaks to hauling labor decoding, fix heap corruption due to array underflow --- plugins/autolabor.cpp | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index d3e91a31d..e7dc5692b 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -710,16 +710,20 @@ private: public: df::unit_labor get_labor(df::job* j) { + df::item* item = 0; if (j->job_type == df::job_type::StoreItemInStockpile && j->item_subtype != -1) return (df::unit_labor) j->item_subtype; - df::item* item; -// if (j->job_type == df::job_type::StoreItemInBarrel) -// item = j->items[1]->item; -// else - item = j->items[0]->item; + for (auto i = j->items.begin(); i != j->items.end(); i++) + { + if ((*i)->role == 7) + { + item = (*i)->item; + break; + } + } - if (item->flags.bits.container && item->getType() != df::item_type::BIN) + if (item && item->flags.bits.container) { for (auto a = item->general_refs.begin(); a != item->general_refs.end(); a++) { @@ -731,8 +735,9 @@ private: } } } - df::unit_labor l = hauling_labor_map[item->getType()]; - if (l == df::unit_labor::HAUL_REFUSE && item->flags.bits.dead_dwarf) + + df::unit_labor l = item ? hauling_labor_map[item->getType()] : df::unit_labor::HAUL_ITEM; + if (item && l == df::unit_labor::HAUL_REFUSE && item->flags.bits.dead_dwarf) l = df::unit_labor::HAUL_BODY; return l; } @@ -1751,7 +1756,7 @@ private: if (labor != df::unit_labor::NONE) { labor_needed[labor]--; - if (!dwarf->dwarf->status.labors[labor]) + if (!dwarf->dwarf->status.labors[labor] && print_debug) { out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", dwarf->dwarf->name.first_name.c_str(), dwarf->dwarf->id, @@ -1986,7 +1991,7 @@ public: std::map to_assign; to_assign.clear(); - + int av = available_dwarfs.size(); while (!pq.empty() && av > 0) @@ -2058,7 +2063,7 @@ public: } if (print_debug) - out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, best_labor).c_str(), best_score); + out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, best_labor).c_str(), best_score); FOR_ENUM_ITEMS(unit_labor, l) { @@ -2073,8 +2078,13 @@ public: if (best_labor >= df::unit_labor::HAUL_STONE && best_labor <= df::unit_labor::HAUL_ANIMAL) canary &= ~(1 << best_labor); - labor_infos[best_labor].active_dwarfs++; - to_assign[best_labor]--; + + if (best_labor != df::unit_labor::NONE) + { + labor_infos[best_labor].active_dwarfs++; + to_assign[best_labor]--; + } + available_dwarfs.erase(bestdwarf); } From 412a004751e1e2c1fb36d54fdf02821dedf2c282 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 10:55:44 -0600 Subject: [PATCH 025/136] Autolabor: identify labors that may involve going outside and apply an assignment penalty for such labors to dwarfs who have minor children (in order to keep the kids inside) --- plugins/autolabor.cpp | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index e7dc5692b..541783354 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -483,9 +483,12 @@ struct dwarf_info_t int high_skill; + bool has_children; + df::unit_labor using_labor; - dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER), high_skill(0) + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), has_axe(false), has_pick(false), has_crossbow(false), + state(OTHER), high_skill(0), has_children(false) { } @@ -1481,6 +1484,7 @@ private: int need_food_water; std::map labor_needed; + std::map labor_outside; std::vector dwarf_info; std::list available_dwarfs; @@ -1648,6 +1652,13 @@ private: if (worker != -1) labor_infos[labor].mark_assigned(); + + if (j->pos.isValid()) + { + df::tile_designation* d = Maps::getTileDesignation(j->pos); + if (d->bits.outside) + labor_outside[labor] = true; + } } } @@ -1707,6 +1718,21 @@ private: } } + // check to see if dwarf has minor children + + for (auto u2 = world->units.active.begin(); u2 != world->units.active.end(); ++u2) + { + if ((*u2)->relations.mother_id == dwarf->dwarf->id && + !(*u2)->flags1.bits.dead && + ((*u2)->profession == df::profession::CHILD || (*u2)->profession == df::profession::BABY)) + { + dwarf->has_children = true; + if (print_debug) + out.print("Dwarf %s has minor children\n", dwarf->dwarf->name.first_name.c_str()); + break; + } + } + // Find the activity state for each dwarf bool is_on_break = false; @@ -1965,7 +1991,8 @@ public: { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - out.print ("labor_needed [%s] = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second); + out.print ("labor_needed [%s] = %d, outside = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, + labor_outside[i->first]); } } @@ -2026,7 +2053,7 @@ public: { std::list::iterator bestdwarf = available_dwarfs.begin(); - int best_score = -10000; + int best_score = INT_MIN; df::unit_labor best_labor = df::unit_labor::NONE; for (auto j = to_assign.begin(); j != to_assign.end(); j++) @@ -2052,6 +2079,8 @@ public: (labor == df::unit_labor::CUTWOOD && d->has_axe) || (labor == df::unit_labor::HUNT && d->has_crossbow)) score += 500; + if (d->has_children && labor_outside[labor]) + score -= 5000; if (score > best_score) { From 72921fbfd513723f3a52fb06615083cbdc9f127c Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 8 Dec 2012 12:50:33 -0500 Subject: [PATCH 026/136] Made workNow only check jobs when the game becomes paused instead of constantly when paused. Also made it enable/disable on command. --- plugins/CMakeLists.txt | 1 + plugins/workNow.cpp | 44 +++++++++++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..75da6f478 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -127,6 +127,7 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(workNow workNow.cpp) endif() diff --git a/plugins/workNow.cpp b/plugins/workNow.cpp index acbb5bc30..906df33dc 100644 --- a/plugins/workNow.cpp +++ b/plugins/workNow.cpp @@ -8,34 +8,60 @@ #include using namespace std; - using namespace DFHack; DFHACK_PLUGIN("workNow"); static bool active = false; +DFhackCExport command_result workNow(color_ostream& out, vector& parameters); + +DFhackCExport command_result plugin_init(color_ostream& out, std::vector &commands) { + commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs every time you pause", workNow, false, "When workNow is active, every time the game pauses, DF will make dwarves perform any appropriate available jobs. This includes when you one step through the game using the pause menu.\n" + "workNow 1\n" + " activate workNow\n" + "workNow 0\n" + " deactivate workNow\n")); + + return CR_OK; +} + DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { + active = false; return CR_OK; } -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { - if ( !DFHack::Core::getInstance().getWorld()->ReadPauseState() ) +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) { + if ( !active ) + return CR_OK; + if ( e == DFHack::SC_WORLD_UNLOADED ) { + active = false; + return CR_OK; + } + if ( e != DFHack::SC_PAUSED ) return CR_OK; + *df::global::process_jobs = true; + return CR_OK; } -DFhackCExport command_result workNow(color_ostream& out, vector& parameters); -DFhackCExport command_result plugin_init(color_ostream& out, std::vector &commands) { - commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs every time you pause", workNow, false, "Full help.")); - - return CR_OK; -} DFhackCExport command_result workNow(color_ostream& out, vector& parameters) { + if ( parameters.size() == 0 ) { + out.print("workNow status = %s\n", active ? "active" : "inactive"); + return CR_OK; + } + if ( parameters.size() > 1 ) { + return CR_WRONG_USAGE; + } + int32_t a = atoi(parameters[0].c_str()); + if (a < 0 || a > 1) + return CR_WRONG_USAGE; + + active = (bool)a; return CR_OK; } From 468412b9fc9a39736f90da55be48b4358078b2b0 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 21:14:23 -0600 Subject: [PATCH 027/136] Autolabor: fix unitialized variable bug causing broker to be inappropriately excluded from work --- plugins/autolabor.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 541783354..5f66d3b69 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1762,6 +1762,8 @@ private: { state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors dwarf->clear_all = true; + if (print_debug) + out.print ("Dwarf %s is disabled, will not be assigned labors\n", dwarf->dwarf->name.first_name.c_str()); } else state = IDLE; @@ -1795,7 +1797,7 @@ private: dwarf->state = state; if (print_debug) - out.print("Dwarf \"%s\": state %s\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state]); + out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); // determine if dwarf has medical needs if (dwarf->dwarf->health) @@ -1884,8 +1886,7 @@ private: dwarf->clear_labor(labor); } } - - if ((state == IDLE) && !dwarf->clear_all) + else if (state == IDLE) available_dwarfs.push_back(dwarf); } @@ -1903,6 +1904,8 @@ public: cnt_setting = cnt_traction = cnt_crutch = 0; need_food_water = 0; + trader_requested = false; + FOR_ENUM_ITEMS(unit_labor, l) { if (l == df::unit_labor::NONE) From 2018ac1d1751563f208399a0e898b535393a1228 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 21:25:16 -0600 Subject: [PATCH 028/136] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 22b01b80a..506ab1e68 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 +Subproject commit 506ab1e68d1522e2f282f134176b7da774f6a73c From e85f4eb880674a51fdf04ce7e5784a5ef70dfbe0 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 9 Dec 2012 21:18:29 -0500 Subject: [PATCH 029/136] First draft of autoSyndrome: a tool for replacing boiling rock syndromes with something more reliable. Uses non-df-recognized tags in material definition raws. --- plugins/CMakeLists.txt | 1 + plugins/autoSyndrome.cpp | 360 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100644 plugins/autoSyndrome.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8aeeee8c3..ed4898112 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -130,6 +130,7 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp) endif() diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp new file mode 100644 index 000000000..28a785adc --- /dev/null +++ b/plugins/autoSyndrome.cpp @@ -0,0 +1,360 @@ +#include "PluginManager.h" +#include "Export.h" +#include "DataDefs.h" +#include "Core.h" +#include "df/job.h" +#include "df/global_objects.h" +#include "df/ui.h" +#include "df/job_type.h" +#include "df/reaction.h" +#include "df/reaction_product.h" +#include "df/reaction_product_type.h" +#include "df/reaction_product_itemst.h" +#include "df/syndrome.h" +#include "df/unit_syndrome.h" +#include "df/unit.h" +#include "df/general_ref.h" +#include "df/general_ref_type.h" +#include "df/general_ref_unit_workerst.h" +#include "modules/Maps.h" +#include "modules/Job.h" + +#include +#include +#include +#include + +using namespace std; +using namespace DFHack; + +/* +Example usage: + +////////////////////////////////////////////// +//in file interaction_duck.txt +interaction_duck + +[OBJECT:INTERACTION] + +[INTERACTION:DUMMY_DUCK_INTERACTION] + [I_SOURCE:CREATURE_ACTION] + [I_TARGET:A:CREATURE] + [IT_LOCATION:CONTEXT_CREATURE] + [IT_MANUAL_INPUT:target] + [IT_IMMUNE_CREATURE:BIRD_DUCK:MALE] + [I_EFFECT:ADD_SYNDROME] + [IE_TARGET:A] + [IE_IMMEDIATE] + [SYNDROME] + [SYN_NAME:chronicDuckSyndrome] + [CE_BODY_TRANSFORMATION:PROB:100:START:0] + [CE:CREATURE:BIRD_DUCK:MALE] +////////////////////////////////////////////// +//In file inorganic_duck.txt +inorganic_stone_duck + +[OBJECT:INORGANIC] + +[INORGANIC:DUCK_ROCK] +[USE_MATERIAL_TEMPLATE:STONE_TEMPLATE] +[STATE_NAME_ADJ:ALL_SOLID:drakium][DISPLAY_COLOR:0:7:0][TILE:'.'] +[IS_STONE] +[SOLID_DENSITY:1][MELTING_POINT:25000] +[CAUSE_SYNDROME:chronicDuckSyndrome] +[BOILING_POINT:50000] +/////////////////////////////////////////////// +//In file building_duck.txt +building_duck + +[OBJECT:BUILDING] + +[BUILDING_WORKSHOP:DUCK_WORKSHOP] + [NAME:Duck Workshop] + [NAME_COLOR:7:0:1] + [DIM:1:1] + [WORK_LOCATION:1:1] + [BLOCK:1:0:0:0] + [TILE:0:1:236] + [COLOR:0:1:0:0:1] + [TILE:1:1:' '] + [COLOR:1:1:0:0:0] + [TILE:2:1:8] + [COLOR:2:1:0:0:1] + [TILE:3:1:8] + [COLOR:3:2:0:4:1] + [BUILD_ITEM:1:NONE:NONE:NONE:NONE] + [BUILDMAT] + [WORTHLESS_STONE_ONLY] + [CAN_USE_ARTIFACT] +/////////////////////////////////////////////// +//In file reaction_duck.txt +reaction_duck + +[OBJECT:REACTION] + +[REACTION:DUCKIFICATION] +[NAME:become a duck] +[BUILDING:DUCK_WORKSHOP:NONE] +[PRODUCT:100:100:STONE:NO_SUBTYPE:STONE:DUCK_ROCK] +////////////////////////////////////////////// +//Add the following lines to your entity in entity_default.txt (or wherever it is) + [PERMITTED_BUILDING:DUCK_WORKSHOP] + [PERMITTED_REACTION:DUCKIFICATION] +////////////////////////////////////////////// + +Next, start a new fort in a new world, build a duck workshop, then have someone become a duck. +*/ + +const int32_t ticksPerYear = 403200; +int32_t lastRun = 0; +unordered_map prevJobs; +unordered_map jobWorkers; +bool enabled = true; + +DFHACK_PLUGIN("autoSyndrome"); + +command_result autoSyndrome(color_ostream& out, vector& parameters); +int32_t processJob(color_ostream& out, int32_t id); +int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome); + +DFhackCExport command_result plugin_shutdown(color_ostream& out) { + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream& out) { + if ( !enabled ) + return CR_OK; + if(DFHack::Maps::IsValid() == false) { + return CR_OK; + } + + //don't run more than once per tick + int32_t time = (*df::global::cur_year)*ticksPerYear + (*df::global::cur_year_tick); + if ( time <= lastRun ) + return CR_OK; + lastRun = time; + + //keep track of all queued jobs. When one completes (and is not cancelled), check if it's a boiling rock job, and if so, give the worker the appropriate syndrome + unordered_map jobs; + df::job_list_link* link = &df::global::world->job_list; + for( ; link != NULL; link = link->next ) { + df::job* item = link->item; + if ( item == NULL ) + continue; + //-1 usually means it hasn't been assigned yet. + if ( item->completion_timer < 0 ) + continue; + //if the completion timer is more than one, then the job will never disappear next tick unless it's cancelled + if ( item->completion_timer > 1 ) + continue; + + //only consider jobs that have been started + int32_t workerId = -1; + for ( size_t a = 0; a < item->references.size(); a++ ) { + if ( item->references[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) + continue; + if ( workerId != -1 ) { + out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__); + } + workerId = ((df::general_ref_unit_workerst*)item->references[a])->unit_id; + } + if ( workerId == -1 ) + continue; + + jobs[item->id] = item; + jobWorkers[item->id] = workerId; + } + + //if it's not on the job list anymore, and its completion timer was 0, then it probably finished and was not cancelled, so process the job. + for ( unordered_map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + int32_t id = (*i).first; + df::job* completion = (*i).second; + if ( jobs.find(id) != jobs.end() ) + continue; + if ( completion->completion_timer > 0 ) + continue; + if ( processJob(out, id) < 0 ) { + //enabled = false; + return CR_FAILURE; + } + } + + //delete obselete job copies + for ( unordered_map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + int32_t id = (*i).first; + df::job* oldJob = (*i).second; + DFHack::Job::deleteJobStruct(oldJob); + if ( jobs.find(id) == jobs.end() ) + jobWorkers.erase(id); + } + prevJobs.clear(); + + //make copies of the jobs we looked at this tick in case they disappear next frame. + for ( unordered_map::iterator i = jobs.begin(); i != jobs.end(); i++ ) { + int32_t id = (*i).first; + df::job* oldJob = (*i).second; + df::job* jobCopy = DFHack::Job::cloneJobStruct(oldJob); + prevJobs[id] = jobCopy; + } + + return CR_OK; +} + +/*DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event e) { + return CR_OK; +}*/ + +DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { + commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false, + "autoSyndrome:\n" + " autoSyndrome 0 //disable\n" + " autoSyndrome 1 //enable\n" + " autoSyndrome disable //disable\n" + " autoSyndrome enable //enable\n" + "\n" + "autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n" + "\n" + "Requirements:\n" + " 1) The job must be a custom reaction.\n" + " 2) The job must produce a stone of some inorganic material.\n" + " 3) The material of one of the stones produced must have a token in its raw file of the form [CAUSE_SYNDROME:syndrome_name].\n" + "\n" + "If a syndrome with the tag [SYN_NAME:syndrome_name] exists, then the unit that completed the job will become afflicted with that syndrome as soon as the job is completed.\n")); + + + return CR_OK; +} + +command_result autoSyndrome(color_ostream& out, vector& parameters) { + if ( parameters.size() > 1 ) + return CR_WRONG_USAGE; + + if ( parameters.size() == 1 ) { + if ( parameters[0] == "enable" ) { + enabled = true; + } else if ( parameters[0] == "disable" ) { + enabled = false; + } else { + int32_t a = atoi(parameters[0].c_str()); + if ( a < 0 || a > 1 ) + return CR_WRONG_USAGE; + + enabled = (bool)a; + } + } + + out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled"); + return CR_OK; +} + +int32_t processJob(color_ostream& out, int32_t jobId) { + df::job* job = prevJobs[jobId]; + if ( job == NULL ) { + out.print("Error %s line %d: couldn't find job %d.\n", __FILE__, __LINE__, jobId); + return -1; + } + + if ( job->job_type != df::job_type::CustomReaction ) + return 0; + + //find the custom reaction raws and see if we have any special tags there + //out.print("job: \"%s\"\n", job->reaction_name.c_str()); + + df::reaction* reaction = NULL; + for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) { + df::reaction* candidate = df::global::world->raws.reactions[a]; + if ( candidate->code != job->reaction_name ) + continue; + reaction = candidate; + break; + } + if ( reaction == NULL ) { + out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() ); + return -1; + } + + //find all of the products it makes. Look for a stone with a material with special tags. + bool foundIt = false; + for ( size_t a = 0; a < reaction->products.size(); a++ ) { + df::reaction_product_type type = reaction->products[a]->getType(); + //out.print("type = %d\n", (int32_t)type); + if ( type != df::enums::reaction_product_type::item ) + continue; + df::reaction_product_itemst* bob = (df::reaction_product_itemst*)reaction->products[a]; + //out.print("item_type = %d\n", (int32_t)bob->item_type); + if ( bob->item_type != df::enums::item_type::BOULDER ) + continue; + //for now don't worry about subtype + + df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index]; + const char* helper = "CAUSE_SYNDROME:"; + for ( size_t b = 0; b < inorganic->str.size(); b++ ) { + //out.print("inorganic str = \"%s\"\n", inorganic->str[b]->c_str()); + size_t c = inorganic->str[b]->find(helper); + if ( c == string::npos ) + continue; + string tail = inorganic->str[b]->substr(c + strlen(helper), inorganic->str[b]->length() - strlen(helper) - 2); + //out.print("tail = %s\n", tail.c_str()); + + //find the syndrome with this name, and give apply it to the dwarf working on the job + //first find out who completed the job + if ( jobWorkers.find(jobId) == jobWorkers.end() ) { + out.print("%s, line %d: could not find job worker for jobs %d.\n", __FILE__, __LINE__, jobId); + return -1; + } + int32_t workerId = jobWorkers[jobId]; + + //find the syndrome + df::syndrome* syndrome = NULL; + for ( size_t d = 0; d < df::global::world->raws.syndromes.all.size(); d++ ) { + df::syndrome* candidate = df::global::world->raws.syndromes.all[d]; + if ( candidate->syn_name != tail ) + continue; + syndrome = candidate; + break; + } + if ( syndrome == NULL ) + return 0; + + if ( giveSyndrome(out, workerId, syndrome) < 0 ) + return -1; + //out.print("Gave syndrome.\n"); + } + } + if ( !foundIt ) + return 0; + + return -2; +} + +/* + * Heavily based on https://gist.github.com/4061959/ + **/ +int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome) { + int32_t index = df::unit::binsearch_index(df::global::world->units.all, workerId); + if ( index < 0 ) { + out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); + return -1; + } + df::unit* unit = df::global::world->units.all[index]; + + df::unit_syndrome* unitSyndrome = new df::unit_syndrome(); + unitSyndrome->type = syndrome->id; + unitSyndrome->year = 0; + unitSyndrome->year_time = 0; + unitSyndrome->ticks = 1; + unitSyndrome->unk1 = 1; + unitSyndrome->flags = 0; //typecast + + for ( size_t a = 0; a < syndrome->ce.size(); a++ ) { + df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms(); + symptom->unk1 = 0; + symptom->unk2 = 0; + symptom->ticks = 1; + symptom->flags = 2; //TODO: ??? + unitSyndrome->symptoms.push_back(symptom); + } + unit->syndromes.active.push_back(unitSyndrome); + return 0; +} + From a914f8e8e0ed0e2ae24bb839277d9be0bd13de84 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 10 Dec 2012 08:34:11 -0600 Subject: [PATCH 030/136] Autolabor: busy dwarfs may be reassigned now, but with a strong preference for their current job; armed dwarfs are given preference for outside jobs; include experience gained toward next level in preference weighting --- plugins/autolabor.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 5f66d3b69..da04de508 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -484,11 +484,12 @@ struct dwarf_info_t int high_skill; bool has_children; + bool armed; df::unit_labor using_labor; dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), has_axe(false), has_pick(false), has_crossbow(false), - state(OTHER), high_skill(0), has_children(false) + state(OTHER), high_skill(0), has_children(false), armed(false) { } @@ -1783,7 +1784,7 @@ private: df::unit_labor labor = labor_mapper->find_job_labor(dwarf->dwarf->job.current_job); if (labor != df::unit_labor::NONE) { - labor_needed[labor]--; + dwarf->using_labor = labor; if (!dwarf->dwarf->status.labors[labor] && print_debug) { out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", @@ -1852,6 +1853,7 @@ private: df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) { + dwarf->armed = true; df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; @@ -1886,7 +1888,7 @@ private: dwarf->clear_labor(labor); } } - else if (state == IDLE) + else if (state == IDLE || state == BUSY) available_dwarfs.push_back(dwarf); } @@ -2071,11 +2073,13 @@ public: { dwarf_info_t* d = (*k); int skill_level = 0; + int xp = 0; if (skill != df::job_skill::NONE) { skill_level = Units::getEffectiveSkill(d->dwarf, skill); + xp = Units::getExperience(d->dwarf, skill, false); } - int score = skill_level * 100 - (d->high_skill - skill_level) * 500; + int score = skill_level * 100 - (d->high_skill - skill_level) * 500 + (xp / (skill_level + 5)); if (d->dwarf->status.labors[labor]) score += 500; if ((labor == df::unit_labor::MINE && d->has_pick) || @@ -2084,7 +2088,10 @@ public: score += 500; if (d->has_children && labor_outside[labor]) score -= 5000; - + if (d->armed && labor_outside[labor]) + score += 1000; + if (d->state == BUSY && d->using_labor == labor) + score += 5000; if (score > best_score) { bestdwarf = k; From 017b98698777d674b608585559e9a5ed4f43f2e6 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 11 Dec 2012 09:06:37 -0600 Subject: [PATCH 031/136] Autolabor: fix wound cleaning labor. --- plugins/autolabor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index da04de508..634425e23 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1178,7 +1178,7 @@ public: job_to_labor_table[df::job_type::DiagnosePatient] = jlf_const(df::unit_labor::DIAGNOSE) ; job_to_labor_table[df::job_type::ImmobilizeBreak] = jlf_const(df::unit_labor::BONE_SETTING) ; job_to_labor_table[df::job_type::DressWound] = jlf_const(df::unit_labor::DRESSING_WOUNDS) ; - job_to_labor_table[df::job_type::CleanPatient] = jlf_const(df::unit_labor::CLEAN) ; + job_to_labor_table[df::job_type::CleanPatient] = jlf_const(df::unit_labor::DRESSING_WOUNDS) ; job_to_labor_table[df::job_type::Surgery] = jlf_const(df::unit_labor::SURGERY) ; job_to_labor_table[df::job_type::Suture] = jlf_const(df::unit_labor::SUTURING); job_to_labor_table[df::job_type::SetBone] = jlf_const(df::unit_labor::BONE_SETTING) ; @@ -1949,7 +1949,7 @@ public: labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; labor_needed[df::unit_labor::BONE_SETTING] += cnt_immobilize; labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_dressing; - labor_needed[df::unit_labor::CLEAN] += cnt_cleaning; + labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_cleaning; labor_needed[df::unit_labor::SURGERY] += cnt_surgery; labor_needed[df::unit_labor::SUTURING] += cnt_suture; labor_needed[df::unit_labor::BONE_SETTING] += cnt_setting; From 3e8ba2dd065f142001a9f4e4092f00a29b447940 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 11 Dec 2012 09:19:38 -0600 Subject: [PATCH 032/136] Autolabor: fix bring-crutch labor --- plugins/autolabor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 634425e23..f26299d22 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1217,7 +1217,7 @@ public: job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_furniture; job_to_labor_table[df::job_type::ConstructTractionBench] = jlf_const(df::unit_labor::MECHANIC); job_to_labor_table[df::job_type::CleanSelf] = jlf_no_labor; - job_to_labor_table[df::job_type::BringCrutch] = jlf_no_labor; + job_to_labor_table[df::job_type::BringCrutch] = jlf_const(df::unit_labor::BONE_SETTING); job_to_labor_table[df::job_type::ApplyCast] = jlf_const(df::unit_labor::BONE_SETTING); job_to_labor_table[df::job_type::CustomReaction] = new jlfunc_custom(); job_to_labor_table[df::job_type::ConstructSlab] = jlf_make_furniture; @@ -1954,7 +1954,7 @@ public: labor_needed[df::unit_labor::SUTURING] += cnt_suture; labor_needed[df::unit_labor::BONE_SETTING] += cnt_setting; labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; - labor_needed[df::unit_labor::HAUL_ITEM] += cnt_crutch; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_crutch; labor_needed[df::unit_labor::FEED_WATER_CIVILIANS] += need_food_water; From bd1756e5d0c8e0bb0a43d8fb51ebdc4eb8735003 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 11 Dec 2012 09:29:03 -0600 Subject: [PATCH 033/136] Autolabor: change the fallback labor (for dwarfs for which nothing seems appropriate) from NONE to CLEAN. Fiddle with weights in assignment algorithm. --- plugins/autolabor.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index f26299d22..21cd299ee 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -2059,7 +2059,7 @@ public: std::list::iterator bestdwarf = available_dwarfs.begin(); int best_score = INT_MIN; - df::unit_labor best_labor = df::unit_labor::NONE; + df::unit_labor best_labor = df::unit_labor::CLEAN; for (auto j = to_assign.begin(); j != to_assign.end(); j++) { @@ -2079,19 +2079,19 @@ public: skill_level = Units::getEffectiveSkill(d->dwarf, skill); xp = Units::getExperience(d->dwarf, skill, false); } - int score = skill_level * 100 - (d->high_skill - skill_level) * 500 + (xp / (skill_level + 5)); + int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); if (d->dwarf->status.labors[labor]) score += 500; if ((labor == df::unit_labor::MINE && d->has_pick) || (labor == df::unit_labor::CUTWOOD && d->has_axe) || (labor == df::unit_labor::HUNT && d->has_crossbow)) - score += 500; + score += 3000; if (d->has_children && labor_outside[labor]) - score -= 5000; + score -= 10000; if (d->armed && labor_outside[labor]) - score += 1000; - if (d->state == BUSY && d->using_labor == labor) score += 5000; + if (d->state == BUSY && d->using_labor == labor) + score += 7500; if (score > best_score) { bestdwarf = k; From b75c1da95ec712d7ced4079f0ec7c4a75e13ce49 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 12 Dec 2012 08:46:52 -0600 Subject: [PATCH 034/136] Autolabor: add build waterwheel. --- plugins/autolabor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 21cd299ee..8531e09c8 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -775,6 +775,7 @@ private: case df::building_type::Construction: case df::building_type::Bridge: case df::building_type::ArcheryTarget: + case df::building_type::WaterWheel: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) From 0fc01c48e16961fb612cac20bbd716a2024afbcd Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 12 Dec 2012 09:52:13 -0600 Subject: [PATCH 035/136] Autolabor: add construct labor for GearAssembly, AxleHorizonal, and AxleVertical (the last is a guess, but probably right) --- plugins/autolabor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 8531e09c8..266a2771d 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -804,6 +804,9 @@ private: case df::building_type::Slab: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: + case df::building_type::GearAssembly: + case df::building_type::AxleHorizontal: + case df::building_type::AxleVertical: return df::unit_labor::MECHANIC; } From 3b9f21a1eac3c3a0a1c1f00bab6f6e1406d28b99 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 12 Dec 2012 10:37:09 -0600 Subject: [PATCH 036/136] Autolabor: do not count designations in hidden squares (since your dwarves can't reach them anyway). Also apply an assignment penalty for assigning a dwarf to a labor other than the one the dwarf is doing --- plugins/autolabor.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 266a2771d..fc8559df0 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1540,6 +1540,9 @@ private: for (int x = 0; x < 16; x++) for (int y = 0; y < 16; y++) { + if (bl->designation[x][y].bits.hidden) + continue; + df::tile_dig_designation dig = bl->designation[x][y].bits.dig; if (dig != df::enums::tile_dig_designation::No) { @@ -2094,8 +2097,11 @@ public: score -= 10000; if (d->armed && labor_outside[labor]) score += 5000; - if (d->state == BUSY && d->using_labor == labor) - score += 7500; + if (d->state == BUSY) + if (d->using_labor == labor) + score += 7500; + else + score -= 7500; if (score > best_score) { bestdwarf = k; From 82031092a9a016166a8912744a0fc83dc753daea Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 12 Dec 2012 14:28:11 -0600 Subject: [PATCH 037/136] Autolabor: rework tool management to try to reduce tool litter --- plugins/autolabor.cpp | 244 +++++++++++++++++++++++------------------- 1 file changed, 136 insertions(+), 108 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index fc8559df0..c0ef5d114 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -385,89 +385,96 @@ struct labor_info }; +enum tools_enum { + TOOL_NONE, TOOL_PICK, TOOL_AXE, TOOL_CROSSBOW, + TOOLS_MAX +}; + + struct labor_default { int priority; int maximum_dwarfs; + tools_enum tool; }; static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { - /* MINE */ {200, 0}, - /* HAUL_STONE */ {100, 0}, - /* HAUL_WOOD */ {100, 0}, - /* HAUL_BODY */ {200, 0}, - /* HAUL_FOOD */ {300, 0}, - /* HAUL_REFUSE */ {100, 0}, - /* HAUL_ITEM */ {100, 0}, - /* HAUL_FURNITURE */ {100, 0}, - /* HAUL_ANIMAL */ {100, 0}, - /* CLEAN */ {200, 0}, - /* CUTWOOD */ {200, 0}, - /* CARPENTER */ {200, 0}, - /* DETAIL */ {200, 0}, - /* MASON */ {200, 0}, - /* ARCHITECT */ {400, 0}, - /* ANIMALTRAIN */ {200, 0}, - /* ANIMALCARE */ {200, 0}, - /* DIAGNOSE */ {1000, 0}, - /* SURGERY */ {1000, 0}, - /* BONE_SETTING */ {1000, 0}, - /* SUTURING */ {1000, 0}, - /* DRESSING_WOUNDS */ {1000, 0}, - /* FEED_WATER_CIVILIANS */ {1000, 0}, - /* RECOVER_WOUNDED */ {200, 0}, - /* BUTCHER */ {200, 0}, - /* TRAPPER */ {200, 0}, - /* DISSECT_VERMIN */ {200, 0}, - /* LEATHER */ {200, 0}, - /* TANNER */ {200, 0}, - /* BREWER */ {200, 0}, - /* ALCHEMIST */ {200, 0}, - /* SOAP_MAKER */ {200, 0}, - /* WEAVER */ {200, 0}, - /* CLOTHESMAKER */ {200, 0}, - /* MILLER */ {200, 0}, - /* PROCESS_PLANT */ {200, 0}, - /* MAKE_CHEESE */ {200, 0}, - /* MILK */ {200, 0}, - /* COOK */ {200, 0}, - /* PLANT */ {200, 0}, - /* HERBALIST */ {200, 0}, - /* FISH */ {100, 0}, - /* CLEAN_FISH */ {200, 0}, - /* DISSECT_FISH */ {200, 0}, - /* HUNT */ {100, 0}, - /* SMELT */ {200, 0}, - /* FORGE_WEAPON */ {200, 0}, - /* FORGE_ARMOR */ {200, 0}, - /* FORGE_FURNITURE */ {200, 0}, - /* METAL_CRAFT */ {200, 0}, - /* CUT_GEM */ {200, 0}, - /* ENCRUST_GEM */ {200, 0}, - /* WOOD_CRAFT */ {200, 0}, - /* STONE_CRAFT */ {200, 0}, - /* BONE_CARVE */ {200, 0}, - /* GLASSMAKER */ {200, 0}, - /* EXTRACT_STRAND */ {200, 0}, - /* SIEGECRAFT */ {200, 0}, - /* SIEGEOPERATE */ {200, 0}, - /* BOWYER */ {200, 0}, - /* MECHANIC */ {200, 0}, - /* POTASH_MAKING */ {200, 0}, - /* LYE_MAKING */ {200, 0}, - /* DYER */ {200, 0}, - /* BURN_WOOD */ {200, 0}, - /* OPERATE_PUMP */ {200, 0}, - /* SHEARER */ {200, 0}, - /* SPINNER */ {200, 0}, - /* POTTERY */ {200, 0}, - /* GLAZING */ {200, 0}, - /* PRESSING */ {200, 0}, - /* BEEKEEPING */ {200, 1}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) - /* WAX_WORKING */ {200, 0}, - /* PUSH_HAUL_VEHICLES */ {200, 0} + /* MINE */ {200, 0, TOOL_PICK}, + /* HAUL_STONE */ {100, 0, TOOL_NONE}, + /* HAUL_WOOD */ {100, 0, TOOL_NONE}, + /* HAUL_BODY */ {200, 0, TOOL_NONE}, + /* HAUL_FOOD */ {300, 0, TOOL_NONE}, + /* HAUL_REFUSE */ {100, 0, TOOL_NONE}, + /* HAUL_ITEM */ {100, 0, TOOL_NONE}, + /* HAUL_FURNITURE */ {100, 0, TOOL_NONE}, + /* HAUL_ANIMAL */ {100, 0, TOOL_NONE}, + /* CLEAN */ {200, 0, TOOL_NONE}, + /* CUTWOOD */ {200, 0, TOOL_AXE}, + /* CARPENTER */ {200, 0, TOOL_NONE}, + /* DETAIL */ {200, 0, TOOL_NONE}, + /* MASON */ {200, 0, TOOL_NONE}, + /* ARCHITECT */ {400, 0, TOOL_NONE}, + /* ANIMALTRAIN */ {200, 0, TOOL_NONE}, + /* ANIMALCARE */ {200, 0, TOOL_NONE}, + /* DIAGNOSE */ {1000, 0, TOOL_NONE}, + /* SURGERY */ {1000, 0, TOOL_NONE}, + /* BONE_SETTING */ {1000, 0, TOOL_NONE}, + /* SUTURING */ {1000, 0, TOOL_NONE}, + /* DRESSING_WOUNDS */ {1000, 0, TOOL_NONE}, + /* FEED_WATER_CIVILIANS */ {1000, 0, TOOL_NONE}, + /* RECOVER_WOUNDED */ {200, 0, TOOL_NONE}, + /* BUTCHER */ {200, 0, TOOL_NONE}, + /* TRAPPER */ {200, 0, TOOL_NONE}, + /* DISSECT_VERMIN */ {200, 0, TOOL_NONE}, + /* LEATHER */ {200, 0, TOOL_NONE}, + /* TANNER */ {200, 0, TOOL_NONE}, + /* BREWER */ {200, 0, TOOL_NONE}, + /* ALCHEMIST */ {200, 0, TOOL_NONE}, + /* SOAP_MAKER */ {200, 0, TOOL_NONE}, + /* WEAVER */ {200, 0, TOOL_NONE}, + /* CLOTHESMAKER */ {200, 0, TOOL_NONE}, + /* MILLER */ {200, 0, TOOL_NONE}, + /* PROCESS_PLANT */ {200, 0, TOOL_NONE}, + /* MAKE_CHEESE */ {200, 0, TOOL_NONE}, + /* MILK */ {200, 0, TOOL_NONE}, + /* COOK */ {200, 0, TOOL_NONE}, + /* PLANT */ {200, 0, TOOL_NONE}, + /* HERBALIST */ {200, 0, TOOL_NONE}, + /* FISH */ {100, 0, TOOL_NONE}, + /* CLEAN_FISH */ {200, 0, TOOL_NONE}, + /* DISSECT_FISH */ {200, 0, TOOL_NONE}, + /* HUNT */ {100, 0, TOOL_CROSSBOW}, + /* SMELT */ {200, 0, TOOL_NONE}, + /* FORGE_WEAPON */ {200, 0, TOOL_NONE}, + /* FORGE_ARMOR */ {200, 0, TOOL_NONE}, + /* FORGE_FURNITURE */ {200, 0, TOOL_NONE}, + /* METAL_CRAFT */ {200, 0, TOOL_NONE}, + /* CUT_GEM */ {200, 0, TOOL_NONE}, + /* ENCRUST_GEM */ {200, 0, TOOL_NONE}, + /* WOOD_CRAFT */ {200, 0, TOOL_NONE}, + /* STONE_CRAFT */ {200, 0, TOOL_NONE}, + /* BONE_CARVE */ {200, 0, TOOL_NONE}, + /* GLASSMAKER */ {200, 0, TOOL_NONE}, + /* EXTRACT_STRAND */ {200, 0, TOOL_NONE}, + /* SIEGECRAFT */ {200, 0, TOOL_NONE}, + /* SIEGEOPERATE */ {200, 0, TOOL_NONE}, + /* BOWYER */ {200, 0, TOOL_NONE}, + /* MECHANIC */ {200, 0, TOOL_NONE}, + /* POTASH_MAKING */ {200, 0, TOOL_NONE}, + /* LYE_MAKING */ {200, 0, TOOL_NONE}, + /* DYER */ {200, 0, TOOL_NONE}, + /* BURN_WOOD */ {200, 0, TOOL_NONE}, + /* OPERATE_PUMP */ {200, 0, TOOL_NONE}, + /* SHEARER */ {200, 0, TOOL_NONE}, + /* SPINNER */ {200, 0, TOOL_NONE}, + /* POTTERY */ {200, 0, TOOL_NONE}, + /* GLAZING */ {200, 0, TOOL_NONE}, + /* PRESSING */ {200, 0, TOOL_NONE}, + /* BEEKEEPING */ {200, 0, TOOL_NONE}, + /* WAX_WORKING */ {200, 0, TOOL_NONE}, + /* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE} }; struct dwarf_info_t @@ -477,9 +484,7 @@ struct dwarf_info_t bool clear_all; - bool has_axe; - bool has_pick; - bool has_crossbow; + bool has_tool[TOOLS_MAX]; int high_skill; @@ -488,9 +493,11 @@ struct dwarf_info_t df::unit_labor using_labor; - dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), has_axe(false), has_pick(false), has_crossbow(false), + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), state(OTHER), high_skill(0), has_children(false), armed(false) { + for (int e = TOOL_NONE; e < TOOLS_MAX; e++) + has_tool[e] = false; } void set_labor(df::unit_labor labor) @@ -498,10 +505,6 @@ struct dwarf_info_t if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) { dwarf->status.labors[labor] = true; - if ((labor == df::unit_labor::MINE && !has_pick) || - (labor == df::unit_labor::CUTWOOD && !has_axe) || - (labor == df::unit_labor::HUNT && !has_crossbow)) - dwarf->military.pickup_flags.bits.update = 1; } } @@ -510,10 +513,6 @@ struct dwarf_info_t if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) { dwarf->status.labors[labor] = false; - if ((labor == df::unit_labor::MINE && has_pick) || - (labor == df::unit_labor::CUTWOOD && has_axe) || - (labor == df::unit_labor::HUNT && has_crossbow)) - dwarf->military.pickup_flags.bits.update = 1; } } @@ -1472,8 +1471,9 @@ private: int tree_count; int plant_count; int detail_count; - int pick_count; - int axe_count; + + int tool_count[TOOLS_MAX]; + bool reequip_needed[TOOLS_MAX]; int cnt_recover_wounded; int cnt_diagnosis; @@ -1542,7 +1542,7 @@ private: { if (bl->designation[x][y].bits.hidden) continue; - + df::tile_dig_designation dig = bl->designation[x][y].bits.dig; if (dig != df::enums::tile_dig_designation::No) { @@ -1570,8 +1570,8 @@ private: void count_tools() { - pick_count = 0; - axe_count = 0; + for (int e = TOOL_NONE; e < TOOLS_MAX; e++) + tool_count[e] = 0; df::item_flags bad_flags; bad_flags.whole = 0; @@ -1598,15 +1598,15 @@ private: df::itemdef_weaponst* weapondef = ((df::item_weaponst*)item)->subtype; df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + df::job_skill weaponsk2 = (df::job_skill) weapondef->skill_ranged; if (weaponsk == df::job_skill::AXE) - axe_count++; + tool_count[TOOL_AXE]++; else if (weaponsk == df::job_skill::MINING) - pick_count++; + tool_count[TOOL_PICK]++; + else if (weaponsk2 = df::job_skill::CROSSBOW) + tool_count[TOOL_CROSSBOW]++; } - if (print_debug) - out.print("Axes = %d, picks = %d\n", axe_count, pick_count); - } void collect_job_list() @@ -1866,19 +1866,21 @@ private: df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; if (weaponsk == df::job_skill::AXE) { - dwarf->has_axe = 1; + dwarf->has_tool[TOOL_AXE] = true; if (state != IDLE) - axe_count--; + tool_count[TOOL_AXE]--; } else if (weaponsk == df::job_skill::MINING) { - dwarf->has_pick = 1; + dwarf->has_tool[TOOL_PICK] = 1; if (state != IDLE) - pick_count--; + tool_count[TOOL_PICK]--; } else if (rangesk == df::job_skill::CROSSBOW) { - dwarf->has_crossbow = 1; + dwarf->has_tool[TOOL_CROSSBOW] = 1; + if (state != IDLE) + tool_count[TOOL_CROSSBOW]--; } } } @@ -1908,11 +1910,14 @@ public: { dwarf_info.clear(); - dig_count = tree_count = plant_count = detail_count = pick_count = axe_count = 0; + dig_count = tree_count = plant_count = detail_count = 0; cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = cnt_setting = cnt_traction = cnt_crutch = 0; need_food_water = 0; + for (int e = 0; e < TOOLS_MAX; e++) + tool_count[e] = 0; + trader_requested = false; FOR_ENUM_ITEMS(unit_labor, l) @@ -1945,8 +1950,8 @@ public: // add job entries for designation-related jobs - labor_needed[df::unit_labor::MINE] += std::min(pick_count, dig_count); - labor_needed[df::unit_labor::CUTWOOD] += std::min(axe_count, tree_count); + labor_needed[df::unit_labor::MINE] += std::min(tool_count[TOOL_PICK], dig_count); + labor_needed[df::unit_labor::CUTWOOD] += std::min(tool_count[TOOL_AXE], tree_count); labor_needed[df::unit_labor::DETAIL] += detail_count; labor_needed[df::unit_labor::HERBALIST] += plant_count; @@ -2089,9 +2094,8 @@ public: int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); if (d->dwarf->status.labors[labor]) score += 500; - if ((labor == df::unit_labor::MINE && d->has_pick) || - (labor == df::unit_labor::CUTWOOD && d->has_axe) || - (labor == df::unit_labor::HUNT && d->has_crossbow)) + if (default_labor_infos[labor].tool != TOOL_NONE && + d->has_tool[default_labor_infos[labor].tool]) score += 3000; if (d->has_children && labor_outside[labor]) score -= 10000; @@ -2120,8 +2124,17 @@ public: continue; if (l == best_labor) + { (*bestdwarf)->set_labor(l); - else + tools_enum t = default_labor_infos[l].tool; + if (t != TOOL_NONE) + { + tool_count[t]--; + if (!(*bestdwarf)->has_tool[t]) + (*bestdwarf)->dwarf->military.pickup_flags.bits.update = true; + } + } + else (*bestdwarf)->clear_labor(l); } @@ -2150,6 +2163,21 @@ public: out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); } + /* set reequip on any dwarfs who are carrying tools needed by others */ + + for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) + { + FOR_ENUM_ITEMS (unit_labor, l) + { + tools_enum t = default_labor_infos[l].tool; + if (t != TOOL_NONE && tool_count[t] < 0 && (*d)->has_tool[t] && !(*d)->dwarf->status.labors[l]) + { + tool_count[t]++; + (*d)->dwarf->military.pickup_flags.bits.update = 1; + } + } + } + print_debug = 0; } From 3a541e26bec867a87192c9b37d7f9bff01a1cc73 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 12 Dec 2012 23:01:05 -0500 Subject: [PATCH 038/136] autoSyndrome: automatically detects boiling rock syndromes better, and checks if each syndrome is applicable to the unit that finished the job. --- plugins/autoSyndrome.cpp | 153 +++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 71 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 28a785adc..77217c6a0 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -2,9 +2,10 @@ #include "Export.h" #include "DataDefs.h" #include "Core.h" -#include "df/job.h" +#include "df/caste_raw.h" +#include "df/creature_raw.h" #include "df/global_objects.h" -#include "df/ui.h" +#include "df/job.h" #include "df/job_type.h" #include "df/reaction.h" #include "df/reaction_product.h" @@ -12,6 +13,7 @@ #include "df/reaction_product_itemst.h" #include "df/syndrome.h" #include "df/unit_syndrome.h" +#include "df/ui.h" #include "df/unit.h" #include "df/general_ref.h" #include "df/general_ref_type.h" @@ -30,25 +32,6 @@ using namespace DFHack; /* Example usage: -////////////////////////////////////////////// -//in file interaction_duck.txt -interaction_duck - -[OBJECT:INTERACTION] - -[INTERACTION:DUMMY_DUCK_INTERACTION] - [I_SOURCE:CREATURE_ACTION] - [I_TARGET:A:CREATURE] - [IT_LOCATION:CONTEXT_CREATURE] - [IT_MANUAL_INPUT:target] - [IT_IMMUNE_CREATURE:BIRD_DUCK:MALE] - [I_EFFECT:ADD_SYNDROME] - [IE_TARGET:A] - [IE_IMMEDIATE] - [SYNDROME] - [SYN_NAME:chronicDuckSyndrome] - [CE_BODY_TRANSFORMATION:PROB:100:START:0] - [CE:CREATURE:BIRD_DUCK:MALE] ////////////////////////////////////////////// //In file inorganic_duck.txt inorganic_stone_duck @@ -60,8 +43,11 @@ inorganic_stone_duck [STATE_NAME_ADJ:ALL_SOLID:drakium][DISPLAY_COLOR:0:7:0][TILE:'.'] [IS_STONE] [SOLID_DENSITY:1][MELTING_POINT:25000] -[CAUSE_SYNDROME:chronicDuckSyndrome] -[BOILING_POINT:50000] +[BOILING_POINT:9999] //This is the critical line: boiling point must be <= 10000 +[SYNDROME] + [SYN_NAME:Chronic Duck Syndrome] + [CE_BODY_TRANSFORMATION:PROB:100:START:0] + [CE:CREATURE:BIRD_DUCK:MALE] //even though we don't have SYN_INHALED, the plugin will add it /////////////////////////////////////////////// //In file building_duck.txt building_duck @@ -117,6 +103,29 @@ command_result autoSyndrome(color_ostream& out, vector& parameters); int32_t processJob(color_ostream& out, int32_t id); int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome); +DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { + commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false, + "autoSyndrome:\n" + " autoSyndrome 0 //disable\n" + " autoSyndrome 1 //enable\n" + " autoSyndrome disable //disable\n" + " autoSyndrome enable //enable\n" + "\n" + "autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n" + "\n" + "Requirements:\n" + " 1) The job must be a custom reaction.\n" + " 2) The job must produce a stone of some inorganic material.\n" + " 3) The stone must have a boiling temperature less than or equal to 10000.\n" + "\n" + "When these conditions are met, the unit that completed the job will immediately become afflicted with all applicable syndromes associated with the inorganic material of the stone, or stones. It should correctly check for whether the creature or caste is affected or immune, but it ignores syn_affected_class tags.\n" + "Multiple syndromes per stone, or multiple boiling rocks produced with the same reaction should work fine.\n" + )); + + + return CR_OK; +} + DFhackCExport command_result plugin_shutdown(color_ostream& out) { return CR_OK; } @@ -204,27 +213,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out) { return CR_OK; }*/ -DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { - commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false, - "autoSyndrome:\n" - " autoSyndrome 0 //disable\n" - " autoSyndrome 1 //enable\n" - " autoSyndrome disable //disable\n" - " autoSyndrome enable //enable\n" - "\n" - "autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n" - "\n" - "Requirements:\n" - " 1) The job must be a custom reaction.\n" - " 2) The job must produce a stone of some inorganic material.\n" - " 3) The material of one of the stones produced must have a token in its raw file of the form [CAUSE_SYNDROME:syndrome_name].\n" - "\n" - "If a syndrome with the tag [SYN_NAME:syndrome_name] exists, then the unit that completed the job will become afflicted with that syndrome as soon as the job is completed.\n")); - - - return CR_OK; -} - command_result autoSyndrome(color_ostream& out, vector& parameters) { if ( parameters.size() > 1 ) return CR_WRONG_USAGE; @@ -273,7 +261,22 @@ int32_t processJob(color_ostream& out, int32_t jobId) { return -1; } - //find all of the products it makes. Look for a stone with a material with special tags. + if ( jobWorkers.find(jobId) == jobWorkers.end() ) { + out.print("%s, line %d: couldn't find worker for job %d.\n", __FILE__, __LINE__, jobId); + return -1; + } + int32_t workerId = jobWorkers[jobId]; + int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId); + if ( workerIndex < 0 ) { + out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); + return -1; + } + df::unit* unit = df::global::world->units.all[workerIndex]; + df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; + std::string& creature_name = creature->creature_id; + std::string& creature_caste = creature->caste[unit->caste]->caste_id; + + //find all of the products it makes. Look for a stone with a low boiling point. bool foundIt = false; for ( size_t a = 0; a < reaction->products.size(); a++ ) { df::reaction_product_type type = reaction->products[a]->getType(); @@ -286,39 +289,47 @@ int32_t processJob(color_ostream& out, int32_t jobId) { continue; //for now don't worry about subtype + //must be a boiling rock syndrome df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index]; - const char* helper = "CAUSE_SYNDROME:"; - for ( size_t b = 0; b < inorganic->str.size(); b++ ) { - //out.print("inorganic str = \"%s\"\n", inorganic->str[b]->c_str()); - size_t c = inorganic->str[b]->find(helper); - if ( c == string::npos ) - continue; - string tail = inorganic->str[b]->substr(c + strlen(helper), inorganic->str[b]->length() - strlen(helper) - 2); - //out.print("tail = %s\n", tail.c_str()); + if ( inorganic->material.heat.boiling_point > 10000 ) + continue; - //find the syndrome with this name, and give apply it to the dwarf working on the job - //first find out who completed the job - if ( jobWorkers.find(jobId) == jobWorkers.end() ) { - out.print("%s, line %d: could not find job worker for jobs %d.\n", __FILE__, __LINE__, jobId); - return -1; + for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) { + //add each syndrome to the guy who did the job + df::syndrome* syndrome = inorganic->material.syndrome[b]; + //check that the syndrome applies to that guy + bool applies = syndrome->syn_affected_creature_1.size() == 0; + if ( applies ) { + //out.print("No syn_affected_creature.\n"); } - int32_t workerId = jobWorkers[jobId]; - - //find the syndrome - df::syndrome* syndrome = NULL; - for ( size_t d = 0; d < df::global::world->raws.syndromes.all.size(); d++ ) { - df::syndrome* candidate = df::global::world->raws.syndromes.all[d]; - if ( candidate->syn_name != tail ) + for ( size_t c = 0; c < syndrome->syn_affected_creature_1.size(); c++ ) { + if ( creature_name != *syndrome->syn_affected_creature_1[c] ) continue; - syndrome = candidate; - break; + if ( *syndrome->syn_affected_creature_2[c] == "ALL" || + *syndrome->syn_affected_creature_2[c] == creature_caste ) { + applies = true; + break; + } + } + if ( !applies ) { + //out.print("Not in syn_affected_creature.\n"); + continue; + } + for ( size_t c = 0; c < syndrome->syn_immune_creature_1.size(); c++ ) { + if ( creature_name != *syndrome->syn_immune_creature_1[c] ) + continue; + if ( *syndrome->syn_immune_creature_2[c] == "ALL" || + *syndrome->syn_immune_creature_2[c] == creature_caste ) { + applies = false; + break; + } + } + if ( !applies ) { + //out.print("Creature is immune.\n"); + continue; } - if ( syndrome == NULL ) - return 0; - if ( giveSyndrome(out, workerId, syndrome) < 0 ) return -1; - //out.print("Gave syndrome.\n"); } } if ( !foundIt ) From 2535b50bfc1d81952224d729da6ae189ab10b03e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 12 Dec 2012 22:25:23 -0600 Subject: [PATCH 039/136] Autolabor: add construct chain labor, add destruct trap labor, change overbroad test for military status (was catching uniformed reservists, who are eligible to do civilian labor) --- plugins/autolabor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index c0ef5d114..357f9bd14 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -801,6 +801,7 @@ private: case df::building_type::NestBox: case df::building_type::TractionBench: case df::building_type::Slab: + case df::building_type::Chain: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: @@ -850,6 +851,8 @@ private: break; case df::building_type::FarmPlot: return df::unit_labor::PLANT; + case df::building_type::Trap: + return df::unit_labor::MECHANIC; } debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", @@ -1758,7 +1761,7 @@ private: { state = CHILD; } - else if (dwarf->dwarf->military.cur_uniform != 0) + else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) state = MILITARY; else if (dwarf->dwarf->job.current_job == NULL) { From a28fc65e6d21578e31d25f3fa08133977cb5388d Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 13 Dec 2012 11:13:56 -0500 Subject: [PATCH 040/136] autoSyndrome now deals with creature_class. --- plugins/autoSyndrome.cpp | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 77217c6a0..a37d806fe 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -118,7 +118,7 @@ DFhackCExport command_result plugin_init(color_ostream& out, vectorunits.all[workerIndex]; df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; + df::caste_raw* caste = creature->caste[unit->caste]; std::string& creature_name = creature->creature_id; - std::string& creature_caste = creature->caste[unit->caste]->caste_id; + std::string& creature_caste = caste->caste_id; //find all of the products it makes. Look for a stone with a low boiling point. bool foundIt = false; @@ -298,10 +299,40 @@ int32_t processJob(color_ostream& out, int32_t jobId) { //add each syndrome to the guy who did the job df::syndrome* syndrome = inorganic->material.syndrome[b]; //check that the syndrome applies to that guy - bool applies = syndrome->syn_affected_creature_1.size() == 0; + bool applies = syndrome->syn_affected_class.size() == 0; if ( applies ) { + //out.print("No syn_affected_class.\n"); + } + for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) { + if ( applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) { + applies = true; + break; + } + } + } + if ( syndrome->syn_affected_creature_1.size() != 0 ) { + applies = false; + } else { //out.print("No syn_affected_creature.\n"); } + for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) { + if ( !applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) { + applies = false; + break; + } + } + } + + if ( syndrome->syn_affected_creature_1.size() != syndrome->syn_affected_creature_2.size() ) { + out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__); + return -1; + } for ( size_t c = 0; c < syndrome->syn_affected_creature_1.size(); c++ ) { if ( creature_name != *syndrome->syn_affected_creature_1[c] ) continue; From af7f11fdfac5b15227a5d65e757dc53926056fc4 Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 13 Dec 2012 11:21:51 -0500 Subject: [PATCH 041/136] autoSyndrome: made the syndrome logic make more sense. --- plugins/autoSyndrome.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index a37d806fe..1298764de 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -299,10 +299,17 @@ int32_t processJob(color_ostream& out, int32_t jobId) { //add each syndrome to the guy who did the job df::syndrome* syndrome = inorganic->material.syndrome[b]; //check that the syndrome applies to that guy - bool applies = syndrome->syn_affected_class.size() == 0; - if ( applies ) { - //out.print("No syn_affected_class.\n"); - } + /* + * If there is no affected class or affected creature, then anybody who isn't immune is fair game. + * + * Otherwise, it works like this: + * add all the affected class creatures + * remove all the immune class creatures + * add all the affected creatures + * remove all the immune creatures + * you're affected if and only if you're in the remaining list after all of that + **/ + bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature_1.size() == 0; for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) { if ( applies ) break; @@ -313,11 +320,6 @@ int32_t processJob(color_ostream& out, int32_t jobId) { } } } - if ( syndrome->syn_affected_creature_1.size() != 0 ) { - applies = false; - } else { - //out.print("No syn_affected_creature.\n"); - } for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) { if ( !applies ) break; @@ -342,10 +344,6 @@ int32_t processJob(color_ostream& out, int32_t jobId) { break; } } - if ( !applies ) { - //out.print("Not in syn_affected_creature.\n"); - continue; - } for ( size_t c = 0; c < syndrome->syn_immune_creature_1.size(); c++ ) { if ( creature_name != *syndrome->syn_immune_creature_1[c] ) continue; @@ -356,7 +354,6 @@ int32_t processJob(color_ostream& out, int32_t jobId) { } } if ( !applies ) { - //out.print("Creature is immune.\n"); continue; } if ( giveSyndrome(out, workerId, syndrome) < 0 ) From cd7c39f2db89204bd3ffdac94c818fd9975085b1 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 14 Dec 2012 09:34:03 -0600 Subject: [PATCH 042/136] Autolabor: add deconstruct labor for cages --- plugins/autolabor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 357f9bd14..49c2e4174 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -853,6 +853,8 @@ private: return df::unit_labor::PLANT; case df::building_type::Trap: return df::unit_labor::MECHANIC; + case df::building_type::Cage: + return df::unit_labor::HAUL_FURNITURE; } debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", From 747723187f342a983a35fa3aad8ae90741486f7d Mon Sep 17 00:00:00 2001 From: expwnent Date: Fri, 14 Dec 2012 21:05:38 -0500 Subject: [PATCH 043/136] EventManager: first draft. --- library/CMakeLists.txt | 2 ++ library/Core.cpp | 3 +++ library/include/modules/Job.h | 2 +- library/modules/Job.cpp | 4 ++-- plugins/CMakeLists.txt | 1 + plugins/eventExample.cpp | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 plugins/eventExample.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 6f33d5c8a..615c223c2 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -111,6 +111,7 @@ include/modules/Burrows.h include/modules/Constructions.h include/modules/Units.h include/modules/Engravings.h +include/modules/EventManager.h include/modules/Gui.h include/modules/Items.h include/modules/Job.h @@ -133,6 +134,7 @@ modules/Burrows.cpp modules/Constructions.cpp modules/Units.cpp modules/Engravings.cpp +modules/EventManager.cpp modules/Gui.cpp modules/Items.cpp modules/Job.cpp diff --git a/library/Core.cpp b/library/Core.cpp index 26c0acbb0..a9164128f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -44,6 +44,7 @@ using namespace std; #include "VersionInfo.h" #include "PluginManager.h" #include "ModuleFactory.h" +#include "modules/EventManager.h" #include "modules/Gui.h" #include "modules/World.h" #include "modules/Graphic.h" @@ -1238,6 +1239,8 @@ static int buildings_timer = 0; void Core::onUpdate(color_ostream &out) { + EventManager::manageEvents(out); + // convert building reagents if (buildings_do_onupdate && (++buildings_timer & 1)) buildings_onUpdate(out); diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 853813073..e865273d9 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -47,7 +47,7 @@ namespace DFHack { namespace Job { // Duplicate the job structure. It is not linked into any DF lists. - DFHACK_EXPORT df::job *cloneJobStruct(df::job *job); + DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepWorkerData=false); // Delete a cloned structure. DFHACK_EXPORT void deleteJobStruct(df::job *job); diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index def3b4192..c0e18f44a 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -54,7 +54,7 @@ using namespace std; using namespace DFHack; using namespace df::enums; -df::job *DFHack::Job::cloneJobStruct(df::job *job) +df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepWorkerData) { CHECK_NULL_POINTER(job); @@ -75,7 +75,7 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job) { df::general_ref *ref = pnew->references[i]; - if (virtual_cast(ref)) + if (!keepWorkerData && virtual_cast(ref)) vector_erase_at(pnew->references, i); else pnew->references[i] = ref->clone(); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8aeeee8c3..16fffd422 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -130,6 +130,7 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + DFHACK_PLUGIN(eventExample eventExample.cpp) endif() diff --git a/plugins/eventExample.cpp b/plugins/eventExample.cpp new file mode 100644 index 000000000..ea8e2cfec --- /dev/null +++ b/plugins/eventExample.cpp @@ -0,0 +1,32 @@ + +#include "Console.h" +#include "Core.h" +#include "Export.h" +#include "modules/EventManager.h" +#include "DataDefs.h" + +using namespace DFHack; + +DFHACK_PLUGIN("eventExample"); + +void jobInitiated(color_ostream& out, void* job); +void jobCompleted(color_ostream& out, void* job); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + EventManager::EventHandler initiateHandler(jobInitiated); + EventManager::EventHandler completeHandler(jobCompleted); + + EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, NULL); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, NULL); + + return CR_OK; +} + +void jobInitiated(color_ostream& out, void* job) { + out.print("Job initiated! 0x%X\n", job); +} + +void jobCompleted(color_ostream& out, void* job) { + out.print("Job completed! 0x%X\n", job); +} + From cf619a519eb031531609604fd29a6e60a0893691 Mon Sep 17 00:00:00 2001 From: expwnent Date: Fri, 14 Dec 2012 22:14:38 -0500 Subject: [PATCH 044/136] EventManager: made event handlers unregister when plugins are unloaded. Also changed PluginManager so that plugins can call core.getPluginManager() during plugin_init. --- library/Core.cpp | 3 +++ library/PluginManager.cpp | 30 ++++++++++++++++++------------ library/include/PluginManager.h | 1 + plugins/eventExample.cpp | 6 ++++-- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index a9164128f..5c397dd05 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -905,6 +905,7 @@ bool Core::Init() cerr << "Initializing Plugins.\n"; // create plugin manager plug_mgr = new PluginManager(this); + plug_mgr->init(this); IODATA *temp = new IODATA; temp->core = this; temp->plug_mgr = plug_mgr; @@ -1254,6 +1255,8 @@ void Core::onUpdate(color_ostream &out) void Core::onStateChange(color_ostream &out, state_change_event event) { + EventManager::onStateChange(out, event); + buildings_onStateChange(out, event); plug_mgr->OnStateChange(out, event); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 0c80639b4..86bab66cd 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -22,6 +22,7 @@ must not be misrepresented as being the original software. distribution. */ +#include "modules/EventManager.h" #include "Internal.h" #include "Core.h" #include "MemAccess.h" @@ -270,6 +271,7 @@ bool Plugin::unload(color_ostream &con) // if we are actually loaded if(state == PS_LOADED) { + EventManager::unregisterAll(this); // notify the plugin about an attempt to shutdown if (plugin_onstatechange && plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) @@ -598,6 +600,22 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn) } PluginManager::PluginManager(Core * core) +{ + cmdlist_mutex = new mutex(); + eval_ruby = NULL; +} + +PluginManager::~PluginManager() +{ + for(size_t i = 0; i < all_plugins.size();i++) + { + delete all_plugins[i]; + } + all_plugins.clear(); + delete cmdlist_mutex; +} + +void PluginManager::init(Core * core) { #ifdef LINUX_BUILD string path = core->getHackPath() + "plugins/"; @@ -606,8 +624,6 @@ PluginManager::PluginManager(Core * core) string path = core->getHackPath() + "plugins\\"; const string searchstr = ".plug.dll"; #endif - cmdlist_mutex = new mutex(); - eval_ruby = NULL; vector filez; getdir(path, filez); for(size_t i = 0; i < filez.size();i++) @@ -622,16 +638,6 @@ PluginManager::PluginManager(Core * core) } } -PluginManager::~PluginManager() -{ - for(size_t i = 0; i < all_plugins.size();i++) - { - delete all_plugins[i]; - } - all_plugins.clear(); - delete cmdlist_mutex; -} - Plugin *PluginManager::getPluginByName (const std::string & name) { for(size_t i = 0; i < all_plugins.size(); i++) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 9ef16703a..62a195867 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -205,6 +205,7 @@ namespace DFHack friend class Plugin; PluginManager(Core * core); ~PluginManager(); + void init(Core* core); void OnUpdate(color_ostream &out); void OnStateChange(color_ostream &out, state_change_event event); void registerCommands( Plugin * p ); diff --git a/plugins/eventExample.cpp b/plugins/eventExample.cpp index ea8e2cfec..3b898ae7b 100644 --- a/plugins/eventExample.cpp +++ b/plugins/eventExample.cpp @@ -2,6 +2,7 @@ #include "Console.h" #include "Core.h" #include "Export.h" +#include "PluginManager.h" #include "modules/EventManager.h" #include "DataDefs.h" @@ -15,9 +16,10 @@ void jobCompleted(color_ostream& out, void* job); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { EventManager::EventHandler initiateHandler(jobInitiated); EventManager::EventHandler completeHandler(jobCompleted); + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); - EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, NULL); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, NULL); + EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); return CR_OK; } From 155a4d044c45cf58ccb0bcbbc8168793faa0bb60 Mon Sep 17 00:00:00 2001 From: expwnent Date: Fri, 14 Dec 2012 23:29:28 -0500 Subject: [PATCH 045/136] EventManager: fiddled with time events. Made it possible to register for time events before a world is loaded. Also added some files I forgot to add to the previous commit. --- library/include/modules/EventManager.h | 51 ++++++ library/modules/EventManager.cpp | 216 +++++++++++++++++++++++++ plugins/eventExample.cpp | 9 ++ 3 files changed, 276 insertions(+) create mode 100644 library/include/modules/EventManager.h create mode 100644 library/modules/EventManager.cpp diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h new file mode 100644 index 000000000..a357bbb81 --- /dev/null +++ b/library/include/modules/EventManager.h @@ -0,0 +1,51 @@ +#pragma once +#ifndef EVENT_MANAGER_H_INCLUDED +#define EVENT_MANAGER_H_INCLUDED + +#include "Core.h" +#include "Export.h" +#include "ColorText.h" +#include "PluginManager.h" +#include "Console.h" + +namespace DFHack { + namespace EventManager { + namespace EventType { + enum EventType { + NONE, + TICK, + TICK_TILE, + JOB_INITIATED, + JOB_COMPLETED, + LIFE, + CREATURE, + ITEM, + TILE, + EVENT_MAX=TILE + }; + } + + struct EventHandler { + void (*eventHandler)(color_ostream&, void*); //called when the event happens + + EventHandler(void (*eventHandlerIn)(color_ostream&, void*)): eventHandler(eventHandlerIn) { + } + + bool operator==(EventHandler& handle) const { + return eventHandler == handle.eventHandler; + } + bool operator!=(EventHandler& handle) const { + return !( *this == handle); + } + }; + + DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); + DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin); + DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin); + DFHACK_EXPORT void unregisterAll(Plugin* plugin); + void manageEvents(color_ostream& out); + void onStateChange(color_ostream& out, state_change_event event); + } +} + +#endif diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp new file mode 100644 index 000000000..c9a312841 --- /dev/null +++ b/library/modules/EventManager.cpp @@ -0,0 +1,216 @@ +#include "Core.h" +#include "modules/EventManager.h" +#include "modules/Job.h" +#include "modules/World.h" + +#include "df/job.h" +#include "df/global_objects.h" +#include "df/job_list_link.h" +#include "df/world.h" + +//#include +#include +//#include +using namespace std; +using namespace DFHack; +using namespace EventManager; + +/* + * TODO: + * error checking + **/ + +//map > tickQueue; +multimap tickQueue; + +multimap handlers[EventType::EVENT_MAX]; + +const uint32_t ticksPerYear = 403200; + +void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) { + handlers[e].insert(pair(plugin, handler)); +} + +void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin) { + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + if ( !Core::getInstance().isWorldLoaded() ) { + tick = 0; + } + + tickQueue.insert(pair(tick+(uint32_t)when, handler)); + handlers[EventType::TICK].insert(pair(plugin,handler)); + return; +} + +void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) { + for ( multimap::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) { + if ( (*i).first != plugin ) + break; + EventHandler handle = (*i).second; + if ( handle == handler ) { + handlers[e].erase(i); + break; + } + } + return; +} + +void DFHack::EventManager::unregisterAll(Plugin* plugin) { + for ( auto i = handlers[EventType::TICK].find(plugin); i != handlers[EventType::TICK].end(); i++ ) { + if ( (*i).first != plugin ) + break; + + //shenanigans to avoid concurrent modification + EventHandler getRidOf = (*i).second; + bool didSomething; + do { + didSomething = false; + for ( auto j = tickQueue.begin(); j != tickQueue.end(); j++ ) { + EventHandler candidate = (*j).second; + if ( getRidOf != candidate ) + continue; + tickQueue.erase(j); + didSomething = true; + break; + } + } while(didSomething); + } + for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { + handlers[a].erase(plugin); + } + return; +} + +static void manageTickEvent(color_ostream& out); +static void manageJobInitiatedEvent(color_ostream& out); +static void manageJobCompletedEvent(color_ostream& out); + +uint32_t lastTick = 0; +int32_t lastJobId = -1; +static map prevJobs; + +void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { + if ( event == DFHack::SC_MAP_UNLOADED ) { + lastTick = 0; + lastJobId = -1; + for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + Job::deleteJobStruct((*i).second); + } + prevJobs.clear(); + tickQueue.clear(); + } else if ( event == DFHack::SC_MAP_LOADED ) { + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + multimap newTickQueue; + for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ ) { + newTickQueue.insert(pair(tick + (*i).first, (*i).second)); + } + tickQueue.clear(); + + tickQueue.insert(newTickQueue.begin(), newTickQueue.end()); + } +} + +void DFHack::EventManager::manageEvents(color_ostream& out) { + if ( !Core::getInstance().isWorldLoaded() ) { + return; + } + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + if ( tick <= lastTick ) + return; + lastTick = tick; + + manageTickEvent(out); + manageJobInitiatedEvent(out); + manageJobCompletedEvent(out); + + return; +} + +static void manageTickEvent(color_ostream& out) { + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + while ( !tickQueue.empty() ) { + if ( tick < (*tickQueue.begin()).first ) + break; + EventHandler handle = (*tickQueue.begin()).second; + tickQueue.erase(tickQueue.begin()); + handle.eventHandler(out, (void*)tick); + } + +} + +static void manageJobInitiatedEvent(color_ostream& out) { + if ( handlers[EventType::JOB_INITIATED].empty() ) + return; + + if ( lastJobId == -1 ) { + lastJobId = *df::global::job_next_id - 1; + return; + } + + if ( lastJobId+1 == *df::global::job_next_id ) { + return; //no new jobs + } + + for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + if ( link->item == NULL ) + continue; + if ( link->item->id <= lastJobId ) + continue; + for ( multimap::iterator i = handlers[EventType::JOB_INITIATED].begin(); i != handlers[EventType::JOB_INITIATED].end(); i++ ) { + (*i).second.eventHandler(out, (void*)link->item); + } + } + + lastJobId = *df::global::job_next_id - 1; +} + + +static void manageJobCompletedEvent(color_ostream& out) { + if ( handlers[EventType::JOB_COMPLETED].empty() ) { + return; + } + + map nowJobs; + for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + if ( link->item == NULL ) + continue; + nowJobs[link->item->id] = link->item; + } + + for ( map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + if ( nowJobs.find((*i).first) != nowJobs.end() ) + continue; + + //recently finished or cancelled job! + for ( multimap::iterator j = handlers[EventType::JOB_COMPLETED].begin(); j != handlers[EventType::JOB_COMPLETED].end(); j++ ) { + (*j).second.eventHandler(out, (void*)(*i).second); + } + } + + //erase old jobs, copy over possibly altered jobs + for ( map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + Job::deleteJobStruct((*i).second); + } + prevJobs.clear(); + + //create new jobs + for ( map::iterator j = nowJobs.begin(); j != nowJobs.end(); j++ ) { + /*map::iterator i = prevJobs.find((*j).first); + if ( i != prevJobs.end() ) { + continue; + }*/ + + df::job* newJob = Job::cloneJobStruct((*j).second, true); + prevJobs[newJob->id] = newJob; + } + + /*//get rid of old pointers to deallocated jobs + for ( size_t a = 0; a < toDelete.size(); a++ ) { + prevJobs.erase(a); + }*/ +} + diff --git a/plugins/eventExample.cpp b/plugins/eventExample.cpp index 3b898ae7b..c2774f422 100644 --- a/plugins/eventExample.cpp +++ b/plugins/eventExample.cpp @@ -12,14 +12,20 @@ DFHACK_PLUGIN("eventExample"); void jobInitiated(color_ostream& out, void* job); void jobCompleted(color_ostream& out, void* job); +void timePassed(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { EventManager::EventHandler initiateHandler(jobInitiated); EventManager::EventHandler completeHandler(jobCompleted); + EventManager::EventHandler timeHandler(timePassed); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); + EventManager::registerTick(timeHandler, 1, me); + EventManager::registerTick(timeHandler, 2, me); + EventManager::registerTick(timeHandler, 4, me); + EventManager::registerTick(timeHandler, 8, me); return CR_OK; } @@ -32,3 +38,6 @@ void jobCompleted(color_ostream& out, void* job) { out.print("Job completed! 0x%X\n", job); } +void timePassed(color_ostream& out, void* ptr) { + out.print("Time: %d\n", (int32_t)(ptr)); +} From b0314755e0c03d66b6c11abccdffd1b5583d606e Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 15 Dec 2012 14:40:11 -0500 Subject: [PATCH 046/136] EventManager: added unit death event. --- library/include/modules/EventManager.h | 9 +++----- library/modules/EventManager.cpp | 31 ++++++++++++++++++++++++-- plugins/eventExample.cpp | 7 ++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index a357bbb81..0794b29e3 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -14,14 +14,11 @@ namespace DFHack { enum EventType { NONE, TICK, - TICK_TILE, JOB_INITIATED, JOB_COMPLETED, - LIFE, - CREATURE, - ITEM, - TILE, - EVENT_MAX=TILE + UNIT_DEATH, + //ITEM_CREATED, + EVENT_MAX=UNIT_DEATH }; } diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index c9a312841..7e97cc3a4 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -6,6 +6,7 @@ #include "df/job.h" #include "df/global_objects.h" #include "df/job_list_link.h" +#include "df/unit.h" #include "df/world.h" //#include @@ -85,10 +86,12 @@ void DFHack::EventManager::unregisterAll(Plugin* plugin) { static void manageTickEvent(color_ostream& out); static void manageJobInitiatedEvent(color_ostream& out); static void manageJobCompletedEvent(color_ostream& out); +static void manageUnitDeathEvent(color_ostream& out); -uint32_t lastTick = 0; -int32_t lastJobId = -1; +static uint32_t lastTick = 0; +static int32_t lastJobId = -1; static map prevJobs; +static set livingUnits; void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { if ( event == DFHack::SC_MAP_UNLOADED ) { @@ -99,6 +102,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event } prevJobs.clear(); tickQueue.clear(); + livingUnits.clear(); } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -125,6 +129,7 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { manageTickEvent(out); manageJobInitiatedEvent(out); manageJobCompletedEvent(out); + manageUnitDeathEvent(out); return; } @@ -214,3 +219,25 @@ static void manageJobCompletedEvent(color_ostream& out) { }*/ } +static void manageUnitDeathEvent(color_ostream& out) { + if ( handlers[EventType::UNIT_DEATH].empty() ) { + return; + } + + for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { + df::unit* unit = df::global::world->units.active[a]; + if ( unit->counters.death_id == -1 ) { + livingUnits.insert(unit->id); + continue; + } + //dead: if dead since last check, trigger events + if ( livingUnits.find(unit->id) == livingUnits.end() ) + continue; + + for ( auto i = handlers[EventType::UNIT_DEATH].begin(); i != handlers[EventType::UNIT_DEATH].end(); i++ ) { + (*i).second.eventHandler(out, (void*)unit->id); + } + livingUnits.erase(unit->id); + } +} + diff --git a/plugins/eventExample.cpp b/plugins/eventExample.cpp index c2774f422..2951c7f82 100644 --- a/plugins/eventExample.cpp +++ b/plugins/eventExample.cpp @@ -13,11 +13,13 @@ DFHACK_PLUGIN("eventExample"); void jobInitiated(color_ostream& out, void* job); void jobCompleted(color_ostream& out, void* job); void timePassed(color_ostream& out, void* ptr); +void unitDeath(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { EventManager::EventHandler initiateHandler(jobInitiated); EventManager::EventHandler completeHandler(jobCompleted); EventManager::EventHandler timeHandler(timePassed); + EventManager::EventHandler deathHandler(unitDeath); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); @@ -26,6 +28,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Sat, 15 Dec 2012 16:49:13 -0500 Subject: [PATCH 047/136] EventManager: added item creation event. --- library/include/modules/EventManager.h | 4 +-- library/modules/EventManager.cpp | 42 ++++++++++++++++++++++++-- plugins/eventExample.cpp | 18 +++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 0794b29e3..31ac01c18 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -17,8 +17,8 @@ namespace DFHack { JOB_INITIATED, JOB_COMPLETED, UNIT_DEATH, - //ITEM_CREATED, - EVENT_MAX=UNIT_DEATH + ITEM_CREATED, + EVENT_MAX }; } diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 7e97cc3a4..63455b664 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -3,8 +3,9 @@ #include "modules/Job.h" #include "modules/World.h" -#include "df/job.h" #include "df/global_objects.h" +#include "df/item.h" +#include "df/job.h" #include "df/job_list_link.h" #include "df/unit.h" #include "df/world.h" @@ -87,11 +88,13 @@ static void manageTickEvent(color_ostream& out); static void manageJobInitiatedEvent(color_ostream& out); static void manageJobCompletedEvent(color_ostream& out); static void manageUnitDeathEvent(color_ostream& out); +static void manageItemCreationEvent(color_ostream& out); static uint32_t lastTick = 0; static int32_t lastJobId = -1; static map prevJobs; static set livingUnits; +static int32_t nextItem; void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { if ( event == DFHack::SC_MAP_UNLOADED ) { @@ -103,6 +106,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event prevJobs.clear(); tickQueue.clear(); livingUnits.clear(); + nextItem = -1; } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -113,6 +117,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event tickQueue.clear(); tickQueue.insert(newTickQueue.begin(), newTickQueue.end()); + + nextItem = *df::global::item_next_id; } } @@ -130,7 +136,8 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { manageJobInitiatedEvent(out); manageJobCompletedEvent(out); manageUnitDeathEvent(out); - + manageItemCreationEvent(out); + return; } @@ -241,3 +248,34 @@ static void manageUnitDeathEvent(color_ostream& out) { } } +static void manageItemCreationEvent(color_ostream& out) { + if ( handlers[EventType::ITEM_CREATED].empty() ) { + return; + } + + if ( nextItem >= *df::global::item_next_id ) { + return; + } + + size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false); + for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) { + df::item* item = df::global::world->items.all[a]; + //invaders + if ( item->flags.bits.foreign ) + continue; + //traders who bring back your items? + if ( item->flags.bits.trader ) + continue; + //migrants + if ( item->flags.bits.owned ) + continue; + //spider webs don't count + if ( item->flags.bits.spider_web ) + continue; + for ( auto i = handlers[EventType::ITEM_CREATED].begin(); i != handlers[EventType::ITEM_CREATED].end(); i++ ) { + (*i).second.eventHandler(out, (void*)item->id); + } + } + nextItem = *df::global::item_next_id; +} + diff --git a/plugins/eventExample.cpp b/plugins/eventExample.cpp index 2951c7f82..04657f1fd 100644 --- a/plugins/eventExample.cpp +++ b/plugins/eventExample.cpp @@ -6,6 +6,9 @@ #include "modules/EventManager.h" #include "DataDefs.h" +#include "df/item.h" +#include "df/world.h" + using namespace DFHack; DFHACK_PLUGIN("eventExample"); @@ -14,12 +17,14 @@ void jobInitiated(color_ostream& out, void* job); void jobCompleted(color_ostream& out, void* job); void timePassed(color_ostream& out, void* ptr); void unitDeath(color_ostream& out, void* ptr); +void itemCreate(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { EventManager::EventHandler initiateHandler(jobInitiated); EventManager::EventHandler completeHandler(jobCompleted); EventManager::EventHandler timeHandler(timePassed); EventManager::EventHandler deathHandler(unitDeath); + EventManager::EventHandler itemHandler(itemCreate); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); @@ -29,6 +34,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vectoritems.all, (int32_t)ptr); + if ( item_index == -1 ) { + out.print("%s, %d: Error.\n", __FILE__, __LINE__); + } + df::item* item = df::global::world->items.all[item_index]; + df::item_type type = item->getType(); + df::coord pos = item->pos; + out.print("Item created: %d, %s, at (%d,%d,%d)\n", (int32_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); +} + From 935058f0a5edbdc52476d4a5e33d766f6fb650bd Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 15 Dec 2012 17:43:41 -0500 Subject: [PATCH 048/136] EventManager: moved files around, made eventExample not run by default, and got rid of the silly NONE event type. --- library/include/modules/EventManager.h | 1 - plugins/CMakeLists.txt | 1 - plugins/devel/CMakeLists.txt | 1 + plugins/{ => devel}/eventExample.cpp | 13 ++++++++++++- 4 files changed, 13 insertions(+), 3 deletions(-) rename plugins/{ => devel}/eventExample.cpp (87%) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 31ac01c18..1a6f15245 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -12,7 +12,6 @@ namespace DFHack { namespace EventManager { namespace EventType { enum EventType { - NONE, TICK, JOB_INITIATED, JOB_COMPLETED, diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 16fffd422..8aeeee8c3 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -130,7 +130,6 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) - DFHACK_PLUGIN(eventExample eventExample.cpp) endif() diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..80d627fa9 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,6 +18,7 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) +DFHACK_PLUGIN(eventExample eventExample.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/eventExample.cpp b/plugins/devel/eventExample.cpp similarity index 87% rename from plugins/eventExample.cpp rename to plugins/devel/eventExample.cpp index 04657f1fd..2099be110 100644 --- a/plugins/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -9,7 +9,10 @@ #include "df/item.h" #include "df/world.h" +#include + using namespace DFHack; +using namespace std; DFHACK_PLUGIN("eventExample"); @@ -19,7 +22,15 @@ void timePassed(color_ostream& out, void* ptr); void unitDeath(color_ostream& out, void* ptr); void itemCreate(color_ostream& out, void* ptr); +command_result eventExample(color_ostream& out, vector& parameters); + + DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand("eventExample", "Sets up a few event triggers.",eventExample)); + return CR_OK; +} + +command_result eventExample(color_ostream& out, vector& parameters) { EventManager::EventHandler initiateHandler(jobInitiated); EventManager::EventHandler completeHandler(jobCompleted); EventManager::EventHandler timeHandler(timePassed); @@ -35,7 +46,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Sat, 15 Dec 2012 18:08:59 -0500 Subject: [PATCH 049/136] EventManager: Allowed absolute time registration. --- library/include/modules/EventManager.h | 2 +- library/modules/EventManager.cpp | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 1a6f15245..c1c09da7d 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -36,7 +36,7 @@ namespace DFHack { }; DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); - DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin); + DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false); DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin); DFHACK_EXPORT void unregisterAll(Plugin* plugin); void manageEvents(color_ostream& out); diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 63455b664..553a69668 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -1,4 +1,5 @@ #include "Core.h" +#include "Console.h" #include "modules/EventManager.h" #include "modules/Job.h" #include "modules/World.h" @@ -33,11 +34,17 @@ void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handlers[e].insert(pair(plugin, handler)); } -void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin) { +void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); if ( !Core::getInstance().isWorldLoaded() ) { tick = 0; + if ( absolute ) { + Core::getInstance().getConsole().print("Warning: absolute flag will not be honored.\n"); + } + } + if ( absolute ) { + tick = 0; } tickQueue.insert(pair(tick+(uint32_t)when, handler)); From 3e5537e3213d2c6d85ec783314b07cbf8efa4137 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 15 Dec 2012 18:47:51 -0500 Subject: [PATCH 050/136] autoSyndrome: made it use EventManager. --- plugins/autoSyndrome.cpp | 151 ++++++++++++--------------------------- 1 file changed, 47 insertions(+), 104 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 1298764de..0f8b1ced3 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -2,6 +2,11 @@ #include "Export.h" #include "DataDefs.h" #include "Core.h" + +#include "modules/EventManager.h" +#include "modules/Job.h" +#include "modules/Maps.h" + #include "df/caste_raw.h" #include "df/creature_raw.h" #include "df/global_objects.h" @@ -18,8 +23,6 @@ #include "df/general_ref.h" #include "df/general_ref_type.h" #include "df/general_ref_unit_workerst.h" -#include "modules/Maps.h" -#include "modules/Job.h" #include #include @@ -92,15 +95,12 @@ Next, start a new fort in a new world, build a duck workshop, then have someone */ const int32_t ticksPerYear = 403200; -int32_t lastRun = 0; -unordered_map prevJobs; -unordered_map jobWorkers; bool enabled = true; DFHACK_PLUGIN("autoSyndrome"); command_result autoSyndrome(color_ostream& out, vector& parameters); -int32_t processJob(color_ostream& out, int32_t id); +void processJob(color_ostream& out, void* jobPtr); int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome); DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { @@ -123,6 +123,9 @@ DFhackCExport command_result plugin_init(color_ostream& out, vectorgetPluginByName("autoSyndrome"); + EventManager::EventHandler handle(processJob); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); return CR_OK; } @@ -130,85 +133,6 @@ DFhackCExport command_result plugin_shutdown(color_ostream& out) { return CR_OK; } -DFhackCExport command_result plugin_onupdate(color_ostream& out) { - if ( !enabled ) - return CR_OK; - if(DFHack::Maps::IsValid() == false) { - return CR_OK; - } - - //don't run more than once per tick - int32_t time = (*df::global::cur_year)*ticksPerYear + (*df::global::cur_year_tick); - if ( time <= lastRun ) - return CR_OK; - lastRun = time; - - //keep track of all queued jobs. When one completes (and is not cancelled), check if it's a boiling rock job, and if so, give the worker the appropriate syndrome - unordered_map jobs; - df::job_list_link* link = &df::global::world->job_list; - for( ; link != NULL; link = link->next ) { - df::job* item = link->item; - if ( item == NULL ) - continue; - //-1 usually means it hasn't been assigned yet. - if ( item->completion_timer < 0 ) - continue; - //if the completion timer is more than one, then the job will never disappear next tick unless it's cancelled - if ( item->completion_timer > 1 ) - continue; - - //only consider jobs that have been started - int32_t workerId = -1; - for ( size_t a = 0; a < item->references.size(); a++ ) { - if ( item->references[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) - continue; - if ( workerId != -1 ) { - out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__); - } - workerId = ((df::general_ref_unit_workerst*)item->references[a])->unit_id; - } - if ( workerId == -1 ) - continue; - - jobs[item->id] = item; - jobWorkers[item->id] = workerId; - } - - //if it's not on the job list anymore, and its completion timer was 0, then it probably finished and was not cancelled, so process the job. - for ( unordered_map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { - int32_t id = (*i).first; - df::job* completion = (*i).second; - if ( jobs.find(id) != jobs.end() ) - continue; - if ( completion->completion_timer > 0 ) - continue; - if ( processJob(out, id) < 0 ) { - //enabled = false; - return CR_FAILURE; - } - } - - //delete obselete job copies - for ( unordered_map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { - int32_t id = (*i).first; - df::job* oldJob = (*i).second; - DFHack::Job::deleteJobStruct(oldJob); - if ( jobs.find(id) == jobs.end() ) - jobWorkers.erase(id); - } - prevJobs.clear(); - - //make copies of the jobs we looked at this tick in case they disappear next frame. - for ( unordered_map::iterator i = jobs.begin(); i != jobs.end(); i++ ) { - int32_t id = (*i).first; - df::job* oldJob = (*i).second; - df::job* jobCopy = DFHack::Job::cloneJobStruct(oldJob); - prevJobs[id] = jobCopy; - } - - return CR_OK; -} - /*DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event e) { return CR_OK; }*/ @@ -217,6 +141,7 @@ command_result autoSyndrome(color_ostream& out, vector& parameters) { if ( parameters.size() > 1 ) return CR_WRONG_USAGE; + bool wasEnabled = enabled; if ( parameters.size() == 1 ) { if ( parameters[0] == "enable" ) { enabled = true; @@ -232,22 +157,31 @@ command_result autoSyndrome(color_ostream& out, vector& parameters) { } out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled"); + if ( enabled == wasEnabled ) + return CR_OK; + + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome"); + if ( enabled ) { + EventManager::EventHandler handle(processJob); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + } else { + EventManager::unregisterAll(me); + } return CR_OK; } -int32_t processJob(color_ostream& out, int32_t jobId) { - df::job* job = prevJobs[jobId]; +void processJob(color_ostream& out, void* jobPtr) { + df::job* job = (df::job*)jobPtr; if ( job == NULL ) { - out.print("Error %s line %d: couldn't find job %d.\n", __FILE__, __LINE__, jobId); - return -1; + out.print("Error %s line %d: null job.\n", __FILE__, __LINE__); + return; } + if ( job->completion_timer > 0 ) + return; if ( job->job_type != df::job_type::CustomReaction ) - return 0; + return; - //find the custom reaction raws and see if we have any special tags there - //out.print("job: \"%s\"\n", job->reaction_name.c_str()); - df::reaction* reaction = NULL; for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) { df::reaction* candidate = df::global::world->raws.reactions[a]; @@ -258,18 +192,27 @@ int32_t processJob(color_ostream& out, int32_t jobId) { } if ( reaction == NULL ) { out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() ); - return -1; + return; } - - if ( jobWorkers.find(jobId) == jobWorkers.end() ) { - out.print("%s, line %d: couldn't find worker for job %d.\n", __FILE__, __LINE__, jobId); - return -1; + + int32_t workerId = -1; + for ( size_t a = 0; a < job->references.size(); a++ ) { + if ( job->references[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) + continue; + if ( workerId != -1 ) { + out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__); + } + workerId = ((df::general_ref_unit_workerst*)job->references[a])->unit_id; + if (workerId == -1) { + out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__); + continue; + } } - int32_t workerId = jobWorkers[jobId]; + int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId); if ( workerIndex < 0 ) { out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); - return -1; + return; } df::unit* unit = df::global::world->units.all[workerIndex]; df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; @@ -333,7 +276,7 @@ int32_t processJob(color_ostream& out, int32_t jobId) { if ( syndrome->syn_affected_creature_1.size() != syndrome->syn_affected_creature_2.size() ) { out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__); - return -1; + return; } for ( size_t c = 0; c < syndrome->syn_affected_creature_1.size(); c++ ) { if ( creature_name != *syndrome->syn_affected_creature_1[c] ) @@ -357,13 +300,13 @@ int32_t processJob(color_ostream& out, int32_t jobId) { continue; } if ( giveSyndrome(out, workerId, syndrome) < 0 ) - return -1; + return; } } if ( !foundIt ) - return 0; + return; - return -2; + return; } /* From 75db99a3c79c6f410c8db3e13117ff9ef5911422 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 15 Dec 2012 18:49:45 -0500 Subject: [PATCH 051/136] autoSyndrome: deleted an unused constant. --- plugins/autoSyndrome.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 0f8b1ced3..f2c86bdfd 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -94,7 +94,6 @@ reaction_duck Next, start a new fort in a new world, build a duck workshop, then have someone become a duck. */ -const int32_t ticksPerYear = 403200; bool enabled = true; DFHACK_PLUGIN("autoSyndrome"); From 8bf359ba028093e8187c78ab3bec592e1519deb7 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 15 Dec 2012 21:18:06 -0600 Subject: [PATCH 052/136] Autolabor: add labor for ivory & horn crafts (yawn) --- plugins/autolabor.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 49c2e4174..3d0af7289 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -886,7 +886,9 @@ private: case df::item_type::BOULDER: return df::unit_labor::STONE_CRAFT; case df::item_type::NONE: - if (j->material_category.bits.bone) + if (j->material_category.bits.bone || + j->material_category.bits.horn || + j->material_category.bits.tooth) return df::unit_labor::BONE_CARVE; else { From 4ac6d9c0c39bffe1a16bd3ae638650047f6a4c3b Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 16 Dec 2012 00:03:26 -0600 Subject: [PATCH 053/136] Autolabor: add a number of destroy furniture labors (all "haul furniture") --- plugins/autolabor.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 3d0af7289..a2bdb4c9f 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -853,7 +853,24 @@ private: return df::unit_labor::PLANT; case df::building_type::Trap: return df::unit_labor::MECHANIC; + case df::building_type::Chair: + case df::building_type::Bed: + case df::building_type::Table: + case df::building_type::Coffin: + case df::building_type::Door: + case df::building_type::Floodgate: + case df::building_type::Box: + case df::building_type::Weaponrack: + case df::building_type::Armorstand: + case df::building_type::Cabinet: + case df::building_type::Statue: + case df::building_type::WindowGlass: + case df::building_type::WindowGem: case df::building_type::Cage: + case df::building_type::NestBox: + case df::building_type::TractionBench: + case df::building_type::Slab: + case df::building_type::Chain: return df::unit_labor::HAUL_FURNITURE; } From 3951d4d204ddaeaad7f393fadae3a98f27c69c4d Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 16 Dec 2012 15:39:39 -0500 Subject: [PATCH 054/136] EventManager: made it safe to register/unregister while events are being triggered. --- library/modules/EventManager.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 553a69668..d8ea0d5de 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -173,13 +173,14 @@ static void manageJobInitiatedEvent(color_ostream& out) { if ( lastJobId+1 == *df::global::job_next_id ) { return; //no new jobs } + multimap copy(handlers[EventType::JOB_INITIATED].begin(), handlers[EventType::JOB_INITIATED].end()); for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; if ( link->item->id <= lastJobId ) continue; - for ( multimap::iterator i = handlers[EventType::JOB_INITIATED].begin(); i != handlers[EventType::JOB_INITIATED].end(); i++ ) { + for ( auto i = copy.begin(); i != copy.end(); i++ ) { (*i).second.eventHandler(out, (void*)link->item); } } @@ -193,6 +194,7 @@ static void manageJobCompletedEvent(color_ostream& out) { return; } + multimap copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end()); map nowJobs; for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { if ( link->item == NULL ) @@ -205,7 +207,7 @@ static void manageJobCompletedEvent(color_ostream& out) { continue; //recently finished or cancelled job! - for ( multimap::iterator j = handlers[EventType::JOB_COMPLETED].begin(); j != handlers[EventType::JOB_COMPLETED].end(); j++ ) { + for ( auto j = copy.begin(); j != copy.end(); j++ ) { (*j).second.eventHandler(out, (void*)(*i).second); } } @@ -238,6 +240,7 @@ static void manageUnitDeathEvent(color_ostream& out) { return; } + multimap copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end()); for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { df::unit* unit = df::global::world->units.active[a]; if ( unit->counters.death_id == -1 ) { @@ -248,7 +251,7 @@ static void manageUnitDeathEvent(color_ostream& out) { if ( livingUnits.find(unit->id) == livingUnits.end() ) continue; - for ( auto i = handlers[EventType::UNIT_DEATH].begin(); i != handlers[EventType::UNIT_DEATH].end(); i++ ) { + for ( auto i = copy.begin(); i != copy.end(); i++ ) { (*i).second.eventHandler(out, (void*)unit->id); } livingUnits.erase(unit->id); @@ -264,6 +267,7 @@ static void manageItemCreationEvent(color_ostream& out) { return; } + multimap copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end()); size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false); for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) { df::item* item = df::global::world->items.all[a]; @@ -279,7 +283,7 @@ static void manageItemCreationEvent(color_ostream& out) { //spider webs don't count if ( item->flags.bits.spider_web ) continue; - for ( auto i = handlers[EventType::ITEM_CREATED].begin(); i != handlers[EventType::ITEM_CREATED].end(); i++ ) { + for ( auto i = copy.begin(); i != copy.end(); i++ ) { (*i).second.eventHandler(out, (void*)item->id); } } From 78aab90f3aa8a11bcdf741328684d8ed4fec4481 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 16 Dec 2012 16:27:08 -0500 Subject: [PATCH 055/136] EventManager: whitespace. --- library/modules/EventManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index d8ea0d5de..187a95819 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -188,7 +188,6 @@ static void manageJobInitiatedEvent(color_ostream& out) { lastJobId = *df::global::job_next_id - 1; } - static void manageJobCompletedEvent(color_ostream& out) { if ( handlers[EventType::JOB_COMPLETED].empty() ) { return; From 01e5e938257ada98c2582b4b11ab84b8cb0f5035 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 16 Dec 2012 23:26:50 -0500 Subject: [PATCH 056/136] Renamed Maps::canWalkBetween to Maps::canPathBetween and added Maps::canWalkBetween, which does what it says. --- library/LuaApi.cpp | 2 +- library/include/modules/Maps.h | 1 + library/modules/Maps.cpp | 46 +++++++++++++++++- plugins/devel/CMakeLists.txt | 1 + plugins/devel/walkBetween.cpp | 87 ++++++++++++++++++++++++++++++++++ plugins/fix-armory.cpp | 2 +- 6 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 plugins/devel/walkBetween.cpp diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 0151ed404..720ccd21f 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1309,7 +1309,7 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPM(Maps, enableBlockUpdates), WRAPM(Maps, getGlobalInitFeature), WRAPM(Maps, getLocalInitFeature), - WRAPM(Maps, canWalkBetween), + WRAPM(Maps, canPathBetween), WRAPM(Maps, spawnFlow), WRAPN(hasTileAssignment, hasTileAssignment), WRAPN(getTileAssignment, getTileAssignment), diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 632e8ec13..2655268d7 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -308,6 +308,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, /// remove a block event from the block by address extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); +DFHACK_EXPORT bool canPathBetween(df::coord pos1, df::coord pos2); DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); } } diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 482b950ba..0db69ea82 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -521,7 +521,7 @@ bool Maps::ReadGeology(vector > *layer_mats, vector return true; } -bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) +bool Maps::canPathBetween(df::coord pos1, df::coord pos2) { auto block1 = getTileBlock(pos1); auto block2 = getTileBlock(pos2); @@ -535,6 +535,50 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) return tile1 && tile1 == tile2; } +bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) +{ + bool b = canPathBetween(pos1, pos2); + if ( !b ) + return false; + + int32_t dx = pos1.x-pos2.x; + int32_t dy = pos1.y-pos2.y; + int32_t dz = pos1.z-pos2.z; + + if ( dx*dx > 1 || dy*dy > 1 || dz*dz > 1 ) + return false; + + if ( dz == 0 ) + return true; + + df::tiletype* type1 = Maps::getTileType(pos1); + df::tiletype* type2 = Maps::getTileType(pos2); + + df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type1); + df::tiletype_shape shape2 = ENUM_ATTR(tiletype,shape,*type2); + + if ( pos2.z < pos1.z ) { + df::tiletype_shape temp = shape1; + shape1 = shape2; + shape2 = temp; + } + if ( dx == 0 && dy == 0 ) { + if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == shape1 ) + return true; + if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == tiletype_shape::STAIR_DOWN ) + return true; + if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_UPDOWN ) + return true; + return false; + } + + //diagonal up: has to be a ramp + if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP ) + return true; + + return false; +} + #define COPY(a,b) memcpy(&a,&b,sizeof(a)) MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..41fcd8130 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -21,3 +21,4 @@ DFHACK_PLUGIN(vshook vshook.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() +DFHACK_PLUGIN(walkBetween walkBetween.cpp) diff --git a/plugins/devel/walkBetween.cpp b/plugins/devel/walkBetween.cpp new file mode 100644 index 000000000..9dafe4bb8 --- /dev/null +++ b/plugins/devel/walkBetween.cpp @@ -0,0 +1,87 @@ + +#include "Core.h" +#include +#include +#include + +// DF data structure definition headers +#include "DataDefs.h" +#include "df/world.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" + +using namespace DFHack; +using namespace df::enums; + + + +command_result walkBetween (color_ostream &out, std::vector & parameters); + +// A plugin must be able to return its name and version. +// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case +DFHACK_PLUGIN("walkBetween"); + +// Mandatory init function. If you have some global state, create it here. +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + // Fill the command list with your commands. + commands.push_back(PluginCommand( + "walkBetween", "Do nothing, look pretty.", + walkBetween, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command does nothing at all.\n" + )); + return CR_OK; +} + +// This is called right before the plugin library is removed from memory. +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + // You *MUST* kill all threads you created before this returns. + // If everything fails, just return CR_FAILURE. Your plugin will be + // in a zombie state, but things won't crash. + return CR_OK; +} + +// Called to notify the plugin about important state changes. +// Invoked with DF suspended, and always before the matching plugin_onupdate. +// More event codes may be added in the future. +/* +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_GAME_LOADED: + // initialize from the world just loaded + break; + case SC_GAME_UNLOADED: + // cleanup + break; + default: + break; + } + return CR_OK; +} +*/ + +// Whatever you put here will be done in each game step. Don't abuse it. +// It's optional, so you can just comment it out like this if you don't need it. +/* +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + // whetever. You don't need to suspend DF execution here. + return CR_OK; +} +*/ + +df::coord prev; + +// A command! It sits around and looks pretty. And it's nice and friendly. +command_result walkBetween (color_ostream &out, std::vector & parameters) +{ + df::coord bob = Gui::getCursorPos(); + out.print("(%d,%d,%d), (%d,%d,%d): canWalkBetween = %d, canPathBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canWalkBetween(prev, bob), Maps::canPathBetween(prev,bob)); + + prev = bob; + return CR_OK; +} diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index b937d40e8..e54639960 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -475,7 +475,7 @@ static bool try_store_item(df::building *target, df::item *item) df::coord tpos(target->centerx, target->centery, target->z); df::coord ipos = Items::getPosition(item); - if (!Maps::canWalkBetween(tpos, ipos)) + if (!Maps::canPathBetween(tpos, ipos)) return false; // Check if the target has enough space left From d2be8f18e131ac5d2c763703b8fe62c53a420e51 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 16 Dec 2012 23:30:35 -0500 Subject: [PATCH 057/136] canWalkBetween: forgot a case with stairs. --- library/modules/Maps.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 0db69ea82..4b468defc 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -569,6 +569,8 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) return true; if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_UPDOWN ) return true; + if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_DOWN ) + return true; return false; } From 1a6a09281b8b3e5d6666bec376bfe526d91b47a3 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 16 Dec 2012 23:37:15 -0500 Subject: [PATCH 058/136] canWalkBetween: forgot a case with ramps. --- library/modules/Maps.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 4b468defc..ff02bcf60 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -551,17 +551,18 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) if ( dz == 0 ) return true; + if ( pos2.z < pos1.z ) { + df::coord temp = pos1; + pos1 = pos2; + pos2 = temp; + } + df::tiletype* type1 = Maps::getTileType(pos1); df::tiletype* type2 = Maps::getTileType(pos2); df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type1); df::tiletype_shape shape2 = ENUM_ATTR(tiletype,shape,*type2); - if ( pos2.z < pos1.z ) { - df::tiletype_shape temp = shape1; - shape1 = shape2; - shape2 = temp; - } if ( dx == 0 && dy == 0 ) { if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == shape1 ) return true; @@ -575,8 +576,12 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) } //diagonal up: has to be a ramp - if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP ) - return true; + if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP ) { + df::coord up = df::coord(pos1.x,pos1.y,pos1.z+1); + df::tiletype* typeUp = Maps::getTileType(up); + df::tiletype_shape shapeUp = ENUM_ATTR(tiletype,shape,*typeUp); + return shapeUp == tiletype_shape::RAMP_TOP; + } return false; } From 22837af8d7440f625e90d46714120c66643c508f Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 17 Dec 2012 00:25:14 -0500 Subject: [PATCH 059/136] canWalkBetween: fixed bug involving ramps. --- library/modules/Maps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index ff02bcf60..f736d385a 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -576,7 +576,7 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) } //diagonal up: has to be a ramp - if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP ) { + if ( shape1 == tiletype_shape::RAMP /*&& shape2 == tiletype_shape::RAMP*/ ) { df::coord up = df::coord(pos1.x,pos1.y,pos1.z+1); df::tiletype* typeUp = Maps::getTileType(up); df::tiletype_shape shapeUp = ENUM_ATTR(tiletype,shape,*typeUp); From a9fec84c72bfd795c2927eba3525fb58628792f5 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 18 Dec 2012 16:23:02 -0600 Subject: [PATCH 060/136] Autolabor: add paved roads. --- plugins/autolabor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index a2bdb4c9f..df1b1ddba 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -775,6 +775,7 @@ private: case df::building_type::Bridge: case df::building_type::ArcheryTarget: case df::building_type::WaterWheel: + case df::building_type::RoadPaved: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) From 555c754636aa8825a7561f634e7fa45528d20df3 Mon Sep 17 00:00:00 2001 From: expwnent Date: Tue, 18 Dec 2012 18:34:38 -0500 Subject: [PATCH 061/136] EventManager: added construction and building events. --- library/include/modules/EventManager.h | 2 + library/modules/EventManager.cpp | 118 +++++++++++++++++++++++-- plugins/devel/eventExample.cpp | 14 +++ 3 files changed, 127 insertions(+), 7 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index c1c09da7d..1c610564e 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -17,6 +17,8 @@ namespace DFHack { JOB_COMPLETED, UNIT_DEATH, ITEM_CREATED, + BUILDING, + CONSTRUCTION, EVENT_MAX }; } diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 187a95819..f2d7e0261 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -4,6 +4,8 @@ #include "modules/Job.h" #include "modules/World.h" +#include "df/building.h" +#include "df/construction.h" #include "df/global_objects.h" #include "df/item.h" #include "df/job.h" @@ -11,9 +13,10 @@ #include "df/unit.h" #include "df/world.h" -//#include #include -//#include +#include +#include + using namespace std; using namespace DFHack; using namespace EventManager; @@ -21,11 +24,13 @@ using namespace EventManager; /* * TODO: * error checking + * consider a typedef instead of a struct for EventHandler **/ //map > tickQueue; multimap tickQueue; +//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever multimap handlers[EventType::EVENT_MAX]; const uint32_t ticksPerYear = 403200; @@ -96,13 +101,31 @@ static void manageJobInitiatedEvent(color_ostream& out); static void manageJobCompletedEvent(color_ostream& out); static void manageUnitDeathEvent(color_ostream& out); static void manageItemCreationEvent(color_ostream& out); +static void manageBuildingEvent(color_ostream& out); +static void manageConstructionEvent(color_ostream& out); +//tick event static uint32_t lastTick = 0; + +//job initiated static int32_t lastJobId = -1; -static map prevJobs; -static set livingUnits; + +//job completed +static unordered_map prevJobs; + +//unit death +static unordered_set livingUnits; + +//item creation static int32_t nextItem; +//building +static int32_t nextBuilding; +static unordered_set buildings; + +//construction +static unordered_set constructions; + void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { if ( event == DFHack::SC_MAP_UNLOADED ) { lastTick = 0; @@ -114,6 +137,9 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event tickQueue.clear(); livingUnits.clear(); nextItem = -1; + nextBuilding = -1; + buildings.clear(); + constructions.clear(); } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -126,6 +152,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event tickQueue.insert(newTickQueue.begin(), newTickQueue.end()); nextItem = *df::global::item_next_id; + nextBuilding = *df::global::building_next_id; + constructions.insert(df::global::world->constructions.begin(), df::global::world->constructions.end()); } } @@ -144,6 +172,8 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { manageJobCompletedEvent(out); manageUnitDeathEvent(out); manageItemCreationEvent(out); + manageBuildingEvent(out); + manageConstructionEvent(out); return; } @@ -201,7 +231,7 @@ static void manageJobCompletedEvent(color_ostream& out) { nowJobs[link->item->id] = link->item; } - for ( map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { if ( nowJobs.find((*i).first) != nowJobs.end() ) continue; @@ -212,13 +242,13 @@ static void manageJobCompletedEvent(color_ostream& out) { } //erase old jobs, copy over possibly altered jobs - for ( map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { Job::deleteJobStruct((*i).second); } prevJobs.clear(); //create new jobs - for ( map::iterator j = nowJobs.begin(); j != nowJobs.end(); j++ ) { + for ( auto j = nowJobs.begin(); j != nowJobs.end(); j++ ) { /*map::iterator i = prevJobs.find((*j).first); if ( i != prevJobs.end() ) { continue; @@ -289,3 +319,77 @@ static void manageItemCreationEvent(color_ostream& out) { nextItem = *df::global::item_next_id; } +static void manageBuildingEvent(color_ostream& out) { + /* + * TODO: could be faster + * consider looking at jobs: building creation / destruction + **/ + if ( handlers[EventType::ITEM_CREATED].empty() ) + return; + + multimap copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end()); + //first alert people about new buildings + for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) { + int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a); + if ( index == -1 ) { + out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a); + } + buildings.insert(a); + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler bob = (*b).second; + bob.eventHandler(out, (void*)a); + } + } + nextBuilding = *df::global::building_next_id; + + //now alert people about destroyed buildings + unordered_set toDelete; + for ( auto a = buildings.begin(); a != buildings.end(); a++ ) { + int32_t id = *a; + int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id); + if ( index != -1 ) + continue; + toDelete.insert(id); + + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler bob = (*b).second; + bob.eventHandler(out, (void*)id); + } + } + + for ( auto a = toDelete.begin(); a != toDelete.end(); a++ ) { + int32_t id = *a; + buildings.erase(id); + } +} + +static void manageConstructionEvent(color_ostream& out) { + if ( handlers[EventType::CONSTRUCTION].empty() ) + return; + + unordered_set constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end()); + + multimap copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end()); + for ( auto a = constructions.begin(); a != constructions.end(); a++ ) { + df::construction* construction = *a; + if ( constructionsNow.find(construction) != constructionsNow.end() ) + continue; + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler handle = (*b).second; + handle.eventHandler(out, (void*)construction); + } + } + + for ( auto a = constructionsNow.begin(); a != constructionsNow.end(); a++ ) { + df::construction* construction = *a; + if ( constructions.find(construction) != constructions.end() ) + continue; + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler handle = (*b).second; + handle.eventHandler(out, (void*)construction); + } + } + + constructions.clear(); + constructions.insert(constructionsNow.begin(), constructionsNow.end()); +} diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index 2099be110..d93396d29 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -21,6 +21,8 @@ void jobCompleted(color_ostream& out, void* job); void timePassed(color_ostream& out, void* ptr); void unitDeath(color_ostream& out, void* ptr); void itemCreate(color_ostream& out, void* ptr); +void building(color_ostream& out, void* ptr); +void construction(color_ostream& out, void* ptr); command_result eventExample(color_ostream& out, vector& parameters); @@ -36,7 +38,10 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::EventHandler timeHandler(timePassed); EventManager::EventHandler deathHandler(unitDeath); EventManager::EventHandler itemHandler(itemCreate); + EventManager::EventHandler buildingHandler(building); + EventManager::EventHandler constructionHandler(construction); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); + EventManager::unregisterAll(me); EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); @@ -46,6 +51,8 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::registerTick(timeHandler, 8, me); EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, me); EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, me); + EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, me); + EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, me); out.print("Events registered.\n"); return CR_OK; } @@ -77,3 +84,10 @@ void itemCreate(color_ostream& out, void* ptr) { out.print("Item created: %d, %s, at (%d,%d,%d)\n", (int32_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); } +void building(color_ostream& out, void* ptr) { + out.print("Building created/destroyed: %d\n", (int32_t)ptr); +} + +void construction(color_ostream& out, void* ptr) { + out.print("Construction created/destroyed: 0x%X\n", ptr); +} From a93c0223a2999944b788092791df342d9131b6e3 Mon Sep 17 00:00:00 2001 From: expwnent Date: Tue, 18 Dec 2012 20:28:30 -0500 Subject: [PATCH 062/136] EventManager: unstable. Temp commit. --- library/include/modules/Buildings.h | 4 ++ library/include/modules/Constructions.h | 2 + library/modules/Buildings.cpp | 82 ++++++++++++++++++++++++- library/modules/Constructions.cpp | 5 ++ library/modules/EventManager.cpp | 22 +++++-- 5 files changed, 110 insertions(+), 5 deletions(-) diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 266aadcb8..5f3a2b48d 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -25,7 +25,9 @@ distribution. #pragma once #include "Export.h" #include "DataDefs.h" +#include "Types.h" #include "df/building.h" +#include "df/building_type.h" #include "df/civzone_type.h" #include "df/furnace_type.h" #include "df/workshop_type.h" @@ -178,5 +180,7 @@ DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector +#include +#include +#include #include +#include #include -#include using namespace std; +#include "ColorText.h" #include "VersionInfo.h" #include "MemAccess.h" #include "Types.h" @@ -77,6 +82,14 @@ using df::global::building_next_id; using df::global::process_jobs; using df::building_def; +struct CoordHash { + size_t operator()(const df::coord pos) const { + return pos.x*65537 + pos.y*17 + pos.z; + } +}; + +static unordered_map locationToBuilding; + static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile) { if (!extent.extents) @@ -222,6 +235,30 @@ df::building *Buildings::findAtTile(df::coord pos) if (!occ || !occ->bits.building) return NULL; + auto a = locationToBuilding.find(pos); + if ( a == locationToBuilding.end() ) { + cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <buildings.all, id); + if ( index == -1 ) { + cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <buildings.all[index]; + if (!building->isSettingOccupancy()) + return NULL; + + if (building->room.extents && building->isExtentShaped()) + { + auto etile = getExtentTile(building->room, pos); + if (!etile || !*etile) + return NULL; + } + return building; + + /* auto &vec = df::building::get_vector(); for (size_t i = 0; i < vec.size(); i++) { @@ -246,6 +283,7 @@ df::building *Buildings::findAtTile(df::coord pos) } return NULL; + */ } bool Buildings::findCivzonesAt(std::vector *pvec, df::coord pos) @@ -1063,3 +1101,45 @@ bool Buildings::deconstruct(df::building *bld) return true; } +void Buildings::updateBuildings(color_ostream& out, void* ptr) { + static unordered_map corner1; + static unordered_map corner2; + out.print("Updating buildings, %s %d\n", __FILE__, __LINE__); + int32_t id = (int32_t)ptr; + + if ( corner1.find(id) == corner1.end() ) { + //new building: mark stuff + int32_t index = df::building::binsearch_index(df::global::world->buildings.all, id); + if ( index == -1 ) { + out.print("%s, line %d: Couldn't find new building id=%d.\n", __FILE__, __LINE__, id); + exit(1); + } + df::building* building = df::global::world->buildings.all[index]; + df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); + df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); + + corner1[id] = p1; + corner2[id] = p2; + + for ( int32_t x = p1.x; x <= p2.x; x++ ) { + for ( int32_t y = p1.y; y <= p2.y; y++ ) { + df::coord pt(x,y,building->z); + locationToBuilding[pt] = id; + } + } + } else { + //existing building: destroy it + df::coord p1 = corner1[id]; + df::coord p2 = corner2[id]; + + for ( int32_t x = p1.x; x <= p2.x; x++ ) { + for ( int32_t y = p1.y; y <= p2.y; y++ ) { + df::coord pt(x,y,p1.z); + locationToBuilding.erase(pt); + } + } + + corner1.erase(id); + corner2.erase(id); + } +} diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 16c1f1b89..2153ce6a2 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -169,3 +169,8 @@ bool Constructions::designateRemove(df::coord pos, bool *immediate) return false; } + + +void Constructions::updateConstructions(color_ostream& out, void* ptr) { + //do stuff +} diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index f2d7e0261..b057adb01 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -1,5 +1,7 @@ #include "Core.h" #include "Console.h" +#include "modules/Buildings.h" +#include "modules/Constructions.h" #include "modules/EventManager.h" #include "modules/Job.h" #include "modules/World.h" @@ -127,6 +129,16 @@ static unordered_set buildings; static unordered_set constructions; void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { + static bool doOnce = false; + if ( !doOnce ) { + //TODO: put this somewhere else + doOnce = true; + EventHandler buildingHandler(Buildings::updateBuildings); + EventHandler constructionHandler(Constructions::updateConstructions); + DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL); + DFHack::EventManager::registerListener(EventType::CONSTRUCTION, constructionHandler, NULL); + out.print("Registered listeners.\n %d", __LINE__); + } if ( event == DFHack::SC_MAP_UNLOADED ) { lastTick = 0; lastJobId = -1; @@ -151,9 +163,9 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event tickQueue.insert(newTickQueue.begin(), newTickQueue.end()); - nextItem = *df::global::item_next_id; - nextBuilding = *df::global::building_next_id; - constructions.insert(df::global::world->constructions.begin(), df::global::world->constructions.end()); + nextItem = 0; + nextBuilding = 0; + lastTick = 0; } } @@ -324,7 +336,7 @@ static void manageBuildingEvent(color_ostream& out) { * TODO: could be faster * consider looking at jobs: building creation / destruction **/ - if ( handlers[EventType::ITEM_CREATED].empty() ) + if ( handlers[EventType::BUILDING].empty() ) return; multimap copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end()); @@ -361,6 +373,8 @@ static void manageBuildingEvent(color_ostream& out) { int32_t id = *a; buildings.erase(id); } + + out.print("Sent building event.\n %d", __LINE__); } static void manageConstructionEvent(color_ostream& out) { From 7972902c81408526d77821d31bb12d355f85a8ed Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 19 Dec 2012 20:30:37 -0500 Subject: [PATCH 063/136] stepBetween: named a few things better, and fixed a lot. --- library/include/modules/Maps.h | 2 +- library/modules/Maps.cpp | 99 ++++++++++++++++--- plugins/devel/CMakeLists.txt | 2 +- .../{walkBetween.cpp => stepBetween.cpp} | 12 +-- 4 files changed, 95 insertions(+), 20 deletions(-) rename plugins/devel/{walkBetween.cpp => stepBetween.cpp} (87%) diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 2655268d7..29d3d69ba 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -309,7 +309,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); DFHACK_EXPORT bool canPathBetween(df::coord pos1, df::coord pos2); -DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); +DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2); } } #endif diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index f736d385a..6517331ee 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -30,10 +30,12 @@ distribution. #include #include #include +#include using namespace std; #include "modules/Maps.h" #include "modules/MapCache.h" +#include "ColorText.h" #include "Error.h" #include "VersionInfo.h" #include "MemAccess.h" @@ -59,6 +61,7 @@ using namespace std; #include "df/z_level_flags.h" #include "df/region_map_entry.h" #include "df/flow_info.h" +#include "df/building_type.h" using namespace DFHack; using namespace df::enums; @@ -535,28 +538,35 @@ bool Maps::canPathBetween(df::coord pos1, df::coord pos2) return tile1 && tile1 == tile2; } -bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) +bool Maps::canStepBetween(df::coord pos1, df::coord pos2) { - bool b = canPathBetween(pos1, pos2); - if ( !b ) - return false; - - int32_t dx = pos1.x-pos2.x; - int32_t dy = pos1.y-pos2.y; - int32_t dz = pos1.z-pos2.z; + color_ostream& out = Core::getInstance().getConsole(); + int32_t dx = pos2.x-pos1.x; + int32_t dy = pos2.y-pos1.y; + int32_t dz = pos2.z-pos1.z; if ( dx*dx > 1 || dy*dy > 1 || dz*dz > 1 ) return false; - if ( dz == 0 ) - return true; - if ( pos2.z < pos1.z ) { df::coord temp = pos1; pos1 = pos2; pos2 = temp; } + df::map_block* block1 = getTileBlock(pos1); + df::map_block* block2 = getTileBlock(pos2); + + if ( !block1 || !block2 ) + return false; + + if ( !index_tile(block1->walkable,pos1) || !index_tile(block2->walkable,pos2) ) { + return false; + } + + if ( dz == 0 ) + return true; + df::tiletype* type1 = Maps::getTileType(pos1); df::tiletype* type2 = Maps::getTileType(pos2); @@ -564,6 +574,11 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) df::tiletype_shape shape2 = ENUM_ATTR(tiletype,shape,*type2); if ( dx == 0 && dy == 0 ) { + //check for forbidden hatches and floors and such + df::enums::tile_building_occ::tile_building_occ upOcc = index_tile(block2->occupancy,pos2).bits.building; + if ( upOcc == df::enums::tile_building_occ::Impassable || upOcc == df::enums::tile_building_occ::Obstacle || upOcc == df::enums::tile_building_occ::Floored ) + return false; + if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == shape1 ) return true; if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == tiletype_shape::STAIR_DOWN ) @@ -572,15 +587,75 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) return true; if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_DOWN ) return true; + if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP_TOP ) { + //it depends + //there has to be a wall next to the ramp + bool foundWall = false; + for ( int32_t x = -1; x <= 1; x++ ) { + for ( int32_t y = -1; y <= 1; y++ ) { + if ( x == 0 && y == 0 ) + continue; + df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z)); + df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type); + if ( shape1 == tiletype_shape::WALL ) { + foundWall = true; + x = 2; + break; + } + } + } + if ( !foundWall ) + return false; //unusable ramp + + //there has to be an unforbidden hatch above the ramp + if ( index_tile(block2->occupancy,pos2).bits.building != df::enums::tile_building_occ::Dynamic ) + return false; + //note that forbidden hatches have Floored occupancy. unforbidden ones have dynamic occupancy + df::building* building = Buildings::findAtTile(pos2); + if ( building == NULL ) { + out << __FILE__ << ", line " << __LINE__ << ": couldn't find hatch.\n"; + return false; + } + if ( building->getType() != df::enums::building_type::Hatch ) { + return false; + } + return true; + } return false; } //diagonal up: has to be a ramp if ( shape1 == tiletype_shape::RAMP /*&& shape2 == tiletype_shape::RAMP*/ ) { df::coord up = df::coord(pos1.x,pos1.y,pos1.z+1); + bool foundWall = false; + for ( int32_t x = -1; x <= 1; x++ ) { + for ( int32_t y = -1; y <= 1; y++ ) { + if ( x == 0 && y == 0 ) + continue; + df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z)); + df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type); + if ( shape1 == tiletype_shape::WALL ) { + foundWall = true; + x = 2; + break; + } + } + } + if ( !foundWall ) + return false; //unusable ramp df::tiletype* typeUp = Maps::getTileType(up); df::tiletype_shape shapeUp = ENUM_ATTR(tiletype,shape,*typeUp); - return shapeUp == tiletype_shape::RAMP_TOP; + if ( shapeUp != tiletype_shape::RAMP_TOP ) + return false; + + df::map_block* blockUp = getTileBlock(up); + if ( !blockUp ) + return false; + + df::enums::tile_building_occ::tile_building_occ occupancy = index_tile(blockUp->occupancy,up).bits.building; + if ( occupancy == df::enums::tile_building_occ::Obstacle || occupancy == df::enums::tile_building_occ::Floored || occupancy == df::enums::tile_building_occ::Impassable ) + return false; + return true; } return false; diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 41fcd8130..8b0778f86 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -21,4 +21,4 @@ DFHACK_PLUGIN(vshook vshook.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() -DFHACK_PLUGIN(walkBetween walkBetween.cpp) +DFHACK_PLUGIN(stepBetween stepBetween.cpp) diff --git a/plugins/devel/walkBetween.cpp b/plugins/devel/stepBetween.cpp similarity index 87% rename from plugins/devel/walkBetween.cpp rename to plugins/devel/stepBetween.cpp index 9dafe4bb8..c1bc3fa1a 100644 --- a/plugins/devel/walkBetween.cpp +++ b/plugins/devel/stepBetween.cpp @@ -16,19 +16,19 @@ using namespace df::enums; -command_result walkBetween (color_ostream &out, std::vector & parameters); +command_result stepBetween (color_ostream &out, std::vector & parameters); // A plugin must be able to return its name and version. // The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case -DFHACK_PLUGIN("walkBetween"); +DFHACK_PLUGIN("stepBetween"); // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { // Fill the command list with your commands. commands.push_back(PluginCommand( - "walkBetween", "Do nothing, look pretty.", - walkBetween, false, /* true means that the command can't be used from non-interactive user interface */ + "stepBetween", "Do nothing, look pretty.", + stepBetween, false, /* true means that the command can't be used from non-interactive user interface */ // Extended help string. Used by CR_WRONG_USAGE and the help command: " This command does nothing at all.\n" )); @@ -77,10 +77,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) df::coord prev; // A command! It sits around and looks pretty. And it's nice and friendly. -command_result walkBetween (color_ostream &out, std::vector & parameters) +command_result stepBetween (color_ostream &out, std::vector & parameters) { df::coord bob = Gui::getCursorPos(); - out.print("(%d,%d,%d), (%d,%d,%d): canWalkBetween = %d, canPathBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canWalkBetween(prev, bob), Maps::canPathBetween(prev,bob)); + out.print("(%d,%d,%d), (%d,%d,%d): canWalkBetween = %d, canPathBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canPathBetween(prev,bob)); prev = bob; return CR_OK; From a4dc79565a95bf6e73e0145e28b03e646c051730 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 22 Dec 2012 00:13:07 -0500 Subject: [PATCH 064/136] AutoSyndrome: allowed for triggering DFHack commands from in game reactions. --- plugins/autoSyndrome.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index f2c86bdfd..6e5b7f636 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -234,12 +234,32 @@ void processJob(color_ostream& out, void* jobPtr) { //must be a boiling rock syndrome df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index]; - if ( inorganic->material.heat.boiling_point > 10000 ) - continue; + if ( inorganic->material.heat.boiling_point > 10000 ) { + //continue; + } for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) { //add each syndrome to the guy who did the job df::syndrome* syndrome = inorganic->material.syndrome[b]; + bool foundCommand = false; + string commandStr; + vector args; + for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) { + std::string* clazz = syndrome->syn_class[c]; + out.print("Class = %s\n", clazz->c_str()); + if ( foundCommand ) { + if ( commandStr == "" ) + commandStr = *clazz; + else + args.push_back(*clazz); + } else if ( *clazz == "command" ) { + foundCommand = true; + } + } + if ( commandStr != "" ) { + out.print("Running command thingy."); + Core::getInstance().runCommand(out, commandStr, args); + } //check that the syndrome applies to that guy /* * If there is no affected class or affected creature, then anybody who isn't immune is fair game. From 6d4c00374852cf2e95f8376693978a128af1f461 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 27 Dec 2012 02:52:54 -0600 Subject: [PATCH 065/136] Autolabor: fix dig-from-below bug regaring mining jobs, add overallocation detection and remediation, fix fishing and hunting --- plugins/autolabor.cpp | 63 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index df1b1ddba..85ef7076b 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -17,9 +17,14 @@ #include "modules/Units.h" #include "modules/World.h" +#include "modules/Maps.h" +#include "modules/MapCache.h" +#include "modules/Items.h" // DF data structure definition headers #include "DataDefs.h" +#include + #include #include #include @@ -62,11 +67,6 @@ #include #include -#include - -#include "modules/MapCache.h" -#include "modules/Items.h" - using std::string; using std::endl; using namespace DFHack; @@ -367,6 +367,8 @@ struct labor_info PersistentDataItem config; int active_dwarfs; + int idle_dwarfs; + int busy_dwarfs; int priority() { return config.ival(1); } void set_priority(int priority) { config.ival(1) = priority; } @@ -1566,7 +1568,12 @@ private: for (int y = 0; y < 16; y++) { if (bl->designation[x][y].bits.hidden) - continue; + { + df::coord p = bl->map_pos; + df::coord c(p.x, p.y, p.z-1); + if (Maps::getTileDesignation(c)->bits.hidden) + continue; + } df::tile_dig_designation dig = bl->designation[x][y].bits.dig; if (dig != df::enums::tile_dig_designation::No) @@ -1799,7 +1806,17 @@ private: out.print ("Dwarf %s is disabled, will not be assigned labors\n", dwarf->dwarf->name.first_name.c_str()); } else + { + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if (dwarf->dwarf->status.labors[l]) + labor_infos[l].idle_dwarfs++; + } + state = IDLE; + } } else { @@ -1817,6 +1834,8 @@ private: if (labor != df::unit_labor::NONE) { dwarf->using_labor = labor; + labor_infos[labor].busy_dwarfs++; + if (!dwarf->dwarf->status.labors[labor] && print_debug) { out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", @@ -1950,7 +1969,7 @@ public: if (l == df::unit_labor::NONE) continue; - labor_infos[l].active_dwarfs = 0; + labor_infos[l].active_dwarfs = labor_infos[l].busy_dwarfs = labor_infos[l].idle_dwarfs = 0; } // scan for specific buildings of interest @@ -2000,6 +2019,7 @@ public: labor_needed[df::unit_labor::HAUL_STONE] += world->stockpile.num_jobs[1]; labor_needed[df::unit_labor::HAUL_WOOD] += world->stockpile.num_jobs[2]; labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[3]; + labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[4]; labor_needed[df::unit_labor::HAUL_BODY] += world->stockpile.num_jobs[5]; labor_needed[df::unit_labor::HAUL_FOOD] += world->stockpile.num_jobs[6]; labor_needed[df::unit_labor::HAUL_REFUSE] += world->stockpile.num_jobs[7]; @@ -2014,11 +2034,11 @@ public: // add fishing & hunting - if (isOptionEnabled(CF_ALLOW_FISHING) && has_fishery) - labor_needed[df::unit_labor::FISH] ++; + labor_needed[df::unit_labor::FISH] = + (isOptionEnabled(CF_ALLOW_FISHING) && has_fishery) ? 1 : 0; - if (isOptionEnabled(CF_ALLOW_HUNTING) && has_butchers) - labor_needed[df::unit_labor::HUNT] ++; + labor_needed[df::unit_labor::HUNT] = + (isOptionEnabled(CF_ALLOW_HUNTING) && has_butchers) ? 1 : 0; /* add animal trainers */ for (auto a = df::global::ui->equipment.training_assignments.begin(); @@ -2029,12 +2049,21 @@ public: // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. } + /* adjust for over/under */ + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + labor_needed[l] = std::min(labor_needed[l], + std::max(1, (std::max(labor_infos[l].busy_dwarfs, labor_needed[l] - labor_infos[l].idle_dwarfs)))); + } + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - out.print ("labor_needed [%s] = %d, outside = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, - labor_outside[i->first]); + out.print ("labor_needed [%s] = %d, outside = %d, idle = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, + labor_outside[i->first], labor_infos[i->first].idle_dwarfs); } } @@ -2054,6 +2083,14 @@ public: } } + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if (labor_infos[l].idle_dwarfs == 0 && labor_infos[l].busy_dwarfs > 0) + pq.push(make_pair(std::min(labor_infos[l].time_since_last_assigned()/12, 25), l)); + } + if (print_debug) out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size()); From 6ab8c8c30ebf64a549ca16ba6149a404f77feca8 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 28 Dec 2012 07:58:29 -0600 Subject: [PATCH 066/136] Autolabor: change fishery build labor to CLEAN_FISH to control random acts of fish extermination; add build labor for wells. --- plugins/autolabor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 85ef7076b..e589b4b58 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -635,7 +635,7 @@ static df::unit_labor workshop_build_labor[] = /* Leatherworks */ df::unit_labor::LEATHER, /* Tanners */ df::unit_labor::TANNER, /* Clothiers */ df::unit_labor::CLOTHESMAKER, - /* Fishery */ df::unit_labor::FISH, + /* Fishery */ df::unit_labor::CLEAN_FISH, /* Still */ df::unit_labor::BREWER, /* Loom */ df::unit_labor::WEAVER, /* Quern */ df::unit_labor::MILLER, @@ -778,6 +778,7 @@ private: case df::building_type::ArcheryTarget: case df::building_type::WaterWheel: case df::building_type::RoadPaved: + case df::building_type::Well: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) From 0572e87d7b14c0be2982ce39bbde6b93f237390d Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 31 Dec 2012 13:50:44 -0500 Subject: [PATCH 067/136] SkyEternal: allocates new z-levels of sky as needed, or on request. --- plugins/CMakeLists.txt | 1 + plugins/skyEternal.cpp | 152 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 plugins/skyEternal.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..aa4f95056 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -127,6 +127,7 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(skyEternal skyEternal.cpp) endif() diff --git a/plugins/skyEternal.cpp b/plugins/skyEternal.cpp new file mode 100644 index 000000000..04a176a8d --- /dev/null +++ b/plugins/skyEternal.cpp @@ -0,0 +1,152 @@ + +#include "Core.h" +#include "Console.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/World.h" + +#include "df/construction.h" +#include "df/game_mode.h" +#include "df/map_block.h" +#include "df/world.h" +#include "df/z_level_flags.h" + +#include +#include +#include + +using namespace std; + +using namespace DFHack; +using namespace df::enums; + +command_result skyEternal (color_ostream &out, std::vector & parameters); + +DFHACK_PLUGIN("skyEternal"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "skyEternal", + "Creates new sky levels on request, or as you construct up.", + skyEternal, false, + "Usage:\n" + " skyEternal\n" + " creates one more z-level\n" + " skyEternal [n]\n" + " creates n more z-level(s)\n" + " skyEternal enable\n" + " enables monitoring of constructions\n" + " skyEternal disable\n" + " disable monitoring of constructions\n" + "\n" + "If construction monitoring is enabled, then the plugin will automatically create new sky z-levels as you construct upward.\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +/* +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_GAME_LOADED: + // initialize from the world just loaded + break; + case SC_GAME_UNLOADED: + // cleanup + break; + default: + break; + } + return CR_OK; +} +*/ + +static size_t constructionSize = 0; +static bool enabled = false; + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + if ( !enabled ) + return CR_OK; + if ( !Core::getInstance().isMapLoaded() ) + return CR_OK; + { + t_gamemodes mode; + if ( !Core::getInstance().getWorld()->ReadGameMode(mode) ) + return CR_FAILURE; + if ( mode.g_mode != df::enums::game_mode::DWARF ) + return CR_OK; + } + + if ( df::global::world->constructions.size() == constructionSize ) + return CR_OK; + int32_t zNow = df::global::world->map.z_count_block; + vector vec; + for ( size_t a = constructionSize; a < df::global::world->constructions.size(); a++ ) { + df::construction* construct = df::global::world->constructions[a]; + if ( construct->pos.z+2 < zNow ) + continue; + skyEternal(out, vec); + zNow = df::global::world->map.z_count_block; + ///break; + } + constructionSize = df::global::world->constructions.size(); + + return CR_OK; +} + +command_result skyEternal (color_ostream &out, std::vector & parameters) +{ + if ( parameters.size() > 1 ) + return CR_WRONG_USAGE; + if ( parameters.size() == 0 ) { + vector vec; + vec.push_back("1"); + return skyEternal(out, vec); + } + if (parameters[0] == "enable") { + enabled = true; + return CR_OK; + } + if (parameters[0] == "disable") { + enabled = false; + constructionSize = 0; + return CR_OK; + } + int32_t howMany = 0; + howMany = atoi(parameters[0].c_str()); + df::world* world = df::global::world; + CoreSuspender suspend; + int32_t x_count_block = world->map.x_count_block; + int32_t y_count_block = world->map.y_count_block; + for ( int32_t count = 0; count < howMany; count++ ) { + //change the size of the pointer stuff + int32_t z_count_block = world->map.z_count_block; + df::map_block**** block_index = world->map.block_index; + for ( int32_t a = 0; a < x_count_block; a++ ) { + for ( int32_t b = 0; b < y_count_block; b++ ) { + df::map_block** column = new df::map_block*[z_count_block+1]; + memcpy(column, block_index[a][b], z_count_block*sizeof(df::map_block*)); + column[z_count_block] = NULL; + delete[] block_index[a][b]; + block_index[a][b] = column; + } + } + df::z_level_flags* flags = new df::z_level_flags[z_count_block+1]; + memcpy(flags, world->map.z_level_flags, z_count_block*sizeof(df::z_level_flags)); + flags[z_count_block].whole = 0; + flags[z_count_block].bits.update = 1; + world->map.z_count_block++; + world->map.z_count++; + } + + return CR_OK; +} From 3a24565728b6a676b14b107b6ef97588b58cacc1 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 1 Jan 2013 15:12:45 -0600 Subject: [PATCH 068/136] Autolabor: add construction labor for hatch. --- plugins/autolabor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index e589b4b58..bfe435b1d 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -875,6 +875,7 @@ private: case df::building_type::TractionBench: case df::building_type::Slab: case df::building_type::Chain: + case df::building_type::Hatch: return df::unit_labor::HAUL_FURNITURE; } From d50aa24ebfe187f8acbb523764006e7ea23b612e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 1 Jan 2013 16:35:09 -0600 Subject: [PATCH 069/136] Autolabor: fix idle stepdown (seems to work much better), change some scoring weights, change autolabor list output to include busy and idle counters --- plugins/autolabor.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index bfe435b1d..5157bda61 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -2056,8 +2056,14 @@ public: { if (l == df::unit_labor::NONE) continue; - labor_needed[l] = std::min(labor_needed[l], - std::max(1, (std::max(labor_infos[l].busy_dwarfs, labor_needed[l] - labor_infos[l].idle_dwarfs)))); + if (labor_infos[l].idle_dwarfs > 0 && labor_needed[l] > labor_infos[l].busy_dwarfs) + { + if (print_debug) + out.print("reducing labor %s to %d (%d needed, %d busy, %d idle)\n", ENUM_KEY_STR(unit_labor, l).c_str(), + labor_infos[l].busy_dwarfs, + labor_needed[l], labor_infos[l].busy_dwarfs, labor_infos[l].idle_dwarfs); + labor_needed[l] = labor_infos[l].busy_dwarfs; + } } if (print_debug) @@ -2089,7 +2095,8 @@ public: { if (l == df::unit_labor::NONE) continue; - if (labor_infos[l].idle_dwarfs == 0 && labor_infos[l].busy_dwarfs > 0) + if (labor_infos[l].idle_dwarfs == 0 && labor_infos[l].busy_dwarfs > 0 && + (labor_infos[l].maximum_dwarfs() == 0 || labor_needed[l] < labor_infos[l].maximum_dwarfs())) pq.push(make_pair(std::min(labor_infos[l].time_since_last_assigned()/12, 25), l)); } @@ -2160,9 +2167,9 @@ public: score += 500; if (default_labor_infos[labor].tool != TOOL_NONE && d->has_tool[default_labor_infos[labor].tool]) - score += 3000; + score += 5000; if (d->has_children && labor_outside[labor]) - score -= 10000; + score -= 15000; if (d->armed && labor_outside[labor]) score += 5000; if (d->state == BUSY) @@ -2295,7 +2302,10 @@ void print_labor (df::unit_labor labor, color_ostream &out) out << ' '; out << "priority " << labor_infos[labor].priority() << ", maximum " << labor_infos[labor].maximum_dwarfs() - << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl; + << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs (" + << labor_infos[labor].busy_dwarfs << " busy, " + << labor_infos[labor].idle_dwarfs << " idle)" + << endl; } df::unit_labor lookup_labor_by_name (std::string& name) From 41615d044616b0f0790f8b950d1f7e18674deb2c Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 1 Jan 2013 17:53:24 -0600 Subject: [PATCH 070/136] Autolabor: adjust idle clawback to deal with "pickup equipment" for miners. --- plugins/autolabor.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 5157bda61..7068ad5fb 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1809,14 +1809,6 @@ private: } else { - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - if (dwarf->dwarf->status.labors[l]) - labor_infos[l].idle_dwarfs++; - } - state = IDLE; } } @@ -1836,7 +1828,6 @@ private: if (labor != df::unit_labor::NONE) { dwarf->using_labor = labor; - labor_infos[labor].busy_dwarfs++; if (!dwarf->dwarf->status.labors[labor] && print_debug) { @@ -1850,6 +1841,18 @@ private: dwarf->state = state; + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if (dwarf->dwarf->status.labors[l]) + if (state == IDLE) + labor_infos[l].idle_dwarfs++; + else if (state == BUSY) + labor_infos[l].busy_dwarfs++; + } + + if (print_debug) out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); @@ -2058,11 +2061,15 @@ public: continue; if (labor_infos[l].idle_dwarfs > 0 && labor_needed[l] > labor_infos[l].busy_dwarfs) { + int clawback = labor_infos[l].busy_dwarfs; + if (clawback == 0 && labor_needed[l] > 0) + clawback = 1; + if (print_debug) out.print("reducing labor %s to %d (%d needed, %d busy, %d idle)\n", ENUM_KEY_STR(unit_labor, l).c_str(), - labor_infos[l].busy_dwarfs, + clawback, labor_needed[l], labor_infos[l].busy_dwarfs, labor_infos[l].idle_dwarfs); - labor_needed[l] = labor_infos[l].busy_dwarfs; + labor_needed[l] = clawback; } } From 4e99841862735d8544bcade9f99010c02ac741c5 Mon Sep 17 00:00:00 2001 From: expwnent Date: Tue, 1 Jan 2013 22:22:31 -0500 Subject: [PATCH 071/136] EventManager: made Buildings module keep track of buildings so that it can do findAtTile in constant time. --- library/include/modules/Buildings.h | 1 + library/include/modules/Constructions.h | 2 -- library/modules/Buildings.cpp | 14 +++++++++++--- library/modules/Constructions.cpp | 5 ----- library/modules/EventManager.cpp | 12 +++++++----- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 5f3a2b48d..4e162be69 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -181,6 +181,7 @@ DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector corner1; +static unordered_map corner2; + +void Buildings::clearBuildings(color_ostream& out) { + corner1.clear(); + corner2.clear(); + locationToBuilding.clear(); +} + void Buildings::updateBuildings(color_ostream& out, void* ptr) { - static unordered_map corner1; - static unordered_map corner2; - out.print("Updating buildings, %s %d\n", __FILE__, __LINE__); + //out.print("Updating buildings, %s %d\n", __FILE__, __LINE__); int32_t id = (int32_t)ptr; if ( corner1.find(id) == corner1.end() ) { diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 2153ce6a2..16c1f1b89 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -169,8 +169,3 @@ bool Constructions::designateRemove(df::coord pos, bool *immediate) return false; } - - -void Constructions::updateConstructions(color_ostream& out, void* ptr) { - //do stuff -} diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index b057adb01..24bd6aeec 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -134,10 +134,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event //TODO: put this somewhere else doOnce = true; EventHandler buildingHandler(Buildings::updateBuildings); - EventHandler constructionHandler(Constructions::updateConstructions); DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL); - DFHack::EventManager::registerListener(EventType::CONSTRUCTION, constructionHandler, NULL); - out.print("Registered listeners.\n %d", __LINE__); + //out.print("Registered listeners.\n %d", __LINE__); } if ( event == DFHack::SC_MAP_UNLOADED ) { lastTick = 0; @@ -152,6 +150,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event nextBuilding = -1; buildings.clear(); constructions.clear(); + + Buildings::clearBuildings(out); } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -344,7 +344,9 @@ static void manageBuildingEvent(color_ostream& out) { for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) { int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a); if ( index == -1 ) { - out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a); + //out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a); + //the tricky thing is that when the game first starts, it's ok to skip buildings, but otherwise, if you skip buildings, something is probably wrong. TODO: make this smarter + continue; } buildings.insert(a); for ( auto b = copy.begin(); b != copy.end(); b++ ) { @@ -374,7 +376,7 @@ static void manageBuildingEvent(color_ostream& out) { buildings.erase(id); } - out.print("Sent building event.\n %d", __LINE__); + //out.print("Sent building event.\n %d", __LINE__); } static void manageConstructionEvent(color_ostream& out) { From b320fb25f3ff04458f007f4fa2986cd6f57b384e Mon Sep 17 00:00:00 2001 From: expwnent Date: Tue, 1 Jan 2013 23:56:31 -0500 Subject: [PATCH 072/136] AutoSyndrome: added smart arguments for location, worker id, and reaction id. --- plugins/autoSyndrome.cpp | 34 +++++++++++++++++++++++++++++----- plugins/devel/CMakeLists.txt | 1 + plugins/devel/printArgs.cpp | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 plugins/devel/printArgs.cpp diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 6e5b7f636..c18055455 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -180,7 +180,7 @@ void processJob(color_ostream& out, void* jobPtr) { if ( job->job_type != df::job_type::CustomReaction ) return; - + df::reaction* reaction = NULL; for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) { df::reaction* candidate = df::global::world->raws.reactions[a]; @@ -250,14 +250,38 @@ void processJob(color_ostream& out, void* jobPtr) { if ( foundCommand ) { if ( commandStr == "" ) commandStr = *clazz; - else - args.push_back(*clazz); - } else if ( *clazz == "command" ) { + else { + stringstream bob; + if ( *clazz == "\\LOCATION" ) { + bob << job->pos.x; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + + bob << job->pos.y; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + + bob << job->pos.z; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + } else if ( *clazz == "\\WORKER_ID" ) { + bob << workerId; + args.push_back(bob.str()); + } else if ( *clazz == "\\REACTION_INDEX" ) { + bob << reaction->index; + args.push_back(bob.str()); + } else { + args.push_back(*clazz); + } + } + } else if ( *clazz == "\\COMMAND" ) { foundCommand = true; } } if ( commandStr != "" ) { - out.print("Running command thingy."); Core::getInstance().runCommand(out, commandStr, args); } //check that the syndrome applies to that guy diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 80d627fa9..c57596e9c 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -19,6 +19,7 @@ DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) +DFHACK_PLUGIN(printArgs printArgs.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/devel/printArgs.cpp b/plugins/devel/printArgs.cpp new file mode 100644 index 000000000..051c7b1dc --- /dev/null +++ b/plugins/devel/printArgs.cpp @@ -0,0 +1,32 @@ + +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include + +using namespace DFHack; +using namespace std; + +command_result printArgs (color_ostream &out, std::vector & parameters); + +DFHACK_PLUGIN("printArgs"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "printArgs", "Print the arguments given.", + printArgs, false + )); + return CR_OK; +} + +command_result printArgs (color_ostream &out, std::vector & parameters) +{ + for ( size_t a = 0; a < parameters.size(); a++ ) { + out << "Argument " << (a+1) << ": \"" << parameters[a] << "\"" << endl; + } + return CR_OK; +} From c3b2ae21377037dff478d4dbd3ef0c6d89a6b405 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 11:07:56 -0500 Subject: [PATCH 073/136] EventManager: allowed plugins to specify how often they need events to be checked, in the event that monitoring is necessary. --- library/include/modules/EventManager.h | 4 +- library/modules/EventManager.cpp | 67 ++++++++++++++++++++++---- plugins/devel/eventExample.cpp | 12 ++--- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 1c610564e..a06439fcd 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -37,9 +37,9 @@ namespace DFHack { } }; - DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); + DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin); DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false); - DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin); + DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin); DFHACK_EXPORT void unregisterAll(Plugin* plugin); void manageEvents(color_ostream& out); void onStateChange(color_ostream& out, state_change_event event); diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 24bd6aeec..2725d80a3 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -34,11 +34,16 @@ multimap tickQueue; //TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever multimap handlers[EventType::EVENT_MAX]; +multimap pluginFrequencies[EventType::EVENT_MAX]; +map eventFrequency[EventType::EVENT_MAX]; +uint32_t eventLastTick[EventType::EVENT_MAX]; const uint32_t ticksPerYear = 403200; -void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) { +void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin) { handlers[e].insert(pair(plugin, handler)); + eventFrequency[e][freq]++; + pluginFrequencies[e].insert(pair(plugin, freq)); } void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) { @@ -59,7 +64,7 @@ void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plug return; } -void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) { +void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin) { for ( multimap::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) { if ( (*i).first != plugin ) break; @@ -69,6 +74,16 @@ void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handl break; } } + if ( eventFrequency[e].find(freq) == eventFrequency[e].end() ) { + Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); + return; + } + eventFrequency[e][freq]--; + if ( eventFrequency[e][freq] == 0 ) { + eventFrequency[e].erase(eventFrequency[e].find(freq)); + } else if ( eventFrequency[e][freq] < 0 ) { + Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); + } return; } @@ -95,6 +110,21 @@ void DFHack::EventManager::unregisterAll(Plugin* plugin) { for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { handlers[a].erase(plugin); } + + for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { + for ( auto b = pluginFrequencies[a].begin(); b != pluginFrequencies[a].end(); b++ ) { + if ( (*b).first != plugin ) + continue; + int32_t freq = (*b).second; + eventFrequency[a][freq]--; + if ( eventFrequency[a][freq] < 0 ) { + Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); + eventFrequency[a].erase(eventFrequency[a].find(freq)); + } else if ( eventFrequency[a][freq] == 0 ) { + eventFrequency[a].erase(eventFrequency[a].find(freq)); + } + } + } return; } @@ -134,7 +164,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event //TODO: put this somewhere else doOnce = true; EventHandler buildingHandler(Buildings::updateBuildings); - DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL); + DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, 100, NULL); //out.print("Registered listeners.\n %d", __LINE__); } if ( event == DFHack::SC_MAP_UNLOADED ) { @@ -175,17 +205,36 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { } uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); + if ( tick <= lastTick ) return; lastTick = tick; manageTickEvent(out); - manageJobInitiatedEvent(out); - manageJobCompletedEvent(out); - manageUnitDeathEvent(out); - manageItemCreationEvent(out); - manageBuildingEvent(out); - manageConstructionEvent(out); + if ( tick - eventLastTick[EventType::JOB_INITIATED] >= (*eventFrequency[EventType::JOB_INITIATED].begin()).first ) { + manageJobInitiatedEvent(out); + eventLastTick[EventType::JOB_INITIATED] = tick; + } + if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= (*eventFrequency[EventType::JOB_COMPLETED].begin()).first ) { + manageJobCompletedEvent(out); + eventLastTick[EventType::JOB_COMPLETED] = tick; + } + if ( tick - eventLastTick[EventType::UNIT_DEATH] >= (*eventFrequency[EventType::UNIT_DEATH].begin()).first ) { + manageUnitDeathEvent(out); + eventLastTick[EventType::UNIT_DEATH] = tick; + } + if ( tick - eventLastTick[EventType::ITEM_CREATED] >= (*eventFrequency[EventType::ITEM_CREATED].begin()).first ) { + manageItemCreationEvent(out); + eventLastTick[EventType::ITEM_CREATED] = tick; + } + if ( tick - eventLastTick[EventType::BUILDING] >= (*eventFrequency[EventType::BUILDING].begin()).first ) { + manageBuildingEvent(out); + eventLastTick[EventType::BUILDING] = tick; + } + if ( tick - eventLastTick[EventType::CONSTRUCTION] >= (*eventFrequency[EventType::CONSTRUCTION].begin()).first ) { + manageConstructionEvent(out); + eventLastTick[EventType::CONSTRUCTION] = tick; + } return; } diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index d93396d29..e6a3dd22f 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -43,16 +43,16 @@ command_result eventExample(color_ostream& out, vector& parameters) { Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::unregisterAll(me); - EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); + EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, 10, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, 5, me); EventManager::registerTick(timeHandler, 1, me); EventManager::registerTick(timeHandler, 2, me); EventManager::registerTick(timeHandler, 4, me); EventManager::registerTick(timeHandler, 8, me); - EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, me); - EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, me); - EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, me); - EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, me); + EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, 500, me); + EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, 1000, me); + EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, 500, me); + EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, 100, me); out.print("Events registered.\n"); return CR_OK; } From f680ef7ee34da948e39bcdf35824da1c661856ec Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 11:30:06 -0500 Subject: [PATCH 074/136] Made git ignore vim swap files. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9f2b009c6..041624475 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ dfhack/python/dist build/CPack*Config.cmake /cmakeall.bat + +# swap files for vim +*.swp From 5e2877be23a01c9817a6c626a826c1919522ccdb Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 13:44:56 -0500 Subject: [PATCH 075/136] AutoSyndrome: added options for worker only (vs all in building), allow multiple targets, and allow multiple syndromes. --- plugins/autoSyndrome.cpp | 210 +++++++++++++++++++++++++-------------- 1 file changed, 138 insertions(+), 72 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index c18055455..2c8023035 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -7,6 +7,7 @@ #include "modules/Job.h" #include "modules/Maps.h" +#include "df/building.h" #include "df/caste_raw.h" #include "df/creature_raw.h" #include "df/global_objects.h" @@ -21,6 +22,7 @@ #include "df/ui.h" #include "df/unit.h" #include "df/general_ref.h" +#include "df/general_ref_building_holderst.h" #include "df/general_ref_type.h" #include "df/general_ref_unit_workerst.h" @@ -124,7 +126,7 @@ DFhackCExport command_result plugin_init(color_ostream& out, vectorgetPluginByName("autoSyndrome"); EventManager::EventHandler handle(processJob); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, 5, me); return CR_OK; } @@ -162,13 +164,81 @@ command_result autoSyndrome(color_ostream& out, vector& parameters) { Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome"); if ( enabled ) { EventManager::EventHandler handle(processJob); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, 5, me); } else { EventManager::unregisterAll(me); } return CR_OK; } +bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df::unit* unit) { + df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; + df::caste_raw* caste = creature->caste[unit->caste]; + std::string& creature_name = creature->creature_id; + std::string& creature_caste = caste->caste_id; + //check that the syndrome applies to that guy + /* + * If there is no affected class or affected creature, then anybody who isn't immune is fair game. + * + * Otherwise, it works like this: + * add all the affected class creatures + * remove all the immune class creatures + * add all the affected creatures + * remove all the immune creatures + * you're affected if and only if you're in the remaining list after all of that + **/ + bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature.size() == 0; + for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) { + if ( applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) { + applies = true; + break; + } + } + } + for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) { + if ( !applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) { + applies = false; + break; + } + } + } + + if ( syndrome->syn_affected_creature.size() != syndrome->syn_affected_caste.size() ) { + out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__); + return false; + } + for ( size_t c = 0; c < syndrome->syn_affected_creature.size(); c++ ) { + if ( creature_name != *syndrome->syn_affected_creature[c] ) + continue; + if ( *syndrome->syn_affected_caste[c] == "ALL" || + *syndrome->syn_affected_caste[c] == creature_caste ) { + applies = true; + break; + } + } + for ( size_t c = 0; c < syndrome->syn_immune_creature.size(); c++ ) { + if ( creature_name != *syndrome->syn_immune_creature[c] ) + continue; + if ( *syndrome->syn_immune_caste[c] == "ALL" || + *syndrome->syn_immune_caste[c] == creature_caste ) { + applies = false; + break; + } + } + if ( !applies ) { + return false; + } + if ( giveSyndrome(out, workerId, syndrome) < 0 ) + return false; + return true; +} + void processJob(color_ostream& out, void* jobPtr) { df::job* job = (df::job*)jobPtr; if ( job == NULL ) { @@ -195,13 +265,13 @@ void processJob(color_ostream& out, void* jobPtr) { } int32_t workerId = -1; - for ( size_t a = 0; a < job->references.size(); a++ ) { - if ( job->references[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) + for ( size_t a = 0; a < job->general_refs.size(); a++ ) { + if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) continue; if ( workerId != -1 ) { out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__); } - workerId = ((df::general_ref_unit_workerst*)job->references[a])->unit_id; + workerId = ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id; if (workerId == -1) { out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__); continue; @@ -214,13 +284,32 @@ void processJob(color_ostream& out, void* jobPtr) { return; } df::unit* unit = df::global::world->units.all[workerIndex]; - df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; - df::caste_raw* caste = creature->caste[unit->caste]; - std::string& creature_name = creature->creature_id; - std::string& creature_caste = caste->caste_id; + //find the building that made it + int32_t buildingId = -1; + for ( size_t a = 0; a < job->general_refs.size(); a++ ) { + if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER ) + continue; + if ( buildingId != -1 ) { + out.print("%s, line %d: Found two buildings for the same job.\n", __FILE__, __LINE__); + } + buildingId = ((df::general_ref_building_holderst*)job->general_refs[a])->building_id; + if (buildingId == -1) { + out.print("%s, line %d: invalid building.\n", __FILE__, __LINE__); + continue; + } + } + df::building* building; + { + int32_t index = df::building::binsearch_index(df::global::world->buildings.all, buildingId); + if ( index == -1 ) { + out.print("%s, line %d: error: couldn't find building %d.\n", __FILE__, __LINE__, buildingId); + return; + } + building = df::global::world->buildings.all[index]; + } //find all of the products it makes. Look for a stone with a low boiling point. - bool foundIt = false; + bool appliedSomething = false; for ( size_t a = 0; a < reaction->products.size(); a++ ) { df::reaction_product_type type = reaction->products[a]->getType(); //out.print("type = %d\n", (int32_t)type); @@ -234,23 +323,34 @@ void processJob(color_ostream& out, void* jobPtr) { //must be a boiling rock syndrome df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index]; - if ( inorganic->material.heat.boiling_point > 10000 ) { - //continue; + if ( inorganic->material.heat.boiling_point > 9000 ) { + continue; } for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) { //add each syndrome to the guy who did the job df::syndrome* syndrome = inorganic->material.syndrome[b]; + bool workerOnly = false; + bool allowMultipleSyndromes = false; + bool allowMultipleTargets = false; bool foundCommand = false; string commandStr; vector args; for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) { std::string* clazz = syndrome->syn_class[c]; - out.print("Class = %s\n", clazz->c_str()); if ( foundCommand ) { - if ( commandStr == "" ) - commandStr = *clazz; - else { + if ( commandStr == "" ) { + if ( *clazz == "\\WORKER_ONLY" ) { + workerOnly = true; + } else if ( *clazz == "\\ALLOW_MULTIPLE_SYNDROMES" ) { + allowMultipleSyndromes = true; + } else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) { + allowMultipleTargets = true; + } + else { + commandStr = *clazz; + } + } else { stringstream bob; if ( *clazz == "\\LOCATION" ) { bob << job->pos.x; @@ -284,70 +384,36 @@ void processJob(color_ostream& out, void* jobPtr) { if ( commandStr != "" ) { Core::getInstance().runCommand(out, commandStr, args); } - //check that the syndrome applies to that guy - /* - * If there is no affected class or affected creature, then anybody who isn't immune is fair game. - * - * Otherwise, it works like this: - * add all the affected class creatures - * remove all the immune class creatures - * add all the affected creatures - * remove all the immune creatures - * you're affected if and only if you're in the remaining list after all of that - **/ - bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature_1.size() == 0; - for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) { - if ( applies ) - break; - for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { - if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) { - applies = true; - break; - } - } - } - for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) { - if ( !applies ) - break; - for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { - if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) { - applies = false; - break; - } - } - } - if ( syndrome->syn_affected_creature_1.size() != syndrome->syn_affected_creature_2.size() ) { - out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__); - return; + //only one syndrome per reaction will be applied, unless multiples are allowed. + if ( appliedSomething && !allowMultipleSyndromes ) + continue; + + if ( maybeApply(out, syndrome, workerId, unit) ) { + appliedSomething = true; + continue; } - for ( size_t c = 0; c < syndrome->syn_affected_creature_1.size(); c++ ) { - if ( creature_name != *syndrome->syn_affected_creature_1[c] ) + + if ( workerOnly ) + continue; + + //now try applying it to everybody inside the building + for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { + df::unit* unit = df::global::world->units.active[a]; + if ( unit->pos.z != building->z ) continue; - if ( *syndrome->syn_affected_creature_2[c] == "ALL" || - *syndrome->syn_affected_creature_2[c] == creature_caste ) { - applies = true; - break; - } - } - for ( size_t c = 0; c < syndrome->syn_immune_creature_1.size(); c++ ) { - if ( creature_name != *syndrome->syn_immune_creature_1[c] ) + if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 ) + continue; + if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 ) continue; - if ( *syndrome->syn_immune_creature_2[c] == "ALL" || - *syndrome->syn_immune_creature_2[c] == creature_caste ) { - applies = false; - break; + if ( maybeApply(out, syndrome, unit->id, unit) ) { + appliedSomething = true; + if ( !allowMultipleTargets ) + break; } } - if ( !applies ) { - continue; - } - if ( giveSyndrome(out, workerId, syndrome) < 0 ) - return; } } - if ( !foundIt ) - return; return; } From 38ef75418ae5147dcc84ed2f808f8eb0b76850a6 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 14:09:16 -0500 Subject: [PATCH 076/136] AutoSyndrome: added an option to delete boiling rocks as they are created (on by default). --- library/xml | 2 +- plugins/autoSyndrome.cpp | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 22b01b80a..fbf671a7d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 +Subproject commit fbf671a7d5aacb41cb44059eb16a1ee9cad419be diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 2c8023035..2fa9fb681 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -11,6 +11,8 @@ #include "df/caste_raw.h" #include "df/creature_raw.h" #include "df/global_objects.h" +#include "df/item.h" +#include "df/item_boulderst.h" #include "df/job.h" #include "df/job_type.h" #include "df/reaction.h" @@ -334,6 +336,7 @@ void processJob(color_ostream& out, void* jobPtr) { bool allowMultipleSyndromes = false; bool allowMultipleTargets = false; bool foundCommand = false; + bool destroyRock = true; string commandStr; vector args; for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) { @@ -346,6 +349,8 @@ void processJob(color_ostream& out, void* jobPtr) { allowMultipleSyndromes = true; } else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) { allowMultipleTargets = true; + } else if ( *clazz == "\\PRESERVE_ROCK" ) { + destroyRock = false; } else { commandStr = *clazz; @@ -385,13 +390,35 @@ void processJob(color_ostream& out, void* jobPtr) { Core::getInstance().runCommand(out, commandStr, args); } + if ( destroyRock ) { + //find the rock and kill it before it can boil and cause problems and ugliness + for ( size_t c = 0; c < df::global::world->items.all.size(); c++ ) { + df::item* item = df::global::world->items.all[c]; + if ( item->pos.z != building->z ) + continue; + if ( item->pos.x < building->x1 || item->pos.x > building->x2 ) + continue; + if ( item->pos.y < building->y1 || item->pos.y > building->y2 ) + continue; + if ( item->getType() != df::enums::item_type::BOULDER ) + continue; + //make sure it's the right type of boulder + df::item_boulderst* boulder = (df::item_boulderst*)item; + if ( boulder->mat_index != bob->mat_index ) + continue; + + boulder->flags.bits.garbage_collect = true; + boulder->flags.bits.forbid = true; + boulder->flags.bits.hidden = true; + } + } + //only one syndrome per reaction will be applied, unless multiples are allowed. if ( appliedSomething && !allowMultipleSyndromes ) continue; if ( maybeApply(out, syndrome, workerId, unit) ) { appliedSomething = true; - continue; } if ( workerOnly ) From 03650dbdbdc2a3fff47df77dfd8be17b1c5201d5 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 17:32:11 -0500 Subject: [PATCH 077/136] True transformation. Temp commit. --- plugins/CMakeLists.txt | 1 + plugins/trueTransformation.cpp | 85 ++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 plugins/trueTransformation.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1c89d9ab7..077f0e4ad 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -132,6 +132,7 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + DFHACK_PLUGIN(trueTransformation trueTransformation.cpp) endif() diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp new file mode 100644 index 000000000..6cd6f16ea --- /dev/null +++ b/plugins/trueTransformation.cpp @@ -0,0 +1,85 @@ + +#include "Core.h" +#include "Console.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +using namespace DFHack; + +DFHACK_PLUGIN("trueTransformation"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "skeleton", "Do nothing, look pretty.", + skeleton, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command does nothing at all.\n" + "Example:\n" + " skeleton\n" + " Does nothing.\n" + )); + return CR_OK; +} + +// This is called right before the plugin library is removed from memory. +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + // You *MUST* kill all threads you created before this returns. + // If everything fails, just return CR_FAILURE. Your plugin will be + // in a zombie state, but things won't crash. + return CR_OK; +} + +// Called to notify the plugin about important state changes. +// Invoked with DF suspended, and always before the matching plugin_onupdate. +// More event codes may be added in the future. +/* +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_GAME_LOADED: + // initialize from the world just loaded + break; + case SC_GAME_UNLOADED: + // cleanup + break; + default: + break; + } + return CR_OK; +} +*/ + +// Whatever you put here will be done in each game step. Don't abuse it. +// It's optional, so you can just comment it out like this if you don't need it. +/* +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + // whetever. You don't need to suspend DF execution here. + return CR_OK; +} +*/ + +// A command! It sits around and looks pretty. And it's nice and friendly. +command_result skeleton (color_ostream &out, std::vector & parameters) +{ + // It's nice to print a help message you get invalid options + // from the user instead of just acting strange. + // This can be achieved by adding the extended help string to the + // PluginCommand registration as show above, and then returning + // CR_WRONG_USAGE from the function. The same string will also + // be used by 'help your-command'. + if (!parameters.empty()) + return CR_WRONG_USAGE; + // Commands are called from threads other than the DF one. + // Suspend this thread until DF has time for us. If you + // use CoreSuspender, it'll automatically resume DF when + // execution leaves the current scope. + CoreSuspender suspend; + // Actually do something here. Yay. + out.print("Hello! I do nothing, remember?\n"); + // Give control back to DF. + return CR_OK; +} From 4e4e382b8f7e359fef6723bebf4cd3e36a578aac Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 18:30:15 -0500 Subject: [PATCH 078/136] EventManager: added syndrome event. --- library/include/modules/EventManager.h | 9 ++++++++ library/modules/EventManager.cpp | 32 ++++++++++++++++++++++++++ library/xml | 2 +- plugins/devel/eventExample.cpp | 10 +++++++- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index a06439fcd..40d6603a7 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -19,6 +19,7 @@ namespace DFHack { ITEM_CREATED, BUILDING, CONSTRUCTION, + SYNDROME, EVENT_MAX }; } @@ -36,6 +37,14 @@ namespace DFHack { return !( *this == handle); } }; + + struct SyndromeData { + int32_t unitId; + int32_t syndromeIndex; + SyndromeData(int32_t unitId_in, int32_t syndromeIndex_in): unitId(unitId_in), syndromeIndex(syndromeIndex_in) { + + } + }; DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin); DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false); diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 2725d80a3..6a355aa36 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -13,6 +13,7 @@ #include "df/job.h" #include "df/job_list_link.h" #include "df/unit.h" +#include "df/unit_syndrome.h" #include "df/world.h" #include @@ -135,6 +136,7 @@ static void manageUnitDeathEvent(color_ostream& out); static void manageItemCreationEvent(color_ostream& out); static void manageBuildingEvent(color_ostream& out); static void manageConstructionEvent(color_ostream& out); +static void manageSyndromeEvent(color_ostream& out); //tick event static uint32_t lastTick = 0; @@ -235,6 +237,10 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { manageConstructionEvent(out); eventLastTick[EventType::CONSTRUCTION] = tick; } + if ( tick - eventLastTick[EventType::SYNDROME] >= (*eventFrequency[EventType::SYNDROME].begin()).first ) { + manageSyndromeEvent(out); + eventLastTick[EventType::SYNDROME] = tick; + } return; } @@ -458,3 +464,29 @@ static void manageConstructionEvent(color_ostream& out) { constructions.clear(); constructions.insert(constructionsNow.begin(), constructionsNow.end()); } + +static void manageSyndromeEvent(color_ostream& out) { + if ( handlers[EventType::SYNDROME].empty() ) + return; + + multimap copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end()); + for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) { + df::unit* unit = *a; + if ( unit->flags1.bits.dead ) + continue; + for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) { + df::unit_syndrome* syndrome = unit->syndromes.active[b]; + uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time; + out.print("start time = %d, time = %d\n", startTime, eventLastTick[EventType::SYNDROME]); + if ( startTime < eventLastTick[EventType::SYNDROME] ) + continue; + + SyndromeData data(unit->id, b); + for ( auto c = copy.begin(); c != copy.end(); c++ ) { + EventHandler handle = (*c).second; + handle.eventHandler(out, (void*)&data); + } + } + } +} + diff --git a/library/xml b/library/xml index 22b01b80a..fbf671a7d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 +Subproject commit fbf671a7d5aacb41cb44059eb16a1ee9cad419be diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index e6a3dd22f..aaaa35a09 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -23,10 +23,10 @@ void unitDeath(color_ostream& out, void* ptr); void itemCreate(color_ostream& out, void* ptr); void building(color_ostream& out, void* ptr); void construction(color_ostream& out, void* ptr); +void syndrome(color_ostream& out, void* ptr); command_result eventExample(color_ostream& out, vector& parameters); - DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("eventExample", "Sets up a few event triggers.",eventExample)); return CR_OK; @@ -40,6 +40,7 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::EventHandler itemHandler(itemCreate); EventManager::EventHandler buildingHandler(building); EventManager::EventHandler constructionHandler(construction); + EventManager::EventHandler syndromeHandler(syndrome); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::unregisterAll(me); @@ -53,6 +54,7 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, 1000, me); EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, 500, me); EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, 100, me); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, 1, me); out.print("Events registered.\n"); return CR_OK; } @@ -91,3 +93,9 @@ void building(color_ostream& out, void* ptr) { void construction(color_ostream& out, void* ptr) { out.print("Construction created/destroyed: 0x%X\n", ptr); } + +void syndrome(color_ostream& out, void* ptr) { + EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr; + out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); +} + From d4afa4b6e45c3b8b76e0c1d551ce566b71607dd7 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 19:21:25 -0500 Subject: [PATCH 079/136] True Transformation: turn into something temporarily, then permanently transform into another creature. If you try to do that with a syndrome, you can't do it recursively. --- plugins/trueTransformation.cpp | 133 +++++++++++++++++---------------- 1 file changed, 69 insertions(+), 64 deletions(-) diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp index 6cd6f16ea..fd1e1a61b 100644 --- a/plugins/trueTransformation.cpp +++ b/plugins/trueTransformation.cpp @@ -5,81 +5,86 @@ #include "Export.h" #include "PluginManager.h" +#include "modules/EventManager.h" + +#include "df/caste_raw.h" +#include "df/creature_raw.h" +#include "df/syndrome.h" +#include "df/unit.h" +#include "df/unit_syndrome.h" +#include "df/world.h" + +#include + using namespace DFHack; +using namespace std; DFHACK_PLUGIN("trueTransformation"); +void syndromeHandler(color_ostream& out, void* ptr); + DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand( - "skeleton", "Do nothing, look pretty.", - skeleton, false, /* true means that the command can't be used from non-interactive user interface */ - // Extended help string. Used by CR_WRONG_USAGE and the help command: - " This command does nothing at all.\n" - "Example:\n" - " skeleton\n" - " Does nothing.\n" - )); - return CR_OK; -} + EventManager::EventHandler syndrome(syndromeHandler); + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("trueTransformation"); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, 1, me); -// This is called right before the plugin library is removed from memory. -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - // You *MUST* kill all threads you created before this returns. - // If everything fails, just return CR_FAILURE. Your plugin will be - // in a zombie state, but things won't crash. return CR_OK; } -// Called to notify the plugin about important state changes. -// Invoked with DF suspended, and always before the matching plugin_onupdate. -// More event codes may be added in the future. -/* -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_GAME_LOADED: - // initialize from the world just loaded - break; - case SC_GAME_UNLOADED: - // cleanup - break; - default: - break; +void syndromeHandler(color_ostream& out, void* ptr) { + EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr; + //out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); + + int32_t index = df::unit::binsearch_index(df::global::world->units.active, data->unitId); + if ( index < 0 ) { + out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__); + return; } - return CR_OK; -} -*/ + df::unit* unit = df::global::world->units.active[index]; + df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex]; + df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type]; + + bool foundIt = false; + int32_t raceId = -1; + df::creature_raw* creatureRaw = NULL; + int32_t casteId = -1; + for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) { + if ( *syndrome->syn_class[a] == "\\PERMANENT" ) { + foundIt = true; + } + if ( foundIt && raceId == -1 ) { + //find the race with the name + string& name = *syndrome->syn_class[a]; + for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) { + df::creature_raw* creature = df::global::world->raws.creatures.all[b]; + if ( creature->creature_id != name ) + continue; + raceId = b; + creatureRaw = creature; + break; + } + continue; + } + if ( foundIt && raceId != -1 ) { + string& name = *syndrome->syn_class[a]; + for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) { + df::caste_raw* caste = creatureRaw->caste[b]; + if ( caste->caste_id != name ) + continue; + casteId = b; + break; + } + break; + } + } + out.print("foundIt = %d, raceId = %d, casteId = %d\n", (int32_t)foundIt, raceId, casteId); + if ( !foundIt || raceId == -1 || casteId == -1 ) + return; -// Whatever you put here will be done in each game step. Don't abuse it. -// It's optional, so you can just comment it out like this if you don't need it. -/* -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) -{ - // whetever. You don't need to suspend DF execution here. - return CR_OK; + unit->enemy.normal_race = raceId; + unit->enemy.normal_caste = casteId; + out.print("Did the thing.\n"); + //that's it! } -*/ -// A command! It sits around and looks pretty. And it's nice and friendly. -command_result skeleton (color_ostream &out, std::vector & parameters) -{ - // It's nice to print a help message you get invalid options - // from the user instead of just acting strange. - // This can be achieved by adding the extended help string to the - // PluginCommand registration as show above, and then returning - // CR_WRONG_USAGE from the function. The same string will also - // be used by 'help your-command'. - if (!parameters.empty()) - return CR_WRONG_USAGE; - // Commands are called from threads other than the DF one. - // Suspend this thread until DF has time for us. If you - // use CoreSuspender, it'll automatically resume DF when - // execution leaves the current scope. - CoreSuspender suspend; - // Actually do something here. Yay. - out.print("Hello! I do nothing, remember?\n"); - // Give control back to DF. - return CR_OK; -} From 6d2773856a22f3ec4fabe3047222a6fce02d6931 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 19:23:40 -0500 Subject: [PATCH 080/136] EventManager: fixed a few things. --- library/modules/EventManager.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 6a355aa36..b3f22b109 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -159,6 +159,7 @@ static unordered_set buildings; //construction static unordered_set constructions; +static bool gameLoaded; void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { static bool doOnce = false; @@ -184,6 +185,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event constructions.clear(); Buildings::clearBuildings(out); + gameLoaded = false; } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -198,11 +200,12 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event nextItem = 0; nextBuilding = 0; lastTick = 0; + gameLoaded = true; } } void DFHack::EventManager::manageEvents(color_ostream& out) { - if ( !Core::getInstance().isWorldLoaded() ) { + if ( !gameLoaded ) { return; } uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear @@ -478,7 +481,7 @@ static void manageSyndromeEvent(color_ostream& out) { df::unit_syndrome* syndrome = unit->syndromes.active[b]; uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time; out.print("start time = %d, time = %d\n", startTime, eventLastTick[EventType::SYNDROME]); - if ( startTime < eventLastTick[EventType::SYNDROME] ) + if ( startTime <= eventLastTick[EventType::SYNDROME] ) continue; SyndromeData data(unit->id, b); From a8b0a7e6958be517ea487202ca0e81b2edf9cf0e Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 19:25:24 -0500 Subject: [PATCH 081/136] EventManager: got rid of silly print statement. --- library/modules/EventManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index b3f22b109..8611417b0 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -480,7 +480,6 @@ static void manageSyndromeEvent(color_ostream& out) { for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) { df::unit_syndrome* syndrome = unit->syndromes.active[b]; uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time; - out.print("start time = %d, time = %d\n", startTime, eventLastTick[EventType::SYNDROME]); if ( startTime <= eventLastTick[EventType::SYNDROME] ) continue; From 5865579b23a0f28e86ddf344084a84a469233e34 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 19:26:37 -0500 Subject: [PATCH 082/136] EventManager: got rid of print statement. --- library/modules/EventManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index b3f22b109..8611417b0 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -480,7 +480,6 @@ static void manageSyndromeEvent(color_ostream& out) { for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) { df::unit_syndrome* syndrome = unit->syndromes.active[b]; uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time; - out.print("start time = %d, time = %d\n", startTime, eventLastTick[EventType::SYNDROME]); if ( startTime <= eventLastTick[EventType::SYNDROME] ) continue; From 285ee3b399f6505b67ce305f952cf52e6f2b46fd Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 19:45:08 -0500 Subject: [PATCH 083/136] True transformation: got rid of debug print statements. --- plugins/trueTransformation.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp index fd1e1a61b..6da6b7e74 100644 --- a/plugins/trueTransformation.cpp +++ b/plugins/trueTransformation.cpp @@ -78,13 +78,11 @@ void syndromeHandler(color_ostream& out, void* ptr) { break; } } - out.print("foundIt = %d, raceId = %d, casteId = %d\n", (int32_t)foundIt, raceId, casteId); if ( !foundIt || raceId == -1 || casteId == -1 ) return; unit->enemy.normal_race = raceId; unit->enemy.normal_caste = casteId; - out.print("Did the thing.\n"); //that's it! } From 123f34bf85f6eb3b9331f563b3d4abd4438047f9 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 21:06:48 -0500 Subject: [PATCH 084/136] Ignore swap files. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9f2b009c6..b4a578ec0 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ dfhack/python/dist build/CPack*Config.cmake /cmakeall.bat + +# vim swap files +*.swp From 796e387398b98b1ef8904d8ab7a6b49dbda28096 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 3 Jan 2013 12:24:32 -0600 Subject: [PATCH 085/136] Add a guard against an invalid item pointer, and don't try to deduce construction labor for item_lost jobs --- plugins/autolabor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 7068ad5fb..ff0670b9b 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -665,7 +665,7 @@ static df::building* get_building_from_job(df::job* j) static df::unit_labor construction_build_labor (df::item* i) { MaterialInfo matinfo; - if (matinfo.decode(i)) + if (i && matinfo.decode(i)) { if (matinfo.material->flags.is_set(df::material_flags::IS_METAL)) return df::unit_labor::METAL_CRAFT; @@ -754,6 +754,9 @@ private: public: df::unit_labor get_labor(df::job* j) { + if (j->flags.bits.item_lost) + return df::unit_labor::NONE; + df::building* bld = get_building_from_job (j); switch (bld->getType()) { From 910e398a7bb3d7deb7f13cdbcd74acef6325a7d7 Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 3 Jan 2013 15:52:56 -0500 Subject: [PATCH 086/136] EventManager: added invasion event. --- library/include/modules/EventManager.h | 1 + library/modules/EventManager.cpp | 27 ++++++++++++++++++++++++++ plugins/devel/eventExample.cpp | 7 +++++++ 3 files changed, 35 insertions(+) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 40d6603a7..1f7758ddf 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -20,6 +20,7 @@ namespace DFHack { BUILDING, CONSTRUCTION, SYNDROME, + INVASION, EVENT_MAX }; } diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 8611417b0..77248038f 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -12,6 +12,7 @@ #include "df/item.h" #include "df/job.h" #include "df/job_list_link.h" +#include "df/ui.h" #include "df/unit.h" #include "df/unit_syndrome.h" #include "df/world.h" @@ -137,6 +138,7 @@ static void manageItemCreationEvent(color_ostream& out); static void manageBuildingEvent(color_ostream& out); static void manageConstructionEvent(color_ostream& out); static void manageSyndromeEvent(color_ostream& out); +static void manageInvasionEvent(color_ostream& out); //tick event static uint32_t lastTick = 0; @@ -161,6 +163,9 @@ static unordered_set buildings; static unordered_set constructions; static bool gameLoaded; +//invasion +static int32_t nextInvasion; + void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { static bool doOnce = false; if ( !doOnce ) { @@ -186,6 +191,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event Buildings::clearBuildings(out); gameLoaded = false; + nextInvasion = -1; } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -200,6 +206,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event nextItem = 0; nextBuilding = 0; lastTick = 0; + nextInvasion = df::global::ui->invasions.next_id; gameLoaded = true; } } @@ -244,6 +251,10 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { manageSyndromeEvent(out); eventLastTick[EventType::SYNDROME] = tick; } + if ( tick - eventLastTick[EventType::INVASION] >= (*eventFrequency[EventType::INVASION].begin()).first ) { + manageInvasionEvent(out); + eventLastTick[EventType::INVASION] = tick; + } return; } @@ -492,3 +503,19 @@ static void manageSyndromeEvent(color_ostream& out) { } } +static void manageInvasionEvent(color_ostream& out) { + if ( handlers[EventType::INVASION].empty() ) + return; + + multimap copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end()); + + if ( df::global::ui->invasions.next_id <= nextInvasion ) + return; + nextInvasion = df::global::ui->invasions.next_id; + + for ( auto a = copy.begin(); a != copy.end(); a++ ) { + EventHandler handle = (*a).second; + handle.eventHandler(out, (void*)nextInvasion); + } +} + diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index aaaa35a09..3bc84879a 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -24,6 +24,7 @@ void itemCreate(color_ostream& out, void* ptr); void building(color_ostream& out, void* ptr); void construction(color_ostream& out, void* ptr); void syndrome(color_ostream& out, void* ptr); +void invasion(color_ostream& out, void* ptr); command_result eventExample(color_ostream& out, vector& parameters); @@ -41,6 +42,7 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::EventHandler buildingHandler(building); EventManager::EventHandler constructionHandler(construction); EventManager::EventHandler syndromeHandler(syndrome); + EventManager::EventHandler invasionHandler(invasion); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::unregisterAll(me); @@ -55,6 +57,7 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, 500, me); EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, 100, me); EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, 1, me); + EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, 1, me); out.print("Events registered.\n"); return CR_OK; } @@ -99,3 +102,7 @@ void syndrome(color_ostream& out, void* ptr) { out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); } +void invasion(color_ostream& out, void* ptr) { + out.print("New invasion! %d\n", (int32_t)ptr); +} + From 9e74ae58f25db9380bfa27e4cd1fa01eab173215 Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 3 Jan 2013 19:07:05 -0500 Subject: [PATCH 087/136] EventManager: Fixed a problem with deregistering event frequencies. --- library/modules/EventManager.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 77248038f..6df6106de 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -120,13 +120,23 @@ void DFHack::EventManager::unregisterAll(Plugin* plugin) { int32_t freq = (*b).second; eventFrequency[a][freq]--; if ( eventFrequency[a][freq] < 0 ) { - Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); + Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister: %d, %d.\n", __FILE__, __LINE__, a, freq); eventFrequency[a].erase(eventFrequency[a].find(freq)); } else if ( eventFrequency[a][freq] == 0 ) { eventFrequency[a].erase(eventFrequency[a].find(freq)); } } } + //now delete the frequencies from the thing + for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) { + for ( auto b = pluginFrequencies[a].begin(); b != pluginFrequencies[a].end(); b++ ) { + if ( (*b).first != plugin ) + continue; + pluginFrequencies[a].erase(b); + a--; + break; + } + } return; } From 715f191c264009c3a30b89e1011620b930ebaa6a Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 3 Jan 2013 19:31:29 -0500 Subject: [PATCH 088/136] EventManager: made the frequency part of EventHandler. --- library/include/modules/EventManager.h | 9 ++-- library/modules/EventManager.cpp | 74 ++++++++------------------ plugins/devel/eventExample.cpp | 34 ++++++------ 3 files changed, 45 insertions(+), 72 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 1f7758ddf..9cca6e0e0 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -27,12 +27,13 @@ namespace DFHack { struct EventHandler { void (*eventHandler)(color_ostream&, void*); //called when the event happens + int32_t freq; - EventHandler(void (*eventHandlerIn)(color_ostream&, void*)): eventHandler(eventHandlerIn) { + EventHandler(void (*eventHandlerIn)(color_ostream&, void*), int32_t freqIn): eventHandler(eventHandlerIn), freq(freqIn) { } bool operator==(EventHandler& handle) const { - return eventHandler == handle.eventHandler; + return eventHandler == handle.eventHandler && freq == handle.freq; } bool operator!=(EventHandler& handle) const { return !( *this == handle); @@ -47,9 +48,9 @@ namespace DFHack { } }; - DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin); + DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false); - DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin); + DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin); DFHACK_EXPORT void unregisterAll(Plugin* plugin); void manageEvents(color_ostream& out); void onStateChange(color_ostream& out, state_change_event event); diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 6df6106de..ff48263fc 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -36,16 +36,12 @@ multimap tickQueue; //TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever multimap handlers[EventType::EVENT_MAX]; -multimap pluginFrequencies[EventType::EVENT_MAX]; -map eventFrequency[EventType::EVENT_MAX]; uint32_t eventLastTick[EventType::EVENT_MAX]; const uint32_t ticksPerYear = 403200; -void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin) { +void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) { handlers[e].insert(pair(plugin, handler)); - eventFrequency[e][freq]++; - pluginFrequencies[e].insert(pair(plugin, freq)); } void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) { @@ -66,7 +62,7 @@ void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plug return; } -void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin) { +void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) { for ( multimap::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) { if ( (*i).first != plugin ) break; @@ -76,16 +72,6 @@ void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handl break; } } - if ( eventFrequency[e].find(freq) == eventFrequency[e].end() ) { - Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); - return; - } - eventFrequency[e][freq]--; - if ( eventFrequency[e][freq] == 0 ) { - eventFrequency[e].erase(eventFrequency[e].find(freq)); - } else if ( eventFrequency[e][freq] < 0 ) { - Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); - } return; } @@ -112,31 +98,6 @@ void DFHack::EventManager::unregisterAll(Plugin* plugin) { for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { handlers[a].erase(plugin); } - - for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { - for ( auto b = pluginFrequencies[a].begin(); b != pluginFrequencies[a].end(); b++ ) { - if ( (*b).first != plugin ) - continue; - int32_t freq = (*b).second; - eventFrequency[a][freq]--; - if ( eventFrequency[a][freq] < 0 ) { - Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister: %d, %d.\n", __FILE__, __LINE__, a, freq); - eventFrequency[a].erase(eventFrequency[a].find(freq)); - } else if ( eventFrequency[a][freq] == 0 ) { - eventFrequency[a].erase(eventFrequency[a].find(freq)); - } - } - } - //now delete the frequencies from the thing - for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) { - for ( auto b = pluginFrequencies[a].begin(); b != pluginFrequencies[a].end(); b++ ) { - if ( (*b).first != plugin ) - continue; - pluginFrequencies[a].erase(b); - a--; - break; - } - } return; } @@ -181,8 +142,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event if ( !doOnce ) { //TODO: put this somewhere else doOnce = true; - EventHandler buildingHandler(Buildings::updateBuildings); - DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, 100, NULL); + EventHandler buildingHandler(Buildings::updateBuildings, 100); + DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL); //out.print("Registered listeners.\n %d", __LINE__); } if ( event == DFHack::SC_MAP_UNLOADED ) { @@ -231,37 +192,48 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { if ( tick <= lastTick ) return; lastTick = tick; + + int32_t eventFrequency[EventType::EVENT_MAX]; + for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) { + int32_t min = 1000000000; + for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) { + EventHandler bob = (*b).second; + if ( bob.freq < min ) + min = bob.freq; + } + eventFrequency[a] = min; + } manageTickEvent(out); - if ( tick - eventLastTick[EventType::JOB_INITIATED] >= (*eventFrequency[EventType::JOB_INITIATED].begin()).first ) { + if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) { manageJobInitiatedEvent(out); eventLastTick[EventType::JOB_INITIATED] = tick; } - if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= (*eventFrequency[EventType::JOB_COMPLETED].begin()).first ) { + if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) { manageJobCompletedEvent(out); eventLastTick[EventType::JOB_COMPLETED] = tick; } - if ( tick - eventLastTick[EventType::UNIT_DEATH] >= (*eventFrequency[EventType::UNIT_DEATH].begin()).first ) { + if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) { manageUnitDeathEvent(out); eventLastTick[EventType::UNIT_DEATH] = tick; } - if ( tick - eventLastTick[EventType::ITEM_CREATED] >= (*eventFrequency[EventType::ITEM_CREATED].begin()).first ) { + if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) { manageItemCreationEvent(out); eventLastTick[EventType::ITEM_CREATED] = tick; } - if ( tick - eventLastTick[EventType::BUILDING] >= (*eventFrequency[EventType::BUILDING].begin()).first ) { + if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) { manageBuildingEvent(out); eventLastTick[EventType::BUILDING] = tick; } - if ( tick - eventLastTick[EventType::CONSTRUCTION] >= (*eventFrequency[EventType::CONSTRUCTION].begin()).first ) { + if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) { manageConstructionEvent(out); eventLastTick[EventType::CONSTRUCTION] = tick; } - if ( tick - eventLastTick[EventType::SYNDROME] >= (*eventFrequency[EventType::SYNDROME].begin()).first ) { + if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) { manageSyndromeEvent(out); eventLastTick[EventType::SYNDROME] = tick; } - if ( tick - eventLastTick[EventType::INVASION] >= (*eventFrequency[EventType::INVASION].begin()).first ) { + if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) { manageInvasionEvent(out); eventLastTick[EventType::INVASION] = tick; } diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index 3bc84879a..3a2970c5b 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -34,30 +34,30 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector& parameters) { - EventManager::EventHandler initiateHandler(jobInitiated); - EventManager::EventHandler completeHandler(jobCompleted); - EventManager::EventHandler timeHandler(timePassed); - EventManager::EventHandler deathHandler(unitDeath); - EventManager::EventHandler itemHandler(itemCreate); - EventManager::EventHandler buildingHandler(building); - EventManager::EventHandler constructionHandler(construction); - EventManager::EventHandler syndromeHandler(syndrome); - EventManager::EventHandler invasionHandler(invasion); + EventManager::EventHandler initiateHandler(jobInitiated, 10); + EventManager::EventHandler completeHandler(jobCompleted, 5); + EventManager::EventHandler timeHandler(timePassed, 1); + EventManager::EventHandler deathHandler(unitDeath, 500); + EventManager::EventHandler itemHandler(itemCreate, 1000); + EventManager::EventHandler buildingHandler(building, 500); + EventManager::EventHandler constructionHandler(construction, 100); + EventManager::EventHandler syndromeHandler(syndrome, 1); + EventManager::EventHandler invasionHandler(invasion, 1000); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::unregisterAll(me); - EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, 10, me); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, 5, me); + EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); EventManager::registerTick(timeHandler, 1, me); EventManager::registerTick(timeHandler, 2, me); EventManager::registerTick(timeHandler, 4, me); EventManager::registerTick(timeHandler, 8, me); - EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, 500, me); - EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, 1000, me); - EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, 500, me); - EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, 100, me); - EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, 1, me); - EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, 1, me); + EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, me); + EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, me); + EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, me); + EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, me); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, me); + EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, me); out.print("Events registered.\n"); return CR_OK; } From ec03d567d28bd6fd5d4f656d7d42e3c2e1e246de Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 3 Jan 2013 22:47:27 -0500 Subject: [PATCH 089/136] EventManager: use WORLD_LOADED instead of MAP_LOADED. --- library/modules/EventManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index ff48263fc..3af096004 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -146,7 +146,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL); //out.print("Registered listeners.\n %d", __LINE__); } - if ( event == DFHack::SC_MAP_UNLOADED ) { + if ( event == DFHack::SC_WORLD_UNLOADED ) { lastTick = 0; lastJobId = -1; for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { @@ -163,7 +163,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event Buildings::clearBuildings(out); gameLoaded = false; nextInvasion = -1; - } else if ( event == DFHack::SC_MAP_LOADED ) { + } else if ( event == DFHack::SC_WORLD_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); multimap newTickQueue; From 9404267c1f9b97daef23efb9f65b4e91d20e1fdb Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 4 Jan 2013 13:14:20 -0600 Subject: [PATCH 090/136] Autolabor: Tell DF to immediately process jobs after each run. --- plugins/autolabor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index ff0670b9b..d96baec9e 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -2259,6 +2259,8 @@ public: } } + *df::global::process_jobs = true; + print_debug = 0; } From 7debd3d983a92dad224dd49574936b413e8cf581 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 4 Jan 2013 13:20:28 -0600 Subject: [PATCH 091/136] Autofarm: correct incorrect logic for determining if a farm is surface or subterranean --- scripts/autofarm.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index 6a7635b90..e1dc62ae3 100644 --- a/scripts/autofarm.rb +++ b/scripts/autofarm.rb @@ -102,9 +102,9 @@ class AutoFarm farms_u = [] df.world.buildings.other[:FARM_PLOT].each { |f| if (f.flags.exists) - outside = df.map_designation_at(f.centerx,f.centery,f.z).outside - farms_s.push(f) if outside - farms_u.push(f) unless outside + underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean + farms_s.push(f) unless underground + farms_u.push(f) if underground end } From 91ee8ac020eafbda852014f59fa38f0854b14737 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Thu, 27 Dec 2012 22:23:13 +1300 Subject: [PATCH 092/136] Refactor search to handle more types of screens cleanly. Added search to screens: * Animals * Military positions assignment * Announcements * Room list * Job list * Burrow assignment --- plugins/search.cpp | 1535 +++++++++++++++++++++++++++++++++----------- 1 file changed, 1172 insertions(+), 363 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index cf1798dcf..d3983f45b 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -5,14 +5,28 @@ #include -//#include "df/viewscreen_petst.h" +#include "df/viewscreen_announcelistst.h" +#include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" #include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_buildinglistst.h" +#include "df/viewscreen_joblistst.h" #include "df/interface_key.h" #include "df/interfacest.h" #include "df/layer_object_listst.h" +#include "df/job.h" +#include "df/report.h" +#include "modules/Job.h" +#include "df/global_objects.h" +#include "df/viewscreen_dwarfmodest.h" +#include "modules/Gui.h" +#include "df/unit.h" +#include "df/misc_trait_type.h" +#include "df/unit_misc_trait.h" using std::set; using std::vector; @@ -51,25 +65,56 @@ static bool is_live_screen(const df::viewscreen *screen) return false; } +static string get_unit_description(df::unit *unit) +{ + string desc; + auto name = Units::getVisibleName(unit); + if (name->has_name) + desc = Translation::TranslateName(name, false); + desc += ", " + Units::getProfessionName(unit); // Check animal type too + + return desc; +} + + // -// START: Base Search functionality +// START: Generic Search functionality // -// Parent class that does most of the work -template -class search_parent +template +class search_generic { public: + bool init(S *screen) + { + if (screen != viewscreen && !reset_on_change()) + return false; + + if (!can_init(screen)) + return false; + + if (!is_valid()) + { + this->viewscreen = screen; + this->cursor_pos = get_viewscreen_cursor(); + this->primary_list = get_primary_list(); + this->select_key = get_search_select_key(); + select_token = (df::interface_key) (ascii_to_enum_offset + select_key); + valid = true; + do_post_init(); + } + + return true; + } + // Called each time you enter or leave a searchable screen. Resets everything. - void reset_all() + virtual void reset_all() { reset_search(); valid = false; - sort_list1 = NULL; - sort_list2 = NULL; + primary_list = NULL; viewscreen = NULL; select_key = 's'; - track_secondary_values = false; } bool reset_on_change() @@ -153,48 +198,35 @@ public: return key_processed || entry_mode; // Only pass unrecognized keys down if not in typing mode } - // Called if the search should be redone after the screen processes the keystroke. - // Used by the stocks screen where changing categories should redo the search on - // the new category. - virtual void do_post_update_check() + // Called after a keystroke has been processed + virtual void do_post_input_feed() { - if (redo_search) - { - do_search(); - redo_search = false; - } } - static search_parent *lock; + static search_generic *lock; protected: - const S *viewscreen; - vector saved_list1, reference_list; - vector saved_list2; - vector *sort_list2; - vector saved_indexes; - - bool redo_search; - bool track_secondary_values; - string search_string; + virtual string get_element_description(T element) const = 0; + virtual void render() const = 0; + virtual int32_t *get_viewscreen_cursor() = 0; + virtual vector *get_primary_list() = 0; - search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0'), shift_offset('A' - 'a') + search_generic() : ascii_to_enum_offset(interface_key::STRING_A048 - '0'), shift_offset('A' - 'a') { reset_all(); } - virtual void init(int *cursor_pos, vector *sort_list1, vector *sort_list2 = NULL, char select_key = 's') + virtual bool can_init(S *screen) + { + return true; + } + + virtual void do_post_init() { - this->cursor_pos = cursor_pos; - this->sort_list1 = sort_list1; - this->sort_list2 = sort_list2; - this->select_key = select_key; - select_token = (df::interface_key) (ascii_to_enum_offset + select_key); - track_secondary_values = false; - valid = true; + } - bool is_entry_mode() + bool in_entry_mode() { return entry_mode; } @@ -204,84 +236,59 @@ protected: entry_mode = true; lock = this; } - + void end_entry_mode() { entry_mode = false; lock = NULL; } - void reset_search() + virtual char get_search_select_key() + { + return 's'; + } + + virtual void reset_search() { end_entry_mode(); search_string = ""; saved_list1.clear(); - saved_list2.clear(); - reference_list.clear(); - saved_indexes.clear(); } - // If the second vector is editable (i.e. Trade screen vector used for marking). then it may - // have been edited while the list was filtered. We have to update the original unfiltered - // list with these values. Uses a stored reference vector to determine if the list has been - // reordered after filtering, in which case indexes must be remapped. - void update_secondary_values() + // Shortcut to clear the search immediately + virtual void clear_search() { - if (sort_list2 != NULL && track_secondary_values) + if (saved_list1.size() > 0) { - bool list_has_been_sorted = (sort_list1->size() == reference_list.size() - && *sort_list1 != reference_list); + *primary_list = saved_list1; + saved_list1.clear(); + } + search_string = ""; + } - for (size_t i = 0; i < saved_indexes.size(); i++) - { - int adjusted_item_index = i; - if (list_has_been_sorted) - { - for (size_t j = 0; j < sort_list1->size(); j++) - { - if ((*sort_list1)[j] == reference_list[i]) - { - adjusted_item_index = j; - break; - } - } - } + virtual void save_original_values() + { + saved_list1 = *primary_list; + } + + virtual void do_pre_incremental_search() + { - update_saved_secondary_list_item(saved_indexes[i], adjusted_item_index); - } - saved_indexes.clear(); - } } - virtual void update_saved_secondary_list_item(size_t i, size_t j) + virtual void clear_viewscreen_vectors() { - saved_list2[i] = (*sort_list2)[j]; + primary_list->clear(); } - // Store a copy of filtered list, used later to work out if filtered list has been sorted after filtering - void store_reference_values() + virtual void add_to_filtered_list(size_t i) { - if (track_secondary_values) - reference_list = *sort_list1; + primary_list->push_back(saved_list1[i]); } - // Shortcut to clear the search immediately - virtual void clear_search() + virtual void do_post_search() { - if (saved_list1.size() > 0) - { - *sort_list1 = saved_list1; - if (sort_list2 != NULL) - { - update_secondary_values(); - *sort_list2 = saved_list2; - } - saved_list1.clear(); - saved_list2.clear(); - } - store_reference_values(); - search_string = ""; } // The actual sort @@ -294,22 +301,12 @@ protected: } if (saved_list1.size() == 0) - { // On first run, save the original list - saved_list1 = *sort_list1; - if (sort_list2 != NULL) - saved_list2 = *sort_list2; - } + save_original_values(); else - update_secondary_values(); // Update original list with any modified values + do_pre_incremental_search(); - // Clear viewscreen vectors - sort_list1->clear(); - if (sort_list2 != NULL) - { - sort_list2->clear(); - saved_indexes.clear(); - } + clear_viewscreen_vectors(); string search_string_l = toLower(search_string); for (size_t i = 0; i < saved_list1.size(); i++ ) @@ -318,17 +315,11 @@ protected: string desc = toLower(get_element_description(element)); if (desc.find(search_string_l) != string::npos) { - sort_list1->push_back(element); - if (sort_list2 != NULL) - { - sort_list2->push_back(saved_list2[i]); - if (track_secondary_values) - saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed - } + add_to_filtered_list(i); } } - store_reference_values(); //Keep a copy, in case user sorts new list + do_post_search(); if (cursor_pos) *cursor_pos = 0; @@ -354,11 +345,13 @@ protected: OutputString(10, x, y, "_"); } - virtual string get_element_description(T element) const = 0; - virtual void render () const = 0; + S *viewscreen; + vector saved_list1, reference_list, *primary_list; + + //bool redo_search; + string search_string; private: - vector *sort_list1; int *cursor_pos; char select_key; bool valid; @@ -369,430 +362,1241 @@ private: const int shift_offset; }; -template search_parent *search_parent ::lock = NULL; -// Parent struct for the hooks, use optional param D to generate multiple classes with same T & V -// but different static modules -template -struct search_hook : T -{ - typedef T interpose_base; +template search_generic *search_generic ::lock = NULL; - static V module; - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) +// Search class helper for layered screens +template +class layered_search : public search_generic +{ +protected: + virtual bool can_init(S *screen) { - if (!module.init(this)) + auto list = getLayerList(screen); + if (!list->active) { - INTERPOSE_NEXT(feed)(input); - return; - } + if (is_valid()) + { + clear_search(); + reset_all(); + } - if (!module.process_input(input)) - { - INTERPOSE_NEXT(feed)(input); - module.do_post_update_check(); + return false; } + return true; } - DEFINE_VMETHOD_INTERPOSE(void, render, ()) + virtual void do_search() { - bool ok = module.init(this); - INTERPOSE_NEXT(render)(); - if (ok) - module.render(); + search_generic::do_search(); + auto list = getLayerList(viewscreen); + list->num_entries = get_primary_list()->size(); + } + + int32_t *get_viewscreen_cursor() + { + auto list = getLayerList(viewscreen); + return &list->cursor; } -}; - -template V search_hook ::module; + virtual void clear_search() + { + search_generic::clear_search(); + auto list = getLayerList(viewscreen); + list->num_entries = get_primary_list()->size(); + } -// -// END: Base Search functionality -// +private: + static df::layer_object_listst *getLayerList(const df::viewscreen_layer *layer) + { + return virtual_cast(vector_get(layer->layer_objects, LIST_ID)); + } +}; -// -// START: Stocks screen search -// -class stocks_search : public search_parent +// Parent class for screens that have more than one primary list to synchronise +template < class S, class T, class PARENT = search_generic > +class search_multicolumn_modifiable_generic : public PARENT { -public: +protected: + vector reference_list; + vector saved_indexes; + bool read_only; - virtual void render() const + virtual void update_saved_secondary_list_item(size_t i, size_t j) = 0; + virtual void save_secondary_values() = 0; + virtual void clear_secondary_viewscreen_vectors() = 0; + virtual void add_to_filtered_secondary_lists(size_t i) = 0; + virtual void clear_secondary_saved_lists() = 0; + virtual void reset_secondary_viewscreen_vectors() = 0; + virtual void restore_secondary_values() = 0; + + virtual void do_post_init() { - if (!viewscreen->in_group_mode) - print_search_option(2); - else - { - auto dim = Screen::getWindowSize(); - int x = 2, y = dim.y - 2; - OutputString(15, x, y, "Tab to enable Search"); - } + // If true, secondary list isn't modifiable so don't bother synchronising values + read_only = false; } - virtual void do_post_update_check() + void reset_all() { - if (viewscreen->in_group_mode) - { - // Disable search if item lists are grouped - clear_search(); - reset_search(); - } - else - search_parent::do_post_update_check(); + PARENT::reset_all(); + reference_list.clear(); + saved_indexes.clear(); + reset_secondary_viewscreen_vectors(); } - bool init(df::viewscreen_storesst *screen) + void reset_search() { - if (screen != viewscreen && !reset_on_change()) - return false; + PARENT::reset_search(); + reference_list.clear(); + saved_indexes.clear(); + clear_secondary_saved_lists(); + } - if (!is_valid()) + virtual void clear_search() + { + if (saved_list1.size() > 0) { - viewscreen = screen; - search_parent::init(&screen->item_cursor, &screen->items); + do_pre_incremental_search(); + restore_secondary_values(); } - - return true; + clear_secondary_saved_lists(); + PARENT::clear_search(); + do_post_search(); } + virtual bool is_match(T &a, T &b) = 0; -private: - virtual string get_element_description(df::item *element) const + virtual bool is_match(vector &a, vector &b) = 0; + + void do_pre_incremental_search() { - return Items::getDescription(element, 0, true); - } + PARENT::do_pre_incremental_search(); + if (read_only) + return; - virtual bool should_check_input(set *input) - { - if (viewscreen->in_group_mode) - return false; + bool list_has_been_sorted = (primary_list->size() == reference_list.size() + && !is_match(*primary_list, reference_list)); - if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) + for (size_t i = 0; i < saved_indexes.size(); i++) { - // Redo search if category changes - saved_list1.clear(); - end_entry_mode(); - if (search_string.length() > 0) - redo_search = true; + int adjusted_item_index = i; + if (list_has_been_sorted) + { + for (size_t j = 0; j < primary_list->size(); j++) + { + if (is_match((*primary_list)[j], reference_list[i])) + { + adjusted_item_index = j; + break; + } + } + } - return false; + update_saved_secondary_list_item(saved_indexes[i], adjusted_item_index); } + saved_indexes.clear(); + } - return true; + void clear_viewscreen_vectors() + { + search_generic::clear_viewscreen_vectors(); + saved_indexes.clear(); + clear_secondary_viewscreen_vectors(); } -}; + void add_to_filtered_list(size_t i) + { + search_generic::add_to_filtered_list(i); + add_to_filtered_secondary_lists(i); + if (!read_only) + saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed + } -typedef search_hook stocks_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); + virtual void do_post_search() + { + if (!read_only) + reference_list = *primary_list; + } -// -// END: Stocks screen search -// + void save_original_values() + { + search_generic::save_original_values(); + save_secondary_values(); + } +}; +// This basic match function is separated out from the generic multi column class, because the +// pets screen, which uses a union in its primary list, will cause a compile failure is this +// match function exists in the generic class +template < class S, class T, class PARENT = search_generic > +class search_multicolumn_modifiable : public search_multicolumn_modifiable_generic +{ + bool is_match(T &a, T &b) + { + return a == b; + } + bool is_match(vector &a, vector &b) + { + return a == b; + } +}; -// -// START: Unit screen search -// -class unitlist_search : public search_parent +// General class for screens that have only one secondary list to keep in sync +template < class S, class T, class V, class PARENT = search_generic > +class search_twocolumn_modifiable : public search_multicolumn_modifiable { public: +protected: + virtual vector * get_secondary_list() = 0; - virtual void render() const + virtual void do_post_init() { - print_search_option(28); + search_multicolumn_modifiable::do_post_init(); + secondary_list = get_secondary_list(); } - bool init(df::viewscreen_unitlistst *screen) + void save_secondary_values() { - if (screen != viewscreen && !reset_on_change()) - return false; - - if (!is_valid()) - { - viewscreen = screen; - search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); - } + saved_secondary_list = *secondary_list; + } - return true; + void reset_secondary_viewscreen_vectors() + { + secondary_list = NULL; } -private: - virtual string get_element_description(df::unit *element) const + virtual void update_saved_secondary_list_item(size_t i, size_t j) { - string desc = Translation::TranslateName(Units::getVisibleName(element), false); - desc += ", " + Units::getProfessionName(element); // Check animal type too + saved_secondary_list[i] = (*secondary_list)[j]; + } - return desc; + void clear_secondary_viewscreen_vectors() + { + secondary_list->clear(); } - virtual bool should_check_input(set *input) + void add_to_filtered_secondary_lists(size_t i) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || - (!is_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) - { - if (!is_entry_mode()) - { - // Changing screens, reset search - clear_search(); - reset_all(); - } - else - input->clear(); // Ignore cursor keys when typing + secondary_list->push_back(saved_secondary_list[i]); + } - return false; - } + void clear_secondary_saved_lists() + { + saved_secondary_list.clear(); + } - return true; + void restore_secondary_values() + { + *secondary_list = saved_secondary_list; } + vector *secondary_list, saved_secondary_list; }; -typedef search_hook unitlist_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); -template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); - -// -// END: Unit screen search -// - - -// -// TODO: Animals screen search -// - -// -// END: Animals screen search -// -// -// START: Trade screen search -// -class trade_search_base : public search_parent +// Parent struct for the hooks, use optional param D to generate multiple search classes in the same +// viewscreen but different static modules +template +struct generic_search_hook : T { + typedef T interpose_base; -private: - virtual string get_element_description(df::item *element) const - { - return Items::getDescription(element, 0, true); - } + static V module; - virtual bool should_check_input(set *input) + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { - if (is_entry_mode()) + if (!module.init(this)) + { + INTERPOSE_NEXT(feed)(input); + return; + } + + if (!module.process_input(input)) + { + INTERPOSE_NEXT(feed)(input); + module.do_post_input_feed(); + } + + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + bool ok = module.init(this); + INTERPOSE_NEXT(render)(); + if (ok) + module.render(); + } +}; + +template V generic_search_hook ::module; + + +// Hook definition helpers +#define IMPLEMENT_HOOKS_WITH_ID(screen, module, id) \ + typedef generic_search_hook module##_hook; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render) + +#define IMPLEMENT_HOOKS(screen, module) \ + typedef generic_search_hook module##_hook; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render) + + +// +// END: Generic Search functionality +// + + +// +// START: Animal screen search +// +typedef df::viewscreen_petst::T_animal T_animal; +typedef df::viewscreen_petst::T_mode T_mode; + +class pets_search : public search_multicolumn_modifiable_generic +{ +public: + void render() const + { + if (viewscreen->mode == T_mode::List) + print_search_option(25, 4); + } + +private: + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor; + } + + vector *get_primary_list() + { + return &viewscreen->animal; + } + + virtual void do_post_init() + { + is_vermin = &viewscreen->is_vermin; + pet_info = &viewscreen->pet_info; + is_tame = &viewscreen->is_tame; + is_adopting = &viewscreen->is_adopting; + } + + string get_element_description(df::viewscreen_petst::T_animal element) const + { + return get_unit_description(element.unit); + } + + bool should_check_input() + { + return viewscreen->mode == T_mode::List; + } + + void save_secondary_values() + { + is_vermin_s = *is_vermin; + pet_info_s = *pet_info; + is_tame_s = *is_tame; + is_adopting_s = *is_adopting; + } + + void reset_secondary_viewscreen_vectors() + { + is_vermin = NULL; + pet_info = NULL; + is_tame = NULL; + is_adopting = NULL; + } + + void update_saved_secondary_list_item(size_t i, size_t j) + { + is_vermin_s[i] = (*is_vermin)[j]; + pet_info_s[i] = (*pet_info)[j]; + is_tame_s[i] = (*is_tame)[j]; + is_adopting_s[i] = (*is_adopting)[j]; + } + + void clear_secondary_viewscreen_vectors() + { + is_vermin->clear(); + pet_info->clear(); + is_tame->clear(); + is_adopting->clear(); + } + + void add_to_filtered_secondary_lists(size_t i) + { + is_vermin->push_back(is_vermin_s[i]); + pet_info->push_back(pet_info_s[i]); + is_tame->push_back(is_tame_s[i]); + is_adopting->push_back(is_adopting_s[i]); + } + + void clear_secondary_saved_lists() + { + is_vermin_s.clear(); + pet_info_s.clear(); + is_tame_s.clear(); + is_adopting_s.clear(); + } + + void restore_secondary_values() + { + *is_vermin = is_vermin_s; + *pet_info = pet_info_s; + *is_tame = is_tame_s; + *is_adopting = is_adopting_s; + } + + bool is_match(T_animal &a, T_animal &b) + { + return a.unit == b.unit; + } + + bool is_match(vector &a, vector &b) + { + for (size_t i = 0; i < a.size(); i++) + { + if (!is_match(a[i], b[i])) + return false; + } + + return true; + } + + std::vector *is_vermin, is_vermin_s; + std::vector *pet_info, pet_info_s; + std::vector *is_tame, is_tame_s; + std::vector *is_adopting, is_adopting_s; +}; + +IMPLEMENT_HOOKS(df::viewscreen_petst, pets_search); + +// +// END: Animal screen search +// + + + +// +// START: Stocks screen search +// +class stocks_search : public search_generic +{ +public: + + void render() const + { + if (!viewscreen->in_group_mode) + print_search_option(2); + else + { + auto dim = Screen::getWindowSize(); + int x = 2, y = dim.y - 2; + OutputString(15, x, y, "Tab to enable Search"); + } + } + + bool process_input(set *input) + { + if (viewscreen->in_group_mode) + return false; + + redo_search = false; + + if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) + { + // Redo search if category changes + saved_list1.clear(); + end_entry_mode(); + if (search_string.length() > 0) + redo_search = true; + + return false; + } + + return search_generic::process_input(input); + } + + virtual void do_post_input_feed() + { + if (viewscreen->in_group_mode) + { + // Disable search if item lists are grouped + clear_search(); + reset_search(); + } + else if (redo_search) + { + do_search(); + redo_search = false; + } + } + +private: + int32_t *get_viewscreen_cursor() + { + return &viewscreen->item_cursor; + } + + virtual vector *get_primary_list() + { + return &viewscreen->items; + } + + +private: + string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } + + bool redo_search; +}; + + +IMPLEMENT_HOOKS(df::viewscreen_storesst, stocks_search); + +// +// END: Stocks screen search +// + + +// +// START: Unit screen search +// +class unitlist_search : public search_twocolumn_modifiable +{ +public: + void render() const + { + print_search_option(28); + } + +private: + void do_post_init() + { + search_twocolumn_modifiable::do_post_init(); + read_only = true; + } + + static string get_non_work_description(df::unit *unit) + { + for (auto p = unit->status.misc_traits.begin(); p < unit->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + { + int i = (*p)->value; + return ".on break"; + } + } + + if (unit->profession == profession::BABY || + unit->profession == profession::CHILD || + unit->profession == profession::DRUNK) + { + return ""; + } + + if (ENUM_ATTR(profession, military, unit->profession)) + return ".military"; + + return ".idle.no job"; + } + + string get_element_description(df::unit *unit) const + { + string desc = get_unit_description(unit); + if (!unit->job.current_job) + { + desc += get_non_work_description(unit); + } + + return desc; + } + + bool should_check_input(set *input) + { + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || + (!in_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) + { + if (!in_entry_mode()) + { + // Changing screens, reset search + clear_search(); + reset_all(); + } + else + input->clear(); // Ignore cursor keys when typing + + return false; + } + + return true; + } + + vector *get_secondary_list() + { + return &viewscreen->jobs[viewscreen->page]; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor_pos[viewscreen->page]; + } + + vector *get_primary_list() + { + return &viewscreen->units[viewscreen->page]; + } +}; + +IMPLEMENT_HOOKS(df::viewscreen_unitlistst, unitlist_search); + +// +// END: Unit screen search +// + + +// +// START: Trade screen search +// +class trade_search_base : public search_twocolumn_modifiable +{ + +private: + string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } + + bool should_check_input(set *input) + { + if (in_entry_mode()) + return true; + + if (input->count(interface_key::TRADE_TRADE) || + input->count(interface_key::TRADE_OFFER) || + input->count(interface_key::TRADE_SEIZE)) + { + // Block the keys if were searching + if (!search_string.empty()) + { + input->clear(); + // Send a force clear to other search class too + input->insert(interface_key::CUSTOM_ALT_C); + } + + clear_search_for_trade(); + return false; + } + else if (input->count(interface_key::CUSTOM_ALT_C)) + { + clear_search_for_trade(); return true; + } + + return true; + } + + void clear_search_for_trade() + { + // Trying to trade, reset search + clear_search(); + reset_all(); + } +}; + + +class trade_search_merc : public trade_search_base +{ +public: + virtual void render() const + { + print_search_option(2, 26); + } + +private: + vector *get_secondary_list() + { + return &viewscreen->trader_selected; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->trader_cursor; + } + + vector *get_primary_list() + { + return &viewscreen->trader_items; + } + + char get_search_select_key() + { + return 'q'; + } +}; + +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_tradegoodsst, trade_search_merc, 1); + + +class trade_search_fort : public trade_search_base +{ +public: + virtual void render() const + { + print_search_option(42, 26); + } + +private: + vector *get_secondary_list() + { + return &viewscreen->broker_selected; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->broker_cursor; + } + + vector *get_primary_list() + { + return &viewscreen->broker_items; + } + + char get_search_select_key() + { + return 'w'; + } +}; - if (input->count(interface_key::TRADE_TRADE) || - input->count(interface_key::TRADE_OFFER) || - input->count(interface_key::TRADE_SEIZE)) - { - // Block the keys if were searching - if (!search_string.empty()) - input->clear(); +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_tradegoodsst, trade_search_fort, 2); - // Trying to trade, reset search - clear_search(); - reset_all(); +// +// END: Trade screen search +// - return false; - } - return true; +// +// START: Stockpile screen search +// +typedef layered_search stocks_layer; +class stockpile_search : public search_twocolumn_modifiable +{ +public: + void update_saved_secondary_list_item(size_t i, size_t j) + { + *saved_secondary_list[i] = *(*secondary_list)[j]; + } + + string get_element_description(string *element) const + { + return *element; + } + + void render() const + { + print_search_option(51, 23); + } + + vector *get_primary_list() + { + return &viewscreen->item_names; } + + vector *get_secondary_list() + { + return &viewscreen->item_status; + } + + + }; +IMPLEMENT_HOOKS(df::viewscreen_layer_stockpilest, stockpile_search); -class trade_search_merc : public trade_search_base +// +// END: Stockpile screen search +// + + +// +// START: Military screen search +// +class military_search : public layered_search { public: - virtual void render() const + + string get_element_description(df::unit *element) const { - print_search_option(2, 26); + return get_unit_description(element); } - bool init(df::viewscreen_tradegoodsst *screen) + void render() const { - if (screen != viewscreen && !reset_on_change()) + print_search_option(52, 22); + } + + char get_search_select_key() + { + return 'q'; + } + + bool can_init(df::viewscreen_layer_militaryst *screen) + { + if (screen->page != df::viewscreen_layer_militaryst::Positions) return false; - if (!is_valid()) + return layered_search::can_init(screen); + } + + vector *get_primary_list() + { + return &viewscreen->positions.candidates; + } + + bool should_check_input(set *input) + { + if (input->count(interface_key::SELECT) && !in_entry_mode() && !search_string.empty()) { - viewscreen = screen; - search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q'); - track_secondary_values = true; + // About to make an assignment, so restore original list (it will be changed by the game) + int32_t *cursor = get_viewscreen_cursor(); + df::unit *selected_unit = get_primary_list()->at(*cursor); + clear_search(); + + for (*cursor = 0; *cursor < get_primary_list()->size(); (*cursor)++) + { + if (get_primary_list()->at(*cursor) == selected_unit) + break; + } + + reset_all(); } return true; } }; -typedef search_hook trade_search_merc_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render); +IMPLEMENT_HOOKS(df::viewscreen_layer_militaryst, military_search); +// +// END: Military screen search +// -class trade_search_fort : public trade_search_base + +// +// START: Room list search +// +static map< df::building_type, vector > room_quality_names; +static int32_t room_value_bounds[] = {1, 100, 250, 500, 1000, 1500, 2500, 10000}; + +class roomlist_search : public search_twocolumn_modifiable { public: - virtual void render() const + void render() const { - print_search_option(42, 26); + print_search_option(2, 23); } - bool init(df::viewscreen_tradegoodsst *screen) +private: + void do_post_init() { - if (screen != viewscreen && !reset_on_change()) - return false; + search_twocolumn_modifiable::do_post_init(); + read_only = true; + } - if (!is_valid()) + string get_element_description(df::building *bld) const + { + bool is_ownable_room = (bld->is_room && room_quality_names.find(bld->getType()) != room_quality_names.end()); + + string desc; + desc.reserve(100); + if (bld->owner) + desc += get_unit_description(bld->owner); + else if (is_ownable_room) + desc += "no owner"; + + desc += "."; + + if (is_ownable_room) + { + int32_t value = bld->getRoomValue(NULL); + vector *names = &room_quality_names[bld->getType()]; + string *room_name = &names->at(0); + for (int i = 1; i < 8; i++) + { + if (room_value_bounds[i] > value) + break; + room_name = &names->at(i); + } + + desc += *room_name; + } + else { - viewscreen = screen; - search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w'); - track_secondary_values = true; + string name; + bld->getName(&name); + if (!name.empty()) + { + desc += name; + } } + + return desc; + } - return true; + vector *get_secondary_list() + { + return &viewscreen->room_value; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor; + } + + vector *get_primary_list() + { + return &viewscreen->buildings; } }; -typedef search_hook trade_search_fort_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); +IMPLEMENT_HOOKS(df::viewscreen_buildinglistst, roomlist_search); // -// END: Trade screen search +// END: Room list search // + // -// START: Stockpile screen search +// START: Announcement list search // - -class stockpile_search : public search_parent +class annoucnement_search : public search_generic { public: - void update_saved_secondary_list_item(size_t i, size_t j) + void render() const { - *saved_list2[i] = *(*sort_list2)[j]; + print_search_option(2, gps->dimy - 3); } - string get_element_description(string *element) const +private: + int32_t *get_viewscreen_cursor() { - return *element; + return &viewscreen->anon_3; } - void render() const + virtual vector *get_primary_list() + { + return &viewscreen->anon_4; + } + + +private: + string get_element_description(void *element) const + { + return ((df::report *) element)->text; + } +}; + + +IMPLEMENT_HOOKS(df::viewscreen_announcelistst, annoucnement_search); + +// +// END: Announcement list search +// + + +// +// START: Nobles search list +// +typedef df::viewscreen_layer_noblelistst::T_candidates T_candidates; + +class nobles_search : public layered_search +{ +public: + + string get_element_description(T_candidates *element) const { - print_search_option(2, 25); + if (!element->unit) + return ""; + + return get_unit_description(element->unit); } - static df::layer_object_listst *getLayerList(const df::viewscreen_layer *layer, int idx) + void render() const { - return virtual_cast(vector_get(layer->layer_objects,idx)); + print_search_option(2, 23); } - bool init(df::viewscreen_layer_stockpilest *screen) + bool can_init(df::viewscreen_layer_noblelistst *screen) { - if (screen != viewscreen && !reset_on_change()) + if (screen->mode != df::viewscreen_layer_noblelistst::Appoint) return false; - auto list3 = getLayerList(screen, 2); - if (!list3->active) + return layered_search::can_init(screen); + } + + vector *get_primary_list() + { + return &viewscreen->candidates; + } +}; + +IMPLEMENT_HOOKS(df::viewscreen_layer_noblelistst, nobles_search); + +// +// END: Nobles search list +// + + +// +// START: Job list search +// +void get_job_details(string &desc, df::job *job) +{ + string job_name = ENUM_KEY_STR(job_type,job->job_type); + for (size_t i = 0; i < job_name.length(); i++) + { + char c = job_name[i]; + if (c >= 'A' && c <= 'Z') + desc += " "; + desc += c; + } + desc += "."; + + df::item_type itype = ENUM_ATTR(job_type, item, job->job_type); + + MaterialInfo mat(job); + if (itype == item_type::FOOD) + mat.decode(-1); + + if (mat.isValid() || job->material_category.whole) + { + desc += mat.toString(); + desc += "."; + if (job->material_category.whole) { - if (is_valid()) - { - clear_search(); - reset_all(); - } + desc += bitfield_to_string(job->material_category); + desc += "."; + } + } - return false; + if (!job->reaction_name.empty()) + { + for (size_t i = 0; i < job->reaction_name.length(); i++) + { + if (job->reaction_name[i] == '_') + desc += " "; + else + desc += job->reaction_name[i]; } - if (!is_valid()) + desc += "."; + } + + if (job->flags.bits.suspend) + desc += "suspended."; +} + +class joblist_search : public search_twocolumn_modifiable +{ +public: + void render() const + { + print_search_option(2); + } + +private: + void do_post_init() + { + search_twocolumn_modifiable::do_post_init(); + read_only = true; + } + + string get_element_description(df::job *element) const + { + if (!element) + return "no job.idle"; + + string desc; + desc.reserve(100); + get_job_details(desc, element); + df::unit *worker = DFHack::Job::getWorker(element); + if (worker) + desc += get_unit_description(worker); + else + desc += "Inactive"; + + return desc; + } + + vector *get_secondary_list() + { + return &viewscreen->units; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor_pos; + } + + vector *get_primary_list() + { + return &viewscreen->jobs; + } +}; + +IMPLEMENT_HOOKS(df::viewscreen_joblistst, joblist_search); + +// +// END: Job list search +// + + +// +// START: Burrow assignment search +// +using df::global::ui; + +class burrow_search : public search_twocolumn_modifiable +{ +public: + bool can_init(df::viewscreen_dwarfmodest *screen) + { + if (ui->main.mode == df::ui_sidebar_mode::Burrows && ui->burrows.in_add_units_mode) + { + return search_twocolumn_modifiable::can_init(screen); + } + else if (is_valid()) { - viewscreen = screen; - search_parent::init(&list3->cursor, &screen->item_names, &screen->item_status); - track_secondary_values = true; + clear_search(); + reset_all(); } - return true; + return false; + } + + string get_element_description(df::unit *element) const + { + return get_unit_description(element); + } + + void render() const + { + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 23; + + print_search_option(x, y); + } + + vector *get_primary_list() + { + return &ui->burrows.list_units; } - void do_search() + vector *get_secondary_list() { - search_parent::do_search(); - auto list3 = getLayerList(viewscreen, 2); - list3->num_entries = viewscreen->item_names.size(); + return &ui->burrows.sel_units; } - void clear_search() + virtual int32_t * get_viewscreen_cursor() { - search_parent::clear_search(); - auto list3 = getLayerList(viewscreen, 2); - list3->num_entries = viewscreen->item_names.size(); + return &ui->burrows.unit_cursor_pos; + } + + + bool should_check_input(set *input) + { + if (input->count(interface_key::SECONDSCROLL_UP) || input->count(interface_key::SECONDSCROLL_DOWN) + || input->count(interface_key::SECONDSCROLL_PAGEUP) || input->count(interface_key::SECONDSCROLL_PAGEDOWN)) + { + end_entry_mode(); + return false; + } + + return true; } }; -typedef search_hook stockpile_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, render); +IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, burrow_search); // -// END: Stockpile screen search +// END: Burrow assignment search // + DFHACK_PLUGIN("search"); +#define SEARCH_HOOKS \ + HOOK_ACTION(unitlist_search_hook) \ + HOOK_ACTION(roomlist_search_hook) \ + HOOK_ACTION(trade_search_merc_hook) \ + HOOK_ACTION(trade_search_fort_hook) \ + HOOK_ACTION(stocks_search_hook) \ + HOOK_ACTION(pets_search_hook) \ + HOOK_ACTION(military_search_hook) \ + HOOK_ACTION(nobles_search_hook) \ + HOOK_ACTION(annoucnement_search_hook) \ + HOOK_ACTION(joblist_search_hook) \ + HOOK_ACTION(burrow_search_hook) \ + HOOK_ACTION(stockpile_search_hook) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!gps || !gview || - !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || - !INTERPOSE_HOOK(unitlist_search_hook, render).apply() || - !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() || - !INTERPOSE_HOOK(trade_search_merc_hook, render).apply() || - !INTERPOSE_HOOK(trade_search_fort_hook, feed).apply() || - !INTERPOSE_HOOK(trade_search_fort_hook, render).apply() || - !INTERPOSE_HOOK(stocks_search_hook, feed).apply() || - !INTERPOSE_HOOK(stocks_search_hook, render).apply() || - !INTERPOSE_HOOK(stockpile_search_hook, feed).apply() || - !INTERPOSE_HOOK(stockpile_search_hook, render).apply()) +#define HOOK_ACTION(hook) \ + !INTERPOSE_HOOK(hook, feed).apply() || \ + !INTERPOSE_HOOK(hook, render).apply() || + + if (!gps || !gview || SEARCH_HOOKS 0) out.printerr("Could not insert Search hooks!\n"); +#undef HOOK_ACTION + + const string a[] = {"Meager Quarters", "Modest Quarters", "Quarters", "Decent Quarters", "Fine Quarters", "Great Bedroom", "Grand Bedroom", "Royal Bedroom"}; + room_quality_names[df::building_type::Bed] = vector(a, a + 8); + + const string b[] = {"Meager Dining Room", "Modest Dining Room", "Dining Room", "Decent Dining Room", "Fine Dining Room", "Great Dining Room", "Grand Dining Room", "Royal Dining Room"}; + room_quality_names[df::building_type::Table] = vector(b, b + 8); + + const string c[] = {"Meager Office", "Modest Office", "Office", "Decent Office", "Splendid Office", "Throne Room", "Opulent Throne Room", "Royal Throne Room"}; + room_quality_names[df::building_type::Chair] = vector(c, c + 8); + + const string d[] = {"Grave", "Servants Burial Chamber", "Burial Chamber", "Tomb", "Fine Tomb", "Mausoleum", "Grand Mausoleum", "Royal Mausoleum"}; + room_quality_names[df::building_type::Coffin] = vector(d, d + 8); + return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { - INTERPOSE_HOOK(unitlist_search_hook, feed).remove(); - INTERPOSE_HOOK(unitlist_search_hook, render).remove(); - INTERPOSE_HOOK(trade_search_merc_hook, feed).remove(); - INTERPOSE_HOOK(trade_search_merc_hook, render).remove(); - INTERPOSE_HOOK(trade_search_fort_hook, feed).remove(); - INTERPOSE_HOOK(trade_search_fort_hook, render).remove(); - INTERPOSE_HOOK(stocks_search_hook, feed).remove(); - INTERPOSE_HOOK(stocks_search_hook, render).remove(); - INTERPOSE_HOOK(stockpile_search_hook, feed).remove(); - INTERPOSE_HOOK(stockpile_search_hook, render).remove(); +#define HOOK_ACTION(hook) \ + INTERPOSE_HOOK(hook, feed).remove(); \ + INTERPOSE_HOOK(hook, render).remove(); + + SEARCH_HOOKS + +#undef HOOK_ACTION + return CR_OK; } DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event event ) { +#define HOOK_ACTION(hook) hook::module.reset_on_change(); + switch (event) { case SC_VIEWSCREEN_CHANGED: - unitlist_search_hook::module.reset_on_change(); - trade_search_merc_hook::module.reset_on_change(); - trade_search_fort_hook::module.reset_on_change(); - stocks_search_hook::module.reset_on_change(); - stockpile_search_hook::module.reset_on_change(); + SEARCH_HOOKS break; default: @@ -800,4 +1604,9 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch } return CR_OK; + +#undef HOOK_ACTION } + +#undef IMPLEMENT_HOOKS +#undef SEARCH_HOOKS \ No newline at end of file From bc5cdf88777e6655857317b60ee2ff655dafb298 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 11:37:12 -0500 Subject: [PATCH 093/136] Auto syndrome: got rid of allow multiple syndromes option that didn't make any sense, and made sure that if allowing multiple targets it doesn't attach the syndrome to the worker twice. --- plugins/autoSyndrome.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 2fa9fb681..574bcf853 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -119,7 +119,7 @@ DFhackCExport command_result plugin_init(color_ostream& out, vectorunits.all[workerIndex]; + df::unit* worker = df::global::world->units.all[workerIndex]; //find the building that made it int32_t buildingId = -1; for ( size_t a = 0; a < job->general_refs.size(); a++ ) { @@ -333,7 +333,6 @@ void processJob(color_ostream& out, void* jobPtr) { //add each syndrome to the guy who did the job df::syndrome* syndrome = inorganic->material.syndrome[b]; bool workerOnly = false; - bool allowMultipleSyndromes = false; bool allowMultipleTargets = false; bool foundCommand = false; bool destroyRock = true; @@ -345,8 +344,6 @@ void processJob(color_ostream& out, void* jobPtr) { if ( commandStr == "" ) { if ( *clazz == "\\WORKER_ONLY" ) { workerOnly = true; - } else if ( *clazz == "\\ALLOW_MULTIPLE_SYNDROMES" ) { - allowMultipleSyndromes = true; } else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) { allowMultipleTargets = true; } else if ( *clazz == "\\PRESERVE_ROCK" ) { @@ -414,10 +411,10 @@ void processJob(color_ostream& out, void* jobPtr) { } //only one syndrome per reaction will be applied, unless multiples are allowed. - if ( appliedSomething && !allowMultipleSyndromes ) + if ( appliedSomething && !allowMultipleTargets ) continue; - if ( maybeApply(out, syndrome, workerId, unit) ) { + if ( maybeApply(out, syndrome, workerId, worker) ) { appliedSomething = true; } @@ -427,6 +424,8 @@ void processJob(color_ostream& out, void* jobPtr) { //now try applying it to everybody inside the building for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { df::unit* unit = df::global::world->units.active[a]; + if ( unit == worker ) + continue; if ( unit->pos.z != building->z ) continue; if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 ) From 5fc466ef7e4967c883efd23678860ddb76b4b0b1 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 12:50:10 -0500 Subject: [PATCH 094/136] Work now: also update dig on pause. --- plugins/workNow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/workNow.cpp b/plugins/workNow.cpp index 906df33dc..5058259f5 100644 --- a/plugins/workNow.cpp +++ b/plugins/workNow.cpp @@ -42,6 +42,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan return CR_OK; *df::global::process_jobs = true; + *df::global::process_dig = true; return CR_OK; } From e1ad933068994c3441fa3e7bafed3dcbbfba1666 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 12:58:06 -0500 Subject: [PATCH 095/136] True transformation: make it compile. --- plugins/trueTransformation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp index 6da6b7e74..6c4245da8 100644 --- a/plugins/trueTransformation.cpp +++ b/plugins/trueTransformation.cpp @@ -25,9 +25,9 @@ void syndromeHandler(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - EventManager::EventHandler syndrome(syndromeHandler); + EventManager::EventHandler syndrome(syndromeHandler, 1); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("trueTransformation"); - EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, 1, me); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, me); return CR_OK; } From 9eafc7443d2f2fb23af5c194b8f3d4b658dabcb0 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 13:15:10 -0500 Subject: [PATCH 096/136] Update submodules. --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 22b01b80a..fbf671a7d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 +Subproject commit fbf671a7d5aacb41cb44059eb16a1ee9cad419be From cf3ac485184d4b0bd18eb19ea91fb88ec8d791d7 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 13:35:49 -0500 Subject: [PATCH 097/136] Merge in autoSyndrome, trueTransformation, ... --- .gitignore | 2 +- plugins/CMakeLists.txt | 2 + plugins/autoSyndrome.cpp | 477 +++++++++++++++++++++++++++++++++ plugins/devel/CMakeLists.txt | 1 + plugins/devel/printArgs.cpp | 32 +++ plugins/trueTransformation.cpp | 88 ++++++ 6 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 plugins/autoSyndrome.cpp create mode 100644 plugins/devel/printArgs.cpp create mode 100644 plugins/trueTransformation.cpp diff --git a/.gitignore b/.gitignore index 041624475..b4a578ec0 100644 --- a/.gitignore +++ b/.gitignore @@ -58,5 +58,5 @@ build/CPack*Config.cmake /cmakeall.bat -# swap files for vim +# vim swap files *.swp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1c89d9ab7..a2f99c206 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -132,6 +132,8 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp) + DFHACK_PLUGIN(trueTransformation trueTransformation.cpp) endif() diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp new file mode 100644 index 000000000..884c7749b --- /dev/null +++ b/plugins/autoSyndrome.cpp @@ -0,0 +1,477 @@ +#include "PluginManager.h" +#include "Export.h" +#include "DataDefs.h" +#include "Core.h" + +#include "modules/EventManager.h" +#include "modules/Job.h" +#include "modules/Maps.h" + +#include "df/building.h" +#include "df/caste_raw.h" +#include "df/creature_raw.h" +#include "df/global_objects.h" +#include "df/item.h" +#include "df/item_boulderst.h" +#include "df/job.h" +#include "df/job_type.h" +#include "df/reaction.h" +#include "df/reaction_product.h" +#include "df/reaction_product_type.h" +#include "df/reaction_product_itemst.h" +#include "df/syndrome.h" +#include "df/unit_syndrome.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/general_ref.h" +#include "df/general_ref_building_holderst.h" +#include "df/general_ref_type.h" +#include "df/general_ref_unit_workerst.h" + +#include +#include +#include +#include + +using namespace std; +using namespace DFHack; + +/* +Example usage: + +////////////////////////////////////////////// +//In file inorganic_duck.txt +inorganic_stone_duck + +[OBJECT:INORGANIC] + +[INORGANIC:DUCK_ROCK] +[USE_MATERIAL_TEMPLATE:STONE_TEMPLATE] +[STATE_NAME_ADJ:ALL_SOLID:drakium][DISPLAY_COLOR:0:7:0][TILE:'.'] +[IS_STONE] +[SOLID_DENSITY:1][MELTING_POINT:25000] +[BOILING_POINT:9999] //This is the critical line: boiling point must be <= 10000 +[SYNDROME] + [SYN_NAME:Chronic Duck Syndrome] + [CE_BODY_TRANSFORMATION:PROB:100:START:0] + [CE:CREATURE:BIRD_DUCK:MALE] //even though we don't have SYN_INHALED, the plugin will add it +/////////////////////////////////////////////// +//In file building_duck.txt +building_duck + +[OBJECT:BUILDING] + +[BUILDING_WORKSHOP:DUCK_WORKSHOP] + [NAME:Duck Workshop] + [NAME_COLOR:7:0:1] + [DIM:1:1] + [WORK_LOCATION:1:1] + [BLOCK:1:0:0:0] + [TILE:0:1:236] + [COLOR:0:1:0:0:1] + [TILE:1:1:' '] + [COLOR:1:1:0:0:0] + [TILE:2:1:8] + [COLOR:2:1:0:0:1] + [TILE:3:1:8] + [COLOR:3:2:0:4:1] + [BUILD_ITEM:1:NONE:NONE:NONE:NONE] + [BUILDMAT] + [WORTHLESS_STONE_ONLY] + [CAN_USE_ARTIFACT] +/////////////////////////////////////////////// +//In file reaction_duck.txt +reaction_duck + +[OBJECT:REACTION] + +[REACTION:DUCKIFICATION] +[NAME:become a duck] +[BUILDING:DUCK_WORKSHOP:NONE] +[PRODUCT:100:100:STONE:NO_SUBTYPE:STONE:DUCK_ROCK] +////////////////////////////////////////////// +//Add the following lines to your entity in entity_default.txt (or wherever it is) + [PERMITTED_BUILDING:DUCK_WORKSHOP] + [PERMITTED_REACTION:DUCKIFICATION] +////////////////////////////////////////////// + +Next, start a new fort in a new world, build a duck workshop, then have someone become a duck. +*/ + +bool enabled = true; + +DFHACK_PLUGIN("autoSyndrome"); + +command_result autoSyndrome(color_ostream& out, vector& parameters); +void processJob(color_ostream& out, void* jobPtr); +int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome); + +DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { + commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false, + "autoSyndrome:\n" + " autoSyndrome 0 //disable\n" + " autoSyndrome 1 //enable\n" + " autoSyndrome disable //disable\n" + " autoSyndrome enable //enable\n" + "\n" + "autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n" + "\n" + "Requirements:\n" + " 1) The job must be a custom reaction.\n" + " 2) The job must produce a stone of some inorganic material.\n" + " 3) The stone must have a boiling temperature less than or equal to 9000.\n" + "\n" + "When these conditions are met, the unit that completed the job will immediately become afflicted with all applicable syndromes associated with the inorganic material of the stone, or stones. It should correctly check for whether the creature or caste is affected or immune, and it should also correctly account for affected and immune creature classes.\n" + "Multiple syndromes per stone, or multiple boiling rocks produced with the same reaction should work fine.\n" + )); + + + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome"); + EventManager::EventHandler handle(processJob, 5); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream& out) { + return CR_OK; +} + +/*DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event e) { + return CR_OK; +}*/ + +command_result autoSyndrome(color_ostream& out, vector& parameters) { + if ( parameters.size() > 1 ) + return CR_WRONG_USAGE; + + bool wasEnabled = enabled; + if ( parameters.size() == 1 ) { + if ( parameters[0] == "enable" ) { + enabled = true; + } else if ( parameters[0] == "disable" ) { + enabled = false; + } else { + int32_t a = atoi(parameters[0].c_str()); + if ( a < 0 || a > 1 ) + return CR_WRONG_USAGE; + + enabled = (bool)a; + } + } + + out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled"); + if ( enabled == wasEnabled ) + return CR_OK; + + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome"); + if ( enabled ) { + EventManager::EventHandler handle(processJob, 5); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + } else { + EventManager::unregisterAll(me); + } + return CR_OK; +} + +bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df::unit* unit) { + df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; + df::caste_raw* caste = creature->caste[unit->caste]; + std::string& creature_name = creature->creature_id; + std::string& creature_caste = caste->caste_id; + //check that the syndrome applies to that guy + /* + * If there is no affected class or affected creature, then anybody who isn't immune is fair game. + * + * Otherwise, it works like this: + * add all the affected class creatures + * remove all the immune class creatures + * add all the affected creatures + * remove all the immune creatures + * you're affected if and only if you're in the remaining list after all of that + **/ + bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature.size() == 0; + for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) { + if ( applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) { + applies = true; + break; + } + } + } + for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) { + if ( !applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) { + applies = false; + break; + } + } + } + + if ( syndrome->syn_affected_creature.size() != syndrome->syn_affected_caste.size() ) { + out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__); + return false; + } + for ( size_t c = 0; c < syndrome->syn_affected_creature.size(); c++ ) { + if ( creature_name != *syndrome->syn_affected_creature[c] ) + continue; + if ( *syndrome->syn_affected_caste[c] == "ALL" || + *syndrome->syn_affected_caste[c] == creature_caste ) { + applies = true; + break; + } + } + for ( size_t c = 0; c < syndrome->syn_immune_creature.size(); c++ ) { + if ( creature_name != *syndrome->syn_immune_creature[c] ) + continue; + if ( *syndrome->syn_immune_caste[c] == "ALL" || + *syndrome->syn_immune_caste[c] == creature_caste ) { + applies = false; + break; + } + } + if ( !applies ) { + return false; + } + if ( giveSyndrome(out, workerId, syndrome) < 0 ) + return false; + return true; +} + +void processJob(color_ostream& out, void* jobPtr) { + df::job* job = (df::job*)jobPtr; + if ( job == NULL ) { + out.print("Error %s line %d: null job.\n", __FILE__, __LINE__); + return; + } + if ( job->completion_timer > 0 ) + return; + + if ( job->job_type != df::job_type::CustomReaction ) + return; + + df::reaction* reaction = NULL; + for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) { + df::reaction* candidate = df::global::world->raws.reactions[a]; + if ( candidate->code != job->reaction_name ) + continue; + reaction = candidate; + break; + } + if ( reaction == NULL ) { + out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() ); + return; + } + + int32_t workerId = -1; + for ( size_t a = 0; a < job->general_refs.size(); a++ ) { + if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) + continue; + if ( workerId != -1 ) { + out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__); + } + workerId = ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id; + if (workerId == -1) { + out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__); + continue; + } + } + + int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId); + if ( workerIndex < 0 ) { + out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); + return; + } + df::unit* worker = df::global::world->units.all[workerIndex]; + //find the building that made it + int32_t buildingId = -1; + for ( size_t a = 0; a < job->general_refs.size(); a++ ) { + if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER ) + continue; + if ( buildingId != -1 ) { + out.print("%s, line %d: Found two buildings for the same job.\n", __FILE__, __LINE__); + } + buildingId = ((df::general_ref_building_holderst*)job->general_refs[a])->building_id; + if (buildingId == -1) { + out.print("%s, line %d: invalid building.\n", __FILE__, __LINE__); + continue; + } + } + df::building* building; + { + int32_t index = df::building::binsearch_index(df::global::world->buildings.all, buildingId); + if ( index == -1 ) { + out.print("%s, line %d: error: couldn't find building %d.\n", __FILE__, __LINE__, buildingId); + return; + } + building = df::global::world->buildings.all[index]; + } + + //find all of the products it makes. Look for a stone with a low boiling point. + bool appliedSomething = false; + for ( size_t a = 0; a < reaction->products.size(); a++ ) { + df::reaction_product_type type = reaction->products[a]->getType(); + //out.print("type = %d\n", (int32_t)type); + if ( type != df::enums::reaction_product_type::item ) + continue; + df::reaction_product_itemst* bob = (df::reaction_product_itemst*)reaction->products[a]; + //out.print("item_type = %d\n", (int32_t)bob->item_type); + if ( bob->item_type != df::enums::item_type::BOULDER ) + continue; + //for now don't worry about subtype + + //must be a boiling rock syndrome + df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index]; + if ( inorganic->material.heat.boiling_point > 9000 ) { + continue; + } + + for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) { + //add each syndrome to the guy who did the job + df::syndrome* syndrome = inorganic->material.syndrome[b]; + bool workerOnly = false; + bool allowMultipleTargets = false; + bool foundCommand = false; + bool destroyRock = true; + string commandStr; + vector args; + for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) { + std::string* clazz = syndrome->syn_class[c]; + if ( foundCommand ) { + if ( commandStr == "" ) { + if ( *clazz == "\\WORKER_ONLY" ) { + workerOnly = true; + } else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) { + allowMultipleTargets = true; + } else if ( *clazz == "\\PRESERVE_ROCK" ) { + destroyRock = false; + } + else { + commandStr = *clazz; + } + } else { + stringstream bob; + if ( *clazz == "\\LOCATION" ) { + bob << job->pos.x; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + + bob << job->pos.y; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + + bob << job->pos.z; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + } else if ( *clazz == "\\WORKER_ID" ) { + bob << workerId; + args.push_back(bob.str()); + } else if ( *clazz == "\\REACTION_INDEX" ) { + bob << reaction->index; + args.push_back(bob.str()); + } else { + args.push_back(*clazz); + } + } + } else if ( *clazz == "\\COMMAND" ) { + foundCommand = true; + } + } + if ( commandStr != "" ) { + Core::getInstance().runCommand(out, commandStr, args); + } + + if ( destroyRock ) { + //find the rock and kill it before it can boil and cause problems and ugliness + for ( size_t c = 0; c < df::global::world->items.all.size(); c++ ) { + df::item* item = df::global::world->items.all[c]; + if ( item->pos.z != building->z ) + continue; + if ( item->pos.x < building->x1 || item->pos.x > building->x2 ) + continue; + if ( item->pos.y < building->y1 || item->pos.y > building->y2 ) + continue; + if ( item->getType() != df::enums::item_type::BOULDER ) + continue; + //make sure it's the right type of boulder + df::item_boulderst* boulder = (df::item_boulderst*)item; + if ( boulder->mat_index != bob->mat_index ) + continue; + + boulder->flags.bits.garbage_collect = true; + boulder->flags.bits.forbid = true; + boulder->flags.bits.hidden = true; + } + } + + //only one syndrome per reaction will be applied, unless multiples are allowed. + if ( appliedSomething && !allowMultipleTargets ) + continue; + + if ( maybeApply(out, syndrome, workerId, worker) ) { + appliedSomething = true; + } + + if ( workerOnly ) + continue; + + //now try applying it to everybody inside the building + for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { + df::unit* unit = df::global::world->units.active[a]; + if ( unit == worker ) + continue; + if ( unit->pos.z != building->z ) + continue; + if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 ) + continue; + if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 ) + continue; + if ( maybeApply(out, syndrome, unit->id, unit) ) { + appliedSomething = true; + if ( !allowMultipleTargets ) + break; + } + } + } + } + + return; +} + +/* + * Heavily based on https://gist.github.com/4061959/ + **/ +int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome) { + int32_t index = df::unit::binsearch_index(df::global::world->units.all, workerId); + if ( index < 0 ) { + out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); + return -1; + } + df::unit* unit = df::global::world->units.all[index]; + + df::unit_syndrome* unitSyndrome = new df::unit_syndrome(); + unitSyndrome->type = syndrome->id; + unitSyndrome->year = 0; + unitSyndrome->year_time = 0; + unitSyndrome->ticks = 1; + unitSyndrome->unk1 = 1; + unitSyndrome->flags = 0; //typecast + + for ( size_t a = 0; a < syndrome->ce.size(); a++ ) { + df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms(); + symptom->unk1 = 0; + symptom->unk2 = 0; + symptom->ticks = 1; + symptom->flags = 2; //TODO: ??? + unitSyndrome->symptoms.push_back(symptom); + } + unit->syndromes.active.push_back(unitSyndrome); + return 0; +} + diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index c2d67c628..8a1edb9cc 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -19,6 +19,7 @@ DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) +DFHACK_PLUGIN(printArgs printArgs.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/devel/printArgs.cpp b/plugins/devel/printArgs.cpp new file mode 100644 index 000000000..051c7b1dc --- /dev/null +++ b/plugins/devel/printArgs.cpp @@ -0,0 +1,32 @@ + +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include + +using namespace DFHack; +using namespace std; + +command_result printArgs (color_ostream &out, std::vector & parameters); + +DFHACK_PLUGIN("printArgs"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "printArgs", "Print the arguments given.", + printArgs, false + )); + return CR_OK; +} + +command_result printArgs (color_ostream &out, std::vector & parameters) +{ + for ( size_t a = 0; a < parameters.size(); a++ ) { + out << "Argument " << (a+1) << ": \"" << parameters[a] << "\"" << endl; + } + return CR_OK; +} diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp new file mode 100644 index 000000000..6c4245da8 --- /dev/null +++ b/plugins/trueTransformation.cpp @@ -0,0 +1,88 @@ + +#include "Core.h" +#include "Console.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/EventManager.h" + +#include "df/caste_raw.h" +#include "df/creature_raw.h" +#include "df/syndrome.h" +#include "df/unit.h" +#include "df/unit_syndrome.h" +#include "df/world.h" + +#include + +using namespace DFHack; +using namespace std; + +DFHACK_PLUGIN("trueTransformation"); + +void syndromeHandler(color_ostream& out, void* ptr); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + EventManager::EventHandler syndrome(syndromeHandler, 1); + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("trueTransformation"); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, me); + + return CR_OK; +} + +void syndromeHandler(color_ostream& out, void* ptr) { + EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr; + //out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); + + int32_t index = df::unit::binsearch_index(df::global::world->units.active, data->unitId); + if ( index < 0 ) { + out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__); + return; + } + df::unit* unit = df::global::world->units.active[index]; + df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex]; + df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type]; + + bool foundIt = false; + int32_t raceId = -1; + df::creature_raw* creatureRaw = NULL; + int32_t casteId = -1; + for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) { + if ( *syndrome->syn_class[a] == "\\PERMANENT" ) { + foundIt = true; + } + if ( foundIt && raceId == -1 ) { + //find the race with the name + string& name = *syndrome->syn_class[a]; + for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) { + df::creature_raw* creature = df::global::world->raws.creatures.all[b]; + if ( creature->creature_id != name ) + continue; + raceId = b; + creatureRaw = creature; + break; + } + continue; + } + if ( foundIt && raceId != -1 ) { + string& name = *syndrome->syn_class[a]; + for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) { + df::caste_raw* caste = creatureRaw->caste[b]; + if ( caste->caste_id != name ) + continue; + casteId = b; + break; + } + break; + } + } + if ( !foundIt || raceId == -1 || casteId == -1 ) + return; + + unit->enemy.normal_race = raceId; + unit->enemy.normal_caste = casteId; + //that's it! +} + From 4920293c2db40454c101bdfcb524aaad6816d37d Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 16:30:48 -0500 Subject: [PATCH 098/136] Infinite sky: get it to compile. --- plugins/skyEternal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/skyEternal.cpp b/plugins/skyEternal.cpp index 04a176a8d..3ab353b4f 100644 --- a/plugins/skyEternal.cpp +++ b/plugins/skyEternal.cpp @@ -80,7 +80,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; { t_gamemodes mode; - if ( !Core::getInstance().getWorld()->ReadGameMode(mode) ) + if ( !World::ReadGameMode(mode) ) return CR_FAILURE; if ( mode.g_mode != df::enums::game_mode::DWARF ) return CR_OK; From 151ff0f29659db1b9d39e480a98e6bf674db77ef Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 16:34:33 -0500 Subject: [PATCH 099/136] Infinite sky: rename from sky eternal. --- plugins/CMakeLists.txt | 2 +- plugins/{skyEternal.cpp => infiniteSky.cpp} | 22 ++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) rename plugins/{skyEternal.cpp => infiniteSky.cpp} (90%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6b8986dc3..22be1c555 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -135,7 +135,7 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp) DFHACK_PLUGIN(trueTransformation trueTransformation.cpp) - DFHACK_PLUGIN(skyEternal skyEternal.cpp) + DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) endif() diff --git a/plugins/skyEternal.cpp b/plugins/infiniteSky.cpp similarity index 90% rename from plugins/skyEternal.cpp rename to plugins/infiniteSky.cpp index 3ab353b4f..f8690b5d0 100644 --- a/plugins/skyEternal.cpp +++ b/plugins/infiniteSky.cpp @@ -22,24 +22,24 @@ using namespace std; using namespace DFHack; using namespace df::enums; -command_result skyEternal (color_ostream &out, std::vector & parameters); +command_result infiniteSky (color_ostream &out, std::vector & parameters); -DFHACK_PLUGIN("skyEternal"); +DFHACK_PLUGIN("infiniteSky"); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "skyEternal", + "infiniteSky", "Creates new sky levels on request, or as you construct up.", - skyEternal, false, + infiniteSky, false, "Usage:\n" - " skyEternal\n" + " infiniteSky\n" " creates one more z-level\n" - " skyEternal [n]\n" + " infiniteSky [n]\n" " creates n more z-level(s)\n" - " skyEternal enable\n" + " infiniteSky enable\n" " enables monitoring of constructions\n" - " skyEternal disable\n" + " infiniteSky disable\n" " disable monitoring of constructions\n" "\n" "If construction monitoring is enabled, then the plugin will automatically create new sky z-levels as you construct upward.\n" @@ -94,7 +94,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) df::construction* construct = df::global::world->constructions[a]; if ( construct->pos.z+2 < zNow ) continue; - skyEternal(out, vec); + infiniteSky(out, vec); zNow = df::global::world->map.z_count_block; ///break; } @@ -103,14 +103,14 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } -command_result skyEternal (color_ostream &out, std::vector & parameters) +command_result infiniteSky (color_ostream &out, std::vector & parameters) { if ( parameters.size() > 1 ) return CR_WRONG_USAGE; if ( parameters.size() == 0 ) { vector vec; vec.push_back("1"); - return skyEternal(out, vec); + return infiniteSky(out, vec); } if (parameters[0] == "enable") { enabled = true; From 4d57a053fc9ed9772e3ee20d541e571e7ca3329e Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 16:51:29 -0500 Subject: [PATCH 100/136] Infinite sky: added helpful print statements. --- plugins/infiniteSky.cpp | 54 ++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/plugins/infiniteSky.cpp b/plugins/infiniteSky.cpp index f8690b5d0..b86ea867a 100644 --- a/plugins/infiniteSky.cpp +++ b/plugins/infiniteSky.cpp @@ -71,6 +71,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan static size_t constructionSize = 0; static bool enabled = false; +void doInfiniteSky(color_ostream& out, int32_t howMany); DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { @@ -89,12 +90,11 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if ( df::global::world->constructions.size() == constructionSize ) return CR_OK; int32_t zNow = df::global::world->map.z_count_block; - vector vec; for ( size_t a = constructionSize; a < df::global::world->constructions.size(); a++ ) { df::construction* construct = df::global::world->constructions[a]; if ( construct->pos.z+2 < zNow ) continue; - infiniteSky(out, vec); + doInfiniteSky(out, 1); zNow = df::global::world->map.z_count_block; ///break; } @@ -103,26 +103,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } -command_result infiniteSky (color_ostream &out, std::vector & parameters) -{ - if ( parameters.size() > 1 ) - return CR_WRONG_USAGE; - if ( parameters.size() == 0 ) { - vector vec; - vec.push_back("1"); - return infiniteSky(out, vec); - } - if (parameters[0] == "enable") { - enabled = true; - return CR_OK; - } - if (parameters[0] == "disable") { - enabled = false; - constructionSize = 0; - return CR_OK; - } - int32_t howMany = 0; - howMany = atoi(parameters[0].c_str()); +void doInfiniteSky(color_ostream& out, int32_t howMany) { df::world* world = df::global::world; CoreSuspender suspend; int32_t x_count_block = world->map.x_count_block; @@ -138,6 +119,9 @@ command_result infiniteSky (color_ostream &out, std::vector & para column[z_count_block] = NULL; delete[] block_index[a][b]; block_index[a][b] = column; + + //deal with map_block_column stuff even though it'd probably be fine + } } df::z_level_flags* flags = new df::z_level_flags[z_count_block+1]; @@ -148,5 +132,31 @@ command_result infiniteSky (color_ostream &out, std::vector & para world->map.z_count++; } +} + +command_result infiniteSky (color_ostream &out, std::vector & parameters) +{ + if ( parameters.size() > 1 ) + return CR_WRONG_USAGE; + if ( parameters.size() == 0 ) { + vector vec; + vec.push_back("1"); + return infiniteSky(out, vec); + } + if (parameters[0] == "enable") { + enabled = true; + out.print("Construction monitoring enabled.\n"); + return CR_OK; + } + if (parameters[0] == "disable") { + enabled = false; + out.print("Construction monitoring disabled.\n"); + constructionSize = 0; + return CR_OK; + } + int32_t howMany = 0; + howMany = atoi(parameters[0].c_str()); + out.print("InfiniteSky: creating %d new z-levels of sky.\n", howMany); + doInfiniteSky(out, howMany); return CR_OK; } From 86c3c385bd3895c8a29ac1da36deaecd4834790d Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 5 Jan 2013 16:27:57 -0600 Subject: [PATCH 101/136] Autolabor: exclude hauling labors from clawback --- plugins/autolabor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index d96baec9e..788247352 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -2062,6 +2062,8 @@ public: { if (l == df::unit_labor::NONE) continue; + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMAL) + continue; if (labor_infos[l].idle_dwarfs > 0 && labor_needed[l] > labor_infos[l].busy_dwarfs) { int clawback = labor_infos[l].busy_dwarfs; From 47b20ea301c267d69e4271691b21593acbd3193e Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 17:32:23 -0500 Subject: [PATCH 102/136] Infinite sky: added glyphs and made it print status when no arguments given. --- plugins/infiniteSky.cpp | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/plugins/infiniteSky.cpp b/plugins/infiniteSky.cpp index b86ea867a..1c99df6d0 100644 --- a/plugins/infiniteSky.cpp +++ b/plugins/infiniteSky.cpp @@ -10,6 +10,7 @@ #include "df/construction.h" #include "df/game_mode.h" #include "df/map_block.h" +#include "df/map_block_column.h" #include "df/world.h" #include "df/z_level_flags.h" @@ -114,14 +115,32 @@ void doInfiniteSky(color_ostream& out, int32_t howMany) { df::map_block**** block_index = world->map.block_index; for ( int32_t a = 0; a < x_count_block; a++ ) { for ( int32_t b = 0; b < y_count_block; b++ ) { - df::map_block** column = new df::map_block*[z_count_block+1]; - memcpy(column, block_index[a][b], z_count_block*sizeof(df::map_block*)); - column[z_count_block] = NULL; + df::map_block** blockColumn = new df::map_block*[z_count_block+1]; + memcpy(blockColumn, block_index[a][b], z_count_block*sizeof(df::map_block*)); + blockColumn[z_count_block] = NULL; delete[] block_index[a][b]; - block_index[a][b] = column; + block_index[a][b] = blockColumn; //deal with map_block_column stuff even though it'd probably be fine - + df::map_block_column* column = world->map.column_index[a][b]; + if ( !column ) { + out.print("%s, line %d: column is null (%d, %d).\n", __FILE__, __LINE__, a, b); + continue; + } + df::map_block_column::T_unmined_glyphs* glyphs = new df::map_block_column::T_unmined_glyphs; + glyphs->x[0] = 0; + glyphs->x[1] = 1; + glyphs->x[2] = 2; + glyphs->x[3] = 3; + glyphs->y[0] = 0; + glyphs->y[1] = 0; + glyphs->y[2] = 0; + glyphs->y[3] = 0; + glyphs->tile[0] = 'e'; + glyphs->tile[1] = 'x'; + glyphs->tile[2] = 'p'; + glyphs->tile[3] = '^'; + column->unmined_glyphs.push_back(glyphs); } } df::z_level_flags* flags = new df::z_level_flags[z_count_block+1]; @@ -139,9 +158,8 @@ command_result infiniteSky (color_ostream &out, std::vector & para if ( parameters.size() > 1 ) return CR_WRONG_USAGE; if ( parameters.size() == 0 ) { - vector vec; - vec.push_back("1"); - return infiniteSky(out, vec); + out.print("Construction monitoring is %s.\n", enabled ? "enabled" : "disabled"); + return CR_OK; } if (parameters[0] == "enable") { enabled = true; @@ -156,7 +174,7 @@ command_result infiniteSky (color_ostream &out, std::vector & para } int32_t howMany = 0; howMany = atoi(parameters[0].c_str()); - out.print("InfiniteSky: creating %d new z-levels of sky.\n", howMany); + out.print("InfiniteSky: creating %d new z-level%s of sky.\n", howMany, howMany == 1 ? "" : "s" ); doInfiniteSky(out, howMany); return CR_OK; } From f8abd5c595e5fa25b210258279478ef06830b9c6 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 18:09:50 -0500 Subject: [PATCH 103/136] StepBetween: renamed stuff back to the way it was. Thought I had already done that. --- library/LuaApi.cpp | 2 +- library/include/modules/Maps.h | 2 +- library/modules/Maps.cpp | 2 +- plugins/devel/stepBetween.cpp | 2 +- plugins/fix-armory.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 720ccd21f..0151ed404 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1309,7 +1309,7 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPM(Maps, enableBlockUpdates), WRAPM(Maps, getGlobalInitFeature), WRAPM(Maps, getLocalInitFeature), - WRAPM(Maps, canPathBetween), + WRAPM(Maps, canWalkBetween), WRAPM(Maps, spawnFlow), WRAPN(hasTileAssignment, hasTileAssignment), WRAPN(getTileAssignment, getTileAssignment), diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 29d3d69ba..265c89a9f 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -308,7 +308,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, /// remove a block event from the block by address extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); -DFHACK_EXPORT bool canPathBetween(df::coord pos1, df::coord pos2); +DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2); } } diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 6517331ee..5c0608765 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -524,7 +524,7 @@ bool Maps::ReadGeology(vector > *layer_mats, vector return true; } -bool Maps::canPathBetween(df::coord pos1, df::coord pos2) +bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) { auto block1 = getTileBlock(pos1); auto block2 = getTileBlock(pos2); diff --git a/plugins/devel/stepBetween.cpp b/plugins/devel/stepBetween.cpp index c1bc3fa1a..32d5be31b 100644 --- a/plugins/devel/stepBetween.cpp +++ b/plugins/devel/stepBetween.cpp @@ -80,7 +80,7 @@ df::coord prev; command_result stepBetween (color_ostream &out, std::vector & parameters) { df::coord bob = Gui::getCursorPos(); - out.print("(%d,%d,%d), (%d,%d,%d): canWalkBetween = %d, canPathBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canPathBetween(prev,bob)); + out.print("(%d,%d,%d), (%d,%d,%d): canStepBetween = %d, canWalkBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canWalkBetween(prev,bob)); prev = bob; return CR_OK; diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index e54639960..b937d40e8 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -475,7 +475,7 @@ static bool try_store_item(df::building *target, df::item *item) df::coord tpos(target->centerx, target->centery, target->z); df::coord ipos = Items::getPosition(item); - if (!Maps::canPathBetween(tpos, ipos)) + if (!Maps::canWalkBetween(tpos, ipos)) return false; // Check if the target has enough space left From 553a31226604b1813d6611791ab4060dc2be484b Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 6 Jan 2013 10:59:20 -0600 Subject: [PATCH 104/136] Autolabor: add deconstruct bridge labor --- plugins/autolabor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 788247352..b95550ec1 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -851,6 +851,7 @@ private: case df::building_type::TradeDepot: case df::building_type::Construction: case df::building_type::Wagon: + case df::building_type::Bridge: { df::building_actual* b = (df::building_actual*) bld; return construction_build_labor(b->contained_items[0]->item); From 6fd9ce339d657ce00e45dc0414f703e95d609b0c Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 7 Jan 2013 12:09:39 -0500 Subject: [PATCH 105/136] Autosyndrome: fixed the rules on when syndromes apply. --- plugins/autoSyndrome.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 884c7749b..79e8b35ec 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -184,8 +184,8 @@ bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df * * Otherwise, it works like this: * add all the affected class creatures - * remove all the immune class creatures * add all the affected creatures + * remove all the immune class creatures * remove all the immune creatures * you're affected if and only if you're in the remaining list after all of that **/ @@ -206,7 +206,7 @@ bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) { applies = false; - break; + return false; } } } @@ -230,7 +230,7 @@ bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df if ( *syndrome->syn_immune_caste[c] == "ALL" || *syndrome->syn_immune_caste[c] == creature_caste ) { applies = false; - break; + return false; } } if ( !applies ) { From 0b6858c7ffdc5dd36c991d7e2fec4c528d7d0753 Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 7 Jan 2013 12:36:11 -0500 Subject: [PATCH 106/136] Update news. --- NEWS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS b/NEWS index ed6290261..5aec19361 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,8 @@ DFHack future Internals: - support for displaying active keybindings properly. - support for reusable widgets in lua screen library. + - Maps::canStepBetween: returns whether you can walk between two tiles in one step. + - EventManager: monitors various in game events centrally so that individual plugins don't have to monitor the same things redundantly. Notable bugfixes: - autobutcher can be re-enabled again after being stopped. - stopped Dwarf Manipulator from unmasking vampires. @@ -59,6 +61,11 @@ DFHack future saving you from having to trawl through long lists of materials each time you place one. Dfusion plugin: Reworked to make use of lua modules, now all the scripts can be used from other scripts. + Auto syndrome plugin: a way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws. + Infinite sky plugin: create new z-levels automatically or on request. + True transformation plugin: a better way of doing permanent transformations that allows later transformations. + Work now plugin: makes the game assign jobs every time you pause. + DFHack v0.34.11-r2 From 7c1fb39ec0f09cc4ec2fa3b7727c04db9b648817 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 9 Jan 2013 10:19:43 -0600 Subject: [PATCH 107/136] No need for these to be separate variables --- CMakeLists.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 50d50d18d..0d8813635 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,14 +58,10 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION_MAJOR "0") -set(DF_VERSION_MINOR "34") -set(DF_VERSION_PATCH "11") -set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}") - +set(DF_VERSION "0.34.11") SET(DFHACK_RELEASE "r2" CACHE STRING "Current release revision.") -set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}") +set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}") ## where to install things (after the build is done, classic 'make install' or package structure) From 44662de6010183bf266464c1a91810dfdbeb29e8 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 13 Jan 2013 15:28:17 -0500 Subject: [PATCH 108/136] Infinite sky: bug with z level flags. --- plugins/infiniteSky.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/infiniteSky.cpp b/plugins/infiniteSky.cpp index 1c99df6d0..8750e5435 100644 --- a/plugins/infiniteSky.cpp +++ b/plugins/infiniteSky.cpp @@ -149,6 +149,8 @@ void doInfiniteSky(color_ostream& out, int32_t howMany) { flags[z_count_block].bits.update = 1; world->map.z_count_block++; world->map.z_count++; + delete[] world->map.z_level_flags; + world->map.z_level_flags = flags; } } From 90a62a82f744213d3ceccf5ecc7d6c754f841733 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 14 Jan 2013 12:12:56 -0600 Subject: [PATCH 109/136] Autolabor: add screw pump build labor --- plugins/autolabor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index b95550ec1..975ac1942 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -782,6 +782,7 @@ private: case df::building_type::WaterWheel: case df::building_type::RoadPaved: case df::building_type::Well: + case df::building_type::ScrewPump: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) From 0073c1bec2e73650e74f33c627aaf749e89123b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 15 Jan 2013 23:16:15 +0100 Subject: [PATCH 110/136] Track xml and stonesense --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 55cb62822..e87942b50 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 55cb628224f9da7d39e88e62a312d877aeed537f +Subproject commit e87942b50481280c027d8b5f64d452c09e3be095 diff --git a/plugins/stonesense b/plugins/stonesense index cb97cf308..8d3533329 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd +Subproject commit 8d35333298ceed10b089d12e730e451d427e616d From e06b6904f1fc06a3d02737c8da75e934ffb8c838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 15 Jan 2013 23:41:43 +0100 Subject: [PATCH 111/136] Small fix to sync dfhack with the structures. --- library/modules/Materials.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index e15e7e83d..17823f5f1 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -766,7 +766,7 @@ bool Materials::ReadCreatureTypesEx (void) for(size_t k = 0; k < sizecolormod;k++) { // color mod [0] -> color list - auto & indexes = colorings[k]->color_indexes; + auto & indexes = colorings[k]->pattern_index; size_t sizecolorlist = indexes.size(); caste.ColorModifier[k].colorlist.resize(sizecolorlist); for(size_t l = 0; l < sizecolorlist; l++) From 8741983aaa9ef166234b4a7de7011e6596ebebc9 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 6 Jan 2013 14:59:39 +1300 Subject: [PATCH 112/136] Fix for gcc errors. It seems calls to base class members in a templated class must be fully template qualified. --- plugins/search.cpp | 48 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index cc1d410d3..53c45806d 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -376,10 +376,10 @@ protected: auto list = getLayerList(screen); if (!list->active) { - if (is_valid()) + if (this->is_valid()) { - clear_search(); - reset_all(); + this->clear_search(); + this->reset_all(); } return false; @@ -388,24 +388,24 @@ protected: return true; } - virtual void do_search() + virtual void do_search() { - search_generic::do_search(); - auto list = getLayerList(viewscreen); - list->num_entries = get_primary_list()->size(); + search_generic::do_search(); + auto list = getLayerList(this->viewscreen); + list->num_entries = this->get_primary_list()->size(); } - - int32_t *get_viewscreen_cursor() + + int32_t *get_viewscreen_cursor() { - auto list = getLayerList(viewscreen); + auto list = getLayerList(this->viewscreen); return &list->cursor; } virtual void clear_search() { - search_generic::clear_search(); - auto list = getLayerList(viewscreen); - list->num_entries = get_primary_list()->size(); + search_generic::clear_search(); + auto list = getLayerList(this->viewscreen); + list->num_entries = this->get_primary_list()->size(); } private: @@ -458,7 +458,7 @@ protected: virtual void clear_search() { - if (saved_list1.size() > 0) + if (this->saved_list1.size() > 0) { do_pre_incremental_search(); restore_secondary_values(); @@ -471,24 +471,24 @@ protected: virtual bool is_match(T &a, T &b) = 0; virtual bool is_match(vector &a, vector &b) = 0; - + void do_pre_incremental_search() { PARENT::do_pre_incremental_search(); if (read_only) return; - bool list_has_been_sorted = (primary_list->size() == reference_list.size() - && !is_match(*primary_list, reference_list)); + bool list_has_been_sorted = (this->primary_list->size() == reference_list.size() + && !is_match(*this->primary_list, reference_list)); for (size_t i = 0; i < saved_indexes.size(); i++) { int adjusted_item_index = i; if (list_has_been_sorted) { - for (size_t j = 0; j < primary_list->size(); j++) + for (size_t j = 0; j < this->primary_list->size(); j++) { - if (is_match((*primary_list)[j], reference_list[i])) + if (is_match((*this->primary_list)[j], reference_list[i])) { adjusted_item_index = j; break; @@ -503,14 +503,14 @@ protected: void clear_viewscreen_vectors() { - search_generic::clear_viewscreen_vectors(); + search_generic::clear_viewscreen_vectors(); saved_indexes.clear(); clear_secondary_viewscreen_vectors(); } void add_to_filtered_list(size_t i) { - search_generic::add_to_filtered_list(i); + search_generic::add_to_filtered_list(i); add_to_filtered_secondary_lists(i); if (!read_only) saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed @@ -519,12 +519,12 @@ protected: virtual void do_post_search() { if (!read_only) - reference_list = *primary_list; + reference_list = *this->primary_list; } void save_original_values() { - search_generic::save_original_values(); + search_generic::save_original_values(); save_secondary_values(); } }; @@ -556,7 +556,7 @@ protected: virtual void do_post_init() { - search_multicolumn_modifiable::do_post_init(); + search_multicolumn_modifiable::do_post_init(); secondary_list = get_secondary_list(); } From 4257c9fe84c5a38ddc6cae3d3a789296210b1abe Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 6 Jan 2013 18:14:25 +1300 Subject: [PATCH 113/136] Ignore vermin in animals screen search --- plugins/search.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/plugins/search.cpp b/plugins/search.cpp index 53c45806d..dac627b68 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -291,6 +291,11 @@ protected: } + virtual bool is_valid_for_search(size_t index) + { + return true; + } + // The actual sort virtual void do_search() { @@ -311,6 +316,9 @@ protected: string search_string_l = toLower(search_string); for (size_t i = 0; i < saved_list1.size(); i++ ) { + if (!is_valid_for_search(i)) + continue; + T element = saved_list1[i]; string desc = toLower(get_element_description(element)); if (desc.find(search_string_l) != string::npos) @@ -697,6 +705,11 @@ private: return viewscreen->mode == T_mode::List; } + bool is_valid_for_search(size_t i) + { + return is_vermin_s[i] == 0; + } + void save_secondary_values() { is_vermin_s = *is_vermin; From ed0baa3f6964b881e3303e3fa5d159e0d2d2857c Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 6 Jan 2013 18:32:49 +1300 Subject: [PATCH 114/136] Restore accidentally removed priority of unit screen search hook's input check over manipulator plugin. --- plugins/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index dac627b68..8d0805947 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -969,8 +969,9 @@ private: } }; -IMPLEMENT_HOOKS(df::viewscreen_unitlistst, unitlist_search); - +typedef generic_search_hook unitlist_search_hook; +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); // // END: Unit screen search // From 309f1625667c7f8c758e1f7e70fc26957eaf52ab Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 16 Jan 2013 18:54:35 +0400 Subject: [PATCH 115/136] Fix line endings again. --- plugins/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 8d0805947..c4884d1a9 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -969,8 +969,8 @@ private: } }; -typedef generic_search_hook unitlist_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); +typedef generic_search_hook unitlist_search_hook; +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); // // END: Unit screen search @@ -1292,7 +1292,7 @@ IMPLEMENT_HOOKS(df::viewscreen_buildinglistst, roomlist_search); // // START: Announcement list search // -class annoucnement_search : public search_generic +class annoucnement_search : public search_generic { public: void render() const From 14d41b8edac56a97480fbf671000a55710ba4112 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 16 Jan 2013 18:55:05 +0400 Subject: [PATCH 116/136] Update search to the newer structure definitions. --- plugins/search.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index c4884d1a9..37fcb5826 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1303,19 +1303,19 @@ public: private: int32_t *get_viewscreen_cursor() { - return &viewscreen->anon_3; + return &viewscreen->sel_idx; } - virtual vector *get_primary_list() + virtual vector *get_primary_list() { - return &viewscreen->anon_4; + return &viewscreen->reports; } private: - string get_element_description(void *element) const + string get_element_description(df::report *element) const { - return ((df::report *) element)->text; + return element->text; } }; From 597074498fd1d0d51e9f8ae7e9cbd82bed34e7ef Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 16 Jan 2013 20:30:11 +0400 Subject: [PATCH 117/136] Fix various issues with updated search. - Priority conflict with tweak military-stable-assign. - The noble screen misbehaves if only one list item is left. - Noble screen search string not reset after Enter/Esc. --- plugins/search.cpp | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 37fcb5826..85334940d 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -91,7 +91,15 @@ public: return false; if (!can_init(screen)) + { + if (is_valid()) + { + clear_search(); + reset_all(); + } + return false; + } if (!is_valid()) { @@ -296,6 +304,11 @@ protected: return true; } + virtual bool force_in_search(size_t index) + { + return false; + } + // The actual sort virtual void do_search() { @@ -316,6 +329,12 @@ protected: string search_string_l = toLower(search_string); for (size_t i = 0; i < saved_list1.size(); i++ ) { + if (force_in_search(i)) + { + add_to_filtered_list(i); + continue; + } + if (!is_valid_for_search(i)) continue; @@ -383,15 +402,7 @@ protected: { auto list = getLayerList(screen); if (!list->active) - { - if (this->is_valid()) - { - this->clear_search(); - this->reset_all(); - } - return false; - } return true; } @@ -655,6 +666,10 @@ template V generic_search_hook ::module; template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \ template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render) +#define IMPLEMENT_HOOKS_PRIO(screen, module, prio) \ + typedef generic_search_hook module##_hook; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, feed, 100); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, render, 100) // // END: Generic Search functionality @@ -1197,7 +1212,7 @@ public: } }; -IMPLEMENT_HOOKS(df::viewscreen_layer_militaryst, military_search); +IMPLEMENT_HOOKS_PRIO(df::viewscreen_layer_militaryst, military_search, 100); // // END: Military screen search @@ -1349,6 +1364,11 @@ public: print_search_option(2, 23); } + bool force_in_search(size_t index) + { + return index == 0; // Leave Vacant + } + bool can_init(df::viewscreen_layer_noblelistst *screen) { if (screen->mode != df::viewscreen_layer_noblelistst::Appoint) @@ -1488,11 +1508,6 @@ public: { return search_twocolumn_modifiable::can_init(screen); } - else if (is_valid()) - { - clear_search(); - reset_all(); - } return false; } From e1a2e6ece40d367325789b44857f6b58f1b0f460 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 16 Jan 2013 20:42:41 +0400 Subject: [PATCH 118/136] Block and grey out the trade screen actions when search is active. After actually trying the search in game, it is obvious that clearing search upon pressing the trade button is confusing, because if you don't pay enough attention, it looks exactly like as if the trade actually happened. --- Readme.rst | 9 ++++++--- plugins/search.cpp | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Readme.rst b/Readme.rst index 24be76f96..236eafb01 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2105,7 +2105,9 @@ directly to the main dwarf mode screen. Search ====== -The search plugin adds search to the Stocks, Trading, Stockpile and Unit List screens. +The search plugin adds search to the Stocks, Animals, Trading, Stockpile, +Noble (assignment candidates), Military (position candidates), Burrows +(unit list), Rooms, Announcements, Job List and Unit List screens. .. image:: images/search.png @@ -2125,8 +2127,9 @@ Leaving any screen automatically clears the filter. In the Trade screen, the actual trade will always only act on items that are actually visible in the list; the same effect applies to the Trade -Value numbers displayed by the screen. Because of this, pressing the 't' -key while search is active clears the search instead of executing the trade. +Value numbers displayed by the screen. Because of this, the 't' key is +blocked while search is active, so you have to reset the filters first. +Pressing Alt-C will clear both search strings. In the stockpile screen the option only appears if the cursor is in the rightmost list: diff --git a/plugins/search.cpp b/plugins/search.cpp index 85334940d..d7b2694bf 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -57,6 +57,27 @@ void OutputString(int8_t color, int &x, int y, const std::string &text) x += text.length(); } +void make_text_dim(int x1, int x2, int y) +{ + for (int x = x1; x <= x2; x++) + { + Screen::Pen pen = Screen::readTile(x,y); + + if (pen.valid()) + { + if (pen.fg != 0) + { + if (pen.fg == 7) + pen.adjust(0,true); + else + pen.bold = 0; + } + + Screen::paintTile(pen,x,y); + } + } +} + static bool is_live_screen(const df::viewscreen *screen) { for (df::viewscreen *cur = &gview->view; cur; cur = cur->child) @@ -1015,13 +1036,8 @@ private: { // Block the keys if were searching if (!search_string.empty()) - { input->clear(); - // Send a force clear to other search class too - input->insert(interface_key::CUSTOM_ALT_C); - } - clear_search_for_trade(); return false; } else if (input->count(interface_key::CUSTOM_ALT_C)) @@ -1048,6 +1064,12 @@ public: virtual void render() const { print_search_option(2, 26); + + if (!search_string.empty()) + { + make_text_dim(2, 37, 22); + make_text_dim(42, gps->dimx-2, 22); + } } private: @@ -1081,6 +1103,12 @@ public: virtual void render() const { print_search_option(42, 26); + + if (!search_string.empty()) + { + make_text_dim(2, 37, 22); + make_text_dim(42, gps->dimx-2, 22); + } } private: From 1a3987bab3bca16ffc71d8e72e731ad34338e10f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 16 Jan 2013 20:42:55 +0400 Subject: [PATCH 119/136] Proper line breaks in the NEWS file. --- NEWS | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index f2a547e10..d9c184832 100644 --- a/NEWS +++ b/NEWS @@ -64,11 +64,14 @@ DFHack future Reworked to make use of lua modules, now all the scripts can be used from other scripts. New Eventful plugin: A collection of lua events, that will allow new ways to interact with df world. - Auto syndrome plugin: a way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws. - Infinite sky plugin: create new z-levels automatically or on request. - True transformation plugin: a better way of doing permanent transformations that allows later transformations. - Work now plugin: makes the game assign jobs every time you pause. - + Auto syndrome plugin: + A way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws. + Infinite sky plugin: + Create new z-levels automatically or on request. + True transformation plugin: + A better way of doing permanent transformations that allows later transformations. + Work now plugin: + Makes the game assign jobs every time you pause. DFHack v0.34.11-r2 From 675e92f350773b733ca8a1159d8cd37eaf61d35f Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 22 Jan 2013 16:34:51 -0600 Subject: [PATCH 120/136] Autolabor: add build floor grate labor, add unbuild screwpump labor, protect pump operators, do not clear labors on already busy dwarfs --- plugins/autolabor.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 975ac1942..15e390b22 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -810,6 +810,7 @@ private: case df::building_type::TractionBench: case df::building_type::Slab: case df::building_type::Chain: + case df::building_type::GrateFloor: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: @@ -853,6 +854,7 @@ private: case df::building_type::Construction: case df::building_type::Wagon: case df::building_type::Bridge: + case df::building_type::ScrewPump: { df::building_actual* b = (df::building_actual*) bld; return construction_build_labor(b->contained_items[0]->item); @@ -2178,7 +2180,10 @@ public: } int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); if (d->dwarf->status.labors[labor]) - score += 500; + if (labor == df::unit_labor::OPERATE_PUMP) + score += 50000; + else + score += 1000; if (default_labor_infos[labor].tool != TOOL_NONE && d->has_tool[default_labor_infos[labor].tool]) score += 5000; @@ -2219,7 +2224,7 @@ public: (*bestdwarf)->dwarf->military.pickup_flags.bits.update = true; } } - else + else if ((*bestdwarf)->state == IDLE) (*bestdwarf)->clear_labor(l); } From 0e384ada753f364a47e818c830c7419bdf659cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 5 Feb 2013 05:34:34 +0100 Subject: [PATCH 121/136] Sync submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index e87942b50..a29068467 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e87942b50481280c027d8b5f64d452c09e3be095 +Subproject commit a290684672102222da7b284424036a60c0ac56f9 diff --git a/plugins/stonesense b/plugins/stonesense index 8d3533329..3877374ff 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 8d35333298ceed10b089d12e730e451d427e616d +Subproject commit 3877374ff5618629ce0fcc3339ec0cf57dd4572e From f90737e2747e37c72ab0dd79fd4d34417856aaeb Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 10 Feb 2013 15:26:48 +0400 Subject: [PATCH 122/136] Add more comments to the vmethod interpose implementation. --- library/VTableInterpose.cpp | 55 ++++++++++++++- library/include/VTableInterpose.h | 109 ++++++++++++++++++------------ 2 files changed, 117 insertions(+), 47 deletions(-) diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 3f9423b45..0db5ac03c 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -39,15 +39,54 @@ using namespace DFHack; /* * Code for accessing method pointers directly. Very compiler-specific. + * + * Pointers to methods in C++ are conceptually similar to pointers to + * functions, but with some complications. Specifically, the target of + * such pointer can be either: + * + * - An ordinary non-virtual method, in which case the pointer behaves + * not much differently from a simple function pointer. + * - A virtual method, in which case calling the pointer must emulate + * an ordinary call to that method, i.e. fetch the real code address + * from the vtable at the appropriate index. + * + * This means that pointers to virtual methods actually have to encode + * the relevant vtable index value in some way. Also, since these two + * types of pointers cannot be distinguished by data type and differ + * only in value, any sane compiler would ensure that any non-virtual + * method that can potentially be called via a pointer uses the same + * parameter passing rules as an equivalent virtual method, so that + * the same parameter passing code would work with both types of pointer. + * + * This means that with a few small low-level compiler-specific wrappers + * to access the data inside such pointers it is possible to: + * + * - Convert a non-virtual method pointer into a code address that + * can be directly put into a vtable. + * - Convert a pointer taken out of a vtable into a fake non-virtual + * method pointer that can be used to easily call the original + * vmethod body. + * - Extract a vtable index out of a virtual method pointer. + * + * Taken together, these features allow delegating all the difficult + * and fragile tasks like passing parameters and calculating the + * vtable index to the C++ compiler. */ #if defined(_MSC_VER) +// MSVC may use up to 3 different representations +// based on context, but adding the /vmg /vmm options +// forces it to stick to this one. It can accomodate +// multiple, but not virtual inheritance. struct MSVC_MPTR { void *method; intptr_t this_shift; }; +// Debug builds sometimes use additional thunks that +// just jump to the real one, presumably to attach some +// additional debug info. static uint32_t *follow_jmp(void *ptr) { uint8_t *p = (uint8_t*)ptr; @@ -56,10 +95,10 @@ static uint32_t *follow_jmp(void *ptr) { switch (*p) { - case 0xE9: + case 0xE9: // jmp near rel32 p += 5 + *(int32_t*)(p+1); break; - case 0xEB: + case 0xEB: // jmp short rel8 p += 2 + *(int8_t*)(p+1); break; default: @@ -120,8 +159,10 @@ void DFHack::addr_to_method_pointer_(void *pptr, void *addr) #elif defined(__GXX_ABI_VERSION) +// GCC seems to always use this structure - possibly unless +// virtual inheritance is involved, but that's irrelevant. struct GCC_MPTR { - intptr_t method; + intptr_t method; // Code pointer or tagged vtable offset intptr_t this_shift; }; @@ -254,6 +295,14 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v { if (vmethod_idx < 0 || interpose_method == NULL) { + /* + * A failure here almost certainly means a problem in one + * of the pointer-to-method access wrappers above: + * + * - vmethod_idx comes from vmethod_pointer_to_idx_ + * - interpose_method comes from method_pointer_to_addr_ + */ + fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n", vmethod_idx, unsigned(interpose_method)); fflush(stderr); diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index bd3c23036..8e2541c55 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -28,6 +28,58 @@ distribution. namespace DFHack { + /* VMethod interpose API. + + This API allows replacing an entry in the original vtable + with code defined by DFHack, while retaining ability to + call the original code. The API can be safely used from + plugins, and multiple hooks for the same vmethod are + automatically chained (subclass before superclass; at same + level highest priority called first; undefined order otherwise). + + Usage: + + struct my_hack : df::someclass { + typedef df::someclass interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) { + // If needed by the code, claim the suspend lock. + // DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK! + // CoreSuspendClaimer suspend; + ... + INTERPOSE_NEXT(foo)(arg) // call the original + ... + } + }; + + IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo); + or + IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority); + + void init() { + if (!INTERPOSE_HOOK(my_hack, foo).apply()) + error(); + } + + void shutdown() { + INTERPOSE_HOOK(my_hack, foo).remove(); + } + + Important caveat: + + This will NOT intercept calls to the superclass vmethod + from overriding vmethod bodies in subclasses, i.e. whenever + DF code contains something like this, the call to "superclass::foo()" + doesn't actually use vtables, and thus will never trigger any hooks: + + class superclass { virtual foo() { ... } }; + class subclass : superclass { virtual foo() { ... superclass::foo(); ... } }; + + The only workaround is to implement and apply a second hook for subclass::foo, + and repeat that for any other subclasses and sub-subclasses that override this + vmethod. + */ + template struct StaticAssert; template<> struct StaticAssert {}; @@ -81,43 +133,6 @@ namespace DFHack return addr_to_method_pointer

(identity.get_vmethod_ptr(idx)); } - /* VMethod interpose API. - - This API allows replacing an entry in the original vtable - with code defined by DFHack, while retaining ability to - call the original code. The API can be safely used from - plugins, and multiple hooks for the same vmethod are - automatically chained (subclass before superclass; at same - level highest priority called first; undefined order otherwise). - - Usage: - - struct my_hack : df::someclass { - typedef df::someclass interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) { - // If needed by the code, claim the suspend lock. - // DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK! - // CoreSuspendClaimer suspend; - ... - INTERPOSE_NEXT(foo)(arg) // call the original - ... - } - }; - - IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo); - or - IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority); - - void init() { - if (!INTERPOSE_HOOK(my_hack, foo).apply()) - error(); - } - - void shutdown() { - INTERPOSE_HOOK(my_hack, foo).remove(); - } - */ #define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \ typedef rtype (interpose_base::*interpose_ptr_##name)args; \ @@ -142,18 +157,21 @@ namespace DFHack friend class virtual_identity; virtual_identity *host; // Class with the vtable - int vmethod_idx; + int vmethod_idx; // Index of the interposed method in the vtable void *interpose_method; // Pointer to the code of the interposing method - void *chain_mptr; // Pointer to the chain field below - int priority; + void *chain_mptr; // Pointer to the chain field in the subclass below + int priority; // Higher priority hooks are called earlier - bool applied; - void *saved_chain; // Previous pointer to the code - VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method + bool applied; // True if this hook is currently applied + void *saved_chain; // Pointer to the code of the original vmethod or next hook - // inherited vtable members + // Chain of hooks within the same host + VMethodInterposeLinkBase *next, *prev; + // Subclasses that inherit this topmost hook directly std::set child_hosts; + // Hooks within subclasses that branch off this topmost hook std::set child_next; + // (See the cpp file for a more detailed description of these links) void set_chain(void *chain); void on_host_delete(virtual_identity *host); @@ -172,6 +190,9 @@ namespace DFHack template class VMethodInterposeLink : public VMethodInterposeLinkBase { public: + // Exactly the same as the saved_chain field of superclass, + // but converted to the appropriate pointer-to-method type. + // Kept up to date via the chain_mptr pointer. Ptr chain; operator Ptr () { return chain; } From 39dbaf743abbbbd9ddc91cced6d942020d89dfa9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 13 Feb 2013 13:54:49 +0400 Subject: [PATCH 123/136] Add a script to fix cloth stockpiles by patching memory objects. This patching needs to be done every time raws are reloaded. --- dfhack.init-example | 3 ++ scripts/fix/cloth-stockpile.lua | 80 +++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 scripts/fix/cloth-stockpile.lua diff --git a/dfhack.init-example b/dfhack.init-example index 1a5aee48f..ddf93de16 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -144,6 +144,9 @@ tweak military-training # write the correct season to gamelog on world load soundsense-season +# patch the material objects in memory to fix cloth stockpiles +fix/cloth-stockpile enable + ####################################################### # Apply binary patches at runtime # # # diff --git a/scripts/fix/cloth-stockpile.lua b/scripts/fix/cloth-stockpile.lua new file mode 100644 index 000000000..7da5d583c --- /dev/null +++ b/scripts/fix/cloth-stockpile.lua @@ -0,0 +1,80 @@ +-- Fixes cloth/thread stockpiles by correcting material object data. + +local raws = df.global.world.raws +local organic_types = raws.mat_table.organic_types +local organic_indexes = raws.mat_table.organic_indexes + +local function verify(category,idx,vtype,vidx) + if idx == -1 then + -- Purely for reporting reasons + return true + end + local tvec = organic_types[category] + if idx < 0 or idx >= #tvec or tvec[idx] ~= vtype then + return false + end + return organic_indexes[category][idx] == vidx +end + +local patched_cnt = 0 +local mat_cnt = 0 + +function patch_material(mat,mat_type,mat_index) + local idxarr = mat.food_mat_index + + -- These refer to fish/eggs, i.e. castes and not materials + idxarr[1] = -1 + idxarr[2] = -1 + idxarr[3] = -1 + + for i = 0,#idxarr-1 do + if not verify(i,idxarr[i],mat_type,mat_index) then + idxarr[i] = -1 + patched_cnt = patched_cnt+1 + end + end + + mat_cnt = mat_cnt + 1 +end + +function patch_materials() + patched_cnt = 0 + mat_cnt = 0 + + print('Fixing cloth stockpile handling (bug 5739)...') + + for i,v in ipairs(raws.inorganics) do + patch_material(v.material, 0, i) + end + + for i,v in ipairs(raws.creatures.all) do + for j,m in ipairs(v.material) do + patch_material(m, 19+j, i) + end + end + + for i,v in ipairs(raws.plants.all) do + for j,m in ipairs(v.material) do + patch_material(m, 419+j, i) + end + end + + print('Patched '..patched_cnt..' bad references in '..mat_cnt..' materials.') +end + +local args = {...} + +if args[1] == 'enable' then + dfhack.onStateChange[_ENV] = function(sc) + if sc == SC_WORLD_LOADED then + patch_materials() + end + end + if dfhack.isWorldLoaded() then + patch_materials() + end +elseif args[1] == 'disable' then + dfhack.onStateChange[_ENV] = nil +else + patch_materials() +end From a17760af4f806d66a07308e4053ff6455a0af377 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 13 Feb 2013 14:45:24 +0400 Subject: [PATCH 124/136] Communicate the Plugin pointer to the plugin in a decent sort of way. --- library/PluginManager.cpp | 6 ++++-- library/include/PluginManager.h | 3 ++- plugins/autoSyndrome.cpp | 3 +-- plugins/trueTransformation.cpp | 3 +-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 86bab66cd..d5636109b 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -212,9 +212,10 @@ bool Plugin::load(color_ostream &con) } const char ** plug_name =(const char ** ) LookupPlugin(plug, "name"); const char ** plug_version =(const char ** ) LookupPlugin(plug, "version"); - if(!plug_name || !plug_version) + Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self"); + if(!plug_name || !plug_version || !plug_self) { - con.printerr("Plugin %s has no name or version.\n", filename.c_str()); + con.printerr("Plugin %s has no name, version or self pointer.\n", filename.c_str()); ClosePlugin(plug); RefAutolock lock(access); state = PS_BROKEN; @@ -229,6 +230,7 @@ bool Plugin::load(color_ostream &con) state = PS_BROKEN; return false; } + *plug_self = this; RefAutolock lock(access); plugin_init = (command_result (*)(color_ostream &, std::vector &)) LookupPlugin(plug, "plugin_init"); if(!plugin_init) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 62a195867..46a46f2df 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -247,7 +247,8 @@ namespace DFHack /// You have to have this in every plugin you write - just once. Ideally on top of the main file. #define DFHACK_PLUGIN(plugin_name) \ DFhackDataExport const char * version = DFHACK_VERSION;\ - DFhackDataExport const char * name = plugin_name; + DFhackDataExport const char * name = plugin_name;\ + DFhackDataExport Plugin *plugin_self = NULL; #define DFHACK_PLUGIN_LUA_COMMANDS \ DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 79e8b35ec..3cee68589 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -126,9 +126,8 @@ DFhackCExport command_result plugin_init(color_ostream& out, vectorgetPluginByName("autoSyndrome"); EventManager::EventHandler handle(processJob, 5); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self); return CR_OK; } diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp index 6c4245da8..1e3403fc5 100644 --- a/plugins/trueTransformation.cpp +++ b/plugins/trueTransformation.cpp @@ -26,8 +26,7 @@ void syndromeHandler(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { EventManager::EventHandler syndrome(syndromeHandler, 1); - Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("trueTransformation"); - EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, me); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, plugin_self); return CR_OK; } From 9ca435544ea49c6e2dbd62f4761c6bb87e34342e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 13 Feb 2013 15:03:15 +0400 Subject: [PATCH 125/136] Nuke unsafe behavior in Buildings::findAtTile from orbit. --- library/modules/Buildings.cpp | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 13d983855..ab70944b7 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -249,31 +249,21 @@ df::building *Buildings::findAtTile(df::coord pos) if (!occ || !occ->bits.building) return NULL; - auto a = locationToBuilding.find(pos); - if ( a == locationToBuilding.end() ) { - cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <buildings.all, id); - if ( index == -1 ) { - cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <buildings.all[index]; - if (!building->isSettingOccupancy()) - return NULL; - - if (building->room.extents && building->isExtentShaped()) + // Try cache lookup in case it works: + auto cached = locationToBuilding.find(pos); + if (cached != locationToBuilding.end()) { - auto etile = getExtentTile(building->room, pos); - if (!etile || !*etile) - return NULL; + auto building = df::building::find(cached->second); + + if (building && building->z == pos.z && + building->isSettingOccupancy() && + containsTile(building, pos, false)) + { + return building; + } } - return building; - /* - //old method: brute-force + // The authentic method, i.e. how the game generally does this: auto &vec = df::building::get_vector(); for (size_t i = 0; i < vec.size(); i++) { @@ -298,7 +288,6 @@ df::building *Buildings::findAtTile(df::coord pos) } return NULL; - */ } bool Buildings::findCivzonesAt(std::vector *pvec, df::coord pos) From 27f5dc76318227f838ba40117d6ce4c57b526d83 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 13 Feb 2013 13:07:54 -0600 Subject: [PATCH 126/136] Autolabor: add in the rest of the building construct and deconstruct labors. Also handle wood crafts at the craftdwarf's shop. --- plugins/autolabor.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 15e390b22..987d343ce 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -760,6 +760,8 @@ private: df::building* bld = get_building_from_job (j); switch (bld->getType()) { + case df::building_type::Hive: + return df::unit_labor::BEEKEEPING; case df::building_type::Workshop: { df::building_workshopst* ws = (df::building_workshopst*) bld; @@ -783,6 +785,10 @@ private: case df::building_type::RoadPaved: case df::building_type::Well: case df::building_type::ScrewPump: + case df::building_type::Wagon: + case df::building_type::Shop: + case df::building_type::Support: + case df::building_type::Windmill: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) @@ -811,12 +817,27 @@ private: case df::building_type::Slab: case df::building_type::Chain: case df::building_type::GrateFloor: + case df::building_type::Hatch: + case df::building_type::BarsFloor: + case df::building_type::BarsVertical: + case df::building_type::GrateWall: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: case df::building_type::AxleHorizontal: case df::building_type::AxleVertical: + case df::building_type::Rollers: return df::unit_labor::MECHANIC; + case df::building_type::AnimalTrap: + return df::unit_labor::TRAPPER; + case df::building_type::Civzone: + case df::building_type::Nest: + case df::building_type::RoadDirt: + case df::building_type::Stockpile: + case df::building_type::Weapon: + return df::unit_labor::NONE; + case df::building_type::SiegeEngine: + return df::unit_labor::SIEGECRAFT; } debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", @@ -837,6 +858,8 @@ private: switch (bld->getType()) { + case df::building_type::Hive: + return df::unit_labor::BEEKEEPING; case df::building_type::Workshop: { df::building_workshopst* ws = (df::building_workshopst*) bld; @@ -855,6 +878,13 @@ private: case df::building_type::Wagon: case df::building_type::Bridge: case df::building_type::ScrewPump: + case df::building_type::ArcheryTarget: + case df::building_type::RoadPaved: + case df::building_type::Shop: + case df::building_type::Support: + case df::building_type::WaterWheel: + case df::building_type::Well: + case df::building_type::Windmill: { df::building_actual* b = (df::building_actual*) bld; return construction_build_labor(b->contained_items[0]->item); @@ -863,6 +893,10 @@ private: case df::building_type::FarmPlot: return df::unit_labor::PLANT; case df::building_type::Trap: + case df::building_type::AxleHorizontal: + case df::building_type::AxleVertical: + case df::building_type::GearAssembly: + case df::building_type::Rollers: return df::unit_labor::MECHANIC; case df::building_type::Chair: case df::building_type::Bed: @@ -883,7 +917,21 @@ private: case df::building_type::Slab: case df::building_type::Chain: case df::building_type::Hatch: + case df::building_type::BarsFloor: + case df::building_type::BarsVertical: + case df::building_type::GrateFloor: + case df::building_type::GrateWall: return df::unit_labor::HAUL_FURNITURE; + case df::building_type::AnimalTrap: + return df::unit_labor::TRAPPER; + case df::building_type::Civzone: + case df::building_type::Nest: + case df::building_type::RoadDirt: + case df::building_type::Stockpile: + case df::building_type::Weapon: + return df::unit_labor::NONE; + case df::building_type::SiegeEngine: + return df::unit_labor::SIEGECRAFT; } debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", @@ -924,6 +972,8 @@ private: debug ("AUTOLABOR: Cannot deduce labor for make crafts job (not bone)\n"); return df::unit_labor::NONE; } + case df::item_type::WOOD: + return df::unit_labor::WOOD_CRAFT; default: debug ("AUTOLABOR: Cannot deduce labor for make crafts job, item type %s\n", ENUM_KEY_STR(item_type, jobitem).c_str()); From ff0012c91f0ca671a603cf4f19e3c45039b3725e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 13 Feb 2013 13:33:32 -0600 Subject: [PATCH 127/136] Move new autolabor to autolabor2 in devel. --- plugins/devel/CMakeLists.txt | 1 + .../{autolabor.cpp => devel/autolabor2.cpp} | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) rename plugins/{autolabor.cpp => devel/autolabor2.cpp} (99%) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..8eee29659 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,6 +18,7 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) +DFHACK_PLUGIN(autolabor2 autolabor2.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/autolabor.cpp b/plugins/devel/autolabor2.cpp similarity index 99% rename from plugins/autolabor.cpp rename to plugins/devel/autolabor2.cpp index 987d343ce..c06e5ea73 100644 --- a/plugins/autolabor.cpp +++ b/plugins/devel/autolabor2.cpp @@ -96,8 +96,8 @@ enum ConfigFlags { command_result autolabor (color_ostream &out, std::vector & parameters); // A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - autolabor.plug.so or autolabor.plug.dll in this case -DFHACK_PLUGIN("autolabor"); +// The name string provided must correspond to the filename - autolabor2.plug.so or autolabor2.plug.dll in this case +DFHACK_PLUGIN("autolabor2"); static void generate_labor_to_skill_map(); @@ -1478,31 +1478,32 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector \n" + " autolabor2 max \n" " Set max number of dwarves assigned to a labor.\n" - " autolabor max none\n" + " autolabor2 max none\n" " Unrestrict the number of dwarves assigned to a labor.\n" - " autolabor priority \n" + " autolabor2 priority \n" " Change the assignment priority of a labor (default is 100)\n" - " autolabor reset \n" + " autolabor2 reset \n" " Return a labor to the default handling.\n" - " autolabor reset-all\n" + " autolabor2 reset-all\n" " Return all labors to the default handling.\n" - " autolabor list\n" + " autolabor2 list\n" " List current status of all labors.\n" - " autolabor status\n" + " autolabor2 status\n" " Show basic status information.\n" "Function:\n" " When enabled, autolabor periodically checks your dwarves and enables or\n" " disables labors. Generally, each dwarf will be assigned exactly one labor.\n" " Warning: autolabor will override any manual changes you make to labors\n" - " while it is enabled.\n" + " while it is enabled. Do not try to run both autolabor and autolabor2 at\n" + " the same time." )); generate_labor_to_skill_map(); From f9a3450acae6d65007cbaa1b51e49a637089b547 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 13 Feb 2013 13:34:39 -0600 Subject: [PATCH 128/136] Reinstate old autolabor in its original place. --- plugins/autolabor.cpp | 1669 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1669 insertions(+) create mode 100644 plugins/autolabor.cpp diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp new file mode 100644 index 000000000..e5047b434 --- /dev/null +++ b/plugins/autolabor.cpp @@ -0,0 +1,1669 @@ +// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D + +// some headers required for a plugin. Nothing special, just the basics. +#include "Core.h" +#include +#include +#include + +#include +#include + +#include "modules/Units.h" +#include "modules/World.h" + +// DF data structure definition headers +#include "DataDefs.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "modules/MapCache.h" +#include "modules/Items.h" + +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; +using df::global::ui; +using df::global::world; + +#define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) + +/* + * Autolabor module for dfhack + * + * The idea behind this module is to constantly adjust labors so that the right dwarves + * are assigned to new tasks. The key is that, for almost all labors, once a dwarf begins + * a job it will finish that job even if the associated labor is removed. Thus the + * strategy is to frequently decide, for each labor, which dwarves should possibly take + * a new job for that labor if it comes in and which shouldn't, and then set the labors + * appropriately. The updating should happen as often as can be reasonably done without + * causing lag. + * + * The obvious thing to do is to just set each labor on a single idle dwarf who is best + * suited to doing new jobs of that labor. This works in a way, but it leads to a lot + * of idle dwarves since only one dwarf will be dispatched for each labor in an update + * cycle, and dwarves that finish tasks will wait for the next update before being + * dispatched. An improvement is to also set some labors on dwarves that are currently + * doing a job, so that they will immediately take a new job when they finish. The + * details of which dwarves should have labors set is mostly a heuristic. + * + * A complication to the above simple scheme is labors that have associated equipment. + * Enabling/disabling these labors causes dwarves to change equipment, and disabling + * them in the middle of a job may cause the job to be abandoned. Those labors + * (mining, hunting, and woodcutting) need to be handled carefully to minimize churn. + */ + +static int enable_autolabor = 0; + +static bool print_debug = 0; + +static std::vector state_count(5); + +static PersistentDataItem config; + +enum ConfigFlags { + CF_ENABLED = 1, +}; + + +// Here go all the command declarations... +// mostly to allow having the mandatory stuff on top of the file and commands on the bottom +command_result autolabor (color_ostream &out, std::vector & parameters); + +// A plugin must be able to return its name and version. +// The name string provided must correspond to the filename - autolabor.plug.so or autolabor.plug.dll in this case +DFHACK_PLUGIN("autolabor"); + +static void generate_labor_to_skill_map(); + +enum labor_mode { + DISABLE, + HAULERS, + AUTOMATIC, +}; + +enum dwarf_state { + // Ready for a new task + IDLE, + + // Busy with a useful task + BUSY, + + // In the military, can't work + MILITARY, + + // Child or noble, can't work + CHILD, + + // Doing something that precludes working, may be busy for a while + OTHER +}; + +const int NUM_STATE = 5; + +static const char *state_names[] = { + "IDLE", + "BUSY", + "MILITARY", + "CHILD", + "OTHER", +}; + +static const dwarf_state dwarf_states[] = { + BUSY /* CarveFortification */, + BUSY /* DetailWall */, + BUSY /* DetailFloor */, + BUSY /* Dig */, + BUSY /* CarveUpwardStaircase */, + BUSY /* CarveDownwardStaircase */, + BUSY /* CarveUpDownStaircase */, + BUSY /* CarveRamp */, + BUSY /* DigChannel */, + BUSY /* FellTree */, + BUSY /* GatherPlants */, + BUSY /* RemoveConstruction */, + BUSY /* CollectWebs */, + BUSY /* BringItemToDepot */, + BUSY /* BringItemToShop */, + OTHER /* Eat */, + OTHER /* GetProvisions */, + OTHER /* Drink */, + OTHER /* Drink2 */, + OTHER /* FillWaterskin */, + OTHER /* FillWaterskin2 */, + OTHER /* Sleep */, + BUSY /* CollectSand */, + BUSY /* Fish */, + BUSY /* Hunt */, + OTHER /* HuntVermin */, + BUSY /* Kidnap */, + BUSY /* BeatCriminal */, + BUSY /* StartingFistFight */, + BUSY /* CollectTaxes */, + BUSY /* GuardTaxCollector */, + BUSY /* CatchLiveLandAnimal */, + BUSY /* CatchLiveFish */, + BUSY /* ReturnKill */, + BUSY /* CheckChest */, + BUSY /* StoreOwnedItem */, + BUSY /* PlaceItemInTomb */, + BUSY /* StoreItemInStockpile */, + BUSY /* StoreItemInBag */, + BUSY /* StoreItemInHospital */, + BUSY /* StoreItemInChest */, + BUSY /* StoreItemInCabinet */, + BUSY /* StoreWeapon */, + BUSY /* StoreArmor */, + BUSY /* StoreItemInBarrel */, + BUSY /* StoreItemInBin */, + BUSY /* SeekArtifact */, + BUSY /* SeekInfant */, + OTHER /* AttendParty */, + OTHER /* GoShopping */, + OTHER /* GoShopping2 */, + BUSY /* Clean */, + OTHER /* Rest */, + BUSY /* PickupEquipment */, + BUSY /* DumpItem */, + OTHER /* StrangeMoodCrafter */, + OTHER /* StrangeMoodJeweller */, + OTHER /* StrangeMoodForge */, + OTHER /* StrangeMoodMagmaForge */, + OTHER /* StrangeMoodBrooding */, + OTHER /* StrangeMoodFell */, + OTHER /* StrangeMoodCarpenter */, + OTHER /* StrangeMoodMason */, + OTHER /* StrangeMoodBowyer */, + OTHER /* StrangeMoodTanner */, + OTHER /* StrangeMoodWeaver */, + OTHER /* StrangeMoodGlassmaker */, + OTHER /* StrangeMoodMechanics */, + BUSY /* ConstructBuilding */, + BUSY /* ConstructDoor */, + BUSY /* ConstructFloodgate */, + BUSY /* ConstructBed */, + BUSY /* ConstructThrone */, + BUSY /* ConstructCoffin */, + BUSY /* ConstructTable */, + BUSY /* ConstructChest */, + BUSY /* ConstructBin */, + BUSY /* ConstructArmorStand */, + BUSY /* ConstructWeaponRack */, + BUSY /* ConstructCabinet */, + BUSY /* ConstructStatue */, + BUSY /* ConstructBlocks */, + BUSY /* MakeRawGlass */, + BUSY /* MakeCrafts */, + BUSY /* MintCoins */, + BUSY /* CutGems */, + BUSY /* CutGlass */, + BUSY /* EncrustWithGems */, + BUSY /* EncrustWithGlass */, + BUSY /* DestroyBuilding */, + BUSY /* SmeltOre */, + BUSY /* MeltMetalObject */, + BUSY /* ExtractMetalStrands */, + BUSY /* PlantSeeds */, + BUSY /* HarvestPlants */, + BUSY /* TrainHuntingAnimal */, + BUSY /* TrainWarAnimal */, + BUSY /* MakeWeapon */, + BUSY /* ForgeAnvil */, + BUSY /* ConstructCatapultParts */, + BUSY /* ConstructBallistaParts */, + BUSY /* MakeArmor */, + BUSY /* MakeHelm */, + BUSY /* MakePants */, + BUSY /* StudWith */, + BUSY /* ButcherAnimal */, + BUSY /* PrepareRawFish */, + BUSY /* MillPlants */, + BUSY /* BaitTrap */, + BUSY /* MilkCreature */, + BUSY /* MakeCheese */, + BUSY /* ProcessPlants */, + BUSY /* ProcessPlantsBag */, + BUSY /* ProcessPlantsVial */, + BUSY /* ProcessPlantsBarrel */, + BUSY /* PrepareMeal */, + BUSY /* WeaveCloth */, + BUSY /* MakeGloves */, + BUSY /* MakeShoes */, + BUSY /* MakeShield */, + BUSY /* MakeCage */, + BUSY /* MakeChain */, + BUSY /* MakeFlask */, + BUSY /* MakeGoblet */, + BUSY /* MakeInstrument */, + BUSY /* MakeToy */, + BUSY /* MakeAnimalTrap */, + BUSY /* MakeBarrel */, + BUSY /* MakeBucket */, + BUSY /* MakeWindow */, + BUSY /* MakeTotem */, + BUSY /* MakeAmmo */, + BUSY /* DecorateWith */, + BUSY /* MakeBackpack */, + BUSY /* MakeQuiver */, + BUSY /* MakeBallistaArrowHead */, + BUSY /* AssembleSiegeAmmo */, + BUSY /* LoadCatapult */, + BUSY /* LoadBallista */, + BUSY /* FireCatapult */, + BUSY /* FireBallista */, + BUSY /* ConstructMechanisms */, + BUSY /* MakeTrapComponent */, + BUSY /* LoadCageTrap */, + BUSY /* LoadStoneTrap */, + BUSY /* LoadWeaponTrap */, + BUSY /* CleanTrap */, + BUSY /* CastSpell */, + BUSY /* LinkBuildingToTrigger */, + BUSY /* PullLever */, + BUSY /* BrewDrink */, + BUSY /* ExtractFromPlants */, + BUSY /* ExtractFromRawFish */, + BUSY /* ExtractFromLandAnimal */, + BUSY /* TameVermin */, + BUSY /* TameAnimal */, + BUSY /* ChainAnimal */, + BUSY /* UnchainAnimal */, + BUSY /* UnchainPet */, + BUSY /* ReleaseLargeCreature */, + BUSY /* ReleasePet */, + BUSY /* ReleaseSmallCreature */, + BUSY /* HandleSmallCreature */, + BUSY /* HandleLargeCreature */, + BUSY /* CageLargeCreature */, + BUSY /* CageSmallCreature */, + BUSY /* RecoverWounded */, + BUSY /* DiagnosePatient */, + BUSY /* ImmobilizeBreak */, + BUSY /* DressWound */, + BUSY /* CleanPatient */, + BUSY /* Surgery */, + BUSY /* Suture */, + BUSY /* SetBone */, + BUSY /* PlaceInTraction */, + BUSY /* DrainAquarium */, + BUSY /* FillAquarium */, + BUSY /* FillPond */, + BUSY /* GiveWater */, + BUSY /* GiveFood */, + BUSY /* GiveWater2 */, + BUSY /* GiveFood2 */, + BUSY /* RecoverPet */, + BUSY /* PitLargeAnimal */, + BUSY /* PitSmallAnimal */, + BUSY /* SlaughterAnimal */, + BUSY /* MakeCharcoal */, + BUSY /* MakeAsh */, + BUSY /* MakeLye */, + BUSY /* MakePotashFromLye */, + BUSY /* FertilizeField */, + BUSY /* MakePotashFromAsh */, + BUSY /* DyeThread */, + BUSY /* DyeCloth */, + BUSY /* SewImage */, + BUSY /* MakePipeSection */, + BUSY /* OperatePump */, + OTHER /* ManageWorkOrders */, + OTHER /* UpdateStockpileRecords */, + OTHER /* TradeAtDepot */, + BUSY /* ConstructHatchCover */, + BUSY /* ConstructGrate */, + BUSY /* RemoveStairs */, + BUSY /* ConstructQuern */, + BUSY /* ConstructMillstone */, + BUSY /* ConstructSplint */, + BUSY /* ConstructCrutch */, + BUSY /* ConstructTractionBench */, + BUSY /* CleanSelf */, + BUSY /* BringCrutch */, + BUSY /* ApplyCast */, + BUSY /* CustomReaction */, + BUSY /* ConstructSlab */, + BUSY /* EngraveSlab */, + BUSY /* ShearCreature */, + BUSY /* SpinThread */, + BUSY /* PenLargeAnimal */, + BUSY /* PenSmallAnimal */, + BUSY /* MakeTool */, + BUSY /* CollectClay */, + BUSY /* InstallColonyInHive */, + BUSY /* CollectHiveProducts */, + OTHER /* CauseTrouble */, + OTHER /* DrinkBlood */, + OTHER /* ReportCrime */, + OTHER /* ExecuteCriminal */, + BUSY /* TrainAnimal */, + BUSY /* CarveTrack */, + BUSY /* PushTrackVehicle */, + BUSY /* PlaceTrackVehicle */, + BUSY /* StoreItemInVehicle */ +}; + +struct labor_info +{ + PersistentDataItem config; + + bool is_exclusive; + int active_dwarfs; + + labor_mode mode() { return (labor_mode) config.ival(0); } + void set_mode(labor_mode mode) { config.ival(0) = mode; } + + int minimum_dwarfs() { return config.ival(1); } + void set_minimum_dwarfs(int minimum_dwarfs) { config.ival(1) = minimum_dwarfs; } + + int maximum_dwarfs() { return config.ival(2); } + void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; } + +}; + +struct labor_default +{ + labor_mode mode; + bool is_exclusive; + int minimum_dwarfs; + int maximum_dwarfs; + int active_dwarfs; +}; + +static int hauler_pct = 33; + +static std::vector labor_infos; + +static const struct labor_default default_labor_infos[] = { + /* MINE */ {AUTOMATIC, true, 2, 200, 0}, + /* HAUL_STONE */ {HAULERS, false, 1, 200, 0}, + /* HAUL_WOOD */ {HAULERS, false, 1, 200, 0}, + /* HAUL_BODY */ {HAULERS, false, 1, 200, 0}, + /* HAUL_FOOD */ {HAULERS, false, 1, 200, 0}, + /* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0}, + /* HAUL_ITEM */ {HAULERS, false, 1, 200, 0}, + /* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0}, + /* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0}, + /* CLEAN */ {HAULERS, false, 1, 200, 0}, + /* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0}, + /* CARPENTER */ {AUTOMATIC, false, 1, 200, 0}, + /* DETAIL */ {AUTOMATIC, false, 1, 200, 0}, + /* MASON */ {AUTOMATIC, false, 1, 200, 0}, + /* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0}, + /* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0}, + /* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0}, + /* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0}, + /* SURGERY */ {AUTOMATIC, false, 1, 200, 0}, + /* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0}, + /* SUTURING */ {AUTOMATIC, false, 1, 200, 0}, + /* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0}, + /* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0}, + /* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0}, + /* BUTCHER */ {AUTOMATIC, false, 1, 200, 0}, + /* TRAPPER */ {AUTOMATIC, false, 1, 200, 0}, + /* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0}, + /* LEATHER */ {AUTOMATIC, false, 1, 200, 0}, + /* TANNER */ {AUTOMATIC, false, 1, 200, 0}, + /* BREWER */ {AUTOMATIC, false, 1, 200, 0}, + /* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0}, + /* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0}, + /* WEAVER */ {AUTOMATIC, false, 1, 200, 0}, + /* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0}, + /* MILLER */ {AUTOMATIC, false, 1, 200, 0}, + /* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0}, + /* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0}, + /* MILK */ {AUTOMATIC, false, 1, 200, 0}, + /* COOK */ {AUTOMATIC, false, 1, 200, 0}, + /* PLANT */ {AUTOMATIC, false, 1, 200, 0}, + /* HERBALIST */ {AUTOMATIC, false, 1, 200, 0}, + /* FISH */ {AUTOMATIC, false, 1, 1, 0}, + /* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0}, + /* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0}, + /* HUNT */ {AUTOMATIC, true, 1, 1, 0}, + /* SMELT */ {AUTOMATIC, false, 1, 200, 0}, + /* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0}, + /* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0}, + /* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0}, + /* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0}, + /* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0}, + /* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0}, + /* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0}, + /* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0}, + /* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0}, + /* BOWYER */ {AUTOMATIC, false, 1, 200, 0}, + /* MECHANIC */ {AUTOMATIC, false, 1, 200, 0}, + /* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0}, + /* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0}, + /* DYER */ {AUTOMATIC, false, 1, 200, 0}, + /* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0}, + /* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0}, + /* SHEARER */ {AUTOMATIC, false, 1, 200, 0}, + /* SPINNER */ {AUTOMATIC, false, 1, 200, 0}, + /* POTTERY */ {AUTOMATIC, false, 1, 200, 0}, + /* GLAZING */ {AUTOMATIC, false, 1, 200, 0}, + /* PRESSING */ {AUTOMATIC, false, 1, 200, 0}, + /* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) + /* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0}, + /* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0} +}; + +static const int responsibility_penalties[] = { + 0, /* LAW_MAKING */ + 0, /* LAW_ENFORCEMENT */ + 3000, /* RECEIVE_DIPLOMATS */ + 0, /* MEET_WORKERS */ + 1000, /* MANAGE_PRODUCTION */ + 3000, /* TRADE */ + 1000, /* ACCOUNTING */ + 0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */ + 0, /* MAKE_INTRODUCTIONS */ + 0, /* MAKE_PEACE_AGREEMENTS */ + 0, /* MAKE_TOPIC_AGREEMENTS */ + 0, /* COLLECT_TAXES */ + 0, /* ESCORT_TAX_COLLECTOR */ + 0, /* EXECUTIONS */ + 0, /* TAME_EXOTICS */ + 0, /* RELIGION */ + 0, /* ATTACK_ENEMIES */ + 0, /* PATROL_TERRITORY */ + 0, /* MILITARY_GOALS */ + 0, /* MILITARY_STRATEGY */ + 0, /* UPGRADE_SQUAD_EQUIPMENT */ + 0, /* EQUIPMENT_MANIFESTS */ + 0, /* SORT_AMMUNITION */ + 0, /* BUILD_MORALE */ + 5000 /* HEALTH_MANAGEMENT */ +}; + +struct dwarf_info_t +{ + int highest_skill; + int total_skill; + int mastery_penalty; + int assigned_jobs; + dwarf_state state; + bool has_exclusive_labor; + int noble_penalty; // penalty for assignment due to noble status + bool medical; // this dwarf has medical responsibility + bool trader; // this dwarf has trade responsibility + bool diplomacy; // this dwarf meets with diplomats + int single_labor; // this dwarf will be exclusively assigned to one labor (-1/NONE for none) +}; + +static bool isOptionEnabled(unsigned flag) +{ + return config.isValid() && (config.ival(0) & flag) != 0; +} + +static void setOptionEnabled(ConfigFlags flag, bool on) +{ + if (!config.isValid()) + return; + + if (on) + config.ival(0) |= flag; + else + config.ival(0) &= ~flag; +} + +static void cleanup_state() +{ + labor_infos.clear(); +} + +static void reset_labor(df::unit_labor labor) +{ + labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs); + labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); + labor_infos[labor].set_mode(default_labor_infos[labor].mode); +} + +static void init_state() +{ + config = World::GetPersistentData("autolabor/config"); + if (config.isValid() && config.ival(0) == -1) + config.ival(0) = 0; + + enable_autolabor = isOptionEnabled(CF_ENABLED); + + if (!enable_autolabor) + return; + + auto cfg_haulpct = World::GetPersistentData("autolabor/haulpct"); + if (cfg_haulpct.isValid()) + { + hauler_pct = cfg_haulpct.ival(0); + } + else + { + hauler_pct = 33; + } + + // Load labors from save + labor_infos.resize(ARRAY_COUNT(default_labor_infos)); + + std::vector items; + World::GetPersistentData(&items, "autolabor/labors/", true); + + for (auto p = items.begin(); p != items.end(); p++) + { + string key = p->key(); + df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); + if (labor >= 0 && labor <= labor_infos.size()) + { + labor_infos[labor].config = *p; + labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive; + labor_infos[labor].active_dwarfs = 0; + } + } + + // Add default labors for those not in save + for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { + if (labor_infos[i].config.isValid()) + continue; + + std::stringstream name; + name << "autolabor/labors/" << i; + + labor_infos[i].config = World::AddPersistentData(name.str()); + + labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive; + labor_infos[i].active_dwarfs = 0; + reset_labor((df::unit_labor) i); + } + + generate_labor_to_skill_map(); + +} + +static df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; + +static void generate_labor_to_skill_map() +{ + // Generate labor -> skill mapping + + for (int i = 0; i <= ENUM_LAST_ITEM(unit_labor); i++) + labor_to_skill[i] = job_skill::NONE; + + FOR_ENUM_ITEMS(job_skill, skill) + { + int labor = ENUM_ATTR(job_skill, labor, skill); + if (labor != unit_labor::NONE) + { + /* + assert(labor >= 0); + assert(labor < ARRAY_COUNT(labor_to_skill)); + */ + + labor_to_skill[labor] = skill; + } + } + +} + + +static void enable_plugin(color_ostream &out) +{ + if (!config.isValid()) + { + config = World::AddPersistentData("autolabor/config"); + config.ival(0) = 0; + } + + setOptionEnabled(CF_ENABLED, true); + enable_autolabor = true; + out << "Enabling the plugin." << endl; + + cleanup_state(); + init_state(); +} + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + // initialize labor infos table from default table + if(ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1) + return CR_FAILURE; + + // Fill the command list with your commands. + commands.push_back(PluginCommand( + "autolabor", "Automatically manage dwarf labors.", + autolabor, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " autolabor enable\n" + " autolabor disable\n" + " Enables or disables the plugin.\n" + " autolabor []\n" + " Set number of dwarves assigned to a labor.\n" + " autolabor haulers\n" + " Set a labor to be handled by hauler dwarves.\n" + " autolabor disable\n" + " Turn off autolabor for a specific labor.\n" + " autolabor reset\n" + " Return a labor to the default handling.\n" + " autolabor reset-all\n" + " Return all labors to the default handling.\n" + " autolabor list\n" + " List current status of all labors.\n" + " autolabor status\n" + " Show basic status information.\n" + "Function:\n" + " When enabled, autolabor periodically checks your dwarves and enables or\n" + " disables labors. It tries to keep as many dwarves as possible busy but\n" + " also tries to have dwarves specialize in specific skills.\n" + " Warning: autolabor will override any manual changes you make to labors\n" + " while it is enabled.\n" + "Examples:\n" + " autolabor MINE 2\n" + " Keep at least 2 dwarves with mining enabled.\n" + " autolabor CUT_GEM 1 1\n" + " Keep exactly 1 dwarf with gemcutting enabled.\n" + " autolabor FEED_WATER_CIVILIANS haulers\n" + " Have haulers feed and water wounded dwarves.\n" + " autolabor CUTWOOD disable\n" + " Turn off autolabor for wood cutting.\n" + )); + + init_state(); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + cleanup_state(); + + return CR_OK; +} + +// sorting objects +struct dwarfinfo_sorter +{ + dwarfinfo_sorter(std::vector & info):dwarf_info(info){}; + bool operator() (int i,int j) + { + if (dwarf_info[i].state == IDLE && dwarf_info[j].state != IDLE) + return true; + if (dwarf_info[i].state != IDLE && dwarf_info[j].state == IDLE) + return false; + return dwarf_info[i].mastery_penalty > dwarf_info[j].mastery_penalty; + }; + std::vector & dwarf_info; +}; +struct laborinfo_sorter +{ + bool operator() (int i,int j) + { + return labor_infos[i].mode() < labor_infos[j].mode(); + }; +}; + +struct values_sorter +{ + values_sorter(std::vector & values):values(values){}; + bool operator() (int i,int j) + { + return values[i] > values[j]; + }; + std::vector & values; +}; + + +static void assign_labor(unit_labor::unit_labor labor, + int n_dwarfs, + std::vector& dwarf_info, + bool trader_requested, + std::vector& dwarfs, + bool has_butchers, + bool has_fishery, + color_ostream& out) +{ + df::job_skill skill = labor_to_skill[labor]; + + if (labor_infos[labor].mode() != AUTOMATIC) + return; + + int best_dwarf = 0; + int best_value = -10000; + + std::vector values(n_dwarfs); + std::vector candidates; + std::map dwarf_skill; + std::vector previously_enabled(n_dwarfs); + + auto mode = labor_infos[labor].mode(); + + // Find candidate dwarfs, and calculate a preference value for each dwarf + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + if (dwarf_info[dwarf].state == CHILD) + continue; + if (dwarf_info[dwarf].state == MILITARY) + continue; + if (dwarf_info[dwarf].trader && trader_requested) + continue; + if (dwarf_info[dwarf].diplomacy) + continue; + + if (labor_infos[labor].is_exclusive && dwarf_info[dwarf].has_exclusive_labor) + continue; + + int value = dwarf_info[dwarf].mastery_penalty; + + if (skill != job_skill::NONE) + { + int skill_level = 0; + int skill_experience = 0; + + for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s < dwarfs[dwarf]->status.souls[0]->skills.end(); s++) + { + if ((*s)->id == skill) + { + skill_level = (*s)->rating; + skill_experience = (*s)->experience; + break; + } + } + + dwarf_skill[dwarf] = skill_level; + + value += skill_level * 100; + value += skill_experience / 20; + if (skill_level > 0 || skill_experience > 0) + value += 200; + if (skill_level >= 15) + value += 1000 * (skill_level - 14); + } + else + { + dwarf_skill[dwarf] = 0; + } + + if (dwarfs[dwarf]->status.labors[labor]) + { + value += 5; + if (labor_infos[labor].is_exclusive) + value += 350; + } + + // bias by happiness + + value += dwarfs[dwarf]->status.happiness; + + values[dwarf] = value; + + candidates.push_back(dwarf); + + } + + // Sort candidates by preference value + values_sorter ivs(values); + std::sort(candidates.begin(), candidates.end(), ivs); + + // Disable the labor on everyone + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + if (dwarf_info[dwarf].state == CHILD) + continue; + + previously_enabled[dwarf] = dwarfs[dwarf]->status.labors[labor]; + dwarfs[dwarf]->status.labors[labor] = false; + } + + int min_dwarfs = labor_infos[labor].minimum_dwarfs(); + int max_dwarfs = labor_infos[labor].maximum_dwarfs(); + + // Special - don't assign hunt without a butchers, or fish without a fishery + if (unit_labor::HUNT == labor && !has_butchers) + min_dwarfs = max_dwarfs = 0; + if (unit_labor::FISH == labor && !has_fishery) + min_dwarfs = max_dwarfs = 0; + + bool want_idle_dwarf = true; + if (state_count[IDLE] < 2) + want_idle_dwarf = false; + + /* + * Assign dwarfs to this labor. We assign at least the minimum number of dwarfs, in + * order of preference, and then assign additional dwarfs that meet any of these conditions: + * - The dwarf is idle and there are no idle dwarves assigned to this labor + * - The dwarf has nonzero skill associated with the labor + * - The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. + * We stop assigning dwarfs when we reach the maximum allowed. + * Note that only idle and busy dwarfs count towards the number of dwarfs. "Other" dwarfs + * (sleeping, eating, on break, etc.) will have labors assigned, but will not be counted. + * Military and children/nobles will not have labors assigned. + * Dwarfs with the "health management" responsibility are always assigned DIAGNOSIS. + */ + for (int i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++) + { + int dwarf = candidates[i]; + + assert(dwarf >= 0); + assert(dwarf < n_dwarfs); + + bool preferred_dwarf = false; + if (want_idle_dwarf && dwarf_info[dwarf].state == IDLE) + preferred_dwarf = true; + if (dwarf_skill[dwarf] > 0) + preferred_dwarf = true; + if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive) + preferred_dwarf = true; + if (dwarf_info[dwarf].medical && labor == df::unit_labor::DIAGNOSE) + preferred_dwarf = true; + if (dwarf_info[dwarf].trader && trader_requested) + continue; + if (dwarf_info[dwarf].diplomacy) + continue; + + if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf) + continue; + + if (!dwarfs[dwarf]->status.labors[labor]) + dwarf_info[dwarf].assigned_jobs++; + + dwarfs[dwarf]->status.labors[labor] = true; + + if (labor_infos[labor].is_exclusive) + { + dwarf_info[dwarf].has_exclusive_labor = true; + // all the exclusive labors require equipment so this should force the dorf to reequip if needed + dwarfs[dwarf]->military.pickup_flags.bits.update = 1; + } + + if (print_debug) + out.print("Dwarf %i \"%s\" assigned %s: value %i %s %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), values[dwarf], dwarf_info[dwarf].trader ? "(trader)" : "", dwarf_info[dwarf].diplomacy ? "(diplomacy)" : ""); + + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) + labor_infos[labor].active_dwarfs++; + + if (dwarf_info[dwarf].state == IDLE) + want_idle_dwarf = false; + } +} + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + cleanup_state(); + init_state(); + break; + case SC_MAP_UNLOADED: + cleanup_state(); + break; + default: + break; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + static int step_count = 0; + // check run conditions + if(!world || !world->map.block_index || !enable_autolabor) + { + // give up if we shouldn't be running' + return CR_OK; + } + + if (++step_count < 60) + return CR_OK; + step_count = 0; + + uint32_t race = ui->race_id; + uint32_t civ = ui->civ_id; + + std::vector dwarfs; + + bool has_butchers = false; + bool has_fishery = false; + bool trader_requested = false; + + for (int i = 0; i < world->buildings.all.size(); ++i) + { + df::building *build = world->buildings.all[i]; + auto type = build->getType(); + if (building_type::Workshop == type) + { + df::workshop_type subType = (df::workshop_type)build->getSubtype(); + if (workshop_type::Butchers == subType) + has_butchers = true; + if (workshop_type::Fishery == subType) + has_fishery = true; + } + else if (building_type::TradeDepot == type) + { + df::building_tradedepotst* depot = (df::building_tradedepotst*) build; + trader_requested = trader_requested || depot->trade_flags.bits.trader_requested; + if (print_debug) + { + if (trader_requested) + out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); + else + out.print("Trade depot found but trader is not requested.\n"); + } + } + } + + for (int i = 0; i < world->units.active.size(); ++i) + { + df::unit* cre = world->units.active[i]; + if (Units::isCitizen(cre)) + { + if (cre->burrows.size() > 0) + continue; // dwarfs assigned to burrows are skipped entirely + dwarfs.push_back(cre); + } + } + + int n_dwarfs = dwarfs.size(); + + if (n_dwarfs == 0) + return CR_OK; + + std::vector dwarf_info(n_dwarfs); + + // Find total skill and highest skill for each dwarf. More skilled dwarves shouldn't be used for minor tasks. + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + dwarf_info[dwarf].single_labor = -1; + + if (dwarfs[dwarf]->status.souls.size() <= 0) + continue; + + // compute noble penalty + + int noble_penalty = 0; + + df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id); + for (int i = 0; i < hf->entity_links.size(); i++) + { + df::histfig_entity_link* hfelink = hf->entity_links.at(i); + if (hfelink->getType() == df::histfig_entity_link_type::POSITION) + { + df::histfig_entity_link_positionst *epos = + (df::histfig_entity_link_positionst*) hfelink; + df::historical_entity* entity = df::historical_entity::find(epos->entity_id); + if (!entity) + continue; + df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); + if (!assignment) + continue; + df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); + if (!position) + continue; + + for (int n = 0; n < 25; n++) + if (position->responsibilities[n]) + noble_penalty += responsibility_penalties[n]; + + if (position->responsibilities[df::entity_position_responsibility::HEALTH_MANAGEMENT]) + dwarf_info[dwarf].medical = true; + + if (position->responsibilities[df::entity_position_responsibility::TRADE]) + dwarf_info[dwarf].trader = true; + + } + + } + + dwarf_info[dwarf].noble_penalty = noble_penalty; + + // identify dwarfs who are needed for meetings and mark them for exclusion + + for (int i = 0; i < ui->activities.size(); ++i) + { + df::activity_info *act = ui->activities[i]; + if (!act) continue; + bool p1 = act->person1 == dwarfs[dwarf]; + bool p2 = act->person2 == dwarfs[dwarf]; + + if (p1 || p2) + { + dwarf_info[dwarf].diplomacy = true; + if (print_debug) + out.print("Dwarf %i \"%s\" has a meeting, will be cleared of all labors\n", dwarf, dwarfs[dwarf]->name.first_name.c_str()); + break; + } + } + + for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s != dwarfs[dwarf]->status.souls[0]->skills.end(); s++) + { + df::job_skill skill = (*s)->id; + + df::job_skill_class skill_class = ENUM_ATTR(job_skill, type, skill); + + int skill_level = (*s)->rating; + int skill_experience = (*s)->experience; + + // Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.) + + if (skill_class != job_skill_class::Normal && skill_class != job_skill_class::Medical) + continue; + + if (dwarf_info[dwarf].highest_skill < skill_level) + dwarf_info[dwarf].highest_skill = skill_level; + dwarf_info[dwarf].total_skill += skill_level; + } + } + + // Calculate a base penalty for using each dwarf for a task he isn't good at. + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + dwarf_info[dwarf].mastery_penalty -= 40 * dwarf_info[dwarf].highest_skill; + dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill; + dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty; + + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + if (labor_infos[labor].is_exclusive && dwarfs[dwarf]->status.labors[labor]) + dwarf_info[dwarf].mastery_penalty -= 100; + } + } + + // Find the activity state for each dwarf. It's important to get this right - a dwarf who we think is IDLE but + // can't work will gum everything up. In the future I might add code to auto-detect slacker dwarves. + + state_count.clear(); + state_count.resize(NUM_STATE); + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + bool is_on_break = false; + + for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + is_on_break = true; + } + + if (dwarfs[dwarf]->profession == profession::BABY || + dwarfs[dwarf]->profession == profession::CHILD || + dwarfs[dwarf]->profession == profession::DRUNK) + { + dwarf_info[dwarf].state = CHILD; + } + else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession)) + dwarf_info[dwarf].state = MILITARY; + else if (dwarfs[dwarf]->job.current_job == NULL) + { + if (is_on_break) + dwarf_info[dwarf].state = OTHER; + else if (dwarfs[dwarf]->specific_refs.size() > 0) + dwarf_info[dwarf].state = OTHER; + else + dwarf_info[dwarf].state = IDLE; + } + else + { + int job = dwarfs[dwarf]->job.current_job->job_type; + if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + dwarf_info[dwarf].state = dwarf_states[job]; + else + { + out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); + dwarf_info[dwarf].state = OTHER; + } + } + + state_count[dwarf_info[dwarf].state]++; + + if (print_debug) + out.print("Dwarf %i \"%s\": penalty %i, state %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), dwarf_info[dwarf].mastery_penalty, state_names[dwarf_info[dwarf].state]); + } + + std::vector labors; + + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + labor_infos[labor].active_dwarfs = 0; + + labors.push_back(labor); + } + laborinfo_sorter lasorter; + std::sort(labors.begin(), labors.end(), lasorter); + + // Handle DISABLED skills (just bookkeeping) + for (auto lp = labors.begin(); lp != labors.end(); ++lp) + { + auto labor = *lp; + + if (labor_infos[labor].mode() != DISABLE) + continue; + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + if ((dwarf_info[dwarf].trader && trader_requested) || + dwarf_info[dwarf].diplomacy) + { + dwarfs[dwarf]->status.labors[labor] = false; + } + + if (dwarfs[dwarf]->status.labors[labor]) + { + if (labor_infos[labor].is_exclusive) + dwarf_info[dwarf].has_exclusive_labor = true; + + dwarf_info[dwarf].assigned_jobs++; + } + } + } + + // Handle all skills except those marked HAULERS + + for (auto lp = labors.begin(); lp != labors.end(); ++lp) + { + auto labor = *lp; + + assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out); + } + + // Set about 1/3 of the dwarfs as haulers. The haulers have all HAULER labors enabled. Having a lot of haulers helps + // make sure that hauling jobs are handled quickly rather than building up. + + int num_haulers = state_count[IDLE] + state_count[BUSY] * hauler_pct / 100; + + if (num_haulers < 1) + num_haulers = 1; + + std::vector hauler_ids; + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + if ((dwarf_info[dwarf].trader && trader_requested) || + dwarf_info[dwarf].diplomacy) + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + if (labor_infos[labor].mode() != HAULERS) + continue; + dwarfs[dwarf]->status.labors[labor] = false; + } + continue; + } + + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) + hauler_ids.push_back(dwarf); + } + dwarfinfo_sorter sorter(dwarf_info); + // Idle dwarves come first, then we sort from least-skilled to most-skilled. + std::sort(hauler_ids.begin(), hauler_ids.end(), sorter); + + // don't set any haulers if everyone is off drinking or something + if (hauler_ids.size() == 0) { + num_haulers = 0; + } + + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + if (labor_infos[labor].mode() != HAULERS) + continue; + + for (int i = 0; i < num_haulers; i++) + { + assert(i < hauler_ids.size()); + + int dwarf = hauler_ids[i]; + + assert(dwarf >= 0); + assert(dwarf < n_dwarfs); + dwarfs[dwarf]->status.labors[labor] = true; + dwarf_info[dwarf].assigned_jobs++; + + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) + labor_infos[labor].active_dwarfs++; + + if (print_debug) + out.print("Dwarf %i \"%s\" assigned %s: hauler\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); + } + + for (int i = num_haulers; i < hauler_ids.size(); i++) + { + assert(i < hauler_ids.size()); + + int dwarf = hauler_ids[i]; + + assert(dwarf >= 0); + assert(dwarf < n_dwarfs); + + dwarfs[dwarf]->status.labors[labor] = false; + } + } + + print_debug = 0; + + return CR_OK; +} + +void print_labor (df::unit_labor labor, color_ostream &out) +{ + string labor_name = ENUM_KEY_STR(unit_labor, labor); + out << labor_name << ": "; + for (int i = 0; i < 20 - (int)labor_name.length(); i++) + out << ' '; + if (labor_infos[labor].mode() == DISABLE) + out << "disabled" << endl; + else + { + if (labor_infos[labor].mode() == HAULERS) + out << "haulers"; + else + out << "minimum " << labor_infos[labor].minimum_dwarfs() << ", maximum " << labor_infos[labor].maximum_dwarfs(); + out << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl; + } +} + + +command_result autolabor (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("World is not loaded: please load a game first.\n"); + return CR_FAILURE; + } + + if (parameters.size() == 1 && + (parameters[0] == "0" || parameters[0] == "enable" || + parameters[0] == "1" || parameters[0] == "disable")) + { + bool enable = (parameters[0] == "1" || parameters[0] == "enable"); + if (enable && !enable_autolabor) + { + enable_plugin(out); + } + else if(!enable && enable_autolabor) + { + enable_autolabor = false; + setOptionEnabled(CF_ENABLED, false); + + out << "The plugin is disabled." << endl; + } + + return CR_OK; + } + else if (parameters.size() == 2 && parameters[0] == "haulpct") + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + int pct = atoi (parameters[1].c_str()); + hauler_pct = pct; + return CR_OK; + } + else if (parameters.size() == 2 || parameters.size() == 3) + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + df::unit_labor labor = unit_labor::NONE; + + FOR_ENUM_ITEMS(unit_labor, test_labor) + { + if (parameters[0] == ENUM_KEY_STR(unit_labor, test_labor)) + labor = test_labor; + } + + if (labor == unit_labor::NONE) + { + out.printerr("Could not find labor %s.\n", parameters[0].c_str()); + return CR_WRONG_USAGE; + } + + if (parameters[1] == "haulers") + { + labor_infos[labor].set_mode(HAULERS); + print_labor(labor, out); + return CR_OK; + } + if (parameters[1] == "disable") + { + labor_infos[labor].set_mode(DISABLE); + print_labor(labor, out); + return CR_OK; + } + if (parameters[1] == "reset") + { + reset_labor(labor); + print_labor(labor, out); + return CR_OK; + } + + int minimum = atoi (parameters[1].c_str()); + int maximum = 200; + if (parameters.size() == 3) + maximum = atoi (parameters[2].c_str()); + + if (maximum < minimum || maximum < 0 || minimum < 0) + { + out.printerr("Syntax: autolabor []\n", maximum, minimum); + return CR_WRONG_USAGE; + } + + labor_infos[labor].set_minimum_dwarfs(minimum); + labor_infos[labor].set_maximum_dwarfs(maximum); + labor_infos[labor].set_mode(AUTOMATIC); + print_labor(labor, out); + + return CR_OK; + } + else if (parameters.size() == 1 && parameters[0] == "reset-all") + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + for (int i = 0; i < labor_infos.size(); i++) + { + reset_labor((df::unit_labor) i); + } + out << "All labors reset." << endl; + return CR_OK; + } + else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + bool need_comma = 0; + for (int i = 0; i < NUM_STATE; i++) + { + if (state_count[i] == 0) + continue; + if (need_comma) + out << ", "; + out << state_count[i] << ' ' << state_names[i]; + need_comma = 1; + } + out << endl; + + if (parameters[0] == "list") + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + print_labor(labor, out); + } + } + + return CR_OK; + } + else if (parameters.size() == 1 && parameters[0] == "debug") + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + print_debug = 1; + + return CR_OK; + } + else + { + out.print("Automatically assigns labors to dwarves.\n" + "Activate with 'autolabor 1', deactivate with 'autolabor 0'.\n" + "Current state: %d.\n", enable_autolabor); + + return CR_OK; + } +} + +struct StockpileInfo { + df::building_stockpilest* sp; + int size; + int free; + int x1, x2, y1, y2, z; + +public: + StockpileInfo(df::building_stockpilest *sp_) : sp(sp_) + { + MapExtras::MapCache mc; + + z = sp_->z; + x1 = sp_->room.x; + x2 = sp_->room.x + sp_->room.width; + y1 = sp_->room.y; + y2 = sp_->room.y + sp_->room.height; + int e = 0; + size = 0; + free = 0; + for (int y = y1; y < y2; y++) + for (int x = x1; x < x2; x++) + if (sp_->room.extents[e++] == 1) + { + size++; + DFCoord cursor (x,y,z); + uint32_t blockX = x / 16; + uint32_t tileX = x % 16; + uint32_t blockY = y / 16; + uint32_t tileY = y % 16; + MapExtras::Block * b = mc.BlockAt(cursor/16); + if(b && b->is_valid()) + { + auto &block = *b->getRaw(); + df::tile_occupancy &occ = block.occupancy[tileX][tileY]; + if (!occ.bits.item) + free++; + } + } + } + + bool isFull() { return free == 0; } + + bool canHold(df::item *i) + { + return false; + } + + bool inStockpile(df::item *i) + { + df::item *container = Items::getContainer(i); + if (container) + return inStockpile(container); + + if (i->pos.z != z) return false; + if (i->pos.x < x1 || i->pos.x >= x2 || + i->pos.y < y1 || i->pos.y >= y2) return false; + int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width; + return sp->room.extents[e] == 1; + } + + int getId() { return sp->id; } +}; + +static int stockcheck(color_ostream &out, vector & parameters) +{ + int count = 0; + + std::vector stockpiles; + + for (int i = 0; i < world->buildings.all.size(); ++i) + { + df::building *build = world->buildings.all[i]; + auto type = build->getType(); + if (building_type::Stockpile == type) + { + df::building_stockpilest *sp = virtual_cast(build); + StockpileInfo *spi = new StockpileInfo(sp); + stockpiles.push_back(spi); + } + + } + + std::vector &items = world->items.other[items_other_id::IN_PLAY]; + + // Precompute a bitmask with the bad flags + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); + F(spider_web); F(owned); F(in_job); +#undef F + + for (size_t i = 0; i < items.size(); i++) + { + df::item *item = items[i]; + if (item->flags.whole & bad_flags.whole) + continue; + + // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG + + df::item_type typ = item->getType(); + if (typ != item_type::MEAT && + typ != item_type::FISH && + typ != item_type::FISH_RAW && + typ != item_type::PLANT && + typ != item_type::CHEESE && + typ != item_type::FOOD && + typ != item_type::EGG) + continue; + + df::item *container = 0; + df::unit *holder = 0; + df::building *building = 0; + + for (size_t i = 0; i < item->general_refs.size(); i++) + { + df::general_ref *ref = item->general_refs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + container = ref->getItem(); + break; + + case general_ref_type::UNIT_HOLDER: + holder = ref->getUnit(); + break; + + case general_ref_type::BUILDING_HOLDER: + building = ref->getBuilding(); + break; + + default: + break; + } + } + + df::item *nextcontainer = container; + df::item *lastcontainer = 0; + + while(nextcontainer) { + df::item *thiscontainer = nextcontainer; + nextcontainer = 0; + for (size_t i = 0; i < thiscontainer->general_refs.size(); i++) + { + df::general_ref *ref = thiscontainer->general_refs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + lastcontainer = nextcontainer = ref->getItem(); + break; + + case general_ref_type::UNIT_HOLDER: + holder = ref->getUnit(); + break; + + case general_ref_type::BUILDING_HOLDER: + building = ref->getBuilding(); + break; + + default: + break; + } + } + } + + if (holder) + continue; // carried items do not rot as far as i know + + if (building) { + df::building_type btype = building->getType(); + if (btype == building_type::TradeDepot || + btype == building_type::Wagon) + continue; // items in trade depot or the embark wagon do not rot + + if (typ == item_type::EGG && btype ==building_type::NestBox) + continue; // eggs in nest box do not rot + } + + int canHoldCount = 0; + StockpileInfo *current = 0; + + for (int idx = 0; idx < stockpiles.size(); idx++) + { + StockpileInfo *spi = stockpiles[idx]; + if (spi->canHold(item)) canHoldCount++; + if (spi->inStockpile(item)) current=spi; + } + + if (current) + continue; + + count++; + + } + + return count; +} From 148a37b2e4aebe1ec8225d54765aecc495fee81b Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 13 Feb 2013 13:55:28 -0600 Subject: [PATCH 129/136] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 506ab1e68..76fb647a4 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 506ab1e68d1522e2f282f134176b7da774f6a73c +Subproject commit 76fb647a42ba2064d11f2a0be7bf04f6e3622bc5 From e35a1c772062241138b9814823b1be65123a0769 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 13 Feb 2013 16:00:09 -0600 Subject: [PATCH 130/136] Correct autolabor2 for changes in structures. --- plugins/devel/autolabor2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/devel/autolabor2.cpp b/plugins/devel/autolabor2.cpp index c06e5ea73..0d5196a77 100644 --- a/plugins/devel/autolabor2.cpp +++ b/plugins/devel/autolabor2.cpp @@ -1858,7 +1858,7 @@ private: state = OTHER; else if (dwarf->dwarf->burrows.size() > 0) state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy - else if (dwarf->dwarf->status2.able_grasp_impair == 0) + else if (dwarf->dwarf->status2.limbs_grasp_count == 0) { state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors dwarf->clear_all = true; From a8f5e54e37dab4495d6888fb7245b8b55173ad88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 14 Feb 2013 09:53:14 +0100 Subject: [PATCH 131/136] Sync submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 76fb647a4..c7e2c28fe 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 76fb647a42ba2064d11f2a0be7bf04f6e3622bc5 +Subproject commit c7e2c28febd6dca06ff7e9951090982fbbee12b5 diff --git a/plugins/stonesense b/plugins/stonesense index cb97cf308..37f6e626b 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd +Subproject commit 37f6e626b054571b72535e2ac0ee3957e07432f1 From 177e45bdd83fe923ffc85205e19f071b6bc8f464 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 14 Feb 2013 12:49:57 +0400 Subject: [PATCH 132/136] Improve fix/cloth-stockpile performance by 30% by reducing garbage. --- scripts/fix/cloth-stockpile.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/fix/cloth-stockpile.lua b/scripts/fix/cloth-stockpile.lua index 7da5d583c..45e5fcc43 100644 --- a/scripts/fix/cloth-stockpile.lua +++ b/scripts/fix/cloth-stockpile.lua @@ -1,8 +1,16 @@ -- Fixes cloth/thread stockpiles by correcting material object data. local raws = df.global.world.raws -local organic_types = raws.mat_table.organic_types -local organic_indexes = raws.mat_table.organic_indexes + +-- Cache references to vectors in lua tables for a speed-up +local organic_types = {} +for i,v in ipairs(raws.mat_table.organic_types) do + organic_types[i] = v +end +local organic_indexes = {} +for i,v in ipairs(raws.mat_table.organic_indexes) do + organic_indexes[i] = v +end local function verify(category,idx,vtype,vidx) if idx == -1 then From 7cbd201f318cd3067f0f00ac77a5bbe18dcebfa7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 14 Feb 2013 13:07:27 +0400 Subject: [PATCH 133/136] Nuke the third exit(1) and change building caching code to make more sense. --- library/modules/Buildings.cpp | 39 +++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index ab70944b7..f2312b05d 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1114,18 +1114,21 @@ void Buildings::clearBuildings(color_ostream& out) { locationToBuilding.clear(); } -void Buildings::updateBuildings(color_ostream& out, void* ptr) { - //out.print("Updating buildings, %s %d\n", __FILE__, __LINE__); +void Buildings::updateBuildings(color_ostream& out, void* ptr) +{ int32_t id = (int32_t)ptr; - - if ( corner1.find(id) == corner1.end() ) { - //new building: mark stuff - int32_t index = df::building::binsearch_index(df::global::world->buildings.all, id); - if ( index == -1 ) { - out.print("%s, line %d: Couldn't find new building id=%d.\n", __FILE__, __LINE__, id); - exit(1); - } - df::building* building = df::global::world->buildings.all[index]; + auto building = df::building::find(id); + + if (building) + { + // Already cached -> weird, so bail out + if (corner1.count(id)) + return; + // Civzones cannot be cached because they can + // overlap each other and normal buildings. + if (!building->isSettingOccupancy()) + return; + df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); @@ -1135,18 +1138,24 @@ void Buildings::updateBuildings(color_ostream& out, void* ptr) { for ( int32_t x = p1.x; x <= p2.x; x++ ) { for ( int32_t y = p1.y; y <= p2.y; y++ ) { df::coord pt(x,y,building->z); - locationToBuilding[pt] = id; + if (containsTile(building, pt, false)) + locationToBuilding[pt] = id; } } - } else { + } + else if (corner1.count(id)) + { //existing building: destroy it df::coord p1 = corner1[id]; df::coord p2 = corner2[id]; - + for ( int32_t x = p1.x; x <= p2.x; x++ ) { for ( int32_t y = p1.y; y <= p2.y; y++ ) { df::coord pt(x,y,p1.z); - locationToBuilding.erase(pt); + + auto cur = locationToBuilding.find(pt); + if (cur != locationToBuilding.end() && cur->second == id) + locationToBuilding.erase(cur); } } From 8de172f1c868f1ead74330954dc8a3f94ca0e3e1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 14 Feb 2013 13:12:23 +0400 Subject: [PATCH 134/136] Binsearch in units.active can't possibly work, ever. --- plugins/trueTransformation.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp index 1e3403fc5..4527871d9 100644 --- a/plugins/trueTransformation.cpp +++ b/plugins/trueTransformation.cpp @@ -35,12 +35,12 @@ void syndromeHandler(color_ostream& out, void* ptr) { EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr; //out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); - int32_t index = df::unit::binsearch_index(df::global::world->units.active, data->unitId); - if ( index < 0 ) { + df::unit* unit = df::unit::find(data->unitId); + if (!unit) { out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__); return; } - df::unit* unit = df::global::world->units.active[index]; + df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex]; df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type]; From 84f2ee75614fdad0486ae662b597cd4c760554db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 14 Feb 2013 12:17:23 +0100 Subject: [PATCH 135/136] Update readme html --- Readme.html | 191 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 125 insertions(+), 66 deletions(-) diff --git a/Readme.html b/Readme.html index 54deb013f..d9a3d0602 100644 --- a/Readme.html +++ b/Readme.html @@ -3,13 +3,13 @@ - + DFHack Readme