Browse Source

feat: try to add a new CharacterType TechOtaku

tags/0.1.0
shangfengh 2 years ago
parent
commit
a458951aa9
18 changed files with 194 additions and 91 deletions
  1. +1
    -0
      logic/GameClass/GameObj/Character/Character.Skill.cs
  2. +4
    -4
      logic/GameClass/GameObj/Character/Character.cs
  3. +44
    -0
      logic/GameClass/GameObj/Character/Skill.cs
  4. +32
    -0
      logic/GameClass/GameObj/Map/Map.cs
  5. +34
    -30
      logic/GameEngine/CollisionChecker.cs
  6. +9
    -3
      logic/GameEngine/MoveEngine.cs
  7. +4
    -4
      logic/Gaming/ActionManager.cs
  8. +9
    -1
      logic/Gaming/CharacterManager .cs
  9. +14
    -14
      logic/Gaming/Game.cs
  10. +2
    -1
      logic/Gaming/PropManager.cs
  11. +28
    -27
      logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs
  12. +3
    -0
      logic/Gaming/SkillManager/SkillManager.cs
  13. +1
    -1
      logic/Preparation/Interface/ICharacter.cs
  14. +1
    -1
      logic/Preparation/Interface/IOccupation.cs
  15. +2
    -1
      logic/Preparation/Utility/EnumType.cs
  16. +4
    -2
      logic/Preparation/Utility/GameData.cs
  17. +1
    -1
      logic/cmd/gameServer.cmd
  18. +1
    -1
      logic/规则Logic.md

+ 1
- 0
logic/GameClass/GameObj/Character/Character.Skill.cs View File

@@ -61,6 +61,7 @@ namespace GameClass.GameObj
this.CanMove = true;
this.score = 0;
this.buffManager = new BuffManager();
Debugger.Output(this, characterType.ToString());
switch (characterType)
{
case CharacterType.Assassin:


+ 4
- 4
logic/GameClass/GameObj/Character/Character.cs View File

@@ -373,8 +373,8 @@ namespace GameClass.GameObj
/// <summary>
/// 角色所属队伍ID
/// </summary>
private long teamID = long.MaxValue;
public long TeamID
private int teamID = int.MaxValue;
public int TeamID
{
get => teamID;
set
@@ -386,8 +386,8 @@ namespace GameClass.GameObj
}
}
}
private long playerID = long.MaxValue;
public long PlayerID
private int playerID = int.MaxValue;
public int PlayerID
{
get => playerID;
set


+ 44
- 0
logic/GameClass/GameObj/Character/Skill.cs View File

@@ -91,6 +91,20 @@ namespace GameClass.GameObj
}
}

public class UseRobot : IActiveSkill
{
public int SkillCD => 0;
public int DurationTime => 0;
private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;

public bool isBeingUsed = false;
public bool IsBeingUsed
{
get => isBeingUsed; set => isBeingUsed = value;
}
}

public class WriteAnswers : IActiveSkill
{
public int SkillCD => GameData.commonSkillCD;
@@ -110,6 +124,32 @@ namespace GameClass.GameObj
}
}
}
public bool isBeingUsed = false;
public bool IsBeingUsed
{
get => isBeingUsed; set => isBeingUsed = value;
}
}

public class SummonGolem : IActiveSkill
{
public int SkillCD => GameData.commonSkillCD;
public int DurationTime => 0;
private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;

private Golem? golemSummoned = null;
public Golem? GolemSummoned
{
get => golemSummoned;
set
{
lock (commonSkillLock)
{
golemSummoned = value;
}
}
}

public bool isBeingUsed = false;
public bool IsBeingUsed
@@ -152,6 +192,8 @@ namespace GameClass.GameObj
return new JumpyBomb();
case ActiveSkillType.WriteAnswers:
return new WriteAnswers();
case ActiveSkillType.SummonGolem:
return new SummonGolem();
default:
return new NullSkill();
}
@@ -175,6 +217,8 @@ namespace GameClass.GameObj
return ActiveSkillType.JumpyBomb;
case WriteAnswers:
return ActiveSkillType.WriteAnswers;
case SummonGolem:
return ActiveSkillType.SummonGolem;
default:
return ActiveSkillType.Null;
}


+ 32
- 0
logic/GameClass/GameObj/Map/Map.cs View File

