diff --git a/data/textures/book_rcraft_cover.png b/data/textures/book_rcraft_cover.png new file mode 100644 index 0000000..7fff7a4 Binary files /dev/null and b/data/textures/book_rcraft_cover.png differ diff --git a/data/textures/book_rcraft_end.png b/data/textures/book_rcraft_end.png new file mode 100644 index 0000000..b202039 Binary files /dev/null and b/data/textures/book_rcraft_end.png differ diff --git a/data/textures/book_rcraft_side.png b/data/textures/book_rcraft_side.png new file mode 100644 index 0000000..84d0a00 Binary files /dev/null and b/data/textures/book_rcraft_side.png differ diff --git a/data/textures/guide_rcraft_bottom.png b/data/textures/guide_rcraft_bottom.png new file mode 100644 index 0000000..6e79365 Binary files /dev/null and b/data/textures/guide_rcraft_bottom.png differ diff --git a/data/textures/guide_rcraft_end.png b/data/textures/guide_rcraft_end.png new file mode 100644 index 0000000..051436f Binary files /dev/null and b/data/textures/guide_rcraft_end.png differ diff --git a/data/textures/guide_rcraft_side.png b/data/textures/guide_rcraft_side.png new file mode 100644 index 0000000..5561f58 Binary files /dev/null and b/data/textures/guide_rcraft_side.png differ diff --git a/data/textures/guide_rcraft_top.png b/data/textures/guide_rcraft_top.png new file mode 100644 index 0000000..37e5eb7 Binary files /dev/null and b/data/textures/guide_rcraft_top.png differ diff --git a/src/content_craft.cpp b/src/content_craft.cpp index d26f134..f924deb 100644 --- a/src/content_craft.cpp +++ b/src/content_craft.cpp @@ -34,6 +34,9 @@ #include "mapnode.h" // For content_t #include "settings.h" // for g_settings +#include +#include + namespace crafting { std::vector shaped_recipes; @@ -784,6 +787,161 @@ int getRecipeCount(InventoryItem *item) 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 + 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 +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 +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 +void addToIngredientList(It results_begin, It results_end, std::vector& ingredient_list) +{ + using namespace std; + + //make a set to hold the items as the list is compiled + set 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& getCraftGuideIngredientList() +{ + using namespace std; + + //the ingredient list, and the number of items that were in the craftguide list at the last check + static vector ingredient_list; + static unsigned last_craftguide_count = 0; + + //get the craftguide list + const vector& 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) { std::vector &creativeinv = lists::get("player-creative"); diff --git a/src/content_craft.h b/src/content_craft.h index 4697a31..bc42c8f 100644 --- a/src/content_craft.h +++ b/src/content_craft.h @@ -283,6 +283,25 @@ namespace crafting { int getResultCount(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& getCraftGuideIngredientList(); + void giveCreative(Player *player); void giveInitial(Player *player); }; diff --git a/src/content_mapnode.h b/src/content_mapnode.h index 2ffc40e..d3e5a86 100644 --- a/src/content_mapnode.h +++ b/src/content_mapnode.h @@ -281,12 +281,14 @@ MapNode mapnode_translate_to_internal(MapNode n_from, u8 version); #define CONTENT_COOK_BOOK 0x8D4 #define CONTENT_DECRAFT_BOOK 0x8D5 #define CONTENT_DIARY_BOOK 0x8D6 -// FREE 8D7-8D9 +#define CONTENT_RCRAFT_BOOK 0x8D7 +// FREE 8D8-8D9 #define CONTENT_BOOK_OPEN 0x8DA #define CONTENT_COOK_BOOK_OPEN 0x8DB #define CONTENT_DECRAFT_BOOK_OPEN 0x8DC #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_JUNGLETREE 0x8F1 #define CONTENT_YOUNG_APPLE_TREE 0x8F2 diff --git a/src/content_mapnode_special.cpp b/src/content_mapnode_special.cpp index 545646c..b0dc41d 100644 --- a/src/content_mapnode_special.cpp +++ b/src/content_mapnode_special.cpp @@ -799,6 +799,44 @@ void content_mapnode_special(bool repeat) lists::add("craftguide",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; f = &content_features(i); f->description = wgettext("Guide"); @@ -941,6 +979,34 @@ void content_mapnode_special(bool repeat) f->initial_metadata = new CraftGuideNodeMetadata(); 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; f = &content_features(i); f->description = wgettext("Fire"); diff --git a/src/content_nodemeta.cpp b/src/content_nodemeta.cpp index 7da8648..95dd052 100644 --- a/src/content_nodemeta.cpp +++ b/src/content_nodemeta.cpp @@ -1528,6 +1528,285 @@ std::string CraftGuideNodeMetadata::getDrawSpecString() 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 &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::iterator page_begin = ingredient_list.begin() + m_page*40; + vector::iterator page_end = ingredient_list.begin() + min((m_page+1)*40, signed(ingredient_list.size())); + for (vector::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"<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(meta)) + m_page = book_meta->getPage(); + + //reload the page + reloadPage(); + + //node updated + return true; +} +bool ReverseCraftGuideNodeMetadata::receiveFields(std::string formname, std::map 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 &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 */ diff --git a/src/content_nodemeta.h b/src/content_nodemeta.h index e77a45a..af63973 100644 --- a/src/content_nodemeta.h +++ b/src/content_nodemeta.h @@ -372,6 +372,37 @@ private: 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 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 { public: