From 6546ed2d5a7d92c5eed5b65a9301fe51c5d542cc Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 30 Oct 2013 14:31:33 +0100 Subject: [PATCH 01/89] scripts/digfort: better csv handling --- scripts/digfort.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/digfort.rb b/scripts/digfort.rb index 5def15c47..cf3625357 100644 --- a/scripts/digfort.rb +++ b/scripts/digfort.rb @@ -8,7 +8,7 @@ if df.cursor.x == -30000 end tiles = planfile.lines.map { |l| - l.sub(/#.*/, '').split(';').map { |t| t.strip } + l.sub(/#.*/, '').split(/[;,]/).map { |t| t = t.strip ; ((t[0] == ?") ? t[1..-2] : t) } } x = x0 = df.cursor.x From 9dc9a3f33a1b109f0544d8c05142166d76aaed23 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 30 Oct 2013 15:25:16 +0100 Subject: [PATCH 02/89] digfort: add start() comment --- NEWS | 3 +++ Readme.rst | 7 ++++++- scripts/digfort.rb | 42 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 2a7508f5f..e52e76998 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ DFHack future - Is not yet known. + Misc improvements: + - digfort: improved csv parsing, add start() comment handling + DFHack v0.34.11-r4 New commands: diff --git a/Readme.rst b/Readme.rst index a2b400206..fe7fb741e 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2171,7 +2171,12 @@ Unrecognized characters are ignored (eg the 'skip this tile' in the sample). Empty lines and data after a ``#`` are ignored as comments. To skip a row in your design, use a single ``;``. -The script takes the plan filename, starting from the root df folder. +One comment in the file may contain the phrase ``start(3,5)``. It is interpreted +as an offset for the pattern: instead of starting at the cursor, it will start +3 tiles left and 5 tiles up from the cursor. + +The script takes the plan filename, starting from the root df folder (where +Dwarf Fortress.exe is found). invasion-now ============ diff --git a/scripts/digfort.rb b/scripts/digfort.rb index cf3625357..74db423d3 100644 --- a/scripts/digfort.rb +++ b/scripts/digfort.rb @@ -4,15 +4,47 @@ raise "usage: digfort " if not $script_args[0] planfile = File.read($script_args[0]) if df.cursor.x == -30000 - raise "place the game cursor to the top-left corner of the design" + puts "place the game cursor to the top-left corner of the design" + throw :script_finished end -tiles = planfile.lines.map { |l| - l.sub(/#.*/, '').split(/[;,]/).map { |t| t = t.strip ; ((t[0] == ?") ? t[1..-2] : t) } +# a sample CSV file +# empty lines are ignored +# a special comment with start(dx, dy) means the actual patterns starts at cursor.x-dx, cursor.y-dy +# the CSV file should be saved in the main DF directory, alongside of Dwarf Fortress.exe +sample_csv = < Date: Wed, 30 Oct 2013 14:19:52 -0500 Subject: [PATCH 03/89] Update "plants" plugin * Add "createplant" command, creates a shrub or sapling at the cursor. * Put help text in command definitions so CR_WRONG_USAGE works properly --- plugins/plants.cpp | 165 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 127 insertions(+), 38 deletions(-) diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 89a3257fa..9b845edc6 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -22,25 +22,8 @@ using df::global::world; const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years -command_result df_grow (color_ostream &out, vector & parameters); -command_result df_immolate (color_ostream &out, vector & parameters); -command_result df_extirpate (color_ostream &out, vector & parameters); - DFHACK_PLUGIN("plants"); -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand("grow", "Grows saplings into trees (with active cursor, only the targetted one).", df_grow)); - commands.push_back(PluginCommand("immolate", "Set plants on fire (under cursor, 'shrubs', 'trees' or 'all').", df_immolate)); - commands.push_back(PluginCommand("extirpate", "Kill plants (same mechanics as immolate).", df_extirpate)); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - enum do_what { do_immolate, @@ -84,19 +67,8 @@ static bool getoptions( vector & parameters, bool & shrubs, bool & tree */ static command_result immolations (color_ostream &out, do_what what, bool shrubs, bool trees, bool help) { - static const char * what1 = "destroys"; - static const char * what2 = "burns"; if(help) - { - out.print("Without any options, this command %s a plant under the cursor.\n" - "Options:\n" - "shrubs - affect all shrubs\n" - "trees - affect all trees\n" - "all - affect all plants\n", - what == do_immolate? what2 : what1 - ); - return CR_OK; - } + return CR_WRONG_USAGE; CoreSuspender suspend; if (!Maps::IsValid()) { @@ -165,26 +137,26 @@ command_result df_immolate (color_ostream &out, vector & parameters) bool shrubs = false, trees = false, help = false; if(getoptions(parameters,shrubs,trees,help)) { - return immolations(out,do_immolate,shrubs,trees, help); + return immolations(out,do_immolate,shrubs,trees,help); } else { out.printerr("Invalid parameter!\n"); - return CR_FAILURE; + return CR_WRONG_USAGE; } } command_result df_extirpate (color_ostream &out, vector & parameters) { bool shrubs = false, trees = false, help = false; - if(getoptions(parameters,shrubs,trees, help)) + if(getoptions(parameters,shrubs,trees,help)) { - return immolations(out,do_extirpate,shrubs,trees, help); + return immolations(out,do_extirpate,shrubs,trees,help); } else { out.printerr("Invalid parameter!\n"); - return CR_FAILURE; + return CR_WRONG_USAGE; } } @@ -193,10 +165,7 @@ command_result df_grow (color_ostream &out, vector & parameters) for(size_t i = 0; i < parameters.size();i++) { if(parameters[i] == "help" || parameters[i] == "?") - { - out.print("This command turns all living saplings into full-grown trees.\n"); - return CR_OK; - } + return CR_WRONG_USAGE; } CoreSuspender suspend; @@ -245,3 +214,123 @@ command_result df_grow (color_ostream &out, vector & parameters) return CR_OK; } +command_result df_createplant (color_ostream &out, vector & parameters) +{ + if ((parameters.size() != 1) || (parameters[0] == "help" || parameters[0] == "?")) + return CR_WRONG_USAGE; + + CoreSuspender suspend; + + if (!Maps::IsValid()) + { + out.printerr("Map is not available!\n"); + return CR_FAILURE; + } + + int32_t x,y,z; + if(!Gui::getCursorCoords(x,y,z)) + { + out.printerr("No cursor detected - please place the cursor over the location in which you wish to create a new plant.\n"); + return CR_FAILURE; + } + df::map_block *map = Maps::getTileBlock(x, y, z); + if (!map) + { + out.printerr("Invalid location selected!\n"); + return CR_FAILURE; + } + int tx = x & 15, ty = y & 15; + int mat = tileMaterial(map->tiletype[tx][ty]); + if ((tileShape(map->tiletype[tx][ty]) != tiletype_shape::FLOOR) || ((mat != tiletype_material::SOIL) && (mat != tiletype_material::GRASS_DARK) && (mat != tiletype_material::GRASS_LIGHT))) + { + out.printerr("Plants can only be placed on dirt or grass floors!\n"); + return CR_FAILURE; + } + + int plant_id = -1; + df::plant_raw *plant_raw = NULL; + for (size_t i = 0; i < world->raws.plants.all.size(); i++) + { + plant_raw = world->raws.plants.all[i]; + if (plant_raw->id == parameters[0]) + { + plant_id = i; + break; + } + } + if (plant_id == -1) + { + out.printerr("Invalid plant ID specified!\n"); + return CR_FAILURE; + } + if (plant_raw->flags.is_set(plant_raw_flags::GRASS)) + { + out.printerr("You cannot plant grass using this command.\n"); + return CR_FAILURE; + } + + df::plant *plant = new df::plant; + if (plant_raw->flags.is_set(plant_raw_flags::TREE)) + plant->hitpoints = 400000; + else + { + plant->hitpoints = 100000; + plant->flags.bits.is_shrub = 1; + } + // for now, always set "watery" for WET-permitted plants, even if they're spawned away from water + // the proper method would be to actually look for nearby water features, but it's not clear exactly how that works + if (plant_raw->flags.is_set(plant_raw_flags::WET)) + plant->flags.bits.watery = 1; + plant->material = plant_id; + plant->pos.x = x; + plant->pos.y = y; + plant->pos.z = z; + plant->update_order = rand() % 10; + plant->temperature_tile_tick = -1; + plant->temperature_tile = 60001; + plant->min_safe_temp = 9900; + plant->max_safe_temp = 60001; + + world->plants.all.push_back(plant); + switch (plant->flags.whole & 3) + { + case 0: world->plants.tree_dry.push_back(plant); break; + case 1: world->plants.tree_wet.push_back(plant); break; + case 2: world->plants.shrub_dry.push_back(plant); break; + case 3: world->plants.shrub_wet.push_back(plant); break; + } + map->plants.push_back(plant); + if (plant->flags.bits.is_shrub) + map->tiletype[tx][ty] = tiletype::Shrub; + else + map->tiletype[tx][ty] = tiletype::Sapling; + + return CR_OK; +} + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand("grow", "Grows saplings into trees (with active cursor, only the targetted one).", df_grow, + "This command turns all living saplings on the map into full-grown trees.\n")); + commands.push_back(PluginCommand("immolate", "Set plants on fire (under cursor, 'shrubs', 'trees' or 'all').", df_immolate, + "Without any options, this command burns a plant under the cursor.\n" + "Options:\n" + "shrubs - affect all shrubs\n" + "trees - affect all trees\n" + "all - affect all plants\n")); + commands.push_back(PluginCommand("extirpate", "Kill plants (same mechanics as immolate).", df_extirpate, + "Without any options, this command destroys a plant under the cursor.\n" + "Options:\n" + "shrubs - affect all shrubs\n" + "trees - affect all trees\n" + "all - affect all plants\n")); + commands.push_back(PluginCommand("createplant", "Create a new plant at the cursor.", df_createplant, + "Specify the type of plant to create by its raw ID (e.g. TOWER_CAP or MUSHROOM_HELMET_PLUMP).\n" + "Only shrubs and saplings can be placed, and they must be located on a dirt or grass floor.\n")); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} \ No newline at end of file From ff939e36bbb8e46d6a1f7405908bbd0ae00754d3 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 30 Oct 2013 14:25:35 -0500 Subject: [PATCH 04/89] Missed parameter in plugin command init --- plugins/plants.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 9b845edc6..9be4c193a 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -310,21 +310,21 @@ command_result df_createplant (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("grow", "Grows saplings into trees (with active cursor, only the targetted one).", df_grow, + commands.push_back(PluginCommand("grow", "Grows saplings into trees (with active cursor, only the targetted one).", df_grow, false, "This command turns all living saplings on the map into full-grown trees.\n")); - commands.push_back(PluginCommand("immolate", "Set plants on fire (under cursor, 'shrubs', 'trees' or 'all').", df_immolate, + commands.push_back(PluginCommand("immolate", "Set plants on fire (under cursor, 'shrubs', 'trees' or 'all').", df_immolate, false, "Without any options, this command burns a plant under the cursor.\n" "Options:\n" "shrubs - affect all shrubs\n" "trees - affect all trees\n" "all - affect all plants\n")); - commands.push_back(PluginCommand("extirpate", "Kill plants (same mechanics as immolate).", df_extirpate, + commands.push_back(PluginCommand("extirpate", "Kill plants (same mechanics as immolate).", df_extirpate, false, "Without any options, this command destroys a plant under the cursor.\n" "Options:\n" "shrubs - affect all shrubs\n" "trees - affect all trees\n" "all - affect all plants\n")); - commands.push_back(PluginCommand("createplant", "Create a new plant at the cursor.", df_createplant, + commands.push_back(PluginCommand("createplant", "Create a new plant at the cursor.", df_createplant, false, "Specify the type of plant to create by its raw ID (e.g. TOWER_CAP or MUSHROOM_HELMET_PLUMP).\n" "Only shrubs and saplings can be placed, and they must be located on a dirt or grass floor.\n")); return CR_OK; From 463bb8d498259ad0c0ec5d81378892f2c9b96779 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 30 Oct 2013 15:58:14 -0500 Subject: [PATCH 05/89] Update plugins to use CR_WRONG_USAGE where appropriate --- plugins/autotrade.cpp | 2 +- plugins/buildingplan.cpp | 2 +- plugins/createitem.cpp | 19 +++++++--------- plugins/lair.cpp | 3 ++- plugins/liquids.cpp | 25 ++++++++------------ plugins/probe.cpp | 12 +++++++--- plugins/regrass.cpp | 3 ++- plugins/reveal.cpp | 49 +++++++++++++++++----------------------- plugins/seedwatch.cpp | 11 ++++++--- plugins/showmood.cpp | 3 ++- plugins/stocks.cpp | 2 +- plugins/tubefill.cpp | 11 ++++----- 12 files changed, 68 insertions(+), 74 deletions(-) diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index 2d3cf0ed2..0d11af9f0 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -626,7 +626,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("createitem", "Create arbitrary item at the selected unit's feet.", df_createitem)); + commands.push_back(PluginCommand("createitem", "Create arbitrary item at the selected unit's feet.", df_createitem, false, + "Syntax: createitem [count]\n" + " - Item token for what you wish to create, as specified in custom\n" + " reactions. If the item has no subtype, omit the :NONE.\n" + " - The material you want the item to be made of, as specified\n" + " in custom reactions. For REMAINS, FISH, FISH_RAW, VERMIN,\n" + " PET, and EGG, replace this with a creature ID and caste.\n" + " [count] - How many of the item you wish to create.\n")); return CR_OK; } @@ -91,17 +98,7 @@ command_result df_createitem (color_ostream &out, vector & parameters) int count = 1; if ((parameters.size() < 2) || (parameters.size() > 3)) - { - out.print("Syntax: createitem [count]\n" - " - Item token for what you wish to create, as specified in custom\n" - " reactions. If the item has no subtype, omit the :NONE.\n" - " - The material you want the item to be made of, as specified\n" - " in custom reactions. For REMAINS, FISH, FISH_RAW, VERMIN,\n" - " PET, and EGG, replace this with a creature ID and caste.\n" - " [count] - How many of the item you wish to create.\n" - ); return CR_WRONG_USAGE; - } item_str = parameters[0]; material_str = parameters[1]; diff --git a/plugins/lair.cpp b/plugins/lair.cpp index 0c2912761..cafbbcc33 100644 --- a/plugins/lair.cpp +++ b/plugins/lair.cpp @@ -58,6 +58,7 @@ command_result lair(color_ostream &out, std::vector & params) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("lair","Mark the map as a monster lair, preventing item scatter ('lair reset' reverts that).",lair)); + commands.push_back(PluginCommand("lair","Mark the map as a monster lair, preventing item scatter.",lair, false, + "Usage: 'lair' to mark entire map as monster lair, 'lair reset' to undo the operation.\n")); return CR_OK; } diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 15ae84c9b..712955b86 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -59,12 +59,16 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters) for(size_t i = 0; i < parameters.size();i++) { if(parameters[i] == "help" || parameters[i] == "?") - { - out.print( "This tool allows placing magma, water and other similar things.\n" - "It is interactive and further help is available when you run it.\n" - "The settings will be remembered until dfhack is closed and you can call\n" - "'liquids-here' (mapped to a hotkey) to paint liquids at the cursor position\n" - "without the need to go back to the dfhack console.\n"); - return CR_OK; - } + return CR_WRONG_USAGE; } if (!Maps::IsValid()) @@ -375,11 +372,7 @@ command_result df_liquids_here (color_ostream &out, vector & parameters for(size_t i = 0; i < parameters.size();i++) { if(parameters[i] == "help" || parameters[i] == "?") - { - out << "This command is supposed to be mapped to a hotkey." << endl; - out << "It will use the current/last parameters set in liquids." << endl; - return CR_OK; - } + return CR_WRONG_USAGE; } out.print("Run liquids-here with these parameters: "); diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 353caea0f..28b62c24e 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -46,13 +46,19 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("regrass", "Regrows surface grass.", df_regrass)); + commands.push_back(PluginCommand("regrass", "Regrows surface grass.", df_regrass, false, + "Specify parameter 'max' to set all grass types to full density, otherwise only one type of grass will be restored per tile.\n")); return CR_OK; } diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 2c51295a8..91ab75686 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -76,12 +76,23 @@ DFHACK_PLUGIN("reveal"); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - commands.push_back(PluginCommand("reveal","Reveal the map. 'reveal hell' will also reveal hell. 'reveal demon' won't pause.",reveal)); - commands.push_back(PluginCommand("unreveal","Revert the map to its previous state.",unreveal)); - commands.push_back(PluginCommand("revtoggle","Reveal/unreveal depending on state.",revtoggle)); - commands.push_back(PluginCommand("revflood","Hide all, reveal all tiles reachable from cursor position.",revflood)); - commands.push_back(PluginCommand("revforget", "Forget the current reveal data, allowing to use reveal again.",revforget)); - commands.push_back(PluginCommand("nopause","Disable pausing (doesn't affect pause forced by reveal).",nopause)); + commands.push_back(PluginCommand("reveal","Reveal the map. 'reveal hell' will also reveal hell. 'reveal demon' won't pause.",reveal,false, + "Reveals the map, by default ignoring hell.\n" + "Options:\n" + "hell - also reveal hell, while forcing the game to pause.\n" + "demon - reveal hell, do not pause.\n")); + commands.push_back(PluginCommand("unreveal","Revert the map to its previous state.",unreveal,false, + "Reverts the previous reveal operation, hiding the map again.\n")); + commands.push_back(PluginCommand("revtoggle","Reveal/unreveal depending on state.",revtoggle,false, + "Toggles between reveal and unreveal.\n")); + commands.push_back(PluginCommand("revflood","Hide all, reveal all tiles reachable from cursor position.",revflood,false, + "This command hides the whole map. Then, starting from the cursor,\n" + "reveals all accessible tiles. Allows repairing parma-revealed maps.\n")); + commands.push_back(PluginCommand("revforget", "Forget the current reveal data, allowing to use reveal again.",revforget,false, + "Forget the current reveal data, allowing to use reveal again.\n")); + commands.push_back(PluginCommand("nopause","Disable pausing (doesn't affect pause forced by reveal).",nopause,false, + "Disable pausing (doesn't affect pause forced by reveal).\n" + "Activate with 'nopause 1', deactivate with 'nopause 0'.\n")); return CR_OK; } @@ -160,14 +171,7 @@ command_result reveal(color_ostream &out, vector & params) if(params[i]=="hell") no_hell = false; else if(params[i] == "help" || params[i] == "?") - { - out.print("Reveals the map, by default ignoring hell.\n" - "Options:\n" - "hell - also reveal hell, while forcing the game to pause.\n" - "demon - reveal hell, do not pause.\n" - ); - return CR_OK; - } + return CR_WRONG_USAGE; } if(params.size() && params[0] == "hell") { @@ -254,10 +258,7 @@ command_result unreveal(color_ostream &out, vector & params) for(size_t i = 0; i < params.size();i++) { if(params[i] == "help" || params[i] == "?") - { - out.print("Reverts the previous reveal operation, hiding the map again.\n"); - return CR_OK; - } + return CR_WRONG_USAGE; } if(!revealed) { @@ -330,12 +331,7 @@ command_result revflood(color_ostream &out, vector & params) for(size_t i = 0; i < params.size();i++) { if(params[i] == "help" || params[i] == "?") - { - out.print("This command hides the whole map. Then, starting from the cursor,\n" - "reveals all accessible tiles. Allows repairing parma-revealed maps.\n" - ); - return CR_OK; - } + return CR_WRONG_USAGE; } CoreSuspender suspend; uint32_t x_max,y_max,z_max; @@ -482,10 +478,7 @@ command_result revforget(color_ostream &out, vector & params) for(size_t i = 0; i < params.size();i++) { if(params[i] == "help" || params[i] == "?") - { - out.print("Forget the current reveal data, allowing to use reveal again.\n"); - return CR_OK; - } + return CR_WRONG_USAGE; } if(!revealed) { diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 19fa9b154..e0c2daeae 100755 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -130,11 +130,15 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) { case 0: printHelp(out); - break; + return CR_WRONG_USAGE; + case 1: par = parameters[0]; - if(par == "help") printHelp(out); - else if(par == "?") printHelp(out); + if ((par == "help") || (par == "?")) + { + printHelp(out); + return CR_WRONG_USAGE; + } else if(par == "start") { running = true; @@ -239,6 +243,7 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) break; default: printHelp(out); + return CR_WRONG_USAGE; break; } diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index 7a3662f82..ba6df034a 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -292,7 +292,8 @@ DFHACK_PLUGIN("showmood"); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("showmood", "Shows items needed for current strange mood.", df_showmood)); + commands.push_back(PluginCommand("showmood", "Shows items needed for current strange mood.", df_showmood, false, + "Run this command without any parameters to display information on the currently active Strange Mood.")); return CR_OK; } diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 10594fe9e..7873595ff 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -1002,7 +1002,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("tubefill","Fill in all the adamantine tubes again.",tubefill)); + commands.push_back(PluginCommand("tubefill","Fill in all the adamantine tubes again.",tubefill, false, + "Replenishes mined out adamantine and hollow adamantine tubes.\n" + "May cause !!FUN!!\n")); return CR_OK; } @@ -39,12 +41,7 @@ command_result tubefill(color_ostream &out, std::vector & params) for(size_t i = 0; i < params.size();i++) { if(params[i] == "help" || params[i] == "?") - { - out.print("Replenishes mined out adamantine and hollow adamantine tubes.\n" - "May cause !!FUN!!\n" - ); - return CR_OK; - } + return CR_WRONG_USAGE; } CoreSuspender suspend; From 883d89bb68d458877645669be6a155e4e45cc447 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 2 Nov 2013 18:54:29 +0100 Subject: [PATCH 06/89] plants: move all commands as "plant" subcommands, update NEWS/Readme --- NEWS | 4 +- Readme.rst | 32 ++++++++------ plugins/plants.cpp | 108 +++++++++++++++++++++++++++------------------ 3 files changed, 87 insertions(+), 57 deletions(-) diff --git a/NEWS b/NEWS index 2a7508f5f..d3745b8e7 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ DFHack future - - Is not yet known. + New commands: + - move the 'grow', 'extirpate' and 'immolate' commands as 'plant' subcommands + - 'plant create' - spawn a new shrub under the cursor DFHack v0.34.11-r4 diff --git a/Readme.rst b/Readme.rst index a2b400206..4a425c775 100644 --- a/Readme.rst +++ b/Readme.rst @@ -679,26 +679,30 @@ tubefill Fills all the adamantine veins again. Veins that were empty will be filled in too, but might still trigger a demon invasion (this is a known bug). -extirpate ---------- -A tool for getting rid of trees and shrubs. By default, it only kills -a tree/shrub under the cursor. The plants are turned into ashes instantly. +plant +----- +A tool for creating shrubs, growing, or getting rid of them. -Options: +Subcommands: + :create: Create a new shrub/sapling. + :grow: Make saplings grow into trees. + :extirpate: Kills trees and shrubs, turning them into ashes instantly. + :immolate: Similar to extirpate, but sets the plants on fire instead. The +fires can and *will* spread ;) + +``create`` creates a new sapling under the cursor. Takes a raw ID as +argument (e.g. TOWER_CAP). The cursor must be located on a dirt or grass +floor tile. + +``grow`` works on the sapling under the cursor, and turns it into a tree. +Works on all shrubs of the map if the cursor is hidden. +``extirpate`` and ``immolate`` work only on the plant under the cursor. +For mass effects, use one of the additional options: :shrubs: affect all shrubs on the map :trees: affect all trees on the map :all: affect every plant! -grow ----- -Makes all saplings present on the map grow into trees (almost) instantly. - -immolate --------- -Very similar to extirpate, but additionally sets the plants on fire. The fires -can and *will* spread ;) - regrass ------- Regrows grass. Not much to it ;) diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 9be4c193a..46bafad5f 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -65,10 +65,8 @@ static bool getoptions( vector & parameters, bool & shrubs, bool & tree * And he cursed the plants and trees for their bloodless wood, turning them into ash and smoldering ruin. * Armok was pleased and great temples were built by the dwarves, for they shared his hatred for trees and plants. */ -static command_result immolations (color_ostream &out, do_what what, bool shrubs, bool trees, bool help) +static command_result immolations (color_ostream &out, do_what what, bool shrubs, bool trees) { - if(help) - return CR_WRONG_USAGE; CoreSuspender suspend; if (!Maps::IsValid()) { @@ -132,32 +130,32 @@ static command_result immolations (color_ostream &out, do_what what, bool shrubs return CR_OK; } -command_result df_immolate (color_ostream &out, vector & parameters) +command_result df_immolate (color_ostream &out, vector & parameters, do_what what) { bool shrubs = false, trees = false, help = false; - if(getoptions(parameters,shrubs,trees,help)) + if (getoptions(parameters, shrubs, trees, help) && !help) { - return immolations(out,do_immolate,shrubs,trees,help); + return immolations(out, what, shrubs, trees); } - else - { - out.printerr("Invalid parameter!\n"); - return CR_WRONG_USAGE; - } -} -command_result df_extirpate (color_ostream &out, vector & parameters) -{ - bool shrubs = false, trees = false, help = false; - if(getoptions(parameters,shrubs,trees,help)) - { - return immolations(out,do_extirpate,shrubs,trees,help); - } + string mode; + if (what == do_immolate) + mode = "Set plants on fire"; else - { + mode = "Kill plants"; + + if (!help) out.printerr("Invalid parameter!\n"); - return CR_WRONG_USAGE; - } + + out << "Usage:\n" << + mode << " (under cursor, 'shrubs', 'trees' or 'all').\n" + "Without any options, this command acts on the plant under the cursor.\n" + "Options:\n" + "shrubs - affect all shrubs\n" + "trees - affect all trees\n" + "all - affect all plants\n"; + + return CR_OK; } command_result df_grow (color_ostream &out, vector & parameters) @@ -165,8 +163,14 @@ command_result df_grow (color_ostream &out, vector & parameters) for(size_t i = 0; i < parameters.size();i++) { if(parameters[i] == "help" || parameters[i] == "?") - return CR_WRONG_USAGE; + { + out << "Usage:\n" + "This command turns all living saplings on the map into full-grown trees.\n" + "With active cursor, work on the targetted one only.\n"; + return CR_OK; + } } + CoreSuspender suspend; if (!Maps::IsValid()) @@ -217,7 +221,13 @@ command_result df_grow (color_ostream &out, vector & parameters) command_result df_createplant (color_ostream &out, vector & parameters) { if ((parameters.size() != 1) || (parameters[0] == "help" || parameters[0] == "?")) - return CR_WRONG_USAGE; + { + out << "Usage:\n" + "Create a new plant at the cursor.\n" + "Specify the type of plant to create by its raw ID (e.g. TOWER_CAP or MUSHROOM_HELMET_PLUMP).\n" + "Only shrubs and saplings can be placed, and they must be located on a dirt or grass floor.\n"; + return CR_OK; + } CoreSuspender suspend; @@ -308,29 +318,43 @@ command_result df_createplant (color_ostream &out, vector & parameters) return CR_OK; } +command_result df_plant (color_ostream &out, vector & parameters) +{ + if (parameters.size() >= 1) + { + if (parameters[0] == "grow") { + parameters.erase(parameters.begin()); + return df_grow(out, parameters); + } else + if (parameters[0] == "immolate") { + parameters.erase(parameters.begin()); + return df_immolate(out, parameters, do_immolate); + } else + if (parameters[0] == "extirpate") { + parameters.erase(parameters.begin()); + return df_immolate(out, parameters, do_extirpate); + } else + if (parameters[0] == "create") { + parameters.erase(parameters.begin()); + return df_createplant(out, parameters); + } + } + return CR_WRONG_USAGE; +} + DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("grow", "Grows saplings into trees (with active cursor, only the targetted one).", df_grow, false, - "This command turns all living saplings on the map into full-grown trees.\n")); - commands.push_back(PluginCommand("immolate", "Set plants on fire (under cursor, 'shrubs', 'trees' or 'all').", df_immolate, false, - "Without any options, this command burns a plant under the cursor.\n" - "Options:\n" - "shrubs - affect all shrubs\n" - "trees - affect all trees\n" - "all - affect all plants\n")); - commands.push_back(PluginCommand("extirpate", "Kill plants (same mechanics as immolate).", df_extirpate, false, - "Without any options, this command destroys a plant under the cursor.\n" - "Options:\n" - "shrubs - affect all shrubs\n" - "trees - affect all trees\n" - "all - affect all plants\n")); - commands.push_back(PluginCommand("createplant", "Create a new plant at the cursor.", df_createplant, false, - "Specify the type of plant to create by its raw ID (e.g. TOWER_CAP or MUSHROOM_HELMET_PLUMP).\n" - "Only shrubs and saplings can be placed, and they must be located on a dirt or grass floor.\n")); + commands.push_back(PluginCommand("plant", "Plant creation and removal.", df_plant, false, + "Command to create, grow or remove plants on the map. For more details, check the subcommand help :\n" + "plant grow help - Grows saplings into trees.\n" + "plant immolate help - Set plants on fire.\n" + "plant extirpate help - Kill plants.\n" + "plant create help - Create a new plant.\n")); + return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; -} \ No newline at end of file +} From 4d2e5b80bf4c48403fb5491929d45e46652aeca1 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 4 Nov 2013 14:55:31 -0600 Subject: [PATCH 07/89] Use df::allocate here for proper compatibility with 40d and earlier --- plugins/plants.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 9be4c193a..c0cd427a9 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -269,7 +269,7 @@ command_result df_createplant (color_ostream &out, vector & parameters) return CR_FAILURE; } - df::plant *plant = new df::plant; + df::plant *plant = df::allocate(); if (plant_raw->flags.is_set(plant_raw_flags::TREE)) plant->hitpoints = 400000; else From 04dce1aa7f8c162eed8c2d98271f3d0f15b4a7e5 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 5 Nov 2013 00:31:32 +0100 Subject: [PATCH 08/89] MaterialInfo: fix decoding for COAL subtypes --- library/modules/Materials.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 5bda6a9bc..60a35fa2b 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -190,6 +190,13 @@ bool MaterialInfo::find(const std::vector &items) } else if (items.size() == 2) { + if (items[0] == "COAL" && findBuiltin(items[0])) { + if (items[1] == "COKE") + this->index = 0; + else if (items[1] == "CHARCOAL") + this->index = 1; + return true; + } if (items[1] == "NONE" && findBuiltin(items[0])) return true; if (findPlant(items[0], items[1])) From b2819ea8693c479f6d578579a98579e73ef47aec Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 7 Nov 2013 11:40:26 +0400 Subject: [PATCH 09/89] Fix wrong argument iteration bounds in dfhack.matinfo.find(). --- library/LuaApi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 3f93e912d..bdc0c39e5 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -565,7 +565,7 @@ static int dfhack_matinfo_find(lua_State *state) { std::vector tokens; - for (int i = 1; i < argc; i++) + for (int i = 1; i <= argc; i++) tokens.push_back(luaL_checkstring(state, i)); info.find(tokens); From 7ce5831257c241eee4c3e87873f6e9aa37a271a6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 7 Nov 2013 11:58:11 +0400 Subject: [PATCH 10/89] Get rid of the std exception. --- plugins/eventful.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index f93c15edc..97527c0ed 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -1,4 +1,5 @@ #include "Core.h" +#include "Error.h" #include #include #include @@ -228,8 +229,8 @@ static void enableEvent(int evType,int freq) { if (freq < 0) return; - if (evType < 0 || evType >= EventManager::EventType::EVENT_MAX || evType == EventManager::EventType::TICK) - throw std::runtime_error("invalid event type to enable"); + CHECK_INVALID_ARGUMENT(evType >= 0 && evType < EventManager::EventType::EVENT_MAX && + evType != EventManager::EventType::TICK); EventManager::EventHandler::callback_t fun_ptr = eventHandlers[evType]; EventManager::EventType::EventType typeToEnable=static_cast(evType); From 53bd1125153f5625249aebca85f37632a3688da7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 7 Nov 2013 12:27:53 +0400 Subject: [PATCH 11/89] Hide fake historical figures from legends xml export. --- library/modules/World.cpp | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 9ae4266b2..0f54e68eb 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -42,11 +42,14 @@ using namespace std; #include "MiscUtils.h" +#include "VTableInterpose.h" + #include "DataDefs.h" #include "df/world.h" #include "df/historical_figure.h" #include "df/map_block.h" #include "df/block_square_event_world_constructionst.h" +#include "df/viewscreen_legendsst.h" using namespace DFHack; using namespace df::enums; @@ -154,14 +157,51 @@ static PersistentDataItem dataFromHFig(df::historical_figure *hfig) return PersistentDataItem(hfig->id, hfig->name.first_name, &hfig->name.nickname, hfig->name.words); } +// Hide fake histfigs from legends xml export +static bool in_export_xml = false; + +struct hide_fake_histfigs_hook : df::viewscreen_legendsst { + typedef df::viewscreen_legendsst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (input->count(interface_key::LEGENDS_EXPORT_XML)) + { + auto &figs = df::historical_figure::get_vector(); + + auto it = figs.begin(); + while (it != figs.end() && (*it)->id <= -100) + ++it; + + // Move our histfigs to a temporary vector + std::vector fakes(figs.begin(), it); + figs.erase(figs.begin(), it); + in_export_xml = true; + + INTERPOSE_NEXT(feed)(input); + + in_export_xml = false; + figs.insert(figs.begin(), fakes.begin(), fakes.end()); + } + else + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(hide_fake_histfigs_hook, feed, -10000); + void World::ClearPersistentCache() { next_persistent_id = 0; persistent_index.clear(); + + INTERPOSE_HOOK(hide_fake_histfigs_hook, feed).apply(Core::getInstance().isWorldLoaded()); } static bool BuildPersistentCache() { + if (in_export_xml) + return false; if (next_persistent_id) return true; if (!Core::getInstance().isWorldLoaded()) From b872cfc40fd9393c716dbd1873ded7d6c22c2479 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Wed, 27 Nov 2013 10:11:19 -0500 Subject: [PATCH 12/89] Update Compatibility section OS X has been supported since 0.34.11r3 --- Readme.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Readme.rst b/Readme.rst index a2b400206..e1365cbf1 100644 --- a/Readme.rst +++ b/Readme.rst @@ -28,15 +28,15 @@ All new releases are announced in the bay12 thread: http://tinyurl.com/dfhack-ng ============= Compatibility ============= -DFHack works on Windows XP, Vista, 7 or any modern Linux distribution. -OSX is not supported due to lack of developers with a Mac. +DFHack works on Windows XP, Vista, 7, any modern Linux distribution, or OS X +10.6.8-10.9. Currently, version 0.34.11 is supported (and tested). If you need DFHack for older versions, look for older releases. On Windows, you have to use the SDL version of DF. -It is possible to use the Windows DFHack under wine/OSX. +It is also possible to use the Windows DFHack with Wine under Linux and OS X. ==================== Installation/Removal From 6e6d830ba6ed61fe3999709dfa52d2fd5269181f Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 24 Dec 2013 14:08:39 +0100 Subject: [PATCH 13/89] compile.rst: add a note on VS2010 SP1 --- Compile.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Compile.rst b/Compile.rst index ddff30aeb..87164ed0c 100644 --- a/Compile.rst +++ b/Compile.rst @@ -174,6 +174,8 @@ to your binary search PATH so the tool can be later run from anywhere. You'll need a copy of Microsoft Visual C++ 2010. The Express version is sufficient. Grab it from Microsoft's site. +You'll also need the Visual Studio 2010 SP1 update. + For the code generation parts, you'll need perl and XML::LibXML. You can install them like this: * download and install strawberry perl from http://strawberryperl.com/ From 540bcc1f468773e4ef1964384d4a775088d15b44 Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 27 Dec 2013 12:53:33 -0600 Subject: [PATCH 14/89] Enhance createitem, can now place items into containers or buildings --- plugins/createitem.cpp | 151 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 3 deletions(-) diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index e1bb50b82..6e9cd6ea1 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -7,6 +7,7 @@ #include "MiscUtils.h" #include "modules/Maps.h" +#include "modules/MapCache.h" #include "modules/Gui.h" #include "modules/Items.h" #include "modules/Materials.h" @@ -22,6 +23,7 @@ #include "df/creature_raw.h" #include "df/caste_raw.h" #include "df/reaction_reagent.h" +#include "df/reaction_reagent_itemst.h" #include "df/reaction_product_itemst.h" using namespace std; @@ -33,18 +35,27 @@ using df::global::gametype; DFHACK_PLUGIN("createitem"); +int dest_container = -1, dest_building = -1; + command_result df_createitem (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("createitem", "Create arbitrary item at the selected unit's feet.", df_createitem, false, + commands.push_back(PluginCommand("createitem", "Create arbitrary items.", df_createitem, false, "Syntax: createitem [count]\n" " - Item token for what you wish to create, as specified in custom\n" " reactions. If the item has no subtype, omit the :NONE.\n" " - The material you want the item to be made of, as specified\n" " in custom reactions. For REMAINS, FISH, FISH_RAW, VERMIN,\n" " PET, and EGG, replace this with a creature ID and caste.\n" - " [count] - How many of the item you wish to create.\n")); + " [count] - How many of the item you wish to create.\n" + "\n" + "By default, items are created at the feet of the selected unit.\n" + "\n" + "Syntax: createitem \n" + " - Where to put subsequently created items.\n" + " Valid values are 'floor', 'item', and 'building'.\n" + )); return CR_OK; } @@ -61,6 +72,14 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it bool is_gloves = (prod->item_type == df::item_type::GLOVES); bool is_shoes = (prod->item_type == df::item_type::SHOES); + df::item *container = NULL; + df::building *building = NULL; + df::reaction_reagent_itemst *reagent = NULL; + if (dest_container != -1) + container = df::item::find(dest_container); + if (dest_building != -1) + building = df::building::find(dest_building); + prod->produce(unit, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE, df::historical_entity::find(unit->civ_id), ((*gametype == df::game_type::DWARF_MAIN) || (*gametype == df::game_type::DWARF_RECLAIM)) ? df::world_site::find(ui->site_id) : NULL); @@ -70,9 +89,28 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it // otherwise, make a second set because shoes are normally made in pairs if (is_shoes && out_items.size() == prod->count * 2) is_shoes = false; + + MapExtras::MapCache mc; + for (size_t i = 0; i < out_items.size(); i++) { - out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z); + bool on_ground = true; + if (container) + { + on_ground = false; + out_items[i]->flags.bits.removed = 1; + if (!Items::moveToContainer(mc, out_items[i], container)) + out_items[i]->moveToGround(container->pos.x, container->pos.y, container->pos.z); + } + if (building) + { + on_ground = false; + out_items[i]->flags.bits.removed = 1; + if (!Items::moveToBuilding(mc, out_items[i], (df::building_actual *)building, 0)) + out_items[i]->moveToGround(building->centerx, building->centery, building->z); + } + if (on_ground) + out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z); if (is_gloves) { // if the reaction creates gloves without handedness, then create 2 sets (left and right) @@ -97,8 +135,104 @@ command_result df_createitem (color_ostream &out, vector & parameters) int32_t mat_index = -1; int count = 1; + if (parameters.size() == 1) + { + if (parameters[0] == "floor") + { + dest_container = -1; + dest_building = -1; + out.print("Items created will be placed on the floor.\n"); + return CR_OK; + } + else if (parameters[0] == "item") + { + dest_building = -1; + df::item *item = Gui::getSelectedItem(out); + if (!item) + { + out.printerr("You must select a container!\n"); + return CR_FAILURE; + } + switch (item->getType()) + { + case df::item_type::FLASK: + case df::item_type::BARREL: + case df::item_type::BUCKET: + case df::item_type::ANIMALTRAP: + case df::item_type::BOX: + case df::item_type::BIN: + case df::item_type::BACKPACK: + case df::item_type::QUIVER: + break; + case df::item_type::TOOL: + if (item->hasToolUse(df::tool_uses::LIQUID_CONTAINER)) + break; + if (item->hasToolUse(df::tool_uses::FOOD_STORAGE)) + break; + if (item->hasToolUse(df::tool_uses::SMALL_OBJECT_STORAGE)) + break; + if (item->hasToolUse(df::tool_uses::TRACK_CART)) + break; + default: + out.printerr("The selected item cannot be used for item storage!\n"); + return CR_FAILURE; + } + dest_container = item->id; + string name; + item->getItemDescription(&name, 0); + out.print("Items created will be placed inside %s.\n", name.c_str()); + return CR_OK; + } + else if (parameters[0] == "building") + { + dest_container = -1; + df::building *building = Gui::getSelectedBuilding(out); + if (!building) + { + out.printerr("You must select a building!\n"); + return CR_FAILURE; + } + switch (building->getType()) + { + case df::building_type::Coffin: + case df::building_type::Furnace: + case df::building_type::TradeDepot: + case df::building_type::Shop: + case df::building_type::Box: + case df::building_type::Weaponrack: + case df::building_type::Armorstand: + case df::building_type::Workshop: + case df::building_type::Cabinet: + case df::building_type::SiegeEngine: + case df::building_type::Trap: + case df::building_type::AnimalTrap: + case df::building_type::Cage: + case df::building_type::Wagon: + case df::building_type::NestBox: + case df::building_type::Hive: + break; + default: + out.printerr("The selected building cannot be used for item storage!\n"); + return CR_FAILURE; + } + if (building->getBuildStage() != building->getMaxBuildStage()) + { + out.printerr("The selected building has not yet been fully constructed!\n"); + return CR_FAILURE; + } + dest_building = building->id; + string name; + building->getName(&name); + out.print("Items created will be placed inside %s.\n", name.c_str()); + return CR_OK; + } + else + return CR_WRONG_USAGE; + } + if ((parameters.size() < 2) || (parameters.size() > 3)) return CR_WRONG_USAGE; + item_str = parameters[0]; material_str = parameters[1]; @@ -250,6 +384,17 @@ command_result df_createitem (color_ostream &out, vector & parameters) break; } + if ((dest_container != -1) && !df::item::find(dest_container)) + { + dest_container = -1; + out.printerr("Previously selected container no longer exists - item will be placed on the floor.\n"); + } + if ((dest_building != -1) && !df::building::find(dest_building)) + { + dest_building = -1; + out.printerr("Previously selected building no longer exists - item will be placed on the floor.\n"); + } + bool result = makeItem(prod, unit); delete prod; if (!result) From 69fc2bec6df2b1246bd81cabe776264411d2e607 Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 27 Dec 2013 13:01:34 -0600 Subject: [PATCH 15/89] A bit of cleanup --- plugins/createitem.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index 6e9cd6ea1..e75e82584 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -23,7 +23,6 @@ #include "df/creature_raw.h" #include "df/caste_raw.h" #include "df/reaction_reagent.h" -#include "df/reaction_reagent_itemst.h" #include "df/reaction_product_itemst.h" using namespace std; @@ -50,11 +49,13 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector\n" - " - Where to put subsequently created items.\n" - " Valid values are 'floor', 'item', and 'building'.\n" + "To use this command, you must select which unit will create the items.\n" + "By default, items created will be placed at that unit's feet.\n" + "To change this, type 'createitem '.\n" + "Valid destinations:\n" + "* floor - Place items on floor beneath maker's feet.\n" + "* item - Place items inside selected container.\n" + "* building - Place items inside selected building.\n" )); return CR_OK; } @@ -74,7 +75,6 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it df::item *container = NULL; df::building *building = NULL; - df::reaction_reagent_itemst *reagent = NULL; if (dest_container != -1) container = df::item::find(dest_container); if (dest_building != -1) From 91a7280ac34c651c4b231cf82751594a8036477d Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 3 Jan 2014 16:42:24 -0600 Subject: [PATCH 16/89] Need to include tool_uses.h --- plugins/createitem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index e75e82584..42f242074 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -24,6 +24,7 @@ #include "df/caste_raw.h" #include "df/reaction_reagent.h" #include "df/reaction_product_itemst.h" +#include "df/tool_uses.h" using namespace std; using namespace DFHack; From 6af362db399a84fe0598da71de81d0df8d344205 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 4 Jan 2014 01:55:48 +0100 Subject: [PATCH 17/89] add scripts/devel/spawn-unit-helper.rb --- scripts/devel/spawn-unit-helper.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 scripts/devel/spawn-unit-helper.rb diff --git a/scripts/devel/spawn-unit-helper.rb b/scripts/devel/spawn-unit-helper.rb new file mode 100644 index 000000000..999fe7e45 --- /dev/null +++ b/scripts/devel/spawn-unit-helper.rb @@ -0,0 +1,29 @@ +# setup stuff to allow arena creature spawn after a mode change + +df.world.arena_spawn.race.clear +df.world.arena_spawn.caste.clear + +df.world.raws.creatures.all.length.times { |r_idx| + df.world.raws.creatures.all[r_idx].caste.length.times { |c_idx| + df.world.arena_spawn.race << r_idx + df.world.arena_spawn.caste << c_idx + } +} + +df.world.arena_spawn.creature_cnt[df.world.arena_spawn.race.length-1] = 0 + +puts < Date: Sat, 4 Jan 2014 15:04:56 +0100 Subject: [PATCH 18/89] ruby: codegen unit.caste_tg helper --- plugins/ruby/codegen.pl | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index a4bebb7e6..f04731e3b 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -296,11 +296,32 @@ sub render_struct_field_refs { sub render_field_reftarget { my ($parent, $field, $name, $reftg) = @_; - my $aux = $field->getAttribute('aux-value'); - return if ($aux); # TODO - my $tg = $global_types{$reftg}; return if (!$tg); + + my $tgname = "${name}_tg"; + $tgname =~ s/_id(.?.?)_tg/_tg$1/; + + my $aux = $field->getAttribute('aux-value'); + if ($aux) { + # minimal support (aims is unit.caste_tg) + return if $aux !~ /^\$\$\.[^_][\w\.]+$/; + $aux =~ s/\$\$\.//; + + for my $codehelper ($tg->findnodes('child::code-helper')) { + if ($codehelper->getAttribute('name') eq 'find-instance') { + my $helper = $codehelper->textContent; + $helper =~ s/\$global/df/; + $helper =~ s/\$\$/$aux/; + $helper =~ s/\$/$name/; + if ($helper =~ /^[\w\.\[\]]+$/) { + push @lines_rb, "def $tgname ; $helper ; end"; + } + } + } + return; + } + my $tgvec = $tg->getAttribute('instance-vector'); return if (!$tgvec); my $idx = $tg->getAttribute('key-field'); @@ -308,9 +329,6 @@ sub render_field_reftarget { $tgvec =~ s/^\$global/df/; return if $tgvec !~ /^[\w\.]+$/; - my $tgname = "${name}_tg"; - $tgname =~ s/_id(.?.?)_tg/_tg$1/; - for my $othername (map { $_->getAttribute('name') } $parent->findnodes('child::ld:field')) { $tgname .= '_' if ($othername and $tgname eq $othername); } From 04f88ef8fb93be0027f8a3f9fdebb09265ca2f1b Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 5 Jan 2014 02:29:01 +0100 Subject: [PATCH 19/89] showmood: fix count of gotten items --- plugins/showmood.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index ba6df034a..57bfd9658 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -169,7 +169,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) // total amount of stuff fetched so far int count_got = 0; for (size_t i = 0; i < job->items.size(); i++) - count_got += job->items[i]->item->getTotalDimension(); + count_got += 1; // XXX thread may need job->items[i]->item->getTotalDimension() for (size_t i = 0; i < job->job_items.size(); i++) { From 24fbf570e62880eb87b786d6cc53bbfe124106f6 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 18 Jan 2014 22:45:42 -0600 Subject: [PATCH 20/89] 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; +} From baf377afba8b17c6c286c8bfa00f0f441493bb83 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 18 Jan 2014 22:56:56 -0600 Subject: [PATCH 21/89] Meant to move this, not copy it --- plugins/strangemood.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 5dd12c627..f3327c382 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -429,12 +429,6 @@ command_result df_strangemood (color_ostream &out, vector & parameters) 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); From 89b3c7bb6774a99900a97b478bd685e1d9c81b6e Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 19 Jan 2014 20:21:41 -0600 Subject: [PATCH 22/89] Change syntax for specifying mood type, also allow overriding mood skill --- plugins/strangemood.cpp | 126 ++++++++++++++++++++++++++++++++-------- 1 file changed, 103 insertions(+), 23 deletions(-) diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index f3327c382..9b64a525c 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -363,6 +363,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) 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++) { @@ -376,18 +377,89 @@ command_result df_strangemood (color_ostream &out, vector & parameters) 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 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; @@ -619,7 +691,10 @@ command_result df_strangemood (color_ostream &out, vector & parameters) unit->job.mood_timeout = 50000; unit->flags1.bits.has_mood = true; unit->flags1.bits.had_mood = true; - unit->job.mood_skill = getMoodSkill(unit); + if (skill == job_skill::NONE) + skill = getMoodSkill(unit); + + unit->job.mood_skill = skill; df::job *job = new df::job(); Job::linkIntoWorld(job); @@ -630,7 +705,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) job->job_type = job_type::StrangeMoodBrooding; else { - switch (unit->job.mood_skill) + switch (skill) { case job_skill::MINING: case job_skill::MASONRY: @@ -697,7 +772,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) // 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)) + // Gem Cutters and Gem Setters have a 50% chance of using only one base item + if (((skill == job_skill::CUTGEM) || (skill == job_skill::ENCRUSTGEM)) && (rand() % 2)) base_item_count = 1; // Choose the base material @@ -741,7 +817,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) { df::item *filter; bool found_pref; - switch (unit->job.mood_skill) + switch (skill) { case job_skill::MINING: case job_skill::DETAILSTONE: @@ -1014,14 +1090,18 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } // 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)) + // 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 = (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) + switch (skill) { case job_skill::MINING: case job_skill::DETAILSTONE: @@ -1214,13 +1294,13 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector - Force the mood to be of a specific type.\n" + " Valid types: fey, secretive, possessed, fell, macabre\n" + " -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" )); return CR_OK; From 7f14e109fa239c0919a150d6ab28964a774fc24d Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 19 Jan 2014 20:31:15 -0600 Subject: [PATCH 23/89] Use DF-compatible RNG for better randomness --- plugins/strangemood.cpp | 98 +++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 9b64a525c..00da1f4f5 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -9,6 +9,7 @@ #include "modules/Items.h" #include "modules/Job.h" #include "modules/Translation.h" +#include "modules/Random.h" #include "DataDefs.h" #include "df/d_init.h" @@ -40,6 +41,8 @@ using df::global::created_item_subtype; using df::global::created_item_mattype; using df::global::created_item_matindex; +Random::MersenneRNG rng; + bool isUnitMoodable (df::unit *unit) { if (!Units::isCitizen(unit)) @@ -107,7 +110,7 @@ df::job_skill getMoodSkill (df::unit *unit) } if (!skills.size()) skills.push_back(job_skill::STONECRAFT); - return skills[rand() % skills.size()]; + return skills[rng.df_trandom(skills.size())]; } int getCreatedMetalBars (int32_t idx) @@ -127,14 +130,14 @@ void selectWord (const df::world_raws::T_language::T_word_table &table, int32_t { if (table.parts[mode].size()) { - int offset = rand() % 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 = rand() % world->raws.language.words.size(); - part = (df::part_of_speech)(rand() % 9); + 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"); } } @@ -147,13 +150,13 @@ void generateName(df::language_name &output, int language, int mode, const df::w { output = df::language_name(); if (language == -1) - language = rand() % world->raws.language.translations.size(); + 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 = rand() % world->raws.language.translations.size(); + output.language = rng.df_trandom(world->raws.language.translations.size()); int r, r2, r3; switch (mode) { @@ -169,7 +172,7 @@ void generateName(df::language_name &output, int language, int mode, const df::w if (mode != 10) { case 4: case 37: // this is not a typo - if (rand() % 2) + 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); @@ -183,10 +186,10 @@ void generateName(df::language_name &output, int language, int mode, const df::w break; case 1: case 13: case 20: - r = rand() % 3; + r = rng.df_trandom(3); if (r == 0 || r == 1) { - if (rand() % 2) + 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); @@ -200,20 +203,20 @@ void generateName(df::language_name &output, int language, int mode, const df::w if (r == 1 || r == 2) { case 3: case 8: case 11: // this is not a typo either - r2 = rand() % 2; + 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 = rand() % 3; - if (rand() % 50) - r3 = rand() % 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 = rand() % 2; + r2 = rng.df_trandom(2); if (r2) selectWord(table2, output.words[6], output.parts_of_speech[6], 5); else @@ -226,14 +229,14 @@ void generateName(df::language_name &output, int language, int mode, const df::w 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)) + if (!(rng.df_trandom(100))) selectWord(table1, output.words[3], output.parts_of_speech[3], 3); break; } } - if (rand() % 100) + if (rng.df_trandom(100)) { - if (rand() % 2) + 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); @@ -246,9 +249,9 @@ void generateName(df::language_name &output, int language, int mode, const df::w 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; + r3 = rng.df_trandom(3); + if (rng.df_trandom(50)) + r3 = rng.df_trandom(2); switch (r3) { case 0: @@ -258,16 +261,16 @@ void generateName(df::language_name &output, int language, int mode, const df::w break; case 1: selectWord(table2, output.words[2], output.parts_of_speech[2], 3); - if (!(rand() % 100)) + if (!(rng.df_trandom(100))) selectWord(table2, output.words[3], output.parts_of_speech[3], 3); break; } - if (rand() % 100) + if (rng.df_trandom(100)) selectWord(table2, output.words[4], output.parts_of_speech[4], 4); break; case 7: - r = rand() % 3; + r = rng.df_trandom(3); if (r == 0 || r == 1) { selectWord(table2, output.words[0], output.parts_of_speech[0], 0); @@ -275,14 +278,14 @@ void generateName(df::language_name &output, int language, int mode, const df::w } if (r == 1 || r == 2) { - r2 = rand() % 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 = rand() % 3; - if (rand() % 50) - r3 = rand() % 2; + r3 = rng.df_trandom(3); + if (rng.df_trandom(50)) + r3 = rng.df_trandom(2); switch (r3) { case 0: @@ -292,12 +295,12 @@ void generateName(df::language_name &output, int language, int mode, const df::w break; case 1: selectWord(table2, output.words[2], output.parts_of_speech[2], 3); - if (!(rand() % 100)) + if (!(rng.df_trandom(100))) selectWord(table2, output.words[3], output.parts_of_speech[3], 3); break; } } - if (rand() % 100) + if (rng.df_trandom(100)) selectWord(table2, output.words[4], output.parts_of_speech[4], 4); break; } @@ -610,7 +613,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) out.printerr("No units are eligible to enter a mood!\n"); return CR_FAILURE; } - unit = moodable_units[tickets[rand() % tickets.size()]]; + unit = moodable_units[tickets[rng.df_trandom(tickets.size())]]; } df::unit_soul *soul = unit->status.current_soul; @@ -626,9 +629,9 @@ command_result df_strangemood (color_ostream &out, vector & parameters) // If no mood type was specified, pick one randomly if (type == mood_type::None) { - if (rand() % 100 > unit->status.happiness) + if (rng.df_trandom(100) > unit->status.happiness) { - switch (rand() % 2) + switch (rng.df_trandom(2)) { case 0: type = mood_type::Fell; break; case 1: type = mood_type::Macabre; break; @@ -636,7 +639,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } else { - switch (rand() % 3) + switch (rng.df_trandom(3)) { case 0: type = mood_type::Fey; break; case 1: type = mood_type::Secretive; break; @@ -771,9 +774,9 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } // The dwarf will want 1-3 of the base material - int base_item_count = 1 + (rand() % 3); + 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)) && (rand() % 2)) + if (((skill == job_skill::CUTGEM) || (skill == job_skill::ENCRUSTGEM)) && (rng.df_trandom(2))) base_item_count = 1; // Choose the base material @@ -789,7 +792,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } else if (job->job_type == job_type::StrangeMoodBrooding) { - switch (rand() % 3) + switch (rng.df_trandom(3)) { case 0: job->job_items.push_back(item = new df::job_item()); @@ -930,7 +933,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } if (!found_pref) { - switch (rand() % 3) + switch (rng.df_trandom(3)) { case 0: item->flags2.bits.silk = true; @@ -1000,7 +1003,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } } if (mats.size()) - item->mat_index = mats[rand() & mats.size()]; + item->mat_index = mats[rng.df_trandom(mats.size())]; item->quantity = base_item_count; } break; @@ -1042,7 +1045,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) 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->mat_type = mats[rng.df_trandom(mats.size())]; } item->quantity = base_item_count; break; @@ -1096,9 +1099,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) (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); + 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) @@ -1137,9 +1138,9 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } for (size_t i = 0; i < extra_items; i++) { - if ((job->job_type == job_type::StrangeMoodBrooding) && (rand() % 2)) + if ((job->job_type == job_type::StrangeMoodBrooding) && (rng.df_trandom(2))) { - switch (rand() % 3) + switch (rng.df_trandom(3)) { case 0: job->job_items.push_back(item = new df::job_item()); @@ -1170,7 +1171,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) item_type = item_type::NONE; mat_type = -1; flags2.whole = 0; - switch (rand() % 10) + switch (rng.df_trandom(10)) { case 0: item_type = item_type::WOOD; @@ -1207,7 +1208,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) case 8: item_type = item_type::CLOTH; mat_type = -1; - switch (rand() % 3) + switch (rng.df_trandom(3)) { case 0: flags2.bits.plant = true; @@ -1222,7 +1223,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) break; case 9: item_type = item_type::ROUGH; - switch (rand() % 3) + switch (rng.df_trandom(3)) { case 0: mat_type = builtin_mats::GLASS_GREEN; @@ -1281,7 +1282,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) 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)) + if (!rng.df_trandom(100)) unit->status.artifact_name = unit->name; } unit->unk_18e = 0; @@ -1302,6 +1303,7 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector Date: Sat, 25 Jan 2014 03:21:56 -0500 Subject: [PATCH 24/89] DFHack compiles under OS X now. --- library/modules/MapCache.cpp | 2 +- plugins/eventful.cpp | 1 + plugins/zone.cpp | 18 +++++++++--------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index 6ca696485..db3b4cdd9 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -1143,7 +1143,7 @@ MapExtras::MapCache::MapCache() memset(biomes[i].layer_stone, -1, sizeof(biomes[i].layer_stone)); - for (size_t j = 0; j < std::min(BiomeInfo::MAX_LAYERS,layer_mats[i].size()); j++) + for (size_t j = 0; j < std::min(BiomeInfo::MAX_LAYERS,layer_mats[i].size()); j++) { biomes[i].layer_stone[j] = layer_mats[i][j]; diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index f93c15edc..1bcf737cb 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 0271cb75f..4def386b7 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -3839,12 +3839,12 @@ void unbutcherRace(int race) static bool autobutcher_isEnabled() { return enable_autobutcher; } static bool autowatch_isEnabled() { return enable_autobutcher_autowatch; } -static size_t autobutcher_getSleep(color_ostream &out) +static unsigned autobutcher_getSleep(color_ostream &out) { return sleep_autobutcher; } -static void autobutcher_setSleep(color_ostream &out, size_t ticks) +static void autobutcher_setSleep(color_ostream &out, unsigned ticks) { sleep_autobutcher = ticks; if(config_autobutcher.isValid()) @@ -3892,7 +3892,7 @@ static void autowatch_setEnabled(color_ostream &out, bool enable) // set all data for a watchlist race in one go // if race is not already on watchlist it will be added // params: (id, fk, mk, fa, ma, watched) -static void autobutcher_setWatchListRace(color_ostream &out, size_t id, size_t fk, size_t mk, size_t fa, size_t ma, bool watched) +static void autobutcher_setWatchListRace(color_ostream &out, unsigned id, unsigned fk, unsigned mk, unsigned fa, unsigned ma, bool watched) { int watched_index = getWatchedIndex(id); if(watched_index != -1) @@ -3921,7 +3921,7 @@ static void autobutcher_setWatchListRace(color_ostream &out, size_t id, size_t f } // remove entry from watchlist -static void autobutcher_removeFromWatchList(color_ostream &out, size_t id) +static void autobutcher_removeFromWatchList(color_ostream &out, unsigned id) { int watched_index = getWatchedIndex(id); if(watched_index != -1) @@ -3940,7 +3940,7 @@ static void autobutcher_sortWatchList(color_ostream &out) } // set default target values for new races -static void autobutcher_setDefaultTargetNew(color_ostream &out, size_t fk, size_t mk, size_t fa, size_t ma) +static void autobutcher_setDefaultTargetNew(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) { default_fk = fk; default_mk = mk; @@ -3956,9 +3956,9 @@ static void autobutcher_setDefaultTargetNew(color_ostream &out, size_t fk, size_ } // set default target values for ALL races (update watchlist and set new default) -static void autobutcher_setDefaultTargetAll(color_ostream &out, size_t fk, size_t mk, size_t fa, size_t ma) +static void autobutcher_setDefaultTargetAll(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) { - for(size_t i=0; ifk = fk; @@ -3970,12 +3970,12 @@ static void autobutcher_setDefaultTargetAll(color_ostream &out, size_t fk, size_ autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma); } -static void autobutcher_butcherRace(color_ostream &out, size_t id) +static void autobutcher_butcherRace(color_ostream &out, unsigned id) { butcherRace(id); } -static void autobutcher_unbutcherRace(color_ostream &out, size_t id) +static void autobutcher_unbutcherRace(color_ostream &out, unsigned id) { unbutcherRace(id); } From 5c28d7eaf6aa591c048b26be81662245b6dd0342 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 25 Jan 2014 09:15:43 -0600 Subject: [PATCH 25/89] Not enough mood tickets given out --- plugins/strangemood.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 00da1f4f5..aa111d189 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -575,6 +575,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) 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) From 56f935aea6471e4c274ae326af2b16855df739e6 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 26 Jan 2014 18:55:52 -0600 Subject: [PATCH 26/89] Add bugfix for #6420 --- plugins/strangemood.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index aa111d189..2886a024f 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -984,7 +984,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) item->item_type = item_type::BAR; item->mat_type = filter->getMaterial(); item->mat_index = filter->getMaterialIndex(); - item->quantity = base_item_count; + item->quantity = base_item_count * 150; + item->min_dimension = 150; } else { @@ -1005,7 +1006,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } if (mats.size()) item->mat_index = mats[rng.df_trandom(mats.size())]; - item->quantity = base_item_count; + item->quantity = base_item_count * 150; + item->min_dimension = 150; } break; @@ -1258,6 +1260,16 @@ command_result df_strangemood (color_ostream &out, vector & parameters) item->mat_type = mat_type; item->flags2.whole = flags2.whole; item->quantity = 1; + if (item_type == item_type::BAR) + { + item->quantity *= 150; + item->min_dimension = 150; + } + if (item_type == item_type::CLOTH) + { + item->quantity *= 10000; + item->min_dimension = 10000; + } } } } From c0d3e9189ddddd31b6b7fc20f1be8c4581e4af16 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 27 Jan 2014 12:40:21 +0100 Subject: [PATCH 27/89] showmood: fix item count for real --- plugins/showmood.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index 57bfd9658..2a6d6b17c 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -169,7 +169,13 @@ command_result df_showmood (color_ostream &out, vector & parameters) // total amount of stuff fetched so far int count_got = 0; for (size_t i = 0; i < job->items.size(); i++) - count_got += 1; // XXX thread may need job->items[i]->item->getTotalDimension() + { + df::item_type type = job->job_items[i]->item_type; + if (type == item_type::BAR || type == item_type::CLOTH) + count_got += job->items[i]->item->getTotalDimension(); + else + count_got += 1; + } for (size_t i = 0; i < job->job_items.size(); i++) { From 6b6164c0995828d60915bf068cfcedecf5d48a51 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 27 Jan 2014 14:13:31 +0100 Subject: [PATCH 28/89] call dfhack.init even in TEXT mode --- library/Core.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index fad01e4b2..0f0b18ae7 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -811,6 +811,15 @@ bool Core::loadScriptFile(color_ostream &out, string fname, bool silent) } } +// Load dfhack.init in a dedicated thread (non-interactive console mode) +void fInitthread(void * iodata) +{ + IODATA * iod = ((IODATA*) iodata); + Core * core = iod->core; + Console & con = core->getConsole(); + core->loadScriptFile(con, "dfhack.init", true); +} + // A thread function... for the interactive console. void fIOthread(void * iodata) { @@ -822,7 +831,7 @@ void fIOthread(void * iodata) main_history.load("dfhack.history"); Console & con = core->getConsole(); - if(plug_mgr == 0 || core == 0) + if (plug_mgr == 0) { con.printerr("Something horrible happened in Core's constructor...\n"); return; @@ -1008,16 +1017,24 @@ bool Core::Init() IODATA *temp = new IODATA; temp->core = this; temp->plug_mgr = plug_mgr; + + HotkeyMutex = new mutex(); + HotkeyCond = new condition_variable(); + if (!is_text_mode) { cerr << "Starting IO thread.\n"; // create IO thread thread * IO = new thread(fIOthread, (void *) temp); } + else + { + cerr << "Starting dfhack.init thread.\n"; + thread * init = new thread(fInitthread, (void *) temp); + } + cerr << "Starting DF input capture thread.\n"; // set up hotkey capture - HotkeyMutex = new mutex(); - HotkeyCond = new condition_variable(); thread * HK = new thread(fHKthread, (void *) temp); screen_window = new Windows::top_level_window(); screen_window->addChild(new Windows::dfhack_dummy(5,10)); From 6ad8d128ba452047389c542542fc942f79f30da7 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 27 Jan 2014 15:10:12 +0100 Subject: [PATCH 29/89] use non-interactive console when calling dfhack.init in TEXT mode --- library/Core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 0f0b18ae7..96fd3903f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -816,8 +816,8 @@ void fInitthread(void * iodata) { IODATA * iod = ((IODATA*) iodata); Core * core = iod->core; - Console & con = core->getConsole(); - core->loadScriptFile(con, "dfhack.init", true); + color_ostream_proxy out(core->getConsole()); + core->loadScriptFile(out, "dfhack.init", true); } // A thread function... for the interactive console. From 7df9957941225b056d2db2e6bf622c4c115365d6 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 27 Jan 2014 15:22:39 +0100 Subject: [PATCH 30/89] dfhack-run: prevent duplicate "not a recognized command" error message --- library/dfhack-run.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/library/dfhack-run.cpp b/library/dfhack-run.cpp index f79e49a3c..69188f2d6 100644 --- a/library/dfhack-run.cpp +++ b/library/dfhack-run.cpp @@ -79,13 +79,10 @@ int main (int argc, char *argv[]) command_result rv = client.run_command(argv[1], args); - if (rv != CR_OK) { - if (rv == CR_NOT_IMPLEMENTED) - out.printerr("%s is not a recognized command.\n", argv[1]); + out.flush(); + if (rv != CR_OK) return 1; - } - out.flush(); return 0; } From 8800cf6f40aa90302f3cb412df2152aaed1a411c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 10 Feb 2014 18:54:52 +0400 Subject: [PATCH 31/89] Export functions for converting between UTF-8 and CP437 to lua. --- Lua API.html | 6 ++++++ Lua API.rst | 8 ++++++++ library/LuaApi.cpp | 5 +++++ 3 files changed, 19 insertions(+) diff --git a/Lua API.html b/Lua API.html index 4aecf48a0..2a214259c 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1119,6 +1119,12 @@ can be omitted.

  • dfhack.TranslateName(name[,in_english,only_last_name])

    Convert a language_name or only the last name part to string.

  • +
  • dfhack.df2utf(string)

    +

    Convert a string from DF's CP437 encoding to UTF-8.

    +
  • +
  • dfhack.utf2df(string)

    +

    Convert a string from UTF-8 to DF's CP437 encoding.

    +
  • Gui module

    diff --git a/Lua API.rst b/Lua API.rst index 1fd5147b5..6c769f004 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -812,6 +812,14 @@ can be omitted. Convert a language_name or only the last name part to string. +* ``dfhack.df2utf(string)`` + + Convert a string from DF's CP437 encoding to UTF-8. + +* ``dfhack.utf2df(string)`` + + Convert a string from UTF-8 to DF's CP437 encoding. + Gui module ---------- diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index bdc0c39e5..c64ad6634 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1279,6 +1279,9 @@ static std::string getHackPath() { return Core::getInstance().getHackPath(); } static bool isWorldLoaded() { return Core::getInstance().isWorldLoaded(); } static bool isMapLoaded() { return Core::getInstance().isMapLoaded(); } +static std::string df2utf(std::string s) { return DF2UTF(s); } +static std::string utf2df(std::string s) { return UTF2DF(s); } + static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(getOSType), WRAP(getDFVersion), @@ -1288,6 +1291,8 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(isWorldLoaded), WRAP(isMapLoaded), WRAPM(Translation, TranslateName), + WRAP(df2utf), + WRAP(utf2df), { NULL, NULL } }; From 7bdb687e4ae5747b0a05f717c3ee5bee51cb1021 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 10 Feb 2014 20:09:06 +0400 Subject: [PATCH 32/89] Support calling a lua function via a protobuf request. Previously the only way to call lua code was to call scripts and parse their output to the stream, which is cumbersome. --- NEWS | 3 ++ library/RemoteTools.cpp | 86 ++++++++++++++++++++++++++++++++ library/dfhack-run.cpp | 47 +++++++++++++++-- library/include/RemoteTools.h | 8 +++ library/proto/CoreProtocol.proto | 7 +++ 5 files changed, 146 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index a672f2766..854c9353e 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ DFHack future + Internals: + - support for calling a lua function via a protobuf request (demonstrated by dfhack-run --lua). + New commands: - move the 'grow', 'extirpate' and 'immolate' commands as 'plant' subcommands - 'plant create' - spawn a new shrub under the cursor diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 718d1b181..17f5258e1 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -55,6 +55,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "modules/Units.h" #include "modules/World.h" +#include "LuaTools.h" + #include "DataDefs.h" #include "df/ui.h" #include "df/ui_advmode.h" @@ -656,6 +658,8 @@ CoreService::CoreService() { addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND); addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND); + addMethod("RunLua", &CoreService::RunLua); + // Functions: addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND); addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND); @@ -730,3 +734,85 @@ command_result CoreService::CoreResume(color_ostream &stream, const EmptyMessage cnt->set_value(--suspend_depth); return CR_OK; } + +namespace { + struct LuaFunctionData { + command_result rv; + const dfproto::CoreRunLuaRequest *in; + StringListMessage *out; + }; +} + +command_result CoreService::RunLua(color_ostream &stream, + const dfproto::CoreRunLuaRequest *in, + StringListMessage *out) +{ + auto L = Lua::Core::State; + LuaFunctionData data = { CR_FAILURE, in, out }; + + lua_pushcfunction(L, doRunLuaFunction); + lua_pushlightuserdata(L, &data); + + if (!Lua::Core::SafeCall(stream, 1, 0)) + return CR_FAILURE; + + return data.rv; +} + +int CoreService::doRunLuaFunction(lua_State *L) +{ + color_ostream &out = *Lua::GetOutput(L); + auto &args = *(LuaFunctionData*)lua_touserdata(L, 1); + + // Verify module name + std::string module = args.in->module(); + size_t len = module.size(); + + bool valid = false; + + if (len > 4) + { + if (module.substr(0,4) == "rpc.") + valid = true; + else if ((module[len-4] == '.' || module[len-4] == '-') && module.substr(len-3) != "rpc") + valid = true; + } + + if (!valid) + { + args.rv = CR_WRONG_USAGE; + out.printerr("Only modules named rpc.* or *.rpc or *-rpc may be called.\n"); + return 0; + } + + // Prepare function and arguments + lua_settop(L, 0); + + if (!Lua::PushModulePublic(out, L, module.c_str(), args.in->function().c_str()) + || lua_isnil(L, 1)) + { + args.rv = CR_NOT_FOUND; + return 0; + } + + luaL_checkstack(L, args.in->arguments_size(), "too many arguments"); + + for (int i = 0; i < args.in->arguments_size(); i++) + lua_pushstring(L, args.in->arguments(i).c_str()); + + // Call + lua_call(L, args.in->arguments_size(), LUA_MULTRET); + + // Store results + int nresults = lua_gettop(L); + + for (int i = 1; i <= nresults; i++) + { + size_t len; + const char *data = lua_tolstring(L, i, &len); + args.out->add_value(std::string(data, len)); + } + + args.rv = CR_OK; + return 0; +} diff --git a/library/dfhack-run.cpp b/library/dfhack-run.cpp index 69188f2d6..abe34f34d 100644 --- a/library/dfhack-run.cpp +++ b/library/dfhack-run.cpp @@ -72,12 +72,49 @@ int main (int argc, char *argv[]) if (!client.connect()) return 2; - // Call the command - std::vector args; - for (int i = 2; i < argc; i++) - args.push_back(argv[i]); + command_result rv; - command_result rv = client.run_command(argv[1], args); + if (strcmp(argv[1], "--lua") == 0) + { + if (argc <= 3) + { + fprintf(stderr, "Usage: dfhack-run --lua [args...]\n"); + return 2; + } + + RemoteFunction run_call; + + if (!run_call.bind(&client, "RunLua")) + { + fprintf(stderr, "No RunLua protocol function found."); + return 3; + } + + run_call.in()->set_module(argv[2]); + run_call.in()->set_function(argv[3]); + for (int i = 4; i < argc; i++) + run_call.in()->add_arguments(argv[i]); + + rv = run_call(); + + out.flush(); + + if (rv == CR_OK) + { + for (int i = 0; i < run_call.out()->value_size(); i++) + printf("%s%s", (i>0?"\t":""), run_call.out()->value(i).c_str()); + printf("\n"); + } + } + else + { + // Call the command + std::vector args; + for (int i = 2; i < argc; i++) + args.push_back(argv[i]); + + rv = client.run_command(argv[1], args); + } out.flush(); diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h index e70cefc00..ead1c0aa1 100644 --- a/library/include/RemoteTools.h +++ b/library/include/RemoteTools.h @@ -48,6 +48,8 @@ namespace DFHack DFHACK_EXPORT void strVectorToRepeatedField(RepeatedPtrField *pf, const std::vector &vec); + using dfproto::StringListMessage; + /** * Represent bitfield bits as a repeated string field. */ @@ -131,6 +133,8 @@ namespace DFHack class CoreService : public RPCService { int suspend_depth; + + static int doRunLuaFunction(lua_State *L); public: CoreService(); ~CoreService(); @@ -144,5 +148,9 @@ namespace DFHack // For batching command_result CoreSuspend(color_ostream &stream, const EmptyMessage*, IntMessage *cnt); command_result CoreResume(color_ostream &stream, const EmptyMessage*, IntMessage *cnt); + + command_result RunLua(color_ostream &stream, + const dfproto::CoreRunLuaRequest *in, + StringListMessage *out); }; } diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto index 92d7c48d9..e53078875 100644 --- a/library/proto/CoreProtocol.proto +++ b/library/proto/CoreProtocol.proto @@ -81,3 +81,10 @@ message CoreRunCommandRequest { // RPC CoreSuspend : EmptyMessage -> IntMessage // RPC CoreResume : EmptyMessage -> IntMessage + +// RPC RunLua : CoreRunLuaRequest -> StringListMessage +message CoreRunLuaRequest { + required string module = 1; + required string function = 2; + repeated string arguments = 3; +} From 67b7681ba62c1a95181bae2e34a6eafdd79511e1 Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 13 Feb 2014 11:36:45 -0600 Subject: [PATCH 33/89] Fix reaction errors, make quantities more consistent --- plugins/raw/reaction_spatter.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/raw/reaction_spatter.txt b/plugins/raw/reaction_spatter.txt index 95b9f1d33..062e82287 100644 --- a/plugins/raw/reaction_spatter.txt +++ b/plugins/raw/reaction_spatter.txt @@ -77,8 +77,8 @@ Reaction name must start with 'SPATTER_ADD_': [NAME:coat weapon with GCS venom] [BUILDING:GREASING_STATION:NONE] [SKILL:DYER] - [REAGENT:extract:150:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON] - [MIN_DIMENSION:150] + [REAGENT:extract:100:LIQUID_MISC:NONE:CREATURE_MAT:SPIDER_CAVE_GIANT:POISON] + [MIN_DIMENSION:100] [REACTION_CLASS:CREATURE_EXTRACT] [REAGENT:extract container:1:NONE:NONE:NONE:NONE] [CONTAINS:extract] @@ -97,7 +97,7 @@ Reaction name must start with 'SPATTER_ADD_': [NAME:coat ammo with GCS venom] [BUILDING:GREASING_STATION:NONE] [SKILL:DYER] - [REAGENT:extract:50:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON] + [REAGENT:extract:50:LIQUID_MISC:NONE:CREATURE_MAT:SPIDER_CAVE_GIANT:POISON] [MIN_DIMENSION:50] [REACTION_CLASS:CREATURE_EXTRACT] [REAGENT:extract container:1:NONE:NONE:NONE:NONE] @@ -117,8 +117,8 @@ Reaction name must start with 'SPATTER_ADD_': [NAME:coat weapon with GDS venom] [BUILDING:GREASING_STATION:NONE] [SKILL:DYER] - [REAGENT:extract:150:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON] - [MIN_DIMENSION:150] + [REAGENT:extract:100:LIQUID_MISC:NONE:CREATURE_MAT:SCORPION_DESERT_GIANT:POISON] + [MIN_DIMENSION:100] [REACTION_CLASS:CREATURE_EXTRACT] [REAGENT:extract container:1:NONE:NONE:NONE:NONE] [CONTAINS:extract] @@ -137,7 +137,7 @@ Reaction name must start with 'SPATTER_ADD_': [NAME:coat ammo with GDS venom] [BUILDING:GREASING_STATION:NONE] [SKILL:DYER] - [REAGENT:extract:50:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON] + [REAGENT:extract:50:LIQUID_MISC:NONE:CREATURE_MAT:SCORPION_DESERT_GIANT:POISON] [MIN_DIMENSION:50] [REACTION_CLASS:CREATURE_EXTRACT] [REAGENT:extract container:1:NONE:NONE:NONE:NONE] From 73a112e06c68a73dc74e9f601cc3074883d550f4 Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 14 Feb 2014 11:06:33 -0600 Subject: [PATCH 34/89] Update tubefill to skip hollow tubes by default, and update docs --- Compile.html | 94 ++++++++++++++++++++++++++++++-------------- Contributors.html | 36 ++++++++++++----- Readme.html | 23 +++++++++-- Readme.rst | 9 ++++- plugins/tubefill.cpp | 28 ++++++++++--- 5 files changed, 139 insertions(+), 51 deletions(-) diff --git a/Compile.html b/Compile.html index 99fdf98e7..7ae0df1c1 100644 --- a/Compile.html +++ b/Compile.html @@ -3,13 +3,13 @@ - + Building DFHACK