package emu.nebula.game.player; import java.util.HashSet; import java.util.Set; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Id; import dev.morphia.annotations.Indexed; import emu.nebula.GameConstants; import emu.nebula.Nebula; import emu.nebula.data.GameData; import emu.nebula.database.GameDatabaseObject; import emu.nebula.game.account.Account; import emu.nebula.game.character.CharacterStorage; import emu.nebula.game.formation.FormationManager; import emu.nebula.game.instance.InstanceManager; import emu.nebula.game.inventory.Inventory; import emu.nebula.game.mail.Mailbox; import emu.nebula.game.story.StoryManager; import emu.nebula.game.tower.StarTowerManager; import emu.nebula.net.GameSession; import emu.nebula.proto.PlayerData.DictionaryEntry; import emu.nebula.proto.PlayerData.DictionaryTab; import emu.nebula.proto.PlayerData.PlayerInfo; import emu.nebula.proto.Public.NewbieInfo; import emu.nebula.proto.Public.QuestType; import emu.nebula.proto.Public.Story; import emu.nebula.proto.Public.WorldClass; import emu.nebula.proto.Public.Title; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import lombok.Getter; @Getter @Entity(value = "players", useDiscriminator = false) public class Player implements GameDatabaseObject { @Id private int uid; @Indexed private String accountUid; private transient Account account; private transient Set sessions; // Details private String name; private String signature; private boolean gender; private int headIcon; private int skinId; private int titlePrefix; private int titleSuffix; private int level; private int exp; private int energy; private IntSet boards; private IntSet headIcons; private IntSet titles; private long createTime; // Managers private final transient CharacterStorage characters; private final transient Inventory inventory; // Referenced data private transient FormationManager formations; private transient Mailbox mailbox; private transient StarTowerManager starTowerManager; private transient InstanceManager instanceManager; private transient StoryManager storyManager; @Deprecated // Morphia only public Player() { this.sessions = new HashSet<>(); this.characters = new CharacterStorage(this); this.inventory = new Inventory(this); } public Player(Account account, String name, boolean gender) { this(); // Set uid first if (account.getReservedPlayerUid() > 0) { this.uid = account.getReservedPlayerUid(); } else { this.uid = Nebula.getGameDatabase().getNextObjectId(Player.class); } // Set basic info this.accountUid = account.getUid(); this.name = name; this.signature = ""; this.gender = gender; this.headIcon = 101; this.skinId = 10301; this.titlePrefix = 1; this.titleSuffix = 2; this.level = 1; this.energy = 240; this.boards = new IntOpenHashSet(); this.headIcons = new IntOpenHashSet(); this.titles = new IntOpenHashSet(); this.createTime = Nebula.getCurrentTime(); // Add starter characters this.getCharacters().addCharacter(103); this.getCharacters().addCharacter(112); this.getCharacters().addCharacter(113); // Add starter discs this.getCharacters().addDisc(211001); this.getCharacters().addDisc(211005); this.getCharacters().addDisc(211007); this.getCharacters().addDisc(211008); // Add titles this.getTitles().add(this.getTitlePrefix()); this.getTitles().add(this.getTitleSuffix()); // Add board ids this.getBoards().add(410301); } public Account getAccount() { if (this.account == null) { this.account = Nebula.getAccountDatabase().getObjectByField(Account.class, "_id", this.getAccountUid()); } return this.account; } public void addSession(GameSession session) { synchronized (this.sessions) { this.sessions.add(session); } } public void removeSession(GameSession session) { synchronized (this.sessions) { this.sessions.remove(session); } } public boolean hasSessions() { synchronized (this.sessions) { return !this.sessions.isEmpty(); } } public boolean getGender() { return this.gender; } public boolean editName(String newName) { // Sanity check if (newName == null || newName.isEmpty() || newName.equals(this.getName())) { return false; } // Limit name length if (newName.length() > 20) { newName = newName.substring(0, 19); } // Set name this.name = newName; // Update in database Nebula.getGameDatabase().update(this, this.getUid(), "name", this.getName()); // Success return true; } public void editGender() { // Set name this.gender = !this.gender; // Update in database Nebula.getGameDatabase().update(this, this.getUid(), "gender", this.getGender()); } public boolean editTitle(int prefix, int suffix) { // Check to make sure we own these titles if (!getTitles().contains(prefix) || !getTitles().contains(suffix)) { return false; } // Skip if we are not changing titles if (this.titlePrefix == prefix && this.titleSuffix == suffix) { return true; } // Set this.titlePrefix = prefix; this.titleSuffix = suffix; // Update in database Nebula.getGameDatabase().update(this, this.getUid(), "titlePrefix", this.getTitlePrefix(), "titleSuffix", this.getTitleSuffix()); return true; } public boolean editSignature(String signature) { // Sanity check if (signature == null) { return false; } // Limit signature to 30 max chars if (signature.length() > 30) { signature = signature.substring(0, 29); } // Set signature this.signature = signature; // Update in database Nebula.getGameDatabase().update(this, this.getUid(), "signature", this.getSignature()); // Success return true; } public void setNewbieInfo(int groupId, int stepId) { // TODO } public int getMaxExp() { var data = GameData.getWorldClassDataTable().get(this.level + 1); return data != null ? data.getExp() : 0; } public PlayerChangeInfo addExp(int amount, PlayerChangeInfo changes) { // Check if changes is null if (changes == null) { changes = new PlayerChangeInfo(); } // Sanity if (amount <= 0) { return changes; } // Setup int oldLevel = this.getLevel(); int oldExp = this.getExp(); int expRequired = this.getMaxExp(); // Add exp this.exp += amount; // Check for level ups while (this.exp >= expRequired && expRequired > 0) { this.level += 1; this.exp -= expRequired; expRequired = this.getMaxExp(); } // Save to database Nebula.getGameDatabase().update( this, this.getUid(), "level", this.getLevel(), "exp", this.getExp() ); // Calculate changes var proto = WorldClass.newInstance() .setAddClass(this.getLevel() - oldLevel) .setExpChange(this.getExp() - oldExp); changes.add(proto); return changes; } public PlayerChangeInfo consumeEnergy(int amount, PlayerChangeInfo changes) { // Check if changes is null if (changes == null) { changes = new PlayerChangeInfo(); } // Sanity if (amount <= 0) { return changes; } // TODO return changes; } public void sendMessage(String string) { // Empty } // Login private T loadManagerFromDatabase(Class cls) { var manager = Nebula.getGameDatabase().getObjectByField(cls, "_id", this.getUid()); if (manager != null) { manager.setPlayer(this); } else { try { manager = cls.getDeclaredConstructor(Player.class).newInstance(this); } catch (Exception e) { e.printStackTrace(); } } return manager; } public void onLoad() { // Debug this.energy = 240; // Load from database this.getCharacters().loadFromDatabase(); this.getInventory().loadFromDatabase(); // Load referenced classes this.formations = this.loadManagerFromDatabase(FormationManager.class); this.mailbox = this.loadManagerFromDatabase(Mailbox.class); this.starTowerManager = this.loadManagerFromDatabase(StarTowerManager.class); this.instanceManager = this.loadManagerFromDatabase(InstanceManager.class); this.storyManager = this.loadManagerFromDatabase(StoryManager.class); } // Proto public PlayerInfo toProto() { PlayerInfo proto = PlayerInfo.newInstance(); var acc = proto.getMutableAcc() .setNickName(this.getName()) .setSignature(this.getSignature()) .setGender(this.getGender()) .setId(this.getUid()) .setHeadIcon(this.getHeadIcon()) .setSkinId(this.getSkinId()) .setTitlePrefix(this.getTitlePrefix()) .setTitleSuffix(this.getTitleSuffix()) .setCreateTime(this.getCreateTime()); proto.getMutableWorldClass() .setCur(this.getLevel()) .setLastExp(this.getExp()); proto.getMutableEnergy() .getMutableEnergy() .setUpdateTime(Nebula.getCurrentTime()) .setNextDuration(60) .setPrimary(this.getEnergy()) .setIsPrimary(true); // Add characters/discs/res/items for (var character : getCharacters().getCharacterCollection()) { proto.addChars(character.toProto()); } for (var disc : getCharacters().getDiscCollection()) { proto.addDiscs(disc.toProto()); } for (var item : getInventory().getItems().values()) { proto.addItems(item.toProto()); } for (var res : getInventory().getResources().values()) { proto.addRes(res.toProto()); } // Formations for (var f : this.getFormations().getFormations().values()) { proto.getMutableFormation().addInfo(f.toProto()); } // Set state var state = proto.getMutableState() .setStorySet(true); state.getMutableMail(); state.getMutableBattlePass(); state.getMutableWorldClassReward(); state.getMutableFriendEnergy(); state.getMutableMallPackage(); state.getMutableAchievement(); state.getMutableTravelerDuelQuest() .setType(QuestType.TravelerDuel); state.getMutableTravelerDuelChallengeQuest() .setType(QuestType.TravelerDuelChallenge); state.getMutableStarTower(); state.getMutableStarTowerBook(); state.getMutableScoreBoss(); state.getMutableCharAffinityRewards(); // Force complete tutorials for (var guide : GameData.getGuideGroupDataTable()) { var info = NewbieInfo.newInstance() .setGroupId(guide.getId()) .setStepId(-1); acc.addNewbies(info); } acc.addNewbies(NewbieInfo.newInstance().setGroupId(GameConstants.INTRO_GUIDE_ID).setStepId(-1)); // Story var story = proto.getMutableStory(); for (int storyId : this.getStoryManager().getCompletedStories()) { var storyProto = Story.newInstance() .setIdx(storyId); story.addStories(storyProto); } // Add titles for (int titleId : this.getTitles()) { var titleProto = Title.newInstance() .setTitleId(titleId); proto.addTitles(titleProto); } // Add board ids for (int boardId : this.getBoards()) { proto.addBoard(boardId); } // Add dictionary tabs for (var dictionaryData : GameData.getDictionaryTabDataTable()) { var dictionaryProto = DictionaryTab.newInstance() .setTabId(dictionaryData.getId()); for (var entry : dictionaryData.getEntries()) { var entryProto = DictionaryEntry.newInstance() .setIndex(entry.getIndex()) .setStatus(2); // 2 = complete dictionaryProto.addEntries(entryProto); } proto.addDictionaries(dictionaryProto); } // Server timestamp proto.setServerTs(Nebula.getCurrentTime()); // Extra proto.setAchievements(new byte[64]); // Add instance this.getInstanceManager().toProto(proto); proto.getMutableVampireSurvivorRecord() .getMutableSeason(); proto.getMutableQuests(); proto.getMutableAgent(); proto.getMutablePhone(); return proto; } }