| @@ -109,7 +109,8 @@ enum StudentType | |||
| ATHLETE = 1; | |||
| STUDENTTYPE2 = 2;//Teacher | |||
| STUDENTTYPE3 = 3;//StraightAStudent | |||
| STUDENTTYPE4 = 4; | |||
| STUDENTTYPE4 = 4;//Robot | |||
| TECH_OTAKU =5; | |||
| } | |||
| enum TrickerType | |||
| @@ -66,6 +66,9 @@ namespace GameClass.GameObj | |||
| case CharacterType.Assassin: | |||
| this.occupation = new Assassin(); | |||
| break; | |||
| case CharacterType.Robot: | |||
| this.occupation = new Robot(); | |||
| break; | |||
| case CharacterType.Teacher: | |||
| this.occupation = new Teacher(); | |||
| break; | |||
| @@ -78,6 +81,9 @@ namespace GameClass.GameObj | |||
| case CharacterType.ANoisyPerson: | |||
| this.occupation = new ANoisyPerson(); | |||
| break; | |||
| case CharacterType.TechOtaku: | |||
| this.occupation = new TechOtaku(); | |||
| break; | |||
| case CharacterType.Athlete: | |||
| default: | |||
| this.occupation = new Athlete(); | |||
| @@ -167,4 +167,23 @@ namespace GameClass.GameObj | |||
| this.MaxGamingAddiction = ((IStudent)Occupation).MaxGamingAddiction; | |||
| } | |||
| } | |||
| public class Golem : Student | |||
| { | |||
| private Character? parent; // 主人 | |||
| public Character? Parent | |||
| { | |||
| get => parent; | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| parent = value; | |||
| } | |||
| } | |||
| } | |||
| public Golem(XY initPos, int initRadius, Character? parent) : base(initPos, initRadius, CharacterType.Robot) | |||
| { | |||
| this.parent = parent; | |||
| } | |||
| } | |||
| } | |||
| @@ -3,20 +3,6 @@ using Preparation.Utility; | |||
| namespace GameClass.GameObj | |||
| { | |||
| public class BecomeVampire : IActiveSkill // 化身吸血鬼 | |||
| { | |||
| public int SkillCD => GameData.commonSkillCD * 4 / 3; | |||
| public int DurationTime => GameData.commonSkillTime; | |||
| private readonly object commonSkillLock = new object(); | |||
| public object ActiveSkillLock => commonSkillLock; | |||
| public bool isBeingUsed = false; | |||
| public bool IsBeingUsed | |||
| { | |||
| get => isBeingUsed; set => isBeingUsed = value; | |||
| } | |||
| } | |||
| public class CanBeginToCharge : IActiveSkill | |||
| { | |||
| public int SkillCD => GameData.commonSkillCD / 5; | |||
| @@ -79,7 +65,7 @@ namespace GameClass.GameObj | |||
| public class JumpyBomb : IActiveSkill | |||
| { | |||
| public int SkillCD => GameData.commonSkillCD / 30 * 5; | |||
| public int SkillCD => GameData.commonSkillCD / 25 * 5; | |||
| public int DurationTime => GameData.commonSkillTime / 2; | |||
| private readonly object commonSkillLock = new object(); | |||
| public object ActiveSkillLock => commonSkillLock; | |||
| @@ -105,20 +91,6 @@ namespace GameClass.GameObj | |||
| } | |||
| } | |||
| public class SuperFast : IActiveSkill // 3倍速 | |||
| { | |||
| public int SkillCD => GameData.commonSkillCD; | |||
| public int DurationTime => GameData.commonSkillTime * 4 / 10; | |||
| private readonly object commonSkillLock = new object(); | |||
| public object ActiveSkillLock => commonSkillLock; | |||
| public bool isBeingUsed = false; | |||
| public bool IsBeingUsed | |||
| { | |||
| get => isBeingUsed; set => isBeingUsed = value; | |||
| } | |||
| } | |||
| public class WriteAnswers : IActiveSkill | |||
| { | |||
| public int SkillCD => GameData.commonSkillCD; | |||
| @@ -23,16 +23,16 @@ namespace Gaming | |||
| { | |||
| if (((Bullet)collisionObj).Parent != player && ((Bullet)collisionObj).TypeOfBullet == BulletType.JumpyDumpty) | |||
| { | |||
| if (AttackManager.BeStunned((Character)player, ((Bullet)collisionObj).AP / GameData.timeFactorOfGhostFainting)) | |||
| if (CharacterManager.BeStunned((Character)player, ((Bullet)collisionObj).AP / GameData.timeFactorOfGhostFainting)) | |||
| player.AddScore(GameData.TrickerScoreStudentBeStunned(((Bullet)collisionObj).AP / GameData.timeFactorOfGhostFainting)); | |||
| gameMap.Remove((GameObj)collisionObj); | |||
| } | |||
| } | |||
| if (player.FindIActiveSkill(ActiveSkillType.CanBeginToCharge).IsBeingUsed && collisionObj.Type == GameObjType.Character && ((Character)collisionObj).IsGhost()) | |||
| { | |||
| if (AttackManager.BeStunned((Character)collisionObj, GameData.TimeOfGhostFaintingWhenCharge)) | |||
| if (CharacterManager.BeStunned((Character)collisionObj, GameData.TimeOfGhostFaintingWhenCharge)) | |||
| player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.TimeOfGhostFaintingWhenCharge)); | |||
| AttackManager.BeStunned(player, GameData.TimeOfStudentFaintingWhenCharge); | |||
| CharacterManager.BeStunned(player, GameData.TimeOfStudentFaintingWhenCharge); | |||
| } | |||
| } | |||
| public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) | |||
| @@ -17,7 +17,9 @@ namespace Gaming | |||
| { | |||
| readonly Map gameMap; | |||
| readonly MoveEngine moveEngine; | |||
| public AttackManager(Map gameMap) | |||
| readonly CharacterManager characterManager; | |||
| public AttackManager(Map gameMap, CharacterManager characterManager) | |||
| { | |||
| this.gameMap = gameMap; | |||
| this.moveEngine = new MoveEngine( | |||
| @@ -29,121 +31,12 @@ namespace Gaming | |||
| }, | |||
| EndMove: obj => | |||
| { | |||
| #if DEBUG | |||
| Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64); | |||
| #endif | |||
| if (obj.CanMove && ((Bullet)obj).TypeOfBullet != BulletType.JumpyDumpty) | |||
| BulletBomb((Bullet)obj, null); | |||
| } | |||
| ); | |||
| } | |||
| private void BeAddictedToGame(Student player, Ghost ghost) | |||
| { | |||
| ghost.AddScore(GameData.TrickerScoreStudentBeAddicted); | |||
| new Thread | |||
| (() => | |||
| { | |||
| if (player.GamingAddiction > GameData.BeginGamingAddiction && player.GamingAddiction < GameData.MidGamingAddiction) | |||
| player.GamingAddiction = GameData.MidGamingAddiction; | |||
| player.PlayerState = PlayerStateType.Addicted; | |||
| #if DEBUG | |||
| Debugger.Output(player, " is addicted "); | |||
| #endif | |||
| new FrameRateTaskExecutor<int>( | |||
| () => (player.PlayerState == PlayerStateType.Addicted || player.PlayerState == PlayerStateType.Rescued) && player.GamingAddiction < player.MaxGamingAddiction && gameMap.Timer.IsGaming, | |||
| () => | |||
| { | |||
| player.GamingAddiction += (player.PlayerState == PlayerStateType.Addicted) ? GameData.frameDuration : 0; | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| () => | |||
| { | |||
| if (player.GamingAddiction == player.MaxGamingAddiction && gameMap.Timer.IsGaming) | |||
| { | |||
| ghost.AddScore(GameData.TrickerScoreStudentDie); | |||
| Die(player); | |||
| } | |||
| return 0; | |||
| } | |||
| ) | |||
| .Start(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| } | |||
| public static bool BeStunned(Character player, int time) | |||
| { | |||
| if (player.PlayerState == PlayerStateType.Stunned || player.NoHp()) return false; | |||
| new Thread | |||
| (() => | |||
| { | |||
| player.PlayerState = PlayerStateType.Stunned; | |||
| Thread.Sleep(time); | |||
| if (player.PlayerState == PlayerStateType.Stunned) | |||
| player.PlayerState = PlayerStateType.Null; | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return true; | |||
| } | |||
| public static bool BackSwing(Character? player, int time) | |||
| { | |||
| if (player == null && time <= 0) return false; | |||
| if (player.PlayerState == PlayerStateType.Swinging || (!player.Commandable() && player.PlayerState != PlayerStateType.TryingToAttack)) return false; | |||
| player.PlayerState = PlayerStateType.Swinging; | |||
| new Thread | |||
| (() => | |||
| { | |||
| Thread.Sleep(time); | |||
| if (player.PlayerState == PlayerStateType.Swinging) | |||
| { | |||
| player.PlayerState = PlayerStateType.Null; | |||
| } | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return true; | |||
| } | |||
| private void Die(Character player) | |||
| { | |||
| #if DEBUG | |||
| Debugger.Output(player, "die."); | |||
| #endif | |||
| player.Die(PlayerStateType.Deceased); | |||
| for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | |||
| { | |||
| Prop? prop = player.UseProp(i); | |||
| if (prop != null) | |||
| { | |||
| prop.ReSetPos(player.Position, gameMap.GetPlaceType(player.Position)); | |||
| gameMap.Add(prop); | |||
| } | |||
| } | |||
| ++gameMap.NumOfDeceasedStudent; | |||
| if (GameData.numOfStudent - gameMap.NumOfDeceasedStudent - gameMap.NumOfEscapedStudent == 1) | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.EmergencyExit].EnterReadLock(); | |||
| try | |||
| { | |||
| foreach (EmergencyExit emergencyExit in gameMap.GameObjDict[GameObjType.EmergencyExit]) | |||
| if (emergencyExit.CanOpen) | |||
| { | |||
| emergencyExit.IsOpen = true; | |||
| break; | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.EmergencyExit].ExitReadLock(); | |||
| } | |||
| } | |||
| this.characterManager = characterManager; | |||
| } | |||
| private void BombObj(Bullet bullet, GameObj objBeingShot) | |||
| @@ -164,11 +57,11 @@ namespace Gaming | |||
| } | |||
| if (whoBeAttacked.BeAttacked(bullet)) | |||
| { | |||
| BeAddictedToGame(whoBeAttacked, (Ghost)bullet.Parent); | |||
| characterManager.BeAddictedToGame(whoBeAttacked, (Ghost)bullet.Parent); | |||
| } | |||
| if (whoBeAttacked.CanBeAwed()) | |||
| { | |||
| if (BeStunned(whoBeAttacked, GameData.basicStunnedTimeOfStudent)) | |||
| if (CharacterManager.BeStunned(whoBeAttacked, GameData.basicStunnedTimeOfStudent)) | |||
| bullet.Parent.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.basicStunnedTimeOfStudent)); | |||
| } | |||
| } | |||
| @@ -201,14 +94,14 @@ namespace Gaming | |||
| { | |||
| if (objBeingShot == null) | |||
| { | |||
| BackSwing((Character?)bullet.Parent, bullet.Backswing); | |||
| CharacterManager.BackSwing((Character?)bullet.Parent, bullet.Backswing); | |||
| return; | |||
| } | |||
| Debugger.Output(bullet, bullet.TypeOfBullet.ToString()); | |||
| BombObj(bullet, objBeingShot); | |||
| BackSwing((Character?)bullet.Parent, bullet.RecoveryFromHit); | |||
| CharacterManager.BackSwing((Character?)bullet.Parent, bullet.RecoveryFromHit); | |||
| return; | |||
| } | |||
| @@ -259,48 +152,14 @@ namespace Gaming | |||
| { | |||
| BombObj(bullet, beAttackedObj); | |||
| } | |||
| beAttackedList.Clear(); | |||
| if (objBeingShot == null) | |||
| { | |||
| if (bullet.Backswing > 0) | |||
| { | |||
| bullet.Parent.PlayerState = PlayerStateType.Swinging; | |||
| new Thread | |||
| (() => | |||
| { | |||
| Thread.Sleep(bullet.Backswing); | |||
| if (gameMap.Timer.IsGaming && bullet.Parent.PlayerState == PlayerStateType.Swinging) | |||
| { | |||
| bullet.Parent.PlayerState = PlayerStateType.Null; | |||
| } | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| } | |||
| CharacterManager.BackSwing((Character?)bullet.Parent, bullet.Backswing); | |||
| } | |||
| else | |||
| { | |||
| if (bullet.RecoveryFromHit > 0) | |||
| { | |||
| bullet.Parent.PlayerState = PlayerStateType.Swinging; | |||
| new Thread | |||
| (() => | |||
| { | |||
| Thread.Sleep(bullet.RecoveryFromHit); | |||
| if (gameMap.Timer.IsGaming && bullet.Parent.PlayerState == PlayerStateType.Swinging) | |||
| { | |||
| bullet.Parent.PlayerState = PlayerStateType.Null; | |||
| } | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| } | |||
| } | |||
| beAttackedList.Clear(); | |||
| CharacterManager.BackSwing((Character?)bullet.Parent, bullet.RecoveryFromHit); | |||
| } | |||
| public bool Attack(Character? player, double angle) | |||
| @@ -0,0 +1,306 @@ | |||
| using System; | |||
| using System.Threading; | |||
| using System.Collections.Generic; | |||
| using GameClass.GameObj; | |||
| using Preparation.Utility; | |||
| using GameEngine; | |||
| using Preparation.Interface; | |||
| using Timothy.FrameRateTask; | |||
| using System.Numerics; | |||
| namespace Gaming | |||
| { | |||
| public partial class Game | |||
| { | |||
| private readonly CharacterManager characterManager; | |||
| private class CharacterManager | |||
| { | |||
| readonly Map gameMap; | |||
| public CharacterManager(Map gameMap) | |||
| { | |||
| this.gameMap = gameMap; | |||
| } | |||
| public Character? AddPlayer(XY pos, int teamID, int playerID, CharacterType characterType, Character? parent = null) | |||
| { | |||
| Character newPlayer; | |||
| if (characterType == CharacterType.Robot) | |||
| { | |||
| newPlayer = new Golem(pos, GameData.characterRadius, parent); | |||
| } | |||
| else newPlayer = (GameData.IsGhost(characterType)) ? new Ghost(pos, GameData.characterRadius, characterType) : new Student(pos, GameData.characterRadius, characterType); | |||
| gameMap.Add(newPlayer); | |||
| newPlayer.TeamID = teamID; | |||
| newPlayer.PlayerID = playerID; | |||
| #region 人物装弹 | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| while (!gameMap.Timer.IsGaming) | |||
| Thread.Sleep(Math.Max(newPlayer.CD, GameData.checkInterval)); | |||
| long lastTime = Environment.TickCount64; | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, | |||
| loopToDo: () => | |||
| { | |||
| long nowTime = Environment.TickCount64; | |||
| if (newPlayer.BulletNum == newPlayer.MaxBulletNum) | |||
| lastTime = nowTime; | |||
| else if (nowTime - lastTime >= newPlayer.CD) | |||
| { | |||
| _ = newPlayer.TryAddBulletNum(); | |||
| lastTime = nowTime; | |||
| } | |||
| }, | |||
| timeInterval: GameData.checkInterval, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| { | |||
| AllowTimeExceed = true/*, | |||
| MaxTolerantTimeExceedCount = 5, | |||
| TimeExceedAction = exceedTooMuch => | |||
| { | |||
| if (exceedTooMuch) Console.WriteLine("The computer runs too slow that it cannot check the color below the player in time!"); | |||
| }*/ | |||
| } | |||
| .Start(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| #endregion | |||
| #region BGM,牵制得分更新 | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| while (!gameMap.Timer.IsGaming) | |||
| Thread.Sleep(GameData.checkInterval); | |||
| int TimePinningDown = 0, ScoreAdded = 0; | |||
| bool noise = false; | |||
| if (!newPlayer.IsGhost()) | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| { | |||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (person.IsGhost()) | |||
| { | |||
| if (person.CharacterType == CharacterType.ANoisyPerson) | |||
| { | |||
| noise = true; | |||
| newPlayer.AddBgm(BgmType.GhostIsComing, 1411180); | |||
| newPlayer.AddBgm(BgmType.GeneratorIsBeingFixed, 154991); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||
| } | |||
| } | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, | |||
| loopToDo: () => | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| { | |||
| if (newPlayer.IsGhost()) | |||
| { | |||
| double bgmVolume = 0; | |||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (!person.IsGhost() && XY.Distance(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment)) | |||
| { | |||
| if ((double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position) > bgmVolume) | |||
| bgmVolume = newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position); | |||
| } | |||
| } | |||
| if (bgmVolume > 0) | |||
| newPlayer.AddBgm(BgmType.StudentIsApproaching, bgmVolume); | |||
| } | |||
| else | |||
| { | |||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (person.IsGhost()) | |||
| { | |||
| if (!noise && XY.Distance(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment)) | |||
| newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position)); | |||
| if (XY.Distance(newPlayer.Position, person.Position) <= GameData.basicViewRange) | |||
| { | |||
| TimePinningDown += GameData.checkInterval; | |||
| newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded); | |||
| ScoreAdded = GameData.StudentScorePinDown(TimePinningDown); | |||
| } | |||
| else TimePinningDown = ScoreAdded = 0; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||
| } | |||
| if (!noise) | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Generator].EnterReadLock(); | |||
| try | |||
| { | |||
| double bgmVolume = 0; | |||
| foreach (Generator generator in gameMap.GameObjDict[GameObjType.Generator]) | |||
| { | |||
| if (XY.Distance(newPlayer.Position, generator.Position) <= newPlayer.AlertnessRadius) | |||
| { | |||
| if ((double)newPlayer.AlertnessRadius * generator.DegreeOfRepair / GameData.degreeOfFixedGenerator / XY.Distance(newPlayer.Position, generator.Position) > bgmVolume) | |||
| bgmVolume = (double)newPlayer.AlertnessRadius * generator.DegreeOfRepair / GameData.degreeOfFixedGenerator / XY.Distance(newPlayer.Position, generator.Position); | |||
| } | |||
| } | |||
| if (bgmVolume > 0) | |||
| newPlayer.AddBgm(BgmType.GeneratorIsBeingFixed, bgmVolume); | |||
| } | |||
| finally | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Generator].ExitReadLock(); | |||
| } | |||
| } | |||
| }, | |||
| timeInterval: GameData.checkInterval, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| { | |||
| AllowTimeExceed = true/*, | |||
| MaxTolerantTimeExceedCount = 5, | |||
| TimeExceedAction = exceedTooMuch => | |||
| { | |||
| if (exceedTooMuch) Console.WriteLine("The computer runs too slow that it cannot check the color below the player in time!"); | |||
| }*/ | |||
| } | |||
| .Start(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| #endregion | |||
| return newPlayer; | |||
| } | |||
| public void BeAddictedToGame(Student player, Ghost ghost) | |||
| { | |||
| ghost.AddScore(GameData.TrickerScoreStudentBeAddicted); | |||
| new Thread | |||
| (() => | |||
| { | |||
| if (player.GamingAddiction > GameData.BeginGamingAddiction && player.GamingAddiction < GameData.MidGamingAddiction) | |||
| player.GamingAddiction = GameData.MidGamingAddiction; | |||
| player.PlayerState = PlayerStateType.Addicted; | |||
| #if DEBUG | |||
| Debugger.Output(player, " is addicted "); | |||
| #endif | |||
| new FrameRateTaskExecutor<int>( | |||
| () => (player.PlayerState == PlayerStateType.Addicted || player.PlayerState == PlayerStateType.Rescued) && player.GamingAddiction < player.MaxGamingAddiction && gameMap.Timer.IsGaming, | |||
| () => | |||
| { | |||
| player.GamingAddiction += (player.PlayerState == PlayerStateType.Addicted) ? GameData.frameDuration : 0; | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| () => | |||
| { | |||
| if (player.GamingAddiction == player.MaxGamingAddiction && gameMap.Timer.IsGaming) | |||
| { | |||
| ghost.AddScore(GameData.TrickerScoreStudentDie); | |||
| Die(player); | |||
| } | |||
| return 0; | |||
| } | |||
| ) | |||
| .Start(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| } | |||
| public static bool BeStunned(Character player, int time) | |||
| { | |||
| if (player.PlayerState == PlayerStateType.Stunned || player.NoHp()) return false; | |||
| new Thread | |||
| (() => | |||
| { | |||
| player.PlayerState = PlayerStateType.Stunned; | |||
| Thread.Sleep(time); | |||
| if (player.PlayerState == PlayerStateType.Stunned) | |||
| player.PlayerState = PlayerStateType.Null; | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return true; | |||
| } | |||
| public static bool BackSwing(Character? player, int time) | |||
| { | |||
| if (player == null || time <= 0) return false; | |||
| if (player.PlayerState == PlayerStateType.Swinging || (!player.Commandable() && player.PlayerState != PlayerStateType.TryingToAttack)) return false; | |||
| player.PlayerState = PlayerStateType.Swinging; | |||
| new Thread | |||
| (() => | |||
| { | |||
| Thread.Sleep(time); | |||
| if (player.PlayerState == PlayerStateType.Swinging) | |||
| { | |||
| player.PlayerState = PlayerStateType.Null; | |||
| } | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return true; | |||
| } | |||
| private void Die(Character player) | |||
| { | |||
| #if DEBUG | |||
| Debugger.Output(player, "die."); | |||
| #endif | |||
| player.Die(PlayerStateType.Deceased); | |||
| for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | |||
| { | |||
| Prop? prop = player.UseProp(i); | |||
| if (prop != null) | |||
| { | |||
| prop.ReSetPos(player.Position, gameMap.GetPlaceType(player.Position)); | |||
| gameMap.Add(prop); | |||
| } | |||
| } | |||
| ++gameMap.NumOfDeceasedStudent; | |||
| if (GameData.numOfStudent - gameMap.NumOfDeceasedStudent - gameMap.NumOfEscapedStudent == 1) | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.EmergencyExit].EnterReadLock(); | |||
| try | |||
| { | |||
| foreach (EmergencyExit emergencyExit in gameMap.GameObjDict[GameObjType.EmergencyExit]) | |||
| if (emergencyExit.CanOpen) | |||
| { | |||
| emergencyExit.IsOpen = true; | |||
| break; | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.EmergencyExit].ExitReadLock(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -14,10 +14,10 @@ namespace Gaming | |||
| public struct PlayerInitInfo | |||
| { | |||
| public uint birthPointIndex; | |||
| public long teamID; | |||
| public long playerID; | |||
| public int teamID; | |||
| public int playerID; | |||
| public CharacterType characterType; | |||
| public PlayerInitInfo(uint birthPointIndex, long teamID, long playerID, CharacterType characterType) | |||
| public PlayerInitInfo(uint birthPointIndex, int teamID, int playerID, CharacterType characterType) | |||
| { | |||
| this.birthPointIndex = birthPointIndex; | |||
| this.teamID = teamID; | |||
| @@ -39,171 +39,12 @@ namespace Gaming | |||
| return GameObj.invalidID; | |||
| XY pos = gameMap.BirthPointList[playerInitInfo.birthPointIndex]; | |||
| Character? newPlayer = characterManager.AddPlayer(pos, playerInitInfo.teamID, playerInitInfo.playerID, playerInitInfo.characterType); | |||
| if (newPlayer == null) return GameObj.invalidID; | |||
| // Console.WriteLine($"x,y: {pos.x},{pos.y}"); | |||
| Character newPlayer = (GameData.IsGhost(playerInitInfo.characterType)) ? new Ghost(pos, GameData.characterRadius, playerInitInfo.characterType) : new Student(pos, GameData.characterRadius, playerInitInfo.characterType); | |||
| gameMap.Add(newPlayer); | |||
| // Console.WriteLine($"GameObjDict[GameObjType.Character] length:{gameMap.GameObjDict[GameObjType.Character].Count}"); | |||
| teamList[(int)playerInitInfo.teamID].AddPlayer(newPlayer); | |||
| newPlayer.TeamID = playerInitInfo.teamID; | |||
| newPlayer.PlayerID = playerInitInfo.playerID; | |||
| #region 人物装弹 | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| while (!gameMap.Timer.IsGaming) | |||
| Thread.Sleep(Math.Max(newPlayer.CD, GameData.checkInterval)); | |||
| long lastTime = Environment.TickCount64; | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, | |||
| loopToDo: () => | |||
| { | |||
| long nowTime = Environment.TickCount64; | |||
| if (newPlayer.BulletNum == newPlayer.MaxBulletNum) | |||
| lastTime = nowTime; | |||
| else if (nowTime - lastTime >= newPlayer.CD) | |||
| { | |||
| _ = newPlayer.TryAddBulletNum(); | |||
| lastTime = nowTime; | |||
| } | |||
| }, | |||
| timeInterval: GameData.checkInterval, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| { | |||
| AllowTimeExceed = true/*, | |||
| MaxTolerantTimeExceedCount = 5, | |||
| TimeExceedAction = exceedTooMuch => | |||
| { | |||
| if (exceedTooMuch) Console.WriteLine("The computer runs too slow that it cannot check the color below the player in time!"); | |||
| }*/ | |||
| } | |||
| .Start(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| #endregion | |||
| #region BGM,牵制得分更新 | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| while (!gameMap.Timer.IsGaming) | |||
| Thread.Sleep(GameData.checkInterval); | |||
| int TimePinningDown = 0, ScoreAdded = 0; | |||
| bool noise = false; | |||
| if (!newPlayer.IsGhost()) | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| { | |||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (person.IsGhost()) | |||
| { | |||
| if (person.CharacterType == CharacterType.ANoisyPerson) | |||
| { | |||
| noise = true; | |||
| newPlayer.AddBgm(BgmType.GhostIsComing, 1411180); | |||
| newPlayer.AddBgm(BgmType.GeneratorIsBeingFixed, 154991); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||
| } | |||
| } | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, | |||
| loopToDo: () => | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| { | |||
| if (newPlayer.IsGhost()) | |||
| { | |||
| double bgmVolume = 0; | |||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (!person.IsGhost() && XY.Distance(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment)) | |||
| { | |||
| if ((double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position) > bgmVolume) | |||
| bgmVolume = newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position); | |||
| } | |||
| } | |||
| if (bgmVolume > 0) | |||
| newPlayer.AddBgm(BgmType.StudentIsApproaching, bgmVolume); | |||
| } | |||
| else | |||
| { | |||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (person.IsGhost()) | |||
| { | |||
| if (!noise && XY.Distance(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment)) | |||
| newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position)); | |||
| if (XY.Distance(newPlayer.Position, person.Position) <= GameData.basicViewRange) | |||
| { | |||
| TimePinningDown += GameData.checkInterval; | |||
| newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded); | |||
| ScoreAdded = GameData.StudentScorePinDown(TimePinningDown); | |||
| } | |||
| else TimePinningDown = ScoreAdded = 0; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||
| } | |||
| if (!noise) | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Generator].EnterReadLock(); | |||
| try | |||
| { | |||
| double bgmVolume = 0; | |||
| foreach (Generator generator in gameMap.GameObjDict[GameObjType.Generator]) | |||
| { | |||
| if (XY.Distance(newPlayer.Position, generator.Position) <= newPlayer.AlertnessRadius) | |||
| { | |||
| if ((double)newPlayer.AlertnessRadius * generator.DegreeOfRepair / GameData.degreeOfFixedGenerator / XY.Distance(newPlayer.Position, generator.Position) > bgmVolume) | |||
| bgmVolume = (double)newPlayer.AlertnessRadius * generator.DegreeOfRepair / GameData.degreeOfFixedGenerator / XY.Distance(newPlayer.Position, generator.Position); | |||
| } | |||
| } | |||
| if (bgmVolume > 0) | |||
| newPlayer.AddBgm(BgmType.GeneratorIsBeingFixed, bgmVolume); | |||
| } | |||
| finally | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Generator].ExitReadLock(); | |||
| } | |||
| } | |||
| }, | |||
| timeInterval: GameData.checkInterval, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| { | |||
| AllowTimeExceed = true/*, | |||
| MaxTolerantTimeExceedCount = 5, | |||
| TimeExceedAction = exceedTooMuch => | |||
| { | |||
| if (exceedTooMuch) Console.WriteLine("The computer runs too slow that it cannot check the color below the player in time!"); | |||
| }*/ | |||
| } | |||
| .Start(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| #endregion | |||
| return newPlayer.ID; | |||
| } | |||
| public bool StartGame(int milliSeconds) | |||
| @@ -529,10 +370,11 @@ namespace Gaming | |||
| teamList.Add(new Team()); | |||
| } | |||
| attackManager = new AttackManager(gameMap); | |||
| characterManager = new CharacterManager(gameMap); | |||
| attackManager = new AttackManager(gameMap, characterManager); | |||
| actionManager = new ActionManager(gameMap); | |||
| propManager = new PropManager(gameMap); | |||
| skillManager = new SkillManager(gameMap, actionManager, attackManager, propManager); | |||
| skillManager = new SkillManager(gameMap, actionManager, attackManager, propManager, characterManager); | |||
| } | |||
| } | |||
| } | |||
| @@ -106,7 +106,7 @@ namespace Gaming | |||
| { | |||
| if (!character.IsGhost() && XY.Distance(character.Position, player.Position) <= player.ViewRange) | |||
| { | |||
| if (AttackManager.BeStunned(character, GameData.TimeOfStudentFaintingWhenHowl)) | |||
| if (CharacterManager.BeStunned(character, GameData.TimeOfStudentFaintingWhenHowl)) | |||
| player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.TimeOfStudentFaintingWhenHowl)); | |||
| break; | |||
| } | |||
| @@ -116,7 +116,7 @@ namespace Gaming | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||
| } | |||
| AttackManager.BackSwing(player, GameData.TimeOfGhostSwingingAfterHowl); | |||
| CharacterManager.BackSwing(player, GameData.TimeOfGhostSwingingAfterHowl); | |||
| Debugger.Output(player, "howled!"); | |||
| }, | |||
| () => | |||
| @@ -137,7 +137,7 @@ namespace Gaming | |||
| || character.PlayerState == PlayerStateType.UsingSkill || character.PlayerState == PlayerStateType.LockingOrOpeningTheDoor || character.PlayerState == PlayerStateType.OpeningTheChest) | |||
| && gameMap.CanSee(player, character)) | |||
| { | |||
| if (AttackManager.BeStunned(character, GameData.TimeOfGhostFaintingWhenPunish + (player.MaxHp - player.HP) / GameData.timeFactorOfGhostFainting)) | |||
| if (CharacterManager.BeStunned(character, GameData.TimeOfGhostFaintingWhenPunish + (player.MaxHp - player.HP) / GameData.timeFactorOfGhostFainting)) | |||
| player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.TimeOfGhostFaintingWhenPunish + (player.MaxHp - player.HP) / GameData.timeFactorOfGhostFainting)); | |||
| break; | |||
| } | |||
| @@ -69,15 +69,17 @@ namespace Gaming | |||
| } | |||
| private readonly Map gameMap; | |||
| private readonly CharacterManager characterManager; | |||
| private readonly ActionManager actionManager; | |||
| private readonly AttackManager attackManager; | |||
| private readonly PropManager propManager; | |||
| public SkillManager(Map gameMap, ActionManager action, AttackManager attack, PropManager prop) | |||
| public SkillManager(Map gameMap, ActionManager action, AttackManager attack, PropManager prop, CharacterManager characterManager) | |||
| { | |||
| this.gameMap = gameMap; | |||
| this.actionManager = action; | |||
| this.propManager = prop; | |||
| this.attackManager = attack; | |||
| this.characterManager = characterManager; | |||
| } | |||
| } | |||
| } | |||
| @@ -98,7 +98,6 @@ namespace Preparation.Interface | |||
| public int speedOfOpenChest = (int)(GameData.basicSpeedOfOpenChest * 1.1); | |||
| public int SpeedOfOpenChest => speedOfOpenChest; | |||
| } | |||
| public class ANoisyPerson : IGhost | |||
| { | |||
| private const int moveSpeed = (int)(GameData.basicMoveSpeed * 1.1); | |||
| @@ -250,7 +249,7 @@ namespace Preparation.Interface | |||
| public const int alertnessRadius = (int)(GameData.basicAlertnessRadius * 0.9); | |||
| public int AlertnessRadius => alertnessRadius; | |||
| public int viewRange = GameData.basicViewRange; | |||
| public int viewRange = (int)(GameData.basicViewRange * 0.9); | |||
| public int ViewRange => viewRange; | |||
| public int timeOfOpeningOrLocking = GameData.basicSpeedOfOpeningOrLocking; | |||
| @@ -262,4 +261,90 @@ namespace Preparation.Interface | |||
| public int speedOfOpenChest = GameData.basicSpeedOfOpenChest; | |||
| public int SpeedOfOpenChest => speedOfOpenChest; | |||
| } | |||
| } | |||
| public class Robot : IStudent | |||
| { | |||
| private const int moveSpeed = (int)(GameData.basicMoveSpeed); | |||
| public int MoveSpeed => moveSpeed; | |||
| private const int maxHp = (int)(GameData.basicHp / 2.5); | |||
| public int MaxHp => maxHp; | |||
| private const int maxGamingAddiction = 0; | |||
| public int MaxGamingAddiction => maxGamingAddiction; | |||
| public const int maxBulletNum = 0; | |||
| public int MaxBulletNum => maxBulletNum; | |||
| public BulletType InitBullet => BulletType.Null; | |||
| public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { }); | |||
| public List<PassiveSkillType> ListOfIPassiveSkill => new(new PassiveSkillType[] { }); | |||
| public const int fixSpeed = GameData.basicFixSpeed; | |||
| public int FixSpeed => fixSpeed; | |||
| public const int treatSpeed = GameData.basicTreatSpeed * 8 / 10; | |||
| public int TreatSpeed => treatSpeed; | |||
| public const double concealment = GameData.basicConcealment; | |||
| public double Concealment => concealment; | |||
| public const int alertnessRadius = (int)(GameData.basicAlertnessRadius); | |||
| public int AlertnessRadius => alertnessRadius; | |||
| public int viewRange = GameData.basicViewRange; | |||
| public int ViewRange => viewRange; | |||
| public int timeOfOpeningOrLocking = GameData.basicSpeedOfOpeningOrLocking; | |||
| public int TimeOfOpeningOrLocking => timeOfOpeningOrLocking; | |||
| public int speedOfClimbingThroughWindows = 1; | |||
| public int SpeedOfClimbingThroughWindows => speedOfClimbingThroughWindows; | |||
| public int speedOfOpenChest = GameData.basicSpeedOfOpenChest; | |||
| public int SpeedOfOpenChest => speedOfOpenChest; | |||
| } | |||
| public class TechOtaku : IStudent | |||
| { | |||
| private const int moveSpeed = (int)(GameData.basicMoveSpeed * 0.75); | |||
| public int MoveSpeed => moveSpeed; | |||
| private const int maxHp = (int)(GameData.basicHp * 0.9); | |||
| public int MaxHp => maxHp; | |||
| private const int maxGamingAddiction = (int)(GameData.basicMaxGamingAddiction * 1.1); | |||
| public int MaxGamingAddiction => maxGamingAddiction; | |||
| public const int maxBulletNum = 0; | |||
| public int MaxBulletNum => maxBulletNum; | |||
| public BulletType InitBullet => BulletType.Null; | |||
| public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { }); | |||
| public List<PassiveSkillType> ListOfIPassiveSkill => new(new PassiveSkillType[] { }); | |||
| public const int fixSpeed = (int)(GameData.basicFixSpeed * 1.1); | |||
| public int FixSpeed => fixSpeed; | |||
| public const int treatSpeed = GameData.basicTreatSpeed * 9 / 10; | |||
| public int TreatSpeed => treatSpeed; | |||
| public const double concealment = GameData.basicConcealment; | |||
| public double Concealment => concealment; | |||
| public const int alertnessRadius = (int)(GameData.basicAlertnessRadius); | |||
| public int AlertnessRadius => alertnessRadius; | |||
| public int viewRange = (int)(GameData.basicViewRange * 0.9); | |||
| public int ViewRange => viewRange; | |||
| public int timeOfOpeningOrLocking = GameData.basicSpeedOfOpeningOrLocking; | |||
| public int TimeOfOpeningOrLocking => timeOfOpeningOrLocking; | |||
| public int speedOfClimbingThroughWindows = (int)(GameData.basicStudentSpeedOfClimbingThroughWindows * 3 / 4); | |||
| public int SpeedOfClimbingThroughWindows => speedOfClimbingThroughWindows; | |||
| public int speedOfOpenChest = GameData.basicSpeedOfOpenChest; | |||
| public int SpeedOfOpenChest => speedOfOpenChest; | |||
| } | |||
| } | |||
| @@ -77,10 +77,11 @@ namespace Preparation.Utility | |||
| Assassin = 1, | |||
| Athlete = 2, | |||
| Klee = 3, | |||
| SpeedUpWhenLeavingGrass = 4, | |||
| TechOtaku = 4, | |||
| Teacher = 5, | |||
| StraightAStudent = 6, | |||
| ANoisyPerson = 7, | |||
| Robot = 8, | |||
| } | |||
| public enum ActiveSkillType // 主动技能 | |||
| { | |||
| @@ -182,6 +182,10 @@ namespace Server | |||
| return StudentType._2; | |||
| case CharacterType.StraightAStudent: | |||
| return StudentType._3; | |||
| case CharacterType.Robot: | |||
| return StudentType._4; | |||
| case CharacterType.TechOtaku: | |||
| return StudentType.TechOtaku; | |||
| default: | |||
| return Protobuf.StudentType.NullStudentType; | |||
| } | |||
| @@ -197,6 +201,10 @@ namespace Server | |||
| return Preparation.Utility.CharacterType.Teacher; | |||
| case StudentType._3: | |||
| return CharacterType.StraightAStudent; | |||
| case StudentType._4: | |||
| return CharacterType.Robot; | |||
| case StudentType.TechOtaku: | |||
| return CharacterType.TechOtaku; | |||
| default: | |||
| return Preparation.Utility.CharacterType.Null; | |||
| } | |||
| @@ -312,7 +312,7 @@ namespace Server | |||
| lock (addPlayerLock) | |||
| { | |||
| Game.PlayerInitInfo playerInitInfo = new(GetBirthPointIdx(request.PlayerId), PlayerTypeToTeamID(request.PlayerType), request.PlayerId, characterType); | |||
| Game.PlayerInitInfo playerInitInfo = new(GetBirthPointIdx(request.PlayerId), PlayerTypeToTeamID(request.PlayerType), (int)request.PlayerId, characterType); | |||
| long newPlayerID = game.AddPlayer(playerInitInfo); | |||
| if (newPlayerID == GameObj.invalidID) | |||
| return; | |||