diff --git a/Common/Database/Avatar.cs b/Common/Database/Avatar.cs index 40a1762..8bac92d 100644 --- a/Common/Database/Avatar.cs +++ b/Common/Database/Avatar.cs @@ -195,7 +195,8 @@ namespace Common.Database } else { - avatarSubSkill.Level++; + if (avatarSubSkill.Level < subSkillData.MaxLv) + avatarSubSkill.Level++; } } else diff --git a/Common/Utils/ExcelReader/GeneralActivity.cs b/Common/Utils/ExcelReader/GeneralActivity.cs new file mode 100644 index 0000000..e8c2036 --- /dev/null +++ b/Common/Utils/ExcelReader/GeneralActivity.cs @@ -0,0 +1,84 @@ +using Newtonsoft.Json; + +namespace Common.Utils.ExcelReader +{ + public class GeneralActivity : BaseExcelReader + { + public override string FileName { get { return "GeneralActivity.json"; } } + + public List GetAllDistinct() + { + return All.Distinct(new GeneralActivityDisplayComparer()).ToList(); + } + } + + public class GeneralActivityDisplayComparer : IEqualityComparer + { +#pragma warning disable CS8767 + public bool Equals(GeneralActivityExcel x, GeneralActivityExcel y) + { + return x.DisplayData == y.DisplayData; + } +#pragma warning restore CS8767 + + public int GetHashCode(GeneralActivityExcel obj) + { + return obj.DisplayData; + } + } + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public partial class GeneralActivityExcel + { + [JsonProperty("Series")] + public int Series { get; set; } + + [JsonProperty("AcitivityType")] + public int AcitivityType { get; set; } + + [JsonProperty("MinLevel")] + public int MinLevel { get; set; } + + [JsonProperty("MaxLevel")] + public int MaxLevel { get; set; } + + [JsonProperty("LinkData")] + public int LinkData { get; set; } + + [JsonProperty("DisplayData")] + public int DisplayData { get; set; } + + [JsonProperty("ScoreData")] + public int ScoreData { get; set; } + + [JsonProperty("RankData")] + public int RankData { get; set; } + + [JsonProperty("ShowRank")] + public int ShowRank { get; set; } + + [JsonProperty("TeamListID")] + public int TeamListId { get; set; } + + [JsonProperty("PreCondType")] + public int PreCondType { get; set; } + + [JsonProperty("PreCondParaStr")] + public string PreCondParaStr { get; set; } + + [JsonProperty("PreUnlockHint")] + public HashName PreUnlockHint { get; set; } + + [JsonProperty("ActivityBuffID")] + public int ActivityBuffId { get; set; } + + [JsonProperty("TicketIDList")] + public int[] TicketIdList { get; set; } + + [JsonProperty("DataImpl")] + public object DataImpl { get; set; } + + [JsonProperty("AcitivityID")] + public int AcitivityId { get; set; } + } +} diff --git a/GameServer/Game/Player.cs b/GameServer/Game/Player.cs index cb11957..bae8b8b 100644 --- a/GameServer/Game/Player.cs +++ b/GameServer/Game/Player.cs @@ -1,4 +1,6 @@ using Common.Database; +using Common.Resources.Proto; +using Common.Utils.ExcelReader; namespace PemukulPaku.GameServer.Game { @@ -12,7 +14,7 @@ namespace PemukulPaku.GameServer.Game { User = user; Equipment = Common.Database.Equipment.FromUid(user.Uid); - AvatarList = Avatar.AvatarsFromUid(user.Uid); + AvatarList = Common.Database.Avatar.AvatarsFromUid(user.Uid); } public void SaveAll() @@ -33,5 +35,33 @@ namespace PemukulPaku.GameServer.Game avatar.TodayHasAddGoodfeel = 0; } } + + public PlayerCardData GetCardData() + { + return new() + { + Uid = User.Uid, + MsgData = new() + { + MsgIndex = 0, + MsgConfig = 1 + }, + OnPhonePendantId = 350005 + }; + } + + public PlayerDetailData GetDetailData() + { + return new() + { + Uid = User.Uid, + Nickname = User.Nick, + Level = (uint)PlayerLevelData.GetInstance().CalculateLevel(User.Exp).Level, + SelfDesc = User.SelfDesc, + CustomHeadId = (uint)User.CustomHeadId, + FrameId = User.FrameId < 200001 ? 200001 : (uint)User.FrameId, + LeaderAvatar = AvatarList.FirstOrDefault(x => x.AvatarId == User.AvatarTeamList.FirstOrDefault()?.AvatarIdLists[0])?.ToDetailData(Equipment) ?? new() { AvatarId = 101 } + }; + } } } diff --git a/GameServer/Handlers/Activity/ChapterActivityTakeDailyRewardReqHandler.cs b/GameServer/Handlers/Activity/ChapterActivityTakeDailyRewardReqHandler.cs new file mode 100644 index 0000000..e6b99c7 --- /dev/null +++ b/GameServer/Handlers/Activity/ChapterActivityTakeDailyRewardReqHandler.cs @@ -0,0 +1,19 @@ +using Common.Resources.Proto; + +namespace PemukulPaku.GameServer.Handlers.Activity +{ + [PacketCmdId(CmdId.ChapterActivityTakeDailyRewardReq)] + internal class ChapterActivityTakeDailyRewardReqHandler : IPacketHandler + { + public void Handle(Session session, Packet packet) + { + ChapterActivityTakeDailyRewardReq Data = packet.GetDecodedBody(); + + session.Send(Packet.FromProto(new ChapterActivityTakeDailyRewardRsp() + { + retcode = ChapterActivityTakeDailyRewardRsp.Retcode.HasTake, + ChapterId = Data.ChapterId + }, CmdId.ChapterActivityTakeDailyRewardRsp)); + } + } +} diff --git a/GameServer/Handlers/One/CreateLobbyReqHandler.cs b/GameServer/Handlers/One/CreateLobbyReqHandler.cs new file mode 100644 index 0000000..1027acf --- /dev/null +++ b/GameServer/Handlers/One/CreateLobbyReqHandler.cs @@ -0,0 +1,27 @@ +using Common.Resources.Proto; +using PemukulPaku.GameServer.MPModule; + +namespace PemukulPaku.GameServer.Handlers.One +{ + [PacketCmdId(CmdId.CreateLobbyReq)] + internal class CreateLobbyReqHandler : IPacketHandler + { + public void Handle(Session session, Packet packet) + { + CreateLobbyReq Data = packet.GetDecodedBody(); + Team team = Lobby.GetInstance().CreateTeam(new(Data.StageId, Data.MinLevel, Data.LobbyEnterType, session, Data.TeamName)); + CreateLobbyRsp Rsp = new() + { + retcode = CreateLobbyRsp.Retcode.Succ, + LobbyId = team.LeaderUid, + StageId = team.StageId, + MinLevel = team.MinLevel, + LobbyEnterType = team.LobbyEnterType, + MaxLevel = 0, + TeamName = team.Name + }; + + session.Send(Packet.FromProto(Rsp, CmdId.CreateLobbyRsp)); + } + } +} diff --git a/GameServer/Handlers/One/GetMpDataReqHandler.cs b/GameServer/Handlers/One/GetMpDataReqHandler.cs new file mode 100644 index 0000000..bf3e167 --- /dev/null +++ b/GameServer/Handlers/One/GetMpDataReqHandler.cs @@ -0,0 +1,24 @@ +using Common.Resources.Proto; + +namespace PemukulPaku.GameServer.Handlers.One +{ + [PacketCmdId(CmdId.GetMpDataReq)] + internal class GetMpDataReqHandler : IPacketHandler + { + public void Handle(Session session, Packet packet) + { + GetMpDataRsp Rsp = new() + { + retcode = GetMpDataRsp.Retcode.Succ, + DataType = MpDataType.MpDataAll, + op_type = GetMpDataRsp.OpType.InitData, + MpLevel = 1, + MpExp = 0, + TeamAvatarId = session.Player.GetDetailData().LeaderAvatar.AvatarId, + PunishEndTime = 0 + }; + + session.Send(Packet.FromProto(Rsp, CmdId.GetMpDataRsp)); + } + } +} diff --git a/GameServer/Handlers/One/GetWeekDayActivityDataReqHandler.cs b/GameServer/Handlers/One/GetWeekDayActivityDataReqHandler.cs new file mode 100644 index 0000000..3f99445 --- /dev/null +++ b/GameServer/Handlers/One/GetWeekDayActivityDataReqHandler.cs @@ -0,0 +1,27 @@ +using Common; +using Common.Resources.Proto; + +namespace PemukulPaku.GameServer.Handlers.One +{ + [PacketCmdId(CmdId.GetWeekDayActivityDataReq)] + internal class GetWeekDayActivityDataReqHandler : IPacketHandler + { + public void Handle(Session session, Packet packet) + { + GetWeekDayActivityDataRsp Rsp = new() { retcode = GetWeekDayActivityDataRsp.Retcode.Succ }; + + Rsp.ActivityLists.Add(new() + { + ActivityId = 1003, + StageIdLists = new uint[] { 101302, 101303, 101304, 101305 }, + EnterTimes = 1, + BeginTime = 0, + EndTime = (uint)Global.GetUnixInSeconds() + 3600 * 24 * 7, + ActivityEndTime = (uint)Global.GetUnixInSeconds() * (10 / 8), + ForceOpenTime = 0 + }); + + session.Send(Packet.FromProto(Rsp, CmdId.GetWeekDayActivityDataRsp)); + } + } +} diff --git a/GameServer/Handlers/One/MpGetTeamReqHandler.cs b/GameServer/Handlers/One/MpGetTeamReqHandler.cs new file mode 100644 index 0000000..b363d2b --- /dev/null +++ b/GameServer/Handlers/One/MpGetTeamReqHandler.cs @@ -0,0 +1,13 @@ +using Common.Resources.Proto; + +namespace PemukulPaku.GameServer.Handlers.One +{ + [PacketCmdId(CmdId.MpGetTeamReq)] + internal class MpGetTeamReqHandler : IPacketHandler + { + public void Handle(Session session, Packet packet) + { + session.Send(Packet.FromProto(new MpGetTeamRsp() { retcode = MpGetTeamRsp.Retcode.NotInTeam }, CmdId.GetMpDataRsp)); + } + } +} diff --git a/GameServer/Handlers/One/MpMemberSetClientStatusReqHandler.cs b/GameServer/Handlers/One/MpMemberSetClientStatusReqHandler.cs new file mode 100644 index 0000000..289268e --- /dev/null +++ b/GameServer/Handlers/One/MpMemberSetClientStatusReqHandler.cs @@ -0,0 +1,13 @@ +using Common.Resources.Proto; + +namespace PemukulPaku.GameServer.Handlers.One +{ + [PacketCmdId(CmdId.MpMemberSetClientStatusReq)] + internal class MpMemberSetClientStatusReqHandler : IPacketHandler + { + public void Handle(Session session, Packet packet) + { + session.Send(Packet.FromProto(new MpMemberSetClientStatusRsp() { retcode = MpMemberSetClientStatusRsp.Retcode.Succ }, CmdId.MpMemberSetClientStatusRsp)); + } + } +} diff --git a/GameServer/Handlers/One/MpTeamEnterLobbyReqHandler.cs b/GameServer/Handlers/One/MpTeamEnterLobbyReqHandler.cs new file mode 100644 index 0000000..44cc5b1 --- /dev/null +++ b/GameServer/Handlers/One/MpTeamEnterLobbyReqHandler.cs @@ -0,0 +1,27 @@ +using Common.Resources.Proto; +using PemukulPaku.GameServer.MPModule; + +namespace PemukulPaku.GameServer.Handlers.One +{ + [PacketCmdId(CmdId.MpTeamEnterLobbyReq)] + public class MpTeamEnterLobbyReqHandler : IPacketHandler + { + public void Handle(Session session, Packet packet) + { + MpTeamEnterLobbyReq Data = packet.GetDecodedBody(); + Lobby.GetInstance().Teams.TryGetValue(session.Player.User.Uid, out Team? team); + MpTeamEnterLobbyRsp Rsp = new() { retcode = MpTeamEnterLobbyRsp.Retcode.NotInTeam }; + if (team is not null) + { + team.StageId = Data.StageId; + Lobby.GetInstance().SyncTeam(session.Player.User.Uid); + Rsp.retcode = MpTeamEnterLobbyRsp.Retcode.Succ; + Rsp.StageId = team.StageId; + Rsp.TeamId = team.LeaderUid; + Rsp.TeamName = team.Name; + } + + session.Send(Packet.FromProto(Rsp, CmdId.MpTeamEnterLobbyRsp)); + } + } +} diff --git a/GameServer/Handlers/Three/GetPhotoDataReqHandler.cs b/GameServer/Handlers/Three/GetPhotoDataReqHandler.cs new file mode 100644 index 0000000..15af27d --- /dev/null +++ b/GameServer/Handlers/Three/GetPhotoDataReqHandler.cs @@ -0,0 +1,19 @@ +using Common.Resources.Proto; + +namespace PemukulPaku.GameServer.Handlers.Three +{ + [PacketCmdId(CmdId.GetPhotoDataReq)] + internal class GetPhotoDataReqHandler : IPacketHandler + { + public void Handle(Session session, Packet packet) + { + GetPhotoDataReq Data = packet.GetDecodedBody(); + + session.Send(Packet.FromProto(new GetPhotoDataRsp() + { + retcode = GetPhotoDataRsp.Retcode.Succ, + Type = Data.Type + }, CmdId.GetPhotoDataRsp)); + } + } +} diff --git a/GameServer/Handlers/Three/GetTeamListReqHandler.cs b/GameServer/Handlers/Three/GetTeamListReqHandler.cs new file mode 100644 index 0000000..4d03648 --- /dev/null +++ b/GameServer/Handlers/Three/GetTeamListReqHandler.cs @@ -0,0 +1,16 @@ +using Common.Resources.Proto; + +namespace PemukulPaku.GameServer.Handlers.Three +{ + [PacketCmdId(CmdId.GetTeamListReq)] + internal class GetTeamListReqHandler : IPacketHandler + { + public void Handle(Session session, Packet packet) + { + GetTeamListReq Data = packet.GetDecodedBody(); + GetTeamListRsp Rsp = new() { retcode = GetTeamListRsp.Retcode.Succ }; + + session.Send(Packet.FromProto(Rsp, CmdId.GetTeamListRsp)); + } + } +} diff --git a/GameServer/Handlers/Two/GetExtraStoryChallengeModeDataReqHandler.cs b/GameServer/Handlers/Two/GetExtraStoryChallengeModeDataReqHandler.cs new file mode 100644 index 0000000..3990395 --- /dev/null +++ b/GameServer/Handlers/Two/GetExtraStoryChallengeModeDataReqHandler.cs @@ -0,0 +1,21 @@ +using Common.Resources.Proto; + +namespace PemukulPaku.GameServer.Handlers.Two +{ + [PacketCmdId(CmdId.GetExtraStoryChallengeModeDataReq)] + internal class GetExtraStoryChallengeModeDataReqHandler : IPacketHandler + { + public void Handle(Session session, Packet packet) + { + GetExtraStoryChallengeModeDataReq Data = packet.GetDecodedBody(); + + session.Send(Packet.FromProto(new GetExtraStoryChallengeModeDataRsp() + { + retcode = GetExtraStoryChallengeModeDataRsp.Retcode.Succ, + ChooseDifficulty = 0, + IsCanReset = true, + ChapterId = Data.ChapterId + }, CmdId.GetExtraStoryChallengeModeDataRsp)); + } + } +} diff --git a/GameServer/Handlers/Two/GetOtherPlayerCardDataReqHandler.cs b/GameServer/Handlers/Two/GetOtherPlayerCardDataReqHandler.cs index 0f0799d..2cbb1a9 100644 --- a/GameServer/Handlers/Two/GetOtherPlayerCardDataReqHandler.cs +++ b/GameServer/Handlers/Two/GetOtherPlayerCardDataReqHandler.cs @@ -19,26 +19,8 @@ namespace PemukulPaku.GameServer.Handlers.Two Player? player = Session.FromUid(user.Uid)?.Player; player ??= new(user); - Rsp.CardData = new() - { - Uid = player.User.Uid, - MsgData = new() - { - MsgIndex = 0, - MsgConfig = 1 - }, - OnPhonePendantId = 350005 - }; - Rsp.PlayerData = new() - { - Uid = player.User.Uid, - Nickname = player.User.Nick, - Level = (uint)PlayerLevelData.GetInstance().CalculateLevel(player.User.Exp).Level, - SelfDesc = player.User.SelfDesc, - CustomHeadId = (uint)player.User.CustomHeadId, - FrameId = player.User.FrameId < 200001 ? 200001 : (uint)player.User.FrameId, - LeaderAvatar = player.AvatarList.FirstOrDefault(x => x.AvatarId == player.User.AvatarTeamList.FirstOrDefault()?.AvatarIdLists[0])?.ToDetailData(player.Equipment) ?? new() { AvatarId = 101 } - }; + Rsp.CardData = player.GetCardData(); + Rsp.PlayerData = player.GetDetailData(); } else { diff --git a/GameServer/MPModule/Lobby.cs b/GameServer/MPModule/Lobby.cs new file mode 100644 index 0000000..941d8b5 --- /dev/null +++ b/GameServer/MPModule/Lobby.cs @@ -0,0 +1,116 @@ +using System; +using Common; +using Common.Resources.Proto; + +namespace PemukulPaku.GameServer.MPModule +{ + public class Lobby + { + private static Lobby? Instance; + public readonly Dictionary Teams = new(); + + public static Lobby GetInstance() + { + return Instance ??= new Lobby(); + } + + public Team CreateTeam(Team team) + { + if (Teams.GetValueOrDefault(team.LeaderUid) is null) + Teams.Add(team.LeaderUid, team); + + SyncTeam(team.LeaderUid); + return team; + } + + public void SyncTeam(uint teamId) + { + Teams.TryGetValue(teamId, out Team? team); + if (team is null) + return; + + MpTeamSyncNotify teamSyncNotify = new() + { + TeamData = new() + { + LeaderUid = team.LeaderUid, + TeamId = teamId, + LobbyEnterType = team.LobbyEnterType, + LobbyStatus = team.LobbyStatus, + MinLevel = team.MinLevel, + MaxLevel = 0, + StageId = team.StageId, + TeamName = team.Name, + Status = MpTeamStatus.TeamStatusInLobby + } + }; + + teamSyncNotify.TeamData.MemberLists.AddRange(team.Members.Select(member => + { + return new MpTeamMember() + { + Index = member.Index, + Uid = member.Session.Player.User.Uid, + MpExp = 0, + Stamina = (uint)member.Session.Player.User.Stamina, + HeadAvatarId = member.Session.Player.GetDetailData().LeaderAvatar.AvatarId, + DressId = member.Session.Player.GetDetailData().LeaderAvatar.DressId, + PunishEndTime = 0, + MemberInfo = new() + { + Card = member.Session.Player.GetCardData(), + Detail = member.Session.Player.GetDetailData() + }, + Status = member.Status, + ClientStatus = member.ClientStatus, + AvatarTrialId = 0, + IsWild = true, + RegionName = Global.config.Gameserver.RegionName, + FrameId = member.Session.Player.GetDetailData().FrameId, + CustomHeadId = member.Session.Player.GetDetailData().CustomHeadId, + NewbieId = 0 + }; + })); + + foreach (Session session in team.Members.Select(x => x.Session)) + { + session.Send(Packet.FromProto(teamSyncNotify, CmdId.MpTeamSyncNotify)); + } + } + } + + public class Team + { + public uint StageId { get; set; } + public uint MinLevel; + public uint LeaderUid; + public LobbyEnterType LobbyEnterType; + public LobbyStatus LobbyStatus = LobbyStatus.LobbyPreparing; + public List Members; + public string Name; + + public Team(uint stageId, uint minLevel, LobbyEnterType lobbyEnterType, in Session leader, string name) + { + StageId = stageId; + MinLevel = minLevel; + LobbyEnterType = lobbyEnterType; + Members = new List { new(leader) }; + LeaderUid = leader.Player.User.Uid; + Name = name; + } + } + + public class TeamMember + { + public Session Session; + public uint Index; + public LobbyClientStatus ClientStatus { get; set; } = LobbyClientStatus.LobbyClientNone; + public LobbyMemberStatus Status { get; set; } = LobbyMemberStatus.LobbyMemberReady; + + public TeamMember(Session session, uint index = 1) + { + Session = session; + Index = index; + } + } +} diff --git a/GameServer/Session.cs b/GameServer/Session.cs index 8dbc6af..7adf0f5 100644 --- a/GameServer/Session.cs +++ b/GameServer/Session.cs @@ -33,50 +33,59 @@ namespace PemukulPaku.GameServer while (Client.Connected) { - Array.Clear(msg, 0, msg.Length); - int len = stream.Read(msg, 0, msg.Length); - - if (len > 0) + try { - List packets = new (); + Array.Clear(msg, 0, msg.Length); + int len = stream.Read(msg, 0, msg.Length); - ReadOnlySpan prefix = new byte[] { 0x01, 0x23, 0x45, 0x67 }; - ReadOnlySpan suffix = new byte[] { 0x89, 0xAB, 0xCD, 0xEF }; - - Span message = msg.AsSpan(); - - for (int offset = 0; offset < message.Length;) + if (len > 0) { - var segment = message[offset..]; - int start = segment.IndexOf(prefix); + List packets = new (); - if (start == -1) - break; + ReadOnlySpan prefix = new byte[] { 0x01, 0x23, 0x45, 0x67 }; + ReadOnlySpan suffix = new byte[] { 0x89, 0xAB, 0xCD, 0xEF }; - int end = segment.IndexOf(suffix); + Span message = msg.AsSpan(); - if (end == -1) - break; - - end += suffix.Length; - - packets.Add(segment[start..end].ToArray()); - offset += end; - } - - foreach (byte[] packet in packets) - { - if (Packet.IsValid(packet)) + for (int offset = 0; offset < message.Length;) { - ProcessPacket(new Packet(packet), true); + var segment = message[offset..]; + int start = segment.IndexOf(prefix); + + if (start == -1) + break; + + int end = segment.IndexOf(suffix); + + if (end == -1) + break; + + end += suffix.Length; + + packets.Add(segment[start..end].ToArray()); + offset += end; } - else + + foreach (byte[] packet in packets) { - c.Error("Invalid packet received:", BitConverter.ToString(packet).Replace("-", "")); + if (Packet.IsValid(packet)) + { + ProcessPacket(new Packet(packet), true); + } + else + { + c.Error("Invalid packet received:", BitConverter.ToString(packet).Replace("-", "")); + } } } } + catch (Exception) + { + break; + } } + + DisconnectProtocol(); } public void KeepAliveLoop() @@ -94,6 +103,11 @@ namespace PemukulPaku.GameServer Thread.Sleep(3000); } + DisconnectProtocol(); + } + + public void DisconnectProtocol() + { Player.SaveAll(); c.Debug("Player data saved to database"); c.Warn($"{Id} disconnected"); @@ -143,6 +157,10 @@ namespace PemukulPaku.GameServer Client.GetStream().Write(packet.Raw, 0, packet.Raw.Length); c.Log(PacketName); } + catch (ObjectDisposedException) + { + DisconnectProtocol(); + } catch (Exception ex) { c.Error($"Failed to send {PacketName}:" + ex.Message); diff --git a/PemukulPaku.csproj b/PemukulPaku.csproj index 117715e..521e433 100644 --- a/PemukulPaku.csproj +++ b/PemukulPaku.csproj @@ -24,4 +24,8 @@ + + + +