Files
minecraft-pe-0.6.1/src/client/gui/screens/crafting/PaneCraftingScreen.cpp

557 lines
17 KiB
C++
Executable File

#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<ItemInstance> 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<CItem*>& 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);
}