@@ -119,6 +119,38 @@ namespace GameClass.GameObj
}
return player;
}
public Character? FindPlayerToAction(long playerID)
{
Character? player = null;
gameObjLockDict[GameObjType.Character].EnterReadLock();
try
{
foreach (Character person in gameObjDict[GameObjType.Character])
{
if (playerID == person.ID)
{
if (person.CharacterType == CharacterType.TechOtaku && person.FindIActiveSkill(ActiveSkillType.UseRobot).IsBeingUsed)
{
foreach (Character character in gameObjDict[GameObjType.Character])
{
if (playerID + GameData.numOfPeople == character.ID)
{
player = character;
break;
}
}
}
else player = person;
break;
}
}
}
finally
{
gameObjLockDict[GameObjType.Character].ExitReadLock();
}
return player;
}
public bool Remove(GameObj gameObj)
{
bool flag = false;


+ 34
- 30
logic/GameEngine/CollisionChecker.cs View File

@@ -7,46 +7,33 @@ namespace GameEngine
{
internal class CollisionChecker
{
/// <summary>
/// 碰撞检测,如果这样行走是否会与之碰撞,返回与之碰撞的物体
/// </summary>
/// <param name="obj">移动的物体</param>
/// <param name="moveVec">移动的位移向量</param>
/// <returns>和它碰撞的物体</returns>
public IGameObj? CheckCollision(IMoveable obj, XY moveVec)
public IGameObj? CheckCollision(IMoveable obj, XY Pos)
{
XY nextPos = obj.Position + moveVec;
if (!obj.IsRigid)
{
if (gameMap.IsOutOfBound(obj))
return gameMap.GetOutOfBound(nextPos);
return null;
}
// 在列表中检查碰撞
Func<IEnumerable<IGameObj>, ReaderWriterLockSlim, IGameObj?> CheckCollisionInList =
(IEnumerable<IGameObj> lst, ReaderWriterLockSlim listLock) =>
{
IGameObj? collisionObj = null;
listLock.EnterReadLock();
try
{
foreach (var listObj in lst)
IGameObj? collisionObj = null;
listLock.EnterReadLock();
try
{
if (obj.WillCollideWith(listObj, nextPos))
foreach (var listObj in lst)
{
collisionObj = listObj;
break;
if (obj.WillCollideWith(listObj, Pos))
{
collisionObj = listObj;
break;
}
}
}
}
finally
{
listLock.ExitReadLock();
}
return collisionObj;
};
finally
{
listLock.ExitReadLock();
}
return collisionObj;
};

IGameObj? collisionObj = null;
IGameObj? collisionObj;
foreach (var list in lists)
{
if ((collisionObj = CheckCollisionInList(list.Item1, list.Item2)) != null)
@@ -58,6 +45,23 @@ namespace GameEngine
return null;
}
/// <summary>
/// 碰撞检测,如果这样行走是否会与之碰撞,返回与之碰撞的物体
/// </summary>
/// <param name="obj">移动的物体</param>
/// <param name="moveVec">移动的位移向量</param>
/// <returns>和它碰撞的物体</returns>
public IGameObj? CheckCollisionWhenMoving(IMoveable obj, XY moveVec)
{
XY nextPos = obj.Position + moveVec;
if (!obj.IsRigid)
{
if (gameMap.IsOutOfBound(obj))
return gameMap.GetOutOfBound(nextPos);
return null;
}
return CheckCollision(obj, nextPos);
}
/// <summary>
/// /// 可移动物体(圆)向矩形物体移动时,可移动且不会碰撞的最大距离。直接用double计算,防止误差
/// </summary>
/// <param name="obj"></param>


+ 9
- 3
logic/GameEngine/MoveEngine.cs View File

@@ -33,6 +33,12 @@ namespace GameEngine
return PlaceType.Null;
}
}

public IGameObj? CheckCollision(IMoveable obj, XY Pos)
{
return collisionChecker.CheckCollision(obj, Pos);
}

private readonly CollisionChecker collisionChecker;
private readonly Func<IMoveable, IGameObj, XY, AfterCollision> OnCollision;
/// <summary>
@@ -65,7 +71,7 @@ namespace GameEngine
XY nextPos = obj.Position + moveVec;
double maxLen = collisionChecker.FindMax(obj, nextPos, moveVec);
maxLen = Math.Min(maxLen, obj.MoveSpeed / GameData.numOfStepPerSecond);
obj.MovingSetPos(new XY(moveVec.Angle(), maxLen), GetPlaceType(nextPos));
obj.MovingSetPos(new XY(moveVec, maxLen), GetPlaceType(nextPos));
}

