#include "PaneCraftingScreen.h" #include "../touch/TouchStartMenuScreen.h" #include "../../Screen.h" #include "../../components/NinePatch.h" #include "../../../Minecraft.h" #include "../../../player/LocalPlayer.h" #include "../../../renderer/Tesselator.h" #include "../../../renderer/entity/ItemRenderer.h" #include "../../../../world/item/Item.h" #include "../../../../world/item/crafting/Recipes.h" #include "../../../../world/item/ItemCategory.h" #include "../../../../world/entity/player/Inventory.h" #include "../../../../util/StringUtils.h" #include "../../../../locale/I18n.h" #include "../../../../world/entity/item/ItemEntity.h" #include "../../../../world/level/Level.h" #include "../../../../world/item/DyePowderItem.h" #include "../../../../world/item/crafting/Recipe.h" #include "platform/input/Keyboard.h" static NinePatchLayer* guiPaneFrame = NULL; const float BorderPixels = 6.0f; const int descFrameWidth = 100; const int rgbActive = 0xfff0f0f0; const int rgbInactive = 0xc0635558; const int rgbInactiveShadow = 0xc0aaaaaa; class CategoryButton: public ImageButton { typedef ImageButton super; public: CategoryButton(int id, const ImageButton* const* selectedPtr, NinePatchLayer* stateNormal, NinePatchLayer* statePressed) : super(id, ""), selectedPtr(selectedPtr), stateNormal(stateNormal), statePressed(statePressed) {} void renderBg(Minecraft* minecraft, int xm, int ym) { //fill(x+1, y+1, x+w-1, y+h-1, 0xff999999); bool hovered = active && (minecraft->useTouchscreen()? (_currentlyDown && xm >= x && ym >= y && xm < x + width && ym < y + height) : isInside(xm, ym)); if (hovered || *selectedPtr == this) statePressed->draw(Tesselator::instance, (float)x, (float)y); else stateNormal->draw(Tesselator::instance, (float)x, (float)y); } bool isSecondImage(bool hovered) { return false; } private: const ImageButton* const* selectedPtr; NinePatchLayer* stateNormal; NinePatchLayer* statePressed; }; PaneCraftingScreen::PaneCraftingScreen(int craftingSize) : craftingSize(craftingSize), currentCategory(-1), currentItem(NULL), pane(NULL), btnCraft(1), btnClose(2, ""), selectedCategoryButton(NULL), guiBackground(NULL), guiSlotCategory(NULL), guiSlotCategorySelected(NULL), numCategories(4) { for (int i = 0; i < numCategories; ++i) { categoryBitmasks.push_back(1 << i); categoryIcons.push_back(i); } } PaneCraftingScreen::~PaneCraftingScreen() { for (unsigned int i = 0; i < _items.size(); ++i) delete _items[i]; for (unsigned int i = 0; i < _categoryButtons.size(); ++i) delete _categoryButtons[i]; clearCategoryItems(); delete pane; delete guiBackground; // statics delete guiSlotCategory; delete guiSlotCategorySelected; delete guiPaneFrame; } void PaneCraftingScreen::init() { ImageDef def; def.name = "gui/spritesheet.png"; def.x = 0; def.y = 1; def.width = def.height = 18; def.setSrc(IntRectangle(60, 0, 18, 18)); btnClose.setImageDef(def, true); btnClose.scaleWhenPressed = false; btnCraft.init(minecraft->textures); buttons.push_back(&btnCraft); buttons.push_back(&btnClose); // GUI patches NinePatchFactory builder(minecraft->textures, "gui/spritesheet.png"); guiBackground = builder.createSymmetrical(IntRectangle(0, 0, 16, 16), 4, 4); guiPaneFrame = builder.createSymmetrical(IntRectangle(0, 20, 8, 8), 1, 2)->setExcluded(1 << 4); guiSlotCategory = builder.createSymmetrical(IntRectangle(8, 32, 8, 8), 2, 2); guiSlotCategorySelected = builder.createSymmetrical(IntRectangle(0, 32, 8, 8), 2, 2); initCategories(); } void PaneCraftingScreen::initCategories() { _categories.resize(numCategories); // Category buttons for (int i = 0; i < numCategories; ++i) { ImageButton* button = new CategoryButton(100 + i, &selectedCategoryButton, guiSlotCategory, guiSlotCategorySelected); _categoryButtons.push_back( button ); buttons.push_back( button ); } const RecipeList& all = Recipes::getInstance()->getRecipes(); RecipeList filtered; filtered.reserve(all.size()); // Apply size filter for (unsigned int i = 0; i < all.size(); ++i) { if (craftingSize >= all[i]->getCraftingSize()) filtered.push_back(all[i]); } // Filter by subclass impl filterRecipes(filtered); // Add items from filtered recipes for (unsigned int i = 0; i < filtered.size(); ++i) addItem(filtered[i]); recheckRecipes(); } void PaneCraftingScreen::setupPositions() { // Left - Categories const int buttonHeight = (height - 16) / (Mth::Max(numCategories, 4)); for (unsigned c = 0; c < _categoryButtons.size(); ++c) { ImageButton* button = _categoryButtons[c]; button->x = (int)BorderPixels; button->y = (int)BorderPixels + c * (1 + buttonHeight); button->width = (int)buttonHeight; button->height = (int)buttonHeight; int icon = categoryIcons[c]; ImageDef def; def.x = 0; def.width = def.height = (float)buttonHeight; def.name = "gui/spritesheet.png"; def.setSrc(IntRectangle(32 * (icon/2), 64 + (icon&1) * 32, 32, 32)); button->setImageDef(def, false); } // Right - Description const int craftW = (int)(100 - 2 * BorderPixels - 0); btnCraft.x = width - descFrameWidth + (descFrameWidth-craftW)/2 - 1;// width - descFrameWidth + (int)BorderPixels + 4; btnCraft.y = 20; btnCraft.setSize((float)craftW, 62); btnClose.width = btnClose.height = 19; btnClose.x = width - btnClose.width; btnClose.y = 0; // Middle - Scrolling pane paneRect.x = buttonHeight + 2 * (int)BorderPixels; paneRect.y = (int)BorderPixels + 2; paneRect.w = width - paneRect.x - descFrameWidth; paneRect.h = height - 2 * (int)BorderPixels - 4; guiPaneFrame->setSize((float)paneRect.w + 2, (float)paneRect.h + 4); guiBackground->setSize((float)width, (float)height); guiSlotCategory->setSize((float)buttonHeight, (float)buttonHeight); guiSlotCategorySelected->setSize((float)buttonHeight, (float)buttonHeight); int oldCategory = currentCategory; currentCategory = -1; buttonClicked(_categoryButtons[pane?oldCategory:0]); } void PaneCraftingScreen::tick() { if (pane) pane->tick(); } void PaneCraftingScreen::render(int xm, int ym, float a) { const int N = 5; static StopwatchNLast r(N); //renderBackground(); Tesselator& t = Tesselator::instance; guiBackground->draw(t, 0, 0); glEnable2(GL_ALPHA_TEST); // Buttons (Left side + crafting) super::render(xm, ym, a); // Mid r.start(); // Blit frame guiPaneFrame->draw(t, (float)paneRect.x - 1, (float)paneRect.y - 2); if (pane) pane->render(xm, ym, a); r.stop(); //r.printEvery(N, "test"); const float slotWidth = (float)btnCraft.width / 2.0f; const float slotHeight = (float)btnCraft.height / 2.0f; const float slotBx = (float)btnCraft.x + slotWidth/2 - 8; const float slotBy = (float)btnCraft.y + slotHeight/2 - 9; ItemInstance reqItem; // Right side if (currentItem) { t.beginOverride(); for (unsigned int i = 0; i < currentItem->neededItems.size(); ++i) { const float xx = slotBx + slotWidth * (float)(i % 2); const float yy = slotBy + slotHeight * (float)(i / 2); CItem::ReqItem& req = currentItem->neededItems[i]; reqItem = req.item; if (reqItem.getAuxValue() == -1) reqItem.setAuxValue(0); ItemRenderer::renderGuiItem(NULL, minecraft->textures, &reqItem, xx, yy, 16, 16, true); } t.endOverrideAndDraw(); char buf[16]; const float scale = 2.0f / 3.0f; const float invScale = 1.0f / scale; t.beginOverride(); t.scale2d(scale, scale); for (unsigned int i = 0; i < currentItem->neededItems.size(); ++i) { const float xx = 4 + invScale * (slotBx + slotWidth * (float)(i % 2)); const float yy = 23 + invScale * (slotBy + slotHeight * (float)(i / 2)); CItem::ReqItem& req = currentItem->neededItems[i]; int bufIndex = 0; bufIndex += Gui::itemCountItoa(&buf[bufIndex], req.has); strcpy(&buf[bufIndex], "/"); bufIndex += 1; bufIndex += Gui::itemCountItoa(&buf[bufIndex], req.item.count); buf[bufIndex] = 0; if (req.enough()) minecraft->font->drawShadow(buf, xx, yy, rgbActive); else { minecraft->font->draw(buf, xx+1, yy+1, rgbInactiveShadow); minecraft->font->draw(buf, xx, yy, rgbInactive); } } t.resetScale(); t.endOverrideAndDraw(); //minecraft->font->drawWordWrap(currentItemDesc, rightBx + 2, (float)btnCraft.y + btnCraft.h + 6, descFrameWidth-4, rgbActive); minecraft->font->drawWordWrap(currentItemDesc, (float)btnCraft.x, (float)(btnCraft.y + btnCraft.height + 6), (float)btnCraft.width, rgbActive); } //glDisable2(GL_ALPHA_TEST); } void PaneCraftingScreen::buttonClicked(Button* button) { if (button == &btnCraft) craftSelectedItem(); if (button == &btnClose) minecraft->setScreen(NULL); // Did we click a category? if (button->id >= 100 && button->id < 200) { int categoryId = button->id - 100; ItemList& cat = _categories[categoryId]; if (!cat.empty()) { onItemSelected(categoryId, cat[0]); pane->setSelected(0, true); } currentCategory = categoryId; selectedCategoryButton = (CategoryButton*)button; } } static void randomlyFillItemPack(ItemPack* ip, int numItems) { int added = 0; ItemInstance item(0, 1, 0); while (added < numItems) { int t = Mth::random(512); if (!Item::items[t]) continue; item.id = t; int id = ItemPack::getIdForItemInstance(&item); int count = Mth::random(10); for (int i = 0; i < count; ++i) ip->add(id); ++added; } } static bool sortCanCraftPredicate(const CItem* a, const CItem* b) { //if (a->maxBuildCount == 0 && b->maxBuildCount > 0) return false; //if (b->maxBuildCount == 0 && a->maxBuildCount > 0) return true; return a->sortText < b->sortText; } void PaneCraftingScreen::recheckRecipes() { ItemPack ip; if (minecraft->player && minecraft->player->inventory) { Inventory* inv = (minecraft->player)->inventory; for (int i = Inventory::MAX_SELECTION_SIZE; i < inv->getContainerSize(); ++i) { if (ItemInstance* item = inv->getItem(i)) ip.add(ItemPack::getIdForItemInstance(item), item->count); } } else { randomlyFillItemPack(&ip, 50); } ip.print(); Stopwatch w; w.start(); for (unsigned int i = 0; i < _items.size(); ++i) { CItem* item = _items[i]; item->neededItems.clear(); item->setCanCraft(true); Recipe* recipe = item->recipe; item->inventoryCount = ip.getCount(ItemPack::getIdForItemInstance(&item->item)); //item->maxBuildCount = recipe->getMaxCraftCount(ip); // Override the canCraft thing, since I'm too lazy // to fix the above (commented out) function std::vector items = recipe->getItemPack().getItemInstances(); for (unsigned int j = 0; j < items.size(); ++j) { ItemInstance& jtem = items[j]; int has = 0; if (!Recipe::isAnyAuxValue(&jtem) && (jtem.getAuxValue() == Recipe::ANY_AUX_VALUE)) { // If the aux value on the item matters, but the recipe says it doesn't, // use this override (by fetching all items with aux-ids 0-15) ItemInstance aux(jtem); for (int i = 0; i < 16; ++i) { aux.setAuxValue(i); has += ip.getCount(ItemPack::getIdForItemInstance(&aux)); } } else { // Else just use the normal aux-value rules has = ip.getCount(ItemPack::getIdForItemInstance(&jtem)); } CItem::ReqItem req(jtem, has); item->neededItems.push_back(req); item->setCanCraft(item->canCraft() && req.enough()); } } w.stop(); w.printEvery(1, "> craft "); for (unsigned int c = 0; c < _categories.size(); ++c) std::stable_sort(_categories[c].begin(), _categories[c].end(), sortCanCraftPredicate); } void PaneCraftingScreen::addItem( Recipe* recipe ) { ItemInstance instance = recipe->getResultItem(); Item* item = instance.getItem(); CItem* ci = new CItem(instance, recipe, instance.getName());//item->getDescriptionId()); if (item->id == Tile::cloth->id) ci->sortText = "Wool " + ci->text; if (item->id == Item::dye_powder->id) ci->sortText = "ZDye " + ci->text; _items.push_back(ci); if (item->category < 0) return; for (int i = 0; i < (int)categoryBitmasks.size(); ++i) { int bitmask = categoryBitmasks[i]; if ((bitmask & item->category) != 0) _categories[i].push_back( ci ); } } void PaneCraftingScreen::onItemSelected(const ItemPane* forPane, int itemIndexInCurrentCategory) { if (currentCategory >= (int)_categories.size()) return; if (itemIndexInCurrentCategory >= (int)_categories[currentCategory].size()) return; onItemSelected(currentCategory, _categories[currentCategory][itemIndexInCurrentCategory]); } void PaneCraftingScreen::onItemSelected(int buttonIndex, CItem* item) { currentItem = item; currentItemDesc = I18n::getDescriptionString(currentItem->item); if (buttonIndex != currentCategory) { // Clear item buttons for this category clearCategoryItems(); // Setup new buttons for the items in this category const int NumCategoryItems = _categories[buttonIndex].size(); if (pane) delete pane; pane = new ItemPane(this, minecraft->textures, paneRect, NumCategoryItems, height, minecraft->height); pane->f = minecraft->font; currentCategory = buttonIndex; } } void PaneCraftingScreen::clearCategoryItems() { for (unsigned int i = 0; i < currentCategoryButtons.size(); ++i) { delete currentCategoryButtons[i]; } currentCategoryButtons.clear(); } void PaneCraftingScreen::keyPressed( int eventKey ) { if (eventKey == Keyboard::KEY_ESCAPE || eventKey == Keyboard::KEY_E) { minecraft->setScreen(NULL); //minecraft->grabMouse(); } else { super::keyPressed(eventKey); } } void PaneCraftingScreen::craftSelectedItem() { if (!currentItem) return; if (!currentItem->canCraft()) return; ItemInstance resultItem = currentItem->item; if (minecraft->player) { // Remove all items required for the recipe and ... for (unsigned int i = 0; i < currentItem->neededItems.size(); ++i) { CItem::ReqItem& req = currentItem->neededItems[i]; // If the recipe allows any aux-value as ingredients, first deplete // aux == 0 from inventory. Since I'm not sure if this always is // correct, let's only do it for ingredient sandstone for now. ItemInstance toRemove = req.item; if (Tile::sandStone->id == req.item.id && Recipe::ANY_AUX_VALUE == req.item.getAuxValue()) { toRemove.setAuxValue(0); toRemove.count = minecraft->player->inventory->removeResource(toRemove, true); toRemove.setAuxValue(Recipe::ANY_AUX_VALUE); } if (toRemove.count > 0) { minecraft->player->inventory->removeResource(toRemove); } } // ... add the new one! (in this order, to fill empty slots better) // if it doesn't fit, throw it on the ground! if (!minecraft->player->inventory->add(&resultItem)) { minecraft->player->drop(new ItemInstance(resultItem), false); } recheckRecipes(); } } bool PaneCraftingScreen::renderGameBehind() { return false; } bool PaneCraftingScreen::closeOnPlayerHurt() { return true; } void PaneCraftingScreen::filterRecipes(RecipeList& recipes) { for (int i = recipes.size() - 1; i >= 0; --i) { if (!filterRecipe(*recipes[i])) recipes.erase(recipes.begin() + i); } } const std::vector& PaneCraftingScreen::getItems(const ItemPane* forPane) { return _categories[currentCategory]; } void PaneCraftingScreen::setSingleCategoryAndIcon(int categoryBitmask, int categoryIcon) { assert(!minecraft && "setSingleCategoryAndIcon needs to be called from subclass constructor!\n"); numCategories = 1; categoryIcons.clear(); categoryIcons.push_back(categoryIcon); categoryBitmasks.clear(); categoryBitmasks.push_back(categoryBitmask); } // // Craft button // CraftButton::CraftButton( int id) : super(id, ""), bg(NULL), bgSelected(NULL), numItems(0) { } CraftButton::~CraftButton() { delete bg; delete bgSelected; } void CraftButton::setSize(float w, float h ) { this->width = (int)w; this->height = (int)h; if (bg && bgSelected) { bg->setSize(w, h); bgSelected->setSize(w, h); } } void CraftButton::init( Textures* textures) { NinePatchFactory builder(textures, "gui/spritesheet.png"); bg = builder.createSymmetrical(IntRectangle(112, 0, 8, 67), 2, 2); bgSelected = builder.createSymmetrical(IntRectangle(120, 0, 8, 67), 2, 2); } IntRectangle CraftButton::getItemPos( int i ) { return IntRectangle(); } void CraftButton::renderBg(Minecraft* minecraft, int xm, int ym) { if (!bg || !bgSelected) return; //fill(x+1, y+1, x+w-1, y+h-1, 0xff999999); bool hovered = active && (minecraft->useTouchscreen()? (_currentlyDown && xm >= x && ym >= y && xm < x + width && ym < y + height) : isInside(xm, ym)); if (hovered || selected) bgSelected->draw(Tesselator::instance, (float)x, (float)y); else bg->draw(Tesselator::instance, (float)x, (float)y); }