Browse Source

Merge branch 'eesast:dev' into new

tags/0.1.0
shangfengh GitHub 2 years ago
parent
commit
415a9739b3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 717 additions and 209 deletions
  1. +51
    -9
      dependency/proto/Message2Clients.proto
  2. +1
    -1
      dependency/proto/Message2Server.proto
  3. +5
    -2
      dependency/proto/MessageType.proto
  4. +6
    -2
      dependency/proto/Services.proto
  5. +11
    -9
      logic/ClientTest/Program.cs
  6. +1
    -1
      logic/GameClass/GameObj/Character/Character.SkillManager.cs
  7. +1
    -1
      logic/GameClass/GameObj/Character/Character.Student.cs
  8. +1
    -0
      logic/Gaming/Game.cs
  9. +297
    -112
      logic/Server/CopyInfo.cs
  10. +118
    -65
      logic/Server/GameServer.cs
  11. +8
    -0
      logic/Server/Properties/launchSettings.json
  12. +134
    -0
      playback/Playback/MessageReader.cs
  13. +74
    -0
      playback/Playback/MessageWriter.cs
  14. +0
    -6
      playback/Playback/Playback.cs
  15. +1
    -1
      playback/Playback/Playback.csproj
  16. +8
    -0
      playback/Playback/PlaybackConstant.cs

+ 51
- 9
dependency/proto/Message2Clients.proto View File

@@ -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; // 是否进入了攻击后摇
@@ -78,6 +78,7 @@ message MessageOfProp // 可拾取道具的信息
bool is_moving = 8;
}


message MessageOfPickedProp //for Unity,直接继承自THUAI5
{
PropType type = 1;
@@ -87,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
@@ -94,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 // 如果打算设计撞墙保留平行速度分量,且需要返回值则可用这个(大概没啥用)


+ 1
- 1
dependency/proto/Message2Server.proto View File

@@ -22,7 +22,7 @@ message MoveMsg
int64 time_in_milliseconds = 3;
}

message PickMsg
message PropMsg
{
int64 player_id = 1;
PropType prop_type = 2;


+ 5
- 2
dependency/proto/MessageType.proto View File

@@ -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;
}

+ 6
- 2
dependency/proto/Services.proto View File

@@ -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); // 结束所有动作
}

+ 11
- 9
logic/ClientTest/Program.cs View File

@@ -7,27 +7,29 @@ namespace ClientTest
{
public static async Task Main(string[] args)
{
Channel channel = new Channel("0.0.0.0:8888", ChannelCredentials.Insecure);
Thread.Sleep(3000);
Channel channel = new Channel("127.0.0.1:8888", ChannelCredentials.Insecure);
var client = new AvailableService.AvailableServiceClient(channel);
PlayerMsg playerInfo = new();
playerInfo.PlayerId = Convert.ToInt32(args[0]);
playerInfo.PlayerType = (PlayerType)Convert.ToInt32(args[1]);
playerInfo.PlayerId = 0;
playerInfo.PlayerType = PlayerType.StudentPlayer;
playerInfo.StudentType = StudentType.NullStudentType;
var call = client.AddPlayer(playerInfo);
while (await call.ResponseStream.MoveNext())
{
var currentGameInfo = call.ResponseStream.Current;
if (playerInfo.PlayerType == PlayerType.HumanPlayer)
if (playerInfo.PlayerType == PlayerType.StudentPlayer)
{
for (int i = 0; i < currentGameInfo.HumanMessage.Count; i++)
for (int i = 0; i < currentGameInfo.StudentMessage.Count; i++)
{
Console.WriteLine($"Human is at ({currentGameInfo.HumanMessage[i].X}, {currentGameInfo.HumanMessage[i].Y})");
Console.WriteLine($"Human is at ({currentGameInfo.StudentMessage[i].X}, {currentGameInfo.StudentMessage[i].Y})");
}
}
if (playerInfo.PlayerType == PlayerType.ButcherPlayer)
if (playerInfo.PlayerType == PlayerType.TrickerPlayer)
{
for (int i = 0; i < currentGameInfo.ButcherMessage.Count; i++)
for (int i = 0; i < currentGameInfo.TrickerMessage.Count; i++)
{
Console.WriteLine($"Butcher is at ({currentGameInfo.ButcherMessage[i].X}, {currentGameInfo.ButcherMessage[i].Y})");
Console.WriteLine($"Butcher is at ({currentGameInfo.TrickerMessage[i].X}, {currentGameInfo.TrickerMessage[i].Y})");
}
}
}


+ 1
- 1
logic/GameClass/GameObj/Character/Character.SkillManager.cs View File

@@ -100,4 +100,4 @@ namespace GameClass.GameObj
Debugger.Output(this, "constructed!");
}
}
}
}

+ 1
- 1
logic/GameClass/GameObj/Character/Character.Student.cs View File

