| @@ -60,12 +60,12 @@ namespace GameClass.GameObj | |||
| this.score = 0; | |||
| this.propInventory = null; | |||
| this.buffManeger = new BuffManeger(); | |||
| IPassiveSkill pSkill; | |||
| IPassiveSkill? pSkill; | |||
| ICommonSkill cSkill; | |||
| switch (characterType) | |||
| { | |||
| case CharacterType.Assassin: | |||
| pSkill = new(); | |||
| pSkill = null; | |||
| break; | |||
| case CharacterType.RecoverAfterBattle: | |||
| pSkill = new RecoverAfterBattle(); | |||
| @@ -1,5 +1,4 @@ | |||
| using Preparation.Utility; | |||
| using Preparation.GameData; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -3,8 +3,6 @@ using System.Collections.Generic; | |||
| using System.Threading; | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using Preparation.GameData; | |||
| namespace GameEngine | |||
| { | |||
| internal class CollisionChecker | |||
| @@ -0,0 +1,60 @@ | |||
| using CommandLine; | |||
| namespace Server | |||
| { | |||
| static class DefaultArgumentOptions | |||
| { | |||
| public static string FileName = "xxxx不用记啊xxxx"; // An impossible name of the playback file to indicate -f is not sepcified. | |||
| public static string Token = "xxxx随手推啊xxxx"; // An impossible name of the token to indicate -f is not sepcified. | |||
| public static string Url = "xxxx闭眼写啊xxxx"; | |||
| public static string MapResource = "xxxx多简单啊xxxx"; | |||
| } | |||
| public class ArgumentOptions | |||
| { | |||
| [Option('p', "port", Required = true, HelpText = "Server listening port")] | |||
| public ushort ServerPort { get; set; } = 10086; | |||
| [Option('t', "teamCount", Required = false, HelpText = "The number of teams, 1 by defualt")] | |||
| public ushort TeamCount { get; set; } = 1; | |||
| [Option('c', "playerCount", Required = false, HelpText = "The number of players per team, 1 by default")] | |||
| public ushort PlayerCountPerTeam { get; set; } = 1; | |||
| [Option('g', "gameTimeInSecond", Required = false, HelpText = "The time of the game in second, 10 minutes by default")] | |||
| public uint GameTimeInSecond { get; set; } = 10 * 60; | |||
| [Option('f', "fileName", Required = false, HelpText = "The file to store playback file or to read file.")] | |||
| public string FileName { get; set; } = DefaultArgumentOptions.FileName; | |||
| [Option('b', "playback", Required = false, HelpText = "Whether open the server in a playback mode.")] | |||
| public bool Playback { get; set; } = false; | |||
| [Option("playbackSpeed", Required = false, HelpText = "The speed of the playback, between 0.25 and 4.0")] | |||
| public double PlaybackSpeed { get; set; } = 1.0; | |||
| [Option("resultOnly", Required = false, HelpText = "In playback mode to get the result directly")] | |||
| public bool ResultOnly { get; set; } = false; | |||
| [Option('k', "token", Required = false, HelpText = "Web API Token")] | |||
| public string Token { get; set; } = DefaultArgumentOptions.Token; | |||
| [Option('u', "url", Required = false, HelpText = "Web Url")] | |||
| public string Url { get; set; } = DefaultArgumentOptions.Url; | |||
| [Option('m', "mapResource", Required = false, HelpText = "Map Resource Path")] | |||
| public string mapResource { get; set; } = DefaultArgumentOptions.MapResource; | |||
| [Option("requestOnly", Required = false, HelpText = "Only send web requests")] | |||
| public bool RequestOnly { get; set; } = false; | |||
| [Option("finalGame", Required = false, HelpText = "Whether it is the final game")] | |||
| public bool FinalGame { get; set; } = false; | |||
| [Option("cheatMode", Required = false, HelpText = "Whether to open the cheat code")] | |||
| public bool CheatMode { get; set; } = false; | |||
| [Option("resultFileName", Required = false, HelpText = "Result file name, saved as .json")] | |||
| public string ResultFileName { get; set; } = DefaultArgumentOptions.FileName; | |||
| } | |||
| } | |||
| @@ -0,0 +1,358 @@ | |||
| using Protobuf; | |||
| using System.Collections.Generic; | |||
| using GameClass.GameObj; | |||
| /* | |||
| namespace Server | |||
| { | |||
| public static class CopyInfo | |||
| { | |||
| public static MessageToClient.Types.GameObjMessage? Auto(GameObj gameObj) | |||
| { | |||
| 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; //先写着防报错 | |||
| } | |||
| private static MessageToClient.Types.GameObjMessage Player(Character player) | |||
| { | |||
| MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage(); | |||
| msg.MessageOfCharacter = new MessageOfCharacter(); | |||
| 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; | |||
| //应该要发队伍分数,这里先发个人分数 | |||
| msg.MessageOfCharacter.Score = player.Score; | |||
| //这条暂时没啥用 | |||
| msg.MessageOfCharacter.TimeUntilUltimateSkillAvailable = 0; | |||
| msg.MessageOfCharacter.Vampire = player.Vampire; | |||
| foreach (KeyValuePair<Preparation.Utility.BuffType, bool> kvp in player.Buff) | |||
| { | |||
| if (kvp.Value) | |||
| { | |||
| switch(kvp.Key) | |||
| { | |||
| case Preparation.Utility.BuffType.Spear: | |||
| msg.MessageOfCharacter.Buff.Add(BuffType.SpearBuff); | |||
| break; | |||
| case Preparation.Utility.BuffType.AddLIFE: | |||
| msg.MessageOfCharacter.Buff.Add(BuffType.AddLife); | |||
| break; | |||
| case Preparation.Utility.BuffType.Shield: | |||
| msg.MessageOfCharacter.Buff.Add(BuffType.ShieldBuff); | |||
| break; | |||
| case Preparation.Utility.BuffType.AddSpeed: | |||
| msg.MessageOfCharacter.Buff.Add(BuffType.MoveSpeed); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| switch (player.Place) | |||
| { | |||
| case Preparation.Utility.PlaceType.Land: | |||
| msg.MessageOfCharacter.Place = Communication.Proto.PlaceType.Land; | |||
| break; | |||
| case Preparation.Utility.PlaceType.Grass1: | |||
| msg.MessageOfCharacter.Place = Communication.Proto.PlaceType.Grass1; | |||
| break; | |||
| case Preparation.Utility.PlaceType.Grass2: | |||
| msg.MessageOfCharacter.Place = Communication.Proto.PlaceType.Grass2; | |||
| break; | |||
| case Preparation.Utility.PlaceType.Grass3: | |||
| msg.MessageOfCharacter.Place = Communication.Proto.PlaceType.Grass3; | |||
| break; | |||
| // case Preparation.Utility.PlaceType.Invisible: | |||
| // msg.MessageOfCharacter.Place = Communication.Proto.PlaceType.Invisible; | |||
| // break; | |||
| default: | |||
| msg.MessageOfCharacter.Place = Communication.Proto.PlaceType.NullPlaceType; | |||
| break; | |||
| } | |||
| //Character的储存方式可能得改,用enum type存道具和子弹,不应该用对象 | |||
| //现在懒得改了,有时间再重整一波 | |||
| if (player.PropInventory == null) | |||
| msg.MessageOfCharacter.Prop = Communication.Proto.PropType.NullPropType; | |||
| else | |||
| { | |||
| switch (player.PropInventory.GetPropType()) | |||
| { | |||
| case Preparation.Utility.PropType.Gem: | |||
| msg.MessageOfCharacter.Prop = Communication.Proto.PropType.Gem; | |||
| break; | |||
| case Preparation.Utility.PropType.addLIFE: | |||
| msg.MessageOfCharacter.Prop = Communication.Proto.PropType.AddLife; | |||
| break; | |||
| case Preparation.Utility.PropType.addSpeed: | |||
| msg.MessageOfCharacter.Prop = Communication.Proto.PropType.AddSpeed; | |||
| break; | |||
| case Preparation.Utility.PropType.Shield: | |||
| msg.MessageOfCharacter.Prop = Communication.Proto.PropType.Shield; | |||
| break; | |||
| case Preparation.Utility.PropType.Spear: | |||
| msg.MessageOfCharacter.Prop = Communication.Proto.PropType.Spear; | |||
| break; | |||
| default: | |||
| msg.MessageOfCharacter.Prop = Communication.Proto.PropType.NullPropType; | |||
| break; | |||
| } | |||
| } | |||
| switch (player.PassiveSkillType) | |||
| { | |||
| case Preparation.Utility.PassiveSkillType.RecoverAfterBattle: | |||
| msg.MessageOfCharacter.PassiveSkillType = Communication.Proto.PassiveSkillType.RecoverAfterBattle; | |||
| break; | |||
| case Preparation.Utility.PassiveSkillType.SpeedUpWhenLeavingGrass: | |||
| msg.MessageOfCharacter.PassiveSkillType = Communication.Proto.PassiveSkillType.SpeedUpWhenLeavingGrass; | |||
| break; | |||
| case Preparation.Utility.PassiveSkillType.Vampire: | |||
| msg.MessageOfCharacter.PassiveSkillType = Communication.Proto.PassiveSkillType.Vampire; | |||
| break; | |||
| default: | |||
| msg.MessageOfCharacter.PassiveSkillType = Communication.Proto.PassiveSkillType.NullPassiveSkillType; | |||
| break; | |||
| } | |||
| switch (player.CommonSkillType) | |||
| { | |||
| case Preparation.Utility.ActiveSkillType.BecomeAssassin: | |||
| msg.MessageOfCharacter.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeAssassin; | |||
| break; | |||
| case Preparation.Utility.ActiveSkillType.BecomeVampire: | |||
| msg.MessageOfCharacter.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeVampire; | |||
| break; | |||
| case Preparation.Utility.ActiveSkillType.NuclearWeapon: | |||
| msg.MessageOfCharacter.ActiveSkillType = Communication.Proto.ActiveSkillType.NuclearWeapon; | |||
| break; | |||
| case Preparation.Utility.ActiveSkillType.SuperFast: | |||
| msg.MessageOfCharacter.ActiveSkillType = Communication.Proto.ActiveSkillType.SuperFast; | |||
| break; | |||
| default: | |||
| msg.MessageOfCharacter.ActiveSkillType = Communication.Proto.ActiveSkillType.NullActiveSkillType; | |||
| break; | |||
| } | |||
| switch (player.BulletOfPlayer) | |||
| { | |||
| case Preparation.Utility.BulletType.AtomBomb: | |||
| msg.MessageOfCharacter.BulletType = Communication.Proto.BulletType.AtomBomb; | |||
| break; | |||
| case Preparation.Utility.BulletType.OrdinaryBullet: | |||
| msg.MessageOfCharacter.BulletType = Communication.Proto.BulletType.OrdinaryBullet; | |||
| break; | |||
| case Preparation.Utility.BulletType.FastBullet: | |||
| msg.MessageOfCharacter.BulletType = Communication.Proto.BulletType.FastBullet; | |||
| break; | |||
| case Preparation.Utility.BulletType.LineBullet: | |||
| msg.MessageOfCharacter.BulletType = Communication.Proto.BulletType.LineBullet; | |||
| break; | |||
| default: | |||
| msg.MessageOfCharacter.BulletType = Communication.Proto.BulletType.NullBulletType; | |||
| break; | |||
| } | |||
| return msg; | |||
| } | |||
| private static MessageToClient.Types.GameObjMessage Bullet(Bullet bullet) | |||
| { | |||
| MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage(); | |||
| msg.MessageOfBullet = new MessageOfBullet(); | |||
| msg.MessageOfBullet.FacingDirection = bullet.FacingDirection; | |||
| msg.MessageOfBullet.Guid = bullet.ID; | |||
| msg.MessageOfBullet.BombRange = BulletFactory.BulletBombRange(bullet.TypeOfBullet); | |||
| switch (bullet.TypeOfBullet) | |||
| { | |||
| case Preparation.Utility.BulletType.AtomBomb: | |||
| msg.MessageOfBullet.Type = Communication.Proto.BulletType.AtomBomb; | |||
| break; | |||
| case Preparation.Utility.BulletType.OrdinaryBullet: | |||
| msg.MessageOfBullet.Type = Communication.Proto.BulletType.OrdinaryBullet; | |||
| break; | |||
| case Preparation.Utility.BulletType.FastBullet: | |||
| msg.MessageOfBullet.Type = Communication.Proto.BulletType.FastBullet; | |||
| break; | |||
| case Preparation.Utility.BulletType.LineBullet: | |||
| msg.MessageOfBullet.Type = Communication.Proto.BulletType.LineBullet; | |||
| break; | |||
| default: | |||
| msg.MessageOfBullet.Type = Communication.Proto.BulletType.NullBulletType; | |||
| break; | |||
| } | |||
| msg.MessageOfBullet.X = bullet.Position.x; | |||
| msg.MessageOfBullet.Y = bullet.Position.y; | |||
| if (bullet.Parent != null) | |||
| msg.MessageOfBullet.ParentTeamID = bullet.Parent.TeamID; | |||
| switch (bullet.Place) | |||
| { | |||
| case Preparation.Utility.PlaceType.Land: | |||
| msg.MessageOfBullet.Place = Communication.Proto.PlaceType.Land; | |||
| break; | |||
| case Preparation.Utility.PlaceType.Grass1: | |||
| msg.MessageOfBullet.Place = Communication.Proto.PlaceType.Grass1; | |||
| break; | |||
| case Preparation.Utility.PlaceType.Grass2: | |||
| msg.MessageOfBullet.Place = Communication.Proto.PlaceType.Grass2; | |||
| break; | |||
| case Preparation.Utility.PlaceType.Grass3: | |||
| msg.MessageOfBullet.Place = Communication.Proto.PlaceType.Grass3; | |||
| break; | |||
| default: | |||
| msg.MessageOfBullet.Place = Communication.Proto.PlaceType.NullPlaceType; | |||
| break; | |||
| } | |||
| return msg; | |||
| } | |||
| private static MessageToClient.Types.GameObjMessage Prop(Prop prop) | |||
| { | |||
| MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage(); | |||
| msg.MessageOfProp = new MessageOfProp(); | |||
| msg.MessageOfProp.FacingDirection = prop.FacingDirection; | |||
| msg.MessageOfProp.Guid = prop.ID; | |||
| msg.MessageOfProp.IsMoving = prop.IsMoving; | |||
| switch (prop.GetPropType()) | |||
| { | |||
| case Preparation.Utility.PropType.Gem: | |||
| msg.MessageOfProp.Type = Communication.Proto.PropType.Gem; | |||
| break; | |||
| case Preparation.Utility.PropType.addLIFE: | |||
| msg.MessageOfProp.Type = Communication.Proto.PropType.AddLife; | |||
| break; | |||
| case Preparation.Utility.PropType.addSpeed: | |||
| msg.MessageOfProp.Type = Communication.Proto.PropType.AddSpeed; | |||
| break; | |||
| case Preparation.Utility.PropType.Shield: | |||
| msg.MessageOfProp.Type = Communication.Proto.PropType.Shield; | |||
| break; | |||
| case Preparation.Utility.PropType.Spear: | |||
| msg.MessageOfProp.Type = Communication.Proto.PropType.Spear; | |||
| break; | |||
| default: | |||
| msg.MessageOfProp.Type = Communication.Proto.PropType.NullPropType; | |||
| break; | |||
| } | |||
| msg.MessageOfProp.X = prop.Position.x; | |||
| msg.MessageOfProp.Y = prop.Position.y; | |||
| if(prop is Gem) | |||
| { | |||
| msg.MessageOfProp.Size = ((Gem)prop).Size; | |||
| } | |||
| else | |||
| { | |||
| msg.MessageOfProp.Size = 1; | |||
| } | |||
| switch (prop.Place) | |||
| { | |||
| case Preparation.Utility.PlaceType.Land: | |||
| msg.MessageOfProp.Place = Communication.Proto.PlaceType.Land; | |||
| break; | |||
| case Preparation.Utility.PlaceType.Grass1: | |||
| msg.MessageOfProp.Place = Communication.Proto.PlaceType.Grass1; | |||
| break; | |||
| case Preparation.Utility.PlaceType.Grass2: | |||
| msg.MessageOfProp.Place = Communication.Proto.PlaceType.Grass2; | |||
| break; | |||
| case Preparation.Utility.PlaceType.Grass3: | |||
| msg.MessageOfProp.Place = Communication.Proto.PlaceType.Grass3; | |||
| break; | |||
| default: | |||
| msg.MessageOfProp.Place = Communication.Proto.PlaceType.NullPlaceType; | |||
| break; | |||
| } | |||
| return msg; | |||
| } | |||
| private static MessageToClient.Types.GameObjMessage BombedBullet(BombedBullet bombedBullet) | |||
| { | |||
| MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage(); | |||
| msg.MessageOfBombedBullet = 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); | |||
| switch (bombedBullet.bulletHasBombed.TypeOfBullet) | |||
| { | |||
| case Preparation.Utility.BulletType.OrdinaryBullet: | |||
| msg.MessageOfBombedBullet.Type = Communication.Proto.BulletType.OrdinaryBullet; | |||
| break; | |||
| case Preparation.Utility.BulletType.AtomBomb: | |||
| msg.MessageOfBombedBullet.Type = Communication.Proto.BulletType.AtomBomb; | |||
| break; | |||
| case Preparation.Utility.BulletType.FastBullet: | |||
| msg.MessageOfBombedBullet.Type = Communication.Proto.BulletType.FastBullet; | |||
| break; | |||
| case Preparation.Utility.BulletType.LineBullet: | |||
| msg.MessageOfBombedBullet.Type = Communication.Proto.BulletType.LineBullet; | |||
| break; | |||
| default: | |||
| msg.MessageOfBombedBullet.Type = Communication.Proto.BulletType.NullBulletType; | |||
| break; | |||
| } | |||
| return msg; | |||
| } | |||
| private static MessageToClient.Types.GameObjMessage PickedProp(PickedProp pickedProp) | |||
| { | |||
| MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage(); | |||
| msg.MessageOfPickedProp = new MessageOfPickedProp(); | |||
| msg.MessageOfPickedProp.MappingID = pickedProp.MappingID; | |||
| msg.MessageOfPickedProp.X = pickedProp.PropHasPicked.Position.x; | |||
| msg.MessageOfPickedProp.Y = pickedProp.PropHasPicked.Position.y; | |||
| msg.MessageOfPickedProp.FacingDirection = pickedProp.PropHasPicked.FacingDirection; | |||
| switch (pickedProp.PropHasPicked.GetPropType()) | |||
| { | |||
| case Preparation.Utility.PropType.Gem: | |||
| msg.MessageOfPickedProp.Type = Communication.Proto.PropType.Gem; | |||
| break; | |||
| case Preparation.Utility.PropType.addLIFE: | |||
| msg.MessageOfPickedProp.Type = Communication.Proto.PropType.AddLife; | |||
| break; | |||
| case Preparation.Utility.PropType.addSpeed: | |||
| msg.MessageOfPickedProp.Type = Communication.Proto.PropType.AddSpeed; | |||
| break; | |||
| case Preparation.Utility.PropType.Shield: | |||
| msg.MessageOfPickedProp.Type = Communication.Proto.PropType.Shield; | |||
| break; | |||
| case Preparation.Utility.PropType.Spear: | |||
| msg.MessageOfPickedProp.Type = Communication.Proto.PropType.Spear; | |||
| break; | |||
| default: | |||
| msg.MessageOfPickedProp.Type = Communication.Proto.PropType.NullPropType; | |||
| break; | |||
| } | |||
| return msg; | |||
| } | |||
| } | |||
| }*/ | |||
| // 等Preparation完成再写 | |||
| @@ -1,109 +0,0 @@ | |||
| using Grpc.Core; | |||
| using Protobuf; | |||
| using System.Threading; | |||
| using Timothy.FrameRateTask; | |||
| using System; | |||
| using System.Net.Http.Headers; | |||
| namespace Server | |||
| { | |||
| public class Game | |||
| { | |||
| private const int gameTime = 3000; | |||
| public int GameTime => gameTime; | |||
| private MessageToClient gameInfo = new(); | |||
| private object gameInfoLock = new(); | |||
| private int isGaming = 0; | |||
| public bool IsGaming | |||
| { | |||
| get => Interlocked.CompareExchange(ref isGaming, 0, 0) != 0; | |||
| set => Interlocked.Exchange(ref isGaming, value ? 1 : 0); | |||
| } | |||
| public MessageToClient GetCopiedGameInfo() | |||
| { | |||
| lock (gameInfoLock) | |||
| { | |||
| return gameInfo.Clone(); | |||
| } | |||
| } | |||
| public void AddPlayer(PlayerMsg player) | |||
| { | |||
| lock (gameInfoLock) | |||
| { | |||
| if (player.PlayerType == PlayerType.NullPlayerType) | |||
| return; | |||
| if (player.PlayerType == PlayerType.HumanPlayer) | |||
| { | |||
| gameInfo.HumanMessage.Add(new MessageOfHuman() | |||
| { | |||
| PlayerId = player.PlayerId | |||
| }); | |||
| return; | |||
| } | |||
| if (player.PlayerType == PlayerType.ButcherPlayer) | |||
| { | |||
| gameInfo.ButcherMessage.Add(new MessageOfButcher() | |||
| { | |||
| PlayerId = player.PlayerId | |||
| }); | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| public void Move() | |||
| { | |||
| } | |||
| public SemaphoreSlim StartGame() | |||
| { | |||
| IsGaming = true; | |||
| var waitHandle = new SemaphoreSlim(0); | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| new FrameRateTaskExecutor<int> | |||
| ( | |||
| () => 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++; | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| 100, | |||
| () => | |||
| { | |||
| IsGaming = false; | |||
| waitHandle.Release(); | |||
| return 0; | |||
| }, | |||
| gameTime | |||
| ).Start(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return waitHandle; | |||
| } | |||
| } | |||
| } | |||
| @@ -4,6 +4,8 @@ using System.Threading; | |||
| using Timothy.FrameRateTask; | |||
| using System; | |||
| using System.Net.Http.Headers; | |||
| using Gaming; | |||
| using GameClass.GameObj; | |||
| namespace Server | |||
| { | |||
| @@ -11,18 +13,81 @@ namespace Server | |||
| { | |||
| private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||
| private object gameLock = new(); | |||
| private const int playerNum = 1; | |||
| private const int playerNum = 2; // 注意修改 | |||
| private MessageToClient currentGameInfo = new(); | |||
| private Game game = new(); | |||
| public int GameTime => game.GameTime; | |||
| private SemaphoreSlim endGameSem = new(0); | |||
| // 以上是测试时用到的定义 | |||
| protected readonly ArgumentOptions options; | |||
| protected readonly Game game; | |||
| private uint spectatorMinPlayerID = 2022; | |||
| private List<Tuple<PlayerType, uint>> spectatorList = new List<Tuple<PlayerType, uint>>(); | |||
| public int TeamCount => options.TeamCount; | |||
| protected long[,] communicationToGameID; // 通信用的ID映射到游戏内的ID,[i,j]表示team:i,player:j的id。 | |||
| private readonly object messageToAllClientsLock = new(); | |||
| public static readonly long SendMessageToClientIntervalInMilliseconds = 50; | |||
| private readonly Semaphore endGameInfoSema = new(0, 1); | |||
| // private MessageWriter? mwr = null; | |||
| public void StartGame() | |||
| { | |||
| bool gameState = game.StartGame((int)options.GameTimeInSecond * 1000); | |||
| var waitHandle = new SemaphoreSlim(gameState == true ? 1 : 0); // 注意修改 | |||
| new Thread(() => | |||
| { | |||
| new FrameRateTaskExecutor<int> | |||
| ( | |||
| () => game.GameMap.Timer.IsGaming, | |||
| ReportGame, | |||
| 1000, | |||
| () => | |||
| { | |||
| ReportGame(); // 最后发一次消息,唤醒发消息的线程,防止发消息的线程由于有概率处在 Wait 状态而卡住 | |||
| return 0; | |||
| } | |||
| ).Start(); | |||
| }) | |||
| { IsBackground = true }.Start(); | |||
| new Thread(() => | |||
| { | |||
| waitHandle.Wait(); | |||
| this.endGameSem.Release(); | |||
| }) | |||
| { IsBackground = true }.Start(); | |||
| } | |||
| public void WaitForEnd() | |||
| { | |||
| this.endGameSem.Wait(); | |||
| } | |||
| public void ReportGame() | |||
| { | |||
| // currentGameInfo = game.GetCopiedGameInfo(); | |||
| currentGameInfo.HumanMessage[0].X = 1; | |||
| currentGameInfo.ButcherMessage[0].X = 1; | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item1.Release(); | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item2.Wait(); | |||
| } | |||
| } | |||
| private uint GetBirthPointIdx(long teamID, long playerID) // 获取出生点位置 | |||
| { | |||
| return (uint)((teamID * options.PlayerCountPerTeam) + playerID); | |||
| } | |||
| public override Task<BoolRes> TryConnection(IDMsg request, ServerCallContext context) | |||
| { | |||
| var onConnection = new BoolRes(); | |||
| lock (gameLock) | |||
| { | |||
| // if (0 <= request.PlayerId && request.PlayerId < playerNum) | |||
| if (0 <= request.PlayerId && request.PlayerId < playerNum) // 注意修改 | |||
| { | |||
| onConnection.ActSuccess = true; | |||
| Console.WriteLine(onConnection.ActSuccess); | |||
| @@ -33,21 +98,44 @@ namespace Server | |||
| return Task.FromResult(onConnection); | |||
| } | |||
| protected readonly object addPlayerLock = new(); | |||
| public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context) | |||
| { | |||
| lock (gameLock) | |||
| if (request.PlayerId >= spectatorMinPlayerID && request.PlayerType == PlayerType.NullPlayerType) | |||
| { | |||
| if (game.IsGaming) | |||
| return; | |||
| game.AddPlayer(request); | |||
| var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | |||
| // 观战模式 | |||
| Tuple<PlayerType, uint> tp = new Tuple<PlayerType, uint>(request.PlayerType, (uint)request.PlayerId); | |||
| if (!spectatorList.Contains(tp)) | |||
| { | |||
| spectatorList.Add(tp); | |||
| Console.WriteLine("A new spectator comes to watch this game."); | |||
| } | |||
| return; | |||
| } | |||
| if (game.GameMap.Timer.IsGaming) | |||
| return; | |||
| /*if (!ValidTeamIDAndPlayerID(msg.TeamID, msg.PlayerID)) //玩家id是否正确 | |||
| return; | |||
| if (communicationToGameID[msg.TeamID, msg.PlayerID] != GameObj.invalidID) //是否已经添加了该玩家 | |||
| return false; | |||
| Preparation.Utility.PassiveSkillType passiveSkill; | |||
| */ | |||
| lock (addPlayerLock) | |||
| { | |||
| /*Game.PlayerInitInfo playerInitInfo = new(GetBirthPointIdx(msg.TeamID, msg.PlayerID), msg.TeamID, msg.PlayerID, passiveSkill, commonSkill); | |||
| long newPlayerID = game.AddPlayer(playerInitInfo); | |||
| if (newPlayerID == GameObj.invalidID) | |||
| return false;*/ | |||
| // 内容待修改 | |||
| var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | |||
| bool start = false; | |||
| Console.WriteLine($"Id: {request.PlayerId} joins."); | |||
| lock (semaDict) | |||
| { | |||
| semaDict.Add(request.PlayerId, temp); | |||
| start = semaDict.Count == playerNum; | |||
| start = semaDict.Count == playerNum; // 之后补上CheckStart函数 | |||
| } | |||
| if (start) | |||
| @@ -66,8 +154,8 @@ namespace Server | |||
| Console.WriteLine("Send!"); | |||
| } | |||
| semaDict[request.PlayerId].Item2.Release(); | |||
| } while (game.IsGaming); | |||
| } | |||
| } while (game.GameMap.Timer.IsGaming); | |||
| } | |||
| public override Task<BoolRes> Attack(AttackMsg request, ServerCallContext context) | |||
| { | |||
| @@ -94,11 +182,6 @@ namespace Server | |||
| return base.Escape(request, context); | |||
| } | |||
| public override int GetHashCode() | |||
| { | |||
| return base.GetHashCode(); | |||
| } | |||
| public override Task GetMessage(IDMsg request, IServerStreamWriter<MsgRes> responseStream, ServerCallContext context) | |||
| { | |||
| return base.GetMessage(request, responseStream, context); | |||
| @@ -148,54 +231,81 @@ namespace Server | |||
| { | |||
| return base.UseSkill(request, context); | |||
| } | |||
| public void StartGame() | |||
| public GameServer(ArgumentOptions options) | |||
| { | |||
| var waitHandle = game.StartGame(); | |||
| new Thread(() => | |||
| /*this.options = options; | |||
| if (options.mapResource == DefaultArgumentOptions.MapResource) | |||
| this.game = new Game(MapInfo.defaultMap, options.TeamCount); | |||
| else | |||
| { | |||
| new FrameRateTaskExecutor<int> | |||
| ( | |||
| () => game.IsGaming, | |||
| ReportGame, | |||
| 1000, | |||
| () => | |||
| uint[,] map = new uint[GameData.rows, GameData.cols]; | |||
| try | |||
| { | |||
| string? line; | |||
| int i = 0, j = 0; | |||
| using (StreamReader sr = new StreamReader(options.mapResource)) | |||
| { | |||
| ReportGame(); // 最后发一次消息,唤醒发消息的线程,防止发消息的线程由于有概率处在 Wait 状态而卡住 | |||
| return 0; | |||
| while (!sr.EndOfStream && i < GameData.rows) | |||
| { | |||
| if ((line = sr.ReadLine()) != null) | |||
| { | |||
| string[] nums = line.Split(' '); | |||
| foreach (string item in nums) | |||
| { | |||
| if (item.Length > 1)//以兼容原方案 | |||
| { | |||
| map[i, j] = (uint)int.Parse(item); | |||
| } | |||
| else | |||
| { | |||
| //2022-04-22 by LHR 十六进制编码地图方案(防止地图编辑员瞎眼x | |||
| map[i, j] = (uint)Preparation.Utility.MapEncoder.Hex2Dec(char.Parse(item)); | |||
| } | |||
| j++; | |||
| if (j >= GameData.cols) | |||
| { | |||
| j = 0; | |||
| break; | |||
| } | |||
| } | |||
| i++; | |||
| } | |||
| } | |||
| } | |||
| ).Start(); | |||
| }) | |||
| { IsBackground = true }.Start(); | |||
| new Thread(() => | |||
| { | |||
| waitHandle.Wait(); | |||
| this.endGameSem.Release(); | |||
| }) | |||
| { IsBackground = true }.Start(); | |||
| } | |||
| public void WaitForEnd() | |||
| { | |||
| this.endGameSem.Wait(); | |||
| } | |||
| public void ReportGame() | |||
| { | |||
| currentGameInfo = game.GetCopiedGameInfo(); | |||
| foreach (var kvp in semaDict) | |||
| } | |||
| catch | |||
| { | |||
| map = MapInfo.defaultMap; | |||
| } | |||
| finally { this.game = new Game(map, options.TeamCount); } | |||
| } | |||
| communicationToGameID = new long[options.TeamCount, options.PlayerCountPerTeam]; | |||
| //创建server时先设定待加入人物都是invalid | |||
| for (int i = 0; i < communicationToGameID.GetLength(0); i++) | |||
| { | |||
| kvp.Value.Item1.Release(); | |||
| for (int j = 0; j < communicationToGameID.GetLength(1); j++) | |||
| { | |||
| communicationToGameID[i, j] = GameObj.invalidID; | |||
| } | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| if (options.FileName != DefaultArgumentOptions.FileName) | |||
| { | |||
| kvp.Value.Item2.Wait(); | |||
| try | |||
| { | |||
| //mwr = new MessageWriter(options.FileName, options.TeamCount, options.PlayerCountPerTeam); | |||
| } | |||
| catch | |||
| { | |||
| Console.WriteLine($"Error: Cannot create the playback file: {options.FileName}!"); | |||
| } | |||
| } | |||
| } | |||
| public GameServer() | |||
| { | |||
| if (options.Url != DefaultArgumentOptions.Url && options.Token != DefaultArgumentOptions.Token) | |||
| { | |||
| //this.httpSender = new HttpSender(options.Url, options.Token, "PUT"); | |||
| }*/ | |||
| } | |||
| } | |||
| } | |||
| @@ -4,20 +4,36 @@ using System.Threading; | |||
| using Timothy.FrameRateTask; | |||
| using System; | |||
| using System.Net.Http.Headers; | |||
| using CommandLine; | |||
| namespace Server | |||
| { | |||
| public class Program | |||
| { | |||
| public static void Main() | |||
| static int Main(string[] args) | |||
| { | |||
| foreach (var arg in args) | |||
| { | |||
| Console.Write($"{arg} "); | |||
| } | |||
| Console.WriteLine(); | |||
| ArgumentOptions? options = null; | |||
| _ = Parser.Default.ParseArguments<ArgumentOptions>(args).WithParsed(o => { options = o; }); | |||
| if (options == null) | |||
| { | |||
| Console.WriteLine("Argument parsing failed!"); | |||
| // return 1; | |||
| } | |||
| // Console.WriteLine("Server begins to run: " + options.ServerPort.ToString()); | |||
| try | |||
| { | |||
| GameServer gameServer = new(); | |||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||
| { | |||
| GameServer? gameServer = new(options); | |||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) { | |||
| Services = { AvailableService.BindService(gameServer) }, | |||
| Ports = { new ServerPort("127.0.0.1", 8888, ServerCredentials.Insecure) } | |||
| Ports = { new ServerPort("0.0.0.0", 8888, ServerCredentials.Insecure) } | |||
| }; | |||
| server.Start(); | |||
| @@ -30,6 +46,7 @@ namespace Server | |||
| { | |||
| Console.WriteLine(ex.ToString()); | |||
| } | |||
| return 0; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <PropertyGroup> | |||
| <OutputType>Exe</OutputType> | |||
| @@ -8,10 +8,11 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | |||
| <PackageReference Include="FrameRateTask" Version="1.2.0" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.21.12" /> | |||
| <PackageReference Include="Grpc" Version="2.46.5" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.5" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Tools" Version="2.51.0"> | |||
| <PrivateAssets>all</PrivateAssets> | |||
| <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
| @@ -20,6 +21,10 @@ | |||
| <ItemGroup> | |||
| <ProjectReference Include="..\..\dependency\proto\Protos.csproj" /> | |||
| <ProjectReference Include="..\GameClass\GameClass.csproj" /> | |||
| <ProjectReference Include="..\GameEngine\GameEngine.csproj" /> | |||
| <ProjectReference Include="..\Gaming\Gaming.csproj" /> | |||
| <ProjectReference Include="..\Preparation\Preparation.csproj" /> | |||
| </ItemGroup> | |||
| </Project> | |||