diff --git a/dependency/proto/.clang-format b/dependency/proto/.clang-format
old mode 100755
new mode 100644
diff --git a/dependency/proto/Message2Clients.proto b/dependency/proto/Message2Clients.proto
old mode 100755
new mode 100644
diff --git a/dependency/proto/Message2Server.proto b/dependency/proto/Message2Server.proto
old mode 100755
new mode 100644
diff --git a/dependency/proto/MessageType.proto b/dependency/proto/MessageType.proto
old mode 100755
new mode 100644
diff --git a/dependency/proto/README.md b/dependency/proto/README.md
old mode 100755
new mode 100644
diff --git a/dependency/proto/cpp_output.sh b/dependency/proto/cpp_output.sh
old mode 100755
new mode 100644
diff --git a/dependency/proto/format.sh b/dependency/proto/format.sh
old mode 100755
new mode 100644
diff --git a/logic/GameClass/GameObj/Character.cs b/logic/GameClass/GameObj/Character.cs
new file mode 100644
index 0000000..9631793
--- /dev/null
+++ b/logic/GameClass/GameObj/Character.cs
@@ -0,0 +1,457 @@
+using Preparation.GameData;
+using Preparation.Interface;
+using Preparation.Utility;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace GameClass.GameObj
+{
+ public partial class Character : GameObj, ICharacter // 负责人LHR摆烂终了
+ {
+ private readonly object beAttackedLock = new();
+
+#region 角色的基本属性及方法,包括与道具的交互方法
+ /*
+ //THUAI5子弹
+ ///
+ /// 装弹冷却
+ ///
+ protected int cd;
+ public int CD
+ {
+ get => cd;
+ private
+ set {
+ lock (gameObjLock)
+ {
+ cd = value;
+ Debugger.Output(this, string.Format("'s CD has been set to: {0}.", value));
+ }
+ }
+ }
+ public int OrgCD { get; protected set; }
+ protected int maxBulletNum;
+ public int MaxBulletNum => maxBulletNum; // 人物最大子弹数
+ protected int bulletNum;
+ public int BulletNum => bulletNum; // 目前持有的子弹数
+ */
+ public int MaxHp { get; protected set; } // 最大血量
+ protected int hp;
+ public int HP
+ {
+ get => hp;
+ set {
+ lock (gameObjLock)
+ hp = value <= MaxHp ? value : MaxHp;
+ }
+ }
+ private int deathCount = 0;
+ public int DeathCount => deathCount; // 玩家的死亡次数
+
+ private int score = 0;
+ public int Score
+ {
+ get => score;
+ }
+
+ // public double AttackRange => BulletFactory.BulletAttackRange(this.BulletOfPlayer);
+
+ private double vampire = 0; // 回血率:0-1之间
+ public double Vampire
+ {
+ get => vampire;
+ set {
+ if (value > 1)
+ lock (gameObjLock)
+ vampire = 1;
+ else if (value < 0)
+ lock (gameObjLock)
+ vampire = 0;
+ else
+ lock (gameObjLock)
+ vampire = value;
+ }
+ }
+ private double OriVampire { get; }
+
+ /*
+ public readonly BulletType OriBulletOfPlayer;
+ private BulletType bulletOfPlayer;
+ public BulletType BulletOfPlayer
+ {
+ get => bulletOfPlayer;
+ set {
+ lock (gameObjLock)
+ bulletOfPlayer = value;
+ }
+ }
+ */
+
+ private Prop? propInventory;
+ public Prop? PropInventory // 持有的道具
+ {
+ get => propInventory;
+ set {
+ lock (gameObjLock)
+ {
+ propInventory = value;
+ Debugger.Output(this, " prop becomes " + (PropInventory == null ? "null" : PropInventory.ToString()));
+ }
+ }
+ }
+
+ ///
+ /// 使用物品栏中的道具
+ ///
+ /// 被使用的道具
+ public Prop? UseProp()
+ {
+ lock (gameObjLock)
+ {
+ var oldProp = PropInventory;
+ PropInventory = null;
+ return oldProp;
+ }
+ }
+
+ ///
+ /// 是否正在更换道具(包括捡起与抛出)
+ ///
+ private bool isModifyingProp = false;
+ public bool IsModifyingProp
+ {
+ get => isModifyingProp;
+ set {
+ lock (gameObjLock)
+ {
+ isModifyingProp = value;
+ }
+ }
+ }
+
+ ///
+ /// 是否在隐身
+ ///
+ private bool isInvisible = false;
+ public bool IsInvisible
+ {
+ get => isInvisible;
+ set {
+ lock (gameObjLock)
+ {
+ isInvisible = value;
+ }
+ }
+ }
+ /*
+ ///
+ /// 进行一次远程攻击
+ ///
+ /// 子弹初始位置偏差值
+ /// 攻击操作发出的子弹
+ public Bullet? RemoteAttack(XY posOffset)
+ {
+ if (TrySubBulletNum())
+ return ProduceOneBullet(this.Position + posOffset);
+ else
+ return null;
+ }
+ protected Bullet? ProduceOneBullet(XY initPos)
+ {
+ var newBullet = BulletFactory.GetBullet(this);
+ newBullet?.SetPosition(initPos);
+ return newBullet;
+ }
+
+ ///
+ /// 尝试将子弹数量减1
+ ///
+ /// 减操作是否成功
+ private bool TrySubBulletNum()
+ {
+ lock (gameObjLock)
+ {
+ if (bulletNum > 0)
+ {
+ --bulletNum;
+ return true;
+ }
+ return false;
+ }
+ }
+ ///
+ /// 尝试将子弹数量加1
+ ///
+ /// 加操作是否成功
+ public bool TryAddBulletNum()
+ {
+ lock (gameObjLock)
+ {
+ if (bulletNum < maxBulletNum)
+ {
+ ++bulletNum;
+ return true;
+ }
+ return false;
+ }
+ }
+ */
+ ///
+ /// 尝试加血
+ ///
+ /// 欲加量
+ /// 加操作是否成功
+ public bool TryAddHp(int add)
+ {
+ if (hp < MaxHp)
+ {
+ lock (gameObjLock)
+ hp = MaxHp > hp + add ? hp + add : MaxHp;
+ Debugger.Output(this, " hp has added to: " + hp.ToString());
+ return true;
+ }
+ return false;
+ }
+ ///
+ /// 尝试减血
+ ///
+ /// 减血量
+ /// 减操作是否成功
+ public bool TrySubHp(int sub)
+ {
+ if (hp > 0)
+ {
+ lock (gameObjLock)
+ hp = 0 >= hp - sub ? 0 : hp - sub;
+ Debugger.Output(this, " hp has subed to: " + hp.ToString());
+ return true;
+ }
+ return false;
+ }
+ ///
+ /// 增加死亡次数
+ ///
+ /// 当前死亡次数
+ private int AddDeathCount()
+ {
+ lock (gameObjLock)
+ {
+ ++deathCount;
+ return deathCount;
+ }
+ }
+ ///
+ /// 加分
+ ///
+ /// 增加量
+ public void AddScore(int add)
+ {
+ lock (gameObjLock)
+ {
+ score += add;
+ Debugger.Output(this, " 's score has been added to: " + score.ToString());
+ }
+ }
+ ///
+ /// 减分
+ ///
+ /// 减少量
+ public void SubScore(int sub)
+ {
+ lock (gameObjLock)
+ {
+ score -= sub;
+ Debugger.Output(this, " 's score has been subed to: " + score.ToString());
+ }
+ }
+ /*
+ ///
+ /// 遭受攻击
+ ///
+ ///
+ ///
+ /// 伤害来源
+ /// 人物在受到攻击后死了吗
+ public bool BeAttack(Bullet bullet)
+ {
+
+ lock (beAttackedLock)
+ {
+ if (hp <= 0)
+ return false; // 原来已经死了
+ if (bullet.Parent.TeamID != this.TeamID)
+ {
+
+ if (HasShield)
+ {
+ if (bullet.HasSpear)
+ _ = TrySubHp(bullet.AP);
+ else
+ return false;
+ }
+ else
+ {
+ TrySubHp(bullet.AP);
+ }
+#if DEBUG
+ Console.WriteLine($"PlayerID:{ID} is being shot! Now his hp is {hp}.");
+#endif
+ if (hp <= 0)
+ TryActivatingLIFE(); // 如果有复活甲
+ }
+ return hp <= 0;
+ }
+ }
+ ///
+ /// 攻击被反弹,反弹伤害不会再被反弹
+ ///
+ ///
+ ///
+ /// 反弹伤害者
+ /// 是否因反弹伤害而死
+ private bool BeBounced(int subHP, bool hasSpear, Character? bouncer)
+ {
+ lock (beAttackedLock)
+ {
+ if (hp <= 0)
+ return false;
+ if (!(bouncer?.TeamID == this.TeamID))
+ {
+ if (hasSpear || !HasShield)
+ _ = TrySubHp(subHP);
+ if (hp <= 0)
+ TryActivatingLIFE();
+ }
+ return hp <= 0;
+ }
+ }
+ */
+ ///
+ /// 角色所属队伍ID
+ ///
+ private long teamID = long.MaxValue;
+ public long TeamID
+ {
+ get => teamID;
+ set {
+ lock (gameObjLock)
+ {
+ teamID = value;
+ Debugger.Output(this, " joins in the team: " + value.ToString());
+ }
+ }
+ }
+ private long playerID = long.MaxValue;
+ public long PlayerID
+ {
+ get => playerID;
+ set {
+ lock (gameObjLock)
+ {
+ playerID = value;
+ }
+ }
+ }
+ ///
+ /// 角色携带的信息
+ ///
+ private string message = "THUAI5";
+ public string Message
+ {
+ get => message;
+ set {
+ lock (gameObjLock)
+ {
+ message = value;
+ }
+ }
+ }
+#endregion
+
+#region 角色拥有的buff相关属性、方法
+ public void AddMoveSpeed(int buffTime, double add = 2.0) => buffManeger.AddMoveSpeed(add, buffTime, newVal =>
+ { MoveSpeed = newVal < GameData.characterMaxSpeed ? newVal : GameData.characterMaxSpeed; },
+ OrgMoveSpeed);
+ public bool HasFasterSpeed => buffManeger.HasFasterSpeed;
+
+ public void AddShield(int shieldTime) => buffManeger.AddShield(shieldTime);
+ public bool HasShield => buffManeger.HasShield;
+
+ public void AddLIFE(int LIFETime) => buffManeger.AddLIFE(LIFETime);
+ public bool HasLIFE => buffManeger.HasLIFE;
+
+ public void AddSpear(int spearTime) => buffManeger.AddSpear(spearTime);
+ public bool HasSpear => buffManeger.HasSpear;
+
+ private Array buffTypeArray = Enum.GetValues(typeof(BuffType));
+ public Dictionary Buff
+ {
+ get {
+ Dictionary buff = new Dictionary();
+ foreach (BuffType type in buffTypeArray)
+ {
+ if (type != BuffType.Null)
+ buff.Add(type, GetBuffStatus(type));
+ }
+ return buff;
+ }
+ }
+ private bool GetBuffStatus(BuffType type)
+ {
+ switch (type)
+ {
+ case BuffType.Spear:
+ return this.HasSpear;
+ case BuffType.AddSpeed:
+ return this.HasFasterSpeed;
+ case BuffType.Shield:
+ return this.HasShield;
+ case BuffType.AddLIFE:
+ return this.HasLIFE;
+ default:
+ return false;
+ }
+ }
+ private void TryActivatingLIFE()
+ {
+ if (buffManeger.TryActivatingLIFE())
+ {
+ hp = MaxHp;
+ }
+ }
+#endregion
+ public override void Reset() // 要加锁吗?
+ {
+ _ = AddDeathCount();
+ base.Reset();
+ this.MoveSpeed = OrgMoveSpeed;
+ HP = MaxHp;
+ PropInventory = null;
+ // BulletOfPlayer = OriBulletOfPlayer;
+ // lock (gameObjLock)
+ // bulletNum = maxBulletNum;
+
+ buffManeger.ClearAll();
+ IsInvisible = false;
+ this.Vampire = this.OriVampire;
+ }
+ public override bool IsRigid => true;
+ public override ShapeType Shape => ShapeType.Circle;
+ protected override bool IgnoreCollideExecutor(IGameObj targetObj)
+ {
+ if (targetObj.Type == GameObjType.BirthPoint)
+ {
+ if (object.ReferenceEquals(((BirthPoint)targetObj).Parent, this)) // 自己的出生点可以忽略碰撞
+ {
+ return true;
+ }
+ }
+ else if (targetObj.Type == GameObjType.Prop) // 自己队的地雷忽略碰撞
+ {
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/logic/GameClass/GameObj/GameObj.cs b/logic/GameClass/GameObj/GameObj.cs
new file mode 100644
index 0000000..27f2abd
--- /dev/null
+++ b/logic/GameClass/GameObj/GameObj.cs
@@ -0,0 +1,185 @@
+using Preparation.Interface;
+using Preparation.Utility;
+using System.Threading;
+
+namespace GameClass.GameObj
+{
+ ///
+ /// 一切游戏元素的总基类,与THUAI4不同,继承IMoveable接口(出于一切物体其实都是可运动的指导思想)——LHR
+ ///
+ public abstract class GameObj : IMoveable
+ {
+ protected readonly object gameObjLock = new();
+ ///
+ /// 可移动物体专用锁
+ ///
+ public object MoveLock => gameObjLock;
+
+ protected readonly XY birthPos;
+
+ private GameObjType type;
+ public GameObjType Type => type;
+
+ private static long currentMaxID = 0; // 目前游戏对象的最大ID
+ public const long invalidID = long.MaxValue; // 无效的ID
+ public const long noneID = long.MinValue;
+ public long ID { get; }
+
+ private XY position;
+ public XY Position
+ {
+ get => position;
+ protected
+ set {
+ lock (gameObjLock)
+ {
+ position = value;
+ }
+ }
+ }
+ public abstract bool IsRigid { get; }
+
+ private XY facingDirection = new(1, 0);
+ public XY FacingDirection
+ {
+ get => facingDirection;
+ set {
+ lock (gameObjLock)
+ facingDirection = value;
+ }
+ }
+ public abstract ShapeType Shape { get; }
+
+ private bool canMove;
+ public bool CanMove
+ {
+ get => canMove;
+ set {
+ lock (gameObjLock)
+ {
+ canMove = value;
+ }
+ }
+ }
+
+ private bool isMoving;
+ public bool IsMoving
+ {
+ get => isMoving;
+ set {
+ lock (gameObjLock)
+ {
+ isMoving = value;
+ }
+ }
+ }
+
+ private bool isResetting;
+ public bool IsResetting
+ {
+ get => isResetting;
+ set {
+ lock (gameObjLock)
+ {
+ isResetting = value;
+ }
+ }
+ }
+ public bool IsAvailable => !IsMoving && CanMove && !IsResetting; // 是否能接收指令
+ public int Radius { get; }
+
+ private PlaceType place;
+ public PlaceType Place
+ {
+ get => place;
+ set {
+ lock (gameObjLock)
+ {
+ place = value;
+ }
+ }
+ }
+ protected int moveSpeed;
+ ///
+ /// 移动速度
+ ///
+ public int MoveSpeed
+ {
+ get => moveSpeed;
+ set {
+ lock (gameObjLock)
+ {
+ moveSpeed = value;
+ }
+ }
+ }
+ ///
+ /// 原初移动速度
+ ///
+ private int orgMoveSpeed;
+ public int OrgMoveSpeed
+ {
+ get => orgMoveSpeed;
+ protected
+ set {
+ orgMoveSpeed = value;
+ }
+ }
+
+ // 移动,改变坐标
+ public long Move(XY moveVec)
+ {
+ lock (gameObjLock)
+ {
+ FacingDirection = moveVec;
+ this.Position += moveVec;
+ }
+ return (long)(moveVec * moveVec);
+ }
+ ///
+ /// 设置位置
+ ///
+ /// 新位置
+ public void SetPosition(XY newpos)
+ {
+ Position = newpos;
+ }
+ ///
+ /// 设置移动速度
+ ///
+ /// 新速度
+ public void SetMoveSpeed(int newMoveSpeed)
+ {
+ MoveSpeed = newMoveSpeed;
+ }
+ ///
+ /// 复活时数据重置
+ ///
+ public virtual void Reset()
+ {
+ lock (gameObjLock)
+ {
+ facingDirection = new XY(1, 0);
+ isMoving = false;
+ canMove = false;
+ isResetting = true;
+ this.position = birthPos;
+ }
+ }
+ ///
+ /// 为了使IgnoreCollide多态化并使GameObj能不报错地继承IMoveable
+ /// 在xfgg点播下设计了这个抽象辅助方法,在具体类中实现
+ ///
+ /// 依具体类及该方法参数而定,默认为false
+ protected virtual bool IgnoreCollideExecutor(IGameObj targetObj) => false;
+ bool IMoveable.IgnoreCollide(IGameObj targetObj) => IgnoreCollideExecutor(targetObj);
+ public GameObj(XY initPos, int initRadius, PlaceType initPlace, GameObjType initType)
+ {
+ this.Position = this.birthPos = initPos;
+ this.Radius = initRadius;
+ this.place = initPlace;
+ this.type = initType;
+ ID = Interlocked.Increment(ref currentMaxID);
+ }
+ }
+}
diff --git a/logic/GameClass/GameObj/Map/BirthPoint.cs b/logic/GameClass/GameObj/Map/BirthPoint.cs
new file mode 100644
index 0000000..3d4ab37
--- /dev/null
+++ b/logic/GameClass/GameObj/Map/BirthPoint.cs
@@ -0,0 +1,29 @@
+using Preparation.Interface;
+using Preparation.Utility;
+using Preparation.GameData;
+
+namespace GameClass.GameObj
+{
+ ///
+ /// 出生点
+ ///
+ public class BirthPoint : ObjOfCharacter
+ {
+ public BirthPoint(XY initPos) :
+ base(initPos, GameData.numOfPosGridPerCell / 2, PlaceType.Land, GameObjType.BirthPoint)
+ {
+ this.CanMove = false;
+ }
+ // 修改建议:需要避免非自己的玩家进入出生点,否则会重叠
+ public override bool IsRigid => true;
+ protected override bool IgnoreCollideExecutor(IGameObj targetObj)
+ {
+ if (targetObj.Type != GameObjType.Character)
+ return true; // 非玩家不碰撞
+ else if (targetObj.Type == GameObjType.Character && targetObj.ID == this.Parent.ID)
+ return true; // 出生点所属的玩家不碰撞
+ return false;
+ }
+ public override ShapeType Shape => ShapeType.Square;
+ }
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/ObjOfCharacter.cs b/logic/GameClass/GameObj/ObjOfCharacter.cs
new file mode 100644
index 0000000..a09c6e5
--- /dev/null
+++ b/logic/GameClass/GameObj/ObjOfCharacter.cs
@@ -0,0 +1,28 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj
+{
+ ///
+ /// 所有物,具有主人(Parent)(特定玩家)属性的对象
+ ///
+ public abstract class ObjOfCharacter : GameObj, IObjOfCharacter
+ {
+ private ICharacter? parent = null; // 主人
+ public ICharacter? Parent
+ {
+ get => parent;
+ set {
+ lock (gameObjLock)
+ {
+ parent = value;
+ }
+ }
+ }
+ // LHR注:本来考虑在构造函数里设置parent属性,见THUAI4在游戏引擎中才设置该属性,作罢。——2021/9/24
+ public ObjOfCharacter(XY initPos, int initRadius, PlaceType initPlace, GameObjType initType) :
+ base(initPos, initRadius, initPlace, initType)
+ {
+ }
+ }
+}
diff --git a/logic/GameEngine/CollisionChecker.cs b/logic/GameEngine/CollisionChecker.cs
new file mode 100644
index 0000000..08ca0f7
--- /dev/null
+++ b/logic/GameEngine/CollisionChecker.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Preparation.Interface;
+using Preparation.Utility;
+using Preparation.GameData;
+
+namespace GameEngine
+{
+ internal class CollisionChecker
+ {
+ ///
+ /// 碰撞检测,如果这样行走是否会与之碰撞,返回与之碰撞的物体
+ ///
+ /// 移动的物体
+ /// 移动的位移向量
+ /// 和它碰撞的物体
+ public IGameObj? CheckCollision(IMoveable obj, XY moveVec)
+ {
+ XY nextPos = obj.Position + XY.VectorToXY(moveVec);
+ if (!obj.IsRigid)
+ {
+ if (gameMap.IsOutOfBound(obj))
+ return gameMap.GetOutOfBound(nextPos);
+ return null;
+ }
+ // 在列表中检查碰撞
+ Func, ReaderWriterLockSlim, IGameObj?> CheckCollisionInList =
+ (IEnumerable lst, ReaderWriterLockSlim listLock) =>
+ {
+ IGameObj? collisionObj = null;
+ listLock.EnterReadLock();
+ try
+ {
+ foreach (var listObj in lst)
+ {
+ if (obj.WillCollideWith(listObj, nextPos))
+ {
+ collisionObj = listObj;
+ break;
+ }
+ }
+ }
+ finally
+ {
+ listLock.ExitReadLock();
+ }
+ return collisionObj;
+ };
+
+ IGameObj? collisionObj = null;
+ foreach (var list in lists)
+ {
+ if ((collisionObj = CheckCollisionInList(list.Item1, list.Item2)) != null)
+ {
+ return collisionObj;
+ }
+ }
+
+ return null;
+ }
+ ///
+ /// /// 可移动物体(圆)向矩形物体移动时,可移动且不会碰撞的最大距离。直接用double计算,防止误差
+ ///
+ ///
+ /// 矩形的中心坐标
+ ///
+ // private double MaxMoveToSquare(IMoveable obj, IGameObj square)
+ //{
+ // double tmpMax;
+ // double angle = Math.Atan2(square.Position.y - obj.Position.y, square.Position.x - obj.Position.x);
+ // if (obj.WillCollideWith(square, obj.Position))
+ // tmpMax = 0;
+ // else tmpMax =
+ // Math.Abs(XYPosition.Distance(obj.Position, square.Position) - obj.Radius -
+ // (square.Radius / Math.Min(Math.Abs(Math.Cos(angle)), Math.Abs(Math.Sin(angle)))));
+ // return tmpMax;
+ // }
+
+ // private double FindMaxOnlyConsiderWall(IMoveable obj, Vector moveVec)
+ //{
+ // var desination = moveVec;
+ // double maxOnlyConsiderWall = moveVec.length;
+ // if (desination.length > 0) //如果length足够长,还是有可能穿墙的
+ // {
+ // XYPosition nextXY = Vector.Vector2XY(desination) + obj.Position + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle)), (int)(obj.Radius * Math.Sin(moveVec.angle)));
+ // if (gameMap.IsWall(nextXY)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置
+ // {
+ // maxOnlyConsiderWall = MaxMoveToSquare(obj, gameMap.GetCell(nextXY));
+ // }
+ // else //考虑物体宽度
+ // {
+ // double dist = 0;
+ // XYPosition nextXYConsiderWidth;
+ // nextXYConsiderWidth = nextXY + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle + Math.PI / 4)), (int)(obj.Radius * Math.Sin(moveVec.angle + Math.PI / 4)));
+ // if (gameMap.IsWall(nextXYConsiderWidth)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置
+ // {
+ // dist = MaxMoveToSquare(obj, gameMap.GetCell(nextXYConsiderWidth));
+ // if (dist < maxOnlyConsiderWall)
+ // maxOnlyConsiderWall = dist;
+ // }
+ // nextXYConsiderWidth = nextXY + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle - Math.PI / 4)), (int)(obj.Radius * Math.Sin(moveVec.angle - Math.PI / 4)));
+ // if (gameMap.IsWall(nextXYConsiderWidth)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置
+ // {
+ // dist = MaxMoveToSquare(obj, gameMap.GetCell(nextXYConsiderWidth));
+ // if (dist < maxOnlyConsiderWall)
+ // maxOnlyConsiderWall = dist;
+ // }
+ // }
+ // }
+ // return maxOnlyConsiderWall;
+ // }
+
+ ///
+ /// 寻找最大可能移动距离
+ ///
+ /// 移动物体,默认obj.Rigid为true
+ /// 下一步要到达的位置
+ /// 移动的位移向量,默认与nextPos协调
+ /// 最大可能的移动距离
+ public double FindMax(IMoveable obj, XY nextPos, XY moveVec)
+ {
+ double maxLen = (double)uint.MaxValue;
+ double tmpMax = maxLen; // 暂存最大值
+
+ // 先找只考虑墙的最大距离
+ // double maxOnlyConsiderWall = FindMaxOnlyConsiderWall(obj, moveVec);
+ double maxDistance = maxLen;
+ foreach (var listWithLock in lists)
+ {
+ var lst = listWithLock.Item1;
+ var listLock = listWithLock.Item2;
+ listLock.EnterReadLock();
+ try
+ {
+ foreach (IGameObj listObj in lst)
+ {
+ // 如果再走一步发生碰撞
+ if (obj.WillCollideWith(listObj, nextPos))
+ {
+ {
+ switch (listObj.Shape) // 默认obj为圆形
+ {
+ case ShapeType.Circle:
+ {
+ // 计算两者之间的距离
+ double mod = XY.Distance(listObj.Position, obj.Position);
+ int orgDeltaX = listObj.Position.x - obj.Position.x;
+ int orgDeltaY = listObj.Position.y - obj.Position.y;
+
+ if (mod < listObj.Radius + obj.Radius) // 如果两者已经重叠
+ {
+ tmpMax = 0;
+ }
+ else
+ {
+ double tmp = mod - obj.Radius - listObj.Radius;
+ // 计算能走的最长距离,好像这么算有一点误差?
+ tmp = tmp / Math.Cos(Math.Atan2(orgDeltaY, orgDeltaX) - moveVec.angle);
+ if (tmp < 0 || tmp > uint.MaxValue || tmp == double.NaN)
+ {
+ tmpMax = uint.MaxValue;
+ }
+ else
+ tmpMax = tmp;
+ }
+ break;
+ }
+ case ShapeType.Square:
+ {
+ // if (obj.WillCollideWith(listObj, obj.Position))
+ // tmpMax = 0;
+ // else tmpMax = MaxMoveToSquare(obj, listObj);
+ // break;
+ if (obj.WillCollideWith(listObj, obj.Position))
+ tmpMax = 0;
+ else
+ {
+ // 二分查找最大可能移动距离
+ int left = 0, right = (int)moveVec.length;
+ while (left < right - 1)
+ {
+ int mid = (right - left) / 2 + left;
+ if (obj.WillCollideWith(listObj, obj.Position + new XY((int)(mid * Math.Cos(moveVec.angle)), (int)(mid * Math.Sin(moveVec.angle)))))
+ {
+ right = mid;
+ }
+ else
+ left = mid;
+ }
+ tmpMax = (uint)left;
+ }
+ break;
+ }
+ default:
+ tmpMax = uint.MaxValue;
+ break;
+ }
+ if (tmpMax < maxDistance)
+ maxDistance = tmpMax;
+ }
+ }
+ }
+ }
+ finally
+ {
+ // maxLen = Math.Min(maxOnlyConsiderWall, maxDistance); //最大可能距离的最小值
+ listLock.ExitReadLock();
+ }
+ }
+ // return maxLen;
+ return maxDistance;
+ }
+
+ readonly IMap gameMap;
+ private readonly Tuple, ReaderWriterLockSlim>[] lists;
+
+ public CollisionChecker(IMap gameMap)
+ {
+ this.gameMap = gameMap;
+ lists = new Tuple, ReaderWriterLockSlim>[gameMap.GameObjDict.Count];
+ int i = 0;
+ foreach (var keyValuePair in gameMap.GameObjDict)
+ {
+ lists[i++] = new Tuple, ReaderWriterLockSlim>(keyValuePair.Value as IList, gameMap.GameObjLockDict[keyValuePair.Key]);
+ }
+ }
+ }
+}
diff --git a/logic/GameEngine/GameEngine.csproj b/logic/GameEngine/GameEngine.csproj
new file mode 100644
index 0000000..2adc507
--- /dev/null
+++ b/logic/GameEngine/GameEngine.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Library
+ net6.0
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/logic/GameEngine/MoveEngine.cs b/logic/GameEngine/MoveEngine.cs
new file mode 100644
index 0000000..867dae3
--- /dev/null
+++ b/logic/GameEngine/MoveEngine.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Threading;
+using Preparation.Interface;
+using Preparation.Utility;
+using Timothy.FrameRateTask;
+using Preparation.GameData;
+
+namespace GameEngine
+{
+ public class MoveEngine
+ {
+ ///
+ /// 碰撞结束后要做的事情
+ ///
+ public enum AfterCollision
+ {
+ ContinueCheck = 0, // 碰撞后继续检查其他碰撞
+ MoveMax = 1, // 行走最远距离
+ Destroyed = 2 // 物体已经毁坏
+ }
+
+ private readonly ITimer gameTimer;
+ private readonly Action EndMove;
+ private readonly CollisionChecker collisionChecker;
+ private readonly Func OnCollision;
+ ///
+ /// Constrctor
+ ///
+ /// 游戏地图
+ /// 发生碰撞时要做的事情,第一个参数为移动的物体,第二个参数为撞到的物体,第三个参数为移动的位移向量,返回值见AfterCollision的定义
+ /// 结束碰撞时要做的事情
+ public MoveEngine(
+ IMap gameMap,
+ Func OnCollision,
+ Action EndMove
+ )
+ {
+ this.gameTimer = gameMap.Timer;
+ this.EndMove = EndMove;
+ this.OnCollision = OnCollision;
+ this.collisionChecker = new CollisionChecker(gameMap);
+ }
+
+ ///
+ /// 在无碰撞的前提下行走最远的距离
+ ///
+ /// 移动物体,默认obj.Rigid为true
+ /// 移动的位移向量
+ private void MoveMax(IMoveable obj, XY moveVec)
+ {
+
+ /*由于四周是墙,所以人物永远不可能与越界方块碰撞*/
+ XY nextPos = obj.Position + XY.VectorToXY(moveVec);
+ double maxLen = collisionChecker.FindMax(obj, nextPos, moveVec);
+ maxLen = Math.Min(maxLen, obj.MoveSpeed / GameData.numOfStepPerSecond);
+ _ = obj.Move(new Vector(moveVec.angle, maxLen));
+ }
+
+ public void MoveObj(IMoveable obj, int moveTime, double direction)
+ {
+ if (obj.IsMoving) // 已经移动的物体不能再移动
+ return;
+ new Thread
+ (
+ ()=>
+ {
+ if (!obj.IsAvailable&&gameTimer.IsGaming) //不能动就直接return,后面都是能动的情况
+ return;
+ lock (obj.MoveLock)
+ obj.IsMoving = true;
+
+ XY moveVec = new(direction, 0.0);
+ double deltaLen = moveVec.length - Math.Sqrt(obj.Move(moveVec)); // 转向,并用deltaLen存储行走的误差
+ IGameObj? collisionObj = null;
+ bool isDestroyed = false;
+ new FrameRateTaskExecutor(
+ () => gameTimer.IsGaming && obj.CanMove && !obj.IsResetting,
+ () =>
+ {
+ moveVec.length = obj.MoveSpeed / GameData.numOfStepPerSecond;
+
+ // 越界情况处理:如果越界,则与越界方块碰撞
+ bool flag; // 循环标志
+ do
+ {
+ flag = false;
+ collisionObj = collisionChecker.CheckCollision(obj, moveVec);
+ if (collisionObj == null)
+ break;
+
+ switch (OnCollision(obj, collisionObj, moveVec))
+ {
+ case AfterCollision.ContinueCheck:
+ flag = true;
+ break;
+ case AfterCollision.Destroyed:
+ Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
+ isDestroyed = true;
+ return false;
+ case AfterCollision.MoveMax:
+ MoveMax(obj, moveVec);
+ moveVec.length = 0;
+ break;
+ }
+ } while (flag);
+
+ deltaLen += moveVec.length - Math.Sqrt(obj.Move(moveVec));
+
+ return true;
+ },
+ GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond,
+ () =>
+ {
+ int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond);
+ bool flag;
+ do
+ {
+ flag = false;
+ if (!isDestroyed)
+ {
+ moveVec.length = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell;
+ if ((collisionObj = collisionChecker.CheckCollision(obj, moveVec)) == null)
+ {
+ obj.Move(moveVec);
+ }
+ else
+ {
+ switch (OnCollision(obj, collisionObj, moveVec))
+ {
+ case AfterCollision.ContinueCheck:
+ flag = true;
+ break;
+ case AfterCollision.Destroyed:
+ Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
+ isDestroyed = true;
+ break;
+ case AfterCollision.MoveMax:
+ MoveMax(obj, moveVec);
+ moveVec.length = 0;
+ break;
+ }
+ }
+ }
+ } while (flag);
+ if (leftTime > 0)
+ {
+ Thread.Sleep(leftTime); // 多移动的在这里补回来
+ }
+ lock (obj.MoveLock)
+ obj.IsMoving = false; // 结束移动
+ EndMove(obj);
+ return 0;
+ },
+ maxTotalDuration: moveTime
+ ) {
+ AllowTimeExceed = true,
+ MaxTolerantTimeExceedCount = ulong.MaxValue,
+ TimeExceedAction = b =>
+ {
+ if (b)
+ Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!");
+
+#if DEBUG
+ else
+ {
+ Console.WriteLine("Debug info: Object moving time exceed for once.");
+ }
+#endif
+ }
+ }.Start();
+ }
+ ).Start();
+ }
+}
+}
diff --git a/logic/Preparation/GameData/GameData.cs b/logic/Preparation/GameData/GameData.cs
index 2d13d58..0903b6d 100644
--- a/logic/Preparation/GameData/GameData.cs
+++ b/logic/Preparation/GameData/GameData.cs
@@ -16,20 +16,20 @@ namespace Preparation.GameData
public const int MinSpeed = 1; // 最小速度
public const int MaxSpeed = int.MaxValue; // 最大速度
- public static XYPosition GetCellCenterPos(int x, int y) // 求格子的中心坐标
+ public static XY GetCellCenterPos(int x, int y) // 求格子的中心坐标
{
- XYPosition ret = new((x * numOfPosGridPerCell) + (numOfPosGridPerCell / 2), (y * numOfPosGridPerCell) + (numOfPosGridPerCell / 2));
+ XY ret = new((x * numOfPosGridPerCell) + (numOfPosGridPerCell / 2), (y * numOfPosGridPerCell) + (numOfPosGridPerCell / 2));
return ret;
}
- public static int PosGridToCellX(XYPosition pos) // 求坐标所在的格子的x坐标
+ public static int PosGridToCellX(XY pos) // 求坐标所在的格子的x坐标
{
return pos.x / numOfPosGridPerCell;
}
- public static int PosGridToCellY(XYPosition pos) // 求坐标所在的格子的y坐标
+ public static int PosGridToCellY(XY pos) // 求坐标所在的格子的y坐标
{
return pos.y / numOfPosGridPerCell;
}
- public static bool IsInTheSameCell(XYPosition pos1, XYPosition pos2)
+ public static bool IsInTheSameCell(XY pos1, XY pos2)
{
return PosGridToCellX(pos1) == PosGridToCellX(pos2) && PosGridToCellY(pos1) == PosGridToCellY(pos2);
}
diff --git a/logic/Preparation/Interface/IGameObj.cs b/logic/Preparation/Interface/IGameObj.cs
index a32a1c5..9be3aab 100644
--- a/logic/Preparation/Interface/IGameObj.cs
+++ b/logic/Preparation/Interface/IGameObj.cs
@@ -4,10 +4,10 @@ namespace Preparation.Interface
{
public interface IGameObj
{
- public GameObjType Type { get; set; }
+ public GameObjType Type { get; }
public long ID { get; }
- public XYPosition Position { get; } // if Square, Pos equals the center
- public double FacingDirection { get; }
+ public XY Position { get; } // if Square, Pos equals the center
+ public XY FacingDirection { get; }
public bool IsRigid { get; }
public ShapeType Shape { get; }
public bool CanMove { get; set; }
diff --git a/logic/Preparation/Interface/IMap.cs b/logic/Preparation/Interface/IMap.cs
index 0d781b3..4ad6e18 100644
--- a/logic/Preparation/Interface/IMap.cs
+++ b/logic/Preparation/Interface/IMap.cs
@@ -13,6 +13,6 @@ namespace Preparation.Interface
Dictionary GameObjLockDict { get; }
public bool IsOutOfBound(IGameObj obj);
- public IOutOfBound GetOutOfBound(XYPosition pos); // 返回新建的一个OutOfBound对象
+ public IOutOfBound GetOutOfBound(XY pos); // 返回新建的一个OutOfBound对象
}
}
diff --git a/logic/Preparation/Interface/IMoveable.cs b/logic/Preparation/Interface/IMoveable.cs
index bc16f5b..ff23718 100644
--- a/logic/Preparation/Interface/IMoveable.cs
+++ b/logic/Preparation/Interface/IMoveable.cs
@@ -7,9 +7,9 @@ namespace Preparation.Interface
{
object MoveLock { get; }
public int MoveSpeed { get; }
- public long Move(Vector moveVec);
- protected bool IgnoreCollide(IGameObj targetObj); // 忽略碰撞,在具体类中实现
- public bool WillCollideWith(IGameObj? targetObj, XYPosition nextPos) // 检查下一位置是否会和目标物碰撞
+ public long Move(XY moveVec);
+ protected bool IgnoreCollide(IGameObj targetObj); // 忽略碰撞,在具体类中实现
+ public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞
{
if (targetObj == null)
return false;
@@ -20,7 +20,7 @@ namespace Preparation.Interface
return false;
if (targetObj.Shape == ShapeType.Circle)
{
- return XYPosition.Distance(nextPos, targetObj.Position) < targetObj.Radius + Radius;
+ return XY.Distance(nextPos, targetObj.Position) < targetObj.Radius + Radius;
}
else // Square
{
diff --git a/logic/Preparation/Utility/EnumType.cs b/logic/Preparation/Utility/EnumType.cs
index 5dfb8cc..1d20b79 100644
--- a/logic/Preparation/Utility/EnumType.cs
+++ b/logic/Preparation/Utility/EnumType.cs
@@ -27,15 +27,16 @@ namespace Preparation.Utility
Circle = 1, // 子弹和人物为圆形,格子为方形
Square = 2
}
- public enum PlaceType // 位置标志,包括陆地(一般默认为陆地,如墙体等),草丛。游戏中每一帧都要刷新各个物体的该属性
+ public enum PlaceType // 位置标志,包括陆地,建筑,草丛。游戏中每一帧都要刷新各个物体的该属性
{
Null = 0,
Land = 1,
- Grass1 = 2,
- Grass2 = 3,
- Grass3 = 4,
- Grass4 = 5,
- Grass5 = 6,
+ Building = 2,
+ Grass1 = 3,
+ Grass2 = 4,
+ Grass3 = 5,
+ Grass4 = 6,
+ Grass5 = 7,
}
public enum BulletType // 子弹类型
{
@@ -54,7 +55,7 @@ namespace Preparation.Utility
Spear = 4,
Gem = 5, // 新增:宝石
}
- public enum PassiveSkillType // 被动技能
+ public enum CharacterType // 职业
{
Null = 0,
RecoverAfterBattle = 1,
diff --git a/logic/Preparation/Utility/Tools.cs b/logic/Preparation/Utility/Tools.cs
deleted file mode 100644
index 78ee847..0000000
--- a/logic/Preparation/Utility/Tools.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-
-namespace Preparation.Utility
-{
- public static class Tools
- {
- public static double CorrectAngle(double angle) // 将幅角转化为主值0~2pi
- {
- if (double.IsNaN(angle) || double.IsInfinity(angle))
- {
- return 0.0;
- }
- while (angle < 0)
- angle += 2 * Math.PI;
- while (angle >= 2 * Math.PI)
- angle -= 2 * Math.PI;
- return angle;
- }
- }
-}
diff --git a/logic/Preparation/Utility/Vector.cs b/logic/Preparation/Utility/Vector.cs
deleted file mode 100644
index 50614f0..0000000
--- a/logic/Preparation/Utility/Vector.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using System;
-
-namespace Preparation.Utility
-{
- public struct Vector
- {
- public double angle;
- public double length;
-
- public static XYPosition VectorToXY(Vector v)
- {
- return new XYPosition((int)(v.length * Math.Cos(v.angle)), (int)(v.length * Math.Sin(v.angle)));
- }
- public Vector2 ToVector2()
- {
- return new Vector2((int)(this.length * Math.Cos(this.angle)), (int)(this.length * Math.Sin(this.angle)));
- }
- public static Vector XYToVector(double x, double y)
- {
- return new Vector(Math.Atan2(y, x), Math.Sqrt((x * x) + (y * y)));
- }
- public Vector(double angle, double length)
- {
- if (length < 0)
- {
- angle += Math.PI;
- length = -length;
- }
- this.angle = Tools.CorrectAngle(angle);
- this.length = length;
- }
- }
-
- public struct Vector2
- {
- public double x;
- public double y;
- public Vector2(double x, double y)
- {
- this.x = x;
- this.y = y;
- }
-
- public static double operator*(Vector2 v1, Vector2 v2)
- {
- return (v1.x * v2.x) + (v1.y * v2.y);
- }
- public static Vector2 operator +(Vector2 v1, Vector2 v2)
- {
- return new Vector2(v1.x + v2.x, v1.y + v2.y);
- }
- public static Vector2 operator -(Vector2 v1, Vector2 v2)
- {
- return new Vector2(v1.x - v2.x, v1.y - v2.y);
- }
- }
-}
diff --git a/logic/Preparation/Utility/XY.cs b/logic/Preparation/Utility/XY.cs
new file mode 100644
index 0000000..ff28482
--- /dev/null
+++ b/logic/Preparation/Utility/XY.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Preparation.Utility
+{
+
+ public struct XY
+ {
+ public int x;
+ public int y;
+ public XY(int x, int y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+ public override string ToString()
+ {
+ return "(" + x.ToString() + "," + y.ToString() + ")";
+ }
+ public static int operator*(XY v1, XY v2)
+ {
+ return (v1.x * v2.x) + (v1.y * v2.y);
+ }
+ public static XY operator +(XY v1, XY v2)
+ {
+ return new XY(v1.x + v2.x, v1.y + v2.y);
+ }
+ public static XY operator -(XY v1, XY v2)
+ {
+ return new XY(v1.x - v2.x, v1.y - v2.y);
+ }
+ public static double Distance(XY p1, XY p2)
+ {
+ return Math.Sqrt(((long)(p1.x - p2.x) * (p1.x - p2.x)) + ((long)(p1.y - p2.y) * (p1.y - p2.y)));
+ }
+ }
+}
diff --git a/logic/Preparation/Utility/XYPosition.cs b/logic/Preparation/Utility/XYPosition.cs
deleted file mode 100644
index 55647ec..0000000
--- a/logic/Preparation/Utility/XYPosition.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System;
-
-namespace Preparation.Utility
-{
- public struct XYPosition
- {
- public int x;
- public int y;
- public XYPosition(int x, int y)
- {
- this.x = x;
- this.y = y;
- }
- public override string ToString()
- {
- return "(" + x.ToString() + "," + y.ToString() + ")";
- }
- public static XYPosition operator +(XYPosition p1, XYPosition p2)
- {
- return new XYPosition(p1.x + p2.x, p1.y + p2.y);
- }
- public static XYPosition operator -(XYPosition p1, XYPosition p2)
- {
- return new XYPosition(p1.x - p2.x, p1.y - p2.y);
- }
- public static double Distance(XYPosition p1, XYPosition p2)
- {
- return Math.Sqrt(((long)(p1.x - p2.x) * (p1.x - p2.x)) + ((long)(p1.y - p2.y) * (p1.y - p2.y)));
- }
- /*public static XYPosition[] GetSquareRange(uint edgeLen) // 从THUAI4的BULLET.CS移植而来,不知还有用否
- {
- XYPosition[] range = new XYPosition[edgeLen * edgeLen];
- int offset = (int)(edgeLen >> 1);
- for (int i = 0; i < (int)edgeLen; ++i)
- {
- for (int j = 0; j < (int)edgeLen; ++j)
- {
- range[i * edgeLen + j].x = i - offset;
- range[i * edgeLen + j].y = j - offset;
- }
- }
- return range;
- }*/
- public Vector2 ToVector2()
- {
- return new Vector2(this.x, this.y);
- }
- }
-}