Browse Source

Merge pull request #534 from shangfengh/new

fix: 🔒 refactor the moveEngine and edit 游戏机制与平衡性调整更新草案.md
tags/v0.1.0
Shawqeem GitHub 2 years ago
parent
commit
af1c8e758d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 394 additions and 235 deletions
  1. +2
    -1
      docs/GameRules.md
  2. +13
    -2
      docs/游戏机制与平衡性调整更新草案.md
  3. +5
    -2
      docs/版本更新说明.md
  4. +1
    -3
      logic/GameClass/GameObj/Bullet/Bullet.cs
  5. +0
    -5
      logic/GameClass/GameObj/Character/Character.Ghost.cs
  6. +28
    -21
      logic/GameClass/GameObj/Character/Character.cs
  7. +57
    -4
      logic/GameClass/GameObj/Map/Window.cs
  8. +32
    -4
      logic/GameClass/GameObj/Moveable.cs
  9. +115
    -112
      logic/GameEngine/MoveEngine.cs
  10. +93
    -63
      logic/Gaming/ActionManager.cs
  11. +2
    -0
      logic/Gaming/AttackManager.cs
  12. +38
    -14
      logic/Gaming/CharacterManager.cs
  13. +1
    -1
      logic/Gaming/PropManager.cs
  14. +3
    -1
      logic/Preparation/Interface/IMoveable.cs
  15. +1
    -0
      logic/Preparation/Utility/EnumType.cs
  16. +2
    -2
      logic/Preparation/Utility/GameData.cs
  17. +1
    -0
      logic/Preparation/Utility/Transformation.cs

+ 2
- 1
docs/GameRules.md View File

@@ -309,7 +309,7 @@ $$
- 不鼓励选手面向地图编程,因为移动过程中你可以受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。 - 不鼓励选手面向地图编程,因为移动过程中你可以受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。


### 人物 ### 人物
- 眩晕状态中的玩家不能再次被眩晕
- 眩晕状态中的玩家不能再次被眩晕(除非是ShowTime)


### 初始状态 ### 初始状态
- 初赛玩家出生点固定且一定为空地 - 初赛玩家出生点固定且一定为空地
@@ -342,6 +342,7 @@ $$
- 开锁门进度中断后清空 - 开锁门进度中断后清空


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


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

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


## 说明 ## 说明
- 该草案尚未完全确定,请大家不要过分依靠该文档进行修改自己的代码 - 该草案尚未完全确定,请大家不要过分依靠该文档进行修改自己的代码
@@ -21,9 +21,9 @@ v1.5
- 未攻击至目标时的后摇改为1200ms - 未攻击至目标时的后摇改为1200ms
- 增强为“可以攻击未写完的作业” - 增强为“可以攻击未写完的作业”
- 增强为“可以攻击使门被打开(可以重新被锁上)” - 增强为“可以攻击使门被打开(可以重新被锁上)”
- 改为“当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上90°,180° ,270° 发出3个小炸弹”
- 小炸弹JumpyDumpty - 小炸弹JumpyDumpty
- 小炸弹不受道具增益影响 - 小炸弹不受道具增益影响
- 修改为“小炸弹与自己无碰撞体积”
- strike(新增) - strike(新增)
- 可以攻击未写完的作业 - 可以攻击未写完的作业


@@ -105,3 +105,14 @@ v1.5
- 普通攻击改为strike - 普通攻击改为strike
- Klee - Klee
- 被动技能Lucky!(新增):开局获得随机的一个道具(不会是钥匙) - 被动技能Lucky!(新增):开局获得随机的一个道具(不会是钥匙)
- 主动技能SparksNSplash(新增):
- CD:45s, 持续时间:10s
- 技能使用瞬间,对于输入的额外参数PlayerID代表的角色,距离最近的本已停止运动的小炸弹开始追踪该角色(每50ms向该角色直线移动)
- 主动技能 蹦蹦炸弹 JumpyBomb
- 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上0°,45°,90°,135°,180°,225°,270°,315° 发出8个小炸弹
- Idol
主动技能ShowTime改为
"持续时间内
- 使警戒范围外的学生眩晕并每**500ms**发送向自己移动**500ms**的指令(速度为学生本应速度*二者距离/警戒范围)
- 对于视野范围(不是可视区域)内的学生每**500ms**加**1500**的沉迷度
- 捣蛋鬼变为0.8倍速"

+ 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

+ 1
- 3
logic/GameClass/GameObj/Bullet/Bullet.cs View File

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


