diff --git a/src/main/java/emu/lunarcore/GameConstants.java b/src/main/java/emu/lunarcore/GameConstants.java index d76538b..3b5be81 100644 --- a/src/main/java/emu/lunarcore/GameConstants.java +++ b/src/main/java/emu/lunarcore/GameConstants.java @@ -12,6 +12,7 @@ public class GameConstants { // Game public static final int HOME_PLANE_ID = 10000; public static final String DEFAULT_NAME = "Trailblazer"; + public static final int TRAILBLAZER_AVATAR_ID = 8001; public static final int MAX_TRAILBLAZER_LEVEL = 70; public static final int MAX_STAMINA = 240; public static final int MAX_AVATARS_IN_TEAM = 4; diff --git a/src/main/java/emu/lunarcore/data/GameData.java b/src/main/java/emu/lunarcore/data/GameData.java index 7ae3283..5257f5d 100644 --- a/src/main/java/emu/lunarcore/data/GameData.java +++ b/src/main/java/emu/lunarcore/data/GameData.java @@ -24,6 +24,7 @@ public class GameData { @Getter private static Int2ObjectMap npcMonsterExcelMap = new Int2ObjectOpenHashMap<>(); @Getter private static Int2ObjectMap stageExcelMap = new Int2ObjectOpenHashMap<>(); @Getter private static Int2ObjectMap mapEntranceExcelMap = new Int2ObjectOpenHashMap<>(); + @Getter private static Int2ObjectMap heroExcelMap = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap avatarPromotionExcelMap = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap avatarSkillTreeExcelMap = new Int2ObjectOpenHashMap<>(); diff --git a/src/main/java/emu/lunarcore/data/excel/HeroExcel.java b/src/main/java/emu/lunarcore/data/excel/HeroExcel.java new file mode 100644 index 0000000..cf13e47 --- /dev/null +++ b/src/main/java/emu/lunarcore/data/excel/HeroExcel.java @@ -0,0 +1,19 @@ +package emu.lunarcore.data.excel; + +import emu.lunarcore.data.GameResource; +import emu.lunarcore.data.ResourceType; +import emu.lunarcore.game.player.PlayerGender; +import lombok.Getter; + +@Getter +@ResourceType(name = {"HeroConfig.json"}) +public class HeroExcel extends GameResource { + private int HeroAvatarID; + private PlayerGender Gender; + + @Override + public int getId() { + return HeroAvatarID; + } + +} diff --git a/src/main/java/emu/lunarcore/game/avatar/AvatarRank.java b/src/main/java/emu/lunarcore/game/avatar/AvatarRank.java new file mode 100644 index 0000000..b0eb5a7 --- /dev/null +++ b/src/main/java/emu/lunarcore/game/avatar/AvatarRank.java @@ -0,0 +1,18 @@ +package emu.lunarcore.game.avatar; + +import dev.morphia.annotations.Entity; +import lombok.Getter; +import lombok.Setter; + +@Entity(useDiscriminator = false) +public class AvatarRank { + @Getter @Setter + private int value; + + /** + * A helper class that contains information about an avatar's rank. + */ + public AvatarRank() { + + } +} diff --git a/src/main/java/emu/lunarcore/game/avatar/AvatarStorage.java b/src/main/java/emu/lunarcore/game/avatar/AvatarStorage.java index e336fb4..4a32fe0 100644 --- a/src/main/java/emu/lunarcore/game/avatar/AvatarStorage.java +++ b/src/main/java/emu/lunarcore/game/avatar/AvatarStorage.java @@ -6,22 +6,24 @@ import java.util.stream.Stream; import emu.lunarcore.LunarRail; import emu.lunarcore.data.GameData; import emu.lunarcore.data.excel.AvatarExcel; +import emu.lunarcore.data.excel.HeroExcel; import emu.lunarcore.game.player.BasePlayerManager; import emu.lunarcore.game.player.Player; import emu.lunarcore.server.packet.send.PacketPlayerSyncScNotify; + import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.Getter; +@Getter public class AvatarStorage extends BasePlayerManager implements Iterable { private final Int2ObjectMap avatars; - + private final Int2ObjectMap heroPaths; + public AvatarStorage(Player player) { super(player); this.avatars = new Int2ObjectOpenHashMap<>(); - } - - public Int2ObjectMap getAvatars() { - return avatars; + this.heroPaths = new Int2ObjectOpenHashMap<>(); } public int getAvatarCount() { @@ -56,9 +58,25 @@ public class AvatarStorage extends BasePlayerManager implements Iterable heroStream = LunarRail.getGameDatabase().getObjects(HeroPath.class, "ownerUid", this.getPlayer().getUid()); + + heroStream.forEach(heroPath -> { + // Load avatar excel data + AvatarExcel excel = GameData.getAvatarExcelMap().get(heroPath.getId()); + if (excel == null) { + return; + } + + heroPath.setExcel(excel); + + this.heroPaths.put(heroPath.getId(), heroPath); + }); + + // Load avatars Stream stream = LunarRail.getGameDatabase().getObjects(GameAvatar.class, "ownerUid", this.getPlayer().getUid()); stream.forEach(avatar -> { @@ -76,15 +110,22 @@ public class AvatarStorage extends BasePlayerManager implements Iterable skills; + private int currentHp; private int currentSp; - private Map skills; - private transient int entityId; private transient Int2ObjectMap equips; + private transient HeroPath heroPath; @Deprecated // Morphia only public GameAvatar() { this.equips = new Int2ObjectOpenHashMap<>(); + this.level = 1; this.currentHp = 10000; this.currentSp = 0; } @@ -74,16 +75,21 @@ public class GameAvatar implements GameEntity { this(); this.excel = excel; this.avatarId = excel.getId(); - this.level = 1; - // Set default skills + // Set defaults + this.rank = new AvatarRank(); this.skills = new HashMap<>(); + + // Add skills for (var skillTree : excel.getDefaultSkillTrees()) { this.skills.put(skillTree.getPointID(), skillTree.getLevel()); } - - // Set stats - this.currentHp = 10000; + } + + public GameAvatar(HeroPath path) { + this(); + this.avatarId = GameConstants.TRAILBLAZER_AVATAR_ID; + this.setHeroPath(path); } public void setOwner(Player player) { @@ -91,14 +97,14 @@ public class GameAvatar implements GameEntity { this.ownerUid = player.getUid(); } - public void setExcel(AvatarExcel excel) { - this.excel = excel; - } - @Override public void setEntityId(int entityId) { this.entityId = entityId; } + + public boolean isHero() { + return GameData.getHeroExcelMap().containsKey(this.getAvatarId()); + } public int getMaxSp() { return this.getExcel().getMaxSp(); @@ -111,6 +117,27 @@ public class GameAvatar implements GameEntity { public void setCurrentSp(int amount) { this.currentSp = Math.max(Math.min(amount, getMaxSp()), 0); } + + public int getRank() { + return this.rank.getValue(); + } + + public void setRank(int rank) { + this.rank.setValue(rank); + } + + public void setHeroPath(HeroPath heroPath) { + // Clear prev set hero path from avatar + if (this.getHeroPath() != null) { + this.getHeroPath().setAvatar(null); + } + + this.rank = heroPath.getRank(); + this.skills = heroPath.getSkills(); + this.excel = heroPath.getExcel(); + this.heroPath = heroPath; + this.heroPath.setAvatar(this); + } // Equips @@ -271,6 +298,11 @@ public class GameAvatar implements GameEntity { // Database public void save() { + // Save avatar LunarRail.getGameDatabase().save(this); + // Save hero path + if (this.getHeroPath() != null) { + this.getHeroPath().save(); + } } } diff --git a/src/main/java/emu/lunarcore/game/avatar/HeroPath.java b/src/main/java/emu/lunarcore/game/avatar/HeroPath.java new file mode 100644 index 0000000..20aed7e --- /dev/null +++ b/src/main/java/emu/lunarcore/game/avatar/HeroPath.java @@ -0,0 +1,67 @@ +package emu.lunarcore.game.avatar; + +import java.util.HashMap; +import java.util.Map; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Id; +import dev.morphia.annotations.Indexed; +import emu.lunarcore.LunarRail; +import emu.lunarcore.data.excel.AvatarExcel; +import emu.lunarcore.game.player.Player; +import emu.lunarcore.proto.AvatarSkillTreeOuterClass.AvatarSkillTree; +import emu.lunarcore.proto.HeroBasicTypeInfoOuterClass.HeroBasicTypeInfo; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Entity(value = "heroPaths", useDiscriminator = false) +public class HeroPath { + @Id + private int id; // Equivalent to HeroBaseType + @Indexed + private int ownerUid; + + private AvatarRank rank; + private Map skills; + + @Setter private transient GameAvatar avatar; + @Setter private transient AvatarExcel excel; + + @Deprecated // Morphia only! + public HeroPath() { + + } + + public HeroPath(Player player, AvatarExcel excel) { + // Set excel avatar id as id + this.id = excel.getId(); + this.ownerUid = player.getUid(); + this.excel = excel; + + // Set defaults + this.rank = new AvatarRank(); + this.skills = new HashMap<>(); + + // Add skills + for (var skillTree : excel.getDefaultSkillTrees()) { + this.skills.put(skillTree.getPointID(), skillTree.getLevel()); + } + } + + public HeroBasicTypeInfo toProto() { + var proto = HeroBasicTypeInfo.newInstance() + .setBasicTypeValue(this.getId()) + .setRank(this.getRank().getValue()); + + for (var skill : getSkills().entrySet()) { + proto.addSkillTreeList(AvatarSkillTree.newInstance().setPointId(skill.getKey()).setLevel(skill.getValue())); + } + + return proto; + } + + public void save() { + LunarRail.getGameDatabase().save(this); + } +} diff --git a/src/main/java/emu/lunarcore/game/player/Player.java b/src/main/java/emu/lunarcore/game/player/Player.java index 3150f6c..9e46821 100644 --- a/src/main/java/emu/lunarcore/game/player/Player.java +++ b/src/main/java/emu/lunarcore/game/player/Player.java @@ -14,6 +14,7 @@ import emu.lunarcore.data.excel.MapEntranceExcel; import emu.lunarcore.game.account.Account; import emu.lunarcore.game.avatar.AvatarStorage; import emu.lunarcore.game.avatar.GameAvatar; +import emu.lunarcore.game.avatar.HeroPath; import emu.lunarcore.game.battle.Battle; import emu.lunarcore.game.gacha.PlayerGachaInfo; import emu.lunarcore.game.inventory.Inventory; @@ -26,6 +27,7 @@ import emu.lunarcore.server.packet.SessionState; import emu.lunarcore.server.packet.send.PacketEnterSceneByServerScNotify; import emu.lunarcore.server.packet.send.PacketPlayerSyncScNotify; import emu.lunarcore.server.packet.send.PacketRevcMsgScNotify; +import emu.lunarcore.server.packet.send.PacketSetHeroBasicTypeScRsp; import emu.lunarcore.util.Position; import lombok.Getter; @@ -39,7 +41,9 @@ public class Player { @Indexed private String accountUid; private String name; private String signature; + private PlayerGender gender; private int birthday; + private int curBasicType; private int level; private int exp; @@ -66,6 +70,8 @@ public class Player { @Deprecated // Morphia only public Player() { + this.curBasicType = 8001; + this.gender = PlayerGender.GENDER_MAN; this.avatars = new AvatarStorage(this); this.inventory = new Inventory(this); } @@ -89,10 +95,13 @@ public class Player { // Setup uid this.initUid(); + + // Setup hero paths + this.getAvatars().setupHeroPaths(); // Give us a starter character. // TODO script tutorial - GameAvatar avatar = new GameAvatar(8001); + GameAvatar avatar = new GameAvatar(this.getCurHeroPath()); this.getAvatars().addAvatar(avatar); this.getLineupManager().getCurrentLineup().getAvatars().add(8001); @@ -147,6 +156,11 @@ public class Player { } public GameAvatar getAvatarById(int avatarId) { + // Check if we are trying to retrieve the hero character + if (GameData.getHeroExcelMap().containsKey(avatarId)) { + avatarId = GameConstants.TRAILBLAZER_AVATAR_ID; + } + return getAvatars().getAvatarById(avatarId); } @@ -209,6 +223,22 @@ public class Player { return this.exp - GameData.getPlayerExpRequired(this.level); } + public HeroPath getCurHeroPath() { + return this.getAvatars().getHeroPathById(this.getCurBasicType()); + } + + public void setHeroBasicType(int heroType) { + HeroPath path = this.getAvatars().getHeroPathById(heroType); + if (path == null) return; + + GameAvatar mainCharacter = this.getAvatarById(GameConstants.TRAILBLAZER_AVATAR_ID); + if (mainCharacter == null) return; + + // Set new hero and cur basic type + mainCharacter.setHeroPath(path); + this.curBasicType = heroType; + } + public boolean isInBattle() { return this.battle != null; } @@ -294,10 +324,10 @@ public class Player { // Load avatars and inventory first this.getAvatars().loadFromDatabase(); this.getInventory().loadFromDatabase(); - this.getAvatars().recalcAvatarStats(); // Recalc stats after items have loaded for the avatars // Load Etc this.getLineupManager().validate(this); + this.getAvatars().setupHeroPaths(); // Enter scene (should happen after everything else loads) this.loadScene(planeId, floorId, entryId); @@ -316,5 +346,4 @@ public class Player { return proto; } - } diff --git a/src/main/java/emu/lunarcore/game/player/PlayerGender.java b/src/main/java/emu/lunarcore/game/player/PlayerGender.java new file mode 100644 index 0000000..c63b63b --- /dev/null +++ b/src/main/java/emu/lunarcore/game/player/PlayerGender.java @@ -0,0 +1,21 @@ +package emu.lunarcore.game.player; + +import dev.morphia.annotations.Entity; +import lombok.Getter; + +@Entity(useDiscriminator = false) +public enum PlayerGender { + GENDER_NONE (0), + GENDER_MAN (1), + GENDER_WOMAN (2); + + @Getter + private final int val; + + /** + * Official name: GenderType + */ + private PlayerGender(int val) { + this.val = val; + } +} diff --git a/src/main/java/emu/lunarcore/game/service/InventoryService.java b/src/main/java/emu/lunarcore/game/service/InventoryService.java index dcce46c..4643c6e 100644 --- a/src/main/java/emu/lunarcore/game/service/InventoryService.java +++ b/src/main/java/emu/lunarcore/game/service/InventoryService.java @@ -143,7 +143,7 @@ public class InventoryService extends BaseGameService { int nextLevel = avatar.getSkills().getOrDefault(pointId, 0) + 1; AvatarSkillTreeExcel skillTree = GameData.getAvatarSkillTreeExcel(pointId, nextLevel); - if (skillTree == null || skillTree.getAvatarID() != avatarId) return; + if (skillTree == null || skillTree.getAvatarID() != avatar.getExcel().getAvatarID()) return; // Verify items for (ItemParam param : skillTree.getMaterialList()) { @@ -171,7 +171,12 @@ public class InventoryService extends BaseGameService { player.save(); // Send packets - player.sendPacket(new PacketPlayerSyncScNotify(avatar)); + if (avatar.getHeroPath() != null) { + player.sendPacket(new PacketPlayerSyncScNotify(avatar.getHeroPath())); + } else { + player.sendPacket(new PacketPlayerSyncScNotify(avatar)); + } + player.sendPacket(new PacketUnlockSkilltreeScRsp(avatarId, pointId, nextLevel)); } @@ -200,7 +205,12 @@ public class InventoryService extends BaseGameService { avatar.save(); // Send packets - player.sendPacket(new PacketPlayerSyncScNotify(avatar)); + if (avatar.getHeroPath() != null) { + player.sendPacket(new PacketPlayerSyncScNotify(avatar.getHeroPath())); + } else { + player.sendPacket(new PacketPlayerSyncScNotify(avatar)); + } + player.sendPacket(new BasePacket(CmdId.RankUpAvatarScRsp)); } diff --git a/src/main/java/emu/lunarcore/server/packet/recv/HandlerGetHeroBasicTypeInfoCsReq.java b/src/main/java/emu/lunarcore/server/packet/recv/HandlerGetHeroBasicTypeInfoCsReq.java index cfb4acb..7309d17 100644 --- a/src/main/java/emu/lunarcore/server/packet/recv/HandlerGetHeroBasicTypeInfoCsReq.java +++ b/src/main/java/emu/lunarcore/server/packet/recv/HandlerGetHeroBasicTypeInfoCsReq.java @@ -11,7 +11,7 @@ public class HandlerGetHeroBasicTypeInfoCsReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] data) throws Exception { - session.send(new PacketGetHeroBasicTypeInfoScRsp()); + session.send(new PacketGetHeroBasicTypeInfoScRsp(session.getPlayer())); } } diff --git a/src/main/java/emu/lunarcore/server/packet/recv/HandlerSetHeroBasicTypeCsReq.java b/src/main/java/emu/lunarcore/server/packet/recv/HandlerSetHeroBasicTypeCsReq.java new file mode 100644 index 0000000..3797f94 --- /dev/null +++ b/src/main/java/emu/lunarcore/server/packet/recv/HandlerSetHeroBasicTypeCsReq.java @@ -0,0 +1,21 @@ +package emu.lunarcore.server.packet.recv; + +import emu.lunarcore.proto.SetHeroBasicTypeCsReqOuterClass.SetHeroBasicTypeCsReq; +import emu.lunarcore.server.game.GameSession; +import emu.lunarcore.server.packet.CmdId; +import emu.lunarcore.server.packet.Opcodes; +import emu.lunarcore.server.packet.PacketHandler; +import emu.lunarcore.server.packet.send.PacketSetHeroBasicTypeScRsp; + +@Opcodes(CmdId.SetHeroBasicTypeCsReq) +public class HandlerSetHeroBasicTypeCsReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] data) throws Exception { + var req = SetHeroBasicTypeCsReq.parseFrom(data); + + session.getPlayer().setHeroBasicType(req.getBasicTypeValue()); + session.send(new PacketSetHeroBasicTypeScRsp(session.getPlayer())); + } + +} diff --git a/src/main/java/emu/lunarcore/server/packet/send/PacketGetHeroBasicTypeInfoScRsp.java b/src/main/java/emu/lunarcore/server/packet/send/PacketGetHeroBasicTypeInfoScRsp.java index fc466cf..9ef7687 100644 --- a/src/main/java/emu/lunarcore/server/packet/send/PacketGetHeroBasicTypeInfoScRsp.java +++ b/src/main/java/emu/lunarcore/server/packet/send/PacketGetHeroBasicTypeInfoScRsp.java @@ -1,24 +1,23 @@ package emu.lunarcore.server.packet.send; -import emu.lunarcore.proto.GenderOuterClass.Gender; +import emu.lunarcore.game.avatar.HeroPath; +import emu.lunarcore.game.player.Player; import emu.lunarcore.proto.GetHeroBasicTypeInfoScRspOuterClass.GetHeroBasicTypeInfoScRsp; -import emu.lunarcore.proto.HeroBasicTypeInfoOuterClass.HeroBasicTypeInfo; -import emu.lunarcore.proto.HeroBasicTypeOuterClass.HeroBasicType; import emu.lunarcore.server.packet.BasePacket; import emu.lunarcore.server.packet.CmdId; public class PacketGetHeroBasicTypeInfoScRsp extends BasePacket { - public PacketGetHeroBasicTypeInfoScRsp() { + public PacketGetHeroBasicTypeInfoScRsp(Player player) { super(CmdId.GetHeroBasicTypeInfoScRsp); - var heroBasicType = HeroBasicTypeInfo.newInstance() - .setBasicType(HeroBasicType.BoyWarrior); - var data = GetHeroBasicTypeInfoScRsp.newInstance() - .setGender(Gender.GenderMan) - .setCurBasicType(HeroBasicType.BoyWarrior) - .addBasicTypeInfoList(heroBasicType); + .setGenderValue(player.getGender().getVal()) + .setCurBasicTypeValue(player.getCurBasicType()); + + for (HeroPath path : player.getAvatars().getHeroPaths().values()) { + data.addAllBasicTypeInfoList(path.toProto()); + } this.setData(data); } diff --git a/src/main/java/emu/lunarcore/server/packet/send/PacketPlayerSyncScNotify.java b/src/main/java/emu/lunarcore/server/packet/send/PacketPlayerSyncScNotify.java index a5ae09e..28e1fc3 100644 --- a/src/main/java/emu/lunarcore/server/packet/send/PacketPlayerSyncScNotify.java +++ b/src/main/java/emu/lunarcore/server/packet/send/PacketPlayerSyncScNotify.java @@ -3,6 +3,7 @@ package emu.lunarcore.server.packet.send; import java.util.Collection; import emu.lunarcore.game.avatar.GameAvatar; +import emu.lunarcore.game.avatar.HeroPath; import emu.lunarcore.game.inventory.GameItem; import emu.lunarcore.game.player.Player; import emu.lunarcore.proto.AvatarSyncOuterClass.AvatarSync; @@ -98,4 +99,13 @@ public class PacketPlayerSyncScNotify extends BasePacket { } } } + + public PacketPlayerSyncScNotify(HeroPath heroPath) { + this(); + + var data = PlayerSyncScNotify.newInstance() + .addBasicTypeInfoList(heroPath.toProto()); + + this.setData(data); + } } diff --git a/src/main/java/emu/lunarcore/server/packet/send/PacketSetHeroBasicTypeScRsp.java b/src/main/java/emu/lunarcore/server/packet/send/PacketSetHeroBasicTypeScRsp.java new file mode 100644 index 0000000..f36f83e --- /dev/null +++ b/src/main/java/emu/lunarcore/server/packet/send/PacketSetHeroBasicTypeScRsp.java @@ -0,0 +1,18 @@ +package emu.lunarcore.server.packet.send; + +import emu.lunarcore.game.player.Player; +import emu.lunarcore.proto.SetHeroBasicTypeScRspOuterClass.SetHeroBasicTypeScRsp; +import emu.lunarcore.server.packet.BasePacket; +import emu.lunarcore.server.packet.CmdId; + +public class PacketSetHeroBasicTypeScRsp extends BasePacket { + + public PacketSetHeroBasicTypeScRsp(Player player) { + super(CmdId.SetHeroBasicTypeScRsp); + + var data = SetHeroBasicTypeScRsp.newInstance() + .setBasicTypeValue(player.getCurBasicType()); + + this.setData(data); + } +}