BattlePassManager.java 14.7 KB
Newer Older
1
2
package emu.grasscutter.game.battlepass;

3
4
5
6
7
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
8
9
10
11
12
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

13
14
15
16
17
import org.bson.types.ObjectId;

import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
18
import emu.grasscutter.GameConstants;
GanyusLeftHorn's avatar
GanyusLeftHorn committed
19
import emu.grasscutter.Grasscutter;
20
21
22
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.BattlePassRewardData;
23
import emu.grasscutter.data.excels.ItemData;
24
import emu.grasscutter.data.excels.RewardData;
25
import emu.grasscutter.database.DatabaseHelper;
26
27
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.MaterialType;
Melledy's avatar
Melledy committed
28
import emu.grasscutter.game.player.BasePlayerDataManager;
29
import emu.grasscutter.game.player.Player;
30
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
31
import emu.grasscutter.game.props.BattlePassMissionStatus;
32
import emu.grasscutter.game.props.ItemUseOp;
33
34
35
36
37
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption;
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
38
import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify;
39
40
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
41

42
import lombok.Getter;
43

44
@Entity(value = "battlepass", useDiscriminator = false)
Melledy's avatar
Melledy committed
45
public class BattlePassManager extends BasePlayerDataManager {
github-actions's avatar
github-actions committed
46
47
48
    @Id @Getter private ObjectId id;

    @Indexed private int ownerUid;
49
50
51
    @Getter private int point;
    @Getter private int cyclePoints; // Weekly maximum cap
    @Getter private int level;
github-actions's avatar
github-actions committed
52

53
    @Getter private boolean viewed;
GanyusLeftHorn's avatar
GanyusLeftHorn committed
54
    private boolean paid;
github-actions's avatar
github-actions committed
55

56
57
    private Map<Integer, BattlePassMission> missions;
    private Map<Integer, BattlePassReward> takenRewards;
github-actions's avatar
github-actions committed
58

59
60
    @Deprecated // Morphia only
    public BattlePassManager() {}
61

62
    public BattlePassManager(Player player) {
Melledy's avatar
Melledy committed
63
        super(player);
64
        this.ownerUid = player.getUid();
65
    }
github-actions's avatar
github-actions committed
66

67
    public void setPlayer(Player player) {
github-actions's avatar
github-actions committed
68
69
        this.player = player;
        this.ownerUid = player.getUid();
70
    }
github-actions's avatar
github-actions committed
71

72
    public void updateViewed() {
github-actions's avatar
github-actions committed
73
        this.viewed = true;
74
75
    }

github-actions's avatar
github-actions committed
76
77
78
79
80
81
82
83
84
85
86
    public boolean setLevel(int level) {
        if (level >= 0 && level <= GameConstants.BATTLE_PASS_MAX_LEVEL) {
            this.level = level;
            this.point = 0;
            this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.player));
            return true;
        }
        return false;
    }

    public void addPoints(int points) {
87
        this.addPointsDirectly(points, false);
github-actions's avatar
github-actions committed
88

89
        this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
90
        this.save();
91
92
    }

93
    public void addPointsDirectly(int points, boolean isWeekly) {
github-actions's avatar
github-actions committed
94
95
96
97
98
99
100
101
102
103
        int amount = points;

        if (isWeekly) {
            amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints);
        }

        if (amount <= 0) {
            return;
        }

104
105
        this.point += amount;
        this.cyclePoints += amount;
github-actions's avatar
github-actions committed
106

