From 24fbf570e62880eb87b786d6cc53bbfe124106f6 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 18 Jan 2014 22:45:42 -0600 Subject: [PATCH] Add "strangemood" plugin, lets you trigger a strange mood --- plugins/CMakeLists.txt | 1 + plugins/strangemood.cpp | 1238 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1239 insertions(+) create mode 100644 plugins/strangemood.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4de3c68bf..29acba8a1 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -162,6 +162,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(treefarm treefarm.cpp) DFHACK_PLUGIN(cleanconst cleanconst.cpp) DFHACK_PLUGIN(3dveins 3dveins.cpp) + DFHACK_PLUGIN(strangemood strangemood.cpp) endif() # this is the skeleton plugin. If you want to make your own, make a copy and then change it diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp new file mode 100644 index 000000000..5dd12c627 --- /dev/null +++ b/plugins/strangemood.cpp @@ -0,0 +1,1238 @@ +// Triggers a strange mood using (mostly) the same logic used in-game + +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/Gui.h" +#include "modules/Units.h" +#include "modules/Items.h" +#include "modules/Job.h" +#include "modules/Translation.h" + +#include "DataDefs.h" +#include "df/d_init.h" +#include "df/world.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/unit_soul.h" +#include "df/unit_skill.h" +#include "df/unit_preference.h" +#include "df/map_block.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/historical_entity.h" +#include "df/entity_raw.h" +#include "df/builtin_mats.h" +#include "df/general_ref_unit_workerst.h" + +using std::string; +using std::vector; +using namespace DFHack; +using namespace df::enums; + +using df::global::world; +using df::global::ui; +using df::global::d_init; +using df::global::created_item_count; +using df::global::created_item_type; +using df::global::created_item_subtype; +using df::global::created_item_mattype; +using df::global::created_item_matindex; + +bool isUnitMoodable (df::unit *unit) +{ + if (!Units::isCitizen(unit)) + return false; + if (!unit->status2.limbs_grasp_count) + return false; + if (unit->mood != mood_type::None) + return false; + if (!ENUM_ATTR(profession,moodable,unit->profession)) + return false; + return true; +} + +df::job_skill getMoodSkill (df::unit *unit) +{ + if (!unit->status.current_soul) + return job_skill::STONECRAFT; + df::historical_entity *civ = df::historical_entity::find(unit->civ_id); + df::unit_soul *soul = unit->status.current_soul; + vector skills; + df::skill_rating level = skill_rating::Dabbling; + for (size_t i = 0; i < soul->skills.size(); i++) + { + df::unit_skill *skill = soul->skills[i]; + switch (skill->id) + { + case job_skill::MINING: + case job_skill::CARPENTRY: + case job_skill::DETAILSTONE: + case job_skill::MASONRY: + case job_skill::TANNER: + case job_skill::WEAVING: + case job_skill::CLOTHESMAKING: + case job_skill::FORGE_WEAPON: + case job_skill::FORGE_ARMOR: + case job_skill::FORGE_FURNITURE: + case job_skill::CUTGEM: + case job_skill::ENCRUSTGEM: + case job_skill::WOODCRAFT: + case job_skill::STONECRAFT: + case job_skill::METALCRAFT: + case job_skill::GLASSMAKER: + case job_skill::LEATHERWORK: + case job_skill::BONECARVE: + case job_skill::BOWYER: + case job_skill::MECHANICS: + if (skill->rating > level) + { + skills.clear(); + level = skill->rating; + } + if (skill->rating == level) + skills.push_back(skill->id); + break; + } + } + if (!skills.size() && civ) + { + if (civ->entity_raw->jobs.permitted_skill[job_skill::WOODCRAFT]) + skills.push_back(job_skill::WOODCRAFT); + if (civ->entity_raw->jobs.permitted_skill[job_skill::STONECRAFT]) + skills.push_back(job_skill::STONECRAFT); + if (civ->entity_raw->jobs.permitted_skill[job_skill::BONECARVE]) + skills.push_back(job_skill::BONECARVE); + } + if (!skills.size()) + skills.push_back(job_skill::STONECRAFT); + return skills[rand() % skills.size()]; +} + +int getCreatedMetalBars (int32_t idx) +{ + for (size_t i = 0; i < created_item_type->size(); i++) + { + if (created_item_type->at(i) == item_type::BAR && + created_item_subtype->at(i) == -1 && + created_item_mattype->at(i) == 0 && + created_item_matindex->at(i) == idx) + return created_item_count->at(i); + } + return 0; +} + +void selectWord (const df::world_raws::T_language::T_word_table &table, int32_t &word, df::enum_field &part, int mode) +{ + if (table.parts[mode].size()) + { + int offset = rand() % table.parts[mode].size(); + word = table.words[mode][offset]; + part = table.parts[mode][offset]; + } + else + { + word = rand() % world->raws.language.words.size(); + part = (df::part_of_speech)(rand() % 9); + Core::getInstance().getConsole().printerr("Impoverished Word Selector"); + } +} + +void generateName(df::language_name &output, int language, int mode, const df::world_raws::T_language::T_word_table &table1, const df::world_raws::T_language::T_word_table &table2) +{ + for (int i = 0; i < 100; i++) + { + if (mode != 8 && mode != 9) + { + output = df::language_name(); + if (language == -1) + language = rand() % world->raws.language.translations.size(); + output.unknown = mode; + output.language = language; + } + output.has_name = 1; + if (output.language == -1) + output.language = rand() % world->raws.language.translations.size(); + int r, r2, r3; + switch (mode) + { + case 0: case 9: case 10: + if (mode != 9) + { + int32_t word; df::enum_field part; + output.first_name.clear(); + selectWord(table1, word, part, 2); + if (word >= 0 && word < world->raws.language.words.size()) + output.first_name = *world->raws.language.translations[language]->words[word]; + } + if (mode != 10) + { + case 4: case 37: // this is not a typo + if (rand() % 2) + { + selectWord(table2, output.words[0], output.parts_of_speech[0], 0); + selectWord(table1, output.words[1], output.parts_of_speech[1], 1); + } + else + { + selectWord(table1, output.words[0], output.parts_of_speech[0], 0); + selectWord(table2, output.words[1], output.parts_of_speech[1], 1); + } + } + break; + + case 1: case 13: case 20: + r = rand() % 3; + if (r == 0 || r == 1) + { + if (rand() % 2) + { + selectWord(table2, output.words[0], output.parts_of_speech[0], 0); + selectWord(table1, output.words[1], output.parts_of_speech[1], 1); + } + else + { + selectWord(table1, output.words[0], output.parts_of_speech[0], 0); + selectWord(table2, output.words[1], output.parts_of_speech[1], 1); + } + } + if (r == 1 || r == 2) + { + case 3: case 8: case 11: // this is not a typo either + r2 = rand() % 2; + if (r2) + selectWord(table1, output.words[5], output.parts_of_speech[5], 2); + else + selectWord(table2, output.words[5], output.parts_of_speech[5], 2); + r3 = rand() % 3; + if (rand() % 50) + r3 = rand() % 2; + switch (r3) + { + case 0: + case 2: + if (r3 == 2) + r2 = rand() % 2; + if (r2) + selectWord(table2, output.words[6], output.parts_of_speech[6], 5); + else + selectWord(table1, output.words[6], output.parts_of_speech[6], 5); + if (r3 == 0) + break; + r2 = -r2; + case 1: + if (r2) + selectWord(table1, output.words[2], output.parts_of_speech[2], 3); + else + selectWord(table2, output.words[2], output.parts_of_speech[2], 3); + if (!(rand() % 100)) + selectWord(table1, output.words[3], output.parts_of_speech[3], 3); + break; + } + } + if (rand() % 100) + { + if (rand() % 2) + selectWord(table1, output.words[4], output.parts_of_speech[4], 4); + else + selectWord(table2, output.words[4], output.parts_of_speech[4], 4); + } + if ((mode == 3) && (output.parts_of_speech[5] == part_of_speech::Noun) && (output.words[5] != -1) && (world->raws.language.words[output.words[5]]->forms[1].length())) + output.parts_of_speech[5] = part_of_speech::NounPlural; + break; + + case 2: case 5: case 6: case 12: case 14: case 15: case 16: case 17: case 18: case 19: + case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30: + case 31: case 32: case 33: case 34: case 35: case 36: case 38: case 39: + selectWord(table1, output.words[5], output.parts_of_speech[5], 2); + r3 = rand() % 3; + if (rand() % 50) + r3 = rand() % 2; + switch (r3) + { + case 0: + case 2: + selectWord(table2, output.words[6], output.parts_of_speech[6], 5); + if (r3 == 0) + break; + case 1: + selectWord(table2, output.words[2], output.parts_of_speech[2], 3); + if (!(rand() % 100)) + selectWord(table2, output.words[3], output.parts_of_speech[3], 3); + break; + } + if (rand() % 100) + selectWord(table2, output.words[4], output.parts_of_speech[4], 4); + break; + + case 7: + r = rand() % 3; + if (r == 0 || r == 1) + { + selectWord(table2, output.words[0], output.parts_of_speech[0], 0); + selectWord(table1, output.words[1], output.parts_of_speech[1], 1); + } + if (r == 1 || r == 2) + { + r2 = rand() % 2; + if (r == 2 || r2 == 1) + selectWord(table1, output.words[5], output.parts_of_speech[5], 2); + else + selectWord(table2, output.words[5], output.parts_of_speech[5], 2); + r3 = rand() % 3; + if (rand() % 50) + r3 = rand() % 2; + switch (r3) + { + case 0: + case 2: + selectWord(table1, output.words[6], output.parts_of_speech[6], 5); + if (r3 == 0) + break; + case 1: + selectWord(table2, output.words[2], output.parts_of_speech[2], 3); + if (!(rand() % 100)) + selectWord(table2, output.words[3], output.parts_of_speech[3], 3); + break; + } + } + if (rand() % 100) + selectWord(table2, output.words[4], output.parts_of_speech[4], 4); + break; + } + if (output.words[2] != -1 && output.words[3] != -1 && + world->raws.language.words[output.words[3]]->adj_dist < world->raws.language.words[output.words[2]]->adj_dist) + { + std::swap(output.words[2], output.words[3]); + std::swap(output.parts_of_speech[2], output.parts_of_speech[3]); + } + bool next = false; + if ((output.parts_of_speech[5] == df::part_of_speech::NounPlural) && (output.parts_of_speech[6] == df::part_of_speech::NounPlural)) + next = true; + if (output.words[0] != -1) + { + if (output.words[6] == -1) next = true; + if (output.words[4] == -1) next = true; + if (output.words[2] == -1) next = true; + if (output.words[3] == -1) next = true; + if (output.words[5] == -1) next = true; + } + if (output.words[1] != -1) + { + if (output.words[6] == -1) next = true; + if (output.words[4] == -1) next = true; + if (output.words[2] == -1) next = true; + if (output.words[3] == -1) next = true; + if (output.words[5] == -1) next = true; + } + if (output.words[4] != -1) + { + if (output.words[6] == -1) next = true; + if (output.words[2] == -1) next = true; + if (output.words[3] == -1) next = true; + if (output.words[5] == -1) next = true; + } + if (output.words[2] != -1) + { + if (output.words[6] == -1) next = true; + if (output.words[3] == -1) next = true; + if (output.words[5] == -1) next = true; + } + if (output.words[3] != -1) + { + if (output.words[6] == -1) next = true; + if (output.words[5] == -1) next = true; + } + if (output.words[5] != -1) + { + if (output.words[6] == -1) next = true; + } + if (!next) + return; + } +} + +command_result df_strangemood (color_ostream &out, vector & parameters) +{ + if (!Translation::IsValid()) + { + out.printerr("Translation data unavailable!\n"); + return CR_FAILURE; + } + bool force = false; + df::unit *unit = NULL; + df::mood_type type = mood_type::None; + + for (size_t i = 0; i < parameters.size(); i++) + { + if(parameters[i] == "help" || parameters[i] == "?") + return CR_WRONG_USAGE; + else if(parameters[i] == "-force") + force = true; + else if(parameters[i] == "-unit") + { + unit = DFHack::Gui::getSelectedUnit(out); + if (!unit) + return CR_FAILURE; + } + else if (parameters[i] == "-fey") + type = mood_type::Fey; + else if (parameters[i] == "-secretive") + type = mood_type::Secretive; + else if (parameters[i] == "-possessed") + type = mood_type::Possessed; + else if (parameters[i] == "-fell") + type = mood_type::Fell; + else if (parameters[i] == "-macabre") + type = mood_type::Macabre; + else + return CR_WRONG_USAGE; + } + + CoreSuspender suspend; + + // First, check if moods are enabled at all + if (!d_init->flags4.is_set(d_init_flags4::ARTIFACTS)) + { + out.printerr("ARTIFACTS are not enabled!\n"); + return CR_FAILURE; + } + if (*df::global::debug_nomoods) + { + out.printerr("Strange moods disabled via debug flag!\n"); + return CR_FAILURE; + } + if (ui->mood_cooldown && !force) + { + out.printerr("Last strange mood happened too recently!\n"); + return CR_FAILURE; + } + + // Also, make sure there isn't a mood already running + for (size_t i = 0; i < world->units.active.size(); i++) + { + df::unit *cur = world->units.active[i]; + if (Units::isCitizen(cur) && cur->flags1.bits.has_mood) + { + ui->mood_cooldown = 1000; + out.printerr("A strange mood is already in progress!\n"); + return CR_FAILURE; + } + } + + // See which units are eligible to enter moods + vector moodable_units; + bool mood_available = false; + for (size_t i = 0; i < world->units.active.size(); i++) + { + df::unit *cur = world->units.active[i]; + if (!isUnitMoodable(cur)) + continue; + if (cur->flags1.bits.has_mood) + { + ui->mood_cooldown = 1000; + out.printerr("A strange mood is already in progress!\n"); + return CR_FAILURE; + } + if (!cur->flags1.bits.had_mood) + mood_available = true; + moodable_units.push_back(cur); + } + if (!mood_available) + { + out.printerr("No dwarves are available to enter a mood!\n"); + return CR_FAILURE; + } + + // If unit was manually selected, redo checks explicitly + if (unit) + { + if (!isUnitMoodable(unit)) + { + out.printerr("Selected unit is not eligible to enter a strange mood!\n"); + return CR_FAILURE; + } + if (unit->flags1.bits.had_mood) + { + out.printerr("Selected unit has already had a strange mood!\n"); + return CR_FAILURE; + } + } + + // Obey in-game mood limits + if (!force) + { + if (moodable_units.size() < 20) + { + out.printerr("Fortress is not eligible for a strange mood at this time - not enough moodable units.\n"); + return CR_FAILURE; + } + int num_items = 0; + for (size_t i = 0; i < created_item_count->size(); i++) + num_items += created_item_count->at(i); + + int num_revealed_tiles = 0; + for (size_t i = 0; i < world->map.map_blocks.size(); i++) + { + df::map_block *blk = world->map.map_blocks[i]; + for (int x = 0; x < 16; x++) + for (int y = 0; y < 16; y++) + if (blk->designation[x][y].bits.subterranean && !blk->designation[x][y].bits.hidden) + num_revealed_tiles++; + } + if (num_revealed_tiles / 2304 < ui->tasks.num_artifacts) + { + out.printerr("Fortress is not eligible for a strange mood at this time - not enough subterranean tiles revealed.\n"); + return CR_FAILURE; + } + if (num_items / 200 < ui->tasks.num_artifacts) + { + out.printerr("Fortress is not eligible for a strange mood at this time - not enough items created\n"); + return CR_FAILURE; + } + } + + // Randomly select a unit to enter a mood + if (!unit) + { + vector tickets; + for (size_t i = 0; i < moodable_units.size(); i++) + { + df::unit *cur = moodable_units[i]; + if (cur->flags1.bits.had_mood) + continue; + if (cur->relations.dragger_id != -1) + continue; + if (cur->relations.draggee_id != -1) + continue; + for (int j = 0; j < 5; j++) + tickets.push_back(i); + switch (cur->profession) + { + case profession::WOODWORKER: + case profession::CARPENTER: + case profession::BOWYER: + case profession::STONEWORKER: + case profession::MASON: + for (int j = 0; j < 5; j++) + tickets.push_back(i); + break; + case profession::METALSMITH: + case profession::WEAPONSMITH: + case profession::ARMORER: + case profession::BLACKSMITH: + case profession::METALCRAFTER: + case profession::JEWELER: + case profession::GEM_CUTTER: + case profession::GEM_SETTER: + case profession::CRAFTSMAN: + case profession::WOODCRAFTER: + case profession::STONECRAFTER: + case profession::LEATHERWORKER: + case profession::BONE_CARVER: + case profession::WEAVER: + case profession::CLOTHIER: + case profession::GLASSMAKER: + for (int j = 0; j < 15; j++) + tickets.push_back(i); + break; + } + } + if (!tickets.size()) + { + out.printerr("No units are eligible to enter a mood!\n"); + return CR_FAILURE; + } + unit = moodable_units[tickets[rand() % tickets.size()]]; + } + df::unit_soul *soul = unit->status.current_soul; + + // Cancel selected unit's current job + if (unit->job.current_job) + { + // TODO: cancel job + out.printerr("Chosen unit '%s' has active job, cannot start mood!\n", Translation::TranslateName(&unit->name, false).c_str()); + return CR_FAILURE; + } + + ui->mood_cooldown = 1000; + // If no mood type was specified, pick one randomly + if (type == mood_type::None) + { + if (rand() % 100 > unit->status.happiness) + { + switch (rand() % 2) + { + case 0: type = mood_type::Fell; break; + case 1: type = mood_type::Macabre; break; + } + } + else + { + switch (rand() % 3) + { + case 0: type = mood_type::Fey; break; + case 1: type = mood_type::Secretive; break; + case 2: type = mood_type::Possessed; break; + } + } + } + + // Display announcement and start setting up the mood job + int color = 0; + bool bright = false; + string msg = Translation::TranslateName(&unit->name, false) + ", " + Units::getProfessionName(unit); + + switch (type) + { + case mood_type::Fey: + color = 7; + bright = true; + msg += " is taken by a fey mood!"; + break; + case mood_type::Secretive: + color = 7; + bright = false; + msg += " withdraws from society..."; + break; + case mood_type::Possessed: + color = 5; + bright = true; + msg += " has been possessed!"; + break; + case mood_type::Macabre: + color = 0; + bright = true; + msg += " begins to stalk and brood..."; + break; + case mood_type::Fell: + color = 5; + bright = false; + msg += " looses a roaring laughter, fell and terrible!"; + break; + default: + out.printerr("Invalid mood type selected?\n"); + return CR_FAILURE; + } + + unit->mood = type; + unit->relations.mood_copy = unit->mood; + Gui::showAutoAnnouncement(announcement_type::STRANGE_MOOD, unit->pos, msg, color, bright); + + unit->status.happiness = 100; + // TODO: make sure unit drops any wrestle items + unit->job.mood_timeout = 50000; + unit->flags1.bits.has_mood = true; + unit->flags1.bits.had_mood = true; + unit->job.mood_skill = getMoodSkill(unit); + df::job *job = new df::job(); + Job::linkIntoWorld(job); + + // Choose the job type + if (unit->mood == mood_type::Fell) + job->job_type = job_type::StrangeMoodFell; + else if (unit->mood == mood_type::Macabre) + job->job_type = job_type::StrangeMoodBrooding; + else + { + switch (unit->job.mood_skill) + { + case job_skill::MINING: + case job_skill::MASONRY: + job->job_type = job_type::StrangeMoodMason; + break; + case job_skill::CARPENTRY: + job->job_type = job_type::StrangeMoodCarpenter; + break; + case job_skill::DETAILSTONE: + case job_skill::WOODCRAFT: + case job_skill::STONECRAFT: + case job_skill::BONECARVE: + job->job_type = job_type::StrangeMoodCrafter; + break; + case job_skill::TANNER: + case job_skill::LEATHERWORK: + job->job_type = job_type::StrangeMoodTanner; + break; + case job_skill::WEAVING: + case job_skill::CLOTHESMAKING: + job->job_type = job_type::StrangeMoodWeaver; + break; + case job_skill::FORGE_WEAPON: + case job_skill::FORGE_ARMOR: + case job_skill::FORGE_FURNITURE: + case job_skill::METALCRAFT: + job->job_type = job_type::StrangeMoodForge; + break; + case job_skill::CUTGEM: + case job_skill::ENCRUSTGEM: + job->job_type = job_type::StrangeMoodJeweller; + break; + case job_skill::GLASSMAKER: + job->job_type = job_type::StrangeMoodGlassmaker; + break; + case job_skill::BOWYER: + job->job_type = job_type::StrangeMoodBowyer; + break; + case job_skill::MECHANICS: + job->job_type = job_type::StrangeMoodMechanics; + break; + } + } + // Check which types of glass are available - we'll need this information later + bool have_glass[3] = {false, false, false}; + for (size_t i = 0; i < created_item_type->size(); i++) + { + if (created_item_type->at(i) == item_type::ROUGH) + { + switch (created_item_mattype->at(i)) + { + case builtin_mats::GLASS_GREEN: + have_glass[0] = true; + break; + case builtin_mats::GLASS_CLEAR: + have_glass[1] = true; + break; + case builtin_mats::GLASS_CRYSTAL: + have_glass[2] = true; + break; + } + } + } + + // The dwarf will want 1-3 of the base material + int base_item_count = 1 + (rand() % 3); + if ((unit->job.mood_skill == job_skill::CUTGEM || unit->job.mood_skill == job_skill::ENCRUSTGEM) && (rand() % 2)) + base_item_count = 1; + + // Choose the base material + df::job_item *item; + if (job->job_type == job_type::StrangeMoodFell) + { + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::CORPSE; + item->flags1.bits.allow_buryable = true; + item->flags1.bits.murdered = true; + item->quantity = 1; + item->vector_id = job_item_vector_id::ANY_MURDERED; + } + else if (job->job_type == job_type::StrangeMoodBrooding) + { + switch (rand() % 3) + { + case 0: + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::REMAINS; + item->flags1.bits.allow_buryable = true; + item->quantity = 1; + break; + case 1: + job->job_items.push_back(item = new df::job_item()); + item->flags1.bits.allow_buryable = true; + item->flags2.bits.bone = true; + item->flags2.bits.body_part = true; + item->quantity = 1; + break; + case 2: + job->job_items.push_back(item = new df::job_item()); + item->flags1.bits.allow_buryable = true; + item->flags2.bits.totemable = true; + item->flags2.bits.body_part = true; + item->quantity = base_item_count; + break; + } + } + else + { + df::item *filter; + bool found_pref; + switch (unit->job.mood_skill) + { + case job_skill::MINING: + case job_skill::DETAILSTONE: + case job_skill::MASONRY: + case job_skill::STONECRAFT: + case job_skill::MECHANICS: + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::BOULDER; + item->quantity = base_item_count; + item->flags3.bits.hard = true; + found_pref = false; + if (soul) + { + for (size_t i = 0; i < soul->preferences.size(); i++) + { + df::unit_preference *pref = soul->preferences[i]; + if (pref->active == 1 && + pref->type == df::unit_preference::T_type::LikeMaterial && + pref->mattype == builtin_mats::INORGANIC) + { + item->mat_type = pref->mattype; + found_pref = true; + break; + } + } + } + if (!found_pref) + item->mat_type = builtin_mats::INORGANIC; + break; + + case job_skill::CARPENTRY: + case job_skill::WOODCRAFT: + case job_skill::BOWYER: + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::WOOD; + item->quantity = base_item_count; + break; + + case job_skill::TANNER: + case job_skill::LEATHERWORK: + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::SKIN_TANNED; + item->quantity = base_item_count; + break; + + case job_skill::WEAVING: + case job_skill::CLOTHESMAKING: + filter = NULL; + // TODO: do proper search through world->items.other[items_other_id::ANY_GENERIC35] for item_type CLOTH, mat_type 0, flags2.deep_material, and min_dimension 10000 + for (size_t i = 0; i < world->items.other[items_other_id::ANY_GENERIC35].size(); i++) + { + filter = world->items.other[items_other_id::ANY_GENERIC35][i]; + if (filter->getType() != item_type::CLOTH) + { + filter = NULL; + continue; + } + if (filter->getMaterial() != 0) + { + filter = NULL; + continue; + } + if (filter->getTotalDimension() < 10000) + { + filter = NULL; + continue; + } + MaterialInfo mat(filter->getMaterial(), filter->getMaterialIndex()); + if (!mat.inorganic->flags.is_set(inorganic_flags::DEEP_SPECIAL)) + { + filter = NULL; + continue; + } + } + if (filter) + { + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::CLOTH; + item->mat_type = filter->getMaterial(); + item->mat_index = filter->getMaterialIndex(); + item->quantity = base_item_count * 10000; + item->min_dimension = 10000; + } + else + { + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::CLOTH; + bool found_pref = false; + if (soul) + { + for (size_t i = 0; i < soul->preferences.size(); i++) + { + df::unit_preference *pref = soul->preferences[i]; + if (pref->active == 1 && + pref->type == df::unit_preference::T_type::LikeMaterial) + { + MaterialInfo mat(pref->mattype, pref->matindex); + if (mat.material->flags.is_set(material_flags::SILK)) + item->flags2.bits.silk = true; + else if (mat.material->flags.is_set(material_flags::THREAD_PLANT)) + item->flags2.bits.plant = true; + else if (mat.material->flags.is_set(material_flags::YARN)) + item->flags2.bits.yarn = true; + else + continue; + found_pref = true; + break; + } + } + } + if (!found_pref) + { + switch (rand() % 3) + { + case 0: + item->flags2.bits.silk = true; + break; + case 1: + item->flags2.bits.plant = true; + break; + case 2: + item->flags2.bits.yarn = true; + break; + } + } + item->quantity = base_item_count * 10000; + item->min_dimension = 10000; + } + break; + + case job_skill::FORGE_WEAPON: + case job_skill::FORGE_ARMOR: + case job_skill::FORGE_FURNITURE: + case job_skill::METALCRAFT: + filter = NULL; + // TODO: do proper search through world->items.other[items_other_id::ANY_GENERIC35] for item_type BAR, mat_type 0, and flags2.deep_material + for (size_t i = 0; i < world->items.other[items_other_id::ANY_GENERIC35].size(); i++) + { + filter = world->items.other[items_other_id::ANY_GENERIC35][i]; + if (filter->getType() != item_type::BAR) + { + filter = NULL; + continue; + } + if (filter->getMaterial() != 0) + { + filter = NULL; + continue; + } + MaterialInfo mat(filter->getMaterial(), filter->getMaterialIndex()); + if (!mat.inorganic->flags.is_set(inorganic_flags::DEEP_SPECIAL)) + { + filter = NULL; + continue; + } + } + if (filter) + { + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::BAR; + item->mat_type = filter->getMaterial(); + item->mat_index = filter->getMaterialIndex(); + item->quantity = base_item_count; + } + else + { + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::BAR; + item->mat_type = 0; + vector mats; + if (soul) + { + for (size_t i = 0; i < soul->preferences.size(); i++) + { + df::unit_preference *pref = soul->preferences[i]; + if (pref->active == 1 && + pref->type == df::unit_preference::T_type::LikeMaterial && + pref->mattype == 0 && getCreatedMetalBars(pref->matindex) > 0) + mats.push_back(pref->matindex); + } + } + if (mats.size()) + item->mat_index = mats[rand() & mats.size()]; + item->quantity = base_item_count; + } + break; + + case job_skill::CUTGEM: + case job_skill::ENCRUSTGEM: + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::ROUGH; + item->mat_type = 0; + item->quantity = base_item_count; + break; + + case job_skill::GLASSMAKER: + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::ROUGH; + found_pref = false; + if (soul) + { + for (size_t i = 0; i < soul->preferences.size(); i++) + { + df::unit_preference *pref = soul->preferences[i]; + if (pref->active == 1 && + pref->type == df::unit_preference::T_type::LikeMaterial && + ((pref->mattype == builtin_mats::GLASS_GREEN) || + (pref->mattype == builtin_mats::GLASS_CLEAR && have_glass[1]) || + (pref->mattype == builtin_mats::GLASS_CRYSTAL && have_glass[2]))) + { + item->mat_type = pref->mattype; + item->mat_index = pref->matindex; + found_pref = true; + } + } + } + if (!found_pref) + { + vector mats; + mats.push_back(builtin_mats::GLASS_GREEN); + if (have_glass[1]) + mats.push_back(builtin_mats::GLASS_CLEAR); + if (have_glass[2]) + mats.push_back(builtin_mats::GLASS_CRYSTAL); + item->mat_type = mats[rand() % mats.size()]; + } + item->quantity = base_item_count; + break; + + case job_skill::BONECARVE: + found_pref = false; + if (soul) + { + for (size_t i = 0; i < soul->preferences.size(); i++) + { + df::unit_preference *pref = soul->preferences[i]; + if (pref->active == 1 && + pref->type == df::unit_preference::T_type::LikeMaterial) + { + MaterialInfo mat(pref->mattype, pref->matindex); + if (mat.material->flags.is_set(material_flags::BONE)) + { + job->job_items.push_back(item = new df::job_item()); + item->flags2.bits.bone = true; + item->flags2.bits.body_part = true; + found_pref = true; + break; + } + else if (mat.material->flags.is_set(material_flags::SHELL)) + { + job->job_items.push_back(item = new df::job_item()); + item->flags2.bits.shell = true; + item->flags2.bits.body_part = true; + found_pref = true; + break; + } + } + } + } + if (!found_pref) + { + job->job_items.push_back(item = new df::job_item()); + item->flags2.bits.bone = true; + item->flags2.bits.body_part = true; + found_pref = true; + } + item->quantity = base_item_count; + break; + } + } + + // Choose additional mood materials + if (!(((unit->job.mood_skill == job_skill::CUTGEM || unit->job.mood_skill == job_skill::ENCRUSTGEM) && base_item_count == 1) || job->job_type == job_type::StrangeMoodFell)) + { + int extra_items = (ui->tasks.num_artifacts * 20 + moodable_units.size()) / 20; + if (extra_items > 0) + extra_items = std::min(rand() % (extra_items + 1), 7); + df::item_type avoid_type = item_type::NONE; + int avoid_glass = 0; + switch (unit->job.mood_skill) + { + case job_skill::MINING: + case job_skill::DETAILSTONE: + case job_skill::MASONRY: + case job_skill::STONECRAFT: + avoid_type = item_type::BLOCKS; + break; + case job_skill::CARPENTRY: + case job_skill::WOODCRAFT: + case job_skill::BOWYER: + avoid_type = item_type::WOOD; + break; + case job_skill::TANNER: + case job_skill::LEATHERWORK: + avoid_type = item_type::SKIN_TANNED; + break; + case job_skill::WEAVING: + case job_skill::CLOTHESMAKING: + avoid_type = item_type::CLOTH; + break; + case job_skill::FORGE_WEAPON: + case job_skill::FORGE_ARMOR: + case job_skill::FORGE_FURNITURE: + case job_skill::METALCRAFT: + avoid_type = item_type::BAR; + break; + case job_skill::CUTGEM: + case job_skill::ENCRUSTGEM: + avoid_type = item_type::SMALLGEM; + case job_skill::GLASSMAKER: + avoid_glass = 1; + break; + } + for (size_t i = 0; i < extra_items; i++) + { + if ((job->job_type == job_type::StrangeMoodBrooding) && (rand() % 2)) + { + switch (rand() % 3) + { + case 0: + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type::REMAINS; + item->flags1.bits.allow_buryable = true; + item->quantity = 1; + break; + case 1: + job->job_items.push_back(item = new df::job_item()); + item->flags1.bits.allow_buryable = true; + item->flags2.bits.bone = true; + item->flags2.bits.body_part = true; + item->quantity = 1; + break; + case 2: + // in older versions, they would request additional skulls + // in 0.34.11, the request becomes "nothing" + break; + } + } + else + { + df::item_type item_type; + int16_t mat_type; + df::job_item_flags2 flags2; + do + { + item_type = item_type::NONE; + mat_type = -1; + flags2.whole = 0; + switch (rand() % 10) + { + case 0: + item_type = item_type::WOOD; + mat_type = -1; + break; + case 1: + item_type = item_type::BAR; + mat_type = builtin_mats::INORGANIC; + break; + case 2: + item_type = item_type::SMALLGEM; + mat_type = -1; + break; + case 3: + item_type = item_type::BLOCKS; + mat_type = builtin_mats::INORGANIC; + break; + case 4: + item_type = item_type::ROUGH; + mat_type = builtin_mats::INORGANIC; + break; + case 5: + item_type = item_type::BOULDER; + mat_type = builtin_mats::INORGANIC; + break; + case 6: + flags2.bits.bone = true; + flags2.bits.body_part = true; + break; + case 7: + item_type = item_type::SKIN_TANNED; + mat_type = -1; + break; + case 8: + item_type = item_type::CLOTH; + mat_type = -1; + switch (rand() % 3) + { + case 0: + flags2.bits.plant = true; + break; + case 1: + flags2.bits.silk = true; + break; + case 2: + flags2.bits.yarn = true; + break; + } + break; + case 9: + item_type = item_type::ROUGH; + switch (rand() % 3) + { + case 0: + mat_type = builtin_mats::GLASS_GREEN; + break; + case 1: + mat_type = builtin_mats::GLASS_CLEAR; + break; + case 2: + mat_type = builtin_mats::GLASS_CRYSTAL; + break; + } + break; + } + item = job->job_items[0]; + if (item->item_type == item_type && item->mat_type == mat_type) + continue; + if (item_type == avoid_type) + continue; + if (avoid_glass && ((mat_type == builtin_mats::GLASS_GREEN) || (mat_type == builtin_mats::GLASS_CLEAR) || (mat_type == builtin_mats::GLASS_CRYSTAL))) + continue; + if ((mat_type == builtin_mats::GLASS_GREEN) && !have_glass[0]) + continue; + if ((mat_type == builtin_mats::GLASS_CLEAR) && !have_glass[1]) + continue; + if ((mat_type == builtin_mats::GLASS_CRYSTAL) && !have_glass[2]) + continue; + break; + } while (1); + job->job_items.push_back(item = new df::job_item()); + item->item_type = item_type; + item->mat_type = mat_type; + item->flags2.whole = flags2.whole; + item->quantity = 1; + } + } + } + + // Attach the Strange Mood job to the dwarf + unit->path.dest.x = -30000; + unit->path.dest.y = -30000; + unit->path.dest.z = -30000; + unit->path.unk_ae = -1; + unit->path.path.x.clear(); + unit->path.path.y.clear(); + unit->path.path.z.clear(); + job->flags.bits.special = true; + df::general_ref *ref = df::allocate(); + ref->setID(unit->id); + job->general_refs.push_back(ref); + unit->job.current_job = job; + job->unk10b_cntdn = 0; + + // Generate the artifact's name + if (type == mood_type::Fell || type == mood_type::Macabre) + generateName(unit->status.artifact_name, unit->name.language, 1, world->raws.language.word_table[0][2], world->raws.language.word_table[1][2]); + else + { + generateName(unit->status.artifact_name, unit->name.language, 1, world->raws.language.word_table[0][1], world->raws.language.word_table[1][1]); + if (!(rand() % 100)) + unit->status.artifact_name = unit->name; + } + unit->unk_18e = 0; + return CR_OK; +} + +DFHACK_PLUGIN("strangemood"); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand("strangemood", "Force a strange mood to happen.\n", df_strangemood, false, + "Options:\n" + " -force - Ignore standard mood preconditions.\n" + " -unit - Use the selected unit instead of picking one randomly.\n" + " -fey - Force the mood to be a fey mood.\n" + " -secretive - Force the mood to be a secretive mood.\n" + " -possessed - Force the mood to be a possession.\n" + " -fell - Force the mood to be a fell mood.\n" + " -macabre - Force the mood to be a macabre mood.\n" + )); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +}