Browse Source

Merge pull request #147 from shangfengh/new

chore: rebuild the skill system again
tags/0.1.0
TCL GitHub 2 years ago
parent
commit
49561b87a4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 716 additions and 492 deletions
  1. +30
    -1
      logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs
  2. +28
    -3
      logic/GameClass/GameObj/Bullet/Bullet.Student.cs
  3. +1
    -0
      logic/GameClass/GameObj/Bullet/Bullet.cs
  4. +16
    -0
      logic/GameClass/GameObj/Character/Character.Skill.cs
  5. +39
    -7
      logic/GameClass/GameObj/Character/Character.cs
  6. +98
    -0
      logic/GameClass/GameObj/Character/Skill.cs
  7. +34
    -0
      logic/GameClass/GameObj/Map/Chest.cs
  8. +16
    -37
      logic/GameClass/GameObj/Map/Map.cs
  9. +2
    -1
      logic/GameClass/GameObj/Prop.cs
  10. +4
    -4
      logic/GameEngine/MoveEngine.cs
  11. +12
    -14
      logic/Gaming/ActionManager.cs
  12. +79
    -65
      logic/Gaming/AttackManager.cs
  13. +4
    -12
      logic/Gaming/Game.cs
  14. +32
    -75
      logic/Gaming/PropManager.cs
  15. +0
    -238
      logic/Gaming/SkillManager/ActiveSkill.cs
  16. +193
    -0
      logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs
  17. +0
    -0
      logic/Gaming/SkillManager/SkillManager.PassiveSkill.cs
  18. +14
    -1
      logic/Gaming/SkillManager/SkillManager.cs
  19. +10
    -2
      logic/Preparation/Interface/IOccupation.cs
  20. +16
    -0
      logic/Preparation/Interface/ISkill.cs
  21. +6
    -3
      logic/Preparation/Utility/EnumType.cs
  22. +37
    -21
      logic/Preparation/Utility/GameData.cs
  23. +5
    -0
      logic/Preparation/Utility/XY.cs
  24. +40
    -8
      logic/规则Logic.md

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

@@ -22,7 +22,11 @@ namespace GameClass.GameObj
{ {
return false; return false;
} }

public override bool CanBeBombed(GameObjType gameObjType)
{
if (gameObjType == GameObjType.Character) return true;
return false;
}
public override BulletType TypeOfBullet => BulletType.CommonAttackOfGhost; public override BulletType TypeOfBullet => BulletType.CommonAttackOfGhost;


} }
@@ -44,6 +48,11 @@ namespace GameClass.GameObj
{ {
return false; return false;
} }
public override bool CanBeBombed(GameObjType gameObjType)
{
if (gameObjType == GameObjType.Character) return true;
return false;
}


public override BulletType TypeOfBullet => BulletType.FlyingKnife; public override BulletType TypeOfBullet => BulletType.FlyingKnife;


@@ -68,6 +77,11 @@ namespace GameClass.GameObj
// 圆形攻击范围 // 圆形攻击范围
return XY.Distance(this.Position, target.Position) <= BulletBombRange; return XY.Distance(this.Position, target.Position) <= BulletBombRange;
} }
public override bool CanBeBombed(GameObjType gameObjType)
{
if (gameObjType == GameObjType.Character) return true;
return false;
}


public override BulletType TypeOfBullet => BulletType.AtomBomb; public override BulletType TypeOfBullet => BulletType.AtomBomb;


@@ -92,6 +106,11 @@ namespace GameClass.GameObj
// 圆形攻击范围 // 圆形攻击范围
return XY.Distance(this.Position, target.Position) <= BulletBombRange; return XY.Distance(this.Position, target.Position) <= BulletBombRange;
} }
public override bool CanBeBombed(GameObjType gameObjType)
{
if (gameObjType == GameObjType.Character) return true;
return false;
}


public override BulletType TypeOfBullet => BulletType.OrdinaryBullet; public override BulletType TypeOfBullet => BulletType.OrdinaryBullet;
} }
@@ -116,6 +135,11 @@ namespace GameClass.GameObj
// 圆形攻击范围 // 圆形攻击范围
return XY.Distance(this.Position, target.Position) <= BulletBombRange; return XY.Distance(this.Position, target.Position) <= BulletBombRange;
} }
public override bool CanBeBombed(GameObjType gameObjType)
{
if (gameObjType == GameObjType.Character) return true;
return false;
}


public override BulletType TypeOfBullet => BulletType.FastBullet; public override BulletType TypeOfBullet => BulletType.FastBullet;
} }
@@ -135,6 +159,11 @@ namespace GameClass.GameObj
public override int RecoveryFromHit => GameData.basicRecoveryFromHit; public override int RecoveryFromHit => GameData.basicRecoveryFromHit;
public override bool IsToBomb => true; public override bool IsToBomb => true;


public override bool CanBeBombed(GameObjType gameObjType)
{
if (gameObjType == GameObjType.Character) return true;
return false;
}
public override bool CanAttack(GameObj target) public override bool CanAttack(GameObj target)
{ {
double FacingAngle = Math.Atan2(this.FacingDirection.y, this.FacingDirection.x); double FacingAngle = Math.Atan2(this.FacingDirection.y, this.FacingDirection.x);


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

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


namespace GameClass.GameObj namespace GameClass.GameObj
{ {
/* internal sealed class Ram : Bullet
{
public Ram(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) :
base(player, radius, placeType, pos)
{
}
public override double BulletBombRange => 0;
public override double BulletAttackRange => 0;
public override int AP => 7220;
public override int Speed => 0;
public override bool IsToBomb => false;
public override int CastTime => 0;
public override int Backswing => 0;
public override int RecoveryFromHit => 0;
public override bool CanAttack(GameObj target)
{
return false;
}
public override bool CanBeBombed(GameObjType gameObjType)
{
// if (gameObjType == GameObjType.Character) return true;
return false;
}

public override BulletType TypeOfBullet => BulletType.Ram;

}*/
} }

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

@@ -30,6 +30,7 @@ namespace GameClass.GameObj
/// <param name="target">被尝试攻击者</param> /// <param name="target">被尝试攻击者</param>
/// <returns>是否可以攻击到</returns> /// <returns>是否可以攻击到</returns>
public abstract bool CanAttack(GameObj target); public abstract bool CanAttack(GameObj target);
public abstract bool CanBeBombed(GameObjType gameObjType);


protected override bool IgnoreCollideExecutor(IGameObj targetObj) protected override bool IgnoreCollideExecutor(IGameObj targetObj)
{ {


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

@@ -12,9 +12,22 @@ namespace GameClass.GameObj
private readonly IOccupation occupation; private readonly IOccupation occupation;
public IOccupation Occupation => occupation; public IOccupation Occupation => occupation;



private Dictionary<ActiveSkillType, int> timeUntilActiveSkillAvailable = new(); private Dictionary<ActiveSkillType, int> timeUntilActiveSkillAvailable = new();
public Dictionary<ActiveSkillType, int> TimeUntilActiveSkillAvailable => timeUntilActiveSkillAvailable; public Dictionary<ActiveSkillType, int> TimeUntilActiveSkillAvailable => timeUntilActiveSkillAvailable;


private Dictionary<ActiveSkillType, IActiveSkill> iActiveSkillDictionary = new();
public Dictionary<ActiveSkillType, IActiveSkill> IActiveSkillDictionary => iActiveSkillDictionary;

public IActiveSkill? UseIActiveSkill(ActiveSkillType activeSkillType)
{
if (Occupation.ListOfIActiveSkill.Contains(activeSkillType))
{
return IActiveSkillDictionary[activeSkillType];
}
return null;
}

public bool SetTimeUntilActiveSkillAvailable(ActiveSkillType activeSkillType, int timeUntilActiveSkillAvailable) public bool SetTimeUntilActiveSkillAvailable(ActiveSkillType activeSkillType, int timeUntilActiveSkillAvailable)
{ {
if (TimeUntilActiveSkillAvailable.ContainsKey(activeSkillType)) if (TimeUntilActiveSkillAvailable.ContainsKey(activeSkillType))
@@ -73,10 +86,13 @@ namespace GameClass.GameObj
this.concealment = Occupation.Concealment; this.concealment = Occupation.Concealment;
this.alertnessRadius = Occupation.AlertnessRadius; this.alertnessRadius = Occupation.AlertnessRadius;
this.characterType = characterType; this.characterType = characterType;
this.TimeOfOpeningOrLocking = Occupation.TimeOfOpeningOrLocking;
this.TimeOfClimbingThroughWindows = Occupation.TimeOfClimbingThroughWindows;


foreach (var activeSkill in this.Occupation.ListOfIActiveSkill) foreach (var activeSkill in this.Occupation.ListOfIActiveSkill)
{ {
this.TimeUntilActiveSkillAvailable.Add(activeSkill, 0); this.TimeUntilActiveSkillAvailable.Add(activeSkill, 0);
this.IActiveSkillDictionary.Add(activeSkill, SkillFactory.FindIActiveSkill(activeSkill));
} }


// UsePassiveSkill(); //创建player时开始被动技能,这一过程也可以放到gamestart时进行 // UsePassiveSkill(); //创建player时开始被动技能,这一过程也可以放到gamestart时进行


+ 39
- 7
logic/GameClass/GameObj/Character/Character.cs View File

@@ -72,6 +72,13 @@ namespace GameClass.GameObj
} }
} }


public bool Commandable() => (playerState != PlayerStateType.IsDeceased && playerState != PlayerStateType.IsEscaped
&& playerState != PlayerStateType.IsAddicted && playerState != PlayerStateType.IsStunned
&& playerState != PlayerStateType.IsSwinging && playerState != PlayerStateType.IsTryingToAttack
&& playerState != PlayerStateType.IsClimbingThroughWindows);
public bool InteractingWithMapWithoutMoving() => (playerState == PlayerStateType.IsLockingTheDoor || playerState == PlayerStateType.IsFixing || playerState == PlayerStateType.IsOpeningTheChest);
public bool NullOrMoving() => (playerState == PlayerStateType.Null || playerState == PlayerStateType.IsMoving);

// private int deathCount = 0; // private int deathCount = 0;
// public int DeathCount => deathCount; // 玩家的死亡次数 // public int DeathCount => deathCount; // 玩家的死亡次数


@@ -130,8 +137,8 @@ namespace GameClass.GameObj
} }
} }


