Browse Source

feat: finish the fuction of OpenDoorWay

tags/0.1.0
shangfengh 2 years ago
parent
commit
a18e84144a
12 changed files with 168 additions and 41 deletions
  1. +1
    -0
      dependency/proto/MessageType.proto
  2. +3
    -3
      logic/GameClass/GameObj/Character/Character.Student.cs
  3. +1
    -1
      logic/GameClass/GameObj/Character/Character.cs
  4. +27
    -1
      logic/GameClass/GameObj/Map/Doorway.cs
  5. +21
    -0
      logic/GameClass/GameObj/Map/Map.cs
  6. +47
    -8
      logic/Gaming/ActionManager.cs
  7. +1
    -1
      logic/Gaming/AttackManager.cs
  8. +4
    -0
      logic/Preparation/Interface/IOccupation.cs
  9. +2
    -1
      logic/Preparation/Utility/EnumType.cs
  10. +8
    -6
      logic/Preparation/Utility/GameData.cs
  11. +42
    -15
      logic/Server/CopyInfo.cs
  12. +11
    -5
      logic/规则Logic.md

+ 1
- 0
dependency/proto/MessageType.proto View File

@@ -76,6 +76,7 @@ enum PlayerState
CLIMBING = 15; // 翻窗 CLIMBING = 15; // 翻窗
OPENING_A_CHEST =16; OPENING_A_CHEST =16;
USING_SPECIAL_SKILL = 17; USING_SPECIAL_SKILL = 17;
OPENING_A_GATE =18;
} }


enum TrickerBuffType // 屠夫可用的增益效果类型 enum TrickerBuffType // 屠夫可用的增益效果类型


+ 3
- 3
logic/GameClass/GameObj/Character/Character.Student.cs View File

@@ -23,7 +23,7 @@ namespace GameClass.GameObj
/// <summary> /// <summary>
/// 原初修理电机速度 /// 原初修理电机速度
/// </summary> /// </summary>
public int OrgFixSpeed { get; protected set; } = GameData.basicFixSpeed;
public int OrgFixSpeed { get; protected set; }


protected int treatSpeed = GameData.basicTreatSpeed; protected int treatSpeed = GameData.basicTreatSpeed;
public int TreatSpeed public int TreatSpeed
@@ -37,7 +37,7 @@ namespace GameClass.GameObj
} }
} }
} }
public int OrgTreatSpeed { get; protected set; } = GameData.basicTreatSpeed;
public int OrgTreatSpeed { get; protected set; }


public int MaxGamingAddiction { get; protected set; } public int MaxGamingAddiction { get; protected set; }
private int gamingAddiction; private int gamingAddiction;
@@ -98,7 +98,7 @@ namespace GameClass.GameObj


public Student(XY initPos, int initRadius, CharacterType characterType) : base(initPos, initRadius, characterType) public Student(XY initPos, int initRadius, CharacterType characterType) : base(initPos, initRadius, characterType)
{ {
this.fixSpeed = ((IStudent)Occupation).FixSpeed;
this.OrgFixSpeed = this.fixSpeed = ((IStudent)Occupation).FixSpeed;
} }
} }
} }

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

@@ -76,7 +76,7 @@ namespace GameClass.GameObj
&& playerState != PlayerStateType.IsAddicted && playerState != PlayerStateType.IsStunned && playerState != PlayerStateType.IsAddicted && playerState != PlayerStateType.IsStunned
&& playerState != PlayerStateType.IsSwinging && playerState != PlayerStateType.IsTryingToAttack && playerState != PlayerStateType.IsSwinging && playerState != PlayerStateType.IsTryingToAttack
&& playerState != PlayerStateType.IsClimbingThroughWindows); && playerState != PlayerStateType.IsClimbingThroughWindows);
public bool InteractingWithMapWithoutMoving() => (playerState == PlayerStateType.IsLockingTheDoor || playerState == PlayerStateType.IsFixing || playerState == PlayerStateType.IsOpeningTheChest);
public bool InteractingWithMapWithoutMoving() => (playerState == PlayerStateType.IsLockingOrOpeningTheDoor || playerState == PlayerStateType.IsFixing || playerState == PlayerStateType.IsOpeningTheChest);
public bool NullOrMoving() => (playerState == PlayerStateType.Null || playerState == PlayerStateType.IsMoving); public bool NullOrMoving() => (playerState == PlayerStateType.Null || playerState == PlayerStateType.IsMoving);


