BattlePassManager.java 13.1 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
32
33
34
35
36
import emu.grasscutter.game.props.BattlePassMissionStatus;
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;
37
import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify;
38
39
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
40

41
import lombok.Getter;
42

43
@Entity(value = "battlepass", useDiscriminator = false)
Melledy's avatar
Melledy committed
44
public class BattlePassManager extends BasePlayerDataManager {
45
	@Id @Getter private ObjectId id;
46
47
	
	@Indexed private int ownerUid;
48
49
50
    @Getter private int point;
    @Getter private int cyclePoints; // Weekly maximum cap
    @Getter private int level;
51
    
52
    @Getter private boolean viewed;
GanyusLeftHorn's avatar
GanyusLeftHorn committed
53
    private boolean paid;
54
55
56
    
    private Map<Integer, BattlePassMission> missions;
    private Map<Integer, BattlePassReward> takenRewards;
57
58
59
    
    @Deprecated // Morphia only
    public BattlePassManager() {}
60

61
    public BattlePassManager(Player player) {
Melledy's avatar
Melledy committed
62
        super(player);
63
64
65
66
67
68
69
    }
    
    public void setPlayer(Player player) {
    	this.player = player;
    	this.ownerUid = player.getUid();
    }
    
70
71
    public void updateViewed() {
		this.viewed = true;
72
73
    }

74
75
76
77
78
79
80
81
	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;
82
83
	}

84
85
	public void addPoints(int points){
        this.addPointsDirectly(points, false);
86
   
87
        this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
88
        this.save();
89
90
    }

91
92
    public void addPointsDirectly(int points, boolean isWeekly) {
    	int amount = points;
93
94
95
96
97
98
99
100
101
102
103
    	
    	if (isWeekly) {
    		amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints);
    	}
    	
    	if (amount <= 0) {
    		return;
    	}
    	
        this.point += amount;
        this.cyclePoints += amount;
104
105
        
        if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL && this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) {
106
        	int levelups = Math.floorDiv(this.point, GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
107
108
109
110
111
112
113
114
        	
        	// 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;
        }
115
    }
116
    
117
118
119
120
121
122
123
124
125
126
127
128
129
130
	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);
	}

GanyusLeftHorn's avatar
GanyusLeftHorn committed
131
132
133
134
135
	public boolean isPaid() {
		// ToDo: Change this when we actually support unlocking "paid" BP.
		return true;
	}

136
137
138
139
140
141
142
	public Map<Integer, BattlePassReward> getTakenRewards() {
		if (this.takenRewards == null) this.takenRewards = new HashMap<>();
		return this.takenRewards;
	}
	
	// Mission triggers
	public void triggerMission(WatcherTriggerType triggerType) {
143
		getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType);
144
145
146
	}
	
	public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
147
		getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType, param, progress);
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
	}
	
	// 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) {
174
				this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh());
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
				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()));
		}
	}
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224

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

		// Get possible item choices.
		String[] choices = rewardItemData.getItemUse().get(0).getUseParam().get(0).split(",");
		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.
		if (rewardItemData.getItemUse().get(0).getUseOp().equals("ITEM_USE_ADD_SELECT_ITEM")) {
			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.");
		}
	}
225
226
	
	public void takeReward(List<BattlePassRewardTakeOption> takeOptionList) {
227
		List<BattlePassRewardTakeOption> rewardList = new ArrayList<>();
228
229
230
231
232
233
234
235
236
237
238
239
		
		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;
			}
			
240
			BattlePassRewardData rewardData = GameData.getBattlePassRewardDataMap().get(GameConstants.BATTLE_PASS_CURRENT_INDEX * 100 + option.getTag().getLevel());
241
242
243
			
			// Sanity check with excel data
			if (rewardData.getFreeRewardIdList().contains(option.getTag().getRewardId())) {
244
				rewardList.add(option);
245
			} else if (this.isPaid() && rewardData.getPaidRewardIdList().contains(option.getTag().getRewardId())) {
246
				rewardList.add(option);
247
			}
248
249
250
			else {
				Grasscutter.getLogger().info("Not in rewards list: {}", option.getTag().getRewardId());
			}
251
252
253
		}
		
		// Get rewards
254
		List<GameItem> rewardItems = null;
255
256
		
		if (rewardList.size() > 0) {
257

258
259
			rewardItems = new ArrayList<>();
			
260
261
262
263
264
			for (var option : rewardList) {
				var tag = option.getTag();
				int index = option.getOptionIdx();

				// Make sure we have reward data.
265
				RewardData reward = GameData.getRewardDataMap().get(tag.getRewardId());
266
267
268
				if (reward == null) {
					continue;
				}
269
				
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
				// 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.
286
287
288
289
290
291
292
293
				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
294
			getPlayer().getInventory().addItems(rewardItems);
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
			getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
		}
		
		getPlayer().sendPacket(new PacketTakeBattlePassRewardRsp(takeOptionList, rewardItems));
	}
	
	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() {
321
322
323
324
325
326
327
328
329
330
331
332
333
		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()));
334
335
336
	}
	
	public void resetWeeklyMissions() {
337
338
339
340
341
342
343
344
345
346
347
348
349
		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()));
350
351
352
353
	}
	
	//
	public BattlePassSchedule getScheduleProto() {
GanyusLeftHorn's avatar
GanyusLeftHorn committed
354
355
356
357
		var currentDate = LocalDate.now();
		var nextSundayDate = (currentDate.getDayOfWeek() == DayOfWeek.SUNDAY) 
			? currentDate 
			: LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
358
359
		var nextSundayTime = LocalDateTime.of(nextSundayDate.getYear(), nextSundayDate.getMonthValue(), nextSundayDate.getDayOfMonth(), 23, 59, 59);
		
360
361
362
363
364
365
366
367
		BattlePassSchedule.Builder schedule = BattlePassSchedule.newBuilder()
                .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
368
369
                .setJPFMGBEBBBJ(2) // Not bought on Playstation.
				.setCurCyclePoints(this.getCyclePoints())
370
371
372
373
374
                .setCurCycle(BattlePassCycle.newBuilder()
					.setBeginTime(0)
					.setEndTime((int)nextSundayTime.atZone(ZoneId.systemDefault()).toEpochSecond())
					.setCycleIdx(3)
				);
375
376
377
378
379
380
381
382
		
		for (BattlePassReward reward : getTakenRewards().values()) {
			schedule.addRewardTakenList(reward.toProto());
		}
		
		return schedule.build();
	}
	
383
384
    public void save() {
    	DatabaseHelper.saveBattlePass(this);
385
386
    }
}