est31 334e70455b Crafting speedup
This greatly increases crafting performance, especially in worlds with many mods.

Approved by @kwolekr.

Introduces a hash-type-layered fall-through mechanism, where every layer specifies one hash algorithm,
and the "deeper the fall", the more collisions to expect for the algorithm. One Craft definition
only resides at one layer, which improves speed for lower layers (and a complete fail), due to most
craft definitions residing at high layers.

Due to the fall-through design, the undocumented behaviour that later craft recipes
override older ones had to be weaked up a bit, but craft recipes with the same hash and layer
will still override.
2015-04-05 09:25:46 +02:00

1223 lines
30 KiB
C++

/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "craftdef.h"
#include "irrlichttypes.h"
#include "log.h"
#include <sstream>
#include <set>
#include <algorithm>
#include "gamedef.h"
#include "inventory.h"
#include "util/serialize.h"
#include "util/string.h"
#include "util/numeric.h"
#include "strfnd.h"
#include "exceptions.h"
inline bool isGroupRecipeStr(const std::string &rec_name)
{
return str_starts_with(rec_name, std::string("group:"));
}
inline u64 getHashForString(const std::string &recipe_str)
{
/*errorstream << "Hashing craft string \"" << recipe_str << "\"";*/
return murmur_hash_64_ua(recipe_str.data(), recipe_str.length(), 0xdeadbeef);
}
static u64 getHashForGrid(CraftHashType type, const std::vector<std::string> &grid_names)
{
switch (type) {
case CRAFT_HASH_TYPE_ITEM_NAMES: {
std::ostringstream os;
bool is_first = true;
for (size_t i = 0; i < grid_names.size(); i++) {
if (grid_names[i] != "") {
os << (is_first ? "" : "\n") << grid_names[i];
is_first = false;
}
}
return getHashForString(os.str());
} case CRAFT_HASH_TYPE_COUNT: {
u64 cnt = 0;
for (size_t i = 0; i < grid_names.size(); i++)
if (grid_names[i] != "")
cnt++;
return cnt;
} case CRAFT_HASH_TYPE_UNHASHED:
return 0;
}
// invalid CraftHashType
assert(false);
}
// Check if input matches recipe
// Takes recipe groups into account
static bool inputItemMatchesRecipe(const std::string &inp_name,
const std::string &rec_name, IItemDefManager *idef)
{
// Exact name
if(inp_name == rec_name)
return true;
// Group
if (isGroupRecipeStr(rec_name) && idef->isKnown(inp_name)) {
const struct ItemDefinition &def = idef->get(inp_name);
Strfnd f(rec_name.substr(6));
bool all_groups_match = true;
do{
std::string check_group = f.next(",");
if(itemgroup_get(def.groups, check_group) == 0){
all_groups_match = false;
break;
}
}while(!f.atend());
if(all_groups_match)
return true;
}
// Didn't match
return false;
}
// Deserialize an itemstring then return the name of the item
static std::string craftGetItemName(const std::string &itemstring, IGameDef *gamedef)
{
ItemStack item;
item.deSerialize(itemstring, gamedef->idef());
return item.name;
}
// (mapcar craftGetItemName itemstrings)
static std::vector<std::string> craftGetItemNames(
const std::vector<std::string> &itemstrings, IGameDef *gamedef)
{
std::vector<std::string> result;
for(std::vector<std::string>::const_iterator
i = itemstrings.begin();
i != itemstrings.end(); i++)
{
result.push_back(craftGetItemName(*i, gamedef));
}
return result;
}
// Get name of each item, and return them as a new list.
static std::vector<std::string> craftGetItemNames(
const std::vector<ItemStack> &items, IGameDef *gamedef)
{
std::vector<std::string> result;
for(std::vector<ItemStack>::const_iterator
i = items.begin();
i != items.end(); i++)
{
result.push_back(i->name);
}
return result;
}
// convert a list of item names, to ItemStacks.
static std::vector<ItemStack> craftGetItems(
const std::vector<std::string> &items, IGameDef *gamedef)
{
std::vector<ItemStack> result;
for(std::vector<std::string>::const_iterator
i = items.begin();
i != items.end(); i++)
{
result.push_back(ItemStack(std::string(*i),(u16)1,(u16)0,"",gamedef->getItemDefManager()));
}
return result;
}
// Compute bounding rectangle given a matrix of items
// Returns false if every item is ""
static bool craftGetBounds(const std::vector<std::string> &items, unsigned int width,
unsigned int &min_x, unsigned int &max_x,
unsigned int &min_y, unsigned int &max_y)
{
bool success = false;
unsigned int x = 0;
unsigned int y = 0;
for(std::vector<std::string>::const_iterator
i = items.begin();
i != items.end(); i++)
{
if(*i != "") // Is this an actual item?
{
if(!success)
{
// This is the first nonempty item
min_x = max_x = x;
min_y = max_y = y;
success = true;
}
else
{
if(x < min_x) min_x = x;
if(x > max_x) max_x = x;
if(y < min_y) min_y = y;
if(y > max_y) max_y = y;
}
}
// Step coordinate
x++;
if(x == width)
{
x = 0;
y++;
}
}
return success;
}
// Removes 1 from each item stack
static void craftDecrementInput(CraftInput &input, IGameDef *gamedef)
{
for(std::vector<ItemStack>::iterator
i = input.items.begin();
i != input.items.end(); i++)
{
if(i->count != 0)
i->remove(1);
}
}
// Removes 1 from each item stack with replacement support
// Example: if replacements contains the pair ("bucket:bucket_water", "bucket:bucket_empty"),
// a water bucket will not be removed but replaced by an empty bucket.
static void craftDecrementOrReplaceInput(CraftInput &input,
const CraftReplacements &replacements,
IGameDef *gamedef)
{
if(replacements.pairs.empty())
{
craftDecrementInput(input, gamedef);
return;
}
// Make a copy of the replacements pair list
std::vector<std::pair<std::string, std::string> > pairs = replacements.pairs;
for(std::vector<ItemStack>::iterator
i = input.items.begin();
i != input.items.end(); i++)
{
if(i->count == 1)
{
// Find an appropriate replacement
bool found_replacement = false;
for(std::vector<std::pair<std::string, std::string> >::iterator
j = pairs.begin();
j != pairs.end(); j++)
{
ItemStack from_item;
from_item.deSerialize(j->first, gamedef->idef());
if(i->name == from_item.name)
{
i->deSerialize(j->second, gamedef->idef());
found_replacement = true;
pairs.erase(j);
break;
}
}
// No replacement was found, simply decrement count to zero
if(!found_replacement)
i->remove(1);
}
else if(i->count >= 2)
{
// Ignore replacements for items with count >= 2
i->remove(1);
}
}
}
// Dump an itemstring matrix
static std::string craftDumpMatrix(const std::vector<std::string> &items,
unsigned int width)
{
std::ostringstream os(std::ios::binary);
os<<"{ ";
unsigned int x = 0;
for(std::vector<std::string>::const_iterator
i = items.begin();
i != items.end(); i++, x++)
{
if(x == width)
{
os<<"; ";
x = 0;
}
else if(x != 0)
{
os<<",";
}
os<<"\""<<(*i)<<"\"";
}
os<<" }";
return os.str();
}
// Dump an item matrix
std::string craftDumpMatrix(const std::vector<ItemStack> &items,
unsigned int width)
{
std::ostringstream os(std::ios::binary);
os<<"{ ";
unsigned int x = 0;
for(std::vector<ItemStack>::const_iterator
i = items.begin();
i != items.end(); i++, x++)
{
if(x == width)
{
os<<"; ";
x = 0;
}
else if(x != 0)
{
os<<",";
}
os<<"\""<<(i->getItemString())<<"\"";
}
os<<" }";
return os.str();
}
/*
CraftInput
*/
std::string CraftInput::dump() const
{
std::ostringstream os(std::ios::binary);
os<<"(method="<<((int)method)<<", items="<<craftDumpMatrix(items, width)<<")";
return os.str();
}
/*
CraftOutput
*/
std::string CraftOutput::dump() const
{
std::ostringstream os(std::ios::binary);
os<<"(item=\""<<item<<"\", time="<<time<<")";
return os.str();
}
/*
CraftReplacements
*/
std::string CraftReplacements::dump() const
{
std::ostringstream os(std::ios::binary);
os<<"{";
const char *sep = "";
for(std::vector<std::pair<std::string, std::string> >::const_iterator
i = pairs.begin();
i != pairs.end(); i++)
{
os<<sep<<"\""<<(i->first)<<"\"=>\""<<(i->second)<<"\"";
sep = ",";
}
os<<"}";
return os.str();
}
void CraftReplacements::serialize(std::ostream &os) const
{
writeU16(os, pairs.size());
for(u32 i=0; i<pairs.size(); i++)
{
os<<serializeString(pairs[i].first);
os<<serializeString(pairs[i].second);
}
}
void CraftReplacements::deSerialize(std::istream &is)
{
pairs.clear();
u32 count = readU16(is);
for(u32 i=0; i<count; i++)
{
std::string first = deSerializeString(is);
std::string second = deSerializeString(is);
pairs.push_back(std::make_pair(first, second));
}
}
/*
CraftDefinition
*/
void CraftDefinition::serialize(std::ostream &os) const
{
writeU8(os, 1); // version
os<<serializeString(getName());
serializeBody(os);
}
CraftDefinition* CraftDefinition::deSerialize(std::istream &is)
{
int version = readU8(is);
if(version != 1) throw SerializationError(
"unsupported CraftDefinition version");
std::string name = deSerializeString(is);
CraftDefinition *def = NULL;
if(name == "shaped")
{
def = new CraftDefinitionShaped;
}
else if(name == "shapeless")
{
def = new CraftDefinitionShapeless;
}
else if(name == "toolrepair")
{
def = new CraftDefinitionToolRepair;
}
else if(name == "cooking")
{
def = new CraftDefinitionCooking;
}
else if(name == "fuel")
{
def = new CraftDefinitionFuel;
}
else
{
infostream<<"Unknown CraftDefinition name=\""<<name<<"\""<<std::endl;
throw SerializationError("Unknown CraftDefinition name");
}
def->deSerializeBody(is, version);
return def;
}
/*
CraftDefinitionShaped
*/
std::string CraftDefinitionShaped::getName() const
{
return "shaped";
}
bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) const
{
if(input.method != CRAFT_METHOD_NORMAL)
return false;
// Get input item matrix
std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
unsigned int inp_width = input.width;
if(inp_width == 0)
return false;
while(inp_names.size() % inp_width != 0)
inp_names.push_back("");
// Get input bounds
unsigned int inp_min_x=0, inp_max_x=0, inp_min_y=0, inp_max_y=0;
if(!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x, inp_min_y, inp_max_y))
return false; // it was empty
std::vector<std::string> rec_names;
if (hash_inited)
rec_names = recipe_names;
else
rec_names = craftGetItemNames(recipe, gamedef);
// Get recipe item matrix
unsigned int rec_width = width;
if(rec_width == 0)
return false;
while(rec_names.size() % rec_width != 0)
rec_names.push_back("");
// Get recipe bounds
unsigned int rec_min_x=0, rec_max_x=0, rec_min_y=0, rec_max_y=0;
if(!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x, rec_min_y, rec_max_y))
return false; // it was empty
// Different sizes?
if(inp_max_x - inp_min_x != rec_max_x - rec_min_x ||
inp_max_y - inp_min_y != rec_max_y - rec_min_y)
return false;
// Verify that all item names in the bounding box are equal
unsigned int w = inp_max_x - inp_min_x + 1;
unsigned int h = inp_max_y - inp_min_y + 1;
for(unsigned int y=0; y < h; y++) {
unsigned int inp_y = (inp_min_y + y) * inp_width;
unsigned int rec_y = (rec_min_y + y) * rec_width;
for(unsigned int x=0; x < w; x++) {
unsigned int inp_x = inp_min_x + x;
unsigned int rec_x = rec_min_x + x;
if(!inputItemMatchesRecipe(
inp_names[inp_y + inp_x],
rec_names[rec_y + rec_x], gamedef->idef())
) {
return false;
}
}
}
return true;
}
CraftOutput CraftDefinitionShaped::getOutput(const CraftInput &input, IGameDef *gamedef) const
{
return CraftOutput(output, 0);
}
CraftInput CraftDefinitionShaped::getInput(const CraftOutput &output, IGameDef *gamedef) const
{
return CraftInput(CRAFT_METHOD_NORMAL,width,craftGetItems(recipe,gamedef));
}
void CraftDefinitionShaped::decrementInput(CraftInput &input, IGameDef *gamedef) const
{
craftDecrementOrReplaceInput(input, replacements, gamedef);
}
CraftHashType CraftDefinitionShaped::getHashType() const
{
assert(hash_inited); //pre-condition
bool has_group = false;
for (size_t i = 0; i < recipe_names.size(); i++) {
if (isGroupRecipeStr(recipe_names[i])) {
has_group = true;
break;
}
}
if (has_group)
return CRAFT_HASH_TYPE_COUNT;
else
return CRAFT_HASH_TYPE_ITEM_NAMES;
}
u64 CraftDefinitionShaped::getHash(CraftHashType type) const
{
assert(hash_inited); //pre-condition
if ((type == CRAFT_HASH_TYPE_ITEM_NAMES) || (type == CRAFT_HASH_TYPE_COUNT)) {
std::vector<std::string> rec_names = recipe_names;
std::sort(rec_names.begin(), rec_names.end());
return getHashForGrid(type, rec_names);
} else {
//illegal hash type for this CraftDefinition (pre-condition)
assert(false);
}
}
void CraftDefinitionShaped::initHash(IGameDef *gamedef)
{
if (hash_inited)
return;
hash_inited = true;
recipe_names = craftGetItemNames(recipe, gamedef);
}
std::string CraftDefinitionShaped::dump() const
{
std::ostringstream os(std::ios::binary);
os<<"(shaped, output=\""<<output
<<"\", recipe="<<craftDumpMatrix(recipe, width)
<<", replacements="<<replacements.dump()<<")";
return os.str();
}
void CraftDefinitionShaped::serializeBody(std::ostream &os) const
{
os<<serializeString(output);
writeU16(os, width);
writeU16(os, recipe.size());
for(u32 i=0; i<recipe.size(); i++)
os<<serializeString(recipe[i]);
replacements.serialize(os);
}
void CraftDefinitionShaped::deSerializeBody(std::istream &is, int version)
{
if(version != 1) throw SerializationError(
"unsupported CraftDefinitionShaped version");
output = deSerializeString(is);
width = readU16(is);
recipe.clear();
u32 count = readU16(is);
for(u32 i=0; i<count; i++)
recipe.push_back(deSerializeString(is));
replacements.deSerialize(is);
}
/*
CraftDefinitionShapeless
*/
std::string CraftDefinitionShapeless::getName() const
{
return "shapeless";
}
bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef) const
{
if(input.method != CRAFT_METHOD_NORMAL)
return false;
// Filter empty items out of input
std::vector<std::string> input_filtered;
for(std::vector<ItemStack>::const_iterator
i = input.items.begin();
i != input.items.end(); i++)
{
if(i->name != "")
input_filtered.push_back(i->name);
}
// If there is a wrong number of items in input, no match
if(input_filtered.size() != recipe.size()){
/*dstream<<"Number of input items ("<<input_filtered.size()
<<") does not match recipe size ("<<recipe.size()<<") "
<<"of recipe with output="<<output<<std::endl;*/
return false;
}
std::vector<std::string> recipe_copy;
if (hash_inited)
recipe_copy = recipe_names;
else {
recipe_copy = craftGetItemNames(recipe, gamedef);
std::sort(recipe_copy.begin(), recipe_copy.end());
}
// Try with all permutations of the recipe,
// start from the lexicographically first permutation (=sorted),
// recipe_names is pre-sorted
do {
// If all items match, the recipe matches
bool all_match = true;
//dstream<<"Testing recipe (output="<<output<<"):";
for(size_t i=0; i<recipe.size(); i++){
//dstream<<" ("<<input_filtered[i]<<" == "<<recipe_copy[i]<<")";
if(!inputItemMatchesRecipe(input_filtered[i], recipe_copy[i],
gamedef->idef())){
all_match = false;
break;
}
}
//dstream<<" -> match="<<all_match<<std::endl;
if(all_match)
return true;
}while(std::next_permutation(recipe_copy.begin(), recipe_copy.end()));
return false;
}
CraftOutput CraftDefinitionShapeless::getOutput(const CraftInput &input, IGameDef *gamedef) const
{
return CraftOutput(output, 0);
}
CraftInput CraftDefinitionShapeless::getInput(const CraftOutput &output, IGameDef *gamedef) const
{
return CraftInput(CRAFT_METHOD_NORMAL,0,craftGetItems(recipe,gamedef));
}
void CraftDefinitionShapeless::decrementInput(CraftInput &input, IGameDef *gamedef) const
{
craftDecrementOrReplaceInput(input, replacements, gamedef);
}
CraftHashType CraftDefinitionShapeless::getHashType() const
{
assert(hash_inited); //pre-condition
bool has_group = false;
for (size_t i = 0; i < recipe_names.size(); i++) {
if (isGroupRecipeStr(recipe_names[i])) {
has_group = true;
break;
}
}
if (has_group)
return CRAFT_HASH_TYPE_COUNT;
else
return CRAFT_HASH_TYPE_ITEM_NAMES;
}
u64 CraftDefinitionShapeless::getHash(CraftHashType type) const
{
assert(hash_inited); //pre-condition
if (type == CRAFT_HASH_TYPE_ITEM_NAMES || type == CRAFT_HASH_TYPE_COUNT) {
return getHashForGrid(type, recipe_names);
} else {
//illegal hash type for this CraftDefinition (pre-condition)
assert(false);
}
}
void CraftDefinitionShapeless::initHash(IGameDef *gamedef)
{
if (hash_inited)
return;
hash_inited = true;
recipe_names = craftGetItemNames(recipe, gamedef);
std::sort(recipe_names.begin(), recipe_names.end());
}
std::string CraftDefinitionShapeless::dump() const
{
std::ostringstream os(std::ios::binary);
os<<"(shapeless, output=\""<<output
<<"\", recipe="<<craftDumpMatrix(recipe, recipe.size())
<<", replacements="<<replacements.dump()<<")";
return os.str();
}
void CraftDefinitionShapeless::serializeBody(std::ostream &os) const
{
os<<serializeString(output);
writeU16(os, recipe.size());
for(u32 i=0; i<recipe.size(); i++)
os<<serializeString(recipe[i]);
replacements.serialize(os);
}
void CraftDefinitionShapeless::deSerializeBody(std::istream &is, int version)
{
if(version != 1) throw SerializationError(
"unsupported CraftDefinitionShapeless version");
output = deSerializeString(is);
recipe.clear();
u32 count = readU16(is);
for(u32 i=0; i<count; i++)
recipe.push_back(deSerializeString(is));
replacements.deSerialize(is);
}
/*
CraftDefinitionToolRepair
*/
static ItemStack craftToolRepair(
const ItemStack &item1,
const ItemStack &item2,
float additional_wear,
IGameDef *gamedef)
{
IItemDefManager *idef = gamedef->idef();
if(item1.count != 1 || item2.count != 1 || item1.name != item2.name
|| idef->get(item1.name).type != ITEM_TOOL
|| idef->get(item2.name).type != ITEM_TOOL)
{
// Failure
return ItemStack();
}
s32 item1_uses = 65536 - (u32) item1.wear;
s32 item2_uses = 65536 - (u32) item2.wear;
s32 new_uses = item1_uses + item2_uses;
s32 new_wear = 65536 - new_uses + floor(additional_wear * 65536 + 0.5);
if(new_wear >= 65536)
return ItemStack();
if(new_wear < 0)
new_wear = 0;
ItemStack repaired = item1;
repaired.wear = new_wear;
return repaired;
}
std::string CraftDefinitionToolRepair::getName() const
{
return "toolrepair";
}
bool CraftDefinitionToolRepair::check(const CraftInput &input, IGameDef *gamedef) const
{
if(input.method != CRAFT_METHOD_NORMAL)
return false;
ItemStack item1;
ItemStack item2;
for(std::vector<ItemStack>::const_iterator
i = input.items.begin();
i != input.items.end(); i++)
{
if(!i->empty())
{
if(item1.empty())
item1 = *i;
else if(item2.empty())
item2 = *i;
else
return false;
}
}
ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
return !repaired.empty();
}
CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameDef *gamedef) const
{
ItemStack item1;
ItemStack item2;
for(std::vector<ItemStack>::const_iterator
i = input.items.begin();
i != input.items.end(); i++)
{
if(!i->empty())
{
if(item1.empty())
item1 = *i;
else if(item2.empty())
item2 = *i;
}
}
ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
return CraftOutput(repaired.getItemString(), 0);
}
CraftInput CraftDefinitionToolRepair::getInput(const CraftOutput &output, IGameDef *gamedef) const
{
std::vector<ItemStack> stack;
stack.push_back(ItemStack());
return CraftInput(CRAFT_METHOD_COOKING,additional_wear,stack);
}
void CraftDefinitionToolRepair::decrementInput(CraftInput &input, IGameDef *gamedef) const
{
craftDecrementInput(input, gamedef);
}
std::string CraftDefinitionToolRepair::dump() const
{
std::ostringstream os(std::ios::binary);
os<<"(toolrepair, additional_wear="<<additional_wear<<")";
return os.str();
}
void CraftDefinitionToolRepair::serializeBody(std::ostream &os) const
{
writeF1000(os, additional_wear);
}
void CraftDefinitionToolRepair::deSerializeBody(std::istream &is, int version)
{
if(version != 1) throw SerializationError(
"unsupported CraftDefinitionToolRepair version");
additional_wear = readF1000(is);
}
/*
CraftDefinitionCooking
*/
std::string CraftDefinitionCooking::getName() const
{
return "cooking";
}
bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) const
{
if(input.method != CRAFT_METHOD_COOKING)
return false;
// Filter empty items out of input
std::vector<std::string> input_filtered;
for(std::vector<ItemStack>::const_iterator
i = input.items.begin();
i != input.items.end(); i++)
{
if(i->name != "")
input_filtered.push_back(i->name);
}
// If there is a wrong number of items in input, no match
if(input_filtered.size() != 1){
/*dstream<<"Number of input items ("<<input_filtered.size()
<<") does not match recipe size (1) "
<<"of cooking recipe with output="<<output<<std::endl;*/
return false;
}
// Check the single input item
return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
}
CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const
{
return CraftOutput(output, cooktime);
}
CraftInput CraftDefinitionCooking::getInput(const CraftOutput &output, IGameDef *gamedef) const
{
std::vector<std::string> rec;
rec.push_back(recipe);
return CraftInput(CRAFT_METHOD_COOKING,cooktime,craftGetItems(rec,gamedef));
}
void CraftDefinitionCooking::decrementInput(CraftInput &input, IGameDef *gamedef) const
{
craftDecrementOrReplaceInput(input, replacements, gamedef);
}
CraftHashType CraftDefinitionCooking::getHashType() const
{
if (isGroupRecipeStr(recipe_name))
return CRAFT_HASH_TYPE_COUNT;
else
return CRAFT_HASH_TYPE_ITEM_NAMES;
}
u64 CraftDefinitionCooking::getHash(CraftHashType type) const
{
if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
return getHashForString(recipe_name);
} else if (type == CRAFT_HASH_TYPE_COUNT) {
return 1;
} else {
//illegal hash type for this CraftDefinition (pre-condition)
assert(false);
}
}
void CraftDefinitionCooking::initHash(IGameDef *gamedef)
{
if (hash_inited)
return;
hash_inited = true;
recipe_name = craftGetItemName(recipe, gamedef);
}
std::string CraftDefinitionCooking::dump() const
{
std::ostringstream os(std::ios::binary);
os<<"(cooking, output=\""<<output
<<"\", recipe=\""<<recipe
<<"\", cooktime="<<cooktime<<")"
<<", replacements="<<replacements.dump()<<")";
return os.str();
}
void CraftDefinitionCooking::serializeBody(std::ostream &os) const
{
os<<serializeString(output);
os<<serializeString(recipe);
writeF1000(os, cooktime);
replacements.serialize(os);
}
void CraftDefinitionCooking::deSerializeBody(std::istream &is, int version)
{
if(version != 1) throw SerializationError(
"unsupported CraftDefinitionCooking version");
output = deSerializeString(is);
recipe = deSerializeString(is);
cooktime = readF1000(is);
replacements.deSerialize(is);
}
/*
CraftDefinitionFuel
*/
std::string CraftDefinitionFuel::getName() const
{
return "fuel";
}
bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) const
{
if(input.method != CRAFT_METHOD_FUEL)
return false;
// Filter empty items out of input
std::vector<std::string> input_filtered;
for(std::vector<ItemStack>::const_iterator
i = input.items.begin();
i != input.items.end(); i++)
{
if(i->name != "")
input_filtered.push_back(i->name);
}
// If there is a wrong number of items in input, no match
if(input_filtered.size() != 1){
/*dstream<<"Number of input items ("<<input_filtered.size()
<<") does not match recipe size (1) "
<<"of fuel recipe with burntime="<<burntime<<std::endl;*/
return false;
}
// Check the single input item
return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
}
CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const
{
return CraftOutput("", burntime);
}
CraftInput CraftDefinitionFuel::getInput(const CraftOutput &output, IGameDef *gamedef) const
{
std::vector<std::string> rec;
rec.push_back(recipe);
return CraftInput(CRAFT_METHOD_COOKING,(int)burntime,craftGetItems(rec,gamedef));
}
void CraftDefinitionFuel::decrementInput(CraftInput &input, IGameDef *gamedef) const
{
craftDecrementOrReplaceInput(input, replacements, gamedef);
}
CraftHashType CraftDefinitionFuel::getHashType() const
{
if (isGroupRecipeStr(recipe_name))
return CRAFT_HASH_TYPE_COUNT;
else
return CRAFT_HASH_TYPE_ITEM_NAMES;
}
u64 CraftDefinitionFuel::getHash(CraftHashType type) const
{
if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
return getHashForString(recipe_name);
} else if (type == CRAFT_HASH_TYPE_COUNT) {
return 1;
} else {
//illegal hash type for this CraftDefinition (pre-condition)
assert(false);
}
}
void CraftDefinitionFuel::initHash(IGameDef *gamedef)
{
if (hash_inited)
return;
hash_inited = true;
recipe_name = craftGetItemName(recipe, gamedef);
}
std::string CraftDefinitionFuel::dump() const
{
std::ostringstream os(std::ios::binary);
os<<"(fuel, recipe=\""<<recipe
<<"\", burntime="<<burntime<<")"
<<", replacements="<<replacements.dump()<<")";
return os.str();
}
void CraftDefinitionFuel::serializeBody(std::ostream &os) const
{
os<<serializeString(recipe);
writeF1000(os, burntime);
replacements.serialize(os);
}
void CraftDefinitionFuel::deSerializeBody(std::istream &is, int version)
{
if(version != 1) throw SerializationError(
"unsupported CraftDefinitionFuel version");
recipe = deSerializeString(is);
burntime = readF1000(is);
replacements.deSerialize(is);
}
/*
Craft definition manager
*/
class CCraftDefManager: public IWritableCraftDefManager
{
public:
CCraftDefManager()
{
m_craft_defs.resize(craft_hash_type_max + 1);
}
virtual ~CCraftDefManager()
{
clear();
}
virtual bool getCraftResult(CraftInput &input, CraftOutput &output,
bool decrementInput, IGameDef *gamedef) const
{
output.item = "";
output.time = 0;
// If all input items are empty, abort.
bool all_empty = true;
for (std::vector<ItemStack>::const_iterator
i = input.items.begin();
i != input.items.end(); i++) {
if (!i->empty()) {
all_empty = false;
break;
}
}
if (all_empty)
return false;
std::vector<std::string> input_names;
input_names = craftGetItemNames(input.items, gamedef);
std::sort(input_names.begin(), input_names.end());
// Try hash types with increasing collision rate, and return if found.
for (int type = 0; type <= craft_hash_type_max; type++) {
u64 hash = getHashForGrid((CraftHashType) type, input_names);
/*errorstream << "Checking type " << type << " with hash " << hash << std::endl;*/
// We'd like to do "const [...] hash_collisions = m_craft_defs[type][hash];"
// but that doesn't compile for some reason. This does.
std::map<u64, std::vector<CraftDefinition*> >::const_iterator
col_iter = (m_craft_defs[type]).find(hash);
if (col_iter == (m_craft_defs[type]).end())
continue;
const std::vector<CraftDefinition*> &hash_collisions = col_iter->second;
// Walk crafting definitions from back to front, so that later
// definitions can override earlier ones.
for (std::vector<CraftDefinition*>::const_reverse_iterator
i = hash_collisions.rbegin();
i != hash_collisions.rend(); i++) {
CraftDefinition *def = *i;
/*errorstream << "Checking " << input.dump() << std::endl
<< " against " << def->dump() << std::endl;*/
if (def->check(input, gamedef)) {
// Get output, then decrement input (if requested)
output = def->getOutput(input, gamedef);
if (decrementInput)
def->decrementInput(input, gamedef);
/*errorstream << "Check RETURNS TRUE" << std::endl;*/
return true;
}
}
}
return false;
}
virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output,
IGameDef *gamedef, unsigned limit=0) const
{
std::vector<CraftDefinition*> recipes;
std::map<std::string, std::vector<CraftDefinition*> >::const_iterator
vec_iter = m_output_craft_definitions.find(output.item);
if (vec_iter == m_output_craft_definitions.end())
return recipes;
const std::vector<CraftDefinition*> &vec = vec_iter->second;
recipes.reserve(limit ? MYMIN(limit, vec.size()) : vec.size());
for (std::vector<CraftDefinition*>::const_reverse_iterator
it = vec.rbegin(); it != vec.rend(); ++it) {
if (limit && recipes.size() >= limit)
break;
recipes.push_back(*it);
}
return recipes;
}
virtual std::string dump() const
{
std::ostringstream os(std::ios::binary);
os << "Crafting definitions:\n";
for (int type = 0; type <= craft_hash_type_max; type++) {
for (std::map<u64, std::vector<CraftDefinition*> >::const_iterator
i = (m_craft_defs[type]).begin();
i != (m_craft_defs[type]).end(); i++) {
for (std::vector<CraftDefinition*>::const_iterator
ii = i->second.begin(); ii != i->second.end(); ii++) {
os << "type " << type << " hash " << i->first << (*ii)->dump() << "\n";
}
}
}
return os.str();
}
virtual void registerCraft(CraftDefinition *def, IGameDef *gamedef)
{
verbosestream<<"registerCraft: registering craft definition: "
<<def->dump()<<std::endl;
m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].push_back(def);
CraftInput input;
std::string output_name = craftGetItemName(
def->getOutput(input, gamedef).item, gamedef);
m_output_craft_definitions[output_name].push_back(def);
}
virtual void clear()
{
for (int type = 0; type <= craft_hash_type_max; type++) {
for (std::map<u64, std::vector<CraftDefinition*> >::iterator
i = m_craft_defs[type].begin();
i != m_craft_defs[type].end(); i++) {
for (std::vector<CraftDefinition*>::iterator
ii = i->second.begin(); ii != i->second.end(); ii++) {
delete *ii;
}
i->second.clear();
}
m_craft_defs[type].clear();
}
m_output_craft_definitions.clear();
}
virtual void initHashes(IGameDef *gamedef)
{
// Move the CraftDefs from the unhashed layer into layers higher up.
for (std::vector<CraftDefinition*>::iterator
i = (m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0]).begin();
i != (m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0]).end(); i++) {
CraftDefinition *def = *i;
// Initialize and get the definition's hash
def->initHash(gamedef);
CraftHashType type = def->getHashType();
u64 hash = def->getHash(type);
// Enter the definition
m_craft_defs[type][hash].push_back(def);
}
m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].clear();
}
private:
//TODO: change both maps to unordered_map when c++11 can be used
std::vector<std::map<u64, std::vector<CraftDefinition*> > > m_craft_defs;
std::map<std::string, std::vector<CraftDefinition*> > m_output_craft_definitions;
};
IWritableCraftDefManager* createCraftDefManager()
{
return new CCraftDefManager();
}