private Prop? propInventory;
public Prop? PropInventory // 持有的道具
private Prop[] propInventory = new Prop[GameData.maxNumOfPropInPropInventory];
public Prop[] PropInventory
{ {
get => propInventory; get => propInventory;
set set
@@ -148,16 +155,30 @@ namespace GameClass.GameObj
/// 使用物品栏中的道具 /// 使用物品栏中的道具
/// </summary> /// </summary>
/// <returns>被使用的道具</returns> /// <returns>被使用的道具</returns>
public Prop? UseProp()
public Prop? UseProp(int indexing)
{ {
if (indexing < 0 || indexing >= GameData.maxNumOfPropInPropInventory)
return null;
lock (gameObjLock) lock (gameObjLock)
{ {
var oldProp = PropInventory;
PropInventory = null;
return oldProp;
Prop prop = propInventory[indexing];
PropInventory[indexing] = null;
return prop;
} }
} }


/// <summary>
/// 如果indexing==GameData.maxNumOfPropInPropInventory表明道具栏为满
/// </summary>
public int IndexingOfAddProp()
{
int indexing = 0;
for (; indexing < GameData.maxNumOfPropInPropInventory; ++indexing)
if (PropInventory[indexing] == null)
break;
return indexing;
}

/// <summary> /// <summary>
/// 是否在隐身 /// 是否在隐身
/// </summary> /// </summary>
@@ -226,7 +247,18 @@ namespace GameClass.GameObj
} }
} }



private int timeOfClimbingThroughWindows;
public int TimeOfClimbingThroughWindows
{
get => timeOfClimbingThroughWindows;
set
{
lock (gameObjLock)
{
timeOfClimbingThroughWindows = value;
}
}
}


/// <summary> /// <summary>
/// 进行一次攻击 /// 进行一次攻击


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

@@ -0,0 +1,98 @@
using Preparation.Interface;
using Preparation.Utility;

namespace GameClass.GameObj
{
public class BecomeVampire : IActiveSkill // 化身吸血鬼
{
public int SkillCD => GameData.commonSkillCD / 3 * 4;
public int DurationTime => GameData.commonSkillTime;

private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;

public bool IsBeingUsed { get; set; }
}
public class CanBeginToCharge : IActiveSkill
{
public int SkillCD => GameData.commonSkillCD / 5;
public int DurationTime => GameData.commonSkillTime / 10 * 6;

private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;

public bool IsBeingUsed { get; set; }
}

public class BecomeInvisible : IActiveSkill
{
public int SkillCD => GameData.commonSkillCD;
public int DurationTime => GameData.commonSkillTime / 10 * 6;

private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;

public bool IsBeingUsed { get; set; }
}

public class NuclearWeapon : IActiveSkill // 核武器
{
public int SkillCD => GameData.commonSkillCD / 3 * 7;
public int DurationTime => GameData.commonSkillTime / 10;
private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;

public bool IsBeingUsed { get; set; }
}

public class UseKnife : IActiveSkill
{
public int SkillCD => GameData.commonSkillCD / 3 * 2;
public int DurationTime => GameData.commonSkillTime / 10;
private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;

public bool IsBeingUsed { get; set; }
}

public class SuperFast : IActiveSkill // 3倍速
{
public int SkillCD => GameData.commonSkillCD;
public int DurationTime => GameData.commonSkillTime / 10 * 4;
private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;

public bool IsBeingUsed { get; set; }
}

public static class SkillFactory
{
public static IActiveSkill? FindIActiveSkill(ActiveSkillType activeSkillType)
{
switch (activeSkillType)
{
case ActiveSkillType.BecomeInvisible:
return new BecomeInvisible();
case ActiveSkillType.UseKnife:
return new UseKnife();
default:
return null;
}
}

public static ActiveSkillType FindActiveSkillType(IActiveSkill ActiveSkill)
{
switch (ActiveSkill)
{
case BecomeInvisible:
return ActiveSkillType.BecomeInvisible;
case UseKnife:
return ActiveSkillType.UseKnife;
case CanBeginToCharge:
return ActiveSkillType.CanBeginToCharge;
default:
return ActiveSkillType.Null;
}
}
}
}

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

@@ -0,0 +1,34 @@
using Preparation.Utility;
using System.Collections.Generic;

