diff --git a/docs/GameRules.md b/docs/GameRules.md index 9f6f16c..42a06f0 100644 --- a/docs/GameRules.md +++ b/docs/GameRules.md @@ -342,6 +342,7 @@ $$ - 开锁门进度中断后清空 ### 窗 +- 由于窗户被占用导致翻窗失败会使先前行动停止 - 翻越窗户是一种交互行为,翻窗一共有两个过程 - 跳上窗:从当前位置到窗边缘中点,位置瞬移,时间=距离/爬窗速度。中断时,停留在原位置 - 爬窗:从窗一侧边缘中点到另一侧格子中心,位置渐移,时间=距离/爬窗速度。中断时,停留在另一侧格子中心 diff --git a/docs/游戏机制与平衡性调整更新草案.md b/docs/游戏机制与平衡性调整更新草案.md index 2159323..8e4418e 100644 --- a/docs/游戏机制与平衡性调整更新草案.md +++ b/docs/游戏机制与平衡性调整更新草案.md @@ -1,5 +1,5 @@ # 游戏机制与平衡性调整更新草案 -v1.5 +v1.6 ## 说明 - 该草案尚未完全确定,请大家不要过分依靠该文档进行修改自己的代码 @@ -106,6 +106,12 @@ v1.5 - 被动技能Lucky!(新增):开局获得随机的一个道具(不会是钥匙) - 主动技能SparksNSplash(新增): - CD:45s, 持续时间:10s - - 技能使用瞬间,离输入的额外参数PlayerID代表的角色最近的本来停止运动的小炸弹开始追踪该角色(每50ms向该角色直线移动) + - 技能使用瞬间,对于输入的额外参数PlayerID代表的角色,距离最近的本已停止运动的小炸弹开始追踪该角色(每50ms向该角色直线移动) - 主动技能 蹦蹦炸弹 JumpyBomb - - 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上0°,45°,90°,135°,180°,225°,270°,315° 发出2个小炸弹 \ No newline at end of file + - 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上0°,45°,90°,135°,180°,225°,270°,315° 发出8个小炸弹 +- Idol + 主动技能ShowTime改为 + "持续时间内 + - 使警戒范围外的学生眩晕并每**500ms**发送向自己移动**500ms**的指令(速度为学生本应速度*二者距离/警戒范围) + - 对于视野范围(不是可视区域)内的学生每**500ms**加**1500**的沉迷度 + - 捣蛋鬼变为0.8倍速" \ No newline at end of file diff --git a/docs/版本更新说明.md b/docs/版本更新说明.md index 70f91f2..61f3801 100644 --- a/docs/版本更新说明.md +++ b/docs/版本更新说明.md @@ -21,11 +21,14 @@ - docs:更新了 游戏机制与平衡性调整更新草案.pdf - change:更改了地图的文件路径 -# 最新更新 +# 5月10日更新 - fix:修复JumpyDumpty的初始位置错误的问题 - fix:修正和重新说明攻击距离 - **攻击距离是指攻击(子弹)的移动距离,也就是说理论上最远被攻击的学生的中心与捣蛋鬼的中心=学生的半径+捣蛋鬼的半径+攻击距离+子弹半径(200)×2** - hotfix:修复小炸弹初始化类型错误的问题 - remove:去除了“实际上唤醒或勉励不同的人是有效的” - **重复发出同一类型的交互指令和移动指令是无效的** -- feat&fix:修复并**将`SendMessage`改为`SendTextMessage`与`SendBinaryMessage`** \ No newline at end of file +- feat&fix:修复并**将`SendMessage`改为`SendTextMessage`与`SendBinaryMessage`** + +# 最新更新 +- docs:更新了 游戏机制与平衡性调整更新草案.pdf \ No newline at end of file diff --git a/logic/GameClass/GameObj/Character/Character.cs b/logic/GameClass/GameObj/Character/Character.cs index 2ecee40..1fe012e 100644 --- a/logic/GameClass/GameObj/Character/Character.cs +++ b/logic/GameClass/GameObj/Character/Character.cs @@ -324,7 +324,17 @@ namespace GameClass.GameObj return (playerState != PlayerStateType.Deceased && playerState != PlayerStateType.Escaped && playerState != PlayerStateType.Addicted && playerState != PlayerStateType.Rescued && playerState != PlayerStateType.Swinging && playerState != PlayerStateType.TryingToAttack - && playerState != PlayerStateType.ClimbingThroughWindows && playerState != PlayerStateType.Stunned); + && playerState != PlayerStateType.ClimbingThroughWindows + && playerState != PlayerStateType.Stunned && playerState != PlayerStateType.Charmed); + } + } + public bool CanPinDown() + { + lock (actionLock) + { + return (playerState != PlayerStateType.Deceased && playerState != PlayerStateType.Escaped + && playerState != PlayerStateType.Addicted && playerState != PlayerStateType.Rescued + && playerState != PlayerStateType.Stunned && playerState != PlayerStateType.Charmed); } } public bool InteractingWithMapWithoutMoving() @@ -345,8 +355,9 @@ namespace GameClass.GameObj { lock (actionLock) return !(playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped - || playerState == PlayerStateType.Addicted || playerState == PlayerStateType.Rescued - || playerState == PlayerStateType.Treated || playerState == PlayerStateType.Stunned + || playerState == PlayerStateType.Addicted + || playerState == PlayerStateType.Rescued || playerState == PlayerStateType.Treated + || playerState == PlayerStateType.Stunned || playerState == PlayerStateType.Charmed || playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); } private GameObj? whatInteractingWith = null; @@ -354,28 +365,24 @@ namespace GameClass.GameObj public long ChangePlayerState(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) { - lock (actionLock) - { - whatInteractingWith = gameObj; - if (value != PlayerStateType.Moving) - IsMoving = false; - playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value; - //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); - return ++stateNum; - } + //只能被SetPlayerState引用 + whatInteractingWith = gameObj; + if (value != PlayerStateType.Moving) + IsMoving = false; + playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value; + //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); + return ++stateNum; } public long ChangePlayerStateInOneThread(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) { - lock (actionLock) - { - whatInteractingWith = gameObj; - if (value != PlayerStateType.Moving) - IsMoving = false; - playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value; - //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); - return stateNum; - } + //只能被SetPlayerState引用 + whatInteractingWith = gameObj; + if (value != PlayerStateType.Moving) + IsMoving = false; + playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value; + //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); + return stateNum; } public long SetPlayerStateNaturally() diff --git a/logic/GameClass/GameObj/Map/Map.cs b/logic/GameClass/GameObj/Map/Map.cs index 3a3869f..7e35cff 100644 --- a/logic/GameClass/GameObj/Map/Map.cs +++ b/logic/GameClass/GameObj/Map/Map.cs @@ -484,7 +484,7 @@ namespace GameClass.GameObj } case (uint)PlaceType.Window: { - Add(new Window(GameData.GetCellCenterPos(i, j))); + Add(new Window(GameData.GetCellCenterPos(i, j), mapResource[i - 1, j] == (uint)PlaceType.Wall)); break; } case (uint)PlaceType.BirthPoint1: diff --git a/logic/GameClass/GameObj/Map/Window.cs b/logic/GameClass/GameObj/Map/Window.cs index 864e58f..e9f2d0e 100644 --- a/logic/GameClass/GameObj/Map/Window.cs +++ b/logic/GameClass/GameObj/Map/Window.cs @@ -1,5 +1,7 @@ using Preparation.Interface; using Preparation.Utility; +using System.Numerics; +using System; namespace GameClass.GameObj { @@ -8,9 +10,10 @@ namespace GameClass.GameObj /// public class Window : Immovable { - public Window(XY initPos) : + public Window(XY initPos, bool xIsWall) : base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Window) { + this.xIsWall = xIsWall; } public override bool IsRigid => true; public override ShapeType Shape => ShapeType.Square; @@ -25,15 +28,68 @@ namespace GameClass.GameObj return false; } + public readonly bool xIsWall; + + private XY stage = new(0, 0); + public XY Stage + { + get + { + GameObjReaderWriterLock.EnterReadLock(); + try + { + return stage; + } + finally { GameObjReaderWriterLock.ExitReadLock(); } + } + } + private Character? whoIsClimbing = null; public Character? WhoIsClimbing { - get => whoIsClimbing; - set + get + { + GameObjReaderWriterLock.EnterReadLock(); + try + { + return whoIsClimbing; + } + finally { GameObjReaderWriterLock.ExitReadLock(); } + } + } + + public bool TryToClimb(Character character) + { + GameObjReaderWriterLock.EnterWriteLock(); + try + { + if (whoIsClimbing == null) + { + stage = new(0, 0); + whoIsClimbing = character; + return true; + } + else return false; + } + finally { GameObjReaderWriterLock.ExitWriteLock(); } + } + public void FinishClimbing() + { + GameObjReaderWriterLock.EnterWriteLock(); + try + { + whoIsClimbing = null; + } + finally { GameObjReaderWriterLock.ExitWriteLock(); } + } + public void Enter2Stage(XY xy) + { + GameObjReaderWriterLock.EnterWriteLock(); + try { - lock (gameObjLock) - whoIsClimbing = value; + stage = xy; } + finally { GameObjReaderWriterLock.ExitWriteLock(); } } } } diff --git a/logic/GameClass/GameObj/Moveable.cs b/logic/GameClass/GameObj/Moveable.cs index f31d0ed..2c78489 100644 --- a/logic/GameClass/GameObj/Moveable.cs +++ b/logic/GameClass/GameObj/Moveable.cs @@ -8,8 +8,23 @@ namespace GameClass.GameObj { protected readonly object actionLock = new(); public object ActionLock => actionLock; + //player.actionLock>其他.actionLock private readonly ReaderWriterLockSlim moveReaderWriterLock = new(); public ReaderWriterLockSlim MoveReaderWriterLock => moveReaderWriterLock; + + private Semaphore threadNum = new(1, 1); + public Semaphore ThreadNum + { + get + { + return threadNum; + } + set + { + threadNum = value; + } + } + protected long stateNum = 0; public long StateNum { @@ -61,14 +76,27 @@ namespace GameClass.GameObj } // 移动,改变坐标 - public long MovingSetPos(XY moveVec) + public long MovingSetPos(XY moveVec, long stateNo) { + if (moveVec.x != 0 || moveVec.y != 0) - lock (actionLock) + { + moveReaderWriterLock.EnterReadLock(); + try { - facingDirection = moveVec; - this.position += moveVec; + lock (actionLock) + { + if (!canMove || isRemoved) return -1; + if (stateNo != stateNum) return -1; + facingDirection = moveVec; + this.position += moveVec; + } } + finally + { + moveReaderWriterLock.ExitReadLock(); + } + } return moveVec * moveVec; } diff --git a/logic/GameEngine/MoveEngine.cs b/logic/GameEngine/MoveEngine.cs index fd84018..8370df5 100644 --- a/logic/GameEngine/MoveEngine.cs +++ b/logic/GameEngine/MoveEngine.cs @@ -64,21 +64,57 @@ namespace GameEngine /// /// 移动物体,默认obj.Rigid为true /// 移动的位移向量 - private void MoveMax(IMoveable obj, XY moveVec) + private bool MoveMax(IMoveable obj, XY moveVec, long stateNum) { /*由于四周是墙,所以人物永远不可能与越界方块碰撞*/ XY nextPos = obj.Position + moveVec; double maxLen = collisionChecker.FindMax(obj, nextPos, moveVec); maxLen = Math.Min(maxLen, obj.MoveSpeed / GameData.numOfStepPerSecond); - obj.MovingSetPos(new XY(moveVec, maxLen)); + return (obj.MovingSetPos(new XY(moveVec, maxLen), stateNum)) >= 0; } - public void MoveObj(IMoveable obj, int moveTime, double direction, long threadNum) + private bool LoopDo(IMoveable obj, double direction, ref double deltaLen, long stateNum) + { + double moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; + XY res = new(direction, moveVecLength); + + // 越界情况处理:如果越界,则与越界方块碰撞 + bool flag; // 循环标志 + do + { + flag = false; + IGameObj? collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res); + if (collisionObj == null) + break; + + switch (OnCollision(obj, collisionObj, res)) + { + case AfterCollision.ContinueCheck: + flag = true; + break; + case AfterCollision.Destroyed: + Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); + return false; + case AfterCollision.MoveMax: + if (!MoveMax(obj, res, stateNum)) return false; + moveVecLength = 0; + res = new XY(direction, moveVecLength); + break; + } + } while (flag); + + long moveL = obj.MovingSetPos(res, stateNum); + if (moveL == -1) return false; + deltaLen = deltaLen + moveVecLength - Math.Sqrt(moveL); + return true; + } + + public void MoveObj(IMoveable obj, int moveTime, double direction, long stateNum) { if (!gameTimer.IsGaming) return; lock (obj.ActionLock) { - if (!obj.IsAvailableForMove) return; + if (!obj.IsAvailableForMove) { obj.ThreadNum.Release(); return; } obj.IsMoving = true; } new Thread @@ -87,9 +123,9 @@ namespace GameEngine { double moveVecLength = 0.0; XY res = new(direction, moveVecLength); - double deltaLen = 0; // 转向,并用deltaLen存储行走的误差 + double deltaLen = (double)0.0; // 转向,并用deltaLen存储行走的误差 IGameObj? collisionObj = null; - bool isDestroyed = false; + bool isEnded = false; bool flag; // 循环标志 do @@ -106,34 +142,76 @@ namespace GameEngine break; case AfterCollision.Destroyed: Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); - isDestroyed = true; + isEnded = true; break; case AfterCollision.MoveMax: break; } } while (flag); - if (!isDestroyed) + if (isEnded) + { + obj.IsMoving = false; + obj.ThreadNum.Release(); + EndMove(obj); + return; + } + else { - new FrameRateTaskExecutor( - () => gameTimer.IsGaming && obj.CanMove && !obj.IsRemoved && obj.IsMoving, - () => + if (moveTime >= GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond) + { + Thread.Sleep(GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); + new FrameRateTaskExecutor( + () => gameTimer.IsGaming && obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved, + () => + { + return !(isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum)); + }, + GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond, + () => + { + return 0; + }, + maxTotalDuration: moveTime - GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond + ) { - moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; - res = new XY(direction, moveVecLength); + AllowTimeExceed = true, + MaxTolerantTimeExceedCount = ulong.MaxValue, + TimeExceedAction = b => + { + if (b) + Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!"); - //对人特殊处理 - if (threadNum > 0 && obj.StateNum != threadNum) return false; +#if DEBUG + else + { + Console.WriteLine("Debug info: Object moving time exceed for once."); + } +#endif + } + }.Start(); + if (!isEnded && obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved) + isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum); + } - // 越界情况处理:如果越界,则与越界方块碰撞 - bool flag; // 循环标志 - do + if (!isEnded && obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved) + { + int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); + if (leftTime > 0) + { + Thread.Sleep(leftTime); // 多移动的在这里补回来 + } + do + { + flag = false; + moveVecLength = (double)deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; + res = new XY(direction, moveVecLength); + if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) + { + obj.MovingSetPos(res, stateNum); + } + else { - flag = false; - collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res); - if (collisionObj == null) - break; - switch (OnCollision(obj, collisionObj, res)) { case AfterCollision.ContinueCheck: @@ -141,87 +219,20 @@ namespace GameEngine break; case AfterCollision.Destroyed: Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); - isDestroyed = true; - return false; + isEnded = true; + break; case AfterCollision.MoveMax: - if (threadNum == 0 || obj.StateNum == threadNum) - MoveMax(obj, res); + MoveMax(obj, res, stateNum); moveVecLength = 0; res = new XY(direction, moveVecLength); break; } - } while (flag); - - if (threadNum == 0 || obj.StateNum == threadNum) - deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res)); - - return true; - }, - GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond, - () => - { - int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); - bool flag; - do - { - flag = false; - if (!isDestroyed) - { - moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; - res = new XY(direction, moveVecLength); - if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) - { - if (threadNum == 0 || obj.StateNum == threadNum) - obj.MovingSetPos(res); - } - else - { - switch (OnCollision(obj, collisionObj, res)) - { - case AfterCollision.ContinueCheck: - flag = true; - break; - case AfterCollision.Destroyed: - Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); - isDestroyed = true; - break; - case AfterCollision.MoveMax: - if (threadNum == 0 || obj.StateNum == threadNum) - MoveMax(obj, res); - moveVecLength = 0; - res = new XY(direction, moveVecLength); - break; - } - } - } - } while (flag); - if (leftTime > 0 && obj.IsMoving) - { - Thread.Sleep(leftTime); // 多移动的在这里补回来 - } - lock (obj.ActionLock) - obj.IsMoving = false; // 结束移动 - EndMove(obj); - return 0; - }, - maxTotalDuration: moveTime - ) - { - AllowTimeExceed = true, - MaxTolerantTimeExceedCount = ulong.MaxValue, - TimeExceedAction = b => - { - if (b) - Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!"); - -#if DEBUG - else - { - Console.WriteLine("Debug info: Object moving time exceed for once."); } -#endif - } - }.Start(); + } while (flag); + } + obj.IsMoving = false; // 结束移动 + obj.ThreadNum.Release(); + EndMove(obj); } } ).Start(); diff --git a/logic/Gaming/ActionManager.cs b/logic/Gaming/ActionManager.cs index 894cab4..64a00ac 100644 --- a/logic/Gaming/ActionManager.cs +++ b/logic/Gaming/ActionManager.cs @@ -36,26 +36,59 @@ namespace Gaming public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) { if (moveTimeInMilliseconds < 5) return false; - if (!playerToMove.Commandable()) return false; - moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection, - characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving)); + long stateNum = characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving); + if (stateNum == -1) return false; + new Thread + ( + () => + { + playerToMove.ThreadNum.WaitOne(); + if (stateNum != playerToMove.StateNum) + playerToMove.ThreadNum.Release(); + else + moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection, stateNum); + } + ) + { IsBackground = true }.Start(); return true; } public bool MovePlayerWhenStunned(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) { - if (!playerToMove.Commandable() && playerToMove.PlayerState != PlayerStateType.Stunned) return false; - characterManager.BeStunned(playerToMove, moveTimeInMilliseconds); - moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection, playerToMove.StateNum); + if (playerToMove.CharacterType == CharacterType.Robot) return false; + long stateNum = characterManager.SetPlayerState(playerToMove, PlayerStateType.Charmed); + if (stateNum == -1) return false; + new Thread + (() => + { + playerToMove.ThreadNum.WaitOne(); + if (stateNum != playerToMove.StateNum) + playerToMove.ThreadNum.Release(); + else + { + moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection, playerToMove.StateNum); + Thread.Sleep(moveTimeInMilliseconds); + lock (playerToMove.ActionLock) + { + if (stateNum == playerToMove.StateNum) + playerToMove.SetPlayerStateNaturally(); + } + } + } + ) + { IsBackground = true }.Start(); return true; } public bool Stop(Character player) { - if (player.Commandable()) + lock (player.ActionLock) { - characterManager.SetPlayerState(player); - return true; + if (player.Commandable()) + { + characterManager.SetPlayerState(player); + return true; + } } return false; } @@ -279,12 +312,11 @@ namespace Gaming } public bool ClimbingThroughWindow(Character player) { - if (!player.Commandable()) - return false; Window? windowForClimb = (Window?)gameMap.OneForInteractInACross(player.Position, GameObjType.Window); + if (windowForClimb == null) return false; - if (windowForClimb == null || windowForClimb.WhoIsClimbing != null) - return false; + long stateNum = characterManager.SetPlayerState(player, PlayerStateType.ClimbingThroughWindows, windowForClimb); + if (stateNum == -1) return false; XY windowToPlayer = new( (Math.Abs(player.Position.x - windowForClimb.Position.x) > GameData.numOfPosGridPerCell / 2) ? (GameData.numOfPosGridPerCell / 2 * (player.Position.x > windowForClimb.Position.x ? 1 : -1)) : 0, @@ -296,59 +328,57 @@ namespace Gaming if (player.IsGhost() && !characterInWindow.IsGhost()) characterManager.BeAttacked((Student)(characterInWindow), player.Attack(characterInWindow.Position)); return false; - }*/ + } - //Wall addWall = new Wall(windowForClimb.Position - 2 * windowToPlayer); - // gameMap.Add(addWall); + Wall addWall = new Wall(windowForClimb.Position - 2 * windowToPlayer); + gameMap.Add(addWall);*/ - characterManager.SetPlayerState(player, PlayerStateType.ClimbingThroughWindows); - long threadNum = player.StateNum; - windowForClimb.WhoIsClimbing = player; new Thread - ( - () => - { - new FrameRateTaskExecutor( - loopCondition: () => threadNum == player.StateNum && gameMap.Timer.IsGaming, - loopToDo: () => { }, - timeInterval: GameData.frameDuration, - finallyReturn: () => 0, - maxTotalDuration: (int)((windowToPlayer + windowForClimb.Position - player.Position).Length() * 1000 / player.MoveSpeed) - ) - .Start(); - if (player.PlayerState != PlayerStateType.ClimbingThroughWindows) - { - windowForClimb.WhoIsClimbing = null; - return; - } - - player.ReSetPos(windowToPlayer + windowForClimb.Position); - player.MoveSpeed = player.SpeedOfClimbingThroughWindows; - - moveEngine.MoveObj(player, (int)(windowToPlayer.Length() * 3.0 * 1000 / player.MoveSpeed), (-1 * windowToPlayer).Angle(), threadNum); - - new FrameRateTaskExecutor( - loopCondition: () => threadNum == player.StateNum && gameMap.Timer.IsGaming, - loopToDo: () => + ( + () => { - }, - timeInterval: GameData.frameDuration, - finallyReturn: () => 0, - maxTotalDuration: (int)(windowToPlayer.Length() * 3.0 * 1000 / player.MoveSpeed) - ) - .Start(); - XY PosJumpOff = windowForClimb.Position - 2 * windowToPlayer; - player.ReSetPos(PosJumpOff); - player.MoveSpeed = player.ReCalculateBuff(BuffType.AddSpeed, player.OrgMoveSpeed, GameData.MaxSpeed, GameData.MinSpeed); - windowForClimb.WhoIsClimbing = null; - // gameMap.Remove(addWall); - if (threadNum == player.StateNum) - { - characterManager.SetPlayerState(player); - } - } - - ) + player.ThreadNum.WaitOne(); + if (stateNum != player.StateNum) + { + player.ThreadNum.Release(); + } + else + { + if (!windowForClimb.TryToClimb(player)) + { + player.ThreadNum.Release(); + player.SetPlayerStateNaturally(); + } + else + { + Thread.Sleep((int)((windowToPlayer + windowForClimb.Position - player.Position).Length() * 1000 / player.MoveSpeed)); + + lock (player.ActionLock) + { + if (player.StateNum != stateNum) return; + player.ReSetPos(windowToPlayer + windowForClimb.Position); + windowForClimb.Enter2Stage(windowForClimb.Position - 2 * windowToPlayer); + } + + player.MoveSpeed = player.SpeedOfClimbingThroughWindows; + moveEngine.MoveObj(player, GameData.numOfPosGridPerCell * 3 * 1000 / player.MoveSpeed / 2, (-1 * windowToPlayer).Angle(), stateNum); + + Thread.Sleep(GameData.numOfPosGridPerCell * 3 * 1000 / player.MoveSpeed / 2); + + player.MoveSpeed = player.ReCalculateBuff(BuffType.AddSpeed, player.OrgMoveSpeed, GameData.MaxSpeed, GameData.MinSpeed); + + lock (player.ActionLock) + { + if (stateNum == player.StateNum) + { + characterManager.SetPlayerState(player); + windowForClimb.FinishClimbing(); + } + } + } + } + } + ) { IsBackground = true }.Start(); return true; @@ -400,7 +430,6 @@ namespace Gaming timeInterval: GameData.frameDuration, finallyReturn: () => 0 ) - .Start(); if (doorToLock.OpenOrLockDegree >= GameData.degreeOfLockingOrOpeningTheDoor) { diff --git a/logic/Gaming/AttackManager.cs b/logic/Gaming/AttackManager.cs index 70aa202..a94a18d 100644 --- a/logic/Gaming/AttackManager.cs +++ b/logic/Gaming/AttackManager.cs @@ -47,7 +47,15 @@ namespace Gaming if (bullet == null) return; Debugger.Output(bullet, "Attack in " + pos.ToString()); gameMap.Add(bullet); - moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms + new Thread + ( + () => + { + bullet.ThreadNum.WaitOne(); + moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms + } + ) + { IsBackground = true }.Start(); } private void BombObj(Bullet bullet, GameObj objBeingShot) @@ -195,7 +203,15 @@ namespace Gaming { Debugger.Output(bullet, "Attack in " + bullet.Position.ToString()); gameMap.Add(bullet); - moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms + new Thread + ( + () => + { + bullet.ThreadNum.WaitOne(); + moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms + } + ) + { IsBackground = true }.Start(); if (bullet.CastTime > 0) { characterManager.SetPlayerState(player, PlayerStateType.TryingToAttack); diff --git a/logic/Gaming/CharacterManager .cs b/logic/Gaming/CharacterManager .cs index 4c80b8b..c0f0f91 100644 --- a/logic/Gaming/CharacterManager .cs +++ b/logic/Gaming/CharacterManager .cs @@ -28,26 +28,48 @@ namespace Gaming case PlayerStateType.Escaped: case PlayerStateType.Deceased: return -1; - case PlayerStateType.OpeningTheChest: - ((Chest)player.WhatInteractingWith!).StopOpen(); - return player.ChangePlayerState(value, gameObj); - case PlayerStateType.OpeningTheDoorway: - Doorway doorway = (Doorway)player.WhatInteractingWith!; - doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime; - doorway.OpenStartTime = 0; - return player.ChangePlayerState(value, gameObj); + case PlayerStateType.Addicted: if (value == PlayerStateType.Rescued) return player.ChangePlayerStateInOneThread(value, gameObj); - else if (value==PlayerStateType.Null) + else if (value == PlayerStateType.Null) return player.ChangePlayerState(value, gameObj); else return -1; case PlayerStateType.Rescued: if (value == PlayerStateType.Addicted) return player.ChangePlayerStateInOneThread(value, gameObj); - else if(value==PlayerStateType.Null) + else if (value == PlayerStateType.Null) + return player.ChangePlayerState(value, gameObj); + else return -1; + + case PlayerStateType.TryingToAttack: + case PlayerStateType.Stunned: + case PlayerStateType.Charmed: + case PlayerStateType.Swinging: + if (value != PlayerStateType.Moving && value != PlayerStateType.ClimbingThroughWindows) + return player.ChangePlayerState(value, gameObj); + else return -1; + case PlayerStateType.ClimbingThroughWindows: + if (value != PlayerStateType.Moving) + { + Window window = (Window)player.WhatInteractingWith!; + window.FinishClimbing(); + if (window.Stage.x == 0) + player.ThreadNum.Release(); + else player.ReSetPos(window.Stage); return player.ChangePlayerState(value, gameObj); + } else return -1; + + case PlayerStateType.OpeningTheChest: + ((Chest)player.WhatInteractingWith!).StopOpen(); + return player.ChangePlayerState(value, gameObj); + case PlayerStateType.OpeningTheDoorway: + Doorway doorway = (Doorway)player.WhatInteractingWith!; + doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime; + doorway.OpenStartTime = 0; + return player.ChangePlayerState(value, gameObj); + default: return player.ChangePlayerState(value, gameObj); } @@ -165,7 +187,7 @@ namespace Gaming newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.DistanceFloor3(newPlayer.Position, person.Position)); else newPlayer.AddBgm(BgmType.GhostIsComing, 0); } - if (newPlayer.CharacterType != CharacterType.Teacher && newPlayer.CharacterType != CharacterType.Robot && !newPlayer.NoHp() && newPlayer.PlayerState != PlayerStateType.Stunned && XY.DistanceFloor3(newPlayer.Position, person.Position) <= GameData.PinningDownRange) + if (newPlayer.CharacterType != CharacterType.Teacher && newPlayer.CharacterType != CharacterType.Robot && newPlayer.CanPinDown() && XY.DistanceFloor3(newPlayer.Position, person.Position) <= GameData.PinningDownRange) { TimePinningDown += GameData.checkInterval; newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded); diff --git a/logic/Gaming/PropManager.cs b/logic/Gaming/PropManager.cs index 2a971a9..5079a73 100644 --- a/logic/Gaming/PropManager.cs +++ b/logic/Gaming/PropManager.cs @@ -54,7 +54,7 @@ namespace Gaming else player.AddAp(GameData.PropDuration); break; case PropType.RecoveryFromDizziness: - if (player.PlayerState == PlayerStateType.Stunned) + if (player.PlayerState == PlayerStateType.Stunned || player.PlayerState == PlayerStateType.Charmed) { player.AddScore(GameData.ScorePropRecoverFromDizziness); player.SetPlayerStateNaturally(); diff --git a/logic/Preparation/Interface/IMoveable.cs b/logic/Preparation/Interface/IMoveable.cs index cd9cb15..01577c7 100644 --- a/logic/Preparation/Interface/IMoveable.cs +++ b/logic/Preparation/Interface/IMoveable.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using Preparation.Utility; namespace Preparation.Interface @@ -11,7 +12,8 @@ namespace Preparation.Interface public bool IsRemoved { get; } public bool IsAvailableForMove { get; } public long StateNum { get; } - public long MovingSetPos(XY moveVec); + public Semaphore ThreadNum { get; set; } + public long MovingSetPos(XY moveVec, long stateNum); public void ReSetCanMove(bool value); public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞 { diff --git a/logic/Preparation/Utility/EnumType.cs b/logic/Preparation/Utility/EnumType.cs index 9f426a3..5bd1032 100644 --- a/logic/Preparation/Utility/EnumType.cs +++ b/logic/Preparation/Utility/EnumType.cs @@ -24,6 +24,7 @@ namespace Preparation.Utility ClimbingThroughWindows = 15, UsingSkill = 16, OpeningTheDoorway = 17, + Charmed = 18, } public enum GameObjType { diff --git a/logic/Preparation/Utility/GameData.cs b/logic/Preparation/Utility/GameData.cs index 72c37b8..63f37d4 100644 --- a/logic/Preparation/Utility/GameData.cs +++ b/logic/Preparation/Utility/GameData.cs @@ -6,13 +6,13 @@ namespace Preparation.Utility public static class GameData { #region 基本常数 - public const int numOfStepPerSecond = 20; // 每秒行走的步数 + public const int numOfStepPerSecond = 100; // 每秒行走的步数 public const int tolerancesLength = 3; public const int adjustLength = 3; public const int frameDuration = 50; // 每帧时长 - public const int checkInterval = 50; // 检查位置标志、补充子弹的帧时长 + public const int checkInterval = 10; public const long gameDuration = 600000; // 游戏时长600000ms = 10min public const int LimitOfStopAndMove = 15; diff --git a/logic/Preparation/Utility/Transformation.cs b/logic/Preparation/Utility/Transformation.cs index 22b3464..0a997be 100644 --- a/logic/Preparation/Utility/Transformation.cs +++ b/logic/Preparation/Utility/Transformation.cs @@ -116,6 +116,7 @@ namespace Preparation.Utility case Preparation.Utility.PlayerStateType.Rescuing: return PlayerState.Rescuing; case Preparation.Utility.PlayerStateType.Stunned: + case Preparation.Utility.PlayerStateType.Charmed: return PlayerState.Stunned; case Preparation.Utility.PlayerStateType.Swinging: return PlayerState.Swinging;