| @@ -21,7 +21,7 @@ | |||||
| #### 人物 | #### 人物 | ||||
| - `def EndAllAction(self) -> Future[bool]`:可以使不处在不可行动状态中的玩家终止当前行动 | - `def EndAllAction(self) -> Future[bool]`:可以使不处在不可行动状态中的玩家终止当前行动 | ||||
| - 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 Stop 指令终止进行的指令 | |||||
| - 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 EndAllAction 指令终止进行的指令 | |||||
| - 实际上唤醒或勉励不同的人是有效的 | - 实际上唤醒或勉励不同的人是有效的 | ||||
| - EndAllAction 及 Move 指令调用数总和一帧内不超过 10 次 | - EndAllAction 及 Move 指令调用数总和一帧内不超过 10 次 | ||||
| @@ -89,13 +89,14 @@ $$ | |||||
| 8. 翻窗 Climbing | 8. 翻窗 Climbing | ||||
| ### 攻击 | ### 攻击 | ||||
| - 攻击距离是指攻击(子弹)的移动距离,也就是说理论上最远被攻击的学生的中心与捣蛋鬼的中心=学生的半径+捣蛋鬼的半径+攻击距离+子弹半径(200)*2 | |||||
| - 攻击类型CommonAttackOfTricker攻击未写完的作业,会造成对应攻击力的损坏 | - 攻击类型CommonAttackOfTricker攻击未写完的作业,会造成对应攻击力的损坏 | ||||
| - 捣蛋鬼攻击交互状态或前后摇的学生,将使学生眩晕4.3s | - 捣蛋鬼攻击交互状态或前后摇的学生,将使学生眩晕4.3s | ||||
| | 攻击(子弹)类型 |搞蛋鬼的一般攻击CommonAttackOfTricker| 飞刀FlyingKnife | 蹦蹦炸弹BombBomb | 小炸弹JumpyDumpty | | | 攻击(子弹)类型 |搞蛋鬼的一般攻击CommonAttackOfTricker| 飞刀FlyingKnife | 蹦蹦炸弹BombBomb | 小炸弹JumpyDumpty | | ||||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | ||||
| | 子弹爆炸范围 | 0 | 0 | 2000 | 1000 | | |||||
| | 子弹攻击距离 | 2200 | 78000 | 2200 | 4400 | | |||||
| | 爆炸范围 | 0 | 0 | 2000 | 1000 | | |||||
| | 攻击距离 | 2200 | 78000 | 2200 | 4400 | | |||||
| | 攻击力 | 1500000 | 1200000 | 1800000 | 900000 | | | 攻击力 | 1500000 | 1200000 | 1800000 | 900000 | | ||||
| | 移动速度/s | 7400 | 18500 | 6000 | 8600 | | | 移动速度/s | 7400 | 18500 | 6000 | 8600 | | ||||
| | 前摇(ms) | 297 | 400 | 366 | - | | | 前摇(ms) | 297 | 400 | 366 | - | | ||||
| @@ -11,11 +11,21 @@ | |||||
| # 5月6日12点更新 | # 5月6日12点更新 | ||||
| - hotfix: 修复了突然的bug(物件锁的相关问题) | - hotfix: 修复了突然的bug(物件锁的相关问题) | ||||
| - rule:增加了每帧最多50条主动指令的限制 | |||||
| - rule:增加了每帧最多50条主动指令的限制 | |||||
| # 5月8日更新 | # 5月8日更新 | ||||
| - feat:增加了可选地图功能 | |||||
| - feat:增加了可选地图功能 | |||||
| - **脚本RunServer(ForDebug).cmd/sh现在支持可选地图功能,但想选择地图,选手需要自行参照使用文档修改命令行或在云盘下载脚本** | - **脚本RunServer(ForDebug).cmd/sh现在支持可选地图功能,但想选择地图,选手需要自行参照使用文档修改命令行或在云盘下载脚本** | ||||
| # 5月9日19:30更新 | |||||
| - docs:更新了 游戏机制与平衡性调整更新草案.pdf | |||||
| - change:更改了地图的文件路径 | |||||
| # 最新更新 | # 最新更新 | ||||
| - docs:更新了 游戏机制与平衡性调整更新草案.pdf | |||||
| - fix:修复JumpyDumpty的初始位置错误的问题 | |||||
| - fix:修正和重新说明攻击距离 | |||||
| - **攻击距离是指攻击(子弹)的移动距离,也就是说理论上最远被攻击的学生的中心与捣蛋鬼的中心=学生的半径+捣蛋鬼的半径+攻击距离+子弹半径(200)×2** | |||||
| - hotfix:修复小炸弹初始化类型错误的问题 | |||||
| - remove:去除了“实际上唤醒或勉励不同的人是有效的” | |||||
| - **重复发出同一类型的交互指令和移动指令是无效的** | |||||
| - feat&fix:修复并**将`SendMessage`改为`SendTextMessage`与`SendBinaryMessage`** | |||||
| @@ -10,7 +10,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => 0; | public override double BulletBombRange => 0; | ||||
| public override double BulletAttackRange => GameData.basicAttackShortRange; | |||||
| public override double AttackDistance => GameData.basicAttackShortRange; | |||||
| public int ap = GameData.basicApOfGhost; | public int ap = GameData.basicApOfGhost; | ||||
| public override int AP | public override int AP | ||||
| { | { | ||||
| @@ -24,7 +24,7 @@ namespace GameClass.GameObj | |||||
| public override int Speed => GameData.basicBulletMoveSpeed; | public override int Speed => GameData.basicBulletMoveSpeed; | ||||
| public override bool IsRemoteAttack => false; | public override bool IsRemoteAttack => false; | ||||
| public override int CastTime => (int)BulletAttackRange * 1000 / Speed; | |||||
| public override int CastTime => (int)AttackDistance * 1000 / Speed; | |||||
| public override int Backswing => GameData.basicBackswing; | public override int Backswing => GameData.basicBackswing; | ||||
| public override int RecoveryFromHit => GameData.basicRecoveryFromHit; | public override int RecoveryFromHit => GameData.basicRecoveryFromHit; | ||||
| public const int cd = GameData.basicBackswing; | public const int cd = GameData.basicBackswing; | ||||
| @@ -57,7 +57,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => 0; | public override double BulletBombRange => 0; | ||||
| public override double BulletAttackRange => GameData.basicRemoteAttackRange * 13; | |||||
| public override double AttackDistance => GameData.basicRemoteAttackRange * 13; | |||||
| public int ap = GameData.basicApOfGhost * 4 / 5; | public int ap = GameData.basicApOfGhost * 4 / 5; | ||||
| public override int AP | public override int AP | ||||
| { | { | ||||
| @@ -104,7 +104,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => GameData.basicBulletBombRange; | public override double BulletBombRange => GameData.basicBulletBombRange; | ||||
| public override double BulletAttackRange => GameData.basicAttackShortRange; | |||||
| public override double AttackDistance => GameData.basicAttackShortRange; | |||||
| public int ap = (int)(GameData.basicApOfGhost * 6.0 / 5); | public int ap = (int)(GameData.basicApOfGhost * 6.0 / 5); | ||||
| public override int AP | public override int AP | ||||
| { | { | ||||
| @@ -118,7 +118,7 @@ namespace GameClass.GameObj | |||||
| public override int Speed => (int)(GameData.basicBulletMoveSpeed * 30 / 37); | public override int Speed => (int)(GameData.basicBulletMoveSpeed * 30 / 37); | ||||
| public override bool IsRemoteAttack => false; | public override bool IsRemoteAttack => false; | ||||
| public override int CastTime => (int)(BulletAttackRange * 1000 / Speed); | |||||
| public override int CastTime => (int)(AttackDistance * 1000 / Speed); | |||||
| public override int Backswing => GameData.basicRecoveryFromHit; | public override int Backswing => GameData.basicRecoveryFromHit; | ||||
| public override int RecoveryFromHit => GameData.basicRecoveryFromHit; | public override int RecoveryFromHit => GameData.basicRecoveryFromHit; | ||||
| public const int cd = GameData.basicCD; | public const int cd = GameData.basicCD; | ||||
| @@ -149,7 +149,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => GameData.basicBulletBombRange / 2; | public override double BulletBombRange => GameData.basicBulletBombRange / 2; | ||||
| public override double BulletAttackRange => GameData.basicAttackShortRange * 2; | |||||
| public override double AttackDistance => GameData.basicAttackShortRange * 2; | |||||
| public int ap = (int)(GameData.basicApOfGhost * 0.6); | public int ap = (int)(GameData.basicApOfGhost * 0.6); | ||||
| public override int AP | public override int AP | ||||
| { | { | ||||
| @@ -9,7 +9,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => 0; | public override double BulletBombRange => 0; | ||||
| public override double BulletAttackRange => 0; | |||||
| public override double AttackDistance => 0; | |||||
| public override int AP => 7220; | public override int AP => 7220; | ||||
| public override int Speed => 0; | public override int Speed => 0; | ||||
| public override bool IsRemoteAttack => false; | public override bool IsRemoteAttack => false; | ||||
| @@ -10,7 +10,7 @@ namespace GameClass.GameObj | |||||
| /// //攻击力 | /// //攻击力 | ||||
| /// </summary> | /// </summary> | ||||
| public abstract double BulletBombRange { get; } | public abstract double BulletBombRange { get; } | ||||
| public abstract double BulletAttackRange { get; } | |||||
| public abstract double AttackDistance { get; } | |||||
| public abstract int AP { get; set; } | public abstract int AP { get; set; } | ||||
| public abstract int Speed { get; } | public abstract int Speed { get; } | ||||
| public abstract bool IsRemoteAttack { get; } | public abstract bool IsRemoteAttack { get; } | ||||
| @@ -56,9 +56,9 @@ namespace GameClass.GameObj | |||||
| public static class BulletFactory | public static class BulletFactory | ||||
| { | { | ||||
| public static Bullet? GetBullet(Character character, XY pos) | |||||
| public static Bullet? GetBullet(Character character, XY pos, BulletType bulletType) | |||||
| { | { | ||||
| switch (character.BulletOfPlayer) | |||||
| switch (bulletType) | |||||
| { | { | ||||
| case BulletType.FlyingKnife: | case BulletType.FlyingKnife: | ||||
| return new FlyingKnife(character, pos); | return new FlyingKnife(character, pos); | ||||
| @@ -1,8 +1,6 @@ | |||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System; | |||||
| using System.Numerics; | |||||
| namespace GameClass.GameObj | namespace GameClass.GameObj | ||||
| { | { | ||||
| @@ -8,9 +8,6 @@ namespace GameClass.GameObj | |||||
| public partial class Character : Moveable, ICharacter // 负责人LHR摆烂终了 | public partial class Character : Moveable, ICharacter // 负责人LHR摆烂终了 | ||||
| { | { | ||||
| #region 装弹、攻击相关的基本属性及方法 | #region 装弹、攻击相关的基本属性及方法 | ||||
| private readonly object attackLock = new(); | |||||
| public object AttackLock => attackLock; | |||||
| /// <summary> | /// <summary> | ||||
| /// 装弹冷却 | /// 装弹冷却 | ||||
| /// </summary> | /// </summary> | ||||
| @@ -19,7 +16,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (attackLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| return cd; | return cd; | ||||
| } | } | ||||
| @@ -33,14 +30,14 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (attackLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| return bulletOfPlayer; | return bulletOfPlayer; | ||||
| } | } | ||||
| } | } | ||||
| set | set | ||||
| { | { | ||||
| lock (attackLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| bulletOfPlayer = value; | bulletOfPlayer = value; | ||||
| cd = OrgCD = (BulletFactory.BulletCD(value)); | cd = OrgCD = (BulletFactory.BulletCD(value)); | ||||
| @@ -55,7 +52,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (attackLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| return maxBulletNum; | return maxBulletNum; | ||||
| } | } | ||||
| @@ -66,7 +63,7 @@ namespace GameClass.GameObj | |||||
| public int UpdateBulletNum(int time) | public int UpdateBulletNum(int time) | ||||
| { | { | ||||
| lock (attackLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| if (bulletNum < maxBulletNum) | if (bulletNum < maxBulletNum) | ||||
| { | { | ||||
| @@ -84,7 +81,7 @@ namespace GameClass.GameObj | |||||
| /// <returns>攻击操作发出的子弹</returns> | /// <returns>攻击操作发出的子弹</returns> | ||||
| public Bullet? Attack(double angle, int time) | public Bullet? Attack(double angle, int time) | ||||
| { | { | ||||
| lock (attackLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| if (bulletOfPlayer == BulletType.Null) | if (bulletOfPlayer == BulletType.Null) | ||||
| return null; | return null; | ||||
| @@ -92,14 +89,16 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| if (bulletNum == maxBulletNum) updateTimeOfBulletNum = time; | if (bulletNum == maxBulletNum) updateTimeOfBulletNum = time; | ||||
| --bulletNum; | --bulletNum; | ||||
| XY res = Position + new XY // 子弹紧贴人物生成。 | XY res = Position + new XY // 子弹紧贴人物生成。 | ||||
| ( | ( | ||||
| (int)(Math.Abs((Radius + BulletFactory.BulletRadius(bulletOfPlayer)) * Math.Cos(angle))) * ((Math.Cos(angle) > 0) ? 1 : -1), | |||||
| (int)(Math.Abs((Radius + BulletFactory.BulletRadius(bulletOfPlayer)) * Math.Sin(angle))) * ((Math.Sin(angle) > 0) ? 1 : -1) | |||||
| (int)(Math.Abs((Radius + BulletFactory.BulletRadius(bulletOfPlayer)) * Math.Cos(angle))) * Math.Sign(Math.Cos(angle)), | |||||
| (int)(Math.Abs((Radius + BulletFactory.BulletRadius(bulletOfPlayer)) * Math.Sin(angle))) * Math.Sign(Math.Sin(angle)) | |||||
| ); | ); | ||||
| Bullet? bullet = BulletFactory.GetBullet(this, res); | |||||
| Bullet? bullet = BulletFactory.GetBullet(this, res, this.bulletOfPlayer); | |||||
| if (bullet == null) return null; | if (bullet == null) return null; | ||||
| facingDirection = new(angle, bullet.BulletAttackRange); | |||||
| bullet.AP += TryAddAp() ? GameData.ApPropAdd : 0; | |||||
| facingDirection = new(angle, bullet.AttackDistance); | |||||
| return bullet; | return bullet; | ||||
| } | } | ||||
| else | else | ||||
| @@ -305,63 +304,88 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (playerState == PlayerStateType.Null && IsMoving) return PlayerStateType.Moving; | |||||
| return playerState; | |||||
| lock (actionLock) | |||||
| { | |||||
| if (playerState == PlayerStateType.Null && IsMoving) return PlayerStateType.Moving; | |||||
| return playerState; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| public bool NoHp() => (playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped | |||||
| || playerState == PlayerStateType.Addicted || playerState == PlayerStateType.Rescued); | |||||
| public bool Commandable() => (playerState != PlayerStateType.Deceased && playerState != PlayerStateType.Escaped | |||||
| && playerState != PlayerStateType.Addicted && playerState != PlayerStateType.Rescued | |||||
| && playerState != PlayerStateType.Swinging && playerState != PlayerStateType.TryingToAttack | |||||
| && playerState != PlayerStateType.ClimbingThroughWindows && playerState != PlayerStateType.Stunned); | |||||
| public bool InteractingWithMapWithoutMoving() => (playerState == PlayerStateType.LockingOrOpeningTheDoor || playerState == PlayerStateType.Fixing || playerState == PlayerStateType.OpeningTheChest); | |||||
| public bool NullOrMoving() => (playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | |||||
| public bool CanBeAwed() => !(playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped | |||||
| || playerState == PlayerStateType.Addicted || playerState == PlayerStateType.Rescued | |||||
| || playerState == PlayerStateType.Treated || playerState == PlayerStateType.Stunned | |||||
| || playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | |||||
| public bool NoHp() | |||||
| { | |||||
| lock (actionLock) | |||||
| return (playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped || playerState == PlayerStateType.Addicted || playerState == PlayerStateType.Rescued); | |||||
| } | |||||
| public bool Commandable() | |||||
| { | |||||
| lock (actionLock) | |||||
| { | |||||
| 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); | |||||
| } | |||||
| } | |||||
| public bool InteractingWithMapWithoutMoving() | |||||
| { | |||||
| lock (actionLock) | |||||
| { | |||||
| return (playerState == PlayerStateType.LockingOrOpeningTheDoor || playerState == PlayerStateType.Fixing || playerState == PlayerStateType.OpeningTheChest); | |||||
| } | |||||
| } | |||||
| public bool NullOrMoving() | |||||
| { | |||||
| lock (actionLock) | |||||
| { | |||||
| return (playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | |||||
| } | |||||
| } | |||||
| public bool CanBeAwed() | |||||
| { | |||||
| lock (actionLock) | |||||
| return !(playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped | |||||
| || playerState == PlayerStateType.Addicted || playerState == PlayerStateType.Rescued | |||||
| || playerState == PlayerStateType.Treated || playerState == PlayerStateType.Stunned | |||||
| || playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | |||||
| } | |||||
| private GameObj? whatInteractingWith = null; | private GameObj? whatInteractingWith = null; | ||||
| public GameObj? WhatInteractingWith => whatInteractingWith; | public GameObj? WhatInteractingWith => whatInteractingWith; | ||||
| private long threadNum = 0; | |||||
| public long ThreadNum => threadNum; | |||||
| public void ChangePlayerState(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||||
| public long ChangePlayerState(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| ++threadNum; | |||||
| whatInteractingWith = gameObj; | whatInteractingWith = gameObj; | ||||
| if (value != PlayerStateType.Moving) | if (value != PlayerStateType.Moving) | ||||
| IsMoving = false; | IsMoving = false; | ||||
| playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value; | playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value; | ||||
| //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); | //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); | ||||
| return ++stateNum; | |||||
| } | } | ||||
| } | } | ||||
| public void ChangePlayerStateInOneThread(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||||
| public long ChangePlayerStateInOneThread(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| whatInteractingWith = gameObj; | whatInteractingWith = gameObj; | ||||
| if (value != PlayerStateType.Moving) | if (value != PlayerStateType.Moving) | ||||
| IsMoving = false; | IsMoving = false; | ||||
| playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value; | playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value; | ||||
| //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); | //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); | ||||
| return stateNum; | |||||
| } | } | ||||
| } | } | ||||
| public void SetPlayerStateNaturally() | |||||
| public long SetPlayerStateNaturally() | |||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| ++threadNum; | |||||
| whatInteractingWith = null; | whatInteractingWith = null; | ||||
| IsMoving = false; | IsMoving = false; | ||||
| playerState = PlayerStateType.Null; | playerState = PlayerStateType.Null; | ||||
| return ++stateNum; | |||||
| } | } | ||||
| } | } | ||||
| @@ -370,11 +394,11 @@ namespace GameClass.GameObj | |||||
| MoveReaderWriterLock.EnterWriteLock(); | MoveReaderWriterLock.EnterWriteLock(); | ||||
| try | try | ||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| playerState = playerStateType; | playerState = playerStateType; | ||||
| canMove = false; | canMove = false; | ||||
| isResetting = true; | |||||
| isRemoved = true; | |||||
| position = GameData.PosWhoDie; | position = GameData.PosWhoDie; | ||||
| } | } | ||||
| } | } | ||||
| @@ -623,7 +647,7 @@ namespace GameClass.GameObj | |||||
| public override ShapeType Shape => ShapeType.Circle; | public override ShapeType Shape => ShapeType.Circle; | ||||
| public override bool IgnoreCollideExecutor(IGameObj targetObj) | public override bool IgnoreCollideExecutor(IGameObj targetObj) | ||||
| { | { | ||||
| if (IsResetting) | |||||
| if (IsRemoved) | |||||
| return true; | return true; | ||||
| if (targetObj.Type == GameObjType.Prop) | if (targetObj.Type == GameObjType.Prop) | ||||
| { | { | ||||
| @@ -6,17 +6,30 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| public abstract class Moveable : GameObj, IMoveable | public abstract class Moveable : GameObj, IMoveable | ||||
| { | { | ||||
| protected readonly object moveObjLock = new(); | |||||
| public object MoveLock => moveObjLock; | |||||
| protected readonly object actionLock = new(); | |||||
| public object ActionLock => actionLock; | |||||
| private readonly ReaderWriterLockSlim moveReaderWriterLock = new(); | private readonly ReaderWriterLockSlim moveReaderWriterLock = new(); | ||||
| public ReaderWriterLockSlim MoveReaderWriterLock => moveReaderWriterLock; | public ReaderWriterLockSlim MoveReaderWriterLock => moveReaderWriterLock; | ||||
| //规定moveReaderWriterLock>moveObjLock | |||||
| protected long stateNum = 0; | |||||
| public long StateNum | |||||
| { | |||||
| get | |||||
| { | |||||
| lock (actionLock) | |||||
| return stateNum; | |||||
| } | |||||
| set | |||||
| { | |||||
| lock (actionLock) stateNum = value; | |||||
| } | |||||
| } | |||||
| //规定moveReaderWriterLock>actionLock | |||||
| public override XY Position | public override XY Position | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| return position; | return position; | ||||
| } | } | ||||
| } | } | ||||
| @@ -25,7 +38,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| return facingDirection; | return facingDirection; | ||||
| } | } | ||||
| } | } | ||||
| @@ -35,12 +48,12 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| return isMoving; | return isMoving; | ||||
| } | } | ||||
| set | set | ||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| isMoving = value; | isMoving = value; | ||||
| } | } | ||||
| @@ -51,7 +64,7 @@ namespace GameClass.GameObj | |||||
| public long MovingSetPos(XY moveVec) | public long MovingSetPos(XY moveVec) | ||||
| { | { | ||||
| if (moveVec.x != 0 || moveVec.y != 0) | if (moveVec.x != 0 || moveVec.y != 0) | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| facingDirection = moveVec; | facingDirection = moveVec; | ||||
| this.position += moveVec; | this.position += moveVec; | ||||
| @@ -61,7 +74,7 @@ namespace GameClass.GameObj | |||||
| public void ReSetPos(XY position) | public void ReSetPos(XY position) | ||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| this.position = position; | this.position = position; | ||||
| } | } | ||||
| @@ -88,7 +101,7 @@ namespace GameClass.GameObj | |||||
| moveReaderWriterLock.EnterWriteLock(); | moveReaderWriterLock.EnterWriteLock(); | ||||
| try | try | ||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| canMove = value; | canMove = value; | ||||
| } | } | ||||
| @@ -99,15 +112,15 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| protected bool isResetting; | |||||
| public bool IsResetting | |||||
| protected bool isRemoved; | |||||
| public bool IsRemoved | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| moveReaderWriterLock.EnterReadLock(); | moveReaderWriterLock.EnterReadLock(); | ||||
| try | try | ||||
| { | { | ||||
| return isResetting; | |||||
| return isRemoved; | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| @@ -116,7 +129,22 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| public bool IsAvailable => !IsMoving && CanMove && !IsResetting; // 是否能接收指令 | |||||
| public bool IsAvailableForMove // 是否能接收移动指令 | |||||
| { | |||||
| get | |||||
| { | |||||
| moveReaderWriterLock.EnterReadLock(); | |||||
| try | |||||
| { | |||||
| lock (actionLock) | |||||
| return !isMoving && canMove && !isRemoved; | |||||
| } | |||||
| finally | |||||
| { | |||||
| moveReaderWriterLock.ExitReadLock(); | |||||
| } | |||||
| } | |||||
| } | |||||
| protected int moveSpeed; | protected int moveSpeed; | ||||
| /// <summary> | /// <summary> | ||||
| @@ -141,7 +169,7 @@ namespace GameClass.GameObj | |||||
| moveReaderWriterLock.EnterWriteLock(); | moveReaderWriterLock.EnterWriteLock(); | ||||
| try | try | ||||
| { | { | ||||
| lock (moveObjLock) | |||||
| lock (actionLock) | |||||
| { | { | ||||
| moveSpeed = value; | moveSpeed = value; | ||||
| } | } | ||||
| @@ -167,7 +195,7 @@ namespace GameClass.GameObj | |||||
| this.FacingDirection = new XY(1, 0); | this.FacingDirection = new XY(1, 0); | ||||
| isMoving = false; | isMoving = false; | ||||
| CanMove = false; | CanMove = false; | ||||
| IsResetting = true; | |||||
| IsRemoved = true; | |||||
| this.Position = birthPos; | this.Position = birthPos; | ||||
| this.Place= place; | this.Place= place; | ||||
| } | } | ||||
| @@ -73,24 +73,21 @@ namespace GameEngine | |||||
| obj.MovingSetPos(new XY(moveVec, maxLen)); | obj.MovingSetPos(new XY(moveVec, maxLen)); | ||||
| } | } | ||||
| public void MoveObj(IMoveable obj, int moveTime, double direction) | |||||
| public void MoveObj(IMoveable obj, int moveTime, double direction, long threadNum) | |||||
| { | { | ||||
| if (obj.IsMoving) // 已经移动的物体不能再移动 | |||||
| return; | |||||
| if (!obj.IsAvailable || !gameTimer.IsGaming) | |||||
| return; | |||||
| long threadNum = (obj.Type == GameObjType.Character) ? ((ICharacter)obj).ThreadNum : 0;//对人特殊处理 | |||||
| if (!gameTimer.IsGaming) return; | |||||
| lock (obj.ActionLock) | |||||
| { | |||||
| if (!obj.IsAvailableForMove) return; | |||||
| obj.IsMoving = true; | |||||
| } | |||||
| new Thread | new Thread | ||||
| ( | ( | ||||
| () => | () => | ||||
| { | { | ||||
| lock (obj.MoveLock) | |||||
| obj.IsMoving = true; | |||||
| double moveVecLength = 0.0; | double moveVecLength = 0.0; | ||||
| XY res = new(direction, moveVecLength); | XY res = new(direction, moveVecLength); | ||||
| double deltaLen = moveVecLength - Math.Sqrt(obj.MovingSetPos(res)); // 转向,并用deltaLen存储行走的误差 | |||||
| double deltaLen = 0; // 转向,并用deltaLen存储行走的误差 | |||||
| IGameObj? collisionObj = null; | IGameObj? collisionObj = null; | ||||
| bool isDestroyed = false; | bool isDestroyed = false; | ||||
| @@ -119,14 +116,14 @@ namespace GameEngine | |||||
| if (!isDestroyed) | if (!isDestroyed) | ||||
| { | { | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| () => gameTimer.IsGaming && obj.CanMove && !obj.IsResetting && obj.IsMoving, | |||||
| () => gameTimer.IsGaming && obj.CanMove && !obj.IsRemoved && obj.IsMoving, | |||||
| () => | () => | ||||
| { | { | ||||
| moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | ||||
| res = new XY(direction, moveVecLength); | res = new XY(direction, moveVecLength); | ||||
| //对人特殊处理 | //对人特殊处理 | ||||
| if (threadNum > 0 && ((ICharacter)obj).ThreadNum != threadNum) return false; | |||||
| if (threadNum > 0 && obj.StateNum != threadNum) return false; | |||||
| // 越界情况处理:如果越界,则与越界方块碰撞 | // 越界情况处理:如果越界,则与越界方块碰撞 | ||||
| bool flag; // 循环标志 | bool flag; // 循环标志 | ||||
| @@ -147,7 +144,7 @@ namespace GameEngine | |||||
| isDestroyed = true; | isDestroyed = true; | ||||
| return false; | return false; | ||||
| case AfterCollision.MoveMax: | case AfterCollision.MoveMax: | ||||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||||
| if (threadNum == 0 || obj.StateNum == threadNum) | |||||
| MoveMax(obj, res); | MoveMax(obj, res); | ||||
| moveVecLength = 0; | moveVecLength = 0; | ||||
| res = new XY(direction, moveVecLength); | res = new XY(direction, moveVecLength); | ||||
| @@ -155,7 +152,7 @@ namespace GameEngine | |||||
| } | } | ||||
| } while (flag); | } while (flag); | ||||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||||
| if (threadNum == 0 || obj.StateNum == threadNum) | |||||
| deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res)); | deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res)); | ||||
| return true; | return true; | ||||
| @@ -174,7 +171,7 @@ namespace GameEngine | |||||
| res = new XY(direction, moveVecLength); | res = new XY(direction, moveVecLength); | ||||
| if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) | if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) | ||||
| { | { | ||||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||||
| if (threadNum == 0 || obj.StateNum == threadNum) | |||||
| obj.MovingSetPos(res); | obj.MovingSetPos(res); | ||||
| } | } | ||||
| else | else | ||||
| @@ -189,7 +186,7 @@ namespace GameEngine | |||||
| isDestroyed = true; | isDestroyed = true; | ||||
| break; | break; | ||||
| case AfterCollision.MoveMax: | case AfterCollision.MoveMax: | ||||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||||
| if (threadNum == 0 || obj.StateNum == threadNum) | |||||
| MoveMax(obj, res); | MoveMax(obj, res); | ||||
| moveVecLength = 0; | moveVecLength = 0; | ||||
| res = new XY(direction, moveVecLength); | res = new XY(direction, moveVecLength); | ||||
| @@ -202,7 +199,7 @@ namespace GameEngine | |||||
| { | { | ||||
| Thread.Sleep(leftTime); // 多移动的在这里补回来 | Thread.Sleep(leftTime); // 多移动的在这里补回来 | ||||
| } | } | ||||
| lock (obj.MoveLock) | |||||
| lock (obj.ActionLock) | |||||
| obj.IsMoving = false; // 结束移动 | obj.IsMoving = false; // 结束移动 | ||||
| EndMove(obj); | EndMove(obj); | ||||
| return 0; | return 0; | ||||
| @@ -21,14 +21,14 @@ namespace Gaming | |||||
| { | { | ||||
| if (((Bullet)collisionObj).Parent != player && ((Bullet)collisionObj).TypeOfBullet == BulletType.JumpyDumpty) | if (((Bullet)collisionObj).Parent != player && ((Bullet)collisionObj).TypeOfBullet == BulletType.JumpyDumpty) | ||||
| { | { | ||||
| if (characterManager.BeStunned((Character)player, GameData.timeOfStunnedWhenJumpyDumpty)) | |||||
| if (characterManager.BeStunned((Character)player, GameData.timeOfStunnedWhenJumpyDumpty) > 0) | |||||
| player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStunnedWhenJumpyDumpty)); | player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStunnedWhenJumpyDumpty)); | ||||
| gameMap.Remove((GameObj)collisionObj); | gameMap.Remove((GameObj)collisionObj); | ||||
| } | } | ||||
| } | } | ||||
| if (player.FindIActiveSkill(ActiveSkillType.CanBeginToCharge).IsBeingUsed && collisionObj.Type == GameObjType.Character && ((Character)collisionObj).IsGhost()) | if (player.FindIActiveSkill(ActiveSkillType.CanBeginToCharge).IsBeingUsed && collisionObj.Type == GameObjType.Character && ((Character)collisionObj).IsGhost()) | ||||
| { | { | ||||
| if (characterManager.BeStunned((Character)collisionObj, GameData.timeOfGhostStunnedWhenCharge)) | |||||
| if (characterManager.BeStunned((Character)collisionObj, GameData.timeOfGhostStunnedWhenCharge) > 0) | |||||
| player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.timeOfGhostStunnedWhenCharge)); | player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.timeOfGhostStunnedWhenCharge)); | ||||
| characterManager.BeStunned(player, GameData.timeOfStudentStunnedWhenCharge); | characterManager.BeStunned(player, GameData.timeOfStudentStunnedWhenCharge); | ||||
| } | } | ||||
| @@ -37,9 +37,8 @@ namespace Gaming | |||||
| { | { | ||||
| if (moveTimeInMilliseconds < 5) return false; | if (moveTimeInMilliseconds < 5) return false; | ||||
| if (!playerToMove.Commandable()) return false; | if (!playerToMove.Commandable()) return false; | ||||
| if (playerToMove.IsMoving) return false; | |||||
| characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving); | |||||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); | |||||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection, | |||||
| characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving)); | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -47,7 +46,7 @@ namespace Gaming | |||||
| { | { | ||||
| if (!playerToMove.Commandable() && playerToMove.PlayerState != PlayerStateType.Stunned) return false; | if (!playerToMove.Commandable() && playerToMove.PlayerState != PlayerStateType.Stunned) return false; | ||||
| characterManager.BeStunned(playerToMove, moveTimeInMilliseconds); | characterManager.BeStunned(playerToMove, moveTimeInMilliseconds); | ||||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); | |||||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection, playerToMove.StateNum); | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -72,14 +71,14 @@ namespace Gaming | |||||
| ++generatorForFix.NumOfFixing; | ++generatorForFix.NumOfFixing; | ||||
| characterManager.SetPlayerState(player, PlayerStateType.Fixing); | characterManager.SetPlayerState(player, PlayerStateType.Fixing); | ||||
| long threadNum = player.ThreadNum; | |||||
| long threadNum = player.StateNum; | |||||
| new Thread | new Thread | ||||
| ( | ( | ||||
| () => | () => | ||||
| { | { | ||||
| Thread.Sleep(GameData.frameDuration); | Thread.Sleep(GameData.frameDuration); | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => gameMap.Timer.IsGaming && threadNum == player.ThreadNum, | |||||
| loopCondition: () => gameMap.Timer.IsGaming && threadNum == player.StateNum, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| if (generatorForFix.Repair(player.FixSpeed * GameData.frameDuration, player)) | if (generatorForFix.Repair(player.FixSpeed * GameData.frameDuration, player)) | ||||
| @@ -173,10 +172,10 @@ namespace Gaming | |||||
| { | { | ||||
| characterManager.SetPlayerState(playerTreated, PlayerStateType.Treated); | characterManager.SetPlayerState(playerTreated, PlayerStateType.Treated); | ||||
| characterManager.SetPlayerState(player, PlayerStateType.Treating); | characterManager.SetPlayerState(player, PlayerStateType.Treating); | ||||
| long threadNum = player.ThreadNum; | |||||
| long threadNum = player.StateNum; | |||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||||
| loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && threadNum == player.StateNum && gameMap.Timer.IsGaming, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| if (playerTreated.AddDegreeOfTreatment(GameData.frameDuration * player.TreatSpeed, player)) | if (playerTreated.AddDegreeOfTreatment(GameData.frameDuration * player.TreatSpeed, player)) | ||||
| @@ -187,7 +186,7 @@ namespace Gaming | |||||
| ) | ) | ||||
| .Start(); | .Start(); | ||||
| if (threadNum == player.ThreadNum) characterManager.SetPlayerState(player); | |||||
| if (threadNum == player.StateNum) characterManager.SetPlayerState(player); | |||||
| else if (playerTreated.PlayerState == PlayerStateType.Treated) characterManager.SetPlayerState(playerTreated); | else if (playerTreated.PlayerState == PlayerStateType.Treated) characterManager.SetPlayerState(playerTreated); | ||||
| } | } | ||||
| ) | ) | ||||
| @@ -205,14 +204,14 @@ namespace Gaming | |||||
| return false; | return false; | ||||
| characterManager.SetPlayerState(player, PlayerStateType.Rescuing); | characterManager.SetPlayerState(player, PlayerStateType.Rescuing); | ||||
| characterManager.SetPlayerState(playerRescued, PlayerStateType.Rescued); | characterManager.SetPlayerState(playerRescued, PlayerStateType.Rescued); | ||||
| long threadNum = player.ThreadNum; | |||||
| long threadNum = player.StateNum; | |||||
| new Thread | new Thread | ||||
| ( | ( | ||||
| () => | () => | ||||
| { | { | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => playerRescued.PlayerState == PlayerStateType.Rescued && threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||||
| loopCondition: () => playerRescued.PlayerState == PlayerStateType.Rescued && threadNum == player.StateNum && gameMap.Timer.IsGaming, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| playerRescued.TimeOfRescue += GameData.frameDuration; | playerRescued.TimeOfRescue += GameData.frameDuration; | ||||
| @@ -234,7 +233,7 @@ namespace Gaming | |||||
| else | else | ||||
| characterManager.SetPlayerState(playerRescued, PlayerStateType.Addicted); | characterManager.SetPlayerState(playerRescued, PlayerStateType.Addicted); | ||||
| } | } | ||||
| if (threadNum == player.ThreadNum) characterManager.SetPlayerState(player); | |||||
| if (threadNum == player.StateNum) characterManager.SetPlayerState(player); | |||||
| playerRescued.TimeOfRescue = 0; | playerRescued.TimeOfRescue = 0; | ||||
| } | } | ||||
| ) | ) | ||||
| @@ -303,14 +302,14 @@ namespace Gaming | |||||
| // gameMap.Add(addWall); | // gameMap.Add(addWall); | ||||
| characterManager.SetPlayerState(player, PlayerStateType.ClimbingThroughWindows); | characterManager.SetPlayerState(player, PlayerStateType.ClimbingThroughWindows); | ||||
| long threadNum = player.ThreadNum; | |||||
| long threadNum = player.StateNum; | |||||
| windowForClimb.WhoIsClimbing = player; | windowForClimb.WhoIsClimbing = player; | ||||
| new Thread | new Thread | ||||
| ( | ( | ||||
| () => | () => | ||||
| { | { | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||||
| loopCondition: () => threadNum == player.StateNum && gameMap.Timer.IsGaming, | |||||
| loopToDo: () => { }, | loopToDo: () => { }, | ||||
| timeInterval: GameData.frameDuration, | timeInterval: GameData.frameDuration, | ||||
| finallyReturn: () => 0, | finallyReturn: () => 0, | ||||
| @@ -326,10 +325,10 @@ namespace Gaming | |||||
| player.ReSetPos(windowToPlayer + windowForClimb.Position); | player.ReSetPos(windowToPlayer + windowForClimb.Position); | ||||
| player.MoveSpeed = player.SpeedOfClimbingThroughWindows; | player.MoveSpeed = player.SpeedOfClimbingThroughWindows; | ||||
| moveEngine.MoveObj(player, (int)(windowToPlayer.Length() * 3.0 * 1000 / player.MoveSpeed), (-1 * windowToPlayer).Angle()); | |||||
| moveEngine.MoveObj(player, (int)(windowToPlayer.Length() * 3.0 * 1000 / player.MoveSpeed), (-1 * windowToPlayer).Angle(), threadNum); | |||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||||
| loopCondition: () => threadNum == player.StateNum && gameMap.Timer.IsGaming, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| }, | }, | ||||
| @@ -343,7 +342,7 @@ namespace Gaming | |||||
| player.MoveSpeed = player.ReCalculateBuff(BuffType.AddSpeed, player.OrgMoveSpeed, GameData.MaxSpeed, GameData.MinSpeed); | player.MoveSpeed = player.ReCalculateBuff(BuffType.AddSpeed, player.OrgMoveSpeed, GameData.MaxSpeed, GameData.MinSpeed); | ||||
| windowForClimb.WhoIsClimbing = null; | windowForClimb.WhoIsClimbing = null; | ||||
| // gameMap.Remove(addWall); | // gameMap.Remove(addWall); | ||||
| if (threadNum == player.ThreadNum) | |||||
| if (threadNum == player.StateNum) | |||||
| { | { | ||||
| characterManager.SetPlayerState(player); | characterManager.SetPlayerState(player); | ||||
| } | } | ||||
| @@ -386,13 +385,13 @@ namespace Gaming | |||||
| if (!flag) return false; | if (!flag) return false; | ||||
| characterManager.SetPlayerState(player, PlayerStateType.LockingOrOpeningTheDoor); | characterManager.SetPlayerState(player, PlayerStateType.LockingOrOpeningTheDoor); | ||||
| long threadNum = player.ThreadNum; | |||||
| long threadNum = player.StateNum; | |||||
| new Thread | new Thread | ||||
| ( | ( | ||||
| () => | () => | ||||
| { | { | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => flag && threadNum == player.ThreadNum && gameMap.Timer.IsGaming && doorToLock.OpenOrLockDegree < GameData.degreeOfLockingOrOpeningTheDoor, | |||||
| loopCondition: () => flag && threadNum == player.StateNum && gameMap.Timer.IsGaming && doorToLock.OpenOrLockDegree < GameData.degreeOfLockingOrOpeningTheDoor, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| flag = ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) == null); | flag = ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) == null); | ||||
| @@ -407,7 +406,7 @@ namespace Gaming | |||||
| { | { | ||||
| doorToLock.IsOpen = (!doorToLock.IsOpen); | doorToLock.IsOpen = (!doorToLock.IsOpen); | ||||
| } | } | ||||
| if (threadNum == player.ThreadNum) | |||||
| if (threadNum == player.StateNum) | |||||
| characterManager.SetPlayerState(player); | characterManager.SetPlayerState(player); | ||||
| doorToLock.OpenOrLockDegree = 0; | doorToLock.OpenOrLockDegree = 0; | ||||
| } | } | ||||
| @@ -39,6 +39,17 @@ namespace Gaming | |||||
| this.characterManager = characterManager; | this.characterManager = characterManager; | ||||
| } | } | ||||
| public void ProduceBulletNaturally(BulletType bulletType, Character player, double angle, XY pos) | |||||
| { | |||||
| // 子弹如果没有和其他物体碰撞,将会一直向前直到超出人物的attackRange | |||||
| if (bulletType == BulletType.Null) return; | |||||
| Bullet? bullet = BulletFactory.GetBullet(player, pos, bulletType); | |||||
| 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 | |||||
| } | |||||
| private void BombObj(Bullet bullet, GameObj objBeingShot) | private void BombObj(Bullet bullet, GameObj objBeingShot) | ||||
| { | { | ||||
| #if DEBUG | #if DEBUG | ||||
| @@ -122,9 +133,21 @@ namespace Gaming | |||||
| if (bullet.TypeOfBullet == BulletType.BombBomb && objBeingShot != null) | if (bullet.TypeOfBullet == BulletType.BombBomb && objBeingShot != null) | ||||
| { | { | ||||
| bullet.Parent!.BulletOfPlayer = BulletType.JumpyDumpty; | |||||
| Attack((Character)bullet.Parent, bullet.FacingDirection.Angle() + Math.PI / 2.0); | |||||
| Attack((Character)bullet.Parent, bullet.FacingDirection.Angle() + Math.PI * 3.0 / 2.0); | |||||
| double angle = bullet.FacingDirection.Angle() + Math.PI / 2.0; | |||||
| XY pos = bullet.Position + new XY // 子弹紧贴人物生成。 | |||||
| ( | |||||
| (int)(Math.Abs((bullet.Radius + BulletFactory.BulletRadius(BulletType.JumpyDumpty)) * Math.Cos(angle))) * Math.Sign(Math.Cos(angle)), | |||||
| (int)(Math.Abs((bullet.Radius + BulletFactory.BulletRadius(BulletType.JumpyDumpty)) * Math.Sin(angle))) * Math.Sign(Math.Sin(angle)) | |||||
| ); | |||||
| ProduceBulletNaturally(BulletType.JumpyDumpty, (Character)bullet.Parent!, angle, pos); | |||||
| angle = bullet.FacingDirection.Angle() + Math.PI * 3.0 / 2.0; | |||||
| pos = bullet.Position + new XY // 子弹紧贴人物生成。 | |||||
| ( | |||||
| (int)(Math.Abs((bullet.Radius + BulletFactory.BulletRadius(BulletType.JumpyDumpty)) * Math.Cos(angle))) * Math.Sign(Math.Cos(angle)), | |||||
| (int)(Math.Abs((bullet.Radius + BulletFactory.BulletRadius(BulletType.JumpyDumpty)) * Math.Sin(angle))) * Math.Sign(Math.Sin(angle)) | |||||
| ); | |||||
| ProduceBulletNaturally(BulletType.JumpyDumpty, (Character)bullet.Parent!, angle, pos); | |||||
| } | } | ||||
| var beAttackedList = new List<IGameObj>(); | var beAttackedList = new List<IGameObj>(); | ||||
| @@ -171,19 +194,18 @@ namespace Gaming | |||||
| if (bullet != null) | if (bullet != null) | ||||
| { | { | ||||
| Debugger.Output(bullet, "Attack in " + bullet.Position.ToString()); | Debugger.Output(bullet, "Attack in " + bullet.Position.ToString()); | ||||
| bullet.AP += player.TryAddAp() ? GameData.ApPropAdd : 0; | |||||
| gameMap.Add(bullet); | gameMap.Add(bullet); | ||||
| moveEngine.MoveObj(bullet, (int)((bullet.BulletAttackRange - player.Radius - BulletFactory.BulletRadius(player.BulletOfPlayer)) * 1000 / bullet.MoveSpeed), angle); // 这里时间参数除出来的单位要是ms | |||||
| moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms | |||||
| if (bullet.CastTime > 0) | if (bullet.CastTime > 0) | ||||
| { | { | ||||
| characterManager.SetPlayerState(player, PlayerStateType.TryingToAttack); | characterManager.SetPlayerState(player, PlayerStateType.TryingToAttack); | ||||
| long threadNum = player.ThreadNum; | |||||
| long threadNum = player.StateNum; | |||||
| new Thread | new Thread | ||||
| (() => | (() => | ||||
| { | { | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||||
| loopCondition: () => threadNum == player.StateNum && gameMap.Timer.IsGaming, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| }, | }, | ||||
| @@ -195,7 +217,7 @@ namespace Gaming | |||||
| if (gameMap.Timer.IsGaming) | if (gameMap.Timer.IsGaming) | ||||
| { | { | ||||
| if (threadNum == player.ThreadNum) | |||||
| if (threadNum == player.StateNum) | |||||
| { | { | ||||
| characterManager.SetPlayerState(player); | characterManager.SetPlayerState(player); | ||||
| } | } | ||||
| @@ -1,5 +1,4 @@ | |||||
| using System; | |||||
| using System.Threading; | |||||
| using System.Threading; | |||||
| using GameClass.GameObj; | using GameClass.GameObj; | ||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| @@ -18,37 +17,37 @@ namespace Gaming | |||||
| this.gameMap = gameMap; | this.gameMap = gameMap; | ||||
| } | } | ||||
| public void SetPlayerState(Character player, PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||||
| public long SetPlayerState(Character player, PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||||
| { | { | ||||
| lock (player.MoveLock) | |||||
| lock (player.ActionLock) | |||||
| { | { | ||||
| switch (player.PlayerState) | |||||
| PlayerStateType nowPlayerState = player.PlayerState; | |||||
| if (nowPlayerState == value) return -1; | |||||
| switch (nowPlayerState) | |||||
| { | { | ||||
| case PlayerStateType.OpeningTheChest: | case PlayerStateType.OpeningTheChest: | ||||
| if (player.NoHp()) return -1; | |||||
| ((Chest)player.WhatInteractingWith!).StopOpen(); | ((Chest)player.WhatInteractingWith!).StopOpen(); | ||||
| player.ChangePlayerState(value, gameObj); | |||||
| break; | |||||
| return player.ChangePlayerState(value, gameObj); | |||||
| case PlayerStateType.OpeningTheDoorway: | case PlayerStateType.OpeningTheDoorway: | ||||
| if (player.NoHp()) return -1; | |||||
| Doorway doorway = (Doorway)player.WhatInteractingWith!; | Doorway doorway = (Doorway)player.WhatInteractingWith!; | ||||
| doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime; | doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime; | ||||
| doorway.OpenStartTime = 0; | doorway.OpenStartTime = 0; | ||||
| player.ChangePlayerState(value, gameObj); | |||||
| break; | |||||
| return player.ChangePlayerState(value, gameObj); | |||||
| case PlayerStateType.Addicted: | case PlayerStateType.Addicted: | ||||
| if (value == PlayerStateType.Rescued) | if (value == PlayerStateType.Rescued) | ||||
| player.ChangePlayerStateInOneThread(value, gameObj); | |||||
| return player.ChangePlayerStateInOneThread(value, gameObj); | |||||
| else | else | ||||
| player.ChangePlayerState(value, gameObj); | |||||
| break; | |||||
| return player.ChangePlayerState(value, gameObj); | |||||
| case PlayerStateType.Rescued: | case PlayerStateType.Rescued: | ||||
| if (value == PlayerStateType.Addicted) | if (value == PlayerStateType.Addicted) | ||||
| player.ChangePlayerStateInOneThread(value, gameObj); | |||||
| return player.ChangePlayerStateInOneThread(value, gameObj); | |||||
| else | else | ||||
| player.ChangePlayerState(value, gameObj); | |||||
| break; | |||||
| return player.ChangePlayerState(value, gameObj); | |||||
| default: | default: | ||||
| player.ChangePlayerState(value, gameObj); | |||||
| break; | |||||
| if (player.NoHp()) return -1; | |||||
| return player.ChangePlayerState(value, gameObj); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -75,7 +74,7 @@ namespace Gaming | |||||
| Thread.Sleep(Math.Max(newPlayer.CD, GameData.checkInterval)); | Thread.Sleep(Math.Max(newPlayer.CD, GameData.checkInterval)); | ||||
| long lastTime = Environment.TickCount64; | long lastTime = Environment.TickCount64; | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, | |||||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsRemoved, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| long nowTime = Environment.TickCount64; | long nowTime = Environment.TickCount64; | ||||
| @@ -133,7 +132,7 @@ namespace Gaming | |||||
| } | } | ||||
| } | } | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, | |||||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsRemoved, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | ||||
| @@ -246,7 +245,7 @@ namespace Gaming | |||||
| } | } | ||||
| } | } | ||||
| SetPlayerState(player, PlayerStateType.Addicted); | SetPlayerState(player, PlayerStateType.Addicted); | ||||
| long threadNum = player.ThreadNum; | |||||
| long threadNum = player.StateNum; | |||||
| new Thread | new Thread | ||||
| (() => | (() => | ||||
| { | { | ||||
| @@ -254,7 +253,7 @@ namespace Gaming | |||||
| Debugger.Output(player, " is addicted "); | Debugger.Output(player, " is addicted "); | ||||
| #endif | #endif | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| () => threadNum == player.ThreadNum && player.GamingAddiction < player.MaxGamingAddiction && gameMap.Timer.IsGaming, | |||||
| () => threadNum == player.StateNum && player.GamingAddiction < player.MaxGamingAddiction && gameMap.Timer.IsGaming, | |||||
| () => | () => | ||||
| { | { | ||||
| player.GamingAddiction += (player.PlayerState == PlayerStateType.Addicted) ? GameData.frameDuration : 0; | player.GamingAddiction += (player.PlayerState == PlayerStateType.Addicted) ? GameData.frameDuration : 0; | ||||
| @@ -276,28 +275,28 @@ namespace Gaming | |||||
| { IsBackground = true }.Start(); | { IsBackground = true }.Start(); | ||||
| } | } | ||||
| public bool BeStunned(Character player, int time) | |||||
| public long BeStunned(Character player, int time) | |||||
| { | { | ||||
| if (player.PlayerState == PlayerStateType.Stunned || player.NoHp() || player.CharacterType == CharacterType.Robot) return false; | |||||
| if (player.CharacterType == CharacterType.Robot) return -1; | |||||
| long threadNum = SetPlayerState(player, PlayerStateType.Stunned); | |||||
| if (threadNum == -1) return -1; | |||||
| new Thread | new Thread | ||||
| (() => | (() => | ||||
| { | { | ||||
| SetPlayerState(player, PlayerStateType.Stunned); | |||||
| long threadNum = player.ThreadNum; | |||||
| Thread.Sleep(time); | Thread.Sleep(time); | ||||
| if (threadNum == player.ThreadNum) | |||||
| if (threadNum == player.StateNum) | |||||
| SetPlayerState(player); | SetPlayerState(player); | ||||
| } | } | ||||
| ) | ) | ||||
| { IsBackground = true }.Start(); | { IsBackground = true }.Start(); | ||||
| return true; | |||||
| return threadNum; | |||||
| } | } | ||||
| public bool TryBeAwed(Student character, Bullet bullet) | public bool TryBeAwed(Student character, Bullet bullet) | ||||
| { | { | ||||
| if (character.CanBeAwed()) | if (character.CanBeAwed()) | ||||
| { | { | ||||
| if (BeStunned(character, GameData.basicStunnedTimeOfStudent)) | |||||
| if (BeStunned(character, GameData.basicStunnedTimeOfStudent) > 0) | |||||
| bullet.Parent!.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.basicStunnedTimeOfStudent)); | bullet.Parent!.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.basicStunnedTimeOfStudent)); | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -373,14 +372,14 @@ namespace Gaming | |||||
| if (time <= 0) return false; | if (time <= 0) return false; | ||||
| if (player.PlayerState == PlayerStateType.Swinging || (!player.Commandable() && player.PlayerState != PlayerStateType.TryingToAttack)) return false; | if (player.PlayerState == PlayerStateType.Swinging || (!player.Commandable() && player.PlayerState != PlayerStateType.TryingToAttack)) return false; | ||||
| SetPlayerState(player, PlayerStateType.Swinging); | SetPlayerState(player, PlayerStateType.Swinging); | ||||
| long threadNum = player.ThreadNum; | |||||
| long threadNum = player.StateNum; | |||||
| new Thread | new Thread | ||||
| (() => | (() => | ||||
| { | { | ||||
| Thread.Sleep(time); | Thread.Sleep(time); | ||||
| if (threadNum == player.ThreadNum) | |||||
| if (threadNum == player.StateNum) | |||||
| { | { | ||||
| SetPlayerState(player); | SetPlayerState(player); | ||||
| } | } | ||||
| @@ -21,7 +21,7 @@ namespace Gaming | |||||
| public void UseProp(Character player, PropType propType) | public void UseProp(Character player, PropType propType) | ||||
| { | { | ||||
| if (player.IsResetting || player.CharacterType == CharacterType.Robot) | |||||
| if (player.IsRemoved || player.CharacterType == CharacterType.Robot) | |||||
| return; | return; | ||||
| Prop prop = player.UseProp(propType); | Prop prop = player.UseProp(propType); | ||||
| switch (prop.GetPropType()) | switch (prop.GetPropType()) | ||||
| @@ -73,7 +73,7 @@ namespace Gaming | |||||
| /// <returns></returns> | /// <returns></returns> | ||||
| public bool PickProp(Character player, PropType propType = PropType.Null) | public bool PickProp(Character player, PropType propType = PropType.Null) | ||||
| { | { | ||||
| if (player.IsResetting) | |||||
| if (player.IsRemoved) | |||||
| return false; | return false; | ||||
| int indexing = player.IndexingOfAddProp(); | int indexing = player.IndexingOfAddProp(); | ||||
| if (indexing == GameData.maxNumOfPropInPropInventory) | if (indexing == GameData.maxNumOfPropInPropInventory) | ||||
| @@ -118,7 +118,7 @@ namespace Gaming | |||||
| public void ThrowProp(Character player, PropType propType) | public void ThrowProp(Character player, PropType propType) | ||||
| { | { | ||||
| if (!gameMap.Timer.IsGaming || player.IsResetting) | |||||
| if (!gameMap.Timer.IsGaming || player.IsRemoved) | |||||
| return; | return; | ||||
| Prop prop = player.UseProp(propType); | Prop prop = player.UseProp(propType); | ||||
| if (prop.GetPropType() == PropType.Null) | if (prop.GetPropType() == PropType.Null) | ||||
| @@ -188,7 +188,7 @@ namespace Gaming | |||||
| { | { | ||||
| if (!character.IsGhost() && !character.NoHp() && XY.DistanceFloor3(character.Position, player.Position) <= player.ViewRange) | if (!character.IsGhost() && !character.NoHp() && XY.DistanceFloor3(character.Position, player.Position) <= player.ViewRange) | ||||
| { | { | ||||
| if (characterManager.BeStunned(character, GameData.timeOfStudentStunnedWhenHowl)) | |||||
| if (characterManager.BeStunned(character, GameData.timeOfStudentStunnedWhenHowl) > 0) | |||||
| player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStudentStunnedWhenHowl)); | player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStudentStunnedWhenHowl)); | ||||
| } | } | ||||
| } | } | ||||
| @@ -219,7 +219,7 @@ namespace Gaming | |||||
| || character.PlayerState == PlayerStateType.UsingSkill || character.PlayerState == PlayerStateType.LockingOrOpeningTheDoor || character.PlayerState == PlayerStateType.ClimbingThroughWindows) | || character.PlayerState == PlayerStateType.UsingSkill || character.PlayerState == PlayerStateType.LockingOrOpeningTheDoor || character.PlayerState == PlayerStateType.ClimbingThroughWindows) | ||||
| && gameMap.CanSee(player, character)) | && gameMap.CanSee(player, character)) | ||||
| { | { | ||||
| if (characterManager.BeStunned(character, GameData.timeOfGhostStunnedWhenPunish + GameData.factorOfTimeStunnedWhenPunish * (player.MaxHp - player.HP))) | |||||
| if (characterManager.BeStunned(character, GameData.timeOfGhostStunnedWhenPunish + GameData.factorOfTimeStunnedWhenPunish * (player.MaxHp - player.HP)) > 0) | |||||
| player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.timeOfGhostStunnedWhenPunish + GameData.factorOfTimeStunnedWhenPunish * (player.MaxHp - player.HP))); | player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.timeOfGhostStunnedWhenPunish + GameData.factorOfTimeStunnedWhenPunish * (player.MaxHp - player.HP))); | ||||
| break; | break; | ||||
| } | } | ||||
| @@ -335,7 +335,7 @@ namespace Gaming | |||||
| startSkill(); | startSkill(); | ||||
| activeSkill.IsBeingUsed = true; | activeSkill.IsBeingUsed = true; | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| () => !player.IsResetting, | |||||
| () => !player.IsRemoved, | |||||
| () => | () => | ||||
| { | { | ||||
| player.AddTimeUntilActiveSkillAvailable(activeSkillType, -(int)GameData.frameDuration); | player.AddTimeUntilActiveSkillAvailable(activeSkillType, -(int)GameData.frameDuration); | ||||
| @@ -355,7 +355,7 @@ namespace Gaming | |||||
| Debugger.Output(player, "return to normal."); | Debugger.Output(player, "return to normal."); | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => player.TimeUntilActiveSkillAvailable[activeSkillType] > 0 && !player.IsResetting, | |||||
| loopCondition: () => player.TimeUntilActiveSkillAvailable[activeSkillType] > 0 && !player.IsRemoved, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| player.AddTimeUntilActiveSkillAvailable(activeSkillType, -(int)GameData.frameDuration); | player.AddTimeUntilActiveSkillAvailable(activeSkillType, -(int)GameData.frameDuration); | ||||
| @@ -22,7 +22,7 @@ namespace Gaming // 被动技能开局时就释放,持续到游戏结束 | |||||
| { | { | ||||
| new FrameRateTaskExecutor<int> | new FrameRateTaskExecutor<int> | ||||
| ( | ( | ||||
| () => gameMap.Timer.IsGaming && !player.IsResetting, | |||||
| () => gameMap.Timer.IsGaming && !player.IsRemoved, | |||||
| () => | () => | ||||
| { | { | ||||
| if (player.Commandable() && player.PlayerState != PlayerStateType.Fixing) activeSkill.DegreeOfMeditation += learningDegree * GameData.frameDuration; | if (player.Commandable() && player.PlayerState != PlayerStateType.Fixing) activeSkill.DegreeOfMeditation += learningDegree * GameData.frameDuration; | ||||
| @@ -14,7 +14,6 @@ namespace Preparation.Interface | |||||
| public BulletType BulletOfPlayer { get; set; } | public BulletType BulletOfPlayer { get; set; } | ||||
| public CharacterType CharacterType { get; } | public CharacterType CharacterType { get; } | ||||
| public int UpdateBulletNum(int time); | public int UpdateBulletNum(int time); | ||||
| public long ThreadNum { get; } | |||||
| public bool IsGhost(); | public bool IsGhost(); | ||||
| } | } | ||||
| @@ -5,11 +5,12 @@ namespace Preparation.Interface | |||||
| { | { | ||||
| public interface IMoveable : IGameObj | public interface IMoveable : IGameObj | ||||
| { | { | ||||
| object MoveLock { get; } | |||||
| object ActionLock { get; } | |||||
| public int MoveSpeed { get; } | public int MoveSpeed { get; } | ||||
| public bool IsMoving { get; set; } | public bool IsMoving { get; set; } | ||||
| public bool IsResetting { get; } // reviving | |||||
| public bool IsAvailable { get; } | |||||
| public bool IsRemoved { get; } | |||||
| public bool IsAvailableForMove { get; } | |||||
| public long StateNum { get; } | |||||
| public long MovingSetPos(XY moveVec); | public long MovingSetPos(XY moveVec); | ||||
| public void ReSetCanMove(bool value); | public void ReSetCanMove(bool value); | ||||
| public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞 | public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞 | ||||