chore: rebuild the skill system againtags/0.1.0
| @@ -22,7 +22,11 @@ namespace GameClass.GameObj | |||
| { | |||
| return false; | |||
| } | |||
| public override bool CanBeBombed(GameObjType gameObjType) | |||
| { | |||
| if (gameObjType == GameObjType.Character) return true; | |||
| return false; | |||
| } | |||
| public override BulletType TypeOfBullet => BulletType.CommonAttackOfGhost; | |||
| } | |||
| @@ -44,6 +48,11 @@ namespace GameClass.GameObj | |||
| { | |||
| return false; | |||
| } | |||
| public override bool CanBeBombed(GameObjType gameObjType) | |||
| { | |||
| if (gameObjType == GameObjType.Character) return true; | |||
| return false; | |||
| } | |||
| public override BulletType TypeOfBullet => BulletType.FlyingKnife; | |||
| @@ -68,6 +77,11 @@ namespace GameClass.GameObj | |||
| // 圆形攻击范围 | |||
| 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; | |||
| @@ -92,6 +106,11 @@ namespace GameClass.GameObj | |||
| // 圆形攻击范围 | |||
| 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; | |||
| } | |||
| @@ -116,6 +135,11 @@ namespace GameClass.GameObj | |||
| // 圆形攻击范围 | |||
| 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; | |||
| } | |||
| @@ -135,6 +159,11 @@ namespace GameClass.GameObj | |||
| public override int RecoveryFromHit => GameData.basicRecoveryFromHit; | |||
| 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) | |||
| { | |||
| double FacingAngle = Math.Atan2(this.FacingDirection.y, this.FacingDirection.x); | |||
| @@ -1,7 +1,32 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| using Preparation.Utility; | |||
| 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; | |||
| }*/ | |||
| } | |||
| @@ -30,6 +30,7 @@ namespace GameClass.GameObj | |||
| /// <param name="target">被尝试攻击者</param> | |||
| /// <returns>是否可以攻击到</returns> | |||
| public abstract bool CanAttack(GameObj target); | |||
| public abstract bool CanBeBombed(GameObjType gameObjType); | |||
| protected override bool IgnoreCollideExecutor(IGameObj targetObj) | |||
| { | |||
| @@ -12,9 +12,22 @@ namespace GameClass.GameObj | |||
| private readonly IOccupation occupation; | |||
| public IOccupation Occupation => occupation; | |||
| private Dictionary<ActiveSkillType, int> timeUntilActiveSkillAvailable = new(); | |||
| 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) | |||
| { | |||
| if (TimeUntilActiveSkillAvailable.ContainsKey(activeSkillType)) | |||
| @@ -73,10 +86,13 @@ namespace GameClass.GameObj | |||
| this.concealment = Occupation.Concealment; | |||
| this.alertnessRadius = Occupation.AlertnessRadius; | |||
| this.characterType = characterType; | |||
| this.TimeOfOpeningOrLocking = Occupation.TimeOfOpeningOrLocking; | |||
| this.TimeOfClimbingThroughWindows = Occupation.TimeOfClimbingThroughWindows; | |||
| foreach (var activeSkill in this.Occupation.ListOfIActiveSkill) | |||
| { | |||
| this.TimeUntilActiveSkillAvailable.Add(activeSkill, 0); | |||
| this.IActiveSkillDictionary.Add(activeSkill, SkillFactory.FindIActiveSkill(activeSkill)); | |||
| } | |||
| // UsePassiveSkill(); //创建player时开始被动技能,这一过程也可以放到gamestart时进行 | |||
| @@ -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; | |||
| // 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; | |||
| set | |||
| @@ -148,16 +155,30 @@ namespace GameClass.GameObj | |||
| /// 使用物品栏中的道具 | |||
| /// </summary> | |||
| /// <returns>被使用的道具</returns> | |||
| public Prop? UseProp() | |||
| public Prop? UseProp(int indexing) | |||
| { | |||
| if (indexing < 0 || indexing >= GameData.maxNumOfPropInPropInventory) | |||
| return null; | |||
| 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> | |||
| @@ -226,7 +247,18 @@ namespace GameClass.GameObj | |||
| } | |||
| } | |||
| private int timeOfClimbingThroughWindows; | |||
| public int TimeOfClimbingThroughWindows | |||
| { | |||
| get => timeOfClimbingThroughWindows; | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| timeOfClimbingThroughWindows = value; | |||
| } | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 进行一次攻击 | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -96,6 +96,18 @@ namespace GameClass.GameObj | |||
| } | |||
| 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) | |||
| { | |||
| GameObjLockDict[gameObj.Type].EnterWriteLock(); | |||
| @@ -134,55 +146,22 @@ namespace GameClass.GameObj | |||
| { | |||
| 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; | |||
| } | |||
| 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; | |||
| } | |||
| 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; | |||
| } | |||
| 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; | |||
| } | |||
| case (uint)PlaceType.BirthPoint1: | |||
| @@ -12,7 +12,8 @@ namespace GameClass.GameObj | |||
| 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 false; | |||
| } | |||
| @@ -74,12 +74,12 @@ namespace GameEngine | |||
| { | |||
| if (obj.IsMoving) // 已经移动的物体不能再移动 | |||
| return; | |||
| if (!obj.IsAvailable || !gameTimer.IsGaming) | |||
| return; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| if (!obj.IsAvailable && gameTimer.IsGaming) | |||
| return; | |||
| lock (obj.MoveLock) | |||
| obj.IsMoving = true; | |||
| @@ -93,7 +93,7 @@ namespace GameEngine | |||
| () => | |||
| { | |||
| moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | |||
| XY res = new XY(direction, moveVecLength); | |||
| res = new XY(direction, moveVecLength); | |||
| // 越界情况处理:如果越界,则与越界方块碰撞 | |||
| bool flag; // 循环标志 | |||
| @@ -136,7 +136,7 @@ namespace GameEngine | |||
| if (!isDestroyed) | |||
| { | |||
| 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) | |||
| { | |||
| obj.MovingSetPos(res, GetPlaceType(obj.Position + res)); | |||
| @@ -25,11 +25,7 @@ namespace Gaming | |||
| 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; | |||
| return true; | |||
| @@ -82,11 +78,11 @@ namespace Gaming | |||
| .Start(); | |||
| 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; | |||
| 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) | |||
| { | |||
| 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; | |||
| if (playerTreated.HP + playerTreated.DegreeOfTreatment >= playerTreated.MaxHp) | |||
| @@ -68,6 +68,32 @@ namespace Gaming | |||
| { 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) | |||
| { | |||
| @@ -82,22 +108,14 @@ namespace Gaming | |||
| // 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(); | |||
| @@ -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) | |||
| { | |||
| switch (objBeingShot.Type) | |||
| { | |||
| case GameObjType.Character: | |||
| if (!((Character)objBeingShot).IsGhost()) | |||
| if ((!((Character)objBeingShot).IsGhost()) && bullet.Parent.IsGhost()) | |||
| if (((Character)objBeingShot).BeAttacked(bullet)) | |||
| { | |||
| BeAddictedToGame((Student)objBeingShot); | |||
| } | |||
| // if (((Character)objBeingShot).IsGhost() && !bullet.Parent.IsGhost() && bullet.TypeOfBullet == BulletType.Ram) | |||
| // BeStunned((Character)objBeingShot, bullet.AP); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| } | |||
| @@ -218,9 +236,9 @@ namespace Gaming | |||
| 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 | |||
| { | |||
| foreach (var item in gameMap.GameObjDict[kvp.Key]) | |||
| @@ -232,7 +250,7 @@ namespace Gaming | |||
| } | |||
| 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)) | |||
| ); | |||
| 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) | |||
| { | |||
| 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 | |||
| Console.WriteLine($"playerID:{player.ID} successfully attacked!"); | |||
| #endif | |||
| @@ -368,4 +382,4 @@ namespace Gaming | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -197,14 +197,6 @@ namespace Gaming | |||
| public void EndGame() | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterWriteLock(); | |||
| /*try | |||
| { | |||
| } | |||
| finally | |||
| { | |||
| }*/ | |||
| gameMap.GameObjLockDict[GameObjType.Character].ExitWriteLock(); | |||
| } | |||
| public bool MovePlayer(long playerID, int moveTimeInMilliseconds, double angle) | |||
| { | |||
| @@ -301,24 +293,24 @@ namespace Gaming | |||
| _ = attackManager.Attack(player, angle); | |||
| } | |||
| } | |||
| public void UseProp(long playerID) | |||
| public void UseProp(long playerID, int indexing) | |||
| { | |||
| if (!gameMap.Timer.IsGaming) | |||
| return; | |||
| Character? player = gameMap.FindPlayer(playerID); | |||
| 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) | |||
| return; | |||
| Character? player = gameMap.FindPlayer(playerID); | |||
| if (player != null) | |||
| { | |||
| propManager.ThrowProp(player, timeInmillionSeconds, angle); | |||
| propManager.ThrowProp(player, indexing); | |||
| } | |||
| } | |||
| public bool PickProp(long playerID, PropType propType = PropType.Null) | |||
| @@ -28,11 +28,11 @@ namespace Gaming | |||
| ProduceProp(); | |||
| } | |||
| public void UseProp(Character player) | |||
| public void UseProp(Character player, int indexing) | |||
| { | |||
| if (player.IsResetting) | |||
| return; | |||
| Prop? prop = player.UseProp(); | |||
| Prop? prop = player.UseProp(indexing); | |||
| switch (prop?.GetPropType()) | |||
| { | |||
| case PropType.Spear: | |||
| @@ -62,6 +62,10 @@ namespace Gaming | |||
| { | |||
| if (player.IsResetting) | |||
| return false; | |||
| int indexing = player.IndexingOfAddProp(); | |||
| if (indexing == GameData.maxNumOfPropInPropInventory) | |||
| return false; | |||
| Prop? pickProp = null; | |||
| if (propType == PropType.Null) // 自动检查有无道具可捡 | |||
| { | |||
| @@ -70,9 +74,9 @@ namespace Gaming | |||
| { | |||
| 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) | |||
| { | |||
| pickProp = prop; | |||
| player.PropInventory[indexing] = prop; | |||
| } | |||
| } | |||
| } | |||
| @@ -105,66 +109,26 @@ namespace Gaming | |||
| 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; | |||
| } | |||
| else | |||
| 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; | |||
| if (player.IsResetting) // 移动中也能扔,但由于“惯性”,可能初始位置会有点变化 | |||
| return; | |||
| Prop? prop = player.UseProp(); | |||
| Prop? prop = player.UseProp(indexing); | |||
| if (prop == null) | |||
| return; | |||
| prop.CanMove = true; | |||
| 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() | |||
| { | |||
| int len = availableCellForGenerateProp.Count; | |||
| @@ -182,30 +146,23 @@ namespace Gaming | |||
| int rand = r.Next(0, len); | |||
| 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, | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -14,7 +14,20 @@ namespace Gaming | |||
| public bool UseActiveSkill(Character character, ActiveSkillType 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; | |||
| } | |||
| public void UsePassiveSkill(Character character, PassiveSkillType passiveSkillType) | |||
| @@ -15,6 +15,7 @@ namespace Preparation.Interface | |||
| public double Concealment { get; } | |||
| public int AlertnessRadius { get; } | |||
| public int TimeOfOpeningOrLocking { get; } | |||
| public int TimeOfClimbingThroughWindows { get; } | |||
| } | |||
| public interface IGhost : IOccupation | |||
| @@ -53,6 +54,10 @@ namespace Preparation.Interface | |||
| public int timeOfOpeningOrLocking = GameData.basicTimeOfOpeningOrLocking; | |||
| public int TimeOfOpeningOrLocking => timeOfOpeningOrLocking; | |||
| public int timeOfClimbingThroughWindows = GameData.basicTimeOfClimbingThroughWindows; | |||
| public int TimeOfClimbingThroughWindows => timeOfClimbingThroughWindows; | |||
| } | |||
| public class Athlete : IStudent | |||
| { | |||
| @@ -70,7 +75,7 @@ namespace Preparation.Interface | |||
| 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 const int fixSpeed = GameData.basicFixSpeed / 10 * 6; | |||
| @@ -82,7 +87,10 @@ namespace Preparation.Interface | |||
| public const int alertnessRadius = (int)(GameData.basicAlertnessRadius * 0.9); | |||
| public int AlertnessRadius => alertnessRadius; | |||
| public int timeOfOpeningOrLocking = GameData.basicTimeOfOpeningOrLocking; | |||
| public int timeOfOpeningOrLocking = GameData.basicTimeOfOpeningOrLocking * 12 / 10; | |||
| public int TimeOfOpeningOrLocking => timeOfOpeningOrLocking; | |||
| public int timeOfClimbingThroughWindows = GameData.basicTimeOfClimbingThroughWindows / 87 * 80; | |||
| public int TimeOfClimbingThroughWindows => timeOfClimbingThroughWindows; | |||
| } | |||
| } | |||
| @@ -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; } | |||
| } | |||
| } | |||
| @@ -20,8 +20,9 @@ namespace Preparation.Utility | |||
| IsStunned = 11, | |||
| IsTryingToAttack = 12,//指前摇 | |||
| IsLockingTheDoor = 13, | |||
| IsRummagingInTheChest = 14, | |||
| IsOpeningTheChest = 14, | |||
| IsClimbingThroughWindows = 15, | |||
| IsUsingSpecialSkill = 16, | |||
| } | |||
| public enum GameObjType | |||
| { | |||
| @@ -40,6 +41,7 @@ namespace Preparation.Utility | |||
| OutOfBoundBlock = 11, // 范围外 | |||
| Window = 12, | |||
| Door = 13, | |||
| Chest = 14, | |||
| } | |||
| public enum ShapeType | |||
| { | |||
| @@ -55,7 +57,8 @@ namespace Preparation.Utility | |||
| FastBullet = 3, // 快速子弹 | |||
| LineBullet = 4, // 直线子弹 | |||
| FlyingKnife = 5, //飞刀 | |||
| CommonAttackOfGhost = 6 | |||
| CommonAttackOfGhost = 6, | |||
| // Ram = 7, | |||
| } | |||
| public enum PropType // 道具类型 | |||
| { | |||
| @@ -84,7 +87,7 @@ namespace Preparation.Utility | |||
| NuclearWeapon = 3, | |||
| SuperFast = 4, | |||
| UseKnife = 5, | |||
| BeginToCharge = 6 | |||
| CanBeginToCharge = 6 | |||
| } | |||
| public enum PassiveSkillType | |||
| { | |||
| @@ -52,38 +52,28 @@ namespace Preparation.Utility | |||
| #endregion | |||
| #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 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 BeginGamingAddiction = 10003; | |||
| public const int MidGamingAddiction = 30000; | |||
| public const int basicTreatmentDegree = 1500000; | |||
| 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 basicBulletMoveSpeed = 2700; // 基本子弹移动速度,单位:s-1 | |||
| public const int characterMaxSpeed = 12000; // 最大速度 | |||
| public const int basicBulletMoveSpeed = 2700; // 基本子弹移动速度,单位:s-1 | |||
| public const double basicConcealment = 1.0; | |||
| public const int basicAlertnessRadius = 30700; | |||
| public const int basicTimeOfOpeningOrLocking = 3000; | |||
| public const int maxNumOfPropInPropInventory = 3; | |||
| 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); | |||
| @@ -95,6 +85,32 @@ namespace Preparation.Utility | |||
| _ => 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 | |||
| #region 道具相关 | |||
| public const int MinPropTypeNum = 1; | |||
| @@ -108,8 +124,8 @@ namespace Preparation.Utility | |||
| #endregion | |||
| #region 物体相关 | |||
| public const int degreeOfFixedGenerator = 10300000; | |||
| public const int maxNumOfPropInChest = 2; | |||
| #endregion | |||
| #region 游戏帧相关 | |||
| public const long checkInterval = 50; // 检查位置标志、补充子弹的帧时长 | |||
| #endregion | |||
| @@ -17,6 +17,11 @@ namespace Preparation.Utility | |||
| this.x = (int)(length * Math.Cos(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() | |||
| { | |||
| return "(" + x.ToString() + "," + y.ToString() + ")"; | |||
| @@ -9,7 +9,10 @@ | |||
| ## 游戏简介 | |||
| - 1位监管者对抗4位求生者的非对称竞技模式 | |||
| - 略 | |||
| - [本届THUAI电子系赛道为以4名同学和1名捣蛋鬼的求学与阻挠展开的非对称竞技模式,同学需要完成足够的家庭作业和考试,相互督促以避免沉迷娱乐生活,利用道具地形躲避捣蛋鬼的各种干扰诱惑,完成学业;捣蛋鬼则要极力阻止。] | |||
| - [我们的设计是一个非对称游戏,类似第五人格,分为学生、捣蛋鬼两个阵营。在游戏中,学生修完若干课程之后通过考试即可顺利毕业,捣蛋鬼试图干扰学生使其沉迷游戏,以致于无法修完规定课程,直至挂科、退学。] | |||
| [对于选手来说,需要提前制定好学生的学习方案以抵御对方捣蛋鬼的干扰,类似地,也需要制定好捣蛋鬼的行动策略以影响对方学生的学习,也即每队至少要写好两份代码以执行不同阵营的不同策略。] | |||
| [当一局比赛结束(场上的学生有且仅有两种状态:退学或毕业)时,分别记录双方总得分;之后双方换边进行下半场比赛。最终将每队的学生方、捣蛋鬼方的得分相加,比较总得分判断胜负。] | |||
| ## 地图 | |||
| - 地图为矩形区域,地图上的游戏对象坐标为(x, y),且x和y均为整数。x坐标轴正方向竖直向下, | |||
| @@ -91,16 +94,24 @@ | |||
| IsStunned = 11, | |||
| IsTryingToAttack = 12,//指前摇 | |||
| IsLockingTheDoor = 13, | |||
| IsRummagingInTheChest = 14, | |||
| IsOpeningTheChest = 14, | |||
| 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(字典) | |||
| - 得分 | |||
| - ~~回血率/原始回血率~~ | |||
| - 当前子弹类型 | |||
| - 原始子弹类型 | |||
| - 持有道具 *(最多三个)(列表)* | |||
| - 持有道具 (最多三个)(数组) | |||
| - 是否隐身 | |||
| - 队伍ID | |||
| - 玩家ID | |||
| @@ -111,7 +122,7 @@ | |||
| - 各个主动技能CD(字典) | |||
| - 警戒半径 | |||
| - double 隐蔽度 | |||
| - *翻窗时间* | |||
| - 翻窗时间 | |||
| - 开锁门时间 | |||
| ### 学生:人物 | |||
| @@ -210,7 +221,7 @@ | |||
| 3. 修理电机 | |||
| 4. 开锁门 | |||
| 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) | |||
| ~~~ | |||
| - 对应下标出现空位,不会对数组进行重新排序 | |||
| ### 治疗 | |||
| - 可行动的求生者可以对受伤的其他求生者进行治疗,治疗完成后会回复被治疗程度的血量。 | |||