dfhack/plugins/strangemood.cpp

1338 lines
49 KiB
C++

// 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 "modules/Random.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"
#include "df/creature_raw.h"
#include "df/caste_raw.h"
#include "df/caste_raw_flags.h"
using std::string;
using std::vector;
using namespace DFHack;
using namespace df::enums;
DFHACK_PLUGIN("strangemood");
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(d_init);
REQUIRE_GLOBAL(created_item_count);
REQUIRE_GLOBAL(created_item_type);
REQUIRE_GLOBAL(created_item_subtype);
REQUIRE_GLOBAL(created_item_mattype);
REQUIRE_GLOBAL(created_item_matindex);
using df::global::debug_nomoods;
Random::MersenneRNG rng;
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;
if (!Units::casteFlagSet(unit->race, unit->caste, caste_raw_flags::STRANGE_MOODS))
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<df::job_skill> 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->resources.permitted_skill[job_skill::WOODCRAFT])
skills.push_back(job_skill::WOODCRAFT);
if (civ->resources.permitted_skill[job_skill::STONECRAFT])
skills.push_back(job_skill::STONECRAFT);
if (civ->resources.permitted_skill[job_skill::BONECARVE])
skills.push_back(job_skill::BONECARVE);
}
if (!skills.size())
skills.push_back(job_skill::STONECRAFT);
return skills[rng.df_trandom(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::language_word_table &table, int32_t &word, df::enum_field<df::part_of_speech,int16_t> &part, int mode)
{
if (table.parts[mode].size())
{
int offset = rng.df_trandom(table.parts[mode].size());
word = table.words[mode][offset];
part = table.parts[mode][offset];
}
else
{
word = rng.df_trandom(world->raws.language.words.size());
part = (df::part_of_speech)(rng.df_trandom(9));
Core::getInstance().getConsole().printerr("Impoverished Word Selector");
}
}
void generateName(df::language_name &output, int language, int mode, const df::language_word_table &table1, const df::language_word_table &table2)
{
for (int i = 0; i < 100; i++)
{
if (mode != 8 && mode != 9)
{
output = df::language_name();
if (language == -1)
language = rng.df_trandom(world->raws.language.translations.size());
output.unknown = mode;
output.language = language;
}
output.has_name = 1;
if (output.language == -1)
output.language = rng.df_trandom(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<df::part_of_speech,int16_t> 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 (rng.df_trandom(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 = rng.df_trandom(3);
if (r == 0 || r == 1)
{
if (rng.df_trandom(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 = rng.df_trandom(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 = rng.df_trandom(3);
if (rng.df_trandom(50))
r3 = rng.df_trandom(2);
switch (r3)
{
case 0:
case 2:
if (r3 == 2)
r2 = rng.df_trandom(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 (!(rng.df_trandom(100)))
selectWord(table1, output.words[3], output.parts_of_speech[3], 3);
break;
}
}
if (rng.df_trandom(100))
{
if (rng.df_trandom(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 = rng.df_trandom(3);
if (rng.df_trandom(50))
r3 = rng.df_trandom(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 (!(rng.df_trandom(100)))
selectWord(table2, output.words[3], output.parts_of_speech[3], 3);
break;
}
if (rng.df_trandom(100))
selectWord(table2, output.words[4], output.parts_of_speech[4], 4);
break;
case 7:
r = rng.df_trandom(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 = rng.df_trandom(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 = rng.df_trandom(3);
if (rng.df_trandom(50))
r3 = rng.df_trandom(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 (!(rng.df_trandom(100)))
selectWord(table2, output.words[3], output.parts_of_speech[3], 3);
break;
}
}
if (rng.df_trandom(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 <string> & 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;
df::job_skill skill = job_skill::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] == "-type")
{
i++;
if (i == parameters.size())
{
out.printerr("No mood type specified!\n");
return CR_WRONG_USAGE;
}
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
{
out.printerr("Mood type '%s' not recognized!\n", parameters[i].c_str());
return CR_WRONG_USAGE;
}
}
else if (parameters[i] == "-skill")
{
i++;
if (i == parameters.size())
{
out.printerr("No mood skill specified!\n");
return CR_WRONG_USAGE;
}
else if (parameters[i] == "miner")
skill = job_skill::MINING;
else if (parameters[i] == "carpenter")
skill = job_skill::CARPENTRY;
else if (parameters[i] == "engraver")
skill = job_skill::DETAILSTONE;
else if (parameters[i] == "mason")
skill = job_skill::MASONRY;
else if (parameters[i] == "tanner")
skill = job_skill::TANNER;
else if (parameters[i] == "weaver")
skill = job_skill::WEAVING;
else if (parameters[i] == "clothier")
skill = job_skill::CLOTHESMAKING;
else if (parameters[i] == "weaponsmith")
skill = job_skill::FORGE_WEAPON;
else if (parameters[i] == "armorsmith")
skill = job_skill::FORGE_ARMOR;
else if (parameters[i] == "metalsmith")
skill = job_skill::FORGE_FURNITURE;
else if (parameters[i] == "gemcutter")
skill = job_skill::CUTGEM;
else if (parameters[i] == "gemsetter")
skill = job_skill::ENCRUSTGEM;
else if (parameters[i] == "woodcrafter")
skill = job_skill::WOODCRAFT;
else if (parameters[i] == "stonecrafter")
skill = job_skill::STONECRAFT;
else if (parameters[i] == "metalcrafter")
skill = job_skill::METALCRAFT;
else if (parameters[i] == "glassmaker")
skill = job_skill::GLASSMAKER;
else if (parameters[i] == "leatherworker")
skill = job_skill::LEATHERWORK;
else if (parameters[i] == "bonecarver")
skill = job_skill::BONECARVE;
else if (parameters[i] == "bowyer")
skill = job_skill::BOWYER;
else if (parameters[i] == "mechanic")
skill = job_skill::MECHANICS;
else
{
out.printerr("Mood skill '%s' not recognized!\n", parameters[i].c_str());
return CR_WRONG_USAGE;
}
}
else
{
out.printerr("Unrecognized parameter: %s\n", parameters[i].c_str());
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 (debug_nomoods && *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<df::unit *> 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.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<int32_t> 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;
tickets.push_back(i);
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[rng.df_trandom(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 (soul && (
(soul->personality.stress_level >= 500000) ||
(soul->personality.stress_level >= 250000 && !rng.df_trandom(2)) ||
(soul->personality.stress_level >= 100000 && !rng.df_trandom(10))
))
{
switch (rng.df_trandom(2))
{
case 0: type = mood_type::Fell; break;
case 1: type = mood_type::Macabre; break;
}
}
else
{
switch (rng.df_trandom(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);
// 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;
if (skill == job_skill::NONE)
skill = getMoodSkill(unit);
unit->job.mood_skill = skill;
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 (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:
case job_skill::PAPERMAKING: // These aren't actually moodable skills
case job_skill::BOOKBINDING: // but the game still checks for them anyways
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 + rng.df_trandom(3);
// Gem Cutters and Gem Setters have a 50% chance of using only one base item
if (((skill == job_skill::CUTGEM) || (skill == job_skill::ENCRUSTGEM)) && (rng.df_trandom(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 (rng.df_trandom(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 (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:
case job_skill::PAPERMAKING:
case job_skill::BOOKBINDING:
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 (rng.df_trandom(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:
// there are actually 2 distinct cases here, but they're identical
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 * 150; // BUGFIX - the game does not adjust here!
item->min_dimension = 150;
}
else
{
job->job_items.push_back(item = new df::job_item());
item->item_type = item_type::BAR;
item->mat_type = 0;
vector<int32_t> 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[rng.df_trandom(mats.size())];
item->quantity = base_item_count * 150; // BUGFIX - the game does not adjust here!
item->min_dimension = 150;
}
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<int32_t> 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[rng.df_trandom(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
// Gem cutters/setters using a single gem require nothing else, and fell moods need only their corpse
if (!(
(((skill == job_skill::CUTGEM) || (skill == job_skill::ENCRUSTGEM)) && base_item_count == 1) ||
(job->job_type == job_type::StrangeMoodFell)
))
{
int extra_items = std::min(rng.df_trandom((ui->tasks.num_artifacts * 20 + moodable_units.size()) / 20 + 1), 7);
df::item_type avoid_type = item_type::NONE;
int avoid_glass = 0;
switch (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) && (rng.df_trandom(2)))
{
switch (rng.df_trandom(2))
{
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;
}
}
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 (rng.df_trandom(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 (rng.df_trandom(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 (rng.df_trandom(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;
if (item_type == item_type::BAR)
{
item->quantity *= 150; // BUGFIX - the game does not adjust here!
item->min_dimension = 150;
}
if (item_type == item_type::CLOTH)
{
item->quantity *= 10000; // BUGFIX - the game does not adjust here!
item->min_dimension = 10000;
}
}
}
}
// 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.goal = unit_path_goal::None;
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<df::general_ref_unit_workerst>();
ref->setID(unit->id);
job->general_refs.push_back(ref);
unit->job.current_job = job;
job->wait_timer = 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 (!rng.df_trandom(100))
unit->status.artifact_name = unit->name;
}
unit->unk_18e = 0;
return CR_OK;
}
DFhackCExport command_result plugin_init (color_ostream &out, std::vector<PluginCommand> &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"
" -type <type> - Force the mood to be of a specific type.\n"
" Valid types: fey, secretive, possessed, fell, macabre\n"
" -skill <skill> - Force the mood to use a specific skill.\n"
" Skill name must be lowercase and without spaces.\n"
" Example: miner, gemcutter, metalcrafter, bonecarver, mason\n"
));
rng.init();
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}