@@ -100,4 +100,4 @@ namespace GameClass.GameObj
this.fixSpeed = ((IStudent)Occupation).FixSpeed;
}
}
}
}

+ 1
- 0
logic/Gaming/Game.cs View File

@@ -39,6 +39,7 @@ namespace Gaming

XY pos = gameMap.BirthPointList[playerInitInfo.birthPointIndex];
// Console.WriteLine($"x,y: {pos.x},{pos.y}");

Character newPlayer = (GameData.IsGhost(playerInitInfo.characterType)) ? new Ghost(pos, GameData.characterRadius, gameMap.GetPlaceType(pos), playerInitInfo.characterType) : new Student(pos, GameData.characterRadius, gameMap.GetPlaceType(pos), playerInitInfo.characterType);
gameMap.GameObjLockDict[GameObjType.Character].EnterWriteLock();
try


+ 297
- 112
logic/Server/CopyInfo.cs View File

@@ -1,187 +1,366 @@
using Protobuf;
using System.Collections.Generic;
using GameClass.GameObj;
/*
namespace Server
{

public static class CopyInfo
{
public static MessageToClient.Types.GameObjMessage? Auto(GameObj gameObj)
// 下面赋值为0的大概率是还没写完 2023-03-03
private static MessageOfStudent Human(Character player)
{
if (gameObj.Type == Preparation.Utility.GameObjType.Character)
return Player((Character)gameObj);
else if (gameObj.Type == Preparation.Utility.GameObjType.Bullet)
return Bullet((Bullet)gameObj);
else if (gameObj.Type == Preparation.Utility.GameObjType.Prop)
return Prop((Prop)gameObj);
else if (gameObj.Type == Preparation.Utility.GameObjType.BombedBullet)
return BombedBullet((BombedBullet)gameObj);
else if (gameObj.Type == Preparation.Utility.GameObjType.PickedProp)
return PickedProp((PickedProp)gameObj);
else return null; //先写着防报错
MessageOfStudent msg = new MessageOfStudent();
if (player.IsGhost()) return null;

msg.X = player.Position.x;
msg.Y = player.Position.y;
msg.Speed = player.MoveSpeed;
msg.Determination = player.HP;
msg.FailNum = 0;
msg.TimeUntilSkillAvailable = 0;
//msg.Place = 0; 下面写了
msg.StudentType = StudentType.NullStudentType; // 下面写
msg.Guid = 0;
msg.State = StudentState.NullStatus;
msg.FailTime = 0;
msg.EmoTime = 0;
msg.PlayerId = 0;
msg.ViewRange = 0;
msg.Radius = 0;
//msg.Buff[0] = StudentBuffType.NullSbuffType; 下面写了

/* THUAI5中的内容
msg.BulletNum = player.BulletNum;
msg.CanMove = player.CanMove;
msg.CD = player.CD;
msg.GemNum = player.GemNum;
msg.Guid = player.ID;
msg.IsResetting = player.IsResetting;
msg.LifeNum = player.DeathCount + 1;
msg.Radius = player.Radius;
msg.TimeUntilCommonSkillAvailable = player.TimeUntilCommonSkillAvailable;
msg.TeamID = player.TeamID;
msg.PlayerID = player.PlayerID;
msg.IsInvisible = player.IsInvisible;
msg.FacingDirection = player.FacingDirection;

//应该要发队伍分数,这里先发个人分数
msg.MessageOfHuman.Score = player.Score;

//这条暂时没啥用
msg.MessageOfHuman.TimeUntilUltimateSkillAvailable = 0;

msg.MessageOfHuman.Vampire = player.Vampire;*/

foreach (KeyValuePair<Preparation.Utility.BuffType, bool> kvp in player.Buff)
{
if (kvp.Value)
{
switch (kvp.Key) // StudentBuffType具体内容待定
{
case Preparation.Utility.BuffType.Spear:
msg.Buff.Add(StudentBuffType.NullSbuffType);
break;
case Preparation.Utility.BuffType.AddLIFE:
msg.Buff.Add(StudentBuffType.NullSbuffType);
break;
case Preparation.Utility.BuffType.Shield:
msg.Buff.Add(StudentBuffType.NullSbuffType);
break;
case Preparation.Utility.BuffType.AddSpeed:
msg.Buff.Add(StudentBuffType.NullSbuffType);
break;
default:
break;
}
}
}
/*switch (player.Place)
{
case Preparation.Utility.PlaceType.Land:
msg.Place = PlaceType.Land;
break;
case Preparation.Utility.PlaceType.Grass1:
msg.Place = PlaceType.Grass;
break;
case Preparation.Utility.PlaceType.Grass2:
msg.Place = PlaceType.Grass;
break;
case Preparation.Utility.PlaceType.Grass3:
msg.Place = PlaceType.Grass;
break;
// case Preparation.Utility.PlaceType.Invisible:
// msg.MessageOfHuman.Place = Communication.Proto.PlaceType.Invisible;
// break;
default:
msg.Place = PlaceType.NullPlaceType;
break;
}*/

