Files
Nebula/src/main/java/emu/nebula/game/player/Player.java
2025-10-30 01:01:46 -07:00

475 lines
14 KiB
Java

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<GameSession> 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 extends PlayerManager> T loadManagerFromDatabase(Class<T> 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;
}
}