namespace GameClass.GameObj namespace GameClass.GameObj
{ {
@@ -27,7 +26,6 @@ namespace GameClass.GameObj
public bool HasSpear => hasSpear; public bool HasSpear => hasSpear;


/// <summary> /// <summary>
/// 与THUAI4不同的一个攻击判定方案,通过这个函数判断爆炸时能否伤害到target
/// </summary> /// </summary>
/// <param name="target">被尝试攻击者</param> /// <param name="target">被尝试攻击者</param>
/// <returns>是否可以攻击到</returns> /// <returns>是否可以攻击到</returns>
@@ -36,7 +34,7 @@ namespace GameClass.GameObj


public override bool IgnoreCollideExecutor(IGameObj targetObj) public override bool IgnoreCollideExecutor(IGameObj targetObj)
{ {
if (targetObj == Parent && CanMove) return true;
if (targetObj == Parent) return true;
if (targetObj.Type == GameObjType.Prop || targetObj.Type == GameObjType.Bullet) if (targetObj.Type == GameObjType.Prop || targetObj.Type == GameObjType.Bullet)
return true; return true;
return false; return false;


+ 0
- 5
logic/GameClass/GameObj/Character/Character.Ghost.cs View File

@@ -1,10 +1,5 @@
using Preparation.Interface; using Preparation.Interface;
using Preparation.Utility; using Preparation.Utility;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Threading;


namespace GameClass.GameObj namespace GameClass.GameObj
{ {


+ 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()


+ 57
- 4
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
{ {
@@ -25,15 +27,66 @@ namespace GameClass.GameObj
return false; return false;
} }


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;
} }




+ 115
- 112
logic/GameEngine/MoveEngine.cs View File

@@ -20,18 +20,6 @@ namespace GameEngine


private readonly ITimer gameTimer; private readonly ITimer gameTimer;
private readonly Action<IMoveable> EndMove; private readonly Action<IMoveable> EndMove;
public readonly uint[,] ProtoGameMap;
public PlaceType GetPlaceType(XY Position)
{
try
{
return (PlaceType)ProtoGameMap[Position.x / GameData.numOfPosGridPerCell, Position.y / GameData.numOfPosGridPerCell];
}
catch
{
return PlaceType.Null;
}
}


public IGameObj? CheckCollision(IMoveable obj, XY Pos) public IGameObj? CheckCollision(IMoveable obj, XY Pos)
{ {
@@ -52,7 +40,6 @@ namespace GameEngine
Action<IMoveable> EndMove Action<IMoveable> EndMove
) )
{ {
this.ProtoGameMap = gameMap.ProtoGameMap;
this.gameTimer = gameMap.Timer; this.gameTimer = gameMap.Timer;
this.EndMove = EndMove; this.EndMove = EndMove;
this.OnCollision = OnCollision; this.OnCollision = OnCollision;
@@ -64,21 +51,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;
}

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

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

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

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


public void MoveObj(IMoveable obj, int moveTime, double direction, long threadNum)
public void MoveObj(IMoveable obj, int moveTime, double direction, long stateNum)
{ {
if (!gameTimer.IsGaming) return; if (!gameTimer.IsGaming) return;
lock (obj.ActionLock) lock (obj.ActionLock)
{ {
if (!obj.IsAvailableForMove) return;
if (!obj.IsAvailableForMove) { EndMove(obj); return; }
obj.IsMoving = true; obj.IsMoving = true;
} }
new Thread new Thread
@@ -87,9 +110,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 +129,82 @@ 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)
{ {
new FrameRateTaskExecutor<int>(
() => gameTimer.IsGaming && obj.CanMove && !obj.IsRemoved && obj.IsMoving,
() =>
obj.IsMoving = false;
EndMove(obj);
return;
}
else
{
if (moveTime >= GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond)
{
Thread.Sleep(GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond);
new FrameRateTaskExecutor<int>(
() => gameTimer.IsGaming,
() =>
{
if (obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved)
return !(isEnded = true);
return !(isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum));
},
GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond,
() =>
{
return 0;
},
maxTotalDuration: moveTime - GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond
)
{ {
moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond;
res = new XY(direction, moveVecLength);

//对人特殊处理
if (threadNum > 0 && obj.StateNum != threadNum) return false;

// 越界情况处理:如果越界,则与越界方块碰撞
bool flag; // 循环标志
do
AllowTimeExceed = true,
MaxTolerantTimeExceedCount = ulong.MaxValue,
TimeExceedAction = b =>
{ {
flag = false;
collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res);
if (collisionObj == null)
break;
if (b)
Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!");


#if DEBUG
else
{
Console.WriteLine("Debug info: Object moving time exceed for once.");
}
#endif
}
}.Start();
if (!isEnded && obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved)
isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum);
}
if (isEnded)
{
obj.IsMoving = false;
EndMove(obj);
return;
}
if (obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved)
{
int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond);
if (leftTime > 0)
{
Thread.Sleep(leftTime); // 多移动的在这里补回来
}
do
{
flag = false;
moveVecLength = (double)deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell;
res = new XY(direction, moveVecLength);
if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null)
{
obj.MovingSetPos(res, stateNum);
}
else
{
switch (OnCollision(obj, collisionObj, res)) switch (OnCollision(obj, collisionObj, res))
{ {
case AfterCollision.ContinueCheck: case AfterCollision.ContinueCheck:
@@ -141,87 +212,19 @@ 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; // 结束移动
EndMove(obj);
} }
} }
).Start(); ).Start();


+ 93
- 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)
{ {
@@ -462,6 +491,7 @@ namespace Gaming
}, },
EndMove: obj => EndMove: obj =>
{ {
obj.ThreadNum.Release();
// Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64); // Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64);
} }
); );


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

@@ -195,7 +195,9 @@ 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 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);


logic/Gaming/CharacterManager .cs → logic/Gaming/CharacterManager.cs View File

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

case PlayerStateType.Addicted: case PlayerStateType.Addicted:
if (value == PlayerStateType.Rescued) if (value == PlayerStateType.Rescued)
return player.ChangePlayerStateInOneThread(value, gameObj); return player.ChangePlayerStateInOneThread(value, gameObj);
else
else if (value == PlayerStateType.Null)
return player.ChangePlayerState(value, gameObj); return player.ChangePlayerState(value, gameObj);
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
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;

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:
if (player.NoHp()) return -1;
return player.ChangePlayerState(value, gameObj); return player.ChangePlayerState(value, gameObj);
} }
} }
@@ -163,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