Browse Source

fix: 🔒 refactor the moveEngine

tags/v0.1.0
shangfengh 2 years ago
parent
commit
5febfb7b8d
16 changed files with 397 additions and 214 deletions
  1. +1
    -0
      docs/GameRules.md
  2. +9
    -3
      docs/游戏机制与平衡性调整更新草案.md
  3. +5
    -2
      docs/版本更新说明.md
  4. +28
    -21
      logic/GameClass/GameObj/Character/Character.cs
  5. +1
    -1
      logic/GameClass/GameObj/Map/Map.cs
  6. +61
    -5
      logic/GameClass/GameObj/Map/Window.cs
  7. +32
    -4
      logic/GameClass/GameObj/Moveable.cs
  8. +109
    -98
      logic/GameEngine/MoveEngine.cs
  9. +92
    -63
      logic/Gaming/ActionManager.cs
  10. +18
    -2
      logic/Gaming/AttackManager.cs
  11. +33
    -11
      logic/Gaming/CharacterManager .cs
  12. +1
    -1
      logic/Gaming/PropManager.cs
  13. +3
    -1
      logic/Preparation/Interface/IMoveable.cs
  14. +1
    -0
      logic/Preparation/Utility/EnumType.cs
  15. +2
    -2
      logic/Preparation/Utility/GameData.cs
  16. +1
    -0
      logic/Preparation/Utility/Transformation.cs

+ 1
- 0
docs/GameRules.md View File

@@ -342,6 +342,7 @@ $$
- 开锁门进度中断后清空 - 开锁门进度中断后清空


### 窗 ### 窗
- 由于窗户被占用导致翻窗失败会使先前行动停止
- 翻越窗户是一种交互行为,翻窗一共有两个过程 - 翻越窗户是一种交互行为,翻窗一共有两个过程
- 跳上窗:从当前位置到窗边缘中点,位置瞬移,时间=距离/爬窗速度。中断时,停留在原位置 - 跳上窗:从当前位置到窗边缘中点,位置瞬移,时间=距离/爬窗速度。中断时,停留在原位置
- 爬窗:从窗一侧边缘中点到另一侧格子中心,位置渐移,时间=距离/爬窗速度。中断时,停留在另一侧格子中心 - 爬窗:从窗一侧边缘中点到另一侧格子中心,位置渐移,时间=距离/爬窗速度。中断时,停留在另一侧格子中心


+ 9
- 3
docs/游戏机制与平衡性调整更新草案.md View File

@@ -1,5 +1,5 @@
# 游戏机制与平衡性调整更新草案 # 游戏机制与平衡性调整更新草案
v1.5
v1.6


## 说明 ## 说明
- 该草案尚未完全确定,请大家不要过分依靠该文档进行修改自己的代码 - 该草案尚未完全确定,请大家不要过分依靠该文档进行修改自己的代码
@@ -106,6 +106,12 @@ v1.5
- 被动技能Lucky!(新增):开局获得随机的一个道具(不会是钥匙) - 被动技能Lucky!(新增):开局获得随机的一个道具(不会是钥匙)
- 主动技能SparksNSplash(新增): - 主动技能SparksNSplash(新增):
- CD:45s, 持续时间:10s - CD:45s, 持续时间:10s
- 技能使用瞬间,离输入的额外参数PlayerID代表的角色最近的本来停止运动的小炸弹开始追踪该角色(每50ms向该角色直线移动)
- 技能使用瞬间,对于输入的额外参数PlayerID代表的角色,距离最近的本已停止运动的小炸弹开始追踪该角色(每50ms向该角色直线移动)
- 主动技能 蹦蹦炸弹 JumpyBomb - 主动技能 蹦蹦炸弹 JumpyBomb
- 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上0°,45°,90°,135°,180°,225°,270°,315° 发出2个小炸弹
- 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上0°,45°,90°,135°,180°,225°,270°,315° 发出8个小炸弹
- Idol
主动技能ShowTime改为
"持续时间内
- 使警戒范围外的学生眩晕并每**500ms**发送向自己移动**500ms**的指令(速度为学生本应速度*二者距离/警戒范围)
- 对于视野范围(不是可视区域)内的学生每**500ms**加**1500**的沉迷度
- 捣蛋鬼变为0.8倍速"

+ 5
- 2
docs/版本更新说明.md View File