// private int deathCount = 0; // private int deathCount = 0;


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

@@ -36,6 +36,32 @@ namespace GameClass.GameObj
} }
} }


public bool IsOpen => powerSupply;
private bool isOpening = false;
public bool IsOpening
{
get => isOpening;
set
{
lock (gameObjLock)
isOpening = value;
}
}

private int openDegree = 0;
public int OpenDegree
{
get => openDegree;
set
{
if (value > 0)
lock (gameObjLock)
openDegree = (value < GameData.degreeOfOpenedDoorway) ? value : GameData.degreeOfOpenedDoorway;
else
lock (gameObjLock)
openDegree = 0;
}
}

public bool IsOpen() => (OpenDegree == GameData.degreeOfOpenedDoorway);
} }
} }

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

@@ -120,6 +120,27 @@ namespace GameClass.GameObj
GameObjLockDict[gameObj.Type].ExitWriteLock(); GameObjLockDict[gameObj.Type].ExitWriteLock();
} }
} }
public GameObj? OneForInteract(XY Pos, GameObjType gameObjType)
{
GameObj? GameObjForInteract = null;
GameObjLockDict[gameObjType].EnterReadLock();
try
{
foreach (GameObj gameObj in GameObjDict[gameObjType])
{
if (GameData.ApproachToInteract(gameObj.Position, Pos))
{
GameObjForInteract = gameObj;
break;
}
}
}
finally
{
GameObjLockDict[gameObjType].ExitReadLock();
}
return GameObjForInteract;
}
public Map(uint[,] mapResource) public Map(uint[,] mapResource)
{ {
gameObjDict = new Dictionary<GameObjType, IList<IGameObj>>(); gameObjDict = new Dictionary<GameObjType, IList<IGameObj>>();


+ 47
- 8
logic/Gaming/ActionManager.cs View File

@@ -15,7 +15,7 @@ namespace Gaming
// 人物移动 // 人物移动
public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection)
{ {
if (playerToMove.PlayerState != PlayerStateType.Null) return false;
if (!playerToMove.Commandable()) return false;
moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection);
return true; return true;
} }
@@ -32,7 +32,7 @@ namespace Gaming


public bool Fix(Student player)// 自动检查有无发电机可修 public bool Fix(Student player)// 自动检查有无发电机可修
{ {
if (player.IsGhost() || (player.PlayerState != PlayerStateType.Null && player.PlayerState != PlayerStateType.IsMoving))
if (player.IsGhost() || (!player.Commandable()) || player.PlayerState == PlayerStateType.IsFixing)
return false; return false;
Generator? generatorForFix = null; Generator? generatorForFix = null;


@@ -108,6 +108,45 @@ namespace Gaming
} }
} }


)
{ IsBackground = true }.Start();

return true;
}

public bool OpenDoorWay(Student player)
{
if (!(player.Commandable()) || player.PlayerState == PlayerStateType.IsOpeningTheDoorWay)
return false;
Doorway? doorwayToOpen = (Doorway?)gameMap.OneForInteract(player.Position, GameObjType.Doorway);
if (doorwayToOpen == null || doorwayToOpen.IsOpening || !doorwayToOpen.PowerSupply)
return false;

player.PlayerState = PlayerStateType.IsOpeningTheDoorWay;
doorwayToOpen.IsOpening = true;
new Thread
(
() =>
{
new FrameRateTaskExecutor<int>(
loopCondition: () => doorwayToOpen.IsOpening && player.PlayerState == PlayerStateType.IsOpeningTheDoorWay && gameMap.Timer.IsGaming && doorwayToOpen.OpenDegree < GameData.degreeOfOpenedDoorway && GameData.ApproachToInteract(player.Position, doorwayToOpen.Position),
loopToDo: () =>
{
doorwayToOpen.OpenDegree += GameData.frameDuration;
},
timeInterval: GameData.frameDuration,
finallyReturn: () => 0
)

.Start();
doorwayToOpen.IsOpening = false;
if (doorwayToOpen.OpenDegree >= GameData.degreeOfOpenedDoorway)
{
if (player.PlayerState == PlayerStateType.IsOpeningTheDoorWay)
player.PlayerState = PlayerStateType.Null;
}
}

) )
{ IsBackground = true }.Start(); { IsBackground = true }.Start();


@@ -116,7 +155,7 @@ namespace Gaming