//Character的储存方式可能得改,用enum type存道具和子弹,不应该用对象
//现在懒得改了,有时间再重整一波
/*if (player.PropInventory == null)
msg.Prop = PropType.NullPropType;
else
{
switch (player.PropInventory.GetPropType())
{
case Preparation.Utility.PropType.Gem:
msg.Prop = PropType.NullPropType;
break;
case Preparation.Utility.PropType.addLIFE:
msg.MessageOfHuman.Prop = Communication.Proto.PropType.AddLife;
break;
case Preparation.Utility.PropType.addSpeed:
msg.MessageOfHuman.Prop = Communication.Proto.PropType.AddSpeed;
break;
case Preparation.Utility.PropType.Shield:
msg.MessageOfHuman.Prop = Communication.Proto.PropType.Shield;
break;
case Preparation.Utility.PropType.Spear:
msg.MessageOfHuman.Prop = Communication.Proto.PropType.Spear;
break;
default:
msg.Prop = PropType.NullPropType;
break;
}
}*/
/*switch (player.PassiveSkillType) 需要对接一下,proto里似乎没有这个
{
case Preparation.Utility.PassiveSkillType.RecoverAfterBattle:
msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.RecoverAfterBattle;
break;
case Preparation.Utility.PassiveSkillType.SpeedUpWhenLeavingGrass:
msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.SpeedUpWhenLeavingGrass;
break;
case Preparation.Utility.PassiveSkillType.Vampire:
msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.Vampire;
break;
default:
msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.NullPassiveSkillType;
break;
}

switch (player.CommonSkillType)
{
case Preparation.Utility.ActiveSkillType.BecomeAssassin:
msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeAssassin;
break;
case Preparation.Utility.ActiveSkillType.BecomeVampire:
msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeVampire;
break;
case Preparation.Utility.ActiveSkillType.NuclearWeapon:
msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.NuclearWeapon;
break;
case Preparation.Utility.ActiveSkillType.SuperFast:
msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.SuperFast;
break;
default:
msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.NullActiveSkillType;
break;
}

switch (player.BulletOfPlayer)
{
case Preparation.Utility.BulletType.AtomBomb:
msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.AtomBomb;
break;
case Preparation.Utility.BulletType.OrdinaryBullet:
msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.OrdinaryBullet;
break;
case Preparation.Utility.BulletType.FastBullet:
msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.FastBullet;
break;
case Preparation.Utility.BulletType.LineBullet:
msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.LineBullet;
break;
default:
msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.NullBulletType;
break;
}*/

return msg;
}

