| @@ -9,9 +9,11 @@ namespace GameClass.GameObj | |||||
| public override bool IsRigid => false; | public override bool IsRigid => false; | ||||
| public long MappingID { get; } | public long MappingID { get; } | ||||
| public Bullet bulletHasBombed; | public Bullet bulletHasBombed; | ||||
| public BombedBullet(Bullet bullet) : | public BombedBullet(Bullet bullet) : | ||||
| base(bullet.Position, bullet.Radius, GameObjType.BombedBullet) | base(bullet.Position, bullet.Radius, GameObjType.BombedBullet) | ||||
| { | { | ||||
| this.place = bullet.Place; | |||||
| this.bulletHasBombed = bullet; | this.bulletHasBombed = bullet; | ||||
| this.MappingID = bullet.ID; | this.MappingID = bullet.ID; | ||||
| this.FacingDirection = bullet.FacingDirection; | this.FacingDirection = bullet.FacingDirection; | ||||
| @@ -6,8 +6,8 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| internal sealed class CommonAttackOfGhost : Bullet | internal sealed class CommonAttackOfGhost : Bullet | ||||
| { | { | ||||
| public CommonAttackOfGhost(Character player, int radius = GameData.bulletRadius) : | |||||
| base(player, radius) | |||||
| public CommonAttackOfGhost(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) : | |||||
| base(player, radius, placeType, pos) | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => 0; | public override double BulletBombRange => 0; | ||||
| @@ -28,8 +28,8 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| internal sealed class FlyingKnife : Bullet | internal sealed class FlyingKnife : Bullet | ||||
| { | { | ||||
| public FlyingKnife(Character player, int radius = GameData.bulletRadius) : | |||||
| base(player, radius) | |||||
| public FlyingKnife(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) : | |||||
| base(player, radius, placeType, pos) | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => 0; | public override double BulletBombRange => 0; | ||||
| @@ -51,8 +51,8 @@ namespace GameClass.GameObj | |||||
| internal sealed class AtomBomb : Bullet | internal sealed class AtomBomb : Bullet | ||||
| { | { | ||||
| public AtomBomb(Character player, int radius = GameData.bulletRadius) : | |||||
| base(player, radius) | |||||
| public AtomBomb(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) : | |||||
| base(player, radius, placeType, pos) | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => GameData.basicBulletBombRange / 3 * 7; | public override double BulletBombRange => GameData.basicBulletBombRange / 3 * 7; | ||||
| @@ -75,8 +75,8 @@ namespace GameClass.GameObj | |||||
| internal sealed class OrdinaryBullet : Bullet // 1倍攻击范围,1倍攻击力,一倍速 | internal sealed class OrdinaryBullet : Bullet // 1倍攻击范围,1倍攻击力,一倍速 | ||||
| { | { | ||||
| public OrdinaryBullet(Character player, int radius = GameData.bulletRadius) : | |||||
| base(player, radius) | |||||
| public OrdinaryBullet(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) : | |||||
| base(player, radius, placeType, pos) | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => GameData.basicBulletBombRange / 6 * 5; | public override double BulletBombRange => GameData.basicBulletBombRange / 6 * 5; | ||||
| @@ -98,8 +98,8 @@ namespace GameClass.GameObj | |||||
| internal sealed class FastBullet : Bullet // 1倍攻击范围,0.2倍攻击力,2倍速 | internal sealed class FastBullet : Bullet // 1倍攻击范围,0.2倍攻击力,2倍速 | ||||
| { | { | ||||
| public FastBullet(Character player, int radius = GameData.bulletRadius) : | |||||
| base(player, radius) | |||||
| public FastBullet(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) : | |||||
| base(player, radius, placeType, pos) | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => GameData.basicBulletBombRange / 4 * 2; | public override double BulletBombRange => GameData.basicBulletBombRange / 4 * 2; | ||||
| @@ -122,8 +122,8 @@ namespace GameClass.GameObj | |||||
| internal sealed class LineBullet : Bullet // 直线爆炸,宽度1格,长度为2倍爆炸范围 | internal sealed class LineBullet : Bullet // 直线爆炸,宽度1格,长度为2倍爆炸范围 | ||||
| { | { | ||||
| public LineBullet(Character player, int radius = GameData.bulletRadius) : | |||||
| base(player, radius) | |||||
| public LineBullet(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) : | |||||
| base(player, radius, placeType, pos) | |||||
| { | { | ||||
| } | } | ||||
| public override double BulletBombRange => GameData.basicBulletBombRange / 3 * 4; | public override double BulletBombRange => GameData.basicBulletBombRange / 3 * 4; | ||||
| @@ -37,9 +37,10 @@ namespace GameClass.GameObj | |||||
| return true; | return true; | ||||
| return false; | return false; | ||||
| } | } | ||||
| public Bullet(Character player, int radius) : | |||||
| base(player.Position, radius, GameObjType.Bullet) | |||||
| public Bullet(Character player, int radius, PlaceType placeType, XY Position) : | |||||
| base(Position, radius, GameObjType.Bullet) | |||||
| { | { | ||||
| this.place = placeType; | |||||
| this.CanMove = true; | this.CanMove = true; | ||||
| this.moveSpeed = this.Speed; | this.moveSpeed = this.Speed; | ||||
| this.hasSpear = player.HasSpear; | this.hasSpear = player.HasSpear; | ||||
| @@ -52,22 +53,22 @@ namespace GameClass.GameObj | |||||
| public static class BulletFactory | public static class BulletFactory | ||||
| { | { | ||||
| public static Bullet? GetBullet(Character character) | |||||
| public static Bullet? GetBullet(Character character, PlaceType place, XY pos) | |||||
| { | { | ||||
| Bullet? newBullet = null; | Bullet? newBullet = null; | ||||
| switch (character.BulletOfPlayer) | switch (character.BulletOfPlayer) | ||||
| { | { | ||||
| case BulletType.AtomBomb: | case BulletType.AtomBomb: | ||||
| newBullet = new AtomBomb(character); | |||||
| newBullet = new AtomBomb(character, place, pos); | |||||
| break; | break; | ||||
| case BulletType.LineBullet: | case BulletType.LineBullet: | ||||
| newBullet = new LineBullet(character); | |||||
| newBullet = new LineBullet(character, place, pos); | |||||
| break; | break; | ||||
| case BulletType.FastBullet: | case BulletType.FastBullet: | ||||
| newBullet = new FastBullet(character); | |||||
| newBullet = new FastBullet(character, place, pos); | |||||
| break; | break; | ||||
| case BulletType.OrdinaryBullet: | case BulletType.OrdinaryBullet: | ||||
| newBullet = new OrdinaryBullet(character); | |||||
| newBullet = new OrdinaryBullet(character, place, pos); | |||||
| break; | break; | ||||
| default: | default: | ||||
| break; | break; | ||||
| @@ -53,16 +53,13 @@ namespace GameClass.GameObj | |||||
| public bool IsGhost() | public bool IsGhost() | ||||
| { | { | ||||
| return this.CharacterType switch | |||||
| { | |||||
| CharacterType.Assassin => true, | |||||
| _ => false, | |||||
| }; | |||||
| return GameData.IsGhost(CharacterType); | |||||
| } | } | ||||
| protected Character(XY initPos, int initRadius, CharacterType characterType) : | protected Character(XY initPos, int initRadius, CharacterType characterType) : | ||||
| base(initPos, initRadius, GameObjType.Character) | base(initPos, initRadius, GameObjType.Character) | ||||
| { | { | ||||
| this.place = PlaceType.Null; | |||||
| this.CanMove = true; | this.CanMove = true; | ||||
| this.score = 0; | this.score = 0; | ||||
| this.propInventory = null; | this.propInventory = null; | ||||
| @@ -88,6 +85,8 @@ namespace GameClass.GameObj | |||||
| this.bulletNum = maxBulletNum; | this.bulletNum = maxBulletNum; | ||||
| this.bulletOfPlayer = Occupation.InitBullet; | this.bulletOfPlayer = Occupation.InitBullet; | ||||
| this.OriBulletOfPlayer = Occupation.InitBullet; | this.OriBulletOfPlayer = Occupation.InitBullet; | ||||
| this.concealment = Occupation.Concealment; | |||||
| this.alertnessRadius = Occupation.AlertnessRadius; | |||||
| this.characterType = characterType; | this.characterType = characterType; | ||||
| foreach (var activeSkill in this.Occupation.ListOfIActiveSkill) | foreach (var activeSkill in this.Occupation.ListOfIActiveSkill) | ||||
| @@ -8,7 +8,7 @@ using System.Threading; | |||||
| namespace GameClass.GameObj | namespace GameClass.GameObj | ||||
| { | { | ||||
| public partial class Character : GameObj, ICharacter // 负责人LHR摆烂终了 | |||||
| public partial class Character : Moveable, ICharacter // 负责人LHR摆烂终了 | |||||
| { | { | ||||
| private readonly object beAttackedLock = new(); | private readonly object beAttackedLock = new(); | ||||
| @@ -175,24 +175,56 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| private Dictionary<BgmType, double> bgmDictionary = new(); | |||||
| public Dictionary<BgmType, double> BgmDictionary | |||||
| { | |||||
| get => bgmDictionary; | |||||
| set | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| bgmDictionary = value; | |||||
| } | |||||
| } | |||||
| } | |||||
| private int alertnessRadius; | |||||
| public int AlertnessRadius | |||||
| { | |||||
| get => alertnessRadius; | |||||
| set | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| alertnessRadius = value; | |||||
| } | |||||
| } | |||||
| } | |||||
| private double concealment; | |||||
| public double Concealment | |||||
| { | |||||
| get => concealment; | |||||
| set | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| concealment = value; | |||||
| } | |||||
| } | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// 进行一次远程攻击 | |||||
| /// 进行一次攻击 | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="posOffset">子弹初始位置偏差值</param> | |||||
| /// <returns>攻击操作发出的子弹</returns> | /// <returns>攻击操作发出的子弹</returns> | ||||
| public Bullet? RemoteAttack(XY posOffset) | |||||
| public Bullet? Attack(XY pos, PlaceType place) | |||||
| { | { | ||||
| if (TrySubBulletNum()) | if (TrySubBulletNum()) | ||||
| return ProduceOneBullet(this.Position + posOffset); | |||||
| return BulletFactory.GetBullet(this, place, pos); | |||||
| else | else | ||||
| return null; | return null; | ||||
| } | } | ||||
| protected Bullet? ProduceOneBullet(XY initPos) | |||||
| { | |||||
| var newBullet = BulletFactory.GetBullet(this); | |||||
| newBullet?.SetPosition(initPos); | |||||
| return newBullet; | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// 尝试将子弹数量减1 | /// 尝试将子弹数量减1 | ||||
| @@ -452,24 +484,24 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| #endregion | #endregion | ||||
| public override void Reset() // 要加锁吗? | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| // _ = AddDeathCount(); | |||||
| base.Reset(); | |||||
| this.MoveSpeed = OrgMoveSpeed; | |||||
| HP = MaxHp; | |||||
| PropInventory = null; | |||||
| BulletOfPlayer = OriBulletOfPlayer; | |||||
| lock (gameObjLock) | |||||
| bulletNum = maxBulletNum; | |||||
| /* public override void Reset() // 要加锁吗? | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| // _ = AddDeathCount(); | |||||
| base.Reset(); | |||||
| this.MoveSpeed = OrgMoveSpeed; | |||||
| HP = MaxHp; | |||||
| PropInventory = null; | |||||
| BulletOfPlayer = OriBulletOfPlayer; | |||||
| lock (gameObjLock) | |||||
| bulletNum = maxBulletNum; | |||||
| buffManager.ClearAll(); | |||||
| IsInvisible = false; | |||||
| this.Vampire = this.OriVampire; | |||||
| } | |||||
| } | |||||
| buffManager.ClearAll(); | |||||
| IsInvisible = false; | |||||
| this.Vampire = this.OriVampire; | |||||
| } | |||||
| }*/ | |||||
| public void Die(PlayerStateType playerStateType) | public void Die(PlayerStateType playerStateType) | ||||
| { | { | ||||
| lock (gameObjLock) | lock (gameObjLock) | ||||
| @@ -478,6 +510,7 @@ namespace GameClass.GameObj | |||||
| CanMove = false; | CanMove = false; | ||||
| IsResetting = true; | IsResetting = true; | ||||
| Position = GameData.PosWhoDie; | Position = GameData.PosWhoDie; | ||||
| place = PlaceType.Grass; | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,13 +7,10 @@ namespace GameClass.GameObj | |||||
| /// <summary> | /// <summary> | ||||
| /// 一切游戏元素的总基类,与THUAI4不同,继承IMoveable接口(出于一切物体其实都是可运动的指导思想)——LHR | /// 一切游戏元素的总基类,与THUAI4不同,继承IMoveable接口(出于一切物体其实都是可运动的指导思想)——LHR | ||||
| /// </summary> | /// </summary> | ||||
| public abstract class GameObj : IMoveable | |||||
| public abstract class GameObj : IGameObj | |||||
| { | { | ||||
| protected readonly object gameObjLock = new(); | protected readonly object gameObjLock = new(); | ||||
| /// <summary> | |||||
| /// 可移动物体专用锁 | |||||
| /// </summary> | |||||
| public object MoveLock => gameObjLock; | |||||
| public object GameLock => gameObjLock; | |||||
| protected readonly XY birthPos; | protected readonly XY birthPos; | ||||
| @@ -38,7 +35,9 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| public abstract bool IsRigid { get; } | |||||
| protected PlaceType place; | |||||
| public PlaceType Place { get => place; } | |||||
| private XY facingDirection = new(1, 0); | private XY facingDirection = new(1, 0); | ||||
| public XY FacingDirection | public XY FacingDirection | ||||
| @@ -50,6 +49,9 @@ namespace GameClass.GameObj | |||||
| facingDirection = value; | facingDirection = value; | ||||
| } | } | ||||
| } | } | ||||
| public abstract bool IsRigid { get; } | |||||
| public abstract ShapeType Shape { get; } | public abstract ShapeType Shape { get; } | ||||
| private bool canMove; | private bool canMove; | ||||
| @@ -65,19 +67,6 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| private bool isMoving; | |||||
| public bool IsMoving | |||||
| { | |||||
| get => isMoving; | |||||
| set | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| isMoving = value; | |||||
| } | |||||
| } | |||||
| } | |||||
| private bool isResetting; | private bool isResetting; | ||||
| public bool IsResetting | public bool IsResetting | ||||
| { | { | ||||
| @@ -90,77 +79,14 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| public bool IsAvailable => !IsMoving && CanMove && !IsResetting; // 是否能接收指令 | |||||
| public int Radius { get; } | |||||
| protected int moveSpeed; | |||||
| /// <summary> | |||||
| /// 移动速度 | |||||
| /// </summary> | |||||
| public int MoveSpeed | |||||
| { | |||||
| get => moveSpeed; | |||||
| set | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| moveSpeed = value; | |||||
| } | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// 原初移动速度 | |||||
| /// </summary> | |||||
| public int OrgMoveSpeed { get; protected set; } | |||||
| public int Radius { get; } | |||||
| // 移动,改变坐标 | |||||
| public long Move(XY moveVec) | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| FacingDirection = moveVec; | |||||
| this.Position += moveVec; | |||||
| } | |||||
| return (long)(moveVec * moveVec); | |||||
| } | |||||
| /// <summary> | |||||
| /// 设置位置 | |||||
| /// </summary> | |||||
| /// <param name="newpos">新位置</param> | |||||
| public void SetPosition(XY newpos) | |||||
| { | |||||
| Position = newpos; | |||||
| } | |||||
| /// <summary> | |||||
| /// 设置移动速度 | |||||
| /// </summary> | |||||
| /// <param name="newMoveSpeed">新速度</param> | |||||
| public void SetMoveSpeed(int newMoveSpeed) | |||||
| { | |||||
| MoveSpeed = newMoveSpeed; | |||||
| } | |||||
| /// <summary> | |||||
| /// 复活时数据重置 | |||||
| /// </summary> | |||||
| public virtual void Reset() | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| facingDirection = new XY(1, 0); | |||||
| isMoving = false; | |||||
| canMove = false; | |||||
| isResetting = true; | |||||
| this.position = birthPos; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// 为了使IgnoreCollide多态化并使GameObj能不报错地继承IMoveable | |||||
| /// 在xfgg点播下设计了这个抽象辅助方法,在具体类中实现 | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> 依具体类及该方法参数而定,默认为false </returns> | /// <returns> 依具体类及该方法参数而定,默认为false </returns> | ||||
| protected virtual bool IgnoreCollideExecutor(IGameObj targetObj) => false; | protected virtual bool IgnoreCollideExecutor(IGameObj targetObj) => false; | ||||
| bool IMoveable.IgnoreCollide(IGameObj targetObj) => IgnoreCollideExecutor(targetObj); | |||||
| bool IGameObj.IgnoreCollide(IGameObj targetObj) => IgnoreCollideExecutor(targetObj); | |||||
| public GameObj(XY initPos, int initRadius, GameObjType initType) | public GameObj(XY initPos, int initRadius, GameObjType initType) | ||||
| { | { | ||||
| this.Position = this.birthPos = initPos; | this.Position = this.birthPos = initPos; | ||||
| @@ -11,6 +11,7 @@ namespace GameClass.GameObj | |||||
| public Doorway(XY initPos) : | public Doorway(XY initPos) : | ||||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Doorway) | base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Doorway) | ||||
| { | { | ||||
| this.place = PlaceType.Doorway; | |||||
| this.CanMove = false; | this.CanMove = false; | ||||
| } | } | ||||
| public override bool IsRigid => true; | public override bool IsRigid => true; | ||||
| @@ -11,6 +11,7 @@ namespace GameClass.GameObj | |||||
| public EmergencyExit(XY initPos) : | public EmergencyExit(XY initPos) : | ||||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.EmergencyExit) | base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.EmergencyExit) | ||||
| { | { | ||||
| this.place = PlaceType.EmergencyExit; | |||||
| this.CanMove = false; | this.CanMove = false; | ||||
| } | } | ||||
| public override bool IsRigid => true; | public override bool IsRigid => true; | ||||
| @@ -10,6 +10,7 @@ namespace GameClass.GameObj | |||||
| public Generator(XY initPos) : | public Generator(XY initPos) : | ||||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Generator) | base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Generator) | ||||
| { | { | ||||
| this.place = PlaceType.Generator; | |||||
| this.CanMove = false; | this.CanMove = false; | ||||
| } | } | ||||
| public override bool IsRigid => true; | public override bool IsRigid => true; | ||||
| @@ -18,12 +18,13 @@ namespace GameClass.GameObj | |||||
| private Dictionary<GameObjType, ReaderWriterLockSlim> gameObjLockDict; | private Dictionary<GameObjType, ReaderWriterLockSlim> gameObjLockDict; | ||||
| public Dictionary<GameObjType, ReaderWriterLockSlim> GameObjLockDict => gameObjLockDict; | public Dictionary<GameObjType, ReaderWriterLockSlim> GameObjLockDict => gameObjLockDict; | ||||
| public readonly uint[,] ProtoGameMap; | |||||
| public PlaceType GetPlaceType(GameObj obj) | |||||
| public readonly uint[,] protoGameMap; | |||||
| public uint[,] ProtoGameMap => protoGameMap; | |||||
| public PlaceType GetPlaceType(IGameObj obj) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| return (PlaceType)ProtoGameMap[obj.Position.x / GameData.numOfPosGridPerCell, obj.Position.y / GameData.numOfPosGridPerCell]; | |||||
| return (PlaceType)protoGameMap[obj.Position.x / GameData.numOfPosGridPerCell, obj.Position.y / GameData.numOfPosGridPerCell]; | |||||
| } | } | ||||
| catch | catch | ||||
| { | { | ||||
| @@ -35,7 +36,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| return (PlaceType)ProtoGameMap[pos.x / GameData.numOfPosGridPerCell, pos.y / GameData.numOfPosGridPerCell]; | |||||
| return (PlaceType)protoGameMap[pos.x / GameData.numOfPosGridPerCell, pos.y / GameData.numOfPosGridPerCell]; | |||||
| } | } | ||||
| catch | catch | ||||
| { | { | ||||
| @@ -120,8 +121,8 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| ProtoGameMap = new uint[mapResource.GetLength(0), mapResource.GetLength(1)]; | |||||
| Array.Copy(mapResource, ProtoGameMap, mapResource.Length); | |||||
| protoGameMap = new uint[mapResource.GetLength(0), mapResource.GetLength(1)]; | |||||
| Array.Copy(mapResource, protoGameMap, mapResource.Length); | |||||
| birthPointList = new Dictionary<uint, XY>(GameData.numOfBirthPoint); | birthPointList = new Dictionary<uint, XY>(GameData.numOfBirthPoint); | ||||
| @@ -10,6 +10,7 @@ namespace GameClass.GameObj | |||||
| public Wall(XY initPos) : | public Wall(XY initPos) : | ||||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Wall) | base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Wall) | ||||
| { | { | ||||
| this.place = PlaceType.Wall; | |||||
| this.CanMove = false; | this.CanMove = false; | ||||
| } | } | ||||
| public override bool IsRigid => true; | public override bool IsRigid => true; | ||||
| @@ -0,0 +1,103 @@ | |||||
| using Preparation.Interface; | |||||
| using Preparation.Utility; | |||||
| using System.Threading; | |||||
| namespace GameClass.GameObj | |||||
| { | |||||
| /// <summary> | |||||
| /// 一切游戏元素的总基类,与THUAI4不同,继承IMoveable接口(出于一切物体其实都是可运动的指导思想)——LHR | |||||
| /// </summary> | |||||
| public abstract class Moveable : GameObj, IMoveable | |||||
| { | |||||
| protected readonly object moveObjLock = new(); | |||||
| public object MoveLock => moveObjLock; | |||||
| private bool isMoving; | |||||
| public bool IsMoving | |||||
| { | |||||
| get => isMoving; | |||||
| set | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| isMoving = value; | |||||
| } | |||||
| } | |||||
| } | |||||
| public bool IsAvailable => !IsMoving && CanMove && !IsResetting; // 是否能接收指令 | |||||
| protected int moveSpeed; | |||||
| /// <summary> | |||||
| /// 移动速度 | |||||
| /// </summary> | |||||
| public int MoveSpeed | |||||
| { | |||||
| get => moveSpeed; | |||||
| set | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| moveSpeed = value; | |||||
| } | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// 原初移动速度 | |||||
| /// </summary> | |||||
| public int OrgMoveSpeed { get; protected set; } | |||||
| // 移动,改变坐标 | |||||
| public long MovingSetPos(XY moveVec, PlaceType place) | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| FacingDirection = moveVec; | |||||
| this.Position += moveVec; | |||||
| this.place = place; | |||||
| } | |||||
| return moveVec * moveVec; | |||||
| } | |||||
| public void ReSetPos(XY pos, PlaceType place) | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| this.Position = pos; | |||||
| this.place = place; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// 设置移动速度 | |||||
| /// </summary> | |||||
| /// <param name="newMoveSpeed">新速度</param> | |||||
| public void SetMoveSpeed(int newMoveSpeed) | |||||
| { | |||||
| MoveSpeed = newMoveSpeed; | |||||
| } | |||||
| /* /// <summary> | |||||
| /// 复活时数据重置 | |||||
| /// </summary> | |||||
| public virtual void Reset(PlaceType place) | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| this.FacingDirection = new XY(1, 0); | |||||
| isMoving = false; | |||||
| CanMove = false; | |||||
| IsResetting = true; | |||||
| this.Position = birthPos; | |||||
| this.Place= place; | |||||
| } | |||||
| }*/ | |||||
| /// <summary> | |||||
| /// 为了使IgnoreCollide多态化并使GameObj能不报错地继承IMoveable | |||||
| /// 在xfgg点播下设计了这个抽象辅助方法,在具体类中实现 | |||||
| /// </summary> | |||||
| /// <returns> 依具体类及该方法参数而定,默认为false </returns> | |||||
| public Moveable(XY initPos, int initRadius, GameObjType initType) : base(initPos, initRadius, initType) | |||||
| { | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -6,7 +6,7 @@ namespace GameClass.GameObj | |||||
| /// <summary> | /// <summary> | ||||
| /// 所有物,具有主人(Parent)(特定玩家)属性的对象 | /// 所有物,具有主人(Parent)(特定玩家)属性的对象 | ||||
| /// </summary> | /// </summary> | ||||
| public abstract class ObjOfCharacter : GameObj, IObjOfCharacter | |||||
| public abstract class ObjOfCharacter : Moveable, IObjOfCharacter | |||||
| { | { | ||||
| private ICharacter? parent = null; // 主人 | private ICharacter? parent = null; // 主人 | ||||
| public ICharacter? Parent | public ICharacter? Parent | ||||
| @@ -11,6 +11,7 @@ namespace GameClass.GameObj | |||||
| public OutOfBoundBlock(XY initPos) : | public OutOfBoundBlock(XY initPos) : | ||||
| base(initPos, int.MaxValue, GameObjType.OutOfBoundBlock) | base(initPos, int.MaxValue, GameObjType.OutOfBoundBlock) | ||||
| { | { | ||||
| this.place = PlaceType.Wall; | |||||
| this.CanMove = false; | this.CanMove = false; | ||||
| } | } | ||||
| @@ -17,6 +17,7 @@ namespace GameClass.GameObj | |||||
| public PickedProp(Prop prop) : | public PickedProp(Prop prop) : | ||||
| base(prop.Position, prop.Radius, GameObjType.PickedProp) | base(prop.Position, prop.Radius, GameObjType.PickedProp) | ||||
| { | { | ||||
| this.place = prop.Place; | |||||
| this.PropHasPicked = prop; | this.PropHasPicked = prop; | ||||
| this.MappingID = prop.ID; | this.MappingID = prop.ID; | ||||
| } | } | ||||
| @@ -21,16 +21,13 @@ namespace GameClass.GameObj | |||||
| public abstract PropType GetPropType(); | public abstract PropType GetPropType(); | ||||
| public Prop(XY initPos, int radius = GameData.PropRadius) : | |||||
| public Prop(XY initPos, PlaceType place, int radius = GameData.PropRadius) : | |||||
| base(initPos, radius, GameObjType.Prop) | base(initPos, radius, GameObjType.Prop) | ||||
| { | { | ||||
| this.place = place; | |||||
| this.CanMove = false; | this.CanMove = false; | ||||
| this.moveSpeed = GameData.PropMoveSpeed; | this.moveSpeed = GameData.PropMoveSpeed; | ||||
| } | } | ||||
| public void SetNewPos(XY pos) | |||||
| { | |||||
| this.Position = pos; | |||||
| } | |||||
| } | } | ||||
| @@ -48,8 +45,8 @@ namespace GameClass.GameObj | |||||
| /// </summary> | /// </summary> | ||||
| public sealed class AddSpeed : Prop | public sealed class AddSpeed : Prop | ||||
| { | { | ||||
| public AddSpeed(XY initPos) : | |||||
| base(initPos) | |||||
| public AddSpeed(XY initPos, PlaceType placeType) : | |||||
| base(initPos, placeType) | |||||
| { | { | ||||
| } | } | ||||
| public override PropType GetPropType() => PropType.addSpeed; | public override PropType GetPropType() => PropType.addSpeed; | ||||
| @@ -59,8 +56,8 @@ namespace GameClass.GameObj | |||||
| /// </summary> | /// </summary> | ||||
| public sealed class AddLIFE : Prop | public sealed class AddLIFE : Prop | ||||
| { | { | ||||
| public AddLIFE(XY initPos) : | |||||
| base(initPos) | |||||
| public AddLIFE(XY initPos, PlaceType placeType) : | |||||
| base(initPos, placeType) | |||||
| { | { | ||||
| } | } | ||||
| public override PropType GetPropType() => PropType.addLIFE; | public override PropType GetPropType() => PropType.addLIFE; | ||||
| @@ -70,8 +67,7 @@ namespace GameClass.GameObj | |||||
| /// </summary> | /// </summary> | ||||
| public sealed class Shield : Prop | public sealed class Shield : Prop | ||||
| { | { | ||||
| public Shield(XY initPos) : | |||||
| base(initPos) | |||||
| public Shield(XY initPos, PlaceType placeType) : base(initPos, placeType) | |||||
| { | { | ||||
| } | } | ||||
| public override PropType GetPropType() => PropType.Shield; | public override PropType GetPropType() => PropType.Shield; | ||||
| @@ -81,8 +77,7 @@ namespace GameClass.GameObj | |||||
| /// </summary> | /// </summary> | ||||
| public sealed class Spear : Prop | public sealed class Spear : Prop | ||||
| { | { | ||||
| public Spear(XY initPos) : | |||||
| base(initPos) | |||||
| public Spear(XY initPos, PlaceType placeType) : base(initPos, placeType) | |||||
| { | { | ||||
| } | } | ||||
| public override PropType GetPropType() => PropType.Spear; | public override PropType GetPropType() => PropType.Spear; | ||||
| @@ -20,6 +20,18 @@ namespace GameEngine | |||||
| private readonly ITimer gameTimer; | private readonly ITimer gameTimer; | ||||
| private readonly Action<IMoveable> EndMove; | private readonly Action<IMoveable> EndMove; | ||||
| public readonly uint[,] ProtoGameMap; | |||||
| public PlaceType GetPlaceType(XY Position) | |||||
| { | |||||
| try | |||||
| { | |||||
| return (PlaceType)ProtoGameMap[Position.x / GameData.numOfPosGridPerCell, Position.y / GameData.numOfPosGridPerCell]; | |||||
| } | |||||
| catch | |||||
| { | |||||
| return PlaceType.Null; | |||||
| } | |||||
| } | |||||
| private readonly CollisionChecker collisionChecker; | private readonly CollisionChecker collisionChecker; | ||||
| private readonly Func<IMoveable, IGameObj, XY, AfterCollision> OnCollision; | private readonly Func<IMoveable, IGameObj, XY, AfterCollision> OnCollision; | ||||
| /// <summary> | /// <summary> | ||||
| @@ -34,6 +46,7 @@ namespace GameEngine | |||||
| Action<IMoveable> EndMove | Action<IMoveable> EndMove | ||||
| ) | ) | ||||
| { | { | ||||
| this.ProtoGameMap = gameMap.ProtoGameMap; | |||||
| this.gameTimer = gameMap.Timer; | this.gameTimer = gameMap.Timer; | ||||
| this.EndMove = EndMove; | this.EndMove = EndMove; | ||||
| this.OnCollision = OnCollision; | this.OnCollision = OnCollision; | ||||
| @@ -50,9 +63,11 @@ namespace GameEngine | |||||
| /*由于四周是墙,所以人物永远不可能与越界方块碰撞*/ | /*由于四周是墙,所以人物永远不可能与越界方块碰撞*/ | ||||
| XY nextPos = obj.Position + moveVec; | XY nextPos = obj.Position + moveVec; | ||||
| double maxLen = collisionChecker.FindMax(obj, nextPos, moveVec); | |||||
| maxLen = Math.Min(maxLen, obj.MoveSpeed / GameData.numOfStepPerSecond); | |||||
| _ = obj.Move(new XY(moveVec.Angle(), maxLen)); | |||||
| //double maxLen | |||||
| _ = collisionChecker.FindMax(obj, nextPos, moveVec); | |||||
| //maxLen = Math.Min(maxLen, obj.MoveSpeed / GameData.numOfStepPerSecond); | |||||
| obj.MovingSetPos(moveVec, GetPlaceType(nextPos)); | |||||
| } | } | ||||
| public void MoveObj(IMoveable obj, int moveTime, double direction) | public void MoveObj(IMoveable obj, int moveTime, double direction) | ||||
| @@ -69,7 +84,8 @@ namespace GameEngine | |||||
| obj.IsMoving = true; | obj.IsMoving = true; | ||||
| double moveVecLength = 0.0; | double moveVecLength = 0.0; | ||||
| double deltaLen = moveVecLength - Math.Sqrt(obj.Move(new XY(direction, moveVecLength))); // 转向,并用deltaLen存储行走的误差 | |||||
| XY res = new XY(direction, moveVecLength); | |||||
| double deltaLen = moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); // 转向,并用deltaLen存储行走的误差 | |||||
| IGameObj? collisionObj = null; | IGameObj? collisionObj = null; | ||||
| bool isDestroyed = false; | bool isDestroyed = false; | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| @@ -77,17 +93,18 @@ namespace GameEngine | |||||
| () => | () => | ||||
| { | { | ||||
| moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | ||||
| XY res = new XY(direction, moveVecLength); | |||||
| // 越界情况处理:如果越界,则与越界方块碰撞 | // 越界情况处理:如果越界,则与越界方块碰撞 | ||||
| bool flag; // 循环标志 | bool flag; // 循环标志 | ||||
| do | do | ||||
| { | { | ||||
| flag = false; | flag = false; | ||||
| collisionObj = collisionChecker.CheckCollision(obj, new XY(direction, moveVecLength)); | |||||
| collisionObj = collisionChecker.CheckCollision(obj, res); | |||||
| if (collisionObj == null) | if (collisionObj == null) | ||||
| break; | break; | ||||
| switch (OnCollision(obj, collisionObj, new XY(direction, moveVecLength))) | |||||
| switch (OnCollision(obj, collisionObj, res)) | |||||
| { | { | ||||
| case AfterCollision.ContinueCheck: | case AfterCollision.ContinueCheck: | ||||
| flag = true; | flag = true; | ||||
| @@ -97,13 +114,14 @@ namespace GameEngine | |||||
| isDestroyed = true; | isDestroyed = true; | ||||
| return false; | return false; | ||||
| case AfterCollision.MoveMax: | case AfterCollision.MoveMax: | ||||
| MoveMax(obj, new XY(direction, moveVecLength)); | |||||
| MoveMax(obj, res); | |||||
| moveVecLength = 0; | moveVecLength = 0; | ||||
| res = new XY(direction, moveVecLength); | |||||
| break; | break; | ||||
| } | } | ||||
| } while (flag); | } while (flag); | ||||
| deltaLen += moveVecLength - Math.Sqrt(obj.Move(new XY(direction, moveVecLength))); | |||||
| deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); | |||||
| return true; | return true; | ||||
| }, | }, | ||||
| @@ -118,13 +136,14 @@ namespace GameEngine | |||||
| if (!isDestroyed) | if (!isDestroyed) | ||||
| { | { | ||||
| moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; | moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; | ||||
| if ((collisionObj = collisionChecker.CheckCollision(obj, new XY(direction, moveVecLength))) == null) | |||||
| XY res = new XY(direction, moveVecLength); | |||||
| if ((collisionObj = collisionChecker.CheckCollision(obj, res)) == null) | |||||
| { | { | ||||
| obj.Move(new XY(direction, moveVecLength)); | |||||
| obj.MovingSetPos(res, GetPlaceType(obj.Position + res)); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| switch (OnCollision(obj, collisionObj, new XY(direction, moveVecLength))) | |||||
| switch (OnCollision(obj, collisionObj, res)) | |||||
| { | { | ||||
| case AfterCollision.ContinueCheck: | case AfterCollision.ContinueCheck: | ||||
| flag = true; | flag = true; | ||||
| @@ -134,8 +153,9 @@ namespace GameEngine | |||||
| isDestroyed = true; | isDestroyed = true; | ||||
| break; | break; | ||||
| case AfterCollision.MoveMax: | case AfterCollision.MoveMax: | ||||
| MoveMax(obj, new XY(direction, moveVecLength)); | |||||
| MoveMax(obj, res); | |||||
| moveVecLength = 0; | moveVecLength = 0; | ||||
| res = new XY(direction, moveVecLength); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Numerics; | |||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||
| using System.Threading; | using System.Threading; | ||||
| using GameClass.GameObj; | using GameClass.GameObj; | ||||
| @@ -38,7 +39,7 @@ namespace Gaming | |||||
| public bool Fix(Student player)// 自动检查有无发电机可修 | public bool Fix(Student player)// 自动检查有无发电机可修 | ||||
| { | { | ||||
| if (player.PlayerState != PlayerStateType.Null || player.IsGhost()) | |||||
| if (player.IsGhost() || (player.PlayerState != PlayerStateType.Null && player.PlayerState != PlayerStateType.IsMoving)) | |||||
| return false; | return false; | ||||
| Generator? generatorForFix = null; | Generator? generatorForFix = null; | ||||
| @@ -72,7 +73,7 @@ namespace Gaming | |||||
| loopCondition: () => player.PlayerState == PlayerStateType.IsFixing && gameMap.Timer.IsGaming && generatorForFix.DegreeOfFRepair < GameData.degreeOfFixedGenerator && GameData.ApproachToInteract(player.Position, generatorForFix.Position), | loopCondition: () => player.PlayerState == PlayerStateType.IsFixing && gameMap.Timer.IsGaming && generatorForFix.DegreeOfFRepair < GameData.degreeOfFixedGenerator && GameData.ApproachToInteract(player.Position, generatorForFix.Position), | ||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| return !generatorForFix.Repair(player.FixSpeed * GameData.frameDuration); | |||||
| generatorForFix.Repair(player.FixSpeed * GameData.frameDuration); | |||||
| }, | }, | ||||
| timeInterval: GameData.frameDuration, | timeInterval: GameData.frameDuration, | ||||
| finallyReturn: () => 0 | finallyReturn: () => 0 | ||||
| @@ -38,7 +38,7 @@ namespace Gaming | |||||
| ); | ); | ||||
| } | } | ||||
| public void BeAddictedToGame(Student player) | |||||
| private void BeAddictedToGame(Student player) | |||||
| { | { | ||||
| new Thread | new Thread | ||||
| (() => | (() => | ||||
| @@ -68,11 +68,10 @@ namespace Gaming | |||||
| { IsBackground = true }.Start(); | { IsBackground = true }.Start(); | ||||
| } | } | ||||
| public void Die(Character player) | |||||
| private void Die(Character player) | |||||
| { | { | ||||
| player.CanMove = false; | |||||
| player.IsResetting = true; | |||||
| player.Die(PlayerStateType.IsDeceased); | |||||
| // gameMap.GameObjLockDict[GameObjType.Character].EnterWriteLock(); | // gameMap.GameObjLockDict[GameObjType.Character].EnterWriteLock(); | ||||
| // try | // try | ||||
| //{ | //{ | ||||
| @@ -87,7 +86,8 @@ namespace Gaming | |||||
| if (player.PropInventory != null) // 若角色原来有道具,则原始道具掉落在原地 | if (player.PropInventory != null) // 若角色原来有道具,则原始道具掉落在原地 | ||||
| { | { | ||||
| dropProp = player.PropInventory; | dropProp = player.PropInventory; | ||||
| dropProp.SetNewPos(GameData.GetCellCenterPos(player.Position.x / GameData.numOfPosGridPerCell, player.Position.y / GameData.numOfPosGridPerCell)); | |||||
| 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(); | gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock(); | ||||
| try | try | ||||
| @@ -298,12 +298,14 @@ namespace Gaming | |||||
| if (player.PlayerState != PlayerStateType.Null || player.PlayerState != PlayerStateType.IsMoving) | if (player.PlayerState != PlayerStateType.Null || player.PlayerState != PlayerStateType.IsMoving) | ||||
| return false; | return false; | ||||
| Bullet? bullet = player.RemoteAttack( | |||||
| new XY // 子弹紧贴人物生成。 | |||||
| XY res = new XY // 子弹紧贴人物生成。 | |||||
| ( | ( | ||||
| (int)((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Cos(angle)), | (int)((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Cos(angle)), | ||||
| (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) | if (bullet.CastTime > 0) | ||||
| { | { | ||||
| @@ -5,6 +5,7 @@ using Preparation.Utility; | |||||
| using Timothy.FrameRateTask; | using Timothy.FrameRateTask; | ||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| using GameClass.GameObj; | using GameClass.GameObj; | ||||
| using System.Numerics; | |||||
| namespace Gaming | namespace Gaming | ||||
| { | { | ||||
| @@ -41,21 +42,14 @@ namespace Gaming | |||||
| // Console.WriteLine($"x,y: {pos.x},{pos.y}"); | // Console.WriteLine($"x,y: {pos.x},{pos.y}"); | ||||
| Character newPlayer = (GameData.IsGhost(playerInitInfo.characterType)) ? new Ghost(pos, GameData.characterRadius, playerInitInfo.characterType) : new Student(pos, GameData.characterRadius, playerInitInfo.characterType); | Character newPlayer = (GameData.IsGhost(playerInitInfo.characterType)) ? new Ghost(pos, GameData.characterRadius, playerInitInfo.characterType) : new Student(pos, GameData.characterRadius, playerInitInfo.characterType); | ||||
| gameMap.GameObjLockDict[GameObjType.Character].EnterWriteLock(); | |||||
| try | |||||
| { | |||||
| gameMap.GameObjDict[GameObjType.Character].Add(newPlayer); | |||||
| } | |||||
| finally | |||||
| { | |||||
| gameMap.GameObjLockDict[GameObjType.Character].ExitWriteLock(); | |||||
| } | |||||
| gameMap.Add(newPlayer); | |||||
| // Console.WriteLine($"GameObjDict[GameObjType.Character] length:{gameMap.GameObjDict[GameObjType.Character].Count}"); | // Console.WriteLine($"GameObjDict[GameObjType.Character] length:{gameMap.GameObjDict[GameObjType.Character].Count}"); | ||||
| teamList[(int)playerInitInfo.teamID].AddPlayer(newPlayer); | teamList[(int)playerInitInfo.teamID].AddPlayer(newPlayer); | ||||
| newPlayer.TeamID = playerInitInfo.teamID; | newPlayer.TeamID = playerInitInfo.teamID; | ||||
| newPlayer.PlayerID = playerInitInfo.playerID; | newPlayer.PlayerID = playerInitInfo.playerID; | ||||
| new Thread //人物装弹 | |||||
| #region 人物装弹 | |||||
| new Thread | |||||
| ( | ( | ||||
| () => | () => | ||||
| { | { | ||||
| @@ -63,19 +57,16 @@ namespace Gaming | |||||
| Thread.Sleep(newPlayer.CD); | Thread.Sleep(newPlayer.CD); | ||||
| long lastTime = Environment.TickCount64; | long lastTime = Environment.TickCount64; | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => gameMap.Timer.IsGaming, | |||||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| if (!newPlayer.IsResetting) | |||||
| long nowTime = Environment.TickCount64; | |||||
| if (newPlayer.BulletNum == newPlayer.MaxBulletNum) | |||||
| lastTime = nowTime; | |||||
| if (nowTime - lastTime >= newPlayer.CD) | |||||
| { | { | ||||
| long nowTime = Environment.TickCount64; | |||||
| if (newPlayer.BulletNum == newPlayer.MaxBulletNum) | |||||
| lastTime = nowTime; | |||||
| if (nowTime - lastTime >= newPlayer.CD) | |||||
| { | |||||
| _ = newPlayer.TryAddBulletNum(); | |||||
| lastTime = nowTime; | |||||
| } | |||||
| _ = newPlayer.TryAddBulletNum(); | |||||
| lastTime = nowTime; | |||||
| } | } | ||||
| }, | }, | ||||
| timeInterval: GameData.checkInterval, | timeInterval: GameData.checkInterval, | ||||
| @@ -93,6 +84,91 @@ namespace Gaming | |||||
| } | } | ||||
| ) | ) | ||||
| { IsBackground = true }.Start(); | { IsBackground = true }.Start(); | ||||
| #endregion | |||||
| #region BGM更新 | |||||
| new Thread | |||||
| ( | |||||
| () => | |||||
| { | |||||
| while (!gameMap.Timer.IsGaming) | |||||
| Thread.Sleep((int)GameData.checkInterval); | |||||
| new FrameRateTaskExecutor<int>( | |||||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, | |||||
| loopToDo: () => | |||||
| { | |||||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||||
| try | |||||
| { | |||||
| if (newPlayer.IsGhost()) | |||||
| { | |||||
| double bgmVolume = 0; | |||||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | |||||
| { | |||||
| if (!person.IsGhost() && XY.Distance(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment)) | |||||
| { | |||||
| if ((double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position) > bgmVolume) | |||||
| bgmVolume = newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position); | |||||
| } | |||||
| } | |||||
| if (bgmVolume > 0) | |||||
| newPlayer.BgmDictionary.Add(BgmType.StudentIsApproaching, bgmVolume); | |||||
| } | |||||
| else | |||||
| { | |||||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | |||||
| { | |||||
| if (person.IsGhost()) | |||||
| { | |||||
| if (XY.Distance(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment)) | |||||
| newPlayer.BgmDictionary.Add(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position)); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| finally | |||||
| { | |||||
| gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||||
| } | |||||
| gameMap.GameObjLockDict[GameObjType.Generator].EnterReadLock(); | |||||
| try | |||||
| { | |||||
| double bgmVolume = 0; | |||||
| foreach (Generator generator in gameMap.GameObjDict[GameObjType.Generator]) | |||||
| { | |||||
| if (XY.Distance(newPlayer.Position, generator.Position) <= newPlayer.AlertnessRadius) | |||||
| { | |||||
| if ((double)newPlayer.AlertnessRadius * generator.DegreeOfFRepair / GameData.degreeOfFixedGenerator / XY.Distance(newPlayer.Position, generator.Position) > bgmVolume) | |||||
| bgmVolume = (double)newPlayer.AlertnessRadius * generator.DegreeOfFRepair / GameData.degreeOfFixedGenerator / XY.Distance(newPlayer.Position, generator.Position); | |||||
| } | |||||
| } | |||||
| if (bgmVolume > 0) | |||||
| newPlayer.BgmDictionary.Add(BgmType.StudentIsApproaching, bgmVolume); | |||||
| } | |||||
| finally | |||||
| { | |||||
| gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||||
| } | |||||
| }, | |||||
| timeInterval: GameData.checkInterval, | |||||
| finallyReturn: () => 0 | |||||
| ) | |||||
| { | |||||
| AllowTimeExceed = true/*, | |||||
| MaxTolerantTimeExceedCount = 5, | |||||
| TimeExceedAction = exceedTooMuch => | |||||
| { | |||||
| if (exceedTooMuch) Console.WriteLine("The computer runs too slow that it cannot check the color below the player in time!"); | |||||
| }*/ | |||||
| } | |||||
| .Start(); | |||||
| } | |||||
| ) | |||||
| { IsBackground = true }.Start(); | |||||
| #endregion | |||||
| return newPlayer.ID; | return newPlayer.ID; | ||||
| } | } | ||||
| @@ -100,20 +176,6 @@ namespace Gaming | |||||
| { | { | ||||
| if (gameMap.Timer.IsGaming) | if (gameMap.Timer.IsGaming) | ||||
| return false; | return false; | ||||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||||
| try | |||||
| { | |||||
| foreach (Character player in gameMap.GameObjDict[GameObjType.Character]) | |||||
| { | |||||
| player.CanMove = true; | |||||
| player.AddShield(GameData.shieldTimeAtBirth); | |||||
| } | |||||
| } | |||||
| finally | |||||
| { | |||||
| gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||||
| } | |||||
| propManager.StartProducing(); | propManager.StartProducing(); | ||||
| @@ -110,7 +110,8 @@ namespace Gaming | |||||
| if (player.PropInventory != null) // 若角色原来有道具,则原始道具掉落在原地 | if (player.PropInventory != null) // 若角色原来有道具,则原始道具掉落在原地 | ||||
| { | { | ||||
| dropProp = player.PropInventory; | dropProp = player.PropInventory; | ||||
| dropProp.SetNewPos(GameData.GetCellCenterPos(player.Position.x / GameData.numOfPosGridPerCell, player.Position.y / GameData.numOfPosGridPerCell)); | |||||
| XY res = GameData.GetCellCenterPos(player.Position.x / GameData.numOfPosGridPerCell, player.Position.y / GameData.numOfPosGridPerCell); | |||||
| dropProp.ReSetPos(res, gameMap.GetPlaceType(res)); | |||||
| } | } | ||||
| player.PropInventory = pickProp; | player.PropInventory = pickProp; | ||||
| gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock(); | gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock(); | ||||
| @@ -151,7 +152,7 @@ namespace Gaming | |||||
| return; | return; | ||||
| prop.CanMove = true; | prop.CanMove = true; | ||||
| prop.SetNewPos(player.Position); | |||||
| prop.ReSetPos(player.Position, gameMap.GetPlaceType(player.Position)); | |||||
| gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock(); | gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock(); | ||||
| try | try | ||||
| { | { | ||||
| @@ -187,16 +188,16 @@ namespace Gaming | |||||
| switch (r.Next(0, 4)) | switch (r.Next(0, 4)) | ||||
| { | { | ||||
| case 0: | case 0: | ||||
| gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new AddLIFE(randPos)); | |||||
| gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new AddLIFE(randPos, gameMap.GetPlaceType(randPos))); | |||||
| break; | break; | ||||
| case 1: | case 1: | ||||
| gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new AddSpeed(randPos)); | |||||
| gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new AddSpeed(randPos, gameMap.GetPlaceType(randPos))); | |||||
| break; | break; | ||||
| case 2: | case 2: | ||||
| gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new Shield(randPos)); | |||||
| gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new Shield(randPos, gameMap.GetPlaceType(randPos))); | |||||
| break; | break; | ||||
| case 3: | case 3: | ||||
| gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new Spear(randPos)); | |||||
| gameMap.GameObjDict[GameObjType.Prop].Add((Preparation.Interface.IGameObj)new Spear(randPos, gameMap.GetPlaceType(randPos))); | |||||
| break; | break; | ||||
| default: | default: | ||||
| break; | break; | ||||
| @@ -230,11 +231,11 @@ namespace Gaming | |||||
| } | } | ||||
| ); | ); | ||||
| availableCellForGenerateProp = new List<XY>(); | availableCellForGenerateProp = new List<XY>(); | ||||
| for (int i = 0; i < gameMap.ProtoGameMap.GetLength(0); i++) | |||||
| for (int i = 0; i < gameMap.protoGameMap.GetLength(0); i++) | |||||
| { | { | ||||
| for (int j = 0; j < gameMap.ProtoGameMap.GetLength(1); j++) | |||||
| for (int j = 0; j < gameMap.protoGameMap.GetLength(1); j++) | |||||
| { | { | ||||
| if (gameMap.ProtoGameMap[i, j] == (int)PlaceType.Null) | |||||
| if (gameMap.protoGameMap[i, j] == (int)PlaceType.Null) | |||||
| { | { | ||||
| availableCellForGenerateProp.Add(GameData.GetCellCenterPos(i, j)); | availableCellForGenerateProp.Add(GameData.GetCellCenterPos(i, j)); | ||||
| } | } | ||||
| @@ -7,13 +7,13 @@ namespace Preparation.Interface | |||||
| public GameObjType Type { get; } | public GameObjType Type { get; } | ||||
| public long ID { get; } | public long ID { get; } | ||||
| public XY Position { get; } // if Square, Pos equals the center | public XY Position { get; } // if Square, Pos equals the center | ||||
| public PlaceType Place { get; } | |||||
| public XY FacingDirection { get; } | public XY FacingDirection { get; } | ||||
| public bool IsRigid { get; } | public bool IsRigid { get; } | ||||
| public ShapeType Shape { get; } | public ShapeType Shape { get; } | ||||
| public bool CanMove { get; set; } | public bool CanMove { get; set; } | ||||
| public bool IsMoving { get; set; } | |||||
| public bool IsResetting { get; set; } // reviving | public bool IsResetting { get; set; } // reviving | ||||
| public bool IsAvailable { get; } | |||||
| public int Radius { get; } // if Square, Radius equals half length of one side | public int Radius { get; } // if Square, Radius equals half length of one side | ||||
| protected bool IgnoreCollide(IGameObj targetObj); // 忽略碰撞,在具体类中实现 | |||||
| } | } | ||||
| } | } | ||||
| @@ -12,6 +12,8 @@ namespace Preparation.Interface | |||||
| Dictionary<GameObjType, IList<IGameObj>> GameObjDict { get; } | Dictionary<GameObjType, IList<IGameObj>> GameObjDict { get; } | ||||
| Dictionary<GameObjType, ReaderWriterLockSlim> GameObjLockDict { get; } | Dictionary<GameObjType, ReaderWriterLockSlim> GameObjLockDict { get; } | ||||
| public uint[,] ProtoGameMap { get; } | |||||
| public PlaceType GetPlaceType(IGameObj obj); | |||||
| public bool IsOutOfBound(IGameObj obj); | public bool IsOutOfBound(IGameObj obj); | ||||
| public IOutOfBound GetOutOfBound(XY pos); // 返回新建的一个OutOfBound对象 | public IOutOfBound GetOutOfBound(XY pos); // 返回新建的一个OutOfBound对象 | ||||
| } | } | ||||
| @@ -7,8 +7,10 @@ namespace Preparation.Interface | |||||
| { | { | ||||
| object MoveLock { get; } | object MoveLock { get; } | ||||
| public int MoveSpeed { get; } | public int MoveSpeed { get; } | ||||
| public long Move(XY moveVec); | |||||
| protected bool IgnoreCollide(IGameObj targetObj); // 忽略碰撞,在具体类中实现 | |||||
| public bool IsMoving { get; set; } | |||||
| public bool IsAvailable { get; } | |||||
| public long MovingSetPos(XY moveVec, PlaceType place); | |||||
| public void ReSetPos(XY pos, PlaceType place); | |||||
| public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞 | public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞 | ||||
| { | { | ||||
| if (targetObj == null) | if (targetObj == null) | ||||
| @@ -12,6 +12,8 @@ namespace Preparation.Interface | |||||
| public int MaxBulletNum { get; } | public int MaxBulletNum { get; } | ||||
| public List<ActiveSkillType> ListOfIActiveSkill { get; } | public List<ActiveSkillType> ListOfIActiveSkill { get; } | ||||
| public List<PassiveSkillType> ListOfIPassiveSkill { get; } | public List<PassiveSkillType> ListOfIPassiveSkill { get; } | ||||
| public double Concealment { get; } | |||||
| public int AlertnessRadius { get; } | |||||
| } | } | ||||
| public interface IGhost : IOccupation | public interface IGhost : IOccupation | ||||
| @@ -41,6 +43,12 @@ namespace Preparation.Interface | |||||
| public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { ActiveSkillType.BecomeInvisible, ActiveSkillType.UseKnife }); | public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { ActiveSkillType.BecomeInvisible, ActiveSkillType.UseKnife }); | ||||
| public List<PassiveSkillType> ListOfIPassiveSkill => new(new PassiveSkillType[] { }); | public List<PassiveSkillType> ListOfIPassiveSkill => new(new PassiveSkillType[] { }); | ||||
| public double concealment = GameData.basicConcealment * 1.5; | |||||
| public double Concealment => concealment; | |||||
| public int alertnessRadius = (int)(GameData.basicAlertnessRadius * 1.3); | |||||
| public int AlertnessRadius => alertnessRadius; | |||||
| } | } | ||||
| public class Athlete : IStudent | public class Athlete : IStudent | ||||
| { | { | ||||
| @@ -61,6 +69,13 @@ namespace Preparation.Interface | |||||
| public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { ActiveSkillType.BeginToCharge }); | public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { ActiveSkillType.BeginToCharge }); | ||||
| public List<PassiveSkillType> ListOfIPassiveSkill => new(new PassiveSkillType[] { }); | public List<PassiveSkillType> ListOfIPassiveSkill => new(new PassiveSkillType[] { }); | ||||
| public int FixSpeed => GameData.basicFixSpeed / 10 * 6; | |||||
| public const int fixSpeed = GameData.basicFixSpeed / 10 * 6; | |||||
| public int FixSpeed => fixSpeed; | |||||
| public const double concealment = GameData.basicConcealment * 0.9; | |||||
| public double Concealment => concealment; | |||||
| public const int alertnessRadius = (int)(GameData.basicAlertnessRadius * 0.9); | |||||
| public int AlertnessRadius => alertnessRadius; | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Reflection.Metadata.Ecma335; | using System.Reflection.Metadata.Ecma335; | ||||
| using System.Threading; | |||||
| namespace Preparation.Utility | namespace Preparation.Utility | ||||
| { | { | ||||
| @@ -75,12 +76,15 @@ namespace Preparation.Utility | |||||
| public const int basicMoveSpeed = 3800; // 基本移动速度,单位:s-1 | public const int basicMoveSpeed = 3800; // 基本移动速度,单位:s-1 | ||||
| public const int basicBulletMoveSpeed = 5400; // 基本子弹移动速度,单位:s-1 | public const int basicBulletMoveSpeed = 5400; // 基本子弹移动速度,单位:s-1 | ||||
| public const int characterMaxSpeed = 12000; // 最大速度 | public const int characterMaxSpeed = 12000; // 最大速度 | ||||
| public const double basicConcealment = 1.0; | |||||
| public const int basicAlertnessRadius = 30700; | |||||
| public const int addScoreWhenKillOneLevelPlayer = 30; // 击杀一级角色获得的加分 | public const int addScoreWhenKillOneLevelPlayer = 30; // 击杀一级角色获得的加分 | ||||
| public const int commonSkillCD = 30000; // 普通技能标准冷却时间 | public const int commonSkillCD = 30000; // 普通技能标准冷却时间 | ||||
| public const int commonSkillTime = 10000; // 普通技能标准持续时间 | public const int commonSkillTime = 10000; // 普通技能标准持续时间 | ||||
| public const int bulletRadius = 200; // 默认子弹半径 | public const int bulletRadius = 200; // 默认子弹半径 | ||||
| public const int reviveTime = 30000; // 复活时间 | public const int reviveTime = 30000; // 复活时间 | ||||
| public const int shieldTimeAtBirth = 3000; // 复活时的护盾时间 | public const int shieldTimeAtBirth = 3000; // 复活时的护盾时间 | ||||
| public static XY PosWhoDie = new XY(1, 1); | public static XY PosWhoDie = new XY(1, 1); | ||||
| public static bool IsGhost(CharacterType characterType) | public static bool IsGhost(CharacterType characterType) | ||||
| @@ -101,9 +105,12 @@ namespace Preparation.Utility | |||||
| public const long GemProduceTime = 10000; | public const long GemProduceTime = 10000; | ||||
| public const long PropProduceTime = 10000; | public const long PropProduceTime = 10000; | ||||
| public const int PropDuration = 10000; | public const int PropDuration = 10000; | ||||
| #endregion | |||||
| #region 物体相关 | |||||
| public const int degreeOfFixedGenerator = 10300000; | public const int degreeOfFixedGenerator = 10300000; | ||||
| #endregion | #endregion | ||||
| #region 游戏帧相关 | #region 游戏帧相关 | ||||
| public const long checkInterval = 50; // 检查位置标志、补充子弹的帧时长 | public const long checkInterval = 50; // 检查位置标志、补充子弹的帧时长 | ||||
| #endregion | #endregion | ||||
| @@ -292,7 +292,7 @@ namespace Server | |||||
| public override Task<MoveRes> Move(MoveMsg request, ServerCallContext context) | public override Task<MoveRes> Move(MoveMsg request, ServerCallContext context) | ||||
| { | { | ||||
| Console.WriteLine($"Move ID: {request.PlayerId}, TimeInMilliseconds: {request.TimeInMilliseconds}"); | |||||
| Console.WriteLine($"SetPos ID: {request.PlayerId}, TimeInMilliseconds: {request.TimeInMilliseconds}"); | |||||
| var gameID = communicationToGameID[PlayerTypeToTeamID(request.PlayerType), request.PlayerId]; | var gameID = communicationToGameID[PlayerTypeToTeamID(request.PlayerType), request.PlayerId]; | ||||
| game.MovePlayer(gameID, (int)request.TimeInMilliseconds, request.Angle); | game.MovePlayer(gameID, (int)request.TimeInMilliseconds, request.Angle); | ||||
| // 之后game.MovePlayer可能改为bool类型 | // 之后game.MovePlayer可能改为bool类型 | ||||
| @@ -1,7 +1,7 @@ | |||||
| # 规则Logic | # 规则Logic | ||||
| ## 说明 | ## 说明 | ||||
| - 版本V1.003 | |||||
| - 版本V2.0 | |||||
| - 该规则直接服务于Sever,并非选手版本 | - 该规则直接服务于Sever,并非选手版本 | ||||
| - *斜体表示Logic底层尚未(完全)实现* | - *斜体表示Logic底层尚未(完全)实现* | ||||
| - []表示待决定 | - []表示待决定 | ||||
| @@ -23,13 +23,11 @@ | |||||
| - 底层实现中的属性,不代表界面全部都需要展示,也可能需要额外展示信息 | - 底层实现中的属性,不代表界面全部都需要展示,也可能需要额外展示信息 | ||||
| - 只展示外部需要的属性,部分属性被省略 | - 只展示外部需要的属性,部分属性被省略 | ||||
| ### Bgm | |||||
| - *double bgmVolume* | |||||
| - *BgmType bgmType* | |||||
| 对于枚举类BgmType | |||||
| 1. *不详的感觉:监管者进入(求生者的警戒半径/监管者的隐蔽度)时,求生者收到;监管者距离求生者越近,Bgm音量越大。bgmVolume=(警戒半径/二者距离)* | |||||
| 2. *期待搞事的感觉:求生者进入(监管者的警戒半径/求生者的隐蔽度)时,监管者收到;监管者距离求生者越近,Bgm音量2.越大。bgmVolume=(警戒半径/二者距离)* | |||||
| 3. *修理电机的声音: 警戒半径内有电机正在被修理时,全员收到;bgmVolume=(警戒半径/二者距离)*电机修理程度/10300000* | |||||
| ### BgmType | |||||
| - 枚举类BgmType | |||||
| 1. 不详的感觉:监管者进入(求生者的警戒半径/监管者的隐蔽度)时,求生者收到;监管者距离求生者越近,Bgm音量越大。bgmVolume=(警戒半径/二者距离) | |||||
| 2. 期待搞事的感觉:求生者进入(监管者的警戒半径/求生者的隐蔽度)时,监管者收到;监管者距离求生者越近,Bgm音量越大。bgmVolume=(警戒半径/可被发觉的最近的求生者距离) | |||||
| 3. 修理电机的声音: 警戒半径内有电机正在被修理时收到;bgmVolume=(警戒半径*电机修理程度/二者距离)/10300000 | |||||
| ~~~csharp | ~~~csharp | ||||
| public enum BgmType | public enum BgmType | ||||
| { | { | ||||
| @@ -58,6 +56,7 @@ | |||||
| ### 物体 | ### 物体 | ||||
| - 位置 | - 位置 | ||||
| - 位置地形 | |||||
| - ID | - ID | ||||
| - 类型 | - 类型 | ||||
| - 面向角度 | - 面向角度 | ||||
| @@ -95,20 +94,20 @@ | |||||
| IsClimbingThroughWindows = 15, | IsClimbingThroughWindows = 15, | ||||
| } | } | ||||
| ~~~ | ~~~ | ||||
| - *Bgm(数组)* | |||||
| - *Bgm(字典)* | |||||
| - 得分 | - 得分 | ||||
| - ~~回血率/原始回血率~~ | - ~~回血率/原始回血率~~ | ||||
| - 当前子弹类型 | - 当前子弹类型 | ||||
| - 原始子弹类型 | - 原始子弹类型 | ||||
| - 持有道具*(最多三个)(数组)* | |||||
| - 持有道具 *(最多三个)(列表)* | |||||
| - 是否隐身 | - 是否隐身 | ||||
| - 队伍ID | - 队伍ID | ||||
| - 玩家ID | - 玩家ID | ||||
| - 当前Buff | - 当前Buff | ||||
| - 职业类型 | - 职业类型 | ||||
| - 拥有的被动技能(数组) | |||||
| - 拥有的主动技能(数组) | |||||
| - 各个主动技能CD(数组) | |||||
| - 拥有的被动技能(列表) | |||||
| - 拥有的主动技能(列表) | |||||
| - 各个主动技能CD(字典) | |||||
| - *警戒半径* | - *警戒半径* | ||||
| - *double 隐蔽度* | - *double 隐蔽度* | ||||
| - *翻墙速度* | - *翻墙速度* | ||||