public bool Escape(Student player) public bool Escape(Student player)
{ {
if (!(player.PlayerState == PlayerStateType.Null || player.PlayerState == PlayerStateType.IsMoving) || player.IsGhost())
if (!(player.Commandable()))
return false; return false;
Doorway? doorwayForEscape = null; Doorway? doorwayForEscape = null;


@@ -149,9 +188,9 @@ namespace Gaming


public bool Treat(Student player, Student playerTreated) public bool Treat(Student player, Student playerTreated)
{ {
if (!((playerTreated.NullOrMoving() || playerTreated.InteractingWithMapWithoutMoving())
&& (player.NullOrMoving() || player.InteractingWithMapWithoutMoving()))
|| playerTreated.HP == playerTreated.MaxHp || !GameData.ApproachToInteract(playerTreated.Position, player.Position))
if ((!player.Commandable()) || player.PlayerState == PlayerStateType.IsTreating ||
(!playerTreated.Commandable()) ||
playerTreated.HP == playerTreated.MaxHp || !GameData.ApproachToInteract(playerTreated.Position, player.Position))
return false; return false;


if (playerTreated.HP + playerTreated.DegreeOfTreatment >= playerTreated.MaxHp) if (playerTreated.HP + playerTreated.DegreeOfTreatment >= playerTreated.MaxHp)
@@ -205,7 +244,7 @@ namespace Gaming


public bool Rescue(Student player, Student playerRescued) public bool Rescue(Student player, Student playerRescued)
{ {
if (player.PlayerState != PlayerStateType.Null || playerRescued.PlayerState != PlayerStateType.IsAddicted || !GameData.ApproachToInteract(playerRescued.Position, player.Position))
if ((!player.Commandable()) || playerRescued.PlayerState != PlayerStateType.IsAddicted || !GameData.ApproachToInteract(playerRescued.Position, player.Position))
return false; return false;
player.PlayerState = PlayerStateType.IsRescuing; player.PlayerState = PlayerStateType.IsRescuing;
playerRescued.PlayerState = PlayerStateType.IsRescued; playerRescued.PlayerState = PlayerStateType.IsRescued;
@@ -247,7 +286,7 @@ namespace Gaming


public bool OpenChest(Character player) public bool OpenChest(Character player)
{ {
if (!player.Commandable() || player.PlayerState == PlayerStateType.IsOpeningTheChest)
if ((!player.Commandable()) || player.PlayerState == PlayerStateType.IsOpeningTheChest)
return false; return false;
Chest? chestToOpen = null; Chest? chestToOpen = null;




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

@@ -313,7 +313,7 @@ namespace Gaming
return false; return false;
} }


if (player.PlayerState != PlayerStateType.Null || player.PlayerState != PlayerStateType.IsMoving)
if (!player.Commandable())
return false; return false;


XY res = new XY // 子弹紧贴人物生成。 XY res = new XY // 子弹紧贴人物生成。


+ 4
- 0
logic/Preparation/Interface/IOccupation.cs View File

@@ -26,6 +26,7 @@ namespace Preparation.Interface
public interface IStudent : IOccupation public interface IStudent : IOccupation
{ {
public int FixSpeed { get; } public int FixSpeed { get; }
public int TreatSpeed { get; }
} }


public class Assassin : IGhost public class Assassin : IGhost
@@ -84,6 +85,9 @@ namespace Preparation.Interface
public const int fixSpeed = GameData.basicFixSpeed / 10 * 6; public const int fixSpeed = GameData.basicFixSpeed / 10 * 6;
public int FixSpeed => fixSpeed; public int FixSpeed => fixSpeed;


public const int treatSpeed = GameData.basicTreatSpeed / 10 * 8;
public int TreatSpeed => treatSpeed;

public const double concealment = GameData.basicConcealment * 0.9; public const double concealment = GameData.basicConcealment * 0.9;
public double Concealment => concealment; public double Concealment => concealment;




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

@@ -19,10 +19,11 @@ namespace Preparation.Utility
IsRescued = 10, IsRescued = 10,
IsStunned = 11, IsStunned = 11,
IsTryingToAttack = 12,//指前摇 IsTryingToAttack = 12,//指前摇
IsLockingTheDoor = 13,
IsLockingOrOpeningTheDoor = 13,
IsOpeningTheChest = 14, IsOpeningTheChest = 14,
IsClimbingThroughWindows = 15, IsClimbingThroughWindows = 15,
IsUsingSpecialSkill = 16, IsUsingSpecialSkill = 16,
IsOpeningTheDoorWay = 17,
} }
public enum GameObjType public enum GameObjType
{ {


+ 8
- 6
logic/Preparation/Utility/GameData.cs View File

@@ -4,18 +4,20 @@ namespace Preparation.Utility
{ {
public static class GameData public static class GameData
{ {
#region 基本常数与常方法
public const int numOfPosGridPerCell = 1000; // 每格的【坐标单位】数
#region 基本常数
public const int numOfStepPerSecond = 20; // 每秒行走的步数 public const int numOfStepPerSecond = 20; // 每秒行走的步数
public const int frameDuration = 50; // 每帧时长 public const int frameDuration = 50; // 每帧时长


public const int lengthOfMap = 50000; // 地图长度
public const int rows = 50; // 行数
public const int cols = 50; // 列数
public const long gameDuration = 600000; // 游戏时长600000ms = 10min public const long gameDuration = 600000; // 游戏时长600000ms = 10min


public const int MinSpeed = 1; // 最小速度 public const int MinSpeed = 1; // 最小速度
public const int MaxSpeed = int.MaxValue; // 最大速度 public const int MaxSpeed = int.MaxValue; // 最大速度
#endregion
#region 地图相关
public const int numOfPosGridPerCell = 1000; // 每格的【坐标单位】数
public const int lengthOfMap = 50000; // 地图长度
public const int rows = 50; // 行数
public const int cols = 50; // 列数


public const int numOfBirthPoint = 5; public const int numOfBirthPoint = 5;
// public const int numOfGenerator = 9; // public const int numOfGenerator = 9;
@@ -49,7 +51,6 @@ namespace Preparation.Utility
{ {
return Math.Abs(PosGridToCellX(pos1) - PosGridToCellX(pos2)) <= 1 && Math.Abs(PosGridToCellY(pos1) - PosGridToCellY(pos2)) <= 1; return Math.Abs(PosGridToCellX(pos1) - PosGridToCellX(pos2)) <= 1 && Math.Abs(PosGridToCellY(pos1) - PosGridToCellY(pos2)) <= 1;
} }

#endregion #endregion
#region 角色相关 #region 角色相关
public const int characterRadius = numOfPosGridPerCell / 2 / 5 * 4; // 人物半径 public const int characterRadius = numOfPosGridPerCell / 2 / 5 * 4; // 人物半径
@@ -128,6 +129,7 @@ namespace Preparation.Utility
#endregion #endregion
#region 物体相关 #region 物体相关
public const int degreeOfFixedGenerator = 10300000; public const int degreeOfFixedGenerator = 10300000;
public const int degreeOfOpenedDoorway = 18000;
public const int maxNumOfPropInChest = 2; public const int maxNumOfPropInChest = 2;
#endregion #endregion
#region 游戏帧相关 #region 游戏帧相关


+ 42
- 15
logic/Server/CopyInfo.cs View File

@@ -71,7 +71,7 @@ namespace Server
return PlayerState.Graduated; return PlayerState.Graduated;
case Preparation.Utility.PlayerStateType.IsFixing: case Preparation.Utility.PlayerStateType.IsFixing:
return PlayerState.Learning; return PlayerState.Learning;
case Preparation.Utility.PlayerStateType.IsLockingTheDoor:
case Preparation.Utility.PlayerStateType.IsLockingOrOpeningTheDoor:
return PlayerState.Locking; return PlayerState.Locking;
case Preparation.Utility.PlayerStateType.IsOpeningTheChest: case Preparation.Utility.PlayerStateType.IsOpeningTheChest:
return PlayerState.OpeningAChest; return PlayerState.OpeningAChest;
@@ -91,6 +91,8 @@ namespace Server
return PlayerState.Attacking; return PlayerState.Attacking;
case Preparation.Utility.PlayerStateType.IsUsingSpecialSkill: case Preparation.Utility.PlayerStateType.IsUsingSpecialSkill:
return PlayerState.UsingSpecialSkill; return PlayerState.UsingSpecialSkill;
case Preparation.Utility.PlayerStateType.IsOpeningTheDoorWay:
return PlayerState.OpeningAGate;
default: default:
return PlayerState.NullStatus; return PlayerState.NullStatus;
} }
@@ -149,22 +151,27 @@ namespace Server


public static MessageOfObj? Auto(GameObj gameObj) public static MessageOfObj? Auto(GameObj gameObj)
{ {
if (gameObj.Type == Preparation.Utility.GameObjType.Character)
switch (gameObj.Type)
{ {
Character character = (Character)gameObj;
if (character.IsGhost())
return Tricker((Ghost)character);
else return Student((Student)character);
case Preparation.Utility.GameObjType.Character:
Character character = (Character)gameObj;
if (character.IsGhost())
return Tricker((Ghost)character);
else return Student((Student)character);
case Preparation.Utility.GameObjType.Bullet:
return Bullet((Bullet)gameObj);
case Preparation.Utility.GameObjType.Prop:
return Prop((Prop)gameObj);
case Preparation.Utility.GameObjType.BombedBullet:
return BombedBullet((BombedBullet)gameObj);
case Preparation.Utility.GameObjType.PickedProp:
return PickedProp((PickedProp)gameObj);
case Preparation.Utility.GameObjType.Generator:
return Classroom((Generator)gameObj);
// case Preparation.Utility.GameObjType.Chest:

default: return null;
} }
else if (gameObj.Type == Preparation.Utility.GameObjType.Bullet)
return Bullet((Bullet)gameObj);
else if (gameObj.Type == Preparation.Utility.GameObjType.Prop)
return Prop((Prop)gameObj);
else if (gameObj.Type == Preparation.Utility.GameObjType.BombedBullet)
return BombedBullet((BombedBullet)gameObj);
else if (gameObj.Type == Preparation.Utility.GameObjType.PickedProp)
return PickedProp((PickedProp)gameObj);
else return null; //先写着防报错
} }
public static MessageOfObj? Auto(MessageOfNews news) public static MessageOfObj? Auto(MessageOfNews news)
{ {
@@ -304,5 +311,25 @@ namespace Server
msg.MessageOfPickedProp.FacingDirection = pickedProp.PropHasPicked.FacingDirection;*/ msg.MessageOfPickedProp.FacingDirection = pickedProp.PropHasPicked.FacingDirection;*/
return msg; return msg;
} }

private static MessageOfObj Classroom(Generator generator)
{
MessageOfObj msg = new MessageOfObj();
msg.ClassroomMessage = new();
msg.ClassroomMessage.X = generator.Position.x;
msg.ClassroomMessage.Y = generator.Position.y;
msg.ClassroomMessage.Progress = generator.DegreeOfFRepair;
return msg;
}

/* private static MessageOfObj Chest(Chest chest)
{
MessageOfObj msg = new MessageOfObj();
msg.ChestMessage = new();
msg.ChestMessage.X=chest.Position.x;
msg.ChestMessage.Y=chest.Position.y;
// msg.ChestMessage.Progress=generator.DegreeOfFRepair;
return msg;
}*/
} }
} }

+ 11
- 5
logic/规则Logic.md View File

@@ -171,7 +171,7 @@


### 出口:物体 ### 出口:物体
- *电力供应(是否可以被打开)* - *电力供应(是否可以被打开)*
- *是否打开*
- *开启进度*


### 紧急出口:物体 ### 紧急出口:物体
- *是否显现* - *是否显现*
@@ -180,15 +180,18 @@
### 墙:物体 ### 墙:物体


### 窗:物体 ### 窗:物体
- *正在翻窗的人*
- *是否正在翻窗*
- *窗的朝向* - *窗的朝向*


### 箱子:物体 ### 箱子:物体
- *是否开启* - *是否开启*
- *开箱进度*
- *是否正在被开启*


### 门:物体 ### 门:物体
- *属于那个教学区* - *属于那个教学区*
- *是否锁上* - *是否锁上*
- *是否正在被锁*
- 不提供是否可以锁上的属性 - 不提供是否可以锁上的属性


### 电机(建议称为homework):物体 ### 电机(建议称为homework):物体
@@ -202,13 +205,15 @@


### 交互 ### 交互
- *除了逃离,交互目标与交互者在一个九宫格内则为可交互* - *除了逃离,交互目标与交互者在一个九宫格内则为可交互*
- *在指令仍在进行时,重复发出同一类型的交互指令是无效的,你需要先发出Stop指令终止进行的指令*


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


### 攻击 ### 攻击
- 每次求生者收到攻击后会损失对应子弹的攻击力的血量 - 每次求生者收到攻击后会损失对应子弹的攻击力的血量
@@ -229,7 +234,8 @@
- *监管者或求生者都需要拿到对应教学区的钥匙才能打开或锁住对应的门* - *监管者或求生者都需要拿到对应教学区的钥匙才能打开或锁住对应的门*
- *锁门和开门都需要一定时间,进出门为正常移动过程* - *锁门和开门都需要一定时间,进出门为正常移动过程*
- *门只有开、锁两种状态,锁住时门有碰撞体积* - *门只有开、锁两种状态,锁住时门有碰撞体积*
- *当门所在格子内有人时,无法锁门*
- *当门所在格子内有人时,无法锁门(必须从门外锁门)*
- *锁门时其他人可以进入门所在格子,锁门过程中断*
- *钥匙只会出现在箱子中,每个教学区都有2把钥匙* - *钥匙只会出现在箱子中,每个教学区都有2把钥匙*


### 窗 ### 窗
@@ -269,7 +275,7 @@
- 可行动的求生者可以对受伤的其他求生者进行治疗,治疗完成后会回复被治疗程度的血量。 - 可行动的求生者可以对受伤的其他求生者进行治疗,治疗完成后会回复被治疗程度的血量。
- 治疗时每毫秒增加相当于治疗者治疗速度的被治疗程度 - 治疗时每毫秒增加相当于治疗者治疗速度的被治疗程度
- 当达到被治疗程度达到1500000或者最大血量与当前血量的差值时,治疗结束。 - 当达到被治疗程度达到1500000或者最大血量与当前血量的差值时,治疗结束。
- 治疗他人未完成时重新发出治疗指令是无效的(无论是否要求治疗同一人)
- 治疗中断时,被治疗程度保留;被治疗者遭到攻击时被治疗程度清空 - 治疗中断时,被治疗程度保留;被治疗者遭到攻击时被治疗程度清空


### 沉迷 ### 沉迷


Loading…
Cancel
Save