[Breaking] Rewrite parts of the avatar system to allow the main character to change paths properly

This commit is contained in:
Melledy
2023-09-28 04:38:33 -07:00
parent b9fcebb401
commit 4a513890c5
15 changed files with 334 additions and 47 deletions

View File

@@ -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;

View File

@@ -24,6 +24,7 @@ public class GameData {
@Getter private static Int2ObjectMap<NpcMonsterExcel> npcMonsterExcelMap = new Int2ObjectOpenHashMap<>();
@Getter private static Int2ObjectMap<StageExcel> stageExcelMap = new Int2ObjectOpenHashMap<>();
@Getter private static Int2ObjectMap<MapEntranceExcel> mapEntranceExcelMap = new Int2ObjectOpenHashMap<>();
@Getter private static Int2ObjectMap<HeroExcel> heroExcelMap = new Int2ObjectOpenHashMap<>();
private static Int2ObjectMap<AvatarPromotionExcel> avatarPromotionExcelMap = new Int2ObjectOpenHashMap<>();
private static Int2ObjectMap<AvatarSkillTreeExcel> avatarSkillTreeExcelMap = new Int2ObjectOpenHashMap<>();

View File

@@ -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;
}
}

View File

@@ -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() {
}
}

View File

@@ -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<GameAvatar> {
private final Int2ObjectMap<GameAvatar> avatars;
private final Int2ObjectMap<HeroPath> heroPaths;
public AvatarStorage(Player player) {
super(player);
this.avatars = new Int2ObjectOpenHashMap<>();
}
public Int2ObjectMap<GameAvatar> getAvatars() {
return avatars;
this.heroPaths = new Int2ObjectOpenHashMap<>();
}
public int getAvatarCount() {
@@ -57,8 +59,24 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<GameAva
return true;
}
public void recalcAvatarStats() {
//this.getAvatars().values().stream().forEach(GameAvatar::recalcStats);
public HeroPath getHeroPathById(int id) {
return getHeroPaths().get(id);
}
/**
* Updates hero types for players. Will create hero types if they dont exist already.
*/
public void setupHeroPaths() {
for (HeroExcel heroExcel : GameData.getHeroExcelMap().values()) {
if (getHeroPaths().containsKey(heroExcel.getId())) continue;
AvatarExcel excel = GameData.getAvatarExcelMap().get(heroExcel.getId());
if (excel == null) continue;
HeroPath path = new HeroPath(getPlayer(), excel);
path.save();
getHeroPaths().put(path.getId(), path);
}
}
@Override
@@ -69,6 +87,22 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<GameAva
// Database
public void loadFromDatabase() {
// Load hero paths
Stream<HeroPath> 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<GameAvatar> stream = LunarRail.getGameDatabase().getObjects(GameAvatar.class, "ownerUid", this.getPlayer().getUid());
stream.forEach(avatar -> {
@@ -77,14 +111,21 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<GameAva
return;
}
// Load avatar excel data
AvatarExcel excel = GameData.getAvatarExcelMap().get(avatar.getAvatarId());
if (excel == null) {
return;
// Set hero path
if (avatar.isHero()) {
avatar.setHeroPath(getPlayer().getCurHeroPath());
} else {
// Load avatar excel data
AvatarExcel excel = GameData.getAvatarExcelMap().get(avatar.getAvatarId());
if (excel == null) {
return;
}
// Set ownerships
avatar.setExcel(excel);
}
// Set ownerships
avatar.setExcel(excel);
// Set ownership
avatar.setOwner(getPlayer());
// Add to avatar storage

View File

@@ -42,25 +42,26 @@ public class GameAvatar implements GameEntity {
@Indexed @Getter private int ownerUid; // Uid of player that this avatar belongs to
private transient Player owner;
private transient AvatarExcel excel;
@Setter private transient AvatarExcel excel;
private int avatarId; // Id of avatar
@Setter private int level;
@Setter private int exp;
@Setter private int promotion;
@Setter private int rank; // Eidolons
private AvatarRank rank; // Eidolons - We use an object for this so we can sync it easily with hero paths
private Map<Integer, Integer> skills;
private int currentHp;
private int currentSp;
private Map<Integer, Integer> skills;
private transient int entityId;
private transient Int2ObjectMap<GameItem> 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,15 +97,15 @@ 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();
}
@@ -112,6 +118,27 @@ public class GameAvatar implements GameEntity {
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
public GameItem getEquipBySlot(int slot) {
@@ -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();
}
}
}

View File

@@ -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<Integer, Integer> 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);
}
}

View File

@@ -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);
}
@@ -90,9 +96,12 @@ 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}

View File

@@ -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()));
}
}

View File

@@ -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()));
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}