add a reverse crafting guide

This commit is contained in:
hdastwb 2015-06-28 18:53:31 -04:00 committed by darkrose
parent cd90e65fb1
commit 15b6d1aebf
13 changed files with 557 additions and 2 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

View File

@ -34,6 +34,9 @@
#include "mapnode.h" // For content_t #include "mapnode.h" // For content_t
#include "settings.h" // for g_settings #include "settings.h" // for g_settings
#include <algorithm>
#include <set>
namespace crafting { namespace crafting {
std::vector<CraftDef> shaped_recipes; std::vector<CraftDef> shaped_recipes;
@ -784,6 +787,161 @@ int getRecipeCount(InventoryItem *item)
return count; return count;
} }
//a little predicate type for determining whether a recipe contains a certain item
struct CraftDefContains {
content_t item;
explicit CraftDefContains(content_t item_in) : item(item_in) {}
template <typename CD>
bool operator()(const CD& def) const
{
return std::find(def.recipe, def.recipe + 9, item) != def.recipe + 9;
}
};
int getReverseRecipeCount(InventoryItem *item)
{
using namespace std;
//make a predicate object to look for recipes containing the right item
CraftDefContains contains_item (item->getContent());
//count up the matching recipes
return count_if(shaped_recipes.begin(), shaped_recipes.end(), contains_item)
+ count_if(shapeless_recipes.begin(), shapeless_recipes.end(), contains_item);
}
//how to create a FoundReverseRecipe from a CraftDef
template <typename CD>
FoundReverseRecipe FRRFromCD(const CD& def)
{
using namespace std;
FoundReverseRecipe recipe;
recipe.result = def.result;
recipe.result_count = def.result_count;
copy(def.recipe, def.recipe + 9, recipe.recipe);
return recipe;
}
//a helper function for reverse recipe lookup
//count is used to count down to the correct recipe:
//it is decremented by the function each time that it finds a viable recipe
//this makes it possible to use this function for searches through multiple ranges
template <typename It>
FoundReverseRecipe reverseRecipeHelper(It cd_begin, It cd_end, content_t item, int &count)
{
using namespace std;
//make a predicate object to aid with the search for recipes containing the item
CraftDefContains contains_item (item);
//look through each of the recipes, finding the ones which contain item
for (It it = cd_begin; it != cd_end; ++it) if (contains_item(*it)) {
//continue on if the target recipe hasn't been reached yet
if (count--) continue;
//if it is the right recipe, make a copy and return it
return FRRFromCD(*it);
}
//if nothing was found yet, return a default FoundReverseRecipe
return FoundReverseRecipe();
}
FoundReverseRecipe getReverseRecipe(InventoryItem *iitem, int index)
{
//ignore negative indeces
if (index < 0)
return FoundReverseRecipe();
//find the content id for the item
content_t item = iitem->getContent();
//where to store the recipe when found
FoundReverseRecipe recipe;
//the recipe counter (counting down)
int count = index;
//look in the shaped recipes
recipe = reverseRecipeHelper(shaped_recipes.begin(), shaped_recipes.end(), item, count);
//if that fails, look in the shapeless recipes
if (not recipe)
recipe = reverseRecipeHelper(shapeless_recipes.begin(), shapeless_recipes.end(), item, count);
//return the located recipe, if one was found
return recipe;
}
//how to update an ingredient list given a range of new craft items
template <typename It>
void addToIngredientList(It results_begin, It results_end, std::vector<content_t>& ingredient_list)
{
using namespace std;
//make a set to hold the items as the list is compiled
set<content_t> ingredients (ingredient_list.begin(), ingredient_list.end());
//go through the result list
for (It it = results_begin; it != results_end; ++it) {
//make a temporary inventory item for the result
InventoryItem *result = InventoryItem::create(*it, 1);
//go through every recipe for this item
for (int rec_ind = getRecipeCount(result); rec_ind--;) {
//get the recipe
content_t *recipe = getRecipe(result, rec_ind);
//eliminate duplicates
sort(recipe, recipe + 9);
content_t *uniq_end = unique(recipe, recipe + 9);
//insert into the ingredients list
ingredients.insert(recipe, uniq_end);
//clean up
delete recipe;
}
//clean up
delete result;
}
//ignore CONTENT_IGNORE
ingredients.erase(CONTENT_IGNORE);
//dump the new ingredients into the ingredient list
ingredient_list.insert(ingredient_list.end(), ingredients.begin(), ingredients.end());
}
std::vector<content_t>& getCraftGuideIngredientList()
{
using namespace std;
//the ingredient list, and the number of items that were in the craftguide list at the last check
static vector<content_t> ingredient_list;
static unsigned last_craftguide_count = 0;
//get the craftguide list
const vector<content_t>& craft_list = lists::get("craftguide");
//check if more items need to be added
if (craft_list.size() > last_craftguide_count) {
//if so, add the new stuff
addToIngredientList(craft_list.begin() + last_craftguide_count, craft_list.end(), ingredient_list);
//and update the craftguide count
last_craftguide_count = craft_list.size();
}
//return the list
return ingredient_list;
}
void giveCreative(Player *player) void giveCreative(Player *player)
{ {
std::vector<content_t> &creativeinv = lists::get("player-creative"); std::vector<content_t> &creativeinv = lists::get("player-creative");

View File

@ -283,6 +283,25 @@ namespace crafting {
int getResultCount(InventoryItem *item); int getResultCount(InventoryItem *item);
int getRecipeCount(InventoryItem *item); int getRecipeCount(InventoryItem *item);
//counts the number of recipes that use item
int getReverseRecipeCount(InventoryItem *item);
//a recipe type for use in showing reverse recipes
//invalid recipes have result == CONTENT_IGNORE
struct FoundReverseRecipe {
content_t recipe [9];
content_t result;
int result_count;
FoundReverseRecipe() : result(CONTENT_IGNORE) {}
operator bool() const {return result != CONTENT_IGNORE;}
};
//retrieves the ith recipe that uses item (the first by default)
FoundReverseRecipe getReverseRecipe(InventoryItem *item, int i = 0);
//retrieves a cached ingredient list that is automatically built from the craftguide list
std::vector<content_t>& getCraftGuideIngredientList();
void giveCreative(Player *player); void giveCreative(Player *player);
void giveInitial(Player *player); void giveInitial(Player *player);
}; };

View File

@ -281,12 +281,14 @@ MapNode mapnode_translate_to_internal(MapNode n_from, u8 version);
#define CONTENT_COOK_BOOK 0x8D4 #define CONTENT_COOK_BOOK 0x8D4
#define CONTENT_DECRAFT_BOOK 0x8D5 #define CONTENT_DECRAFT_BOOK 0x8D5
#define CONTENT_DIARY_BOOK 0x8D6 #define CONTENT_DIARY_BOOK 0x8D6
// FREE 8D7-8D9 #define CONTENT_RCRAFT_BOOK 0x8D7
// FREE 8D8-8D9
#define CONTENT_BOOK_OPEN 0x8DA #define CONTENT_BOOK_OPEN 0x8DA
#define CONTENT_COOK_BOOK_OPEN 0x8DB #define CONTENT_COOK_BOOK_OPEN 0x8DB
#define CONTENT_DECRAFT_BOOK_OPEN 0x8DC #define CONTENT_DECRAFT_BOOK_OPEN 0x8DC
#define CONTENT_DIARY_BOOK_OPEN 0x8DD #define CONTENT_DIARY_BOOK_OPEN 0x8DD
// FREE 8D3-8DF #define CONTENT_RCRAFT_BOOK_OPEN 0x8DE
// FREE 8DF-8EF
#define CONTENT_YOUNG_TREE 0x8F0 #define CONTENT_YOUNG_TREE 0x8F0
#define CONTENT_YOUNG_JUNGLETREE 0x8F1 #define CONTENT_YOUNG_JUNGLETREE 0x8F1
#define CONTENT_YOUNG_APPLE_TREE 0x8F2 #define CONTENT_YOUNG_APPLE_TREE 0x8F2

View File

@ -799,6 +799,44 @@ void content_mapnode_special(bool repeat)
lists::add("craftguide",i); lists::add("craftguide",i);
lists::add("creative",i); lists::add("creative",i);
i = CONTENT_RCRAFT_BOOK;
f = &content_features(i);
f->description = wgettext("Reverse Craft Book");
f->setTexture(0, "book_rcraft_cover.png");
f->setTexture(1, "book_rcraft_cover.png^[transformFX");
f->setTexture(2, "book_rcraft_side.png^[transformFY");
f->setTexture(3, "book_rcraft_side.png");
f->setTexture(4, "book_rcraft_end.png");
f->setTexture(5, "book_rcraft_end.png^[transformFX");
f->param_type = CPT_LIGHT;
f->param2_type = CPT_FACEDIR_SIMPLE;
f->draw_type = CDT_NODEBOX;
f->rotate_tile_with_nodebox = true;
f->light_propagates = true;
f->air_equivalent = true;
f->onpunch_replace_node = CONTENT_RCRAFT_BOOK_OPEN;
f->flammable = 1;
f->dug_item = std::string("MaterialItem2 ")+itos(i)+" 1";
f->solidness = 0;
content_nodebox_book(f);
f->setInventoryTextureNodeBox(i, "book_rcraft_cover.png", "book_rcraft_end.png^[transformFX", "book_rcraft_side.png^[transformFY");
f->type = CMT_DIRT;
f->hardness = 1.0;
f->pressure_type = CST_CRUSHABLE;
f->suffocation_per_second = 0;
{
content_t r[9] = {
CONTENT_CRAFTITEM_STICK, CONTENT_IGNORE, CONTENT_IGNORE,
CONTENT_IGNORE, CONTENT_CRAFTITEM_STICK, CONTENT_IGNORE,
CONTENT_CRAFTITEM_STICK, CONTENT_CRAFTITEM_STICK, CONTENT_IGNORE
};
crafting::setRecipe(r,CONTENT_RCRAFT_BOOK,1);
}
if (f->initial_metadata == NULL)
f->initial_metadata = new ClosedBookNodeMetadata();
lists::add("craftguide",i);
lists::add("creative",i);
i = CONTENT_BOOK_OPEN; i = CONTENT_BOOK_OPEN;
f = &content_features(i); f = &content_features(i);
f->description = wgettext("Guide"); f->description = wgettext("Guide");
@ -941,6 +979,34 @@ void content_mapnode_special(bool repeat)
f->initial_metadata = new CraftGuideNodeMetadata(); f->initial_metadata = new CraftGuideNodeMetadata();
f->sound_access = "open-book"; f->sound_access = "open-book";
i = CONTENT_RCRAFT_BOOK_OPEN;
f = &content_features(i);
f->description = wgettext("Reverse Craft Guide");
f->setAllTextures("guide_rcraft_side.png");
f->setTexture(0, "guide_rcraft_top.png");
f->setTexture(1, "guide_rcraft_bottom.png");
f->setTexture(4, "guide_rcraft_end.png");
f->setTexture(5, "guide_rcraft_end.png");
f->param_type = CPT_LIGHT;
f->param2_type = CPT_FACEDIR_SIMPLE;
f->draw_type = CDT_NODEBOX;
f->rotate_tile_with_nodebox = true;
f->light_propagates = true;
f->air_equivalent = true;
f->onpunch_replace_node = CONTENT_RCRAFT_BOOK;
f->flammable = 1;
f->dug_item = std::string("MaterialItem2 ")+itos(CONTENT_RCRAFT_BOOK)+" 1";
f->solidness = 0;
content_nodebox_guide(f);
f->setInventoryTextureNodeBox(i, "guide_rcraft_top.png", "guide_rcraft_end.png", "guide_rcraft_side.png");
f->type = CMT_DIRT;
f->hardness = 1.0;
f->pressure_type = CST_CRUSHABLE;
f->suffocation_per_second = 0;
if (f->initial_metadata == NULL)
f->initial_metadata = new ReverseCraftGuideNodeMetadata();
f->sound_access = "open-book";
i = CONTENT_FIRE; i = CONTENT_FIRE;
f = &content_features(i); f = &content_features(i);
f->description = wgettext("Fire"); f->description = wgettext("Fire");

View File

@ -1528,6 +1528,285 @@ std::string CraftGuideNodeMetadata::getDrawSpecString()
return spec; return spec;
} }
/*
ReverseCraftGuideNodeMetadata
*/
// Prototype
ReverseCraftGuideNodeMetadata proto_ReverseCraftGuideNodeMetadata;
ReverseCraftGuideNodeMetadata::ReverseCraftGuideNodeMetadata()
{
//make sure that the type gets registered for this metadata
NodeMetadata::registerType(typeId(), create);
//start on the first page, with the first recipe
m_page = 0;
m_recipe = 0;
//build the inventory
m_inventory = new Inventory;
m_inventory->addList("list", 300);
m_inventory->addList("item", 1);
m_inventory->addList("recipe", 9);
m_inventory->addList("result", 1);
}
ReverseCraftGuideNodeMetadata::~ReverseCraftGuideNodeMetadata()
{
delete m_inventory;
}
u16 ReverseCraftGuideNodeMetadata::typeId() const
{
return CONTENT_RCRAFT_BOOK_OPEN;
}
void ReverseCraftGuideNodeMetadata::reloadPage()
{
using namespace std;
//get the inventory list and clear it
InventoryList *inv_list = m_inventory->getList("list");
inv_list->clearItems();
//retrieve the list of things in the ingredient list
vector<content_t> &ingredient_list = crafting::getCraftGuideIngredientList();
//get the number of pages
if (ingredient_list.size() == 0) return;
u16 page_count = ingredient_list.size()/40;
if (ingredient_list.size()%40) ++page_count;
//make sure the page is actually in range (via modulus)
if (s16(m_page) >= page_count) m_page %= page_count;
else if (s16(m_page) < 0) m_page = s16(m_page)%page_count + page_count;
//go through each item on the current page
vector<content_t>::iterator page_begin = ingredient_list.begin() + m_page*40;
vector<content_t>::iterator page_end = ingredient_list.begin() + min((m_page+1)*40, signed(ingredient_list.size()));
for (vector<content_t>::iterator it = page_begin; it != page_end; ++it) {
//create an inventory item for it
InventoryItem *cur_item = InventoryItem::create(*it, 1);
//make extra sure that it actually has recipes in order to not look stupid
if (not crafting::getReverseRecipe(cur_item)) delete cur_item;
//if it does, add it
else inv_list->addItem(cur_item);
}
}
NodeMetadata* ReverseCraftGuideNodeMetadata::clone()
{
//create a new metadata object
ReverseCraftGuideNodeMetadata *d = new ReverseCraftGuideNodeMetadata;
//copy over the inventory
*d->m_inventory = *m_inventory;
//keep the same page
d->m_page = m_page;
//rebuild the page on the copy
d->reloadPage();
//return the completed copy
return d;
}
NodeMetadata* ReverseCraftGuideNodeMetadata::create(std::istream &is)
{
//create a new metadata object
ReverseCraftGuideNodeMetadata *d = new ReverseCraftGuideNodeMetadata;
//deserialize the inventory
d->m_inventory->deSerialize(is);
//read in the page and recipe
is>>d->m_page;
is>>d->m_recipe;
//return the completed object
return d;
}
void ReverseCraftGuideNodeMetadata::serializeBody(std::ostream &os)
{
//serialize the inventory
m_inventory->serialize(os);
//also serialize the page and recipe numbers
os << itos(m_page) << " ";
os << itos(m_recipe) << " ";
}
bool ReverseCraftGuideNodeMetadata::nodeRemovalDisabled()
{
//the player can always remove this node
return false;
}
void ReverseCraftGuideNodeMetadata::inventoryModified()
{
infostream<<"ReverseCraftGuide inventory modification callback"<<std::endl;
}
bool ReverseCraftGuideNodeMetadata::step(float dtime, v3s16 pos, ServerEnvironment *env)
{
//get the item in the item box
InventoryItem *item = m_inventory->getList("item")->getItem(0);
//if there's no item in the item box, do nothing
if (not item or item->getContent() == CONTENT_IGNORE)
return false;
//attempt to look up the recipe
crafting::FoundReverseRecipe recipe = crafting::getReverseRecipe(item, m_recipe);
//if it doesn't exist, attempt to start over on the first recipe
if (not recipe) {
//if it's already on the first recipe, give up
if (m_recipe == 0)
return false;
//otherwise, switch to the first recipe
m_recipe = 0;
recipe = crafting::getReverseRecipe(item, m_recipe);
//give up if that doesn't work
if (not recipe)
return false;
}
//clear the recipe box
InventoryList *rec_list = m_inventory->getList("recipe");
rec_list->clearItems();
//load the recipe into the recipe box
for (int i=0; i<9; i++) {
if (recipe.recipe[i] == CONTENT_IGNORE)
continue;
InventoryItem *item = InventoryItem::create(recipe.recipe[i], 1);
rec_list->addItem(i, item);
}
//load the result box too
{
InventoryList *res_list = m_inventory->getList("result");
res_list->clearItems();
InventoryItem *result = InventoryItem::create(recipe.result, recipe.result_count);
res_list->addItem(0, result);
}
//the node has now been updated
return true;
}
bool ReverseCraftGuideNodeMetadata::import(NodeMetadata *meta)
{
using namespace std;
//if the metadata is from a book being opened, stay on the same page
if (ClosedBookNodeMetadata *book_meta = dynamic_cast<ClosedBookNodeMetadata*>(meta))
m_page = book_meta->getPage();
//reload the page
reloadPage();
//node updated
return true;
}
bool ReverseCraftGuideNodeMetadata::receiveFields(std::string formname, std::map<std::string, std::string> fields, Player *player)
{
//if the player wants to change the recipe
if (fields["rprev"] != "" or fields["rnext"] != "") {
//find the ingredient item
InventoryItem *item = m_inventory->getList("item")->getItem(0);
//advance the recipe counter appropriately
if (fields["rprev"] != "") {
if (m_recipe > 0)
m_recipe--;
} else {
m_recipe++;
}
//get the recipe count
int rec_count = 1;
if (item && item->getContent() != CONTENT_IGNORE)
rec_count = crafting::getReverseRecipeCount(item);
//fix the counter if needed
if (m_recipe >= rec_count)
m_recipe = rec_count - 1;
//this node now needs updating
step(0, v3s16(0,0,0), NULL);
return true;
}
//if the player wants to change the list page
if (fields["prev"] != "" or fields["next"] != "") {
//advance m_page correctly
if (fields["prev"] != "") --m_page;
if (fields["next"] != "") ++m_page;
//reload the page (if the number is out of bounds it will fix it correctly automatically)
reloadPage();
//the node has been updated
return true;
}
//nothing happened
return false;
}
std::string ReverseCraftGuideNodeMetadata::getDrawSpecString()
{
using namespace std;
//get the ingredient item
InventoryItem *item = m_inventory->getList("item")->getItem(0);
int recipe_count = 0;
if (item && item->getContent() != CONTENT_IGNORE) {
recipe_count = crafting::getReverseRecipeCount(item);
}
//get the number of pages
vector<content_t> &ingredient_list = crafting::getCraftGuideIngredientList();
if (ingredient_list.size() == 0) return "";
u16 page_count = ingredient_list.size()/40;
if (ingredient_list.size()%40) ++page_count;
//write the page count string
char buff[256];
snprintf(buff, 256, gettext("Page %d of %d"), (int)(m_page+1), page_count);
//build the formspec
string spec("size[8,10]");
spec += "label[0.5,0.75;";
spec += gettext("Add item here to see recipe");
spec += "]";
spec += "list[current_name;item;2,1;1,1;]";
if (recipe_count > 1) {
char rbuff[256];
snprintf(rbuff,256,gettext("Recipe %d of %d"), (int)(m_recipe+1), recipe_count);
spec += "button[2.5,3.5;1,0.75;rprev;<<]";
spec += "label[3.5,3.5;";
spec += rbuff;
spec += "]";
spec += "button[5.5,3.5;1,0.75;rnext;>>]";
}
spec += "list[current_name;recipe;4,0;3,3;]";
spec += "button[0.25,4.5;2.5,0.75;prev;";
spec += gettext("<< Previous Page");
spec += "]";
spec += "label[3.5,4.5;";
spec += buff;
spec += "]";
spec += "button[6,4.5;2.5,0.75;next;";
spec += gettext("Next Page >>");
spec += "]";
spec += "list[current_name;result;7,0;1,1;]";
spec += "list[current_name;list;0,5;8,5;]";
return spec;
}
/* /*
CookBookNodeMetadata CookBookNodeMetadata
*/ */

