CookingManager.java 7.57 KB
Newer Older
1
2
3
4
5
6
7
8
9
package emu.grasscutter.game.managers;

import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import emu.grasscutter.data.GameData;
10
11
12
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem;
Melledy's avatar
Melledy committed
13
import emu.grasscutter.game.player.BasePlayerManager;
14
import emu.grasscutter.game.player.Player;
15
import emu.grasscutter.game.props.ActionReason;
16
import emu.grasscutter.game.props.ItemUseOp;
17
import emu.grasscutter.net.proto.CookRecipeDataOuterClass;
18
19
20
import emu.grasscutter.net.proto.PlayerCookArgsReqOuterClass.PlayerCookArgsReq;
import emu.grasscutter.net.proto.PlayerCookReqOuterClass.PlayerCookReq;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
21
import emu.grasscutter.server.packet.send.PacketCookDataNotify;
22
23
24
import emu.grasscutter.server.packet.send.PacketCookRecipeDataNotify;
import emu.grasscutter.server.packet.send.PacketPlayerCookArgsRsp;
import emu.grasscutter.server.packet.send.PacketPlayerCookRsp;
25
import io.netty.util.internal.ThreadLocalRandom;
26

Melledy's avatar
Melledy committed
27
public class CookingManager extends BasePlayerManager {
28
    private static final int MANUAL_PERFECT_COOK_QUALITY = 3;
29
30
31
    private static Set<Integer> defaultUnlockedRecipies;

    public CookingManager(Player player) {
Melledy's avatar
Melledy committed
32
        super(player);
33
34
35
36
37
38
39
40
41
42
43
44
45
    }

    public static void initialize() {
        // Initialize the set of recipies that are unlocked by default.
        defaultUnlockedRecipies = new HashSet<>();

        for (var recipe : GameData.getCookRecipeDataMap().values()) {
            if (recipe.isDefaultUnlocked()) {
                defaultUnlockedRecipies.add(recipe.getId());
            }
        }
    }

46
47
48
49
    /********************
     * Unlocking for recipies.
     ********************/
    public synchronized boolean unlockRecipe(GameItem recipeItem) {
50
        // Make sure this is actually a cooking recipe.
51
        if (recipeItem.getItemData().getItemUse().get(0).getUseOp() != ItemUseOp.ITEM_USE_UNLOCK_COOK_RECIPE) {
52
53
            return false;
        }
54

55
        // Determine the recipe we should unlock.
56
        int recipeId = Integer.parseInt(recipeItem.getItemData().getItemUse().get(0).getUseParam()[0]);
57

58
59
60
        // 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);
61

62
63
64
        // 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));
65

66
67
        return true;
    }
68

69
70
71
    /********************
     * Perform cooking.
     ********************/
72
73
74
75
76
77
78
79
80
81
    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;
        };
    }

82
83
84
85
86
    public void handlePlayerCookReq(PlayerCookReq req) {
        // Get info from the request.
        int recipeId = req.getRecipeId();
        int quality = req.getQteQuality();
        int count = req.getCookCount();
87
        int avatar = req.getAssistAvatar();
88
89
90
91
92
93
94
95
96
97
98
99

        // 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.
100
101
102
103
        boolean success = player.getInventory().payItems(recipeData.getInputVec().toArray(new ItemParamData[0]), count, ActionReason.Cook);
        if (!success) {
            this.player.sendPacket(new PacketPlayerCookRsp(Retcode.RET_FAIL));
        }
104

105
        // Get result item information.
github-actions's avatar
github-actions committed
106
107
108
        int qualityIndex =
            quality == 0
            ? 2
109
110
111
112
113
            : quality - 1;

        ItemParamData resultParam = recipeData.getQualityOutputVec().get(qualityIndex);
        ItemData resultItemData = GameData.getItemDataMap().get(resultParam.getItemId());

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
        // 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<GameItem> 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);
        }
142
143
144
145
146
147
148
149

        // 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.
150
        this.player.sendPacket(new PacketPlayerCookRsp(cookResults, quality, count, recipeId, proficiency));
151
152
153
154
155
156
157
158
    }

    /********************
     * Cooking arguments.
     ********************/
    public void handleCookArgsReq(PlayerCookArgsReq req) {
        this.player.sendPacket(new PacketPlayerCookArgsRsp());
    }
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200

     /********************
     * Notify unlocked recipies.
     ********************/
    private void addDefaultUnlocked() {
        // Get recipies that are already unlocked.
        var unlockedRecipies = this.player.getUnlockedRecipies();

        // Get recipies that should be unlocked by default but aren't.
        var additionalRecipies = new HashSet<>(defaultUnlockedRecipies);
        additionalRecipies.removeAll(unlockedRecipies.keySet());

        // Add them to the player.
        for (int id : additionalRecipies) {
            unlockedRecipies.put(id, 0);
        }
    }

    public void sendCookDataNofity() {
        // Default unlocked recipies to player if they don't have them yet.
        this.addDefaultUnlocked();

        // Get unlocked recipies.
        var unlockedRecipies = this.player.getUnlockedRecipies();

        // Construct CookRecipeData protos.
        List<CookRecipeDataOuterClass.CookRecipeData> data = new ArrayList<>();
        for (var recipe : unlockedRecipies.entrySet()) {
            int recipeId = recipe.getKey();
            int proficiency = recipe.getValue();

            CookRecipeDataOuterClass.CookRecipeData proto = CookRecipeDataOuterClass.CookRecipeData.newBuilder()
                .setRecipeId(recipeId)
                .setProficiency(proficiency)
                .build();
            data.add(proto);
        }

        // Send packet.
        this.player.sendPacket(new PacketCookDataNotify(data));
    }
}