public void MoveObj(IMoveable obj, int moveTime, double direction)
@@ -98,7 +104,7 @@ namespace GameEngine
do
{
flag = false;
collisionObj = collisionChecker.CheckCollision(obj, res);
collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res);
if (collisionObj == null)
break;

@@ -135,7 +141,7 @@ namespace GameEngine
{
moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell;
res = new XY(direction, moveVecLength);
if ((collisionObj = collisionChecker.CheckCollision(obj, res)) == null)
if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null)
{
obj.MovingSetPos(res, GetPlaceType(obj.Position + res));
}


+ 4
- 4
logic/Gaming/ActionManager.cs View File

@@ -375,7 +375,7 @@ namespace Gaming
loopToDo: () => { },
timeInterval: GameData.frameDuration,
finallyReturn: () => 0,
maxTotalDuration: (int)((windowToPlayer + windowForClimb.Position - player.Position).Length() / player.MoveSpeed)
maxTotalDuration: (int)((windowToPlayer + windowForClimb.Position - player.Position).Length() * 1000 / player.MoveSpeed)
)
.Start();
if (player.PlayerState != PlayerStateType.ClimbingThroughWindows)
@@ -386,13 +386,13 @@ namespace Gaming

player.ReSetPos(windowToPlayer + windowForClimb.Position, PlaceType.Window);
player.MoveSpeed = player.SpeedOfClimbingThroughWindows;
MovePlayer(player, (int)(windowToPlayer.Length() * 3.0 / player.MoveSpeed), (-1 * windowToPlayer).Angle());
MovePlayer(player, (int)(windowToPlayer.Length() * 3.0 * 1000 / player.MoveSpeed), (-1 * windowToPlayer).Angle());
new FrameRateTaskExecutor<int>(
loopCondition: () => player.PlayerState == PlayerStateType.ClimbingThroughWindows && player.IsMoving && gameMap.Timer.IsGaming,
loopToDo: () => { },
timeInterval: GameData.frameDuration,
finallyReturn: () => 0,
maxTotalDuration: (int)(windowToPlayer.Length() * 3.0 / player.MoveSpeed)
maxTotalDuration: (int)(windowToPlayer.Length() * 3.0 * 1000 / player.MoveSpeed)
)
.Start();
XY PosJumpOff = windowForClimb.Position - 2 * windowToPlayer;
@@ -497,7 +497,7 @@ namespace Gaming
*/

