Merge remote-tracking branch 'myk002/myk_orders_reactions' into develop

develop
lethosor 2021-08-17 00:26:02 -04:00
commit 6b83a39f52
No known key found for this signature in database
GPG Key ID: 76A269552F4F58C1
3 changed files with 239 additions and 15 deletions

@ -43,6 +43,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Misc Improvements
- `buildingplan`: now displays which items are attached and which items are still missing for planned buildings
- `orders`: support importing and exporting reaction-specific item conditions, like "lye-containing" for soap production orders
- `tiletypes-here`, `tiletypes-here-point`: add --cursor and --quiet options to support non-interactive use cases
- `quickfort`: Dreamfort blueprint set improvements: extensive revision based on playtesting and feedback. includes updated ``onMapLoad.init`` settings file and enhanced automation orders. see full changelog at https://github.com/DFHack/dfhack/pull/1921

@ -29,6 +29,8 @@
#include "df/manager_order.h"
#include "df/manager_order_condition_item.h"
#include "df/manager_order_condition_order.h"
#include "df/reaction.h"
#include "df/reaction_reagent.h"
#include "df/world.h"
using namespace DFHack;
@ -377,7 +379,26 @@ static command_result orders_export_command(color_ostream & out, const std::stri
condition["tool"] = enum_item_key(it2->has_tool_use);
}
// TODO: anon_1, anon_2, anon_3
if (it2->min_dimension != -1)
{
condition["min_dimension"] = it2->min_dimension;
}
if (it2->reaction_id != -1)
{
df::reaction *reaction = world->raws.reactions.reactions[it2->reaction_id];
condition["reaction_id"] = reaction->code;
if (!it2->contains.empty())
{
Json::Value contains(Json::arrayValue);
for (int32_t contains_val : it2->contains)
{
contains.append(reaction->reagents[contains_val]->code);
}
condition["contains"] = contains;
}
}
conditions.append(condition);
}
@ -705,7 +726,72 @@ static command_result orders_import(color_ostream &out, Json::Value &orders)
}
}
// TODO: anon_1, anon_2, anon_3
if (it2.isMember("min_dimension"))
{
condition->min_dimension = it2["min_dimension"].asInt();
}
if (it2.isMember("reaction_id"))
{
std::string reaction_code = it2["reaction_id"].asString();
df::reaction *reaction = NULL;
int32_t reaction_id = -1;
size_t num_reactions = world->raws.reactions.reactions.size();
for (size_t idx = 0; idx < num_reactions; ++idx)
{
reaction = world->raws.reactions.reactions[idx];
if (reaction->code == reaction_code)
{
reaction_id = idx;
break;
}
}
if (reaction_id < 0)
{
delete condition;
out << COLOR_YELLOW << "Reaction code not found for imported manager order: " << reaction_code << std::endl;
continue;
}
condition->reaction_id = reaction_id;
if (it2.isMember("contains"))
{
size_t num_reagents = reaction->reagents.size();
std::string bad_reagent_code;
for (Json::Value & contains_val : it2["contains"])
{
std::string reagent_code = contains_val.asString();
bool reagent_found = false;
for (size_t idx = 0; idx < num_reagents; ++idx)
{
df::reaction_reagent *reagent = reaction->reagents[idx];
if (reagent->code == reagent_code)
{
condition->contains.push_back(idx);
reagent_found = true;
break;
}
}
if (!reagent_found)
{
bad_reagent_code = reagent_code;
break;
}
}
if (!bad_reagent_code.empty())
{
delete condition;
out << COLOR_YELLOW << "Invalid reagent code for imported manager order: " << bad_reagent_code << std::endl;
continue;
}
}
}
order->item_conditions.push_back(condition);
}

