diff --git a/src/main/java/emu/grasscutter/game/avatar/Avatar.java b/src/main/java/emu/grasscutter/game/avatar/Avatar.java index e516d7d47..a5c4466f9 100644 --- a/src/main/java/emu/grasscutter/game/avatar/Avatar.java +++ b/src/main/java/emu/grasscutter/game/avatar/Avatar.java @@ -44,6 +44,8 @@ import it.unimi.dsi.fastutil.ints.*; import java.util.*; import java.util.stream.Stream; import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -59,7 +61,7 @@ public class Avatar { @Indexed @Getter private int ownerId; // Id of player that this avatar belongs to @Transient private Player owner; @Transient @Getter private AvatarData avatarData; - @Transient @Getter private AvatarSkillDepotData skillDepot; + @Nullable @Transient @Getter private AvatarSkillDepotData skillDepot; @Transient @Getter private long guid; // Player unique id @Getter private int avatarId; // Id of avatar @Getter @Setter private int level = 1; @@ -123,6 +125,7 @@ public class Avatar { public Avatar(AvatarData data) { this(); + this.avatarId = data.getId(); this.nameCardRewardId = data.getNameCardRewardId(); this.nameCardId = data.getNameCardId(); @@ -135,19 +138,17 @@ public class Avatar { // Combat properties Stream.of(FightProperty.values()) - .map(FightProperty::getId) - .filter(id -> (id > 0) && (id < 3000)) - .forEach(id -> this.setFightProperty(id, 0f)); + .map(FightProperty::getId) + .filter(id -> (id > 0) && (id < 3000)) + .forEach(id -> this.setFightProperty(id, 0f)); - // Skill depot - this.setSkillDepotData( - switch (this.avatarId) { - case GameConstants.MAIN_CHARACTER_MALE -> GameData.getAvatarSkillDepotDataMap() - .get(504); // Hack to start with anemo skills - case GameConstants.MAIN_CHARACTER_FEMALE -> GameData.getAvatarSkillDepotDataMap() - .get(704); - default -> data.getSkillDepot(); - }); + this.setSkillDepotData(switch (this.getAvatarId()) { + case GameConstants.MAIN_CHARACTER_MALE -> + GameData.getAvatarSkillDepotDataMap().get(501); + case GameConstants.MAIN_CHARACTER_FEMALE -> + GameData.getAvatarSkillDepotDataMap().get(701); + default -> data.getSkillDepot(); + }); // Set stats this.recalcStats(); @@ -175,6 +176,16 @@ public class Avatar { return 0; } + /** + * @return True if the avatar is a main character. + */ + public boolean isMainCharacter() { + return List.of( + GameConstants.MAIN_CHARACTER_MALE, + GameConstants.MAIN_CHARACTER_FEMALE + ).contains(this.getAvatarId()); + } + public Player getPlayer() { return this.owner; } @@ -192,6 +203,11 @@ public class Avatar { this.owner = player; this.ownerId = player.getUid(); this.guid = player.getNextGameGuid(); + + if (this.isMainCharacter()) { + // Apply skill depot based on player resonance. + this.changeElement(player.getMainCharacterElement(), false); + } } public boolean addSatiation(int value) { @@ -273,14 +289,28 @@ public class Avatar { } /** - * Changes the avatar's element to the target element, if the character has values for it set in - * the candSkillDepot + * Changes the avatar's element to the target element. + * Only applies if the avatar has the element in its 'candSkillDepot's. * - * @param elementTypeToChange element to change to - * @return false if failed or already using that element, true if it actually changed + * @param newElement The new element to change to. + * @return True if the element was changed, false otherwise. */ - public boolean changeElement(@Nonnull ElementType elementTypeToChange) { - var candSkillDepotIdsList = this.avatarData.getCandSkillDepotIds(); + public boolean changeElement(@Nonnull ElementType newElement) { + return this.changeElement(newElement, true); + } + + /** + * Changes the avatar's element to the target element. + * Only applies if the avatar has the element in its 'candSkillDepot's. + * + * @param elementTypeToChange The new element to change to. + * @param notify Whether to notify the player of the change. + * @return True if the element was changed, false otherwise. + */ + public boolean changeElement(@Nonnull ElementType elementTypeToChange, boolean notify) { + if (elementTypeToChange == ElementType.None) return true; + + var candSkillDepotIdsList = this.getAvatarData().getCandSkillDepotIds(); var candSkillDepotIndex = elementTypeToChange.getDepotIndex(); // if no candidate skill to change or index out of bound @@ -297,7 +327,9 @@ public class Avatar { } // Set skill depot - setSkillDepotData(skillDepot, true); + this.setSkillDepotData(skillDepot, notify); + // Set element. + this.getPlayer().setMainCharacterElement(elementTypeToChange); return true; } @@ -383,17 +415,21 @@ public class Avatar { } public IntSet getTalentIdList() { // Returns a copy of the unlocked constellations for the current - // skillDepot. - var talents = new IntOpenHashSet(this.getSkillDepot().getTalents()); - talents.removeIf(id -> !this.talentIdList.contains(id)); - return talents; + if (this.getSkillDepot() != null) { + // skillDepot. + var talents = new IntOpenHashSet(this.getSkillDepot().getTalents()); + talents.removeIf(id -> !this.talentIdList.contains(id)); + return talents; + } else return new IntOpenHashSet(); } public int getCoreProudSkillLevel() { - var lockedTalents = new IntOpenHashSet(this.getSkillDepot().getTalents()); - lockedTalents.removeAll(this.getTalentIdList()); - // One below the lowest locked talent, or 6 if there are no locked talents. - return lockedTalents.intStream().map(i -> i % 10).min().orElse(7) - 1; + if (this.getSkillDepot() != null) { + var lockedTalents = new IntOpenHashSet(this.getSkillDepot().getTalents()); + lockedTalents.removeAll(this.getTalentIdList()); + // One below the lowest locked talent, or 6 if there are no locked talents. + return lockedTalents.intStream().map(i -> i % 10).min().orElse(7) - 1; + } else return 0; } public boolean equipItem(GameItem item, boolean shouldRecalc) { @@ -624,14 +660,16 @@ public class Avatar { AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(this.getSkillDepotId()); this.getProudSkillList().clear(); - for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) { - if (openData.getProudSkillGroupId() == 0) { - continue; - } - if (openData.getNeedAvatarPromoteLevel() <= this.getPromoteLevel()) { - int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1; - if (GameData.getProudSkillDataMap().containsKey(proudSkillId)) { - this.getProudSkillList().add(proudSkillId); + if (skillDepot != null) { + for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) { + if (openData.getProudSkillGroupId() == 0) { + continue; + } + if (openData.getNeedAvatarPromoteLevel() <= this.getPromoteLevel()) { + int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1; + if (GameData.getProudSkillDataMap().containsKey(proudSkillId)) { + this.getProudSkillList().add(proudSkillId); + } } } } diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 58a7a62bf..a34b9374a 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -40,10 +40,7 @@ import emu.grasscutter.game.managers.forging.ForgingManager; import emu.grasscutter.game.managers.mapmark.MapMark; import emu.grasscutter.game.managers.mapmark.MapMarksManager; import emu.grasscutter.game.managers.stamina.StaminaManager; -import emu.grasscutter.game.props.ActionReason; -import emu.grasscutter.game.props.ClimateType; -import emu.grasscutter.game.props.PlayerProperty; -import emu.grasscutter.game.props.WatcherTriggerType; +import emu.grasscutter.game.props.*; import emu.grasscutter.game.quest.QuestManager; import emu.grasscutter.game.quest.enums.QuestCond; import emu.grasscutter.game.quest.enums.QuestContent; @@ -64,7 +61,6 @@ import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture; import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; -import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason; import emu.grasscutter.scripts.data.SceneRegion; import emu.grasscutter.server.event.player.PlayerJoinEvent; import emu.grasscutter.server.event.player.PlayerQuitEvent; @@ -88,7 +84,6 @@ import java.time.ZoneId; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.LinkedBlockingQueue; -import java.util.stream.Stream; import static emu.grasscutter.config.Configuration.GAME_OPTIONS; @@ -213,6 +208,8 @@ public class Player { @Getter private PlayerProgress playerProgress; @Getter private Set activeQuestTimers; + @Getter @Setter private ElementType mainCharacterElement = ElementType.None; + @Deprecated @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only! public Player() { @@ -291,6 +288,7 @@ public class Player { // On player creation public Player(GameSession session) { this(); + this.account = session.getAccount(); this.accountId = this.getAccount().getId(); this.session = session; @@ -300,6 +298,10 @@ public class Player { this.birthday = new PlayerBirthday(); this.codex = new PlayerCodex(this); + this.applyProperties(); + this.getFlyCloakList().add(140001); + this.getNameCardList().add(210001); + this.messageHandler = null; this.mapMarksManager = new MapMarksManager(this); this.staminaManager = new StaminaManager(this); @@ -313,10 +315,6 @@ public class Player { this.cookingManager = new CookingManager(this); this.cookingCompoundManager = new CookingCompoundManager(this); this.satiationManager = new SatiationManager(this); - - this.applyProperties(); - this.getFlyCloakList().add(140001); - this.getNameCardList().add(210001); } /** @@ -537,10 +535,9 @@ public class Player { * @param defaultValue The value to apply if the property doesn't exist. */ private void setOrFetch(PlayerProperty property, int defaultValue) { - if (!this.properties.containsKey(property.getId())) { - this.setProperty(property, defaultValue, false); - this.save(); - } + this.setProperty(property, + this.properties.containsKey(property.getId()) ? + this.getProperty(property) : defaultValue, false); } public int getPrimogems() { @@ -864,80 +861,6 @@ public class Player { addAvatar(new Avatar(avatarId), true); } - public List getTrialAvatarParam (int trialAvatarId) { - if (GameData.getTrialAvatarCustomData().isEmpty()) { // use default data if custom data not available - if (GameData.getTrialAvatarDataMap().get(trialAvatarId) == null) return List.of(); - - return GameData.getTrialAvatarDataMap().get(trialAvatarId) - .getTrialAvatarParamList(); - } - // use custom data - if (GameData.getTrialAvatarCustomData().get(trialAvatarId) == null) return List.of(); - - var trialCustomParams = GameData.getTrialAvatarCustomData().get(trialAvatarId).getTrialAvatarParamList(); - return trialCustomParams.isEmpty() ? List.of() : Stream.of(trialCustomParams.get(0).split(";")).map(Integer::parseInt).toList(); - } - - public boolean addTrialAvatar(int trialAvatarId, GrantReason reason, int questMainId){ - List trialAvatarBasicParam = getTrialAvatarParam(trialAvatarId); - if (trialAvatarBasicParam.isEmpty()) return false; - - Avatar avatar = new Avatar(trialAvatarBasicParam.get(0)); - if (avatar.getAvatarData() == null || !hasSentLoginPackets()) return false; - - avatar.setOwner(this); - // Add trial weapons and relics - avatar.setTrialAvatarInfo(trialAvatarBasicParam.get(1), trialAvatarId, reason, questMainId); - avatar.equipTrialItems(); - // Recalc stats - avatar.recalcStats(); - - // Packet, mimic official server behaviour, add to player's bag but not saving to db - sendPacket(new PacketAvatarAddNotify(avatar, false)); - // add to avatar to temporary trial team - getTeamManager().addAvatarToTrialTeam(avatar); - return true; - } - - public boolean addTrialAvatarForQuest(int trialAvatarId, int questMainId) { - // TODO: Find method for 'setupTrialAvatarTeamForQuest'. - getTeamManager().setupTrialAvatars(true); - if (!addTrialAvatar( - trialAvatarId, - GrantReason.GRANT_REASON_BY_QUEST, - questMainId)) return false; - getTeamManager().trialAvatarTeamPostUpdate(); - // Packet, mimic official server behaviour, neccessary to stop player from modifying team - sendPacket(new PacketAvatarTeamUpdateNotify(this)); - return true; - } - - public void addTrialAvatarsForActivity(List trialAvatarIds) { - getTeamManager().setupTrialAvatars(false); - trialAvatarIds.forEach(trialAvatarId -> addTrialAvatar( - trialAvatarId, - GrantReason.GRANT_REASON_BY_TRIAL_AVATAR_ACTIVITY, - 0)); - getTeamManager().trialAvatarTeamPostUpdate(0); - } - - public boolean removeTrialAvatarForQuest(int trialAvatarId) { - if (!getTeamManager().isUsingTrialTeam()) return false; - - sendPacket(new PacketAvatarDelNotify(List.of(getTeamManager().getTrialAvatarGuid(trialAvatarId)))); - getTeamManager().removeTrialAvatarTeam(trialAvatarId); - sendPacket(new PacketAvatarTeamUpdateNotify()); - return true; - } - - public void removeTrialAvatarForActivity() { - if (!getTeamManager().isUsingTrialTeam()) return; - - sendPacket(new PacketAvatarDelNotify(getTeamManager().getActiveTeam().stream() - .map(x -> x.getAvatar().getGuid()).toList())); - getTeamManager().removeTrialAvatarTeam(); - } - public void addFlycloak(int flycloakId) { this.getFlyCloakList().add(flycloakId); this.sendPacket(new PacketAvatarGainFlycloakNotify(flycloakId));