private static MessageToClient.Types.GameObjMessage Player(Character player)
private static MessageOfTricker Butcher(Character player)
{
MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage();
msg.MessageOfCharacter = new MessageOfCharacter();
MessageOfTricker msg = new MessageOfTricker();
if (!player.IsGhost()) return null;

msg.X = player.Position.x;
msg.Y = player.Position.y;
msg.Speed = player.MoveSpeed;
msg.Damage = 0;
msg.TimeUntilSkillAvailable = 0;
//msg.Place = 0; 下面写了
//msg.Prop = PropType.NullPropType; // 下面写
msg.TrickerType = TrickerType.NullTrickerType; // 下面写
msg.Guid = 0;
msg.Movable = false;
msg.PlayerId = 0;
msg.ViewRange = 0;
msg.Radius = 0;
//msg.Buff[0] = ButcherBuffType.NullSbuffType; 下面写了

msg.MessageOfCharacter.X = player.Position.x;
msg.MessageOfCharacter.Y = player.Position.y;
msg.MessageOfCharacter.AttackRange = player.AttackRange;
msg.MessageOfCharacter.BulletNum = player.BulletNum;
msg.MessageOfCharacter.CanMove = player.CanMove;
msg.MessageOfCharacter.CD = player.CD;
msg.MessageOfCharacter.GemNum = player.GemNum;
msg.MessageOfCharacter.Guid = player.ID;
msg.MessageOfCharacter.IsResetting = player.IsResetting;
msg.MessageOfCharacter.Life = player.HP;
msg.MessageOfCharacter.LifeNum = player.DeathCount + 1;
msg.MessageOfCharacter.Radius = player.Radius;
msg.MessageOfCharacter.Speed = player.MoveSpeed;
msg.MessageOfCharacter.TimeUntilCommonSkillAvailable = player.TimeUntilCommonSkillAvailable;
msg.MessageOfCharacter.TeamID = player.TeamID;
msg.MessageOfCharacter.PlayerID = player.PlayerID;
msg.MessageOfCharacter.IsInvisible = player.IsInvisible;
msg.MessageOfCharacter.FacingDirection = player.FacingDirection;
/* THUAI5中的内容
msg.BulletNum = player.BulletNum;
msg.CanMove = player.CanMove;
msg.CD = player.CD;
msg.GemNum = player.GemNum;
msg.Guid = player.ID;
msg.IsResetting = player.IsResetting;
msg.LifeNum = player.DeathCount + 1;
msg.Radius = player.Radius;
msg.TimeUntilCommonSkillAvailable = player.TimeUntilCommonSkillAvailable;
msg.TeamID = player.TeamID;
msg.PlayerID = player.PlayerID;
msg.IsInvisible = player.IsInvisible;
msg.FacingDirection = player.FacingDirection;

//应该要发队伍分数,这里先发个人分数
msg.MessageOfCharacter.Score = player.Score;
msg.MessageOfHuman.Score = player.Score;

//这条暂时没啥用
msg.MessageOfCharacter.TimeUntilUltimateSkillAvailable = 0;
msg.MessageOfHuman.TimeUntilUltimateSkillAvailable = 0;

msg.MessageOfHuman.Vampire = player.Vampire;*/

msg.MessageOfCharacter.Vampire = player.Vampire;
foreach (KeyValuePair<Preparation.Utility.BuffType, bool> kvp in player.Buff)
{
if (kvp.Value)
{
switch(kvp.Key)
switch (kvp.Key) // ButcherBuffType具体内容待定
{
case Preparation.Utility.BuffType.Spear:
msg.MessageOfCharacter.Buff.Add(BuffType.SpearBuff);
msg.Buff.Add(TrickerBuffType.NullTbuffType);
break;
case Preparation.Utility.BuffType.AddLIFE:
msg.MessageOfCharacter.Buff.Add(BuffType.AddLife);
msg.Buff.Add(TrickerBuffType.NullTbuffType);
break;
case Preparation.Utility.BuffType.Shield:
msg.MessageOfCharacter.Buff.Add(BuffType.ShieldBuff);
msg.Buff.Add(TrickerBuffType.NullTbuffType);
break;
case Preparation.Utility.BuffType.AddSpeed:
msg.MessageOfCharacter.Buff.Add(BuffType.MoveSpeed);
msg.Buff.Add(TrickerBuffType.NullTbuffType);
break;
default:
break;
}
}
}
switch (player.Place)
/*switch (player.Place)
{
case Preparation.Utility.PlaceType.Null:
msg.MessageOfCharacter.Place = Communication.Proto.PlaceType.Null;
case Preparation.Utility.PlaceType.Land:
msg.Place = PlaceType.Land;
break;
case Preparation.Utility.PlaceType.Grass:
msg.MessageOfCharacter.Place = Communication.Proto.PlaceType.Grass;
case Preparation.Utility.PlaceType.Grass1:
msg.Place = PlaceType.Grass;
break;
case Preparation.Utility.PlaceType.Grass:
msg.MessageOfCharacter.Place = Communication.Proto.PlaceType.Grass;
case Preparation.Utility.PlaceType.Grass2:
msg.Place = PlaceType.Grass;
break;
case Preparation.Utility.PlaceType.Grass:
msg.MessageOfCharacter.Place = Communication.Proto.PlaceType.Grass;
case Preparation.Utility.PlaceType.Grass3:
msg.Place = PlaceType.Grass;
break;
// case Preparation.Utility.PlacccceType.Invisible:
// msg.MessageOfCharacter.Place = Communication.Proto.PlacccceType.Invisible;
// break;
// case Preparation.Utility.PlaceType.Invisible:
// msg.MessageOfHuman.Place = Communication.Proto.PlaceType.Invisible;
// break;
default:
msg.MessageOfCharacter.Place = Communication.Proto.PlacccceType.NullPlaceType;
msg.Place = PlaceType.NullPlaceType;
break;
}
}*/

//Character的储存方式可能得改,用enum type存道具和子弹,不应该用对象
//现在懒得改了,有时间再重整一波
if (player.PropInventory == null)
msg.MessageOfCharacter.Prop = Communication.Proto.PropType.NullPropType;
/*if (player.PropInventory == null)
msg.Prop = PropType.NullPropType;
else
{
switch (player.PropInventory.GetPropType())
{
case Preparation.Utility.PropType.Gem:
msg.MessageOfCharacter.Prop = Communication.Proto.PropType.Gem;
msg.Prop = PropType.NullPropType;
break;
case Preparation.Utility.PropType.addLIFE:
msg.MessageOfCharacter.Prop = Communication.Proto.PropType.AddLife;
msg.MessageOfHuman.Prop = Communication.Proto.PropType.AddLife;
break;
case Preparation.Utility.PropType.addSpeed:
msg.MessageOfCharacter.Prop = Communication.Proto.PropType.AddSpeed;
msg.MessageOfHuman.Prop = Communication.Proto.PropType.AddSpeed;
break;
case Preparation.Utility.PropType.Shield:
msg.MessageOfCharacter.Prop = Communication.Proto.PropType.Shield;
msg.MessageOfHuman.Prop = Communication.Proto.PropType.Shield;
break;
case Preparation.Utility.PropType.Spear:
msg.MessageOfCharacter.Prop = Communication.Proto.PropType.Spear;
msg.MessageOfHuman.Prop = Communication.Proto.PropType.Spear;
break;
default:
msg.MessageOfCharacter.Prop = Communication.Proto.PropType.NullPropType;
msg.Prop = PropType.NullPropType;
break;
}
}
switch (player.PassiveSkillType)
}*/
/*switch (player.PassiveSkillType) 需要对接一下,proto里似乎没有这个
{
case Preparation.Utility.PassiveSkillType.RecoverAfterBattle:
msg.MessageOfCharacter.PassiveSkillType = Communication.Proto.PassiveSkillType.RecoverAfterBattle;
msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.RecoverAfterBattle;
break;
case Preparation.Utility.PassiveSkillType.SpeedUpWhenLeavingGrass:
msg.MessageOfCharacter.PassiveSkillType = Communication.Proto.PassiveSkillType.SpeedUpWhenLeavingGrass;
msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.SpeedUpWhenLeavingGrass;
break;
case Preparation.Utility.PassiveSkillType.Vampire:
msg.MessageOfCharacter.PassiveSkillType = Communication.Proto.PassiveSkillType.Vampire;
msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.Vampire;
break;
default:
msg.MessageOfCharacter.PassiveSkillType = Communication.Proto.PassiveSkillType.NullPassiveSkillType;
msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.NullPassiveSkillType;
break;
}

switch (player.CommonSkillType)
{
case Preparation.Utility.ActiveSkillType.BecomeAssassin:
msg.MessageOfCharacter.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeAssassin;
msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeAssassin;
break;
case Preparation.Utility.ActiveSkillType.BecomeVampire:
msg.MessageOfCharacter.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeVampire;
msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeVampire;
break;
case Preparation.Utility.ActiveSkillType.NuclearWeapon:
msg.MessageOfCharacter.ActiveSkillType = Communication.Proto.ActiveSkillType.NuclearWeapon;
msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.NuclearWeapon;
break;
case Preparation.Utility.ActiveSkillType.SuperFast:
msg.MessageOfCharacter.ActiveSkillType = Communication.Proto.ActiveSkillType.SuperFast;
msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.SuperFast;
break;
default:
msg.MessageOfCharacter.ActiveSkillType = Communication.Proto.ActiveSkillType.NullActiveSkillType;
msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.NullActiveSkillType;
break;
}

switch (player.BulletOfPlayer)
{
case Preparation.Utility.BulletType.AtomBomb:
msg.MessageOfCharacter.BulletType = Communication.Proto.BulletType.AtomBomb;
msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.AtomBomb;
break;
case Preparation.Utility.BulletType.OrdinaryBullet:
msg.MessageOfCharacter.BulletType = Communication.Proto.BulletType.OrdinaryBullet;
msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.OrdinaryBullet;
break;
case Preparation.Utility.BulletType.FastBullet:
msg.MessageOfCharacter.BulletType = Communication.Proto.BulletType.FastBullet;
msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.FastBullet;
break;
case Preparation.Utility.BulletType.LineBullet:
msg.MessageOfCharacter.BulletType = Communication.Proto.BulletType.LineBullet;
msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.LineBullet;
break;
default:
msg.MessageOfCharacter.BulletType = Communication.Proto.BulletType.NullBulletType;
msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.NullBulletType;
break;
}
}*/

return msg;
}