107
        if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL && this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) {
github-actions's avatar
github-actions committed
108
109
110
111
112
113
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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
            int levelups = Math.floorDiv(this.point, GameConstants.BATTLE_PASS_POINT_PER_LEVEL);

            // Make sure player cant go above max BP level
            levelups = Math.min(levelups, GameConstants.BATTLE_PASS_MAX_LEVEL - levelups);

            // Set new points after level up
            this.point = this.point - (levelups * GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
            this.level += levelups;
        }
    }

    public Map<Integer, BattlePassMission> getMissions() {
        if (this.missions == null) this.missions = new HashMap<>();
        return this.missions;
    }

    // Will return a new empty mission if the mission id is not found
    public BattlePassMission loadMissionById(int id) {
        return getMissions().computeIfAbsent(id, i -> new BattlePassMission(i));
    }

    public boolean hasMission(int id) {
        return getMissions().containsKey(id);
    }

    public boolean isPaid() {
        // ToDo: Change this when we actually support unlocking "paid" BP.
        return true;
    }

    public Map<Integer, BattlePassReward> getTakenRewards() {
        if (this.takenRewards == null) this.takenRewards = new HashMap<>();
        return this.takenRewards;
    }

    // Mission triggers
    public void triggerMission(WatcherTriggerType triggerType) {
        getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType);
    }

    public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
        getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType, param, progress);
    }

    // Handlers
    public void takeMissionPoint(List<Integer> missionIdList) {
        // Obvious exploit check
        if (missionIdList.size() > GameData.getBattlePassMissionDataMap().size()) {
            return;
        }

        List<BattlePassMission> updatedMissions = new ArrayList<>(missionIdList.size());

        for (int id : missionIdList) {
            // Skip if we dont have this mission
            if (!this.hasMission(id)) {
                continue;
            }

            BattlePassMission mission = this.loadMissionById(id);

            if (mission.getData() == null) {
                this.getMissions().remove(mission.getId());
                continue;
            }

            // Take reward
            if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) {
                this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh());
                mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN);

                updatedMissions.add(mission);
            }
        }

        if (updatedMissions.size() > 0) {
            // Save to db
            this.save();

            // Packet
            getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(updatedMissions));
            getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
        }
    }

    private void takeRewardsFromSelectChest(ItemData rewardItemData, int index, ItemParamData entry, List<GameItem> rewardItems) {
        // Sanity checks.
        if (rewardItemData.getItemUse().size() < 1) {
            return;
        }

        // Get possible item choices.
200
        String[] choices = rewardItemData.getItemUse().get(0).getUseParam()[0].split(",");
github-actions's avatar
github-actions committed
201
202
203
204
205
206
207
208
209
        if (choices.length < index) {
            return;
        }

        // Get data for the selected item.
        // This depends on the type of chest.
        int chosenId = Integer.parseInt(choices[index - 1]);

        // For ITEM_USE_ADD_SELECT_ITEM chests, we can directly add the item specified in the chest's data.
210
        if (rewardItemData.getItemUse().get(0).getUseOp() == ItemUseOp.ITEM_USE_ADD_SELECT_ITEM) {
github-actions's avatar
github-actions committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
            GameItem rewardItem = new GameItem(GameData.getItemDataMap().get(chosenId), entry.getItemCount());
            rewardItems.add(rewardItem);
        }
        // For ITEM_USE_GRANT_SELECT_REWARD chests, we have to again look up reward data.
        else if (rewardItemData.getItemUse().get(0).getUseOp().equals("ITEM_USE_GRANT_SELECT_REWARD")) {
            RewardData selectedReward = GameData.getRewardDataMap().get(chosenId);

            for (var r : selectedReward.getRewardItemList()) {
                GameItem rewardItem = new GameItem(GameData.getItemDataMap().get(r.getItemId()), r.getItemCount());
                rewardItems.add(rewardItem);
            }
        }
        else {
            Grasscutter.getLogger().error("Invalid chest type for BP reward.");
        }
    }

    public void takeReward(List<BattlePassRewardTakeOption> takeOptionList) {
        List<BattlePassRewardTakeOption> rewardList = new ArrayList<>();

        for (BattlePassRewardTakeOption option : takeOptionList) {
            // Duplicate check
            if (option.getTag().getRewardId() == 0 || getTakenRewards().containsKey(option.getTag().getRewardId())) {
                continue;
            }

            // Level check
            if (option.getTag().getLevel() > this.getLevel()) {
                continue;
            }

            BattlePassRewardData rewardData = GameData.getBattlePassRewardDataMap().get(GameConstants.BATTLE_PASS_CURRENT_INDEX * 100 + option.getTag().getLevel());

            // Sanity check with excel data
            if (rewardData.getFreeRewardIdList().contains(option.getTag().getRewardId())) {
                rewardList.add(option);
            } else if (this.isPaid() && rewardData.getPaidRewardIdList().contains(option.getTag().getRewardId())) {
                rewardList.add(option);
            }
            else {
                Grasscutter.getLogger().info("Not in rewards list: {}", option.getTag().getRewardId());
            }
        }

        // Get rewards
        List<GameItem> rewardItems = null;

        if (rewardList.size() > 0) {

            rewardItems = new ArrayList<>();

            for (var option : rewardList) {
                var tag = option.getTag();
                int index = option.getOptionIdx();

                // Make sure we have reward data.
                RewardData reward = GameData.getRewardDataMap().get(tag.getRewardId());
                if (reward == null) {
                    continue;
                }

                // Add reward items.
                for (var entry : reward.getRewardItemList()) {
                    ItemData rewardItemData = GameData.getItemDataMap().get(entry.getItemId());

                    // Some rewards are chests where the user can select the item they want.
                    if (rewardItemData.getMaterialType() == MaterialType.MATERIAL_SELECTABLE_CHEST) {
                        this.takeRewardsFromSelectChest(rewardItemData, index, entry, rewardItems);
                    }
                    // All other rewards directly give us the right item.
                    else {
                        GameItem rewardItem = new GameItem(rewardItemData, entry.getItemCount());
                        rewardItems.add(rewardItem);
                    }
                }

                // Construct the reward and set as taken.
                BattlePassReward bpReward = new BattlePassReward(tag.getLevel(), tag.getRewardId(), tag.getUnlockStatus() == BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID);
                this.getTakenRewards().put(bpReward.getRewardId(), bpReward);
            }

            // Save to db
            this.save();

            // Add items and send battle pass schedule packet
            getPlayer().getInventory().addItems(rewardItems);
            getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
298
        }
github-actions's avatar
github-actions committed
299
300

        getPlayer().sendPacket(new PacketTakeBattlePassRewardRsp(takeOptionList, rewardItems));
301
    }
