diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index 41108ace0..289c50b91 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -60,6 +60,7 @@ #include "Common/Network/PlatformNetworkManagerStub.h" #endif +#include "../Minecraft.World/Recipes.h" #ifdef _DURANGO #include "../Minecraft.World/DurangoStats.h" @@ -244,6 +245,10 @@ void ClientConnection::handleLogin(shared_ptr packet) iUserID=m_userIndex; TelemetryManager->SetMultiplayerInstanceId(packet->m_multiplayerInstanceId); + + if (Recipes::getInstance()->m_bPendingRecipeRebuild) { + Recipes::getInstance()->rebuildRecipeArray(); + } } else { @@ -2013,6 +2018,7 @@ void ClientConnection::handleEntityActionAtPosition(shared_ptr packet) { // printf("Client: handlePreLogin\n"); + Recipes::getInstance()->m_bPendingRecipeRebuild = true; #if 1 // 4J - Check that we can play with all the players already in the game who have Friends-Only UGC set BOOL canPlay = TRUE; @@ -3742,7 +3748,12 @@ void ClientConnection::handleSoundEvent(shared_ptr packet) void ClientConnection::handleCustomPayload(shared_ptr customPayloadPacket) { - if (CustomPayloadPacket::TRADER_LIST_PACKET.compare(customPayloadPacket->identifier) == 0) + if (CustomPayloadPacket::UPDATE_CRAFTING_RECIPES_PACKET.compare(customPayloadPacket->identifier) == 0) + { + Recipes::getInstance()->rebuildRecipeArray(customPayloadPacket); + Recipes::getInstance()->m_bPendingRecipeRebuild = false; + } + else if (CustomPayloadPacket::TRADER_LIST_PACKET.compare(customPayloadPacket->identifier) == 0) { ByteArrayInputStream bais(customPayloadPacket->data); DataInputStream input(&bais); diff --git a/Minecraft.Client/PendingConnection.cpp b/Minecraft.Client/PendingConnection.cpp index 88608cb17..47e88f4ac 100644 --- a/Minecraft.Client/PendingConnection.cpp +++ b/Minecraft.Client/PendingConnection.cpp @@ -14,6 +14,7 @@ #include "../Minecraft.World/net.minecraft.world.item.h" #include "../Minecraft.World/SharedConstants.h" #include "Settings.h" +#include "../Minecraft.World/Recipes.h" #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) #include "../Minecraft.Server/ServerLogManager.h" #include "../Minecraft.Server/Access/Access.h" @@ -336,6 +337,7 @@ void PendingConnection::handleAcceptedLogin(shared_ptr packet) #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) ServerRuntime::ServerLogManager::OnAcceptedPlayerLogin(GetPendingConnectionSmallId(connection), name); #endif + connection->send(std::make_shared(CustomPayloadPacket::UPDATE_CRAFTING_RECIPES_PACKET, Recipes::getInstance()->buildSyncedRecipeArray())); server->getPlayers()->placeNewPlayer(connection, playerEntity, packet); connection = nullptr; // We've moved responsibility for this over to the new PlayerConnection, nullptr so we don't delete our reference to it here in our dtor } diff --git a/Minecraft.World/CustomPayloadPacket.cpp b/Minecraft.World/CustomPayloadPacket.cpp index e86f01de0..8d6149eb3 100644 --- a/Minecraft.World/CustomPayloadPacket.cpp +++ b/Minecraft.World/CustomPayloadPacket.cpp @@ -14,6 +14,9 @@ const wstring CustomPayloadPacket::SET_ADVENTURE_COMMAND_PACKET = L"MC|AdvCdm"; const wstring CustomPayloadPacket::SET_BEACON_PACKET = L"MC|Beacon"; const wstring CustomPayloadPacket::SET_ITEM_NAME_PACKET = L"MC|ItemName"; +// MinecraftConsoles-defined custom packets +const wstring CustomPayloadPacket::UPDATE_CRAFTING_RECIPES_PACKET = L"MC|Recipes"; + CustomPayloadPacket::CustomPayloadPacket() { } diff --git a/Minecraft.World/CustomPayloadPacket.h b/Minecraft.World/CustomPayloadPacket.h index 82a3f6e26..b60c3af74 100644 --- a/Minecraft.World/CustomPayloadPacket.h +++ b/Minecraft.World/CustomPayloadPacket.h @@ -17,6 +17,9 @@ public: static const wstring SET_BEACON_PACKET; static const wstring SET_ITEM_NAME_PACKET; + // MinecraftConsoles-defined custom packets + static const wstring UPDATE_CRAFTING_RECIPES_PACKET; + wstring identifier; int length; byteArray data; diff --git a/Minecraft.World/Recipes.cpp b/Minecraft.World/Recipes.cpp index 48a04e15a..94e5faac5 100644 --- a/Minecraft.World/Recipes.cpp +++ b/Minecraft.World/Recipes.cpp @@ -1,3 +1,5 @@ +#include "Recipes.h" +#include "Recipes.h" #include "stdafx.h" #include "Container.h" #include "AbstractContainerMenu.h" @@ -8,6 +10,8 @@ #include "net.minecraft.world.level.tile.h" #include "net.minecraft.world.item.crafting.h" +#include "../Minecraft.World/CustomPayloadPacket.h" + Recipes *Recipes::instance = nullptr; ArmorRecipes *Recipes::pArmorRecipes=nullptr; ClothDyeRecipes *Recipes::pClothDyeRecipes=nullptr; @@ -30,25 +34,23 @@ void Recipes::_init() recipies = new RecipyList(); } -Recipes::Recipes() -{ - int iCount=0; +void Recipes::loadAllRecipes() { + int iCount = 0; _init(); - - pArmorRecipes = new ArmorRecipes; - pClothDyeRecipes = new ClothDyeRecipes; - pFoodRecipies = new FoodRecipies; - pOreRecipies = new OreRecipies; - pStructureRecipies = new StructureRecipies; - pToolRecipies = new ToolRecipies; - pWeaponRecipies = new WeaponRecipies; + if (pArmorRecipes == nullptr) pArmorRecipes = new ArmorRecipes; + if (pClothDyeRecipes == nullptr) pClothDyeRecipes = new ClothDyeRecipes; + if (pFoodRecipies == nullptr) pFoodRecipies = new FoodRecipies; + if (pOreRecipies == nullptr) pOreRecipies = new OreRecipies; + if (pStructureRecipies == nullptr) pStructureRecipies = new StructureRecipies; + if (pToolRecipies == nullptr) pToolRecipies = new ToolRecipies; + if (pWeaponRecipies == nullptr) pWeaponRecipies = new WeaponRecipies; // 4J Stu - These just don't work with our crafting menu //recipies->push_back(new ArmorDyeRecipe()); //recipies->add(new MapCloningRecipe()); //recipies->add(new MapExtendingRecipe()); //recipies->add(new FireworksRecipe()); - pFireworksRecipes = new FireworksRecipe(); + if (pFireworksRecipes == nullptr) pFireworksRecipes = new FireworksRecipe(); addShapedRecipy(new ItemInstance(Tile::wood, 4, 0), // @@ -542,7 +544,7 @@ Recipes::Recipes() L'#', Tile::wood, L'V'); - addShapedRecipy(new ItemInstance((Item *)Item::fishingRod, 1), // + addShapedRecipy(new ItemInstance((Item*)Item::fishingRod, 1), // L"ssscicig", L" #", // L" #X", // @@ -575,7 +577,7 @@ Recipes::Recipes() L'F'); // Moved bow and arrow in from weapons to avoid stacking on the group name display - addShapedRecipy(new ItemInstance((Item *)Item::bow, 1), // + addShapedRecipy(new ItemInstance((Item*)Item::bow, 1), // L"ssscicig", L" #X", // L"# X", // @@ -739,12 +741,12 @@ Recipes::Recipes() addShapelessRecipy(new ItemInstance(Item::fireball, 3), // L"iiig", - Item::gunpowder, Item::blazePowder,Item::coal, + Item::gunpowder, Item::blazePowder, Item::coal, L'T'); addShapelessRecipy(new ItemInstance(Item::fireball, 3), // L"iizg", - Item::gunpowder, Item::blazePowder,new ItemInstance(Item::coal, 1, CoalItem::CHAR_COAL), + Item::gunpowder, Item::blazePowder, new ItemInstance(Item::coal, 1, CoalItem::CHAR_COAL), L'T'); addShapedRecipy(new ItemInstance(Item::lead, 2), // @@ -951,21 +953,21 @@ Recipes::Recipes() L'D'); // 4J - TODO - put these new 1.7.3 items in required place within recipes - addShapedRecipy(new ItemInstance(static_cast(Tile::pistonBase), 1), // + addShapedRecipy(new ItemInstance(static_cast(Tile::pistonBase), 1), // L"sssctcicictg", - L"TTT", // - L"#X#", // - L"#R#", // + L"TTT", // + L"#X#", // + L"#R#", // - L'#', Tile::cobblestone, L'X', Item::ironIngot, L'R', Item::redStone, L'T', Tile::wood, + L'#', Tile::cobblestone, L'X', Item::ironIngot, L'R', Item::redStone, L'T', Tile::wood, L'M'); - addShapedRecipy(new ItemInstance(static_cast(Tile::pistonStickyBase), 1), // + addShapedRecipy(new ItemInstance(static_cast(Tile::pistonStickyBase), 1), // L"sscictg", - L"S", // - L"P", // + L"S", // + L"P", // - L'S', Item::slimeBall, L'P', Tile::pistonBase, + L'S', Item::slimeBall, L'P', Tile::pistonBase, L'M'); @@ -978,7 +980,7 @@ Recipes::Recipes() L'P', Item::paper, L'G', Item::gunpowder, L'D'); - addShapedRecipy(new ItemInstance(Item::fireworksCharge,1), // + addShapedRecipy(new ItemInstance(Item::fireworksCharge, 1), // L"sscicig", L" D ", // L" G ", // @@ -986,7 +988,7 @@ Recipes::Recipes() L'D', Item::dye_powder, L'G', Item::gunpowder, L'D'); - addShapedRecipy(new ItemInstance(Item::fireworksCharge,1), // + addShapedRecipy(new ItemInstance(Item::fireworksCharge, 1), // L"sscicig", L" D ", // L" C ", // @@ -1025,6 +1027,23 @@ Recipes::Recipes() buildRecipeIngredientsArray(); } +void Recipes::deleteAllRecipes() { + for (size_t i = 0; i < recipies->size(); i++) { + delete recipies->at(i); + } + + delete recipies; + recipies = nullptr; + + delete m_pRecipeIngredientsRequired; + m_pRecipeIngredientsRequired = nullptr; +} + +Recipes::Recipes() +{ + loadAllRecipes(); +} + // 4J-PB - this function has been substantially changed due to the differences with a va_list of classes in C++ and Java ShapedRecipy *Recipes::addShapedRecipy(ItemInstance *result, ...) { @@ -1322,4 +1341,181 @@ void Recipes::buildRecipeIngredientsArray(void) Recipy::INGREDIENTS_REQUIRED *Recipes::getRecipeIngredientsArray(void) { return m_pRecipeIngredientsRequired; -} \ No newline at end of file +} + +inline void serializeItemInstance(DataOutputStream* dos, ItemInstance* result) { + dos->writeInt(result->id); + dos->writeInt(result->count); + dos->writeInt(result->getAuxValue()); + + unsigned char itemFlags = 0; + { + if (result->isEnchanted()) { + itemFlags |= 0x01; + } + + if (result->tag != nullptr) { + if (result->tag->contains(L"display")) { + CompoundTag* displayTag = result->tag->getCompound(L"display"); + if (displayTag->contains(L"Name")) + { + //title = displayTag->getString(L"Name"); + itemFlags |= 0x02; + } + if (displayTag->contains(L"Lore")) { + if (displayTag->getList(L"Lore")->size() > 0) { + itemFlags |= 0x03; + } + } + } + } + } + + + dos->writeByte(itemFlags); + + if (itemFlags & 0x01) { + ListTag* list = result->getEnchantmentTags(); + if (list != nullptr) { + dos->writeInt(list->size()); + for (int i = 0; i < list->size(); i++) { + dos->writeShort(list->get(i)->getShort((wchar_t*)ItemInstance::TAG_ENCH_ID)); + dos->writeShort(list->get(i)->getShort((wchar_t*)ItemInstance::TAG_ENCH_LEVEL)); + } + } + } + + if (itemFlags & 0x02) { + dos->writeUTF(result->tag->getCompound(L"display")->getString(L"Name")); + } + + if (itemFlags & 0x03) { + ListTag* lore = (ListTag *) result->tag->getCompound(L"display")->getList(L"Lore"); + dos->writeInt(lore->size()); + for (int i = 0; i < lore->size(); i++) + { + dos->writeUTF(lore->get(i)->data); + } + } +} + +inline ItemInstance* parseItemInstance(DataInputStream* dis) { + int itemId = dis->readInt(); + int itemCount = dis->readInt(); + int itemAux = dis->readInt(); + + unsigned char itemFlags = dis->readByte(); + + ItemInstance* item = new ItemInstance(itemId, itemCount, itemAux); + + if (itemFlags & 0x01) { + int enchantmentCount = dis->readInt(); + if (enchantmentCount > 0) { + if (item->tag == nullptr) item->setTag(new CompoundTag()); + if (!item->tag->contains(L"ench")) item->tag->put(L"ench", new ListTag(L"ench")); + + ListTag* list = static_cast *>(item->tag->get(L"ench")); + + for (int i = 0; i < enchantmentCount; i++) { + short enchantmentId = dis->readShort(); + short enchantmentLevel = dis->readShort(); + + CompoundTag* ench = new CompoundTag(); + ench->putShort((wchar_t*)ItemInstance::TAG_ENCH_ID, static_cast(enchantmentId)); + ench->putShort((wchar_t*)ItemInstance::TAG_ENCH_LEVEL, static_cast(enchantmentLevel)); + list->add(ench); + } + } + } + + if (itemFlags & 0x02) { + item->setHoverName(dis->readUTF()); + } + + if (itemFlags & 0x03) { + int loreCount = dis->readInt(); + + if (loreCount > 0) { + if (item->tag == nullptr) item->setTag(new CompoundTag()); + if (!item->tag->contains(L"display")) item->tag->putCompound(L"display", new CompoundTag()); + CompoundTag* displayTag = item->tag->getCompound(L"display"); + if (!displayTag->contains(L"Lore")) displayTag->put(L"Lore", new ListTag(L"Lore")); + + + ListTag* list = static_cast *>(item->tag->get(L"Lore")); + for (int i = 0; i < loreCount; i++) { + wstring loreLine = dis->readUTF(); + list->add(new StringTag(loreLine)); + } + } + } + + return item; +} + +void Recipes::rebuildRecipeArray() { + deleteAllRecipes(); + loadAllRecipes(); +} + +void Recipes::rebuildRecipeArray(std::shared_ptr packet) { + deleteAllRecipes(); + + ByteArrayInputStream bais(packet->data); + DataInputStream input(&bais); + _init(); + + int recipeCount = input.readInt(); + for (int i = 0; i < recipeCount; i++) { + unsigned char group = input.readByte(); + unsigned char recipeType = input.readByte(); + + if (recipeType == 0) { // Shapeless recipe + int ingredientCount = input.readInt(); + vector* ingredients = new vector(); + for (int j = 0; j < ingredientCount; j++) { + ingredients->emplace_back(parseItemInstance(&input)); + } + + ItemInstance* result = parseItemInstance(&input); + ShapelessRecipy* recipe = new ShapelessRecipy(result, ingredients, static_cast(group)); + recipies->push_back(recipe); + } + } + + buildRecipeIngredientsArray(); //we manually add recipes so we need to build the ingredients array +} + +byteArray Recipes::buildSyncedRecipeArray() { + int iRecipeC = static_cast(recipies->size()); + + ByteArrayOutputStream baos; + DataOutputStream dos(&baos); + + dos.writeInt(iRecipeC); + for (int i = 0; i < iRecipeC; i++) { + Recipy* recipe = (*recipies)[i]; + dos.writeByte(recipe->getGroup()); + + if (dynamic_cast(recipe) != nullptr) { + ShapelessRecipy* shapeless = static_cast(recipe); + dos.writeByte(0); //0 for shapeless recipe + + std::vector* ingredients = shapeless->getIngredients(); + dos.writeInt(static_cast(ingredients->size())); + + for (auto& ingredient : *ingredients) { + serializeItemInstance(&dos, ingredient); + } + } else if (dynamic_cast(recipe) != nullptr) { + ShapedRecipy* shapedRecipe = static_cast(recipe); + dos.writeByte(1); //1 for shaped recipe + + continue; + } + + serializeItemInstance(&dos, const_cast(recipe->getResultItem())); + } + + return baos.toByteArray(); +} diff --git a/Minecraft.World/Recipes.h b/Minecraft.World/Recipes.h index d6e508a89..2e2799593 100644 --- a/Minecraft.World/Recipes.h +++ b/Minecraft.World/Recipes.h @@ -21,6 +21,7 @@ import net.minecraft.world.level.tile.Tile; using namespace std; class CraftingContainer; +class CustomPayloadPacket; class FireTile; class ArmorRecipes; @@ -84,6 +85,8 @@ public: private: void _init(); // 4J add + void loadAllRecipes(); + void deleteAllRecipes(); Recipes(); public: @@ -97,6 +100,13 @@ public: shared_ptr getItemForRecipe(Recipy *r); Recipy::INGREDIENTS_REQUIRED *getRecipeIngredientsArray(); + bool m_bPendingRecipeRebuild = false; + + void rebuildRecipeArray(); + void rebuildRecipeArray(std::shared_ptr packet); + + byteArray buildSyncedRecipeArray(); + private: void buildRecipeIngredientsArray(); Recipy::INGREDIENTS_REQUIRED *m_pRecipeIngredientsRequired; diff --git a/Minecraft.World/ShapelessRecipy.h b/Minecraft.World/ShapelessRecipy.h index bb1dfcf7d..713a41faa 100644 --- a/Minecraft.World/ShapelessRecipy.h +++ b/Minecraft.World/ShapelessRecipy.h @@ -16,6 +16,8 @@ public: virtual shared_ptr assemble(shared_ptr craftSlots); virtual int size(); + vector* getIngredients() { return ingredients; } + // 4J-PB - to return the items required to make a recipe virtual bool reqs(int iRecipe); virtual void reqs(INGREDIENTS_REQUIRED *pIngReq);