private static MessageToClient.Types.GameObjMessage Bullet(Bullet bullet)
/*private static MessageToClient.Types.GameObjMessage Bullet(Bullet bullet)
{
MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage();
msg.MessageOfBullet = new MessageOfBullet();
@@ -229,39 +408,47 @@ namespace Server
break;
}
return msg;
}
}*/

private static MessageToClient.Types.GameObjMessage Prop(Prop prop)
private static MessageOfProp Prop(Prop prop)
{
MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage();
msg.MessageOfProp = new MessageOfProp();
MessageOfProp msg = new MessageOfProp();
//msg.Type = PropType.NullPropType; 下面写
msg.X = prop.Position.x;
msg.Y = prop.Position.y;
msg.FacingDirection = 0;
msg.Guid = 0;
msg.Place = PlaceType.NullPlaceType;
msg.Size = 0;
msg.IsMoving = false;
/* THUAI5中的内容
msg.MessageOfProp.FacingDirection = prop.FacingDirection;
msg.MessageOfProp.Guid = prop.ID;
msg.MessageOfProp.IsMoving = prop.IsMoving;
msg.MessageOfProp.IsMoving = prop.IsMoving;*/

switch (prop.GetPropType())
{
case Preparation.Utility.PropType.Gem:
msg.MessageOfProp.Type = Communication.Proto.PropType.Gem;
/*case Preparation.Utility.PropType.Gem:
msg.Type = PropType.Gem;
break;
case Preparation.Utility.PropType.addLIFE:
msg.MessageOfProp.Type = Communication.Proto.PropType.AddLife;
msg.Type = PropType.AddLife;
break;
case Preparation.Utility.PropType.addSpeed:
msg.MessageOfProp.Type = Communication.Proto.PropType.AddSpeed;
msg.Type = PropType.AddSpeed;
break;
case Preparation.Utility.PropType.Shield:
msg.MessageOfProp.Type = Communication.Proto.PropType.Shield;
msg.Type = PropType.Shield;
break;
case Preparation.Utility.PropType.Spear:
msg.MessageOfProp.Type = Communication.Proto.PropType.Spear;
break;
msg.Type = PropType.Spear;
break;*/
default:
msg.MessageOfProp.Type = Communication.Proto.PropType.NullPropType;
msg.Type = PropType.NullPropType;
break;
}
msg.MessageOfProp.X = prop.Position.x;
msg.MessageOfProp.Y = prop.Position.y;
if(prop is Gem)

/*if(prop is Gem)
{
msg.MessageOfProp.Size = ((Gem)prop).Size;
}
@@ -286,20 +473,19 @@ namespace Server
default:
msg.MessageOfProp.Place = Communication.Proto.PlacccceType.NullPlaceType;
break;
}
}*/
return msg;
}