namespace GameClass.GameObj
{
/// <summary>
/// 箱子
/// </summary>
public class Chest : GameObj
{
public Chest(XY initPos) :
base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Chest)
{
this.place = PlaceType.Chest;
this.CanMove = false;
}
public override bool IsRigid => true;
public override ShapeType Shape => ShapeType.Square;

private Prop[] propInChest = new Prop[GameData.maxNumOfPropInChest];
public Prop[] PropInChest => propInChest;

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

+ 16
- 37
logic/GameClass/GameObj/Map/Map.cs View File

@@ -96,6 +96,18 @@ namespace GameClass.GameObj
} }
return flag; return flag;
} }
public bool RemoveJustFromMap(GameObj gameObj)
{
GameObjLockDict[gameObj.Type].EnterWriteLock();
try
{
return GameObjDict[gameObj.Type].Remove(gameObj);
}
finally
{
GameObjLockDict[gameObj.Type].ExitWriteLock();
}
}
public void Add(GameObj gameObj) public void Add(GameObj gameObj)
{ {
GameObjLockDict[gameObj.Type].EnterWriteLock(); GameObjLockDict[gameObj.Type].EnterWriteLock();
@@ -134,55 +146,22 @@ namespace GameClass.GameObj
{ {
case (uint)PlaceType.Wall: case (uint)PlaceType.Wall:
{ {
GameObjLockDict[GameObjType.Wall].EnterWriteLock();
try
{
GameObjDict[GameObjType.Wall].Add(new Wall(GameData.GetCellCenterPos(i, j)));
}
finally
{
GameObjLockDict[GameObjType.Wall].ExitWriteLock();
}
Add(new Wall(GameData.GetCellCenterPos(i, j)));
break; break;
} }
case (uint)PlaceType.Doorway: case (uint)PlaceType.Doorway:
{ {
GameObjLockDict[GameObjType.Doorway].EnterWriteLock();
try
{
GameObjDict[GameObjType.Doorway].Add(new Doorway(GameData.GetCellCenterPos(i, j)));
}
finally
{
GameObjLockDict[GameObjType.Doorway].ExitWriteLock();
}
Add(new Doorway(GameData.GetCellCenterPos(i, j)));
break; break;
} }

case (uint)PlaceType.EmergencyExit: case (uint)PlaceType.EmergencyExit:
{ {
GameObjLockDict[GameObjType.EmergencyExit].EnterWriteLock();
try
{
GameObjDict[GameObjType.EmergencyExit].Add(new EmergencyExit(GameData.GetCellCenterPos(i, j)));
}
finally
{
GameObjLockDict[GameObjType.EmergencyExit].ExitWriteLock();
}
Add(new EmergencyExit(GameData.GetCellCenterPos(i, j)));
break; break;
} }
case (uint)PlaceType.Generator: case (uint)PlaceType.Generator:
{ {
GameObjLockDict[GameObjType.Generator].EnterWriteLock();
try
{
GameObjDict[GameObjType.Generator].Add(new Generator(GameData.GetCellCenterPos(i, j)));
}
finally
{
GameObjLockDict[GameObjType.Generator].ExitWriteLock();
}
Add(new Generator(GameData.GetCellCenterPos(i, j)));
break; break;
} }
case (uint)PlaceType.BirthPoint1: case (uint)PlaceType.BirthPoint1:


+ 2
- 1
logic/GameClass/GameObj/Prop.cs View File

@@ -12,7 +12,8 @@ namespace GameClass.GameObj


protected override bool IgnoreCollideExecutor(IGameObj targetObj) protected override bool IgnoreCollideExecutor(IGameObj targetObj)
{ {
if (targetObj.Type == GameObjType.Prop || targetObj.Type == GameObjType.Bullet || targetObj.Type == GameObjType.Character)
if (targetObj.Type == GameObjType.Prop || targetObj.Type == GameObjType.Bullet
|| targetObj.Type == GameObjType.Character || targetObj.Type == GameObjType.Chest)
return true; return true;
return false; return false;
} }


+ 4
- 4
logic/GameEngine/MoveEngine.cs View File

@@ -74,12 +74,12 @@ namespace GameEngine
{ {
if (obj.IsMoving) // 已经移动的物体不能再移动 if (obj.IsMoving) // 已经移动的物体不能再移动
return; return;
if (!obj.IsAvailable || !gameTimer.IsGaming)
return;
new Thread new Thread
( (
() => () =>
{ {
if (!obj.IsAvailable && gameTimer.IsGaming)
return;
lock (obj.MoveLock) lock (obj.MoveLock)
obj.IsMoving = true; obj.IsMoving = true;


@@ -93,7 +93,7 @@ namespace GameEngine
() => () =>
{ {
moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond;
XY res = new XY(direction, moveVecLength);
res = new XY(direction, moveVecLength);


// 越界情况处理:如果越界,则与越界方块碰撞 // 越界情况处理:如果越界,则与越界方块碰撞
bool flag; // 循环标志 bool flag; // 循环标志
@@ -136,7 +136,7 @@ namespace GameEngine
if (!isDestroyed) if (!isDestroyed)
{ {
moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell;
XY res = new XY(direction, moveVecLength);
res = new XY(direction, moveVecLength);
if ((collisionObj = collisionChecker.CheckCollision(obj, res)) == null) if ((collisionObj = collisionChecker.CheckCollision(obj, res)) == null)
{ {
obj.MovingSetPos(res, GetPlaceType(obj.Position + res)); obj.MovingSetPos(res, GetPlaceType(obj.Position + res));


+ 12
- 14
logic/Gaming/ActionManager.cs View File

@@ -25,11 +25,7 @@ namespace Gaming


public bool Stop(Character player) public bool Stop(Character player)
{ {
if (player.PlayerState == PlayerStateType.IsRescuing || player.PlayerState == PlayerStateType.IsRescued
|| player.PlayerState == PlayerStateType.IsFixing || player.PlayerState == PlayerStateType.IsMoving
|| player.PlayerState == PlayerStateType.IsTreated || player.PlayerState == PlayerStateType.IsTreating
|| player.PlayerState == PlayerStateType.IsRummagingInTheChest || player.PlayerState == PlayerStateType.IsLockingTheDoor
|| player.PlayerState == PlayerStateType.IsClimbingThroughWindows)
if (player.Commandable())
{ {
player.PlayerState = PlayerStateType.Null; player.PlayerState = PlayerStateType.Null;
return true; return true;
@@ -82,11 +78,11 @@ namespace Gaming
.Start(); .Start();
if (generatorForFix.DegreeOfFRepair == GameData.degreeOfFixedGenerator) if (generatorForFix.DegreeOfFRepair == GameData.degreeOfFixedGenerator)
{ {
gameMap.GameObjLockDict[GameObjType.Generator].EnterReadLock();
try
Doorway exit = (Doorway)gameMap.GameObjDict[GameObjType.Doorway][1];
if (!exit.PowerSupply)
{ {
Doorway exit = (Doorway)gameMap.GameObjDict[GameObjType.Doorway][1];
if (!exit.PowerSupply)
gameMap.GameObjLockDict[GameObjType.Generator].EnterReadLock();
try
{ {
int numOfFixedGenerator = 0; int numOfFixedGenerator = 0;
foreach (Generator generator in gameMap.GameObjDict[GameObjType.Generator]) foreach (Generator generator in gameMap.GameObjDict[GameObjType.Generator])
@@ -106,12 +102,12 @@ namespace Gaming
} }
} }
} }
finally
{
gameMap.GameObjLockDict[GameObjType.Generator].ExitReadLock();
}
} }


finally
{
gameMap.GameObjLockDict[GameObjType.Generator].ExitReadLock();
}
} }
} }


@@ -156,7 +152,9 @@ namespace Gaming


public bool Treat(Student player, Student playerTreated) public bool Treat(Student player, Student playerTreated)
{ {
if (playerTreated.PlayerState == PlayerStateType.Null || player.PlayerState == PlayerStateType.Null || playerTreated.HP == playerTreated.MaxHp || !GameData.ApproachToInteract(playerTreated.Position, player.Position))
if (!((playerTreated.NullOrMoving() || playerTreated.InteractingWithMapWithoutMoving())
&& (player.NullOrMoving() || player.InteractingWithMapWithoutMoving()))
|| 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)


+ 79
- 65
logic/Gaming/AttackManager.cs View File

@@ -68,6 +68,32 @@ namespace Gaming
{ IsBackground = true }.Start(); { IsBackground = true }.Start();
} }


public void BeStunned(Character player, int time)
{
new Thread
(() =>
{
player.PlayerState = PlayerStateType.IsStunned;
new FrameRateTaskExecutor<int>(
() => player.PlayerState == PlayerStateType.IsStunned && gameMap.Timer.IsGaming,
() =>
{
},
timeInterval: GameData.frameDuration,
() =>
{
if (player.PlayerState == PlayerStateType.IsStunned)
player.PlayerState = PlayerStateType.Null;
return 0;
},
maxTotalDuration: time
)
.Start();
}
)
{ IsBackground = true }.Start();
}

private void Die(Character player) private void Die(Character player)
{ {


@@ -82,22 +108,14 @@ namespace Gaming
// gameMap.GameObjLockDict[GameObjType.Character].ExitWriteLock(); // gameMap.GameObjLockDict[GameObjType.Character].ExitWriteLock();
// } // }


Prop? dropProp = null;
if (player.PropInventory != null) // 若角色原来有道具,则原始道具掉落在原地
{
dropProp = player.PropInventory;
XY res = GameData.GetCellCenterPos(player.Position.x / GameData.numOfPosGridPerCell, player.Position.y / GameData.numOfPosGridPerCell);
dropProp.ReSetPos(res, gameMap.GetPlaceType(res));
}
gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock();
try
for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++)
{ {
if (dropProp != null)
gameMap.GameObjDict[GameObjType.Prop].Add(dropProp);
}
finally
{
gameMap.GameObjLockDict[GameObjType.Prop].ExitWriteLock();
Prop? prop = player.UseProp(i);
if (prop != null)
{
prop.ReSetPos(player.Position, gameMap.GetPlaceType(player.Position));
gameMap.Add(prop);
}
} }


// player.Reset(); // player.Reset();
@@ -129,21 +147,21 @@ namespace Gaming
*/ */
} }


private bool CanBeBombed(Bullet bullet, GameObjType gameObjType)
{
if (gameObjType == GameObjType.Character) return true;
return false;
}
private void BombObj(Bullet bullet, GameObj objBeingShot) private void BombObj(Bullet bullet, GameObj objBeingShot)
{ {
switch (objBeingShot.Type) switch (objBeingShot.Type)
{ {
case GameObjType.Character: case GameObjType.Character:
if (!((Character)objBeingShot).IsGhost())

if ((!((Character)objBeingShot).IsGhost()) && bullet.Parent.IsGhost())
if (((Character)objBeingShot).BeAttacked(bullet)) if (((Character)objBeingShot).BeAttacked(bullet))
{ {
BeAddictedToGame((Student)objBeingShot); BeAddictedToGame((Student)objBeingShot);
} }
// if (((Character)objBeingShot).IsGhost() && !bullet.Parent.IsGhost() && bullet.TypeOfBullet == BulletType.Ram)
// BeStunned((Character)objBeingShot, bullet.AP);
break;
default:
break; break;
} }
} }
@@ -218,9 +236,9 @@ namespace Gaming


foreach (var kvp in gameMap.GameObjDict) foreach (var kvp in gameMap.GameObjDict)
{ {
if (CanBeBombed(bullet, kvp.Key))
if (bullet.CanBeBombed(kvp.Key))
{ {
gameMap.GameObjLockDict[kvp.Key].EnterWriteLock();
gameMap.GameObjLockDict[kvp.Key].EnterReadLock();
try try
{ {
foreach (var item in gameMap.GameObjDict[kvp.Key]) foreach (var item in gameMap.GameObjDict[kvp.Key])
@@ -232,7 +250,7 @@ namespace Gaming
} }
finally finally
{ {
gameMap.GameObjLockDict[kvp.Key].ExitWriteLock();
gameMap.GameObjLockDict[kvp.Key].ExitReadLock();
} }
} }
} }
@@ -304,55 +322,51 @@ namespace Gaming
(int)((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Sin(angle)) (int)((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Sin(angle))
); );


Bullet? bullet = player.Attack(
res, gameMap.GetPlaceType(res)
);
if (bullet.CastTime > 0)
Bullet? bullet = player.Attack(res, gameMap.GetPlaceType(res));

if (bullet != null)
{ {
player.PlayerState = PlayerStateType.IsTryingToAttack;
bullet.CanMove = true;
gameMap.Add(bullet);
moveEngine.MoveObj(bullet, (int)((bullet.BulletAttackRange - player.Radius - BulletFactory.BulletRadius(player.BulletOfPlayer)) * 1000 / bullet.MoveSpeed), angle); // 这里时间参数除出来的单位要是ms


new Thread
(() =>
{
new FrameRateTaskExecutor<int>(
loopCondition: () => player.PlayerState == PlayerStateType.IsTryingToAttack && gameMap.Timer.IsGaming,
loopToDo: () =>
{
},
timeInterval: GameData.frameDuration,
finallyReturn: () => 0,
maxTotalDuration: bullet.CastTime
)

.Start();

if (gameMap.Timer.IsGaming)

if (bullet.CastTime > 0)
{
player.PlayerState = PlayerStateType.IsTryingToAttack;

new Thread
(() =>
{ {
if (player.PlayerState == PlayerStateType.IsTryingToAttack)
new FrameRateTaskExecutor<int>(
loopCondition: () => player.PlayerState == PlayerStateType.IsTryingToAttack && gameMap.Timer.IsGaming,
loopToDo: () =>
{
},
timeInterval: GameData.frameDuration,
finallyReturn: () => 0,
maxTotalDuration: bullet.CastTime
)

.Start();

if (gameMap.Timer.IsGaming)
{ {
player.PlayerState = PlayerStateType.Null;
if (player.PlayerState == PlayerStateType.IsTryingToAttack)
{
player.PlayerState = PlayerStateType.Null;
}
else
bullet.IsMoving = false;
gameMap.Remove(bullet);
} }
else
bullet.IsMoving = false;
gameMap.Remove(bullet);
} }
}
)
{ IsBackground = true }.Start();
)
{ IsBackground = true }.Start();
}
} }
if (bullet != null) if (bullet != null)
{ {
bullet.CanMove = true;
gameMap.GameObjLockDict[GameObjType.Bullet].EnterWriteLock();
try
{
gameMap.GameObjDict[GameObjType.Bullet].Add(bullet);
}
finally
{
gameMap.GameObjLockDict[GameObjType.Bullet].ExitWriteLock();
}
moveEngine.MoveObj(bullet, (int)((bullet.BulletAttackRange - player.Radius - BulletFactory.BulletRadius(player.BulletOfPlayer)) * 1000 / bullet.MoveSpeed), angle); // 这里时间参数除出来的单位要是ms
#if DEBUG #if DEBUG
Console.WriteLine($"playerID:{player.ID} successfully attacked!"); Console.WriteLine($"playerID:{player.ID} successfully attacked!");
#endif #endif
@@ -368,4 +382,4 @@ namespace Gaming
} }
} }
} }
}
}

+ 4
- 12
logic/Gaming/Game.cs View File

@@ -197,14 +197,6 @@ namespace Gaming


public void EndGame() public void EndGame()
{ {
gameMap.GameObjLockDict[GameObjType.Character].EnterWriteLock();
/*try
{
}
finally
{
}*/
gameMap.GameObjLockDict[GameObjType.Character].ExitWriteLock();
} }
public bool MovePlayer(long playerID, int moveTimeInMilliseconds, double angle) public bool MovePlayer(long playerID, int moveTimeInMilliseconds, double angle)
{ {
@@ -301,24 +293,24 @@ namespace Gaming
_ = attackManager.Attack(player, angle); _ = attackManager.Attack(player, angle);
} }
} }
public void UseProp(long playerID)
public void UseProp(long playerID, int indexing)
{ {
if (!gameMap.Timer.IsGaming) if (!gameMap.Timer.IsGaming)
return; return;
Character? player = gameMap.FindPlayer(playerID); Character? player = gameMap.FindPlayer(playerID);
if (player != null) if (player != null)
{ {
propManager.UseProp(player);
propManager.UseProp(player, indexing);
} }
} }
public void ThrowProp(long playerID, int timeInmillionSeconds, double angle)
public void ThrowProp(long playerID, int indexing)
{ {
if (!gameMap.Timer.IsGaming) if (!gameMap.Timer.IsGaming)
return; return;
Character? player = gameMap.FindPlayer(playerID); Character? player = gameMap.FindPlayer(playerID);
if (player != null) if (player != null)
{ {
propManager.ThrowProp(player, timeInmillionSeconds, angle);
propManager.ThrowProp(player, indexing);
} }
} }
public bool PickProp(long playerID, PropType propType = PropType.Null) public bool PickProp(long playerID, PropType propType = PropType.Null)


+ 32
- 75
logic/Gaming/PropManager.cs View File

@@ -28,11 +28,11 @@ namespace Gaming
ProduceProp(); ProduceProp();
} }


public void UseProp(Character player)
public void UseProp(Character player, int indexing)
{ {
if (player.IsResetting) if (player.IsResetting)
return; return;
Prop? prop = player.UseProp();
Prop? prop = player.UseProp(indexing);
switch (prop?.GetPropType()) switch (prop?.GetPropType())
{ {
case PropType.Spear: case PropType.Spear:
@@ -62,6 +62,10 @@ namespace Gaming
{ {
if (player.IsResetting) if (player.IsResetting)
return false; return false;
int indexing = player.IndexingOfAddProp();
if (indexing == GameData.maxNumOfPropInPropInventory)
return false;

Prop? pickProp = null; Prop? pickProp = null;
if (propType == PropType.Null) // 自动检查有无道具可捡 if (propType == PropType.Null) // 自动检查有无道具可捡
{ {
@@ -70,9 +74,9 @@ namespace Gaming
{ {
foreach (Prop prop in gameMap.GameObjDict[GameObjType.Prop]) foreach (Prop prop in gameMap.GameObjDict[GameObjType.Prop])
{ {
if (GameData.IsInTheSameCell(prop.Position, player.Position) && prop.CanMove == false)
if (GameData.IsInTheSameCell(prop.Position, player.Position))
{ {
pickProp = prop;
player.PropInventory[indexing] = prop;
} }
} }
} }
@@ -92,7 +96,7 @@ namespace Gaming
{ {
if (GameData.IsInTheSameCell(prop.Position, player.Position) && prop.CanMove == false) if (GameData.IsInTheSameCell(prop.Position, player.Position) && prop.CanMove == false)
{ {
pickProp = prop;
player.PropInventory[indexing] = prop;
} }
} }
} }
@@ -105,66 +109,26 @@ namespace Gaming


if (pickProp != null) if (pickProp != null)
{ {
// pickProp.CanMove = false;
Prop? dropProp = null;
if (player.PropInventory != null) // 若角色原来有道具,则原始道具掉落在原地
{
dropProp = player.PropInventory;
XY res = GameData.GetCellCenterPos(player.Position.x / GameData.numOfPosGridPerCell, player.Position.y / GameData.numOfPosGridPerCell);
dropProp.ReSetPos(res, gameMap.GetPlaceType(res));
}
player.PropInventory = pickProp;
gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock();
try
{
gameMap.GameObjDict[GameObjType.Prop].Remove((Preparation.Interface.IGameObj)pickProp);
if (dropProp != null)
gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)dropProp);
}
finally
{
gameMap.GameObjLockDict[GameObjType.Prop].ExitWriteLock();
}
gameMap.GameObjLockDict[GameObjType.PickedProp].EnterWriteLock();
try
{
gameMap.GameObjDict[GameObjType.PickedProp].Add(new PickedProp(pickProp));
}
finally
{
gameMap.GameObjLockDict[GameObjType.PickedProp].ExitWriteLock();
}

gameMap.Remove(pickProp);
gameMap.Add(new PickedProp(pickProp));
return true; return true;
} }
else else
return false; return false;
} }


public void ThrowProp(Character player, int timeInMilliseconds, double angle)
public void ThrowProp(Character player, int indexing)
{ {
if (!gameMap.Timer.IsGaming)
if (!gameMap.Timer.IsGaming || player.IsResetting)
return; return;
if (player.IsResetting) // 移动中也能扔,但由于“惯性”,可能初始位置会有点变化
return;
Prop? prop = player.UseProp();
Prop? prop = player.UseProp(indexing);
if (prop == null) if (prop == null)
return; return;


prop.CanMove = true;
prop.ReSetPos(player.Position, gameMap.GetPlaceType(player.Position)); prop.ReSetPos(player.Position, gameMap.GetPlaceType(player.Position));
gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock();
try
{
gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)prop);
}
finally
{
gameMap.GameObjLockDict[GameObjType.Prop].ExitWriteLock();
}
timeInMilliseconds = timeInMilliseconds < GameData.PropMaxMoveDistance / prop.MoveSpeed * 1000 ? timeInMilliseconds : GameData.PropMaxMoveDistance / prop.MoveSpeed * 1000;
moveEngine.MoveObj(prop, timeInMilliseconds, angle);
gameMap.Add(prop);
} }

private void ProduceProp() private void ProduceProp()
{ {
int len = availableCellForGenerateProp.Count; int len = availableCellForGenerateProp.Count;
@@ -182,30 +146,23 @@ namespace Gaming
int rand = r.Next(0, len); int rand = r.Next(0, len);
XY randPos = availableCellForGenerateProp[rand]; XY randPos = availableCellForGenerateProp[rand];


gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock();
try
{
switch (r.Next(0, 4))
{
case 0:
gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new AddLIFE(randPos, gameMap.GetPlaceType(randPos)));
break;
case 1:
gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new AddSpeed(randPos, gameMap.GetPlaceType(randPos)));
break;
case 2:
gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new Shield(randPos, gameMap.GetPlaceType(randPos)));
break;
case 3:
gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new Spear(randPos, gameMap.GetPlaceType(randPos)));
break;
default:
break;
}
}
finally

switch (r.Next(0, 4))
{ {
gameMap.GameObjLockDict[GameObjType.Prop].ExitWriteLock();
case 0:
gameMap.Add(new AddLIFE(randPos, gameMap.GetPlaceType(randPos)));
break;
case 1:
gameMap.Add(new AddSpeed(randPos, gameMap.GetPlaceType(randPos)));
break;
case 2:
gameMap.Add(new Shield(randPos, gameMap.GetPlaceType(randPos)));
break;
case 3:
gameMap.Add(new Spear(randPos, gameMap.GetPlaceType(randPos)));
break;
default:
break;
} }
}, },
GameData.PropProduceTime, GameData.PropProduceTime,