View File

@ -372,6 +372,37 @@ private:
u16 m_recipe; u16 m_recipe;
}; };
class ReverseCraftGuideNodeMetadata : public NodeMetadata
{
public:
ReverseCraftGuideNodeMetadata();
~ReverseCraftGuideNodeMetadata();
virtual u16 typeId() const;
NodeMetadata* clone();
static NodeMetadata* create(std::istream &is);
virtual void serializeBody(std::ostream &os);
virtual std::wstring infoText() {return wgettext("Reverse Craft Guide");}
virtual Inventory* getInventory() {return m_inventory;}
virtual bool nodeRemovalDisabled();
virtual void inventoryModified();
virtual bool step(float dtime, v3s16 pos, ServerEnvironment *env);
virtual bool import(NodeMetadata *meta);
virtual bool receiveFields(std::string formname, std::map<std::string, std::string> fields, Player *player);
virtual std::string getDrawSpecString();
u16 getPage() {return m_page;}
private:
Inventory *m_inventory;
u16 m_page;
u16 m_recipe;
//a helper function to reload the current page, to reduce repeated code
//this will also automatically wrap around the page number, casting it to a signed type and using modulus
void reloadPage();
};
class CookBookNodeMetadata : public NodeMetadata class CookBookNodeMetadata : public NodeMetadata
{ {
public: public: