feat: challenge peak

This commit is contained in:
StopWuyu
2025-08-15 20:18:07 +08:00
parent 9b1d029eda
commit ae22e035ff
12 changed files with 560 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Enums.Mission;
using EggLink.DanhengServer.GameServer.Game.Battle;
using EggLink.DanhengServer.GameServer.Game.Challenge.Definitions;
using EggLink.DanhengServer.GameServer.Game.Player;
using EggLink.DanhengServer.GameServer.Game.Scene.Entity;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.ChallengePeak;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lineup;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Proto.ServerSide;
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.GameServer.Game.Challenge.Instances;
public class ChallengePeakInstance(PlayerInstance player, ChallengeDataPb data) : BaseChallengeInstance(player, data)
{
#region Properties
public ChallengePeakConfigExcel Config { get; } = GameData.ChallengePeakConfigData[(int)data.Peak.CurrentPeakLevelId];
public bool IsWin { get; private set; }
#endregion
#region Setter & Getter
public override Dictionary<int, List<ChallengeConfigExcel.ChallengeMonsterInfo>> GetStageMonsters()
{
if (!Data.Peak.IsHard || Config.BossExcel == null) return Config.ChallengeMonsters;
Dictionary<int, List<ChallengeConfigExcel.ChallengeMonsterInfo>> monsters = [];
monsters.Add(Config.MazeGroupID, []);
for (var i = 0; i < Config.ConfigIDList.Count; i++)
{
monsters[Config.MazeGroupID].Add(new ChallengeConfigExcel.ChallengeMonsterInfo(Config.ConfigIDList[i],
Config.NpcMonsterIDList[i], Config.BossExcel.HardEventIDList[i]));
}
return monsters;
}
#endregion
//#region Serialization
//#endregion
#region Handlers
public override void OnBattleStart(BattleInstance battle)
{
foreach (var peakBuff in Data.Peak.Buffs)
{
battle.Buffs.Add(new MazeBuff((int)peakBuff, 1, -1)
{
WaveFlag = -1
});
}
if (Data.Peak.IsHard && Config.BossExcel != null)
{
var excel = GameData.BattleTargetConfigData.GetValueOrDefault(Config.BossExcel.HardTarget);
if (excel != null)
battle.AddBattleTarget(5, excel.ID, 0, excel.TargetParam);
}
foreach (var targetId in Config.NormalTargetList)
{
var excel = GameData.BattleTargetConfigData.GetValueOrDefault(targetId);
if (excel != null)
battle.AddBattleTarget(5, excel.ID, 0, excel.TargetParam);
}
}
public override async ValueTask OnBattleEnd(BattleInstance battle, PVEBattleResultCsReq req)
{
switch (req.EndStatus)
{
case BattleEndStatus.BattleEndWin:
// Get monster count in stage
long monsters = Player.SceneInstance!.Entities.Values.OfType<EntityMonster>().Count();
if (monsters == 0)
{
Data.Peak.CurStatus = (int)ChallengeStatus.ChallengeFinish;
Data.Peak.Stars = CalculateStars(req);
IsWin = true;
await Player.SendPacket(new PacketChallengePeakSettleScNotify(this));
// Call MissionManager
await Player.MissionManager!.HandleFinishType(MissionFinishTypeEnum.ChallengeFinish, this);
}
// Set saved technique points (This will be restored if the player resets the challenge)
Data.Peak.SavedMp = (uint)Player.LineupManager!.GetCurLineup()!.Mp;
break;
case BattleEndStatus.BattleEndQuit:
// Reset technique points and move back to start position
var lineup = Player.LineupManager!.GetCurLineup()!;
lineup.Mp = (int)Data.Peak.SavedMp;
if (Data.Peak.StartPos != null && Data.Peak.StartRot != null)
await Player.MoveTo(Data.Peak.StartPos.ToPosition(), Data.Peak.StartRot.ToPosition());
await Player.SendPacket(new PacketSyncLineupNotify(lineup));
break;
default:
// Determine challenge result
// Fail challenge
Data.Peak.CurStatus = (int)ChallengeStatus.ChallengeFailed;
// Send challenge result data
await Player.SendPacket(new PacketChallengePeakSettleScNotify(this));
break;
}
}
public uint CalculateStars(PVEBattleResultCsReq req)
{
var targets = Config.NormalTargetList;
var stars = 0u;
foreach (var targetId in targets)
{
var target = req.Stt.BattleTargetInfo[5].BattleTargetList_.FirstOrDefault(x => x.Id == targetId);
if (target == null) continue;
if (target.Progress <= target.TotalProgress)
stars++;
}
return Math.Min(stars, 7);
}
#endregion
}

View File

@@ -0,0 +1,220 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Database.Challenge;
using EggLink.DanhengServer.Database.Lineup;
using EggLink.DanhengServer.GameServer.Game.Challenge.Definitions;
using EggLink.DanhengServer.GameServer.Game.Challenge.Instances;
using EggLink.DanhengServer.GameServer.Game.Player;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Challenge;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.ChallengePeak;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Proto.ServerSide;
using System.Drawing.Drawing2D;
using EggLink.DanhengServer.Util;
using ChallengePeakInfo = EggLink.DanhengServer.Proto.ChallengePeakInfo;
using ChallengePeakLevelInfo = EggLink.DanhengServer.Proto.ChallengePeakLevelInfo;
namespace EggLink.DanhengServer.GameServer.Game.ChallengePeak;
/// <summary>
/// this class is used to manage the challenge peak for a player
/// but the challenge instance shouldnt be stored here ( see ChallengeManager )
/// </summary>
/// <see cref="EggLink.DanhengServer.GameServer.Game.Challenge.ChallengeManager"/>
public class ChallengePeakManager(PlayerInstance player) : BasePlayerManager(player)
{
public ChallengePeakInfo GetChallengePeakInfo(int groupId)
{
var proto = new ChallengePeakInfo
{
CurPeakGroupId = (uint)groupId,
};
var data = GameData.ChallengePeakGroupConfigData.GetValueOrDefault(groupId);
if (data == null) return proto;
foreach (var levelId in data.PreLevelIDList)
{
var levelData = GameData.ChallengePeakConfigData.GetValueOrDefault(levelId);
if (levelData == null) continue;
var levelProto = new ChallengePeakLevelInfo
{
PeakLevelId = (uint)levelId,
IsRead = true
};
if (Player.ChallengeManager!.ChallengeData.PeakLevelDatas.TryGetValue(levelId, out var levelPbData))
{
levelProto.PeakStar = levelPbData.PeakStar;
levelProto.PeakLevelLineup.AddRange(levelPbData.BaseAvatarList);
foreach (var avatarId in levelPbData.BaseAvatarList)
{
var avatar = Player.AvatarManager!.GetFormalAvatar((int)avatarId);
if (avatar == null) continue;
levelProto.PeakAvatarInfoList.Add(avatar.ToPeakAvatarProto());
proto.PeakAvatarInfoList.Add(avatar.ToPeakAvatarProto());
}
}
proto.PeakLevelInfoList.Add(levelProto);
}
// boss
var bossLevelId = data.BossLevelID;
if (bossLevelId <= 0) return proto;
var bossLevelData = GameData.ChallengePeakBossConfigData.GetValueOrDefault(bossLevelId);
if (bossLevelData == null) return proto;
var bossProto = new ChallengePeakLevelInfo
{
PeakLevelId = (uint)bossLevelId,
IsRead = true
};
if (Player.ChallengeManager!.ChallengeData.PeakLevelDatas.TryGetValue(bossLevelId, out var bossPbData))
{
bossProto.PeakStar = bossPbData.PeakStar;
bossProto.PeakLevelLineup.AddRange(bossPbData.BaseAvatarList);
foreach (var avatarId in bossPbData.BaseAvatarList)
{
var avatar = Player.AvatarManager!.GetFormalAvatar((int)avatarId);
if (avatar == null) continue;
bossProto.PeakAvatarInfoList.Add(avatar.ToPeakAvatarProto());
proto.PeakAvatarInfoList.Add(avatar.ToPeakAvatarProto());
}
}
proto.PeakLevelInfoList.Add(bossProto);
return proto;
}
public async ValueTask SetLineupAvatars(int groupId, List<ChallengePeakLineup> lineups)
{
var datas = Player.ChallengeManager!.ChallengeData.PeakLevelDatas;
foreach (var lineup in lineups)
{
if (!datas.TryGetValue((int)lineup.PeakLevelId,
out var data))
{
datas[(int)lineup.PeakLevelId] = new ChallengePeakLevelData
{
LevelId = (int)lineup.PeakLevelId,
BaseAvatarList = lineup.PeakLevelLineup.ToList()
};
}
else
{
data.BaseAvatarList = lineup.PeakLevelLineup.ToList();
}
}
await Player.SendPacket(new PacketChallengePeakGroupDataUpdateScNotify(GetChallengePeakInfo(groupId)));
}
public async ValueTask StartChallenge(int levelId, uint buffId, List<int> avatarIdList)
{
// Get challenge excel
if (!GameData.ChallengePeakConfigData.TryGetValue(levelId, out var excel))
{
await Player.SendPacket(new PacketStartChallengePeakScRsp(Retcode.RetChallengeNotExist));
return;
}
// Format to base avatar id
List<int> avatarIds = [];
foreach (var avatarId in avatarIdList)
{
var avatar = Player.AvatarManager!.GetFormalAvatar(avatarId);
if (avatar != null)
avatarIds.Add(avatar.BaseAvatarId);
}
// Get lineup
var lineup = Player.LineupManager!.GetExtraLineup(ExtraLineupType.LineupChallenge)!;
if (avatarIds.Count > 0)
{
lineup.BaseAvatars = avatarIds.Select(x => new LineupAvatarInfo
{
BaseAvatarId = x
}).ToList();
}
else
{
lineup.BaseAvatars = Player.ChallengeManager!.ChallengeData.PeakLevelDatas.GetValueOrDefault(levelId)
?.BaseAvatarList
.Select(x => new LineupAvatarInfo
{
BaseAvatarId = (int)x
}).ToList() ?? [];
}
// Set technique points to full
lineup.Mp = 5; // Max Mp
// Make sure this lineup has avatars set
if (Player.AvatarManager!.AvatarData!.FormalAvatars.Count == 0)
{
await Player.SendPacket(new PacketStartChallengePeakScRsp(Retcode.RetChallengeLineupEmpty));
return;
}
// Reset hp/sp
foreach (var avatar in Player.AvatarManager!.AvatarData.FormalAvatars)
{
avatar.SetCurHp(10000, true);
avatar.SetCurSp(5000, true);
}
// Set challenge data for player
var data = new ChallengeDataPb
{
Peak = new ChallengePeakDataPb
{
CurrentPeakLevelId = (uint)levelId,
CurrentExtraLineup = ChallengeLineupTypePb.Challenge1,
CurStatus = 1
}
};
if (buffId > 0)
{
data.Peak.Buffs.Add(buffId);
}
var instance = new ChallengePeakInstance(Player, data);
Player.ChallengeManager!.ChallengeInstance = instance;
// Set first lineup before we enter scenes
await Player.LineupManager!.SetCurLineup((int)instance.Data.Peak.CurrentExtraLineup + 10);
// Enter scene
try
{
await Player.EnterScene(excel.MapEntranceID, 0, true);
}
catch
{
// Reset lineup/instance if entering scene failed
Player.ChallengeManager!.ChallengeInstance = null;
// Send error packet
await Player.SendPacket(new PacketStartChallengePeakScRsp(Retcode.RetChallengeNotExist));
return;
}
// Save start positions
data.Peak.StartPos = Player.Data.Pos!.ToVector3Pb();
data.Peak.StartPos = Player.Data.Rot!.ToVector3Pb();
data.Peak.SavedMp = (uint)Player.LineupManager.GetCurLineup()!.Mp;
// Send packet
await Player.SendPacket(new PacketStartChallengePeakScRsp(Retcode.RetSucc));
// Save instance
Player.ChallengeManager!.SaveInstance(instance);
}
}

View File

@@ -0,0 +1,13 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.ChallengePeak;
using EggLink.DanhengServer.Kcp;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.ChallengePeak;
[Opcode(CmdIds.GetChallengePeakDataCsReq)]
public class HandlerGetChallengePeakDataCsReq : Handler
{
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
{
await connection.SendPacket(new PacketGetChallengePeakDataScRsp(connection.Player!));
}
}

View File

@@ -0,0 +1,13 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.ChallengePeak;
using EggLink.DanhengServer.Kcp;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.ChallengePeak;
[Opcode(CmdIds.GetCurChallengePeakCsReq)]
public class HandlerGetCurChallengePeakCsReq : Handler
{
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
{
await connection.SendPacket(new PacketGetCurChallengePeakScRsp(connection.Player!));
}
}

View File

@@ -0,0 +1,42 @@
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
using static EggLink.DanhengServer.GameServer.Plugin.Event.PluginEvent;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.ChallengePeak;
[Opcode(CmdIds.LeaveChallengePeakCsReq)]
public class HandlerLeaveChallengePeakCsReq : Handler
{
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
{
var player = connection.Player!;
// TODO: check for plane type
if (player.SceneInstance != null)
{
// As of 1.5.0, the server now has to handle the player leaving battle too
await player.ForceQuitBattle();
// Reset lineup
player.LineupManager!.SetExtraLineup(ExtraLineupType.LineupChallenge, []);
InvokeOnPlayerQuitChallenge(player, player.ChallengeManager!.ChallengeInstance);
player.ChallengeManager!.ChallengeInstance = null;
player.ChallengeManager!.ClearInstance();
// Leave scene
await player.LineupManager.SetCurLineup(0);
// Heal avatars (temproary solution)
foreach (var avatar in player.LineupManager.GetCurLineup()!.AvatarData!.FormalAvatars)
avatar.CurrentHp = 10000;
var leaveEntryId = GameConstants.CHALLENGE_PEAK_ENTRANCE;
if (player.SceneInstance.LeaveEntryId != 0) leaveEntryId = player.SceneInstance.LeaveEntryId;
await player.EnterScene(leaveEntryId, 0, true);
}
await connection.SendPacket(CmdIds.LeaveChallengePeakScRsp);
}
}

View File

@@ -0,0 +1,17 @@
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.ChallengePeak;
[Opcode(CmdIds.SetChallengePeakMobLineupAvatarCsReq)]
public class HandlerSetChallengePeakMobLineupAvatarCsReq : Handler
{
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = SetChallengePeakMobLineupAvatarCsReq.Parser.ParseFrom(data);
await connection.Player!.ChallengePeakManager!.SetLineupAvatars((int)req.CurPeakGroupId, req.LineupList.ToList());
await connection.SendPacket(CmdIds.SetChallengePeakMobLineupAvatarScRsp);
}
}

View File

@@ -0,0 +1,16 @@
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.ChallengePeak;
[Opcode(CmdIds.StartChallengePeakCsReq)]
public class HandlerStartChallengePeakCsReq : Handler
{
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = StartChallengePeakCsReq.Parser.ParseFrom(data);
await connection.Player!.ChallengePeakManager!.StartChallenge((int)req.PeakLevelId, req.PeakBossBuff,
req.PeakLevelLineup.Select(x => (int)x).ToList());
}
}

View File

@@ -0,0 +1,17 @@
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.ChallengePeak;
public class PacketChallengePeakGroupDataUpdateScNotify : BasePacket
{
public PacketChallengePeakGroupDataUpdateScNotify(ChallengePeakInfo info) : base(CmdIds.ChallengePeakGroupDataUpdateScNotify)
{
var proto = new ChallengePeakGroupDataUpdateScNotify
{
UpdatePeakData = info
};
SetData(proto);
}
}

View File

@@ -0,0 +1,20 @@
using EggLink.DanhengServer.GameServer.Game.Challenge.Instances;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.ChallengePeak;
public class PacketChallengePeakSettleScNotify : BasePacket
{
public PacketChallengePeakSettleScNotify(ChallengePeakInstance inst) : base(CmdIds.ChallengePeakSettleScNotify)
{
var proto = new ChallengePeakSettleScNotify
{
PeakStar = inst.Data.Peak.Stars,
IsWin = inst.IsWin,
PeakLevelId = inst.Data.Peak.CurrentPeakLevelId
};
SetData(proto);
}
}

View File

@@ -0,0 +1,24 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.GameServer.Game.Player;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.ChallengePeak;
public class PacketGetChallengePeakDataScRsp : BasePacket
{
public PacketGetChallengePeakDataScRsp(PlayerInstance player) : base(CmdIds.GetChallengePeakDataScRsp)
{
var proto = new GetChallengePeakDataScRsp
{
FHODLMICBGP = 1
};
foreach (var groupId in GameData.ChallengePeakGroupConfigData.Keys)
{
proto.ChallengePeakList.Add(player.ChallengePeakManager!.GetChallengePeakInfo(groupId));
}
SetData(proto);
}
}

View File

@@ -0,0 +1,24 @@
using EggLink.DanhengServer.GameServer.Game.Challenge.Instances;
using EggLink.DanhengServer.GameServer.Game.Player;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.ChallengePeak;
public class PacketGetCurChallengePeakScRsp : BasePacket
{
public PacketGetCurChallengePeakScRsp(PlayerInstance player) : base(CmdIds.GetCurChallengePeakScRsp)
{
var proto = new GetCurChallengePeakScRsp();
if (player.ChallengeManager!.ChallengeInstance is ChallengePeakInstance peak)
{
proto.IsRead = true;
proto.PeakLevelId = peak.Data.Peak.CurrentPeakLevelId;
proto.PeakBossBuff = peak.Data.Peak.Buffs.FirstOrDefault(0u);
proto.PeakStar = peak.Data.Peak.Stars;
}
SetData(proto);
}
}

View File

@@ -0,0 +1,17 @@
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.ChallengePeak;
public class PacketStartChallengePeakScRsp : BasePacket
{
public PacketStartChallengePeakScRsp(Retcode retcode) : base(CmdIds.StartChallengePeakScRsp)
{
var proto = new StartChallengePeakScRsp
{
Retcode = (uint)retcode
};
SetData(proto);
}
}