| @@ -13,7 +13,7 @@ message MessageOfStudent | |||
| int32 fail_num = 5; // 挂科的科目数 | |||
| double time_until_skill_available = 6; | |||
| PlaceType place = 7; | |||
| PropType prop = 8; | |||
| repeated PropType prop = 8; | |||
| StudentType student_type = 9; | |||
| int64 guid = 10; | |||
| StudentState state = 11; | |||
| @@ -34,7 +34,7 @@ message MessageOfTricker | |||
| int32 damage = 4; // 对学生造成的心理伤害 | |||
| double time_until_skill_available = 5; | |||
| PlaceType place = 6; | |||
| PropType prop = 7; | |||
| repeated PropType prop = 7; | |||
| TrickerType tricker_type = 8; | |||
| int64 guid = 9; | |||
| bool movable = 10; // 是否进入了攻击后摇 | |||
| @@ -88,6 +88,37 @@ message MessageOfPickedProp //for Unity,直接继承自THUAI5 | |||
| 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 Row | |||
| @@ -95,17 +126,27 @@ message MessageOfMap | |||
| repeated PlaceType col = 1; | |||
| } | |||
| 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 | |||
| { | |||
| 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 // 如果打算设计撞墙保留平行速度分量,且需要返回值则可用这个(大概没啥用) | |||
| @@ -22,7 +22,7 @@ message MoveMsg | |||
| int64 time_in_milliseconds = 3; | |||
| } | |||
| message PickMsg | |||
| message PropMsg | |||
| { | |||
| int64 player_id = 1; | |||
| PropType prop_type = 2; | |||
| @@ -23,6 +23,8 @@ enum PlaceType // 地图中的所有物件类型 | |||
| CLASSROOM = 4; | |||
| GATE = 5; | |||
| HIDDEN_GATE = 6; | |||
| WINDOW = 7; | |||
| DOOR = 8; | |||
| // 待补充有特殊效果的地形 | |||
| } | |||
| @@ -109,6 +111,7 @@ enum GameState | |||
| { | |||
| NULL_GAME_STATE = 0; | |||
| 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 PickProp (PickMsg) returns (BoolRes); | |||
| rpc PickProp (PropMsg) returns (BoolRes); | |||
| rpc UseProp (IDMsg) returns (BoolRes); | |||
| rpc UseSkill (IDMsg) returns (BoolRes); | |||
| rpc SendMessage (SendMsg) returns (BoolRes); | |||
| @@ -23,5 +23,9 @@ service AvailableService | |||
| rpc StartHealMate (IDMsg) returns (BoolRes); | |||
| rpc Trick (TrickMsg) 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"> | |||
| <PropertyGroup> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks> | |||
| <ImplicitUsings>enable</ImplicitUsings> | |||
| <Nullable>enable</Nullable> | |||
| </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 | |||
| } | |||
| } | |||