Merge remote-tracking branch 'ramblurr/stockpiles'
Also update stockpiles plugin to use REQUIRE_GLOBAL() Conflicts: plugins/stockpiles.cppdevelop
commit
6e36b224d1
@ -0,0 +1,199 @@
|
|||||||
|
local _ENV = mkmodule('plugins.stockpiles')
|
||||||
|
|
||||||
|
--[[
|
||||||
|
|
||||||
|
Native functions:
|
||||||
|
|
||||||
|
* stockpiles_list_settings(dir_path), list files in directory
|
||||||
|
* stockpiles_load(file), with full path
|
||||||
|
* stockpiles_save(file), with full path
|
||||||
|
* isEnabled()
|
||||||
|
|
||||||
|
--]]
|
||||||
|
--
|
||||||
|
function safe_require(module)
|
||||||
|
local status, module = pcall(require, module)
|
||||||
|
return status and module or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local gui = require 'gui'
|
||||||
|
local widgets = require('gui.widgets')
|
||||||
|
local dlg = require('gui.dialogs')
|
||||||
|
local script = require 'gui.script'
|
||||||
|
local persist = safe_require('persist-table')
|
||||||
|
|
||||||
|
|
||||||
|
function ListFilterDialog(args)
|
||||||
|
args.text = args.prompt or 'Type or select an option'
|
||||||
|
args.text_pen = COLOR_WHITE
|
||||||
|
args.with_filter = true
|
||||||
|
args.icon_width = 2
|
||||||
|
|
||||||
|
local choices = {}
|
||||||
|
|
||||||
|
if not args.hide_none then
|
||||||
|
table.insert(choices, {
|
||||||
|
icon = '?', text = args.none_caption or 'none',
|
||||||
|
index = -1, name = -1
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local filter = args.item_filter
|
||||||
|
|
||||||
|
for i,v in ipairs(args.items) do
|
||||||
|
if not filter or filter(v,-1) then
|
||||||
|
local name = v
|
||||||
|
local icon
|
||||||
|
table.insert(choices, {
|
||||||
|
icon = icon, search_key = string.lower(name), text = name, index = i
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
args.choices = choices
|
||||||
|
|
||||||
|
if args.on_select then
|
||||||
|
local cb = args.on_select
|
||||||
|
args.on_select = function(idx, obj)
|
||||||
|
return cb(obj.index, args.items[obj.index])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return dlg.ListBox(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
function showFilterPrompt(title, list, text,item_filter,hide_none)
|
||||||
|
ListFilterDialog{
|
||||||
|
frame_title=title,
|
||||||
|
items=list,
|
||||||
|
prompt=text,
|
||||||
|
item_filter=item_filter,
|
||||||
|
hide_none=hide_none,
|
||||||
|
on_select=script.mkresume(true),
|
||||||
|
on_cancel=script.mkresume(false),
|
||||||
|
on_close=script.qresume(nil)
|
||||||
|
}:show()
|
||||||
|
|
||||||
|
return script.wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
function init()
|
||||||
|
if persist == nil then return end
|
||||||
|
if dfhack.isMapLoaded() then
|
||||||
|
if persist.GlobalTable.stockpiles == nil then
|
||||||
|
persist.GlobalTable.stockpiles = {}
|
||||||
|
persist.GlobalTable.stockpiles['settings_path'] = './stocksettings'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tablify(iterableObject)
|
||||||
|
t={}
|
||||||
|
for k,v in ipairs(iterableObject) do
|
||||||
|
t[k] = v~=nil and v or 'nil'
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function load_settings()
|
||||||
|
init()
|
||||||
|
local path = get_path()
|
||||||
|
local ok, list = pcall(stockpiles_list_settings, path)
|
||||||
|
if not ok then
|
||||||
|
show_message_box("Stockpile Settings", "The stockpile settings folder doesn't exist.", true)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if #list == 0 then
|
||||||
|
show_message_box("Stockpile Settings", "There are no saved stockpile settings.", true)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local choice_list = {}
|
||||||
|
for i,v in ipairs(list) do
|
||||||
|
choice_list[i] = string.gsub(v, "/", "/ ")
|
||||||
|
choice_list[i] = string.gsub(choice_list[i], "-", " - ")
|
||||||
|
choice_list[i] = string.gsub(choice_list[i], "_", " ")
|
||||||
|
end
|
||||||
|
|
||||||
|
script.start(function()
|
||||||
|
local ok2,index,name=showFilterPrompt('Stockpile Settings', choice_list, 'Choose a stockpile', function(item) return true end, true)
|
||||||
|
if ok2 then
|
||||||
|
local filename = list[index];
|
||||||
|
stockpiles_load(path..'/'..filename)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function save_settings(stockpile)
|
||||||
|
init()
|
||||||
|
script.start(function()
|
||||||
|
local suggested = stockpile.name
|
||||||
|
if #suggested == 0 then
|
||||||
|
suggested = 'Stock1'
|
||||||
|
end
|
||||||
|
local path = get_path()
|
||||||
|
local sok,filename = script.showInputPrompt('Stockpile Settings', 'Enter stockpile name', COLOR_WHITE, suggested)
|
||||||
|
if sok then
|
||||||
|
if filename == nil or filename == '' then
|
||||||
|
script.showMessage('Stockpile Settings', 'Invalid File Name', COLOR_RED)
|
||||||
|
else
|
||||||
|
print("saving...", path..'/'..filename)
|
||||||
|
stockpiles_save(path..'/'..filename)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function manage_settings(sp)
|
||||||
|
init()
|
||||||
|
if not guard() then return false end
|
||||||
|
script.start(function()
|
||||||
|
local list = {'Load', 'Save'}
|
||||||
|
local tok,i = script.showListPrompt('Stockpile Settings','Load or Save Settings?',COLOR_WHITE,tablify(list))
|
||||||
|
if tok then
|
||||||
|
if i == 1 then
|
||||||
|
load_settings()
|
||||||
|
else
|
||||||
|
save_settings(sp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function show_message_box(title, msg, iserror)
|
||||||
|
local color = COLOR_WHITE
|
||||||
|
if iserror then
|
||||||
|
color = COLOR_RED
|
||||||
|
end
|
||||||
|
script.start(function()
|
||||||
|
script.showMessage(title, msg, color)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function guard()
|
||||||
|
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Stockpile') then
|
||||||
|
qerror("This script requires a stockpile selected in the 'q' mode")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function set_path(path)
|
||||||
|
init()
|
||||||
|
if persist == nil then
|
||||||
|
qerror("This version of DFHack doesn't support setting the stockpile settings path. Sorry.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
persist.GlobalTable.stockpiles['settings_path'] = path
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_path()
|
||||||
|
init()
|
||||||
|
if persist == nil then
|
||||||
|
return "stocksettings"
|
||||||
|
end
|
||||||
|
return persist.GlobalTable.stockpiles['settings_path']
|
||||||
|
end
|
||||||
|
|
||||||
|
return _ENV
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,44 @@
|
|||||||
|
PROJECT(stockpiles)
|
||||||
|
|
||||||
|
# add *our* headers here.
|
||||||
|
SET(PROJECT_HDRS
|
||||||
|
StockpileUtils.h
|
||||||
|
OrganicMatLookup.h
|
||||||
|
StockpileSerializer.h
|
||||||
|
)
|
||||||
|
|
||||||
|
SET(PROJECT_SRCS
|
||||||
|
OrganicMatLookup.cpp
|
||||||
|
StockpileSerializer.cpp
|
||||||
|
stockpiles.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
SET(PROJECT_PROTOS
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/proto/stockpiles.proto
|
||||||
|
)
|
||||||
|
|
||||||
|
#Create new lists of what sources and headers protoc will output after we invoke it
|
||||||
|
STRING(REPLACE ".proto" ".pb.cc;" PROJECT_PROTO_SRCS ${PROJECT_PROTOS})
|
||||||
|
STRING(REPLACE ".proto" ".pb.h;" PROJECT_PROTO_HDRS ${PROJECT_PROTOS})
|
||||||
|
|
||||||
|
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO_HDRS} PROPERTIES GENERATED TRUE)
|
||||||
|
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO_SRCS} PROPERTIES GENERATED TRUE)
|
||||||
|
|
||||||
|
LIST(APPEND PROJECT_HDRS ${PROJECT_PROTO_HDRS})
|
||||||
|
LIST(APPEND PROJECT_SRCS ${PROJECT_PROTO_SRCS})
|
||||||
|
|
||||||
|
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||||
|
LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS})
|
||||||
|
|
||||||
|
#Generate sources from our proto files and store them in the source tree
|
||||||
|
ADD_CUSTOM_COMMAND(
|
||||||
|
OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS}
|
||||||
|
COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/ ${PROJECT_PROTOS}
|
||||||
|
DEPENDS protoc-bin ${PROJECT_PROTOS}
|
||||||
|
)
|
||||||
|
|
||||||
|
IF(WIN32)
|
||||||
|
DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite lua)
|
||||||
|
ELSE()
|
||||||
|
DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite lua)
|
||||||
|
ENDIF()
|
@ -0,0 +1,155 @@
|
|||||||
|
#include "OrganicMatLookup.h"
|
||||||
|
|
||||||
|
#include "StockpileUtils.h"
|
||||||
|
|
||||||
|
#include "modules/Materials.h"
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/world_data.h"
|
||||||
|
|
||||||
|
#include "df/creature_raw.h"
|
||||||
|
#include "df/caste_raw.h"
|
||||||
|
#include "df/material.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
using df::global::world;
|
||||||
|
|
||||||
|
using std::endl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for mapping the various organic mats between their material indices
|
||||||
|
* and their index in the stockpile_settings structures.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void OrganicMatLookup::food_mat_by_idx ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, std::vector<int16_t>::size_type food_idx, FoodMat & food_mat )
|
||||||
|
{
|
||||||
|
out << "food_lookup: food_idx(" << food_idx << ") ";
|
||||||
|
df::world_raws &raws = world->raws;
|
||||||
|
df::special_mat_table table = raws.mat_table;
|
||||||
|
int32_t main_idx = table.organic_indexes[mat_category][food_idx];
|
||||||
|
int16_t type = table.organic_types[mat_category][food_idx];
|
||||||
|
if ( mat_category == organic_mat_category::Fish ||
|
||||||
|
mat_category == organic_mat_category::UnpreparedFish ||
|
||||||
|
mat_category == organic_mat_category::Eggs )
|
||||||
|
{
|
||||||
|
food_mat.creature = raws.creatures.all[type];
|
||||||
|
food_mat.caste = food_mat.creature->caste[main_idx];
|
||||||
|
out << " special creature type(" << type << ") caste("<< main_idx <<")" <<endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
food_mat.material.decode ( type, main_idx );
|
||||||
|
out << " type(" << type << ") index("<< main_idx <<") token(" << food_mat.material.getToken() << ")" << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string OrganicMatLookup::food_token_by_idx ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, std::vector<int16_t>::size_type idx )
|
||||||
|
{
|
||||||
|
FoodMat food_mat;
|
||||||
|
food_mat_by_idx ( out, mat_category, idx, food_mat );
|
||||||
|
if ( food_mat.material.isValid() )
|
||||||
|
{
|
||||||
|
return food_mat.material.getToken();
|
||||||
|
}
|
||||||
|
else if ( food_mat.creature )
|
||||||
|
{
|
||||||
|
return food_mat.creature->creature_id + ":" + food_mat.caste->caste_id;
|
||||||
|
}
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t OrganicMatLookup::food_max_size ( organic_mat_category::organic_mat_category mat_category )
|
||||||
|
{
|
||||||
|
return world->raws.mat_table.organic_types[mat_category].size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrganicMatLookup::food_build_map ( std::ostream &out )
|
||||||
|
{
|
||||||
|
if ( index_built )
|
||||||
|
return;
|
||||||
|
df::world_raws &raws = world->raws;
|
||||||
|
df::special_mat_table table = raws.mat_table;
|
||||||
|
using df::enums::organic_mat_category::organic_mat_category;
|
||||||
|
df::enum_traits<organic_mat_category> traits;
|
||||||
|
for ( int32_t mat_category = traits.first_item_value; mat_category <= traits.last_item_value; ++mat_category )
|
||||||
|
{
|
||||||
|
for ( size_t i = 0; i < table.organic_indexes[mat_category].size(); ++i )
|
||||||
|
{
|
||||||
|
int16_t type = table.organic_types[mat_category].at ( i );
|
||||||
|
int32_t index = table.organic_indexes[mat_category].at ( i );
|
||||||
|
food_index[mat_category].insert ( std::make_pair ( std::make_pair ( type,index ), i ) ); // wtf.. only in c++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index_built = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t OrganicMatLookup::food_idx_by_token ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, const std::string & token )
|
||||||
|
{
|
||||||
|
int16_t food_idx = -1;
|
||||||
|
df::world_raws &raws = world->raws;
|
||||||
|
df::special_mat_table table = raws.mat_table;
|
||||||
|
out << "food_idx_by_token: ";
|
||||||
|
if ( mat_category == organic_mat_category::Fish ||
|
||||||
|
mat_category == organic_mat_category::UnpreparedFish ||
|
||||||
|
mat_category == organic_mat_category::Eggs )
|
||||||
|
{
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
split_string ( &tokens, token, ":" );
|
||||||
|
if ( tokens.size() != 2 )
|
||||||
|
{
|
||||||
|
out << "creature " << "invalid CREATURE:CASTE token: " << token << endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int16_t creature_idx = find_creature ( tokens[0] );
|
||||||
|
if ( creature_idx < 0 )
|
||||||
|
{
|
||||||
|
out << " creature invalid token " << tokens[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
food_idx = linear_index ( table.organic_types[mat_category], creature_idx );
|
||||||
|
if ( tokens[1] == "MALE" )
|
||||||
|
food_idx += 1;
|
||||||
|
if ( table.organic_types[mat_category][food_idx] == creature_idx )
|
||||||
|
out << "creature " << token << " caste " << tokens[1] << " creature_idx(" << creature_idx << ") food_idx("<< food_idx << ")" << endl;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out << "ERROR creature caste not found: " << token << " caste " << tokens[1] << " creature_idx(" << creature_idx << ") food_idx("<< food_idx << ")" << endl;
|
||||||
|
food_idx = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ( !index_built )
|
||||||
|
food_build_map ( out );
|
||||||
|
MaterialInfo mat_info = food_mat_by_token ( out, token );
|
||||||
|
int16_t type = mat_info.type;
|
||||||
|
int32_t index = mat_info.index;
|
||||||
|
int16_t food_idx2 = -1;
|
||||||
|
auto it = food_index[mat_category].find ( std::make_pair ( type, index ) );
|
||||||
|
if ( it != food_index[mat_category].end() )
|
||||||
|
{
|
||||||
|
out << "matinfo: " << token << " type(" << mat_info.type << ") idx(" << mat_info.index << ") food_idx(" << it->second << ")" << endl;
|
||||||
|
food_idx = it->second;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out << "matinfo: " << token << " type(" << mat_info.type << ") idx(" << mat_info.index << ") food_idx not found :(" << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return food_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialInfo OrganicMatLookup::food_mat_by_token ( std::ostream &out, const std::string & token )
|
||||||
|
{
|
||||||
|
MaterialInfo mat_info;
|
||||||
|
mat_info.find ( token );
|
||||||
|
return mat_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OrganicMatLookup::index_built = false;
|
||||||
|
std::vector<OrganicMatLookup::FoodMatMap> OrganicMatLookup::food_index = std::vector<OrganicMatLookup::FoodMatMap> ( 37 );
|
@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "modules/Materials.h"
|
||||||
|
|
||||||
|
#include "df/organic_mat_category.h"
|
||||||
|
|
||||||
|
namespace df {
|
||||||
|
struct creature_raw;
|
||||||
|
struct caste_raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for mapping the various organic mats between their material indices
|
||||||
|
* and their index in the stockpile_settings structures.
|
||||||
|
*/
|
||||||
|
class OrganicMatLookup
|
||||||
|
{
|
||||||
|
|
||||||
|
// pair of material type and index
|
||||||
|
typedef std::pair<int16_t, int32_t> FoodMatPair;
|
||||||
|
// map for using type,index pairs to find the food index
|
||||||
|
typedef std::map<FoodMatPair, size_t> FoodMatMap;
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct FoodMat
|
||||||
|
{
|
||||||
|
DFHack::MaterialInfo material;
|
||||||
|
df::creature_raw *creature;
|
||||||
|
df::caste_raw * caste;
|
||||||
|
FoodMat() : material ( -1 ), creature ( 0 ), caste ( 0 ) {}
|
||||||
|
};
|
||||||
|
static void food_mat_by_idx ( std::ostream &out, df::enums::organic_mat_category::organic_mat_category mat_category, std::vector<int16_t>::size_type food_idx, FoodMat & food_mat );
|
||||||
|
static std::string food_token_by_idx ( std::ostream &out, df::enums::organic_mat_category::organic_mat_category mat_category, std::vector<int16_t>::size_type idx );
|
||||||
|
|
||||||
|
static size_t food_max_size ( df::enums::organic_mat_category::organic_mat_category mat_category );
|
||||||
|
static void food_build_map ( std::ostream &out );
|
||||||
|
|
||||||
|
static int16_t food_idx_by_token ( std::ostream &out, df::enums::organic_mat_category::organic_mat_category mat_category, const std::string & token );
|
||||||
|
|
||||||
|
static DFHack::MaterialInfo food_mat_by_token ( std::ostream &out, const std::string & token );
|
||||||
|
|
||||||
|
static bool index_built;
|
||||||
|
static std::vector<FoodMatMap> food_index;
|
||||||
|
private:
|
||||||
|
OrganicMatLookup();
|
||||||
|
};
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,349 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// stockpiles plugin
|
||||||
|
#include "proto/stockpiles.pb.h"
|
||||||
|
|
||||||
|
// dfhack
|
||||||
|
#include "modules/Materials.h"
|
||||||
|
#include "modules/Items.h"
|
||||||
|
|
||||||
|
// df
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/world_data.h"
|
||||||
|
#include "df/organic_mat_category.h"
|
||||||
|
#include "df/furniture_type.h"
|
||||||
|
#include "df/item_quality.h"
|
||||||
|
#include "df/item_type.h"
|
||||||
|
|
||||||
|
// stl
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <ostream>
|
||||||
|
#include <istream>
|
||||||
|
|
||||||
|
namespace df {
|
||||||
|
struct building_stockpilest;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Null buffer that acts like /dev/null for when debug is disabled
|
||||||
|
*/
|
||||||
|
class NullBuffer : public std::streambuf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int overflow ( int c );
|
||||||
|
};
|
||||||
|
|
||||||
|
class NullStream : public std::ostream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NullStream();
|
||||||
|
private:
|
||||||
|
NullBuffer m_sb;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for serializing the stockpile_settings structure into a Google protobuf
|
||||||
|
*/
|
||||||
|
class StockpileSerializer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @param out for debugging
|
||||||
|
* @param stockpile stockpile to read or write settings to
|
||||||
|
*/
|
||||||
|
|
||||||
|
StockpileSerializer ( df::building_stockpilest * stockpile );
|
||||||
|
|
||||||
|
~StockpileSerializer();
|
||||||
|
|
||||||
|
void enable_debug ( std::ostream &out );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since we depend on protobuf-lite, not the full lib, we copy this function from
|
||||||
|
* protobuf message.cc
|
||||||
|
*/
|
||||||
|
bool serialize_to_ostream(std::ostream* output);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will serialize stockpile settings to a file (overwrites existing files)
|
||||||
|
* @return success/failure
|
||||||
|
*/
|
||||||
|
bool serialize_to_file ( const std::string & file );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Again, copied from message.cc
|
||||||
|
*/
|
||||||
|
bool parse_from_istream(std::istream* input);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read stockpile settings from file
|
||||||
|
*/
|
||||||
|
bool unserialize_from_file ( const std::string & file );
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool mDebug;
|
||||||
|
std::ostream * mOut;
|
||||||
|
NullStream mNull;
|
||||||
|
df::building_stockpilest * mPile;
|
||||||
|
dfstockpiles::StockpileSettings mBuffer;
|
||||||
|
std::map<int, std::string> mOtherMatsFurniture;
|
||||||
|
std::map<int, std::string> mOtherMatsFinishedGoods;
|
||||||
|
std::map<int, std::string> mOtherMatsBars;
|
||||||
|
std::map<int, std::string> mOtherMatsBlocks;
|
||||||
|
std::map<int, std::string> mOtherMatsWeaponsArmor;
|
||||||
|
|
||||||
|
|
||||||
|
std::ostream & debug();
|
||||||
|
|
||||||
|
/**
|
||||||
|
read memory structures and serialize to protobuf
|
||||||
|
*/
|
||||||
|
void write();
|
||||||
|
|
||||||
|
// parse serialized data into ui indices
|
||||||
|
void read ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an enum's value based off the string label.
|
||||||
|
* @param traits the enum's trait struct
|
||||||
|
* @param token the string value in key_table
|
||||||
|
* @return the enum's value, -1 if not found
|
||||||
|
*/
|
||||||
|
template<typename E>
|
||||||
|
static typename df::enum_traits<E>::base_type linear_index ( std::ostream & out, df::enum_traits<E> traits, const std::string &token )
|
||||||
|
{
|
||||||
|
auto j = traits.first_item_value;
|
||||||
|
auto limit = traits.last_item_value;
|
||||||
|
// sometimes enums start at -1, which is bad news for array indexing
|
||||||
|
if ( j < 0 )
|
||||||
|
{
|
||||||
|
j += abs ( traits.first_item_value );
|
||||||
|
limit += abs ( traits.first_item_value );
|
||||||
|
}
|
||||||
|
for ( ; j <= limit; ++j )
|
||||||
|
{
|
||||||
|
// out << " linear_index("<< token <<") = table["<<j<<"/"<<limit<<"]: " <<traits.key_table[j] << endl;
|
||||||
|
if ( token.compare ( traits.key_table[j] ) == 0 )
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the token from the serailized list during import
|
||||||
|
typedef std::function<std::string ( const size_t& ) > FuncReadImport;
|
||||||
|
// add the token to the serialized list during export
|
||||||
|
typedef std::function<void ( const std::string & ) > FuncWriteExport;
|
||||||
|
// are item's of item_type allowed?
|
||||||
|
typedef std::function<bool ( df::enums::item_type::item_type ) > FuncItemAllowed;
|
||||||
|
// is this material allowed?
|
||||||
|
typedef std::function<bool ( const DFHack::MaterialInfo & ) > FuncMaterialAllowed;
|
||||||
|
|
||||||
|
// convenient struct for parsing food stockpile items
|
||||||
|
struct food_pair
|
||||||
|
{
|
||||||
|
// exporting
|
||||||
|
FuncWriteExport set_value;
|
||||||
|
std::vector<char> * stockpile_values;
|
||||||
|
// importing
|
||||||
|
FuncReadImport get_value;
|
||||||
|
size_t serialized_count;
|
||||||
|
bool valid;
|
||||||
|
|
||||||
|
food_pair ( FuncWriteExport s, std::vector<char>* sp_v, FuncReadImport g, size_t count )
|
||||||
|
: set_value ( s )
|
||||||
|
, stockpile_values ( sp_v )
|
||||||
|
, get_value ( g )
|
||||||
|
, serialized_count ( count )
|
||||||
|
, valid ( true )
|
||||||
|
{}
|
||||||
|
food_pair(): valid( false ) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are many repeated (un)serialization cases throughout the stockpile_settings structure,
|
||||||
|
* so the most common cases have been generalized into generic functions using lambdas.
|
||||||
|
*
|
||||||
|
* The basic process to serialize a stockpile_settings structure is:
|
||||||
|
* 1. loop through the list
|
||||||
|
* 2. for every element that is TRUE:
|
||||||
|
* 3. map the specific stockpile_settings index into a general material, creature, etc index
|
||||||
|
* 4. verify that type is allowed in the list (e.g., no stone in gems stockpiles)
|
||||||
|
* 5. add it to the protobuf using FuncWriteExport
|
||||||
|
*
|
||||||
|
* The unserialization process is the same in reverse.
|
||||||
|
*/
|
||||||
|
void serialize_list_organic_mat ( FuncWriteExport add_value, const std::vector<char> * list, df::enums::organic_mat_category::organic_mat_category cat );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void unserialize_list_organic_mat ( FuncReadImport get_value, size_t list_size, std::vector<char> *pile_list, df::enums::organic_mat_category::organic_mat_category cat );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void serialize_list_item_type ( FuncItemAllowed is_allowed, FuncWriteExport add_value, const std::vector<char> &list );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void unserialize_list_item_type ( FuncItemAllowed is_allowed, FuncReadImport read_value, int32_t list_size, std::vector<char> *pile_list );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void serialize_list_material ( FuncMaterialAllowed is_allowed, FuncWriteExport add_value, const std::vector<char> &list );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void unserialize_list_material ( FuncMaterialAllowed is_allowed, FuncReadImport read_value, int32_t list_size, std::vector<char> *pile_list );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void serialize_list_quality ( FuncWriteExport add_value, const bool ( &quality_list ) [7] );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all values in a bool[7] to false
|
||||||
|
*/
|
||||||
|
void quality_clear ( bool ( &pile_list ) [7] );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void unserialize_list_quality ( FuncReadImport read_value, int32_t list_size, bool ( &pile_list ) [7] );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void serialize_list_other_mats ( const std::map<int, std::string> other_mats, FuncWriteExport add_value, std::vector<char> list );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void unserialize_list_other_mats ( const std::map<int, std::string> other_mats, FuncReadImport read_value, int32_t list_size, std::vector<char> *pile_list );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void serialize_list_itemdef ( FuncWriteExport add_value, std::vector<char> list, std::vector<df::itemdef *> items, df::enums::item_type::item_type type );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see serialize_list_organic_mat
|
||||||
|
*/
|
||||||
|
void unserialize_list_itemdef ( FuncReadImport read_value, int32_t list_size, std::vector<char> *pile_list, df::enums::item_type::item_type type );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of other_materials and an index, return its corresponding token
|
||||||
|
* @return empty string if not found
|
||||||
|
* @see other_mats_token
|
||||||
|
*/
|
||||||
|
std::string other_mats_index ( const std::map<int, std::string> other_mats, int idx );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of other_materials and a token, return its corresponding index
|
||||||
|
* @return -1 if not found
|
||||||
|
* @see other_mats_index
|
||||||
|
*/
|
||||||
|
int other_mats_token ( const std::map<int, std::string> other_mats, const std::string & token );
|
||||||
|
|
||||||
|
void write_general();
|
||||||
|
void read_general();
|
||||||
|
|
||||||
|
|
||||||
|
void write_animals();
|
||||||
|
void read_animals();
|
||||||
|
|
||||||
|
|
||||||
|
food_pair food_map ( df::enums::organic_mat_category::organic_mat_category cat );
|
||||||
|
|
||||||
|
|
||||||
|
void write_food();
|
||||||
|
void read_food();
|
||||||
|
|
||||||
|
void furniture_setup_other_mats();
|
||||||
|
void write_furniture();
|
||||||
|
bool furniture_mat_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
void read_furniture();
|
||||||
|
|
||||||
|
bool refuse_creature_is_allowed ( const df::creature_raw *raw );
|
||||||
|
|
||||||
|
|
||||||
|
void refuse_write_helper ( std::function<void ( const std::string & ) > add_value, const std::vector<char> & list );
|
||||||
|
|
||||||
|
|
||||||
|
bool refuse_type_is_allowed ( df::enums::item_type::item_type type );
|
||||||
|
|
||||||
|
|
||||||
|
void write_refuse();
|
||||||
|
void refuse_read_helper ( std::function<std::string ( const size_t& ) > get_value, size_t list_size, std::vector<char>* pile_list );
|
||||||
|
|
||||||
|
void read_refuse();
|
||||||
|
|
||||||
|
bool stone_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
|
||||||
|
void write_stone();
|
||||||
|
|
||||||
|
void read_stone();
|
||||||
|
|
||||||
|
bool ammo_mat_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
|
||||||
|
void write_ammo();
|
||||||
|
void read_ammo();
|
||||||
|
bool coins_mat_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
|
||||||
|
void write_coins();
|
||||||
|
|
||||||
|
void read_coins();
|
||||||
|
|
||||||
|
void bars_blocks_setup_other_mats();
|
||||||
|
|
||||||
|
bool bars_mat_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
|
||||||
|
bool blocks_mat_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
|
||||||
|
void write_bars_blocks();
|
||||||
|
void read_bars_blocks();
|
||||||
|
bool gem_mat_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
bool gem_cut_mat_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
bool gem_other_mat_is_allowed(DFHack::MaterialInfo &mi );
|
||||||
|
|
||||||
|
void write_gems();
|
||||||
|
|
||||||
|
void read_gems();
|
||||||
|
|
||||||
|
bool finished_goods_type_is_allowed ( df::enums::item_type::item_type type );
|
||||||
|
void finished_goods_setup_other_mats();
|
||||||
|
bool finished_goods_mat_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
void write_finished_goods();
|
||||||
|
void read_finished_goods();
|
||||||
|
void write_leather();
|
||||||
|
void read_leather();
|
||||||
|
void write_cloth();
|
||||||
|
void read_cloth();
|
||||||
|
bool wood_mat_is_allowed ( const df::plant_raw * plant );
|
||||||
|
void write_wood();
|
||||||
|
void read_wood();
|
||||||
|
bool weapons_mat_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
void write_weapons();
|
||||||
|
void read_weapons();
|
||||||
|
void weapons_armor_setup_other_mats();
|
||||||
|
bool armor_mat_is_allowed ( const DFHack::MaterialInfo &mi );
|
||||||
|
void write_armor();
|
||||||
|
void read_armor();
|
||||||
|
|
||||||
|
};
|
@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/world_data.h"
|
||||||
|
#include "df/creature_raw.h"
|
||||||
|
#include "df/plant_raw.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
// os
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
// Utility Functions {{{
|
||||||
|
// A set of convenience functions for doing common lookups
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve creature raw from index
|
||||||
|
*/
|
||||||
|
static df::creature_raw* find_creature ( int32_t idx )
|
||||||
|
{
|
||||||
|
return df::global::world->raws.creatures.all[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve creature index from id string
|
||||||
|
* @return -1 if not found
|
||||||
|
*/
|
||||||
|
static int16_t find_creature ( const std::string &creature_id )
|
||||||
|
{
|
||||||
|
return linear_index ( df::global::world->raws.creatures.all, &df::creature_raw::creature_id, creature_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve plant raw from index
|
||||||
|
*/
|
||||||
|
static df::plant_raw* find_plant ( size_t idx )
|
||||||
|
{
|
||||||
|
return df::global::world->raws.plants.all[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve plant index from id string
|
||||||
|
* @return -1 if not found
|
||||||
|
*/
|
||||||
|
static size_t find_plant ( const std::string &plant_id )
|
||||||
|
{
|
||||||
|
return linear_index ( df::global::world->raws.plants.all, &df::plant_raw::id, plant_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
struct less_than_no_case: public std::binary_function< char,char,bool >
|
||||||
|
{
|
||||||
|
bool operator () (char x, char y) const
|
||||||
|
{
|
||||||
|
return toupper( static_cast< unsigned char >(x)) < toupper( static_cast< unsigned char >(y));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool CompareNoCase(const std::string &a, const std::string &b)
|
||||||
|
{
|
||||||
|
return std::lexicographical_compare( a.begin(),a.end(), b.begin(),b.end(), less_than_no_case() );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the parameter has the dfstock extension.
|
||||||
|
* Doesn't check if the file exists or not.
|
||||||
|
*/
|
||||||
|
static bool is_dfstockfile ( const std::string& filename )
|
||||||
|
{
|
||||||
|
return filename.rfind ( ".dfstock" ) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}} utility Functions
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
|||||||
|
*.pb.cc
|
||||||
|
*.pb.cc.rule
|
||||||
|
*.pb.h
|
@ -0,0 +1,562 @@
|
|||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "DataFuncs.h"
|
||||||
|
#include "LuaTools.h"
|
||||||
|
#include "modules/Filesystem.h"
|
||||||
|
|
||||||
|
#include "../uicommon.h"
|
||||||
|
|
||||||
|
#include "StockpileUtils.h"
|
||||||
|
#include "StockpileSerializer.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "modules/Filesystem.h"
|
||||||
|
#include "modules/Gui.h"
|
||||||
|
#include "modules/Filesystem.h"
|
||||||
|
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/world_data.h"
|
||||||
|
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "df/ui.h"
|
||||||
|
#include "df/building_stockpilest.h"
|
||||||
|
#include "df/stockpile_settings.h"
|
||||||
|
#include "df/global_objects.h"
|
||||||
|
#include "df/viewscreen_dwarfmodest.h"
|
||||||
|
|
||||||
|
// stl
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using std::vector;
|
||||||
|
using std::string;
|
||||||
|
using std::endl;
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
using namespace google::protobuf;
|
||||||
|
using namespace dfstockpiles;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN ( "stockpiles" );
|
||||||
|
REQUIRE_GLOBAL(world);
|
||||||
|
REQUIRE_GLOBAL(ui);
|
||||||
|
REQUIRE_GLOBAL(selection_rect);
|
||||||
|
|
||||||
|
using df::building_stockpilest;
|
||||||
|
using std::placeholders::_1;
|
||||||
|
|
||||||
|
static command_result copystock ( color_ostream &out, vector <string> & parameters );
|
||||||
|
static bool copystock_guard ( df::viewscreen *top );
|
||||||
|
|
||||||
|
static command_result savestock ( color_ostream &out, vector <string> & parameters );
|
||||||
|
static bool savestock_guard ( df::viewscreen *top );
|
||||||
|
|
||||||
|
static command_result loadstock ( color_ostream &out, vector <string> & parameters );
|
||||||
|
static bool loadstock_guard ( df::viewscreen *top );
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands )
|
||||||
|
{
|
||||||
|
if ( world && ui )
|
||||||
|
{
|
||||||
|
commands.push_back (
|
||||||
|
PluginCommand (
|
||||||
|
"copystock", "Copy stockpile under cursor.",
|
||||||
|
copystock, copystock_guard,
|
||||||
|
" - In 'q' or 't' mode: select a stockpile and invoke in order\n"
|
||||||
|
" to switch to the 'p' stockpile creation mode, and initialize\n"
|
||||||
|
" the custom settings from the selected stockpile.\n"
|
||||||
|
" - In 'p': invoke in order to switch back to 'q'.\n"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
commands.push_back (
|
||||||
|
PluginCommand (
|
||||||
|
"savestock", "Save the active stockpile's settings to a file.",
|
||||||
|
savestock, savestock_guard,
|
||||||
|
"Must be in 'q' mode and have a stockpile selected.\n"
|
||||||
|
"example: 'savestock food.dfstock' will save the settings to 'food.dfstock'\n"
|
||||||
|
"in your stockpile folder.\n"
|
||||||
|
"Omitting the filename will result in text output directly to the console\n\n"
|
||||||
|
" -d, --debug: enable debug output\n"
|
||||||
|
" <filename> : filename to save stockpile settings to (will be overwritten!)\n"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
commands.push_back (
|
||||||
|
PluginCommand (
|
||||||
|
"loadstock", "Load settings from a file and apply them to the active stockpile.",
|
||||||
|
loadstock, loadstock_guard,
|
||||||
|
"Must be in 'q' mode and have a stockpile selected.\n"
|
||||||
|
"example: 'loadstock food.dfstock' will load the settings from 'food.dfstock'\n"
|
||||||
|
"in your stockpile folder and apply them to the selected stockpile.\n"
|
||||||
|
" -d, --debug: enable debug output\n"
|
||||||
|
" <filename> : filename to load stockpile settings from\n"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "world: " << sizeof ( df::world ) << " ui: " << sizeof ( df::ui )
|
||||||
|
<< " b_stock: " << sizeof ( building_stockpilest ) << endl;
|
||||||
|
|
||||||
|
if ( !Filesystem::isdir ( "stocksettings" ) )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event event )
|
||||||
|
{
|
||||||
|
switch ( event )
|
||||||
|
{
|
||||||
|
case SC_MAP_LOADED:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool copystock_guard ( df::viewscreen *top )
|
||||||
|
{
|
||||||
|
using namespace ui_sidebar_mode;
|
||||||
|
|
||||||
|
if ( !Gui::dwarfmode_hotkey ( top ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch ( ui->main.mode )
|
||||||
|
{
|
||||||
|
case Stockpiles:
|
||||||
|
return true;
|
||||||
|
case BuildingItems:
|
||||||
|
case QueryBuilding:
|
||||||
|
return !!virtual_cast<building_stockpilest> ( world->selected_building );
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static command_result copystock ( color_ostream &out, vector <string> & parameters )
|
||||||
|
{
|
||||||
|
// HOTKEY COMMAND: CORE ALREADY SUSPENDED
|
||||||
|
|
||||||
|
// For convenience: when used in the stockpiles mode, switch to 'q'
|
||||||
|
if ( ui->main.mode == ui_sidebar_mode::Stockpiles )
|
||||||
|
{
|
||||||
|
world->selected_building = NULL; // just in case it contains some kind of garbage
|
||||||
|
ui->main.mode = ui_sidebar_mode::QueryBuilding;
|
||||||
|
selection_rect->start_x = -30000;
|
||||||
|
|
||||||
|
out << "Switched back to query building." << endl;
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
building_stockpilest *sp = virtual_cast<building_stockpilest> ( world->selected_building );
|
||||||
|
if ( !sp )
|
||||||
|
{
|
||||||
|
out.printerr ( "Selected building isn't a stockpile.\n" );
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->stockpile.custom_settings = sp->settings;
|
||||||
|
ui->main.mode = ui_sidebar_mode::Stockpiles;
|
||||||
|
world->selected_stockpile_type = stockpile_category::Custom;
|
||||||
|
|
||||||
|
out << "Stockpile options copied." << endl;
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool savestock_guard ( df::viewscreen *top )
|
||||||
|
{
|
||||||
|
using namespace ui_sidebar_mode;
|
||||||
|
|
||||||
|
if ( !Gui::dwarfmode_hotkey ( top ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch ( ui->main.mode )
|
||||||
|
{
|
||||||
|
case Stockpiles:
|
||||||
|
return true;
|
||||||
|
case BuildingItems:
|
||||||
|
case QueryBuilding:
|
||||||
|
return !!virtual_cast<building_stockpilest> ( world->selected_building );
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool loadstock_guard ( df::viewscreen *top )
|
||||||
|
{
|
||||||
|
using namespace ui_sidebar_mode;
|
||||||
|
|
||||||
|
if ( !Gui::dwarfmode_hotkey ( top ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch ( ui->main.mode )
|
||||||
|
{
|
||||||
|
case Stockpiles:
|
||||||
|
return true;
|
||||||
|
case BuildingItems:
|
||||||
|
case QueryBuilding:
|
||||||
|
return !!virtual_cast<building_stockpilest> ( world->selected_building );
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exporting
|
||||||
|
static command_result savestock ( color_ostream &out, vector <string> & parameters )
|
||||||
|
{
|
||||||
|
building_stockpilest *sp = virtual_cast<building_stockpilest> ( world->selected_building );
|
||||||
|
if ( !sp )
|
||||||
|
{
|
||||||
|
out.printerr ( "Selected building isn't a stockpile.\n" );
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( parameters.size() > 2 )
|
||||||
|
{
|
||||||
|
out.printerr ( "Invalid parameters\n" );
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool debug = false;
|
||||||
|
std::string file;
|
||||||
|
for ( size_t i = 0; i < parameters.size(); ++i )
|
||||||
|
{
|
||||||
|
const std::string o = parameters.at ( i );
|
||||||
|
if ( o == "--debug" || o == "-d" )
|
||||||
|
debug = true;
|
||||||
|
else if ( !o.empty() && o[0] != '-' )
|
||||||
|
{
|
||||||
|
file = o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( file.empty() )
|
||||||
|
{
|
||||||
|
out.printerr ( "You must supply a valid filename.\n" );
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
StockpileSerializer cereal ( sp );
|
||||||
|
if ( debug )
|
||||||
|
cereal.enable_debug ( out );
|
||||||
|
|
||||||
|
if ( !is_dfstockfile ( file ) ) file += ".dfstock";
|
||||||
|
if ( !cereal.serialize_to_file ( file ) )
|
||||||
|
{
|
||||||
|
out.printerr ( "serialize failed\n" );
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// importing
|
||||||
|
static command_result loadstock ( color_ostream &out, vector <string> & parameters )
|
||||||
|
{
|
||||||
|
building_stockpilest *sp = virtual_cast<building_stockpilest> ( world->selected_building );
|
||||||
|
if ( !sp )
|
||||||
|
{
|
||||||
|
out.printerr ( "Selected building isn't a stockpile.\n" );
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( parameters.size() < 1 || parameters.size() > 2 )
|
||||||
|
{
|
||||||
|
out.printerr ( "Invalid parameters\n" );
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool debug = false;
|
||||||
|
std::string file;
|
||||||
|
for ( size_t i = 0; i < parameters.size(); ++i )
|
||||||
|
{
|
||||||
|
const std::string o = parameters.at ( i );
|
||||||
|
if ( o == "--debug" || o == "-d" )
|
||||||
|
debug = true;
|
||||||
|
else if ( !o.empty() && o[0] != '-' )
|
||||||
|
{
|
||||||
|
file = o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( file.empty() ) {
|
||||||
|
out.printerr ( "ERROR: missing .dfstock file parameter\n");
|
||||||
|
return DFHack::CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
if ( !is_dfstockfile ( file ) )
|
||||||
|
file += ".dfstock";
|
||||||
|
if ( !Filesystem::exists ( file ) )
|
||||||
|
{
|
||||||
|
out.printerr ( "ERROR: the .dfstock file doesn't exist: %s\n", file.c_str());
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
StockpileSerializer cereal ( sp );
|
||||||
|
if ( debug )
|
||||||
|
cereal.enable_debug ( out );
|
||||||
|
if ( !cereal.unserialize_from_file ( file ) )
|
||||||
|
{
|
||||||
|
out.printerr ( "unserialization failed\n" );
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calls the lua function manage_settings() to kickoff the GUI
|
||||||
|
*/
|
||||||
|
bool manage_settings ( building_stockpilest *sp )
|
||||||
|
{
|
||||||
|
auto L = Lua::Core::State;
|
||||||
|
color_ostream_proxy out ( Core::getInstance().getConsole() );
|
||||||
|
|
||||||
|
CoreSuspendClaimer suspend;
|
||||||
|
Lua::StackUnwinder top ( L );
|
||||||
|
|
||||||
|
if ( !lua_checkstack ( L, 2 ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( !Lua::PushModulePublic ( out, L, "plugins.stockpiles", "manage_settings" ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Lua::Push ( L, sp );
|
||||||
|
|
||||||
|
if ( !Lua::SafeCall ( out, L, 1, 2 ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool show_message_box ( const std::string & title, const std::string & msg, bool is_error = false )
|
||||||
|
{
|
||||||
|
auto L = Lua::Core::State;
|
||||||
|
color_ostream_proxy out ( Core::getInstance().getConsole() );
|
||||||
|
|
||||||
|
CoreSuspendClaimer suspend;
|
||||||
|
Lua::StackUnwinder top ( L );
|
||||||
|
|
||||||
|
if ( !lua_checkstack ( L, 4 ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( !Lua::PushModulePublic ( out, L, "plugins.stockpiles", "show_message_box" ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Lua::Push ( L, title );
|
||||||
|
Lua::Push ( L, msg );
|
||||||
|
Lua::Push ( L, is_error );
|
||||||
|
|
||||||
|
if ( !Lua::SafeCall ( out, L, 3, 0 ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stockpiles_import_hook : public df::viewscreen_dwarfmodest
|
||||||
|
{
|
||||||
|
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||||
|
|
||||||
|
bool handleInput ( set<df::interface_key> *input )
|
||||||
|
{
|
||||||
|
df::building_stockpilest *sp = get_selected_stockpile();
|
||||||
|
if ( !sp )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( input->count ( interface_key::CUSTOM_L ) )
|
||||||
|
{
|
||||||
|
manage_settings ( sp );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_VMETHOD_INTERPOSE ( void, feed, ( set<df::interface_key> *input ) )
|
||||||
|
{
|
||||||
|
if ( !handleInput ( input ) )
|
||||||
|
INTERPOSE_NEXT ( feed ) ( input );
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_VMETHOD_INTERPOSE ( void, render, () )
|
||||||
|
{
|
||||||
|
INTERPOSE_NEXT ( render ) ();
|
||||||
|
|
||||||
|
df::building_stockpilest *sp = get_selected_stockpile();
|
||||||
|
if ( !sp )
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto dims = Gui::getDwarfmodeViewDims();
|
||||||
|
int left_margin = dims.menu_x1 + 1;
|
||||||
|
int x = left_margin;
|
||||||
|
int y = dims.y2 - 7;
|
||||||
|
|
||||||
|
int links = 0;
|
||||||
|
links += sp->links.give_to_pile.size();
|
||||||
|
links += sp->links.take_from_pile.size();
|
||||||
|
links += sp->links.give_to_workshop.size();
|
||||||
|
links += sp->links.take_from_workshop.size();
|
||||||
|
if ( links + 12 >= y )
|
||||||
|
{
|
||||||
|
y += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputHotkeyString ( x, y, "Load/Save Settings", "l", true, left_margin, COLOR_WHITE, COLOR_LIGHTRED );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IMPLEMENT_VMETHOD_INTERPOSE ( stockpiles_import_hook, feed );
|
||||||
|
IMPLEMENT_VMETHOD_INTERPOSE ( stockpiles_import_hook, render );
|
||||||
|
|
||||||
|
DFHACK_PLUGIN_IS_ENABLED ( is_enabled );
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable )
|
||||||
|
{
|
||||||
|
if ( !gps )
|
||||||
|
return CR_FAILURE;
|
||||||
|
|
||||||
|
if ( enable != is_enabled )
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
!INTERPOSE_HOOK ( stockpiles_import_hook, feed ).apply ( enable ) ||
|
||||||
|
!INTERPOSE_HOOK ( stockpiles_import_hook, render ).apply ( enable )
|
||||||
|
)
|
||||||
|
return CR_FAILURE;
|
||||||
|
|
||||||
|
is_enabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> list_dir ( const std::string &path, bool recursive = false )
|
||||||
|
{
|
||||||
|
// color_ostream_proxy out ( Core::getInstance().getConsole() );
|
||||||
|
std::vector<std::string> files;
|
||||||
|
std::stack<std::string> dirs;
|
||||||
|
dirs.push(path);
|
||||||
|
// out << "list_dir start" << endl;
|
||||||
|
while (!dirs.empty() ) {
|
||||||
|
const std::string current = dirs.top();
|
||||||
|
// out << "\t walking " << current << endl;
|
||||||
|
dirs.pop();
|
||||||
|
std::vector<std::string> entries;
|
||||||
|
const int res = DFHack::getdir(current, entries);
|
||||||
|
if ( res != 0 )
|
||||||
|
continue;
|
||||||
|
for ( std::vector<std::string>::iterator it = entries.begin() ; it != entries.end(); ++it )
|
||||||
|
{
|
||||||
|
if ( (*it).empty() || (*it)[0] == '.' ) continue;
|
||||||
|
// shitty cross platform c++ we've got to construct the actual path manually
|
||||||
|
std::ostringstream child_path_s;
|
||||||
|
child_path_s << current << "/" << *it;
|
||||||
|
const std::string child = child_path_s.str();
|
||||||
|
if ( recursive && Filesystem::isdir ( child ) )
|
||||||
|
{
|
||||||
|
// out << "\t\tgot child dir: " << child << endl;
|
||||||
|
dirs.push ( child );
|
||||||
|
}
|
||||||
|
else if ( Filesystem::isfile ( child ) )
|
||||||
|
{
|
||||||
|
const std::string rel_path ( child.substr ( std::string ( "./"+path).length()-1 ) );
|
||||||
|
// out << "\t\t adding file: " << child << " as " << rel_path << endl;
|
||||||
|
files.push_back ( rel_path );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// out << "listdir_stop" << endl;
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> clean_dfstock_list ( const std::string &path )
|
||||||
|
{
|
||||||
|
if ( !Filesystem::exists ( path ) )
|
||||||
|
{
|
||||||
|
return std::vector<std::string>();
|
||||||
|
}
|
||||||
|
std::vector<std::string> files ( list_dir ( path, true) );
|
||||||
|
files.erase ( std::remove_if ( files.begin(), files.end(), [] ( const std::string &f )
|
||||||
|
{
|
||||||
|
return !is_dfstockfile ( f );
|
||||||
|
} ), files.end() );
|
||||||
|
std::transform ( files.begin(), files.end(), files.begin(), [] ( const std::string &f )
|
||||||
|
{
|
||||||
|
return f.substr ( 0, f.find_last_of ( "." ) );
|
||||||
|
} );
|
||||||
|
std::sort ( files.begin(),files.end(), CompareNoCase );
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isEnabled( lua_State *L )
|
||||||
|
{
|
||||||
|
Lua::Push(L, is_enabled);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stockpiles_list_settings ( lua_State *L )
|
||||||
|
{
|
||||||
|
auto path = luaL_checkstring ( L, 1 );
|
||||||
|
if ( !Filesystem::exists ( path ) )
|
||||||
|
{
|
||||||
|
lua_pushfstring ( L, "stocksettings folder doesn't exist: %s", path );
|
||||||
|
lua_error ( L );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
color_ostream &out = *Lua::GetOutput ( L );
|
||||||
|
if ( !Filesystem::isdir(path) )
|
||||||
|
{
|
||||||
|
lua_pushfstring ( L, "stocksettings path invalid: %s", path );
|
||||||
|
lua_error ( L );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::vector<std::string> files = clean_dfstock_list ( path );
|
||||||
|
Lua::PushVector ( L, files, true );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stockpiles_load ( color_ostream &out, std::string filename )
|
||||||
|
{
|
||||||
|
out << "stockpiles_load " << filename << " ";
|
||||||
|
std::vector<std::string> params;
|
||||||
|
params.push_back ( filename );
|
||||||
|
command_result r = loadstock ( out, params );
|
||||||
|
out << " result = "<< r << endl;
|
||||||
|
if ( r != CR_OK )
|
||||||
|
show_message_box ( "Stockpile Settings Error", "Couldn't load. Does the folder exist?", true );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void stockpiles_save ( color_ostream &out, std::string filename )
|
||||||
|
{
|
||||||
|
|
||||||
|
out << "stockpiles_save " << filename << " ";
|
||||||
|
std::vector<std::string> params;
|
||||||
|
params.push_back ( filename );
|
||||||
|
command_result r = savestock ( out, params );
|
||||||
|
out << " result = "<< r << endl;
|
||||||
|
if ( r != CR_OK )
|
||||||
|
show_message_box ( "Stockpile Settings Error", "Couldn't save. Does the folder exist?", true );
|
||||||
|
}
|
||||||
|
|
||||||
|
DFHACK_PLUGIN_LUA_FUNCTIONS
|
||||||
|
{
|
||||||
|
DFHACK_LUA_FUNCTION ( stockpiles_load ),
|
||||||
|
DFHACK_LUA_FUNCTION ( stockpiles_save ),
|
||||||
|
DFHACK_LUA_END
|
||||||
|
};
|
||||||
|
|
||||||
|
DFHACK_PLUGIN_LUA_COMMANDS
|
||||||
|
{
|
||||||
|
DFHACK_LUA_COMMAND ( stockpiles_list_settings ),
|
||||||
|
DFHACK_LUA_END
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
|||||||
|
-- lave/load stockpile settings with a GUI
|
||||||
|
|
||||||
|
local stock = require 'plugins.stockpiles'
|
||||||
|
|
||||||
|
function check_enabled()
|
||||||
|
return stock.isEnabled()
|
||||||
|
end
|
||||||
|
|
||||||
|
function world_guard()
|
||||||
|
if not dfhack.isMapLoaded() then
|
||||||
|
qerror("World is not loaded")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function guard()
|
||||||
|
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Stockpile') then
|
||||||
|
qerror("This script requires a stockpile selected in the 'q' mode")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
utils = require('utils')
|
||||||
|
validArgs = validArgs or utils.invert({
|
||||||
|
'help',
|
||||||
|
'load',
|
||||||
|
'save',
|
||||||
|
'dir',
|
||||||
|
})
|
||||||
|
|
||||||
|
args = utils.processArgs({...}, validArgs)
|
||||||
|
|
||||||
|
function usage()
|
||||||
|
print("")
|
||||||
|
print("Stockpile Settings. Arguments: ")
|
||||||
|
print("-save to save the current stockpile")
|
||||||
|
print("-load to load settings into the current stockpile")
|
||||||
|
print("-dir <path> set the default directory to save settings into")
|
||||||
|
if dfhack.isMapLoaded() then
|
||||||
|
print(" Current directory is: " .. stock.get_path())
|
||||||
|
end
|
||||||
|
print("")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not check_enabled() then
|
||||||
|
qerror("Stockpiles plugin not enabled. Enable it with: enable stockpiles")
|
||||||
|
elseif args.load then
|
||||||
|
if not guard() then return end
|
||||||
|
stock.load_settings()
|
||||||
|
elseif args.save then
|
||||||
|
if not guard() then return end
|
||||||
|
local sp = dfhack.gui.getSelectedBuilding(true)
|
||||||
|
stock.save_settings(sp)
|
||||||
|
elseif args.dir then
|
||||||
|
if not world_guard() then return end
|
||||||
|
stock.set_path(args.dir)
|
||||||
|
else
|
||||||
|
usage()
|
||||||
|
end
|
Loading…
Reference in New Issue