@@ -21,11 +21,14 @@
- docs:更新了 游戏机制与平衡性调整更新草案.pdf - docs:更新了 游戏机制与平衡性调整更新草案.pdf
- change:更改了地图的文件路径 - change:更改了地图的文件路径


# 最新更新
# 5月10日更新
- fix:修复JumpyDumpty的初始位置错误的问题 - fix:修复JumpyDumpty的初始位置错误的问题
- fix:修正和重新说明攻击距离 - fix:修正和重新说明攻击距离
- **攻击距离是指攻击(子弹)的移动距离,也就是说理论上最远被攻击的学生的中心与捣蛋鬼的中心=学生的半径+捣蛋鬼的半径+攻击距离+子弹半径(200)×2** - **攻击距离是指攻击(子弹)的移动距离,也就是说理论上最远被攻击的学生的中心与捣蛋鬼的中心=学生的半径+捣蛋鬼的半径+攻击距离+子弹半径(200)×2**
- hotfix:修复小炸弹初始化类型错误的问题 - hotfix:修复小炸弹初始化类型错误的问题
- remove:去除了“实际上唤醒或勉励不同的人是有效的” - remove:去除了“实际上唤醒或勉励不同的人是有效的”
- **重复发出同一类型的交互指令和移动指令是无效的** - **重复发出同一类型的交互指令和移动指令是无效的**
- feat&fix:修复并**将`SendMessage`改为`SendTextMessage`与`SendBinaryMessage`**
- feat&fix:修复并**将`SendMessage`改为`SendTextMessage`与`SendBinaryMessage`**

# 最新更新
- docs:更新了 游戏机制与平衡性调整更新草案.pdf

+ 28
- 21
logic/GameClass/GameObj/Character/Character.cs View File

@@ -324,7 +324,17 @@ namespace GameClass.GameObj
return (playerState != PlayerStateType.Deceased && playerState != PlayerStateType.Escaped return (playerState != PlayerStateType.Deceased && playerState != PlayerStateType.Escaped
&& playerState != PlayerStateType.Addicted && playerState != PlayerStateType.Rescued && playerState != PlayerStateType.Addicted && playerState != PlayerStateType.Rescued
&& playerState != PlayerStateType.Swinging && playerState != PlayerStateType.TryingToAttack && 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() public bool InteractingWithMapWithoutMoving()
@@ -345,8 +355,9 @@ namespace GameClass.GameObj
{ {
lock (actionLock) lock (actionLock)
return !(playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped 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); || playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving);
} }
private GameObj? whatInteractingWith = null; private GameObj? whatInteractingWith = null;
@@ -354,28 +365,24 @@ namespace GameClass.GameObj


public long ChangePlayerState(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) 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) 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() public long SetPlayerStateNaturally()


+ 1
- 1
logic/GameClass/GameObj/Map/Map.cs View File

@@ -484,7 +484,7 @@ namespace GameClass.GameObj
} }
case (uint)PlaceType.Window: case (uint)PlaceType.Window:
{ {
Add(new Window(GameData.GetCellCenterPos(i, j)));
Add(new Window(GameData.GetCellCenterPos(i, j), mapResource[i - 1, j] == (uint)PlaceType.Wall));
break; break;
} }
case (uint)PlaceType.BirthPoint1: case (uint)PlaceType.BirthPoint1:


+ 61
- 5
logic/GameClass/GameObj/Map/Window.cs View File

@@ -1,5 +1,7 @@
using Preparation.Interface; using Preparation.Interface;
using Preparation.Utility; using Preparation.Utility;
using System.Numerics;
using System;


namespace GameClass.GameObj namespace GameClass.GameObj
{ {
@@ -8,9 +10,10 @@ namespace GameClass.GameObj
/// </summary> /// </summary>
public class Window : Immovable public class Window : Immovable
{ {
public Window(XY initPos) :
public Window(XY initPos, bool xIsWall) :
base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Window) base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Window)
{ {
this.xIsWall = xIsWall;
} }
public override bool IsRigid => true; public override bool IsRigid => true;
public override ShapeType Shape => ShapeType.Square; public override ShapeType Shape => ShapeType.Square;
@@ -25,15 +28,68 @@ namespace GameClass.GameObj
return false; return false;
} }


public readonly bool xIsWall;

private XY stage = new(0, 0);
public XY Stage
{
get
{
GameObjReaderWriterLock.EnterReadLock();
try
{
return stage;
}
finally { GameObjReaderWriterLock.ExitReadLock(); }
}
}