private static MessageToClient.Types.GameObjMessage BombedBullet(BombedBullet bombedBullet)
/*private static MessageOfBombedBullet BombedBullet(BombedBullet bombedBullet)
{
MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage();
msg.MessageOfBombedBullet = new MessageOfBombedBullet();
MessageOfBombedBullet msg = new MessageOfBombedBullet;

msg.MessageOfBombedBullet.FacingDirection = bombedBullet.bulletHasBombed.FacingDirection;
msg.MessageOfBombedBullet.X = bombedBullet.bulletHasBombed.Position.x;
msg.MessageOfBombedBullet.Y = bombedBullet.bulletHasBombed.Position.y;
msg.MessageOfBombedBullet.MappingID = bombedBullet.MappingID;
msg.MessageOfBombedBullet.BombRange = BulletFactory.BulletBombRange(bombedBullet.bulletHasBombed.TypeOfBullet);
msg.FacingDirection = bombedBullet.FacingDirection;
msg.X = bombedBullet.bulletHasBombed.Position.x;
msg.Y = bombedBullet.bulletHasBombed.Position.y;
msg.MappingID = bombedBullet.MappingID;
msg.BombRange = BulletFactory.BulletBombRange(bombedBullet.bulletHasBombed.TypeOfBullet);
switch (bombedBullet.bulletHasBombed.TypeOfBullet)
{
case Preparation.Utility.BulletType.OrdinaryBullet:
@@ -319,9 +505,9 @@ namespace Server
break;
}
return msg;
}
}*/

private static MessageToClient.Types.GameObjMessage PickedProp(PickedProp pickedProp)
/*private static MessageToClient.Types.GameObjMessage PickedProp(PickedProp pickedProp)
{
MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage();
msg.MessageOfPickedProp = new MessageOfPickedProp();
@@ -352,7 +538,6 @@ namespace Server
break;
}
return msg;
}
}*/
}
}*/
// 等Preparation完成再写
}

+ 118
- 65
logic/Server/GameServer.cs View File

@@ -6,6 +6,8 @@ using System;
using System.Net.Http.Headers;
using Gaming;
using GameClass.GameObj;
using Preparation.Utility;


