| @@ -13,7 +13,7 @@ message MessageOfStudent | |||||
| int32 fail_num = 5; // 挂科的科目数 | int32 fail_num = 5; // 挂科的科目数 | ||||
| double time_until_skill_available = 6; | double time_until_skill_available = 6; | ||||
| PlaceType place = 7; | PlaceType place = 7; | ||||
| PropType prop = 8; | |||||
| repeated PropType prop = 8; | |||||
| StudentType student_type = 9; | StudentType student_type = 9; | ||||
| int64 guid = 10; | int64 guid = 10; | ||||
| StudentState state = 11; | StudentState state = 11; | ||||
| @@ -34,7 +34,7 @@ message MessageOfTricker | |||||
| int32 damage = 4; // 对学生造成的心理伤害 | int32 damage = 4; // 对学生造成的心理伤害 | ||||
| double time_until_skill_available = 5; | double time_until_skill_available = 5; | ||||
| PlaceType place = 6; | PlaceType place = 6; | ||||
| PropType prop = 7; | |||||
| repeated PropType prop = 7; | |||||
| TrickerType tricker_type = 8; | TrickerType tricker_type = 8; | ||||
| int64 guid = 9; | int64 guid = 9; | ||||
| bool movable = 10; // 是否进入了攻击后摇 | bool movable = 10; // 是否进入了攻击后摇 | ||||
| @@ -88,6 +88,37 @@ message MessageOfPickedProp //for Unity,直接继承自THUAI5 | |||||
| int64 mapping_id = 5; | int64 mapping_id = 5; | ||||
| } | } | ||||
| message MessageOfClassroom | |||||
| { | |||||
| int32 x = 1; | |||||
| int32 y = 2; | |||||
| int32 progress = 3; | |||||
| } | |||||
| message MessageOfGate | |||||
| { | |||||
| int32 x = 1; | |||||
| int32 y = 2; | |||||
| int32 progress = 3; | |||||
| } | |||||
| message MessageOfDoor | |||||
| { | |||||
| int32 x = 1; | |||||
| int32 y = 2; | |||||
| bool is_open = 3; | |||||
| } | |||||
| message MessageOfMapObj | |||||
| { | |||||
| oneof message_of_map_obj | |||||
| { | |||||
| MessageOfClassroom classroom_message = 1; | |||||
| MessageOfDoor door_message = 2; | |||||
| MessageOfGate gate_message = 3; | |||||
| } | |||||
| } | |||||
| message MessageOfMap | message MessageOfMap | ||||
| { | { | ||||
| message Row | message Row | ||||
| @@ -95,17 +126,27 @@ message MessageOfMap | |||||
| repeated PlaceType col = 1; | repeated PlaceType col = 1; | ||||
| } | } | ||||
| repeated Row row = 2; | repeated Row row = 2; | ||||
| repeated MessageOfMapObj map_obj_message = 3; | |||||
| } | |||||
| message MessageOfObj | |||||
| { | |||||
| oneof message_of_obj | |||||
| { | |||||
| MessageOfStudent student_message = 1; | |||||
| MessageOfTricker tricker_message = 2; | |||||
| MessageOfProp prop_message = 3; | |||||
| MessageOfBullet bullet_message = 4; | |||||
| MessageOfBombedBullet bombed_bullet_message = 5; | |||||
| } | |||||
| } | } | ||||
| message MessageToClient | message MessageToClient | ||||
| { | { | ||||
| repeated MessageOfStudent student_message = 1; | |||||
| repeated MessageOfTricker tricker_message = 2; // 是否真正repeated待定 | |||||
| repeated MessageOfProp prop_message = 3; | |||||
| repeated MessageOfBullet bullet_message = 4; | |||||
| repeated MessageOfBombedBullet bombed_bullet_message = 5; | |||||
| MessageOfMap map_message = 6; | |||||
| GameState game_state = 7; | |||||
| repeated MessageOfObj obj_message = 1; | |||||
| MessageOfMap map_message = 2; | |||||
| GameState game_state = 3; | |||||
| int32 finished_num = 4; // 完成的科目数 | |||||
| } | } | ||||
| message MoveRes // 如果打算设计撞墙保留平行速度分量,且需要返回值则可用这个(大概没啥用) | message MoveRes // 如果打算设计撞墙保留平行速度分量,且需要返回值则可用这个(大概没啥用) | ||||
| @@ -22,7 +22,7 @@ message MoveMsg | |||||
| int64 time_in_milliseconds = 3; | int64 time_in_milliseconds = 3; | ||||
| } | } | ||||
| message PickMsg | |||||
| message PropMsg | |||||
| { | { | ||||
| int64 player_id = 1; | int64 player_id = 1; | ||||
| PropType prop_type = 2; | PropType prop_type = 2; | ||||
| @@ -23,6 +23,8 @@ enum PlaceType // 地图中的所有物件类型 | |||||
| CLASSROOM = 4; | CLASSROOM = 4; | ||||
| GATE = 5; | GATE = 5; | ||||
| HIDDEN_GATE = 6; | HIDDEN_GATE = 6; | ||||
| WINDOW = 7; | |||||
| DOOR = 8; | |||||
| // 待补充有特殊效果的地形 | // 待补充有特殊效果的地形 | ||||
| } | } | ||||
| @@ -109,6 +111,7 @@ enum GameState | |||||
| { | { | ||||
| NULL_GAME_STATE = 0; | NULL_GAME_STATE = 0; | ||||
| GAME_START = 1; | GAME_START = 1; | ||||
| GAME_RUNNING = 2; | |||||
| GAME_END = 3; | |||||
| STAGE_1 = 2; // 第一阶段:大门没有开 | |||||
| STAGE_2 = 3; // 第二阶段:大门已经开了 | |||||
| GAME_END = 4; | |||||
| } | } | ||||
| @@ -13,7 +13,7 @@ service AvailableService | |||||
| // 游戏过程中玩家执行操作的服务 | // 游戏过程中玩家执行操作的服务 | ||||
| rpc Move (MoveMsg) returns (MoveRes); | rpc Move (MoveMsg) returns (MoveRes); | ||||
| rpc PickProp (PickMsg) returns (BoolRes); | |||||
| rpc PickProp (PropMsg) returns (BoolRes); | |||||
| rpc UseProp (IDMsg) returns (BoolRes); | rpc UseProp (IDMsg) returns (BoolRes); | ||||
| rpc UseSkill (IDMsg) returns (BoolRes); | rpc UseSkill (IDMsg) returns (BoolRes); | ||||
| rpc SendMessage (SendMsg) returns (BoolRes); | rpc SendMessage (SendMsg) returns (BoolRes); | ||||
| @@ -23,5 +23,9 @@ service AvailableService | |||||
| rpc StartHealMate (IDMsg) returns (BoolRes); | rpc StartHealMate (IDMsg) returns (BoolRes); | ||||
| rpc Trick (TrickMsg) returns (BoolRes); // 攻击 | rpc Trick (TrickMsg) returns (BoolRes); // 攻击 | ||||
| rpc Graduate (IDMsg) returns (BoolRes); // 相当于逃跑 | rpc Graduate (IDMsg) returns (BoolRes); // 相当于逃跑 | ||||
| rpc OpenDoor (IDMsg) returns (BoolRes); // 开门 | |||||
| rpc CloseDoor (IDMsg) returns (BoolRes); // 关门 | |||||
| rpc SkipWindow (IDMsg) returns (BoolRes); // 窗户 | |||||
| rpc StartOpenGate (IDMsg) returns (BoolRes); // 开闸门 | |||||
| rpc EndAllAction (IDMsg) returns (BoolRes); // 结束所有动作 | |||||
| } | } | ||||
| @@ -0,0 +1,134 @@ | |||||
| using Communication.Proto; | |||||
| using Google.Protobuf; | |||||
| using System; | |||||
| using System.IO; | |||||
| using System.IO.Compression; | |||||
| namespace Playback | |||||
| { | |||||
| public class FileFormatNotLegalException : Exception | |||||
| { | |||||
| private readonly string fileName; | |||||
| public FileFormatNotLegalException(string fileName) | |||||
| { | |||||
| this.fileName = fileName; | |||||
| } | |||||
| public override string Message => $"The file: " + this.fileName + " is not a legal playback file for THUAI6."; | |||||
| } | |||||
| public class MessageReader : IDisposable | |||||
| { | |||||
| private FileStream? fs; | |||||
| private CodedInputStream cos; | |||||
| private GZipStream gzs; | |||||
| private byte[] buffer; | |||||
| public bool Finished { get; private set; } = false; | |||||
| public readonly uint teamCount; | |||||
| public readonly uint playerCount; | |||||
| const int bufferMaxSize = 1024 * 1024; // 1M | |||||
| public MessageReader(string fileName) | |||||
| { | |||||
| if (!fileName.EndsWith(PlayBackConstant.ExtendedName)) | |||||
| { | |||||
| fileName += PlayBackConstant.ExtendedName; | |||||
| } | |||||
| fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); | |||||
| try | |||||
| { | |||||
| var prefixLen = PlayBackConstant.Prefix.Length; | |||||
| byte[] bt = new byte[prefixLen + sizeof(UInt32) * 2]; | |||||
| fs.Read(bt, 0, bt.Length); | |||||
| for (int i = 0; i < prefixLen; ++i) | |||||
| { | |||||
| if (bt[i] != PlayBackConstant.Prefix[i]) throw new FileFormatNotLegalException(fileName); | |||||
| } | |||||
| teamCount = BitConverter.ToUInt32(bt, prefixLen); | |||||
| playerCount = BitConverter.ToUInt32(bt, prefixLen + sizeof(UInt32)); | |||||
| } | |||||
| catch | |||||
| { | |||||
| throw new FileFormatNotLegalException(fileName); | |||||
| } | |||||
| gzs = new GZipStream(fs, CompressionMode.Decompress); | |||||
| var tmpBuffer = new byte[bufferMaxSize]; | |||||
| var bufferSize = gzs.Read(tmpBuffer); | |||||
| if (bufferSize == 0) | |||||
| { | |||||
| buffer = tmpBuffer; | |||||
| Finished = true; | |||||
| } | |||||
| else if (bufferSize != bufferMaxSize) // 不留空位,防止 CodedInputStream 获取信息错误 | |||||
| { | |||||
| if (bufferSize == 0) | |||||
| { | |||||
| Finished = true; | |||||
| } | |||||
| buffer = new byte[bufferSize]; | |||||
| Array.Copy(tmpBuffer, buffer, bufferSize); | |||||
| } | |||||
| else | |||||
| { | |||||
| buffer = tmpBuffer; | |||||
| } | |||||
| cos = new CodedInputStream(buffer); | |||||
| } | |||||
| public MessageToClient? ReadOne() | |||||
| { | |||||
| beginRead: | |||||
| if (Finished) return null; | |||||
| var pos = cos.Position; | |||||
| try | |||||
| { | |||||
| MessageToClient? msg = new MessageToClient(); | |||||
| cos.ReadMessage(msg); | |||||
| return msg; | |||||
| } | |||||
| catch (InvalidProtocolBufferException) | |||||
| { | |||||
| var leftByte = buffer.Length - pos; // 上次读取剩余的字节 | |||||
| for (int i = 0; i < leftByte; ++i) | |||||
| { | |||||
| buffer[i] = buffer[pos + i]; | |||||
| } | |||||
| var bufferSize = gzs.Read(buffer, (int)leftByte, (int)(buffer.Length - leftByte)) + leftByte; | |||||
| if (bufferSize == leftByte) | |||||
| { | |||||
| Finished = true; | |||||
| return null; | |||||
| } | |||||
| if (bufferSize != buffer.Length) // 不留空位,防止 CodedInputStream 获取信息错误 | |||||
| { | |||||
| var tmpBuffer = new byte[bufferSize]; | |||||
| Array.Copy(buffer, tmpBuffer, bufferSize); | |||||
| buffer = tmpBuffer; | |||||
| } | |||||
| cos = new CodedInputStream(buffer); | |||||
| goto beginRead; | |||||
| } | |||||
| } | |||||
| public void Dispose() | |||||
| { | |||||
| Finished = true; | |||||
| if (fs == null) | |||||
| return; | |||||
| if (fs.CanRead) | |||||
| { | |||||
| fs.Close(); | |||||
| } | |||||
| } | |||||
| ~MessageReader() | |||||
| { | |||||
| Dispose(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,74 @@ | |||||
| using Communication.Proto; | |||||
| using Google.Protobuf; | |||||
| using System; | |||||
| using System.IO; | |||||
| using System.IO.Compression; | |||||
| namespace Playback | |||||
| { | |||||
| public class MessageWriter : IDisposable | |||||
| { | |||||
| private FileStream fs; | |||||
| private CodedOutputStream cos; | |||||
| private MemoryStream ms; | |||||
| private GZipStream gzs; | |||||
| private const int memoryCapacity = 1024 * 1024; // 1M | |||||
| private static void ClearMemoryStream(MemoryStream msToClear) | |||||
| { | |||||
| msToClear.Position = 0; | |||||
| msToClear.SetLength(0); | |||||
| } | |||||
| public MessageWriter(string fileName, uint teamCount, uint playerCount) | |||||
| { | |||||
| if (!fileName.EndsWith(PlayBackConstant.ExtendedName)) | |||||
| { | |||||
| fileName += PlayBackConstant.ExtendedName; | |||||
| } | |||||
| fs = new FileStream(fileName, FileMode.Create, FileAccess.Write); | |||||
| fs.Write(PlayBackConstant.Prefix); // 写入前缀 | |||||
| fs.Write(BitConverter.GetBytes((UInt32)teamCount)); // 写入队伍数 | |||||
| fs.Write(BitConverter.GetBytes((UInt32)playerCount)); // 写入每队的玩家人数 | |||||
| ms = new MemoryStream(memoryCapacity); | |||||
| cos = new CodedOutputStream(ms); | |||||
| gzs = new GZipStream(fs, CompressionMode.Compress); | |||||
| } | |||||
| public void WriteOne(MessageToClient msg) | |||||
| { | |||||
| cos.WriteMessage(msg); | |||||
| if (ms.Length > memoryCapacity) | |||||
| Flush(); | |||||
| } | |||||
| public void Flush() | |||||
| { | |||||
| if (fs.CanWrite) | |||||
| { | |||||
| cos.Flush(); | |||||
| gzs.Write(ms.GetBuffer(), 0, (int)ms.Length); | |||||
| ClearMemoryStream(ms); | |||||
| fs.Flush(); | |||||
| } | |||||
| } | |||||
| public void Dispose() | |||||
| { | |||||
| if (fs.CanWrite) | |||||
| { | |||||
| Flush(); | |||||
| cos.Dispose(); | |||||
| gzs.Dispose(); | |||||
| fs.Dispose(); | |||||
| } | |||||
| } | |||||
| ~MessageWriter() | |||||
| { | |||||
| Dispose(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,6 +0,0 @@ | |||||
| namespace Playback | |||||
| { | |||||
| public class Playback | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -1,7 +1,7 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <TargetFramework>net6.0</TargetFramework> | |||||
| <TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks> | |||||
| <ImplicitUsings>enable</ImplicitUsings> | <ImplicitUsings>enable</ImplicitUsings> | ||||
| <Nullable>enable</Nullable> | <Nullable>enable</Nullable> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| @@ -0,0 +1,8 @@ | |||||
| namespace Playback | |||||
| { | |||||
| public static class PlayBackConstant | |||||
| { | |||||
| public static string ExtendedName = ".thuaipb"; | |||||
| public static byte[] Prefix = { (byte)'P', (byte)'B', 6, 0 }; // 文件前缀,用于标识文件类型,版本号为6 | |||||
| } | |||||
| } | |||||