diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..763efa6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,24 @@ +name: build +on: [push, pull_request] +jobs: + dotnet-build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: Setup .NET Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + + - name: Build Logic + run: dotnet build "./logic/logic.sln" -c Release + + - name: Build Installer + run: dotnet build "./installer/installer.sln" -c Release + + - name: Build Launcher + run: dotnet build "./launcher/launcher.sln" -c Release + + - name: Build Playback + run: dotnet build "./playback/playback.sln" -c Release + diff --git a/CAPI/cpp/API/include/API.h b/CAPI/cpp/API/include/API.h index 0e99b41..88c15d0 100644 --- a/CAPI/cpp/API/include/API.h +++ b/CAPI/cpp/API/include/API.h @@ -61,6 +61,13 @@ public: virtual bool StartTreatMate() = 0; virtual bool StartRescueMate() = 0; + virtual bool OpenDoor() = 0; + virtual bool CloseDoor() = 0; + virtual bool SkipWindow() = 0; + virtual bool StartOpenGate() = 0; + virtual bool StartOpenChest() = 0; + virtual bool EndAllAction() = 0; + // ITrickerAPI使用的部分 virtual bool Attack(double angle) = 0; @@ -84,6 +91,14 @@ public: virtual std::future PickProp(THUAI6::PropType prop) = 0; virtual std::future UseProp(THUAI6::PropType prop) = 0; virtual std::future UseSkill(int32_t skillID) = 0; + virtual std::future Attack(double angleInRadian) = 0; + + virtual std::future OpenDoor() = 0; + virtual std::future CloseDoor() = 0; + virtual std::future SkipWindow() = 0; + virtual std::future StartOpenGate() = 0; + virtual std::future StartOpenChest() = 0; + virtual std::future EndAllAction() = 0; // 发送信息、接受信息,注意收消息时无消息则返回nullopt virtual std::future SendMessage(int64_t, std::string) = 0; @@ -149,7 +164,6 @@ class ITrickerAPI : public IAPI public: /*****捣蛋鬼阵营的特定函数*****/ - virtual std::future Attack(double angleInRadian) = 0; [[nodiscard]] virtual std::shared_ptr GetSelfInfo() const = 0; }; @@ -190,6 +204,15 @@ public: std::future UseProp(THUAI6::PropType prop) override; std::future UseSkill(int32_t skillID) override; + std::future Attack(double angleInRadian) override; + + std::future OpenDoor() override; + std::future CloseDoor() override; + std::future SkipWindow() override; + std::future StartOpenGate() override; + std::future StartOpenChest() override; + std::future EndAllAction() override; + std::future SendMessage(int64_t, std::string) override; [[nodiscard]] std::future HaveMessage() override; [[nodiscard]] std::future>> GetMessage() override; @@ -256,6 +279,13 @@ public: std::future UseProp(THUAI6::PropType prop) override; std::future UseSkill(int32_t skillID) override; + std::future OpenDoor() override; + std::future CloseDoor() override; + std::future SkipWindow() override; + std::future StartOpenGate() override; + std::future StartOpenChest() override; + std::future EndAllAction() override; + std::future SendMessage(int64_t, std::string) override; [[nodiscard]] std::future HaveMessage() override; [[nodiscard]] std::future>> GetMessage() override; @@ -312,6 +342,15 @@ public: std::future UseProp(THUAI6::PropType prop) override; std::future UseSkill(int32_t skillID) override; + std::future Attack(double angleInRadian) override; + + std::future OpenDoor() override; + std::future CloseDoor() override; + std::future SkipWindow() override; + std::future StartOpenGate() override; + std::future StartOpenChest() override; + std::future EndAllAction() override; + std::future SendMessage(int64_t, std::string) override; [[nodiscard]] std::future HaveMessage() override; [[nodiscard]] std::future>> GetMessage() override; @@ -365,6 +404,13 @@ public: std::future UseProp(THUAI6::PropType prop) override; std::future UseSkill(int32_t skillID) override; + std::future OpenDoor() override; + std::future CloseDoor() override; + std::future SkipWindow() override; + std::future StartOpenGate() override; + std::future StartOpenChest() override; + std::future EndAllAction() override; + std::future SendMessage(int64_t, std::string) override; [[nodiscard]] std::future HaveMessage() override; [[nodiscard]] std::future>> GetMessage() override; diff --git a/CAPI/cpp/API/include/logic.h b/CAPI/cpp/API/include/logic.h index eefa8d4..c4c21b9 100644 --- a/CAPI/cpp/API/include/logic.h +++ b/CAPI/cpp/API/include/logic.h @@ -118,6 +118,13 @@ private: bool Attack(double angle) override; + bool OpenDoor() override; + bool CloseDoor() override; + bool SkipWindow() override; + bool StartOpenGate() override; + bool StartOpenChest() override; + bool EndAllAction() override; + bool WaitThread() override; int GetCounter() const override; diff --git a/CAPI/cpp/API/src/API.cpp b/CAPI/cpp/API/src/API.cpp index 3be14f2..2afe849 100644 --- a/CAPI/cpp/API/src/API.cpp +++ b/CAPI/cpp/API/src/API.cpp @@ -101,6 +101,78 @@ std::future TrickerAPI::UseSkill(int32_t skillID) { return logic.UseSkill(skillID); }); } +std::future StudentAPI::OpenDoor() +{ + return std::async(std::launch::async, [&]() + { return logic.OpenDoor(); }); +} + +std::future TrickerAPI::OpenDoor() +{ + return std::async(std::launch::async, [&]() + { return logic.OpenDoor(); }); +} + +std::future StudentAPI::CloseDoor() +{ + return std::async(std::launch::async, [&]() + { return logic.CloseDoor(); }); +} + +std::future TrickerAPI::CloseDoor() +{ + return std::async(std::launch::async, [&]() + { return logic.CloseDoor(); }); +} + +std::future StudentAPI::SkipWindow() +{ + return std::async(std::launch::async, [&]() + { return logic.SkipWindow(); }); +} + +std::future TrickerAPI::SkipWindow() +{ + return std::async(std::launch::async, [&]() + { return logic.SkipWindow(); }); +} + +std::future StudentAPI::StartOpenGate() +{ + return std::async(std::launch::async, [&]() + { return logic.StartOpenGate(); }); +} + +std::future TrickerAPI::StartOpenGate() +{ + return std::async(std::launch::async, [&]() + { return logic.StartOpenGate(); }); +} + +std::future StudentAPI::StartOpenChest() +{ + return std::async(std::launch::async, [&]() + { return logic.StartOpenChest(); }); +} + +std::future TrickerAPI::StartOpenChest() +{ + return std::async(std::launch::async, [&]() + { return logic.StartOpenChest(); }); +} + +std::future StudentAPI::EndAllAction() +{ + return std::async(std::launch::async, [&]() + { return logic.EndAllAction(); }); +} + +std::future TrickerAPI::EndAllAction() +{ + return std::async(std::launch::async, [&]() + { return logic.EndAllAction(); }); +} + std::future StudentAPI::SendMessage(int64_t toID, std::string message) { return std::async(std::launch::async, [&]() @@ -252,6 +324,12 @@ std::future TrickerAPI::Attack(double angleInRadian) { return logic.Attack(angleInRadian); }); } +std::future StudentAPI::Attack(double angleInRadian) +{ + return std::async(std::launch::async, [&]() + { return logic.Attack(angleInRadian); }); +} + std::shared_ptr TrickerAPI::GetSelfInfo() const { return logic.TrickerGetSelfInfo(); diff --git a/CAPI/cpp/API/src/DebugAPI.cpp b/CAPI/cpp/API/src/DebugAPI.cpp index 6995a17..e5aeed7 100644 --- a/CAPI/cpp/API/src/DebugAPI.cpp +++ b/CAPI/cpp/API/src/DebugAPI.cpp @@ -206,6 +206,78 @@ std::future TrickerDebugAPI::UseSkill(int32_t skillID) return result; }); } +std::future StudentDebugAPI::OpenDoor() +{ + return std::async(std::launch::async, [&]() + { return logic.OpenDoor(); }); +} + +std::future TrickerDebugAPI::OpenDoor() +{ + return std::async(std::launch::async, [&]() + { return logic.OpenDoor(); }); +} + +std::future StudentDebugAPI::CloseDoor() +{ + return std::async(std::launch::async, [&]() + { return logic.CloseDoor(); }); +} + +std::future TrickerDebugAPI::CloseDoor() +{ + return std::async(std::launch::async, [&]() + { return logic.CloseDoor(); }); +} + +std::future StudentDebugAPI::SkipWindow() +{ + return std::async(std::launch::async, [&]() + { return logic.SkipWindow(); }); +} + +std::future TrickerDebugAPI::SkipWindow() +{ + return std::async(std::launch::async, [&]() + { return logic.SkipWindow(); }); +} + +std::future StudentDebugAPI::StartOpenGate() +{ + return std::async(std::launch::async, [&]() + { return logic.StartOpenGate(); }); +} + +std::future TrickerDebugAPI::StartOpenGate() +{ + return std::async(std::launch::async, [&]() + { return logic.StartOpenGate(); }); +} + +std::future StudentDebugAPI::StartOpenChest() +{ + return std::async(std::launch::async, [&]() + { return logic.StartOpenChest(); }); +} + +std::future TrickerDebugAPI::StartOpenChest() +{ + return std::async(std::launch::async, [&]() + { return logic.StartOpenChest(); }); +} + +std::future StudentDebugAPI::EndAllAction() +{ + return std::async(std::launch::async, [&]() + { return logic.EndAllAction(); }); +} + +std::future TrickerDebugAPI::EndAllAction() +{ + return std::async(std::launch::async, [&]() + { return logic.EndAllAction(); }); +} + std::future StudentDebugAPI::SendMessage(int64_t toID, std::string message) { logger->info("SendMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint)); @@ -403,6 +475,16 @@ std::future TrickerDebugAPI::Attack(double angleInRadian) return result; }); } +std::future StudentDebugAPI::Attack(double angleInRadian) +{ + logger->info("Attack: angleInRadian = {}, called at {}ms", angleInRadian, Time::TimeSinceStart(startPoint)); + return std::async(std::launch::async, [=]() + { auto result = logic.Attack(angleInRadian); + if (!result) + logger->warn("Attack: failed at {}ms", Time::TimeSinceStart(startPoint)); + return result; }); +} + std::shared_ptr TrickerDebugAPI::GetSelfInfo() const { return logic.TrickerGetSelfInfo(); diff --git a/CAPI/cpp/API/src/logic.cpp b/CAPI/cpp/API/src/logic.cpp index 8715d1e..f5f278d 100644 --- a/CAPI/cpp/API/src/logic.cpp +++ b/CAPI/cpp/API/src/logic.cpp @@ -147,6 +147,42 @@ bool Logic::Attack(double angle) return pComm->Attack(angle, playerID); } +bool Logic::OpenDoor() +{ + logger->debug("Called OpenDoor"); + return pComm->OpenDoor(playerID); +} + +bool Logic::CloseDoor() +{ + logger->debug("Called CloseDoor"); + return pComm->CloseDoor(playerID); +} + +bool Logic::SkipWindow() +{ + logger->debug("Called SkipWindow"); + return pComm->SkipWindow(playerID); +} + +bool Logic::StartOpenGate() +{ + logger->debug("Called StartOpenGate"); + return pComm->StartOpenGate(playerID); +} + +bool Logic::StartOpenChest() +{ + logger->debug("Called StartOpenChest"); + return pComm->StartOpenChest(playerID); +} + +bool Logic::EndAllAction() +{ + logger->debug("Called EndAllAction"); + return pComm->EndAllAction(playerID); +} + bool Logic::WaitThread() { Update(); diff --git a/README.md b/README.md index f16f76b..9819ced 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # THUAI6 + 清华大学第六届人工智能挑战赛电子系赛道(原电子系第 24 届队式程序设计大赛 teamstyle24) Gitee 镜像地址:[THUAI6: Gitee Mirror](https://gitee.com/eesast/THUAI6) GitLink 镜像地址:[THUAI6: GitLink Mirror](https://www.gitlink.org.cn/EESAST/THUAI6) +项目主页:[THUAI6 Project Home Page](https://eesast.github.io/THUAI6) + +关于本届及历届清华大学人工智能挑战赛与队式程序设计大赛的更多内容参见:[THUAI6 Github Wiki](https://github.com/eesast/THUAI6/wiki) + ## 赛题背景 待定 diff --git a/dependency/proto/Message2Clients.proto b/dependency/proto/Message2Clients.proto index f915329..c3d14ca 100755 --- a/dependency/proto/Message2Clients.proto +++ b/dependency/proto/Message2Clients.proto @@ -10,8 +10,8 @@ message MessageOfStudent int32 y = 2; int32 speed = 3; int32 determination = 4; // 剩余的学习毅力,相当于血量 - int32 fail_num = 5; // 挂科的科目数 - double time_until_skill_available = 6; + //int32 fail_num = 5; // 挂科的科目数 + repeated double time_until_skill_available = 6; PlaceType place = 7; repeated PropType prop = 8; PlayerState player_state = 9; @@ -142,7 +142,6 @@ message MessageOfMap repeated PlaceType col = 1; } repeated Row row = 2; - repeated MessageOfMapObj map_obj_message = 3; } message MessageOfObj @@ -154,6 +153,7 @@ message MessageOfObj MessageOfProp prop_message = 3; MessageOfBullet bullet_message = 4; MessageOfBombedBullet bombed_bullet_message = 5; + MessageOfMapObj map_obj_message = 6; } } diff --git a/logic/GameClass/GameObj/Bullet/BombedBullet.cs b/logic/GameClass/GameObj/Bullet/BombedBullet.cs index 3f7d67d..c9b245f 100644 --- a/logic/GameClass/GameObj/Bullet/BombedBullet.cs +++ b/logic/GameClass/GameObj/Bullet/BombedBullet.cs @@ -9,9 +9,11 @@ namespace GameClass.GameObj public override bool IsRigid => false; public long MappingID { get; } public Bullet bulletHasBombed; + public BombedBullet(Bullet bullet) : base(bullet.Position, bullet.Radius, GameObjType.BombedBullet) { + this.place = bullet.Place; this.bulletHasBombed = bullet; this.MappingID = bullet.ID; this.FacingDirection = bullet.FacingDirection; diff --git a/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs b/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs index ed9fc2b..d4a1895 100644 --- a/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs +++ b/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs @@ -6,8 +6,8 @@ namespace GameClass.GameObj { 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; @@ -28,8 +28,8 @@ namespace GameClass.GameObj } 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; @@ -51,8 +51,8 @@ namespace GameClass.GameObj 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; @@ -75,8 +75,8 @@ namespace GameClass.GameObj 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; @@ -98,8 +98,8 @@ namespace GameClass.GameObj 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; @@ -122,8 +122,8 @@ namespace GameClass.GameObj 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; diff --git a/logic/GameClass/GameObj/Bullet/Bullet.cs b/logic/GameClass/GameObj/Bullet/Bullet.cs index bf204cd..2e4611c 100644 --- a/logic/GameClass/GameObj/Bullet/Bullet.cs +++ b/logic/GameClass/GameObj/Bullet/Bullet.cs @@ -37,9 +37,10 @@ namespace GameClass.GameObj return true; 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.moveSpeed = this.Speed; this.hasSpear = player.HasSpear; @@ -52,22 +53,22 @@ namespace GameClass.GameObj public static class BulletFactory { - public static Bullet? GetBullet(Character character) + public static Bullet? GetBullet(Character character, PlaceType place, XY pos) { Bullet? newBullet = null; switch (character.BulletOfPlayer) { case BulletType.AtomBomb: - newBullet = new AtomBomb(character); + newBullet = new AtomBomb(character, place, pos); break; case BulletType.LineBullet: - newBullet = new LineBullet(character); + newBullet = new LineBullet(character, place, pos); break; case BulletType.FastBullet: - newBullet = new FastBullet(character); + newBullet = new FastBullet(character, place, pos); break; case BulletType.OrdinaryBullet: - newBullet = new OrdinaryBullet(character); + newBullet = new OrdinaryBullet(character, place, pos); break; default: break; diff --git a/logic/GameClass/GameObj/Character/Character.SkillManager.cs b/logic/GameClass/GameObj/Character/Character.Skill.cs similarity index 94% rename from logic/GameClass/GameObj/Character/Character.SkillManager.cs rename to logic/GameClass/GameObj/Character/Character.Skill.cs index f657030..1be4129 100644 --- a/logic/GameClass/GameObj/Character/Character.SkillManager.cs +++ b/logic/GameClass/GameObj/Character/Character.Skill.cs @@ -53,16 +53,13 @@ namespace GameClass.GameObj public bool IsGhost() { - return this.CharacterType switch - { - CharacterType.Assassin => true, - _ => false, - }; + return GameData.IsGhost(CharacterType); } protected Character(XY initPos, int initRadius, CharacterType characterType) : base(initPos, initRadius, GameObjType.Character) { + this.place = PlaceType.Null; this.CanMove = true; this.score = 0; this.propInventory = null; @@ -88,6 +85,8 @@ namespace GameClass.GameObj this.bulletNum = maxBulletNum; this.bulletOfPlayer = Occupation.InitBullet; this.OriBulletOfPlayer = Occupation.InitBullet; + this.concealment = Occupation.Concealment; + this.alertnessRadius = Occupation.AlertnessRadius; this.characterType = characterType; foreach (var activeSkill in this.Occupation.ListOfIActiveSkill) diff --git a/logic/GameClass/GameObj/Character/Character.cs b/logic/GameClass/GameObj/Character/Character.cs index ea17b85..88632c7 100644 --- a/logic/GameClass/GameObj/Character/Character.cs +++ b/logic/GameClass/GameObj/Character/Character.cs @@ -8,7 +8,7 @@ using System.Threading; namespace GameClass.GameObj { - public partial class Character : GameObj, ICharacter // 负责人LHR摆烂终了 + public partial class Character : Moveable, ICharacter // 负责人LHR摆烂终了 { private readonly object beAttackedLock = new(); @@ -175,24 +175,56 @@ namespace GameClass.GameObj } } + private Dictionary bgmDictionary = new(); + public Dictionary 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; + } + } + } + /// - /// 进行一次远程攻击 + /// 进行一次攻击 /// - /// 子弹初始位置偏差值 /// 攻击操作发出的子弹 - public Bullet? RemoteAttack(XY posOffset) + public Bullet? Attack(XY pos, PlaceType place) { if (TrySubBulletNum()) - return ProduceOneBullet(this.Position + posOffset); + return BulletFactory.GetBullet(this, place, pos); else return null; } - protected Bullet? ProduceOneBullet(XY initPos) - { - var newBullet = BulletFactory.GetBullet(this); - newBullet?.SetPosition(initPos); - return newBullet; - } /// /// 尝试将子弹数量减1 @@ -452,24 +484,24 @@ namespace GameClass.GameObj } } #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) { lock (gameObjLock) @@ -478,6 +510,7 @@ namespace GameClass.GameObj CanMove = false; IsResetting = true; Position = GameData.PosWhoDie; + place = PlaceType.Grass; } } diff --git a/logic/GameClass/GameObj/GameObj.cs b/logic/GameClass/GameObj/GameObj.cs index a1a4783..483de98 100644 --- a/logic/GameClass/GameObj/GameObj.cs +++ b/logic/GameClass/GameObj/GameObj.cs @@ -7,13 +7,10 @@ namespace GameClass.GameObj /// /// 一切游戏元素的总基类,与THUAI4不同,继承IMoveable接口(出于一切物体其实都是可运动的指导思想)——LHR /// - public abstract class GameObj : IMoveable + public abstract class GameObj : IGameObj { protected readonly object gameObjLock = new(); - /// - /// 可移动物体专用锁 - /// - public object MoveLock => gameObjLock; + public object GameLock => gameObjLock; 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); public XY FacingDirection @@ -50,6 +49,9 @@ namespace GameClass.GameObj facingDirection = value; } } + + public abstract bool IsRigid { get; } + public abstract ShapeType Shape { get; } 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; public bool IsResetting { @@ -90,77 +79,14 @@ namespace GameClass.GameObj } } } - public bool IsAvailable => !IsMoving && CanMove && !IsResetting; // 是否能接收指令 - public int Radius { get; } - protected int moveSpeed; - /// - /// 移动速度 - /// - public int MoveSpeed - { - get => moveSpeed; - set - { - lock (gameObjLock) - { - moveSpeed = value; - } - } - } - /// - /// 原初移动速度 - /// - 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); - } - /// - /// 设置位置 - /// - /// 新位置 - 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); + bool IGameObj.IgnoreCollide(IGameObj targetObj) => IgnoreCollideExecutor(targetObj); public GameObj(XY initPos, int initRadius, GameObjType initType) { this.Position = this.birthPos = initPos; diff --git a/logic/GameClass/GameObj/Map/Doorway.cs b/logic/GameClass/GameObj/Map/Doorway.cs index eb7cf4d..251afb3 100644 --- a/logic/GameClass/GameObj/Map/Doorway.cs +++ b/logic/GameClass/GameObj/Map/Doorway.cs @@ -11,6 +11,7 @@ namespace GameClass.GameObj public Doorway(XY initPos) : base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Doorway) { + this.place = PlaceType.Doorway; this.CanMove = false; } public override bool IsRigid => true; diff --git a/logic/GameClass/GameObj/Map/EmergencyExit.cs b/logic/GameClass/GameObj/Map/EmergencyExit.cs index ba06228..95de4d5 100644 --- a/logic/GameClass/GameObj/Map/EmergencyExit.cs +++ b/logic/GameClass/GameObj/Map/EmergencyExit.cs @@ -11,6 +11,7 @@ namespace GameClass.GameObj public EmergencyExit(XY initPos) : base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.EmergencyExit) { + this.place = PlaceType.EmergencyExit; this.CanMove = false; } public override bool IsRigid => true; diff --git a/logic/GameClass/GameObj/Map/Generator.cs b/logic/GameClass/GameObj/Map/Generator.cs index a7067a9..1b4d59b 100644 --- a/logic/GameClass/GameObj/Map/Generator.cs +++ b/logic/GameClass/GameObj/Map/Generator.cs @@ -10,6 +10,7 @@ namespace GameClass.GameObj public Generator(XY initPos) : base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Generator) { + this.place = PlaceType.Generator; this.CanMove = false; } public override bool IsRigid => true; diff --git a/logic/GameClass/GameObj/Map/Map.cs b/logic/GameClass/GameObj/Map/Map.cs index 20920cd..0a373f6 100644 --- a/logic/GameClass/GameObj/Map/Map.cs +++ b/logic/GameClass/GameObj/Map/Map.cs @@ -18,12 +18,13 @@ namespace GameClass.GameObj private Dictionary gameObjLockDict; public Dictionary 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 { - 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 { @@ -35,7 +36,7 @@ namespace GameClass.GameObj { try { - return (PlaceType)ProtoGameMap[pos.x / GameData.numOfPosGridPerCell, pos.y / GameData.numOfPosGridPerCell]; + return (PlaceType)protoGameMap[pos.x / GameData.numOfPosGridPerCell, pos.y / GameData.numOfPosGridPerCell]; } 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(GameData.numOfBirthPoint); diff --git a/logic/GameClass/GameObj/Map/Wall.cs b/logic/GameClass/GameObj/Map/Wall.cs index 1cef8e9..3a81ee4 100644 --- a/logic/GameClass/GameObj/Map/Wall.cs +++ b/logic/GameClass/GameObj/Map/Wall.cs @@ -10,6 +10,7 @@ namespace GameClass.GameObj public Wall(XY initPos) : base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Wall) { + this.place = PlaceType.Wall; this.CanMove = false; } public override bool IsRigid => true; diff --git a/logic/GameClass/GameObj/Moveable.cs b/logic/GameClass/GameObj/Moveable.cs new file mode 100644 index 0000000..70c0c3c --- /dev/null +++ b/logic/GameClass/GameObj/Moveable.cs @@ -0,0 +1,103 @@ +using Preparation.Interface; +using Preparation.Utility; +using System.Threading; + +namespace GameClass.GameObj +{ + /// + /// 一切游戏元素的总基类,与THUAI4不同,继承IMoveable接口(出于一切物体其实都是可运动的指导思想)——LHR + /// + 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; + /// + /// 移动速度 + /// + public int MoveSpeed + { + get => moveSpeed; + set + { + lock (gameObjLock) + { + moveSpeed = value; + } + } + } + /// + /// 原初移动速度 + /// + 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; + } + } + + /// + /// 设置移动速度 + /// + /// 新速度 + public void SetMoveSpeed(int newMoveSpeed) + { + MoveSpeed = newMoveSpeed; + } + /* /// + /// 复活时数据重置 + /// + 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; + } + }*/ + /// + /// 为了使IgnoreCollide多态化并使GameObj能不报错地继承IMoveable + /// 在xfgg点播下设计了这个抽象辅助方法,在具体类中实现 + /// + /// 依具体类及该方法参数而定,默认为false + public Moveable(XY initPos, int initRadius, GameObjType initType) : base(initPos, initRadius, initType) + { + } + } +} diff --git a/logic/GameClass/GameObj/ObjOfCharacter.cs b/logic/GameClass/GameObj/ObjOfCharacter.cs index 0da79a6..1004891 100644 --- a/logic/GameClass/GameObj/ObjOfCharacter.cs +++ b/logic/GameClass/GameObj/ObjOfCharacter.cs @@ -6,7 +6,7 @@ namespace GameClass.GameObj /// /// 所有物,具有主人(Parent)(特定玩家)属性的对象 /// - public abstract class ObjOfCharacter : GameObj, IObjOfCharacter + public abstract class ObjOfCharacter : Moveable, IObjOfCharacter { private ICharacter? parent = null; // 主人 public ICharacter? Parent diff --git a/logic/GameClass/GameObj/OutOfBoundBlock.cs b/logic/GameClass/GameObj/OutOfBoundBlock.cs index 3cd88eb..1c62417 100644 --- a/logic/GameClass/GameObj/OutOfBoundBlock.cs +++ b/logic/GameClass/GameObj/OutOfBoundBlock.cs @@ -11,6 +11,7 @@ namespace GameClass.GameObj public OutOfBoundBlock(XY initPos) : base(initPos, int.MaxValue, GameObjType.OutOfBoundBlock) { + this.place = PlaceType.Wall; this.CanMove = false; } diff --git a/logic/GameClass/GameObj/PickedProp.cs b/logic/GameClass/GameObj/PickedProp.cs index 221e6e8..33418c2 100644 --- a/logic/GameClass/GameObj/PickedProp.cs +++ b/logic/GameClass/GameObj/PickedProp.cs @@ -17,6 +17,7 @@ namespace GameClass.GameObj public PickedProp(Prop prop) : base(prop.Position, prop.Radius, GameObjType.PickedProp) { + this.place = prop.Place; this.PropHasPicked = prop; this.MappingID = prop.ID; } diff --git a/logic/GameClass/GameObj/Prop.cs b/logic/GameClass/GameObj/Prop.cs index 2fcb837..5ebcdbd 100644 --- a/logic/GameClass/GameObj/Prop.cs +++ b/logic/GameClass/GameObj/Prop.cs @@ -21,16 +21,13 @@ namespace GameClass.GameObj 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) { + this.place = place; this.CanMove = false; this.moveSpeed = GameData.PropMoveSpeed; } - public void SetNewPos(XY pos) - { - this.Position = pos; - } } @@ -48,8 +45,8 @@ namespace GameClass.GameObj /// 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; @@ -59,8 +56,8 @@ namespace GameClass.GameObj /// 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; @@ -70,8 +67,7 @@ namespace GameClass.GameObj /// 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; @@ -81,8 +77,7 @@ namespace GameClass.GameObj /// 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; diff --git a/logic/GameEngine/MoveEngine.cs b/logic/GameEngine/MoveEngine.cs index 6df1548..4169731 100644 --- a/logic/GameEngine/MoveEngine.cs +++ b/logic/GameEngine/MoveEngine.cs @@ -20,6 +20,18 @@ namespace GameEngine private readonly ITimer gameTimer; private readonly Action 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 Func OnCollision; /// @@ -34,6 +46,7 @@ namespace GameEngine Action EndMove ) { + this.ProtoGameMap = gameMap.ProtoGameMap; this.gameTimer = gameMap.Timer; this.EndMove = EndMove; this.OnCollision = OnCollision; @@ -50,9 +63,11 @@ namespace GameEngine /*由于四周是墙,所以人物永远不可能与越界方块碰撞*/ 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) @@ -69,7 +84,8 @@ namespace GameEngine obj.IsMoving = true; 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; bool isDestroyed = false; new FrameRateTaskExecutor( @@ -77,17 +93,18 @@ namespace GameEngine () => { moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; + XY res = new XY(direction, moveVecLength); // 越界情况处理:如果越界,则与越界方块碰撞 bool flag; // 循环标志 do { flag = false; - collisionObj = collisionChecker.CheckCollision(obj, new XY(direction, moveVecLength)); + collisionObj = collisionChecker.CheckCollision(obj, res); if (collisionObj == null) break; - switch (OnCollision(obj, collisionObj, new XY(direction, moveVecLength))) + switch (OnCollision(obj, collisionObj, res)) { case AfterCollision.ContinueCheck: flag = true; @@ -97,13 +114,14 @@ namespace GameEngine isDestroyed = true; return false; case AfterCollision.MoveMax: - MoveMax(obj, new XY(direction, moveVecLength)); + MoveMax(obj, res); moveVecLength = 0; + res = new XY(direction, moveVecLength); break; } } 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; }, @@ -118,13 +136,14 @@ namespace GameEngine if (!isDestroyed) { 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 { - switch (OnCollision(obj, collisionObj, new XY(direction, moveVecLength))) + switch (OnCollision(obj, collisionObj, res)) { case AfterCollision.ContinueCheck: flag = true; @@ -134,8 +153,9 @@ namespace GameEngine isDestroyed = true; break; case AfterCollision.MoveMax: - MoveMax(obj, new XY(direction, moveVecLength)); + MoveMax(obj, res); moveVecLength = 0; + res = new XY(direction, moveVecLength); break; } } diff --git a/logic/Gaming/ActionManager.cs b/logic/Gaming/ActionManager.cs index e10c668..6290dac 100644 --- a/logic/Gaming/ActionManager.cs +++ b/logic/Gaming/ActionManager.cs @@ -1,4 +1,5 @@ using System; +using System.Numerics; using System.Runtime.InteropServices; using System.Threading; using GameClass.GameObj; @@ -38,7 +39,7 @@ namespace Gaming 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; 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), loopToDo: () => { - return !generatorForFix.Repair(player.FixSpeed * GameData.frameDuration); + generatorForFix.Repair(player.FixSpeed * GameData.frameDuration); }, timeInterval: GameData.frameDuration, finallyReturn: () => 0 diff --git a/logic/Gaming/AttackManager.cs b/logic/Gaming/AttackManager.cs index c6f964e..bd30d58 100644 --- a/logic/Gaming/AttackManager.cs +++ b/logic/Gaming/AttackManager.cs @@ -38,7 +38,7 @@ namespace Gaming ); } - public void BeAddictedToGame(Student player) + private void BeAddictedToGame(Student player) { new Thread (() => @@ -68,11 +68,10 @@ namespace Gaming { 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(); // try //{ @@ -87,7 +86,8 @@ namespace Gaming if (player.PropInventory != null) // 若角色原来有道具,则原始道具掉落在原地 { 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(); try @@ -298,12 +298,14 @@ namespace Gaming if (player.PlayerState != PlayerStateType.Null || player.PlayerState != PlayerStateType.IsMoving) 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.Sin(angle)) - ) + ); + + Bullet? bullet = player.Attack( + res, gameMap.GetPlaceType(res) ); if (bullet.CastTime > 0) { diff --git a/logic/Gaming/Game.cs b/logic/Gaming/Game.cs index 6bb3949..aaebe29 100644 --- a/logic/Gaming/Game.cs +++ b/logic/Gaming/Game.cs @@ -5,6 +5,7 @@ using Preparation.Utility; using Timothy.FrameRateTask; using Preparation.Interface; using GameClass.GameObj; +using System.Numerics; namespace Gaming { @@ -41,21 +42,14 @@ namespace Gaming // 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); - 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}"); teamList[(int)playerInitInfo.teamID].AddPlayer(newPlayer); newPlayer.TeamID = playerInitInfo.teamID; newPlayer.PlayerID = playerInitInfo.playerID; - - new Thread //人物装弹 + #region 人物装弹 + new Thread ( () => { @@ -63,19 +57,16 @@ namespace Gaming Thread.Sleep(newPlayer.CD); long lastTime = Environment.TickCount64; new FrameRateTaskExecutor( - loopCondition: () => gameMap.Timer.IsGaming, + loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, 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, @@ -93,6 +84,91 @@ namespace Gaming } ) { IsBackground = true }.Start(); + #endregion + #region BGM更新 + new Thread + ( + () => + { + while (!gameMap.Timer.IsGaming) + Thread.Sleep((int)GameData.checkInterval); + new FrameRateTaskExecutor( + 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; } @@ -100,20 +176,6 @@ namespace Gaming { if (gameMap.Timer.IsGaming) 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(); diff --git a/logic/Gaming/PropManager.cs b/logic/Gaming/PropManager.cs index f70fe2c..716631a 100644 --- a/logic/Gaming/PropManager.cs +++ b/logic/Gaming/PropManager.cs @@ -110,7 +110,8 @@ namespace Gaming if (player.PropInventory != null) // 若角色原来有道具,则原始道具掉落在原地 { 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; gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock(); @@ -151,7 +152,7 @@ namespace Gaming return; prop.CanMove = true; - prop.SetNewPos(player.Position); + prop.ReSetPos(player.Position, gameMap.GetPlaceType(player.Position)); gameMap.GameObjLockDict[GameObjType.Prop].EnterWriteLock(); try { @@ -187,16 +188,16 @@ namespace Gaming switch (r.Next(0, 4)) { 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; 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; 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; 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; default: break; @@ -230,11 +231,11 @@ namespace Gaming } ); availableCellForGenerateProp = new List(); - 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)); } diff --git a/logic/Preparation/Interface/IGameObj.cs b/logic/Preparation/Interface/IGameObj.cs index f5adb16..d7124a7 100644 --- a/logic/Preparation/Interface/IGameObj.cs +++ b/logic/Preparation/Interface/IGameObj.cs @@ -7,13 +7,13 @@ namespace Preparation.Interface public GameObjType Type { get; } public long ID { get; } public XY Position { get; } // if Square, Pos equals the center + public PlaceType Place { get; } public XY FacingDirection { get; } public bool IsRigid { get; } public ShapeType Shape { get; } public bool CanMove { get; set; } - public bool IsMoving { get; set; } public bool IsResetting { get; set; } // reviving - public bool IsAvailable { get; } public int Radius { get; } // if Square, Radius equals half length of one side + protected bool IgnoreCollide(IGameObj targetObj); // 忽略碰撞,在具体类中实现 } } diff --git a/logic/Preparation/Interface/IMap.cs b/logic/Preparation/Interface/IMap.cs index 479eb82..e504121 100644 --- a/logic/Preparation/Interface/IMap.cs +++ b/logic/Preparation/Interface/IMap.cs @@ -12,6 +12,8 @@ namespace Preparation.Interface Dictionary> GameObjDict { get; } Dictionary GameObjLockDict { get; } + public uint[,] ProtoGameMap { get; } + public PlaceType GetPlaceType(IGameObj obj); public bool IsOutOfBound(IGameObj obj); public IOutOfBound GetOutOfBound(XY pos); // 返回新建的一个OutOfBound对象 } diff --git a/logic/Preparation/Interface/IMoveable.cs b/logic/Preparation/Interface/IMoveable.cs index ff23718..b5b3751 100644 --- a/logic/Preparation/Interface/IMoveable.cs +++ b/logic/Preparation/Interface/IMoveable.cs @@ -7,8 +7,10 @@ namespace Preparation.Interface { object MoveLock { 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) // 检查下一位置是否会和目标物碰撞 { if (targetObj == null) diff --git a/logic/Preparation/Interface/IOccupation.cs b/logic/Preparation/Interface/IOccupation.cs index 648ebf7..3175d4b 100644 --- a/logic/Preparation/Interface/IOccupation.cs +++ b/logic/Preparation/Interface/IOccupation.cs @@ -12,6 +12,8 @@ namespace Preparation.Interface public int MaxBulletNum { get; } public List ListOfIActiveSkill { get; } public List ListOfIPassiveSkill { get; } + public double Concealment { get; } + public int AlertnessRadius { get; } } public interface IGhost : IOccupation @@ -41,6 +43,12 @@ namespace Preparation.Interface public List ListOfIActiveSkill => new(new ActiveSkillType[] { ActiveSkillType.BecomeInvisible, ActiveSkillType.UseKnife }); public List 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 { @@ -61,6 +69,13 @@ namespace Preparation.Interface public List ListOfIActiveSkill => new(new ActiveSkillType[] { ActiveSkillType.BeginToCharge }); public List 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; } } diff --git a/logic/Preparation/Utility/GameData.cs b/logic/Preparation/Utility/GameData.cs index 9dbcf60..a581f1b 100644 --- a/logic/Preparation/Utility/GameData.cs +++ b/logic/Preparation/Utility/GameData.cs @@ -1,5 +1,6 @@ using System; using System.Reflection.Metadata.Ecma335; +using System.Threading; namespace Preparation.Utility { @@ -75,12 +76,15 @@ namespace Preparation.Utility public const int basicMoveSpeed = 3800; // 基本移动速度,单位:s-1 public const int basicBulletMoveSpeed = 5400; // 基本子弹移动速度,单位:s-1 public const int characterMaxSpeed = 12000; // 最大速度 + public const double basicConcealment = 1.0; + public const int basicAlertnessRadius = 30700; public const int addScoreWhenKillOneLevelPlayer = 30; // 击杀一级角色获得的加分 public const int commonSkillCD = 30000; // 普通技能标准冷却时间 public const int commonSkillTime = 10000; // 普通技能标准持续时间 public const int bulletRadius = 200; // 默认子弹半径 public const int reviveTime = 30000; // 复活时间 public const int shieldTimeAtBirth = 3000; // 复活时的护盾时间 + public static XY PosWhoDie = new XY(1, 1); public static bool IsGhost(CharacterType characterType) @@ -101,9 +105,12 @@ namespace Preparation.Utility public const long GemProduceTime = 10000; public const long PropProduceTime = 10000; public const int PropDuration = 10000; + #endregion + #region 物体相关 public const int degreeOfFixedGenerator = 10300000; #endregion + #region 游戏帧相关 public const long checkInterval = 50; // 检查位置标志、补充子弹的帧时长 #endregion diff --git a/logic/Server/CopyInfo.cs b/logic/Server/CopyInfo.cs index e210d5b..44da0c0 100644 --- a/logic/Server/CopyInfo.cs +++ b/logic/Server/CopyInfo.cs @@ -8,13 +8,14 @@ namespace Server public static class CopyInfo { // 下面赋值为0的大概率是还没写完 2023-03-03 - /*public static MessageOfObj? Auto(GameObj gameObj) + public static MessageOfObj? Auto(GameObj gameObj) { if (gameObj.Type == Preparation.Utility.GameObjType.Character) { Character character = (Character)gameObj; if (character.IsGhost()) - return Tri + return Tricker((Character)character); + else return Student((Character)character); } else if (gameObj.Type == Preparation.Utility.GameObjType.Bullet) return Bullet((Bullet)gameObj); @@ -25,28 +26,33 @@ namespace Server else if (gameObj.Type == Preparation.Utility.GameObjType.PickedProp) return PickedProp((PickedProp)gameObj); else return null; //先写着防报错 - }*/ + } - private static MessageOfStudent? Student(Character player) + private static MessageOfObj? Student(Character player) { - MessageOfStudent msg = new MessageOfStudent(); + MessageOfObj msg = new MessageOfObj(); if (player.IsGhost()) return null; - msg.X = player.Position.x; - msg.Y = player.Position.y; - msg.Speed = player.MoveSpeed; - msg.Determination = player.HP; - msg.FailNum = 0; - msg.TimeUntilSkillAvailable = 0; - msg.StudentType = StudentType.NullStudentType; // 下面写 - msg.Guid = 0; - msg.State = StudentState.NullStatus; - msg.FailTime = 0; - msg.EmoTime = 0; - msg.PlayerId = 0; - msg.ViewRange = 0; - msg.Radius = 0; - + msg.StudentMessage.X = player.Position.x; + msg.StudentMessage.Y = player.Position.y; + msg.StudentMessage.Speed = player.MoveSpeed; + msg.StudentMessage.Determination = player.HP; + //msg.StudentMessage.FailNum = 0; + foreach (var keyValue in player.TimeUntilActiveSkillAvailable) + msg.StudentMessage.TimeUntilSkillAvailable.Add(keyValue.Value); + //msg.StudentMessage.StudentType; // 下面写 + msg.StudentMessage.Guid = player.ID; + msg.StudentMessage.State = StudentState.NullStatus; + msg.StudentMessage.FailTime = 0; + msg.StudentMessage.EmoTime = 0; + msg.StudentMessage.PlayerId = 0; + msg.StudentMessage.ViewRange = 0; + msg.StudentMessage.Radius = 0; + msg.StudentMessage.Damage = 0; + msg.StudentMessage.DangerAlert = 0; + msg.StudentMessage.Score = 0; + msg.StudentMessage.TreatProgress = 0; + msg.StudentMessage.RescueProgress = 0; foreach (KeyValuePair kvp in player.Buff) { @@ -55,178 +61,76 @@ namespace Server switch (kvp.Key) // StudentBuffType具体内容待定 { case Preparation.Utility.BuffType.Spear: - msg.Buff.Add(StudentBuffType.NullSbuffType); + msg.StudentMessage.Buff.Add(StudentBuffType.NullSbuffType); break; case Preparation.Utility.BuffType.AddLIFE: - msg.Buff.Add(StudentBuffType.NullSbuffType); + msg.StudentMessage.Buff.Add(StudentBuffType.NullSbuffType); break; case Preparation.Utility.BuffType.Shield: - msg.Buff.Add(StudentBuffType.NullSbuffType); + msg.StudentMessage.Buff.Add(StudentBuffType.NullSbuffType); break; case Preparation.Utility.BuffType.AddSpeed: - msg.Buff.Add(StudentBuffType.NullSbuffType); + msg.StudentMessage.Buff.Add(StudentBuffType.NullSbuffType); break; default: break; } } } - switch (player.Place) - { - case Preparation.Utility.PlaceType.EmergencyExit: - msg.Place = PlaceType.HiddenGate; - break; - case Preparation.Utility.PlaceType.Doorway: - msg.Place = PlaceType.Gate; - break; - case Preparation.Utility.PlaceType.Grass: - msg.Place = PlaceType.Grass; - break; - case Preparation.Utility.PlaceType.BirthPoint1: - case Preparation.Utility.PlaceType.BirthPoint2: - case Preparation.Utility.PlaceType.BirthPoint3: - case Preparation.Utility.PlaceType.BirthPoint4: - case Preparation.Utility.PlaceType.BirthPoint5: - case Preparation.Utility.PlaceType.Null: - msg.Place = PlaceType.Land; - break; - // case Preparation.Utility.PlaceType.Invisible: - // msg.MessageOfHuman.Place = Communication.Proto.PlaceType.Invisible; - // break; - default: - msg.Place = PlaceType.NullPlaceType; - break; - } //Character的储存方式可能得改,用enum type存道具和子弹,不应该用对象 //现在懒得改了,有时间再重整一波 if (player.PropInventory == null) - msg.Prop.Add(PropType.NullPropType); + msg.StudentMessage.Prop.Add(PropType.NullPropType); else { switch (player.PropInventory.GetPropType()) { case Preparation.Utility.PropType.Gem: - msg.Prop.Add(PropType.NullPropType); + msg.StudentMessage.Prop.Add(PropType.NullPropType); break; /*case Preparation.Utility.PropType.addLIFE: - msg.MessageOfHuman.Prop = Communication.Proto.PropType.AddLife; + msg.StudentMessage.MessageOfHuman.Prop = Communication.Proto.PropType.AddLife; break; case Preparation.Utility.PropType.addSpeed: - msg.MessageOfHuman.Prop = Communication.Proto.PropType.AddSpeed; + msg.StudentMessage.MessageOfHuman.Prop = Communication.Proto.PropType.AddSpeed; break; case Preparation.Utility.PropType.Shield: - msg.MessageOfHuman.Prop = Communication.Proto.PropType.Shield; + msg.StudentMessage.MessageOfHuman.Prop = Communication.Proto.PropType.Shield; break; case Preparation.Utility.PropType.Spear: - msg.MessageOfHuman.Prop = Communication.Proto.PropType.Spear; + msg.StudentMessage.MessageOfHuman.Prop = Communication.Proto.PropType.Spear; break; default: - msg.Prop = PropType.NullPropType; + msg.StudentMessage.Prop = PropType.NullPropType; break;*/ } } - /*switch (player.PassiveSkillType) 需要对接一下,proto里似乎没有这个 - { - case Preparation.Utility.PassiveSkillType.RecoverAfterBattle: - msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.RecoverAfterBattle; - break; - case Preparation.Utility.PassiveSkillType.SpeedUpWhenLeavingGrass: - msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.SpeedUpWhenLeavingGrass; - break; - case Preparation.Utility.PassiveSkillType.Vampire: - msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.Vampire; - break; - default: - msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.NullPassiveSkillType; - break; - } - - switch (player.CommonSkillType) - { - case Preparation.Utility.ActiveSkillType.BecomeAssassin: - msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeAssassin; - break; - case Preparation.Utility.ActiveSkillType.BecomeVampire: - msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeVampire; - break; - case Preparation.Utility.ActiveSkillType.NuclearWeapon: - msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.NuclearWeapon; - break; - case Preparation.Utility.ActiveSkillType.SuperFast: - msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.SuperFast; - break; - default: - msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.NullActiveSkillType; - break; - } - - switch (player.BulletOfPlayer) - { - case Preparation.Utility.BulletType.AtomBomb: - msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.AtomBomb; - break; - case Preparation.Utility.BulletType.OrdinaryBullet: - msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.OrdinaryBullet; - break; - case Preparation.Utility.BulletType.FastBullet: - msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.FastBullet; - break; - case Preparation.Utility.BulletType.LineBullet: - msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.LineBullet; - break; - default: - msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.NullBulletType; - break; - }*/ return msg; } - private static MessageOfTricker? Butcher(Character player) + private static MessageOfObj? Tricker(Character player) { - MessageOfTricker msg = new MessageOfTricker(); + MessageOfObj msg = new MessageOfObj(); if (!player.IsGhost()) return null; - msg.X = player.Position.x; - msg.Y = player.Position.y; - msg.Speed = player.MoveSpeed; - msg.Damage = 0; - msg.TimeUntilSkillAvailable = 0; - //msg.Place = 0; 下面写了 - //msg.Prop = PropType.NullPropType; // 下面写 - msg.TrickerType = TrickerType.NullTrickerType; // 下面写 - msg.Guid = 0; - msg.Movable = false; - msg.PlayerId = 0; - msg.ViewRange = 0; - msg.Radius = 0; - //msg.Buff[0] = ButcherBuffType.NullSbuffType; 下面写了 - - /* THUAI5中的内容 - msg.BulletNum = player.BulletNum; - msg.CanMove = player.CanMove; - msg.CD = player.CD; - msg.GemNum = player.GemNum; - msg.Guid = player.ID; - msg.IsDeceased = player.IsDeceased; - - msg.LifeNum = player.DeathCount + 1; - msg.Radius = player.Radius; - - msg.TimeUntilCommonSkillAvailable = player.TimeUntilCommonSkillAvailable; - msg.TeamID = player.TeamID; - msg.PlayerID = player.PlayerID; - msg.IsInvisible = player.IsInvisible; - msg.FacingDirection = player.FacingDirection; + msg.TrickerMessage.X = player.Position.x; + msg.TrickerMessage.Y = player.Position.y; + msg.TrickerMessage.Speed = player.MoveSpeed; + msg.TrickerMessage.Damage = 0; + msg.TrickerMessage.TimeUntilSkillAvailable = 0; + //msg.TrickerMessage.Place = 0; 下面写了 + //msg.TrickerMessage.Prop = PropType.NullPropType; // 下面写 + msg.TrickerMessage.TrickerType = TrickerType.NullTrickerType; // 下面写 + msg.TrickerMessage.Guid = 0; + msg.TrickerMessage.Movable = false; + msg.TrickerMessage.PlayerId = 0; + msg.TrickerMessage.ViewRange = 0; + msg.TrickerMessage.Radius = 0; + //msg.TrickerMessage.Buff[0] = ButcherBuffType.NullSbuffType; 下面写了 - //应该要发队伍分数,这里先发个人分数 - msg.MessageOfHuman.Score = player.Score; - //这条暂时没啥用 - msg.MessageOfHuman.TimeUntilUltimateSkillAvailable = 0; - - msg.MessageOfHuman.Vampire = player.Vampire;*/ foreach (KeyValuePair kvp in player.Buff) { @@ -235,16 +139,16 @@ namespace Server switch (kvp.Key) // ButcherBuffType具体内容待定 { case Preparation.Utility.BuffType.Spear: - msg.Buff.Add(TrickerBuffType.NullTbuffType); + msg.TrickerMessage.Buff.Add(TrickerBuffType.NullTbuffType); break; case Preparation.Utility.BuffType.AddLIFE: - msg.Buff.Add(TrickerBuffType.NullTbuffType); + msg.TrickerMessage.Buff.Add(TrickerBuffType.NullTbuffType); break; case Preparation.Utility.BuffType.Shield: - msg.Buff.Add(TrickerBuffType.NullTbuffType); + msg.TrickerMessage.Buff.Add(TrickerBuffType.NullTbuffType); break; case Preparation.Utility.BuffType.AddSpeed: - msg.Buff.Add(TrickerBuffType.NullTbuffType); + msg.TrickerMessage.Buff.Add(TrickerBuffType.NullTbuffType); break; default: break; @@ -254,261 +158,205 @@ namespace Server /*switch (player.Place) { case Preparation.Utility.PlaceType.Land: - msg.Place = PlaceType.Land; + msg.TrickerMessage.Place = PlaceType.Land; break; case Preparation.Utility.PlaceType.Grass1: - msg.Place = PlaceType.Grass; + msg.TrickerMessage.Place = PlaceType.Grass; break; case Preparation.Utility.PlaceType.Grass2: - msg.Place = PlaceType.Grass; + msg.TrickerMessage.Place = PlaceType.Grass; break; case Preparation.Utility.PlaceType.Grass3: - msg.Place = PlaceType.Grass; + msg.TrickerMessage.Place = PlaceType.Grass; break; // case Preparation.Utility.PlaceType.Invisible: - // msg.MessageOfHuman.Place = Communication.Proto.PlaceType.Invisible; + // msg.TrickerMessage.MessageOfHuman.Place = Communication.Proto.PlaceType.Invisible; // break; default: - msg.Place = PlaceType.NullPlaceType; + msg.TrickerMessage.Place = PlaceType.NullPlaceType; break; }*/ //Character的储存方式可能得改,用enum type存道具和子弹,不应该用对象 //现在懒得改了,有时间再重整一波 /*if (player.PropInventory == null) - msg.Prop = PropType.NullPropType; + msg.TrickerMessage.Prop = PropType.NullPropType; else { switch (player.PropInventory.GetPropType()) { case Preparation.Utility.PropType.Gem: - msg.Prop = PropType.NullPropType; + msg.TrickerMessage.Prop = PropType.NullPropType; break; case Preparation.Utility.PropType.addLIFE: - msg.MessageOfHuman.Prop = Communication.Proto.PropType.AddLife; + msg.TrickerMessage.MessageOfHuman.Prop = Communication.Proto.PropType.AddLife; break; case Preparation.Utility.PropType.addSpeed: - msg.MessageOfHuman.Prop = Communication.Proto.PropType.AddSpeed; + msg.TrickerMessage.MessageOfHuman.Prop = Communication.Proto.PropType.AddSpeed; break; case Preparation.Utility.PropType.Shield: - msg.MessageOfHuman.Prop = Communication.Proto.PropType.Shield; + msg.TrickerMessage.MessageOfHuman.Prop = Communication.Proto.PropType.Shield; break; case Preparation.Utility.PropType.Spear: - msg.MessageOfHuman.Prop = Communication.Proto.PropType.Spear; + msg.TrickerMessage.MessageOfHuman.Prop = Communication.Proto.PropType.Spear; break; default: - msg.Prop = PropType.NullPropType; + msg.TrickerMessage.Prop = PropType.NullPropType; break; } }*/ - /*switch (player.PassiveSkillType) 需要对接一下,proto里似乎没有这个 - { - case Preparation.Utility.PassiveSkillType.RecoverAfterBattle: - msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.RecoverAfterBattle; - break; - case Preparation.Utility.PassiveSkillType.SpeedUpWhenLeavingGrass: - msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.SpeedUpWhenLeavingGrass; - break; - case Preparation.Utility.PassiveSkillType.Vampire: - msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.Vampire; - break; - default: - msg.MessageOfHuman.PassiveSkillType = Communication.Proto.PassiveSkillType.NullPassiveSkillType; - break; - } - - switch (player.CommonSkillType) - { - case Preparation.Utility.ActiveSkillType.BecomeAssassin: - msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeAssassin; - break; - case Preparation.Utility.ActiveSkillType.BecomeVampire: - msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.BecomeVampire; - break; - case Preparation.Utility.ActiveSkillType.NuclearWeapon: - msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.NuclearWeapon; - break; - case Preparation.Utility.ActiveSkillType.SuperFast: - msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.SuperFast; - break; - default: - msg.MessageOfHuman.ActiveSkillType = Communication.Proto.ActiveSkillType.NullActiveSkillType; - break; - } - - switch (player.BulletOfPlayer) - { - case Preparation.Utility.BulletType.AtomBomb: - msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.AtomBomb; - break; - case Preparation.Utility.BulletType.OrdinaryBullet: - msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.OrdinaryBullet; - break; - case Preparation.Utility.BulletType.FastBullet: - msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.FastBullet; - break; - case Preparation.Utility.BulletType.LineBullet: - msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.LineBullet; - break; - default: - msg.MessageOfHuman.BulletType = Communication.Proto.BulletType.NullBulletType; - break; - }*/ return msg; } - /*private static MessageToClient.Types.GameObjMessage Bullet(Bullet bullet) + private static MessageOfObj Bullet(Bullet bullet) { - MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage(); - msg.MessageOfBullet = new MessageOfBullet(); - msg.MessageOfBullet.FacingDirection = bullet.FacingDirection; - msg.MessageOfBullet.Guid = bullet.ID; - msg.MessageOfBullet.BombRange = BulletFactory.BulletBombRange(bullet.TypeOfBullet); + MessageOfObj msg = new MessageOfObj(); + msg.BulletMessage.X = bullet.Position.x; + msg.BulletMessage.Y = bullet.Position.y; + //msg.BulletMessage.FacingDirection = bullet.FacingDirection; // XY转double? + msg.BulletMessage.Guid = bullet.ID; + msg.BulletMessage.Team = PlayerType.NullPlayerType; + msg.BulletMessage.Place = PlaceType.NullPlaceType; + msg.BulletMessage.BombRange = 0; switch (bullet.TypeOfBullet) { case Preparation.Utility.BulletType.AtomBomb: - msg.MessageOfBullet.Type = Communication.Proto.BulletType.AtomBomb; + msg.BulletMessage.Type = BulletType.AtomBomb; break; case Preparation.Utility.BulletType.OrdinaryBullet: - msg.MessageOfBullet.Type = Communication.Proto.BulletType.OrdinaryBullet; + msg.BulletMessage.Type = BulletType.OrdinaryBullet; break; case Preparation.Utility.BulletType.FastBullet: - msg.MessageOfBullet.Type = Communication.Proto.BulletType.FastBullet; + msg.BulletMessage.Type = BulletType.FastBullet; break; case Preparation.Utility.BulletType.LineBullet: - msg.MessageOfBullet.Type = Communication.Proto.BulletType.LineBullet; + msg.BulletMessage.Type = BulletType.LineBullet; break; default: - msg.MessageOfBullet.Type = Communication.Proto.BulletType.NullBulletType; + msg.BulletMessage.Type = BulletType.NullBulletType; break; } - msg.MessageOfBullet.X = bullet.Position.x; - msg.MessageOfBullet.Y = bullet.Position.y; - if (bullet.Parent != null) - msg.MessageOfBullet.ParentTeamID = bullet.Parent.TeamID; - switch (bullet.Place) + //if (bullet.Parent != null) + //msg.BulletMessage.MessageOfBullet.ParentTeamID = bullet.Parent.TeamID; + /*switch (bullet.Place) { case Preparation.Utility.PlaceType.Null: - msg.MessageOfBullet.Place = Communication.Proto.PlaceType.Null; + msg.BulletMessage.MessageOfBullet.Place = Communication.Proto.PlaceType.Null; break; case Preparation.Utility.PlaceType.Grass: - msg.MessageOfBullet.Place = Communication.Proto.PlaceType.Grass; + msg.BulletMessage.MessageOfBullet.Place = Communication.Proto.PlaceType.Grass; break; case Preparation.Utility.PlaceType.Grass: - msg.MessageOfBullet.Place = Communication.Proto.PlaceType.Grass; + msg.BulletMessage.MessageOfBullet.Place = Communication.Proto.PlaceType.Grass; break; case Preparation.Utility.PlaceType.Grass: - msg.MessageOfBullet.Place = Communication.Proto.PlaceType.Grass; + msg.BulletMessage.MessageOfBullet.Place = Communication.Proto.PlaceType.Grass; break; default: - msg.MessageOfBullet.Place = Communication.Proto.PlacccceType.NullPlaceType; + msg.BulletMessage.MessageOfBullet.Place = Communication.Proto.PlacccceType.NullPlaceType; break; - } + }*/ return msg; - }*/ + } - private static MessageOfProp Prop(Prop prop) + private static MessageOfObj Prop(Prop prop) { - MessageOfProp msg = new MessageOfProp(); - //msg.Type = PropType.NullPropType; 下面写 - msg.X = prop.Position.x; - msg.Y = prop.Position.y; - msg.FacingDirection = 0; - msg.Guid = 0; - msg.Place = PlaceType.NullPlaceType; - msg.Size = 0; - msg.IsMoving = false; - /* THUAI5中的内容 - msg.MessageOfProp.FacingDirection = prop.FacingDirection; - msg.MessageOfProp.Guid = prop.ID; - msg.MessageOfProp.IsMoving = prop.IsMoving;*/ + MessageOfObj msg = new MessageOfObj(); + //msg.PropMessage.Type = PropType.NullPropType; 下面写 + msg.PropMessage.X = prop.Position.x; + msg.PropMessage.Y = prop.Position.y; + msg.PropMessage.FacingDirection = 0; + msg.PropMessage.Guid = 0; + msg.PropMessage.Place = PlaceType.NullPlaceType; + msg.PropMessage.Size = 0; + msg.PropMessage.IsMoving = false; switch (prop.GetPropType()) { /*case Preparation.Utility.PropType.Gem: - msg.Type = PropType.Gem; + msg.PropMessage.Type = PropType.Gem; break; case Preparation.Utility.PropType.addLIFE: - msg.Type = PropType.AddLife; + msg.PropMessage.Type = PropType.AddLife; break; case Preparation.Utility.PropType.addSpeed: - msg.Type = PropType.AddSpeed; + msg.PropMessage.Type = PropType.AddSpeed; break; case Preparation.Utility.PropType.Shield: - msg.Type = PropType.Shield; + msg.PropMessage.Type = PropType.Shield; break; case Preparation.Utility.PropType.Spear: - msg.Type = PropType.Spear; + msg.PropMessage.Type = PropType.Spear; break;*/ default: - msg.Type = PropType.NullPropType; + msg.PropMessage.Type = PropType.NullPropType; break; } /*if(prop is Gem) { - msg.MessageOfProp.Size = ((Gem)prop).Size; + msg.PropMessage.MessageOfProp.Size = ((Gem)prop).Size; } else { - msg.MessageOfProp.Size = 1; + msg.PropMessage.MessageOfProp.Size = 1; } switch (prop.Place) { case Preparation.Utility.PlaceType.Null: - msg.MessageOfProp.Place = Communication.Proto.PlaceType.Null; + msg.PropMessage.MessageOfProp.Place = Communication.Proto.PlaceType.Null; break; case Preparation.Utility.PlaceType.Grass: - msg.MessageOfProp.Place = Communication.Proto.PlaceType.Grass; + msg.PropMessage.MessageOfProp.Place = Communication.Proto.PlaceType.Grass; break; case Preparation.Utility.PlaceType.Grass: - msg.MessageOfProp.Place = Communication.Proto.PlaceType.Grass; + msg.PropMessage.MessageOfProp.Place = Communication.Proto.PlaceType.Grass; break; case Preparation.Utility.PlaceType.Grass: - msg.MessageOfProp.Place = Communication.Proto.PlaceType.Grass; + msg.PropMessage.MessageOfProp.Place = Communication.Proto.PlaceType.Grass; break; default: - msg.MessageOfProp.Place = Communication.Proto.PlacccceType.NullPlaceType; + msg.PropMessage.MessageOfProp.Place = Communication.Proto.PlacccceType.NullPlaceType; break; }*/ return msg; } - /*private static MessageOfBombedBullet BombedBullet(BombedBullet bombedBullet) + private static MessageOfObj BombedBullet(BombedBullet bombedBullet) { - MessageOfBombedBullet msg = new MessageOfBombedBullet; + MessageOfObj msg = new MessageOfObj(); - msg.FacingDirection = bombedBullet.FacingDirection; - msg.X = bombedBullet.bulletHasBombed.Position.x; - msg.Y = bombedBullet.bulletHasBombed.Position.y; - msg.MappingID = bombedBullet.MappingID; - msg.BombRange = BulletFactory.BulletBombRange(bombedBullet.bulletHasBombed.TypeOfBullet); + msg.BombedBulletMessage.X = bombedBullet.bulletHasBombed.Position.x; + msg.BombedBulletMessage.Y = bombedBullet.bulletHasBombed.Position.y; + //msg.BombedBulletMessage.FacingDirection = bombedBullet.FacingDirection; XY类型转double? + msg.BombedBulletMessage.MappingId = bombedBullet.MappingID; + msg.BombedBulletMessage.BombRange = BulletFactory.BulletRadius(bombedBullet.bulletHasBombed.TypeOfBullet); // 待确认 switch (bombedBullet.bulletHasBombed.TypeOfBullet) { case Preparation.Utility.BulletType.OrdinaryBullet: - msg.MessageOfBombedBullet.Type = Communication.Proto.BulletType.OrdinaryBullet; + msg.BombedBulletMessage.Type = BulletType.OrdinaryBullet; break; case Preparation.Utility.BulletType.AtomBomb: - msg.MessageOfBombedBullet.Type = Communication.Proto.BulletType.AtomBomb; + msg.BombedBulletMessage.Type = BulletType.AtomBomb; break; case Preparation.Utility.BulletType.FastBullet: - msg.MessageOfBombedBullet.Type = Communication.Proto.BulletType.FastBullet; + msg.BombedBulletMessage.Type = BulletType.FastBullet; break; case Preparation.Utility.BulletType.LineBullet: - msg.MessageOfBombedBullet.Type = Communication.Proto.BulletType.LineBullet; + msg.BombedBulletMessage.Type = BulletType.LineBullet; break; default: - msg.MessageOfBombedBullet.Type = Communication.Proto.BulletType.NullBulletType; + msg.BombedBulletMessage.Type = BulletType.NullBulletType; break; } return msg; - }*/ + } - /*private static MessageToClient.Types.GameObjMessage PickedProp(PickedProp pickedProp) + private static MessageOfObj PickedProp(PickedProp pickedProp) { - MessageToClient.Types.GameObjMessage msg = new MessageToClient.Types.GameObjMessage(); - msg.MessageOfPickedProp = new MessageOfPickedProp(); + MessageOfObj msg = new MessageOfObj(); // MessageOfObj中没有PickedProp + /*msg.MessageOfPickedProp = new MessageOfPickedProp(); msg.MessageOfPickedProp.MappingID = pickedProp.MappingID; msg.MessageOfPickedProp.X = pickedProp.PropHasPicked.Position.x; @@ -534,8 +382,8 @@ namespace Server default: msg.MessageOfPickedProp.Type = Communication.Proto.PropType.NullPropType; break; - } + }*/ return msg; - }*/ + } } } diff --git a/logic/Server/GameServer.cs b/logic/Server/GameServer.cs index 1066a45..8d612bb 100644 --- a/logic/Server/GameServer.cs +++ b/logic/Server/GameServer.cs @@ -7,6 +7,7 @@ using System.Net.Http.Headers; using Gaming; using GameClass.GameObj; using Preparation.Utility; +using Playback; namespace Server @@ -37,7 +38,7 @@ namespace Server private readonly object messageToAllClientsLock = new(); public static readonly long SendMessageToClientIntervalInMilliseconds = 50; private readonly Semaphore endGameInfoSema = new(0, 1); - // private MessageWriter? mwr = null; + private MessageWriter? mwr = null; public SemaphoreSlim StartGameTest() { @@ -85,14 +86,24 @@ namespace Server var waitHandle = new SemaphoreSlim(gameState == true ? 1 : 0); // 注意修改 new Thread(() => { + bool flag = true; new FrameRateTaskExecutor ( () => game.GameMap.Timer.IsGaming, - ReportGame, - 1000, () => { - ReportGame(); // 最后发一次消息,唤醒发消息的线程,防止发消息的线程由于有概率处在 Wait 状态而卡住 + if (flag == true) + { + ReportGame(GameState.GameStart); + flag = false; + } + else ReportGame(GameState.GameRunning); + }, + SendMessageToClientIntervalInMilliseconds, + () => + { + ReportGame(GameState.GameEnd); // 最后发一次消息,唤醒发消息的线程,防止发消息的线程由于有概率处在 Wait 状态而卡住 + OnGameEnd(); return 0; } ).Start(); @@ -109,12 +120,41 @@ namespace Server public void WaitForEnd() { this.endGameSem.Wait(); + mwr?.Dispose(); } - public void ReportGame() + private void OnGameEnd() + { + game.ClearAllLists(); + mwr?.Flush(); + //if (options.ResultFileName != DefaultArgumentOptions.FileName) + //SaveGameResult(options.ResultFileName + ".json"); + //SendGameResult(); + endGameInfoSema.Release(); + } + + public void ReportGame(GameState gameState, bool requiredGaming = true) { - //currentGameInfo = null; var gameObjList = game.GetGameObj(); + lock (messageToAllClientsLock) + { + //currentGameInfo.MapMessage = (Messa(game.GameMap)); + switch (gameState) + { + case GameState.GameRunning: + case GameState.GameStart: + case GameState.GameEnd: + foreach (GameObj gameObj in gameObjList) + { + currentGameInfo.ObjMessage.Add(CopyInfo.Auto(gameObj)); + } + currentGameInfo.GameState = gameState; + mwr?.WriteOne(currentGameInfo); + break; + default: + break; + } + } foreach (var kvp in semaDict) { @@ -145,7 +185,19 @@ namespace Server return true; return false; } - + private MessageOfMap MapMsg(Map map) + { + MessageOfMap msgOfMap = new MessageOfMap(); + for (int i = 0; i < GameData.rows; i++) + { + msgOfMap.Row.Add(new MessageOfMap.Types.Row()); + for (int j = 0; j < GameData.cols; j++) + { + //msgOfMap.Row[i].Col.Add((int)map.ProtoGameMap[i, j]); int转placetype + } + } + return msgOfMap; + } public override Task TryConnection(IDMsg request, ServerCallContext context) { Console.WriteLine($"TryConnection ID: {request.PlayerId}"); @@ -224,7 +276,8 @@ namespace Server } while (game.GameMap.Timer.IsGaming); } - public override Task Trick(TrickMsg request, ServerCallContext context) + public override Task Attack(AttackMsg request, ServerCallContext context) + { game.Attack(request.PlayerId, request.Angle); BoolRes boolRes = new(); @@ -239,7 +292,7 @@ namespace Server public override Task 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]; game.MovePlayer(gameID, (int)request.TimeInMilliseconds, request.Angle); // 之后game.MovePlayer可能改为bool类型 @@ -348,7 +401,7 @@ namespace Server { try { - //mwr = new MessageWriter(options.FileName, options.TeamCount, options.PlayerCountPerTeam); + mwr = new MessageWriter(options.FileName, options.TeamCount, options.PlayerCountPerTeam); } catch { diff --git a/logic/Server/Server.csproj b/logic/Server/Server.csproj index 680d77a..59c5ae7 100644 --- a/logic/Server/Server.csproj +++ b/logic/Server/Server.csproj @@ -21,6 +21,7 @@ + diff --git a/logic/logic.sln b/logic/logic.sln index a030118..bf66570 100644 --- a/logic/logic.sln +++ b/logic/logic.sln @@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Protos", "..\dependency\pro EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameEngine", "GameEngine\GameEngine.csproj", "{1D1D07F3-C332-4407-AC1B-EAD73F8BB3F3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Playback", "..\playback\Playback\Playback.csproj", "{FF22960A-6BD9-4C80-A029-9A39FB8F64C4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +53,10 @@ Global {1D1D07F3-C332-4407-AC1B-EAD73F8BB3F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {1D1D07F3-C332-4407-AC1B-EAD73F8BB3F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {1D1D07F3-C332-4407-AC1B-EAD73F8BB3F3}.Release|Any CPU.Build.0 = Release|Any CPU + {FF22960A-6BD9-4C80-A029-9A39FB8F64C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF22960A-6BD9-4C80-A029-9A39FB8F64C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF22960A-6BD9-4C80-A029-9A39FB8F64C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF22960A-6BD9-4C80-A029-9A39FB8F64C4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/logic/规则Logic.md b/logic/规则Logic.md index 2beb5b1..7aa4128 100644 --- a/logic/规则Logic.md +++ b/logic/规则Logic.md @@ -1,7 +1,7 @@ # 规则Logic ## 说明 -- 版本V1.003 +- 版本V2.0 - 该规则直接服务于Sever,并非选手版本 - *斜体表示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 public enum BgmType { @@ -58,6 +56,7 @@ ### 物体 - 位置 +- 位置地形 - ID - 类型 - 面向角度 @@ -95,20 +94,20 @@ IsClimbingThroughWindows = 15, } ~~~ -- *Bgm(数组)* +- *Bgm(字典)* - 得分 - ~~回血率/原始回血率~~ - 当前子弹类型 - 原始子弹类型 -- 持有道具*(最多三个)(数组)* +- 持有道具 *(最多三个)(列表)* - 是否隐身 - 队伍ID - 玩家ID - 当前Buff - 职业类型 -- 拥有的被动技能(数组) -- 拥有的主动技能(数组) -- 各个主动技能CD(数组) +- 拥有的被动技能(列表) +- 拥有的主动技能(列表) +- 各个主动技能CD(字典) - *警戒半径* - *double 隐蔽度* - *翻墙速度* diff --git a/playback/Playback/MessageReader.cs b/playback/Playback/MessageReader.cs index f4badfe..e9c24b2 100644 --- a/playback/Playback/MessageReader.cs +++ b/playback/Playback/MessageReader.cs @@ -1,5 +1,5 @@ -using Communication.Proto; -using Google.Protobuf; +using Google.Protobuf; +using Protobuf; using System; using System.IO; using System.IO.Compression; diff --git a/playback/Playback/MessageWriter.cs b/playback/Playback/MessageWriter.cs index 923a4ab..a23d701 100644 --- a/playback/Playback/MessageWriter.cs +++ b/playback/Playback/MessageWriter.cs @@ -1,5 +1,5 @@ -using Communication.Proto; using Google.Protobuf; +using Protobuf; using System; using System.IO; using System.IO.Compression; diff --git a/playback/Playback/Playback.csproj b/playback/Playback/Playback.csproj index 70e6d49..74fc21b 100644 --- a/playback/Playback/Playback.csproj +++ b/playback/Playback/Playback.csproj @@ -1,9 +1,17 @@ - + - net6.0;netstandard2.1 + net6.0 enable enable + + + + + + + +