private Character? whoIsClimbing = null; private Character? whoIsClimbing = null;
public Character? WhoIsClimbing 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(); }
} }
} }
} }

+ 32
- 4
logic/GameClass/GameObj/Moveable.cs View File

@@ -8,8 +8,23 @@ namespace GameClass.GameObj
{ {
protected readonly object actionLock = new(); protected readonly object actionLock = new();
public object ActionLock => actionLock; public object ActionLock => actionLock;
//player.actionLock>其他.actionLock
private readonly ReaderWriterLockSlim moveReaderWriterLock = new(); private readonly ReaderWriterLockSlim moveReaderWriterLock = new();
public ReaderWriterLockSlim MoveReaderWriterLock => moveReaderWriterLock; public ReaderWriterLockSlim MoveReaderWriterLock => moveReaderWriterLock;

private Semaphore threadNum = new(1, 1);
public Semaphore ThreadNum
{
get
{
return threadNum;
}
set
{
threadNum = value;
}
}

protected long stateNum = 0; protected long stateNum = 0;
public long StateNum 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) 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; return moveVec * moveVec;
} }




+ 109
- 98
logic/GameEngine/MoveEngine.cs View File

@@ -64,21 +64,57 @@ namespace GameEngine
/// </summary> /// </summary>
/// <param name="obj">移动物体,默认obj.Rigid为true</param> /// <param name="obj">移动物体,默认obj.Rigid为true</param>
/// <param name="moveVec">移动的位移向量</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; XY nextPos = obj.Position + moveVec;
double maxLen = collisionChecker.FindMax(obj, nextPos, moveVec); double maxLen = collisionChecker.FindMax(obj, nextPos, moveVec);
maxLen = Math.Min(maxLen, obj.MoveSpeed / GameData.numOfStepPerSecond); maxLen = Math.Min(maxLen, obj.MoveSpeed / GameData.numOfStepPerSecond);
obj.MovingSetPos(new XY(moveVec, maxLen));
return (obj.MovingSetPos(new XY(moveVec, maxLen), stateNum)) >= 0;
} }


public void MoveObj(IMoveable obj, int moveTime, double direction, long threadNum)
private bool LoopDo(IMoveable obj, double direction, ref double deltaLen, long stateNum)
{
double moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond;
XY res = new(direction, moveVecLength);

// 越界情况处理:如果越界,则与越界方块碰撞
bool flag; // 循环标志
do
{
flag = false;
IGameObj? collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res);
if (collisionObj == null)
break;

switch (OnCollision(obj, collisionObj, res))
{
case AfterCollision.ContinueCheck:
flag = true;
break;
case AfterCollision.Destroyed:
Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
return false;
case AfterCollision.MoveMax:
if (!MoveMax(obj, res, stateNum)) return false;
moveVecLength = 0;
res = new XY(direction, moveVecLength);
break;
}
} while (flag);

long moveL = obj.MovingSetPos(res, stateNum);
if (moveL == -1) return false;
deltaLen = deltaLen + moveVecLength - Math.Sqrt(moveL);
return true;
}

public void MoveObj(IMoveable obj, int moveTime, double direction, long stateNum)
{ {
if (!gameTimer.IsGaming) return; if (!gameTimer.IsGaming) return;
lock (obj.ActionLock) lock (obj.ActionLock)
{ {
if (!obj.IsAvailableForMove) return;
if (!obj.IsAvailableForMove) { obj.ThreadNum.Release(); return; }
obj.IsMoving = true; obj.IsMoving = true;
} }
new Thread new Thread
@@ -87,9 +123,9 @@ namespace GameEngine
{ {
double moveVecLength = 0.0; double moveVecLength = 0.0;
XY res = new(direction, moveVecLength); XY res = new(direction, moveVecLength);
double deltaLen = 0; // 转向,并用deltaLen存储行走的误差
double deltaLen = (double)0.0; // 转向,并用deltaLen存储行走的误差
IGameObj? collisionObj = null; IGameObj? collisionObj = null;
bool isDestroyed = false;
bool isEnded = false;


bool flag; // 循环标志 bool flag; // 循环标志
do do
@@ -106,34 +142,76 @@ namespace GameEngine
break; break;
case AfterCollision.Destroyed: case AfterCollision.Destroyed:
Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
isDestroyed = true;
isEnded = true;
break; break;
case AfterCollision.MoveMax: case AfterCollision.MoveMax:
break; break;
} }
} while (flag); } while (flag);


