| @@ -10,11 +10,12 @@ | |||
| <None Remove="Message2Clients.proto" /> | |||
| <None Remove="Message2Server.proto" /> | |||
| <None Remove="MessageType.proto" /> | |||
| <None Remove="Services.proto" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="FrameRateTask" Version="1.1.2" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.21.9" /> | |||
| <PackageReference Include="Google.Protobuf.Tools" Version="3.21.9" /> | |||
| <PackageReference Include="Grpc" Version="2.46.5" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.5" /> | |||
| <PackageReference Include="Grpc.Tools" Version="2.50.0"> | |||
| @@ -27,6 +28,7 @@ | |||
| <Protobuf Include="Message2Clients.proto" /> | |||
| <Protobuf Include="Message2Server.proto" /> | |||
| <Protobuf Include="MessageType.proto" /> | |||
| <Protobuf Include="Services.proto" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -14,7 +14,7 @@ | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ProjectReference Include="..\Protos\Protos.csproj" /> | |||
| <ProjectReference Include="..\..\dependency\proto\Protos.csproj" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -7,7 +7,7 @@ namespace ClientTest | |||
| { | |||
| public static async Task Main(string[] args) | |||
| { | |||
| Channel channel = new Channel("127.0.0.1:8888", ChannelCredentials.Insecure); | |||
| Channel channel = new Channel("0.0.0.0:8888", ChannelCredentials.Insecure); | |||
| var client = new AvailableService.AvailableServiceClient(channel); | |||
| PlayerMsg playerInfo = new(); | |||
| playerInfo.PlayerId = Convert.ToInt32(args[0]); | |||
| @@ -1,125 +0,0 @@ | |||
| // Message2Client | |||
| syntax = "proto3"; | |||
| package protobuf; | |||
| import "MessageType.proto"; | |||
| import "Message2Server.proto"; | |||
| message MessageOfHuman | |||
| { | |||
| int32 x = 1; | |||
| int32 y = 2; | |||
| int32 speed = 3; | |||
| int32 life = 4; // 本次未倒地前的血量,也即还可以受的伤害 | |||
| int32 hanged_time = 5; // 被挂上的次数 | |||
| double time_until_skill_available = 6; | |||
| PlaceType place = 7; | |||
| PropType prop = 8; | |||
| HumanType human_type = 9; | |||
| int64 guid = 10; | |||
| bool on_chair = 11; // 是否被挂 | |||
| double chair_time = 12; // 被挂的时间 | |||
| bool on_ground = 13; // 是否倒地 | |||
| double ground_time = 14; // 倒地时间 | |||
| int64 player_id = 15; | |||
| int32 view_range = 16; // 视野距离 | |||
| repeated HumanBuffType buff = 17; | |||
| } | |||
| message MessageOfButcher | |||
| { | |||
| int32 x = 1; | |||
| int32 y = 2; | |||
| int32 speed = 3; | |||
| int32 damage = 4; | |||
| double time_until_skill_available = 5; | |||
| PlaceType place = 6; | |||
| PropType prop = 7; | |||
| ButcherType butcher_type = 8; | |||
| int64 guid = 9; | |||
| bool movable = 10; // 是否进入了攻击后摇 | |||
| int64 playerID = 11; | |||
| int32 view_range = 12; // 视野距离 | |||
| repeated ButcherBuffType buff = 13; | |||
| } | |||
| message MessageOfProp // 可拾取道具的信息 | |||
| { | |||
| PropType type = 1; | |||
| int32 x = 2; | |||
| int32 y = 3; | |||
| double facing_direction = 4; | |||
| int64 guid = 5; | |||
| PlaceType place = 6; | |||
| int32 size = 7; | |||
| bool is_moving = 8; | |||
| } | |||
| message MessageOfPickedProp //for Unity,直接继承自THUAI5 | |||
| { | |||
| PropType type = 1; | |||
| int32 x = 2; | |||
| int32 y = 3; | |||
| double facing_direction = 4; | |||
| int64 mapping_id = 5; | |||
| } | |||
| message MessageOfMap | |||
| { | |||
| message Row | |||
| { | |||
| repeated PlaceType col = 1; | |||
| } | |||
| repeated Row row = 2; | |||
| } | |||
| message MessageToClient | |||
| { | |||
| repeated MessageOfHuman human_message = 1; | |||
| repeated MessageOfButcher butcher_message = 2; // 是否真正repeated待定 | |||
| repeated MessageOfProp prop_message = 3; | |||
| MessageOfMap map_massage = 4; | |||
| } | |||
| message MoveRes // 如果打算设计撞墙保留平行速度分量,且需要返回值则可用这个(大概没啥用) | |||
| { | |||
| int64 actual_speed = 1; | |||
| double actual_angle = 2; | |||
| } | |||
| message BoolRes // 用于只需要判断执行操作是否成功的行为,如捡起道具、使用道具 | |||
| { | |||
| bool act_success = 1; | |||
| } | |||
| message MsgRes // 用于获取队友发来的消息 | |||
| { | |||
| bool have_message = 1; // 是否有待接收的消息 | |||
| int64 from_player_id = 2; | |||
| string message_received = 3; | |||
| } | |||
| service AvailableService | |||
| { | |||
| rpc TryConnection(IDMsg) returns(BoolRes); | |||
| // 游戏开局调用一次的服务 | |||
| rpc AddPlayer(PlayerMsg) returns(stream MessageToClient); // 连接上后等待游戏开始,server会定时通过该服务向所有client发送消息。 | |||
| // 游戏过程中玩家执行操作的服务 | |||
| rpc Move(MoveMsg) returns (MoveRes); | |||
| rpc PickProp(PickMsg) returns (BoolRes); | |||
| rpc UseProp(IDMsg) returns (BoolRes); | |||
| rpc UseSkill(IDMsg) returns (BoolRes); | |||
| rpc SendMessage(SendMsg) returns (BoolRes); | |||
| rpc HaveMessage(IDMsg) returns (BoolRes); | |||
| rpc GetMessage(IDMsg) returns (MsgRes); | |||
| rpc FixMachine(stream IDMsg) returns (stream BoolRes); // 若正常修复且未被打断则返回修复成功,位置错误/被打断则返回修复失败,下同 | |||
| rpc SaveHuman(stream IDMsg) returns (stream BoolRes); | |||
| rpc Attack (AttackMsg) returns (BoolRes); | |||
| rpc CarryHuman (IDMsg) returns (BoolRes); | |||
| rpc ReleaseHuman (IDMsg) returns (BoolRes); | |||
| rpc HangHuman (IDMsg) returns (BoolRes); | |||
| rpc Escape (IDMsg) returns (BoolRes); | |||
| } | |||
| @@ -1,62 +0,0 @@ | |||
| // Message2Server | |||
| syntax = "proto3"; | |||
| package protobuf; | |||
| import "MessageType.proto"; | |||
| message PlayerMsg | |||
| { | |||
| int64 player_id = 1; | |||
| PlayerType player_type = 2; | |||
| oneof job_type | |||
| { | |||
| HumanType human_type = 3; | |||
| ButcherType butcher_type = 4; | |||
| } | |||
| } | |||
| message MoveMsg | |||
| { | |||
| int64 player_id = 1; | |||
| double angle = 2; | |||
| int64 time_in_milliseconds = 3; | |||
| } | |||
| message PickMsg | |||
| { | |||
| int64 player_id = 1; | |||
| PropType prop_type = 2; | |||
| } | |||
| message SendMsg | |||
| { | |||
| int64 player_id = 1; | |||
| int64 to_player_id = 2; | |||
| string message = 3; | |||
| } | |||
| message AttackMsg | |||
| { | |||
| int64 player_id = 1; | |||
| double angle = 2; | |||
| } | |||
| message IDMsg | |||
| { | |||
| int64 player_id = 1; | |||
| } | |||
| // 基本继承于THUAI5,为了使发送的信息尽可能不被浪费,暂定不发这类大包。 | |||
| // message MessageToServer | |||
| // { | |||
| // MessageType messageType = 1; | |||
| // int64 playerID = 2; // 消息发送者的playerID | |||
| // PlayerType playerType = 3; | |||
| // HumanType humanType= 4; | |||
| // ButcherType butcherType = 5; | |||
| // double angle = 6; // messageType为Move, Attack时的角度 | |||
| // PropType propType = 7; // messageType为PickProp时要捡起的道具类型,防止多个道具堆叠时出现问题 | |||
| // int64 timeInMilliseconds = 8;//时间参数 | |||
| // int64 ToPlayerID = 9; // messageType为Send时要发送的对象的ID | |||
| // string message = 10; // messageType为Send时发送的消息内容 | |||
| // } | |||
| @@ -1,110 +0,0 @@ | |||
| // MessageType | |||
| syntax = "proto3"; | |||
| package protobuf; | |||
| enum PlaceType // 地图中的所有物件类型 | |||
| { | |||
| NULL_PLACE_TYPE = 0; | |||
| // 地图情况,其中Gate是总体的大门,HiddenGate是地窖 | |||
| LAND = 1; | |||
| WALL = 2; | |||
| GRASS = 3; | |||
| MACHINE = 4; | |||
| GATE = 5; | |||
| HIDDEN_GATE = 6; | |||
| // 待补充有特殊效果的地形 | |||
| } | |||
| enum ShapeType // 形状类型 | |||
| { | |||
| NULL_SHAPE_TYPE = 0; | |||
| CIRCLE = 1; // 人类、屠夫、可拾取道具等为圆形 | |||
| SQUARE = 2; // 地形均为方形 | |||
| } | |||
| enum PropType // 地图中的可拾取道具类型 | |||
| { | |||
| NULL_PROP_TYPE = 0; | |||
| PTYPE1 = 1; | |||
| PTYPE2 = 2; | |||
| PTYPE3 = 3; | |||
| PTYPE4 = 4; | |||
| } | |||
| enum HumanBuffType // 人类可用的增益效果类型 | |||
| { | |||
| NULL_HBUFF_TYPE = 0; | |||
| HBUFFTYPE1 = 1; | |||
| HBUFFTYPE2 = 2; | |||
| HBUFFTYPE3 = 3; | |||
| HBUFFTYPE4 = 4; | |||
| } | |||
| enum ButcherBuffType // 屠夫可用的增益效果类型 | |||
| { | |||
| NULL_BBUFF_TYPE = 0; | |||
| BBUFFTYPE1 = 1; | |||
| BBUFFTYPE2 = 2; | |||
| BBUFFTYPE3 = 3; | |||
| BBUFFTYPE4 = 4; | |||
| } | |||
| // 特别说明:由于Human阵营和Butcher阵营有显著的隔离,且暂定职业、主动技能和被动效果相互绑定,故不按照THUAI5的方式区分ActiveSkillType和PassiveSkillType,而是选择了按照阵营来给不同阵营赋予不同的职业(及技能)。 | |||
| enum PlayerType | |||
| { | |||
| NULL_PLAYER_TYPE = 0; | |||
| HUMAN_PLAYER = 1; | |||
| BUTCHER_PLAYER = 2; | |||
| } | |||
| enum HumanType | |||
| { | |||
| NULL_HUMAN_TYPE = 0; | |||
| HUMANTYPE1 = 1; | |||
| HUMANTYPE2 = 2; | |||
| HUMANTYPE3 = 3; | |||
| HUMANTYPE4 = 4; | |||
| } | |||
| enum ButcherType | |||
| { | |||
| NULL_BUTCHER_TYPE = 0; | |||
| BUTCHERTYPE1 = 1; | |||
| BUTCHERTYPE2 = 2; | |||
| BUTCHERTYPE3 = 3; | |||
| BUTCHERTYPE4 = 4; | |||
| } | |||
| // 取消了大包之后,MessageType的枚举是否有必要保留还有待商榷 | |||
| // enum MessageType | |||
| // { | |||
| // // 公共信息类型 | |||
| // Move = 0; | |||
| // PickProp = 1; | |||
| // UseProp = 2; | |||
| // UseSkill = 3; | |||
| // Map = 4; | |||
| // Send = 5; | |||
| // // 人类限定信息类型 | |||
| // FixMachine = 6; | |||
| // SaveHuman = 7; | |||
| // // 屠夫限定信息类型 | |||
| // Attack = 8; | |||
| // CarryHuman = 9; | |||
| // ReleaseHuman = 10; | |||
| // HangHuman = 11; | |||
| // // 游戏相关信息类型 | |||
| // AddPlayer = 12; | |||
| // InvalidPlayer = 13; | |||
| // ValidPlayer = 14; | |||
| // StartGame = 15; | |||
| // Gaming = 16; | |||
| // EndGame = 17; | |||
| // } | |||
| @@ -18,7 +18,7 @@ namespace Server | |||
| public bool IsGaming | |||
| { | |||
| get => Interlocked.CompareExchange(ref isGaming, 0, 0) != 0; | |||
| set => Interlocked.Exchange(ref isGaming, value ? 1 : 0); | |||
| set => Interlocked.Exchange(ref isGaming, value? 1: 0); | |||
| } | |||
| public MessageToClient GetCopiedGameInfo() | |||
| @@ -32,19 +32,18 @@ namespace Server | |||
| { | |||
| lock (gameInfoLock) | |||
| { | |||
| if (player.PlayerType == PlayerType.NullPlayerType) return; | |||
| if (player.PlayerType == PlayerType.NullPlayerType) | |||
| return; | |||
| if (player.PlayerType == PlayerType.HumanPlayer) | |||
| { | |||
| gameInfo.HumanMessage.Add(new MessageOfHuman() | |||
| { | |||
| gameInfo.HumanMessage.Add(new MessageOfHuman() { | |||
| PlayerId = player.PlayerId | |||
| }); | |||
| return; | |||
| } | |||
| if (player.PlayerType == PlayerType.ButcherPlayer) | |||
| { | |||
| gameInfo.ButcherMessage.Add(new MessageOfButcher() | |||
| { | |||
| gameInfo.ButcherMessage.Add(new MessageOfButcher() { | |||
| PlayerID = player.PlayerId | |||
| }); | |||
| return; | |||
| @@ -66,39 +65,39 @@ namespace Server | |||
| () => IsGaming, | |||
| () => | |||
| { | |||
| lock (gameInfo) | |||
| { | |||
| for (int i = 0; i < gameInfo.HumanMessage.Count; i++) | |||
| { | |||
| if (gameInfo.HumanMessage[i] != null) | |||
| { | |||
| gameInfo.HumanMessage[i].X++; | |||
| gameInfo.HumanMessage[i].Y--; | |||
| } | |||
| } | |||
| for (int i = 0; i < gameInfo.ButcherMessage.Count; i++) | |||
| { | |||
| if (gameInfo.ButcherMessage[i] != null) | |||
| { | |||
| gameInfo.ButcherMessage[i].X--; | |||
| gameInfo.ButcherMessage[i].Y++; | |||
| } | |||
| } | |||
| } | |||
| lock (gameInfo) | |||
| { | |||
| for (int i = 0; i < gameInfo.HumanMessage.Count; i++) | |||
| { | |||
| if (gameInfo.HumanMessage[i] != null) | |||
| { | |||
| gameInfo.HumanMessage[i].X++; | |||
| gameInfo.HumanMessage[i].Y--; | |||
| } | |||
| } | |||
| for (int i = 0; i < gameInfo.ButcherMessage.Count; i++) | |||
| { | |||
| if (gameInfo.ButcherMessage[i] != null) | |||
| { | |||
| gameInfo.ButcherMessage[i].X--; | |||
| gameInfo.ButcherMessage[i].Y++; | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| 100, | |||
| () => | |||
| { | |||
| IsGaming = false; | |||
| waitHandle.Release(); | |||
| return 0; | |||
| IsGaming = false; | |||
| waitHandle.Release(); | |||
| return 0; | |||
| }, | |||
| gameTime | |||
| ).Start(); | |||
| } | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return waitHandle; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -11,7 +11,7 @@ namespace Server | |||
| { | |||
| private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||
| private object gameLock = new(); | |||
| private const int playerNum = 2; | |||
| private const int playerNum = 1; | |||
| private MessageToClient currentGameInfo = new(); | |||
| private Game game = new(); | |||
| public int GameTime => game.GameTime; | |||
| @@ -22,11 +22,11 @@ namespace Server | |||
| var onConnection = new BoolRes(); | |||
| lock (gameLock) | |||
| { | |||
| if (0 <= request.PlayerId && request.PlayerId < playerNum) | |||
| // if (0 <= request.PlayerId && request.PlayerId < playerNum) | |||
| { | |||
| onConnection.ActSuccess = true; | |||
| return Task.FromResult(onConnection); | |||
| } | |||
| } | |||
| } | |||
| onConnection.ActSuccess = false; | |||
| return Task.FromResult(onConnection); | |||
| @@ -53,13 +53,12 @@ namespace Server | |||
| { | |||
| Console.WriteLine("Game starts!"); | |||
| StartGame(); | |||
| } | |||
| } | |||
| } | |||
| do | |||
| { | |||
| semaDict[request.PlayerId].Item1.Wait(); | |||
| if (currentGameInfo != null) | |||
| { | |||
| await responseStream.WriteAsync(currentGameInfo); | |||
| @@ -80,44 +79,42 @@ namespace Server | |||
| 1000, | |||
| () => | |||
| { | |||
| ReportGame(); // 最后发一次消息,唤醒发消息的线程,防止发消息的线程由于有概率处在 Wait 状态而卡住 | |||
| return 0; | |||
| ReportGame(); // 最后发一次消息,唤醒发消息的线程,防止发消息的线程由于有概率处在 Wait 状态而卡住 | |||
| return 0; | |||
| } | |||
| ).Start(); | |||
| }) | |||
| }) | |||
| { IsBackground = true }.Start(); | |||
| new Thread(() => | |||
| { | |||
| waitHandle.Wait(); | |||
| this.endGameSem.Release(); | |||
| }) | |||
| this.endGameSem.Release(); | |||
| }) | |||
| { IsBackground = true }.Start(); | |||
| } | |||
| public void WaitForEnd() | |||
| { | |||
| this.endGameSem.Wait(); | |||
| } | |||
| } | |||
| public void WaitForEnd() | |||
| { | |||
| this.endGameSem.Wait(); | |||
| } | |||
| public void ReportGame() | |||
| { | |||
| currentGameInfo = game.GetCopiedGameInfo(); | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item1.Release(); | |||
| } | |||
| public void ReportGame() | |||
| { | |||
| currentGameInfo = game.GetCopiedGameInfo(); | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item2.Wait(); | |||
| } | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item1.Release(); | |||
| } | |||
| public GameServer() | |||
| { | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item2.Wait(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| public GameServer() | |||
| { | |||
| } | |||
| } | |||
| } | |||
| @@ -14,8 +14,7 @@ namespace Server | |||
| try | |||
| { | |||
| GameServer gameServer = new(); | |||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||
| { | |||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) { | |||
| Services = { AvailableService.BindService(gameServer) }, | |||
| Ports = { new ServerPort("0.0.0.0", 8888, ServerCredentials.Insecure) } | |||
| }; | |||
| @@ -19,7 +19,7 @@ | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ProjectReference Include="..\Protos\Protos.csproj" /> | |||
| <ProjectReference Include="..\..\dependency\proto\Protos.csproj" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -5,9 +5,9 @@ VisualStudioVersion = 17.0.32014.148 | |||
| MinimumVisualStudioVersion = 10.0.40219.1 | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{D033B809-2FB7-4340-B8B4-DDA30D6CA6FF}" | |||
| EndProject | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Protos", "Protos\Protos.csproj", "{EA81EE05-A0E3-4D3E-8D7A-C5CD0C9255CD}" | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientTest", "ClientTest\ClientTest.csproj", "{F3C98717-DD4F-45B8-B0F0-C217E7E2B5D4}" | |||
| EndProject | |||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientTest", "ClientTest\ClientTest.csproj", "{F3C98717-DD4F-45B8-B0F0-C217E7E2B5D4}" | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Protos", "..\dependency\proto\Protos.csproj", "{9ADA1EF8-DF2F-4C2E-9DE2-BC94DF89B44D}" | |||
| EndProject | |||
| Global | |||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
| @@ -19,14 +19,14 @@ Global | |||
| {D033B809-2FB7-4340-B8B4-DDA30D6CA6FF}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {D033B809-2FB7-4340-B8B4-DDA30D6CA6FF}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {D033B809-2FB7-4340-B8B4-DDA30D6CA6FF}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| {EA81EE05-A0E3-4D3E-8D7A-C5CD0C9255CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
| {EA81EE05-A0E3-4D3E-8D7A-C5CD0C9255CD}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {EA81EE05-A0E3-4D3E-8D7A-C5CD0C9255CD}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {EA81EE05-A0E3-4D3E-8D7A-C5CD0C9255CD}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| {F3C98717-DD4F-45B8-B0F0-C217E7E2B5D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
| {F3C98717-DD4F-45B8-B0F0-C217E7E2B5D4}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {F3C98717-DD4F-45B8-B0F0-C217E7E2B5D4}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {F3C98717-DD4F-45B8-B0F0-C217E7E2B5D4}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| {9ADA1EF8-DF2F-4C2E-9DE2-BC94DF89B44D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
| {9ADA1EF8-DF2F-4C2E-9DE2-BC94DF89B44D}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {9ADA1EF8-DF2F-4C2E-9DE2-BC94DF89B44D}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {9ADA1EF8-DF2F-4C2E-9DE2-BC94DF89B44D}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| EndGlobalSection | |||
| GlobalSection(SolutionProperties) = preSolution | |||
| HideSolutionNode = FALSE | |||