fix: 🔒 refactor the moveEngine and edit 游戏机制与平衡性调整更新草案.md
tags/v0.1.0
| @@ -309,7 +309,7 @@ $$ | |||
| - 不鼓励选手面向地图编程,因为移动过程中你可以受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。 | |||
| ### 人物 | |||
| - 眩晕状态中的玩家不能再次被眩晕 | |||
| - 眩晕状态中的玩家不能再次被眩晕(除非是ShowTime) | |||
| ### 初始状态 | |||
| - 初赛玩家出生点固定且一定为空地 | |||
| @@ -342,6 +342,7 @@ $$ | |||
| - 开锁门进度中断后清空 | |||
| ### 窗 | |||
| - 由于窗户被占用导致翻窗失败会使先前行动停止 | |||
| - 翻越窗户是一种交互行为,翻窗一共有两个过程 | |||
| - 跳上窗:从当前位置到窗边缘中点,位置瞬移,时间=距离/爬窗速度。中断时,停留在原位置 | |||
| - 爬窗:从窗一侧边缘中点到另一侧格子中心,位置渐移,时间=距离/爬窗速度。中断时,停留在另一侧格子中心 | |||
| @@ -1,5 +1,5 @@ | |||
| # 游戏机制与平衡性调整更新草案 | |||
| v1.5 | |||
| v1.6 | |||
| ## 说明 | |||
| - 该草案尚未完全确定,请大家不要过分依靠该文档进行修改自己的代码 | |||
| @@ -21,9 +21,9 @@ v1.5 | |||
| - 未攻击至目标时的后摇改为1200ms | |||
| - 增强为“可以攻击未写完的作业” | |||
| - 增强为“可以攻击使门被打开(可以重新被锁上)” | |||
| - 改为“当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上90°,180° ,270° 发出3个小炸弹” | |||
| - 小炸弹JumpyDumpty | |||
| - 小炸弹不受道具增益影响 | |||
| - 修改为“小炸弹与自己无碰撞体积” | |||
| - strike(新增) | |||
| - 可以攻击未写完的作业 | |||
| @@ -105,3 +105,14 @@ v1.5 | |||
| - 普通攻击改为strike | |||
| - Klee | |||
| - 被动技能Lucky!(新增):开局获得随机的一个道具(不会是钥匙) | |||
| - 主动技能SparksNSplash(新增): | |||
| - CD:45s, 持续时间:10s | |||
| - 技能使用瞬间,对于输入的额外参数PlayerID代表的角色,距离最近的本已停止运动的小炸弹开始追踪该角色(每50ms向该角色直线移动) | |||
| - 主动技能 蹦蹦炸弹 JumpyBomb | |||
| - 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上0°,45°,90°,135°,180°,225°,270°,315° 发出8个小炸弹 | |||
| - Idol | |||
| 主动技能ShowTime改为 | |||
| "持续时间内 | |||
| - 使警戒范围外的学生眩晕并每**500ms**发送向自己移动**500ms**的指令(速度为学生本应速度*二者距离/警戒范围) | |||
| - 对于视野范围(不是可视区域)内的学生每**500ms**加**1500**的沉迷度 | |||
| - 捣蛋鬼变为0.8倍速" | |||
| @@ -21,11 +21,14 @@ | |||
| - docs:更新了 游戏机制与平衡性调整更新草案.pdf | |||
| - change:更改了地图的文件路径 | |||
| # 最新更新 | |||
| # 5月10日更新 | |||
| - fix:修复JumpyDumpty的初始位置错误的问题 | |||
| - fix:修正和重新说明攻击距离 | |||
| - **攻击距离是指攻击(子弹)的移动距离,也就是说理论上最远被攻击的学生的中心与捣蛋鬼的中心=学生的半径+捣蛋鬼的半径+攻击距离+子弹半径(200)×2** | |||
| - hotfix:修复小炸弹初始化类型错误的问题 | |||
| - remove:去除了“实际上唤醒或勉励不同的人是有效的” | |||
| - **重复发出同一类型的交互指令和移动指令是无效的** | |||
| - feat&fix:修复并**将`SendMessage`改为`SendTextMessage`与`SendBinaryMessage`** | |||
| - feat&fix:修复并**将`SendMessage`改为`SendTextMessage`与`SendBinaryMessage`** | |||
| # 最新更新 | |||
| - docs:更新了 游戏机制与平衡性调整更新草案.pdf | |||
| @@ -1,6 +1,5 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -27,7 +26,6 @@ namespace GameClass.GameObj | |||
| public bool HasSpear => hasSpear; | |||
| /// <summary> | |||
| /// 与THUAI4不同的一个攻击判定方案,通过这个函数判断爆炸时能否伤害到target | |||
| /// </summary> | |||
| /// <param name="target">被尝试攻击者</param> | |||
| /// <returns>是否可以攻击到</returns> | |||
| @@ -36,7 +34,7 @@ namespace GameClass.GameObj | |||
| public override bool IgnoreCollideExecutor(IGameObj targetObj) | |||
| { | |||
| if (targetObj == Parent && CanMove) return true; | |||
| if (targetObj == Parent) return true; | |||
| if (targetObj.Type == GameObjType.Prop || targetObj.Type == GameObjType.Bullet) | |||
| return true; | |||
| return false; | |||
| @@ -1,10 +1,5 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Numerics; | |||
| using System.Runtime.InteropServices; | |||
| using System.Threading; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -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() | |||
| @@ -1,5 +1,7 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System.Numerics; | |||
| using System; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -25,15 +27,66 @@ namespace GameClass.GameObj | |||
| return false; | |||
| } | |||
| 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(); } | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -20,18 +20,6 @@ namespace GameEngine | |||
| private readonly ITimer gameTimer; | |||
| private readonly Action<IMoveable> EndMove; | |||
| public readonly uint[,] ProtoGameMap; | |||
| public PlaceType GetPlaceType(XY Position) | |||
| { | |||
| try | |||
| { | |||
| return (PlaceType)ProtoGameMap[Position.x / GameData.numOfPosGridPerCell, Position.y / GameData.numOfPosGridPerCell]; | |||
| } | |||
| catch | |||
| { | |||
| return PlaceType.Null; | |||
| } | |||
| } | |||
| public IGameObj? CheckCollision(IMoveable obj, XY Pos) | |||
| { | |||
| @@ -52,7 +40,6 @@ namespace GameEngine | |||
| Action<IMoveable> EndMove | |||
| ) | |||
| { | |||
| this.ProtoGameMap = gameMap.ProtoGameMap; | |||
| this.gameTimer = gameMap.Timer; | |||
| this.EndMove = EndMove; | |||
| this.OnCollision = OnCollision; | |||
| @@ -64,21 +51,57 @@ namespace GameEngine | |||
| /// </summary> | |||
| /// <param name="obj">移动物体,默认obj.Rigid为true</param> | |||
| /// <param name="moveVec">移动的位移向量</param> | |||
| 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; | |||
| } | |||
| 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 threadNum) | |||
| 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) { EndMove(obj); return; } | |||
| obj.IsMoving = true; | |||
| } | |||
| new Thread | |||
| @@ -87,9 +110,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 +129,82 @@ 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) | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| () => gameTimer.IsGaming && obj.CanMove && !obj.IsRemoved && obj.IsMoving, | |||
| () => | |||
| obj.IsMoving = false; | |||
| EndMove(obj); | |||
| return; | |||
| } | |||
| else | |||
| { | |||
| if (moveTime >= GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond) | |||
| { | |||
| Thread.Sleep(GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); | |||
| new FrameRateTaskExecutor<int>( | |||
| () => gameTimer.IsGaming, | |||
| () => | |||
| { | |||
| if (obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved) | |||
| return !(isEnded = true); | |||
| 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); | |||
| //对人特殊处理 | |||
| if (threadNum > 0 && obj.StateNum != threadNum) return false; | |||
| // 越界情况处理:如果越界,则与越界方块碰撞 | |||
| bool flag; // 循环标志 | |||
| do | |||
| AllowTimeExceed = true, | |||
| MaxTolerantTimeExceedCount = ulong.MaxValue, | |||
| TimeExceedAction = b => | |||
| { | |||
| flag = false; | |||
| collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res); | |||
| if (collisionObj == null) | |||
| break; | |||
| 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(); | |||
| if (!isEnded && obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved) | |||
| isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum); | |||
| } | |||
| if (isEnded) | |||
| { | |||
| obj.IsMoving = false; | |||
| EndMove(obj); | |||
| return; | |||
| } | |||
| if (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 | |||
| { | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| @@ -141,87 +212,19 @@ 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; // 结束移动 | |||
| EndMove(obj); | |||
| } | |||
| } | |||
| ).Start(); | |||
| @@ -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<int>( | |||
| 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<int>( | |||
| 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) | |||
| { | |||
| @@ -462,6 +491,7 @@ namespace Gaming | |||
| }, | |||
| EndMove: obj => | |||
| { | |||
| obj.ThreadNum.Release(); | |||
| // Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64); | |||
| } | |||
| ); | |||
| @@ -195,7 +195,9 @@ 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 | |||
| if (bullet.CastTime > 0) | |||
| { | |||
| characterManager.SetPlayerState(player, PlayerStateType.TryingToAttack); | |||
| @@ -25,28 +25,52 @@ namespace Gaming | |||
| if (nowPlayerState == value) return -1; | |||
| switch (nowPlayerState) | |||
| { | |||
| case PlayerStateType.OpeningTheChest: | |||
| if (player.NoHp()) return -1; | |||
| ((Chest)player.WhatInteractingWith!).StopOpen(); | |||
| return player.ChangePlayerState(value, gameObj); | |||
| case PlayerStateType.OpeningTheDoorway: | |||
| if (player.NoHp()) return -1; | |||
| Doorway doorway = (Doorway)player.WhatInteractingWith!; | |||
| doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime; | |||
| doorway.OpenStartTime = 0; | |||
| return player.ChangePlayerState(value, gameObj); | |||
| case PlayerStateType.Escaped: | |||
| case PlayerStateType.Deceased: | |||
| return -1; | |||
| case PlayerStateType.Addicted: | |||
| if (value == PlayerStateType.Rescued) | |||
| return player.ChangePlayerStateInOneThread(value, gameObj); | |||
| else | |||
| 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 | |||
| 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: | |||
| if (player.NoHp()) return -1; | |||
| return player.ChangePlayerState(value, gameObj); | |||
| } | |||
| } | |||
| @@ -163,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); | |||
| @@ -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(); | |||
| @@ -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) // 检查下一位置是否会和目标物碰撞 | |||
| { | |||
| @@ -24,6 +24,7 @@ namespace Preparation.Utility | |||
| ClimbingThroughWindows = 15, | |||
| UsingSkill = 16, | |||
| OpeningTheDoorway = 17, | |||
| Charmed = 18, | |||
| } | |||
| public enum GameObjType | |||
| { | |||
| @@ -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; | |||
| @@ -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; | |||