diff --git a/src/main/java/emu/grasscutter/game/managers/CookingManager.java b/src/main/java/emu/grasscutter/game/managers/CookingManager.java index ad5b92b2a..bcc31bb3e 100644 --- a/src/main/java/emu/grasscutter/game/managers/CookingManager.java +++ b/src/main/java/emu/grasscutter/game/managers/CookingManager.java @@ -7,11 +7,23 @@ import java.util.List; import java.util.Set; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.data.excels.ItemData; +import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.net.proto.CookRecipeDataOuterClass; +import emu.grasscutter.net.proto.PlayerCookArgsReqOuterClass.PlayerCookArgsReq; +import emu.grasscutter.net.proto.PlayerCookReqOuterClass.PlayerCookReq; +import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; import emu.grasscutter.server.packet.send.PacketCookDataNotify; +import emu.grasscutter.server.packet.send.PacketCookRecipeDataNotify; +import emu.grasscutter.server.packet.send.PacketPlayerCookArgsRsp; +import emu.grasscutter.server.packet.send.PacketPlayerCookRsp; public class CookingManager { + private static final int MANUAL_PERFECT_COOK_QUALITY = 3; + private static Set defaultUnlockedRecipies; private final Player player; @@ -30,10 +42,83 @@ public class CookingManager { } } + /******************** + * Unlocking for recipies. + ********************/ + public synchronized boolean unlockRecipe(GameItem recipeItem) { + // Make sure this is actually a cooking recipe. + if (!recipeItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_COOK_RECIPE")) { + return false; + } + + // Determine the recipe we should unlock. + int recipeId = Integer.parseInt(recipeItem.getItemData().getItemUse().get(0).getUseParam().get(0)); + + // Remove the item from the player's inventory. + // We need to do this here, before sending CookRecipeDataNotify, or the the UI won't correctly update. + player.getInventory().removeItem(recipeItem, 1); + + // Tell the client that this blueprint is now unlocked and add the unlocked item to the player. + this.player.getUnlockedRecipies().put(recipeId, 0); + this.player.sendPacket(new PacketCookRecipeDataNotify(recipeId)); + + return true; + } + /******************** * Perform cooking. ********************/ - + public void handlePlayerCookReq(PlayerCookReq req) { + // Get info from the request. + int recipeId = req.getRecipeId(); + int quality = req.getQteQuality(); + int count = req.getCookCount(); + + // Get recipe data. + var recipeData = GameData.getCookRecipeDataMap().get(recipeId); + if (recipeData == null) { + this.player.sendPacket(new PacketPlayerCookRsp(Retcode.RET_FAIL)); + return; + } + + // Get proficiency for player. + int proficiency = this.player.getUnlockedRecipies().getOrDefault(recipeId, 0); + + // Try consuming materials. + boolean success = player.getInventory().payItems(recipeData.getInputVec().toArray(new ItemParamData[0]), count, ActionReason.Cook); + if (!success) { + this.player.sendPacket(new PacketPlayerCookRsp(Retcode.RET_FAIL)); //ToDo: Probably the wrong return code. + } + + // Obtain results. + int qualityIndex = + quality == 0 + ? 2 + : quality - 1; + + ItemParamData resultParam = recipeData.getQualityOutputVec().get(qualityIndex); + ItemData resultItemData = GameData.getItemDataMap().get(resultParam.getItemId()); + + GameItem cookResult = new GameItem(resultItemData, resultParam.getCount() * count); + this.player.getInventory().addItem(cookResult); + + // Increase player proficiency, if this was a manual perfect cook. + if (quality == MANUAL_PERFECT_COOK_QUALITY) { + proficiency = Math.min(proficiency + 1, recipeData.getMaxProficiency()); + this.player.getUnlockedRecipies().put(recipeId, proficiency); + } + + // Send response. + this.player.sendPacket(new PacketPlayerCookRsp(cookResult, quality, count, recipeId, proficiency)); + } + + + /******************** + * Cooking arguments. + ********************/ + public void handleCookArgsReq(PlayerCookArgsReq req) { + this.player.sendPacket(new PacketPlayerCookArgsRsp()); + } /******************** * Notify unlocked recipies. diff --git a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java index 47fc2d8c1..01224ea37 100644 --- a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java +++ b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java @@ -852,6 +852,11 @@ public class InventoryManager { // Unlock. useSuccess = player.getServer().getCombineManger().unlockCombineDiagram(player, useItem); } + // Handle cooking recipies. + if (useItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_COOK_RECIPE")) { + // Unlock. + useSuccess = player.getCookingManager().unlockRecipe(useItem); + } break; case MATERIAL_FURNITURE_FORMULA: case MATERIAL_FURNITURE_SUITE_FORMULA: diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerCookArgsReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerCookArgsReq.java new file mode 100644 index 000000000..c55dca3be --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerCookArgsReq.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerCookArgsReqOuterClass; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.PlayerCookArgsReq) +public class HandlerPlayerCookArgsReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PlayerCookArgsReqOuterClass.PlayerCookArgsReq req = PlayerCookArgsReqOuterClass.PlayerCookArgsReq.parseFrom(payload); + session.getPlayer().getCookingManager().handleCookArgsReq(req); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerCookReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerCookReq.java new file mode 100644 index 000000000..462083508 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerCookReq.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerCookReqOuterClass; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.PlayerCookReq) +public class HandlerPlayerCookReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PlayerCookReqOuterClass.PlayerCookReq req = PlayerCookReqOuterClass.PlayerCookReq.parseFrom(payload); + session.getPlayer().getCookingManager().handlePlayerCookReq(req); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCookRecipeDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCookRecipeDataNotify.java new file mode 100644 index 000000000..00762d92a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCookRecipeDataNotify.java @@ -0,0 +1,28 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CookRecipeDataNotifyOuterClass.CookRecipeDataNotify; +import emu.grasscutter.net.proto.CookRecipeDataOuterClass.CookRecipeData; + +public class PacketCookRecipeDataNotify extends BasePacket { + public PacketCookRecipeDataNotify(CookRecipeData recipe) { + super(PacketOpcodes.CookRecipeDataNotify); + + CookRecipeDataNotify proto = CookRecipeDataNotify.newBuilder() + .setRecipeData(recipe) + .build(); + + this.setData(proto); + } + + public PacketCookRecipeDataNotify(int recipeId) { + super(PacketOpcodes.CookRecipeDataNotify); + + CookRecipeDataNotify proto = CookRecipeDataNotify.newBuilder() + .setRecipeData(CookRecipeData.newBuilder().setRecipeId(recipeId)) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerCookArgsRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerCookArgsRsp.java new file mode 100644 index 000000000..bfdbe3347 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerCookArgsRsp.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerCookArgsRspOuterClass.PlayerCookArgsRsp; + +public class PacketPlayerCookArgsRsp extends BasePacket { + + public PacketPlayerCookArgsRsp() { + super(PacketOpcodes.PlayerCookArgsRsp); + + PlayerCookArgsRsp proto = PlayerCookArgsRsp.newBuilder() + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerCookRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerCookRsp.java new file mode 100644 index 000000000..517ba36e2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerCookRsp.java @@ -0,0 +1,42 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CookRecipeDataOuterClass.CookRecipeData; +import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; +import emu.grasscutter.net.proto.PlayerCookRspOuterClass.PlayerCookRsp; +import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; + +public class PacketPlayerCookRsp extends BasePacket { + public PacketPlayerCookRsp(Retcode retcode) { + super(PacketOpcodes.PlayerCookRsp); + + PlayerCookRsp proto = PlayerCookRsp.newBuilder() + .setRetcode(retcode.getNumber()) + .build(); + + this.setData(proto); + } + + public PacketPlayerCookRsp(GameItem output, int quality, int count, int recipeId, int proficiency) { + super(PacketOpcodes.PlayerCookRsp); + + PlayerCookRsp proto = PlayerCookRsp.newBuilder() + .setRecipeData( + CookRecipeData.newBuilder() + .setRecipeId(recipeId) + .setProficiency(proficiency) + ) + .setQteQuality(quality) + .addItemList( + ItemParam.newBuilder() + .setItemId(output.getItemId()) + .setCount(output.getCount()) + ) + .setCookCount(count) + .build(); + + this.setData(proto); + } +}