private readonly Map gameMap;
private readonly MoveEngine moveEngine;
public readonly MoveEngine moveEngine;
public ActionManager(Map gameMap)
{
this.gameMap = gameMap;


+ 9
- 1
logic/Gaming/CharacterManager .cs View File

@@ -287,7 +287,15 @@ namespace Gaming
gameMap.Add(prop);
}
}
if (player.CharacterType == CharacterType.Robot) return;
if (player.CharacterType == CharacterType.Robot)
{
if (((Golem)player).Parent != null && ((Golem)player).Parent.CharacterType == CharacterType.TechOtaku)
{
((SummonGolem)player.FindIActiveSkill(ActiveSkillType.SummonGolem)).GolemSummoned = null;
player.FindIActiveSkill(ActiveSkillType.UseRobot).IsBeingUsed = false;
}
return;
}
++gameMap.NumOfDeceasedStudent;
if (GameData.numOfStudent - gameMap.NumOfDeceasedStudent - gameMap.NumOfEscapedStudent == 1)
{


+ 14
- 14
logic/Gaming/Game.cs View File

@@ -77,7 +77,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
Character? player = gameMap.FindPlayer(playerID);
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
bool res = actionManager.MovePlayer(player, moveTimeInMilliseconds, angle);
@@ -100,7 +100,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
ICharacter? player = gameMap.FindPlayer(playerID);
ICharacter? player = gameMap.FindPlayerToAction(playerID);
if (playerTreatedID == -1)
{
if (player != null && !player.IsGhost())
@@ -121,7 +121,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
ICharacter? player = gameMap.FindPlayer(playerID);
ICharacter? player = gameMap.FindPlayerToAction(playerID);
if (playerRescuedID == -1)
{
if (player != null && !player.IsGhost())
@@ -143,7 +143,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
ICharacter? player = gameMap.FindPlayer(playerID);
ICharacter? player = gameMap.FindPlayerToAction(playerID);
if (player != null && !player.IsGhost())
return actionManager.Fix((Student)player);
return false;
@@ -152,7 +152,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
ICharacter? player = gameMap.FindPlayer(playerID);
ICharacter? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
if (!player.IsGhost())
@@ -164,7 +164,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
Character? player = gameMap.FindPlayer(playerID);
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
return ActionManager.Stop(player);
@@ -175,7 +175,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
Character? player = gameMap.FindPlayer(playerID);
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null && !player.IsGhost())
{
return actionManager.OpenDoorway((Student)player);
@@ -186,7 +186,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
Character? player = gameMap.FindPlayer(playerID);
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
return actionManager.OpenChest(player);
@@ -197,7 +197,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
Character? player = gameMap.FindPlayer(playerID);
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
return actionManager.ClimbingThroughWindow(player);
@@ -208,7 +208,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
Character? player = gameMap.FindPlayer(playerID);
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
return actionManager.LockOrOpenDoor(player);
@@ -219,7 +219,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return;
Character? player = gameMap.FindPlayer(playerID);
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
_ = attackManager.Attack(player, angle);
@@ -229,7 +229,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return;
Character? player = gameMap.FindPlayer(playerID);
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
PropManager.UseProp(player, propType);
@@ -239,7 +239,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return;
Character? player = gameMap.FindPlayer(playerID);
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
propManager.ThrowProp(player, propType);
@@ -249,7 +249,7 @@ namespace Gaming
{
if (!gameMap.Timer.IsGaming)
return false;
Character? player = gameMap.FindPlayer(playerID);
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
return propManager.PickProp(player, propType);


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

@@ -186,7 +186,7 @@ namespace Gaming
{
gameMap.GameObjLockDict[GameObjType.Chest].ExitWriteLock();
}
/*
new Thread
(
() =>
@@ -208,6 +208,7 @@ namespace Gaming
}
)
{ IsBackground = true }.Start();
*/
}
public PropManager(Map gameMap) // 道具不能扔过墙
{


+ 28
- 27
logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs View File

@@ -11,20 +11,6 @@ namespace Gaming
{
private partial class SkillManager
{
public bool BecomeVampire(Character player)
{
return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.BecomeVampire), player, () =>
{
player.Vampire += 0.5;
Debugger.Output(player, "becomes vampire!");
},
() =>
{
double tempVam = player.Vampire - 0.5;
player.Vampire = tempVam < player.OriVampire ? player.OriVampire : tempVam;
});
}

public bool CanBeginToCharge(Character player)
{

@@ -42,9 +28,9 @@ namespace Gaming
{ });
}


public static bool BecomeInvisible(Character player)
{
if ((!player.Commandable())) return false;
IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.BecomeInvisible);
return ActiveSkillEffect(activeSkill, player, () =>
{
@@ -55,6 +41,14 @@ namespace Gaming
{ });
}

public static bool UseRobot(Character player)
{
if ((!player.Commandable()) || ((SummonGolem)player.FindIActiveSkill(ActiveSkillType.SummonGolem)).GolemSummoned == null) return false;
IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.UseRobot);
activeSkill.IsBeingUsed = (activeSkill.IsBeingUsed) ? false : true;
return true;
}

public bool JumpyBomb(Character player)
{
return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.JumpyBomb), player, () =>
@@ -68,6 +62,7 @@ namespace Gaming

public bool WriteAnswers(Character player)
{
if ((!player.Commandable())) return false;
IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.WriteAnswers);
return ActiveSkillEffect(activeSkill, player, () =>
{
@@ -83,6 +78,22 @@ namespace Gaming
{ });
}

public bool SummonGolem(Character player)
{
if ((!player.Commandable())) return false;
IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.SummonGolem);
if (((SummonGolem)activeSkill).GolemSummoned != null) return false;
XY res = player.Position + new XY(player.FacingDirection, player.Radius * 2);
if (actionManager.moveEngine.CheckCollision(player, res) != null)
return false;

return ActiveSkillEffect(activeSkill, player, () =>
{
characterManager.AddPlayer(res, player.TeamID, player.PlayerID + GameData.numOfPeople, CharacterType.Robot, player);
},
() =>
{ });
}

public static bool UseKnife(Character player)
{
@@ -97,6 +108,7 @@ namespace Gaming

public bool Howl(Character player)
{
if ((!player.Commandable())) return false;
return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.Howl), player, () =>
{
gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock();
@@ -125,6 +137,7 @@ namespace Gaming

public bool Punish(Character player)
{
if ((!player.Commandable())) return false;
return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.Punish), player, () =>
{
gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock();
@@ -153,18 +166,6 @@ namespace Gaming
{ });
}

public bool SuperFast(Character player)
{
return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.SuperFast), player, () =>
{
player.AddMoveSpeed(player.FindIActiveSkill(ActiveSkillType.SuperFast).DurationTime, 3.0);
Debugger.Output(player, "moves very fast!");
},
() =>
{ });
}