if (!isDestroyed)
if (isEnded)
{
obj.IsMoving = false;
obj.ThreadNum.Release();
EndMove(obj);
return;
}
else
{ {
new FrameRateTaskExecutor<int>(
() => gameTimer.IsGaming && obj.CanMove && !obj.IsRemoved && obj.IsMoving,
() =>
if (moveTime >= GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond)
{
Thread.Sleep(GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond);
new FrameRateTaskExecutor<int>(
() => gameTimer.IsGaming && obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved,
() =>
{
return !(isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum));
},
GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond,
() =>
{
return 0;
},
maxTotalDuration: moveTime - GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond
)
{ {
moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond;
res = new XY(direction, moveVecLength);
AllowTimeExceed = true,
MaxTolerantTimeExceedCount = ulong.MaxValue,
TimeExceedAction = b =>
{
if (b)
Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!");


//对人特殊处理
if (threadNum > 0 && obj.StateNum != threadNum) return false;
#if DEBUG
else
{
Console.WriteLine("Debug info: Object moving time exceed for once.");
}
#endif
}
}.Start();
if (!isEnded && obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved)
isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum);
}


// 越界情况处理:如果越界,则与越界方块碰撞
bool flag; // 循环标志
do
if (!isEnded && obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved)
{
int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond);
if (leftTime > 0)
{
Thread.Sleep(leftTime); // 多移动的在这里补回来
}
do
{
flag = false;
moveVecLength = (double)deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell;
res = new XY(direction, moveVecLength);
if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null)
{
obj.MovingSetPos(res, stateNum);
}
else
{ {
flag = false;
collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res);
if (collisionObj == null)
break;

switch (OnCollision(obj, collisionObj, res)) switch (OnCollision(obj, collisionObj, res))
{ {
case AfterCollision.ContinueCheck: case AfterCollision.ContinueCheck:
@@ -141,87 +219,20 @@ namespace GameEngine
break; break;
case AfterCollision.Destroyed: case AfterCollision.Destroyed:
Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
isDestroyed = true;
return false;
isEnded = true;
break;
case AfterCollision.MoveMax: case AfterCollision.MoveMax:
if (threadNum == 0 || obj.StateNum == threadNum)
MoveMax(obj, res);
MoveMax(obj, res, stateNum);
moveVecLength = 0; moveVecLength = 0;
res = new XY(direction, moveVecLength); res = new XY(direction, moveVecLength);
break; break;
} }
} while (flag);

if (threadNum == 0 || obj.StateNum == threadNum)
deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res));

return true;
},
GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond,
() =>
{
int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond);
bool flag;
do
{
flag = false;
if (!isDestroyed)
{
moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell;
res = new XY(direction, moveVecLength);
if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null)
{
if (threadNum == 0 || obj.StateNum == threadNum)
obj.MovingSetPos(res);
}
else
{
switch (OnCollision(obj, collisionObj, res))
{
case AfterCollision.ContinueCheck:
flag = true;
break;
case AfterCollision.Destroyed:
Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
isDestroyed = true;
break;
case AfterCollision.MoveMax:
if (threadNum == 0 || obj.StateNum == threadNum)
MoveMax(obj, res);
moveVecLength = 0;
res = new XY(direction, moveVecLength);
break;
}
}
}
} while (flag);
if (leftTime > 0 && obj.IsMoving)
{
Thread.Sleep(leftTime); // 多移动的在这里补回来
}
lock (obj.ActionLock)
obj.IsMoving = false; // 结束移动
EndMove(obj);
return 0;
},
maxTotalDuration: moveTime
)
{
AllowTimeExceed = true,
MaxTolerantTimeExceedCount = ulong.MaxValue,
TimeExceedAction = b =>
{
if (b)
Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!");

#if DEBUG
else
{
Console.WriteLine("Debug info: Object moving time exceed for once.");
} }
#endif
}
}.Start();
} while (flag);
}
obj.IsMoving = false; // 结束移动
obj.ThreadNum.Release();
EndMove(obj);
} }
} }
).Start(); ).Start();


+ 92
- 63
logic/Gaming/ActionManager.cs View File

@@ -36,26 +36,59 @@ namespace Gaming
public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection)
{ {
if (moveTimeInMilliseconds < 5) return false; 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; return true;
} }


public bool MovePlayerWhenStunned(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) 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; return true;
} }


