package emu.nebula.game.achievement; import java.util.HashMap; import java.util.Map; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Id; import emu.nebula.Nebula; import emu.nebula.data.GameData; import emu.nebula.data.resources.AchievementDef; import emu.nebula.database.GameDatabaseObject; import emu.nebula.game.inventory.ItemParamMap; import emu.nebula.game.player.Player; import emu.nebula.game.player.PlayerChangeInfo; import emu.nebula.game.player.PlayerManager; import emu.nebula.net.NetMsgId; import emu.nebula.proto.Public.Achievements; import emu.nebula.proto.Public.Events; import lombok.Getter; import us.hebi.quickbuf.RepeatedInt; @Getter @Entity(value = "achievements", useDiscriminator = false) public class AchievementManager extends PlayerManager implements GameDatabaseObject { @Id private int uid; // Achievement data private Map achievements; // Flags private transient boolean queueSave; private transient boolean hasCompleted; @Deprecated // Morphia only public AchievementManager() { } public AchievementManager(Player player) { super(player); this.uid = player.getUid(); this.achievements = new HashMap<>(); this.save(); } public synchronized int getCompletedAchievementsCount() { return (int) this.getAchievements().values().stream() .filter(GameAchievement::isComplete) .count(); } /** * Returns true if there are any unclaimed achievements */ public synchronized boolean hasNewAchievements() { for (var achievement : this.getAchievements().values()) { if (achievement.isClaimable()) { return true; } } return false; } public synchronized GameAchievement getAchievement(AchievementDef data) { // Try and get achievement normally var achievement = this.getAchievements().get(data.getId()); // Create achievement if it doesnt exist if (achievement == null) { achievement = new GameAchievement(data); this.getAchievements().put(achievement.getId(), achievement); } return achievement; } public synchronized void handleClientEvents(Events events) { // Parse events for (var event : events.getList()) { // Check id if (event.getId() != AchievementCondition.ClientReport.getValue()) { continue; } // Sanity check if (event.getData().length() < 2) { continue; } // Get achievement id and progress int id = event.getData().get(1); int progress = event.getData().get(0); if (progress <= 0) { continue; } // Get achievement data var data = GameData.getAchievementDataTable().get(id); if (data == null) continue; // Make sure achivement can be completed by the client if (data.getCompleteCond() != AchievementCondition.ClientReport.getValue()) { continue; } // Get achievement var achievement = this.getAchievement(data); // Update achievement boolean changed = achievement.trigger(true, progress, 0, 0); // Sync with client if achievement was changed if (changed) { this.syncAchievement(achievement); } } // Update total achievements if (this.hasCompleted) { this.hasCompleted = false; this.getPlayer().trigger(AchievementCondition.AchievementTotal, this.getCompletedAchievementsCount()); } } public synchronized void trigger(AchievementCondition condition, int progress) { this.trigger(condition.getValue(), progress, 0, 0); } public synchronized void trigger(AchievementCondition condition, int progress, int param1, int param2) { this.trigger(condition.getValue(), progress, param1, param2); } public synchronized void trigger(int condition, int progress, int param1, int param2) { // Sanity check if (progress <= 0) { return; } // Blacklist if (condition == AchievementCondition.ClientReport.getValue()) { return; } // Get achievements to trigger var triggerList = AchievementHelper.getAchievementsByCondition(condition); if (triggerList == null) { return; } // Check what type of achievement condition this is boolean isTotal = AchievementHelper.isIncrementalAchievement(condition); // Parse achievements for (var data : triggerList) { // Get achievement var achievement = this.getAchievement(data); // Update achievement boolean changed = achievement.trigger(isTotal, progress, param1, param2); // Sync with client if achievement was changed if (changed) { this.syncAchievement(achievement); } } // Update total achievements if (this.hasCompleted) { this.hasCompleted = false; this.getPlayer().trigger(AchievementCondition.AchievementTotal, this.getCompletedAchievementsCount()); } } public synchronized void triggerOne(int id, int progress, int param1, int param2) { // Get achievement data var data = GameData.getAchievementDataTable().get(id); if (data == null) return; // Get achievement var achievement = this.getAchievement(data); // Check what type of achievement condition this is boolean isTotal = AchievementHelper.isIncrementalAchievement(data.getCompleteCond()); // Update achievement boolean changed = achievement.trigger(isTotal, progress, param1, param2); // Sync with client if achievement was changed if (changed) { this.syncAchievement(achievement); } // Update total achievements if (this.hasCompleted) { this.hasCompleted = false; this.getPlayer().trigger(AchievementCondition.AchievementTotal, this.getCompletedAchievementsCount()); } } /** * Update this achievement on the player client */ private void syncAchievement(GameAchievement achievement) { if (!getPlayer().hasSession()) { return; } // Set save flag this.queueSave = true; // Check if achievement was completed if (achievement.isComplete()) { this.hasCompleted = true; } // Send update to player getPlayer().addNextPackage( NetMsgId.achievement_change_notify, achievement.toProto() ); } public synchronized PlayerChangeInfo recvRewards(RepeatedInt ids) { // Sanity check if (ids.length() <= 0) { return null; } // Init variables var rewards = new ItemParamMap(); // Claim achievements for (int id : ids) { // Get achievement var achievement = this.getAchievements().get(id); if (achievement == null) continue; // Check if we can claim this achievement if (achievement.isClaimable()) { // Claim achievement.setClaimed(true); // Add rewards rewards.add(achievement.getData().getTid1(), achievement.getData().getQty1()); // Save this.queueSave = true; } } // Success return this.getPlayer().getInventory().addItems(rewards); } // Proto public synchronized Achievements toProto() { var proto = Achievements.newInstance(); for (var achievement : this.getAchievements().values()) { proto.addList(achievement.toProto()); } return proto; } // Database @Override public void save() { Nebula.getGameDatabase().save(this); this.queueSave = false; } }