mirror of
https://github.com/Melledy/Nebula.git
synced 2025-12-15 05:44:36 +01:00
Initial Commit
This commit is contained in:
87
src/main/java/emu/nebula/game/GameContext.java
Normal file
87
src/main/java/emu/nebula/game/GameContext.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package emu.nebula.game;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import emu.nebula.game.player.PlayerModule;
|
||||
import emu.nebula.net.GameSession;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class GameContext {
|
||||
private final Object2ObjectMap<String, GameSession> sessions;
|
||||
|
||||
// Modules
|
||||
private final PlayerModule playerModule;
|
||||
|
||||
// Cleanup thread
|
||||
private final Timer cleanupTimer;
|
||||
|
||||
public GameContext() {
|
||||
this.sessions = new Object2ObjectOpenHashMap<>();
|
||||
this.playerModule = new PlayerModule(this);
|
||||
|
||||
this.cleanupTimer = new Timer();
|
||||
this.cleanupTimer.scheduleAtFixedRate(new CleanupTask(this), 0, TimeUnit.SECONDS.toMillis(60));
|
||||
}
|
||||
|
||||
public synchronized GameSession getSessionByToken(String token) {
|
||||
return sessions.get(token);
|
||||
}
|
||||
|
||||
public synchronized void addSession(GameSession session) {
|
||||
this.sessions.put(session.getToken(), session);
|
||||
}
|
||||
|
||||
public synchronized void generateSessionToken(GameSession session) {
|
||||
// Remove token
|
||||
if (session.getToken() != null) {
|
||||
this.sessions.remove(session.getToken());
|
||||
}
|
||||
|
||||
// Generate token
|
||||
String token = null;
|
||||
|
||||
do {
|
||||
token = session.generateToken();
|
||||
} while (this.getSessions().containsKey(token));
|
||||
|
||||
// Register session
|
||||
this.sessions.put(session.getToken(), session);
|
||||
}
|
||||
|
||||
// TODO add timeout to config
|
||||
public synchronized void cleanupInactiveSessions() {
|
||||
var it = this.getSessions().entrySet().iterator();
|
||||
long timeout = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(600); // 10 minutes
|
||||
|
||||
while (it.hasNext()) {
|
||||
var session = it.next().getValue();
|
||||
|
||||
if (timeout > session.getLastActiveTime()) {
|
||||
// Remove from session map
|
||||
it.remove();
|
||||
|
||||
// Clear player
|
||||
session.clearPlayer(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class CleanupTask extends TimerTask {
|
||||
private GameContext gameContext;
|
||||
|
||||
public CleanupTask(GameContext gameContext) {
|
||||
this.gameContext = gameContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.getGameContext().cleanupInactiveSessions();
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/main/java/emu/nebula/game/GameContextModule.java
Normal file
13
src/main/java/emu/nebula/game/GameContextModule.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package emu.nebula.game;
|
||||
|
||||
public abstract class GameContextModule {
|
||||
private transient GameContext context;
|
||||
|
||||
public GameContextModule(GameContext player) {
|
||||
this.context = player;
|
||||
}
|
||||
|
||||
public GameContext getGameContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
167
src/main/java/emu/nebula/game/account/Account.java
Normal file
167
src/main/java/emu/nebula/game/account/Account.java
Normal file
@@ -0,0 +1,167 @@
|
||||
package emu.nebula.game.account;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import dev.morphia.annotations.Indexed;
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.database.AccountDatabaseOnly;
|
||||
import emu.nebula.util.Snowflake;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AccountDatabaseOnly
|
||||
@Entity(value = "accounts", useDiscriminator = false)
|
||||
public class Account {
|
||||
@Id private String uid;
|
||||
|
||||
@Indexed
|
||||
private String email;
|
||||
private String code;
|
||||
|
||||
private String nickname;
|
||||
private String picture;
|
||||
|
||||
@Indexed private String loginToken;
|
||||
@Indexed private String gameToken;
|
||||
|
||||
private Set<String> permissions;
|
||||
|
||||
private int reservedPlayerUid;
|
||||
private long createdAt;
|
||||
|
||||
@Deprecated
|
||||
public Account() {
|
||||
// Morphia only
|
||||
}
|
||||
|
||||
public Account(String email, String password, int reservedUid) {
|
||||
this.uid = Long.toString(Snowflake.newUid());
|
||||
this.email = email;
|
||||
this.nickname = "";
|
||||
this.picture = "";
|
||||
this.permissions = new HashSet<>();
|
||||
this.reservedPlayerUid = reservedUid;
|
||||
this.createdAt = System.currentTimeMillis() / 1000;
|
||||
}
|
||||
|
||||
public boolean verifyCode(String code) {
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setNickname(String value) {
|
||||
this.nickname = value;
|
||||
}
|
||||
|
||||
// Tokens
|
||||
|
||||
public String generateLoginToken() {
|
||||
this.loginToken = AccountHelper.createSessionKey(this.getUid());
|
||||
this.save();
|
||||
return this.loginToken;
|
||||
}
|
||||
|
||||
public String generateGameToken() {
|
||||
this.gameToken = AccountHelper.createSessionKey(this.getUid());
|
||||
this.save();
|
||||
return this.gameToken;
|
||||
}
|
||||
|
||||
// Permissions
|
||||
|
||||
public Set<String> getPermissions() {
|
||||
if (this.permissions == null) {
|
||||
this.permissions = new HashSet<>();
|
||||
this.save();
|
||||
}
|
||||
return this.permissions;
|
||||
}
|
||||
|
||||
public boolean addPermission(String permission) {
|
||||
if (this.getPermissions().contains(permission)) {
|
||||
return false;
|
||||
}
|
||||
this.getPermissions().add(permission);
|
||||
this.save();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean permissionMatchesWildcard(String wildcard, String[] permissionParts) {
|
||||
String[] wildcardParts = wildcard.split("\\.");
|
||||
if (permissionParts.length < wildcardParts.length) { // A longer wildcard can never match a shorter permission
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < wildcardParts.length; i++) {
|
||||
switch (wildcardParts[i]) {
|
||||
case "**": // Recursing match
|
||||
return true;
|
||||
case "*": // Match only one layer
|
||||
if (i >= (permissionParts.length-1)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default: // This layer isn't a wildcard, it needs to match exactly
|
||||
if (!wildcardParts[i].equals(permissionParts[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// At this point the wildcard will have matched every layer, but if it is shorter then the permission then this is not a match at this point (no **).
|
||||
return wildcardParts.length == permissionParts.length;
|
||||
}
|
||||
|
||||
public boolean hasPermission(String permission) {
|
||||
// Skip if permission isnt required
|
||||
if (permission.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Default permissions
|
||||
var defaultPermissions = Nebula.getConfig().getServerOptions().getDefaultPermissions();
|
||||
|
||||
if (defaultPermissions.contains("*")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add default permissions if it doesn't exist
|
||||
List<String> permissions = Stream.of(this.getPermissions(), defaultPermissions)
|
||||
.flatMap(Collection::stream)
|
||||
.distinct().toList();
|
||||
|
||||
if (permissions.contains(permission)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String[] permissionParts = permission.split("\\.");
|
||||
for (String p : permissions) {
|
||||
if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) return false;
|
||||
if (permissionMatchesWildcard(p, permissionParts)) return true;
|
||||
}
|
||||
|
||||
return permissions.contains("*");
|
||||
}
|
||||
|
||||
public boolean removePermission(String permission) {
|
||||
boolean res = this.getPermissions().remove(permission);
|
||||
if (res) this.save();
|
||||
return res;
|
||||
}
|
||||
|
||||
public void clearPermission() {
|
||||
this.getPermissions().clear();
|
||||
this.save();
|
||||
}
|
||||
|
||||
// Database
|
||||
|
||||
public void save() {
|
||||
Nebula.getAccountDatabase().save(this);
|
||||
}
|
||||
}
|
||||
71
src/main/java/emu/nebula/game/account/AccountHelper.java
Normal file
71
src/main/java/emu/nebula/game/account/AccountHelper.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package emu.nebula.game.account;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
import emu.nebula.Nebula;
|
||||
|
||||
/**
|
||||
* Helper class for handling account related stuff
|
||||
*/
|
||||
public class AccountHelper {
|
||||
|
||||
public static Account createAccount(String email, String password, int reservedUid) {
|
||||
Account account = Nebula.getAccountDatabase().getObjectByField(Account.class, "email", email);
|
||||
|
||||
if (account != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
account = new Account(email, password, reservedUid);
|
||||
account.save();
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
public static Account getAccountByEmail(String email) {
|
||||
if (email == null || email.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Nebula.getAccountDatabase().getObjectByField(Account.class, "email", email);
|
||||
}
|
||||
|
||||
public static Account getAccountByLoginToken(String token) {
|
||||
if (token == null || token.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Nebula.getAccountDatabase().getObjectByField(Account.class, "loginToken", token);
|
||||
}
|
||||
|
||||
public static boolean deleteAccount(String username) {
|
||||
Account account = Nebula.getAccountDatabase().getObjectByField(Account.class, "username", username);
|
||||
|
||||
if (account == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the account first
|
||||
return Nebula.getAccountDatabase().delete(account);
|
||||
}
|
||||
|
||||
// Simple way to create a unique session key
|
||||
public static String createSessionKey(String accountUid) {
|
||||
byte[] random = new byte[64];
|
||||
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
secureRandom.nextBytes(random);
|
||||
|
||||
String temp = accountUid + "." + System.currentTimeMillis() + "." + secureRandom.toString();
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||
byte[] bytes = md.digest(temp.getBytes());
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
} catch (Exception e) {
|
||||
return Base64.getEncoder().encodeToString(temp.getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
283
src/main/java/emu/nebula/game/character/Character.java
Normal file
283
src/main/java/emu/nebula/game/character/Character.java
Normal file
@@ -0,0 +1,283 @@
|
||||
package emu.nebula.game.character;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
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.data.resources.CharacterDef;
|
||||
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.proto.Public.Char;
|
||||
import emu.nebula.proto.Public.CharGemPreset;
|
||||
import emu.nebula.proto.Public.CharGemSlot;
|
||||
import emu.nebula.proto.PublicStarTower.StarTowerChar;
|
||||
import emu.nebula.proto.PublicStarTower.StarTowerCharGem;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(value = "characters", useDiscriminator = false)
|
||||
public class Character implements GameDatabaseObject {
|
||||
@Id
|
||||
private ObjectId uid;
|
||||
@Indexed
|
||||
private int playerUid;
|
||||
|
||||
private transient CharacterDef data;
|
||||
private transient Player player;
|
||||
|
||||
private int charId;
|
||||
private int advance;
|
||||
private int level;
|
||||
private int exp;
|
||||
private int skin;
|
||||
private int[] skills;
|
||||
private byte[] talents;
|
||||
|
||||
private long createTime;
|
||||
|
||||
@Deprecated // Morphia only!
|
||||
public Character() {
|
||||
|
||||
}
|
||||
|
||||
public Character(Player player, int charId) {
|
||||
this(player, GameData.getCharacterDataTable().get(charId));
|
||||
}
|
||||
|
||||
public Character(Player player, CharacterDef data) {
|
||||
this.player = player;
|
||||
this.playerUid = player.getUid();
|
||||
this.charId = data.getId();
|
||||
this.data = data;
|
||||
this.level = 1;
|
||||
this.skin = data.getDefaultSkinId();
|
||||
this.skills = new int[] {1, 1, 1, 1, 1};
|
||||
this.talents = new byte[8];
|
||||
this.createTime = Nebula.getCurrentTime();
|
||||
}
|
||||
|
||||
public void setPlayer(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public void setData(CharacterDef data) {
|
||||
if (this.data == null && data.getId() == this.getCharId()) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxGainableExp() {
|
||||
if (this.getLevel() >= this.getMaxLevel()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int maxLevel = this.getMaxLevel();
|
||||
int max = 0;
|
||||
|
||||
for (int i = this.getLevel() + 1; i <= maxLevel; i++) {
|
||||
var data = GameData.getCharacterUpgradeDataTable().get(i);
|
||||
|
||||
if (data != null) {
|
||||
max += data.getExp();
|
||||
}
|
||||
}
|
||||
|
||||
return Math.max(max - this.getExp(), 0);
|
||||
}
|
||||
|
||||
public int getMaxExp() {
|
||||
if (this.getLevel() >= this.getMaxLevel()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var data = GameData.getCharacterUpgradeDataTable().get(this.level + 1);
|
||||
return data != null ? data.getExp() : 0;
|
||||
}
|
||||
|
||||
public int getMaxLevel() {
|
||||
return 10 + (this.getAdvance() * 10);
|
||||
}
|
||||
|
||||
public void addExp(int amount) {
|
||||
// Setup
|
||||
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();
|
||||
}
|
||||
|
||||
// Clamp exp
|
||||
if (this.getLevel() >= this.getMaxLevel()) {
|
||||
this.exp = 0;
|
||||
}
|
||||
|
||||
// Save to database
|
||||
this.save();
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
public PlayerChangeInfo upgrade(ItemParamMap params) {
|
||||
// Calculate exp gained
|
||||
int exp = 0;
|
||||
|
||||
// Check if item is an exp item
|
||||
for (var entry : params.getEntrySet()) {
|
||||
var data = GameData.getCharItemExpDataTable().get(entry.getIntKey());
|
||||
if (data == null) return null;
|
||||
|
||||
exp += data.getExpValue() * entry.getIntValue();
|
||||
}
|
||||
|
||||
// Clamp exp gain
|
||||
exp = Math.min(this.getMaxGainableExp(), exp);
|
||||
|
||||
// Calculate gold required
|
||||
params.add(GameConstants.GOLD_ITEM_ID, (int) Math.ceil(exp * 0.15D));
|
||||
|
||||
// Verify that the player has the items
|
||||
if (!this.getPlayer().getInventory().verifyItems(params)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove items
|
||||
var changes = this.getPlayer().getInventory().removeItems(params, null);
|
||||
|
||||
// Add exp
|
||||
this.addExp(exp);
|
||||
|
||||
// Success
|
||||
return changes.setSuccess(true);
|
||||
}
|
||||
|
||||
public PlayerChangeInfo advance() {
|
||||
// TODO check player level to make sure they can advance this character
|
||||
|
||||
// Get advance data
|
||||
int advanceId = (this.getData().getAdvanceGroup() * 100) + (this.advance + 1);
|
||||
var data = GameData.getCharacterAdvanceDataTable().get(advanceId);
|
||||
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verify that the player has the items
|
||||
if (!this.getPlayer().getInventory().verifyItems(data.getMaterials())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove items
|
||||
var changes = this.getPlayer().getInventory().removeItems(data.getMaterials(), null);
|
||||
|
||||
// Add advance level
|
||||
this.advance++;
|
||||
|
||||
// Save to database
|
||||
this.save();
|
||||
|
||||
// Success
|
||||
return changes.setSuccess(true);
|
||||
}
|
||||
|
||||
public PlayerChangeInfo upgradeSkill(int index) {
|
||||
// TODO check player level to make sure they can advance this character
|
||||
|
||||
// Sanity check
|
||||
if (index < 0 || index >= this.skills.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get advance data
|
||||
int upgradeId = (this.getData().getSkillsUpgradeGroup(index) * 100) + (this.skills[index] + 1);
|
||||
var data = GameData.getCharacterSkillUpgradeDataTable().get(upgradeId);
|
||||
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verify that the player has the items
|
||||
if (!this.getPlayer().getInventory().verifyItems(data.getMaterials())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove items
|
||||
var changes = this.getPlayer().getInventory().removeItems(data.getMaterials(), null);
|
||||
|
||||
// Add skill level
|
||||
this.skills[index]++;
|
||||
|
||||
// Save to database
|
||||
this.save();
|
||||
|
||||
// Success
|
||||
return changes.setSuccess(true);
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public Char toProto() {
|
||||
var proto = Char.newInstance()
|
||||
.setTid(this.getCharId())
|
||||
.setLevel(this.getLevel())
|
||||
.setSkin(this.getSkin())
|
||||
.setAdvance(this.getAdvance())
|
||||
.setTalentNodes(this.getTalents())
|
||||
.addAllSkillLvs(this.getSkills())
|
||||
.setCreateTime(this.getCreateTime());
|
||||
|
||||
var gemPresets = proto.getMutableCharGemPresets()
|
||||
.getMutableCharGemPresets();
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
var preset = CharGemPreset.newInstance()
|
||||
.addAllSlotGem(-1, -1, -1);
|
||||
|
||||
gemPresets.add(preset);
|
||||
}
|
||||
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
var slot = CharGemSlot.newInstance()
|
||||
.setId(i);
|
||||
|
||||
proto.addCharGemSlots(slot);
|
||||
}
|
||||
|
||||
proto.getMutableAffinityQuests();
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public StarTowerChar toStarTowerProto() {
|
||||
var proto = StarTowerChar.newInstance()
|
||||
.setId(this.getCharId())
|
||||
.setAdvance(this.getAdvance())
|
||||
.setLevel(this.getLevel())
|
||||
.setTalentNodes(this.getTalents())
|
||||
.addAllSkillLvs(this.getSkills());
|
||||
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
var slot = StarTowerCharGem.newInstance()
|
||||
.setSlotId(i)
|
||||
.addAllAttributes(new int[] {0, 0, 0, 0});
|
||||
|
||||
proto.addGems(slot);
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
151
src/main/java/emu/nebula/game/character/CharacterStorage.java
Normal file
151
src/main/java/emu/nebula/game/character/CharacterStorage.java
Normal file
@@ -0,0 +1,151 @@
|
||||
package emu.nebula.game.character;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.data.GameData;
|
||||
import emu.nebula.data.resources.CharacterDef;
|
||||
import emu.nebula.data.resources.DiscDef;
|
||||
import emu.nebula.game.player.PlayerManager;
|
||||
import emu.nebula.game.player.Player;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class CharacterStorage extends PlayerManager {
|
||||
private final Int2ObjectMap<Character> characters;
|
||||
private final Int2ObjectMap<GameDisc> discs;
|
||||
|
||||
public CharacterStorage(Player player) {
|
||||
super(player);
|
||||
|
||||
this.characters = new Int2ObjectOpenHashMap<>();
|
||||
this.discs = new Int2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
// Characters
|
||||
|
||||
public Character getCharacterById(int id) {
|
||||
if (id <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.characters.get(id);
|
||||
}
|
||||
|
||||
public boolean hasCharacter(int id) {
|
||||
return this.characters.containsKey(id);
|
||||
}
|
||||
|
||||
public Character addCharacter(int charId) {
|
||||
// Sanity check to make sure we dont have this character already
|
||||
if (this.hasCharacter(charId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.addCharacter(GameData.getCharacterDataTable().get(charId));
|
||||
}
|
||||
|
||||
private Character addCharacter(CharacterDef data) {
|
||||
// Sanity check to make sure we dont have this character already
|
||||
if (this.hasCharacter(data.getId())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create character
|
||||
var character = new Character(this.getPlayer(), data);
|
||||
|
||||
// Save to database
|
||||
character.save();
|
||||
|
||||
// Add to characters
|
||||
this.characters.put(character.getCharId(), character);
|
||||
return character;
|
||||
}
|
||||
|
||||
public Collection<Character> getCharacterCollection() {
|
||||
return this.getCharacters().values();
|
||||
}
|
||||
|
||||
// Discs
|
||||
|
||||
public GameDisc getDiscById(int id) {
|
||||
if (id <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.discs.get(id);
|
||||
}
|
||||
|
||||
public boolean hasDisc(int id) {
|
||||
return this.discs.containsKey(id);
|
||||
}
|
||||
|
||||
public GameDisc addDisc(int discId) {
|
||||
// Sanity check to make sure we dont have this character already
|
||||
if (this.hasDisc(discId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.addDisc(GameData.getDiscDataTable().get(discId));
|
||||
}
|
||||
|
||||
private GameDisc addDisc(DiscDef data) {
|
||||
// Sanity check to make sure we dont have this character already
|
||||
if (this.hasDisc(data.getId())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create disc
|
||||
var disc = new GameDisc(this.getPlayer(), data);
|
||||
|
||||
// Save to database
|
||||
disc.save();
|
||||
|
||||
// Add to discs
|
||||
this.discs.put(disc.getDiscId(), disc);
|
||||
return disc;
|
||||
}
|
||||
|
||||
public Collection<GameDisc> getDiscCollection() {
|
||||
return this.getDiscs().values();
|
||||
}
|
||||
|
||||
|
||||
// Database
|
||||
|
||||
public void loadFromDatabase() {
|
||||
var db = Nebula.getGameDatabase();
|
||||
|
||||
db.getObjects(Character.class, "playerUid", getPlayerUid()).forEach(character -> {
|
||||
// Get data
|
||||
var data = GameData.getCharacterDataTable().get(character.getCharId());
|
||||
|
||||
// Validate
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
character.setPlayer(this.getPlayer());
|
||||
character.setData(data);
|
||||
|
||||
// Add to characters
|
||||
this.characters.put(character.getCharId(), character);
|
||||
});
|
||||
|
||||
|
||||
|
||||
db.getObjects(GameDisc.class, "playerUid", getPlayerUid()).forEach(disc -> {
|
||||
// Get data
|
||||
var data = GameData.getDiscDataTable().get(disc.getDiscId());
|
||||
if (data == null) return;
|
||||
|
||||
disc.setPlayer(this.getPlayer());
|
||||
disc.setData(data);
|
||||
|
||||
// Add
|
||||
this.discs.put(disc.getDiscId(), disc);
|
||||
});
|
||||
}
|
||||
}
|
||||
216
src/main/java/emu/nebula/game/character/GameDisc.java
Normal file
216
src/main/java/emu/nebula/game/character/GameDisc.java
Normal file
@@ -0,0 +1,216 @@
|
||||
package emu.nebula.game.character;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
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.data.resources.DiscDef;
|
||||
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.proto.Public.Disc;
|
||||
import emu.nebula.proto.PublicStarTower.StarTowerDisc;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(value = "discs", useDiscriminator = false)
|
||||
public class GameDisc implements GameDatabaseObject {
|
||||
@Id
|
||||
private ObjectId uid;
|
||||
@Indexed
|
||||
private int playerUid;
|
||||
|
||||
private transient DiscDef data;
|
||||
private transient Player player;
|
||||
|
||||
private int discId;
|
||||
private int level;
|
||||
private int exp;
|
||||
private int phase;
|
||||
private int star;
|
||||
|
||||
private long createTime;
|
||||
|
||||
@Deprecated // Morphia only!
|
||||
public GameDisc() {
|
||||
|
||||
}
|
||||
|
||||
public GameDisc(Player player, int discId) {
|
||||
this(player, GameData.getDiscDataTable().get(discId));
|
||||
}
|
||||
|
||||
public GameDisc(Player player, DiscDef data) {
|
||||
this.player = player;
|
||||
this.playerUid = player.getUid();
|
||||
this.data = data;
|
||||
this.discId = data.getId();
|
||||
this.level = 1;
|
||||
this.createTime = Nebula.getCurrentTime();
|
||||
}
|
||||
|
||||
public void setPlayer(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public void setData(DiscDef data) {
|
||||
if (this.data == null && data.getId() == this.getDiscId()) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxGainableExp() {
|
||||
if (this.getLevel() >= this.getMaxLevel()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int maxLevel = this.getMaxLevel();
|
||||
int max = 0;
|
||||
|
||||
for (int i = this.getLevel() + 1; i <= maxLevel; i++) {
|
||||
int dataId = (this.getData().getStrengthenGroupId() * 1000) + i;
|
||||
var data = GameData.getDiscStrengthenDataTable().get(dataId);
|
||||
|
||||
if (data != null) {
|
||||
max += data.getExp();
|
||||
}
|
||||
}
|
||||
|
||||
return Math.max(max - this.getExp(), 0);
|
||||
}
|
||||
|
||||
public int getMaxExp() {
|
||||
if (this.getLevel() >= this.getMaxLevel()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dataId = (this.getData().getStrengthenGroupId() * 1000) + (this.level + 1);
|
||||
var data = GameData.getDiscStrengthenDataTable().get(dataId);
|
||||
return data != null ? data.getExp() : 0;
|
||||
}
|
||||
|
||||
public int getMaxLevel() {
|
||||
return 10 + (this.getPhase() * 10);
|
||||
}
|
||||
|
||||
public void addExp(int amount) {
|
||||
// Setup
|
||||
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();
|
||||
}
|
||||
|
||||
// Clamp exp
|
||||
if (this.getLevel() >= this.getMaxLevel()) {
|
||||
this.exp = 0;
|
||||
}
|
||||
|
||||
// Save to database
|
||||
this.save();
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
public PlayerChangeInfo upgrade(ItemParamMap params) {
|
||||
// Calculate exp gained
|
||||
int exp = 0;
|
||||
|
||||
// Check if item is an exp item
|
||||
for (var entry : params.getEntrySet()) {
|
||||
var data = GameData.getDiscItemExpDataTable().get(entry.getIntKey());
|
||||
if (data == null) return null;
|
||||
|
||||
exp += data.getExp() * entry.getIntValue();
|
||||
}
|
||||
|
||||
// Clamp exp gain
|
||||
exp = Math.min(this.getMaxGainableExp(), exp);
|
||||
|
||||
// Calculate gold required
|
||||
params.add(GameConstants.GOLD_ITEM_ID, (int) Math.ceil(exp * 0.25D));
|
||||
|
||||
// Verify that the player has the items
|
||||
if (!this.getPlayer().getInventory().verifyItems(params)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create change info
|
||||
var changes = new PlayerChangeInfo();
|
||||
|
||||
// Remove items
|
||||
this.getPlayer().getInventory().removeItems(params, changes);
|
||||
|
||||
// Add exp
|
||||
this.addExp(exp);
|
||||
|
||||
// Success
|
||||
return changes.setSuccess(true);
|
||||
}
|
||||
|
||||
public PlayerChangeInfo promote() {
|
||||
// TODO check player level to make sure they can advance this disc
|
||||
|
||||
// Get promote data
|
||||
int phaseId = (this.getData().getPromoteGroupId() * 1000) + (this.phase + 1);
|
||||
var data = GameData.getDiscPromoteDataTable().get(phaseId);
|
||||
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verify that the player has the items
|
||||
if (!this.getPlayer().getInventory().verifyItems(data.getMaterials())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove items
|
||||
var changes = this.getPlayer().getInventory().removeItems(data.getMaterials(), null);
|
||||
|
||||
// Add phase level
|
||||
this.phase++;
|
||||
|
||||
// Save to database
|
||||
this.save();
|
||||
|
||||
// Success
|
||||
return changes.setSuccess(true);
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public Disc toProto() {
|
||||
var proto = Disc.newInstance()
|
||||
.setId(this.getDiscId())
|
||||
.setLevel(this.getLevel())
|
||||
.setExp(this.getExp())
|
||||
.setPhase(this.getPhase())
|
||||
.setStar(this.getStar())
|
||||
.setCreateTime(this.getCreateTime());
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public StarTowerDisc toStarTowerProto() {
|
||||
var proto = StarTowerDisc.newInstance()
|
||||
.setId(this.getDiscId())
|
||||
.setLevel(this.getLevel())
|
||||
.setPhase(this.getPhase())
|
||||
.setStar(this.getStar());
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
57
src/main/java/emu/nebula/game/formation/Formation.java
Normal file
57
src/main/java/emu/nebula/game/formation/Formation.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package emu.nebula.game.formation;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.nebula.proto.Public.FormationInfo;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(useDiscriminator = false)
|
||||
public class Formation {
|
||||
private int num;
|
||||
private int[] charIds;
|
||||
private int[] discIds;
|
||||
|
||||
@Deprecated
|
||||
public Formation() {
|
||||
|
||||
}
|
||||
|
||||
public Formation(int num) {
|
||||
this.num = num;
|
||||
this.charIds = new int[3];
|
||||
this.discIds = new int[6];
|
||||
}
|
||||
|
||||
public Formation(FormationInfo formation) {
|
||||
this.num = formation.getNumber();
|
||||
this.charIds = formation.getCharIds().toArray();
|
||||
this.discIds = formation.getDiscIds().toArray();
|
||||
}
|
||||
|
||||
public int getCharIdAt(int i) {
|
||||
if (i < 0 || i >= this.charIds.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this.charIds[i];
|
||||
}
|
||||
|
||||
public int getDiscIdAt(int i) {
|
||||
if (i < 0 || i >= this.discIds.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this.discIds[i];
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public FormationInfo toProto() {
|
||||
var proto = FormationInfo.newInstance()
|
||||
.setNumber(this.getNum())
|
||||
.addAllCharIds(this.getCharIds())
|
||||
.addAllDiscIds(this.getDiscIds());
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package emu.nebula.game.formation;
|
||||
|
||||
import emu.nebula.game.player.PlayerManager;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import emu.nebula.GameConstants;
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.database.GameDatabaseObject;
|
||||
import emu.nebula.game.player.Player;
|
||||
import emu.nebula.proto.Public.FormationInfo;
|
||||
import emu.nebula.proto.Public.TowerFormation;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(value = "formations", useDiscriminator = false)
|
||||
public class FormationManager extends PlayerManager implements GameDatabaseObject {
|
||||
@Id
|
||||
private int uid;
|
||||
|
||||
private Map<Integer, Formation> formations;
|
||||
|
||||
@Deprecated // Morphia only
|
||||
public FormationManager() {
|
||||
|
||||
}
|
||||
|
||||
public FormationManager(Player player) {
|
||||
super(player);
|
||||
this.uid = player.getUid();
|
||||
this.formations = new HashMap<>();
|
||||
|
||||
this.save();
|
||||
}
|
||||
|
||||
public Formation getFormationById(int num) {
|
||||
return this.formations.get(num);
|
||||
}
|
||||
|
||||
public boolean updateFormation(FormationInfo info) {
|
||||
// Sanity check
|
||||
if (info.getNumber() < 1 || info.getNumber() > GameConstants.MAX_FORMATIONS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// More sanity
|
||||
if (info.getCharIds().length() < 1 || info.getCharIds().length() > 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.getDiscIds().length() < 3 || info.getDiscIds().length() > 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate formation to make sure we have all the chars and discs
|
||||
// TODO
|
||||
|
||||
// Create formation
|
||||
var formation = new Formation(info);
|
||||
|
||||
// Add to formations map
|
||||
this.formations.put(formation.getNum(), formation);
|
||||
|
||||
// Save to db
|
||||
Nebula.getGameDatabase().update(this, this.getPlayerUid(), "formations." + formation.getNum(), formation, true);
|
||||
|
||||
// Success
|
||||
return true;
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public TowerFormation toProto() {
|
||||
var proto = TowerFormation.newInstance();
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
}
|
||||
65
src/main/java/emu/nebula/game/inventory/GameItem.java
Normal file
65
src/main/java/emu/nebula/game/inventory/GameItem.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package emu.nebula.game.inventory;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import dev.morphia.annotations.Indexed;
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.database.GameDatabaseObject;
|
||||
import emu.nebula.game.player.Player;
|
||||
import emu.nebula.proto.Public.Item;
|
||||
import emu.nebula.util.Utils;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(value = "items", useDiscriminator = false)
|
||||
public class GameItem implements GameDatabaseObject {
|
||||
@Id
|
||||
private ObjectId uid;
|
||||
@Indexed
|
||||
private int playerUid;
|
||||
|
||||
private int itemId;
|
||||
private int count;
|
||||
|
||||
@Deprecated
|
||||
public GameItem() {
|
||||
|
||||
}
|
||||
|
||||
public GameItem(Player player, int id, int count) {
|
||||
this.playerUid = player.getUid();
|
||||
this.itemId = id;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public int add(int amount) {
|
||||
int oldCount = this.count;
|
||||
this.count = Utils.safeAdd(this.count, amount, Integer.MAX_VALUE, 0);
|
||||
return this.count - oldCount;
|
||||
}
|
||||
|
||||
// Database
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
if (this.getCount() <= 0) {
|
||||
if (this.getUid() != null) {
|
||||
Nebula.getGameDatabase().delete(this);
|
||||
}
|
||||
} else {
|
||||
Nebula.getGameDatabase().save(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public Item toProto() {
|
||||
var proto = Item.newInstance()
|
||||
.setTid(this.getItemId())
|
||||
.setQty(this.getCount());
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
63
src/main/java/emu/nebula/game/inventory/GameResource.java
Normal file
63
src/main/java/emu/nebula/game/inventory/GameResource.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package emu.nebula.game.inventory;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import dev.morphia.annotations.Indexed;
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.database.GameDatabaseObject;
|
||||
import emu.nebula.game.player.Player;
|
||||
import emu.nebula.proto.Public.Res;
|
||||
import emu.nebula.util.Utils;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(value = "resources", useDiscriminator = false)
|
||||
public class GameResource implements GameDatabaseObject {
|
||||
@Id
|
||||
private ObjectId uid;
|
||||
@Indexed
|
||||
private int playerUid;
|
||||
|
||||
public int resourceId;
|
||||
public int count;
|
||||
|
||||
@Deprecated // Morphia only
|
||||
public GameResource() {
|
||||
|
||||
}
|
||||
|
||||
public GameResource(Player player, int id, int count) {
|
||||
this.playerUid = player.getUid();
|
||||
this.resourceId = id;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public int add(int amount) {
|
||||
int oldCount = this.count;
|
||||
this.count = Utils.safeAdd(this.count, amount, Integer.MAX_VALUE, 0);
|
||||
return this.count - oldCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
if (this.getCount() <= 0) {
|
||||
if (this.getUid() != null) {
|
||||
Nebula.getGameDatabase().delete(this);
|
||||
}
|
||||
} else {
|
||||
Nebula.getGameDatabase().save(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public Res toProto() {
|
||||
var proto = Res.newInstance()
|
||||
.setTid(this.getResourceId())
|
||||
.setQty(this.getCount());
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
310
src/main/java/emu/nebula/game/inventory/Inventory.java
Normal file
310
src/main/java/emu/nebula/game/inventory/Inventory.java
Normal file
@@ -0,0 +1,310 @@
|
||||
package emu.nebula.game.inventory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.data.GameData;
|
||||
import emu.nebula.game.player.PlayerManager;
|
||||
import emu.nebula.proto.Public.Item;
|
||||
import emu.nebula.proto.Public.Res;
|
||||
import emu.nebula.game.player.Player;
|
||||
import emu.nebula.game.player.PlayerChangeInfo;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class Inventory extends PlayerManager {
|
||||
private final Int2ObjectMap<GameResource> resources;
|
||||
private final Int2ObjectMap<GameItem> items;
|
||||
|
||||
public Inventory(Player player) {
|
||||
super(player);
|
||||
|
||||
this.resources = new Int2ObjectOpenHashMap<>();
|
||||
this.items = new Int2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
// Resources
|
||||
|
||||
public synchronized int getResourceCount(int id) {
|
||||
var res = this.resources.get(id);
|
||||
return res != null ? res.getCount() : 0;
|
||||
}
|
||||
|
||||
// Items
|
||||
|
||||
public synchronized int getItemCount(int id) {
|
||||
var item = this.getItems().get(id);
|
||||
return item != null ? item.getCount() : 0;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public synchronized PlayerChangeInfo addItem(int id, int count, PlayerChangeInfo changes) {
|
||||
// Changes
|
||||
if (changes == null) {
|
||||
changes = new PlayerChangeInfo();
|
||||
}
|
||||
|
||||
// Sanity
|
||||
if (count == 0) {
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Get game data
|
||||
var data = GameData.getItemDataTable().get(id);
|
||||
if (data == null) {
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Set amount
|
||||
int amount = count;
|
||||
|
||||
// Add item
|
||||
switch (data.getItemType()) {
|
||||
case Res -> {
|
||||
var res = this.resources.get(id);
|
||||
int diff = 0;
|
||||
|
||||
if (amount > 0) {
|
||||
// Add resource
|
||||
if (res == null) {
|
||||
res = new GameResource(this.getPlayer(), id, amount);
|
||||
this.resources.put(res.getResourceId(), res);
|
||||
|
||||
diff = amount;
|
||||
} else {
|
||||
diff = res.add(amount);
|
||||
}
|
||||
|
||||
res.save();
|
||||
} else {
|
||||
// Remove resource
|
||||
if (res == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
diff = res.add(amount);
|
||||
res.save();
|
||||
|
||||
if (res.getCount() < 0) {
|
||||
this.resources.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (diff != 0) {
|
||||
var change = Res.newInstance()
|
||||
.setTid(id)
|
||||
.setQty(diff);
|
||||
|
||||
changes.add(change);
|
||||
}
|
||||
}
|
||||
case Item -> {
|
||||
var item = this.items.get(id);
|
||||
int diff = 0;
|
||||
|
||||
if (amount > 0) {
|
||||
// Add resource
|
||||
if (item == null) {
|
||||
item = new GameItem(this.getPlayer(), id, amount);
|
||||
this.items.put(item.getItemId(), item);
|
||||
|
||||
diff = amount;
|
||||
} else {
|
||||
diff = item.add(amount);
|
||||
}
|
||||
|
||||
item.save();
|
||||
} else {
|
||||
// Remove resource
|
||||
if (item == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
diff = item.add(amount);
|
||||
item.save();
|
||||
|
||||
if (item.getCount() < 0) {
|
||||
this.resources.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (diff != 0) {
|
||||
var change = Item.newInstance()
|
||||
.setTid(id)
|
||||
.setQty(diff);
|
||||
|
||||
changes.add(change);
|
||||
}
|
||||
}
|
||||
case Disc -> {
|
||||
if (amount <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
var disc = getPlayer().getCharacters().addDisc(id);
|
||||
|
||||
if (disc != null) {
|
||||
changes.add(disc.toProto());
|
||||
}
|
||||
}
|
||||
case Char -> {
|
||||
if (amount <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
var character = getPlayer().getCharacters().addCharacter(id);
|
||||
|
||||
if (character != null) {
|
||||
changes.add(character.toProto());
|
||||
}
|
||||
}
|
||||
case WorldRankExp -> {
|
||||
this.getPlayer().addExp(amount, changes);
|
||||
}
|
||||
default -> {
|
||||
// Not implemented
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
return changes;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public synchronized PlayerChangeInfo addItems(List<ItemParam> params, PlayerChangeInfo changes) {
|
||||
// Changes
|
||||
if (changes == null) {
|
||||
changes = new PlayerChangeInfo();
|
||||
}
|
||||
|
||||
for (ItemParam param : params) {
|
||||
this.addItem(param.getId(), param.getCount(), changes);
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
public synchronized PlayerChangeInfo addItems(ItemParamMap params) {
|
||||
return this.addItems(params, null);
|
||||
}
|
||||
|
||||
public synchronized PlayerChangeInfo addItems(ItemParamMap params, PlayerChangeInfo changes) {
|
||||
// Changes
|
||||
if (changes == null) {
|
||||
changes = new PlayerChangeInfo();
|
||||
}
|
||||
|
||||
for (var param : params.getEntrySet()) {
|
||||
this.addItem(param.getIntKey(), param.getIntValue(), changes);
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
public synchronized PlayerChangeInfo removeItem(int id, int count, PlayerChangeInfo changes) {
|
||||
if (count > 0) {
|
||||
count = -count;
|
||||
}
|
||||
|
||||
return this.addItem(id, count, changes);
|
||||
}
|
||||
|
||||
public synchronized PlayerChangeInfo removeItems(ItemParamMap params) {
|
||||
return this.removeItems(params, null);
|
||||
}
|
||||
|
||||
public synchronized PlayerChangeInfo removeItems(ItemParamMap params, PlayerChangeInfo changes) {
|
||||
// Changes
|
||||
if (changes == null) {
|
||||
changes = new PlayerChangeInfo();
|
||||
}
|
||||
|
||||
for (var param : params.getEntrySet()) {
|
||||
this.removeItem(param.getIntKey(), param.getIntValue(), changes);
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the player has enough quanity of this item
|
||||
*/
|
||||
public synchronized boolean verifyItem(int id, int count) {
|
||||
// Sanity check
|
||||
if (count == 0) {
|
||||
return true;
|
||||
} else if (count < 0) {
|
||||
// Return false if we are trying to verify negative numbers
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get game data
|
||||
var data = GameData.getItemDataTable().get(id);
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean result = switch (data.getItemType()) {
|
||||
case Res -> {
|
||||
yield this.getResourceCount(id) >= count;
|
||||
}
|
||||
case Item -> {
|
||||
yield this.getItemCount(id) >= count;
|
||||
}
|
||||
case Disc -> {
|
||||
yield getPlayer().getCharacters().hasDisc(id);
|
||||
}
|
||||
case Char -> {
|
||||
yield getPlayer().getCharacters().hasCharacter(id);
|
||||
}
|
||||
default -> {
|
||||
// Not implemented
|
||||
yield false;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
public synchronized boolean verifyItems(ItemParamMap params) {
|
||||
boolean hasItems = true;
|
||||
|
||||
for (var param : params.getEntrySet()) {
|
||||
hasItems = this.verifyItem(param.getIntKey(), param.getIntValue());
|
||||
|
||||
if (!hasItems) {
|
||||
return hasItems;
|
||||
}
|
||||
}
|
||||
|
||||
return hasItems;
|
||||
}
|
||||
|
||||
// Database
|
||||
|
||||
public void loadFromDatabase() {
|
||||
var db = Nebula.getGameDatabase();
|
||||
|
||||
db.getObjects(GameItem.class, "playerUid", getPlayerUid()).forEach(item -> {
|
||||
// Get data
|
||||
var data = GameData.getItemDataTable().get(item.getItemId());
|
||||
if (data == null) return;
|
||||
|
||||
// Add
|
||||
this.items.put(item.getItemId(), item);
|
||||
});
|
||||
|
||||
db.getObjects(GameResource.class, "playerUid", getPlayerUid()).forEach(res -> {
|
||||
// Get data
|
||||
var data = GameData.getItemDataTable().get(res.getResourceId());
|
||||
if (data == null) return;
|
||||
|
||||
// Add
|
||||
this.resources.put(res.getResourceId(), res);
|
||||
});
|
||||
}
|
||||
}
|
||||
30
src/main/java/emu/nebula/game/inventory/ItemParam.java
Normal file
30
src/main/java/emu/nebula/game/inventory/ItemParam.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package emu.nebula.game.inventory;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.nebula.proto.Public.ItemTpl;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(useDiscriminator = false)
|
||||
public class ItemParam {
|
||||
public int id;
|
||||
public int count;
|
||||
|
||||
@Deprecated // Morphia only
|
||||
public ItemParam() {
|
||||
|
||||
}
|
||||
|
||||
public ItemParam(int id, int count) {
|
||||
this.id = id;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public ItemTpl toProto() {
|
||||
var proto = ItemTpl.newInstance()
|
||||
.setTid(this.getId())
|
||||
.setQty(this.getCount());
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
100
src/main/java/emu/nebula/game/inventory/ItemParamMap.java
Normal file
100
src/main/java/emu/nebula/game/inventory/ItemParamMap.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package emu.nebula.game.inventory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import emu.nebula.proto.Public.ItemInfo;
|
||||
import emu.nebula.proto.Public.ItemTpl;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import us.hebi.quickbuf.RepeatedMessage;
|
||||
|
||||
public class ItemParamMap extends Int2IntOpenHashMap {
|
||||
private static final long serialVersionUID = -4186524272780523459L;
|
||||
|
||||
public FastEntrySet entries() {
|
||||
return this.int2IntEntrySet();
|
||||
}
|
||||
|
||||
@Override @Deprecated
|
||||
public int addTo(int itemId, int count) {
|
||||
return this.add(itemId, count);
|
||||
}
|
||||
|
||||
public int add(int itemId, int count) {
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return super.addTo(itemId, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all item params from the other map to this one
|
||||
* @param map The other item param map
|
||||
*/
|
||||
public void add(ItemParamMap map) {
|
||||
for (var entry : map.entries()) {
|
||||
this.add(entry.getIntKey(), entry.getIntValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ItemParamMap with item amounts multiplied
|
||||
* @param mult Value to multiply all item amounts in this map by
|
||||
* @return
|
||||
*/
|
||||
public ItemParamMap mulitply(int multiplier) {
|
||||
var params = new ItemParamMap();
|
||||
|
||||
for (var entry : this.int2IntEntrySet()) {
|
||||
params.put(entry.getIntKey(), entry.getIntValue() * multiplier);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public FastEntrySet getEntrySet() {
|
||||
return this.int2IntEntrySet();
|
||||
}
|
||||
|
||||
public List<ItemParam> toList() {
|
||||
List<ItemParam> list = new ArrayList<>();
|
||||
|
||||
for (var entry : this.int2IntEntrySet()) {
|
||||
list.add(new ItemParam(entry.getIntKey(), entry.getIntValue()));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public Stream<ItemTpl> itemTemplateStream() {
|
||||
return getEntrySet()
|
||||
.stream()
|
||||
.map(e -> ItemTpl.newInstance().setTid(e.getIntKey()).setQty(e.getIntValue()));
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public static ItemParamMap fromTemplates(RepeatedMessage<ItemTpl> items) {
|
||||
var map = new ItemParamMap();
|
||||
|
||||
for (var template : items) {
|
||||
map.add(template.getTid(), template.getQty());
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public static ItemParamMap fromItemInfos(RepeatedMessage<ItemInfo> items) {
|
||||
var map = new ItemParamMap();
|
||||
|
||||
for (var template : items) {
|
||||
map.add(template.getTid(), template.getQty());
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
56
src/main/java/emu/nebula/game/inventory/ItemSubType.java
Normal file
56
src/main/java/emu/nebula/game/inventory/ItemSubType.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package emu.nebula.game.inventory;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
|
||||
public enum ItemSubType {
|
||||
Res (1),
|
||||
Item (2),
|
||||
Char (3),
|
||||
Energy (4),
|
||||
WorldRankExp (5),
|
||||
CharShard (6),
|
||||
Disc (8),
|
||||
TalentStrengthen (9),
|
||||
DiscStrengthen (12),
|
||||
DiscPromote (13),
|
||||
TreasureBox (17),
|
||||
GearTreasureBox (18),
|
||||
SubNoteSkill (19),
|
||||
SkillStrengthen (24),
|
||||
CharacterLimitBreak (25),
|
||||
MonthlyCard (30),
|
||||
EnergyItem (31),
|
||||
ComCYO (32),
|
||||
OutfitCYO (33),
|
||||
RandomPackage (34),
|
||||
Equipment (35),
|
||||
FateCard (37),
|
||||
EquipmentExp (38),
|
||||
DiscLimitBreak (40),
|
||||
Potential (41),
|
||||
SpecificPotential (42),
|
||||
Honor (43),
|
||||
CharacterYO (44),
|
||||
PlayHead (45),
|
||||
CharacterSkin (46);
|
||||
|
||||
@Getter
|
||||
private final int value;
|
||||
private final static Int2ObjectMap<ItemSubType> map = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
static {
|
||||
for (ItemSubType type : ItemSubType.values()) {
|
||||
map.put(type.getValue(), type);
|
||||
}
|
||||
}
|
||||
|
||||
private ItemSubType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ItemSubType getByValue(int value) {
|
||||
return map.get(value);
|
||||
}
|
||||
}
|
||||
39
src/main/java/emu/nebula/game/inventory/ItemType.java
Normal file
39
src/main/java/emu/nebula/game/inventory/ItemType.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package emu.nebula.game.inventory;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
|
||||
public enum ItemType {
|
||||
Res (1),
|
||||
Item (2),
|
||||
Char (3),
|
||||
Energy (4),
|
||||
WorldRankExp (5),
|
||||
RogueItem (6),
|
||||
Disc (7),
|
||||
Equipment (8),
|
||||
CharacterSkin (9),
|
||||
MonthlyCard (10),
|
||||
Title (11),
|
||||
Honor (12),
|
||||
HeadItem (13);
|
||||
|
||||
@Getter
|
||||
private final int value;
|
||||
private final static Int2ObjectMap<ItemType> map = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
static {
|
||||
for (ItemType type : ItemType.values()) {
|
||||
map.put(type.getValue(), type);
|
||||
}
|
||||
}
|
||||
|
||||
private ItemType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ItemType getByValue(int value) {
|
||||
return map.get(value);
|
||||
}
|
||||
}
|
||||
86
src/main/java/emu/nebula/game/mail/GameMail.java
Normal file
86
src/main/java/emu/nebula/game/mail/GameMail.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package emu.nebula.game.mail;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.game.inventory.ItemParamMap;
|
||||
import emu.nebula.proto.Public.Mail;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Entity(useDiscriminator = false)
|
||||
public class GameMail {
|
||||
private int id;
|
||||
|
||||
private String author;
|
||||
private String subject;
|
||||
private String desc;
|
||||
|
||||
private ItemParamMap attachments;
|
||||
|
||||
@Setter private boolean read;
|
||||
@Setter private boolean recv;
|
||||
@Setter private boolean pin;
|
||||
|
||||
private long flag;
|
||||
private long time;
|
||||
private long expiry;
|
||||
|
||||
@Deprecated // Morphia only
|
||||
public GameMail() {
|
||||
|
||||
}
|
||||
|
||||
public GameMail(String author, String subject, String desc) {
|
||||
this.author = author;
|
||||
this.subject = subject;
|
||||
this.desc = desc;
|
||||
this.time = Nebula.getCurrentTime();
|
||||
this.expiry = this.time + TimeUnit.DAYS.toSeconds(30);
|
||||
}
|
||||
|
||||
protected void setId(int id) {
|
||||
if (this.id == 0) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canRemove() {
|
||||
return (this.isRead() || this.isRecv()) && !this.isPin() && (this.hasAttachments() && this.isRecv());
|
||||
}
|
||||
|
||||
public boolean hasAttachments() {
|
||||
return this.attachments != null;
|
||||
}
|
||||
|
||||
public void addAttachment(int itemId, int count) {
|
||||
if (this.attachments == null) {
|
||||
this.attachments = new ItemParamMap();
|
||||
}
|
||||
|
||||
this.attachments.add(itemId, count);
|
||||
}
|
||||
|
||||
public Mail toProto() {
|
||||
var proto = Mail.newInstance()
|
||||
.setId(this.getId())
|
||||
.setAuthor(this.getAuthor())
|
||||
.setSubject(this.getSubject())
|
||||
.setDesc(this.getDesc())
|
||||
.setTime(this.getTime())
|
||||
.setRead(this.isRead())
|
||||
.setRecv(this.isRecv())
|
||||
.setPin(this.isPin())
|
||||
.setFlag(this.getFlag())
|
||||
.setDeadline(this.getExpiry());
|
||||
|
||||
if (this.getAttachments() != null) {
|
||||
this.getAttachments().itemTemplateStream()
|
||||
.forEach(proto::addAttachments);
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
197
src/main/java/emu/nebula/game/mail/Mailbox.java
Normal file
197
src/main/java/emu/nebula/game/mail/Mailbox.java
Normal file
@@ -0,0 +1,197 @@
|
||||
package emu.nebula.game.mail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.database.GameDatabaseObject;
|
||||
import emu.nebula.game.player.Player;
|
||||
import emu.nebula.game.player.PlayerChangeInfo;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(value = "mailbox", useDiscriminator = false)
|
||||
public class Mailbox implements GameDatabaseObject, Iterable<GameMail> {
|
||||
@Id
|
||||
private int uid;
|
||||
private int lastMailId;
|
||||
|
||||
private List<GameMail> list;
|
||||
|
||||
@Deprecated // Morphia only
|
||||
public Mailbox() {
|
||||
|
||||
}
|
||||
|
||||
public Mailbox(Player player) {
|
||||
this.uid = player.getUid();
|
||||
this.list = new ArrayList<>();
|
||||
this.save();
|
||||
}
|
||||
|
||||
// TODO optimize to an O(n) algorithm like a map
|
||||
public GameMail getMailById(int id) {
|
||||
return this.getList().stream()
|
||||
.filter(m -> m.getId() == id)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public void sendMail(GameMail mail) {
|
||||
// Set mail id
|
||||
mail.setId(++this.lastMailId);
|
||||
|
||||
// Add to mail list
|
||||
this.list.add(mail);
|
||||
|
||||
// Save to database
|
||||
Nebula.getGameDatabase().update(this, getUid(), "lastMailId", this.getLastMailId());
|
||||
Nebula.getGameDatabase().addToList(this, getUid(), "list", mail);
|
||||
}
|
||||
|
||||
public boolean readMail(int id, long flag) {
|
||||
// Get mail
|
||||
var mail = this.getMailById(id);
|
||||
|
||||
if (mail == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set read
|
||||
mail.setRead(true);
|
||||
|
||||
// Update in database
|
||||
Nebula.getGameDatabase().updateNested(this, getUid(), "list.id", id, "list.$.read", true);
|
||||
|
||||
// Success
|
||||
return true;
|
||||
}
|
||||
|
||||
public GameMail pinMail(int id, long flag, boolean pin) {
|
||||
// Get mail
|
||||
var mail = this.getMailById(id);
|
||||
|
||||
if (mail == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set pin
|
||||
mail.setPin(pin);
|
||||
|
||||
// Update in database
|
||||
Nebula.getGameDatabase().updateNested(this, getUid(), "list.id", id, "list.$.pin", true);
|
||||
|
||||
// Success
|
||||
return mail;
|
||||
}
|
||||
|
||||
public PlayerChangeInfo recvMail(Player player, int id) {
|
||||
// Get mails that we want to claim
|
||||
List<GameMail> mails = null;
|
||||
|
||||
if (id == 0) {
|
||||
// Claim all
|
||||
mails = this.getList()
|
||||
.stream()
|
||||
.filter(mail -> !mail.isRecv() && mail.hasAttachments())
|
||||
.toList();
|
||||
} else {
|
||||
// Claim one
|
||||
var mail = this.getMailById(id);
|
||||
|
||||
if (mail != null && !mail.isRecv() && mail.hasAttachments()) {
|
||||
mails = List.of(mail);
|
||||
}
|
||||
}
|
||||
|
||||
// Create change info
|
||||
var changes = new PlayerChangeInfo();
|
||||
|
||||
// Sanity
|
||||
if (mails == null || mails.isEmpty()) {
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Recieved mail id list
|
||||
var recvMails = new IntArrayList();
|
||||
|
||||
// Recv mails
|
||||
for (var mail : mails) {
|
||||
// Add attachments to player
|
||||
player.getInventory().addItems(mail.getAttachments(), changes);
|
||||
|
||||
// Set claimed flag
|
||||
mail.setRecv(true);
|
||||
|
||||
// Add to recvied mail list
|
||||
recvMails.add(mail.getId());
|
||||
|
||||
// Update in database
|
||||
Nebula.getGameDatabase().updateNested(this, getUid(), "list.id", mail.getId(), "list.$.recv", true);
|
||||
}
|
||||
|
||||
// Set extra change data
|
||||
changes.setExtraData(recvMails);
|
||||
|
||||
// Success
|
||||
return changes.setSuccess(true);
|
||||
}
|
||||
|
||||
public IntList removeMail(Player player, int id) {
|
||||
// Get mails that we want to claim
|
||||
Set<GameMail> toRemove = null;
|
||||
|
||||
if (id == 0) {
|
||||
// Claim all
|
||||
toRemove = this.getList()
|
||||
.stream()
|
||||
.filter(mail -> mail.canRemove())
|
||||
.collect(Collectors.toSet());
|
||||
} else {
|
||||
// Claim one
|
||||
var mail = this.getMailById(id);
|
||||
|
||||
if (mail != null && mail.canRemove()) {
|
||||
toRemove = Set.of(mail);
|
||||
}
|
||||
}
|
||||
|
||||
// Recieved mail id list
|
||||
var removed = new IntArrayList();
|
||||
|
||||
// Sanity check
|
||||
if (toRemove == null || toRemove.isEmpty()) {
|
||||
return removed;
|
||||
}
|
||||
|
||||
// Remove
|
||||
var it = this.getList().iterator();
|
||||
while (it.hasNext()) {
|
||||
var mail = it.next();
|
||||
|
||||
if (toRemove.contains(mail)) {
|
||||
removed.add(mail.getId());
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Save
|
||||
this.save();
|
||||
|
||||
// Success
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<GameMail> iterator() {
|
||||
return this.getList().iterator();
|
||||
}
|
||||
}
|
||||
358
src/main/java/emu/nebula/game/player/Player.java
Normal file
358
src/main/java/emu/nebula/game/player/Player.java
Normal file
@@ -0,0 +1,358 @@
|
||||
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.inventory.Inventory;
|
||||
import emu.nebula.game.mail.Mailbox;
|
||||
import emu.nebula.game.tower.StarTowerManager;
|
||||
import emu.nebula.net.GameSession;
|
||||
import emu.nebula.proto.PlayerData.PlayerInfo;
|
||||
import emu.nebula.proto.Public.NewbieInfo;
|
||||
import emu.nebula.proto.Public.QuestType;
|
||||
import emu.nebula.proto.Public.WorldClass;
|
||||
|
||||
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 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 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;
|
||||
|
||||
@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.gender = gender;
|
||||
this.headIcon = 101;
|
||||
this.skinId = 10301;
|
||||
this.titlePrefix = 1;
|
||||
this.titleSuffix = 2;
|
||||
this.level = 1;
|
||||
this.boards = 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 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 void sendMessage(String string) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
// Login
|
||||
|
||||
public void onLoad() {
|
||||
// Load from database
|
||||
this.getCharacters().loadFromDatabase();
|
||||
this.getInventory().loadFromDatabase();
|
||||
|
||||
// Load referenced classes
|
||||
this.formations = Nebula.getGameDatabase().getObjectByField(FormationManager.class, "_id", this.getUid());
|
||||
if (this.formations == null) {
|
||||
this.formations = new FormationManager(this);
|
||||
} else {
|
||||
this.formations.setPlayer(this);
|
||||
}
|
||||
|
||||
this.mailbox = Nebula.getGameDatabase().getObjectByField(Mailbox.class, "_id", this.getUid());
|
||||
if (this.mailbox == null) {
|
||||
this.mailbox = new Mailbox(this);
|
||||
}
|
||||
|
||||
this.starTowerManager = Nebula.getGameDatabase().getObjectByField(StarTowerManager.class, "_id", this.getUid());
|
||||
if (this.starTowerManager == null) {
|
||||
this.starTowerManager = new StarTowerManager(this);
|
||||
} else {
|
||||
this.starTowerManager.setPlayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public PlayerInfo toProto() {
|
||||
PlayerInfo proto = PlayerInfo.newInstance();
|
||||
|
||||
var acc = proto.getMutableAcc()
|
||||
.setNickName(this.getName())
|
||||
.setGender(this.getGender())
|
||||
.setId(this.getUid())
|
||||
.setHeadIcon(this.getHeadIcon())
|
||||
.setSkinId(this.getSkinId())
|
||||
.setTitlePrefix(this.getTitlePrefix())
|
||||
.setTitleSuffix(this.getTitleSuffix())
|
||||
.setCreateTime(this.getCreateTime());
|
||||
|
||||
proto.getMutableWorldClass()
|
||||
.setStage(3)
|
||||
.setCur(this.getLevel())
|
||||
.setLastExp(this.getExp());
|
||||
|
||||
proto.getMutableEnergy()
|
||||
.getMutableEnergy()
|
||||
.setUpdateTime(Nebula.getCurrentTime())
|
||||
.setNextDuration(60)
|
||||
.setPrimary(240)
|
||||
.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));
|
||||
|
||||
//
|
||||
proto.addBoard(410301);
|
||||
proto.setServerTs(Nebula.getCurrentTime());
|
||||
|
||||
// Extra
|
||||
proto.setAchievements(new byte[64]);
|
||||
|
||||
proto.getMutableVampireSurvivorRecord()
|
||||
.getMutableSeason();
|
||||
|
||||
proto.getMutableQuests();
|
||||
proto.getMutableAgent();
|
||||
proto.getMutablePhone();
|
||||
proto.getMutableStory();
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
49
src/main/java/emu/nebula/game/player/PlayerChangeInfo.java
Normal file
49
src/main/java/emu/nebula/game/player/PlayerChangeInfo.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package emu.nebula.game.player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import emu.nebula.GameConstants;
|
||||
import emu.nebula.proto.AnyOuterClass.Any;
|
||||
import emu.nebula.proto.Public.ChangeInfo;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
|
||||
@Getter
|
||||
public class PlayerChangeInfo {
|
||||
private boolean success;
|
||||
private List<Any> list;
|
||||
|
||||
@Setter
|
||||
private Object extraData;
|
||||
|
||||
public PlayerChangeInfo() {
|
||||
this.list = new ArrayList<>();
|
||||
}
|
||||
|
||||
public PlayerChangeInfo setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void add(ProtoMessage<?> proto) {
|
||||
var any = Any.newInstance()
|
||||
.setTypeUrl(GameConstants.PROTO_BASE_TYPE_URL + proto.getClass().getSimpleName())
|
||||
.setValue(proto.toByteArray());
|
||||
|
||||
this.list.add(any);
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public ChangeInfo toProto() {
|
||||
var proto = ChangeInfo.newInstance();
|
||||
|
||||
for (var any : this.getList()) {
|
||||
proto.addProps(any);
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
27
src/main/java/emu/nebula/game/player/PlayerManager.java
Normal file
27
src/main/java/emu/nebula/game/player/PlayerManager.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package emu.nebula.game.player;
|
||||
|
||||
public abstract class PlayerManager {
|
||||
private transient Player player;
|
||||
|
||||
public PlayerManager() {
|
||||
|
||||
}
|
||||
|
||||
public PlayerManager(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
public void setPlayer(Player player) {
|
||||
if (this.player == null) {
|
||||
this.player = player;
|
||||
}
|
||||
}
|
||||
|
||||
public int getPlayerUid() {
|
||||
return this.getPlayer().getUid();
|
||||
}
|
||||
}
|
||||
116
src/main/java/emu/nebula/game/player/PlayerModule.java
Normal file
116
src/main/java/emu/nebula/game/player/PlayerModule.java
Normal file
@@ -0,0 +1,116 @@
|
||||
package emu.nebula.game.player;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.game.GameContext;
|
||||
import emu.nebula.game.GameContextModule;
|
||||
import emu.nebula.game.account.Account;
|
||||
import emu.nebula.net.GameSession;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
|
||||
public class PlayerModule extends GameContextModule {
|
||||
private final Int2ObjectMap<Player> cachedPlayers;
|
||||
private final Object2ObjectMap<String, Player> cachedPlayersByAccount;
|
||||
|
||||
public PlayerModule(GameContext gameContext) {
|
||||
super(gameContext);
|
||||
|
||||
this.cachedPlayers = new Int2ObjectOpenHashMap<>();
|
||||
this.cachedPlayersByAccount = new Object2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
public Int2ObjectMap<Player> getCachedPlayers() {
|
||||
return cachedPlayers;
|
||||
}
|
||||
|
||||
private void addToCache(Player player) {
|
||||
this.cachedPlayers.put(player.getUid(), player);
|
||||
this.cachedPlayersByAccount.put(player.getAccountUid(), player);
|
||||
}
|
||||
|
||||
public void removeFromCache(Player player) {
|
||||
this.cachedPlayers.remove(player.getUid());
|
||||
this.cachedPlayersByAccount.remove(player.getAccountUid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a player object that has been previously cached. Returns null if the player isnt in the cache.
|
||||
* @param uid User id of the player
|
||||
* @return
|
||||
*/
|
||||
public synchronized Player getCachedPlayerByUid(int uid) {
|
||||
return getCachedPlayers().get(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a player object with the given account. Returns null if the player doesnt exist.
|
||||
* @param uid User id of the player
|
||||
* @return
|
||||
*/
|
||||
public synchronized Player getPlayerByAccount(Account account) {
|
||||
// Get player from cache
|
||||
Player player = this.cachedPlayersByAccount.get(account.getUid());
|
||||
|
||||
if (player == null) {
|
||||
// Retrieve player object from database if its not there
|
||||
player = Nebula.getGameDatabase().getObjectByField(Player.class, "accountUid", account.getUid());
|
||||
|
||||
if (player != null) {
|
||||
// Load player
|
||||
player.onLoad();
|
||||
|
||||
// Put in cache
|
||||
this.addToCache(player);
|
||||
}
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a player with the specified user id.
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
public synchronized Player createPlayer(GameSession session, String name, boolean gender) {
|
||||
// Make sure player doesnt already exist
|
||||
if (Nebula.getGameDatabase().checkIfObjectExists(Player.class, "accountUid", session.getAccount().getUid())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Limit name length
|
||||
if (name.length() > 20) {
|
||||
name = name.substring(0, 19);
|
||||
}
|
||||
|
||||
// Create player and save to db
|
||||
var player = new Player(session.getAccount(), name, gender);
|
||||
player.onLoad();
|
||||
player.save();
|
||||
|
||||
// Put in cache
|
||||
this.addToCache(player);
|
||||
|
||||
// Set player for session
|
||||
session.setPlayer(player);
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of recent players that have logged on (for followers)
|
||||
* @param player Player that requested this
|
||||
*/
|
||||
public synchronized List<Player> getRandomPlayerList(Player player) {
|
||||
List<Player> list = getCachedPlayers().values().stream().filter(p -> p != player).collect(Collectors.toList());
|
||||
Collections.shuffle(list);
|
||||
return list.stream().limit(15).toList();
|
||||
}
|
||||
}
|
||||
12
src/main/java/emu/nebula/game/story/StoryManager.java
Normal file
12
src/main/java/emu/nebula/game/story/StoryManager.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package emu.nebula.game.story;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.nebula.database.GameDatabaseObject;
|
||||
import emu.nebula.game.player.PlayerManager;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(value = "story", useDiscriminator = false)
|
||||
public class StoryManager extends PlayerManager implements GameDatabaseObject {
|
||||
|
||||
}
|
||||
39
src/main/java/emu/nebula/game/tower/CaseType.java
Normal file
39
src/main/java/emu/nebula/game/tower/CaseType.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package emu.nebula.game.tower;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
|
||||
public enum CaseType {
|
||||
Battle (1),
|
||||
OpenDoor (2),
|
||||
PotentialSelect (3),
|
||||
FateCardSelect (4),
|
||||
NoteSelect (5),
|
||||
NpcEvent (6),
|
||||
SelectSpecialPotential (7),
|
||||
RecoveryHP (8),
|
||||
NpcRecoveryHP (9),
|
||||
Hawker (10),
|
||||
StrengthenMachine (11),
|
||||
DoorDanger (12),
|
||||
SyncHP (13);
|
||||
|
||||
@Getter
|
||||
private final int value;
|
||||
private final static Int2ObjectMap<CaseType> map = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
static {
|
||||
for (CaseType type : CaseType.values()) {
|
||||
map.put(type.getValue(), type);
|
||||
}
|
||||
}
|
||||
|
||||
private CaseType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static CaseType getByValue(int value) {
|
||||
return map.get(value);
|
||||
}
|
||||
}
|
||||
55
src/main/java/emu/nebula/game/tower/StarTowerCase.java
Normal file
55
src/main/java/emu/nebula/game/tower/StarTowerCase.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package emu.nebula.game.tower;
|
||||
|
||||
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class StarTowerCase {
|
||||
private int id;
|
||||
|
||||
@Setter(AccessLevel.NONE)
|
||||
private CaseType type;
|
||||
|
||||
// Extra data
|
||||
private int teamLevel;
|
||||
|
||||
private int floorId;
|
||||
|
||||
// Select
|
||||
private int[] ids;
|
||||
|
||||
public StarTowerCase(CaseType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public StarTowerRoomCase toProto() {
|
||||
var proto = StarTowerRoomCase.newInstance()
|
||||
.setId(this.getId());
|
||||
|
||||
switch (this.type) {
|
||||
case Battle -> {
|
||||
proto.getMutableBattleCase();
|
||||
}
|
||||
case OpenDoor -> {
|
||||
proto.getMutableDoorCase();
|
||||
}
|
||||
case SyncHP -> {
|
||||
proto.getMutableSyncHPCase();
|
||||
}
|
||||
case SelectSpecialPotential -> {
|
||||
proto.getMutableSelectSpecialPotentialCase();
|
||||
}
|
||||
case PotentialSelect -> {
|
||||
proto.getMutableSelectPotentialCase();
|
||||
}
|
||||
default -> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
256
src/main/java/emu/nebula/game/tower/StarTowerInstance.java
Normal file
256
src/main/java/emu/nebula/game/tower/StarTowerInstance.java
Normal file
@@ -0,0 +1,256 @@
|
||||
package emu.nebula.game.tower;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.nebula.data.resources.StarTowerDef;
|
||||
import emu.nebula.game.formation.Formation;
|
||||
import emu.nebula.game.player.Player;
|
||||
import emu.nebula.proto.PublicStarTower.StarTowerChar;
|
||||
import emu.nebula.proto.PublicStarTower.StarTowerDisc;
|
||||
import emu.nebula.proto.PublicStarTower.StarTowerInfo;
|
||||
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
|
||||
import emu.nebula.proto.StarTowerApply.StarTowerApplyReq;
|
||||
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
|
||||
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
|
||||
import emu.nebula.util.Snowflake;
|
||||
import emu.nebula.util.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@Getter
|
||||
@Entity(useDiscriminator = false)
|
||||
public class StarTowerInstance {
|
||||
private transient StarTowerManager manager;
|
||||
private transient StarTowerDef data;
|
||||
|
||||
// Tower id
|
||||
private int id;
|
||||
|
||||
// Room
|
||||
private int floor;
|
||||
private int mapId;
|
||||
private int mapTableId;
|
||||
private String mapParam;
|
||||
private int paramId;
|
||||
|
||||
// Team
|
||||
private int formationId;
|
||||
private int buildId;
|
||||
private int teamLevel;
|
||||
private int teamExp;
|
||||
private int charHp;
|
||||
private int battleTime;
|
||||
private List<StarTowerChar> chars;
|
||||
private List<StarTowerDisc> discs;
|
||||
|
||||
private int lastCaseId = 0;
|
||||
private List<StarTowerCase> cases;
|
||||
|
||||
private Int2IntMap items;
|
||||
|
||||
@Deprecated // Morphia only
|
||||
public StarTowerInstance() {
|
||||
|
||||
}
|
||||
|
||||
public StarTowerInstance(StarTowerManager manager, StarTowerDef data, Formation formation, StarTowerApplyReq req) {
|
||||
this.manager = manager;
|
||||
this.data = data;
|
||||
|
||||
this.id = req.getId();
|
||||
|
||||
this.mapId = req.getMapId();
|
||||
this.mapTableId = req.getMapTableId();
|
||||
this.mapParam = req.getMapParam();
|
||||
this.paramId = req.getParamId();
|
||||
|
||||
this.formationId = req.getFormationId();
|
||||
this.buildId = Snowflake.newUid();
|
||||
this.teamLevel = 1;
|
||||
this.floor = 1;
|
||||
this.charHp = -1;
|
||||
this.chars = new ArrayList<>();
|
||||
this.discs = new ArrayList<>();
|
||||
|
||||
this.cases = new ArrayList<>();
|
||||
this.items = new Int2IntOpenHashMap();
|
||||
|
||||
// Init formation
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int id = formation.getCharIdAt(i);
|
||||
var character = getPlayer().getCharacters().getCharacterById(id);
|
||||
|
||||
if (character != null) {
|
||||
chars.add(character.toStarTowerProto());
|
||||
} else {
|
||||
chars.add(StarTowerChar.newInstance());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int id = formation.getDiscIdAt(i);
|
||||
var disc = getPlayer().getCharacters().getDiscById(id);
|
||||
|
||||
if (disc != null) {
|
||||
discs.add(disc.toStarTowerProto());
|
||||
} else {
|
||||
discs.add(StarTowerDisc.newInstance());
|
||||
}
|
||||
}
|
||||
|
||||
// Add cases
|
||||
this.addCase(new StarTowerCase(CaseType.Battle));
|
||||
this.addCase(new StarTowerCase(CaseType.SyncHP));
|
||||
|
||||
|
||||
var doorCase = this.addCase(new StarTowerCase(CaseType.OpenDoor));
|
||||
doorCase.setFloorId(this.getFloor() + 1);
|
||||
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return this.manager.getPlayer();
|
||||
}
|
||||
|
||||
public StarTowerCase addCase(StarTowerCase towerCase) {
|
||||
return this.addCase(null, towerCase);
|
||||
}
|
||||
|
||||
public StarTowerCase addCase(StarTowerInteractResp rsp, StarTowerCase towerCase) {
|
||||
// Add to cases list
|
||||
this.cases.add(towerCase);
|
||||
|
||||
// Increment id
|
||||
towerCase.setId(++this.lastCaseId);
|
||||
|
||||
// Set proto
|
||||
if (rsp != null) {
|
||||
rsp.getMutableCases().add(towerCase.toProto());
|
||||
}
|
||||
|
||||
return towerCase;
|
||||
}
|
||||
|
||||
public StarTowerInteractResp handleInteract(StarTowerInteractReq req) {
|
||||
var rsp = StarTowerInteractResp.newInstance()
|
||||
.setId(req.getId());
|
||||
|
||||
if (req.hasBattleEndReq()) {
|
||||
this.onBattleEnd(req, rsp);
|
||||
} else if (req.hasRecoveryHPReq()) {
|
||||
var proto = req.getRecoveryHPReq();
|
||||
} else if (req.hasSelectReq()) {
|
||||
|
||||
} else if (req.hasEnterReq()) {
|
||||
this.onEnterReq(req, rsp);
|
||||
}
|
||||
|
||||
// Set data protos
|
||||
rsp.getMutableData();
|
||||
rsp.getMutableChange();
|
||||
//rsp.getMutableNextPackage();
|
||||
|
||||
return rsp;
|
||||
}
|
||||
|
||||
// Interact events
|
||||
|
||||
@SneakyThrows
|
||||
public void onBattleEnd(StarTowerInteractReq req, StarTowerInteractResp rsp) {
|
||||
var proto = req.getBattleEndReq();
|
||||
|
||||
if (proto.hasVictory()) {
|
||||
// Add team level
|
||||
this.teamLevel++;
|
||||
|
||||
// Add clear time
|
||||
this.battleTime += proto.getVictory().getTime();
|
||||
|
||||
// Handle victory
|
||||
rsp.getMutableBattleEndResp()
|
||||
.getMutableVictory()
|
||||
.setLv(this.getTeamLevel())
|
||||
.setBattleTime(this.getBattleTime());
|
||||
|
||||
// Add potential selector TODO
|
||||
} else {
|
||||
// Handle defeat
|
||||
}
|
||||
}
|
||||
|
||||
public void onSelect(StarTowerInteractReq req, StarTowerInteractResp rsp) {
|
||||
|
||||
}
|
||||
|
||||
public void onEnterReq(StarTowerInteractReq req, StarTowerInteractResp rsp) {
|
||||
var proto = req.getEnterReq();
|
||||
|
||||
// Set
|
||||
this.floor = this.floor++;
|
||||
this.mapId = proto.getMapId();
|
||||
this.mapTableId = proto.getMapTableId();
|
||||
|
||||
// Clear cases TODO
|
||||
this.lastCaseId = 0;
|
||||
this.cases.clear();
|
||||
|
||||
// Add cases
|
||||
var syncHpCase = this.addCase(new StarTowerCase(CaseType.SyncHP));
|
||||
var doorCase = this.addCase(new StarTowerCase(CaseType.OpenDoor));
|
||||
doorCase.setFloorId(this.getFloor() + 1);
|
||||
|
||||
// Proto
|
||||
var room = rsp.getMutableEnterResp().getMutableRoom();
|
||||
|
||||
room.getMutableData()
|
||||
.setMapId(this.getMapId())
|
||||
.setMapTableId(this.getMapTableId())
|
||||
.setFloor(this.getFloor());
|
||||
|
||||
room.addAllCases(syncHpCase.toProto(), doorCase.toProto());
|
||||
}
|
||||
|
||||
public void onRecoveryHP(StarTowerInteractReq req, StarTowerInteractResp rsp) {
|
||||
// Add case
|
||||
this.addCase(rsp, new StarTowerCase(CaseType.RecoveryHP));
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public StarTowerInfo toProto() {
|
||||
var proto = StarTowerInfo.newInstance();
|
||||
|
||||
proto.getMutableMeta()
|
||||
.setId(this.getId())
|
||||
.setCharHp(this.getCharHp())
|
||||
.setTeamLevel(this.getTeamLevel())
|
||||
.setNPCInteractions(1)
|
||||
.setBuildId(this.getBuildId());
|
||||
|
||||
this.getChars().forEach(proto.getMutableMeta()::addChars);
|
||||
this.getDiscs().forEach(proto.getMutableMeta()::addDiscs);
|
||||
|
||||
proto.getMutableRoom().getMutableData()
|
||||
.setFloor(this.getFloor())
|
||||
.setMapId(this.getMapId())
|
||||
.setMapTableId(this.getMapTableId())
|
||||
.setMapParam(this.getMapParam())
|
||||
.setParamId(this.getParamId());
|
||||
|
||||
// Cases
|
||||
for (var starTowerCase : this.getCases()) {
|
||||
proto.getMutableRoom().addCases(starTowerCase.toProto());
|
||||
}
|
||||
|
||||
// TODO
|
||||
proto.getMutableBag();
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
51
src/main/java/emu/nebula/game/tower/StarTowerManager.java
Normal file
51
src/main/java/emu/nebula/game/tower/StarTowerManager.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package emu.nebula.game.tower;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import emu.nebula.data.GameData;
|
||||
import emu.nebula.database.GameDatabaseObject;
|
||||
import emu.nebula.game.player.Player;
|
||||
import emu.nebula.game.player.PlayerManager;
|
||||
import emu.nebula.proto.StarTowerApply.StarTowerApplyReq;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(value = "star_tower", useDiscriminator = false)
|
||||
public class StarTowerManager extends PlayerManager implements GameDatabaseObject {
|
||||
@Id
|
||||
private int uid;
|
||||
|
||||
private transient StarTowerInstance instance;
|
||||
|
||||
@Deprecated // Morphia only
|
||||
public StarTowerManager() {
|
||||
|
||||
}
|
||||
|
||||
public StarTowerManager(Player player) {
|
||||
super(player);
|
||||
this.uid = player.getUid();
|
||||
|
||||
this.save();
|
||||
}
|
||||
|
||||
public StarTowerInstance apply(StarTowerApplyReq req) {
|
||||
// Sanity checks
|
||||
var data = GameData.getStarTowerDataTable().get(req.getId());
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get formation
|
||||
var formation = getPlayer().getFormations().getFormationById(req.getFormationId());
|
||||
if (formation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create instance
|
||||
this.instance = new StarTowerInstance(this, data, formation, req);
|
||||
|
||||
// Success
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user