github-actions's avatar
github-actions committed
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362

    public int buyLevels(int buyLevel) {
        int boughtLevels = Math.min(buyLevel, GameConstants.BATTLE_PASS_MAX_LEVEL - buyLevel);

        if (boughtLevels > 0) {
            int price = GameConstants.BATTLE_PASS_LEVEL_PRICE * boughtLevels;

            if (getPlayer().getPrimogems() < price) {
                return 0;
            }

            this.level += boughtLevels;
            this.save();

            getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
        }

        return boughtLevels;
    }

    public void resetDailyMissions() {
        var resetMissions = new ArrayList<BattlePassMission>();

        for (var mission : this.missions.values()) {
            if (mission.getData().getRefreshType() == null || mission.getData().getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_DAILY) {
                mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
                mission.setProgress(0);

                resetMissions.add(mission);
            }
        }

        this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
        this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
    }

    public void resetWeeklyMissions() {
        var resetMissions = new ArrayList<BattlePassMission>();

        for (var mission : this.missions.values()) {
            if (mission.getData().getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE) {
                mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
                mission.setProgress(0);

                resetMissions.add(mission);
            }
        }

        this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
        this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
    }

    //
    public BattlePassSchedule getScheduleProto() {
        var currentDate = LocalDate.now();
        var nextSundayDate = (currentDate.getDayOfWeek() == DayOfWeek.SUNDAY)
            ? currentDate
            : LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
        var nextSundayTime = LocalDateTime.of(nextSundayDate.getYear(), nextSundayDate.getMonthValue(), nextSundayDate.getDayOfMonth(), 23, 59, 59);

        BattlePassSchedule.Builder schedule = BattlePassSchedule.newBuilder()
363
364
365
366
367
368
369
                .setScheduleId(2700)
                .setLevel(this.getLevel())
                .setPoint(this.getPoint())
                .setBeginTime(0)
                .setEndTime(2059483200)
                .setIsViewed(this.isViewed())
                .setUnlockStatus(this.isPaid() ? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID : BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE)
GanyusLeftHorn's avatar
GanyusLeftHorn committed
370
                .setJPFMGBEBBBJ(2) // Not bought on Playstation.
github-actions's avatar
github-actions committed
371
                .setCurCyclePoints(this.getCyclePoints())
372
                .setCurCycle(BattlePassCycle.newBuilder()
github-actions's avatar
github-actions committed
373
374
375
376
377
378
379
380
381
382
383
384
                    .setBeginTime(0)
                    .setEndTime((int)nextSundayTime.atZone(ZoneId.systemDefault()).toEpochSecond())
                    .setCycleIdx(3)
                );

        for (BattlePassReward reward : getTakenRewards().values()) {
            schedule.addRewardTakenList(reward.toProto());
        }

        return schedule.build();
    }

385
    public void save() {
github-actions's avatar
github-actions committed
386
        DatabaseHelper.saveBattlePass(this);
387
388
    }
}