@ -1,26 +1,70 @@
config.mode = 'fortress'
TMP_FILE_NAME = 'tmp-test'
TMP_FILE_PATH = ('dfhack-config/orders/%s.json'):format(TMP_FILE_NAME)
local FILE_PATH_PATTERN = 'dfhack-config/orders/%s.json'
function run_orders_import(file_content)
local f = io.open(TMP_FILE_PATH, 'w')
f:write(file_content)
f:close()
local BACKUP_FILE_NAME = 'tmp-backup'
local BACKUP_FILE_PATH = FILE_PATH_PATTERN:format(BACKUP_FILE_NAME)
local TMP_FILE_NAME = 'tmp-test'
local TMP_FILE_PATH = FILE_PATH_PATTERN:format(TMP_FILE_NAME)
local function test_wrapper(test_fn)
-- backup and clear active orders
dfhack.run_command_silent{'orders', 'export', BACKUP_FILE_NAME}
dfhack.run_command_silent{'orders', 'clear'}
df.global.world.manager_order_next_id = 0
return dfhack.with_finalize(
function()
-- clear test orders, restore original orders, remove temp files
dfhack.run_command_silent{'orders', 'clear'}
df.global.world.manager_order_next_id = 0
dfhack.run_command_silent{'orders', 'import', BACKUP_FILE_NAME}
df.global.world.manager_order_next_id =
#df.global.world.manager_orders
os.remove(BACKUP_FILE_PATH)
os.remove(TMP_FILE_PATH)
end,
function()
return dfhack.run_command_silent{'orders', 'import', TMP_FILE_NAME}
end
)
test_fn)
end
config.wrapper = test_wrapper
function check_import_success(file_content)
local output, result = run_orders_import(file_content)
-- returns export command result and exported file content
function run_orders_export()
local _, result = dfhack.run_command_silent{'orders', 'export',
TMP_FILE_NAME}
local f = io.open(TMP_FILE_PATH, 'r')
return dfhack.with_finalize(
function() f:close() end,
function() return result, f:read('*all') end)
end
function run_orders_import(file_content)
local f = io.open(TMP_FILE_PATH, 'w')
f:write(file_content)
f:close()
return dfhack.run_command_silent{'orders', 'import', TMP_FILE_NAME}
end
local function normalize_whitespace(str)
return str:gsub('%s+', ' '):trim()
end
function check_export_success(expected_file_content)
local result, file_content = run_orders_export()
expect.eq(result, CR_OK)
-- ignore whitespace (otherwise the expected file content is impossible to
-- format properly in this file)
expect.eq(normalize_whitespace(expected_file_content),
normalize_whitespace(file_content))
end
function check_import_success(file_content, comment, num_expected_orders)
local prev_num_orders = #df.global.world.manager_orders
local output, result = run_orders_import(file_content)
expect.eq(result, CR_OK, comment)
expect.eq(prev_num_orders + num_expected_orders,
#df.global.world.manager_orders, comment)
end
function check_import_fail(file_content, comment, prefix)
@ -35,7 +79,7 @@ function check_import_fail(file_content, comment, prefix)
end
function test.import_empty()
check_import_success('[]')
check_import_success('[]', 'empty input', 0)
end
function test.import_non_array()
@ -121,3 +165,96 @@ function test.import_valid_and_invalid_orders()
]
]], 'empty order before valid order')
end
function test.import_export_reaction_condition()
local file_content = [[
[
{
"amount_left" : 1,
"amount_total" : 1,
"frequency" : "Daily",
"id" : 0,
"is_active" : false,
"is_validated" : false,
"item_conditions" :
[
{
"condition" : "AtLeast",
"contains" :
[
"lye"
],
"reaction_id" : "MAKE_SOAP_FROM_TALLOW",
"value" : 5
}
],
"job" : "CustomReaction",
"reaction" : "MAKE_SOAP_FROM_TALLOW"
}
]
]]
check_import_success(file_content, 'valid reaction condition', 1)
check_export_success(file_content)
end
local function get_last_order()
return df.global.world.manager_orders[#df.global.world.manager_orders-1]
end
function test.import_invalid_reaction_conditions()
check_import_success([[
[
{
"amount_left" : 1,
"amount_total" : 1,
"frequency" : "OneTime",
"id" : 0,
"is_active" : false,
"is_validated" : true,
"item_conditions" :
[
{
"condition" : "AtLeast",
"contains" :
[
"lye"
],
"reaction_id" : "MAKE_SOAP_FROM_TALLOW_xxx",
"value" : 5
}
],
"job" : "CustomReaction",
"reaction" : "MAKE_SOAP_FROM_TALLOW"
}
]
]], 'condition ignored for bad reaction id', 1)
expect.eq(0, #get_last_order().item_conditions)
check_import_success([[
[
{
"amount_left" : 1,
"amount_total" : 1,
"frequency" : "OneTime",
"id" : 0,
"is_active" : false,
"is_validated" : true,
"item_conditions" :
[
{
"condition" : "AtLeast",
"contains" :
[
"lye_xxx"
],
"reaction_id" : "MAKE_SOAP_FROM_TALLOW",
"value" : 5
}
],
"job" : "CustomReaction",
"reaction" : "MAKE_SOAP_FROM_TALLOW"
}
]
]], 'condition ignored for bad reagent name', 1)
expect.eq(0, #get_last_order().item_conditions)
end