namespace Server
{
@@ -13,9 +15,17 @@ 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 SemaphoreSlim endGameSem = new(0);

object gameInfo = new();
private int isGaming = 0;
public bool IsGaming
{
get => Interlocked.CompareExchange(ref isGaming, 0, 0) != 0;
set => Interlocked.Exchange(ref isGaming, value ? 1 : 0);
}
// 以上是测试时用到的定义

protected readonly ArgumentOptions options;
@@ -29,6 +39,46 @@ namespace Server
private readonly Semaphore endGameInfoSema = new(0, 1);
// private MessageWriter? mwr = null;

public SemaphoreSlim StartGameTest()
{
IsGaming = true;
var waitHandle = new SemaphoreSlim(0);

new Thread
(
() =>
{
new FrameRateTaskExecutor<int>
(
() => IsGaming,
() =>
{
lock (gameInfo)
{
/*for (int i = 0; i < gameInfo.GameObjs.Count; i++)
{
if (gameInfo.GameObjs[i].Character != null)
{
gameInfo.GameObjs[i].Character.X++;
gameInfo.GameObjs[i].Character.Y--;
}
}*/
}
},
100,
() =>
{
IsGaming = false;
waitHandle.Release();
return 0;
},
3000//gameTime
).Start();
}
)
{ IsBackground = true }.Start();
return waitHandle;
}
public void StartGame()
{
bool gameState = game.StartGame((int)options.GameTimeInSecond * 1000);
@@ -63,9 +113,7 @@ namespace Server

public void ReportGame()
{
// currentGameInfo = game.GetCopiedGameInfo();
currentGameInfo.HumanMessage[0].X = 1;
currentGameInfo.ButcherMessage[0].X = 1;
//currentGameInfo = null;

foreach (var kvp in semaDict)
{
@@ -77,13 +125,29 @@ namespace Server
kvp.Value.Item2.Wait();
}
}
private uint GetBirthPointIdx(long teamID, long playerID) // 获取出生点位置

private int PlayerTypeToTeamID(PlayerType playerType)
{
return (uint)((teamID * options.PlayerCountPerTeam) + playerID);
if (playerType == PlayerType.StudentPlayer) return 0;
if (playerType == PlayerType.TrickerPlayer) return 1;
return -1;
}
private uint GetBirthPointIdx(PlayerType playerType, long playerID) // 获取出生点位置
{
return (uint)((PlayerTypeToTeamID(playerType) * options.PlayerCountPerTeam) + playerID + 1);
}
private bool ValidPlayerTypeAndPlayerID(PlayerType playerType, long playerID)
{
if (playerType == PlayerType.StudentPlayer && 0 <= playerID && playerID < options.PlayerCountPerTeam)
return true; // 人数待修改
if (playerType == PlayerType.TrickerPlayer && 0 <= playerID && playerID < options.PlayerCountPerTeam)
return true;
return false;
}

public override Task<BoolRes> TryConnection(IDMsg request, ServerCallContext context)
{
Console.WriteLine($"TryConnection ID: {request.PlayerId}");
var onConnection = new BoolRes();
lock (gameLock)
{
@@ -101,6 +165,7 @@ namespace Server
protected readonly object addPlayerLock = new();
public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context)
{
Console.WriteLine($"AddPlayer: {request.PlayerId}");
if (request.PlayerId >= spectatorMinPlayerID && request.PlayerType == PlayerType.NullPlayerType)
{
// 观战模式
@@ -115,23 +180,23 @@ namespace Server

if (game.GameMap.Timer.IsGaming)
return;
/*if (!ValidTeamIDAndPlayerID(msg.TeamID, msg.PlayerID)) //玩家id是否正确
return;
if (communicationToGameID[msg.TeamID, msg.PlayerID] != GameObj.invalidID) //是否已经添加了该玩家
return false;
if (!ValidPlayerTypeAndPlayerID(request.PlayerType, request.PlayerId)) //玩家id是否正确
return;
//if (communicationToGameID[PlayerTypeToTeamID(request.PlayerType), request.PlayerId] != GameObj.invalidID) //是否已经添加了该玩家
//return;

Preparation.Utility.CharacterType characterType = Preparation.Utility.CharacterType.Athlete; // 待修改

Preparation.Utility.PassiveSkillType passiveSkill;
*/
lock (addPlayerLock)
{
/*Game.PlayerInitInfo playerInitInfo = new(GetBirthPointIdx(msg.TeamID, msg.PlayerID), msg.TeamID, msg.PlayerID, passiveSkill, commonSkill);
Game.PlayerInitInfo playerInitInfo = new(GetBirthPointIdx(request.PlayerType, request.PlayerId), PlayerTypeToTeamID(request.PlayerType), request.PlayerId, characterType);
long newPlayerID = game.AddPlayer(playerInitInfo);
if (newPlayerID == GameObj.invalidID)
return false;*/
//if (newPlayerID == GameObj.invalidID)
//return;
// 内容待修改
var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1));
bool start = false;
Console.WriteLine($"Id: {request.PlayerId} joins.");
Console.WriteLine($"PlayerType: {request.PlayerType} Id: {request.PlayerId} joins.");
lock (semaDict)
{
semaDict.Add(request.PlayerId, temp);
@@ -157,29 +222,12 @@ namespace Server
} while (game.GameMap.Timer.IsGaming);
}

public override Task<BoolRes> Attack(AttackMsg request, ServerCallContext context)
{
return base.Attack(request, context);
}

public override Task<BoolRes> CarryHuman(IDMsg request, ServerCallContext context)
public override Task<BoolRes> Trick(TrickMsg request, ServerCallContext context)
{
return base.CarryHuman(request, context);
}

public override Task<BoolRes> EndFixMachine(IDMsg request, ServerCallContext context)
{
return base.EndFixMachine(request, context);
}

public override Task<BoolRes> EndSaveHuman(IDMsg request, ServerCallContext context)
{
return base.EndSaveHuman(request, context);
}

public override Task<BoolRes> Escape(IDMsg request, ServerCallContext context)
{
return base.Escape(request, context);
game.Attack(request.PlayerId, request.Angle);
BoolRes boolRes = new();
boolRes.ActSuccess = true;
return Task.FromResult(boolRes);
}

public override Task GetMessage(IDMsg request, IServerStreamWriter<MsgRes> responseStream, ServerCallContext context)
@@ -187,57 +235,62 @@ namespace Server
return base.GetMessage(request, responseStream, context);
}

public override Task<BoolRes> HangHuman(IDMsg request, ServerCallContext context)
public override Task<MoveRes> Move(MoveMsg request, ServerCallContext context)
{
return base.HangHuman(request, context);
Console.WriteLine($"Move ID: {request.PlayerId}, TimeInMilliseconds: {request.TimeInMilliseconds}");
game.MovePlayer(request.PlayerId, (int)request.TimeInMilliseconds, request.Angle);
// 之后game.MovePlayer可能改为bool类型
MoveRes moveRes = new();
moveRes.ActSuccess = true;
return Task.FromResult(moveRes);
}

public override Task<MoveRes> Move(MoveMsg request, ServerCallContext context)
public override Task<BoolRes> PickProp(PropMsg request, ServerCallContext context)
{
return base.Move(request, context);
BoolRes boolRes = new();
if (request.PropType == Protobuf.PropType.NullPropType)
boolRes.ActSuccess = game.PickProp(request.PlayerId, Preparation.Utility.PropType.Null);
return Task.FromResult(boolRes);
}

public override Task<BoolRes> PickProp(PickMsg request, ServerCallContext context)
public override Task<BoolRes> SendMessage(SendMsg request, ServerCallContext context)
{
return base.PickProp(request, context);
return base.SendMessage(request, context);
}

public override Task<BoolRes> ReleaseHuman(IDMsg request, ServerCallContext context)
public override Task<BoolRes> UseProp(IDMsg request, ServerCallContext context)
{
return base.ReleaseHuman(request, context);
return base.UseProp(request, context);
}

public override Task<BoolRes> SendMessage(SendMsg request, ServerCallContext context)
public override Task<BoolRes> UseSkill(IDMsg request, ServerCallContext context)
{
return base.SendMessage(request, context);
return base.UseSkill(request, context);
}

public override Task<BoolRes> StartFixMachine(IDMsg request, ServerCallContext context)
public override Task<BoolRes> Graduate(IDMsg request, ServerCallContext context)
{
return base.StartFixMachine(request, context);
return base.Graduate(request, context);
}

public override Task<BoolRes> StartSaveHuman(IDMsg request, ServerCallContext context)
public override Task<BoolRes> StartHealMate(IDMsg request, ServerCallContext context)
{
return base.StartSaveHuman(request, context);
return base.StartHealMate(request, context);
}

public override Task<BoolRes> UseProp(IDMsg request, ServerCallContext context)
public override Task<BoolRes> StartHelpMate(IDMsg request, ServerCallContext context)
{
return base.UseProp(request, context);
return base.StartHelpMate(request, context);
}

public override Task<BoolRes> UseSkill(IDMsg request, ServerCallContext context)
public override Task<BoolRes> StartLearning(IDMsg request, ServerCallContext context)
{
return base.UseSkill(request, context);
return base.StartLearning(request, context);
}

public GameServer(ArgumentOptions options)
{
/*this.options = options;
if (options.mapResource == DefaultArgumentOptions.MapResource)
this.game = new Game(MapInfo.defaultMap, options.TeamCount);
else
this.options = options;
//if (options.mapResource == DefaultArgumentOptions.MapResource)
// this.game = new Game(MapInfo.defaultMap, options.TeamCount);
//else
{
uint[,] map = new uint[GameData.rows, GameData.cols];
try
@@ -305,7 +358,7 @@ namespace Server
if (options.Url != DefaultArgumentOptions.Url && options.Token != DefaultArgumentOptions.Token)
{
//this.httpSender = new HttpSender(options.Url, options.Token, "PUT");
}*/
}
}
}
}
}

+ 8
- 0
logic/Server/Properties/launchSettings.json View File

@@ -0,0 +1,8 @@
{
"profiles": {
"Server": {
"commandName": "Project",
"commandLineArgs": "-p 8888"
}
}
}

+ 134
- 0
playback/Playback/MessageReader.cs View File

@@ -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();
}
}
}

+ 74
- 0
playback/Playback/MessageWriter.cs View File

@@ -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();
}
}
}

+ 0
- 6
playback/Playback/Playback.cs View File

@@ -1,6 +0,0 @@
namespace Playback
{
public class Playback
{
}
}

+ 1
- 1
playback/Playback/Playback.csproj View File

@@ -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>


+ 8
- 0
playback/Playback/PlaybackConstant.cs View File

@@ -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
}
}

Loading…
Cancel
Save