Refactored Trial Avatar relevant code (#68)

* Moved trial avatar to a separate class for easier management

* Mainly refactored team manager.
Moved the response packets to their handler so that the functions in team manager is more reusable.

* Refactored adding and removing TrialAvatar for dungeons/activity. Have the TrialAvatars added through their corresponding handler if not specifically called by enter dungeon request.

* Added interfaces for both original excels and custom data, so that it can perform polymorphic calls.

* Added implementation for Element trial dungeon. It is impossible to complete though. Since challenges are practically broken

* Moved around some stuff so that the addition and removal of the trial avatars for quest and dungeons work well.
This commit is contained in:
mjolsic 2023-06-22 22:19:40 +10:00 committed by GitHub
parent 151a325506
commit 72874edddc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 797 additions and 899 deletions

View File

@ -28,26 +28,26 @@ public final class TeamCommand implements CommandHandler {
}
switch (args.get(0)) {
case "add":
case "add" -> {
if (!addCommand(sender, targetPlayer, args)) return;
break;
case "remove":
}
case "remove" -> {
if (!removeCommand(sender, targetPlayer, args)) return;
break;
case "set":
}
case "set" -> {
if (!setCommand(sender, targetPlayer, args)) return;
break;
default:
}
default -> {
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage");
sendUsageMessage(sender);
return;
}
}
targetPlayer.getTeamManager().updateTeamEntities(
new PacketChangeMpTeamAvatarRsp(targetPlayer, targetPlayer.getTeamManager().getCurrentTeamInfo()));
targetPlayer.getTeamManager().updateTeamEntities(true);
// TODO might not be wise since no request is sent
targetPlayer.sendPacket(new PacketChangeMpTeamAvatarRsp(targetPlayer, targetPlayer.getTeamManager().getCurrentTeamInfo()));
}
private boolean addCommand(Player sender, Player targetPlayer, List<String> args) {
@ -112,7 +112,6 @@ public final class TeamCommand implements CommandHandler {
indexes.add(currentTeamAvatars.get(avatarIndex - 1));
} catch (Exception e) {
ignoreList.add(avatarIndex);
continue;
}
}
}

View File

@ -97,6 +97,7 @@ public class GameData {
@Getter private static final Int2ObjectMap<CompoundData> compoundDataMap=new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<DungeonElementChallengeData> dungeonElementChallengeDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<DungeonEntryData> dungeonEntryDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<EnvAnimalGatherConfigData> envAnimalGatherConfigDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<EquipAffixData> equipAffixDataMap = new Int2ObjectOpenHashMap<>();

View File

@ -712,7 +712,7 @@ public class ResourceLoader {
private static void loadTrialAvatarCustomData() {
try {
String pathName = "CustomResources/TrialAvatarExcels/";
String pathName = "TrialAvatar/";
try {
JsonUtils.loadToList(
getResourcePath(pathName + "TrialAvatarActivityDataExcelConfigData.json"),
@ -735,9 +735,8 @@ public class ResourceLoader {
Grasscutter.getLogger().debug("Loaded trial activity schedule custom data.");
try {
JsonUtils.loadToList(
getResourcePath(pathName + "TrialAvatarData.json"),
getResourcePath(pathName + "TrialAvatarCustomConfigData.json"),
TrialAvatarCustomData.class).forEach(instance -> {
instance.onLoad();
GameData.getTrialAvatarCustomData()
.put(instance.getTrialAvatarId(), instance);
});

View File

@ -0,0 +1,9 @@
package emu.grasscutter.data.common;
import java.util.List;
public interface BaseTrialAvatarData {
List<Integer> getTrialAvatarParamList();
List<Integer> getTrialAvatarWeaponList();
int getTrialSkillDepotId();
}

View File

@ -0,0 +1,8 @@
package emu.grasscutter.data.common;
import java.util.List;
public interface BaseTrialAvatarTemplateData {
int getTrialAvatarSkillLevel();
List<Integer> getTrialReliquaryList();
}

View File

@ -1,16 +1,16 @@
package emu.grasscutter.data.custom;
import emu.grasscutter.data.common.BaseTrialAvatarData;
import emu.grasscutter.data.common.BaseTrialAvatarTemplateData;
import lombok.*;
import java.util.List;
@Data
public class TrialAvatarCustomData {
public class TrialAvatarCustomData implements BaseTrialAvatarData, BaseTrialAvatarTemplateData {
private int trialAvatarId;
private List<String> trialAvatarParamList;
private int coreProudSkillLevel;
private int skillDepotId;
public void onLoad() {
this.trialAvatarParamList = trialAvatarParamList.stream().filter(x -> !x.isBlank()).toList();
}
private List<Integer> trialAvatarParamList;
private List<Integer> trialAvatarWeaponList;
private List<Integer> trialReliquaryList;
private int trialSkillDepotId;
private int trialAvatarSkillLevel;
}

View File

@ -0,0 +1,21 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@EqualsAndHashCode(callSuper = false)
@Data
@ResourceType(name = "DungeonElementChallengeExcelConfigData.json")
public class DungeonElementChallengeData extends GameResource {
private int dungeonId;
private List<Integer> trialAvatarId;
private int tutorialId;
public int getId() {
return dungeonId;
}
}

View File

@ -2,18 +2,25 @@ package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.BaseTrialAvatarData;
import lombok.*;
import java.util.List;
@ResourceType(name = "TrialAvatarExcelConfigData.json")
@EqualsAndHashCode(callSuper=false)
@Data
public class TrialAvatarData extends GameResource {
public class TrialAvatarData extends GameResource implements BaseTrialAvatarData {
private int trialAvatarId;
private List<Integer> trialAvatarParamList;
private int trialSkillDepotId;
@Override
public int getId() {
return trialAvatarId;
}
@Override
public List<Integer> getTrialAvatarWeaponList() {
return List.of();
}
}

View File

@ -2,13 +2,14 @@ package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.BaseTrialAvatarTemplateData;
import lombok.*;
import java.util.List;
@ResourceType(name = "TrialAvatarTemplateExcelConfigData.json")
@ResourceType(name = "TrialAvatarTemplateExcelConfigData.json")
@EqualsAndHashCode(callSuper=false)
@Data
public class TrialAvatarTemplateData extends GameResource {
public class TrialAvatarTemplateData extends GameResource implements BaseTrialAvatarTemplateData {
private int TrialAvatarLevel;
private List<Integer> TrialReliquaryList;
private int TrialAvatarSkillLevel;

View File

@ -6,7 +6,6 @@ import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.game.activity.ActivityWatcher;
import emu.grasscutter.game.activity.DefaultWatcher;
import emu.grasscutter.game.dungeons.DungeonTrialTeam;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.WatcherTriggerType;
@ -23,41 +22,36 @@ import emu.grasscutter.utils.JsonUtils;
import java.util.*;
import java.util.stream.*;
import lombok.*;
import org.jetbrains.annotations.NotNull;
@GameActivity(ActivityType.NEW_ACTIVITY_TRIAL_AVATAR)
public class TrialAvatarActivityHandler extends ActivityHandler {
@Getter @Setter private int selectedTrialAvatarIndex;
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
public void onInitPlayerActivityData(@NotNull PlayerActivityData playerActivityData) {
TrialAvatarPlayerData trialAvatarPlayerData = TrialAvatarPlayerData.create(getActivityConfigItem().getScheduleId());
playerActivityData.setDetail(trialAvatarPlayerData);
}
@Override
public void onProtoBuild(PlayerActivityData playerActivityData, ActivityInfo.Builder activityInfo) {
public void onProtoBuild(PlayerActivityData playerActivityData, @NotNull ActivityInfo.Builder activityInfo) {
TrialAvatarPlayerData trialAvatarPlayerData = getTrialAvatarPlayerData(playerActivityData);
activityInfo.setTrialAvatarInfo(trialAvatarPlayerData.toProto());
}
@Override
public void initWatchers(Map<WatcherTriggerType, ConstructorAccess<?>> activityWatcherTypeMap) {
public void initWatchers(@NotNull Map<WatcherTriggerType, ConstructorAccess<?>> activityWatcherTypeMap) {
var watcherType = activityWatcherTypeMap.get(WatcherTriggerType.TRIGGER_FINISH_CHALLENGE);
ActivityWatcher watcher;
if(watcherType != null){
watcher = (ActivityWatcher) watcherType.newInstance();
}else{
watcher = new DefaultWatcher();
}
ActivityWatcher watcher = watcherType == null ? new DefaultWatcher() : (ActivityWatcher) watcherType.newInstance();
watcher.setActivityHandler(this);
getWatchersMap().computeIfAbsent(WatcherTriggerType.TRIGGER_FINISH_CHALLENGE, k -> new ArrayList<>());
getWatchersMap().get(WatcherTriggerType.TRIGGER_FINISH_CHALLENGE).add(watcher);
}
public TrialAvatarPlayerData getTrialAvatarPlayerData(PlayerActivityData playerActivityData) {
public TrialAvatarPlayerData getTrialAvatarPlayerData(@NotNull PlayerActivityData playerActivityData) {
if (playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()) {
onInitPlayerActivityData(playerActivityData);
playerActivityData.save();
@ -68,49 +62,37 @@ public class TrialAvatarActivityHandler extends ActivityHandler {
public int getTrialActivityDungeonId(int trialAvatarIndexId) {
val data = GameData.getTrialAvatarActivityDataByAvatarIndex(trialAvatarIndexId);
return data!=null ? data.getDungeonId() : -1;
return data != null ? data.getDungeonId() : -1;
}
public List<String> getTriggerParamList() {
val data = GameData.getTrialAvatarActivityDataByAvatarIndex(getSelectedTrialAvatarIndex());
return data!=null ? data.getTriggerConfig().getParamList() : Collections.emptyList();
return data != null ? data.getTriggerConfig().getParamList() : Collections.emptyList();
}
public boolean enterTrialDungeon(Player player, int trialAvatarIndexId, int enterPointId) {
public boolean enterTrialDungeon(@NotNull Player player, int trialAvatarIndexId, int enterPointId) {
// TODO, not sure if this will cause problem in MP, since we are entering trial activity dungeon
player.sendPacket(new PacketScenePlayerLocationNotify(player.getScene())); // official does send this
if (!player.getServer().getDungeonSystem().enterDungeon(
player,
enterPointId,
getTrialActivityDungeonId(trialAvatarIndexId))) return false;
player,
enterPointId,
getTrialActivityDungeonId(trialAvatarIndexId))) return false;
setSelectedTrialAvatarIndex(trialAvatarIndexId);
return true;
}
public List<Integer> getBattleAvatarsList() {
val activityData = GameData.getTrialAvatarActivityDataByAvatarIndex(getSelectedTrialAvatarIndex());
if (activityData == null || activityData.getBattleAvatarsList().isBlank()) return List.of();
return Stream.of(activityData.getBattleAvatarsList().split(",")).map(Integer::parseInt).toList();
}
public DungeonTrialTeam getTrialAvatarDungeonTeam(){
List<Integer> battleAvatarsList = getBattleAvatarsList();
if (battleAvatarsList.isEmpty()) return null;
return new DungeonTrialTeam(battleAvatarsList, GrantReason.GRANT_REASON_BY_TRIAL_AVATAR_ACTIVITY);
}
public void unsetTrialAvatarTeam(Player player) {
if (getSelectedTrialAvatarIndex() <= 0) return;
player.removeTrialAvatarForActivity();
setSelectedTrialAvatarIndex(0);
}
public boolean getReward(Player player, int trialAvatarIndexId) {
val playerActivityData = player.getActivityManager().getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_TRIAL_AVATAR);
public boolean getReward(@NotNull Player player, int trialAvatarIndexId) {
val playerActivityData = player.getActivityManager()
.getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_TRIAL_AVATAR);
if(playerActivityData.isEmpty()){
return false;

View File

@ -34,9 +34,6 @@ import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass.ShowAvatarInfo;
import emu.grasscutter.net.proto.ShowEquipOuterClass.ShowEquip;
import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.*;
@ -89,15 +86,6 @@ public class Avatar {
@Getter @Setter private int nameCardRewardId;
@Getter @Setter private int nameCardId;
// trial avatar property
@Getter @Setter private int trialAvatarId = 0;
// cannot store to db if grant reason is not integer
@Getter @Setter private int grantReason = GrantReason.GRANT_REASON_INVALID.getNumber();
@Getter @Setter private int fromParentQuestId = 0;
// so far no outer class or prop value has information of this, but from packet i sniff
// 1 = normal, 2 = trial avatar
@Getter @Setter private int avatarType = 1;
@Deprecated // Do not use. Morhpia only!
public Avatar() {
this.equips = new Int2ObjectOpenHashMap<>();
@ -841,11 +829,11 @@ public class Avatar {
}
public void save() {
if (getTrialAvatarId() > 0) return; // dont save trial avatar
if (this instanceof TrialAvatar) return;
DatabaseHelper.saveAvatar(this);
}
public AvatarInfo toProto() {
public AvatarInfo.Builder protoBuilder() {
int fetterLevel = this.getFetterLevel();
AvatarFetterInfo.Builder avatarFetter = AvatarFetterInfo.newBuilder()
.setExpLevel(fetterLevel);
@ -873,17 +861,15 @@ public class Avatar {
.putAllSkillLevelMap(this.getSkillLevelMap())
.addAllInherentProudSkillList(this.getProudSkillList())
.putAllProudSkillExtraLevelMap(getProudSkillBonusMap())
.setAvatarType(this.getAvatarType())
.setAvatarType(1)
.setBornTime(this.getBornTime())
.setWearingFlycloakId(this.getFlyCloak())
.setCostumeId(this.getCostume())
.setIsFocus(false);
if (this.getAvatarType() == 1){
avatarInfo.setFetterInfo(avatarFetter);
if (this.getPlayer().getNameCardList().contains(this.getNameCardId())) {
avatarFetter.addRewardedFetterLevelList(10);
}
avatarInfo.setFetterInfo(avatarFetter);
if (this.getPlayer().getNameCardList().contains(this.getNameCardId())) {
avatarFetter.addRewardedFetterLevelList(10);
}
this.getSkillExtraChargeMap().forEach((skillId, count) ->
@ -897,8 +883,11 @@ public class Avatar {
avatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, 0));
avatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_PENALTY_TIME, 0));
avatarInfo.setTrialAvatarInfo(trialAvatarInfoProto());
return avatarInfo.build();
return avatarInfo;
}
public AvatarInfo toProto() {
return this.protoBuilder().build();
}
// used only in character showcase
@ -941,129 +930,6 @@ public class Avatar {
return showAvatarInfo.build();
}
public void setTrialAvatarInfo(int avatarLevel, int trialAvatarId, GrantReason grantReason, int fromParentQuestId){
this.setLevel(avatarLevel);
this.setPromoteLevel(getMinPromoteLevel(avatarLevel));
this.setTrialAvatarId(trialAvatarId);
this.setGrantReason(grantReason.getNumber());
this.setFromParentQuestId(fromParentQuestId);
this.setAvatarType(2);
this.setTrialSkillLevel();
this.setTrialItems();
}
private int getTrialAvatarTemplateLevel(){
return getLevel() <= 9 ? 1 :
(int) (Math.floor(getLevel() / 10f) * 10); // round trial level to fit template levels
}
public int getTrialSkillLevel() {
if (GameData.getTrialAvatarCustomData().isEmpty()) { // use default data if custom data not available
int trialAvatarTemplateLevel = getTrialAvatarTemplateLevel(); // round trial level to fit template levels
TrialAvatarTemplateData templateData = GameData.getTrialAvatarTemplateDataMap().get(trialAvatarTemplateLevel);
return templateData == null ? 1 : templateData.getTrialAvatarSkillLevel();
}
if (GameData.getTrialAvatarCustomData().get(getTrialAvatarId()) == null) return 1;
return GameData.getTrialAvatarCustomData().get(getTrialAvatarId()).getCoreProudSkillLevel(); // enhanced version of weapon
}
public void setTrialSkillLevel() {
getSkillLevelMap().keySet().stream().forEach(skill -> setSkillLevel(skill, getTrialSkillLevel()));
}
public int getTrialWeaponId() {
if (GameData.getTrialAvatarCustomData().isEmpty()) { // use default data if custom data not available
if (GameData.getTrialAvatarDataMap().get(getTrialAvatarId()) == null)
return getAvatarData().getInitialWeapon();
return GameData.getItemDataMap().get(getAvatarData().getInitialWeapon()+100) == null ?
getAvatarData().getInitialWeapon() :
getAvatarData().getInitialWeapon()+100; // enhanced version of weapon
}
// use custom data
if (GameData.getTrialAvatarCustomData().get(getTrialAvatarId()) == null) return 0;
val trialCustomParams = GameData.getTrialAvatarCustomData().get(trialAvatarId).getTrialAvatarParamList();
return trialCustomParams.size() < 2 ? getAvatarData().getInitialWeapon() :
Integer.parseInt(trialCustomParams.get(1).split(";")[0]);
}
public List<Integer> getTrialReliquary() {
if (!GameData.getTrialAvatarCustomData().isEmpty()) {
// try using custom data
if (GameData.getTrialAvatarCustomData().get(getTrialAvatarId()) != null) {
val trialCustomParams = GameData.getTrialAvatarCustomData().get(getTrialAvatarId()).getTrialAvatarParamList();
if (trialCustomParams.size() > 2) {
return Stream.of(trialCustomParams.get(2).split(";")).map(Integer::parseInt).toList();
}
}
}
int trialAvatarTemplateLevel = getTrialAvatarTemplateLevel();
TrialAvatarTemplateData templateData = GameData.getTrialAvatarTemplateDataMap().get(trialAvatarTemplateLevel);
return templateData == null ? List.of() : templateData.getTrialReliquaryList();
}
public void setTrialItems(){
// add enhanced verion of trial weapon
GameItem weapon = new GameItem(getTrialWeaponId());
weapon.setLevel(getLevel());
weapon.setExp(0);
weapon.setPromoteLevel(getMinPromoteLevel(getLevel()));
getEquips().put(weapon.getEquipSlot(), weapon);
// add Trial Artifacts
getTrialReliquary().forEach(id -> {
TrialReliquaryData reliquaryData = GameData.getTrialReliquaryDataMap().get(id);
if (reliquaryData == null) return;
GameItem relic = new GameItem(reliquaryData.getReliquaryId());
relic.setLevel(reliquaryData.getLevel());
relic.setMainPropId(reliquaryData.getMainPropId());
relic.getAppendPropIdList().addAll(reliquaryData.getAppendPropList());
getEquips().put(relic.getEquipSlot(), relic);
});
// add costume if any (ambor, rosiaria, mona, Jean)
GameData.getAvatarCostumeDataItemIdMap().values().stream().forEach(costumeData -> {
if (costumeData.getCharacterId() != getAvatarId()) return;
setCostume(costumeData.getId());
});
}
public void equipTrialItems(){
getEquips().forEach((itemEquipTypeValues, item) -> {
item.setEquipCharacter(getAvatarId());
item.setOwner(getPlayer());
if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON && getPlayer().getWorld() != null) {
item.setWeaponEntityId(this.getPlayer().getWorld().getNextEntityId(EntityIdType.WEAPON));
getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item));
}
});
}
public TrialAvatarInfo trialAvatarInfoProto(){
TrialAvatarInfo.Builder trialAvatar = TrialAvatarInfo.newBuilder()
.setTrialAvatarId(this.getTrialAvatarId())
.setGrantRecord(TrialAvatarGrantRecord.newBuilder()
.setGrantReason(this.getGrantReason())
.setFromParentQuestId(this.getFromParentQuestId()));
if (this.getTrialAvatarId() > 0){ // if it is actual trial avatar
// add artifacts or wepon for trial character
int itemCount = 0;
for (GameItem item : this.getEquips().values()) {
trialAvatar.addTrialEquipList(itemCount, item.toProto());
itemCount++;
}
}
return trialAvatar.build();
}
@PostLoad
private void onLoad() {

View File

@ -17,10 +17,11 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import lombok.Getter;
public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar> {
private final Int2ObjectMap<Avatar> avatars;
private final Long2ObjectMap<Avatar> avatarsGuid;
@Getter private final Long2ObjectMap<Avatar> avatarsGuid;
public AvatarStorage(Player player) {
super(player);
@ -49,7 +50,12 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar>
}
public boolean addAvatar(Avatar avatar) {
if (avatar.getAvatarData() == null || this.hasAvatar(avatar.getAvatarId())) {
// Only both avatar id and guid existed would not pass the key
// i.e. both id and guid existed = normal character in bag
// id existed but not guid (before adding) = trial avatars
if (avatar.getAvatarData() == null ||
(this.hasAvatar(avatar.getAvatarId())
&& this.getAvatarsGuid().containsKey(avatar.getGuid()))) {
return false;
}
@ -57,7 +63,11 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar>
avatar.setOwner(getPlayer());
// Put into maps
this.avatars.put(avatar.getAvatarId(), avatar);
if (!(avatar instanceof TrialAvatar)) {
// Don't need to replace with new avatar using
// avatar id if it is trial avatars
this.avatars.put(avatar.getAvatarId(), avatar);
}
this.avatarsGuid.put(avatar.getGuid(), avatar);
avatar.save();
@ -65,6 +75,15 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar>
return true;
}
public boolean removeAvatarByGuid(long guid) {
// the only ones removable are trial avatars, which only guid are used in adding
if (this.getAvatarsGuid().containsKey(guid)) {
return false;
}
this.avatarsGuid.remove(guid);
return true;
}
public void addStartingWeapon(Avatar avatar) {
// Make sure avatar owner is this player
if (avatar.getPlayer() != this.getPlayer()) {

View File

@ -0,0 +1,154 @@
package emu.grasscutter.game.avatar;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.BaseTrialAvatarData;
import emu.grasscutter.data.common.BaseTrialAvatarTemplateData;
import emu.grasscutter.data.excels.AvatarCostumeData;
import emu.grasscutter.data.excels.TrialReliquaryData;
import emu.grasscutter.game.inventory.EquipType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.net.proto.AvatarInfoOuterClass;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason;
import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo;
import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class TrialAvatar extends Avatar{
// trial avatar property
@Getter @Setter private int trialAvatarId = 0;
// cannot store to db if grant reason is not integer
@Getter @Setter private int grantReason = GrantReason.GRANT_REASON_INVALID.getNumber();
@Getter @Setter private int fromParentQuestId = 0;
// so far no outer class or prop value has information of this, but from packet I sniff
// 1 = normal, 2 = trial avatar
@Getter private final int avatarType = 2;
public TrialAvatar(@NotNull List<Integer> trialAvatarParam, int trialAvatarId, @NotNull GrantReason grantReason, int fromParentQuestId) {
super(trialAvatarParam.get(0));
this.setLevel(trialAvatarParam.get(1));
this.setPromoteLevel(getMinPromoteLevel(trialAvatarParam.get(1)));
this.setTrialAvatarId(trialAvatarId);
this.setGrantReason(grantReason.getNumber());
this.setFromParentQuestId(fromParentQuestId);
this.setTrialSkillLevel();
this.setTrialItems();
}
public static boolean useCustomData() {
return !(GameData.getTrialAvatarCustomData() == null || GameData.getTrialAvatarCustomData().isEmpty());
}
public static List<Integer> getTrialAvatarParam(int trialAvatarId) {
val trialData = TrialAvatar.useCustomData() ? GameData.getTrialAvatarCustomData() : GameData.getTrialAvatarDataMap();
BaseTrialAvatarData trialAvatarData = trialData.get(trialAvatarId);
return (trialAvatarData == null) ? List.of() : trialAvatarData.getTrialAvatarParamList();
}
private int getTrialAvatarTemplateLevel(){
return GameData.getTrialAvatarTemplateDataMap().keySet().stream()
.min(Comparator.comparingInt(value -> Math.abs(value - getLevel())))
.stream().findFirst().orElse(0);
}
public int getTrialSkillLevel() {
val trialData = useCustomData() ? GameData.getTrialAvatarCustomData() : GameData.getTrialAvatarTemplateDataMap();
int skillOrId = useCustomData() ? getTrialAvatarId() : getTrialAvatarTemplateLevel();
BaseTrialAvatarTemplateData trialAvatarData = trialData.get(skillOrId);
return (trialAvatarData == null) ? 1 : trialAvatarData.getTrialAvatarSkillLevel();
}
public void setTrialSkillLevel() {
getSkillLevelMap().keySet().forEach(skill -> setSkillLevel(skill, getTrialSkillLevel()));
}
public int getTrialWeaponId() {
val trialData = useCustomData() ? GameData.getTrialAvatarCustomData() : GameData.getTrialAvatarDataMap();
BaseTrialAvatarData trialAvatarData = trialData.get(getTrialAvatarId());
return (trialAvatarData == null || trialAvatarData.getTrialAvatarWeaponList().size() < 1) ?
getAvatarData().getInitialWeapon() + 100 : trialAvatarData.getTrialAvatarWeaponList().get(0);
}
public List<Integer> getTrialReliquary() {
val trialData = useCustomData() ? GameData.getTrialAvatarCustomData() : GameData.getTrialAvatarTemplateDataMap();
int skillOrId = useCustomData() ? getTrialAvatarId() : getTrialAvatarTemplateLevel();
BaseTrialAvatarTemplateData trialAvatarData = trialData.get(skillOrId);
return (trialAvatarData == null || trialAvatarData.getTrialReliquaryList().isEmpty()) ?
GameData.getTrialAvatarTemplateDataMap().get(getTrialAvatarTemplateLevel()).getTrialReliquaryList() :
trialAvatarData.getTrialReliquaryList();
}
public void setTrialItems(){
// add enhanced version of trial weapon
GameItem weapon = new GameItem(getTrialWeaponId());
weapon.setLevel(getLevel());
weapon.setExp(0);
Grasscutter.getLogger().info("Min promote level: {}", getMinPromoteLevel(getLevel()));
weapon.setPromoteLevel(getMinPromoteLevel(getLevel()));
getEquips().put(weapon.getEquipSlot(), weapon);
// add Trial Artifacts
getTrialReliquary().forEach(id -> {
TrialReliquaryData reliquaryData = GameData.getTrialReliquaryDataMap().get(id.intValue());
if (reliquaryData == null) return;
GameItem relic = new GameItem(reliquaryData.getReliquaryId());
relic.setLevel(reliquaryData.getLevel());
relic.setMainPropId(reliquaryData.getMainPropId());
relic.getAppendPropIdList().addAll(reliquaryData.getAppendPropList());
getEquips().put(relic.getEquipSlot(), relic);
});
// add costume if any (Amber, Rosaria, Mona, Jean)
this.setCostume(GameData.getAvatarCostumeDataItemIdMap().values()
.stream().map(AvatarCostumeData::getCharacterId)
.filter(characterId -> characterId == this.getAvatarId())
.findAny().orElse(0));
}
public void equipTrialItems(){
getEquips().forEach((itemEquipTypeValues, item) -> {
item.setEquipCharacter(getAvatarId());
item.setOwner(getPlayer());
if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON && getPlayer().getWorld() != null) {
item.setWeaponEntityId(this.getPlayer().getWorld().getNextEntityId(EntityIdType.WEAPON));
getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item));
}
});
}
public TrialAvatarInfo trialAvatarInfoProto(){
TrialAvatarInfo.Builder trialAvatar = TrialAvatarInfo.newBuilder()
.setTrialAvatarId(this.getTrialAvatarId())
.setGrantRecord(TrialAvatarGrantRecord.newBuilder()
.setGrantReason(this.getGrantReason())
.setFromParentQuestId(this.getFromParentQuestId()));
if (this.getTrialAvatarId() > 0){ // if it is actual trial avatar
// add artifacts and weapon for trial character
AtomicInteger itemCount = new AtomicInteger();
this.getEquips().values().forEach(item ->
trialAvatar.addTrialEquipList(itemCount.getAndIncrement(), item.toProto()));
}
return trialAvatar.build();
}
@Override
public AvatarInfoOuterClass.AvatarInfo.Builder protoBuilder() {
return super.protoBuilder()
.setAvatarType(getAvatarType())
.setTrialAvatarInfo(this.trialAvatarInfoProto());
}
}

View File

@ -16,6 +16,7 @@ import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketDungeonWayPointNotify;
@ -52,8 +53,6 @@ public class DungeonManager {
private int newestWayPoint = 0;
@Getter private int startSceneTime = 0;
DungeonTrialTeam trialTeam = null;
public DungeonManager(@NonNull Scene scene, @NonNull DungeonData dungeonData) {
this.scene = scene;
this.dungeonData = dungeonData;
@ -229,32 +228,31 @@ public class DungeonManager {
if (getDungeonData() == null) return;
switch (getDungeonData().getType()) {
// case DUNGEON_PLOT is handled by quest execs
// case DUNGEON_PLOT is handled by quest execs
case DUNGEON_ACTIVITY -> {
switch (getDungeonData().getPlayType()) {
case DUNGEON_PLAY_TYPE_TRIAL_AVATAR -> {
val activityHandler = player.getActivityManager()
.getActivityHandlerAs(ActivityType.NEW_ACTIVITY_TRIAL_AVATAR, TrialAvatarActivityHandler.class);
activityHandler.ifPresent(trialAvatarActivityHandler ->
this.trialTeam = trialAvatarActivityHandler.getTrialAvatarDungeonTeam());
player.getActivityManager().getActivityHandlerAs(
ActivityType.NEW_ACTIVITY_TRIAL_AVATAR, TrialAvatarActivityHandler.class)
.ifPresent(handler -> player.addTrialAvatarsForDungeon(
handler.getBattleAvatarsList(), GrantReason.GRANT_REASON_BY_TRIAL_AVATAR_ACTIVITY));
}
case DUNGEON_PLAY_TYPE_MIST_TRIAL -> {} // TODO
}
}
case DUNGEON_ELEMENT_CHALLENGE -> {} // TODO
case DUNGEON_ELEMENT_CHALLENGE -> {
Optional.ofNullable(GameData.getDungeonElementChallengeDataMap().get(getDungeonData().getId()))
.ifPresent(elementDungeonData -> {
player.addTrialAvatarsForDungeon(elementDungeonData.getTrialAvatarId(),
GrantReason.GRANT_REASON_BY_DUNGEON_ELEMENT_CHALLENGE);
});
}
}
if(this.trialTeam != null) {
player.addTrialAvatarsForActivity(trialTeam.trialAvatarIds);
if (player.getTeamManager().isUseTrialTeam()){
player.getTeamManager().updateTeamEntities(false);
}
}
public void unsetTrialTeam(Player player){
if(this.trialTeam==null){
return;
}
player.removeTrialAvatarForActivity();
this.trialTeam = null;
}
public void startDungeon() {
this.startSceneTime = scene.getSceneTimeSeconds();
scene.getPlayers().forEach(p -> {

View File

@ -147,14 +147,11 @@ public class DungeonSystem extends BaseGameSystem {
if(!dungeonManager.isFinishedSuccessfully()){
dungeonManager.quitDungeon();
}
dungeonManager.unsetTrialTeam(player);
}
// clean temp team if it has
player.getTeamManager().cleanTemporaryTeam();
player.getTowerManager().clearEntry();
// Transfer player back to world
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
}

View File

@ -1,14 +0,0 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@AllArgsConstructor
public class DungeonTrialTeam {
List<Integer> trialAvatarIds;
TrialAvatarGrantRecord.GrantReason grantReason;
}

View File

@ -54,12 +54,13 @@ public class EntityAvatar extends GameEntity {
super(scene);
this.avatar = avatar;
this.avatar.setCurrentEnergy();
if (scene != null)
if (scene != null) {
this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR);
GameItem weapon = this.getAvatar().getWeapon();
if (weapon != null) {
weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON));
GameItem weapon = this.getAvatar().getWeapon();
if (weapon != null) {
weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON));
}
}
}

View File

@ -4,7 +4,6 @@ import dev.morphia.annotations.*;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityData;
import emu.grasscutter.data.binout.config.ConfigLevelEntity;
import emu.grasscutter.data.binout.config.fields.ConfigAbilityData;
import emu.grasscutter.data.excels.*;
@ -15,6 +14,7 @@ import emu.grasscutter.game.ability.AbilityManager;
import emu.grasscutter.game.activity.ActivityManager;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.avatar.TrialAvatar;
import emu.grasscutter.game.battlepass.BattlePassManager;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity;
@ -81,13 +81,13 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingQueue;
@ -832,38 +832,25 @@ public class Player {
addAvatar(new Avatar(avatarId), true);
}
public List<Integer> 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();
val 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<Integer> trialAvatarBasicParam = getTrialAvatarParam(trialAvatarId);
List<Integer> trialAvatarBasicParam = TrialAvatar.getTrialAvatarParam(trialAvatarId);
if (trialAvatarBasicParam.isEmpty()) return false;
Avatar avatar = new Avatar(trialAvatarBasicParam.get(0));
if (avatar.getAvatarData() == null || !hasSentLoginPackets()) return false;
TrialAvatar trialAvatar = new TrialAvatar(trialAvatarBasicParam, trialAvatarId, reason, questMainId);
if (trialAvatar.getAvatarData() == null || !hasSentLoginPackets()) return false;
avatar.setOwner(this);
// Add trial weapons and relics
avatar.setTrialAvatarInfo(trialAvatarBasicParam.get(1), trialAvatarId, reason, questMainId);
avatar.equipTrialItems();
// add trial avatar to storage
boolean result = this.getAvatars().addAvatar(trialAvatar);
if (!result) return false;
trialAvatar.equipTrialItems();
// Recalc stats
avatar.recalcStats();
trialAvatar.recalcStats();
// Packet, mimic official server behaviour, add to player's bag but not saving to db
sendPacket(new PacketAvatarAddNotify(avatar, false));
sendPacket(new PacketAvatarAddNotify(trialAvatar, false));
// add to avatar to temporary trial team
getTeamManager().addAvatarToTrialTeam(avatar);
getTeamManager().addAvatarToTrialTeam(trialAvatar);
return true;
}
@ -873,36 +860,52 @@ public class Player {
trialAvatarId,
GrantReason.GRANT_REASON_BY_QUEST,
questMainId)) return false;
getTeamManager().trialAvatarTeamPostUpdate();
// Packet, mimic official server behaviour, neccessary to stop player from modifying team
// Packet, mimic official server behaviour, necessary to stop player from modifying team
sendPacket(new PacketAvatarTeamUpdateNotify(this));
getTeamManager().updateTeamEntities(false);
return true;
}
public void addTrialAvatarsForActivity(List<Integer> trialAvatarIds) {
getTeamManager().setupTrialAvatarTeamForActivity();
trialAvatarIds.forEach(trialAvatarId -> addTrialAvatar(
trialAvatarId,
GrantReason.GRANT_REASON_BY_TRIAL_AVATAR_ACTIVITY,
0));
getTeamManager().trialAvatarTeamPostUpdate(0);
public void addTrialAvatarsForDungeon(@NotNull List<Integer> trialAvatarIds, GrantReason reason) {
getTeamManager().setupTrialAvatarTeamForDungeon();
trialAvatarIds.forEach(trialAvatarId -> addTrialAvatar(trialAvatarId, reason, 0));
}
public boolean removeTrialAvatarForQuest(int trialAvatarId) {
if (!getTeamManager().isUseTrialTeam()) return false;
sendPacket(new PacketAvatarDelNotify(List.of(getTeamManager().getTrialAvatarGuid(trialAvatarId))));
getTeamManager().removeTrialAvatarTeamForQuest(trialAvatarId);
List<Integer> trialAvatarBasicParam = TrialAvatar.getTrialAvatarParam(trialAvatarId);
if (trialAvatarBasicParam.isEmpty()) return false;
// allows player to modify team again
sendPacket(new PacketAvatarTeamUpdateNotify());
long trialAvatarGuid = getTeamManager().getEntityGuids().get(trialAvatarBasicParam.get(0));
// remove trial avatar from storage
this.getAvatars().removeAvatarByGuid(trialAvatarGuid);
// remove trial avatar entities
getTeamManager().removeTrialAvatarTeam();
// send remove packets
sendPacket(new PacketAvatarDelNotify(List.of(trialAvatarGuid)));
return true;
}
public void removeTrialAvatarForActivity() {
public void removeTrialAvatarForDungeon() {
if (!getTeamManager().isUseTrialTeam()) return;
List<Long> tempGuids = getTeamManager().getEntityGuids().values().stream().toList();
sendPacket(new PacketAvatarDelNotify(getTeamManager().getActiveTeam().stream()
.map(x -> x.getAvatar().getGuid()).toList()));
getTeamManager().removeTrialAvatarTeamForActivity();
// remove trial avatar from storage
tempGuids.forEach(guid -> this.getAvatars().removeAvatarByGuid(guid));
// remove trial avatar entities
getTeamManager().removeTrialAvatarTeam();
// send remove packets
sendPacket(new PacketAvatarDelNotify(tempGuids));
}
public void addFlycloak(int flycloakId) {

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,6 @@ import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.entity.gadget.GadgetWorktop;
import emu.grasscutter.game.managers.blossom.BlossomManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.TeamInfo;
import emu.grasscutter.game.props.*;
import emu.grasscutter.game.quest.QuestGroupSuite;
import emu.grasscutter.game.world.data.TeleportProperties;
@ -29,7 +28,6 @@ import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.scripts.SceneScriptManager;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneBlock;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
@ -40,6 +38,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
@ -197,7 +196,7 @@ public class Scene {
player.setScene(null);
// Remove player avatars
this.removePlayerAvatars(player);
this.removePlayerAvatars(player);
// Remove player gadgets
for (EntityBaseGadget gadget : player.getTeamManager().getGadgets()) {
@ -212,34 +211,18 @@ public class Scene {
this.saveGroups();
}
private void setupPlayerAvatars(Player player) {
private void setupPlayerAvatars(@NotNull Player player) {
// Clear entities from old team
player.getTeamManager().getActiveTeam().clear();
// Add new entities for player
TeamInfo teamInfo = player.getTeamManager().getCurrentTeamInfo();
for (int avatarId : teamInfo.getAvatars()) {
Avatar avatar = player.getAvatars().getAvatarById(avatarId);
if (avatar == null) {
if (player.getTeamManager().isUseTrialTeam()) {
avatar = player.getTeamManager().getTrialAvatars().get(avatarId);
}
if (avatar == null) continue;
}
player.getTeamManager().getActiveTeam().add(new EntityAvatar(player.getScene(), avatar));
}
// Limit character index in case its out of bounds
if (player.getTeamManager().getCurrentCharacterIndex() >= player.getTeamManager().getActiveTeam().size() || player.getTeamManager().getCurrentCharacterIndex() < 0) {
player.getTeamManager().setCurrentCharacterIndex(player.getTeamManager().getCurrentCharacterIndex() - 1);
}
List<EntityAvatar> activeTeam = player.getTeamManager().getActiveTeam();
activeTeam.clear();
Optional.ofNullable(player.getTeamManager().getCurrentTeamInfo())
.ifPresent(info -> info.getAvatars().forEach(avatarId -> activeTeam.add(
new EntityAvatar(player.getScene(), player.getAvatars().getAvatarById(avatarId))
)));
}
private synchronized void removePlayerAvatars(Player player) {
var team = player.getTeamManager().getActiveTeam();
// removeEntities(team, VisionType.VISION_TYPE_REMOVE); // List<SubType> isn't cool apparently :(
team.forEach(e -> removeEntity(e, VisionType.VISION_TYPE_REMOVE));
team.clear();
private synchronized void removePlayerAvatars(@NotNull Player player) {
removeEntities(player.getTeamManager().getActiveTeam(), VisionType.VISION_TYPE_MISS);
}
public void spawnPlayer(Player player) {
@ -321,9 +304,9 @@ public class Scene {
}
}
public synchronized void removeEntities(List<GameEntity> entity, VisionType visionType) {
public synchronized void removeEntities(List<? extends GameEntity> entity, VisionType visionType) {
var toRemove = entity.stream()
.filter(e -> e != null)
.filter(Objects::nonNull)
.map(this::removeEntityDirectly)
.filter(Objects::nonNull)
.toList();
@ -363,6 +346,11 @@ public class Scene {
}
// Sanity check
// if (target instanceof EntityMonster monsterTarget) {
// monsterTarget.damage(result.getDamage(), result.getAttackerId(), attackType,
// ElementReactionType.getTypeByValue(result.getAmplifyReactionType()));
// return;
// }
target.damage(result.getDamage(), result.getAttackerId(), attackType);
}

View File

@ -340,7 +340,7 @@ public class World implements Iterable<Player> {
// Update team of all players since max players has been changed - Probably not the best way to do it
if (this.isMultiplayer()) {
player.getTeamManager().getMpTeam().copyFrom(player.getTeamManager().getMpTeam(), player.getTeamManager().getMaxTeamSize());
player.getTeamManager().updateTeamEntities(null);
player.getTeamManager().updateTeamEntities(true);
}
// Don't send packets if player is loading into the scene

View File

@ -3,12 +3,21 @@ package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAddCustomTeamRsp;
import emu.grasscutter.server.packet.send.PacketCustomTeamListNotify;
import org.jetbrains.annotations.NotNull;
@Opcodes(PacketOpcodes.AddCustomTeamReq)
public class HandlerAddCustomTeamReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
session.getPlayer().getTeamManager().addNewCustomTeam();
public void handle(@NotNull GameSession session, byte[] header, byte[] payload) throws Exception {
boolean result = session.getPlayer().getTeamManager().addNewCustomTeam();
// Send packets.
if (result) {
session.getPlayer().sendPacket(new PacketCustomTeamListNotify(session.getPlayer()));
}
session.getPlayer().sendPacket(new PacketAddCustomTeamRsp(result ? Retcode.RET_SUCC : Retcode.RET_FAIL));
}
}

View File

@ -5,15 +5,19 @@ import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarDieAnimationEndReqOuterClass.AvatarDieAnimationEndReq;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAvatarDieAnimationEndRsp;
@Opcodes(PacketOpcodes.AvatarDieAnimationEndReq)
public class HandlerAvatarDieAnimationEndReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
AvatarDieAnimationEndReq req = AvatarDieAnimationEndReq.parseFrom(payload);
// TODO There is also reborn pos being sent, which might be helpful
session.getPlayer().getTeamManager().onAvatarDie(req.getDieGuid());
// Response packet
session.getPlayer().sendPacket(new PacketAvatarDieAnimationEndRsp(req.getDieGuid(), req.getSkillId()));
}
}

View File

@ -5,15 +5,19 @@ import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChangeAvatarReqOuterClass.ChangeAvatarReq;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketChangeAvatarRsp;
@Opcodes(PacketOpcodes.ChangeAvatarReq)
public class HandlerChangeAvatarReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
ChangeAvatarReq req = ChangeAvatarReq.parseFrom(payload);
session.getPlayer().getTeamManager().changeAvatar(req.getGuid());
session.getPlayer().sendPacket(new PacketChangeAvatarRsp(
session.getPlayer().getTeamManager().changeAvatar(req.getGuid()),
req.getGuid()
));
}
}

View File

@ -4,16 +4,22 @@ import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChangeMpTeamAvatarReqOuterClass.ChangeMpTeamAvatarReq;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp;
import org.jetbrains.annotations.NotNull;
@Opcodes(PacketOpcodes.ChangeMpTeamAvatarReq)
public class HandlerChangeMpTeamAvatarReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
public void handle(@NotNull GameSession session, byte[] header, byte[] payload) throws Exception {
ChangeMpTeamAvatarReq req = ChangeMpTeamAvatarReq.parseFrom(payload);
session.getPlayer().getTeamManager().setupMpTeam(req.getAvatarGuidListList());
boolean result = session.getPlayer().getTeamManager().setupMpTeam(req.getAvatarGuidListList(), req.getCurAvatarGuid());
new PacketChangeMpTeamAvatarRsp(
result ? Retcode.RET_SUCC_VALUE : Retcode.RET_FAIL_VALUE,
req.getAvatarGuidListList(), req.getCurAvatarGuid());
}
}

View File

@ -4,16 +4,23 @@ import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChangeTeamNameReqOuterClass.ChangeTeamNameReq;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketChangeTeamNameRsp;
import org.jetbrains.annotations.NotNull;
@Opcodes(PacketOpcodes.ChangeTeamNameReq)
public class HandlerChangeTeamNameReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
public void handle(@NotNull GameSession session, byte[] header, byte[] payload) throws Exception {
ChangeTeamNameReq req = ChangeTeamNameReq.parseFrom(payload);
session.getPlayer().getTeamManager().setTeamName(req.getTeamId(), req.getTeamName());
boolean result = session.getPlayer().getTeamManager().setTeamName(req.getTeamId(), req.getTeamName());
session.getPlayer().sendPacket(new PacketChangeTeamNameRsp(
result ? Retcode.RET_SUCC_VALUE : Retcode.RET_FAIL_VALUE,
req.getTeamId(), req.getTeamName()));
}
}

View File

@ -4,16 +4,23 @@ import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChooseCurAvatarTeamReqOuterClass.ChooseCurAvatarTeamReq;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketChooseCurAvatarTeamRsp;
import org.jetbrains.annotations.NotNull;
@Opcodes(PacketOpcodes.ChooseCurAvatarTeamReq)
public class HandlerChooseCurAvatarTeamReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
public void handle(@NotNull GameSession session, byte[] header, byte[] payload) throws Exception {
ChooseCurAvatarTeamReq req = ChooseCurAvatarTeamReq.parseFrom(payload);
session.getPlayer().getTeamManager().setCurrentTeam(req.getTeamId());
boolean result = session.getPlayer().getTeamManager().setCurrentTeam(req.getTeamId());
session.getPlayer().sendPacket(new PacketChooseCurAvatarTeamRsp(
result ? Retcode.RET_SUCC_VALUE : Retcode.RET_FAIL_VALUE,
req.getTeamId()));
}
}

View File

@ -9,9 +9,11 @@ import emu.grasscutter.server.packet.send.PacketEnterSceneReadyRsp;
@Opcodes(PacketOpcodes.EnterSceneReadyReq)
public class HandlerEnterSceneReadyReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) {
// This is how official behaves, don't remove, not sure if tower team also behaves the same way
session.getPlayer().removeTrialAvatarForDungeon();
session.send(new PacketEnterScenePeerNotify(session.getPlayer()));
session.send(new PacketEnterSceneReadyRsp(session.getPlayer()));
}

View File

@ -4,13 +4,25 @@ import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.RemoveCustomTeamReqOuterClass.RemoveCustomTeamReq;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketCustomTeamListNotify;
import emu.grasscutter.server.packet.send.PacketRemoveCustomTeamRsp;
import org.jetbrains.annotations.NotNull;
@Opcodes(PacketOpcodes.RemoveCustomTeamReq)
public class HandlerRemoveCustomTeamReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
public void handle(@NotNull GameSession session, byte[] header, byte[] payload) throws Exception {
RemoveCustomTeamReq req = RemoveCustomTeamReq.parseFrom(payload);
session.getPlayer().getTeamManager().removeCustomTeam(req.getId());
boolean result = session.getPlayer().getTeamManager().removeCustomTeam(req.getId());
// Send packets.
if (result) {
session.getPlayer().sendPacket(new PacketCustomTeamListNotify(session.getPlayer()));
}
session.getPlayer().sendPacket(new PacketRemoveCustomTeamRsp(
result ? Retcode.RET_SUCC : Retcode.RET_FAIL,
req.getId()));
}
}

View File

@ -2,18 +2,26 @@ package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.net.proto.SetUpAvatarTeamReqOuterClass.SetUpAvatarTeamReq;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketSetUpAvatarTeamRsp;
import org.jetbrains.annotations.NotNull;
@Opcodes(PacketOpcodes.SetUpAvatarTeamReq)
public class HandlerSetUpAvatarTeamReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
public void handle(@NotNull GameSession session, byte[] header, byte[] payload) throws Exception {
SetUpAvatarTeamReq req = SetUpAvatarTeamReq.parseFrom(payload);
session.getPlayer().getTeamManager().setupAvatarTeam(req.getTeamId(), req.getAvatarTeamGuidListList());
boolean result = session.getPlayer().getTeamManager().setupAvatarTeam(
req.getTeamId(), req.getAvatarTeamGuidListList(), req.getCurAvatarGuid());
session.getPlayer().sendPacket(new PacketSetUpAvatarTeamRsp(
req.getTeamId(), req.getCurAvatarGuid(), req.getAvatarTeamGuidListList(),
result ? Retcode.RET_SUCC_VALUE : Retcode.RET_FAIL_VALUE));
}
}

View File

@ -3,18 +3,17 @@ package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChangeAvatarRspOuterClass.ChangeAvatarRsp;
import emu.grasscutter.net.proto.RetcodeOuterClass;
public class PacketChangeAvatarRsp extends BasePacket {
public PacketChangeAvatarRsp(long guid) {
public PacketChangeAvatarRsp(int ret, long guid) {
super(PacketOpcodes.ChangeAvatarRsp);
ChangeAvatarRsp p = ChangeAvatarRsp.newBuilder()
.setRetcode(RetcodeOuterClass.Retcode.RET_SUCC_VALUE)
.setRetcode(ret)
.setCurGuid(guid)
.build();
this.setData(p);
}
}

View File

@ -6,18 +6,31 @@ import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChangeMpTeamAvatarRspOuterClass.ChangeMpTeamAvatarRsp;
import java.util.List;
public class PacketChangeMpTeamAvatarRsp extends BasePacket {
public PacketChangeMpTeamAvatarRsp(Player player, TeamInfo teamInfo) {
super(PacketOpcodes.ChangeMpTeamAvatarRsp);
ChangeMpTeamAvatarRsp.Builder proto = ChangeMpTeamAvatarRsp.newBuilder()
.setCurAvatarGuid(player.getTeamManager().getCurrentCharacterGuid());
for (int avatarId : teamInfo.getAvatars()) {
proto.addAvatarGuidList(player.getAvatars().getAvatarById(avatarId).getGuid());
}
this.setData(proto);
}
public PacketChangeMpTeamAvatarRsp(int retVal, List<Long> guidList, long guid) {
super(PacketOpcodes.ChangeMpTeamAvatarRsp);
ChangeMpTeamAvatarRsp.Builder proto = ChangeMpTeamAvatarRsp.newBuilder()
.setCurAvatarGuid(guid)
.addAllAvatarGuidList(guidList)
.setRetcode(retVal);
this.setData(proto);
}
}

View File

@ -5,15 +5,16 @@ import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChangeTeamNameRspOuterClass.ChangeTeamNameRsp;
public class PacketChangeTeamNameRsp extends BasePacket {
public PacketChangeTeamNameRsp(int teamId, String teamName) {
public PacketChangeTeamNameRsp(int ret, int teamId, String teamName) {
super(PacketOpcodes.ChangeTeamNameRsp);
ChangeTeamNameRsp proto = ChangeTeamNameRsp.newBuilder()
.setTeamId(teamId)
.setTeamName(teamName)
.setRetcode(ret)
.build();
this.setData(proto);
}
}

View File

@ -5,14 +5,15 @@ import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChooseCurAvatarTeamRspOuterClass.ChooseCurAvatarTeamRsp;
public class PacketChooseCurAvatarTeamRsp extends BasePacket {
public PacketChooseCurAvatarTeamRsp(int teamId) {
public PacketChooseCurAvatarTeamRsp(int retVal, int teamId) {
super(PacketOpcodes.ChooseCurAvatarTeamRsp);
ChooseCurAvatarTeamRsp proto = ChooseCurAvatarTeamRsp.newBuilder()
.setCurTeamId(teamId)
.build();
.setCurTeamId(teamId)
.setRetcode(retVal)
.build();
this.setData(proto);
}
}

View File

@ -1,24 +1,21 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.TeamInfo;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.SetUpAvatarTeamRspOuterClass.SetUpAvatarTeamRsp;
public class PacketSetUpAvatarTeamRsp extends BasePacket {
public PacketSetUpAvatarTeamRsp(Player player, int teamId, TeamInfo teamInfo) {
super(PacketOpcodes.SetUpAvatarTeamRsp);
import java.util.Collection;
SetUpAvatarTeamRsp.Builder proto = SetUpAvatarTeamRsp.newBuilder()
.setTeamId(teamId)
.setCurAvatarGuid(player.getTeamManager().getCurrentCharacterGuid());
for (int avatarId : teamInfo.getAvatars()) {
proto.addAvatarTeamGuidList(player.getAvatars().getAvatarById(avatarId).getGuid());
}
this.setData(proto);
}
public class PacketSetUpAvatarTeamRsp extends BasePacket {
public PacketSetUpAvatarTeamRsp(int teamId, long guid, Collection<Long> guidList, int retVal) {
super(PacketOpcodes.SetUpAvatarTeamRsp);
SetUpAvatarTeamRsp.Builder proto = SetUpAvatarTeamRsp.newBuilder()
.setTeamId(teamId)
.setCurAvatarGuid(guid)
.addAllAvatarTeamGuidList(guidList)
.setRetcode(retVal);
this.setData(proto);
}
}