+ 0
- 238
logic/Gaming/SkillManager/ActiveSkill.cs View File

@@ -1,238 +0,0 @@
using GameClass.GameObj;
using System.Threading;
using Preparation.Interface;
using Preparation.Utility;
using System;
using Timothy.FrameRateTask;

namespace Gaming
{
public partial class Game
{
private partial class SkillManager
{
public interface IActiveSkill
{
public int SkillCD { get; }
public int DurationTime { get; } //技能持续时间
public object ActiveSkillLock { get; }
public bool SkillEffect(Character player);
}
public class BecomeVampire : IActiveSkill // 化身吸血鬼
{
public int SkillCD => GameData.commonSkillCD / 3 * 4;
public int DurationTime => GameData.commonSkillTime;

private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;

public bool SkillEffect(Character player)
{
return ActiveSkillEffect(this, player, () =>
{
player.Vampire += 0.5;
Debugger.Output(player, "becomes vampire!");
},
() =>
{
double tempVam = player.Vampire - 0.5;
player.Vampire = tempVam < player.OriVampire ? player.OriVampire : tempVam;
});
}
}
private IActiveSkill becomeVampire = new BecomeVampire();

public class BeginToCharge : IActiveSkill
{
public int SkillCD => GameData.commonSkillCD / 3 * 4;
public int DurationTime => GameData.commonSkillTime;

private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;

public bool SkillEffect(Character player)
{
return ActiveSkillEffect(this, player, () =>
{
player.Vampire += 0.5;
Debugger.Output(player, "becomes vampire!");
},
() =>
{
double tempVam = player.Vampire - 0.5;
player.Vampire = tempVam < player.OriVampire ? player.OriVampire : tempVam;
});
}
}
private IActiveSkill beginToCharge = new BeginToCharge();

public class BecomeInvisible : IActiveSkill
{
public int SkillCD => GameData.commonSkillCD;
public int DurationTime => GameData.commonSkillTime / 10 * 6;

private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;
public bool SkillEffect(Character player)
{
return ActiveSkillEffect(this, player, () =>
{
player.IsInvisible = true;
Debugger.Output(player, "become invisible!");
},
() =>
{ player.IsInvisible = false; });
}
}
private IActiveSkill becomeInvisible = new BecomeInvisible();

public class NuclearWeapon : IActiveSkill // 核武器
{
public int SkillCD => GameData.commonSkillCD / 3 * 7;
public int DurationTime => GameData.commonSkillTime / 10;
private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;
public bool SkillEffect(Character player)
{
return ActiveSkillEffect(this, player, () =>
{
player.BulletOfPlayer = BulletType.AtomBomb;
Debugger.Output(player, "uses atombomb!");
},
() =>
{ player.BulletOfPlayer = player.OriBulletOfPlayer; });
}
}
private IActiveSkill nuclearWeapon = new NuclearWeapon();

public class UseKnife : IActiveSkill
{
public int SkillCD => GameData.commonSkillCD / 3 * 2;
public int DurationTime => GameData.commonSkillTime / 10;
private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;
public bool SkillEffect(Character player)
{
return ActiveSkillEffect(this, player, () =>
{
player.BulletOfPlayer = BulletType.FlyingKnife;
Debugger.Output(player, "uses flyingknife!");
},
() =>
{ player.BulletOfPlayer = player.OriBulletOfPlayer; });
}
}
private IActiveSkill useKnife = new UseKnife();

public class SuperFast : IActiveSkill // 3倍速
{
public int SkillCD => GameData.commonSkillCD;
public int DurationTime => GameData.commonSkillTime / 10 * 4;
private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock;
public bool SkillEffect(Character player)
{
return ActiveSkillEffect(this, player, () =>
{
player.AddMoveSpeed(this.DurationTime, 3.0);
Debugger.Output(player, "moves very fast!");
},
() =>
{ });
}
}
private IActiveSkill superFast = new SuperFast();

public IActiveSkill? FindIActiveSkill(ActiveSkillType activeSkillType)
{
switch (activeSkillType)
{
case ActiveSkillType.BecomeInvisible:
return this.becomeInvisible;
default:
return null;
}
}
public static ActiveSkillType FindActiveSkillType(IActiveSkill ActiveSkill)
{
switch (ActiveSkill)
{
case BecomeInvisible:
return ActiveSkillType.BecomeInvisible;
case UseKnife:
return ActiveSkillType.UseKnife;
case BeginToCharge:
return ActiveSkillType.BeginToCharge;
default:
return ActiveSkillType.Null;
}
}

public static bool ActiveSkillEffect(IActiveSkill activeSkill, Character player, Action startSkill, Action endSkill)
{
lock (activeSkill.ActiveSkillLock)
{
ActiveSkillType activeSkillType = FindActiveSkillType(activeSkill);
if (player.TimeUntilActiveSkillAvailable[activeSkillType] == 0)
{

player.SetTimeUntilActiveSkillAvailable(activeSkillType, activeSkill.SkillCD);
new Thread
(() =>
{
startSkill();
new FrameRateTaskExecutor<int>(
() => !player.IsResetting,
() =>
{
player.AddTimeUntilActiveSkillAvailable(activeSkillType, -(int)GameData.frameDuration);
},
timeInterval: GameData.frameDuration,
() => 0,
maxTotalDuration: (long)(activeSkill.DurationTime)
)
{
AllowTimeExceed = true,
MaxTolerantTimeExceedCount = ulong.MaxValue,
}
.Start();

endSkill();
Debugger.Output(player, "return to normal.");

new FrameRateTaskExecutor<int>(
() => player.TimeUntilActiveSkillAvailable[activeSkillType] > 0 && !player.IsResetting,
() =>
{
player.AddTimeUntilActiveSkillAvailable(activeSkillType, -(int)GameData.frameDuration);
},
timeInterval: GameData.frameDuration,
() => 0,
maxTotalDuration: (long)(activeSkill.SkillCD - activeSkill.DurationTime)
)
{
AllowTimeExceed = true,
MaxTolerantTimeExceedCount = ulong.MaxValue,
}
.Start();

player.SetTimeUntilActiveSkillAvailable(activeSkillType, 0);
Debugger.Output(player, "ActiveSkill is ready.");
}
)
{ IsBackground = true }.Start();

return true;
}
else
{
Debugger.Output(player, "CommonSkill is cooling down!");
return false;
}
}
}

}

}
}

+ 193
- 0
logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs View File

@@ -0,0 +1,193 @@
using GameClass.GameObj;
using System.Threading;
using Preparation.Interface;
using Preparation.Utility;
using System;
using Timothy.FrameRateTask;

namespace Gaming
{
public partial class Game
{
private partial class SkillManager
{
public bool BecomeVampire(Character player)
{
return ActiveSkillEffect(player.UseIActiveSkill(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)
{

if ((!player.Commandable())) return false;
IActiveSkill skill = player.UseIActiveSkill(ActiveSkillType.CanBeginToCharge);
return ActiveSkillEffect(skill, player, () =>
{
player.AddMoveSpeed(skill.DurationTime, 3.0);
//player.BulletOfPlayer = BulletType.Ram;
new Thread
(
() =>
{
new FrameRateTaskExecutor<int>(
loopCondition: () => player.Commandable() && gameMap.Timer.IsGaming,
loopToDo: () =>
{
gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock();
try
{
foreach (Character character in gameMap.GameObjDict[GameObjType.Character])
{
if (character.IsGhost() != player.IsGhost() && XY.Distance(player.Position + new XY(player.FacingDirection, player.Radius), character.Position) <= character.Radius)
{
attackManager.BeStunned(character, GameData.TimeOfGhostFainting);
attackManager.BeStunned(player, GameData.TimeOfStudentFainting);
break;
}
}
}
finally
{
gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock();
}
},
timeInterval: GameData.frameDuration,
finallyReturn: () => 0,
maxTotalDuration: skill.DurationTime
)

.Start();
}

)
{ IsBackground = true }.Start();
Debugger.Output(player, "can begin to charge!");
},
() =>
{
double tempVam = player.Vampire - 0.5;
player.Vampire = tempVam < player.OriVampire ? player.OriVampire : tempVam;
});
}


public bool BecomeInvisible(Character player)
{
return ActiveSkillEffect(player.UseIActiveSkill(ActiveSkillType.BecomeInvisible), player, () =>
{
player.IsInvisible = true;
Debugger.Output(player, "become invisible!");
},
() =>
{ player.IsInvisible = false; });
}

public bool NuclearWeapon(Character player)
{
return ActiveSkillEffect(player.UseIActiveSkill(ActiveSkillType.NuclearWeapon), player, () =>
{
player.BulletOfPlayer = BulletType.AtomBomb;
Debugger.Output(player, "uses atombomb!");
},
() =>
{ player.BulletOfPlayer = player.OriBulletOfPlayer; });
}


public bool UseKnife(Character player)
{
return ActiveSkillEffect(player.UseIActiveSkill(ActiveSkillType.UseKnife), player, () =>
{
player.BulletOfPlayer = BulletType.FlyingKnife;
Debugger.Output(player, "uses flyingknife!");
},
() =>
{ player.BulletOfPlayer = player.OriBulletOfPlayer; });
}

public bool SuperFast(Character player)
{
return ActiveSkillEffect(player.UseIActiveSkill(ActiveSkillType.SuperFast), player, () =>
{
player.AddMoveSpeed(player.UseIActiveSkill(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)
{
ActiveSkillType activeSkillType = SkillFactory.FindActiveSkillType(activeSkill);
if (player.TimeUntilActiveSkillAvailable[activeSkillType] == 0)
{

player.SetTimeUntilActiveSkillAvailable(activeSkillType, activeSkill.SkillCD);
new Thread
(() =>
{
startSkill();
new FrameRateTaskExecutor<int>(
() => !player.IsResetting,
() =>
{
player.AddTimeUntilActiveSkillAvailable(activeSkillType, -(int)GameData.frameDuration);
},
timeInterval: GameData.frameDuration,
() => 0,
maxTotalDuration: (long)(activeSkill.DurationTime)
)
{
AllowTimeExceed = true,
MaxTolerantTimeExceedCount = ulong.MaxValue,
}
.Start();

endSkill();
Debugger.Output(player, "return to normal.");

new FrameRateTaskExecutor<int>(
loopCondition: () => player.TimeUntilActiveSkillAvailable[activeSkillType] > 0 && !player.IsResetting,
loopToDo: () =>
{
player.AddTimeUntilActiveSkillAvailable(activeSkillType, -(int)GameData.frameDuration);
},
timeInterval: GameData.frameDuration,
finallyReturn: () => 0
)
{
AllowTimeExceed = true,
MaxTolerantTimeExceedCount = ulong.MaxValue,
}
.Start();

player.SetTimeUntilActiveSkillAvailable(activeSkillType, 0);
Debugger.Output(player, "ActiveSkill is ready.");
}
)
{ IsBackground = true }.Start();

return true;
}
else
{
Debugger.Output(player, "CommonSkill is cooling down!");
return false;
}
}
}
}
}
}

logic/Gaming/SkillManager/PassiveSkill.cs → logic/Gaming/SkillManager/SkillManager.PassiveSkill.cs View File


+ 14
- 1
logic/Gaming/SkillManager/SkillManager.cs View File

@@ -14,7 +14,20 @@ namespace Gaming
public bool UseActiveSkill(Character character, ActiveSkillType activeSkillType) public bool UseActiveSkill(Character character, ActiveSkillType activeSkillType)
{ {
if (character.Occupation.ListOfIActiveSkill.Contains(activeSkillType)) if (character.Occupation.ListOfIActiveSkill.Contains(activeSkillType))
return FindIActiveSkill(activeSkillType).SkillEffect(character);
switch (activeSkillType)
{
case ActiveSkillType.BecomeInvisible:
BecomeInvisible(character);
break;
case ActiveSkillType.UseKnife:
UseKnife(character);
break;
case ActiveSkillType.CanBeginToCharge:
CanBeginToCharge(character);
break;
default:
return false;
}
return false; return false;
} }
public void UsePassiveSkill(Character character, PassiveSkillType passiveSkillType) public void UsePassiveSkill(Character character, PassiveSkillType passiveSkillType)


+ 10
- 2
logic/Preparation/Interface/IOccupation.cs View File

@@ -15,6 +15,7 @@ namespace Preparation.Interface
public double Concealment { get; } public double Concealment { get; }
public int AlertnessRadius { get; } public int AlertnessRadius { get; }
public int TimeOfOpeningOrLocking { get; } public int TimeOfOpeningOrLocking { get; }
public int TimeOfClimbingThroughWindows { get; }
} }


public interface IGhost : IOccupation public interface IGhost : IOccupation
@@ -53,6 +54,10 @@ namespace Preparation.Interface


public int timeOfOpeningOrLocking = GameData.basicTimeOfOpeningOrLocking; public int timeOfOpeningOrLocking = GameData.basicTimeOfOpeningOrLocking;
public int TimeOfOpeningOrLocking => timeOfOpeningOrLocking; public int TimeOfOpeningOrLocking => timeOfOpeningOrLocking;

public int timeOfClimbingThroughWindows = GameData.basicTimeOfClimbingThroughWindows;
public int TimeOfClimbingThroughWindows => timeOfClimbingThroughWindows;

} }
public class Athlete : IStudent public class Athlete : IStudent
{ {
@@ -70,7 +75,7 @@ namespace Preparation.Interface


public BulletType InitBullet => BulletType.Null; public BulletType InitBullet => BulletType.Null;


public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { ActiveSkillType.BeginToCharge });
public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { ActiveSkillType.CanBeginToCharge });
public List<PassiveSkillType> ListOfIPassiveSkill => new(new PassiveSkillType[] { }); public List<PassiveSkillType> ListOfIPassiveSkill => new(new PassiveSkillType[] { });


public const int fixSpeed = GameData.basicFixSpeed / 10 * 6; public const int fixSpeed = GameData.basicFixSpeed / 10 * 6;
@@ -82,7 +87,10 @@ namespace Preparation.Interface
public const int alertnessRadius = (int)(GameData.basicAlertnessRadius * 0.9); public const int alertnessRadius = (int)(GameData.basicAlertnessRadius * 0.9);
public int AlertnessRadius => alertnessRadius; public int AlertnessRadius => alertnessRadius;


public int timeOfOpeningOrLocking = GameData.basicTimeOfOpeningOrLocking;
public int timeOfOpeningOrLocking = GameData.basicTimeOfOpeningOrLocking * 12 / 10;
public int TimeOfOpeningOrLocking => timeOfOpeningOrLocking; public int TimeOfOpeningOrLocking => timeOfOpeningOrLocking;

public int timeOfClimbingThroughWindows = GameData.basicTimeOfClimbingThroughWindows / 87 * 80;
public int TimeOfClimbingThroughWindows => timeOfClimbingThroughWindows;
} }
} }

+ 16
- 0
logic/Preparation/Interface/ISkill.cs View File

@@ -0,0 +1,16 @@
namespace Preparation.Interface
{
public interface ISkill
{
}
public interface IPassiveSkill : ISkill
{
}
public interface IActiveSkill : ISkill
{
public int SkillCD { get; }
public int DurationTime { get; } //技能持续时间
public object ActiveSkillLock { get; }
public bool IsBeingUsed { get; set; }
}
}

+ 6
- 3
logic/Preparation/Utility/EnumType.cs View File

@@ -20,8 +20,9 @@ namespace Preparation.Utility
IsStunned = 11, IsStunned = 11,
IsTryingToAttack = 12,//指前摇 IsTryingToAttack = 12,//指前摇
IsLockingTheDoor = 13, IsLockingTheDoor = 13,
IsRummagingInTheChest = 14,
IsOpeningTheChest = 14,
IsClimbingThroughWindows = 15, IsClimbingThroughWindows = 15,
IsUsingSpecialSkill = 16,
} }
public enum GameObjType public enum GameObjType
{ {
@@ -40,6 +41,7 @@ namespace Preparation.Utility
OutOfBoundBlock = 11, // 范围外 OutOfBoundBlock = 11, // 范围外
Window = 12, Window = 12,
Door = 13, Door = 13,
Chest = 14,
} }
public enum ShapeType public enum ShapeType
{ {
@@ -55,7 +57,8 @@ namespace Preparation.Utility
FastBullet = 3, // 快速子弹 FastBullet = 3, // 快速子弹
LineBullet = 4, // 直线子弹 LineBullet = 4, // 直线子弹
FlyingKnife = 5, //飞刀 FlyingKnife = 5, //飞刀
CommonAttackOfGhost = 6
CommonAttackOfGhost = 6,
// Ram = 7,
} }
public enum PropType // 道具类型 public enum PropType // 道具类型
{ {
@@ -84,7 +87,7 @@ namespace Preparation.Utility
NuclearWeapon = 3, NuclearWeapon = 3,
SuperFast = 4, SuperFast = 4,
UseKnife = 5, UseKnife = 5,
BeginToCharge = 6
CanBeginToCharge = 6
} }
public enum PassiveSkillType public enum PassiveSkillType
{ {


+ 37
- 21
logic/Preparation/Utility/GameData.cs View File

@@ -52,38 +52,28 @@ namespace Preparation.Utility


#endregion #endregion
#region 角色相关 #region 角色相关
public const int characterRadius = numOfPosGridPerCell / 2; // 人物半径
public const int basicApOfGhost = 1500000; // 攻击力
public const int characterRadius = numOfPosGridPerCell / 2 / 5 * 4; // 人物半径
public const int basicTreatSpeed = 100; public const int basicTreatSpeed = 100;
public const int basicFixSpeed = 100; public const int basicFixSpeed = 100;
public const int basicTimeOfOpeningOrLocking = 3000;
public const int basicTimeOfClimbingThroughWindows = 870;

public const int basicHp = 3000000; // 初始血量
public const int basicMaxGamingAddiction = 60000;//基本完全沉迷时间 public const int basicMaxGamingAddiction = 60000;//基本完全沉迷时间
public const int BeginGamingAddiction = 10003; public const int BeginGamingAddiction = 10003;
public const int MidGamingAddiction = 30000; public const int MidGamingAddiction = 30000;
public const int basicTreatmentDegree = 1500000; public const int basicTreatmentDegree = 1500000;
public const int basicRescueDegree = 100000; public const int basicRescueDegree = 100000;
public const int basicHp = 3000000; // 初始血量
public const int basicCD = 3000; // 初始子弹冷却
public const int basicCastTime = 500;//基本前摇时间
public const int basicBackswing = 500;//基本后摇时间
public const int basicRecoveryFromHit = 4300;//基本命中攻击恢复时长
public const int basicBulletNum = 3; // 基本初始子弹量
public const int MinAP = 0; // 最小攻击力
public const int MaxAP = int.MaxValue; // 最大攻击力
public const double basicRemoteAttackRange = 9000; // 基本远程攻击范围
public const double basicAttackShortRange = 2700; // 基本近程攻击范围
public const double basicBulletBombRange = 3000; // 基本子弹爆炸范围

public const int basicMoveSpeed = 1260; // 基本移动速度,单位:s-1 public const int basicMoveSpeed = 1260; // 基本移动速度,单位:s-1
public const int basicBulletMoveSpeed = 2700; // 基本子弹移动速度,单位:s-1
public const int characterMaxSpeed = 12000; // 最大速度 public const int characterMaxSpeed = 12000; // 最大速度
public const int basicBulletMoveSpeed = 2700; // 基本子弹移动速度,单位:s-1

public const double basicConcealment = 1.0; public const double basicConcealment = 1.0;
public const int basicAlertnessRadius = 30700; public const int basicAlertnessRadius = 30700;
public const int basicTimeOfOpeningOrLocking = 3000;
public const int maxNumOfPropInPropInventory = 3;
public const int addScoreWhenKillOneLevelPlayer = 30; // 击杀一级角色获得的加分 public const int addScoreWhenKillOneLevelPlayer = 30; // 击杀一级角色获得的加分
public const int commonSkillCD = 30000; // 普通技能标准冷却时间
public const int commonSkillTime = 10000; // 普通技能标准持续时间
public const int bulletRadius = 200; // 默认子弹半径
public const int reviveTime = 30000; // 复活时间
public const int shieldTimeAtBirth = 3000; // 复活时的护盾时间


public static XY PosWhoDie = new XY(1, 1); public static XY PosWhoDie = new XY(1, 1);


@@ -95,6 +85,32 @@ namespace Preparation.Utility
_ => false, _ => false,
}; };
} }
#endregion
#region 攻击与子弹相关
public const int basicApOfGhost = 1500000; // 捣蛋鬼攻击力
public const int MinAP = 0; // 最小攻击力
public const int MaxAP = int.MaxValue; // 最大攻击力

public const int basicCD = 3000; // 初始子弹冷却
public const int basicCastTime = 500;//基本前摇时间
public const int basicBackswing = 500;//基本后摇时间
public const int basicRecoveryFromHit = 4300;//基本命中攻击恢复时长

public const int bulletRadius = 200; // 默认子弹半径
public const int basicBulletNum = 3; // 基本初始子弹量
public const double basicRemoteAttackRange = 9000; // 基本远程攻击范围
public const double basicAttackShortRange = 2700; // 基本近程攻击范围
public const double basicBulletBombRange = 3000; // 基本子弹爆炸范围
#endregion
#region 技能相关
public const int commonSkillCD = 30000; // 普通技能标准冷却时间
public const int commonSkillTime = 10000; // 普通技能标准持续时间
/// <summary>
/// BeginToCharge
/// </summary>
public const int TimeOfGhostFainting = 7220;//=AP of Ram
public const int TimeOfStudentFainting = 2090;

#endregion #endregion
#region 道具相关 #region 道具相关
public const int MinPropTypeNum = 1; public const int MinPropTypeNum = 1;
@@ -108,8 +124,8 @@ namespace Preparation.Utility
#endregion #endregion
#region 物体相关 #region 物体相关
public const int degreeOfFixedGenerator = 10300000; public const int degreeOfFixedGenerator = 10300000;
public const int maxNumOfPropInChest = 2;
#endregion #endregion