public bool Stop(Character player) 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; return false;
} }
@@ -279,12 +312,11 @@ namespace Gaming
} }
public bool ClimbingThroughWindow(Character player) public bool ClimbingThroughWindow(Character player)
{ {
if (!player.Commandable())
return false;
Window? windowForClimb = (Window?)gameMap.OneForInteractInACross(player.Position, GameObjType.Window); 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( 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, (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()) if (player.IsGhost() && !characterInWindow.IsGhost())
characterManager.BeAttacked((Student)(characterInWindow), player.Attack(characterInWindow.Position)); characterManager.BeAttacked((Student)(characterInWindow), player.Attack(characterInWindow.Position));
return false; 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 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(); { IsBackground = true }.Start();


return true; return true;
@@ -400,7 +430,6 @@ namespace Gaming
timeInterval: GameData.frameDuration, timeInterval: GameData.frameDuration,
finallyReturn: () => 0 finallyReturn: () => 0
) )

.Start(); .Start();
if (doorToLock.OpenOrLockDegree >= GameData.degreeOfLockingOrOpeningTheDoor) if (doorToLock.OpenOrLockDegree >= GameData.degreeOfLockingOrOpeningTheDoor)
{ {


+ 18
- 2
logic/Gaming/AttackManager.cs View File

@@ -47,7 +47,15 @@ namespace Gaming
if (bullet == null) return; if (bullet == null) return;
Debugger.Output(bullet, "Attack in " + pos.ToString()); Debugger.Output(bullet, "Attack in " + pos.ToString());
gameMap.Add(bullet); gameMap.Add(bullet);
moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms
new Thread
(
() =>
{
bullet.ThreadNum.WaitOne();
moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms
}
)
{ IsBackground = true }.Start();
} }


private void BombObj(Bullet bullet, GameObj objBeingShot) private void BombObj(Bullet bullet, GameObj objBeingShot)
@@ -195,7 +203,15 @@ namespace Gaming
{ {
Debugger.Output(bullet, "Attack in " + bullet.Position.ToString()); Debugger.Output(bullet, "Attack in " + bullet.Position.ToString());
gameMap.Add(bullet); gameMap.Add(bullet);
moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms
new Thread
(
() =>
{
bullet.ThreadNum.WaitOne();
moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms
}
)
{ IsBackground = true }.Start();
if (bullet.CastTime > 0) if (bullet.CastTime > 0)
{ {
characterManager.SetPlayerState(player, PlayerStateType.TryingToAttack); characterManager.SetPlayerState(player, PlayerStateType.TryingToAttack);


+ 33
- 11
logic/Gaming/CharacterManager .cs View File

@@ -28,26 +28,48 @@ namespace Gaming
case PlayerStateType.Escaped: case PlayerStateType.Escaped:
case PlayerStateType.Deceased: case PlayerStateType.Deceased:
return -1; return -1;
case PlayerStateType.OpeningTheChest:
((Chest)player.WhatInteractingWith!).StopOpen();
return player.ChangePlayerState(value, gameObj);
case PlayerStateType.OpeningTheDoorway:
Doorway doorway = (Doorway)player.WhatInteractingWith!;
doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime;
doorway.OpenStartTime = 0;
return player.ChangePlayerState(value, gameObj);

case PlayerStateType.Addicted: case PlayerStateType.Addicted:
if (value == PlayerStateType.Rescued) if (value == PlayerStateType.Rescued)
return player.ChangePlayerStateInOneThread(value, gameObj); return player.ChangePlayerStateInOneThread(value, gameObj);
else if (value==PlayerStateType.Null)
else if (value == PlayerStateType.Null)
return player.ChangePlayerState(value, gameObj); return player.ChangePlayerState(value, gameObj);
else return -1; else return -1;
case PlayerStateType.Rescued: case PlayerStateType.Rescued:
if (value == PlayerStateType.Addicted) if (value == PlayerStateType.Addicted)
return player.ChangePlayerStateInOneThread(value, gameObj); return player.ChangePlayerStateInOneThread(value, gameObj);
else if(value==PlayerStateType.Null)
else if (value == PlayerStateType.Null)
return player.ChangePlayerState(value, gameObj);
else return -1;

case PlayerStateType.TryingToAttack:
case PlayerStateType.Stunned:
case PlayerStateType.Charmed:
case PlayerStateType.Swinging:
if (value != PlayerStateType.Moving && value != PlayerStateType.ClimbingThroughWindows)
return player.ChangePlayerState(value, gameObj);
else return -1;
case PlayerStateType.ClimbingThroughWindows:
if (value != PlayerStateType.Moving)
{
Window window = (Window)player.WhatInteractingWith!;
window.FinishClimbing();
if (window.Stage.x == 0)
player.ThreadNum.Release();
else player.ReSetPos(window.Stage);
return player.ChangePlayerState(value, gameObj); return player.ChangePlayerState(value, gameObj);
}
else return -1; 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: default:
return player.ChangePlayerState(value, gameObj); return player.ChangePlayerState(value, gameObj);
} }
@@ -165,7 +187,7 @@ namespace Gaming
newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.DistanceFloor3(newPlayer.Position, person.Position)); newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.DistanceFloor3(newPlayer.Position, person.Position));
else newPlayer.AddBgm(BgmType.GhostIsComing, 0); 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; TimePinningDown += GameData.checkInterval;
newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded); newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded);