public static bool ActiveSkillEffect(IActiveSkill activeSkill, Character player, Action startSkill, Action endSkill)
{
lock (activeSkill.ActiveSkillLock)


+ 3
- 0
logic/Gaming/SkillManager/SkillManager.cs View File

@@ -37,6 +37,9 @@ namespace Gaming
case ActiveSkillType.WriteAnswers:
WriteAnswers(character);
break;
case ActiveSkillType.SummonGolem:
SummonGolem(character);
break;
default:
return false;
}


+ 1
- 1
logic/Preparation/Interface/ICharacter.cs View File

@@ -5,7 +5,7 @@ namespace Preparation.Interface
{
public interface ICharacter : IGameObj
{
public long TeamID { get; }
public int TeamID { get; }
public int HP { get; set; }
public int Score { get; }
public void AddScore(int add);


+ 1
- 1
logic/Preparation/Interface/IOccupation.cs View File

@@ -295,7 +295,7 @@ namespace Preparation.Interface

public BulletType InitBullet => BulletType.Null;

public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { });
public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { ActiveSkillType.SummonGolem, ActiveSkillType.UseRobot });
public List<PassiveSkillType> ListOfIPassiveSkill => new(new PassiveSkillType[] { });

public const int fixSpeed = (int)(GameData.basicFixSpeed * 1.1);


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

@@ -89,12 +89,13 @@ namespace Preparation.Utility
BecomeInvisible = 1,
BecomeVampire = 2,
JumpyBomb = 3,
SuperFast = 4,
SummonGolem = 4,
UseKnife = 5,
CanBeginToCharge = 6,
Punish = 7,
WriteAnswers = 8,
Howl = 9,
UseRobot = 10,
}
public enum PassiveSkillType
{


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

@@ -16,6 +16,9 @@ namespace Preparation.Utility

public const int MinSpeed = 1; // 最小速度
public const int MaxSpeed = int.MaxValue; // 最大速度

public const int numOfStudent = 4;
public const int numOfPeople = 5;
#endregion
#region 地图相关
public const int numOfPosGridPerCell = 1000; // 每格的【坐标单位】数
@@ -24,7 +27,7 @@ namespace Preparation.Utility
public const int cols = 50; // 列数

public const int numOfBirthPoint = 5;
public const int numOfGenerator = 9;
public const int numOfGenerator = 10;
public const int numOfChest = 8;

public static bool IsMap(GameObjType gameObjType)
@@ -77,7 +80,6 @@ namespace Preparation.Utility
}
#endregion
#region 角色相关
public const int numOfStudent = 4;
public const int characterRadius = numOfPosGridPerCell * 4 / 10; // 人物半径

public const int basicTreatSpeed = 100;


+ 1
- 1
logic/cmd/gameServer.cmd View File

@@ -12,6 +12,6 @@ start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --ch

ping -n 2 127.0.0.1 > NUL

start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 1 --type 1 --occupation 3
start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 1 --type 1 --occupation 5

ping -n 2 127.0.0.1 > NUL

+ 1
- 1
logic/规则Logic.md View File

@@ -208,7 +208,7 @@
- 在指令仍在进行时,重复发出同一类型的交互指令是无效的,你需要先发出Stop指令终止进行的指令

### 破译与逃脱
- 每张地图都会刷新 9台电机,求生者需要破译其中的7台,并开启任意一个大门后从任意一个开启的大门- 逃脱,亦或者在只剩1名求生者的情况下从紧急出口逃脱;
- 每张地图都有10台电机,求生者需要破译其中的7台,并开启任意一个大门后从任意一个开启的大门- 逃脱,亦或者在只剩1名求生者的情况下从紧急出口逃脱;
- 求生者和监管者在靠近电机时,可以看到电机的破译进度条。
- 紧急出口会在电机破译完成3台的情况下在地图的3-5个固定紧急出口刷新点之一随机刷新。
- 当求生者只剩1名时,紧急出口盖将会自动打开,该求生者可从紧急出口逃脱。


Loading…
Cancel
Save