#region 游戏帧相关 #region 游戏帧相关
public const long checkInterval = 50; // 检查位置标志、补充子弹的帧时长 public const long checkInterval = 50; // 检查位置标志、补充子弹的帧时长
#endregion #endregion


+ 5
- 0
logic/Preparation/Utility/XY.cs View File

@@ -17,6 +17,11 @@ namespace Preparation.Utility
this.x = (int)(length * Math.Cos(angle)); this.x = (int)(length * Math.Cos(angle));
this.y = (int)(length * Math.Sin(angle)); this.y = (int)(length * Math.Sin(angle));
} }
public XY(XY Direction, double length)
{
this.x = (int)(length * Math.Cos(Direction.Angle()));
this.y = (int)(length * Math.Sin(Direction.Angle()));
}
public override string ToString() public override string ToString()
{ {
return "(" + x.ToString() + "," + y.ToString() + ")"; return "(" + x.ToString() + "," + y.ToString() + ")";


+ 40
- 8
logic/规则Logic.md View File

@@ -9,7 +9,10 @@


## 游戏简介 ## 游戏简介
- 1位监管者对抗4位求生者的非对称竞技模式 - 1位监管者对抗4位求生者的非对称竞技模式
- 略
- [本届THUAI电子系赛道为以4名同学和1名捣蛋鬼的求学与阻挠展开的非对称竞技模式,同学需要完成足够的家庭作业和考试,相互督促以避免沉迷娱乐生活,利用道具地形躲避捣蛋鬼的各种干扰诱惑,完成学业;捣蛋鬼则要极力阻止。]
- [我们的设计是一个非对称游戏,类似第五人格,分为学生、捣蛋鬼两个阵营。在游戏中,学生修完若干课程之后通过考试即可顺利毕业,捣蛋鬼试图干扰学生使其沉迷游戏,以致于无法修完规定课程,直至挂科、退学。]
[对于选手来说,需要提前制定好学生的学习方案以抵御对方捣蛋鬼的干扰,类似地,也需要制定好捣蛋鬼的行动策略以影响对方学生的学习,也即每队至少要写好两份代码以执行不同阵营的不同策略。]
[当一局比赛结束(场上的学生有且仅有两种状态:退学或毕业)时,分别记录双方总得分;之后双方换边进行下半场比赛。最终将每队的学生方、捣蛋鬼方的得分相加,比较总得分判断胜负。]


## 地图 ## 地图
- 地图为矩形区域,地图上的游戏对象坐标为(x, y),且x和y均为整数。x坐标轴正方向竖直向下, - 地图为矩形区域,地图上的游戏对象坐标为(x, y),且x和y均为整数。x坐标轴正方向竖直向下,
@@ -91,16 +94,24 @@
IsStunned = 11, IsStunned = 11,
IsTryingToAttack = 12,//指前摇 IsTryingToAttack = 12,//指前摇
IsLockingTheDoor = 13, IsLockingTheDoor = 13,
IsRummagingInTheChest = 14,
IsOpeningTheChest = 14,
IsClimbingThroughWindows = 15, IsClimbingThroughWindows = 15,
IsUsingSpecialSkill = 16,
} }
~~~ ~~~
- 可执行指令的(不用给选手)
~~~csharp
public bool Commandable() => (playerState!=PlayerStateType.IsDeceased&&playerState!=PlayerStateType.IsEscaped
&&playerState!=PlayerStateType.IsAddicted &&playerState!=PlayerStateType.IsStunned
&&playerState!=PlayerStateType.IsSwinging&&playerState!=PlayerStateType.IsTryingToAttack
&&playerState!=PlayerStateType.IsClimbingThroughWindows);
~~~
- Bgm(字典) - Bgm(字典)
- 得分 - 得分
- ~~回血率/原始回血率~~ - ~~回血率/原始回血率~~
- 当前子弹类型 - 当前子弹类型
- 原始子弹类型 - 原始子弹类型
- 持有道具 *(最多三个)(列表)*
- 持有道具 (最多三个)(数组)
- 是否隐身 - 是否隐身
- 队伍ID - 队伍ID
- 玩家ID - 玩家ID
@@ -111,7 +122,7 @@
- 各个主动技能CD(字典) - 各个主动技能CD(字典)
- 警戒半径 - 警戒半径
- double 隐蔽度 - double 隐蔽度
- *翻窗时间*
- 翻窗时间
- 开锁门时间 - 开锁门时间


### 学生:人物 ### 学生:人物
@@ -210,7 +221,7 @@
3. 修理电机 3. 修理电机
4. 开锁门 4. 开锁门
5. 翻窗 5. 翻窗
6. 翻找箱子
6. 开启箱子


### 门 ### 门
- *门分别属于三个教学区:三教,五教,六教* - *门分别属于三个教学区:三教,五教,六教*
@@ -227,10 +238,31 @@
- *翻越窗户是一种交互行为,执行时,实质是限定方向的减速运动* - *翻越窗户是一种交互行为,执行时,实质是限定方向的减速运动*


### 箱子 ### 箱子
- *监管者和求生者都能与箱子交互,同一时刻就允许一人进行翻找*
- *监管者和求生者都能与箱子交互,同一时刻只允许一人进行开启*
- *开启箱子有不同概率获得不同道具。* - *开启箱子有不同概率获得不同道具。*
- *搜寻物品的基础持续时间为10秒。*
- *未搜寻完成的箱子在下一次需要重新开始搜寻。*
- *开启箱子的基础持续时间为10秒。*
- *未开启完成的箱子在下一次需要重新开始开启。*
- *箱子开启后其中道具才可以被观测和拿取*
- [箱子道具不刷新]
- [箱子不可被关闭]
- [箱子内道具最多两个]

### 道具
- 每次玩家试图捡起道具时,需要确保道具栏有空位
- indexing指道具栏数组下标从0开始
- 扔道具
- Logic内实现
~~~csharp
public void ThrowProp(long playerID, int indexing)
~~~
- 对应下标出现空位,不会对数组进行重新排序
- 使用道具
- Logic内实现
~~~csharp
public void UseProp(long playerID,int indexing)
~~~
- 对应下标出现空位,不会对数组进行重新排序



### 治疗 ### 治疗
- 可行动的求生者可以对受伤的其他求生者进行治疗,治疗完成后会回复被治疗程度的血量。 - 可行动的求生者可以对受伤的其他求生者进行治疗,治疗完成后会回复被治疗程度的血量。


Loading…
Cancel
Save