From 13a7f08665e408c8d26016eb4e4e6b43823bda1d Mon Sep 17 00:00:00 2001 From: GanyusLeftHorn <1244229+GanyusLeftHorn@users.noreply.github.com> Date: Sat, 2 Jul 2022 02:14:43 -0700 Subject: [PATCH] Add character's specialty food. --- .../java/emu/grasscutter/data/GameData.java | 5 ++ .../data/excels/CookBonusData.java | 45 ++++++++++ .../game/managers/CookingManager.java | 83 ++++++++++++++----- .../packet/send/PacketPlayerCookRsp.java | 20 +++-- 4 files changed, 122 insertions(+), 31 deletions(-) create mode 100644 src/main/java/emu/grasscutter/data/excels/CookBonusData.java diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 56f420d68..89bf06df1 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -99,6 +99,7 @@ public class GameData { private static final Int2ObjectMap battlePassMissionDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap battlePassRewardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap cookRecipeDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap cookBonusDataMap = new Int2ObjectOpenHashMap<>(); @Getter private static final Int2ObjectMap activityDataMap = new Int2ObjectOpenHashMap<>(); @Getter private static final Int2ObjectMap activityWatcherDataMap = new Int2ObjectOpenHashMap<>(); @@ -441,4 +442,8 @@ public class GameData { public static Int2ObjectMap getCookRecipeDataMap() { return cookRecipeDataMap; } + + public static Int2ObjectMap getCookBonusDataMap() { + return cookBonusDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/excels/CookBonusData.java b/src/main/java/emu/grasscutter/data/excels/CookBonusData.java new file mode 100644 index 000000000..9e9ef4e7c --- /dev/null +++ b/src/main/java/emu/grasscutter/data/excels/CookBonusData.java @@ -0,0 +1,45 @@ +package emu.grasscutter.data.excels; + +import java.util.List; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import emu.grasscutter.data.ResourceType.LoadPriority; +import emu.grasscutter.data.common.ItemParamData; + +@ResourceType(name = {"CookBonusExcelConfigData.json"}, loadPriority = LoadPriority.LOW) +public class CookBonusData extends GameResource { + private int avatarId; + private int recipeId; + private int[] paramVec; + private int[] complexParamVec; + + @Override + public int getId() { + return this.avatarId; + } + + public int getAvatarId() { + return avatarId; + } + + public int getRecipeId() { + return recipeId; + } + + public int[] getParamVec() { + return paramVec; + } + + public int[] getComplexParamVec() { + return complexParamVec; + } + + public int getReplacementItemId() { + return this.paramVec[0]; + } + + @Override + public void onLoad() { + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/CookingManager.java b/src/main/java/emu/grasscutter/game/managers/CookingManager.java index bcc31bb3e..855fd56ec 100644 --- a/src/main/java/emu/grasscutter/game/managers/CookingManager.java +++ b/src/main/java/emu/grasscutter/game/managers/CookingManager.java @@ -20,6 +20,7 @@ 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; +import io.netty.util.internal.ThreadLocalRandom; public class CookingManager { private static final int MANUAL_PERFECT_COOK_QUALITY = 3; @@ -46,33 +47,44 @@ 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; - } + // 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)); + // 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); + // 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)); + // 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; - } + return true; + } /******************** * Perform cooking. ********************/ + private double getSpecialtyChance(ItemData cookedItem) { + // Chances taken from the Wiki. + return switch (cookedItem.getRankLevel()) { + case 1 -> 0.25; + case 2 -> 0.2; + case 3 -> 0.15; + default -> 0; + }; + } + public void handlePlayerCookReq(PlayerCookReq req) { // Get info from the request. int recipeId = req.getRecipeId(); int quality = req.getQteQuality(); int count = req.getCookCount(); + int avatar = req.getAssistAvatar(); // Get recipe data. var recipeData = GameData.getCookRecipeDataMap().get(recipeId); @@ -85,12 +97,12 @@ public class CookingManager { 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. - } + boolean success = player.getInventory().payItems(recipeData.getInputVec().toArray(new ItemParamData[0]), count, ActionReason.Cook); + if (!success) { + this.player.sendPacket(new PacketPlayerCookRsp(Retcode.RET_FAIL)); + } - // Obtain results. + // Get result item information. int qualityIndex = quality == 0 ? 2 @@ -99,8 +111,34 @@ public class CookingManager { 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); + // Handle character's specialties. + int specialtyCount = 0; + double specialtyChance = this.getSpecialtyChance(resultItemData); + + var bonusData = GameData.getCookBonusDataMap().get(avatar); + if (bonusData != null && recipeId == bonusData.getRecipeId()) { + // Roll for specialy replacements. + for (int i = 0; i < count; i++) { + if (ThreadLocalRandom.current().nextDouble() <= specialtyChance) { + specialtyCount++; + } + } + } + + // Obtain results. + List cookResults = new ArrayList<>(); + + int normalCount = count - specialtyCount; + GameItem cookResultNormal = new GameItem(resultItemData, resultParam.getCount() * normalCount); + cookResults.add(cookResultNormal); + this.player.getInventory().addItem(cookResultNormal); + + if (specialtyCount > 0) { + ItemData specialtyItemData = GameData.getItemDataMap().get(bonusData.getReplacementItemId()); + GameItem cookResultSpecialty = new GameItem(specialtyItemData, resultParam.getCount() * specialtyCount); + cookResults.add(cookResultSpecialty); + this.player.getInventory().addItem(cookResultSpecialty); + } // Increase player proficiency, if this was a manual perfect cook. if (quality == MANUAL_PERFECT_COOK_QUALITY) { @@ -109,10 +147,9 @@ public class CookingManager { } // Send response. - this.player.sendPacket(new PacketPlayerCookRsp(cookResult, quality, count, recipeId, proficiency)); + this.player.sendPacket(new PacketPlayerCookRsp(cookResults, quality, count, recipeId, proficiency)); } - /******************** * Cooking arguments. ********************/ diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerCookRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerCookRsp.java index 517ba36e2..8a3d6d5d4 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerCookRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerCookRsp.java @@ -1,5 +1,7 @@ package emu.grasscutter.server.packet.send; +import java.util.List; + import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; @@ -19,23 +21,25 @@ public class PacketPlayerCookRsp extends BasePacket { this.setData(proto); } - public PacketPlayerCookRsp(GameItem output, int quality, int count, int recipeId, int proficiency) { + public PacketPlayerCookRsp(List output, int quality, int count, int recipeId, int proficiency) { super(PacketOpcodes.PlayerCookRsp); - PlayerCookRsp proto = PlayerCookRsp.newBuilder() + PlayerCookRsp.Builder proto = PlayerCookRsp.newBuilder() .setRecipeData( CookRecipeData.newBuilder() .setRecipeId(recipeId) .setProficiency(proficiency) ) .setQteQuality(quality) - .addItemList( + .setCookCount(count); + + for (var item : output) { + proto.addItemList( ItemParam.newBuilder() - .setItemId(output.getItemId()) - .setCount(output.getCount()) - ) - .setCookCount(count) - .build(); + .setItemId(item.getItemId()) + .setCount(item.getCount()) + ); + } this.setData(proto); }