+ 1
- 1
logic/Gaming/PropManager.cs View File

@@ -54,7 +54,7 @@ namespace Gaming
else player.AddAp(GameData.PropDuration); else player.AddAp(GameData.PropDuration);
break; break;
case PropType.RecoveryFromDizziness: case PropType.RecoveryFromDizziness:
if (player.PlayerState == PlayerStateType.Stunned)
if (player.PlayerState == PlayerStateType.Stunned || player.PlayerState == PlayerStateType.Charmed)
{ {
player.AddScore(GameData.ScorePropRecoverFromDizziness); player.AddScore(GameData.ScorePropRecoverFromDizziness);
player.SetPlayerStateNaturally(); player.SetPlayerStateNaturally();


+ 3
- 1
logic/Preparation/Interface/IMoveable.cs View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using Preparation.Utility; using Preparation.Utility;


namespace Preparation.Interface namespace Preparation.Interface
@@ -11,7 +12,8 @@ namespace Preparation.Interface
public bool IsRemoved { get; } public bool IsRemoved { get; }
public bool IsAvailableForMove { get; } public bool IsAvailableForMove { get; }
public long StateNum { 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 void ReSetCanMove(bool value);
public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞 public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞
{ {


+ 1
- 0
logic/Preparation/Utility/EnumType.cs View File

@@ -24,6 +24,7 @@ namespace Preparation.Utility
ClimbingThroughWindows = 15, ClimbingThroughWindows = 15,
UsingSkill = 16, UsingSkill = 16,
OpeningTheDoorway = 17, OpeningTheDoorway = 17,
Charmed = 18,
} }
public enum GameObjType public enum GameObjType
{ {


+ 2
- 2
logic/Preparation/Utility/GameData.cs View File

@@ -6,13 +6,13 @@ namespace Preparation.Utility
public static class GameData public static class GameData
{ {
#region 基本常数 #region 基本常数
public const int numOfStepPerSecond = 20; // 每秒行走的步数
public const int numOfStepPerSecond = 100; // 每秒行走的步数


public const int tolerancesLength = 3; public const int tolerancesLength = 3;
public const int adjustLength = 3; public const int adjustLength = 3;


public const int frameDuration = 50; // 每帧时长 public const int frameDuration = 50; // 每帧时长
public const int checkInterval = 50; // 检查位置标志、补充子弹的帧时长
public const int checkInterval = 10;
public const long gameDuration = 600000; // 游戏时长600000ms = 10min public const long gameDuration = 600000; // 游戏时长600000ms = 10min


public const int LimitOfStopAndMove = 15; public const int LimitOfStopAndMove = 15;


+ 1
- 0
logic/Preparation/Utility/Transformation.cs View File

@@ -116,6 +116,7 @@ namespace Preparation.Utility
case Preparation.Utility.PlayerStateType.Rescuing: case Preparation.Utility.PlayerStateType.Rescuing:
return PlayerState.Rescuing; return PlayerState.Rescuing;
case Preparation.Utility.PlayerStateType.Stunned: case Preparation.Utility.PlayerStateType.Stunned:
case Preparation.Utility.PlayerStateType.Charmed:
return PlayerState.Stunned; return PlayerState.Stunned;
case Preparation.Utility.PlayerStateType.Swinging: case Preparation.Utility.PlayerStateType.Swinging:
return PlayerState.Swinging; return PlayerState.Swinging;


Loading…
Cancel
Save