| @@ -78,6 +78,10 @@ jobs: | |||
| cp -r ./THUAI6/win/CAPI ./THUAI6/linux/ | |||
| cp -r ./THUAI6/win/CAPI ./THUAI6/osx/ | |||
| cp -r ./logic/cmd/map ./THUAI6/win/ | |||
| cp -r ./logic/cmd/map ./THUAI6/linux/ | |||
| cp -r ./logic/cmd/map ./THUAI6/osx/ | |||
| - name: Copy shell | |||
| run: | | |||
| cp -r ./CAPI/cmd/* ./THUAI6/win/ | |||
| @@ -1,5 +1,5 @@ | |||
| @echo off | |||
| .\win64\Server.exe --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName video | |||
| .\win64\Server.exe --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName video --mapResource ".\map\map1_final.txt" | |||
| pause | |||
| @@ -1,5 +1,5 @@ | |||
| @echo off | |||
| .\win64\Debug\Server.exe --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 | |||
| .\win64\Debug\Server.exe --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName video --mapResource ".\map\map1_final.txt" | |||
| pause | |||
| @@ -60,7 +60,7 @@ public: | |||
| virtual bool PickProp(THUAI6::PropType prop) = 0; | |||
| virtual bool UseProp(THUAI6::PropType prop) = 0; | |||
| virtual bool ThrowProp(THUAI6::PropType prop) = 0; | |||
| virtual bool UseSkill(int32_t skillID) = 0; | |||
| virtual bool UseSkill(int32_t skillID, int32_t skillParam) = 0; | |||
| virtual bool SendMessage(int64_t toID, std::string message, bool binary) = 0; | |||
| virtual bool HaveMessage() = 0; | |||
| virtual std::pair<int64_t, std::string> GetMessage() = 0; | |||
| @@ -108,7 +108,7 @@ public: | |||
| virtual std::future<bool> PickProp(THUAI6::PropType prop) = 0; | |||
| virtual std::future<bool> UseProp(THUAI6::PropType prop) = 0; | |||
| virtual std::future<bool> ThrowProp(THUAI6::PropType prop) = 0; | |||
| virtual std::future<bool> UseSkill(int32_t skillID) = 0; | |||
| virtual std::future<bool> UseSkill(int32_t skillID, int32_t skillParam = 0) = 0; | |||
| virtual std::future<bool> Attack(double angleInRadian) = 0; | |||
| virtual std::future<bool> OpenDoor() = 0; | |||
| @@ -236,7 +236,7 @@ public: | |||
| std::future<bool> PickProp(THUAI6::PropType prop) override; | |||
| std::future<bool> UseProp(THUAI6::PropType prop) override; | |||
| std::future<bool> ThrowProp(THUAI6::PropType prop) override; | |||
| std::future<bool> UseSkill(int32_t skillID) override; | |||
| std::future<bool> UseSkill(int32_t skillID, int32_t skillParam = 0) override; | |||
| std::future<bool> Attack(double angleInRadian) override; | |||
| @@ -329,7 +329,7 @@ public: | |||
| std::future<bool> PickProp(THUAI6::PropType prop) override; | |||
| std::future<bool> UseProp(THUAI6::PropType prop) override; | |||
| std::future<bool> ThrowProp(THUAI6::PropType prop) override; | |||
| std::future<bool> UseSkill(int32_t skillID) override; | |||
| std::future<bool> UseSkill(int32_t skillID, int32_t skillParam = 0) override; | |||
| std::future<bool> OpenDoor() override; | |||
| std::future<bool> CloseDoor() override; | |||
| @@ -410,7 +410,7 @@ public: | |||
| std::future<bool> PickProp(THUAI6::PropType prop) override; | |||
| std::future<bool> UseProp(THUAI6::PropType prop) override; | |||
| std::future<bool> ThrowProp(THUAI6::PropType prop) override; | |||
| std::future<bool> UseSkill(int32_t skillID) override; | |||
| std::future<bool> UseSkill(int32_t skillID, int32_t skillParam = 0) override; | |||
| std::future<bool> Attack(double angleInRadian) override; | |||
| @@ -488,7 +488,7 @@ public: | |||
| std::future<bool> PickProp(THUAI6::PropType prop) override; | |||
| std::future<bool> UseProp(THUAI6::PropType prop) override; | |||
| std::future<bool> ThrowProp(THUAI6::PropType prop) override; | |||
| std::future<bool> UseSkill(int32_t skillID) override; | |||
| std::future<bool> UseSkill(int32_t skillID, int32_t skillParam = 0) override; | |||
| std::future<bool> OpenDoor() override; | |||
| std::future<bool> CloseDoor() override; | |||
| @@ -31,7 +31,7 @@ public: | |||
| bool PickProp(THUAI6::PropType prop, int64_t playerID); | |||
| bool UseProp(THUAI6::PropType prop, int64_t playerID); | |||
| bool ThrowProp(THUAI6::PropType prop, int64_t playerID); | |||
| bool UseSkill(int32_t skillID, int64_t playerID); | |||
| bool UseSkill(int32_t skillID, int32_t skillParam, int64_t playerID); | |||
| bool SendMessage(int64_t toID, std::string message, bool binary, int64_t playerID); | |||
| bool OpenDoor(int64_t playerID); | |||
| bool CloseDoor(int64_t playerID); | |||
| @@ -57,6 +57,11 @@ private: | |||
| bool haveNewMessage = false; | |||
| protobuf::MessageToClient message2Client; | |||
| std::mutex mtxMessage; | |||
| std::mutex mtxLimit; | |||
| int counter; | |||
| int counterMove; | |||
| static constexpr const int limit = 50; | |||
| static constexpr const int moveLimit = 10; | |||
| std::condition_variable cvMessage; | |||
| }; | |||
| @@ -32,10 +32,10 @@ namespace Constants | |||
| // 人物属性相关 | |||
| SCCI int basicEncourageSpeed = 100; | |||
| SCCI int basicFixSpeed = 123; | |||
| SCCI int basicSpeedOfOpeningOrLocking = 4000; | |||
| SCCI int basicSpeedOfOpeningOrLocking = 5000; | |||
| SCCI int basicStudentSpeedOfClimbingThroughWindows = 1222; | |||
| SCCI int basicTrickerSpeedOfClimbingThroughWindows = 2540; | |||
| SCCI int basicSpeedOfOpenChest = 1000; | |||
| SCCI int basicSpeedOfOpenChest = 1250; | |||
| SCCI int basicHp = 3000000; | |||
| SCCI int basicMaxGamingAddiction = 60000; | |||
| @@ -138,19 +138,19 @@ namespace Constants | |||
| SCCI int moveSpeed = basicStudentSpeed * 9 / 10; | |||
| SCCI int maxHp = basicHp * 10; | |||
| SCCI int maxAddiction = basicMaxGamingAddiction * 10; | |||
| SCCI int fixSpeed = basicFixSpeed * 0; | |||
| SCCI int fixSpeed = (int)(basicFixSpeed * 50 / 123); | |||
| SCCI int encourageSpeed = basicEncourageSpeed * 8 / 10; | |||
| SCCI double concealment = 0.5; | |||
| SCCI int alertnessRadius = basicStudentAlertnessRadius / 2; | |||
| SCCI int viewRange = basicStudentViewRange * 9 / 10; | |||
| SCCI int alertnessRadius = basicStudentAlertnessRadius * 2 / 3; | |||
| SCCI int viewRange = basicStudentViewRange * 8 / 10; | |||
| SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking; | |||
| SCCI int speedOfClimbingThroughWindows = basicStudentSpeedOfClimbingThroughWindows / 2; | |||
| SCCI int speedOfClimbingThroughWindows = (int)(basicStudentSpeedOfClimbingThroughWindows * 1000 / 1222); | |||
| SCCI int speedOfOpenChest = basicSpeedOfOpenChest; | |||
| }; | |||
| struct StraightAStudent | |||
| { | |||
| SCCI int moveSpeed = basicStudentSpeed * 96 / 100; | |||
| SCCI int moveSpeed = basicStudentSpeed * 24 / 25; | |||
| SCCI int maxHp = basicHp * 11 / 10; | |||
| SCCI int maxAddiction = basicMaxGamingAddiction * 13 / 10; | |||
| SCCI int fixSpeed = basicFixSpeed * 11 / 10; | |||
| @@ -165,41 +165,41 @@ namespace Constants | |||
| struct Robot | |||
| { | |||
| SCCI int moveSpeed = basicStudentSpeed; | |||
| SCCI int maxHp = basicHp * 2 / 5; | |||
| SCCI int moveSpeed = basicStudentSpeed * 9 / 10; | |||
| SCCI int maxHp = basicHp * 3 / 10; | |||
| SCCI int maxAddiction = basicMaxGamingAddiction * 0; | |||
| SCCI int fixSpeed = basicFixSpeed; | |||
| SCCI int fixSpeed = basicFixSpeed * 7 / 10; | |||
| SCCI int encourageSpeed = 0; | |||
| SCCI double concealment = 1; | |||
| SCCI int alertnessRadius = basicStudentAlertnessRadius * 1; | |||
| SCCI int viewRange = basicStudentViewRange; | |||
| SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking; | |||
| SCCI double concealment = 0.8; | |||
| SCCI int alertnessRadius = 0; | |||
| SCCI int viewRange = 0; | |||
| SCCI int speedOfOpeningOrLocking = 0; | |||
| SCCI int speedOfClimbingThroughWindows = 1; | |||
| SCCI int speedOfOpenChest = basicSpeedOfOpenChest; | |||
| SCCI int speedOfOpenChest = basicSpeedOfOpenChest * 4 / 5; | |||
| }; | |||
| struct TechOtaku | |||
| { | |||
| SCCI int moveSpeed = basicStudentSpeed * 3 / 4; | |||
| SCCI int moveSpeed = (int)(basicStudentSpeed * 0.96); | |||
| SCCI int maxHp = basicHp * 9 / 10; | |||
| SCCI int maxAddiction = basicMaxGamingAddiction * 11 / 10; | |||
| SCCI int fixSpeed = basicFixSpeed * 11 / 10; | |||
| SCCI int encourageSpeed = basicEncourageSpeed * 9 / 10; | |||
| SCCI double concealment = 1; | |||
| SCCI int maxAddiction = basicMaxGamingAddiction; | |||
| SCCI int fixSpeed = (int)(basicFixSpeed * 0.9); | |||
| SCCI int encourageSpeed = basicEncourageSpeed; | |||
| SCCI double concealment = 1.1; | |||
| SCCI int alertnessRadius = basicStudentAlertnessRadius; | |||
| SCCI int viewRange = basicStudentViewRange * 9 / 10; | |||
| SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking; | |||
| SCCI int speedOfClimbingThroughWindows = basicStudentSpeedOfClimbingThroughWindows * 3 / 4; | |||
| SCCI int speedOfOpenChest = basicSpeedOfOpenChest; | |||
| SCCI int speedOfClimbingThroughWindows = (int)(basicStudentSpeedOfClimbingThroughWindows * 0.9); | |||
| SCCI int speedOfOpenChest = basicSpeedOfOpenChest * 22 / 25; | |||
| }; | |||
| struct Sunshine | |||
| { | |||
| SCCI int moveSpeed = basicStudentSpeed; | |||
| SCCI int maxHp = basicHp * 32 / 30; | |||
| SCCI int maxHp = basicHp * 16 / 15; | |||
| SCCI int maxAddiction = basicMaxGamingAddiction * 11 / 10; | |||
| SCCI int fixSpeed = basicFixSpeed; | |||
| SCCI int encourageSpeed = basicEncourageSpeed * 12 / 10; | |||
| SCCI int encourageSpeed = basicEncourageSpeed * 6 / 5; | |||
| SCCI double concealment = 1; | |||
| SCCI int alertnessRadius = basicStudentAlertnessRadius; | |||
| SCCI int viewRange = basicStudentViewRange; | |||
| @@ -244,7 +244,19 @@ namespace Constants | |||
| struct Punish | |||
| { | |||
| SCCI int skillCD = commonSkillCD * 1; | |||
| SCCI int skillCD = commonSkillCD * 3 / 2; | |||
| SCCI int durationTime = commonSkillTime * 0; | |||
| }; | |||
| struct SparksNSplash | |||
| { | |||
| SCCI int skillCD = commonSkillCD * 3 / 2; | |||
| SCCI int durationTime = commonSkillTime * 1; | |||
| }; | |||
| struct HaveTea | |||
| { | |||
| SCCI int skillCD = commonSkillCD * 3; | |||
| SCCI int durationTime = commonSkillTime * 0; | |||
| }; | |||
| @@ -292,7 +304,7 @@ namespace Constants | |||
| struct UseRobot | |||
| { | |||
| SCCI int skillCD = commonSkillCD / 300; | |||
| SCCI int skillCD = commonSkillCD / 15; | |||
| SCCI int durationTime = commonSkillTime * 0; | |||
| }; | |||
| @@ -304,8 +316,8 @@ namespace Constants | |||
| struct SummonGolem | |||
| { | |||
| SCCI int skillCD = commonSkillCD * 1; | |||
| SCCI int durationTime = commonSkillTime * 0; | |||
| SCCI int skillCD = commonSkillCD * 4 / 3; | |||
| SCCI int durationTime = commonSkillTime * 6; | |||
| }; | |||
| struct CommonAttackOfTricker | |||
| @@ -331,10 +343,10 @@ namespace Constants | |||
| SCCI int Speed = basicBulletMoveSpeed * 25 / 10; | |||
| SCCI bool IsRemoteAttack = true; | |||
| SCCI int CastTime = basicCastTime * 4 / 5; | |||
| SCCI int CastTime = basicCastTime * 6 / 5; | |||
| SCCI int Backswing = 0; | |||
| SCCI int RecoveryFromHit = 0; | |||
| SCCI int cd = basicBackswing / 2; | |||
| SCCI int cd = basicBackswing * 3 / 4; | |||
| SCCI int maxBulletNum = 1; | |||
| }; | |||
| @@ -347,7 +359,7 @@ namespace Constants | |||
| SCCI bool IsRemoteAttack = false; | |||
| SCCI int CastTime = (int)BulletAttackRange * 1000 / Speed; | |||
| SCCI int Backswing = basicRecoveryFromHit; | |||
| SCCI int Backswing = basicBackswing * 3 / 2; | |||
| SCCI int RecoveryFromHit = basicRecoveryFromHit; | |||
| SCCI int cd = basicCD; | |||
| SCCI int maxBulletNum = 1; | |||
| @@ -356,10 +368,25 @@ namespace Constants | |||
| struct JumpyDumpty | |||
| { | |||
| SCCI double BulletBombRange = basicBulletBombRange / 2; | |||
| SCCI double BulletAttackRange = basicRemoteAttackRange * 2; | |||
| SCCI double BulletAttackRange = basicAttackShortRange * 16 / 22; | |||
| SCCI int ap = (int)(basicApOfTricker * 0.6); | |||
| SCCI int Speed = basicBulletMoveSpeed * 43 / 37; | |||
| SCCI bool IsRemoteAttack = false; | |||
| }; | |||
| struct Strike | |||
| { | |||
| SCCI double BulletBombRange = 0; | |||
| SCCI double BulletAttackRange = basicAttackShortRange; | |||
| SCCI int ap = basicApOfTricker * 16 / 15; | |||
| SCCI int Speed = basicBulletMoveSpeed * 125 / 148; | |||
| SCCI bool IsRemoteAttack = false; | |||
| SCCI int CastTime = basicCastTime * 16 / 25; | |||
| SCCI int Backswing = basicBackswing; | |||
| SCCI int RecoveryFromHit = basicRecoveryFromHit; | |||
| SCCI int cd = basicBackswing; | |||
| SCCI int maxBulletNum = 1; | |||
| }; | |||
| } // namespace Constants | |||
| #endif | |||
| @@ -116,7 +116,7 @@ private: | |||
| bool PickProp(THUAI6::PropType prop) override; | |||
| bool UseProp(THUAI6::PropType prop) override; | |||
| bool ThrowProp(THUAI6::PropType prop) override; | |||
| bool UseSkill(int32_t skillID) override; | |||
| bool UseSkill(int32_t skillID, int32_t skillParam) override; | |||
| bool SendMessage(int64_t toID, std::string message, bool binary) override; | |||
| bool HaveMessage() override; | |||
| @@ -62,6 +62,7 @@ namespace THUAI6 | |||
| AddHpOrAp = 6, | |||
| ShieldOrSpear = 7, | |||
| RecoveryFromDizziness = 8, | |||
| CraftingBench = 9, | |||
| }; | |||
| enum class BulletType : unsigned char | |||
| @@ -71,7 +72,7 @@ namespace THUAI6 | |||
| CommonAttackOfTricker = 2, | |||
| BombBomb = 3, | |||
| JumpyDumpty = 4, | |||
| AtomBomb = 5, | |||
| Strike = 5, | |||
| }; | |||
| // 玩家类型 | |||
| @@ -197,7 +198,7 @@ namespace THUAI6 | |||
| PlayerType playerType; // 玩家类型 | |||
| std::vector<PropType> props; | |||
| PlaceType place; // 所处格子的类型 | |||
| // PlaceType place; // 所处格子的类型 | |||
| BulletType bulletType; | |||
| PlayerState playerState; | |||
| @@ -232,9 +233,9 @@ namespace THUAI6 | |||
| double facingDirection; // 朝向 | |||
| int64_t guid; // 全局唯一ID | |||
| PlayerType team; // 子弹所属队伍 | |||
| PlaceType place; // 所处格子的类型 | |||
| double bombRange; // 炸弹爆炸范围 | |||
| int32_t speed; // 子弹速度 | |||
| // PlaceType place; // 所处格子的类型 | |||
| double bombRange; // 炸弹爆炸范围 | |||
| int32_t speed; // 子弹速度 | |||
| }; | |||
| struct BombedBullet | |||
| @@ -253,7 +254,7 @@ namespace THUAI6 | |||
| int32_t y; | |||
| int64_t guid; | |||
| PropType type; | |||
| PlaceType place; | |||
| // PlaceType place; | |||
| double facingDirection; // 朝向 | |||
| }; | |||
| @@ -363,6 +364,7 @@ namespace THUAI6 | |||
| {PropType::AddHpOrAp, "AddHpOrAp"}, | |||
| {PropType::ShieldOrSpear, "ShieldOrSpear"}, | |||
| {PropType::RecoveryFromDizziness, "RecoveryFromDizziness"}, | |||
| {PropType::CraftingBench, "CraftingBench"}, | |||
| }; | |||
| @@ -372,7 +374,7 @@ namespace THUAI6 | |||
| {BulletType::CommonAttackOfTricker, "CommonAttackOfTricker"}, | |||
| {BulletType::BombBomb, "BombBomb"}, | |||
| {BulletType::JumpyDumpty, "JumpyDumpty"}, | |||
| {BulletType::AtomBomb, "AtomBomb"}, | |||
| {BulletType::Strike, "Strike"}, | |||
| }; | |||
| inline std::map<StudentBuffType, std::string> studentBuffDict{ | |||
| @@ -108,6 +108,7 @@ namespace Proto2THUAI6 | |||
| {protobuf::PropType::ADD_LIFE_OR_CLAIRAUDIENCE, THUAI6::PropType::AddLifeOrClairaudience}, | |||
| {protobuf::PropType::SHIELD_OR_SPEAR, THUAI6::PropType::ShieldOrSpear}, | |||
| {protobuf::PropType::RECOVERY_FROM_DIZZINESS, THUAI6::PropType::RecoveryFromDizziness}, | |||
| {protobuf::PropType::CRAFTING_BENCH, THUAI6::PropType::CraftingBench}, | |||
| }; | |||
| inline std::map<protobuf::PlayerType, THUAI6::PlayerType> playerTypeDict{ | |||
| @@ -186,7 +187,7 @@ namespace Proto2THUAI6 | |||
| {protobuf::BulletType::COMMON_ATTACK_OF_TRICKER, THUAI6::BulletType::CommonAttackOfTricker}, | |||
| {protobuf::BulletType::BOMB_BOMB, THUAI6::BulletType::BombBomb}, | |||
| {protobuf::BulletType::JUMPY_DUMPTY, THUAI6::BulletType::JumpyDumpty}, | |||
| {protobuf::BulletType::ATOM_BOMB, THUAI6::BulletType::AtomBomb}, | |||
| {protobuf::BulletType::STRIKE, THUAI6::BulletType::Strike}, | |||
| }; | |||
| inline std::map<protobuf::MessageOfObj::MessageOfObjCase, THUAI6::MessageOfObj> messageOfObjDict{ | |||
| @@ -227,7 +228,7 @@ namespace Proto2THUAI6 | |||
| tricker->timeUntilSkillAvailable.clear(); | |||
| for (int i = 0; i < trickerMsg.time_until_skill_available().size(); i++) | |||
| tricker->timeUntilSkillAvailable.push_back(trickerMsg.time_until_skill_available(i)); | |||
| tricker->place = placeTypeDict[trickerMsg.place()]; | |||
| // tricker->place = placeTypeDict[trickerMsg.place()]; | |||
| tricker->playerState = playerStateDict[trickerMsg.player_state()]; | |||
| tricker->props.clear(); | |||
| for (int i = 0; i < trickerMsg.prop().size(); i++) | |||
| @@ -269,7 +270,7 @@ namespace Proto2THUAI6 | |||
| student->props.clear(); | |||
| for (int i = 0; i < studentMsg.prop().size(); i++) | |||
| student->props.push_back(propTypeDict[studentMsg.prop(i)]); | |||
| student->place = placeTypeDict[studentMsg.place()]; | |||
| // student->place = placeTypeDict[studentMsg.place()]; | |||
| student->playerState = playerStateDict[studentMsg.player_state()]; | |||
| student->determination = studentMsg.determination(); | |||
| student->addiction = studentMsg.addiction(); | |||
| @@ -286,7 +287,7 @@ namespace Proto2THUAI6 | |||
| prop->x = propMsg.x(); | |||
| prop->y = propMsg.y(); | |||
| prop->type = propTypeDict[propMsg.type()]; | |||
| prop->place = placeTypeDict[propMsg.place()]; | |||
| // prop->place = placeTypeDict[propMsg.place()]; | |||
| prop->guid = propMsg.guid(); | |||
| prop->facingDirection = propMsg.facing_direction(); | |||
| return prop; | |||
| @@ -313,7 +314,7 @@ namespace Proto2THUAI6 | |||
| bullet->facingDirection = bulletMsg.facing_direction(); | |||
| bullet->guid = bulletMsg.guid(); | |||
| bullet->team = playerTypeDict[bulletMsg.team()]; | |||
| bullet->place = placeTypeDict[bulletMsg.place()]; | |||
| // bullet->place = placeTypeDict[bulletMsg.place()]; | |||
| bullet->bombRange = bulletMsg.bomb_range(); | |||
| bullet->speed = bulletMsg.speed(); | |||
| return bullet; | |||
| @@ -373,6 +374,7 @@ namespace THUAI62Proto | |||
| {THUAI6::PropType::AddLifeOrClairaudience, protobuf::PropType::ADD_LIFE_OR_CLAIRAUDIENCE}, | |||
| {THUAI6::PropType::AddSpeed, protobuf::PropType::ADD_SPEED}, | |||
| {THUAI6::PropType::ShieldOrSpear, protobuf::PropType::SHIELD_OR_SPEAR}, | |||
| {THUAI6::PropType::CraftingBench, protobuf::PropType::CRAFTING_BENCH}, | |||
| }; | |||
| inline std::map<THUAI6::PlayerType, protobuf::PlayerType> playerTypeDict{ | |||
| @@ -486,11 +488,12 @@ namespace THUAI62Proto | |||
| return attackMsg; | |||
| } | |||
| inline protobuf::SkillMsg THUAI62ProtobufSkill(int32_t skillID, int64_t id) | |||
| inline protobuf::SkillMsg THUAI62ProtobufSkill(int32_t skillID, int32_t skillParam, int64_t id) | |||
| { | |||
| protobuf::SkillMsg skillMsg; | |||
| skillMsg.set_skill_id(skillID); | |||
| skillMsg.set_player_id(id); | |||
| skillMsg.set_skill_param(skillParam); | |||
| return skillMsg; | |||
| } | |||
| } // namespace THUAI62Proto | |||
| @@ -106,16 +106,16 @@ std::future<bool> TrickerAPI::ThrowProp(THUAI6::PropType prop) | |||
| { return logic.ThrowProp(prop); }); | |||
| } | |||
| std::future<bool> StudentAPI::UseSkill(int32_t skillID) | |||
| std::future<bool> StudentAPI::UseSkill(int32_t skillID, int32_t skillParam) | |||
| { | |||
| return std::async(std::launch::async, [=]() | |||
| { return logic.UseSkill(skillID); }); | |||
| { return logic.UseSkill(skillID, skillParam); }); | |||
| } | |||
| std::future<bool> TrickerAPI::UseSkill(int32_t skillID) | |||
| std::future<bool> TrickerAPI::UseSkill(int32_t skillID, int32_t skillParam) | |||
| { | |||
| return std::async(std::launch::async, [=]() | |||
| { return logic.UseSkill(skillID); }); | |||
| { return logic.UseSkill(skillID, skillParam); }); | |||
| } | |||
| std::future<bool> StudentAPI::OpenDoor() | |||
| @@ -20,6 +20,13 @@ Communication::Communication(std::string sIP, std::string sPort) | |||
| bool Communication::Move(int64_t time, double angle, int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit || counterMove >= moveLimit) | |||
| return false; | |||
| counter++; | |||
| counterMove++; | |||
| } | |||
| protobuf::MoveRes moveResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufMove(time, angle, playerID); | |||
| @@ -32,6 +39,12 @@ bool Communication::Move(int64_t time, double angle, int64_t playerID) | |||
| bool Communication::PickProp(THUAI6::PropType prop, int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes pickPropResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufProp(prop, playerID); | |||
| @@ -44,6 +57,12 @@ bool Communication::PickProp(THUAI6::PropType prop, int64_t playerID) | |||
| bool Communication::UseProp(THUAI6::PropType prop, int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes usePropResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufProp(prop, playerID); | |||
| @@ -56,6 +75,12 @@ bool Communication::UseProp(THUAI6::PropType prop, int64_t playerID) | |||
| bool Communication::ThrowProp(THUAI6::PropType prop, int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes throwPropResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufProp(prop, playerID); | |||
| @@ -66,11 +91,17 @@ bool Communication::ThrowProp(THUAI6::PropType prop, int64_t playerID) | |||
| return false; | |||
| } | |||
| bool Communication::UseSkill(int32_t skillID, int64_t playerID) | |||
| bool Communication::UseSkill(int32_t skillID, int32_t skillParam, int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes useSkillResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufSkill(skillID, playerID); | |||
| auto request = THUAI62Proto::THUAI62ProtobufSkill(skillID, skillParam, playerID); | |||
| auto status = THUAI6Stub->UseSkill(&context, request, &useSkillResult); | |||
| if (status.ok()) | |||
| return useSkillResult.act_success(); | |||
| @@ -80,6 +111,12 @@ bool Communication::UseSkill(int32_t skillID, int64_t playerID) | |||
| bool Communication::SendMessage(int64_t toID, std::string message, bool binary, int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes sendMessageResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufSend(std::move(message), toID, binary, playerID); | |||
| @@ -92,6 +129,12 @@ bool Communication::SendMessage(int64_t toID, std::string message, bool binary, | |||
| bool Communication::OpenDoor(int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes openDoorResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufID(playerID); | |||
| @@ -104,6 +147,12 @@ bool Communication::OpenDoor(int64_t playerID) | |||
| bool Communication::CloseDoor(int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes closeDoorResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufID(playerID); | |||
| @@ -116,6 +165,12 @@ bool Communication::CloseDoor(int64_t playerID) | |||
| bool Communication::SkipWindow(int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes skipWindowResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufID(playerID); | |||
| @@ -128,6 +183,12 @@ bool Communication::SkipWindow(int64_t playerID) | |||
| bool Communication::StartOpenGate(int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes startOpenGateResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufID(playerID); | |||
| @@ -140,6 +201,12 @@ bool Communication::StartOpenGate(int64_t playerID) | |||
| bool Communication::StartOpenChest(int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes startOpenChestResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufID(playerID); | |||
| @@ -152,6 +219,13 @@ bool Communication::StartOpenChest(int64_t playerID) | |||
| bool Communication::EndAllAction(int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit || counterMove >= moveLimit) | |||
| return false; | |||
| counter++; | |||
| counterMove++; | |||
| } | |||
| protobuf::BoolRes endAllActionResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufID(playerID); | |||
| @@ -164,6 +238,12 @@ bool Communication::EndAllAction(int64_t playerID) | |||
| bool Communication::Graduate(int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes graduateResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufID(playerID); | |||
| @@ -176,6 +256,12 @@ bool Communication::Graduate(int64_t playerID) | |||
| bool Communication::StartLearning(int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes startLearningResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufID(playerID); | |||
| @@ -188,6 +274,12 @@ bool Communication::StartLearning(int64_t playerID) | |||
| bool Communication::StartRouseMate(int64_t playerID, int64_t mateID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes saveStudentResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufTreatAndRescue(playerID, mateID); | |||
| @@ -200,6 +292,12 @@ bool Communication::StartRouseMate(int64_t playerID, int64_t mateID) | |||
| bool Communication::StartEncourageMate(int64_t playerID, int64_t mateID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes healStudentResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufTreatAndRescue(playerID, mateID); | |||
| @@ -212,6 +310,12 @@ bool Communication::StartEncourageMate(int64_t playerID, int64_t mateID) | |||
| bool Communication::Attack(double angle, int64_t playerID) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| if (counter >= limit) | |||
| return false; | |||
| counter++; | |||
| } | |||
| protobuf::BoolRes attackResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufAttack(angle, playerID); | |||
| @@ -229,9 +333,7 @@ bool Communication::TryConnection(int64_t playerID) | |||
| auto request = THUAI62Proto::THUAI62ProtobufID(playerID); | |||
| auto status = THUAI6Stub->TryConnection(&context, request, &reply); | |||
| if (status.ok()) | |||
| { | |||
| return true; | |||
| } | |||
| else | |||
| return false; | |||
| } | |||
| @@ -254,6 +356,8 @@ void Communication::AddPlayer(int64_t playerID, THUAI6::PlayerType playerType, T | |||
| auto MessageReader = THUAI6Stub->AddPlayer(&context, playerMsg); | |||
| protobuf::MessageToClient buffer2Client; | |||
| counter = 0; | |||
| counterMove = 0; | |||
| while (MessageReader->Read(&buffer2Client)) | |||
| { | |||
| @@ -261,6 +365,11 @@ void Communication::AddPlayer(int64_t playerID, THUAI6::PlayerType playerType, T | |||
| std::lock_guard<std::mutex> lock(mtxMessage); | |||
| message2Client = std::move(buffer2Client); | |||
| haveNewMessage = true; | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxLimit); | |||
| counter = 0; | |||
| counterMove = 0; | |||
| } | |||
| } | |||
| cvMessage.notify_one(); | |||
| } | |||
| @@ -212,21 +212,21 @@ std::future<bool> TrickerDebugAPI::ThrowProp(THUAI6::PropType prop) | |||
| return result; }); | |||
| } | |||
| std::future<bool> StudentDebugAPI::UseSkill(int32_t skillID) | |||
| std::future<bool> StudentDebugAPI::UseSkill(int32_t skillID, int32_t skillParam) | |||
| { | |||
| logger->info("UseSkill: skillID={}, called at {}ms", skillID, Time::TimeSinceStart(startPoint)); | |||
| logger->info("UseSkill: skillID={}, skillParam={}, called at {}ms", skillID, skillParam, Time::TimeSinceStart(startPoint)); | |||
| return std::async(std::launch::async, [=]() | |||
| { auto result = logic.UseSkill(skillID); | |||
| { auto result = logic.UseSkill(skillID, skillParam); | |||
| if (!result) | |||
| logger->warn("UseSkill: failed at {}ms", Time::TimeSinceStart(startPoint)); | |||
| return result; }); | |||
| } | |||
| std::future<bool> TrickerDebugAPI::UseSkill(int32_t skillID) | |||
| std::future<bool> TrickerDebugAPI::UseSkill(int32_t skillID, int32_t skillParam) | |||
| { | |||
| logger->info("UseSkill: skillID={}, called at {}ms", skillID, Time::TimeSinceStart(startPoint)); | |||
| logger->info("UseSkill: skillID={}, skillParam={}, called at {}ms", skillID, skillParam, Time::TimeSinceStart(startPoint)); | |||
| return std::async(std::launch::async, [=]() | |||
| { auto result = logic.UseSkill(skillID); | |||
| { auto result = logic.UseSkill(skillID, skillParam); | |||
| if (!result) | |||
| logger->warn("UseSkill: failed at {}ms", Time::TimeSinceStart(startPoint)); | |||
| return result; }); | |||
| @@ -684,7 +684,7 @@ void StudentDebugAPI::PrintStudent() const | |||
| { | |||
| logger->info("******Student Info******"); | |||
| logger->info("playerID={}, GUID={}, x={}, y={}", student->playerID, student->guid, student->x, student->y); | |||
| logger->info("speed={}, view range={}, place={}, radius={}", student->speed, student->viewRange, THUAI6::placeTypeDict[student->place], student->radius); | |||
| logger->info("speed={}, view range={}, radius={}", student->speed, student->viewRange, student->radius); | |||
| std::string skillTime = ""; | |||
| for (const auto& time : student->timeUntilSkillAvailable) | |||
| skillTime += std::to_string(time) + ", "; | |||
| @@ -709,7 +709,7 @@ void TrickerDebugAPI::PrintStudent() const | |||
| { | |||
| logger->info("******Student Info******"); | |||
| logger->info("playerID={}, GUID={}, x={}, y={}", student->playerID, student->guid, student->x, student->y); | |||
| logger->info("speed={}, view range={}, place={}, radius={}", student->speed, student->viewRange, THUAI6::placeTypeDict[student->place], student->radius); | |||
| logger->info("speed={}, view range={}, radius={}", student->speed, student->viewRange, student->radius); | |||
| std::string skillTime = ""; | |||
| for (const auto& time : student->timeUntilSkillAvailable) | |||
| skillTime += std::to_string(time) + ", "; | |||
| @@ -734,7 +734,7 @@ void StudentDebugAPI::PrintTricker() const | |||
| { | |||
| logger->info("******Tricker Info******"); | |||
| logger->info("playerID={}, GUID={}, x={}, y={}", tricker->playerID, tricker->guid, tricker->x, tricker->y); | |||
| logger->info("speed={}, view range={}, place={}, radius={}", tricker->speed, tricker->viewRange, THUAI6::placeTypeDict[tricker->place], tricker->radius); | |||
| logger->info("speed={}, view range={}, radius={}", tricker->speed, tricker->viewRange, tricker->radius); | |||
| std::string skillTime = ""; | |||
| for (const auto& time : tricker->timeUntilSkillAvailable) | |||
| skillTime += std::to_string(time) + ", "; | |||
| @@ -758,7 +758,7 @@ void TrickerDebugAPI::PrintTricker() const | |||
| { | |||
| logger->info("******Tricker Info******"); | |||
| logger->info("playerID={}, GUID={}, x={}, y={}", tricker->playerID, tricker->guid, tricker->x, tricker->y); | |||
| logger->info("speed={}, view range={}, place={}, radius={}", tricker->speed, tricker->viewRange, THUAI6::placeTypeDict[tricker->place], tricker->radius); | |||
| logger->info("speed={}, view range={}, radius={}", tricker->speed, tricker->viewRange, tricker->radius); | |||
| std::string skillTime = ""; | |||
| for (const auto& time : tricker->timeUntilSkillAvailable) | |||
| skillTime += std::to_string(time) + ", "; | |||
| @@ -781,7 +781,7 @@ void StudentDebugAPI::PrintProp() const | |||
| for (auto prop : logic.GetProps()) | |||
| { | |||
| logger->info("******Prop Info******"); | |||
| logger->info("GUID={}, x={}, y={}, place={}, facing direction={}", prop->guid, prop->x, prop->y, THUAI6::placeTypeDict[prop->place], prop->facingDirection); | |||
| logger->info("GUID={}, x={}, y={}, facing direction={}", prop->guid, prop->x, prop->y, prop->facingDirection); | |||
| logger->info("*********************\n"); | |||
| } | |||
| } | |||
| @@ -791,7 +791,7 @@ void TrickerDebugAPI::PrintProp() const | |||
| for (auto prop : logic.GetProps()) | |||
| { | |||
| logger->info("******Prop Info******"); | |||
| logger->info("GUID={}, x={}, y={}, place={}, facing direction={}", prop->guid, prop->x, prop->y, THUAI6::placeTypeDict[prop->place], prop->facingDirection); | |||
| logger->info("GUID={}, x={}, y={}, facing direction={}", prop->guid, prop->x, prop->y, prop->facingDirection); | |||
| logger->info("*********************\n"); | |||
| } | |||
| } | |||
| @@ -801,7 +801,7 @@ void StudentDebugAPI::PrintSelfInfo() const | |||
| auto student = logic.StudentGetSelfInfo(); | |||
| logger->info("******Self Info******"); | |||
| logger->info("playerID={}, GUID={}, x={}, y={}", student->playerID, student->guid, student->x, student->y); | |||
| logger->info("speed={}, view range={}, place={}, radius={}", student->speed, student->viewRange, THUAI6::placeTypeDict[student->place], student->radius); | |||
| logger->info("speed={}, view range={}, radius={}", student->speed, student->viewRange, student->radius); | |||
| std::string skillTime = ""; | |||
| for (const auto& time : student->timeUntilSkillAvailable) | |||
| skillTime += std::to_string(time) + ", "; | |||
| @@ -824,7 +824,7 @@ void TrickerDebugAPI::PrintSelfInfo() const | |||
| auto tricker = logic.TrickerGetSelfInfo(); | |||
| logger->info("******Self Info******"); | |||
| logger->info("playerID={}, GUID={}, x={}, y={}", tricker->playerID, tricker->guid, tricker->x, tricker->y); | |||
| logger->info("speed={}, view range={}, place={}, radius={}", tricker->speed, tricker->viewRange, THUAI6::placeTypeDict[tricker->place], tricker->radius); | |||
| logger->info("speed={}, view range={}, radius={}", tricker->speed, tricker->viewRange, tricker->radius); | |||
| std::string skillTime = ""; | |||
| for (const auto& time : tricker->timeUntilSkillAvailable) | |||
| skillTime += std::to_string(time) + ", "; | |||
| @@ -208,10 +208,10 @@ bool Logic::ThrowProp(THUAI6::PropType prop) | |||
| return pComm->ThrowProp(prop, playerID); | |||
| } | |||
| bool Logic::UseSkill(int32_t skill) | |||
| bool Logic::UseSkill(int32_t skill, int32_t skillParam) | |||
| { | |||
| logger->debug("Called UseSkill"); | |||
| return pComm->UseSkill(skill, playerID); | |||
| return pComm->UseSkill(skill, skillParam, playerID); | |||
| } | |||
| bool Logic::SendMessage(int64_t toID, std::string message, bool binary) | |||
| @@ -315,68 +315,81 @@ void Logic::ProcessMessage() | |||
| { | |||
| auto messageThread = [this]() | |||
| { | |||
| logger->info("Message thread start!"); | |||
| pComm->AddPlayer(playerID, playerType, studentType, trickerType); | |||
| while (gameState != THUAI6::GameState::GameEnd) | |||
| try | |||
| { | |||
| auto clientMsg = pComm->GetMessage2Client(); // 在获得新消息之前阻塞 | |||
| logger->debug("Get message from server!"); | |||
| gameState = Proto2THUAI6::gameStateDict[clientMsg.game_state()]; | |||
| switch (gameState) | |||
| logger->info("Message thread start!"); | |||
| pComm->AddPlayer(playerID, playerType, studentType, trickerType); | |||
| while (gameState != THUAI6::GameState::GameEnd) | |||
| { | |||
| case THUAI6::GameState::GameStart: | |||
| logger->info("Game Start!"); | |||
| auto clientMsg = pComm->GetMessage2Client(); // 在获得新消息之前阻塞 | |||
| logger->debug("Get message from server!"); | |||
| gameState = Proto2THUAI6::gameStateDict[clientMsg.game_state()]; | |||
| switch (gameState) | |||
| { | |||
| case THUAI6::GameState::GameStart: | |||
| logger->info("Game Start!"); | |||
| // 读取地图 | |||
| for (const auto& item : clientMsg.obj_message()) | |||
| if (Proto2THUAI6::messageOfObjDict[item.message_of_obj_case()] == THUAI6::MessageOfObj::MapMessage) | |||
| { | |||
| auto map = std::vector<std::vector<THUAI6::PlaceType>>(); | |||
| auto mapResult = item.map_message(); | |||
| for (int i = 0; i < item.map_message().row_size(); i++) | |||
| // 读取地图 | |||
| for (const auto& item : clientMsg.obj_message()) | |||
| if (Proto2THUAI6::messageOfObjDict[item.message_of_obj_case()] == THUAI6::MessageOfObj::MapMessage) | |||
| { | |||
| std::vector<THUAI6::PlaceType> row; | |||
| for (int j = 0; j < mapResult.row(i).col_size(); j++) | |||
| auto map = std::vector<std::vector<THUAI6::PlaceType>>(); | |||
| auto mapResult = item.map_message(); | |||
| for (int i = 0; i < item.map_message().row_size(); i++) | |||
| { | |||
| if (Proto2THUAI6::placeTypeDict.count(mapResult.row(i).col(j)) == 0) | |||
| logger->error("Unknown place type!"); | |||
| row.push_back(Proto2THUAI6::placeTypeDict[mapResult.row(i).col(j)]); | |||
| std::vector<THUAI6::PlaceType> row; | |||
| for (int j = 0; j < mapResult.row(i).col_size(); j++) | |||
| { | |||
| if (Proto2THUAI6::placeTypeDict.count(mapResult.row(i).col(j)) == 0) | |||
| logger->error("Unknown place type!"); | |||
| row.push_back(Proto2THUAI6::placeTypeDict[mapResult.row(i).col(j)]); | |||
| } | |||
| map.push_back(std::move(row)); | |||
| } | |||
| map.push_back(std::move(row)); | |||
| bufferState->gameMap = std::move(map); | |||
| currentState->gameMap = bufferState->gameMap; | |||
| logger->info("Map loaded!"); | |||
| break; | |||
| } | |||
| bufferState->gameMap = std::move(map); | |||
| currentState->gameMap = bufferState->gameMap; | |||
| logger->info("Map loaded!"); | |||
| break; | |||
| if (currentState->gameMap.empty()) | |||
| { | |||
| logger->error("Map not loaded!"); | |||
| throw std::runtime_error("Map not loaded!"); | |||
| } | |||
| if (currentState->gameMap.empty()) | |||
| { | |||
| logger->error("Map not loaded!"); | |||
| throw std::runtime_error("Map not loaded!"); | |||
| } | |||
| LoadBuffer(clientMsg); | |||
| LoadBuffer(clientMsg); | |||
| AILoop = true; | |||
| UnBlockAI(); | |||
| AILoop = true; | |||
| UnBlockAI(); | |||
| break; | |||
| case THUAI6::GameState::GameRunning: | |||
| break; | |||
| case THUAI6::GameState::GameRunning: | |||
| LoadBuffer(clientMsg); | |||
| break; | |||
| default: | |||
| logger->debug("Unknown GameState!"); | |||
| break; | |||
| LoadBuffer(clientMsg); | |||
| break; | |||
| default: | |||
| logger->debug("Unknown GameState!"); | |||
| break; | |||
| } | |||
| } | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxBuffer); | |||
| bufferUpdated = true; | |||
| counterBuffer = -1; | |||
| } | |||
| cvBuffer.notify_one(); | |||
| logger->info("Game End!"); | |||
| AILoop = false; | |||
| } | |||
| catch (const std::exception& e) | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxBuffer); | |||
| bufferUpdated = true; | |||
| counterBuffer = -1; | |||
| std::cerr << "C++ Exception: " << e.what() << std::endl; | |||
| AILoop = false; | |||
| } | |||
| catch (...) | |||
| { | |||
| std::cerr << "Unknown Exception!" << std::endl; | |||
| AILoop = false; | |||
| } | |||
| cvBuffer.notify_one(); | |||
| logger->info("Game End!"); | |||
| AILoop = false; | |||
| }; | |||
| std::thread(messageThread).detach(); | |||
| } | |||
| @@ -570,13 +583,13 @@ void Logic::LoadBufferCase(const protobuf::MessageOfObj& item) | |||
| { | |||
| if (Proto2THUAI6::newsTypeDict[news.news_case()] == THUAI6::NewsType::TextMessage) | |||
| { | |||
| messageQueue.emplace(std::make_pair(news.to_id(), news.text_message())); | |||
| messageQueue.emplace(std::make_pair(news.from_id(), news.text_message())); | |||
| logger->debug("Add News!"); | |||
| } | |||
| else if (Proto2THUAI6::newsTypeDict[news.news_case()] == THUAI6::NewsType::BinaryMessage) | |||
| { | |||
| messageQueue.emplace(std::make_pair(news.to_id(), news.binary_message())); | |||
| logger->debug("Add News!"); | |||
| messageQueue.emplace(std::make_pair(news.from_id(), news.binary_message())); | |||
| logger->debug("Add Binary News!"); | |||
| } | |||
| else | |||
| logger->error("Unknown NewsType!"); | |||
| @@ -699,7 +712,7 @@ bool Logic::HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) | |||
| void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool file, bool print, bool warnOnly) | |||
| { | |||
| // 建立日志组件 | |||
| auto fileLogger = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/logic-log.txt", true); | |||
| auto fileLogger = std::make_shared<spdlog::sinks::basic_file_sink_mt>(fmt::format("logs/logic-{}-log.txt", playerID), true); | |||
| auto printLogger = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); | |||
| std::string pattern = "[logic] [%H:%M:%S.%e] [%l] %v"; | |||
| fileLogger->set_pattern(pattern); | |||
| @@ -747,30 +760,41 @@ void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool f | |||
| // 构造AI线程 | |||
| auto AIThread = [&]() | |||
| { | |||
| try | |||
| { | |||
| std::unique_lock<std::mutex> lock(mtxAI); | |||
| cvAI.wait(lock, [this]() | |||
| { return AIStart; }); | |||
| } | |||
| auto ai = createAI(playerID); | |||
| while (AILoop) | |||
| { | |||
| if (asynchronous) | |||
| { | |||
| Wait(); | |||
| timer->StartTimer(); | |||
| timer->Play(*ai); | |||
| timer->EndTimer(); | |||
| std::unique_lock<std::mutex> lock(mtxAI); | |||
| cvAI.wait(lock, [this]() | |||
| { return AIStart; }); | |||
| } | |||
| else | |||
| auto ai = createAI(playerID); | |||
| while (AILoop) | |||
| { | |||
| Update(); | |||
| timer->StartTimer(); | |||
| timer->Play(*ai); | |||
| timer->EndTimer(); | |||
| if (asynchronous) | |||
| { | |||
| Wait(); | |||
| timer->StartTimer(); | |||
| timer->Play(*ai); | |||
| timer->EndTimer(); | |||
| } | |||
| else | |||
| { | |||
| Update(); | |||
| timer->StartTimer(); | |||
| timer->Play(*ai); | |||
| timer->EndTimer(); | |||
| } | |||
| } | |||
| } | |||
| catch (const std::exception& e) | |||
| { | |||
| std::cerr << "C++ Exception: " << e.what() << std::endl; | |||
| } | |||
| catch (...) | |||
| { | |||
| std::cerr << "Unknown Exception!" << std::endl; | |||
| } | |||
| }; | |||
| // 连接服务器 | |||
| @@ -115,7 +115,11 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||
| } | |||
| catch (const std::exception& e) | |||
| { | |||
| std::cerr << e.what() << '\n'; | |||
| std::cerr << "C++ Exception: " << e.what() << '\n'; | |||
| } | |||
| catch (...) | |||
| { | |||
| std::cerr << "Unknown Exception\n"; | |||
| } | |||
| return 0; | |||
| } | |||
| @@ -52,6 +52,56 @@ C++ 通信组件与选手接口 | |||
| - 避免忙等待,注意线程安全,做好线程同步 | |||
| - 善于使用 [Google](https://www.google.com/) 并使用[**英文**](https://en.wikipedia.org/wiki/American_English)搜索,善于查阅 [Microsoft Learn](https://learn.microsoft.com/)、[cppreference](https://en.cppreference.com/)、[StackOverflow](https://stackoverflow.com/) 以及第三方库官方文档等;不应轻信 [CSDN](https://www.csdn.net/) 等劣质博客社区以及[博客园](https://www.cnblogs.com/)、[简书](https://www.jianshu.com/)等质量参差不齐的博客社区,对其内容需全方位多角度仔细求证方可相信 | |||
| ## 开发纪实 | |||
| ### C++ SDK Windows 环境配置 | |||
| #### 配置方法 | |||
| 起初 C++ 开发时使用 Linux 开发,但在选手包发布 Windows SDK 时遇到诸多问题。Windows 平台的 C++ 接口使用 Visual Studio 进行环境配置。期间遇到诸多问题,配置环境方法如下: | |||
| 1. 更新 Visual Studio:由于届时要求选手与开发组使用**完全相同**版本的 Visual Studio,否则可能会出现找不到一些标准库内置的符号的问题,因此在选手包发布前夕,将开发组的 Visual Studio 升级到最新版后执行步骤 2 | |||
| 2. 编译 gRPC:分别编译 Debug 和 Release 的 gRPC 依赖库各一份。方法有二: | |||
| - 方法一:根据 [gRPC Windows 生成教程](https://github.com/grpc/grpc/blob/0b2609a61d50f2dbba4604978ef9243543a2675a/BUILDING.md#windows-using-visual-studio-2019-or-later) 在本地分别编译一份 `Debug` 和 `Release`(即在 CMake config 参数中分别指定 Debug 和 Release 各生成一份 Visual Studio 工程)的 x64 平台的依赖库。注意使用静态编译,根据[此原则](https://github.com/grpc/grpc/blob/0b2609a61d50f2dbba4604978ef9243543a2675a/BUILDING.md#windows-a-note-on-building-shared-libs-dlls)**不生成** gRPC 和 Protocol Buffers 本身的 DLL,即只有 `.lib` 无 `.dll`(但是可能会有一些其依赖的第三方库的 `.dll`),将 Debug 和 Release 生成的 `.lib` 和 `.dll`(如果有的话)均分别保存 | |||
| - 方法二:根据 [gRPC vcpkg 安装教程](https://github.com/grpc/grpc/tree/master/src/cpp#install-using-vcpkg-package) 安装 **`x64-windows-static`** 的 gRPC,即执行 `vcpkg install grpc:x64-windows-static`,以生成静态库(注意 vcpkg 需要调用本地的 Visual Studio 进行现场编译和生成,所以所耗时间可能较长,请耐心等待;并且此步骤由于需要从 GitHub 上现场拉源代码,因此可能需要科学上网)。然后进入 vcpkg 的安装目录,将里面安装的 gRPC 的所有 Debug 和 Release 的 `.lib` 和 `.dll`(如果有的话)分别保存一份 | |||
| 3. 提取 gRPC 头文件:参考 [gRPC 所有所需头文件](https://github.com/eesast/THUAI6/tree/217d87aeedf6735e3257acb1e2c70574df45b823/CAPI/cpp/grpc)将头文件放入本仓库的相应目录内,以供使用(记得放该版本对应的 LICENSE x)。 | |||
| 4. 配置 `.vcxproj` 和 `.vcxproj.filters`:`.vcxproj` 用于配置项目的编译与链接,`.vcxproj.filters` 用于配置 Visual Studio 的筛选器的结构(即解决方案资源管理器的结构)。参考 THUAI6 的 [`API.vcxproj`](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj) 和 [`API.vcxproj.filters`](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj.filters)。基本上可以直接无脑复制过来,不用做什么修改。如果非要在 Visual Studio 里手动配置,需要配置以下几项(以下的链接均以 x64 Release 为例,Debug 的相应配置在相应链接的上面一些的位置): | |||
| - 添加头文件和源文件:[将**自己编写**的 `.h` 和 `.cpp` 文件加入到项目中](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj#L169-L197)(包括 protoc 生成的那些 `.pb.h` 和 `.pb.cc`,但这些最好参照 `API.vcxproj.filters` 的组织结构[各单独开一个子筛选器](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj.filters#L16-L21)放进去以使得 Visual Studio 打开时更美观) | |||
| - 在项目属性中的 Debug 和 Release 的“C/C++”的“语言”中[均设置 C++ 语言标准为 `/std:c++17`](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj#L154)、[C 语言标准为 `/std:c17`](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj#L155) | |||
| - 在项目属性中的 Debug 和 Release 的[附加编译选项(即在“C/C++”的“命令行”)里均增加 `/source-charset:utf-8` 选项](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj#L157),以保证编译时编译器使用 UTF-8 编码进行源文件读取(注意这个与 Visual Studio 的高级保存选项不同,高级保存选项决定了文件的保存时的保存编码,而非编译器编译时读取使用的编码) | |||
| - 设置头文件的搜索路径:在项目属性的“C/C++”的首页的“附加包含目录”中设置 Debug 和 Release 下的[搜索头文件的根路径](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj#L156),注意 Windows 下的路径分隔符最好使用 `\` 而非 `/`,负责可能会出现一些玄学问题 | |||
| - 设置生成方式为静态生成:在项目属性的“C/C++”的“代码生成”的“运行库”中,[Debug 下设置其为“多线程调试(`/MTd`)”](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj#L137),[Release 下设置其为“多线程(`/MT`)”](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj#L158) | |||
| - 将之前提取的 Debug 和 Release 的 `.lib` 分别放在项目中的单独的文件夹里(THUAI6 使用的是 `CAPI\cpp\lib\debug` 和 `CAPI\cpp\lib\release`),并[使用 `.gitignore` 忽略掉](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/.gitignore#L502) | |||
| - 在项目属性的“链接器”的首页的“附加库目录”中分别配置 Debug 和 Release 的 [`.lib` 文件的相应路径](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj#L166) | |||
| - 在项目属性中的“链接器”的“输入”的“附加依赖库”中分别配置 Debug 和 Release [所需要链接的库的文件名](https://github.com/eesast/THUAI6/blob/c8e1fbe299c67a6e101fa02e85bcc971acd0f48b/CAPI/cpp/API/API.vcxproj#L165)。注意 Debug 和 Release 链接的库可能并不完全相同,建议在 cmd 中使用 `dir /b` 将其自动列举并复制。还需要注意需要手动指定链接一些 Windows 自带的 `lib`,例如 `Ws2_32.lib`、`Crypt32.lib`、`Iphlpapi.lib` 等。如果生成过程中不通过,表示找不到一些函数,则在 Google 中搜索该函数,如果发现是 Windows 系统的 API 函数则会搜到[微软官方文档](https://learn.microsoft.com) 的对应链接的页面,则在页面最下方会表明它所在的 `.lib`(例如 [`CreateProcessA` 的页面](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa#requirements)),加进去即可 | |||
| - 然后进行生成解决方案。如果感觉编译的速度过慢,可以在项目属性的 `C/C++` 的“所有选项”中搜索并行编译,并开启之(`/Qpar`)。不过由于 THUAI6 的疏忽,忘记开启了并行编译 | |||
| - 然后开始运行。如果提示缺少一些 DLL,可以把之前保存的 `.dll` 文件(如果有的话)放在与 `.exe` 相同的目录下。该目录为**与 `.sln` 相同目录的**(不是与 `.vcxproj` 相同目录的)`x64\Debug` 和 `x64\Release` | |||
| - 如果 x64 的 Debug 和 x64 的 Release 均生成成功,那么找一台没配过的电脑再试一次 | |||
| - 随便写点 AI 代码,重新生成解决方案,确认成功后发布选手包 | |||
| - 有兴趣也可以支持一下 x86 的 Windows(x | |||
| - 有兴趣还可以支持一下 Cygwin(x | |||
| ### Q&A | |||
| #### 为什么不用 CMake | |||
| 1. 由于 CMake 生成的 Visual Studio 工程是绝对路径,因此不能将 CMake 生成的 Visual Studio 工程直接给选手,需要选手使用 CMake,增加了选手尤其是大一小白选手的负担 | |||
| 2. 对 Debug 和 Release 分别编译时,需要 CMake 分别生成 Debug 和 Release 的 Visual Studio 工程,而难以使用 Visual Studio 和 MSBuild 自带的 Debug 和 Release 的切换方式,这就需要生成两份 Visual Studio 工程,对选手的测试来说也是一种麻烦 | |||
| #### 为什么不让选手用 vcpkg | |||
| 1. vcpkg 是将代码拉下来本地编译。拉代码过程需要科学上网,这对一些大一小白选手来说是个不小的负担 | |||
| 2. vcpkg 编译过程极其漫长,会极大消磨选手的耐心 | |||
| #### 为什么不用 CLion 配合 MinGW、MinGW-w64 等 | |||
| 1. 贵校程设课大多数使用 Visual Studio。对大多数选手来说,这是最易上手的 | |||
| 2. 开箱即用,方便 | |||
| 3. Visual Studio 已使用多年,坑全部踩过了一遍,突然变更又要重新踩坑 | |||
| 4. MinGW 已停止维护,正在维护的是 MinGW-w64,两者名称很像,选手尤其是使用百度等劣质搜索引擎或查阅 CSDN 等劣质博客安装时极易混淆 | |||
| 5. MinGW 需要附加更多的其自己的依赖库如 glibc 等,此 glibc 依然依赖 msvcrt,多一层依赖库便增加了一些版本不兼容问题的风险 | |||
| 6. 其与 MSVC 的 Name Mangling 的规则、ABI 等并不一致或不完全一致,增加各种不兼容问题的风险 | |||
| ## 开发人员 | |||
| - ......(自己加) | |||
| @@ -318,21 +318,20 @@ namespace protobuf | |||
| kSpeedFieldNumber = 3, | |||
| kDeterminationFieldNumber = 4, | |||
| kAddictionFieldNumber = 5, | |||
| kPlaceFieldNumber = 7, | |||
| kGuidFieldNumber = 10, | |||
| kPlayerStateFieldNumber = 9, | |||
| kGuidFieldNumber = 10, | |||
| kBulletTypeFieldNumber = 12, | |||
| kLearningSpeedFieldNumber = 13, | |||
| kTreatSpeedFieldNumber = 14, | |||
| kPlayerIdFieldNumber = 15, | |||
| kTreatSpeedFieldNumber = 14, | |||
| kViewRangeFieldNumber = 16, | |||
| kRadiusFieldNumber = 17, | |||
| kDangerAlertFieldNumber = 19, | |||
| kScoreFieldNumber = 20, | |||
| kDangerAlertFieldNumber = 19, | |||
| kTreatProgressFieldNumber = 21, | |||
| kRescueProgressFieldNumber = 22, | |||
| kStudentTypeFieldNumber = 23, | |||
| kFacingDirectionFieldNumber = 24, | |||
| kStudentTypeFieldNumber = 23, | |||
| }; | |||
| // repeated double time_until_skill_available = 6; | |||
| int time_until_skill_available_size() const; | |||
| @@ -452,14 +451,14 @@ namespace protobuf | |||
| void _internal_set_addiction(int32_t value); | |||
| public: | |||
| // .protobuf.PlaceType place = 7; | |||
| void clear_place(); | |||
| ::protobuf::PlaceType place() const; | |||
| void set_place(::protobuf::PlaceType value); | |||
| // .protobuf.PlayerState player_state = 9; | |||
| void clear_player_state(); | |||
| ::protobuf::PlayerState player_state() const; | |||
| void set_player_state(::protobuf::PlayerState value); | |||
| private: | |||
| ::protobuf::PlaceType _internal_place() const; | |||
| void _internal_set_place(::protobuf::PlaceType value); | |||
| ::protobuf::PlayerState _internal_player_state() const; | |||
| void _internal_set_player_state(::protobuf::PlayerState value); | |||
| public: | |||
| // int64 guid = 10; | |||
| @@ -471,16 +470,6 @@ namespace protobuf | |||
| int64_t _internal_guid() const; | |||
| void _internal_set_guid(int64_t value); | |||
| public: | |||
| // .protobuf.PlayerState player_state = 9; | |||
| void clear_player_state(); | |||
| ::protobuf::PlayerState player_state() const; | |||
| void set_player_state(::protobuf::PlayerState value); | |||
| private: | |||
| ::protobuf::PlayerState _internal_player_state() const; | |||
| void _internal_set_player_state(::protobuf::PlayerState value); | |||
| public: | |||
| // .protobuf.BulletType bullet_type = 12; | |||
| void clear_bullet_type(); | |||
| @@ -501,16 +490,6 @@ namespace protobuf | |||
| int32_t _internal_learning_speed() const; | |||
| void _internal_set_learning_speed(int32_t value); | |||
| public: | |||
| // int32 treat_speed = 14; | |||
| void clear_treat_speed(); | |||
| int32_t treat_speed() const; | |||
| void set_treat_speed(int32_t value); | |||
| private: | |||
| int32_t _internal_treat_speed() const; | |||
| void _internal_set_treat_speed(int32_t value); | |||
| public: | |||
| // int64 player_id = 15; | |||
| void clear_player_id(); | |||
| @@ -521,6 +500,16 @@ namespace protobuf | |||
| int64_t _internal_player_id() const; | |||
| void _internal_set_player_id(int64_t value); | |||
| public: | |||
| // int32 treat_speed = 14; | |||
| void clear_treat_speed(); | |||
| int32_t treat_speed() const; | |||
| void set_treat_speed(int32_t value); | |||
| private: | |||
| int32_t _internal_treat_speed() const; | |||
| void _internal_set_treat_speed(int32_t value); | |||
| public: | |||
| // int32 view_range = 16; | |||
| void clear_view_range(); | |||
| @@ -541,16 +530,6 @@ namespace protobuf | |||
| int32_t _internal_radius() const; | |||
| void _internal_set_radius(int32_t value); | |||
| public: | |||
| // double danger_alert = 19; | |||
| void clear_danger_alert(); | |||
| double danger_alert() const; | |||
| void set_danger_alert(double value); | |||
| private: | |||
| double _internal_danger_alert() const; | |||
| void _internal_set_danger_alert(double value); | |||
| public: | |||
| // int32 score = 20; | |||
| void clear_score(); | |||
| @@ -561,6 +540,16 @@ namespace protobuf | |||
| int32_t _internal_score() const; | |||
| void _internal_set_score(int32_t value); | |||
| public: | |||
| // double danger_alert = 19; | |||
| void clear_danger_alert(); | |||
| double danger_alert() const; | |||
| void set_danger_alert(double value); | |||
| private: | |||
| double _internal_danger_alert() const; | |||
| void _internal_set_danger_alert(double value); | |||
| public: | |||
| // int32 treat_progress = 21; | |||
| void clear_treat_progress(); | |||
| @@ -581,16 +570,6 @@ namespace protobuf | |||
| int32_t _internal_rescue_progress() const; | |||
| void _internal_set_rescue_progress(int32_t value); | |||
| public: | |||
| // .protobuf.StudentType student_type = 23; | |||
| void clear_student_type(); | |||
| ::protobuf::StudentType student_type() const; | |||
| void set_student_type(::protobuf::StudentType value); | |||
| private: | |||
| ::protobuf::StudentType _internal_student_type() const; | |||
| void _internal_set_student_type(::protobuf::StudentType value); | |||
| public: | |||
| // double facing_direction = 24; | |||
| void clear_facing_direction(); | |||
| @@ -601,6 +580,16 @@ namespace protobuf | |||
| double _internal_facing_direction() const; | |||
| void _internal_set_facing_direction(double value); | |||
| public: | |||
| // .protobuf.StudentType student_type = 23; | |||
| void clear_student_type(); | |||
| ::protobuf::StudentType student_type() const; | |||
| void set_student_type(::protobuf::StudentType value); | |||
| private: | |||
| ::protobuf::StudentType _internal_student_type() const; | |||
| void _internal_set_student_type(::protobuf::StudentType value); | |||
| public: | |||
| // @@protoc_insertion_point(class_scope:protobuf.MessageOfStudent) | |||
| @@ -623,21 +612,20 @@ namespace protobuf | |||
| int32_t speed_; | |||
| int32_t determination_; | |||
| int32_t addiction_; | |||
| int place_; | |||
| int64_t guid_; | |||
| int player_state_; | |||
| int64_t guid_; | |||
| int bullet_type_; | |||
| int32_t learning_speed_; | |||
| int32_t treat_speed_; | |||
| int64_t player_id_; | |||
| int32_t treat_speed_; | |||
| int32_t view_range_; | |||
| int32_t radius_; | |||
| double danger_alert_; | |||
| int32_t score_; | |||
| double danger_alert_; | |||
| int32_t treat_progress_; | |||
| int32_t rescue_progress_; | |||
| int student_type_; | |||
| double facing_direction_; | |||
| int student_type_; | |||
| mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; | |||
| }; | |||
| union | |||
| @@ -812,18 +800,17 @@ namespace protobuf | |||
| kXFieldNumber = 1, | |||
| kYFieldNumber = 2, | |||
| kSpeedFieldNumber = 3, | |||
| kPlaceFieldNumber = 6, | |||
| kGuidFieldNumber = 9, | |||
| kTrickerTypeFieldNumber = 8, | |||
| kScoreFieldNumber = 10, | |||
| kGuidFieldNumber = 9, | |||
| kPlayerIdFieldNumber = 11, | |||
| kScoreFieldNumber = 10, | |||
| kViewRangeFieldNumber = 12, | |||
| kRadiusFieldNumber = 13, | |||
| kPlayerStateFieldNumber = 14, | |||
| kTrickDesireFieldNumber = 15, | |||
| kClassVolumeFieldNumber = 16, | |||
| kPlayerStateFieldNumber = 14, | |||
| kBulletTypeFieldNumber = 18, | |||
| kFacingDirectionFieldNumber = 17, | |||
| kBulletTypeFieldNumber = 18, | |||
| }; | |||
| // repeated double time_until_skill_available = 5; | |||
| int time_until_skill_available_size() const; | |||
| @@ -923,14 +910,14 @@ namespace protobuf | |||
| void _internal_set_speed(int32_t value); | |||
| public: | |||
| // .protobuf.PlaceType place = 6; | |||
| void clear_place(); | |||
| ::protobuf::PlaceType place() const; | |||
| void set_place(::protobuf::PlaceType value); | |||
| // .protobuf.TrickerType tricker_type = 8; | |||
| void clear_tricker_type(); | |||
| ::protobuf::TrickerType tricker_type() const; | |||
| void set_tricker_type(::protobuf::TrickerType value); | |||
| private: | |||
| ::protobuf::PlaceType _internal_place() const; | |||
| void _internal_set_place(::protobuf::PlaceType value); | |||
| ::protobuf::TrickerType _internal_tricker_type() const; | |||
| void _internal_set_tricker_type(::protobuf::TrickerType value); | |||
| public: | |||
| // int64 guid = 9; | |||
| @@ -943,14 +930,14 @@ namespace protobuf | |||
| void _internal_set_guid(int64_t value); | |||
| public: | |||
| // .protobuf.TrickerType tricker_type = 8; | |||
| void clear_tricker_type(); | |||
| ::protobuf::TrickerType tricker_type() const; | |||
| void set_tricker_type(::protobuf::TrickerType value); | |||
| // int64 player_id = 11; | |||
| void clear_player_id(); | |||
| int64_t player_id() const; | |||
| void set_player_id(int64_t value); | |||
| private: | |||
| ::protobuf::TrickerType _internal_tricker_type() const; | |||
| void _internal_set_tricker_type(::protobuf::TrickerType value); | |||
| int64_t _internal_player_id() const; | |||
| void _internal_set_player_id(int64_t value); | |||
| public: | |||
| // int32 score = 10; | |||
| @@ -962,16 +949,6 @@ namespace protobuf | |||
| int32_t _internal_score() const; | |||
| void _internal_set_score(int32_t value); | |||
| public: | |||
| // int64 player_id = 11; | |||
| void clear_player_id(); | |||
| int64_t player_id() const; | |||
| void set_player_id(int64_t value); | |||
| private: | |||
| int64_t _internal_player_id() const; | |||
| void _internal_set_player_id(int64_t value); | |||
| public: | |||
| // int32 view_range = 12; | |||
| void clear_view_range(); | |||
| @@ -992,6 +969,16 @@ namespace protobuf | |||
| int32_t _internal_radius() const; | |||
| void _internal_set_radius(int32_t value); | |||
| public: | |||
| // .protobuf.PlayerState player_state = 14; | |||
| void clear_player_state(); | |||
| ::protobuf::PlayerState player_state() const; | |||
| void set_player_state(::protobuf::PlayerState value); | |||
| private: | |||
| ::protobuf::PlayerState _internal_player_state() const; | |||
| void _internal_set_player_state(::protobuf::PlayerState value); | |||
| public: | |||
| // double trick_desire = 15; | |||
| void clear_trick_desire(); | |||
| @@ -1013,14 +1000,14 @@ namespace protobuf | |||
| void _internal_set_class_volume(double value); | |||
| public: | |||
| // .protobuf.PlayerState player_state = 14; | |||
| void clear_player_state(); | |||
| ::protobuf::PlayerState player_state() const; | |||
| void set_player_state(::protobuf::PlayerState value); | |||
| // double facing_direction = 17; | |||
| void clear_facing_direction(); | |||
| double facing_direction() const; | |||
| void set_facing_direction(double value); | |||
| private: | |||
| ::protobuf::PlayerState _internal_player_state() const; | |||
| void _internal_set_player_state(::protobuf::PlayerState value); | |||
| double _internal_facing_direction() const; | |||
| void _internal_set_facing_direction(double value); | |||
| public: | |||
| // .protobuf.BulletType bullet_type = 18; | |||
| @@ -1032,16 +1019,6 @@ namespace protobuf | |||
| ::protobuf::BulletType _internal_bullet_type() const; | |||
| void _internal_set_bullet_type(::protobuf::BulletType value); | |||
| public: | |||
| // double facing_direction = 17; | |||
| void clear_facing_direction(); | |||
| double facing_direction() const; | |||
| void set_facing_direction(double value); | |||
| private: | |||
| double _internal_facing_direction() const; | |||
| void _internal_set_facing_direction(double value); | |||
| public: | |||
| // @@protoc_insertion_point(class_scope:protobuf.MessageOfTricker) | |||
| @@ -1062,18 +1039,17 @@ namespace protobuf | |||
| int32_t x_; | |||
| int32_t y_; | |||
| int32_t speed_; | |||
| int place_; | |||
| int64_t guid_; | |||
| int tricker_type_; | |||
| int32_t score_; | |||
| int64_t guid_; | |||
| int64_t player_id_; | |||
| int32_t score_; | |||
| int32_t view_range_; | |||
| int32_t radius_; | |||
| int player_state_; | |||
| double trick_desire_; | |||
| double class_volume_; | |||
| int player_state_; | |||
| int bullet_type_; | |||
| double facing_direction_; | |||
| int bullet_type_; | |||
| mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; | |||
| }; | |||
| union | |||
| @@ -1249,7 +1225,6 @@ namespace protobuf | |||
| kTeamFieldNumber = 6, | |||
| kGuidFieldNumber = 5, | |||
| kBombRangeFieldNumber = 8, | |||
| kPlaceFieldNumber = 7, | |||
| kSpeedFieldNumber = 9, | |||
| }; | |||
| // .protobuf.BulletType type = 1; | |||
| @@ -1321,16 +1296,6 @@ namespace protobuf | |||
| double _internal_bomb_range() const; | |||
| void _internal_set_bomb_range(double value); | |||
| public: | |||
| // .protobuf.PlaceType place = 7; | |||
| void clear_place(); | |||
| ::protobuf::PlaceType place() const; | |||
| void set_place(::protobuf::PlaceType value); | |||
| private: | |||
| ::protobuf::PlaceType _internal_place() const; | |||
| void _internal_set_place(::protobuf::PlaceType value); | |||
| public: | |||
| // int32 speed = 9; | |||
| void clear_speed(); | |||
| @@ -1360,7 +1325,6 @@ namespace protobuf | |||
| int team_; | |||
| int64_t guid_; | |||
| double bomb_range_; | |||
| int place_; | |||
| int32_t speed_; | |||
| mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; | |||
| }; | |||
| @@ -1785,9 +1749,8 @@ namespace protobuf | |||
| kTypeFieldNumber = 1, | |||
| kXFieldNumber = 2, | |||
| kFacingDirectionFieldNumber = 4, | |||
| kYFieldNumber = 3, | |||
| kPlaceFieldNumber = 6, | |||
| kGuidFieldNumber = 5, | |||
| kYFieldNumber = 3, | |||
| }; | |||
| // .protobuf.PropType type = 1; | |||
| void clear_type(); | |||
| @@ -1818,26 +1781,6 @@ namespace protobuf | |||
| double _internal_facing_direction() const; | |||
| void _internal_set_facing_direction(double value); | |||
| public: | |||
| // int32 y = 3; | |||
| void clear_y(); | |||
| int32_t y() const; | |||
| void set_y(int32_t value); | |||
| private: | |||
| int32_t _internal_y() const; | |||
| void _internal_set_y(int32_t value); | |||
| public: | |||
| // .protobuf.PlaceType place = 6; | |||
| void clear_place(); | |||
| ::protobuf::PlaceType place() const; | |||
| void set_place(::protobuf::PlaceType value); | |||
| private: | |||
| ::protobuf::PlaceType _internal_place() const; | |||
| void _internal_set_place(::protobuf::PlaceType value); | |||
| public: | |||
| // int64 guid = 5; | |||
| void clear_guid(); | |||
| @@ -1848,6 +1791,16 @@ namespace protobuf | |||
| int64_t _internal_guid() const; | |||
| void _internal_set_guid(int64_t value); | |||
| public: | |||
| // int32 y = 3; | |||
| void clear_y(); | |||
| int32_t y() const; | |||
| void set_y(int32_t value); | |||
| private: | |||
| int32_t _internal_y() const; | |||
| void _internal_set_y(int32_t value); | |||
| public: | |||
| // @@protoc_insertion_point(class_scope:protobuf.MessageOfProp) | |||
| @@ -1863,9 +1816,8 @@ namespace protobuf | |||
| int type_; | |||
| int32_t x_; | |||
| double facing_direction_; | |||
| int32_t y_; | |||
| int place_; | |||
| int64_t guid_; | |||
| int32_t y_; | |||
| mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; | |||
| }; | |||
| union | |||
| @@ -5502,30 +5454,6 @@ namespace protobuf | |||
| return _internal_mutable_time_until_skill_available(); | |||
| } | |||
| // .protobuf.PlaceType place = 7; | |||
| inline void MessageOfStudent::clear_place() | |||
| { | |||
| _impl_.place_ = 0; | |||
| } | |||
| inline ::protobuf::PlaceType MessageOfStudent::_internal_place() const | |||
| { | |||
| return static_cast<::protobuf::PlaceType>(_impl_.place_); | |||
| } | |||
| inline ::protobuf::PlaceType MessageOfStudent::place() const | |||
| { | |||
| // @@protoc_insertion_point(field_get:protobuf.MessageOfStudent.place) | |||
| return _internal_place(); | |||
| } | |||
| inline void MessageOfStudent::_internal_set_place(::protobuf::PlaceType value) | |||
| { | |||
| _impl_.place_ = value; | |||
| } | |||
| inline void MessageOfStudent::set_place(::protobuf::PlaceType value) | |||
| { | |||
| _internal_set_place(value); | |||
| // @@protoc_insertion_point(field_set:protobuf.MessageOfStudent.place) | |||
| } | |||
| // repeated .protobuf.PropType prop = 8; | |||
| inline int MessageOfStudent::_internal_prop_size() const | |||
| { | |||
| @@ -6105,30 +6033,6 @@ namespace protobuf | |||
| return _internal_mutable_time_until_skill_available(); | |||
| } | |||
| // .protobuf.PlaceType place = 6; | |||
| inline void MessageOfTricker::clear_place() | |||
| { | |||
| _impl_.place_ = 0; | |||
| } | |||
| inline ::protobuf::PlaceType MessageOfTricker::_internal_place() const | |||
| { | |||
| return static_cast<::protobuf::PlaceType>(_impl_.place_); | |||
| } | |||
| inline ::protobuf::PlaceType MessageOfTricker::place() const | |||
| { | |||
| // @@protoc_insertion_point(field_get:protobuf.MessageOfTricker.place) | |||
| return _internal_place(); | |||
| } | |||
| inline void MessageOfTricker::_internal_set_place(::protobuf::PlaceType value) | |||
| { | |||
| _impl_.place_ = value; | |||
| } | |||
| inline void MessageOfTricker::set_place(::protobuf::PlaceType value) | |||
| { | |||
| _internal_set_place(value); | |||
| // @@protoc_insertion_point(field_set:protobuf.MessageOfTricker.place) | |||
| } | |||
| // repeated .protobuf.PropType prop = 7; | |||
| inline int MessageOfTricker::_internal_prop_size() const | |||
| { | |||
| @@ -6649,30 +6553,6 @@ namespace protobuf | |||
| // @@protoc_insertion_point(field_set:protobuf.MessageOfBullet.team) | |||
| } | |||
| // .protobuf.PlaceType place = 7; | |||
| inline void MessageOfBullet::clear_place() | |||
| { | |||
| _impl_.place_ = 0; | |||
| } | |||
| inline ::protobuf::PlaceType MessageOfBullet::_internal_place() const | |||
| { | |||
| return static_cast<::protobuf::PlaceType>(_impl_.place_); | |||
| } | |||
| inline ::protobuf::PlaceType MessageOfBullet::place() const | |||
| { | |||
| // @@protoc_insertion_point(field_get:protobuf.MessageOfBullet.place) | |||
| return _internal_place(); | |||
| } | |||
| inline void MessageOfBullet::_internal_set_place(::protobuf::PlaceType value) | |||
| { | |||
| _impl_.place_ = value; | |||
| } | |||
| inline void MessageOfBullet::set_place(::protobuf::PlaceType value) | |||
| { | |||
| _internal_set_place(value); | |||
| // @@protoc_insertion_point(field_set:protobuf.MessageOfBullet.place) | |||
| } | |||
| // double bomb_range = 8; | |||
| inline void MessageOfBullet::clear_bomb_range() | |||
| { | |||
| @@ -6993,30 +6873,6 @@ namespace protobuf | |||
| // @@protoc_insertion_point(field_set:protobuf.MessageOfProp.guid) | |||
| } | |||
| // .protobuf.PlaceType place = 6; | |||
| inline void MessageOfProp::clear_place() | |||
| { | |||
| _impl_.place_ = 0; | |||
| } | |||
| inline ::protobuf::PlaceType MessageOfProp::_internal_place() const | |||
| { | |||
| return static_cast<::protobuf::PlaceType>(_impl_.place_); | |||
| } | |||
| inline ::protobuf::PlaceType MessageOfProp::place() const | |||
| { | |||
| // @@protoc_insertion_point(field_get:protobuf.MessageOfProp.place) | |||
| return _internal_place(); | |||
| } | |||
| inline void MessageOfProp::_internal_set_place(::protobuf::PlaceType value) | |||
| { | |||
| _impl_.place_ = value; | |||
| } | |||
| inline void MessageOfProp::set_place(::protobuf::PlaceType value) | |||
| { | |||
| _internal_set_place(value); | |||
| // @@protoc_insertion_point(field_set:protobuf.MessageOfProp.place) | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // MessageOfPickedProp | |||
| @@ -180,7 +180,7 @@ namespace protobuf | |||
| ::_pbi::ConstantInitialized | |||
| ) : | |||
| _impl_{ | |||
| /*decltype(_impl_.player_id_)*/ int64_t{0}, /*decltype(_impl_.skill_id_)*/ 0, /*decltype(_impl_._cached_size_)*/ {}} | |||
| /*decltype(_impl_.player_id_)*/ int64_t{0}, /*decltype(_impl_.skill_id_)*/ 0, /*decltype(_impl_.skill_param_)*/ 0, /*decltype(_impl_._cached_size_)*/ {}} | |||
| { | |||
| } | |||
| struct SkillMsgDefaultTypeInternal | |||
| @@ -274,6 +274,7 @@ const uint32_t TableStruct_Message2Server_2eproto::offsets[] PROTOBUF_SECTION_VA | |||
| ~0u, // no _inlined_string_donated_ | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::SkillMsg, _impl_.player_id_), | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::SkillMsg, _impl_.skill_id_), | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::SkillMsg, _impl_.skill_param_), | |||
| }; | |||
| static const ::_pbi::MigrationSchema schemas[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = { | |||
| {0, -1, -1, sizeof(::protobuf::PlayerMsg)}, | |||
| @@ -313,9 +314,9 @@ const char descriptor_table_protodef_Message2Server_2eproto[] PROTOBUF_SECTION_V | |||
| "\t\n\007message\"-\n\tAttackMsg\022\021\n\tplayer_id\030\001 \001" | |||
| "(\003\022\r\n\005angle\030\002 \001(\001\"\032\n\005IDMsg\022\021\n\tplayer_id\030" | |||
| "\001 \001(\003\"<\n\021TreatAndRescueMsg\022\021\n\tplayer_id\030" | |||
| "\001 \001(\003\022\024\n\014to_player_id\030\002 \001(\003\"/\n\010SkillMsg\022" | |||
| "\021\n\tplayer_id\030\001 \001(\003\022\020\n\010skill_id\030\002 \001(\005b\006pr" | |||
| "oto3"; | |||
| "\001 \001(\003\022\024\n\014to_player_id\030\002 \001(\003\"D\n\010SkillMsg\022" | |||
| "\021\n\tplayer_id\030\001 \001(\003\022\020\n\010skill_id\030\002 \001(\005\022\023\n\013" | |||
| "skill_param\030\003 \001(\005b\006proto3"; | |||
| static const ::_pbi::DescriptorTable* const descriptor_table_Message2Server_2eproto_deps[1] = { | |||
| &::descriptor_table_MessageType_2eproto, | |||
| }; | |||
| @@ -323,7 +324,7 @@ static ::_pbi::once_flag descriptor_table_Message2Server_2eproto_once; | |||
| const ::_pbi::DescriptorTable descriptor_table_Message2Server_2eproto = { | |||
| false, | |||
| false, | |||
| 684, | |||
| 705, | |||
| descriptor_table_protodef_Message2Server_2eproto, | |||
| "Message2Server.proto", | |||
| &descriptor_table_Message2Server_2eproto_once, | |||
| @@ -2341,10 +2342,10 @@ namespace protobuf | |||
| SkillMsg* const _this = this; | |||
| (void)_this; | |||
| new (&_impl_) Impl_{ | |||
| decltype(_impl_.player_id_){}, decltype(_impl_.skill_id_){}, /*decltype(_impl_._cached_size_)*/ {}}; | |||
| decltype(_impl_.player_id_){}, decltype(_impl_.skill_id_){}, decltype(_impl_.skill_param_){}, /*decltype(_impl_._cached_size_)*/ {}}; | |||
| _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); | |||
| ::memcpy(&_impl_.player_id_, &from._impl_.player_id_, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.skill_id_) - reinterpret_cast<char*>(&_impl_.player_id_)) + sizeof(_impl_.skill_id_)); | |||
| ::memcpy(&_impl_.player_id_, &from._impl_.player_id_, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.skill_param_) - reinterpret_cast<char*>(&_impl_.player_id_)) + sizeof(_impl_.skill_param_)); | |||
| // @@protoc_insertion_point(copy_constructor:protobuf.SkillMsg) | |||
| } | |||
| @@ -2355,7 +2356,7 @@ namespace protobuf | |||
| (void)arena; | |||
| (void)is_message_owned; | |||
| new (&_impl_) Impl_{ | |||
| decltype(_impl_.player_id_){int64_t{0}}, decltype(_impl_.skill_id_){0}, /*decltype(_impl_._cached_size_)*/ {}}; | |||
| decltype(_impl_.player_id_){int64_t{0}}, decltype(_impl_.skill_id_){0}, decltype(_impl_.skill_param_){0}, /*decltype(_impl_._cached_size_)*/ {}}; | |||
| } | |||
| SkillMsg::~SkillMsg() | |||
| @@ -2386,7 +2387,7 @@ namespace protobuf | |||
| // Prevent compiler warnings about cached_has_bits being unused | |||
| (void)cached_has_bits; | |||
| ::memset(&_impl_.player_id_, 0, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.skill_id_) - reinterpret_cast<char*>(&_impl_.player_id_)) + sizeof(_impl_.skill_id_)); | |||
| ::memset(&_impl_.player_id_, 0, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.skill_param_) - reinterpret_cast<char*>(&_impl_.player_id_)) + sizeof(_impl_.skill_param_)); | |||
| _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); | |||
| } | |||
| @@ -2421,6 +2422,16 @@ namespace protobuf | |||
| else | |||
| goto handle_unusual; | |||
| continue; | |||
| // int32 skill_param = 3; | |||
| case 3: | |||
| if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 24)) | |||
| { | |||
| _impl_.skill_param_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); | |||
| CHK_(ptr); | |||
| } | |||
| else | |||
| goto handle_unusual; | |||
| continue; | |||
| default: | |||
| goto handle_unusual; | |||
| } // switch | |||
| @@ -2469,6 +2480,13 @@ namespace protobuf | |||
| target = ::_pbi::WireFormatLite::WriteInt32ToArray(2, this->_internal_skill_id(), target); | |||
| } | |||
| // int32 skill_param = 3; | |||
| if (this->_internal_skill_param() != 0) | |||
| { | |||
| target = stream->EnsureSpace(target); | |||
| target = ::_pbi::WireFormatLite::WriteInt32ToArray(3, this->_internal_skill_param(), target); | |||
| } | |||
| if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) | |||
| { | |||
| target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( | |||
| @@ -2500,6 +2518,12 @@ namespace protobuf | |||
| total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_skill_id()); | |||
| } | |||
| // int32 skill_param = 3; | |||
| if (this->_internal_skill_param() != 0) | |||
| { | |||
| total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_skill_param()); | |||
| } | |||
| return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); | |||
| } | |||
| @@ -2528,6 +2552,10 @@ namespace protobuf | |||
| { | |||
| _this->_internal_set_skill_id(from._internal_skill_id()); | |||
| } | |||
| if (from._internal_skill_param() != 0) | |||
| { | |||
| _this->_internal_set_skill_param(from._internal_skill_param()); | |||
| } | |||
| _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); | |||
| } | |||
| @@ -2550,7 +2578,7 @@ namespace protobuf | |||
| using std::swap; | |||
| _internal_metadata_.InternalSwap(&other->_internal_metadata_); | |||
| ::PROTOBUF_NAMESPACE_ID::internal::memswap< | |||
| PROTOBUF_FIELD_OFFSET(SkillMsg, _impl_.skill_id_) + sizeof(SkillMsg::_impl_.skill_id_) - PROTOBUF_FIELD_OFFSET(SkillMsg, _impl_.player_id_)>( | |||
| PROTOBUF_FIELD_OFFSET(SkillMsg, _impl_.skill_param_) + sizeof(SkillMsg::_impl_.skill_param_) - PROTOBUF_FIELD_OFFSET(SkillMsg, _impl_.player_id_)>( | |||
| reinterpret_cast<char*>(&_impl_.player_id_), | |||
| reinterpret_cast<char*>(&other->_impl_.player_id_) | |||
| ); | |||
| @@ -1813,6 +1813,7 @@ namespace protobuf | |||
| { | |||
| kPlayerIdFieldNumber = 1, | |||
| kSkillIdFieldNumber = 2, | |||
| kSkillParamFieldNumber = 3, | |||
| }; | |||
| // int64 player_id = 1; | |||
| void clear_player_id(); | |||
| @@ -1833,6 +1834,16 @@ namespace protobuf | |||
| int32_t _internal_skill_id() const; | |||
| void _internal_set_skill_id(int32_t value); | |||
| public: | |||
| // int32 skill_param = 3; | |||
| void clear_skill_param(); | |||
| int32_t skill_param() const; | |||
| void set_skill_param(int32_t value); | |||
| private: | |||
| int32_t _internal_skill_param() const; | |||
| void _internal_set_skill_param(int32_t value); | |||
| public: | |||
| // @@protoc_insertion_point(class_scope:protobuf.SkillMsg) | |||
| @@ -1847,6 +1858,7 @@ namespace protobuf | |||
| { | |||
| int64_t player_id_; | |||
| int32_t skill_id_; | |||
| int32_t skill_param_; | |||
| mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; | |||
| }; | |||
| union | |||
| @@ -2597,6 +2609,30 @@ namespace protobuf | |||
| // @@protoc_insertion_point(field_set:protobuf.SkillMsg.skill_id) | |||
| } | |||
| // int32 skill_param = 3; | |||
| inline void SkillMsg::clear_skill_param() | |||
| { | |||
| _impl_.skill_param_ = 0; | |||
| } | |||
| inline int32_t SkillMsg::_internal_skill_param() const | |||
| { | |||
| return _impl_.skill_param_; | |||
| } | |||
| inline int32_t SkillMsg::skill_param() const | |||
| { | |||
| // @@protoc_insertion_point(field_get:protobuf.SkillMsg.skill_param) | |||
| return _internal_skill_param(); | |||
| } | |||
| inline void SkillMsg::_internal_set_skill_param(int32_t value) | |||
| { | |||
| _impl_.skill_param_ = value; | |||
| } | |||
| inline void SkillMsg::set_skill_param(int32_t value) | |||
| { | |||
| _internal_set_skill_param(value); | |||
| // @@protoc_insertion_point(field_set:protobuf.SkillMsg.skill_param) | |||
| } | |||
| #ifdef __GNUC__ | |||
| #pragma GCC diagnostic pop | |||
| #endif // __GNUC__ | |||
| @@ -30,49 +30,49 @@ static constexpr ::_pbi::MigrationSchema* schemas = nullptr; | |||
| static constexpr ::_pb::Message* const* file_default_instances = nullptr; | |||
| const char descriptor_table_protodef_MessageType_2eproto[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = | |||
| "\n\021MessageType.proto\022\010protobuf*\202\001\n\nBullet" | |||
| "Type\022\024\n\020NULL_BULLET_TYPE\020\000\022\020\n\014FLYING_KNI" | |||
| "FE\020\001\022\034\n\030COMMON_ATTACK_OF_TRICKER\020\002\022\r\n\tBO" | |||
| "MB_BOMB\020\003\022\020\n\014JUMPY_DUMPTY\020\004\022\r\n\tATOM_BOMB" | |||
| "\020\005*\241\001\n\tPlaceType\022\023\n\017NULL_PLACE_TYPE\020\000\022\010\n" | |||
| "\004LAND\020\001\022\010\n\004WALL\020\002\022\t\n\005GRASS\020\003\022\r\n\tCLASSROO" | |||
| "M\020\004\022\010\n\004GATE\020\005\022\017\n\013HIDDEN_GATE\020\006\022\n\n\006WINDOW" | |||
| "\020\007\022\t\n\005DOOR3\020\010\022\t\n\005DOOR5\020\t\022\t\n\005DOOR6\020\n\022\t\n\005C" | |||
| "HEST\020\013*8\n\tShapeType\022\023\n\017NULL_SHAPE_TYPE\020\000" | |||
| "\022\n\n\006CIRCLE\020\001\022\n\n\006SQUARE\020\002*\256\001\n\010PropType\022\022\n" | |||
| "\016NULL_PROP_TYPE\020\000\022\r\n\tADD_SPEED\020\001\022\035\n\031ADD_" | |||
| "LIFE_OR_CLAIRAUDIENCE\020\002\022\020\n\014ADD_HP_OR_AP\020" | |||
| "\003\022\023\n\017SHIELD_OR_SPEAR\020\004\022\010\n\004KEY3\020\005\022\010\n\004KEY5" | |||
| "\020\006\022\010\n\004KEY6\020\007\022\033\n\027RECOVERY_FROM_DIZZINESS\020" | |||
| "\010*n\n\017StudentBuffType\022\023\n\017NULL_SBUFF_TYPE\020" | |||
| "\000\022\025\n\021STUDENT_ADD_SPEED\020\001\022\014\n\010ADD_LIFE\020\002\022\n" | |||
| "\n\006SHIELD\020\003\022\025\n\021STUDENT_INVISIBLE\020\004*\251\002\n\013Pl" | |||
| "ayerState\022\017\n\013NULL_STATUS\020\000\022\010\n\004IDLE\020\001\022\014\n\010" | |||
| "LEARNING\020\002\022\014\n\010ADDICTED\020\003\022\010\n\004QUIT\020\004\022\r\n\tGR" | |||
| "ADUATED\020\005\022\013\n\007TREATED\020\006\022\013\n\007RESCUED\020\007\022\013\n\007S" | |||
| "TUNNED\020\010\022\014\n\010TREATING\020\t\022\014\n\010RESCUING\020\n\022\014\n\010" | |||
| "SWINGING\020\013\022\r\n\tATTACKING\020\014\022\013\n\007LOCKING\020\r\022\r" | |||
| "\n\tRUMMAGING\020\016\022\014\n\010CLIMBING\020\017\022\023\n\017OPENING_A" | |||
| "_CHEST\020\020\022\027\n\023USING_SPECIAL_SKILL\020\021\022\022\n\016OPE" | |||
| "NING_A_GATE\020\022*~\n\017TrickerBuffType\022\023\n\017NULL" | |||
| "_TBUFF_TYPE\020\000\022\025\n\021TRICKER_ADD_SPEED\020\001\022\t\n\005" | |||
| "SPEAR\020\002\022\n\n\006ADD_AP\020\003\022\021\n\rCLAIRAUDIENCE\020\004\022\025" | |||
| "\n\021TRICKER_INVISIBLE\020\005*J\n\nPlayerType\022\024\n\020N" | |||
| "ULL_PLAYER_TYPE\020\000\022\022\n\016STUDENT_PLAYER\020\001\022\022\n" | |||
| "\016TRICKER_PLAYER\020\002*\177\n\013StudentType\022\025\n\021NULL" | |||
| "_STUDENT_TYPE\020\000\022\013\n\007ATHLETE\020\001\022\013\n\007TEACHER\020" | |||
| "\002\022\026\n\022STRAIGHT_A_STUDENT\020\003\022\t\n\005ROBOT\020\004\022\016\n\n" | |||
| "TECH_OTAKU\020\005\022\014\n\010SUNSHINE\020\006*Z\n\013TrickerTyp" | |||
| "e\022\025\n\021NULL_TRICKER_TYPE\020\000\022\014\n\010ASSASSIN\020\001\022\010" | |||
| "\n\004KLEE\020\002\022\022\n\016A_NOISY_PERSON\020\003\022\010\n\004IDOL\020\004*P" | |||
| "\n\tGameState\022\023\n\017NULL_GAME_STATE\020\000\022\016\n\nGAME" | |||
| "_START\020\001\022\020\n\014GAME_RUNNING\020\002\022\014\n\010GAME_END\020\003" | |||
| "b\006proto3"; | |||
| "\n\021MessageType.proto\022\010protobuf*\177\n\nBulletT" | |||
| "ype\022\024\n\020NULL_BULLET_TYPE\020\000\022\020\n\014FLYING_KNIF" | |||
| "E\020\001\022\034\n\030COMMON_ATTACK_OF_TRICKER\020\002\022\r\n\tBOM" | |||
| "B_BOMB\020\003\022\020\n\014JUMPY_DUMPTY\020\004\022\n\n\006STRIKE\020\005*\241" | |||
| "\001\n\tPlaceType\022\023\n\017NULL_PLACE_TYPE\020\000\022\010\n\004LAN" | |||
| "D\020\001\022\010\n\004WALL\020\002\022\t\n\005GRASS\020\003\022\r\n\tCLASSROOM\020\004\022" | |||
| "\010\n\004GATE\020\005\022\017\n\013HIDDEN_GATE\020\006\022\n\n\006WINDOW\020\007\022\t" | |||
| "\n\005DOOR3\020\010\022\t\n\005DOOR5\020\t\022\t\n\005DOOR6\020\n\022\t\n\005CHEST" | |||
| "\020\013*8\n\tShapeType\022\023\n\017NULL_SHAPE_TYPE\020\000\022\n\n\006" | |||
| "CIRCLE\020\001\022\n\n\006SQUARE\020\002*\302\001\n\010PropType\022\022\n\016NUL" | |||
| "L_PROP_TYPE\020\000\022\r\n\tADD_SPEED\020\001\022\035\n\031ADD_LIFE" | |||
| "_OR_CLAIRAUDIENCE\020\002\022\020\n\014ADD_HP_OR_AP\020\003\022\023\n" | |||
| "\017SHIELD_OR_SPEAR\020\004\022\010\n\004KEY3\020\005\022\010\n\004KEY5\020\006\022\010" | |||
| "\n\004KEY6\020\007\022\033\n\027RECOVERY_FROM_DIZZINESS\020\010\022\022\n" | |||
| "\016CRAFTING_BENCH\020\t*n\n\017StudentBuffType\022\023\n\017" | |||
| "NULL_SBUFF_TYPE\020\000\022\025\n\021STUDENT_ADD_SPEED\020\001" | |||
| "\022\014\n\010ADD_LIFE\020\002\022\n\n\006SHIELD\020\003\022\025\n\021STUDENT_IN" | |||
| "VISIBLE\020\004*\251\002\n\013PlayerState\022\017\n\013NULL_STATUS" | |||
| "\020\000\022\010\n\004IDLE\020\001\022\014\n\010LEARNING\020\002\022\014\n\010ADDICTED\020\003" | |||
| "\022\010\n\004QUIT\020\004\022\r\n\tGRADUATED\020\005\022\013\n\007TREATED\020\006\022\013" | |||
| "\n\007RESCUED\020\007\022\013\n\007STUNNED\020\010\022\014\n\010TREATING\020\t\022\014" | |||
| "\n\010RESCUING\020\n\022\014\n\010SWINGING\020\013\022\r\n\tATTACKING\020" | |||
| "\014\022\013\n\007LOCKING\020\r\022\r\n\tRUMMAGING\020\016\022\014\n\010CLIMBIN" | |||
| "G\020\017\022\023\n\017OPENING_A_CHEST\020\020\022\027\n\023USING_SPECIA" | |||
| "L_SKILL\020\021\022\022\n\016OPENING_A_GATE\020\022*~\n\017Tricker" | |||
| "BuffType\022\023\n\017NULL_TBUFF_TYPE\020\000\022\025\n\021TRICKER" | |||
| "_ADD_SPEED\020\001\022\t\n\005SPEAR\020\002\022\n\n\006ADD_AP\020\003\022\021\n\rC" | |||
| "LAIRAUDIENCE\020\004\022\025\n\021TRICKER_INVISIBLE\020\005*J\n" | |||
| "\nPlayerType\022\024\n\020NULL_PLAYER_TYPE\020\000\022\022\n\016STU" | |||
| "DENT_PLAYER\020\001\022\022\n\016TRICKER_PLAYER\020\002*\177\n\013Stu" | |||
| "dentType\022\025\n\021NULL_STUDENT_TYPE\020\000\022\013\n\007ATHLE" | |||
| "TE\020\001\022\013\n\007TEACHER\020\002\022\026\n\022STRAIGHT_A_STUDENT\020" | |||
| "\003\022\t\n\005ROBOT\020\004\022\016\n\nTECH_OTAKU\020\005\022\014\n\010SUNSHINE" | |||
| "\020\006*Z\n\013TrickerType\022\025\n\021NULL_TRICKER_TYPE\020\000" | |||
| "\022\014\n\010ASSASSIN\020\001\022\010\n\004KLEE\020\002\022\022\n\016A_NOISY_PERS" | |||
| "ON\020\003\022\010\n\004IDOL\020\004*P\n\tGameState\022\023\n\017NULL_GAME" | |||
| "_STATE\020\000\022\016\n\nGAME_START\020\001\022\020\n\014GAME_RUNNING" | |||
| "\020\002\022\014\n\010GAME_END\020\003b\006proto3"; | |||
| static ::_pbi::once_flag descriptor_table_MessageType_2eproto_once; | |||
| const ::_pbi::DescriptorTable descriptor_table_MessageType_2eproto = { | |||
| false, | |||
| false, | |||
| 1488, | |||
| 1504, | |||
| descriptor_table_protodef_MessageType_2eproto, | |||
| "MessageType.proto", | |||
| &descriptor_table_MessageType_2eproto_once, | |||
| @@ -179,6 +179,7 @@ namespace protobuf | |||
| case 6: | |||
| case 7: | |||
| case 8: | |||
| case 9: | |||
| return true; | |||
| default: | |||
| return false; | |||
| @@ -57,13 +57,13 @@ namespace protobuf | |||
| COMMON_ATTACK_OF_TRICKER = 2, | |||
| BOMB_BOMB = 3, | |||
| JUMPY_DUMPTY = 4, | |||
| ATOM_BOMB = 5, | |||
| STRIKE = 5, | |||
| BulletType_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits<int32_t>::min(), | |||
| BulletType_INT_MAX_SENTINEL_DO_NOT_USE_ = std::numeric_limits<int32_t>::max() | |||
| }; | |||
| bool BulletType_IsValid(int value); | |||
| constexpr BulletType BulletType_MIN = NULL_BULLET_TYPE; | |||
| constexpr BulletType BulletType_MAX = ATOM_BOMB; | |||
| constexpr BulletType BulletType_MAX = STRIKE; | |||
| constexpr int BulletType_ARRAYSIZE = BulletType_MAX + 1; | |||
| const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* BulletType_descriptor(); | |||
| @@ -163,12 +163,13 @@ namespace protobuf | |||
| KEY5 = 6, | |||
| KEY6 = 7, | |||
| RECOVERY_FROM_DIZZINESS = 8, | |||
| CRAFTING_BENCH = 9, | |||
| PropType_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits<int32_t>::min(), | |||
| PropType_INT_MAX_SENTINEL_DO_NOT_USE_ = std::numeric_limits<int32_t>::max() | |||
| }; | |||
| bool PropType_IsValid(int value); | |||
| constexpr PropType PropType_MIN = NULL_PROP_TYPE; | |||
| constexpr PropType PropType_MAX = RECOVERY_FROM_DIZZINESS; | |||
| constexpr PropType PropType_MAX = CRAFTING_BENCH; | |||
| constexpr int PropType_ARRAYSIZE = PropType_MAX + 1; | |||
| const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* PropType_descriptor(); | |||
| @@ -25,6 +25,8 @@ | |||
| #include <grpcpp/impl/codegen/stub_options.h> | |||
| #include <grpcpp/impl/codegen/sync_stream.h> | |||
| #undef SendMessage | |||
| namespace protobuf | |||
| { | |||
| @@ -6,7 +6,6 @@ from typing import List, cast, Tuple, Union | |||
| class StudentAPI(IStudentAPI, IGameTimer): | |||
| def __init__(self, logic: ILogic) -> None: | |||
| self.__logic = logic | |||
| self.__pool = ThreadPoolExecutor(20) | |||
| @@ -44,8 +43,8 @@ class StudentAPI(IStudentAPI, IGameTimer): | |||
| def ThrowProp(self, propType: THUAI6.PropType) -> Future[bool]: | |||
| return self.__pool.submit(self.__logic.ThrowProp, propType) | |||
| def UseSkill(self, skillID: int) -> Future[bool]: | |||
| return self.__pool.submit(self.__logic.UseSkill, skillID) | |||
| def UseSkill(self, skillID: int, skillParam: int = 0) -> Future[bool]: | |||
| return self.__pool.submit(self.__logic.UseSkill, skillID, skillParam) | |||
| # 与地图交互相关 | |||
| def OpenDoor(self) -> Future[bool]: | |||
| @@ -133,7 +132,13 @@ class StudentAPI(IStudentAPI, IGameTimer): | |||
| return self.__logic.GetGameInfo() | |||
| def HaveView(self, gridX: int, gridY: int) -> bool: | |||
| return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange) | |||
| return self.__logic.HaveView( | |||
| gridX, | |||
| gridY, | |||
| self.GetSelfInfo().x, | |||
| self.GetSelfInfo().y, | |||
| self.GetSelfInfo().viewRange, | |||
| ) | |||
| # 用于DEBUG的输出函数,仅在DEBUG模式下有效 | |||
| @@ -182,7 +187,6 @@ class StudentAPI(IStudentAPI, IGameTimer): | |||
| class TrickerAPI(ITrickerAPI, IGameTimer): | |||
| def __init__(self, logic: ILogic) -> None: | |||
| self.__logic = logic | |||
| self.__pool = ThreadPoolExecutor(20) | |||
| @@ -220,8 +224,8 @@ class TrickerAPI(ITrickerAPI, IGameTimer): | |||
| def ThrowProp(self, propType: THUAI6.PropType) -> Future[bool]: | |||
| return self.__pool.submit(self.__logic.ThrowProp, propType) | |||
| def UseSkill(self, skillID: int) -> Future[bool]: | |||
| return self.__pool.submit(self.__logic.UseSkill, skillID) | |||
| def UseSkill(self, skillID: int, skillParam: int = 0) -> Future[bool]: | |||
| return self.__pool.submit(self.__logic.UseSkill, skillID, skillParam) | |||
| # 与地图交互相关 | |||
| def OpenDoor(self) -> Future[bool]: | |||
| @@ -309,7 +313,13 @@ class TrickerAPI(ITrickerAPI, IGameTimer): | |||
| return self.__logic.GetGameInfo() | |||
| def HaveView(self, gridX: int, gridY: int) -> bool: | |||
| return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange) | |||
| return self.__logic.HaveView( | |||
| gridX, | |||
| gridY, | |||
| self.GetSelfInfo().x, | |||
| self.GetSelfInfo().y, | |||
| self.GetSelfInfo().viewRange, | |||
| ) | |||
| # 用于DEBUG的输出函数,仅在DEBUG模式下有效 | |||
| @@ -19,17 +19,31 @@ class BoolErrorHandler(IErrorHandler): | |||
| class Communication: | |||
| def __init__(self, sIP: str, sPort: str): | |||
| aim = sIP + ':' + sPort | |||
| aim = sIP + ":" + sPort | |||
| channel = grpc.insecure_channel(aim) | |||
| self.__THUAI6Stub = Services.AvailableServiceStub(channel) | |||
| self.__haveNewMessage = False | |||
| self.__cvMessage = threading.Condition() | |||
| self.__message2Client: Message2Clients.MessageToClient | |||
| self.__mtxLimit = threading.Lock() | |||
| self.__counter = 0 | |||
| self.__counterMove = 0 | |||
| self.__limit = 50 | |||
| self.__moveLimit = 10 | |||
| def Move(self, time: int, angle: float, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if ( | |||
| self.__counter >= self.__limit | |||
| or self.__counterMove >= self.__moveLimit | |||
| ): | |||
| return False | |||
| self.__counter += 1 | |||
| self.__counterMove += 1 | |||
| moveResult = self.__THUAI6Stub.Move( | |||
| THUAI62Proto.THUAI62ProtobufMove(time, angle, playerID)) | |||
| THUAI62Proto.THUAI62ProtobufMove(time, angle, playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -37,8 +51,13 @@ class Communication: | |||
| def PickProp(self, propType: THUAI6.PropType, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| pickResult = self.__THUAI6Stub.PickProp( | |||
| THUAI62Proto.THUAI62ProtobufProp(propType, playerID)) | |||
| THUAI62Proto.THUAI62ProtobufProp(propType, playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -46,8 +65,13 @@ class Communication: | |||
| def UseProp(self, propType: THUAI6.PropType, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| useResult = self.__THUAI6Stub.UseProp( | |||
| THUAI62Proto.THUAI62ProtobufProp(propType, playerID)) | |||
| THUAI62Proto.THUAI62ProtobufProp(propType, playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -55,17 +79,27 @@ class Communication: | |||
| def ThrowProp(self, propType: THUAI6.PropType, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| throwResult = self.__THUAI6Stub.ThrowProp( | |||
| THUAI62Proto.THUAI62ProtobufProp(propType, playerID)) | |||
| THUAI62Proto.THUAI62ProtobufProp(propType, playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| return throwResult.act_success | |||
| def UseSkill(self, skillID: int, playerID: int) -> bool: | |||
| def UseSkill(self, skillID: int, skillParam: int, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| useResult = self.__THUAI6Stub.UseSkill( | |||
| THUAI62Proto.THUAI62ProtobufSkill(skillID, playerID)) | |||
| THUAI62Proto.THUAI62ProtobufSkill(skillID, skillParam, playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -73,8 +107,13 @@ class Communication: | |||
| def SendMessage(self, toID: int, message: Union[str, bytes], playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| sendResult = self.__THUAI6Stub.SendMessage( | |||
| THUAI62Proto.THUAI62ProtobufSend(message, toID, playerID)) | |||
| THUAI62Proto.THUAI62ProtobufSend(message, toID, playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -82,8 +121,13 @@ class Communication: | |||
| def Graduate(self, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| escapeResult = self.__THUAI6Stub.Graduate( | |||
| THUAI62Proto.THUAI62ProtobufID(playerID)) | |||
| THUAI62Proto.THUAI62ProtobufID(playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -91,8 +135,13 @@ class Communication: | |||
| def StartLearning(self, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| learnResult = self.__THUAI6Stub.StartLearning( | |||
| THUAI62Proto.THUAI62ProtobufID(playerID)) | |||
| THUAI62Proto.THUAI62ProtobufID(playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -100,8 +149,13 @@ class Communication: | |||
| def StartEncourageMate(self, playerID: int, mateID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| helpResult = self.__THUAI6Stub.StartTreatMate( | |||
| THUAI62Proto.THUAI62ProtobufTreatAndRescue(playerID, mateID)) | |||
| THUAI62Proto.THUAI62ProtobufTreatAndRescue(playerID, mateID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -109,8 +163,13 @@ class Communication: | |||
| def StartRouseMate(self, playerID: int, mateID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| helpResult = self.__THUAI6Stub.StartRescueMate( | |||
| THUAI62Proto.THUAI62ProtobufTreatAndRescue(playerID, mateID)) | |||
| THUAI62Proto.THUAI62ProtobufTreatAndRescue(playerID, mateID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -118,8 +177,13 @@ class Communication: | |||
| def Attack(self, angle: float, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| attackResult = self.__THUAI6Stub.Attack( | |||
| THUAI62Proto.THUAI62ProtobufAttack(angle, playerID)) | |||
| THUAI62Proto.THUAI62ProtobufAttack(angle, playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -127,8 +191,13 @@ class Communication: | |||
| def OpenDoor(self, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| openResult = self.__THUAI6Stub.OpenDoor( | |||
| THUAI62Proto.THUAI62ProtobufID(playerID)) | |||
| THUAI62Proto.THUAI62ProtobufID(playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -136,8 +205,13 @@ class Communication: | |||
| def CloseDoor(self, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| closeResult = self.__THUAI6Stub.CloseDoor( | |||
| THUAI62Proto.THUAI62ProtobufID(playerID)) | |||
| THUAI62Proto.THUAI62ProtobufID(playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -145,8 +219,13 @@ class Communication: | |||
| def SkipWindow(self, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| skipResult = self.__THUAI6Stub.SkipWindow( | |||
| THUAI62Proto.THUAI62ProtobufID(playerID)) | |||
| THUAI62Proto.THUAI62ProtobufID(playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -154,8 +233,13 @@ class Communication: | |||
| def StartOpenGate(self, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| openResult = self.__THUAI6Stub.StartOpenGate( | |||
| THUAI62Proto.THUAI62ProtobufID(playerID)) | |||
| THUAI62Proto.THUAI62ProtobufID(playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -163,8 +247,13 @@ class Communication: | |||
| def StartOpenChest(self, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if self.__counter >= self.__limit: | |||
| return False | |||
| self.__counter += 1 | |||
| openResult = self.__THUAI6Stub.StartOpenChest( | |||
| THUAI62Proto.THUAI62ProtobufID(playerID)) | |||
| THUAI62Proto.THUAI62ProtobufID(playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -172,8 +261,17 @@ class Communication: | |||
| def EndAllAction(self, playerID: int) -> bool: | |||
| try: | |||
| with self.__mtxLimit: | |||
| if ( | |||
| self.__counter >= self.__limit | |||
| or self.__counterMove >= self.__moveLimit | |||
| ): | |||
| return False | |||
| self.__counter += 1 | |||
| self.__counterMove += 1 | |||
| endResult = self.__THUAI6Stub.EndAllAction( | |||
| THUAI62Proto.THUAI62ProtobufID(playerID)) | |||
| THUAI62Proto.THUAI62ProtobufID(playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -182,7 +280,8 @@ class Communication: | |||
| def TryConnection(self, playerID: int) -> bool: | |||
| try: | |||
| connectResult = self.__THUAI6Stub.TryConnection( | |||
| THUAI62Proto.THUAI62ProtobufID(playerID)) | |||
| THUAI62Proto.THUAI62ProtobufID(playerID) | |||
| ) | |||
| except grpc.RpcError as e: | |||
| return False | |||
| else: | |||
| @@ -202,12 +301,16 @@ class Communication: | |||
| else: | |||
| studentType = THUAI6.StudentType.NullStudentType | |||
| playerMsg = THUAI62Proto.THUAI62ProtobufPlayer( | |||
| playerID, playerType, studentType, Setting.trickerType()) | |||
| playerID, playerType, studentType, Setting.trickerType() | |||
| ) | |||
| for msg in self.__THUAI6Stub.AddPlayer(playerMsg): | |||
| with self.__cvMessage: | |||
| self.__haveNewMessage = True | |||
| self.__message2Client = msg | |||
| self.__cvMessage.notify() | |||
| with self.__mtxLimit: | |||
| self.__counter = 0 | |||
| self.__counterMove = 0 | |||
| except grpc.RpcError as e: | |||
| return | |||
| @@ -10,22 +10,34 @@ from PyAPI.Interface import ILogic, IStudentAPI, ITrickerAPI, IGameTimer, IAI | |||
| class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| def __init__(self, logic: ILogic, file: bool, screen: bool, warnOnly: bool, playerID: int) -> None: | |||
| def __init__( | |||
| self, logic: ILogic, file: bool, screen: bool, warnOnly: bool, playerID: int | |||
| ) -> None: | |||
| self.__logic = logic | |||
| self.__pool = ThreadPoolExecutor(20) | |||
| self.__startPoint = datetime.datetime.now() | |||
| self.__logger = logging.getLogger("api " + str(playerID)) | |||
| self.__logger.setLevel(logging.DEBUG) | |||
| formatter = logging.Formatter( | |||
| "[%(name)s] [%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", '%H:%M:%S') | |||
| "[%(name)s] [%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", | |||
| "%H:%M:%S", | |||
| ) | |||
| # 确保文件存在 | |||
| if not os.path.exists(os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs"): | |||
| os.makedirs(os.path.dirname(os.path.dirname( | |||
| os.path.realpath(__file__))) + "/logs") | |||
| fileHandler = logging.FileHandler(os.path.dirname( | |||
| os.path.dirname(os.path.realpath(__file__))) + "/logs/api-" + str(playerID) + "-log.txt", mode="w+", encoding="utf-8") | |||
| if not os.path.exists( | |||
| os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs" | |||
| ): | |||
| os.makedirs( | |||
| os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs" | |||
| ) | |||
| fileHandler = logging.FileHandler( | |||
| os.path.dirname(os.path.dirname(os.path.realpath(__file__))) | |||
| + "/logs/api-" | |||
| + str(playerID) | |||
| + "-log.txt", | |||
| mode="w+", | |||
| encoding="utf-8", | |||
| ) | |||
| screenHandler = logging.StreamHandler() | |||
| if file: | |||
| fileHandler.setLevel(logging.DEBUG) | |||
| @@ -43,13 +55,13 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| def Move(self, timeInMilliseconds: int, angle: float) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"Move: timeInMilliseconds = {timeInMilliseconds}, angle = {angle}, called at {self.__GetTime()}ms") | |||
| f"Move: timeInMilliseconds = {timeInMilliseconds}, angle = {angle}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logMove() -> bool: | |||
| result = self.__logic.Move(timeInMilliseconds, angle) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"Move: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"Move: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logMove) | |||
| @@ -69,14 +81,12 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| return self.Move(timeInMilliseconds, 0) | |||
| def Attack(self, angle: float) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"Attack: angle = {angle}, called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"Attack: angle = {angle}, called at {self.__GetTime()}ms") | |||
| def logAttack() -> bool: | |||
| result = self.__logic.Attack(angle) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"Attack: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"Attack: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logAttack) | |||
| @@ -85,131 +95,119 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| def PickProp(self, propType: THUAI6.PropType) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"PickProp: prop = {propType.name}, called at {self.__GetTime()}ms") | |||
| f"PickProp: prop = {propType.name}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logPick() -> bool: | |||
| result = self.__logic.PickProp(propType) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"PickProp: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"PickProp: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logPick) | |||
| def UseProp(self, propType: THUAI6.PropType) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"UseProp: prop = {propType.name}, called at {self.__GetTime()}ms") | |||
| f"UseProp: prop = {propType.name}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logUse() -> bool: | |||
| result = self.__logic.UseProp(propType) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"UseProp: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"UseProp: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logUse) | |||
| def ThrowProp(self, propType: THUAI6.PropType) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"ThrowProp: prop = {propType.name}, called at {self.__GetTime()}ms") | |||
| f"ThrowProp: prop = {propType.name}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logThrow() -> bool: | |||
| result = self.__logic.ThrowProp(propType) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"ThrowProp: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"ThrowProp: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logThrow) | |||
| def UseSkill(self, skillID: int) -> Future[bool]: | |||
| def UseSkill(self, skillID: int, skillParam: int = 0) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"UseSkill: skillID = {skillID}, called at {self.__GetTime()}ms") | |||
| f"UseSkill: skillID = {skillID}, skillParam = {skillParam}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logUse() -> bool: | |||
| result = self.__logic.UseSkill(skillID) | |||
| result = self.__logic.UseSkill(skillID, skillParam) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"UseSkill: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"UseSkill: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logUse) | |||
| # 与地图交互相关 | |||
| def OpenDoor(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"OpenDoor: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"OpenDoor: called at {self.__GetTime()}ms") | |||
| def logOpen() -> bool: | |||
| result = self.__logic.OpenDoor() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"OpenDoor: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"OpenDoor: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logOpen) | |||
| def CloseDoor(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"CloseDoor: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"CloseDoor: called at {self.__GetTime()}ms") | |||
| def logClose() -> bool: | |||
| result = self.__logic.CloseDoor() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"CloseDoor: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"CloseDoor: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logClose) | |||
| def SkipWindow(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"SkipWindow: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"SkipWindow: called at {self.__GetTime()}ms") | |||
| def logSkip() -> bool: | |||
| result = self.__logic.SkipWindow() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"SkipWindow: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"SkipWindow: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logSkip) | |||
| def StartOpenGate(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"StartOpenGate: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"StartOpenGate: called at {self.__GetTime()}ms") | |||
| def logStart() -> bool: | |||
| result = self.__logic.StartOpenGate() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"StartOpenGate: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"StartOpenGate: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logStart) | |||
| def StartOpenChest(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"StartOpenChest: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"StartOpenChest: called at {self.__GetTime()}ms") | |||
| def logStart() -> bool: | |||
| result = self.__logic.StartOpenChest() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"StartOpenChest: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"StartOpenChest: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logStart) | |||
| def EndAllAction(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"EndAllAction: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"EndAllAction: called at {self.__GetTime()}ms") | |||
| def logEnd() -> bool: | |||
| result = self.__logic.EndAllAction() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"EndAllAction: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"EndAllAction: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logEnd) | |||
| @@ -218,40 +216,35 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| def SendMessage(self, toID: int, message: Union[str, bytes]) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"SendMessage: toID = {toID}, message = {message}, called at {self.__GetTime()}ms") | |||
| f"SendMessage: toID = {toID}, message = {message}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logSend() -> bool: | |||
| result = self.__logic.SendMessage(toID, message) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"SendMessage: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"SendMessage: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logSend) | |||
| def HaveMessage(self) -> bool: | |||
| self.__logger.info( | |||
| f"HaveMessage: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"HaveMessage: called at {self.__GetTime()}ms") | |||
| result = self.__logic.HaveMessage() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"HaveMessage: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"HaveMessage: failed at {self.__GetTime()}ms") | |||
| return result | |||
| def GetMessage(self) -> Tuple[int, Union[str, bytes]]: | |||
| self.__logger.info( | |||
| f"GetMessage: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"GetMessage: called at {self.__GetTime()}ms") | |||
| result = self.__logic.GetMessage() | |||
| if result[0] == -1: | |||
| self.__logger.warning( | |||
| f"GetMessage: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"GetMessage: failed at {self.__GetTime()}ms") | |||
| return result | |||
| # 等待下一帧 | |||
| def Wait(self) -> bool: | |||
| self.__logger.info( | |||
| f"Wait: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"Wait: called at {self.__GetTime()}ms") | |||
| if self.__logic.GetCounter() == -1: | |||
| return False | |||
| else: | |||
| @@ -305,7 +298,13 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| return self.__logic.GetGameInfo() | |||
| def HaveView(self, gridX: int, gridY: int) -> bool: | |||
| return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange) | |||
| return self.__logic.HaveView( | |||
| gridX, | |||
| gridY, | |||
| self.GetSelfInfo().x, | |||
| self.GetSelfInfo().y, | |||
| self.GetSelfInfo().viewRange, | |||
| ) | |||
| # 用于DEBUG的输出函数,仅在DEBUG模式下有效 | |||
| @@ -316,20 +315,26 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| for student in self.__logic.GetStudents(): | |||
| self.__logger.info("******Student Info******") | |||
| self.__logger.info( | |||
| f"playerID={student.playerID}, GUID={student.guid}, x={student.x}, y={student.y}") | |||
| f"playerID={student.playerID}, GUID={student.guid}, x={student.x}, y={student.y}" | |||
| ) | |||
| self.__logger.info( | |||
| f"speed={student.speed}, view range={student.viewRange}, place={student.place.name}, radius={student.radius}") | |||
| f"speed={student.speed}, view range={student.viewRange}, radius={student.radius}" | |||
| ) | |||
| self.__logger.info( | |||
| f"score={student.score}, facing direction={student.facingDirection}, skill time={student.timeUntilSkillAvailable}") | |||
| f"score={student.score}, facing direction={student.facingDirection}, skill time={student.timeUntilSkillAvailable}" | |||
| ) | |||
| studentProp = "" | |||
| for prop in student.prop: | |||
| studentProp += prop.name + ", " | |||
| self.__logger.info( | |||
| f"state={student.playerState.name}, bullet={student.bulletType.name}, prop={studentProp}") | |||
| f"state={student.playerState.name}, bullet={student.bulletType.name}, prop={studentProp}" | |||
| ) | |||
| self.__logger.info( | |||
| f"type={student.studentType.name}, determination={student.determination}, addiction={student.addiction}, danger alert={student.dangerAlert}") | |||
| f"type={student.studentType.name}, determination={student.determination}, addiction={student.addiction}, danger alert={student.dangerAlert}" | |||
| ) | |||
| self.__logger.info( | |||
| f"learning speed={student.learningSpeed}, encourage speed={student.encourageSpeed}, encourage progress={student.encourageProgress}, rouse progress={student.rouseProgress}") | |||
| f"learning speed={student.learningSpeed}, encourage speed={student.encourageSpeed}, encourage progress={student.encourageProgress}, rouse progress={student.rouseProgress}" | |||
| ) | |||
| studentBuff = "" | |||
| for buff in student.buff: | |||
| studentBuff += buff.name + ", " | |||
| @@ -340,18 +345,23 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| for tricker in self.__logic.GetTrickers(): | |||
| self.__logger.info("******Tricker Info******") | |||
| self.__logger.info( | |||
| f"playerID={tricker.playerID}, GUID={tricker.guid}, x={tricker.x}, y={tricker.y}") | |||
| f"playerID={tricker.playerID}, GUID={tricker.guid}, x={tricker.x}, y={tricker.y}" | |||
| ) | |||
| self.__logger.info( | |||
| f"speed={tricker.speed}, view range={tricker.viewRange}, place={tricker.place.name}, radius={tricker.radius}") | |||
| f"speed={tricker.speed}, view range={tricker.viewRange}, radius={tricker.radius}" | |||
| ) | |||
| self.__logger.info( | |||
| f"score={tricker.score}, facing direction={tricker.facingDirection}, skill time={tricker.timeUntilSkillAvailable}") | |||
| f"score={tricker.score}, facing direction={tricker.facingDirection}, skill time={tricker.timeUntilSkillAvailable}" | |||
| ) | |||
| trickerProp = "" | |||
| for prop in tricker.prop: | |||
| trickerProp += prop.name + ", " | |||
| self.__logger.info( | |||
| f"state={tricker.playerState.name}, bullet={tricker.bulletType.name}, prop={trickerProp}") | |||
| f"state={tricker.playerState.name}, bullet={tricker.bulletType.name}, prop={trickerProp}" | |||
| ) | |||
| self.__logger.info( | |||
| f"type={tricker.trickerType.name}, trick desire={tricker.trickDesire}, class volume={tricker.classVolume}") | |||
| f"type={tricker.trickerType.name}, trick desire={tricker.trickDesire}, class volume={tricker.classVolume}" | |||
| ) | |||
| trickerBuff = "" | |||
| for buff in tricker.buff: | |||
| trickerBuff += buff.name + ", " | |||
| @@ -362,27 +372,34 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| for prop in self.__logic.GetProps(): | |||
| self.__logger.info("******Prop Info******") | |||
| self.__logger.info( | |||
| f"GUID={prop.guid}, x={prop.x}, y={prop.y}, place={prop.place.name}, facing direction={prop.facingDirection}") | |||
| f"GUID={prop.guid}, x={prop.x}, y={prop.y}, facing direction={prop.facingDirection}" | |||
| ) | |||
| self.__logger.info("*********************") | |||
| def PrintSelfInfo(self) -> None: | |||
| student = cast(THUAI6.Student, self.__logic.GetSelfInfo()) | |||
| self.__logger.info("******Student Info******") | |||
| self.__logger.info( | |||
| f"playerID={student.playerID}, GUID={student.guid}, x={student.x}, y={student.y}") | |||
| f"playerID={student.playerID}, GUID={student.guid}, x={student.x}, y={student.y}" | |||
| ) | |||
| self.__logger.info( | |||
| f"speed={student.speed}, view range={student.viewRange}, place={student.place.name}, radius={student.radius}") | |||
| f"speed={student.speed}, view range={student.viewRange}, radius={student.radius}" | |||
| ) | |||
| self.__logger.info( | |||
| f"score={student.score}, facing direction={student.facingDirection}, skill time={student.timeUntilSkillAvailable}") | |||
| f"score={student.score}, facing direction={student.facingDirection}, skill time={student.timeUntilSkillAvailable}" | |||
| ) | |||
| studentProp = "" | |||
| for prop in student.prop: | |||
| studentProp += prop.name + ", " | |||
| self.__logger.info( | |||
| f"state={student.playerState.name}, bullet={student.bulletType.name}, prop={studentProp}") | |||
| f"state={student.playerState.name}, bullet={student.bulletType.name}, prop={studentProp}" | |||
| ) | |||
| self.__logger.info( | |||
| f"type={student.studentType.name}, determination={student.determination}, addiction={student.addiction}, danger alert={student.dangerAlert}") | |||
| f"type={student.studentType.name}, determination={student.determination}, addiction={student.addiction}, danger alert={student.dangerAlert}" | |||
| ) | |||
| self.__logger.info( | |||
| f"learning speed={student.learningSpeed}, encourage speed={student.encourageSpeed}, encourage progress={student.encourageProgress}, rouse progress={student.rouseProgress}") | |||
| f"learning speed={student.learningSpeed}, encourage speed={student.encourageSpeed}, encourage progress={student.encourageProgress}, rouse progress={student.rouseProgress}" | |||
| ) | |||
| studentBuff = "" | |||
| for buff in student.buff: | |||
| studentBuff += buff.name + ", " | |||
| @@ -392,53 +409,47 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| # 人类阵营的特殊函数 | |||
| def Graduate(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"Graduate: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"Graduate: called at {self.__GetTime()}ms") | |||
| def logGraduate() -> bool: | |||
| result = self.__logic.Graduate() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"Graduate: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"Graduate: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logGraduate) | |||
| def StartLearning(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"StartLearning: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"StartLearning: called at {self.__GetTime()}ms") | |||
| def logStart() -> bool: | |||
| result = self.__logic.StartLearning() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"StartLearning: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"StartLearning: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logStart) | |||
| def StartEncourageMate(self, mateID: int) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"StartEncourageMate: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"StartEncourageMate: called at {self.__GetTime()}ms") | |||
| def logStartEncourageMate() -> bool: | |||
| result = self.__logic.StartEncourageMate(mateID) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"StartEncourageMate: failed at {self.__GetTime()}ms") | |||
| f"StartEncourageMate: failed at {self.__GetTime()}ms" | |||
| ) | |||
| return result | |||
| return self.__pool.submit(logStartEncourageMate) | |||
| def StartRouseMate(self, mateID: int) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"StartRouseMate: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"StartRouseMate: called at {self.__GetTime()}ms") | |||
| def logStartRouseMate() -> bool: | |||
| result = self.__logic.StartRouseMate(mateID) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"StartRouseMate: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"StartRouseMate: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logStartRouseMate) | |||
| @@ -449,7 +460,9 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| # Timer用 | |||
| def __GetTime(self) -> float: | |||
| return (datetime.datetime.now() - self.__startPoint) / datetime.timedelta(milliseconds=1) | |||
| return (datetime.datetime.now() - self.__startPoint) / datetime.timedelta( | |||
| milliseconds=1 | |||
| ) | |||
| def StartTimer(self) -> None: | |||
| self.__startPoint = datetime.datetime.now() | |||
| @@ -464,21 +477,33 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| def __init__(self, logic: ILogic, file: bool, screen: bool, warnOnly: bool, playerID: int) -> None: | |||
| def __init__( | |||
| self, logic: ILogic, file: bool, screen: bool, warnOnly: bool, playerID: int | |||
| ) -> None: | |||
| self.__logic = logic | |||
| self.__pool = ThreadPoolExecutor(20) | |||
| self.__logger = logging.getLogger("api " + str(playerID)) | |||
| self.__logger.setLevel(logging.DEBUG) | |||
| formatter = logging.Formatter( | |||
| "[%(name)s] [%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", '%H:%M:%S') | |||
| "[%(name)s] [%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", | |||
| "%H:%M:%S", | |||
| ) | |||
| # 确保文件存在 | |||
| if not os.path.exists(os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs"): | |||
| os.makedirs(os.path.dirname(os.path.dirname( | |||
| os.path.realpath(__file__))) + "/logs") | |||
| fileHandler = logging.FileHandler(os.path.dirname( | |||
| os.path.dirname(os.path.realpath(__file__))) + "/logs/api-" + str(playerID) + "-log.txt", mode="w+", encoding="utf-8") | |||
| if not os.path.exists( | |||
| os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs" | |||
| ): | |||
| os.makedirs( | |||
| os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs" | |||
| ) | |||
| fileHandler = logging.FileHandler( | |||
| os.path.dirname(os.path.dirname(os.path.realpath(__file__))) | |||
| + "/logs/api-" | |||
| + str(playerID) | |||
| + "-log.txt", | |||
| mode="w+", | |||
| encoding="utf-8", | |||
| ) | |||
| screenHandler = logging.StreamHandler() | |||
| if file: | |||
| fileHandler.setLevel(logging.DEBUG) | |||
| @@ -496,13 +521,13 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| def Move(self, timeInMilliseconds: int, angle: float) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"Move: timeInMilliseconds = {timeInMilliseconds}, angle = {angle}, called at {self.__GetTime()}ms") | |||
| f"Move: timeInMilliseconds = {timeInMilliseconds}, angle = {angle}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logMove() -> bool: | |||
| result = self.__logic.Move(timeInMilliseconds, angle) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"Move: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"Move: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logMove) | |||
| @@ -524,14 +549,12 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| # 道具和技能相关 | |||
| def Attack(self, angle: float) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"Attack: angle = {angle}, called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"Attack: angle = {angle}, called at {self.__GetTime()}ms") | |||
| def logAttack() -> bool: | |||
| result = self.__logic.Attack(angle) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"Attack: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"Attack: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logAttack) | |||
| @@ -540,131 +563,119 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| def PickProp(self, propType: THUAI6.PropType) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"PickProp: prop = {propType.name}, called at {self.__GetTime()}ms") | |||
| f"PickProp: prop = {propType.name}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logPick() -> bool: | |||
| result = self.__logic.PickProp(propType) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"PickProp: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"PickProp: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logPick) | |||
| def UseProp(self, propType: THUAI6.PropType) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"UseProp: prop = {propType.name}, called at {self.__GetTime()}ms") | |||
| f"UseProp: prop = {propType.name}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logUse() -> bool: | |||
| result = self.__logic.UseProp(propType) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"UseProp: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"UseProp: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logUse) | |||
| def ThrowProp(self, propType: THUAI6.PropType) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"ThrowProp: prop = {propType.name}, called at {self.__GetTime()}ms") | |||
| f"ThrowProp: prop = {propType.name}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logThrow() -> bool: | |||
| result = self.__logic.ThrowProp(propType) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"ThrowProp: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"ThrowProp: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logThrow) | |||
| def UseSkill(self, skillID: int) -> Future[bool]: | |||
| def UseSkill(self, skillID: int, skillParam: int = 0) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"UseSkill: skillID = {skillID}, called at {self.__GetTime()}ms") | |||
| f"UseSkill: skillID = {skillID}, skillParam = {skillParam}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logUse() -> bool: | |||
| result = self.__logic.UseSkill(skillID) | |||
| result = self.__logic.UseSkill(skillID, skillParam) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"UseSkill: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"UseSkill: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logUse) | |||
| # 与地图交互相关 | |||
| def OpenDoor(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"OpenDoor: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"OpenDoor: called at {self.__GetTime()}ms") | |||
| def logOpen() -> bool: | |||
| result = self.__logic.OpenDoor() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"OpenDoor: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"OpenDoor: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logOpen) | |||
| def CloseDoor(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"CloseDoor: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"CloseDoor: called at {self.__GetTime()}ms") | |||
| def logClose() -> bool: | |||
| result = self.__logic.CloseDoor() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"CloseDoor: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"CloseDoor: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logClose) | |||
| def SkipWindow(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"SkipWindow: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"SkipWindow: called at {self.__GetTime()}ms") | |||
| def logSkip() -> bool: | |||
| result = self.__logic.SkipWindow() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"SkipWindow: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"SkipWindow: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logSkip) | |||
| def StartOpenGate(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"StartOpenGate: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"StartOpenGate: called at {self.__GetTime()}ms") | |||
| def logStart() -> bool: | |||
| result = self.__logic.StartOpenGate() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"StartOpenGate: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"StartOpenGate: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logStart) | |||
| def StartOpenChest(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"StartOpenChest: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"StartOpenChest: called at {self.__GetTime()}ms") | |||
| def logStart() -> bool: | |||
| result = self.__logic.StartOpenChest() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"StartOpenChest: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"StartOpenChest: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logStart) | |||
| def EndAllAction(self) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"EndAllAction: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"EndAllAction: called at {self.__GetTime()}ms") | |||
| def logEnd() -> bool: | |||
| result = self.__logic.EndAllAction() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"EndAllAction: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"EndAllAction: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logEnd) | |||
| @@ -673,40 +684,35 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| def SendMessage(self, toID: int, message: Union[str, bytes]) -> Future[bool]: | |||
| self.__logger.info( | |||
| f"SendMessage: toID = {toID}, message = {message}, called at {self.__GetTime()}ms") | |||
| f"SendMessage: toID = {toID}, message = {message}, called at {self.__GetTime()}ms" | |||
| ) | |||
| def logSend() -> bool: | |||
| result = self.__logic.SendMessage(toID, message) | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"SendMessage: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"SendMessage: failed at {self.__GetTime()}ms") | |||
| return result | |||
| return self.__pool.submit(logSend) | |||
| def HaveMessage(self) -> bool: | |||
| self.__logger.info( | |||
| f"HaveMessage: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"HaveMessage: called at {self.__GetTime()}ms") | |||
| result = self.__logic.HaveMessage() | |||
| if not result: | |||
| self.__logger.warning( | |||
| f"HaveMessage: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"HaveMessage: failed at {self.__GetTime()}ms") | |||
| return result | |||
| def GetMessage(self) -> Tuple[int, Union[str, bytes]]: | |||
| self.__logger.info( | |||
| f"GetMessage: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"GetMessage: called at {self.__GetTime()}ms") | |||
| result = self.__logic.GetMessage() | |||
| if result[0] == -1: | |||
| self.__logger.warning( | |||
| f"GetMessage: failed at {self.__GetTime()}ms") | |||
| self.__logger.warning(f"GetMessage: failed at {self.__GetTime()}ms") | |||
| return result | |||
| # 等待下一帧 | |||
| def Wait(self) -> bool: | |||
| self.__logger.info( | |||
| f"Wait: called at {self.__GetTime()}ms") | |||
| self.__logger.info(f"Wait: called at {self.__GetTime()}ms") | |||
| if self.__logic.GetCounter() == -1: | |||
| return False | |||
| else: | |||
| @@ -760,7 +766,13 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| return self.__logic.GetGameInfo() | |||
| def HaveView(self, gridX: int, gridY: int) -> bool: | |||
| return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange) | |||
| return self.__logic.HaveView( | |||
| gridX, | |||
| gridY, | |||
| self.GetSelfInfo().x, | |||
| self.GetSelfInfo().y, | |||
| self.GetSelfInfo().viewRange, | |||
| ) | |||
| # 用于DEBUG的输出函数,仅在DEBUG模式下有效 | |||
| @@ -771,20 +783,26 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| for student in self.__logic.GetStudents(): | |||
| self.__logger.info("******Student Info******") | |||
| self.__logger.info( | |||
| f"playerID={student.playerID}, GUID={student.guid}, x={student.x}, y={student.y}") | |||
| f"playerID={student.playerID}, GUID={student.guid}, x={student.x}, y={student.y}" | |||
| ) | |||
| self.__logger.info( | |||
| f"speed={student.speed}, view range={student.viewRange}, place={student.place.name}, radius={student.radius}") | |||
| f"speed={student.speed}, view range={student.viewRange}, radius={student.radius}" | |||
| ) | |||
| self.__logger.info( | |||
| f"score={student.score}, facing direction={student.facingDirection}, skill time={student.timeUntilSkillAvailable}") | |||
| f"score={student.score}, facing direction={student.facingDirection}, skill time={student.timeUntilSkillAvailable}" | |||
| ) | |||
| studentProp = "" | |||
| for prop in student.prop: | |||
| studentProp += prop.name + ", " | |||
| self.__logger.info( | |||
| f"state={student.playerState.name}, bullet={student.bulletType.name}, prop={studentProp}") | |||
| f"state={student.playerState.name}, bullet={student.bulletType.name}, prop={studentProp}" | |||
| ) | |||
| self.__logger.info( | |||
| f"type={student.studentType.name}, determination={student.determination}, addiction={student.addiction}, danger alert={student.dangerAlert}") | |||
| f"type={student.studentType.name}, determination={student.determination}, addiction={student.addiction}, danger alert={student.dangerAlert}" | |||
| ) | |||
| self.__logger.info( | |||
| f"learning speed={student.learningSpeed}, encourage speed={student.encourageSpeed}, encourage progress={student.encourageProgress}, rouse progress={student.rouseProgress}") | |||
| f"learning speed={student.learningSpeed}, encourage speed={student.encourageSpeed}, encourage progress={student.encourageProgress}, rouse progress={student.rouseProgress}" | |||
| ) | |||
| studentBuff = "" | |||
| for buff in student.buff: | |||
| studentBuff += buff.name + ", " | |||
| @@ -795,18 +813,23 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| for tricker in self.__logic.GetTrickers(): | |||
| self.__logger.info("******Tricker Info******") | |||
| self.__logger.info( | |||
| f"playerID={tricker.playerID}, GUID={tricker.guid}, x={tricker.x}, y={tricker.y}") | |||
| f"playerID={tricker.playerID}, GUID={tricker.guid}, x={tricker.x}, y={tricker.y}" | |||
| ) | |||
| self.__logger.info( | |||
| f"speed={tricker.speed}, view range={tricker.viewRange}, place={tricker.place.name}, radius={tricker.radius}") | |||
| f"speed={tricker.speed}, view range={tricker.viewRange}, radius={tricker.radius}" | |||
| ) | |||
| self.__logger.info( | |||
| f"score={tricker.score}, facing direction={tricker.facingDirection}, skill time={tricker.timeUntilSkillAvailable}") | |||
| f"score={tricker.score}, facing direction={tricker.facingDirection}, skill time={tricker.timeUntilSkillAvailable}" | |||
| ) | |||
| trickerProp = "" | |||
| for prop in tricker.prop: | |||
| trickerProp += prop.name + ", " | |||
| self.__logger.info( | |||
| f"state={tricker.playerState.name}, bullet={tricker.bulletType.name}, prop={trickerProp}") | |||
| f"state={tricker.playerState.name}, bullet={tricker.bulletType.name}, prop={trickerProp}" | |||
| ) | |||
| self.__logger.info( | |||
| f"type={tricker.trickerType.name}, trick desire={tricker.trickDesire}, class volume={tricker.classVolume}") | |||
| f"type={tricker.trickerType.name}, trick desire={tricker.trickDesire}, class volume={tricker.classVolume}" | |||
| ) | |||
| trickerBuff = "" | |||
| for buff in tricker.buff: | |||
| trickerBuff += buff.name + ", " | |||
| @@ -817,25 +840,31 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| for prop in self.__logic.GetProps(): | |||
| self.__logger.info("******Prop Info******") | |||
| self.__logger.info( | |||
| f"GUID={prop.guid}, x={prop.x}, y={prop.y}, place={prop.place.name}, facing direction={prop.facingDirection}") | |||
| f"GUID={prop.guid}, x={prop.x}, y={prop.y}, facing direction={prop.facingDirection}" | |||
| ) | |||
| self.__logger.info("*********************") | |||
| def PrintSelfInfo(self) -> None: | |||
| tricker = cast(THUAI6.Tricker, self.__logic.GetSelfInfo()) | |||
| self.__logger.info("******Tricker Info******") | |||
| self.__logger.info( | |||
| f"playerID={tricker.playerID}, GUID={tricker.guid}, x={tricker.x}, y={tricker.y}") | |||
| f"playerID={tricker.playerID}, GUID={tricker.guid}, x={tricker.x}, y={tricker.y}" | |||
| ) | |||
| self.__logger.info( | |||
| f"speed={tricker.speed}, view range={tricker.viewRange}, place={tricker.place.name}, radius={tricker.radius}") | |||
| f"speed={tricker.speed}, view range={tricker.viewRange}, radius={tricker.radius}" | |||
| ) | |||
| self.__logger.info( | |||
| f"score={tricker.score}, facing direction={tricker.facingDirection}, skill time={tricker.timeUntilSkillAvailable}") | |||
| f"score={tricker.score}, facing direction={tricker.facingDirection}, skill time={tricker.timeUntilSkillAvailable}" | |||
| ) | |||
| trickerProp = "" | |||
| for prop in tricker.prop: | |||
| trickerProp += prop.name + ", " | |||
| self.__logger.info( | |||
| f"state={tricker.playerState.name}, bullet={tricker.bulletType.name}, prop={trickerProp}") | |||
| f"state={tricker.playerState.name}, bullet={tricker.bulletType.name}, prop={trickerProp}" | |||
| ) | |||
| self.__logger.info( | |||
| f"type={tricker.trickerType.name}, trick desire={tricker.trickDesire}, class volume={tricker.classVolume}") | |||
| f"type={tricker.trickerType.name}, trick desire={tricker.trickDesire}, class volume={tricker.classVolume}" | |||
| ) | |||
| trickerBuff = "" | |||
| for buff in tricker.buff: | |||
| trickerBuff += buff.name + ", " | |||
| @@ -850,7 +879,9 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| # Timer用 | |||
| def __GetTime(self) -> float: | |||
| return (datetime.datetime.now() - self.__startPoint) / datetime.timedelta(milliseconds=1) | |||
| return (datetime.datetime.now() - self.__startPoint) / datetime.timedelta( | |||
| milliseconds=1 | |||
| ) | |||
| def StartTimer(self) -> None: | |||
| self.__startPoint = datetime.datetime.now() | |||
| @@ -5,7 +5,6 @@ import PyAPI.structures as THUAI6 | |||
| class ILogic(metaclass=ABCMeta): | |||
| # IAPI统一可用的接口 | |||
| @abstractmethod | |||
| @@ -81,7 +80,7 @@ class ILogic(metaclass=ABCMeta): | |||
| pass | |||
| @abstractmethod | |||
| def UseSkill(self, skillID: int) -> bool: | |||
| def UseSkill(self, skillID: int, skillParam: int) -> bool: | |||
| pass | |||
| @abstractmethod | |||
| @@ -155,12 +154,13 @@ class ILogic(metaclass=ABCMeta): | |||
| pass | |||
| @abstractmethod | |||
| def HaveView(self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int) -> bool: | |||
| def HaveView( | |||
| self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int | |||
| ) -> bool: | |||
| pass | |||
| class IAPI(metaclass=ABCMeta): | |||
| # 选手可执行的操作 | |||
| # 指挥本角色进行移动,`timeInMilliseconds` 为移动时间,单位为毫秒;`angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴 | |||
| @@ -201,7 +201,7 @@ class IAPI(metaclass=ABCMeta): | |||
| pass | |||
| @abstractmethod | |||
| def UseSkill(self, skillID: int) -> Future[bool]: | |||
| def UseSkill(self, skillID: int, skillParam: int = 0) -> Future[bool]: | |||
| pass | |||
| @abstractmethod | |||
| @@ -346,7 +346,6 @@ class IAPI(metaclass=ABCMeta): | |||
| class IStudentAPI(IAPI, metaclass=ABCMeta): | |||
| # 人类阵营的特殊函数 | |||
| @abstractmethod | |||
| @@ -371,7 +370,6 @@ class IStudentAPI(IAPI, metaclass=ABCMeta): | |||
| class ITrickerAPI(IAPI, metaclass=ABCMeta): | |||
| # 屠夫阵营的特殊函数 | |||
| @abstractmethod | |||
| @@ -390,7 +388,6 @@ class IAI(metaclass=ABCMeta): | |||
| class IGameTimer(metaclass=ABCMeta): | |||
| # 用于计时的接口 | |||
| @abstractmethod | |||
| @@ -15,6 +15,7 @@ class State: | |||
| self.mapInfo = THUAI6.GameMap() | |||
| self.gameInfo = THUAI6.GameInfo() | |||
| self.guids = [] | |||
| teamScore: int | |||
| self: Union[THUAI6.Student, THUAI6.Tricker] | |||
| @@ -10,27 +10,27 @@ class NoInstance: | |||
| class Constants(NoInstance): | |||
| frameDuration = 50 # 每帧毫秒数 | |||
| numOfGridPerCell = 1000 # 单位坐标数 | |||
| rows = 50 # 地图行数 | |||
| cols = 50 # 地图列数 | |||
| rows = 50 # 地图行数 | |||
| cols = 50 # 地图列数 | |||
| numOfClassroom = 10 # 教室数量 | |||
| numOfChest = 8 # 宝箱数量 | |||
| numOfChest = 8 # 宝箱数量 | |||
| maxClassroomProgress = 10000000 # 教室最大进度 | |||
| maxDoorProgress = 10000000 # 开关门最大进度 | |||
| maxChestProgress = 10000000 # 宝箱最大进度 | |||
| maxGateProgress = 18000 # 大门最大进度 | |||
| maxDoorProgress = 10000000 # 开关门最大进度 | |||
| maxChestProgress = 10000000 # 宝箱最大进度 | |||
| maxGateProgress = 18000 # 大门最大进度 | |||
| numOfRequiredClassroomForGate = 7 # 打开大门需要完成的教室数量 | |||
| numOfRequiredClassroomForGate = 7 # 打开大门需要完成的教室数量 | |||
| numOfRequiredClassroomForHiddenGate = 3 # 打开隐藏门需要完成的教室数量 | |||
| # 人物属性相关 | |||
| basicEncourageSpeed = 100 | |||
| basicLearnSpeed = 123 | |||
| basicSpeedOfOpeningOrLocking = 4000 | |||
| basicSpeedOfOpeningOrLocking = 5000 | |||
| basicStudentSpeedOfClimbingThroughWindows = 1222 | |||
| basicTrickerSpeedOfClimbingThroughWindows = 2540 | |||
| basicSpeedOfOpenChest = 1000 | |||
| basicSpeedOfOpenChest = 1250 | |||
| basicHp = 3000000 | |||
| basicMaxGamingAddiction = 60000 | |||
| @@ -52,21 +52,21 @@ class Constants(NoInstance): | |||
| # 攻击相关 | |||
| basicApOfTricker = 1500000 | |||
| basicCD = 3000 # 初始子弹冷却 | |||
| basicCastTime = 500 # 基本前摇时间 | |||
| basicBackswing = 800 # 基本后摇时间 | |||
| basicCD = 3000 # 初始子弹冷却 | |||
| basicCastTime = 500 # 基本前摇时间 | |||
| basicBackswing = 800 # 基本后摇时间 | |||
| basicRecoveryFromHit = 3700 # 基本命中攻击恢复时长 | |||
| basicStunnedTimeOfStudent = 4300 | |||
| basicBulletMoveSpeed = 7400 # 基本子弹移动速度 | |||
| basicBulletMoveSpeed = 7400 # 基本子弹移动速度 | |||
| basicRemoteAttackRange = 6000 # 基本远程攻击范围 | |||
| basicAttackShortRange = 2200 # 基本近程攻击范围 | |||
| basicBulletBombRange = 2000 # 基本子弹爆炸范围 | |||
| basicAttackShortRange = 2200 # 基本近程攻击范围 | |||
| basicBulletBombRange = 2000 # 基本子弹爆炸范围 | |||
| # 道具相关 | |||
| apPropAdd = basicApOfTricker * 12 / 10 | |||
| apSpearAdd = basicApOfTricker * 6 / 10 | |||
| apPropAdd = basicApOfTricker * 12 // 10 | |||
| apSpearAdd = basicApOfTricker * 6 // 10 | |||
| # 技能相关 | |||
| maxNumOfSkill = 3 | |||
| @@ -85,7 +85,7 @@ class Constants(NoInstance): | |||
| addedTimeOfSpeedWhenInspire = 1.6 | |||
| timeOfAddingSpeedWhenInspire = 6000 | |||
| addHpWhenEncourage = basicHp / 4 | |||
| addHpWhenEncourage = basicHp // 4 | |||
| checkIntervalWhenShowTime = 200 | |||
| addAddictionPer100msWhenShowTime = 300 | |||
| @@ -96,10 +96,10 @@ class Assassin: | |||
| concealment = 1.5 * Constants.basicConcealment | |||
| alertnessRadius = (int)(1.3 * Constants.basicTrickerAlertnessRadius) | |||
| viewRange = (int)(1.2 * Constants.basicTrickerViewRange) | |||
| speedOfOpeningOrLocking = (int)( | |||
| 1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfOpeningOrLocking = (int)(1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = (int)( | |||
| 1.0 * Constants.basicTrickerSpeedOfClimbingThroughWindows) | |||
| 1.0 * Constants.basicTrickerSpeedOfClimbingThroughWindows | |||
| ) | |||
| speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest) | |||
| @@ -108,10 +108,10 @@ class Klee: | |||
| concealment = 1.0 * Constants.basicConcealment | |||
| alertnessRadius = (int)(1.0 * Constants.basicTrickerAlertnessRadius) | |||
| viewRange = (int)(1.0 * Constants.basicTrickerViewRange) | |||
| speedOfOpeningOrLocking = (int)( | |||
| 1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfOpeningOrLocking = (int)(1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = (int)( | |||
| 1.0 * Constants.basicTrickerSpeedOfClimbingThroughWindows) | |||
| 1.0 * Constants.basicTrickerSpeedOfClimbingThroughWindows | |||
| ) | |||
| speedOfOpenChest = (int)(1.1 * Constants.basicSpeedOfOpenChest) | |||
| @@ -120,10 +120,10 @@ class ANoisyPerson: | |||
| concealment = 0.8 * Constants.basicConcealment | |||
| alertnessRadius = (int)(0.9 * Constants.basicTrickerAlertnessRadius) | |||
| viewRange = (int)(1.0 * Constants.basicTrickerViewRange) | |||
| speedOfOpeningOrLocking = (int)( | |||
| 1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfOpeningOrLocking = (int)(1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = (int)( | |||
| 1.1 * Constants.basicTrickerSpeedOfClimbingThroughWindows) | |||
| 1.1 * Constants.basicTrickerSpeedOfClimbingThroughWindows | |||
| ) | |||
| speedOfOpenChest = (int)(1.1 * Constants.basicSpeedOfOpenChest) | |||
| @@ -132,10 +132,10 @@ class Idol: | |||
| concealment = 0.75 * Constants.basicConcealment | |||
| alertnessRadius = (int)(1.0 * Constants.basicTrickerAlertnessRadius) | |||
| viewRange = (int)(1.1 * Constants.basicTrickerViewRange) | |||
| speedOfOpeningOrLocking = (int)( | |||
| 1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfOpeningOrLocking = (int)(1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = (int)( | |||
| 1.0 * Constants.basicTrickerSpeedOfClimbingThroughWindows) | |||
| 1.0 * Constants.basicTrickerSpeedOfClimbingThroughWindows | |||
| ) | |||
| speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest) | |||
| @@ -148,10 +148,10 @@ class Athlete: | |||
| concealment = 0.9 * Constants.basicConcealment | |||
| alertnessRadius = (int)(1.0 * Constants.basicStudentAlertnessRadius) | |||
| viewRange = (int)(1.1 * Constants.basicStudentViewRange) | |||
| speedOfOpeningOrLocking = (int)( | |||
| 1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfOpeningOrLocking = (int)(1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = (int)( | |||
| 1.2 * Constants.basicStudentSpeedOfClimbingThroughWindows) | |||
| 1.2 * Constants.basicStudentSpeedOfClimbingThroughWindows | |||
| ) | |||
| speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest) | |||
| @@ -159,15 +159,15 @@ class Teacher: | |||
| moveSpeed = (int)(0.9 * Constants.basicStudentSpeed) | |||
| maxHp = (int)(10.0 * Constants.basicHp) | |||
| maxAddiction = (int)(10.0 * Constants.basicMaxGamingAddiction) | |||
| LearnSpeed = (int)(0.0 * Constants.basicLearnSpeed) | |||
| LearnSpeed = (int)( Constants.basicLearnSpeed* 50//123) | |||
| EncourageSpeed = (int)(0.8 * Constants.basicEncourageSpeed) | |||
| concealment = 0.5 * Constants.basicConcealment | |||
| alertnessRadius = (int)(0.5 * Constants.basicStudentAlertnessRadius) | |||
| viewRange = (int)(0.9 * Constants.basicStudentViewRange) | |||
| speedOfOpeningOrLocking = (int)( | |||
| 1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| alertnessRadius = (int)(Constants.basicStudentAlertnessRadius * 2 // 3) | |||
| viewRange = (int)(0.8 * Constants.basicStudentViewRange) | |||
| speedOfOpeningOrLocking = (int)(1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = (int)( | |||
| 0.5 * Constants.basicStudentSpeedOfClimbingThroughWindows) | |||
| Constants.basicStudentSpeedOfClimbingThroughWindows* 1000 // 1222 | |||
| ) | |||
| speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest) | |||
| @@ -180,43 +180,41 @@ class StraightAStudent: | |||
| concealment = 0.9 * Constants.basicConcealment | |||
| alertnessRadius = (int)(0.9 * Constants.basicStudentAlertnessRadius) | |||
| viewRange = (int)(0.9 * Constants.basicStudentViewRange) | |||
| speedOfOpeningOrLocking = (int)( | |||
| 1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfOpeningOrLocking = (int)(1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = (int)( | |||
| 0.83333 * Constants.basicStudentSpeedOfClimbingThroughWindows) | |||
| 0.83333 * Constants.basicStudentSpeedOfClimbingThroughWindows | |||
| ) | |||
| speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest) | |||
| class Robot: | |||
| moveSpeed = (int)(1.0 * Constants.basicStudentSpeed) | |||
| maxHp = (int)(0.4 * Constants.basicHp) | |||
| moveSpeed = (int)(0.9 * Constants.basicStudentSpeed) | |||
| maxHp = (int)(0.3 * Constants.basicHp) | |||
| maxAddiction = (int)(0.0 * Constants.basicMaxGamingAddiction) | |||
| LearnSpeed = (int)(1.0 * Constants.basicLearnSpeed) | |||
| LearnSpeed = (int)(0.7 * Constants.basicLearnSpeed) | |||
| EncourageSpeed = 0 | |||
| concealment = 1.0 * Constants.basicConcealment | |||
| alertnessRadius = (int)(1.0 * Constants.basicStudentAlertnessRadius) | |||
| viewRange = (int)(1.0 * Constants.basicStudentViewRange) | |||
| speedOfOpeningOrLocking = (int)( | |||
| 1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = (int)( | |||
| 0.0016 * Constants.basicStudentSpeedOfClimbingThroughWindows) | |||
| speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest) | |||
| concealment = 0.8 * Constants.basicConcealment | |||
| alertnessRadius = (int)(0.0 * Constants.basicStudentAlertnessRadius) | |||
| viewRange = (int)(0.0 * Constants.basicStudentViewRange) | |||
| speedOfOpeningOrLocking = (int)(0.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = 1 | |||
| speedOfOpenChest = (int)(0.8 * Constants.basicSpeedOfOpenChest) | |||
| class TechOtaku: | |||
| moveSpeed = (int)(0.75 * Constants.basicStudentSpeed) | |||
| moveSpeed = (int)(0.96 * Constants.basicStudentSpeed) | |||
| maxHp = (int)(0.9 * Constants.basicHp) | |||
| maxAddiction = (int)(1.1 * Constants.basicMaxGamingAddiction) | |||
| LearnSpeed = (int)(1.1 * Constants.basicLearnSpeed) | |||
| EncourageSpeed = (int)(0.9 * Constants.basicEncourageSpeed) | |||
| concealment = 1.0 * Constants.basicConcealment | |||
| maxAddiction = (int)(1.0 * Constants.basicMaxGamingAddiction) | |||
| LearnSpeed = (int)(0.9 * Constants.basicLearnSpeed) | |||
| EncourageSpeed = (int)(1.0 * Constants.basicEncourageSpeed) | |||
| concealment = 1.1 * Constants.basicConcealment | |||
| alertnessRadius = (int)(1.0 * Constants.basicStudentAlertnessRadius) | |||
| viewRange = (int)(0.9 * Constants.basicStudentViewRange) | |||
| speedOfOpeningOrLocking = (int)( | |||
| 1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfOpeningOrLocking = (int)(1.0 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = (int)( | |||
| 0.75 * Constants.basicStudentSpeedOfClimbingThroughWindows) | |||
| speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest) | |||
| 0.9 * Constants.basicStudentSpeedOfClimbingThroughWindows | |||
| ) | |||
| speedOfOpenChest = (int)(0.88 * Constants.basicSpeedOfOpenChest) | |||
| class Sunshine: | |||
| @@ -228,10 +226,10 @@ class Sunshine: | |||
| concealment = 1.0 * Constants.basicConcealment | |||
| alertnessRadius = (int)(1.0 * Constants.basicStudentAlertnessRadius) | |||
| viewRange = (int)(1.0 * Constants.basicStudentViewRange) | |||
| speedOfOpeningOrLocking = (int)( | |||
| 0.7 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfOpeningOrLocking = (int)(0.7 * Constants.basicSpeedOfOpeningOrLocking) | |||
| speedOfClimbingThroughWindows = (int)( | |||
| 1.0 * Constants.basicStudentSpeedOfClimbingThroughWindows) | |||
| 1.0 * Constants.basicStudentSpeedOfClimbingThroughWindows | |||
| ) | |||
| speedOfOpenChest = (int)(0.9 * Constants.basicSpeedOfOpenChest) | |||
| @@ -241,12 +239,22 @@ class CanBeginToCharge: | |||
| class BecomeInvisible: | |||
| skillCD = (int)(4 * Constants.commonSkillCD / 3) | |||
| skillCD = (int)(4 * Constants.commonSkillCD // 3) | |||
| durationTime = (int)(Constants.commonSkillTime) | |||
| class Punish: | |||
| skillCD = (int)(1.0 * Constants.commonSkillCD) | |||
| skillCD = (int)(1.5 * Constants.commonSkillCD) | |||
| durationTime = (int)(0.0 * Constants.commonSkillTime) | |||
| class SparksNSplash: | |||
| skillCD = (int)(1.5 * Constants.commonSkillCD) | |||
| durationTime = (int)(1.0 * Constants.commonSkillTime) | |||
| class HaveTea: | |||
| skillCD = (int)(3 * Constants.commonSkillCD) | |||
| durationTime = (int)(0.0 * Constants.commonSkillTime) | |||
| @@ -271,7 +279,7 @@ class Howl: | |||
| class ShowTime: | |||
| skillCD = (int)(8 * Constants.commonSkillCD / 3) | |||
| skillCD = (int)(8 * Constants.commonSkillCD // 3) | |||
| durationTime = (int)(1.0 * Constants.commonSkillTime) | |||
| @@ -286,7 +294,7 @@ class UseKnife: | |||
| class UseRobot: | |||
| skillCD = (int)(0.0017 * Constants.commonSkillCD) | |||
| skillCD = (int)(2 * Constants.commonSkillCD // 30) | |||
| durationTime = (int)(0.0 * Constants.commonSkillTime) | |||
| @@ -296,8 +304,9 @@ class WriteAnswers: | |||
| class SummonGolem: | |||
| skillCD = (int)(1.0 * Constants.commonSkillCD) | |||
| durationTime = (int)(0.0 * Constants.commonSkillTime) | |||
| skillCD = (int)(Constants.commonSkillCD * 4 // 3) | |||
| durationTime = (int)(6.0 * Constants.commonSkillTime) | |||
| class CommonAttackOfTricker: | |||
| BulletBombRange = 0 | |||
| @@ -305,39 +314,55 @@ class CommonAttackOfTricker: | |||
| ap = Constants.basicApOfTricker | |||
| Speed = Constants.basicBulletMoveSpeed | |||
| IsRemoteAttack = False | |||
| CastTime = BulletAttackRange * 1000 / Speed | |||
| CastTime = BulletAttackRange * 1000 // Speed | |||
| Backswing = Constants.basicBackswing | |||
| RecoveryFromHit = Constants.basicRecoveryFromHit | |||
| cd = Constants.basicBackswing | |||
| maxBulletNum = 1 | |||
| class FlyingKnife: | |||
| BulletBombRange = 0 | |||
| BulletAttackRange = Constants.basicRemoteAttackRange * 13 | |||
| ap = Constants.basicApOfTricker* 4 / 5 | |||
| Speed = Constants.basicBulletMoveSpeed* 25 / 10 | |||
| ap = Constants.basicApOfTricker * 4 // 5 | |||
| Speed = Constants.basicBulletMoveSpeed * 25 // 10 | |||
| IsRemoteAttack = True | |||
| CastTime = Constants.basicCastTime * 4 / 5 | |||
| Backswing =0 | |||
| RecoveryFromHit =0 | |||
| cd = Constants.basicBackswing / 2 | |||
| CastTime = Constants.basicCastTime * 6 // 5 | |||
| Backswing = 0 | |||
| RecoveryFromHit = 0 | |||
| cd = Constants.basicBackswing * 3 // 4 | |||
| maxBulletNum = 1 | |||
| class BombBomb: | |||
| BulletBombRange = Constants.basicBulletBombRange | |||
| BulletAttackRange = Constants.basicAttackShortRange | |||
| ap = Constants.basicApOfTricker * 6 / 5 | |||
| Speed = Constants.basicBulletMoveSpeed* 30 / 37 | |||
| ap = Constants.basicApOfTricker * 6 // 5 | |||
| Speed = Constants.basicBulletMoveSpeed * 30 // 37 | |||
| IsRemoteAttack = False | |||
| CastTime = BulletAttackRange * 1000 / Speed | |||
| Backswing =Constants.basicRecoveryFromHit | |||
| RecoveryFromHit =Constants.basicRecoveryFromHit | |||
| CastTime = BulletAttackRange * 1000 // Speed | |||
| Backswing = Constants.basicBackswing * 3 // 2 | |||
| RecoveryFromHit = Constants.basicRecoveryFromHit | |||
| cd = Constants.basicCD | |||
| maxBulletNum = 1 | |||
| class JumpyDumpty: | |||
| BulletBombRange = Constants.basicBulletBombRange / 2 | |||
| BulletAttackRange = Constants.basicRemoteAttackRange * 2 | |||
| ap = (int)(Constants.basicApOfTricker* 0.6) | |||
| Speed = Constants.basicBulletMoveSpeed* 43 / 37 | |||
| BulletBombRange = Constants.basicBulletBombRange // 2 | |||
| BulletAttackRange = Constants.basicAttackShortRange * 16 / 22 | |||
| ap = (int)(Constants.basicApOfTricker * 0.6) | |||
| Speed = Constants.basicBulletMoveSpeed * 43 // 37 | |||
| IsRemoteAttack = False | |||
| class Strike: | |||
| BulletBombRange = 0 | |||
| BulletAttackRange = Constants.basicAttackShortRange | |||
| ap = Constants.basicApOfTricker * 16 // 5 | |||
| Speed = Constants.basicBulletMoveSpeed * 125 // 148 | |||
| IsRemoteAttack = False | |||
| CastTime = Constants.basicCastTime * 16 // 25 | |||
| Backswing = Constants.basicBackswing | |||
| RecoveryFromHit = Constants.basicRecoveryFromHit | |||
| cd = Constants.basicBackswing | |||
| maxBulletNum = 1 | |||
| @@ -20,7 +20,6 @@ from PyAPI.Interface import ILogic, IGameTimer | |||
| class Logic(ILogic): | |||
| def __init__(self, playerID: int, playerType: THUAI6.PlayerType) -> None: | |||
| # ID | |||
| self.__playerID: int = playerID | |||
| @@ -103,7 +102,12 @@ class Logic(ILogic): | |||
| def GetPlaceType(self, x: int, y: int) -> THUAI6.PlaceType: | |||
| with self.__mtxState: | |||
| if x < 0 or x >= len(self.__currentState.gameMap) or y < 0 or y >= len(self.__currentState.gameMap[0]): | |||
| if ( | |||
| x < 0 | |||
| or x >= len(self.__currentState.gameMap) | |||
| or y < 0 | |||
| or y >= len(self.__currentState.gameMap[0]) | |||
| ): | |||
| self.__logger.warning("Invalid position") | |||
| return THUAI6.PlaceType.NullPlaceType | |||
| self.__logger.debug("Called GetPlaceType") | |||
| @@ -149,7 +153,9 @@ class Logic(ILogic): | |||
| with self.__mtxState: | |||
| self.__logger.debug("Called GetHiddenGateState") | |||
| if (x, y) in self.__currentState.mapInfo.hiddenGateState: | |||
| return copy.deepcopy(self.__currentState.mapInfo.hiddenGateState[(x, y)]) | |||
| return copy.deepcopy( | |||
| self.__currentState.mapInfo.hiddenGateState[(x, y)] | |||
| ) | |||
| else: | |||
| self.__logger.warning("HiddenGate not found") | |||
| return THUAI6.HiddenGateState.Null | |||
| @@ -184,9 +190,9 @@ class Logic(ILogic): | |||
| self.__logger.debug("Called ThrowProp") | |||
| return self.__comm.ThrowProp(propType, self.__playerID) | |||
| def UseSkill(self, skillID: int) -> bool: | |||
| def UseSkill(self, skillID: int, skillParam: int) -> bool: | |||
| self.__logger.debug("Called UseSkill") | |||
| return self.__comm.UseSkill(skillID, self.__playerID) | |||
| return self.__comm.UseSkill(skillID, skillParam, self.__playerID) | |||
| def SendMessage(self, toID: int, message: Union[str, bytes]) -> bool: | |||
| self.__logger.debug("Called SendMessage") | |||
| @@ -262,9 +268,13 @@ class Logic(ILogic): | |||
| self.__logger.debug("Called EndAllAction") | |||
| return self.__comm.EndAllAction(self.__playerID) | |||
| def HaveView(self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int) -> bool: | |||
| def HaveView( | |||
| self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int | |||
| ) -> bool: | |||
| with self.__mtxState: | |||
| return AssistFunction.HaveView(viewRange, selfX, selfY, gridX, gridY, self.__currentState.gameMap) | |||
| return AssistFunction.HaveView( | |||
| viewRange, selfX, selfY, gridX, gridY, self.__currentState.gameMap | |||
| ) | |||
| # Logic内部逻辑 | |||
| def __TryConnection(self) -> bool: | |||
| @@ -281,8 +291,7 @@ class Logic(ILogic): | |||
| # 读取消息,无消息时此处阻塞 | |||
| clientMsg = self.__comm.GetMessage2Client() | |||
| self.__logger.debug("Get message from server!") | |||
| self.__gameState = Proto2THUAI6.gameStateDict[ | |||
| clientMsg.game_state] | |||
| self.__gameState = Proto2THUAI6.gameStateDict[clientMsg.game_state] | |||
| if self.__gameState == THUAI6.GameState.GameStart: | |||
| # 读取玩家的GUID | |||
| @@ -294,8 +303,7 @@ class Logic(ILogic): | |||
| for row in obj.map_message.row: | |||
| col: List[THUAI6.PlaceType] = [] | |||
| for place in row.col: | |||
| col.append( | |||
| Proto2THUAI6.placeTypeDict[place]) | |||
| col.append(Proto2THUAI6.placeTypeDict[place]) | |||
| gameMap.append(col) | |||
| self.__currentState.gameMap = gameMap | |||
| self.__bufferState.gameMap = gameMap | |||
| @@ -330,127 +338,238 @@ class Logic(ILogic): | |||
| if item.WhichOneof("message_of_obj") == "student_message": | |||
| if item.student_message.player_id == self.__playerID: | |||
| self.__bufferState.self = Proto2THUAI6.Protobuf2THUAI6Student( | |||
| item.student_message) | |||
| self.__bufferState.students.append( | |||
| self.__bufferState.self) | |||
| item.student_message | |||
| ) | |||
| self.__bufferState.students.append(self.__bufferState.self) | |||
| else: | |||
| self.__bufferState.students.append( | |||
| Proto2THUAI6.Protobuf2THUAI6Student(item.student_message)) | |||
| Proto2THUAI6.Protobuf2THUAI6Student(item.student_message) | |||
| ) | |||
| self.__logger.debug("Add Student!") | |||
| else: | |||
| for item in message.obj_message: | |||
| if item.WhichOneof("message_of_obj") == "tricker_message": | |||
| if item.tricker_message.player_id == self.__playerID: | |||
| self.__bufferState.self = Proto2THUAI6.Protobuf2THUAI6Tricker( | |||
| item.tricker_message) | |||
| self.__bufferState.trickers.append( | |||
| self.__bufferState.self) | |||
| item.tricker_message | |||
| ) | |||
| self.__bufferState.trickers.append(self.__bufferState.self) | |||
| else: | |||
| self.__bufferState.trickers.append( | |||
| Proto2THUAI6.Protobuf2THUAI6Tricker(item.tricker_message)) | |||
| Proto2THUAI6.Protobuf2THUAI6Tricker(item.tricker_message) | |||
| ) | |||
| self.__logger.debug("Add Tricker!") | |||
| def __LoadBufferCase(self, item: Message2Clients.MessageOfObj) -> None: | |||
| if self.__playerType == THUAI6.PlayerType.StudentPlayer and item.WhichOneof("message_of_obj") == "tricker_message": | |||
| if ( | |||
| self.__playerType == THUAI6.PlayerType.StudentPlayer | |||
| and item.WhichOneof("message_of_obj") == "tricker_message" | |||
| ): | |||
| if MessageType.TRICKER_INVISIBLE in item.tricker_message.buff: | |||
| return | |||
| if AssistFunction.HaveView(self.__bufferState.self.viewRange, self.__bufferState.self.x, self.__bufferState.self.y, item.tricker_message.x, item.tricker_message.y, self.__bufferState.gameMap): | |||
| if AssistFunction.HaveView( | |||
| self.__bufferState.self.viewRange, | |||
| self.__bufferState.self.x, | |||
| self.__bufferState.self.y, | |||
| item.tricker_message.x, | |||
| item.tricker_message.y, | |||
| self.__bufferState.gameMap, | |||
| ): | |||
| self.__bufferState.trickers.append( | |||
| Proto2THUAI6.Protobuf2THUAI6Tricker(item.tricker_message)) | |||
| Proto2THUAI6.Protobuf2THUAI6Tricker(item.tricker_message) | |||
| ) | |||
| self.__logger.debug("Add Tricker!") | |||
| elif self.__playerType == THUAI6.PlayerType.TrickerPlayer and item.WhichOneof("message_of_obj") == "student_message": | |||
| elif ( | |||
| self.__playerType == THUAI6.PlayerType.TrickerPlayer | |||
| and item.WhichOneof("message_of_obj") == "student_message" | |||
| ): | |||
| if THUAI6.TrickerBuffType.Clairaudience in self.__bufferState.self.buff: | |||
| self.__bufferState.students.append( | |||
| Proto2THUAI6.Protobuf2THUAI6Student(item.student_message)) | |||
| Proto2THUAI6.Protobuf2THUAI6Student(item.student_message) | |||
| ) | |||
| self.__logger.debug("Add Student!") | |||
| return | |||
| if MessageType.STUDENT_INVISIBLE in item.student_message.buff: | |||
| return | |||
| if AssistFunction.HaveView(self.__bufferState.self.viewRange, self.__bufferState.self.x, self.__bufferState.self.y, item.student_message.x, item.student_message.y, self.__bufferState.gameMap): | |||
| if AssistFunction.HaveView( | |||
| self.__bufferState.self.viewRange, | |||
| self.__bufferState.self.x, | |||
| self.__bufferState.self.y, | |||
| item.student_message.x, | |||
| item.student_message.y, | |||
| self.__bufferState.gameMap, | |||
| ): | |||
| self.__bufferState.students.append( | |||
| Proto2THUAI6.Protobuf2THUAI6Student(item.student_message)) | |||
| Proto2THUAI6.Protobuf2THUAI6Student(item.student_message) | |||
| ) | |||
| self.__logger.debug("Add Student!") | |||
| elif item.WhichOneof("message_of_obj") == "prop_message": | |||
| if AssistFunction.HaveView(self.__bufferState.self.viewRange, self.__bufferState.self.x, self.__bufferState.self.y, item.prop_message.x, item.prop_message.y, self.__bufferState.gameMap): | |||
| if AssistFunction.HaveView( | |||
| self.__bufferState.self.viewRange, | |||
| self.__bufferState.self.x, | |||
| self.__bufferState.self.y, | |||
| item.prop_message.x, | |||
| item.prop_message.y, | |||
| self.__bufferState.gameMap, | |||
| ): | |||
| self.__bufferState.props.append( | |||
| Proto2THUAI6.Protobuf2THUAI6Prop(item.prop_message)) | |||
| Proto2THUAI6.Protobuf2THUAI6Prop(item.prop_message) | |||
| ) | |||
| self.__logger.debug("Add Prop!") | |||
| elif item.WhichOneof("message_of_obj") == "bullet_message": | |||
| if AssistFunction.HaveView(self.__bufferState.self.viewRange, self.__bufferState.self.x, self.__bufferState.self.y, item.bullet_message.x, item.bullet_message.y, self.__bufferState.gameMap): | |||
| if AssistFunction.HaveView( | |||
| self.__bufferState.self.viewRange, | |||
| self.__bufferState.self.x, | |||
| self.__bufferState.self.y, | |||
| item.bullet_message.x, | |||
| item.bullet_message.y, | |||
| self.__bufferState.gameMap, | |||
| ): | |||
| self.__bufferState.bullets.append( | |||
| Proto2THUAI6.Protobuf2THUAI6Bullet(item.bullet_message)) | |||
| Proto2THUAI6.Protobuf2THUAI6Bullet(item.bullet_message) | |||
| ) | |||
| self.__logger.debug("Add Bullet!") | |||
| elif item.WhichOneof("message_of_obj") == "classroom_message": | |||
| if AssistFunction.HaveView(self.__bufferState.self.viewRange, self.__bufferState.self.x, self.__bufferState.self.y, item.classroom_message.x, item.classroom_message.y, self.__bufferState.gameMap): | |||
| pos = (AssistFunction.GridToCell( | |||
| item.classroom_message.x), AssistFunction.GridToCell(item.classroom_message.y)) | |||
| if AssistFunction.HaveView( | |||
| self.__bufferState.self.viewRange, | |||
| self.__bufferState.self.x, | |||
| self.__bufferState.self.y, | |||
| item.classroom_message.x, | |||
| item.classroom_message.y, | |||
| self.__bufferState.gameMap, | |||
| ): | |||
| pos = ( | |||
| AssistFunction.GridToCell(item.classroom_message.x), | |||
| AssistFunction.GridToCell(item.classroom_message.y), | |||
| ) | |||
| if pos not in self.__bufferState.mapInfo.classroomState: | |||
| self.__bufferState.mapInfo.classroomState[pos] = item.classroom_message.progress | |||
| self.__bufferState.mapInfo.classroomState[ | |||
| pos | |||
| ] = item.classroom_message.progress | |||
| self.__logger.debug("Add Classroom!") | |||
| else: | |||
| self.__bufferState.mapInfo.classroomState[pos] = item.classroom_message.progress | |||
| self.__bufferState.mapInfo.classroomState[ | |||
| pos | |||
| ] = item.classroom_message.progress | |||
| self.__logger.debug("Update Classroom!") | |||
| elif item.WhichOneof("message_of_obj") == "chest_message": | |||
| if AssistFunction.HaveView(self.__bufferState.self.viewRange, self.__bufferState.self.x, self.__bufferState.self.y, item.chest_message.x, item.chest_message.y, self.__bufferState.gameMap): | |||
| pos = (AssistFunction.GridToCell( | |||
| item.chest_message.x), AssistFunction.GridToCell(item.chest_message.y)) | |||
| if AssistFunction.HaveView( | |||
| self.__bufferState.self.viewRange, | |||
| self.__bufferState.self.x, | |||
| self.__bufferState.self.y, | |||
| item.chest_message.x, | |||
| item.chest_message.y, | |||
| self.__bufferState.gameMap, | |||
| ): | |||
| pos = ( | |||
| AssistFunction.GridToCell(item.chest_message.x), | |||
| AssistFunction.GridToCell(item.chest_message.y), | |||
| ) | |||
| if pos not in self.__bufferState.mapInfo.chestState: | |||
| self.__bufferState.mapInfo.chestState[pos] = item.chest_message.progress | |||
| self.__logger.debug( | |||
| f"Add Chest at {pos[0]}, {pos[1]}") | |||
| self.__bufferState.mapInfo.chestState[ | |||
| pos | |||
| ] = item.chest_message.progress | |||
| self.__logger.debug(f"Add Chest at {pos[0]}, {pos[1]}") | |||
| else: | |||
| self.__bufferState.mapInfo.chestState[pos] = item.chest_message.progress | |||
| self.__logger.debug( | |||
| f"Update Chest at {pos[0]}, {pos[1]}") | |||
| self.__bufferState.mapInfo.chestState[ | |||
| pos | |||
| ] = item.chest_message.progress | |||
| self.__logger.debug(f"Update Chest at {pos[0]}, {pos[1]}") | |||
| elif item.WhichOneof("message_of_obj") == "door_message": | |||
| if AssistFunction.HaveView(self.__bufferState.self.viewRange, self.__bufferState.self.x, self.__bufferState.self.y, item.door_message.x, item.door_message.y, self.__bufferState.gameMap): | |||
| pos = (AssistFunction.GridToCell( | |||
| item.door_message.x), AssistFunction.GridToCell(item.door_message.y)) | |||
| if AssistFunction.HaveView( | |||
| self.__bufferState.self.viewRange, | |||
| self.__bufferState.self.x, | |||
| self.__bufferState.self.y, | |||
| item.door_message.x, | |||
| item.door_message.y, | |||
| self.__bufferState.gameMap, | |||
| ): | |||
| pos = ( | |||
| AssistFunction.GridToCell(item.door_message.x), | |||
| AssistFunction.GridToCell(item.door_message.y), | |||
| ) | |||
| if pos not in self.__bufferState.mapInfo.doorState: | |||
| self.__bufferState.mapInfo.doorState[pos] = item.door_message.is_open | |||
| self.__bufferState.mapInfo.doorProgress[pos] = item.door_message.progress | |||
| self.__bufferState.mapInfo.doorState[ | |||
| pos | |||
| ] = item.door_message.is_open | |||
| self.__bufferState.mapInfo.doorProgress[ | |||
| pos | |||
| ] = item.door_message.progress | |||
| self.__logger.debug("Add Door!") | |||
| else: | |||
| self.__bufferState.mapInfo.doorState[pos] = item.door_message.is_open | |||
| self.__bufferState.mapInfo.doorProgress[pos] = item.door_message.progress | |||
| self.__bufferState.mapInfo.doorState[ | |||
| pos | |||
| ] = item.door_message.is_open | |||
| self.__bufferState.mapInfo.doorProgress[ | |||
| pos | |||
| ] = item.door_message.progress | |||
| self.__logger.debug("Update Door!") | |||
| elif item.WhichOneof("message_of_obj") == "hidden_gate_message": | |||
| if AssistFunction.HaveView(self.__bufferState.self.viewRange, self.__bufferState.self.x, self.__bufferState.self.y, item.hidden_gate_message.x, item.hidden_gate_message.y, self.__bufferState.gameMap): | |||
| pos = (AssistFunction.GridToCell( | |||
| item.hidden_gate_message.x), AssistFunction.GridToCell(item.hidden_gate_message.y)) | |||
| if AssistFunction.HaveView( | |||
| self.__bufferState.self.viewRange, | |||
| self.__bufferState.self.x, | |||
| self.__bufferState.self.y, | |||
| item.hidden_gate_message.x, | |||
| item.hidden_gate_message.y, | |||
| self.__bufferState.gameMap, | |||
| ): | |||
| pos = ( | |||
| AssistFunction.GridToCell(item.hidden_gate_message.x), | |||
| AssistFunction.GridToCell(item.hidden_gate_message.y), | |||
| ) | |||
| if pos not in self.__bufferState.mapInfo.hiddenGateState: | |||
| self.__bufferState.mapInfo.hiddenGateState[pos] = Proto2THUAI6.Bool2HiddenGateState( | |||
| item.hidden_gate_message.opened) | |||
| self.__bufferState.mapInfo.hiddenGateState[ | |||
| pos | |||
| ] = Proto2THUAI6.Bool2HiddenGateState( | |||
| item.hidden_gate_message.opened | |||
| ) | |||
| self.__logger.debug("Add HiddenGate!") | |||
| else: | |||
| self.__bufferState.mapInfo.hiddenGateState[pos] = Proto2THUAI6.Bool2HiddenGateState( | |||
| item.hidden_gate_message.opened) | |||
| self.__bufferState.mapInfo.hiddenGateState[ | |||
| pos | |||
| ] = Proto2THUAI6.Bool2HiddenGateState( | |||
| item.hidden_gate_message.opened | |||
| ) | |||
| self.__logger.debug("Update HiddenGate!") | |||
| elif item.WhichOneof("message_of_obj") == "gate_message": | |||
| if AssistFunction.HaveView(self.__bufferState.self.viewRange, self.__bufferState.self.x, self.__bufferState.self.y, item.gate_message.x, item.gate_message.y, self.__bufferState.gameMap): | |||
| pos = (AssistFunction.GridToCell( | |||
| item.gate_message.x), AssistFunction.GridToCell(item.gate_message.y)) | |||
| if AssistFunction.HaveView( | |||
| self.__bufferState.self.viewRange, | |||
| self.__bufferState.self.x, | |||
| self.__bufferState.self.y, | |||
| item.gate_message.x, | |||
| item.gate_message.y, | |||
| self.__bufferState.gameMap, | |||
| ): | |||
| pos = ( | |||
| AssistFunction.GridToCell(item.gate_message.x), | |||
| AssistFunction.GridToCell(item.gate_message.y), | |||
| ) | |||
| if pos not in self.__bufferState.mapInfo.gateState: | |||
| self.__bufferState.mapInfo.gateState[pos] = item.gate_message.progress | |||
| self.__bufferState.mapInfo.gateState[ | |||
| pos | |||
| ] = item.gate_message.progress | |||
| self.__logger.debug("Add Gate!") | |||
| else: | |||
| self.__bufferState.mapInfo.gateState[pos] = item.gate_message.progress | |||
| self.__bufferState.mapInfo.gateState[ | |||
| pos | |||
| ] = item.gate_message.progress | |||
| self.__logger.debug("Update Gate!") | |||
| elif item.WhichOneof("message_of_obj") == "news_message": | |||
| if item.news_message.to_id == self.__playerID: | |||
| if item.news_message.WhichOneof("news") == "text_message": | |||
| self.__messageQueue.put( | |||
| (item.news_message.from_id, item.news_message.text_message)) | |||
| (item.news_message.from_id, item.news_message.text_message) | |||
| ) | |||
| self.__logger.debug("Add News!") | |||
| elif item.news_message.WhichOneof("news") == "binary_message": | |||
| self.__messageQueue.put( | |||
| (item.news_message.from_id, item.news_message.binary_message)) | |||
| (item.news_message.from_id, item.news_message.binary_message) | |||
| ) | |||
| self.__logger.debug("Add News!") | |||
| else: | |||
| self.__logger.error("Unknown News!") | |||
| else: | |||
| self.__logger.debug( | |||
| "Unknown Message!") | |||
| self.__logger.debug("Unknown Message!") | |||
| def __LoadBuffer(self, message: Message2Clients.MessageToClient) -> None: | |||
| with self.__cvBuffer: | |||
| @@ -470,14 +589,18 @@ class Logic(ILogic): | |||
| self.__bufferState.guids.append(obj.tricker_message.guid) | |||
| self.__bufferState.gameInfo = Proto2THUAI6.Protobuf2THUAI6GameInfo( | |||
| message.all_message) | |||
| message.all_message | |||
| ) | |||
| self.__LoadBufferSelf(message) | |||
| for item in message.obj_message: | |||
| self.__LoadBufferCase(item) | |||
| if Setting.asynchronous(): | |||
| with self.__mtxState: | |||
| self.__currentState, self.__bufferState = self.__bufferState, self.__currentState | |||
| self.__currentState, self.__bufferState = ( | |||
| self.__bufferState, | |||
| self.__currentState, | |||
| ) | |||
| self.__counterState = self.__counterBuffer | |||
| self.__logger.info("Update state!") | |||
| self.__freshed = True | |||
| @@ -496,7 +619,10 @@ class Logic(ILogic): | |||
| with self.__cvBuffer: | |||
| self.__cvBuffer.wait_for(lambda: self.__bufferUpdated) | |||
| with self.__mtxState: | |||
| self.__bufferState, self.__currentState = self.__currentState, self.__bufferState | |||
| self.__bufferState, self.__currentState = ( | |||
| self.__currentState, | |||
| self.__bufferState, | |||
| ) | |||
| self.__counterState = self.__counterBuffer | |||
| self.__bufferUpdated = False | |||
| self.__logger.info("Update state!") | |||
| @@ -506,12 +632,21 @@ class Logic(ILogic): | |||
| with self.__cvBuffer: | |||
| self.__cvBuffer.wait_for(lambda: self.__freshed) | |||
| def Main(self, createAI: Callable, IP: str, port: str, file: bool, screen: bool, warnOnly: bool) -> None: | |||
| def Main( | |||
| self, | |||
| createAI: Callable, | |||
| IP: str, | |||
| port: str, | |||
| file: bool, | |||
| screen: bool, | |||
| warnOnly: bool, | |||
| ) -> None: | |||
| # 建立日志组件 | |||
| self.__logger.setLevel(logging.DEBUG) | |||
| formatter = logging.Formatter( | |||
| "[%(name)s] [%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", '%H:%M:%S') | |||
| "[%(name)s] [%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", | |||
| "%H:%M:%S", | |||
| ) | |||
| # 确保文件存在 | |||
| # if not os.path.exists(os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs"): | |||
| # os.makedirs(os.path.dirname(os.path.dirname( | |||
| @@ -519,13 +654,21 @@ class Logic(ILogic): | |||
| if platform.system().lower() == "windows": | |||
| os.system( | |||
| f"mkdir \"{os.path.dirname(os.path.dirname(os.path.realpath(__file__)))}\\logs\"") | |||
| f'mkdir "{os.path.dirname(os.path.dirname(os.path.realpath(__file__)))}\\logs"' | |||
| ) | |||
| else: | |||
| os.system( | |||
| f"mkdir -p \"{os.path.dirname(os.path.dirname(os.path.realpath(__file__)))}/logs\"") | |||
| fileHandler = logging.FileHandler(os.path.dirname( | |||
| os.path.dirname(os.path.realpath(__file__))) + "/logs/logic" + str(self.__playerID) + "-log.txt", "w+", encoding="utf-8") | |||
| f'mkdir -p "{os.path.dirname(os.path.dirname(os.path.realpath(__file__)))}/logs"' | |||
| ) | |||
| fileHandler = logging.FileHandler( | |||
| os.path.dirname(os.path.dirname(os.path.realpath(__file__))) | |||
| + "/logs/logic" | |||
| + str(self.__playerID) | |||
| + "-log.txt", | |||
| "w+", | |||
| encoding="utf-8", | |||
| ) | |||
| screenHandler = logging.StreamHandler() | |||
| if file: | |||
| fileHandler.setLevel(logging.DEBUG) | |||
| @@ -555,13 +698,15 @@ class Logic(ILogic): | |||
| self.__timer = StudentAPI(self) | |||
| else: | |||
| self.__timer = StudentDebugAPI( | |||
| self, file, screen, warnOnly, self.__playerID) | |||
| self, file, screen, warnOnly, self.__playerID | |||
| ) | |||
| elif self.__playerType == THUAI6.PlayerType.TrickerPlayer: | |||
| if not file and not screen: | |||
| self.__timer = TrickerAPI(self) | |||
| else: | |||
| self.__timer = TrickerDebugAPI( | |||
| self, file, screen, warnOnly, self.__playerID) | |||
| self, file, screen, warnOnly, self.__playerID | |||
| ) | |||
| # 构建AI线程 | |||
| def AIThread(): | |||
| @@ -583,7 +728,8 @@ class Logic(ILogic): | |||
| if self.__TryConnection(): | |||
| self.__logger.info( | |||
| "Connect to the server successfully, AI thread will be started.") | |||
| "Connect to the server successfully, AI thread will be started." | |||
| ) | |||
| self.__threadAI = threading.Thread(target=AIThread) | |||
| self.__threadAI.start() | |||
| self.__ProcessMessage() | |||
| @@ -1,8 +1,8 @@ | |||
| import os | |||
| import sys | |||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) | |||
| sys.path.append(os.path.dirname(os.path.dirname( | |||
| os.path.realpath(__file__))) + '/proto') | |||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/proto") | |||
| from PyAPI.Interface import IAI | |||
| from PyAPI.AI import AI | |||
| @@ -42,19 +42,50 @@ def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None: | |||
| screen: bool = True | |||
| warnOnly: bool = False | |||
| parser = argparse.ArgumentParser( | |||
| description="THUAI6 Python Interface Commandline Parameter Introduction") | |||
| parser.add_argument("-I", type=str, required=True, | |||
| help="Server`s IP 127.0.0.1 in default", dest="sIP", default="127.0.0.1") | |||
| parser.add_argument("-P", type=str, required=True, | |||
| help="Server`s Port 8888 in default", dest="sPort", default="8888") | |||
| parser.add_argument("-p", type=int, required=True, | |||
| help="Player`s ID", dest="pID", choices=[0, 1, 2, 3, 4]) | |||
| parser.add_argument("-d", action='store_true', | |||
| help="Set this flag to save the debug log to ./logs folder", dest="file") | |||
| parser.add_argument("-o", action='store_true', | |||
| help="Set this flag to print the debug log to the screen", dest="screen") | |||
| parser.add_argument("-w", action='store_true', | |||
| help="Set this flag to only print warning on the screen", dest="warnOnly") | |||
| description="THUAI6 Python Interface Commandline Parameter Introduction" | |||
| ) | |||
| parser.add_argument( | |||
| "-I", | |||
| type=str, | |||
| required=True, | |||
| help="Server`s IP 127.0.0.1 in default", | |||
| dest="sIP", | |||
| default="127.0.0.1", | |||
| ) | |||
| parser.add_argument( | |||
| "-P", | |||
| type=str, | |||
| required=True, | |||
| help="Server`s Port 8888 in default", | |||
| dest="sPort", | |||
| default="8888", | |||
| ) | |||
| parser.add_argument( | |||
| "-p", | |||
| type=int, | |||
| required=True, | |||
| help="Player`s ID", | |||
| dest="pID", | |||
| choices=[0, 1, 2, 3, 4], | |||
| ) | |||
| parser.add_argument( | |||
| "-d", | |||
| action="store_true", | |||
| help="Set this flag to save the debug log to ./logs folder", | |||
| dest="file", | |||
| ) | |||
| parser.add_argument( | |||
| "-o", | |||
| action="store_true", | |||
| help="Set this flag to print the debug log to the screen", | |||
| dest="screen", | |||
| ) | |||
| parser.add_argument( | |||
| "-w", | |||
| action="store_true", | |||
| help="Set this flag to only print warning on the screen", | |||
| dest="warnOnly", | |||
| ) | |||
| args = parser.parse_args() | |||
| pID = args.pID | |||
| sIP = args.sIP | |||
| @@ -79,5 +110,5 @@ def CreateAI(pID: int) -> IAI: | |||
| return AI(pID) | |||
| if __name__ == '__main__': | |||
| if __name__ == "__main__": | |||
| THUAI6Main(sys.argv, CreateAI) | |||
| @@ -52,6 +52,7 @@ class PropType(Enum): | |||
| AddHpOrAp = 6 | |||
| ShieldOrSpear = 7 | |||
| RecoveryFromDizziness = 8 | |||
| CraftingBench = 9 | |||
| class BulletType(Enum): | |||
| @@ -60,7 +61,7 @@ class BulletType(Enum): | |||
| CommonAttackOfTricker = 2 | |||
| BombBomb = 3 | |||
| JumpyDumpty = 4 | |||
| AtomBomb = 5 | |||
| Strike = 5 | |||
| class StudentType(Enum): | |||
| @@ -156,7 +157,6 @@ class Player: | |||
| self.timeUntilSkillAvailable: List[float] = [] | |||
| self.playerType: PlayerType = PlayerType.NullPlayerType | |||
| self.prop: List[PropType] = [] | |||
| self.place: PlaceType = PlaceType.NullPlaceType | |||
| self.bulletType: BulletType = BulletType.NullBulletType | |||
| self.playerState: PlayerState = PlayerState.NullState | |||
| @@ -190,7 +190,6 @@ class Prop: | |||
| self.y: int = 0 | |||
| self.guid: int = 0 | |||
| self.type: PropType = PropType.NullPropType | |||
| self.place: PlaceType = PlaceType.NullPlaceType | |||
| self.facingDirection: float = 0.0 | |||
| @@ -202,7 +201,6 @@ class Bullet: | |||
| self.facingDirection: float = 0.0 | |||
| self.guid: int = 0 | |||
| self.team: PlayerType = PlayerType.NullPlayerType | |||
| self.place: PlaceType = PlaceType.NullPlaceType | |||
| self.bombRange: float = 0.0 | |||
| self.speed: int = 0 | |||
| @@ -15,7 +15,6 @@ class NoInstance: | |||
| class AssistFunction(NoInstance): | |||
| @staticmethod | |||
| def CellToGrid(cell: int) -> int: | |||
| return cell * numOfGridPerCell + numOfGridPerCell // 2 | |||
| @@ -25,14 +24,19 @@ class AssistFunction(NoInstance): | |||
| return grid // numOfGridPerCell | |||
| @staticmethod | |||
| def HaveView(viewRange: int, x: int, y: int, newX: int, newY: int, map: List[List[THUAI6.PlaceType]]) -> bool: | |||
| def HaveView( | |||
| viewRange: int, | |||
| x: int, | |||
| y: int, | |||
| newX: int, | |||
| newY: int, | |||
| map: List[List[THUAI6.PlaceType]], | |||
| ) -> bool: | |||
| deltaX: int = newX - x | |||
| deltaY: int = newY - y | |||
| distance: float = deltaX**2 + deltaY**2 | |||
| myPlace = map[AssistFunction.GridToCell( | |||
| x)][AssistFunction.GridToCell(y)] | |||
| newPlace = map[AssistFunction.GridToCell( | |||
| newX)][AssistFunction.GridToCell(newY)] | |||
| myPlace = map[AssistFunction.GridToCell(x)][AssistFunction.GridToCell(y)] | |||
| newPlace = map[AssistFunction.GridToCell(newX)][AssistFunction.GridToCell(newY)] | |||
| if myPlace != THUAI6.PlaceType.Grass and newPlace == THUAI6.PlaceType.Grass: | |||
| return False | |||
| if distance <= viewRange * viewRange: | |||
| @@ -47,7 +51,12 @@ class AssistFunction(NoInstance): | |||
| for i in range(divide): | |||
| selfX += dx | |||
| selfY += dy | |||
| if map[AssistFunction.GridToCell(int(selfX))][AssistFunction.GridToCell(int(selfY))] != THUAI6.PlaceType.Grass: | |||
| if ( | |||
| map[AssistFunction.GridToCell(int(selfX))][ | |||
| AssistFunction.GridToCell(int(selfY)) | |||
| ] | |||
| != THUAI6.PlaceType.Grass | |||
| ): | |||
| return False | |||
| else: | |||
| return True | |||
| @@ -55,7 +64,12 @@ class AssistFunction(NoInstance): | |||
| for i in range(divide): | |||
| selfX += dx | |||
| selfY += dy | |||
| if map[AssistFunction.GridToCell(int(selfX))][AssistFunction.GridToCell(int(selfY))] == THUAI6.PlaceType.Wall: | |||
| if ( | |||
| map[AssistFunction.GridToCell(int(selfX))][ | |||
| AssistFunction.GridToCell(int(selfY)) | |||
| ] | |||
| == THUAI6.PlaceType.Wall | |||
| ): | |||
| return False | |||
| else: | |||
| return True | |||
| @@ -76,12 +90,14 @@ class Proto2THUAI6(NoInstance): | |||
| MessageType.DOOR3: THUAI6.PlaceType.Door3, | |||
| MessageType.DOOR5: THUAI6.PlaceType.Door5, | |||
| MessageType.DOOR6: THUAI6.PlaceType.Door6, | |||
| MessageType.CHEST: THUAI6.PlaceType.Chest, } | |||
| MessageType.CHEST: THUAI6.PlaceType.Chest, | |||
| } | |||
| shapeTypeDict: Final[dict] = { | |||
| MessageType.NULL_SHAPE_TYPE: THUAI6.ShapeType.NullShapeType, | |||
| MessageType.SQUARE: THUAI6.ShapeType.Square, | |||
| MessageType.CIRCLE: THUAI6.ShapeType.Circle} | |||
| MessageType.CIRCLE: THUAI6.ShapeType.Circle, | |||
| } | |||
| propTypeDict: Final[dict] = { | |||
| MessageType.NULL_PROP_TYPE: THUAI6.PropType.NullPropType, | |||
| @@ -92,12 +108,15 @@ class Proto2THUAI6(NoInstance): | |||
| MessageType.ADD_HP_OR_AP: THUAI6.PropType.AddHpOrAp, | |||
| MessageType.ADD_LIFE_OR_CLAIRAUDIENCE: THUAI6.PropType.AddLifeOrClairaudience, | |||
| MessageType.SHIELD_OR_SPEAR: THUAI6.PropType.ShieldOrSpear, | |||
| MessageType.RECOVERY_FROM_DIZZINESS: THUAI6.PropType.RecoveryFromDizziness, } | |||
| MessageType.RECOVERY_FROM_DIZZINESS: THUAI6.PropType.RecoveryFromDizziness, | |||
| MessageType.CRAFTING_BENCH: THUAI6.PropType.CraftingBench, | |||
| } | |||
| playerTypeDict: Final[dict] = { | |||
| MessageType.NULL_PLAYER_TYPE: THUAI6.PlayerType.NullPlayerType, | |||
| MessageType.STUDENT_PLAYER: THUAI6.PlayerType.StudentPlayer, | |||
| MessageType.TRICKER_PLAYER: THUAI6.PlayerType.TrickerPlayer, } | |||
| MessageType.TRICKER_PLAYER: THUAI6.PlayerType.TrickerPlayer, | |||
| } | |||
| studentTypeDict: Final[dict] = { | |||
| MessageType.NULL_STUDENT_TYPE: THUAI6.StudentType.NullStudentType, | |||
| @@ -106,21 +125,24 @@ class Proto2THUAI6(NoInstance): | |||
| MessageType.STRAIGHT_A_STUDENT: THUAI6.StudentType.StraightAStudent, | |||
| MessageType.ROBOT: THUAI6.StudentType.Robot, | |||
| MessageType.TECH_OTAKU: THUAI6.StudentType.TechOtaku, | |||
| MessageType.SUNSHINE: THUAI6.StudentType.Sunshine, } | |||
| MessageType.SUNSHINE: THUAI6.StudentType.Sunshine, | |||
| } | |||
| trickerTypeDict: Final[dict] = { | |||
| MessageType.NULL_TRICKER_TYPE: THUAI6.TrickerType.NullTrickerType, | |||
| MessageType.ASSASSIN: THUAI6.TrickerType.Assassin, | |||
| MessageType.KLEE: THUAI6.TrickerType.Klee, | |||
| MessageType.A_NOISY_PERSON: THUAI6.TrickerType.ANoisyPerson, | |||
| MessageType.IDOL: THUAI6.TrickerType.Idol, } | |||
| MessageType.IDOL: THUAI6.TrickerType.Idol, | |||
| } | |||
| studentBuffTypeDict: Final[dict] = { | |||
| MessageType.NULL_SBUFF_TYPE: THUAI6.StudentBuffType.NullStudentBuffType, | |||
| MessageType.STUDENT_ADD_SPEED: THUAI6.StudentBuffType.AddSpeed, | |||
| MessageType.ADD_LIFE: THUAI6.StudentBuffType.AddLife, | |||
| MessageType.SHIELD: THUAI6.StudentBuffType.Shield, | |||
| MessageType.STUDENT_INVISIBLE: THUAI6.StudentBuffType.Invisible, } | |||
| MessageType.STUDENT_INVISIBLE: THUAI6.StudentBuffType.Invisible, | |||
| } | |||
| trickerBuffTypeDict: Final[dict] = { | |||
| MessageType.NULL_TBUFF_TYPE: THUAI6.TrickerBuffType.NullTrickerBuffType, | |||
| @@ -128,7 +150,8 @@ class Proto2THUAI6(NoInstance): | |||
| MessageType.SPEAR: THUAI6.TrickerBuffType.Spear, | |||
| MessageType.ADD_AP: THUAI6.TrickerBuffType.AddAp, | |||
| MessageType.CLAIRAUDIENCE: THUAI6.TrickerBuffType.Clairaudience, | |||
| MessageType.TRICKER_INVISIBLE: THUAI6.TrickerBuffType.Invisible, } | |||
| MessageType.TRICKER_INVISIBLE: THUAI6.TrickerBuffType.Invisible, | |||
| } | |||
| playerStateDict: Final[dict] = { | |||
| MessageType.NULL_STATUS: THUAI6.PlayerState.NullState, | |||
| @@ -149,13 +172,15 @@ class Proto2THUAI6(NoInstance): | |||
| MessageType.CLIMBING: THUAI6.PlayerState.Climbing, | |||
| MessageType.OPENING_A_CHEST: THUAI6.PlayerState.OpeningAChest, | |||
| MessageType.USING_SPECIAL_SKILL: THUAI6.PlayerState.UsingSpecialSkill, | |||
| MessageType.OPENING_A_GATE: THUAI6.PlayerState.OpeningAGate, } | |||
| MessageType.OPENING_A_GATE: THUAI6.PlayerState.OpeningAGate, | |||
| } | |||
| gameStateDict: Final[dict] = { | |||
| MessageType.NULL_GAME_STATE: THUAI6.GameState.NullGameState, | |||
| MessageType.GAME_START: THUAI6.GameState.GameStart, | |||
| MessageType.GAME_RUNNING: THUAI6.GameState.GameRunning, | |||
| MessageType.GAME_END: THUAI6.GameState.GameEnd} | |||
| MessageType.GAME_END: THUAI6.GameState.GameEnd, | |||
| } | |||
| bulletTypeDict: Final[dict] = { | |||
| MessageType.NULL_BULLET_TYPE: THUAI6.BulletType.NullBulletType, | |||
| @@ -163,11 +188,14 @@ class Proto2THUAI6(NoInstance): | |||
| MessageType.BOMB_BOMB: THUAI6.BulletType.BombBomb, | |||
| MessageType.COMMON_ATTACK_OF_TRICKER: THUAI6.BulletType.CommonAttackOfTricker, | |||
| MessageType.JUMPY_DUMPTY: THUAI6.BulletType.JumpyDumpty, | |||
| MessageType.ATOM_BOMB: THUAI6.BulletType.AtomBomb, } | |||
| MessageType.STRIKE: THUAI6.BulletType.Strike, | |||
| } | |||
| # 用于将Proto的对象转为THUAI6的对象 | |||
| @ staticmethod | |||
| def Protobuf2THUAI6Tricker(trickerMsg: Message2Clients.MessageOfTricker) -> THUAI6.Tricker: | |||
| @staticmethod | |||
| def Protobuf2THUAI6Tricker( | |||
| trickerMsg: Message2Clients.MessageOfTricker, | |||
| ) -> THUAI6.Tricker: | |||
| tricker = THUAI6.Tricker() | |||
| tricker.x = trickerMsg.x | |||
| tricker.y = trickerMsg.y | |||
| @@ -179,7 +207,6 @@ class Proto2THUAI6(NoInstance): | |||
| tricker.bulletType = Proto2THUAI6.bulletTypeDict[trickerMsg.bullet_type] | |||
| for time in trickerMsg.time_until_skill_available: | |||
| tricker.timeUntilSkillAvailable.append(time) | |||
| tricker.place = Proto2THUAI6.placeTypeDict[trickerMsg.place] | |||
| tricker.playerState = Proto2THUAI6.playerStateDict[trickerMsg.player_state] | |||
| for item in trickerMsg.prop: | |||
| tricker.prop.append(Proto2THUAI6.propTypeDict[item]) | |||
| @@ -193,8 +220,10 @@ class Proto2THUAI6(NoInstance): | |||
| tricker.playerType = THUAI6.PlayerType.TrickerPlayer | |||
| return tricker | |||
| @ staticmethod | |||
| def Protobuf2THUAI6Student(studentMsg: Message2Clients.MessageOfStudent) -> THUAI6.Student: | |||
| @staticmethod | |||
| def Protobuf2THUAI6Student( | |||
| studentMsg: Message2Clients.MessageOfStudent, | |||
| ) -> THUAI6.Student: | |||
| student = THUAI6.Student() | |||
| student.x = studentMsg.x | |||
| student.y = studentMsg.y | |||
| @@ -211,7 +240,6 @@ class Proto2THUAI6(NoInstance): | |||
| student.dangerAlert = studentMsg.danger_alert | |||
| for time in studentMsg.time_until_skill_available: | |||
| student.timeUntilSkillAvailable.append(time) | |||
| student.place = Proto2THUAI6.placeTypeDict[studentMsg.place] | |||
| for item in studentMsg.prop: | |||
| student.prop.append(Proto2THUAI6.propTypeDict[item]) | |||
| student.studentType = Proto2THUAI6.studentTypeDict[studentMsg.student_type] | |||
| @@ -225,7 +253,7 @@ class Proto2THUAI6(NoInstance): | |||
| student.playerType = THUAI6.PlayerType.StudentPlayer | |||
| return student | |||
| @ staticmethod | |||
| @staticmethod | |||
| def Protobuf2THUAI6Prop(propMsg: Message2Clients.MessageOfProp) -> THUAI6.Prop: | |||
| prop = THUAI6.Prop() | |||
| prop.x = propMsg.x | |||
| @@ -235,7 +263,7 @@ class Proto2THUAI6(NoInstance): | |||
| prop.facingDirection = propMsg.facing_direction | |||
| return prop | |||
| @ staticmethod | |||
| @staticmethod | |||
| def Protobuf2THUAI6GameInfo(allMsg: Message2Clients.MessageOfAll): | |||
| gameInfo = THUAI6.GameInfo() | |||
| gameInfo.gameTime = allMsg.game_time | |||
| @@ -246,8 +274,10 @@ class Proto2THUAI6(NoInstance): | |||
| gameInfo.trickerScore = allMsg.tricker_score | |||
| return gameInfo | |||
| @ staticmethod | |||
| def Protobuf2THUAI6Bullet(bulletMsg: Message2Clients.MessageOfBullet) -> THUAI6.Bullet: | |||
| @staticmethod | |||
| def Protobuf2THUAI6Bullet( | |||
| bulletMsg: Message2Clients.MessageOfBullet, | |||
| ) -> THUAI6.Bullet: | |||
| bullet = THUAI6.Bullet() | |||
| bullet.x = bulletMsg.x | |||
| bullet.y = bulletMsg.y | |||
| @@ -255,12 +285,13 @@ class Proto2THUAI6(NoInstance): | |||
| bullet.facingDirection = bulletMsg.facing_direction | |||
| bullet.guid = bulletMsg.guid | |||
| bullet.team = Proto2THUAI6.playerTypeDict[bulletMsg.team] | |||
| bullet.place = Proto2THUAI6.placeTypeDict[bulletMsg.place] | |||
| bullet.bombRange = bulletMsg.bomb_range | |||
| return bullet | |||
| @ staticmethod | |||
| def Protobuf2THUAI6BombedBullet(bulletMsg: Message2Clients.MessageOfBombedBullet) -> THUAI6.BombedBullet: | |||
| @staticmethod | |||
| def Protobuf2THUAI6BombedBullet( | |||
| bulletMsg: Message2Clients.MessageOfBombedBullet, | |||
| ) -> THUAI6.BombedBullet: | |||
| bullet = THUAI6.BombedBullet() | |||
| bullet.x = bulletMsg.x | |||
| bullet.y = bulletMsg.y | |||
| @@ -270,7 +301,7 @@ class Proto2THUAI6(NoInstance): | |||
| bullet.bombRange = bulletMsg.bomb_range | |||
| return bullet | |||
| @ staticmethod | |||
| @staticmethod | |||
| def Bool2HiddenGateState(gateMsg: bool) -> THUAI6.HiddenGateState: | |||
| if gateMsg: | |||
| return THUAI6.HiddenGateState.Opened | |||
| @@ -291,12 +322,14 @@ class THUAI62Proto(NoInstance): | |||
| THUAI6.PlaceType.Door5: MessageType.DOOR5, | |||
| THUAI6.PlaceType.Door6: MessageType.DOOR6, | |||
| THUAI6.PlaceType.Chest: MessageType.CHEST, | |||
| THUAI6.PlaceType.Window: MessageType.WINDOW, } | |||
| THUAI6.PlaceType.Window: MessageType.WINDOW, | |||
| } | |||
| playerTypeDict: Final[dict] = { | |||
| THUAI6.PlayerType.NullPlayerType: MessageType.NULL_PLAYER_TYPE, | |||
| THUAI6.PlayerType.StudentPlayer: MessageType.STUDENT_PLAYER, | |||
| THUAI6.PlayerType.TrickerPlayer: MessageType.TRICKER_PLAYER} | |||
| THUAI6.PlayerType.TrickerPlayer: MessageType.TRICKER_PLAYER, | |||
| } | |||
| studentTypeDict: Final[dict] = { | |||
| THUAI6.StudentType.NullStudentType: MessageType.NULL_STUDENT_TYPE, | |||
| @@ -305,14 +338,16 @@ class THUAI62Proto(NoInstance): | |||
| THUAI6.StudentType.StraightAStudent: MessageType.STRAIGHT_A_STUDENT, | |||
| THUAI6.StudentType.Robot: MessageType.ROBOT, | |||
| THUAI6.StudentType.TechOtaku: MessageType.TECH_OTAKU, | |||
| THUAI6.StudentType.Sunshine: MessageType.SUNSHINE, } | |||
| THUAI6.StudentType.Sunshine: MessageType.SUNSHINE, | |||
| } | |||
| trickerTypeDict: Final[dict] = { | |||
| THUAI6.TrickerType.NullTrickerType: MessageType.NULL_TRICKER_TYPE, | |||
| THUAI6.TrickerType.Assassin: MessageType.ASSASSIN, | |||
| THUAI6.TrickerType.Klee: MessageType.KLEE, | |||
| THUAI6.TrickerType.ANoisyPerson: MessageType.A_NOISY_PERSON, | |||
| THUAI6.TrickerType.Idol: MessageType.IDOL, } | |||
| THUAI6.TrickerType.Idol: MessageType.IDOL, | |||
| } | |||
| propTypeDict: Final[dict] = { | |||
| THUAI6.PropType.NullPropType: MessageType.NULL_PROP_TYPE, | |||
| @@ -322,44 +357,75 @@ class THUAI62Proto(NoInstance): | |||
| THUAI6.PropType.AddHpOrAp: MessageType.ADD_HP_OR_AP, | |||
| THUAI6.PropType.AddLifeOrClairaudience: MessageType.ADD_LIFE_OR_CLAIRAUDIENCE, | |||
| THUAI6.PropType.AddSpeed: MessageType.ADD_SPEED, | |||
| THUAI6.PropType.ShieldOrSpear: MessageType.SHIELD_OR_SPEAR, } | |||
| THUAI6.PropType.ShieldOrSpear: MessageType.SHIELD_OR_SPEAR, | |||
| THUAI6.PropType.CraftingBench: MessageType.CRAFTING_BENCH, | |||
| } | |||
| # 用于将THUAI6的对象转为Proto的对象 | |||
| @ staticmethod | |||
| def THUAI62ProtobufPlayer(playerID: int, playerType: THUAI6.PlayerType, studentType: THUAI6.StudentType, trickerType: THUAI6.TrickerType) -> Message2Server.PlayerMsg: | |||
| @staticmethod | |||
| def THUAI62ProtobufPlayer( | |||
| playerID: int, | |||
| playerType: THUAI6.PlayerType, | |||
| studentType: THUAI6.StudentType, | |||
| trickerType: THUAI6.TrickerType, | |||
| ) -> Message2Server.PlayerMsg: | |||
| if playerType == THUAI6.PlayerType.StudentPlayer: | |||
| return Message2Server.PlayerMsg(player_id=playerID, player_type=MessageType.STUDENT_PLAYER, student_type=THUAI62Proto.studentTypeDict[studentType]) | |||
| return Message2Server.PlayerMsg( | |||
| player_id=playerID, | |||
| player_type=MessageType.STUDENT_PLAYER, | |||
| student_type=THUAI62Proto.studentTypeDict[studentType], | |||
| ) | |||
| else: | |||
| return Message2Server.PlayerMsg(player_id=playerID, player_type=MessageType.TRICKER_PLAYER, tricker_type=THUAI62Proto.trickerTypeDict[trickerType]) | |||
| return Message2Server.PlayerMsg( | |||
| player_id=playerID, | |||
| player_type=MessageType.TRICKER_PLAYER, | |||
| tricker_type=THUAI62Proto.trickerTypeDict[trickerType], | |||
| ) | |||
| @ staticmethod | |||
| @staticmethod | |||
| def THUAI62ProtobufID(playerID: int) -> Message2Server.IDMsg: | |||
| return Message2Server.IDMsg(player_id=playerID) | |||
| @ staticmethod | |||
| @staticmethod | |||
| def THUAI62ProtobufMove(time: int, angle: float, id: int) -> Message2Server.MoveMsg: | |||
| return Message2Server.MoveMsg(player_id=id, angle=angle, time_in_milliseconds=time) | |||
| return Message2Server.MoveMsg( | |||
| player_id=id, angle=angle, time_in_milliseconds=time | |||
| ) | |||
| @ staticmethod | |||
| def THUAI62ProtobufTreatAndRescue(playerID: int, mateID: int) -> Message2Server.TreatAndRescueMsg: | |||
| @staticmethod | |||
| def THUAI62ProtobufTreatAndRescue( | |||
| playerID: int, mateID: int | |||
| ) -> Message2Server.TreatAndRescueMsg: | |||
| return Message2Server.TreatAndRescueMsg(player_id=playerID, to_player_id=mateID) | |||
| @ staticmethod | |||
| @staticmethod | |||
| def THUAI62ProtobufProp(prop: THUAI6.PropType, id: int) -> Message2Server.PropMsg: | |||
| return Message2Server.PropMsg(player_id=id, prop_type=THUAI62Proto.propTypeDict[prop]) | |||
| return Message2Server.PropMsg( | |||
| player_id=id, prop_type=THUAI62Proto.propTypeDict[prop] | |||
| ) | |||
| @ staticmethod | |||
| def THUAI62ProtobufSend(msg: Union[str, bytes], toID: int, id: int) -> Message2Server.SendMsg: | |||
| @staticmethod | |||
| def THUAI62ProtobufSend( | |||
| msg: Union[str, bytes], toID: int, id: int | |||
| ) -> Message2Server.SendMsg: | |||
| if isinstance(msg, str): | |||
| return Message2Server.SendMsg(player_id=id, to_player_id=toID, text_message=msg) | |||
| return Message2Server.SendMsg( | |||
| player_id=id, to_player_id=toID, text_message=msg | |||
| ) | |||
| elif isinstance(msg, bytes): | |||
| return Message2Server.SendMsg(player_id=id, to_player_id=toID, binary_message=msg) | |||
| return Message2Server.SendMsg( | |||
| player_id=id, to_player_id=toID, binary_message=msg | |||
| ) | |||
| @ staticmethod | |||
| @staticmethod | |||
| def THUAI62ProtobufAttack(angle: float, id: int) -> Message2Server.AttackMsg: | |||
| return Message2Server.AttackMsg(player_id=id, angle=angle) | |||
| @ staticmethod | |||
| def THUAI62ProtobufSkill(skillID: int, id: int) -> Message2Server.SkillMsg: | |||
| return Message2Server.SkillMsg(player_id=id, skill_id=skillID) | |||
| @staticmethod | |||
| def THUAI62ProtobufSkill( | |||
| skillID: int, skillParam: int, id: int | |||
| ) -> Message2Server.SkillMsg: | |||
| return Message2Server.SkillMsg( | |||
| player_id=id, skill_id=skillID, skill_param=skillParam | |||
| ) | |||
| @@ -1,3 +1,3 @@ | |||
| grpcio==1.52.0 | |||
| grpcio-tools==1.52.0 | |||
| grpcio==1.54.2 | |||
| grpcio-tools==1.54.2 | |||
| numpy | |||
| @@ -1,7 +1,7 @@ | |||
| #!/usr/bin/env bash | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0 -d -o& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -o& | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -d -o& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4& | |||
| @@ -1,3 +1,3 @@ | |||
| #!/usr/bin/env bash | |||
| ./linux64/Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 | |||
| ./linux64/Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName video --mapResource ".\map\map1_final.txt" | |||
| @@ -1,3 +1,3 @@ | |||
| #!/usr/bin/env bash | |||
| ./linux64/Debug/Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 | |||
| ./linux64/Debug/Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName video --mapResource ".\map\map1_final.txt" | |||
| @@ -70,6 +70,8 @@ THUAI6 开发组成员与其他贡献者应当遵循以下流程: | |||
| 3. 在新的分支上进行修改与开发 | |||
| 4. 向 `eesast/THUAI6` 的 `dev` 分支提出 pull request | |||
| 5. 等待其他开发组成员 review 与 merge | |||
| 6. 待需要发布新版本时,从 `dev` 向 `main` 分支提出 pull request,等待 review 与 merge | |||
| 7. 若非必要,**严禁**直接修改 `main` 分支。若有极特殊情况需要直接修改 `main` 分支,则需要**立即**从 `main` 分支(反向)提出 pull request 到 `dev` 并 merge,以将更改和 Git 提交历史同步到 `dev` 分支,既保证 `dev` 为最新的内容,又防止 `dev` 与 `main` 分支在之后发生冲突 | |||
| ### 使用 Git 与 Github 时的注意事项 | |||
| @@ -152,7 +154,7 @@ THUAI6 开发组成员与其他贡献者应当遵循以下流程: | |||
| ## 文档风格 | |||
| 仓库的文档使用 Markdown 语法,具体语法可以参照 [Markdown 语法文档](https://docs.github.com/zh/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#ignoring-markdown-formatting)。 | |||
| 仓库的文档使用 Markdown 语法,具体语法可以参照 [Markdown 语法文档](https://docs.github.com/zh/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax)。 | |||
| 中文文档的书写须严格遵循:[中午技术文档规范](https://github.com/ruanyf/document-style-guide)、[中文文案排版指北](https://mazhuang.org/wiki/chinese-copywriting-guidelines/),以写出更美观的中文文档。例如:[中文文字与西文文字间空格](https://github.com/ruanyf/document-style-guide/blob/master/docs/text.md#%E5%AD%97%E9%97%B4%E8%B7%9D)、[全角标点符号的正确使用](https://github.com/ruanyf/document-style-guide/blob/master/docs/marks.md),等等。 | |||
| @@ -4,6 +4,7 @@ MAINTAINER eesast | |||
| WORKDIR /usr/local | |||
| COPY ./CAPI/cpp /usr/local/PlayerCode/CAPI/cpp | |||
| COPY ./CAPI/python /usr/local/PlayerCode/CAPI/python | |||
| COPY ./dependency/proto /usr/local/PlayerCode/dependency/proto | |||
| COPY ./dependency/shell /usr/local/PlayerCode/dependency/shell | |||
| WORKDIR /usr/local/PlayerCode/dependency/proto | |||
| @@ -12,7 +12,7 @@ message MessageOfStudent | |||
| int32 determination = 4; // 剩余的学习毅力,相当于血量 | |||
| int32 addiction = 5; // 沉迷程度,相当于淘汰进度 | |||
| repeated double time_until_skill_available = 6; | |||
| PlaceType place = 7; | |||
| // PlaceType place = 7; | |||
| repeated PropType prop = 8; | |||
| PlayerState player_state = 9; | |||
| int64 guid = 10; | |||
| @@ -37,7 +37,7 @@ message MessageOfTricker | |||
| int32 y = 2; | |||
| int32 speed = 3; | |||
| repeated double time_until_skill_available = 5; | |||
| PlaceType place = 6; | |||
| // PlaceType place = 6; | |||
| repeated PropType prop = 7; | |||
| TrickerType tricker_type = 8; | |||
| int64 guid = 9; | |||
| @@ -61,7 +61,7 @@ message MessageOfBullet | |||
| double facing_direction = 4; | |||
| int64 guid = 5; | |||
| PlayerType team = 6; | |||
| PlaceType place = 7; | |||
| // PlaceType place = 7; | |||
| double bomb_range = 8; | |||
| int32 speed = 9; | |||
| } | |||
| @@ -83,7 +83,7 @@ message MessageOfProp // 可拾取道具的信息 | |||
| int32 y = 3; | |||
| double facing_direction = 4; | |||
| int64 guid = 5; | |||
| PlaceType place = 6; | |||
| // PlaceType place = 6; | |||
| } | |||
| @@ -61,6 +61,7 @@ message SkillMsg | |||
| { | |||
| int64 player_id = 1; | |||
| int32 skill_id = 2; | |||
| int32 skill_param = 3; | |||
| } | |||
| // 基本继承于THUAI5,为了使发送的信息尽可能不被浪费,暂定不发这类大包。 | |||
| @@ -9,7 +9,7 @@ enum BulletType | |||
| COMMON_ATTACK_OF_TRICKER = 2; | |||
| BOMB_BOMB = 3; | |||
| JUMPY_DUMPTY = 4; | |||
| ATOM_BOMB = 5; | |||
| STRIKE = 5; | |||
| } | |||
| enum PlaceType // 地图中的所有物件类型 | |||
| @@ -49,6 +49,7 @@ enum PropType // 地图中的可拾取道具类型 | |||
| KEY5 = 6; | |||
| KEY6 = 7; | |||
| RECOVERY_FROM_DIZZINESS = 8; | |||
| CRAFTING_BENCH = 9; | |||
| } | |||
| enum StudentBuffType // 人类可用的增益效果类型 | |||
| @@ -78,7 +79,7 @@ enum PlayerState | |||
| LOCKING = 13; | |||
| RUMMAGING = 14; | |||
| CLIMBING = 15; // 翻窗 | |||
| OPENING_A_CHEST =16; | |||
| OPENING_A_CHEST = 16; | |||
| USING_SPECIAL_SKILL = 17; | |||
| OPENING_A_GATE =18; | |||
| } | |||
| @@ -109,7 +110,7 @@ enum StudentType | |||
| TEACHER = 2; | |||
| STRAIGHT_A_STUDENT = 3; | |||
| ROBOT = 4; | |||
| TECH_OTAKU =5; | |||
| TECH_OTAKU = 5; | |||
| SUNSHINE = 6; | |||
| } | |||
| @@ -14,8 +14,8 @@ | |||
| </ItemGroup>--> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Google.Protobuf.Tools" Version="3.22.3" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.4" /> | |||
| <PackageReference Include="Google.Protobuf.Tools" Version="3.22.4" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Tools" Version="2.54.0"> | |||
| @@ -0,0 +1,205 @@ | |||
| import os | |||
| import json | |||
| import re | |||
| winGamesMap = {} | |||
| totalGamesMap = {} | |||
| studentTotalGame = {} | |||
| trickerTotalGame = {} | |||
| winRateMap = {} | |||
| studentWinMap = {} | |||
| trickerWinMap = {} | |||
| studentWinRateMap = {} | |||
| trickerWinRateMap = {} | |||
| winGamesMap1 = {} | |||
| totalGamesMap1 = {} | |||
| studentTotalGame1 = {} | |||
| trickerTotalGame1 = {} | |||
| winRateMap1 = {} | |||
| studentWinMap1 = {} | |||
| trickerWinMap1 = {} | |||
| studentWinRateMap1 = {} | |||
| trickerWinRateMap1 = {} | |||
| studentWinNum = 0 | |||
| trickerWinNum = 0 | |||
| totalGameNum = 0 | |||
| studentWinNum1 = 0 | |||
| trickerWinNum1 = 0 | |||
| totalGameNum1 = 0 | |||
| teamIDtoName = { | |||
| "951c89eb-aa9c-45d4-af10-ad9785a047d6": "无名万物之始", "2e504ec6-50b1-4985-b2fd-995927ea9717": "LQ说什么都队", "fb472ad6-65e0-494b-a7be-f6f25ecda018": "是啊我诶", | |||
| "3376909b-5ddb-41ab-994c-3c5b5ba60158": "叛逃者联盟", | |||
| "9b0f2257-734c-42e2-8061-c267e3d0d231": "ChatGPA", | |||
| "2de908c9-1b99-4811-ae00-68140d1c4527": "昊天上帝和他的三个神父", | |||
| "94866510-af51-439c-a41a-b4cb42596e25": "少年毕不了业", | |||
| "4613ef48-4988-4508-a258-66857a3250b8": "PKT48TeamTS", | |||
| "8e14b2a3-fc37-4fb6-b8ac-a722a10707d7": "京ICP备2022019669号-1 2022 EESAST", | |||
| "e8db213c-a636-483f-a6ed-84310b3093a4": "拉拉队", | |||
| "04abd472-ed7a-4840-8680-87d20db52721": "努力少女戏尔危", | |||
| "2bc1b761-ace3-4403-af83-e46ca328bcd0": "测试", | |||
| "4c1d6333-e25c-4b0f-bc06-9851db446bd7": "摆烂吧,少女", | |||
| "7f819704-99c0-41d8-bc61-26eec0bd73bb": "一会吃萤火虫", | |||
| "de915bbf-0751-4a9d-ab30-a470807406b2": "小小做题家", | |||
| "28baa2bf-5130-4a1e-ab9c-36e8faf87f84": "数析少女队", | |||
| "5c868c42-3b07-4280-a825-a6f80e0d5a2c": "沙壁北京", | |||
| "ed03d1ac-810a-4547-b54a-d56cd5213364": "我会出手队", | |||
| "5d59e45f-cb0e-4294-90f4-282adb1d476d": "闪电骑士团", | |||
| "637e20c1-a904-4f6f-b706-2cea7c4daf5e": "Mukava Poikaa", | |||
| "7185eb49-0cb0-43c0-a469-a39e636d66d4": "劝退吧,少女", | |||
| "d2a7ba71-4a86-4278-a362-8a8953a368f8": "纵火犯在何方", | |||
| "4e266301-7749-4699-b2de-511d458cf537": "土木清华没有水", | |||
| "454f37bd-2f54-4463-94d9-2df9aedb4f21": "电电做不队", | |||
| "a2573713-28e4-4af7-8c53-ab21f385f789": "王牌飞行队", | |||
| "194c3ddf-6846-47ec-a262-ca24f5639531": "快乐Debug", | |||
| "97cf5969-e8ff-410e-b85c-0c8359923289": "卷动量守恒", | |||
| "4646739a-9ff5-4854-a3b2-27d5b85ea504": "龙井队", | |||
| "c431d105-a2b3-4659-b713-6bc97132ec7f": "疯狂Thurs队", | |||
| "9ee48de1-a76a-40eb-b267-59c985bbe6cb": "蒸馍", | |||
| "5ef8ffbb-0776-4a74-a84f-3d80d5b4c2ae": "你说什么都队", | |||
| "65f94306-69c7-42a2-8c68-44cb45749aae": "closeAI", | |||
| "ab0406ae-6a0e-4c1e-9d36-eb14115de076": "N/A", | |||
| "82cbff06-9ed1-429b-afc3-7e050318bf93": "代码一行都不队", | |||
| "6b52346c-4528-424b-ac75-22fa573ebaad": "pqfobj", | |||
| "93e7f3f1-d47f-4588-b433-72877089f0bd": "你说得队", | |||
| "2f6f9ce3-f2d3-4799-b291-38dc04d048a0": "少女终末旅行", | |||
| "07c0ad6c-f612-4375-9b79-52bb89c79d76": "大括号换行委员会", | |||
| "bdf5b1c5-4dbc-4589-a6bc-8c5932c04de7": "孤客若风", | |||
| "f0d75eee-34a6-4484-8e23-720d26db747d": "/", | |||
| "acea715f-d5b0-4113-b6c3-9f6d7822f2e9": "难崩" | |||
| } | |||
| dirs = os.listdir(".") | |||
| for dir in dirs: | |||
| if dir.startswith("Team"): | |||
| dirdir = dir.replace("Team_", "") | |||
| dirdir = dirdir.split("--vs--") | |||
| try: | |||
| with open(f"{dir}/result.json", 'r') as f: | |||
| result = json.load(f) | |||
| for i in (0, 1): | |||
| if dirdir[i] not in winGamesMap: | |||
| winGamesMap[dirdir[i]] = 0 | |||
| if dirdir[i] not in totalGamesMap: | |||
| totalGamesMap[dirdir[i]] = 0 | |||
| if dirdir[i] not in studentWinMap: | |||
| studentWinMap[dirdir[i]] = 0 | |||
| if dirdir[i] not in trickerWinMap: | |||
| trickerWinMap[dirdir[i]] = 0 | |||
| if dirdir[i] not in studentTotalGame: | |||
| studentTotalGame[dirdir[i]] = 0 | |||
| if dirdir[i] not in trickerTotalGame: | |||
| trickerTotalGame[dirdir[i]] = 0 | |||
| if dirdir[i] not in winGamesMap1: | |||
| winGamesMap1[dirdir[i]] = 0 | |||
| if dirdir[i] not in totalGamesMap1: | |||
| totalGamesMap1[dirdir[i]] = 0 | |||
| if dirdir[i] not in studentWinMap1: | |||
| studentWinMap1[dirdir[i]] = 0 | |||
| if dirdir[i] not in trickerWinMap1: | |||
| trickerWinMap1[dirdir[i]] = 0 | |||
| if dirdir[i] not in studentTotalGame1: | |||
| studentTotalGame1[dirdir[i]] = 0 | |||
| if dirdir[i] not in trickerTotalGame1: | |||
| trickerTotalGame1[dirdir[i]] = 0 | |||
| totalGamesMap[dirdir[0]] += 1 | |||
| totalGamesMap[dirdir[1]] += 1 | |||
| studentTotalGame[dirdir[0]] += 1 | |||
| trickerTotalGame[dirdir[1]] += 1 | |||
| totalGameNum += 1 | |||
| if result["Student"] < result["Tricker"]: | |||
| winGamesMap[dirdir[1]] += 1 | |||
| trickerWinMap[dirdir[1]] += 1 | |||
| trickerWinNum += 1 | |||
| elif result["Student"] > result["Tricker"]: | |||
| winGamesMap[dirdir[0]] += 1 | |||
| studentWinMap[dirdir[0]] += 1 | |||
| studentWinNum += 1 | |||
| else: | |||
| winGamesMap[dirdir[0]] += 0.5 | |||
| winGamesMap[dirdir[1]] += 0.5 | |||
| if result["Student"] != 0 and result["Tricker"] != 0: | |||
| totalGameNum1 += 1 | |||
| totalGamesMap1[dirdir[0]] += 1 | |||
| totalGamesMap1[dirdir[1]] += 1 | |||
| studentTotalGame1[dirdir[0]] += 1 | |||
| trickerTotalGame1[dirdir[1]] += 1 | |||
| if result["Student"] < result["Tricker"]: | |||
| winGamesMap1[dirdir[1]] += 1 | |||
| trickerWinMap1[dirdir[1]] += 1 | |||
| trickerWinNum1 += 1 | |||
| elif result["Student"] > result["Tricker"]: | |||
| winGamesMap1[dirdir[0]] += 1 | |||
| studentWinMap1[dirdir[0]] += 1 | |||
| studentWinNum1 += 1 | |||
| else: | |||
| winGamesMap1[dirdir[0]] += 0.5 | |||
| winGamesMap1[dirdir[1]] += 0.5 | |||
| except: | |||
| pass | |||
| for i in totalGamesMap: | |||
| winRateMap[i] = winGamesMap[i] / totalGamesMap[i] | |||
| if studentTotalGame[i] == 0: | |||
| studentWinRateMap[i] = 0 | |||
| else: | |||
| studentWinRateMap[i] = studentWinMap[i] / studentTotalGame[i] | |||
| if trickerTotalGame[i] == 0: | |||
| trickerWinRateMap[i] = 0 | |||
| else: | |||
| trickerWinRateMap[i] = trickerWinMap[i] / trickerTotalGame[i] | |||
| sortedMap = sorted(winRateMap.items(), key=lambda kv: ( | |||
| kv[1], kv[0]), reverse=True) | |||
| print("************************ALL GAMES(with 0 player)************************") | |||
| for i in sortedMap: | |||
| width = 33 - len(re.findall('([\u4e00-\u9fa5])', teamIDtoName[i[0]])) | |||
| print( | |||
| f"Team {teamIDtoName[i[0]]:{width}}({i[0]}) wins {winGamesMap[i[0]]:4}/{totalGamesMap[i[0]]:<2} games({i[1]:.3f}), student wins {studentWinMap[i[0]]:2}/{studentTotalGame[i[0]]:<2}({studentWinRateMap[i[0]]:.3f}), tricker wins {trickerWinMap[i[0]]:2}/{trickerTotalGame[i[0]]:<2}({trickerWinRateMap[i[0]]:.3f})") | |||
| print( | |||
| f"Total games: {totalGameNum}, student wins {studentWinNum}, tricker wins {trickerWinNum}") | |||
| for i in totalGamesMap1: | |||
| if totalGamesMap1[i] == 0: | |||
| winRateMap1[i] = 0 | |||
| else: | |||
| winRateMap1[i] = winGamesMap1[i] / totalGamesMap1[i] | |||
| if studentTotalGame1[i] == 0: | |||
| studentWinRateMap1[i] = 0 | |||
| else: | |||
| studentWinRateMap1[i] = studentWinMap1[i] / studentTotalGame1[i] | |||
| if trickerTotalGame1[i] == 0: | |||
| trickerWinRateMap1[i] = 0 | |||
| else: | |||
| trickerWinRateMap1[i] = trickerWinMap1[i] / trickerTotalGame1[i] | |||
| sortedMap1 = sorted(winRateMap1.items(), key=lambda kv: ( | |||
| kv[1], kv[0]), reverse=True) | |||
| print("************************NON-0 GAMES(no 0 player)************************") | |||
| for i in sortedMap1: | |||
| width = 33 - len(re.findall('([\u4e00-\u9fa5])', teamIDtoName[i[0]])) | |||
| print(f"Team {teamIDtoName[i[0]]:{width}}({i[0]}) wins {winGamesMap1[i[0]]:4}/{totalGamesMap1[i[0]]:<2} games({i[1]:.3f}), student wins {studentWinMap1[i[0]]:2}/{studentTotalGame1[i[0]]:<2}({studentWinRateMap1[i[0]]:.3f}), tricker wins {trickerWinMap1[i[0]]:2}/{trickerTotalGame1[i[0]]:<2}({trickerWinRateMap1[i[0]]:.3f})") | |||
| print( | |||
| f"Total games: {totalGameNum1}, student wins {studentWinNum1}, tricker wins {trickerWinNum1}") | |||
| @@ -13,8 +13,17 @@ do | |||
| flag=0 | |||
| fi | |||
| mv ./compile_log$i.txt $bind/compile_log$i.txt | |||
| elif [ ! -f "${bind}/player${i}.py" ]; then | |||
| flag=0 | |||
| elif [ -f "${bind}/player${i}.py" ]; then | |||
| pushd ../python | |||
| cp -f $bind/player$i.py ./PyAPI/AI.py | |||
| python3 -m compileall ./PyAPI >compile_log$i.txt 2>&1 | |||
| if [ ! -f ./PyAPI/__pycache__/AI.cpython-39.pyc ]; then | |||
| flag=0 | |||
| else | |||
| rm -rf ./PyAPI/__pycache__/AI.cpython-39.pyc | |||
| fi | |||
| mv ./compile_log$i.txt $bind/compile_log$i.txt | |||
| popd | |||
| fi | |||
| let "i++" | |||
| done | |||
| @@ -51,8 +51,12 @@ if [ -f $playback_dir/start.lock ]; then | |||
| while [ $? -eq 0 ] | |||
| do | |||
| sleep 1 | |||
| ps -p $server_pid | |||
| ps -p $server_pid > /dev/null 2>&1 | |||
| done | |||
| # result=$(cat /usr/local/playback/result.json) | |||
| # score0=$(echo "$result" | grep -oP '(?<="Student":)\d+') | |||
| # score1=$(echo "$result" | grep -oP '(?<="Tricker":)\d+') | |||
| # curl $URL -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"result":[{"team_id":0, "score":'${score0}'}, {"team_id":1, "score":'${score1}'}], "mode":'${MODE}'}'> $playback_dir/send.log 2>&1 | |||
| touch $playback_dir/finish.lock | |||
| echo "Finish" | |||
| else | |||
| @@ -62,8 +66,3 @@ else | |||
| mv -f temp.lock $playback_dir/video.thuaipb | |||
| kill -9 $server_pid | |||
| fi | |||
| result=$(cat /usr/local/playback/result.json) | |||
| score0=$(echo "$result" | grep -oP '(?<="Student":)\d+') | |||
| score1=$(echo "$result" | grep -oP '(?<="Tricker":)\d+') | |||
| curl $URL -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"result":[{"team_id":0, "score":'${score0}'}, {"team_id":1, "score":'${score1}'}], "mode":'${MODE}'}' | |||
| @@ -4,82 +4,86 @@ | |||
| ## 接口解释 | |||
| ### 主动指令 | |||
| - 每帧最多发送50条主动指令 | |||
| - EndAllAction 及 Move 指令调用数总和一帧内不超过 10 次 | |||
| #### 移动 | |||
| - `std::future<bool> Move(int64_t timeInMilliseconds, double angleInRadian)`:移动,`timeInMilliseconds` 为移动时间,单位毫秒;`angleInRadian` 表示移动方向,单位弧度,使用极坐标,**竖直向下方向为x轴,水平向右方向为y轴**。因为移动过程中你会受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。 | |||
| - `std::future<bool> MoveRight(uint32_t timeInMilliseconds)`即向右移动,`MoveLeft`、`MoveDown`、`MoveUp`同理 | |||
| - `std::future<bool> Move(int64_t timeInMilliseconds, double angleInRadian)`:移动,`timeInMilliseconds` 为移动时间,单位毫秒;`angleInRadian` 表示移动方向,单位弧度,使用极坐标,**竖直向下方向为 x 轴,水平向右方向为 y 轴**。因为移动过程中你会受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。 | |||
| - `std::future<bool> MoveRight(uint32_t timeInMilliseconds)`即向右移动,`MoveLeft`、`MoveDown`、`MoveUp` 同理 | |||
| #### 使用技能 | |||
| - `std::future<bool> UseSkill(int32_t skillID)`:使用对应序号的主动技能 | |||
| - `std::future<bool> UseSkill(int32_t skillID)`:使用对应序号的主动技能 | |||
| #### 人物 | |||
| - `std::future<bool> EndAllAction()`:可以使不处在不可行动状态中的玩家终止当前行动 | |||
| - 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 Stop 指令终止进行的指令 | |||
| - `std::future<bool> EndAllAction()`:可以使不处在不可行动状态中的玩家终止当前行动 | |||
| - 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 EndAllAction 指令终止进行的指令 | |||
| - 实际上唤醒或勉励不同的人是有效的 | |||
| - EndAllAction() 及 Move 指令调用数总和一帧内不超过 10 次 | |||
| #### 攻击 | |||
| - `std::future<bool> Attack(double angleInRadian)`:`angleInRadian`为攻击方向 | |||
| - `std::future<bool> Attack(double angleInRadian)`:`angleInRadian` 为攻击方向 | |||
| #### 学习与毕业 | |||
| - `std::future<bool> StartLearning()`:在教室里开始做作业 | |||
| - `std::future<bool> StartOpenGate()`:开始开启校门 | |||
| - `std::future<bool> Graduate()`:从开启的校门或隐藏校门毕业。 | |||
| - `std::future<bool> StartLearning()`:在教室里开始做作业 | |||
| - `std::future<bool> StartOpenGate()`:开始开启校门 | |||
| - `std::future<bool> Graduate()`:从开启的校门或隐藏校门毕业。 | |||
| #### 勉励与唤醒 | |||
| - `std::future<bool> StartEncourageMate(int64_t mateID)`:勉励对应玩家ID的学生。 | |||
| - `std::future<bool> StartRouseMate(int64_t mateID)`:唤醒对应玩家ID的沉迷的学生。 | |||
| - `std::future<bool> StartEncourageMate(int64_t mateID)`:勉励对应玩家 ID 的学生。 | |||
| - `std::future<bool> StartRouseMate(int64_t mateID)`:唤醒对应玩家 ID 的沉迷的学生。 | |||
| #### 地图互动 | |||
| - `std::future<bool> OpenDoor()`:开门 | |||
| - `std::future<bool> CloseDoor()`:关门 | |||
| - `std::future<bool> SkipWindow()`:翻窗 | |||
| - `std::future<bool> StartOpenChest()`:开箱 | |||
| - `std::future<bool> OpenDoor()`:开门 | |||
| - `std::future<bool> CloseDoor()`:关门 | |||
| - `std::future<bool> SkipWindow()`:翻窗 | |||
| - `std::future<bool> StartOpenChest()`:开箱 | |||
| #### 道具 | |||
| - `bool PickProp(THUAI6::PropType prop)`捡起与自己处于同一个格子(cell)的道具。 | |||
| - `bool UseProp(THUAI6::PropType prop)`使用对应类型的道具 | |||
| - `bool ThrowProp(THUAI6::PropType prop)`将对应类型的道具扔在原地 | |||
| - `bool PickProp(THUAI6::PropType prop)`:捡起与自己处于同一个格子(cell)的道具。 | |||
| - `bool UseProp(THUAI6::PropType prop)`:使用对应类型的道具 | |||
| - `bool ThrowProp(THUAI6::PropType prop)`:将对应类型的道具扔在原地 | |||
| ### 信息获取 | |||
| #### 队内信息 | |||
| - `std::future<bool> SendMessage(int64_t, std::string)`:给同队的队友发送消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 | |||
| - `bool HaveMessage()`:是否有队友发来的尚未接收的信息。 | |||
| - `std::pair<int64_t, std::string> GetMessage()`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。 | |||
| - `std::future<bool> SendTextMessage(int64_t, std::string)`:给同队的队友发送文本消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,形式为 Unicode 字符串,不得超过 256 字节。 | |||
| - `std::future<bool> SendBinaryMessage(int64_t, std::string)`:给同队的队友发送二进制消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,形式为二进制字符串,不得超过 256 字节。 | |||
| - `bool HaveMessage()`:是否有队友发来的尚未接收的信息。 | |||
| - `std::pair<int64_t, std::string> GetMessage()`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的 PlayerID。 | |||
| > 需要注意,收到的二进制消息和文本消息都会被这个函数一并返回,选手需要自行约定消息类型。 | |||
| #### 查询可视范围内的信息 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Student>> GetStudents() const` :对于学生,返回所有学生的信息;对于捣蛋鬼,返回可视学生的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const` :返回所有可视捣蛋鬼的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const` :返回所有可视道具的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const` :返回所有可视子弹(攻击)的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Student>> GetStudents() const`:对于学生,返回所有学生的信息;对于捣蛋鬼,返回可视学生的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const`:返回所有可视捣蛋鬼的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const`:返回所有可视道具的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const`:返回所有可视子弹(攻击)的信息。 | |||
| - `bool HaveView(int gridX, int gridY) const`:判断坐标是否可见 | |||
| #### 查询特定位置物体的信息 | |||
| 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | |||
| - `THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY)` :返回某一位置场地种类信息。场地种类详见 structure.h 。 | |||
| - `THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY)`:返回某一位置场地种类信息。场地种类详见 structure.h。 | |||
| - 以下指令,若查询物品当前在视野内,则返回最新进度/状态;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度/状态;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。 | |||
| - `int32_t GetChestProgress(int32_t cellX, int32_t cellY) const`:查询特定位置箱子开启进度 | |||
| - `int32_t GetGateProgress(int32_t cellX, int32_t cellY) const`:查询特定位置校门开启进度 | |||
| - `int32_t GetClassroomProgress(int32_t cellX, int32_t cellY) const`:查询特定位置教室作业完成进度 | |||
| - `int32_t GetDoorProgress(int32_t cellX, int32_t cellY) const`:查询特定位置门开启状态 | |||
| - `bool IsDoorOpen(int32_t cellX, int32_t cellY) const`:查询特定位置门是否开启,没有门/不在视野内也返回false | |||
| - `THUAI6::HiddenGateState GetHiddenGateState(int32_t cellX, int32_t cellY) const`::查询特定位置隐藏校门状态,没有隐藏校门/不在视野内返回THUAI6::HiddenGateState::Null | |||
| - `int32_t GetChestProgress(int32_t cellX, int32_t cellY) const`:查询特定位置箱子开启进度 | |||
| - `int32_t GetGateProgress(int32_t cellX, int32_t cellY) const`:查询特定位置校门开启进度 | |||
| - `int32_t GetClassroomProgress(int32_t cellX, int32_t cellY) const`:查询特定位置教室作业完成进度 | |||
| - `int32_t GetDoorProgress(int32_t cellX, int32_t cellY) const`:查询特定位置门开启状态 | |||
| - `bool IsDoorOpen(int32_t cellX, int32_t cellY) const`:查询特定位置门是否开启,没有门/不在视野内也返回 false | |||
| - `THUAI6::HiddenGateState GetHiddenGateState(int32_t cellX, int32_t cellY) const`:查询特定位置隐藏校门状态,没有隐藏校门/不在视野内返回 THUAI6::HiddenGateState::Null | |||
| #### 其他 | |||
| - `std::shared_ptr<const THUAI6::GameInfo> GetGameInfo() const`:查询当前游戏状态 | |||
| - `std::vector<int64_t> GetPlayerGUIDs() const`:获取所有玩家的GUID | |||
| - `int GetFrameCount() const`:获取目前所进行的帧数 | |||
| - `std::shared_ptr<const THUAI6::Tricker> GetSelfInfo() const`或`std::shared_ptr<const THUAI6::Student> GetSelfInfo() const`:获取自己的信息 | |||
| - `std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const`:返回整张地图的地形信息。可以写成类似`api.GetFullMap()[x][y]`,其中x为地图自上到下第几行,y为自左向右第几列,注意从0开始 | |||
| - `std::shared_ptr<const THUAI6::GameInfo> GetGameInfo() const`:查询当前游戏状态 | |||
| - `std::vector<int64_t> GetPlayerGUIDs() const`:获取所有玩家的 GUID | |||
| - `int GetFrameCount() const`:获取目前所进行的帧数 | |||
| - `std::shared_ptr<const THUAI6::Tricker> GetSelfInfo() const` 或 `std::shared_ptr<const THUAI6::Student> GetSelfInfo() const`:获取自己的信息 | |||
| - `std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const`:返回整张地图的地形信息。可以写成类似 `api.GetFullMap()[x][y]`,其中 x 为地图自上到下第几行,y 为自左向右第几列,注意从 0 开始 | |||
| ### 辅助函数 | |||
| `static inline int CellToGrid(int cell) noexcept`:将地图格数 cell 转换为绝对坐标 grid。 | |||
| `static inline int CellToGrid(int cell) noexcept`:将地图格数 cell 转换为绝对坐标 grid。 | |||
| `static inline int GridToCell(int grid) noexcept`:将绝对坐标 grid 转换为地图格数 cell。 | |||
| `static inline int GridToCell(int grid) noexcept`:将绝对坐标 grid 转换为地图格数 cell。 | |||
| 下面为用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用 | |||
| 下面为用于 DEBUG 的输出函数,选手仅在开启 Debug 模式的情况下可以使用 | |||
| ~~~c++ | |||
| void Print(std::string str) const; | |||
| void PrintStudent() const; | |||
| @@ -93,7 +97,7 @@ | |||
| struct Player | |||
| { | |||
| std::vector<PropType> props;//大小固定为3,空的位置为NullPropType | |||
| } | |||
| }; | |||
| ~~~ | |||
| ## 接口一览 | |||
| @@ -121,7 +125,8 @@ | |||
| virtual std::future<bool> EndAllAction() = 0; | |||
| // 发送信息、接受信息,注意收消息时无消息则返回nullopt | |||
| virtual std::future<bool> SendMessage(int64_t, std::string) = 0; | |||
| virtual std::future<bool> SendTextMessage(int64_t, std::string) = 0; | |||
| virtual std::future<bool> SendBinaryMessage(int64_t, std::string) = 0; | |||
| [[nodiscard]] virtual bool HaveMessage() = 0; | |||
| [[nodiscard]] virtual std::pair<int64_t, std::string> GetMessage() = 0; | |||
| @@ -5,96 +5,99 @@ | |||
| ## 接口解释 | |||
| ### 主动指令 | |||
| - 每帧最多发送 50 条主动指令 | |||
| - EndAllAction 及 Move 指令调用数总和一帧内不超过 10 次 | |||
| #### 移动 | |||
| - `def Move(self, timeInMilliseconds: int, angle: float) -> Future[bool]`:移动,`timeInMilliseconds` 为移动时间,单位毫秒;`angleInRadian` 表示移动方向,单位弧度,使用极坐标,**竖直向下方向为 x 轴,水平向右方向为 y 轴**因为移动过程中你会受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。 | |||
| - 5ms以内的移动指令会被禁止,你不应当使用过小的移动指令 | |||
| - `def MoveRight(self, timeInMilliseconds: int) -> Future[bool]`即向右移动,`MoveLeft`、`MoveDown`、`MoveUp`同理 | |||
| - `def Move(self, timeInMilliseconds: int, angle: float) -> Future[bool]`:移动,`timeInMilliseconds` 为移动时间,单位毫秒;`angleInRadian` 表示移动方向,单位弧度,使用极坐标,**竖直向下方向为 x 轴,水平向右方向为 y 轴**因为移动过程中你会受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。 | |||
| - 5ms 以内的移动指令会被禁止,你不应当使用过小的移动指令 | |||
| - `def MoveRight(self, timeInMilliseconds: int) -> Future[bool]` 即向右移动, `MoveLeft`、`MoveDown`、`MoveUp` 同理 | |||
| #### 使用技能 | |||
| - `def UseSkill(self, skillID: int) -> Future[bool]`:使用对应序号的主动技能 | |||
| - `def UseSkill(self, skillID: int) -> Future[bool]`:使用对应序号的主动技能 | |||
| #### 人物 | |||
| - `def EndAllAction(self) -> Future[bool]`:可以使不处在不可行动状态中的玩家终止当前行动 | |||
| - 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 Stop 指令终止进行的指令 | |||
| - `def EndAllAction(self) -> Future[bool]`:可以使不处在不可行动状态中的玩家终止当前行动 | |||
| - 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 EndAllAction 指令终止进行的指令 | |||
| - 实际上唤醒或勉励不同的人是有效的 | |||
| - EndAllAction() 及 Move 指令调用数总和一帧内不超过 10 次 | |||
| - EndAllAction 及 Move 指令调用数总和一帧内不超过 10 次 | |||
| #### 攻击 | |||
| - `def Attack(self, angle: float) -> Future[bool]`:`angleInRadian`为攻击方向 | |||
| - `def Attack(self, angle: float) -> Future[bool]`:`angleInRadian` 为攻击方向 | |||
| #### 学习与毕业 | |||
| - `def StartLearning(self) -> Future[bool]`:在教室里开始做作业 | |||
| - `def StartOpenGate(self) -> Future[bool]`:开始开启校门 | |||
| - `def Graduate(self) -> Future[bool]`:从开启的校门或隐藏校门毕业。 | |||
| - `def StartLearning(self) -> Future[bool]`:在教室里开始做作业 | |||
| - `def StartOpenGate(self) -> Future[bool]`:开始开启校门 | |||
| - `def Graduate(self) -> Future[bool]`:从开启的校门或隐藏校门毕业。 | |||
| #### 勉励与唤醒 | |||
| - `def StartEncourageMate(self, mateID: int) -> Future[bool]`:勉励对应玩家 ID 的学生。 | |||
| - `def StartEncourageMate(self, mateID: int) -> Future[bool]`:勉励对应玩家 ID 的学生。 | |||
| - `def StartRouseMate(self, mateID: int) -> Future[bool]`:唤醒对应玩家 ID 的沉迷的学生。 | |||
| #### 地图互动 | |||
| - `def OpenDoor(self) -> Future[bool]`:开门 | |||
| - `def CloseDoor(self) -> Future[bool]`:关门 | |||
| - `def SkipWindow(self) -> Future[bool]`:翻窗 | |||
| - `def StartOpenChest(self) -> Future[bool]`:开箱 | |||
| - `def OpenDoor(self) -> Future[bool]`:开门 | |||
| - `def CloseDoor(self) -> Future[bool]`:关门 | |||
| - `def SkipWindow(self) -> Future[bool]`:翻窗 | |||
| - `def StartOpenChest(self) -> Future[bool]`:开箱 | |||
| #### 道具 | |||
| - `def PickProp(self, propType: THUAI6.PropType) -> Future[bool]`捡起与自己处于同一个格子(cell)的道具。 | |||
| - `def UseProp(self, propType: THUAI6.PropType) -> Future[bool]`使用对应类型的道具 | |||
| - `def ThrowProp(self, propType: THUAI6.PropType) -> Future[bool]`将对应类型的道具扔在原地 | |||
| - `def PickProp(self, propType: THUAI6.PropType) -> Future[bool]`:捡起与自己处于同一个格子(cell)的道具。 | |||
| - `def UseProp(self, propType: THUAI6.PropType) -> Future[bool]`:使用对应类型的道具 | |||
| - `def ThrowProp(self, propType: THUAI6.PropType) -> Future[bool]`:将对应类型的道具扔在原地 | |||
| ### 信息获取 | |||
| #### 队内信息 | |||
| - `def GetMessage(self) -> Tuple[int, str]`:给同队的队友发送消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 | |||
| - `def HaveMessage(self) -> bool`:是否有队友发来的尚未接收的信息。 | |||
| - `def GetMessage(self) -> Tuple[int, str]`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。 | |||
| - `def SendMessage(self, toID: int, message: Union[str, bytes]) -> Future[bool]`:给同队的队友发送消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,可以是Unicode字符串,也可以是二进制字符串;不得超过 256 字节。 | |||
| - `def HaveMessage(self) -> bool`:是否有队友发来的尚未接收的信息。 | |||
| - `def GetMessage(self) -> Tuple[int, Union[str, bytes]]`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的 PlayerID。 | |||
| > 需要注意,文本消息和二进制消息都会在这里接受,选手可以简单地使用 `isinstance()` 来判断消息类型。 | |||
| #### 查询可视范围内的信息 | |||
| - `def GetStudents(self) -> List[THUAI6.Student]` :对于学生,返回所有学生的信息;对于捣蛋鬼,返回可视学生的信息。 | |||
| - `def GetTrickers(self) -> List[THUAI6.Tricker]` :返回所有可视捣蛋鬼的信息。 | |||
| - `def GetProps(self) -> List[THUAI6.Prop]` :返回所有可视道具的信息。 | |||
| - `def GetBullets(self) -> List[THUAI6.Bullet]` :返回所有可视子弹(攻击)的信息。 | |||
| - `def GetStudents(self) -> List[THUAI6.Student]`:对于学生,返回所有学生的信息;对于捣蛋鬼,返回可视学生的信息。 | |||
| - `def GetTrickers(self) -> List[THUAI6.Tricker]`:返回所有可视捣蛋鬼的信息。 | |||
| - `def GetProps(self) -> List[THUAI6.Prop]`:返回所有可视道具的信息。 | |||
| - `def GetBullets(self) -> List[THUAI6.Bullet]`:返回所有可视子弹(攻击)的信息。 | |||
| - `def HaveView(self, gridX: int, gridY: int) -> bool`:判断坐标是否可见 | |||
| #### 查询特定位置物体的信息 | |||
| 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | |||
| - `def GetPlaceType(self, cellX: int, cellY: int) -> THUAI6.PlaceType` :返回某一位置场地种类信息。场地种类详见 structure.h 。 | |||
| - `def GetPlaceType(self, cellX: int, cellY: int) -> THUAI6.PlaceType`:返回某一位置场地种类信息。场地种类详见 structure.h。 | |||
| - 以下指令,若查询物品当前在视野内,则返回最新进度/状态;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度/状态;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。 | |||
| - `def GetChestProgress(self, cellX: int, cellY: int) -> int`:查询特定位置箱子开启进度 | |||
| - `def GetGateProgress(self, cellX: int, cellY: int) -> int`:查询特定位置校门开启进度 | |||
| - `def GetClassroomProgress(self, cellX: int, cellY: int) -> int`:查询特定位置教室作业完成进度 | |||
| - `def GetDoorProgress(self, cellX: int, cellY: int) -> int`:查询特定位置门开启状态 | |||
| - `def IsDoorOpen(self, cellX: int, cellY: int) -> bool`:查询特定位置门是否开启,没有门/不在视野内也返回false | |||
| - `def GetHiddenGateState(self, cellX: int, cellY: int) -> THUAI6.HiddenGateState`::查询特定位置隐藏校门状态,没有隐藏校门/不在视野内返回THUAI6::HiddenGateState::Null | |||
| - `def GetChestProgress(self, cellX: int, cellY: int) -> int`:查询特定位置箱子开启进度 | |||
| - `def GetGateProgress(self, cellX: int, cellY: int) -> int`:查询特定位置校门开启进度 | |||
| - `def GetClassroomProgress(self, cellX: int, cellY: int) -> int`:查询特定位置教室作业完成进度 | |||
| - `def GetDoorProgress(self, cellX: int, cellY: int) -> int`:查询特定位置门开启状态 | |||
| - `def IsDoorOpen(self, cellX: int, cellY: int) -> bool`:查询特定位置门是否开启,没有门/不在视野内也返回 false | |||
| - `def GetHiddenGateState(self, cellX: int, cellY: int) -> THUAI6.HiddenGateState`:查询特定位置隐藏校门状态,没有隐藏校门/不在视野内返回 THUAI6::HiddenGateState::Null | |||
| #### 其他 | |||
| - `def GetGameInfo(self) -> THUAI6.GameInfo`:查询当前游戏状态 | |||
| - `def GetPlayerGUIDs(self) -> List[int]`:获取所有玩家的GUID | |||
| - `def GetFrameCount(self) -> int`:获取目前所进行的帧数 | |||
| - `def GetGameInfo(self) -> THUAI6.GameInfo`:查询当前游戏状态 | |||
| - `def GetPlayerGUIDs(self) -> List[int]`:获取所有玩家的 GUID | |||
| - `def GetFrameCount(self) -> int`:获取目前所进行的帧数 | |||
| - `def GetSelfInfo(self) -> Union[THUAI6.Student, THUAI6.Tricker]`:获取自己的信息 | |||
| - `def GetFullMap(self) -> List[List[THUAI6.PlaceType]]`:返回整张地图的地形信息。可以写成类似`self.GetFullMap()[x][y]`,其中x为地图自上到下第几行,y为自左向右第几列,注意从0开始 | |||
| - `def GetFullMap(self) -> List[List[THUAI6.PlaceType]]`:返回整张地图的地形信息。可以写成类似 `self.GetFullMap()[x][y]`,其中 x 为地图自上到下第几行,y 为自左向右第几列,注意从 0 开始 | |||
| ### 辅助函数 | |||
| `def CellToGrid(cell: int) -> int`:将地图格数 cell 转换为绝对坐标 grid。 | |||
| `def CellToGrid(cell: int) -> int`:将地图格数 cell 转换为绝对坐标 grid。 | |||
| `def GridToCell(grid: int) -> int`:将绝对坐标 grid 转换为地图格数 cell。 | |||
| `def GridToCell(grid: int) -> int`:将绝对坐标 grid 转换为地图格数 cell。 | |||
| 下面为用于DEBUG的输出函数,选手仅在开启 Debug 模式的情况下可以使用 | |||
| 下面为用于 DEBUG 的输出函数,选手仅在开启 Debug 模式的情况下可以使用 | |||
| ~~~python | |||
| def Print(self, cont: str) -> None: | |||
| @@ -190,7 +193,7 @@ class IAPI(metaclass=ABCMeta): | |||
| # 消息相关,接收消息时无消息则返回(-1, '') | |||
| @abstractmethod | |||
| def SendMessage(self, toID: int, message: str) -> Future[bool]: | |||
| def SendMessage(self, toID: int, message: Union[str, bytes]) -> Future[bool]: | |||
| pass | |||
| @abstractmethod | |||
| @@ -198,7 +201,7 @@ class IAPI(metaclass=ABCMeta): | |||
| pass | |||
| @abstractmethod | |||
| def GetMessage(self) -> Tuple[int, str]: | |||
| def GetMessage(self) -> Tuple[int, Union[str, bytes]]: | |||
| pass | |||
| # 等待下一帧 | |||
| @@ -1,5 +1,5 @@ | |||
| # 规则 | |||
| V5.5 | |||
| V5.6.2 | |||
| - [规则](#规则) | |||
| - [简则](#简则) | |||
| - [地图](#地图) | |||
| @@ -89,13 +89,14 @@ $$ | |||
| 8. 翻窗 Climbing | |||
| ### 攻击 | |||
| - 攻击距离是指攻击(子弹)的移动距离,也就是说理论上最远被攻击的学生的中心与捣蛋鬼的中心=学生的半径+捣蛋鬼的半径+攻击距离+子弹半径(200)*2 | |||
| - 攻击类型CommonAttackOfTricker攻击未写完的作业,会造成对应攻击力的损坏 | |||
| - 捣蛋鬼攻击交互状态或前后摇的学生,将使学生眩晕4.3s | |||
| | 攻击(子弹)类型 |搞蛋鬼的一般攻击CommonAttackOfTricker| 飞刀FlyingKnife | 蹦蹦炸弹BombBomb | 小炸弹JumpyDumpty | | |||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | |||
| | 子弹爆炸范围 | 0 | 0 | 2000 | 1000 | | |||
| | 子弹攻击距离 | 2200 | 78000 | 2200 | 4400 | | |||
| | 爆炸范围 | 0 | 0 | 2000 | 1000 | | |||
| | 攻击距离 | 2200 | 78000 | 2200 | 4400 | | |||
| | 攻击力 | 1500000 | 1200000 | 1800000 | 900000 | | |||
| | 移动速度/s | 7400 | 18500 | 6000 | 8600 | | |||
| | 前摇(ms) | 297 | 400 | 366 | - | | |||
| @@ -192,7 +193,7 @@ $$ | |||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | |||
| | 移动速度/s | 3960 | 3600 | 3852 | 3600 | | |||
| | 隐蔽度 | 1.5 | 1 | 0.8 | 0.75| | |||
| | 警戒范围 | 22,100 | 17000 | 15300 | 17000 | |||
| | 警戒范围 | 22100 | 17000 | 15300 | 17000| | |||
| | 视野范围 | 15600 | 13000 | 13000 | 14300| | |||
| | 开锁门速度/ms | 4000 | 4000 | 4000 |4000 | | |||
| | 翻窗速度/s | 2540 | 2540 | 2794 | 2540| | |||
| @@ -217,7 +218,7 @@ $$ | |||
| - CD:15s 持续时间:3s | |||
| - 在持续时间内,攻击类型变为蹦蹦炸弹 | |||
| - 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上90°,270° 发出2个小炸弹 | |||
| - 2个小炸弹运动停止前会因为碰撞爆炸,停止运动后学生碰撞会造成眩晕3.07s | |||
| - 小炸弹运动停止前会因为碰撞爆炸,停止运动后学生碰撞会造成眩晕3.07s | |||
| - 不直接得分,通过眩晕等获得对应得分 | |||
| #### 喧哗者ANoisyPerson | |||
| @@ -246,14 +247,14 @@ $$ | |||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | |||
| | 移动速度/s | 2700 | 3150 | 2880 | 3000 | | |||
| | 最大毅力值 | 30000000 | 3000000 | 3300000 | 3200000 | | |||
| | 最大沉迷度 | 600000 | 54,000 | 78,000 | 66,000 | | |||
| | 学习一科速度 | 0 | 73 | 135 | 123 | | |||
| | 最大沉迷度 | 600000 | 54000 | 78000 | 66000 | | |||
| | 学习速度/ms | 0 | 73 | 135 | 123 | | |||
| | 勉励速度/ms | 80 | 90 | 100 | 120 | | |||
| | 隐蔽度 | 0.5 | 0.9 | 0.9 | 0.8 | | |||
| | 警戒范围 | 7500 | 15000 | 13,500 | 15000 | | |||
| | 视野范围 | 9,000 | 11000 | 9,000 | 10000 | | |||
| | 警戒范围 | 7500 | 15000 | 13500 | 15000 | | |||
| | 视野范围 | 9000 | 11000 | 9000 | 10000 | | |||
| | 开锁门速度/ms | 4000 | 4000 | 4000 | 2800 | | |||
| | 翻窗速度/ms | 1270 | 3048 | 2116 | 2540 | | |||
| | 翻窗速度/ms | 611 | 1466 | 1018 | 1222 | | |||
| | 翻箱速度/ms | 1000 | 1000 | 1000 | 900 | | |||
| #### 运动员 | |||
| @@ -308,13 +309,14 @@ $$ | |||
| - 不鼓励选手面向地图编程,因为移动过程中你可以受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。 | |||
| ### 人物 | |||
| - 眩晕状态中的玩家不能再次被眩晕 | |||
| - 眩晕状态中的玩家不能再次被眩晕(除非是ShowTime) | |||
| ### 初始状态 | |||
| - 初赛玩家出生点固定且一定为空地 | |||
| ### 道具 | |||
| - 使用钥匙相当于销毁 | |||
| - 可接受指令状态下能捡起或扔道具,在场上即可使用道具 | |||
| ### 交互 | |||
| - 被唤醒或被勉励不属于交互状态,翻窗属于交互状态 | |||
| @@ -341,6 +343,7 @@ $$ | |||
| - 开锁门进度中断后清空 | |||
| ### 窗 | |||
| - 由于窗户被占用导致翻窗失败会使先前行动停止 | |||
| - 翻越窗户是一种交互行为,翻窗一共有两个过程 | |||
| - 跳上窗:从当前位置到窗边缘中点,位置瞬移,时间=距离/爬窗速度。中断时,停留在原位置 | |||
| - 爬窗:从窗一侧边缘中点到另一侧格子中心,位置渐移,时间=距离/爬窗速度。中断时,停留在另一侧格子中心 | |||
| @@ -49,7 +49,7 @@ | |||
| Server脚本中参数格式一般如下: | |||
| ```shell | |||
| --ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName video | |||
| --ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName video --mapResource ".\map\map1_final.txt" | |||
| ``` | |||
| `--ip`是服务器ipv4地址 | |||
| @@ -75,6 +75,9 @@ Server脚本中参数格式一般如下: | |||
| `--fileName`为回放文件的保存名称 | |||
| `--mapResource`为地图文件存放的相对(该脚本)路径 | |||
| - 不写则为默认地图 | |||
| ### 启动Client(RunPython或RunCpp) | |||
| `RunCpp.cmd`或`RunPython.cmd`的脚本参数格式如下: | |||
| @@ -144,8 +147,8 @@ start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 0 -d -o -w | |||
| | 1 | Athlete | | |||
| | 2 | Teacher | | |||
| | 3 | StraightAStudent | | |||
| | 4 | Robot(目前未实现所有功能) | | |||
| | 5 | TechOtaku(目前未实现所有功能) | | |||
| | 4 | Robot | | |||
| | 5 | TechOtaku | | |||
| | 6 | Sunshine | | |||
| * 捣蛋鬼 | |||
| @@ -0,0 +1,119 @@ | |||
| # 游戏机制与平衡性调整更新草案 | |||
| v1.6 | |||
| ## 说明 | |||
| - 该草案尚未完全确定,请大家不要过分依靠该文档进行修改自己的代码 | |||
| - 有任何问题都可以在选手群中提出建议和讨论 | |||
| ## 游戏接口 | |||
| - 删除structures.h中Player的属性place | |||
| ## 游戏规则 | |||
| ## 游戏机制 | |||
| ## 攻击 | |||
| - 飞刀FlyingKnife | |||
| - 前摇与CD变为600ms | |||
| - 搞蛋鬼的一般攻击CommonAttackOfTricker | |||
| - 改为“不能攻击未写完的作业” | |||
| - 蹦蹦炸弹BombBomb | |||
| - 未攻击至目标时的后摇改为1200ms | |||
| - 增强为“可以攻击未写完的作业” | |||
| - 增强为“可以攻击使门被打开(可以重新被锁上)” | |||
| - 小炸弹JumpyDumpty | |||
| - 攻击距离改为1600 | |||
| - 小炸弹不受道具增益影响 | |||
| - 修改为“小炸弹与自己无碰撞体积” | |||
| - strike(新增) | |||
| - 可以攻击未写完的作业 | |||
| 修改后: | |||
| | 攻击(子弹)类型 |搞蛋鬼的一般攻击CommonAttackOfTricker|飞刀FlyingKnife | 蹦蹦炸弹BombBomb | 小炸弹JumpyDumpty | strike | | |||
| | :--------------------- | :---------------------| :--------------------- | :--------------------- | :--------------------- | :--------------------- | | |||
| | 子弹爆炸范围 | 0 | 0 | 2000 | 1000 | 0 | | |||
| | 子弹攻击距离 | 2200 | 78000 | 2200 | 1600 | 2000 | | |||
| | 攻击力 | 1500000 | 1200000 | 1800000 | 900000 | 1600000 | | |||
| | 移动速度/s | 7400 | 18500 | 6000 | 8600 | 6250 | | |||
| | 前摇(ms) | 297 | 600 | 366 | - | 320 | | |||
| |未攻击至目标时的后摇(ms)| 800 | 0 | 1200 | - | 800 | | |||
| |攻击至目标时的后摇(ms) | 3700 | 0 | 3700 | - | 3700 | | |||
| | CD(ms) | 800 | 600 | 3000 | - | 800 | | |||
| | 最大子弹容量 | 1 | 1 | 1 | - | 1 | | |||
| ## 职业 | |||
| - 所有角色开锁门速度加速至1.25倍 | |||
| - 所有角色翻箱速度加速至1.25倍 | |||
| ### 学生 | |||
| - 先前学生翻窗数据有误 | |||
| - Teacher | |||
| - 学习速度由0改为50 | |||
| - 警戒范围由7500改为10000 | |||
| - 翻窗速度改为1000 | |||
| - 特质: | |||
| - 扣血则得分100×受到伤害/基本伤害(1500000) | |||
| - 技能惩罚(Punish)改为 | |||
| - CD:45s | |||
| - “使用瞬间,在**视野距离/3范围内(不是可视范围)的**翻窗、开锁门、攻击前后摇、**使用技能期间**的捣蛋鬼会被眩晕(3070+**500***已受伤害/基本伤害(1500000))ms” | |||
| - 技能喝茶(HaveTea)(新增) | |||
| - CD:90s | |||
| - 使用瞬间,向额外参数/1000.0的角度瞬移3000(可以穿墙),如果会碰撞则失败。 | |||
| - Robot(新增) | |||
| - 无技能 | |||
| - 特性 | |||
| - 不可被眩晕 | |||
| - 不可毕业 | |||
| - 不可救人 | |||
| - 无牵制得分 | |||
| - 不可使用道具(可以捡起和扔道具) | |||
| - TechOtaku(新增) | |||
| - 一名TechOtaku最多可以在场上同时最多拥有3个Robot,无法共享视野 | |||
| 1. SummonGolem | |||
| - CD:40s,持续时间:6s | |||
| - 在持续时间中,学生进入人物状态进入UsingSpecialSkill(不能移动),进入其他状态会导致制作机器人失败。 | |||
| - 在持续时间中,学生面前生成道具CraftingBench;学生进入其他状态或该道具被碰撞后,CraftingBench消失且制作机器人失败。 | |||
| - 持续时间结束后,道具CraftingBench所在位置生成一个Robot,CraftingBench消失 | |||
| - TechOtaku的Robot的PlayerId = TechOtaku的PlayerId + n×5(一局游戏理论人数),其中1<=n<=3(自己的n为0) | |||
| - 新造的Robot的PlayerId的n总是尽量小 | |||
| 2. UseRobot | |||
| - CD:2s,持续时间:0s | |||
| - 输入额外参数PlayerID,切换到要使用的角色。 | |||
| - 切换到其他角色时,自己进入UsingSkill状态。 | |||
| 修改后: | |||
| | 学生职业 | 教师Teacher | 健身狂Athlete |学霸StraightAStudent | 开心果Sunshine | 机器人Robot | 技术宅TechOtaku | | |||
| | :------------ | :------------------ | :------------------ | :------------------ | :------------------ | :------------------ | :------------------ | | |||
| | 移动速度/s | 2700 | 3150 | 2880 | 3000 | 2700 | 2880 | | |||
| | 最大毅力值 | 30000000 | 3000000 | 3300000 | 3200000 | 900000 | 2700000 | | |||
| | 最大沉迷度 | 600000 | 54000 | 78000 | 60000 | 0 | 60000 | | |||
| | 学习速度/ms | 50 | 73 | 135 | 123 | 85 | 110 | | |||
| | 勉励速度/ms | 80 | 90 | 100 | 120 | 0 | 100 | | |||
| | 隐蔽度 | 0.5 | 0.9 | 0.9 | 0.8 | 0.8 | 1.1 | | |||
| | 警戒范围 | 10000 | 15000 | 13500 | 15000 | 0 | 15000 | | |||
| | 视野范围 | 9000 | 11000 | 9000 | 10000 | 0 | 9000 | | |||
| | 开锁门速度/ms | 5000 | 5000 | 5000 | 3500 | 0 | 5000 | | |||
| | 翻窗速度/ms | 1000 | 1466 | 1018 | 1222 | 1 | 1100 | | |||
| | 翻箱速度/ms | 1250 | 1250 | 1250 | 1125 | 1000 | 1100 | | |||
| ### 捣蛋鬼 | |||
| - Assassin | |||
| - 技能隐身(BecomeInvisible)添加约束条件“使用隐身技能后,人物状态进入UsingSpecialSkill,进入其他状态会使得隐身状态解除" | |||
| - 喧哗者ANoisyPerson | |||
| - 普通攻击改为strike | |||
| - Klee | |||
| - 被动技能Lucky!(新增):开局获得随机的一个道具(不会是钥匙) | |||
| - 主动技能SparksNSplash(新增): | |||
| - CD:45s, 持续时间:10s | |||
| - 技能使用瞬间,对于输入的额外参数PlayerID代表的角色,距离最近的本已停止运动的小炸弹开始追踪该角色(每100ms向该角色直线移动)(该角色无血量则失败) | |||
| - 主动技能 蹦蹦炸弹 JumpyBomb | |||
| - 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上0°,45°,90°,135°,180°,225°,270°,315° 发出8个小炸弹 | |||
| - Idol | |||
| 主动技能ShowTime改为 | |||
| "持续时间内 | |||
| - 使警戒范围外的学生眩晕并每**500ms**发送向自己移动**500ms**的指令(速度为学生本应速度*二者距离/警戒范围) | |||
| - 对于视野范围(不是可视区域)内的学生每**500ms**加**1500**的沉迷度 | |||
| - 捣蛋鬼变为0.8倍速" | |||
| @@ -0,0 +1,53 @@ | |||
| # 版本更新说明 | |||
| # 说明 | |||
| - 只说明对于选手较为重要的修改 | |||
| # 5月6日3点更新 | |||
| - docs:添加了 游戏机制与平衡性调整更新草案.pdf | |||
| - docs:添加了 版本更新说明.pdf | |||
| - docs&hotfix: 修正了GameRules.pdf中学生翻窗速度的错误 | |||
| - remove: 删除了Place属性 | |||
| # 5月6日12点更新 | |||
| - hotfix: 修复了突然的bug(物件锁的相关问题) | |||
| - rule:增加了每帧最多50条主动指令的限制 | |||
| # 5月8日更新 | |||
| - feat:增加了可选地图功能 | |||
| - **脚本RunServer(ForDebug).cmd/sh现在支持可选地图功能,但想选择地图,选手需要自行参照使用文档修改命令行或在云盘下载脚本** | |||
| # 5月9日19:30更新 | |||
| - docs:更新了 游戏机制与平衡性调整更新草案.pdf | |||
| - change:更改了地图的文件路径 | |||
| # 5月10日更新 | |||
| - fix:修复JumpyDumpty的初始位置错误的问题 | |||
| - fix:修正和重新说明攻击距离 | |||
| - **攻击距离是指攻击(子弹)的移动距离,也就是说理论上最远被攻击的学生的中心与捣蛋鬼的中心=学生的半径+捣蛋鬼的半径+攻击距离+子弹半径(200)×2** | |||
| - hotfix:修复小炸弹初始化类型错误的问题 | |||
| - remove:去除了“实际上唤醒或勉励不同的人是有效的” | |||
| - **重复发出同一类型的交互指令和移动指令是无效的** | |||
| - feat&fix:修复并**将`SendMessage`改为`SendTextMessage`与`SendBinaryMessage`** | |||
| # 5月13日更新 | |||
| - docs:更新了 游戏机制与平衡性调整更新草案.pdf | |||
| - feat:更新了下载器 | |||
| # 5月15日更新 | |||
| - feat:Robot可用 | |||
| - hotfix: 修复了移动相关的bug | |||
| # 5月19日13:00更新 | |||
| - feat:TechOtaku可用 | |||
| - feat:Klee、Idol已调整 | |||
| - fix:修复了InSpire会给Tricker加速的问题 | |||
| - fix:修复了开锁门的bug | |||
| # 5月19日15:00更新 | |||
| - docs:更新了 游戏机制与平衡性调整更新草案.pdf | |||
| - hotfix:修复了移动状态设置错误 | |||
| # 5月20日1:30更新 | |||
| - docs:更新了 游戏机制与平衡性调整更新草案.pdf | |||
| - feat:游戏机制与平衡性调整已完成 | |||
| @@ -52,7 +52,7 @@ | |||
| <Button Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Name="UninstBtn" Content="卸载选手包" Command="{Binding ClickUninstCommand}" Visibility="{Binding MenuVis}" /> | |||
| <Button Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="2" Name="MenuBackBtn" Content="回到登陆界面" Command="{Binding ClickBackCommand}" Visibility="{Binding MenuVis}" /> | |||
| <TextBlock Grid.Row="3" Grid.Column="3" Text="正在处理……" Grid.ColumnSpan="2" Visibility="{Binding ProgressVis}"/> | |||
| <TextBlock Grid.Row="3" Grid.Column="3" Text="{Binding ProcessingIntro}" Grid.ColumnSpan="2" Visibility="{Binding ProgressVis}"/> | |||
| <ProgressBar Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="7" Minimum="0" Maximum="100" Name="Progress" Visibility="{Binding ProgressVis}" IsIndeterminate="True"/> | |||
| <TextBlock Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="4" Text="操作完成!你可以继续操作或退出" Visibility="{Binding CompleteVis}"/> | |||
| @@ -81,10 +81,21 @@ | |||
| <TextBlock Grid.Row="5" Grid.Column="1" Foreground="Red" Text=" 用户名或密码错误!" Visibility="{Binding LoginFailVis}"/> | |||
| </Grid> | |||
| </StackPanel> | |||
| <Button Grid.Row="7" Grid.Column="1" Name="Login" Content="登录" Command="{Binding ClickLoginCommand}" Visibility="{Binding LoginVis}"/> | |||
| <Button Grid.Row="7" Grid.Column="2" Name="Launch" FontSize="11" Content="{Binding LaunchBtnCont}" Command="{Binding ClickLaunchCommand}" Visibility="{Binding LoginVis}"/> | |||
| <Button Grid.Row="7" Grid.Column="3" Name="ShiftLanguage" FontSize="11" Content="更改语言" Command="{Binding ClickShiftLanguageCommand}" Visibility="{Binding LaunchVis}"/> | |||
| <StackPanel Grid.Row="7" Grid.Column="1" Grid.ColumnSpan="3"> | |||
| <Grid> | |||
| <Grid.RowDefinitions> | |||
| <RowDefinition Height="20"/> | |||
| </Grid.RowDefinitions> | |||
| <Grid.ColumnDefinitions> | |||
| <ColumnDefinition Width="70"/> | |||
| <ColumnDefinition Width="70"/> | |||
| <ColumnDefinition Width="60"/> | |||
| </Grid.ColumnDefinitions> | |||
| <Button Grid.Row="0" Grid.Column="0" Name="Login" Content="登录" Command="{Binding ClickLoginCommand}" Visibility="{Binding LoginVis}"/> | |||
| <Button Grid.Row="0" Grid.Column="1" Name="Launch" Content="{Binding LaunchBtnCont}" Command="{Binding ClickLaunchCommand}" Visibility="{Binding LoginVis}" IsEnabled="{Binding UpdatePlanned}"/> | |||
| <Button Grid.Row="0" Grid.Column="2" Name="ShiftLanguage" FontSize="11" Content="配置启动器" Command="{Binding ClickShiftLanguageCommand}" Visibility="{Binding LaunchVis}" IsEnabled="False"/> | |||
| </Grid> | |||
| </StackPanel> | |||
| <Button Grid.Row="7" Grid.Column="4" Grid.ColumnSpan="2" Name="Edit" Content="修改文件" Command="{Binding ClickEditCommand}" Visibility="{Binding LoginVis}"/> | |||
| @@ -114,5 +125,40 @@ | |||
| <Button Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="2" Content="申请对战" IsEnabled="False" Command="{Binding ClickRequestCommand}" Visibility="{Binding WebVis}" /> | |||
| <TextBox Grid.Row="4" Grid.Column="3" Grid.ColumnSpan="2" Text="暂不支持" IsEnabled="False" Visibility="{Binding WebVis}" /> | |||
| <!--objects below are enabled--> | |||
| <StackPanel Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="7"> | |||
| <Grid> | |||
| <Grid.RowDefinitions> | |||
| <RowDefinition Height="20"/> | |||
| </Grid.RowDefinitions> | |||
| <Grid.ColumnDefinitions> | |||
| <ColumnDefinition Width="90"/> | |||
| <ColumnDefinition Width="90"/> | |||
| <ColumnDefinition Width="90"/> | |||
| <ColumnDefinition Width="*"/> | |||
| </Grid.ColumnDefinitions> | |||
| <Button Grid.Column="0" Name ="ConfigServer" Content="服务端" Command="{Binding ClickCfgServCommand}" IsEnabled="{Binding ServerCfgNotSelected}" Visibility="{Binding CfgVis}"/> | |||
| <Button Grid.Column="1" Name ="ConfigAI" Content="客户端" Command="{Binding ClickCfgCliCommand}" IsEnabled="{Binding CliCfhNotSelected}" Visibility="{Binding CfgVis}"/> | |||
| <Button Grid.Column="2" Name ="ConfigCliend" Content="图形界面" Command="{Binding ClickCfgGUICommand}" IsEnabled="{Binding GUICfgNotSelected}" Visibility="{Binding CfgVis}"/> | |||
| </Grid> | |||
| </StackPanel> | |||
| <StackPanel Grid.Row="3" Grid.Column="1" Grid.RowSpan="5" Grid.ColumnSpan="7"> | |||
| <Grid> | |||
| <Grid.RowDefinitions> | |||
| <RowDefinition Height="20"/> | |||
| <RowDefinition Height="20"/> | |||
| <RowDefinition Height="20"/> | |||
| <RowDefinition Height="20"/> | |||
| <RowDefinition Height="20"/> | |||
| </Grid.RowDefinitions> | |||
| <Grid.ColumnDefinitions> | |||
| <ColumnDefinition Width="50"/> | |||
| <ColumnDefinition Width="75"/> | |||
| <ColumnDefinition Width="25"/> | |||
| <ColumnDefinition Width="75"/> | |||
| <ColumnDefinition Width="*"/> | |||
| </Grid.ColumnDefinitions> | |||
| </Grid> | |||
| </StackPanel> | |||
| </Grid> | |||
| </Window> | |||
| @@ -20,6 +20,7 @@ using System.Threading.Tasks; | |||
| using System.Net.Http; | |||
| using System.Windows; | |||
| using System.Windows.Shapes; | |||
| using System.Collections.Concurrent; | |||
| //using System.Windows.Forms; | |||
| using System.Threading; | |||
| @@ -198,7 +199,113 @@ namespace starter.viewmodel.settings | |||
| { | |||
| if (Tencent_cos_download.CheckAlreadyDownload()) | |||
| { | |||
| Process.Start(System.IO.Path.Combine(Data.FilePath, startName)); | |||
| //Process.Start(System.IO.Path.Combine(Data.FilePath, startName)); | |||
| switch (RunProgram.RunInfo.mode) | |||
| { | |||
| case RunProgram.RunMode.ServerOnly: | |||
| RunProgram.StartServer(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.gameTimeSec, RunProgram.RunInfo.playbackFileName); | |||
| break; | |||
| case RunProgram.RunMode.ServerForDebugOnly: | |||
| RunProgram.StartServerForDebug(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.gameTimeSec, RunProgram.RunInfo.playbackFileName); | |||
| break; | |||
| case RunProgram.RunMode.GUIAttendGameOnly: | |||
| RunProgram.RunGUIClient(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.characterID, | |||
| false, RunProgram.RunInfo.occupation, RunProgram.RunInfo.type); | |||
| break; | |||
| case RunProgram.RunMode.GUIVisit: | |||
| RunProgram.RunGUIClient(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, 0, true, 1, 1); | |||
| break; | |||
| case RunProgram.RunMode.GUIAndAICpp: | |||
| RunProgram.StartServerForDebug(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.gameTimeSec, RunProgram.RunInfo.playbackFileName); | |||
| Task.Delay(100); | |||
| RunProgram.RunCpp(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.saveDebugLog, RunProgram.RunInfo.showDebugLog, | |||
| RunProgram.RunInfo.warningOnly, RunProgram.RunInfo.playerId, RunProgram.RunInfo.filePath); | |||
| RunProgram.RunInfo.playerId = null; | |||
| RunProgram.RunInfo.filePath = null; | |||
| RunProgram.RunGUIClient(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.characterID, | |||
| false, RunProgram.RunInfo.occupation, RunProgram.RunInfo.type); | |||
| break; | |||
| case RunProgram.RunMode.GUIAndAIPython: | |||
| RunProgram.StartServerForDebug(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.gameTimeSec, RunProgram.RunInfo.playbackFileName); | |||
| Task.Delay(100); | |||
| RunProgram.RunPython(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.saveDebugLog, RunProgram.RunInfo.showDebugLog, | |||
| RunProgram.RunInfo.warningOnly, RunProgram.RunInfo.playerId, RunProgram.RunInfo.filePath); | |||
| RunProgram.RunInfo.playerId = null; | |||
| RunProgram.RunInfo.filePath = null; | |||
| RunProgram.RunGUIClient(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.characterID, | |||
| false, RunProgram.RunInfo.occupation, RunProgram.RunInfo.type); | |||
| break; | |||
| case RunProgram.RunMode.ServerAndCpp: | |||
| RunProgram.StartServer(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.gameTimeSec, RunProgram.RunInfo.playbackFileName); | |||
| Task.Delay(100); | |||
| RunProgram.RunCpp(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.saveDebugLog, RunProgram.RunInfo.showDebugLog, | |||
| RunProgram.RunInfo.warningOnly, RunProgram.RunInfo.playerId, RunProgram.RunInfo.filePath); | |||
| RunProgram.RunInfo.playerId = null; | |||
| RunProgram.RunInfo.filePath = null; | |||
| break; | |||
| case RunProgram.RunMode.ServerAndPython: | |||
| RunProgram.StartServer(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.gameTimeSec, RunProgram.RunInfo.playbackFileName); | |||
| Task.Delay(100); | |||
| RunProgram.RunPython(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.saveDebugLog, RunProgram.RunInfo.showDebugLog, | |||
| RunProgram.RunInfo.warningOnly, RunProgram.RunInfo.playerId, RunProgram.RunInfo.filePath); | |||
| RunProgram.RunInfo.playerId = null; | |||
| RunProgram.RunInfo.filePath = null; | |||
| break; | |||
| case RunProgram.RunMode.ServerAndCppVisit: | |||
| RunProgram.StartServer(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.gameTimeSec, RunProgram.RunInfo.playbackFileName); | |||
| Task.Delay(100); | |||
| RunProgram.RunCpp(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.saveDebugLog, RunProgram.RunInfo.showDebugLog, | |||
| RunProgram.RunInfo.warningOnly, RunProgram.RunInfo.playerId, RunProgram.RunInfo.filePath); | |||
| RunProgram.RunInfo.playerId = null; | |||
| RunProgram.RunInfo.filePath = null; | |||
| RunProgram.RunGUIClient(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.characterID, true, 0, 1); | |||
| break; | |||
| case RunProgram.RunMode.ServerAndPythonVisit: | |||
| RunProgram.StartServer(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.gameTimeSec, RunProgram.RunInfo.playbackFileName); | |||
| Task.Delay(100); | |||
| RunProgram.RunPython(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.saveDebugLog, RunProgram.RunInfo.showDebugLog, | |||
| RunProgram.RunInfo.warningOnly, RunProgram.RunInfo.playerId, RunProgram.RunInfo.filePath); | |||
| RunProgram.RunInfo.playerId = null; | |||
| RunProgram.RunInfo.filePath = null; | |||
| RunProgram.RunGUIClient(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.characterID, true, 0, 1); | |||
| break; | |||
| case RunProgram.RunMode.ServerDebugAndCppVisit: | |||
| RunProgram.StartServerForDebug(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.gameTimeSec, RunProgram.RunInfo.playbackFileName); | |||
| Task.Delay(100); | |||
| RunProgram.RunCpp(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.saveDebugLog, RunProgram.RunInfo.showDebugLog, | |||
| RunProgram.RunInfo.warningOnly, RunProgram.RunInfo.playerId, RunProgram.RunInfo.filePath); | |||
| RunProgram.RunInfo.playerId = null; | |||
| RunProgram.RunInfo.filePath = null; | |||
| RunProgram.RunGUIClient(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.characterID, true, 0, 1); | |||
| break; | |||
| case RunProgram.RunMode.ServerDebugAndPythonVisit: | |||
| RunProgram.StartServerForDebug(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.gameTimeSec, RunProgram.RunInfo.playbackFileName); | |||
| Task.Delay(100); | |||
| RunProgram.RunPython(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.studentCount, | |||
| RunProgram.RunInfo.trickerCount, RunProgram.RunInfo.saveDebugLog, RunProgram.RunInfo.showDebugLog, | |||
| RunProgram.RunInfo.warningOnly, RunProgram.RunInfo.playerId, RunProgram.RunInfo.filePath); | |||
| RunProgram.RunInfo.playerId = null; | |||
| RunProgram.RunInfo.filePath = null; | |||
| RunProgram.RunGUIClient(RunProgram.RunInfo.IP, RunProgram.RunInfo.port, RunProgram.RunInfo.characterID, true, 0, 1); | |||
| break; | |||
| } | |||
| return true; | |||
| } | |||
| else | |||
| @@ -298,7 +405,7 @@ namespace starter.viewmodel.settings | |||
| /// <summary> | |||
| /// 关于介绍的屏幕显示信息 | |||
| /// </summary> | |||
| public enum Status { newUser, menu, move, working, disconnected, error, successful, login, web }; | |||
| public enum Status { newUser, menu, move, working, initializing, disconnected, error, successful, login, web, launch }; | |||
| public Status status | |||
| { | |||
| get; set; | |||
| @@ -361,8 +468,9 @@ namespace Downloader | |||
| class Program | |||
| { | |||
| static List<string> newFileName = new List<string>(); // 新文件名 | |||
| static List<string> updateFileName = new List<string>(); // 更新文件名 | |||
| static ConcurrentQueue<string> newFileName = new ConcurrentQueue<string>(); | |||
| //static List<string> newFileName = new List<string>(); // 新文件名 | |||
| static ConcurrentQueue<string> updateFileName = new ConcurrentQueue<string>(); // 更新文件名 | |||
| static List<string> updateFailed = new List<string>(); //更新失败的文件名 | |||
| static public List<string> UpdateFailed | |||
| { | |||
| @@ -505,7 +613,6 @@ namespace Downloader | |||
| string secretId = "***"; //"云 API 密钥 SecretId"; | |||
| string secretKey = "***"; //"云 API 密钥 SecretKey"; | |||
| long durationSecond = 1000; // 每次请求签名有效时长,单位为秒 | |||
| QCloudCredentialProvider cosCredentialProvider = new DefaultQCloudCredentialProvider( | |||
| secretId, secretKey, durationSecond | |||
| @@ -654,7 +761,7 @@ namespace Downloader | |||
| { | |||
| MD5 = GetFileMd5Hash(System.IO.Path.Combine(Data.FilePath, pair.Key.TrimStart(new char[] { '.', '/' }))); | |||
| if (MD5.Length == 0) // 文档不存在 | |||
| newFileName.Add(pair.Key); | |||
| newFileName.Enqueue(pair.Key); | |||
| else if (MD5.Equals("conflict")) | |||
| { | |||
| if (pair.Key.Equals("THUAI6/win/CAPI/cpp/.vs/CAPI/v17/Browse.VC.db")) | |||
| @@ -670,7 +777,7 @@ namespace Downloader | |||
| MessageBox.Show($"检查{pair.Key}更新时遇到问题,请反馈", "读取出错", MessageBoxButton.OK, MessageBoxImage.Error); | |||
| } | |||
| else if (!MD5.Equals(pair.Value) && !IsUserFile(System.IO.Path.GetFileName(pair.Key))) // MD5不匹配 | |||
| updateFileName.Add(pair.Key); | |||
| updateFileName.Enqueue(pair.Key); | |||
| } | |||
| } | |||
| @@ -736,38 +843,110 @@ namespace Downloader | |||
| { | |||
| try | |||
| { | |||
| foreach (string filename in newFileName) | |||
| int cnt = newFileName.Count; | |||
| if (cnt <= 20) | |||
| { | |||
| //Console.WriteLine(newFile + 1 + "/" + totalnew + ":开始下载" + filename); | |||
| Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename.TrimStart(new char[] { '.', '/' })); | |||
| //Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||
| newFile++; | |||
| while (newFileName.TryDequeue(out var filename)) | |||
| { | |||
| Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename.TrimStart(new char[] { '.', '/' })); | |||
| //Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||
| Interlocked.Increment(ref newFile); | |||
| } | |||
| } | |||
| foreach (string filename in updateFileName) | |||
| else | |||
| { | |||
| //Console.WriteLine(updateFile + 1 + "/" + totalupdate + ":开始下载" + filename); | |||
| try | |||
| const int nthread = 8; | |||
| var thrds = new List<Thread>(); | |||
| for (int i = 0; i < nthread; i++) | |||
| { | |||
| File.Delete(System.IO.Path.Combine(@Data.FilePath, filename)); | |||
| Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename.TrimStart(new char[] { '.', '/' })); | |||
| var thrd = new Thread(() => | |||
| { | |||
| while (newFileName.TryDequeue(out var filename)) | |||
| { | |||
| Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename.TrimStart(new char[] { '.', '/' })); | |||
| //Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||
| Interlocked.Increment(ref newFile); | |||
| } | |||
| }); | |||
| thrd.Start(); | |||
| thrds.Add(thrd); | |||
| } | |||
| catch (System.IO.IOException) | |||
| foreach (var thrd in thrds) | |||
| { | |||
| updateFailed = updateFailed.Append(filename).ToList(); | |||
| thrd.Join(); | |||
| } | |||
| catch | |||
| } | |||
| // 读取 Interlocked.CompareExchange(ref newFile, 0, 0); | |||
| int upcnt = updateFileName.Count; | |||
| if (upcnt <= 20) | |||
| { | |||
| while (updateFileName.TryDequeue(out var filename)) | |||
| { | |||
| if (filename.Substring(filename.Length - 4, 4).Equals(".pdf")) | |||
| try | |||
| { | |||
| File.Delete(System.IO.Path.Combine(@Data.FilePath, filename)); | |||
| Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename.TrimStart(new char[] { '.', '/' })); | |||
| } | |||
| catch (System.IO.IOException) | |||
| { | |||
| updateFailed = updateFailed.Append(filename).ToList(); | |||
| } | |||
| catch | |||
| { | |||
| MessageBox.Show($"由于曾经发生过的访问冲突,下载器无法更新{filename}\n" | |||
| + $"请手动删除{filename},然后再试一次。"); | |||
| if (filename.Substring(filename.Length - 4, 4).Equals(".pdf")) | |||
| { | |||
| MessageBox.Show($"由于曾经发生过的访问冲突,下载器无法更新{filename}\n" | |||
| + $"请手动删除{filename},然后再试一次。"); | |||
| } | |||
| else | |||
| MessageBox.Show($"更新{filename}时遇到未知问题,请反馈"); | |||
| updateFailed = updateFailed.Append(filename).ToList(); | |||
| } | |||
| else | |||
| MessageBox.Show($"更新{filename}时遇到未知问题,请反馈"); | |||
| updateFailed = updateFailed.Append(filename).ToList(); | |||
| Interlocked.Increment(ref newFile); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| const int nthread = 8; | |||
| var thrds = new List<Thread>(); | |||
| for (int i = 0; i < nthread; i++) | |||
| { | |||
| var thrd = new Thread(() => | |||
| { | |||
| while (updateFileName.TryDequeue(out var filename)) | |||
| { | |||
| try | |||
| { | |||
| File.Delete(System.IO.Path.Combine(@Data.FilePath, filename)); | |||
| Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename.TrimStart(new char[] { '.', '/' })); | |||
| } | |||
| catch (System.IO.IOException) | |||
| { | |||
| updateFailed = updateFailed.Append(filename).ToList(); | |||
| } | |||
| catch | |||
| { | |||
| if (filename.Substring(filename.Length - 4, 4).Equals(".pdf")) | |||
| { | |||
| MessageBox.Show($"由于曾经发生过的访问冲突,下载器无法更新{filename}\n" | |||
| + $"请手动删除{filename},然后再试一次。"); | |||
| } | |||
| else | |||
| MessageBox.Show($"更新{filename}时遇到未知问题,请反馈"); | |||
| updateFailed = updateFailed.Append(filename).ToList(); | |||
| } | |||
| Interlocked.Increment(ref newFile); | |||
| } | |||
| }); | |||
| thrd.Start(); | |||
| thrds.Add(thrd); | |||
| } | |||
| foreach (var thrd in thrds) | |||
| { | |||
| thrd.Join(); | |||
| } | |||
| //Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||
| updateFile++; | |||
| } | |||
| if (updateFailed.Count == 0) | |||
| UpdatePlanned = false; | |||
| @@ -869,7 +1048,7 @@ namespace Downloader | |||
| newFileName.Clear(); | |||
| updateFileName.Clear(); | |||
| newFileName.Add("THUAI6.tar.gz"); | |||
| newFileName.Enqueue("THUAI6.tar.gz"); | |||
| Download(); | |||
| Stream? inStream = null; | |||
| Stream? gzipStream = null; | |||
| @@ -1373,6 +1552,297 @@ namespace Downloader | |||
| sw.Close(); | |||
| } | |||
| } | |||
| public class RunProgram | |||
| { | |||
| public enum RunMode | |||
| { | |||
| ServerOnly, //只启动Server | |||
| ServerForDebugOnly, //只启动ServerForDebug | |||
| GUIAttendGameOnly, //只用GUIClient参与游戏 | |||
| GUIVisit, //只用GUIClient观战 | |||
| GUIAndAICpp, //用GUI参与游戏并且让cpp的AI同时参与 | |||
| GUIAndAIPython, //用GUI参与游戏并且让python的AI同时参与 | |||
| ServerAndCpp, //只运行Server和cpp, | |||
| ServerAndPython, //只运行Server和python | |||
| ServerAndCppVisit, //运行Server和Cpp并用GUI观战 | |||
| ServerAndPythonVisit, //运行Server和python并用GUI观战 | |||
| ServerDebugAndCpp, //运行ServerForDebug... | |||
| ServerDebugAndPython, | |||
| ServerDebugAndCppVisit, | |||
| ServerDebugAndPythonVisit, | |||
| } | |||
| public class RunInfo //UI需要在调用Launch函数前保证其中数据已经更新并有效 | |||
| { | |||
| static public RunMode mode; | |||
| static public string? IP; | |||
| static public int port; | |||
| static public int studentCount; | |||
| static public int trickerCount; | |||
| static public int gameTimeSec; | |||
| static public string? playbackFileName; | |||
| static public int characterID; | |||
| static public int type; | |||
| static public bool saveDebugLog; | |||
| static public bool showDebugLog; | |||
| static public bool warningOnly; | |||
| static public bool visiting; | |||
| static public int occupation; | |||
| static public List<int>? playerId = new List<int>(); //两者长度必须与studentCount + trickerCount一致 | |||
| static public List<string>? filePath = new List<string>(); | |||
| } | |||
| /// <summary> | |||
| /// 运行cmd命令 | |||
| /// 会显示命令窗口 | |||
| /// </summary> | |||
| /// <param name="cmdExe">指定应用程序的完整路径</param> | |||
| /// <param name="cmdStr">执行命令行参数</param> | |||
| static bool RunCmd(string cmdExe, string cmdStr) | |||
| { | |||
| bool result = false; | |||
| try | |||
| { | |||
| using (Process myPro = new Process()) | |||
| { | |||
| //指定启动进程是调用的应用程序和命令行参数 | |||
| ProcessStartInfo psi = new ProcessStartInfo(cmdExe, cmdStr); | |||
| myPro.StartInfo = psi; | |||
| myPro.Start(); | |||
| //myPro.WaitForExitAsync(); | |||
| result = true; | |||
| } | |||
| } | |||
| catch | |||
| { | |||
| } | |||
| return result; | |||
| } | |||
| /// <summary> | |||
| /// 启动Server | |||
| /// 会显示命令窗口 | |||
| /// </summary> | |||
| /// <param name="IP">指定Server运行的IPV4地址</param> | |||
| /// <param name="port">指定Server运行的端口</param> | |||
| /// <param name="studentCount">指定学生人数</param> | |||
| /// <param name="trickerCount">指定捣蛋鬼人数</param> | |||
| /// <param name="gameTimeSec">指定游戏最大时长</param> | |||
| /// <param name="playbackFileName">指定回放文件名称</param> | |||
| public static int StartServer(string? IP, int port, int studentCount, int trickerCount, int gameTimeSec, string? playbackFileName) | |||
| { | |||
| if (System.Diagnostics.Process.GetProcessesByName("Server").ToList().Count > 0) | |||
| { | |||
| System.Diagnostics.Process.GetProcessesByName("Server")[0].Kill(); | |||
| } | |||
| string cmdExe = $"{Data.FilePath}\\THUAI6\\win\\win64\\Server.exe"; | |||
| string cmdPara = $"--ip {IP} --port {port} --studentCount {studentCount} --trickerCount {trickerCount}" + | |||
| $" --gameTimeInSecond {gameTimeSec} --fileName {playbackFileName}"; | |||
| RunCmd(cmdExe, cmdPara); | |||
| return 0; | |||
| } | |||
| /// <summary> | |||
| /// 启动Debug用的Server | |||
| /// 会显示命令窗口 | |||
| /// </summary> | |||
| /// <param name="IP">指定Server运行的IPV4地址</param> | |||
| /// <param name="port">指定Server运行的端口</param> | |||
| /// <param name="studentCount">指定学生人数</param> | |||
| /// <param name="trickerCount">指定捣蛋鬼人数</param> | |||
| /// <param name="gameTimeSec">指定游戏最大时长</param> | |||
| /// <param name="playbackFileName">指定回放文件名称</param> | |||
| public static int StartServerForDebug(string? IP, int port, int studentCount, int trickerCount, int gameTimeSec, string? playbackFileName) | |||
| { | |||
| if (System.Diagnostics.Process.GetProcessesByName("Server").ToList().Count > 0) | |||
| { | |||
| System.Diagnostics.Process.GetProcessesByName("Server")[0].Kill(); | |||
| } | |||
| string cmdExe = $"{Data.FilePath}\\THUAI6\\win\\win64\\Debug\\Server.exe"; | |||
| string cmdPara = $"--ip {IP} --port {port} --studentCount {studentCount} --trickerCount {trickerCount} " + | |||
| $"--gameTimeInSecond {gameTimeSec} --fileName {playbackFileName}"; | |||
| RunCmd(cmdExe, cmdPara); | |||
| return 0; | |||
| } | |||
| /// <summary> | |||
| /// 启动cpp,在这之前要先启动Server,可能还需要一些延时 | |||
| /// 会显示命令窗口 | |||
| /// </summary> | |||
| /// <param name="IP">指定Server运行的IPV4地址</param> | |||
| /// <param name="port">指定Server运行的端口</param> | |||
| /// <param name="studentCount">指定学生人数</param> | |||
| /// <param name="trickerCount">指定捣蛋鬼人数</param> | |||
| /// <param name="saveDebugLog">是否保存Debug日志文件</param> | |||
| /// <param name="showDebugLog">是否将日志输出到屏幕上</param> | |||
| /// <param name="warningOnly">在showDebugLog == true时,是否只输出警告或者报错(不影响日志保存)</param> | |||
| /// <param name="playerIp">默认为空,如果不为空,playerIp将会按照顺序代替默认从0~3的IP</param> | |||
| public static int RunCpp(string? IP, int port, int studentCount, int trickerCount, bool saveDebugLog, | |||
| bool showDebugLog, bool warningOnly, List<int>? playerIp = null, List<string>? filePath = null) | |||
| { | |||
| string cmdExe; | |||
| string cmdBase = $"-I {IP} -P {port} "; | |||
| for (int i = 0; i < studentCount; i++) | |||
| { | |||
| string cmdPara; | |||
| if (playerIp == null) | |||
| { | |||
| cmdPara = cmdBase + $" -p {i}"; | |||
| } | |||
| else | |||
| { | |||
| cmdPara = cmdBase + $" -p {playerIp[i]}"; | |||
| } | |||
| if (saveDebugLog) | |||
| { | |||
| cmdPara = cmdPara + " -d"; | |||
| } | |||
| if (showDebugLog) | |||
| { | |||
| cmdPara = cmdPara + " -o"; | |||
| } | |||
| if (warningOnly) | |||
| { | |||
| cmdPara = cmdPara + " -w"; | |||
| } | |||
| if (filePath == null) | |||
| { | |||
| cmdExe = $"{Data.FilePath}\\THUAI6\\win\\CAPI\\cpp\\x64\\Debug\\API.exe"; | |||
| } | |||
| else | |||
| { | |||
| cmdExe = filePath[i]; | |||
| } | |||
| RunCmd(cmdExe, cmdPara); | |||
| } | |||
| if (trickerCount != 0) | |||
| { | |||
| string cmdPara; | |||
| cmdPara = cmdBase + " -p 4"; | |||
| if (saveDebugLog) | |||
| { | |||
| cmdPara = cmdPara + " -d"; | |||
| } | |||
| if (showDebugLog) | |||
| { | |||
| cmdPara = cmdPara + " -o"; | |||
| } | |||
| if (warningOnly) | |||
| { | |||
| cmdPara = cmdPara + " -w"; | |||
| } | |||
| if (filePath == null) | |||
| { | |||
| cmdExe = $"{Data.FilePath}\\THUAI6\\win\\CAPI\\cpp\\x64\\Debug\\API.exe"; | |||
| } | |||
| else | |||
| { | |||
| cmdExe = filePath[studentCount]; | |||
| } | |||
| RunCmd(cmdExe, cmdPara); | |||
| } | |||
| return 0; | |||
| } | |||
| /// <summary> | |||
| /// 启动客户端 | |||
| /// 会显示命令窗口 | |||
| /// </summary> | |||
| /// <param name="IP">指定Server运行的IPV4地址</param> | |||
| /// <param name="port">指定Server运行的端口</param> | |||
| /// <param name="characterID">进入游戏时的角色ID 0~4,0~3为学生,4为捣蛋鬼</param> | |||
| /// <param name="visiting">是否观战,当值为true时,characterID无效</param> | |||
| /// <param name="occupation">指定参加游戏时的角色职业,详情见说明文档</param> | |||
| /// <param name="type">角色类别,type = 1 表示学生,type = 2 表示捣蛋鬼</param> | |||
| public static int RunGUIClient(string? IP, int port, int characterID, bool visiting, int occupation = 1, int type = 1) | |||
| { | |||
| string cmdExe = $"{Data.FilePath}\\THUAI6\\win\\win64\\Client.exe"; | |||
| string cmdBase = $"--port {port} --ip {IP} --type {type} --occupation {occupation}"; | |||
| if (visiting) | |||
| { | |||
| cmdBase = cmdBase + " --characterID 200000"; | |||
| } | |||
| else | |||
| { | |||
| cmdBase = cmdBase + $" --characterID {characterID}"; | |||
| } | |||
| cmdBase = cmdBase + " --cl"; | |||
| RunCmd(cmdExe, cmdBase); | |||
| return 0; | |||
| } | |||
| //启动python,参数同RunCpp | |||
| public static int RunPython(string? IP, int port, int studentCount, int trickerCount, bool saveDebugLog, | |||
| bool showDebugLog, bool warningOnly, List<int>? playerIp = null, List<string>? filePath = null) | |||
| { | |||
| string cmdExe = $"python"; | |||
| string cmdBase; | |||
| for (int i = 0; i < studentCount; i++) | |||
| { | |||
| string cmdPara; | |||
| if (filePath == null) | |||
| { | |||
| cmdBase = $" {Data.FilePath}\\THUAI6\\win\\CAPI\\python\\PyAPI\\main.py -I {IP} -P {port} "; | |||
| } | |||
| else | |||
| { | |||
| cmdBase = " " + filePath[i] + $" -I {IP} -P {port} "; | |||
| } | |||
| if (playerIp == null) | |||
| { | |||
| cmdPara = cmdBase + $" -p {i}"; | |||
| } | |||
| else | |||
| { | |||
| cmdPara = cmdBase + $" -p {playerIp[i]}"; | |||
| } | |||
| if (saveDebugLog) | |||
| { | |||
| cmdPara = cmdPara + " -d"; | |||
| } | |||
| if (showDebugLog) | |||
| { | |||
| cmdPara = cmdPara + " -o"; | |||
| } | |||
| if (warningOnly) | |||
| { | |||
| cmdPara = cmdPara + " -w"; | |||
| } | |||
| RunCmd(cmdExe, cmdPara); | |||
| } | |||
| if (trickerCount != 0) | |||
| { | |||
| string cmdPara; | |||
| if (filePath == null) | |||
| { | |||
| cmdBase = $" {Data.FilePath}\\THUAI6\\win\\CAPI\\python\\PyAPI\\main.py -I {IP} -P {port} "; | |||
| } | |||
| else | |||
| { | |||
| cmdBase = " " + filePath[studentCount] + $" -I {IP} -P {port} "; | |||
| } | |||
| cmdPara = cmdBase + " -p 4"; | |||
| if (saveDebugLog) | |||
| { | |||
| cmdPara = cmdPara + " -d"; | |||
| } | |||
| if (showDebugLog) | |||
| { | |||
| cmdPara = cmdPara + " -o"; | |||
| } | |||
| if (warningOnly) | |||
| { | |||
| cmdPara = cmdPara + " -w"; | |||
| } | |||
| RunCmd(cmdExe, cmdPara); | |||
| } | |||
| return 0; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -22,6 +22,7 @@ namespace starter.viewmodel.settings | |||
| //定义BackgroundWorker | |||
| BackgroundWorker asyncDownloader; | |||
| BackgroundWorker asyncUpdater; | |||
| BackgroundWorker asyncInitializer; | |||
| /// <summary> | |||
| /// Model object | |||
| /// </summary> | |||
| @@ -53,44 +54,91 @@ namespace starter.viewmodel.settings | |||
| } | |||
| } | |||
| //实例化BackgroundWorker | |||
| asyncDownloader = new BackgroundWorker(); | |||
| asyncUpdater = new BackgroundWorker(); | |||
| //指示BackgroundWorker是否可以报告进度更新 | |||
| //当该属性值为True是,将可以成功调用ReportProgress方法,否则将引发InvalidOperationException异常。 | |||
| asyncDownloader.WorkerReportsProgress = true; | |||
| asyncUpdater.WorkerReportsProgress = true; | |||
| //挂载方法: | |||
| asyncDownloader.DoWork += AsyncDownloader_DoWork; | |||
| asyncUpdater.DoWork += AsyncUpdater_DoWork; | |||
| //完成通知器: | |||
| asyncDownloader.RunWorkerCompleted += AsyncDownloader_RunWorkerCompleted; | |||
| asyncUpdater.RunWorkerCompleted += AsyncUpdater_RunWorkerCompleted; | |||
| UpdateInfoVis = Visibility.Collapsed; | |||
| if (Downloader.Program.Tencent_cos_download.CheckAlreadyDownload()) | |||
| asyncInitializer = new BackgroundWorker(); | |||
| asyncInitializer.WorkerReportsProgress = true; | |||
| asyncInitializer.DoWork += AsyncInitializer_DoWork; | |||
| asyncInitializer.RunWorkerCompleted += AsyncInitializer_RunWorkerCompleted; | |||
| asyncInitializer.RunWorkerAsync(); | |||
| Status = SettingsModel.Status.initializing; | |||
| } | |||
| private void AsyncInitializer_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e) | |||
| { | |||
| if (e.Result == null) | |||
| { | |||
| Status = SettingsModel.Status.error; | |||
| } | |||
| else if ((int)e.Result == 1) | |||
| { | |||
| obj.checkUpdate(); | |||
| Status = SettingsModel.Status.login; | |||
| this.RaisePropertyChanged("WindowWidth"); | |||
| this.RaisePropertyChanged("LaunchVis"); | |||
| if (obj.RecallUser()) | |||
| RememberMe = true; | |||
| else | |||
| RememberMe = false; | |||
| this.RaisePropertyChanged("RememberMe"); | |||
| this.RaisePropertyChanged("SwitchOSBtnCont"); | |||
| //在启动时立刻检查更新,确保选手启动最新版选手包 | |||
| //若有更新,将启动键改为更新键; | |||
| //相应地,使用login界面启动; | |||
| //结构:上方为登录框架,下方有“修改选手包”按钮 | |||
| this.RaisePropertyChanged("UpdateBtnCont"); | |||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||
| this.RaisePropertyChanged("Updateinfo"); | |||
| this.RaisePropertyChanged("UpdatePlanned"); | |||
| } | |||
| else | |||
| { | |||
| Route = Environment.GetEnvironmentVariable("USERPROFILE") + "\\THUAI6"; | |||
| Status = SettingsModel.Status.newUser; | |||
| this.RaisePropertyChanged("WindowWidth"); | |||
| } | |||
| } | |||
| private void AsyncInitializer_DoWork(object? sender, DoWorkEventArgs e) | |||
| { | |||
| if (asyncInitializer.CancellationPending) | |||
| { | |||
| Status = SettingsModel.Status.error; | |||
| e.Cancel = true; | |||
| return; | |||
| } | |||
| else | |||
| { | |||
| //实例化BackgroundWorker | |||
| asyncDownloader = new BackgroundWorker(); | |||
| asyncUpdater = new BackgroundWorker(); | |||
| //指示BackgroundWorker是否可以报告进度更新 | |||
| //当该属性值为True是,将可以成功调用ReportProgress方法,否则将引发InvalidOperationException异常。 | |||
| asyncDownloader.WorkerReportsProgress = true; | |||
| asyncUpdater.WorkerReportsProgress = true; | |||
| //挂载方法: | |||
| asyncDownloader.DoWork += AsyncDownloader_DoWork; | |||
| asyncUpdater.DoWork += AsyncUpdater_DoWork; | |||
| //完成通知器: | |||
| asyncDownloader.RunWorkerCompleted += AsyncDownloader_RunWorkerCompleted; | |||
| asyncUpdater.RunWorkerCompleted += AsyncUpdater_RunWorkerCompleted; | |||
| if (Downloader.Program.Tencent_cos_download.CheckAlreadyDownload()) | |||
| { | |||
| obj.checkUpdate(); | |||
| if (obj.RecallUser()) | |||
| RememberMe = true; | |||
| else | |||
| RememberMe = false; | |||
| //在启动时立刻检查更新,确保选手启动最新版选手包 | |||
| //若有更新,将启动键改为更新键; | |||
| //相应地,使用login界面启动; | |||
| //结构:上方为登录框架,下方有“修改选手包”按钮 | |||
| //下面几行是用来运行测试的代码 | |||
| //Program.RunProgram.StartServerForDebug("0.0.0.0",8888,4,1,600,"video"); | |||
| //Program.RunProgram.RunCpp("127.0.0.1",8888,4,1,false,true,false); | |||
| //Program.RunProgram.RunGUIClient("127.0.0.1", 8888, 0, true); | |||
| e.Result = 1; | |||
| } | |||
| else | |||
| { | |||
| e.Result = 2; | |||
| } | |||
| } | |||
| } | |||
| @@ -148,6 +196,7 @@ namespace starter.viewmodel.settings | |||
| this.RaisePropertyChanged("UpdateInfo"); | |||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||
| } | |||
| this.RaisePropertyChanged("UpdatePlanned"); | |||
| } | |||
| } | |||
| @@ -252,6 +301,7 @@ namespace starter.viewmodel.settings | |||
| this.RaisePropertyChanged("LaunchVis"); | |||
| this.RaisePropertyChanged("NewUserVis"); | |||
| this.RaisePropertyChanged("ConfirmBtnCont"); | |||
| this.RaisePropertyChanged("ProcessingIntro"); | |||
| } | |||
| } | |||
| public string Intro | |||
| @@ -293,6 +343,21 @@ namespace starter.viewmodel.settings | |||
| } | |||
| } | |||
| } | |||
| public string ProcessingIntro | |||
| { | |||
| get | |||
| { | |||
| switch (Status) | |||
| { | |||
| case SettingsModel.Status.working: | |||
| return "正在下载"; | |||
| case SettingsModel.Status.initializing: | |||
| return "正在检查更新"; | |||
| default: | |||
| return ""; | |||
| } | |||
| } | |||
| } | |||
| public string AbortOrSelLanguage | |||
| { | |||
| get | |||
| @@ -448,7 +513,7 @@ namespace starter.viewmodel.settings | |||
| { | |||
| get | |||
| { | |||
| return Status == SettingsModel.Status.working ? Visibility.Visible : Visibility.Collapsed; | |||
| return (Status == SettingsModel.Status.working || Status == SettingsModel.Status.initializing) ? Visibility.Visible : Visibility.Collapsed; | |||
| } | |||
| } | |||
| public Visibility CompleteVis | |||
| @@ -503,6 +568,13 @@ namespace starter.viewmodel.settings | |||
| return obj.status == SettingsModel.Status.login && (!obj.UpdatePlanned) ? Visibility.Visible : Visibility.Collapsed; | |||
| } | |||
| } | |||
| public Visibility CfgVis | |||
| { | |||
| get | |||
| { | |||
| return obj.status == SettingsModel.Status.launch ? Visibility.Visible : Visibility.Collapsed; | |||
| } | |||
| } | |||
| public string UpdateBtnCont | |||
| { | |||
| @@ -529,9 +601,9 @@ namespace starter.viewmodel.settings | |||
| if (obj.UpdatePlanned) | |||
| ans = "更新"; | |||
| else if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||
| ans = "启动c++包"; | |||
| ans = "启动选手包"; | |||
| else | |||
| ans = "启动python包"; | |||
| ans = "启动选手包"; | |||
| return ans; | |||
| } | |||
| } | |||
| @@ -565,6 +637,11 @@ namespace starter.viewmodel.settings | |||
| } | |||
| } | |||
| public bool UpdatePlanned | |||
| { | |||
| get { return obj.UpdatePlanned; } | |||
| } | |||
| public string RouteSelectWindow(string type) | |||
| { | |||
| if (type == "Folder") | |||
| @@ -714,6 +791,7 @@ namespace starter.viewmodel.settings | |||
| this.RaisePropertyChanged("UpdateBtnCont"); | |||
| this.RaisePropertyChanged("UpdateInfo"); | |||
| this.RaisePropertyChanged("LaunchVis"); | |||
| this.RaisePropertyChanged("UpdatePlanned"); | |||
| } | |||
| })); | |||
| } | |||
| @@ -15,7 +15,7 @@ | |||
| <ItemGroup> | |||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | |||
| <PackageReference Include="FrameRateTask" Version="1.2.0" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.4" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| </ItemGroup> | |||
| @@ -58,6 +58,7 @@ namespace Client | |||
| listOfDoor = new List<MessageOfDoor>(); | |||
| listOfGate = new List<MessageOfGate>(); | |||
| listOfHiddenGate = new List<MessageOfHiddenGate>(); | |||
| countList = new List<int>(); | |||
| WindowStartupLocation = WindowStartupLocation.CenterScreen; | |||
| unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50; | |||
| unitFontsize = unit / 13; | |||
| @@ -112,7 +113,7 @@ namespace Client | |||
| return; | |||
| } | |||
| _ = Parser.Default.ParseArguments<ArgumentOptions>(args).WithParsed(o => | |||
| { options = o; }); | |||
| { options = o; }); | |||
| if (options != null && Convert.ToInt64(options.PlayerID) > 2023) | |||
| { | |||
| isSpectatorMode = true; | |||
| @@ -159,7 +160,7 @@ namespace Client | |||
| { | |||
| var pbClient = new PlaybackClient(fileName, pbSpeed); | |||
| int[,]? map; | |||
| if ((map = pbClient.ReadDataFromFile(listOfProp, listOfHuman, listOfButcher, listOfBullet, listOfBombedBullet, listOfAll, listOfChest, listOfClassroom, listOfDoor, listOfHiddenGate, listOfGate, drawPicLock)) != null) | |||
| if ((map = pbClient.ReadDataFromFile(listOfProp, listOfHuman, listOfButcher, listOfBullet, listOfBombedBullet, listOfAll, listOfChest, listOfClassroom, listOfDoor, listOfHiddenGate, listOfGate, drawPicLock, countList)) != null) | |||
| { | |||
| isClientStocked = false; | |||
| PorC.Content = "⏸"; | |||
| @@ -311,6 +312,15 @@ namespace Client | |||
| private void DrawMap() | |||
| { | |||
| classroomArray = new TextBlock[countList[0]]; | |||
| doorArray = new TextBlock[countList[1]]; | |||
| chestArray = new TextBlock[countList[2]]; | |||
| gateArray = new TextBlock[countList[3]]; | |||
| classroomPositionIndex = new int[countList[0]]; | |||
| doorPositionIndex = new int[countList[1]]; | |||
| chestPositionIndex = new int[countList[2]]; | |||
| gatePositionIndex = new int[countList[3]]; | |||
| int cntOfClassroom = 0, cntOfDoor = 0, cntOfGate = 0, cntOfChest = 0; | |||
| for (int i = 0; i < defaultMap.GetLength(0); i++) | |||
| { | |||
| for (int j = 0; j < defaultMap.GetLength(1); j++) | |||
| @@ -336,10 +346,38 @@ namespace Client | |||
| case 8: | |||
| mapPatches[i, j].Fill = Brushes.LightPink; | |||
| mapPatches[i, j].Stroke = Brushes.LightPink; | |||
| break;//machine | |||
| classroomPositionIndex[cntOfClassroom] = 50 * i + j; | |||
| classroomArray[cntOfClassroom] = new TextBlock() | |||
| { | |||
| FontSize = 8 * unitFontsize,// | |||
| Width = unitWidth,// | |||
| Height = unitHeight,// | |||
| Text = Convert.ToString(-1),// | |||
| TextAlignment = TextAlignment.Center, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(j * unitWidth / 1000.0 - unitWidth / 2, i * unitHeight / 1000.0 - unitHeight / 2, 0, 0),// | |||
| Background = Brushes.Transparent, | |||
| }; | |||
| ++cntOfClassroom; | |||
| break;//classroom | |||
| case 9: | |||
| mapPatches[i, j].Fill = Brushes.LightSkyBlue; | |||
| mapPatches[i, j].Stroke = Brushes.LightSkyBlue; | |||
| gatePositionIndex[cntOfGate] = 50 * i + j; | |||
| gateArray[cntOfGate] = new TextBlock() | |||
| { | |||
| FontSize = 8 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString(-1), | |||
| TextAlignment = TextAlignment.Center, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(j * unitWidth / 1000.0 - unitWidth / 2, i * unitHeight / 1000.0 - unitHeight / 2, 0, 0), | |||
| Background = Brushes.Transparent, | |||
| }; | |||
| ++cntOfGate; | |||
| break;//gate | |||
| case 10: | |||
| break;//emergency | |||
| @@ -352,10 +390,38 @@ namespace Client | |||
| case 14: | |||
| mapPatches[i, j].Fill = Brushes.Khaki; | |||
| mapPatches[i, j].Stroke = Brushes.Khaki; | |||
| doorPositionIndex[cntOfDoor] = 50 * i + j; | |||
| doorArray[cntOfDoor] = new TextBlock() | |||
| { | |||
| FontSize = 9 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString(-1), | |||
| TextAlignment = TextAlignment.Center, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(j * unitWidth / 1000.0 - unitWidth / 2, i * unitHeight / 1000.0 - unitHeight / 2, 0, 0), | |||
| Background = Brushes.Transparent, | |||
| }; | |||
| ++cntOfDoor; | |||
| break;//door | |||
| case 15: | |||
| mapPatches[i, j].Fill = Brushes.Orange; | |||
| mapPatches[i, j].Stroke = Brushes.Orange; | |||
| chestPositionIndex[cntOfChest] = 50 * i + j; | |||
| chestArray[cntOfChest] = new TextBlock() | |||
| { | |||
| FontSize = 8 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString(-1), | |||
| TextAlignment = TextAlignment.Center, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(j * unitWidth / 1000.0 - unitWidth / 2, i * unitHeight / 1000.0 - unitHeight / 2, 0, 0), | |||
| Background = Brushes.Transparent, | |||
| }; | |||
| ++cntOfChest; | |||
| break;//chest | |||
| default: | |||
| break; | |||
| @@ -416,6 +482,8 @@ namespace Client | |||
| listOfHiddenGate.Clear(); | |||
| listOfGate.Clear(); | |||
| MessageToClient content = responseStream.ResponseStream.Current; | |||
| MessageOfMap mapMessage = new MessageOfMap(); | |||
| bool mapMessageExist = false; | |||
| switch (content.GameState) | |||
| { | |||
| case GameState.GameStart: | |||
| @@ -459,11 +527,17 @@ namespace Client | |||
| listOfGate.Add(obj.GateMessage); | |||
| break; | |||
| case MessageOfObj.MessageOfObjOneofCase.MapMessage: | |||
| GetMap(obj.MapMessage); | |||
| mapMessage = obj.MapMessage; | |||
| break; | |||
| } | |||
| } | |||
| listOfAll.Add(content.AllMessage); | |||
| countList.Clear(); | |||
| countList.Add(listOfClassroom.Count); | |||
| countList.Add(listOfDoor.Count); | |||
| countList.Add(listOfChest.Count); | |||
| countList.Add(listOfGate.Count); | |||
| GetMap(mapMessage); | |||
| break; | |||
| case GameState.GameRunning: | |||
| foreach (var obj in content.ObjMessage) | |||
| @@ -509,11 +583,22 @@ namespace Client | |||
| listOfHiddenGate.Add(obj.HiddenGateMessage); | |||
| break; | |||
| case MessageOfObj.MessageOfObjOneofCase.MapMessage: | |||
| GetMap(obj.MapMessage); | |||
| mapMessage = obj.MapMessage; | |||
| mapMessageExist = true;//只有中间加入游戏的旁观者着一种可能,使得在这里收到地图 | |||
| break; | |||
| } | |||
| } | |||
| listOfAll.Add(content.AllMessage); | |||
| if (mapMessageExist) | |||
| { | |||
| countList.Clear(); | |||
| countList.Add(listOfClassroom.Count); | |||
| countList.Add(listOfDoor.Count); | |||
| countList.Add(listOfChest.Count); | |||
| countList.Add(listOfGate.Count); | |||
| GetMap(mapMessage); | |||
| mapMessageExist = false; | |||
| } | |||
| break; | |||
| case GameState.GameEnd: | |||
| MessageBox.Show("Game Over!"); | |||
| @@ -522,9 +607,17 @@ namespace Client | |||
| switch (obj.MessageOfObjCase) | |||
| { | |||
| case MessageOfObj.MessageOfObjOneofCase.StudentMessage: | |||
| if (humanOrButcher && obj.StudentMessage.PlayerId == playerID) | |||
| { | |||
| human = obj.StudentMessage; | |||
| } | |||
| listOfHuman.Add(obj.StudentMessage); | |||
| break; | |||
| case MessageOfObj.MessageOfObjOneofCase.TrickerMessage: | |||
| if (!humanOrButcher && obj.TrickerMessage.PlayerId == playerID) | |||
| { | |||
| butcher = obj.TrickerMessage; | |||
| } | |||
| listOfButcher.Add(obj.TrickerMessage); | |||
| break; | |||
| case MessageOfObj.MessageOfObjOneofCase.PropMessage: | |||
| @@ -574,94 +667,94 @@ namespace Client | |||
| { | |||
| if (msg.PlayerState == PlayerState.Quit || msg.PlayerState == PlayerState.Graduated) | |||
| return false; | |||
| if (isSpectatorMode || isPlaybackMode) | |||
| return true; | |||
| if (humanOrButcher && human != null) | |||
| { | |||
| if (msg.Place == human.Place) | |||
| return true; | |||
| if (msg.PlayerId == playerID + Preparation.Utility.GameData.numOfPeople)//robot and its owner | |||
| return true; | |||
| } | |||
| else if (!humanOrButcher && butcher != null) | |||
| { | |||
| if (msg.Place == butcher.Place) | |||
| return true; | |||
| } | |||
| if (msg.Place == Protobuf.PlaceType.Grass) | |||
| return false; | |||
| //if (isSpectatorMode || isPlaybackMode) | |||
| // return true; | |||
| //if (humanOrButcher && human != null) | |||
| //{ | |||
| // if (msg.Place == human.Place) | |||
| // return true; | |||
| // if (msg.PlayerId == playerID + Preparation.Utility.GameData.numOfPeople)//robot and its owner | |||
| // return true; | |||
| //} | |||
| //else if (!humanOrButcher && butcher != null) | |||
| //{ | |||
| // if (msg.Place == butcher.Place) | |||
| // return true; | |||
| //} | |||
| //if (msg.Place == Protobuf.PlaceType.Grass) | |||
| // return false; | |||
| return true; | |||
| } | |||
| private bool CanSee(MessageOfTricker msg) | |||
| { | |||
| if (isSpectatorMode || isPlaybackMode) | |||
| return true; | |||
| if (!humanOrButcher && butcher != null) | |||
| { | |||
| if (butcher.Guid == msg.Guid) // 自己能看见自己 | |||
| return true; | |||
| } | |||
| if (humanOrButcher && human != null) | |||
| { | |||
| if (msg.TrickerType == Protobuf.TrickerType.Assassin) | |||
| { | |||
| foreach (var buff in msg.Buff) | |||
| { | |||
| if (buff == Protobuf.TrickerBuffType.TrickerInvisible) | |||
| return false; | |||
| } | |||
| } | |||
| if (msg.Place == human.Place) | |||
| return true; | |||
| } | |||
| if (msg.Place == Protobuf.PlaceType.Grass) | |||
| return false; | |||
| //if (isSpectatorMode || isPlaybackMode) | |||
| // return true; | |||
| //if (!humanOrButcher && butcher != null) | |||
| //{ | |||
| // if (butcher.Guid == msg.Guid) // 自己能看见自己 | |||
| // return true; | |||
| //} | |||
| //if (humanOrButcher && human != null) | |||
| //{ | |||
| // if (msg.TrickerType == Protobuf.TrickerType.Assassin) | |||
| // { | |||
| // foreach (var buff in msg.Buff) | |||
| // { | |||
| // if (buff == Protobuf.TrickerBuffType.TrickerInvisible) | |||
| // return false; | |||
| // } | |||
| // } | |||
| // if (msg.Place == human.Place) | |||
| // return true; | |||
| //} | |||
| //if (msg.Place == Protobuf.PlaceType.Grass) | |||
| // return false; | |||
| return true; | |||
| } | |||
| private bool CanSee(MessageOfProp msg) | |||
| { | |||
| if (isSpectatorMode || isPlaybackMode) | |||
| return true; | |||
| if (humanOrButcher && human != null) | |||
| { | |||
| if (msg.Place == human.Place) | |||
| return true; | |||
| } | |||
| else if (!humanOrButcher && butcher != null) | |||
| { | |||
| if (msg.Place == butcher.Place) | |||
| return true; | |||
| } | |||
| if (msg.Place == Protobuf.PlaceType.Grass) | |||
| return false; | |||
| //if (isSpectatorMode || isPlaybackMode) | |||
| // return true; | |||
| //if (humanOrButcher && human != null) | |||
| //{ | |||
| // if (msg.Place == human.Place) | |||
| // return true; | |||
| //} | |||
| //else if (!humanOrButcher && butcher != null) | |||
| //{ | |||
| // if (msg.Place == butcher.Place) | |||
| // return true; | |||
| //} | |||
| //if (msg.Place == Protobuf.PlaceType.Grass) | |||
| // return false; | |||
| return true; | |||
| } | |||
| private bool CanSee(MessageOfBullet msg) | |||
| { | |||
| if (isSpectatorMode || isPlaybackMode) | |||
| return true; | |||
| if (humanOrButcher && human != null) | |||
| { | |||
| if (msg.Place == human.Place) | |||
| return true; | |||
| } | |||
| else if (!humanOrButcher && butcher != null) | |||
| { | |||
| if (msg.Place == butcher.Place) | |||
| return true; | |||
| } | |||
| if (msg.Place == Protobuf.PlaceType.Grass) | |||
| return false; | |||
| //if (isSpectatorMode || isPlaybackMode) | |||
| // return true; | |||
| //if (humanOrButcher && human != null) | |||
| //{ | |||
| // if (msg.Place == human.Place) | |||
| // return true; | |||
| //} | |||
| //else if (!humanOrButcher && butcher != null) | |||
| //{ | |||
| // if (msg.Place == butcher.Place) | |||
| // return true; | |||
| //} | |||
| //if (msg.Place == Protobuf.PlaceType.Grass) | |||
| // return false; | |||
| return true; | |||
| } | |||
| private bool CanSee(MessageOfBombedBullet msg) | |||
| { | |||
| if (isSpectatorMode || isPlaybackMode) | |||
| return true; | |||
| //if (isSpectatorMode || isPlaybackMode) | |||
| // return true; | |||
| //if (humanOrButcher && human != null) | |||
| //{ | |||
| // if (msg.Place == human.Place) | |||
| @@ -677,65 +770,109 @@ namespace Client | |||
| return true; | |||
| } | |||
| private int FindIndexOfClassroom(MessageOfClassroom msg) | |||
| { | |||
| for (int i = 0; i < classroomPositionIndex.Length; ++i) | |||
| { | |||
| int k = msg.X / 1000 * 50 + msg.Y / 1000; | |||
| if (k == classroomPositionIndex[i]) | |||
| return i; | |||
| } | |||
| return -1; | |||
| } | |||
| private int FindIndexOfGate(MessageOfGate msg) | |||
| { | |||
| for (int i = 0; i < gatePositionIndex.Length; ++i) | |||
| { | |||
| int k = msg.X / 1000 * 50 + msg.Y / 1000; | |||
| if (k == gatePositionIndex[i]) | |||
| return i; | |||
| } | |||
| return -1; | |||
| } | |||
| private int FindIndexOfDoor(MessageOfDoor msg) | |||
| { | |||
| for (int i = 0; i < doorPositionIndex.Length; ++i) | |||
| { | |||
| int k = msg.X / 1000 * 50 + msg.Y / 1000; | |||
| if (k == doorPositionIndex[i]) | |||
| return i; | |||
| } | |||
| return -1; | |||
| } | |||
| private int FindIndexOfChest(MessageOfChest msg) | |||
| { | |||
| for (int i = 0; i < chestPositionIndex.Length; ++i) | |||
| { | |||
| int k = msg.X / 1000 * 50 + msg.Y / 1000; | |||
| if (k == chestPositionIndex[i]) | |||
| return i; | |||
| } | |||
| return -1; | |||
| } | |||
| private void Refresh(object? sender, EventArgs e) //log未更新 | |||
| { | |||
| lock (drawPicLock) // 加锁是必要的,画图操作和接收信息操作不能同时进行 | |||
| try | |||
| { | |||
| // Bonus(); | |||
| if (WindowState == WindowState.Maximized) | |||
| MaxButton.Content = "❐"; | |||
| else | |||
| MaxButton.Content = "🗖"; | |||
| foreach (var obj in listOfHuman) | |||
| lock (drawPicLock) // 加锁是必要的,画图操作和接收信息操作不能同时进行 | |||
| { | |||
| if (!isDataFixed[obj.PlayerId] && obj.PlayerId < GameData.numOfStudent && obj.StudentType != StudentType.Robot) | |||
| // Bonus(); | |||
| if (WindowState == WindowState.Maximized) | |||
| MaxButton.Content = "❐"; | |||
| else | |||
| MaxButton.Content = "🗖"; | |||
| foreach (var obj in listOfHuman) | |||
| { | |||
| IStudentType occupation = (IStudentType)OccupationFactory.FindIOccupation(Transformation.ToStudentType(obj.StudentType)); | |||
| totalLife[obj.PlayerId] = occupation.MaxHp; | |||
| totalDeath[obj.PlayerId] = occupation.MaxGamingAddiction; | |||
| int i = 0; | |||
| foreach (var skill in occupation.ListOfIActiveSkill) | |||
| if (obj.PlayerId < GameData.numOfStudent && !isDataFixed[obj.PlayerId]) | |||
| { | |||
| var iActiveSkill = SkillFactory.FindIActiveSkill(skill); | |||
| coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD; | |||
| ++i; | |||
| IStudentType occupation = (IStudentType)OccupationFactory.FindIOccupation(Transformation.ToStudentType(obj.StudentType)); | |||
| totalLife[obj.PlayerId] = occupation.MaxHp; | |||
| totalDeath[obj.PlayerId] = occupation.MaxGamingAddiction; | |||
| int i = 0; | |||
| foreach (var skill in occupation.ListOfIActiveSkill) | |||
| { | |||
| var iActiveSkill = SkillFactory.FindActiveSkill(skill); | |||
| coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD; | |||
| ++i; | |||
| } | |||
| isDataFixed[obj.PlayerId] = true; | |||
| } | |||
| isDataFixed[obj.PlayerId] = true; | |||
| } | |||
| } | |||
| foreach (var obj in listOfButcher) | |||
| { | |||
| if (!isDataFixed[obj.PlayerId]) | |||
| foreach (var obj in listOfButcher) | |||
| { | |||
| IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType)); | |||
| int j = 0; | |||
| foreach (var skill in occupation1.ListOfIActiveSkill) | |||
| if (!isDataFixed[obj.PlayerId]) | |||
| { | |||
| var iActiveSkill = SkillFactory.FindIActiveSkill(skill); | |||
| coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD; | |||
| ++j; | |||
| IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType)); | |||
| int j = 0; | |||
| foreach (var skill in occupation1.ListOfIActiveSkill) | |||
| { | |||
| var iActiveSkill = SkillFactory.FindActiveSkill(skill); | |||
| coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD; | |||
| ++j; | |||
| } | |||
| isDataFixed[obj.PlayerId] = true; | |||
| } | |||
| isDataFixed[obj.PlayerId] = true; | |||
| } | |||
| } | |||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||
| { | |||
| StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime); | |||
| } | |||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||
| { | |||
| StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime); | |||
| } | |||
| StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime); | |||
| StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime); | |||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||
| { | |||
| StatusBarsOfSurvivor[i].SetFontSize(12 * unitFontsize); | |||
| } | |||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||
| { | |||
| StatusBarsOfSurvivor[i].SetFontSize(12 * unitFontsize); | |||
| } | |||
| StatusBarsOfHunter.SetFontSize(12 * unitFontsize); | |||
| StatusBarsOfCircumstance.SetFontSize(12 * unitFontsize); | |||
| if (!isClientStocked) | |||
| { | |||
| try | |||
| StatusBarsOfHunter.SetFontSize(12 * unitFontsize); | |||
| StatusBarsOfCircumstance.SetFontSize(12 * unitFontsize); | |||
| if (!isClientStocked) | |||
| { | |||
| UpperLayerOfMap.Children.Clear(); | |||
| foreach (var data in listOfAll) | |||
| @@ -748,7 +885,7 @@ namespace Client | |||
| } | |||
| foreach (var data in listOfHuman) | |||
| { | |||
| if (data.StudentType != StudentType.Robot) | |||
| if (data.PlayerId < GameData.numOfStudent) | |||
| StatusBarsOfSurvivor[data.PlayerId].SetValue(data, data.PlayerId); | |||
| if (CanSee(data)) | |||
| { | |||
| @@ -763,22 +900,21 @@ namespace Client | |||
| }; | |||
| if (data.StudentType == StudentType.Robot) | |||
| icon.Fill = Brushes.Gray; | |||
| TextBox num = new() | |||
| TextBlock num = new() | |||
| { | |||
| FontSize = 7 * unitFontsize, | |||
| Width = 2 * radiusTimes * unitWidth, | |||
| Height = 2 * radiusTimes * unitHeight, | |||
| Text = Convert.ToString(data.PlayerId), | |||
| TextAlignment = TextAlignment.Center, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth * radiusTimes, data.X * unitHeight / 1000.0 - unitHeight * radiusTimes, 0, 0), | |||
| Background = Brushes.Transparent, | |||
| BorderBrush = Brushes.Transparent, | |||
| IsReadOnly = true, | |||
| Foreground = Brushes.White, | |||
| }; | |||
| if (data.StudentType == StudentType.Robot) | |||
| num.Text = Convert.ToString(data.PlayerId - Preparation.Utility.GameData.numOfPeople); | |||
| //if (data.StudentType == StudentType.Robot) | |||
| // num.Text = Convert.ToString(data.PlayerId - Preparation.Utility.GameData.numOfPeople); | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| UpperLayerOfMap.Children.Add(num); | |||
| } | |||
| @@ -830,6 +966,9 @@ namespace Client | |||
| case Protobuf.PropType.RecoveryFromDizziness: | |||
| DrawProp(data, "🕶"); | |||
| break; | |||
| case Protobuf.PropType.CraftingBench: | |||
| DrawProp(data, "🎰"); | |||
| break; | |||
| default: | |||
| DrawProp(data, ""); | |||
| break; | |||
| @@ -857,6 +996,7 @@ namespace Client | |||
| case Protobuf.BulletType.CommonAttackOfTricker: | |||
| case Protobuf.BulletType.BombBomb: | |||
| case Protobuf.BulletType.JumpyDumpty: | |||
| case Protobuf.BulletType.Strike: | |||
| icon.Fill = Brushes.Red; | |||
| break; | |||
| default: | |||
| @@ -901,12 +1041,6 @@ namespace Client | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| break; | |||
| } | |||
| //case Protobuf.BulletType.LineBullet: | |||
| // { | |||
| // double bombRange = data.BombRange / 1000; | |||
| // DrawLaser(new Point(data.Y * unitWidth / 1000.0, data.X * unitHeight / 1000.0), -data.FacingDirection + Math.PI / 2, bombRange * unitHeight, 0.5 * unitWidth); | |||
| // break; | |||
| // } | |||
| default: | |||
| break; | |||
| } | |||
| @@ -915,93 +1049,65 @@ namespace Client | |||
| foreach (var data in listOfClassroom) | |||
| { | |||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfFixedGenerator); | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 8 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString(deg), | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0), | |||
| Background = Brushes.Transparent, | |||
| BorderBrush = Brushes.Transparent, | |||
| IsReadOnly = true | |||
| }; | |||
| int idx = FindIndexOfClassroom(data); | |||
| classroomArray[idx].FontSize = 8 * unitFontsize; | |||
| classroomArray[idx].Width = unitWidth; | |||
| classroomArray[idx].Height = unitHeight; | |||
| classroomArray[idx].Text = Convert.ToString(deg); | |||
| classroomArray[idx].Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0); | |||
| if (deg == 100) | |||
| { | |||
| icon.Text = "A+"; | |||
| classroomArray[idx].Text = "A+"; | |||
| } | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| UpperLayerOfMap.Children.Add(classroomArray[idx]); | |||
| } | |||
| foreach (var data in listOfChest) | |||
| { | |||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedChest); | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 8 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString(deg), | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0), | |||
| Background = Brushes.Transparent, | |||
| BorderBrush = Brushes.Transparent, | |||
| IsReadOnly = true | |||
| }; | |||
| int idx = FindIndexOfChest(data); | |||
| chestArray[idx].FontSize = 8 * unitFontsize; | |||
| chestArray[idx].Width = unitWidth; | |||
| chestArray[idx].Height = unitHeight; | |||
| chestArray[idx].Text = Convert.ToString(deg); | |||
| chestArray[idx].Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0); | |||
| if (deg == 100) | |||
| { | |||
| icon.Text = "Ø"; | |||
| chestArray[idx].Text = "Ø"; | |||
| } | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| UpperLayerOfMap.Children.Add(chestArray[idx]); | |||
| } | |||
| foreach (var data in listOfGate) | |||
| { | |||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedDoorway); | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 8 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString(deg), | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0), | |||
| Background = Brushes.Transparent, | |||
| BorderBrush = Brushes.Transparent, | |||
| IsReadOnly = true | |||
| }; | |||
| int idx = FindIndexOfGate(data); | |||
| gateArray[idx].FontSize = 8 * unitFontsize; | |||
| gateArray[idx].Width = unitWidth; | |||
| gateArray[idx].Height = unitHeight; | |||
| gateArray[idx].Text = Convert.ToString(deg); | |||
| gateArray[idx].Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0); | |||
| if (deg == 100) | |||
| { | |||
| gateOpened = true; | |||
| icon.Text = "🔓"; | |||
| gateArray[idx].Text = "🔓"; | |||
| } | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| UpperLayerOfMap.Children.Add(gateArray[idx]); | |||
| } | |||
| foreach (var data in listOfDoor) | |||
| { | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 9 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0), | |||
| Background = Brushes.Transparent, | |||
| BorderBrush = Brushes.Transparent, | |||
| IsReadOnly = true | |||
| }; | |||
| int idx = FindIndexOfDoor(data); | |||
| doorArray[idx].FontSize = 9 * unitFontsize; | |||
| doorArray[idx].Width = unitWidth; | |||
| doorArray[idx].Height = unitHeight; | |||
| doorArray[idx].Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0); | |||
| if (data.IsOpen) | |||
| { | |||
| icon.Text = Convert.ToString("🔓"); | |||
| doorArray[idx].Text = Convert.ToString("🔓"); | |||
| } | |||
| else | |||
| { | |||
| icon.Text = Convert.ToString("🔒"); | |||
| doorArray[idx].Text = Convert.ToString("🔒"); | |||
| } | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| UpperLayerOfMap.Children.Add(doorArray[idx]); | |||
| } | |||
| foreach (var data in listOfHiddenGate) | |||
| { | |||
| @@ -1014,32 +1120,31 @@ namespace Client | |||
| if (data.Opened) | |||
| { | |||
| isEmergencyOpened = true; | |||
| TextBox icon = new() | |||
| hiddenGateArray = new TextBlock() | |||
| { | |||
| FontSize = 9 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString("🔓"), | |||
| TextAlignment = TextAlignment.Center, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0), | |||
| Background = Brushes.Transparent, | |||
| BorderBrush = Brushes.Transparent, | |||
| IsReadOnly = true | |||
| }; | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| UpperLayerOfMap.Children.Add(hiddenGateArray); | |||
| } | |||
| } | |||
| } | |||
| catch (Exception exc) | |||
| { | |||
| ErrorDisplayer error = new("Error: " + exc.ToString()); | |||
| error.Show(); | |||
| isClientStocked = true; | |||
| PorC.Content = "▶"; | |||
| } | |||
| counter++; | |||
| } | |||
| counter++; | |||
| } | |||
| catch (Exception exc) | |||
| { | |||
| ErrorDisplayer error = new("Error: " + exc.ToString()); | |||
| error.Show(); | |||
| isClientStocked = true; | |||
| PorC.Content = "▶"; | |||
| } | |||
| } | |||
| @@ -1204,6 +1309,7 @@ namespace Client | |||
| { | |||
| PlayerId = playerID, | |||
| SkillId = 0, | |||
| SkillParam = 0, | |||
| }; | |||
| client.UseSkill(msgB); | |||
| break; | |||
| @@ -1416,6 +1522,18 @@ namespace Client | |||
| private List<MessageOfDoor> listOfDoor; | |||
| private List<MessageOfGate> listOfGate; | |||
| private List<MessageOfHiddenGate> listOfHiddenGate; | |||
| private TextBlock[] classroomArray; | |||
| private int[] classroomPositionIndex; | |||
| private TextBlock[] chestArray; | |||
| private int[] chestPositionIndex; | |||
| private TextBlock[] doorArray; | |||
| private int[] doorPositionIndex; | |||
| private TextBlock[] gateArray; | |||
| private int[] gatePositionIndex; | |||
| private TextBlock hiddenGateArray;//make a map from the position of icons to the index | |||
| private List<int> countList; | |||
| private object drawPicLock = new object(); | |||
| private MessageOfStudent? human = null; | |||
| private MessageOfTricker? butcher = null; | |||
| @@ -1489,4 +1607,4 @@ namespace Client | |||
| private int[] totalLife = new int[4] { 100, 100, 100, 100 }, totalDeath = new int[4] { 100, 100, 100, 100 }; | |||
| private int[,] coolTime = new int[3, 5] { { 100, 100, 100, 100, 100 }, { 100, 100, 100, 100, 100 }, { 100, 100, 100, 100, 100 } }; | |||
| } | |||
| } | |||
| } | |||
| @@ -39,7 +39,7 @@ namespace Client | |||
| public int[,]? ReadDataFromFile(List<MessageOfProp> listOfProp, List<MessageOfStudent> listOfHuman, List<MessageOfTricker> listOfButcher, List<MessageOfBullet> listOfBullet, | |||
| List<MessageOfBombedBullet> listOfBombedBullet, List<MessageOfAll> listOfAll, List<MessageOfChest> listOfChest, List<MessageOfClassroom> listOfClassroom, | |||
| List<MessageOfDoor> listOfDoor, List<MessageOfHiddenGate> listOfHiddenGate, List<MessageOfGate> listOfGate, object dataLock) | |||
| List<MessageOfDoor> listOfDoor, List<MessageOfHiddenGate> listOfHiddenGate, List<MessageOfGate> listOfGate, object? dataLock, List<int> countList) | |||
| { | |||
| if (Reader == null) | |||
| return null; | |||
| @@ -158,6 +158,10 @@ namespace Client | |||
| break; | |||
| } | |||
| } | |||
| countList.Add(listOfClassroom.Count); | |||
| countList.Add(listOfDoor.Count); | |||
| countList.Add(listOfChest.Count); | |||
| countList.Add(listOfGate.Count); | |||
| listOfAll.Add(content.AllMessage); | |||
| break; | |||
| case GameState.GameRunning: | |||
| @@ -270,4 +274,4 @@ namespace Client | |||
| return map; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -2,7 +2,7 @@ | |||
| "profiles": { | |||
| "Client": { | |||
| "commandName": "Project", | |||
| "commandLineArgs": "--port 8892 --characterID 8880 --type 1 --occupation 1 --ip thuai6.eesast.com --cl" | |||
| "commandLineArgs": "--port 8888 --characterID 1 --type 1 --occupation 3" | |||
| } | |||
| } | |||
| } | |||
| @@ -169,6 +169,9 @@ namespace Client | |||
| case Protobuf.PropType.RecoveryFromDizziness: | |||
| prop0.Text = "🕶"; | |||
| break; | |||
| case Protobuf.PropType.CraftingBench: | |||
| prop0.Text = "🎰"; | |||
| break; | |||
| default: | |||
| prop0.Text = ""; | |||
| break; | |||
| @@ -202,6 +205,9 @@ namespace Client | |||
| case Protobuf.PropType.RecoveryFromDizziness: | |||
| prop1.Text = "🕶"; | |||
| break; | |||
| case Protobuf.PropType.CraftingBench: | |||
| prop1.Text = "🎰"; | |||
| break; | |||
| default: | |||
| prop1.Text = ""; | |||
| break; | |||
| @@ -235,6 +241,9 @@ namespace Client | |||
| case Protobuf.PropType.RecoveryFromDizziness: | |||
| prop2.Text = "🕶"; | |||
| break; | |||
| case Protobuf.PropType.CraftingBench: | |||
| prop2.Text = "🎰"; | |||
| break; | |||
| default: | |||
| prop2.Text = ""; | |||
| break; | |||
| @@ -174,6 +174,9 @@ namespace Client | |||
| case Protobuf.PropType.RecoveryFromDizziness: | |||
| prop0.Text = "🕶"; | |||
| break; | |||
| case Protobuf.PropType.CraftingBench: | |||
| prop0.Text = "🎰"; | |||
| break; | |||
| default: | |||
| prop0.Text = ""; | |||
| break; | |||
| @@ -207,6 +210,9 @@ namespace Client | |||
| case Protobuf.PropType.RecoveryFromDizziness: | |||
| prop1.Text = "🕶"; | |||
| break; | |||
| case Protobuf.PropType.CraftingBench: | |||
| prop1.Text = "🎰"; | |||
| break; | |||
| default: | |||
| prop1.Text = ""; | |||
| break; | |||
| @@ -240,6 +246,9 @@ namespace Client | |||
| case Protobuf.PropType.RecoveryFromDizziness: | |||
| prop2.Text = "🕶"; | |||
| break; | |||
| case Protobuf.PropType.CraftingBench: | |||
| prop2.Text = "🎰"; | |||
| break; | |||
| default: | |||
| prop2.Text = ""; | |||
| break; | |||
| @@ -8,7 +8,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.4" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| </ItemGroup> | |||
| @@ -3,7 +3,7 @@ | |||
| namespace GameClass.GameObj | |||
| { | |||
| // 为方便界面组做子弹爆炸特效,现引入“爆炸中的子弹”,在每帧发送给界面组 | |||
| public sealed class BombedBullet : GameObj | |||
| public sealed class BombedBullet : Immovable | |||
| { | |||
| public override ShapeType Shape => ShapeType.Circle; | |||
| public override bool IsRigid => false; | |||
| @@ -13,10 +13,9 @@ namespace GameClass.GameObj | |||
| 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; | |||
| this.facingDirection = bullet.FacingDirection; | |||
| } | |||
| } | |||
| } | |||
| @@ -5,26 +5,17 @@ namespace GameClass.GameObj | |||
| { | |||
| internal sealed class CommonAttackOfGhost : Bullet | |||
| { | |||
| public CommonAttackOfGhost(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) : | |||
| base(player, radius, placeType, pos) | |||
| public CommonAttackOfGhost(Character player, XY pos, int radius = GameData.bulletRadius) : | |||
| base(player, radius, pos) | |||
| { | |||
| ap = GameData.basicApOfGhost; | |||
| } | |||
| public override double BulletBombRange => 0; | |||
| public override double BulletAttackRange => GameData.basicAttackShortRange; | |||
| public int ap = GameData.basicApOfGhost; | |||
| public override int AP | |||
| { | |||
| get => ap; | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| ap = value; | |||
| } | |||
| } | |||
| public override double AttackDistance => GameData.basicAttackShortRange; | |||
| public override int Speed => GameData.basicBulletMoveSpeed; | |||
| public override bool IsRemoteAttack => false; | |||
| public override int CastTime => (int)BulletAttackRange * 1000 / Speed; | |||
| public override int CastTime => (int)AttackDistance * 1000 / Speed; | |||
| public override int Backswing => GameData.basicBackswing; | |||
| public override int RecoveryFromHit => GameData.basicRecoveryFromHit; | |||
| public const int cd = GameData.basicBackswing; | |||
| @@ -41,7 +32,6 @@ namespace GameClass.GameObj | |||
| switch (gameObjType) | |||
| { | |||
| case GameObjType.Character: | |||
| case GameObjType.Generator: | |||
| return true; | |||
| default: | |||
| return false; | |||
| @@ -50,31 +40,62 @@ namespace GameClass.GameObj | |||
| public override BulletType TypeOfBullet => BulletType.CommonAttackOfGhost; | |||
| } | |||
| internal sealed class FlyingKnife : Bullet | |||
| internal sealed class Strike : Bullet | |||
| { | |||
| public FlyingKnife(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) : | |||
| base(player, radius, placeType, pos) | |||
| public Strike(Character player, XY pos, int radius = GameData.bulletRadius) : | |||
| base(player, radius, pos) | |||
| { | |||
| ap = GameData.basicApOfGhost * 16 / 15; | |||
| } | |||
| public override double BulletBombRange => 0; | |||
| public override double BulletAttackRange => GameData.basicRemoteAttackRange * 13; | |||
| public int ap = GameData.basicApOfGhost * 4 / 5; | |||
| public override int AP | |||
| public override double AttackDistance => GameData.basicAttackShortRange * 20 / 22; | |||
| public override int Speed => GameData.basicBulletMoveSpeed * 625 / 740; | |||
| public override bool IsRemoteAttack => false; | |||
| public override int CastTime => (int)AttackDistance * 1000 / Speed; | |||
| public override int Backswing => GameData.basicBackswing; | |||
| public override int RecoveryFromHit => GameData.basicRecoveryFromHit; | |||
| public const int cd = GameData.basicBackswing; | |||
| public override int CD => cd; | |||
| public const int maxBulletNum = 1; | |||
| public override int MaxBulletNum => maxBulletNum; | |||
| public override bool CanAttack(GameObj target) | |||
| { | |||
| get => ap; | |||
| set | |||
| return false; | |||
| } | |||
| public override bool CanBeBombed(GameObjType gameObjType) | |||
| { | |||
| switch (gameObjType) | |||
| { | |||
| lock (gameObjLock) | |||
| ap = value; | |||
| case GameObjType.Character: | |||
| case GameObjType.Generator: | |||
| return true; | |||
| default: | |||
| return false; | |||
| } | |||
| } | |||
| public override BulletType TypeOfBullet => BulletType.Strike; | |||
| } | |||
| internal sealed class FlyingKnife : Bullet | |||
| { | |||
| public FlyingKnife(Character player, XY pos, int radius = GameData.bulletRadius) : | |||
| base(player, radius, pos) | |||
| { | |||
| ap = GameData.basicApOfGhost * 4 / 5; | |||
| } | |||
| public override double BulletBombRange => 0; | |||
| public override double AttackDistance => GameData.basicRemoteAttackRange * 13; | |||
| public override int Speed => GameData.basicBulletMoveSpeed * 25 / 10; | |||
| public override bool IsRemoteAttack => true; | |||
| public override int CastTime => GameData.basicCastTime * 4 / 5; | |||
| public const int castTime = GameData.basicCastTime * 6 / 5; | |||
| public override int CastTime => castTime; | |||
| public override int Backswing => 0; | |||
| public override int RecoveryFromHit => 0; | |||
| public const int cd = GameData.basicBackswing / 2; | |||
| public const int cd = castTime; | |||
| public override int CD => cd; | |||
| public const int maxBulletNum = 1; | |||
| public override int MaxBulletNum => maxBulletNum; | |||
| @@ -100,27 +121,18 @@ namespace GameClass.GameObj | |||
| internal sealed class BombBomb : Bullet | |||
| { | |||
| public BombBomb(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) : | |||
| base(player, radius, placeType, pos) | |||
| public BombBomb(Character player, XY pos, int radius = GameData.bulletRadius) : base(player, radius, pos) | |||
| { | |||
| ap = (int)(GameData.basicApOfGhost * 6.0 / 5); | |||
| } | |||
| public override double BulletBombRange => GameData.basicBulletBombRange; | |||
| public override double BulletAttackRange => GameData.basicAttackShortRange; | |||
| public int ap = (int)(GameData.basicApOfGhost * 6.0 / 5); | |||
| public override int AP | |||
| { | |||
| get => ap; | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| ap = value; | |||
| } | |||
| } | |||
| public override double AttackDistance => GameData.basicAttackShortRange; | |||
| public override int Speed => (int)(GameData.basicBulletMoveSpeed * 30 / 37); | |||
| public override bool IsRemoteAttack => false; | |||
| public override int CastTime => (int)(BulletAttackRange * 1000 / Speed); | |||
| public override int Backswing => GameData.basicRecoveryFromHit; | |||
| public override int CastTime => (int)(AttackDistance * 1000 / Speed); | |||
| public override int Backswing => GameData.basicBackswing * 3 / 2; | |||
| public override int RecoveryFromHit => GameData.basicRecoveryFromHit; | |||
| public const int cd = GameData.basicCD; | |||
| public override int CD => cd; | |||
| @@ -136,6 +148,8 @@ namespace GameClass.GameObj | |||
| switch (gameObjType) | |||
| { | |||
| case GameObjType.Character: | |||
| case GameObjType.Generator: | |||
| case GameObjType.Door: | |||
| return true; | |||
| default: | |||
| return false; | |||
| @@ -146,22 +160,13 @@ namespace GameClass.GameObj | |||
| } | |||
| internal sealed class JumpyDumpty : Bullet | |||
| { | |||
| public JumpyDumpty(Character player, PlaceType placeType, XY pos, int radius = GameData.bulletRadius) : | |||
| base(player, radius, placeType, pos) | |||
| public JumpyDumpty(Character player, XY pos, int radius = GameData.bulletRadius) : base(player, radius, pos) | |||
| { | |||
| ap = (int)(GameData.basicApOfGhost * 0.6); | |||
| } | |||
| public override double BulletBombRange => GameData.basicBulletBombRange / 2; | |||
| public override double BulletAttackRange => GameData.basicAttackShortRange * 2; | |||
| public int ap = (int)(GameData.basicApOfGhost * 0.6); | |||
| public override int AP | |||
| { | |||
| get => ap; | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| ap = value; | |||
| } | |||
| } | |||
| public override double AttackDistance => GameData.basicAttackShortRange * 16 / 22; | |||
| public override int Speed => (int)(GameData.basicBulletMoveSpeed * 43 / 37); | |||
| public override bool IsRemoteAttack => false; | |||
| @@ -9,7 +9,7 @@ namespace GameClass.GameObj | |||
| { | |||
| } | |||
| public override double BulletBombRange => 0; | |||
| public override double BulletAttackRange => 0; | |||
| public override double AttackDistance => 0; | |||
| public override int AP => 7220; | |||
| public override int Speed => 0; | |||
| public override bool IsRemoteAttack => false; | |||
| @@ -1,6 +1,6 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| using System.Threading; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -10,8 +10,17 @@ namespace GameClass.GameObj | |||
| /// //攻击力 | |||
| /// </summary> | |||
| public abstract double BulletBombRange { get; } | |||
| public abstract double BulletAttackRange { get; } | |||
| public abstract int AP { get; set; } | |||
| public abstract double AttackDistance { get; } | |||
| protected int ap; | |||
| public int AP | |||
| { | |||
| get => Interlocked.CompareExchange(ref ap, 0, 0); | |||
| } | |||
| public void AddAP(int addAp) | |||
| { | |||
| Interlocked.Add(ref ap, addAp); | |||
| } | |||
| public abstract int Speed { get; } | |||
| public abstract bool IsRemoteAttack { get; } | |||
| public abstract int CastTime { get; } | |||
| @@ -27,7 +36,6 @@ namespace GameClass.GameObj | |||
| public bool HasSpear => hasSpear; | |||
| /// <summary> | |||
| /// 与THUAI4不同的一个攻击判定方案,通过这个函数判断爆炸时能否伤害到target | |||
| /// </summary> | |||
| /// <param name="target">被尝试攻击者</param> | |||
| /// <returns>是否可以攻击到</returns> | |||
| @@ -36,17 +44,16 @@ namespace GameClass.GameObj | |||
| public override bool IgnoreCollideExecutor(IGameObj targetObj) | |||
| { | |||
| if (targetObj == Parent && CanMove) return true; | |||
| if (targetObj.Type == GameObjType.Prop || targetObj.Type == GameObjType.Bullet) | |||
| if (targetObj == Parent) return true; | |||
| if (targetObj.Type == GameObjType.Gadget || targetObj.Type == GameObjType.Bullet) | |||
| return true; | |||
| return false; | |||
| } | |||
| public Bullet(Character player, int radius, PlaceType placeType, XY Position) : | |||
| public Bullet(Character player, int radius, XY Position) : | |||
| base(Position, radius, GameObjType.Bullet) | |||
| { | |||
| this.place = placeType; | |||
| this.CanMove = true; | |||
| this.moveSpeed = this.Speed; | |||
| this.ReSetCanMove(true); | |||
| this.MoveSpeed = this.Speed; | |||
| this.hasSpear = player.TryUseSpear(); | |||
| this.Parent = player; | |||
| } | |||
| @@ -57,18 +64,20 @@ namespace GameClass.GameObj | |||
| public static class BulletFactory | |||
| { | |||
| public static Bullet? GetBullet(Character character, PlaceType place, XY pos) | |||
| public static Bullet? GetBullet(Character character, XY pos, BulletType bulletType) | |||
| { | |||
| switch (character.BulletOfPlayer) | |||
| switch (bulletType) | |||
| { | |||
| case BulletType.FlyingKnife: | |||
| return new FlyingKnife(character, place, pos); | |||
| return new FlyingKnife(character, pos); | |||
| case BulletType.CommonAttackOfGhost: | |||
| return new CommonAttackOfGhost(character, place, pos); | |||
| return new CommonAttackOfGhost(character, pos); | |||
| case BulletType.JumpyDumpty: | |||
| return new JumpyDumpty(character, place, pos); | |||
| return new JumpyDumpty(character, pos); | |||
| case BulletType.BombBomb: | |||
| return new BombBomb(character, place, pos); | |||
| return new BombBomb(character, pos); | |||
| case BulletType.Strike: | |||
| return new Strike(character, pos); | |||
| default: | |||
| return null; | |||
| } | |||
| @@ -94,6 +103,8 @@ namespace GameClass.GameObj | |||
| return BombBomb.cd; | |||
| case BulletType.JumpyDumpty: | |||
| return JumpyDumpty.cd; | |||
| case BulletType.Strike: | |||
| return Strike.cd; | |||
| default: | |||
| return GameData.basicCD; | |||
| } | |||
| @@ -110,6 +121,8 @@ namespace GameClass.GameObj | |||
| return BombBomb.maxBulletNum; | |||
| case BulletType.JumpyDumpty: | |||
| return JumpyDumpty.maxBulletNum; | |||
| case BulletType.Strike: | |||
| return Strike.maxBulletNum; | |||
| default: | |||
| return 0; | |||
| } | |||
| @@ -197,6 +197,19 @@ namespace GameClass.GameObj | |||
| return false; | |||
| } | |||
| public bool TryDeleteInvisible() | |||
| { | |||
| if (HasInvisible) | |||
| { | |||
| lock (buffListLock[(int)BuffType.Invisible]) | |||
| { | |||
| buffList[(int)BuffType.Invisible].RemoveFirst(); | |||
| } | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| public void AddClairaudience(int shieldTime) => AddBuff(new BuffValue(), shieldTime, BuffType.Clairaudience, () => | |||
| { }); | |||
| public bool HasClairaudience | |||
| @@ -1,10 +1,5 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Numerics; | |||
| using System.Runtime.InteropServices; | |||
| using System.Threading; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -1,8 +1,6 @@ | |||
| using Preparation.Utility; | |||
| using Preparation.Interface; | |||
| using System.Collections.Generic; | |||
| using System; | |||
| using System.Numerics; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -13,37 +11,32 @@ namespace GameClass.GameObj | |||
| private readonly IOccupation occupation; | |||
| public IOccupation Occupation => occupation; | |||
| private Dictionary<ActiveSkillType, int> timeUntilActiveSkillAvailable = new(); | |||
| public Dictionary<ActiveSkillType, int> TimeUntilActiveSkillAvailable => timeUntilActiveSkillAvailable; | |||
| private Dictionary<ActiveSkillType, ActiveSkill> activeSkillDictionary = new(); | |||
| public Dictionary<ActiveSkillType, ActiveSkill> ActiveSkillDictionary => activeSkillDictionary; | |||
| private Dictionary<ActiveSkillType, IActiveSkill> iActiveSkillDictionary = new(); | |||
| public Dictionary<ActiveSkillType, IActiveSkill> IActiveSkillDictionary => iActiveSkillDictionary; | |||
| public IActiveSkill FindIActiveSkill(ActiveSkillType activeSkillType) | |||
| public ActiveSkill FindActiveSkill(ActiveSkillType activeSkillType) | |||
| { | |||
| if (Occupation.ListOfIActiveSkill.Contains(activeSkillType)) | |||
| { | |||
| return IActiveSkillDictionary[activeSkillType]; | |||
| return ActiveSkillDictionary[activeSkillType]; | |||
| } | |||
| return new NullSkill(); | |||
| } | |||
| public bool SetTimeUntilActiveSkillAvailable(ActiveSkillType activeSkillType, int timeUntilActiveSkillAvailable) | |||
| { | |||
| if (TimeUntilActiveSkillAvailable.ContainsKey(activeSkillType)) | |||
| if (Occupation.ListOfIActiveSkill.Contains(activeSkillType)) | |||
| { | |||
| lock (gameObjLock) | |||
| this.timeUntilActiveSkillAvailable[activeSkillType] = (timeUntilActiveSkillAvailable > 0) ? timeUntilActiveSkillAvailable : 0; | |||
| ActiveSkillDictionary[activeSkillType].TimeUntilActiveSkillAvailable = (timeUntilActiveSkillAvailable > 0) ? timeUntilActiveSkillAvailable : 0; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| public bool AddTimeUntilActiveSkillAvailable(ActiveSkillType activeSkillType, int addTimeUntilActiveSkillAvailable) | |||
| { | |||
| if (TimeUntilActiveSkillAvailable.ContainsKey(activeSkillType)) | |||
| if (Occupation.ListOfIActiveSkill.Contains(activeSkillType)) | |||
| { | |||
| lock (gameObjLock) | |||
| this.timeUntilActiveSkillAvailable[activeSkillType] = (timeUntilActiveSkillAvailable[activeSkillType] + addTimeUntilActiveSkillAvailable > 0) ? timeUntilActiveSkillAvailable[activeSkillType] + addTimeUntilActiveSkillAvailable : 0; | |||
| ActiveSkillDictionary[activeSkillType].TimeUntilActiveSkillAvailable += addTimeUntilActiveSkillAvailable; | |||
| return true; | |||
| } | |||
| return false; | |||
| @@ -57,13 +50,12 @@ namespace GameClass.GameObj | |||
| protected Character(XY initPos, int initRadius, CharacterType characterType) : | |||
| base(initPos, initRadius, GameObjType.Character) | |||
| { | |||
| this.place = PlaceType.Null; | |||
| this.CanMove = true; | |||
| this.ReSetCanMove(true); | |||
| this.score = 0; | |||
| this.buffManager = new BuffManager(); | |||
| this.occupation = OccupationFactory.FindIOccupation(characterType); | |||
| this.MaxHp = this.hp = Occupation.MaxHp; | |||
| this.OrgMoveSpeed = this.moveSpeed = Occupation.MoveSpeed; | |||
| this.MoveSpeed = this.OrgMoveSpeed = Occupation.MoveSpeed; | |||
| this.BulletOfPlayer = this.OriBulletOfPlayer = Occupation.InitBullet; | |||
| this.concealment = Occupation.Concealment; | |||
| this.alertnessRadius = Occupation.AlertnessRadius; | |||
| @@ -75,12 +67,10 @@ namespace GameClass.GameObj | |||
| foreach (var activeSkill in this.Occupation.ListOfIActiveSkill) | |||
| { | |||
| this.IActiveSkillDictionary.Add(activeSkill, SkillFactory.FindIActiveSkill(activeSkill)); | |||
| this.TimeUntilActiveSkillAvailable.Add(activeSkill, 0); | |||
| this.ActiveSkillDictionary.Add(activeSkill, SkillFactory.FindActiveSkill(activeSkill)); | |||
| } | |||
| // UsePassiveSkill(); //创建player时开始被动技能,这一过程也可以放到gamestart时进行 | |||
| // 这可以放在AddPlayer中做 | |||
| // UsePassiveSkill(); //这一过程放到gamestart时进行 | |||
| Debugger.Output(this, "constructed!"); | |||
| } | |||
| @@ -141,7 +141,7 @@ namespace GameClass.GameObj | |||
| } | |||
| } | |||
| } | |||
| public override void AddScore(int add) | |||
| public override void AddScore(long add) | |||
| { | |||
| if (parent == null) | |||
| base.AddScore(add); | |||
| @@ -2,100 +2,116 @@ | |||
| using Preparation.Utility; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Numerics; | |||
| using System.Runtime.InteropServices; | |||
| using System.Threading; | |||
| namespace GameClass.GameObj | |||
| { | |||
| public partial class Character : Moveable, ICharacter // 负责人LHR摆烂终了 | |||
| { | |||
| #region 装弹、攻击相关的基本属性及方法 | |||
| protected readonly object beAttackedLock = new(); | |||
| private readonly ReaderWriterLockSlim hpReaderWriterLock = new(); | |||
| public ReaderWriterLockSlim HPReadWriterLock => hpReaderWriterLock; | |||
| private readonly object vampireLock = new(); | |||
| public object VampireLock => vampire; | |||
| private readonly object inventoryLock = new(); | |||
| public object InventoryLock => inventoryLock; | |||
| #region 装弹、攻击相关的基本属性及方法 | |||
| /// <summary> | |||
| /// 装弹冷却 | |||
| /// </summary> | |||
| protected int cd; | |||
| public int CD | |||
| { | |||
| get => cd; | |||
| private set | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| lock (actionLock) | |||
| { | |||
| cd = value; | |||
| Debugger.Output(this, string.Format("'s CD has been set to: {0}.", value)); | |||
| return cd; | |||
| } | |||
| } | |||
| } | |||
| public int OrgCD { get; protected set; } | |||
| protected int maxBulletNum; | |||
| public int MaxBulletNum => maxBulletNum; // 人物最大子弹数 | |||
| protected int bulletNum; | |||
| public int BulletNum => bulletNum; // 目前持有的子弹数 | |||
| public readonly BulletType OriBulletOfPlayer; | |||
| private BulletType bulletOfPlayer; | |||
| public BulletType BulletOfPlayer | |||
| { | |||
| get => bulletOfPlayer; | |||
| get | |||
| { | |||
| lock (actionLock) | |||
| { | |||
| return bulletOfPlayer; | |||
| } | |||
| } | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| lock (actionLock) | |||
| { | |||
| bulletOfPlayer = value; | |||
| OrgCD = (BulletFactory.BulletCD(value)); | |||
| CD = 0; | |||
| cd = OrgCD = (BulletFactory.BulletCD(value)); | |||
| Debugger.Output(this, string.Format("'s CD has been set to: {0}.", cd)); | |||
| maxBulletNum = bulletNum = (BulletFactory.BulletNum(value)); | |||
| } | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 进行一次攻击 | |||
| /// </summary> | |||
| /// <returns>攻击操作发出的子弹</returns> | |||
| public Bullet? Attack(XY pos, PlaceType place) | |||
| protected int maxBulletNum; | |||
| public int MaxBulletNum | |||
| { | |||
| if (TrySubBulletNum()) | |||
| return BulletFactory.GetBullet(this, place, pos); | |||
| else | |||
| return null; | |||
| get | |||
| { | |||
| lock (actionLock) | |||
| { | |||
| return maxBulletNum; | |||
| } | |||
| } | |||
| } | |||
| private int bulletNum; | |||
| private int updateTimeOfBulletNum = 0; | |||
| /// <summary> | |||
| /// 尝试将子弹数量减1 | |||
| /// </summary> | |||
| /// <returns>减操作是否成功</returns> | |||
| private bool TrySubBulletNum() | |||
| public int UpdateBulletNum(int time) | |||
| { | |||
| lock (gameObjLock) | |||
| lock (actionLock) | |||
| { | |||
| if (bulletNum > 0) | |||
| if (bulletNum < maxBulletNum) | |||
| { | |||
| --bulletNum; | |||
| return true; | |||
| int add = Math.Min(maxBulletNum - bulletNum, (time - updateTimeOfBulletNum) / cd); | |||
| updateTimeOfBulletNum += add * cd; | |||
| return (bulletNum += add); | |||
| } | |||
| return false; | |||
| return maxBulletNum; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 尝试将子弹数量加1 | |||
| /// 进行一次攻击 | |||
| /// </summary> | |||
| /// <returns>加操作是否成功</returns> | |||
| public bool TryAddBulletNum() | |||
| /// <returns>攻击操作发出的子弹</returns> | |||
| public Bullet? Attack(double angle, int time) | |||
| { | |||
| lock (gameObjLock) | |||
| lock (actionLock) | |||
| { | |||
| if (bulletNum < maxBulletNum) | |||
| if (bulletOfPlayer == BulletType.Null) | |||
| return null; | |||
| if (UpdateBulletNum(time) > 0) | |||
| { | |||
| ++bulletNum; | |||
| return true; | |||
| if (bulletNum == maxBulletNum) updateTimeOfBulletNum = time; | |||
| --bulletNum; | |||
| XY res = Position + new XY // 子弹紧贴人物生成。 | |||
| ( | |||
| (int)(Math.Abs((Radius + BulletFactory.BulletRadius(bulletOfPlayer)) * Math.Cos(angle))) * Math.Sign(Math.Cos(angle)), | |||
| (int)(Math.Abs((Radius + BulletFactory.BulletRadius(bulletOfPlayer)) * Math.Sin(angle))) * Math.Sign(Math.Sin(angle)) | |||
| ); | |||
| Bullet? bullet = BulletFactory.GetBullet(this, res, this.bulletOfPlayer); | |||
| if (bullet == null) return null; | |||
| if (TryAddAp()) bullet.AddAP(GameData.ApPropAdd); | |||
| facingDirection = new(angle, bullet.AttackDistance); | |||
| return bullet; | |||
| } | |||
| return false; | |||
| else | |||
| return null; | |||
| } | |||
| } | |||
| @@ -224,21 +240,66 @@ namespace GameClass.GameObj | |||
| } | |||
| #endregion | |||
| #region 血量相关的基本属性及方法 | |||
| public int MaxHp { get; protected set; } // 最大血量 | |||
| private int maxHp; | |||
| public int MaxHp | |||
| { | |||
| get | |||
| { | |||
| HPReadWriterLock.EnterReadLock(); | |||
| try | |||
| { | |||
| return maxHp; | |||
| } | |||
| finally | |||
| { | |||
| HPReadWriterLock.ExitReadLock(); | |||
| } | |||
| } | |||
| protected set | |||
| { | |||
| HPReadWriterLock.EnterWriteLock(); | |||
| try | |||
| { | |||
| maxHp = value; | |||
| if (hp > maxHp) hp = maxHp; | |||
| } | |||
| finally | |||
| { | |||
| HPReadWriterLock.ExitWriteLock(); | |||
| } | |||
| } | |||
| } // 最大血量 | |||
| protected int hp; | |||
| public int HP | |||
| { | |||
| get => hp; | |||
| set | |||
| get | |||
| { | |||
| if (value > 0) | |||
| HPReadWriterLock.EnterReadLock(); | |||
| try | |||
| { | |||
| lock (gameObjLock) | |||
| hp = value <= MaxHp ? value : MaxHp; | |||
| return hp; | |||
| } | |||
| else | |||
| lock (gameObjLock) | |||
| finally | |||
| { | |||
| HPReadWriterLock.ExitReadLock(); | |||
| } | |||
| } | |||
| set | |||
| { | |||
| HPReadWriterLock.EnterWriteLock(); | |||
| try | |||
| { | |||
| if (value > 0) | |||
| { | |||
| hp = value <= maxHp ? value : maxHp; | |||
| } | |||
| else | |||
| hp = 0; | |||
| } | |||
| finally | |||
| { | |||
| HPReadWriterLock.ExitWriteLock(); | |||
| } | |||
| } | |||
| } | |||
| @@ -246,50 +307,51 @@ namespace GameClass.GameObj | |||
| /// 尝试减血 | |||
| /// </summary> | |||
| /// <param name="sub">减血量</param> | |||
| /// <returns>减操作是否成功</returns> | |||
| public int TrySubHp(int sub) | |||
| { | |||
| int previousHp = hp; | |||
| lock (gameObjLock) | |||
| hp = hp <= sub ? 0 : hp - sub; | |||
| Debugger.Output(this, " hp has subed to: " + hp.ToString()); | |||
| return previousHp - hp; | |||
| HPReadWriterLock.EnterWriteLock(); | |||
| try | |||
| { | |||
| int previousHp = hp; | |||
| if (hp <= sub) | |||
| { | |||
| hp = 0; | |||
| return hp; | |||
| } | |||
| else | |||
| { | |||
| hp -= sub; | |||
| return sub; | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| HPReadWriterLock.ExitWriteLock(); | |||
| } | |||
| } | |||
| private double vampire = 0; // 回血率:0-1之间 | |||
| public double Vampire | |||
| { | |||
| get => vampire; | |||
| set | |||
| get | |||
| { | |||
| if (value > 1) | |||
| lock (gameObjLock) | |||
| vampire = 1; | |||
| else if (value < 0) | |||
| lock (gameObjLock) | |||
| vampire = 0; | |||
| else | |||
| lock (gameObjLock) | |||
| vampire = value; | |||
| lock (vampireLock) | |||
| return vampire; | |||
| } | |||
| } | |||
| private double oriVampire = 0; | |||
| public double OriVampire | |||
| { | |||
| get => oriVampire; | |||
| set | |||
| { | |||
| if (value > 1) | |||
| lock (gameObjLock) | |||
| lock (vampireLock) | |||
| { | |||
| if (value > 1) | |||
| vampire = 1; | |||
| else if (value < 0) | |||
| lock (gameObjLock) | |||
| else if (value < 0) | |||
| vampire = 0; | |||
| else | |||
| lock (gameObjLock) | |||
| else | |||
| vampire = value; | |||
| } | |||
| } | |||
| } | |||
| public double OriVampire { get; protected set; } | |||
| #endregion | |||
| #region 状态相关的基本属性与方法 | |||
| private PlayerStateType playerState = PlayerStateType.Null; | |||
| @@ -297,140 +359,295 @@ namespace GameClass.GameObj | |||
| { | |||
| get | |||
| { | |||
| if (playerState == PlayerStateType.Null && IsMoving) return PlayerStateType.Moving; | |||
| return playerState; | |||
| lock (actionLock) | |||
| { | |||
| if (playerState == PlayerStateType.Moving) | |||
| return (IsMoving) ? PlayerStateType.Moving : PlayerStateType.Null; | |||
| return playerState; | |||
| } | |||
| } | |||
| } | |||
| public bool NoHp() => (playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped | |||
| || playerState == PlayerStateType.Addicted || playerState == PlayerStateType.Rescued); | |||
| public bool Commandable() => (playerState != PlayerStateType.Deceased && playerState != PlayerStateType.Escaped | |||
| && playerState != PlayerStateType.Addicted && playerState != PlayerStateType.Rescued | |||
| && playerState != PlayerStateType.Swinging && playerState != PlayerStateType.TryingToAttack | |||
| && playerState != PlayerStateType.ClimbingThroughWindows && playerState != PlayerStateType.Stunned); | |||
| public bool InteractingWithMapWithoutMoving() => (playerState == PlayerStateType.LockingOrOpeningTheDoor || playerState == PlayerStateType.Fixing || playerState == PlayerStateType.OpeningTheChest); | |||
| public bool NullOrMoving() => (playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | |||
| public bool CanBeAwed() => !(playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped | |||
| || playerState == PlayerStateType.Addicted || playerState == PlayerStateType.Rescued | |||
| || playerState == PlayerStateType.Treated || playerState == PlayerStateType.Stunned | |||
| || playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | |||
| public bool NoHp() | |||
| { | |||
| lock (actionLock) | |||
| return (playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped || playerState == PlayerStateType.Addicted || playerState == PlayerStateType.Rescued); | |||
| } | |||
| public bool Commandable() | |||
| { | |||
| lock (actionLock) | |||
| { | |||
| return (playerState != PlayerStateType.Deceased && playerState != PlayerStateType.Escaped | |||
| && playerState != PlayerStateType.Addicted && playerState != PlayerStateType.Rescued | |||
| && playerState != PlayerStateType.Swinging && playerState != PlayerStateType.TryingToAttack | |||
| && playerState != PlayerStateType.ClimbingThroughWindows | |||
| && playerState != PlayerStateType.Stunned && playerState != PlayerStateType.Charmed); | |||
| } | |||
| } | |||
| public bool CanPinDown() | |||
| { | |||
| lock (actionLock) | |||
| { | |||
| return (playerState != PlayerStateType.Deceased && playerState != PlayerStateType.Escaped | |||
| && playerState != PlayerStateType.Addicted && playerState != PlayerStateType.Rescued | |||
| && playerState != PlayerStateType.Stunned && playerState != PlayerStateType.Charmed); | |||
| } | |||
| } | |||
| public bool InteractingWithMapWithoutMoving() | |||
| { | |||
| lock (actionLock) | |||
| { | |||
| return (playerState == PlayerStateType.LockingTheDoor || playerState == PlayerStateType.OpeningTheDoor | |||
| || playerState == PlayerStateType.Fixing || playerState == PlayerStateType.OpeningTheChest); | |||
| } | |||
| } | |||
| public bool NullOrMoving() | |||
| { | |||
| lock (actionLock) | |||
| { | |||
| return (playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | |||
| } | |||
| } | |||
| public bool CanBeAwed() | |||
| { | |||
| lock (actionLock) | |||
| return !(playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped | |||
| || playerState == PlayerStateType.Addicted | |||
| || playerState == PlayerStateType.Rescued || playerState == PlayerStateType.Treated | |||
| || playerState == PlayerStateType.Stunned || playerState == PlayerStateType.Charmed | |||
| || playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | |||
| } | |||
| private GameObj? whatInteractingWith = null; | |||
| public GameObj? WhatInteractingWith => whatInteractingWith; | |||
| private long threadNum = 0; | |||
| public long ThreadNum => threadNum; | |||
| private long ChangePlayerState(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||
| { | |||
| //只能被SetPlayerState引用 | |||
| whatInteractingWith = gameObj; | |||
| playerState = value; | |||
| //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); | |||
| return ++stateNum; | |||
| } | |||
| public void ChangePlayerState(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||
| private long ChangePlayerStateInOneThread(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| ++threadNum; | |||
| whatInteractingWith = gameObj; | |||
| if (value != PlayerStateType.Moving) | |||
| IsMoving = false; | |||
| playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value; | |||
| //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); | |||
| } | |||
| //只能被SetPlayerState引用 | |||
| whatInteractingWith = gameObj; | |||
| playerState = value; | |||
| //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); | |||
| return stateNum; | |||
| } | |||
| public void ChangePlayerStateInOneThread(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||
| public long SetPlayerState(PlayerStateType value = PlayerStateType.Null, IGameObj? obj = null) | |||
| { | |||
| lock (gameObjLock) | |||
| GameObj? gameObj = (GameObj?)obj; | |||
| lock (actionLock) | |||
| { | |||
| whatInteractingWith = gameObj; | |||
| if (value != PlayerStateType.Moving) | |||
| IsMoving = false; | |||
| playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value; | |||
| //Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString()); | |||
| PlayerStateType nowPlayerState = PlayerState; | |||
| if (nowPlayerState == value && value != PlayerStateType.UsingSkill) return -1; | |||
| switch (nowPlayerState) | |||
| { | |||
| case PlayerStateType.Escaped: | |||
| case PlayerStateType.Deceased: | |||
| return -1; | |||
| case PlayerStateType.Addicted: | |||
| if (value == PlayerStateType.Rescued) | |||
| return ChangePlayerStateInOneThread(value, gameObj); | |||
| else if (value == PlayerStateType.Null) | |||
| return ChangePlayerState(value, gameObj); | |||
| else return -1; | |||
| case PlayerStateType.Rescued: | |||
| if (value == PlayerStateType.Addicted) | |||
| return ChangePlayerStateInOneThread(value, gameObj); | |||
| else if (value == PlayerStateType.Null) | |||
| return ChangePlayerState(value, gameObj); | |||
| else return -1; | |||
| case PlayerStateType.TryingToAttack: | |||
| if (value == PlayerStateType.Addicted || value == PlayerStateType.Swinging | |||
| || value == PlayerStateType.Deceased || value == PlayerStateType.Stunned | |||
| || value == PlayerStateType.Charmed || value == PlayerStateType.Null) | |||
| return ChangePlayerState(value, gameObj); | |||
| else return -1; | |||
| case PlayerStateType.Stunned: | |||
| case PlayerStateType.Charmed: | |||
| if (value == PlayerStateType.Addicted || value == PlayerStateType.Deceased | |||
| || value == PlayerStateType.Null) | |||
| return ChangePlayerState(value, gameObj); | |||
| else return -1; | |||
| case PlayerStateType.Swinging: | |||
| if (value == PlayerStateType.Addicted | |||
| || value == PlayerStateType.Deceased || value == PlayerStateType.Stunned | |||
| || value == PlayerStateType.Charmed || value == PlayerStateType.Null) | |||
| { | |||
| try | |||
| { | |||
| return ChangePlayerState(value, gameObj); | |||
| } | |||
| finally | |||
| { | |||
| ThreadNum.Release(); | |||
| } | |||
| } | |||
| else return -1; | |||
| case PlayerStateType.ClimbingThroughWindows: | |||
| if (value == PlayerStateType.Addicted | |||
| || value == PlayerStateType.Deceased || value == PlayerStateType.Stunned | |||
| || value == PlayerStateType.Charmed || value == PlayerStateType.Null) | |||
| { | |||
| Window window = (Window)WhatInteractingWith!; | |||
| try | |||
| { | |||
| window.FinishClimbing(); | |||
| return ChangePlayerState(value, gameObj); | |||
| } | |||
| finally | |||
| { | |||
| if (window.Stage.x == 0) | |||
| ThreadNum.Release(); | |||
| else ReSetPos(window.Stage); | |||
| } | |||
| } | |||
| else return -1; | |||
| case PlayerStateType.OpeningTheChest: | |||
| ((Chest)whatInteractingWith!).StopOpen(); | |||
| return ChangePlayerState(value, gameObj); | |||
| case PlayerStateType.OpeningTheDoorway: | |||
| Doorway doorway = (Doorway)whatInteractingWith!; | |||
| doorway.StopOpenning(); | |||
| return ChangePlayerState(value, gameObj); | |||
| case PlayerStateType.OpeningTheDoor: | |||
| Door door = (Door)whatInteractingWith!; | |||
| try | |||
| { | |||
| door.StopOpen(); | |||
| ReleaseTool(door.DoorNum switch | |||
| { | |||
| 3 => PropType.Key3, | |||
| 5 => PropType.Key5, | |||
| _ => PropType.Key6, | |||
| } | |||
| ); | |||
| return ChangePlayerState(value, gameObj); | |||
| } | |||
| finally | |||
| { | |||
| ThreadNum.Release(); | |||
| } | |||
| case PlayerStateType.UsingSkill: | |||
| { | |||
| switch (CharacterType) | |||
| { | |||
| case CharacterType.TechOtaku: | |||
| { | |||
| if (typeof(CraftingBench).IsInstanceOfType(whatInteractingWith)) | |||
| { | |||
| try | |||
| { | |||
| ((CraftingBench)whatInteractingWith!).StopSkill(); | |||
| return ChangePlayerState(value, gameObj); | |||
| } | |||
| finally | |||
| { | |||
| ThreadNum.Release(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if (value != PlayerStateType.UsingSkill) | |||
| ((UseRobot)FindActiveSkill(ActiveSkillType.UseRobot)).NowPlayerID = (int)playerID; | |||
| return ChangePlayerState(value, gameObj); | |||
| } | |||
| } | |||
| case CharacterType.Assassin: | |||
| if (value == PlayerStateType.Moving) return StateNum; | |||
| else | |||
| { | |||
| TryDeleteInvisible(); | |||
| return ChangePlayerState(value, gameObj); | |||
| } | |||
| default: | |||
| return ChangePlayerState(value, gameObj); | |||
| } | |||
| } | |||
| default: | |||
| return ChangePlayerState(value, gameObj); | |||
| } | |||
| } | |||
| } | |||
| public void SetPlayerStateNaturally() | |||
| public long SetPlayerStateNaturally() | |||
| { | |||
| lock (gameObjLock) | |||
| lock (actionLock) | |||
| { | |||
| ++threadNum; | |||
| whatInteractingWith = null; | |||
| IsMoving = false; | |||
| playerState = PlayerStateType.Null; | |||
| return ++stateNum; | |||
| } | |||
| } | |||
| public void RemoveFromGame(PlayerStateType playerStateType) | |||
| { | |||
| lock (gameObjLock) | |||
| lock (actionLock) | |||
| { | |||
| playerState = playerStateType; | |||
| CanMove = false; | |||
| IsResetting = true; | |||
| Position = GameData.PosWhoDie; | |||
| place = PlaceType.Grass; | |||
| TryToRemove(); | |||
| ReSetCanMove(false); | |||
| SetPlayerState(playerStateType); | |||
| position = GameData.PosWhoDie; | |||
| } | |||
| } | |||
| #endregion | |||
| private int score = 0; | |||
| public int Score | |||
| private long score = 0; | |||
| public long Score | |||
| { | |||
| get => score; | |||
| get => Interlocked.Read(ref score); | |||
| } | |||
| /// <summary> | |||
| /// 加分 | |||
| /// </summary> | |||
| /// <param name="add">增加量</param> | |||
| public virtual void AddScore(int add) | |||
| public virtual void AddScore(long add) | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| score += add; | |||
| //Debugger.Output(this, " 's score has been added to: " + score.ToString()); | |||
| } | |||
| Interlocked.Add(ref score, add); | |||
| //Debugger.Output(this, " 's score has been added to: " + score.ToString()); | |||
| } | |||
| /// <summary> | |||
| /// 角色所属队伍ID | |||
| /// </summary> | |||
| private int teamID = int.MaxValue; | |||
| public int TeamID | |||
| private long teamID = long.MaxValue; | |||
| public long TeamID | |||
| { | |||
| get => teamID; | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| teamID = value; | |||
| Debugger.Output(this, " joins in the team: " + value.ToString()); | |||
| } | |||
| } | |||
| get => Interlocked.Read(ref teamID); | |||
| set => Interlocked.Exchange(ref teamID, value); | |||
| } | |||
| private int playerID = int.MaxValue; | |||
| public int PlayerID | |||
| private long playerID = long.MaxValue; | |||
| public long PlayerID | |||
| { | |||
| get => playerID; | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| playerID = value; | |||
| } | |||
| } | |||
| get => Interlocked.Read(ref playerID); | |||
| set => Interlocked.Exchange(ref playerID, value); | |||
| } | |||
| #region 道具和buff相关属性、方法 | |||
| private Prop[] propInventory = new Prop[GameData.maxNumOfPropInPropInventory] | |||
| private Gadget[] propInventory = new Gadget[GameData.maxNumOfPropInPropInventory] | |||
| {new NullProp(), new NullProp(),new NullProp() }; | |||
| public Prop[] PropInventory | |||
| public Gadget[] PropInventory | |||
| { | |||
| get => propInventory; | |||
| get | |||
| { | |||
| lock (inventoryLock) | |||
| return propInventory; | |||
| } | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| lock (inventoryLock) | |||
| propInventory = value; | |||
| Debugger.Output(this, " prop becomes " + (PropInventory == null ? "null" : PropInventory.ToString())); | |||
| } | |||
| } | |||
| } | |||
| @@ -438,45 +655,81 @@ namespace GameClass.GameObj | |||
| /// 使用物品栏中的道具 | |||
| /// </summary> | |||
| /// <returns>被使用的道具</returns> | |||
| public Prop UseProp(int indexing) | |||
| public Gadget UseProp(int indexing) | |||
| { | |||
| if (indexing < 0 || indexing >= GameData.maxNumOfPropInPropInventory) | |||
| return new NullProp(); | |||
| lock (gameObjLock) | |||
| lock (inventoryLock) | |||
| { | |||
| Prop prop = propInventory[indexing]; | |||
| Gadget prop = propInventory[indexing]; | |||
| if (!prop.IsUsable()) return new NullProp(); | |||
| PropInventory[indexing] = new NullProp(); | |||
| return prop; | |||
| } | |||
| } | |||
| public Prop UseProp(PropType propType) | |||
| public Gadget UseProp(PropType propType) | |||
| { | |||
| lock (gameObjLock) | |||
| if (propType == PropType.Null) | |||
| { | |||
| if (propType == PropType.Null) | |||
| lock (inventoryLock) | |||
| { | |||
| for (int indexing = 0; indexing < GameData.maxNumOfPropInPropInventory; ++indexing) | |||
| { | |||
| if (PropInventory[indexing].GetPropType() != PropType.Null) | |||
| if (PropInventory[indexing].IsUsable()) | |||
| { | |||
| Prop prop = PropInventory[indexing]; | |||
| Gadget prop = PropInventory[indexing]; | |||
| PropInventory[indexing] = new NullProp(); | |||
| return prop; | |||
| } | |||
| } | |||
| } | |||
| else | |||
| } | |||
| else | |||
| { | |||
| lock (inventoryLock) | |||
| { | |||
| for (int indexing = 0; indexing < GameData.maxNumOfPropInPropInventory; ++indexing) | |||
| { | |||
| if (PropInventory[indexing].GetPropType() == propType) | |||
| if (PropInventory[indexing].GetPropType() == propType && PropInventory[indexing].IsUsable()) | |||
| { | |||
| Prop prop = PropInventory[indexing]; | |||
| Gadget prop = PropInventory[indexing]; | |||
| PropInventory[indexing] = new NullProp(); | |||
| return prop; | |||
| } | |||
| } | |||
| return new NullProp(); | |||
| } | |||
| } | |||
| return new NullProp(); | |||
| } | |||
| public bool UseTool(PropType propType) | |||
| { | |||
| lock (inventoryLock) | |||
| { | |||
| for (int indexing = 0; indexing < GameData.maxNumOfPropInPropInventory; ++indexing) | |||
| { | |||
| if (PropInventory[indexing].GetPropType() == propType && PropInventory[indexing].IsUsable()) | |||
| { | |||
| return ((Tool)PropInventory[indexing]).IsUsed = true; | |||
| } | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| public void ReleaseTool(PropType propType) | |||
| { | |||
| lock (inventoryLock) | |||
| { | |||
| for (int indexing = 0; indexing < GameData.maxNumOfPropInPropInventory; ++indexing) | |||
| { | |||
| if (PropInventory[indexing].GetPropType() == propType && ((Tool)PropInventory[indexing]).IsUsed) | |||
| { | |||
| ((Tool)PropInventory[indexing]).IsUsed = false; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -486,9 +739,10 @@ namespace GameClass.GameObj | |||
| public int IndexingOfAddProp() | |||
| { | |||
| int indexing = 0; | |||
| for (; indexing < GameData.maxNumOfPropInPropInventory; ++indexing) | |||
| if (PropInventory[indexing].GetPropType() == PropType.Null) | |||
| break; | |||
| lock (inventoryLock) | |||
| for (; indexing < GameData.maxNumOfPropInPropInventory; ++indexing) | |||
| if (propInventory[indexing].GetPropType() == PropType.Null) | |||
| break; | |||
| return indexing; | |||
| } | |||
| @@ -551,6 +805,7 @@ namespace GameClass.GameObj | |||
| return false; | |||
| } | |||
| } | |||
| public void TryActivatingLIFE() | |||
| { | |||
| if (buffManager.TryActivatingLIFE()) | |||
| @@ -575,6 +830,11 @@ namespace GameClass.GameObj | |||
| return buffManager.TryUseSpear(); | |||
| } | |||
| public bool TryDeleteInvisible() | |||
| { | |||
| return buffManager.TryDeleteInvisible(); | |||
| } | |||
| public bool TryUseShield() | |||
| { | |||
| if (buffManager.TryUseShield()) | |||
| @@ -608,9 +868,9 @@ namespace GameClass.GameObj | |||
| public override ShapeType Shape => ShapeType.Circle; | |||
| public override bool IgnoreCollideExecutor(IGameObj targetObj) | |||
| { | |||
| if (IsResetting) | |||
| if (IsRemoved) | |||
| return true; | |||
| if (targetObj.Type == GameObjType.Prop) | |||
| if (targetObj.Type == GameObjType.Gadget) | |||
| { | |||
| return true; | |||
| } | |||
| @@ -17,7 +17,7 @@ namespace GameClass.GameObj | |||
| { | |||
| int score = 0; | |||
| foreach (var player in playerList) | |||
| score += player.Score; | |||
| score += (int)player.Score; | |||
| return score; | |||
| } | |||
| } | |||
| @@ -9,75 +9,43 @@ namespace GameClass.GameObj | |||
| /// </summary> | |||
| public abstract class GameObj : IGameObj | |||
| { | |||
| private readonly ReaderWriterLockSlim gameObjReaderWriterLock = new(); | |||
| public ReaderWriterLockSlim GameObjReaderWriterLock => gameObjReaderWriterLock; | |||
| protected readonly object gameObjLock = new(); | |||
| public object GameLock => gameObjLock; | |||
| protected readonly XY birthPos; | |||
| private GameObjType type; | |||
| private readonly 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; | |||
| } | |||
| } | |||
| } | |||
| protected XY position; | |||
| public abstract XY Position { get; } | |||
| protected PlaceType place; | |||
| public PlaceType Place { get => place; } | |||
| protected XY facingDirection = new(1, 0); | |||
| public abstract XY FacingDirection { get; } | |||
| private XY facingDirection = new(1, 0); | |||
| public XY FacingDirection | |||
| { | |||
| get => facingDirection; | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| facingDirection = value; | |||
| } | |||
| } | |||
| public abstract bool CanMove { get; } | |||
| public abstract bool IsRigid { get; } | |||
| public abstract ShapeType Shape { get; } | |||
| private bool canMove; | |||
| public bool CanMove | |||
| protected int isRemoved = 0; | |||
| public bool IsRemoved | |||
| { | |||
| get => canMove; | |||
| set | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| canMove = value; | |||
| } | |||
| return (Interlocked.CompareExchange(ref isRemoved, 0, 0) == 1); | |||
| } | |||
| } | |||
| private bool isResetting; | |||
| public bool IsResetting | |||
| public virtual void TryToRemove() | |||
| { | |||
| get => isResetting; | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| isResetting = value; | |||
| } | |||
| } | |||
| Interlocked.Exchange(ref isRemoved, 1); | |||
| } | |||
| public int Radius { get; } | |||
| @@ -85,7 +53,7 @@ namespace GameClass.GameObj | |||
| public virtual bool IgnoreCollideExecutor(IGameObj targetObj) => false; | |||
| public GameObj(XY initPos, int initRadius, GameObjType initType) | |||
| { | |||
| this.Position = this.birthPos = initPos; | |||
| this.position = this.birthPos = initPos; | |||
| this.Radius = initRadius; | |||
| this.type = initType; | |||
| ID = Interlocked.Increment(ref currentMaxID); | |||
| @@ -0,0 +1,18 @@ | |||
| using Preparation.Utility; | |||
| namespace GameClass.GameObj | |||
| { | |||
| public abstract class Immovable : GameObj | |||
| { | |||
| public override XY Position => position; | |||
| public override XY FacingDirection => facingDirection; | |||
| public override bool CanMove => false; | |||
| public Immovable(XY initPos, int initRadius, GameObjType initType) : base(initPos, initRadius, initType) | |||
| { | |||
| } | |||
| } | |||
| } | |||
| @@ -1,24 +1,22 @@ | |||
| using Preparation.Utility; | |||
| using System.Collections.Generic; | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| namespace GameClass.GameObj | |||
| { | |||
| /// <summary> | |||
| /// 箱子 | |||
| /// </summary> | |||
| public class Chest : GameObj | |||
| public class Chest : Immovable, IChest | |||
| { | |||
| public Chest(XY initPos) : | |||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Chest) | |||
| { | |||
| this.place = PlaceType.Chest; | |||
| this.CanMove = false; | |||
| } | |||
| public override bool IsRigid => true; | |||
| public override ShapeType Shape => ShapeType.Square; | |||
| private Prop[] propInChest = new Prop[GameData.maxNumOfPropInChest] { new NullProp(), new NullProp() }; | |||
| public Prop[] PropInChest => propInChest; | |||
| private readonly Gadget[] propInChest = new Gadget[GameData.maxNumOfPropInChest] { new NullProp(), new NullProp() }; | |||
| public Gadget[] PropInChest => propInChest; | |||
| private int openStartTime = 0; | |||
| public int OpenStartTime => openStartTime; | |||
| @@ -26,7 +24,7 @@ namespace GameClass.GameObj | |||
| public Character? WhoOpen => whoOpen; | |||
| public void Open(int startTime, Character character) | |||
| { | |||
| lock (gameObjLock) | |||
| lock (GameObjReaderWriterLock) | |||
| { | |||
| openStartTime = startTime; | |||
| whoOpen = character; | |||
| @@ -34,7 +32,7 @@ namespace GameClass.GameObj | |||
| } | |||
| public void StopOpen() | |||
| { | |||
| lock (gameObjLock) | |||
| lock (GameObjReaderWriterLock) | |||
| { | |||
| openStartTime = 0; | |||
| whoOpen = null; | |||
| @@ -1,45 +1,160 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| using System.Threading; | |||
| namespace GameClass.GameObj | |||
| { | |||
| /// <summary> | |||
| /// 门 | |||
| /// </summary> | |||
| public class Door : GameObj | |||
| public class Door : Immovable | |||
| { | |||
| public Door(XY initPos, PlaceType placeType) : | |||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Door) | |||
| { | |||
| this.place = placeType; | |||
| this.CanMove = false; | |||
| switch (placeType) | |||
| { | |||
| case PlaceType.Door3: | |||
| doorNum = 3; | |||
| break; | |||
| case PlaceType.Door5: | |||
| doorNum = 5; | |||
| break; | |||
| case PlaceType.Door6: | |||
| default: | |||
| doorNum = 6; | |||
| break; | |||
| } | |||
| } | |||
| private readonly int doorNum; | |||
| public int DoorNum => doorNum; | |||
| public override bool IsRigid => !isOpen; | |||
| public override ShapeType Shape => ShapeType.Square; | |||
| private Character? whoLockOrOpen = null; | |||
| public Character? WhoLockOrOpen | |||
| { | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| return whoLockOrOpen; | |||
| } | |||
| } | |||
| private bool isOpen = true; | |||
| public bool IsOpen | |||
| { | |||
| get => isOpen; | |||
| set | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| isOpen = value; | |||
| return isOpen; | |||
| } | |||
| } | |||
| private int openOrLockDegree = 0; | |||
| public int OpenOrLockDegree | |||
| private int lockDegree = 0; | |||
| public int LockDegree | |||
| { | |||
| get => openOrLockDegree; | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| return lockDegree; | |||
| } | |||
| set | |||
| { | |||
| if (value > 0) | |||
| lock (gameObjLock) | |||
| openOrLockDegree = (value > GameData.degreeOfLockingOrOpeningTheDoor) ? GameData.degreeOfLockingOrOpeningTheDoor : value; | |||
| else | |||
| lock (gameObjLock) | |||
| openOrLockDegree = 0; | |||
| value = (value > GameData.degreeOfLockingOrOpeningTheDoor) ? GameData.degreeOfLockingOrOpeningTheDoor : value; | |||
| lock (gameObjLock) | |||
| lockDegree = value; | |||
| } | |||
| } | |||
| private long openStartTime = 0; | |||
| public long OpenStartTime | |||
| { | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| return openStartTime; | |||
| } | |||
| } | |||
| public bool TryOpen(Character character) | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| if (isOpen) return false; | |||
| if (whoLockOrOpen != null) return false; | |||
| openStartTime = Environment.TickCount64; | |||
| whoLockOrOpen = character; | |||
| return true; | |||
| } | |||
| } | |||
| public void StopOpen() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| if (whoLockOrOpen != null) | |||
| { | |||
| if ((Environment.TickCount64 - openStartTime) >= GameData.degreeOfLockingOrOpeningTheDoor / whoLockOrOpen.SpeedOfOpeningOrLocking) | |||
| isOpen = true; | |||
| whoLockOrOpen = null; | |||
| } | |||
| } | |||
| } | |||
| public void FinishOpen() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| isOpen = true; | |||
| whoLockOrOpen = null; | |||
| } | |||
| } | |||
| public bool TryLock(Character character) | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| if (!isOpen) return false; | |||
| if (whoLockOrOpen != null) return false; | |||
| lockDegree = 0; | |||
| whoLockOrOpen = character; | |||
| return true; | |||
| } | |||
| } | |||
| public void StopLock() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| if (lockDegree >= GameData.degreeOfLockingOrOpeningTheDoor) | |||
| isOpen = false; | |||
| whoLockOrOpen = null; | |||
| } | |||
| } | |||
| public void FinishLock() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| isOpen = false; | |||
| whoLockOrOpen = null; | |||
| } | |||
| } | |||
| public void ForceToOpen() | |||
| { | |||
| Character? character; | |||
| lock (gameObjLock) | |||
| { | |||
| character = whoLockOrOpen; | |||
| whoLockOrOpen = null; | |||
| isOpen = true; | |||
| } | |||
| if (character != null) | |||
| { | |||
| lock (character.ActionLock) | |||
| if (character.PlayerState == PlayerStateType.OpeningTheDoor) | |||
| character.SetPlayerState(); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,18 +1,18 @@ | |||
| using Preparation.Interface; | |||
| using Google.Protobuf.WellKnownTypes; | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| namespace GameClass.GameObj | |||
| { | |||
| /// <summary> | |||
| /// 出口 | |||
| /// </summary> | |||
| public class Doorway : GameObj | |||
| public class Doorway : Immovable, IDoorway | |||
| { | |||
| public Doorway(XY initPos) : | |||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Doorway) | |||
| { | |||
| this.place = PlaceType.Doorway; | |||
| this.CanMove = false; | |||
| } | |||
| public override bool IsRigid => true; | |||
| public override ShapeType Shape => ShapeType.Square; | |||
| @@ -27,7 +27,11 @@ namespace GameClass.GameObj | |||
| private bool powerSupply = false; | |||
| public bool PowerSupply | |||
| { | |||
| get => powerSupply; | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| return powerSupply; | |||
| } | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| @@ -38,29 +42,58 @@ namespace GameClass.GameObj | |||
| private int openStartTime = 0; | |||
| public int OpenStartTime | |||
| { | |||
| get => openStartTime; | |||
| set | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| openStartTime = value; | |||
| return openStartTime; | |||
| } | |||
| } | |||
| public bool TryToOpen() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| if (!powerSupply || openStartTime > 0) return false; | |||
| openStartTime = Environment.TickCount; | |||
| return true; | |||
| } | |||
| } | |||
| public bool StopOpenning() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| if (openDegree + Environment.TickCount - openStartTime >= GameData.degreeOfOpenedDoorway) | |||
| { | |||
| openDegree = GameData.degreeOfOpenedDoorway; | |||
| return true; | |||
| } | |||
| else | |||
| { | |||
| openDegree = openDegree + Environment.TickCount - openStartTime; | |||
| openStartTime = 0; | |||
| return false; | |||
| } | |||
| } | |||
| } | |||
| public void FinishOpenning() | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| openDegree = GameData.degreeOfOpenedDoorway; | |||
| } | |||
| } | |||
| private int openDegree = 0; | |||
| public int OpenDegree | |||
| { | |||
| get => openDegree; | |||
| set | |||
| get | |||
| { | |||
| if (value > 0) | |||
| lock (gameObjLock) | |||
| openDegree = (value < GameData.degreeOfOpenedDoorway) ? value : GameData.degreeOfOpenedDoorway; | |||
| else | |||
| lock (gameObjLock) | |||
| openDegree = 0; | |||
| lock (gameObjLock) | |||
| return openDegree; | |||
| } | |||
| } | |||
| public bool IsOpen() => (openDegree == GameData.degreeOfOpenedDoorway); | |||
| public bool IsOpen() => (OpenDegree == GameData.degreeOfOpenedDoorway); | |||
| } | |||
| } | |||
| @@ -6,13 +6,11 @@ namespace GameClass.GameObj | |||
| /// <summary> | |||
| /// 紧急出口 | |||
| /// </summary> | |||
| public class EmergencyExit : GameObj | |||
| public class EmergencyExit : Immovable | |||
| { | |||
| public EmergencyExit(XY initPos) : | |||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.EmergencyExit) | |||
| { | |||
| this.place = PlaceType.EmergencyExit; | |||
| this.CanMove = false; | |||
| } | |||
| public override bool IsRigid => true; | |||
| public override ShapeType Shape => ShapeType.Square; | |||
| @@ -5,13 +5,11 @@ namespace GameClass.GameObj | |||
| /// <summary> | |||
| /// 发电机 | |||
| /// </summary> | |||
| public class Generator : GameObj | |||
| public class Generator : Immovable | |||
| { | |||
| public Generator(XY initPos) : | |||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Generator) | |||
| { | |||
| this.place = PlaceType.Generator; | |||
| this.CanMove = false; | |||
| } | |||
| public override bool IsRigid => true; | |||
| public override ShapeType Shape => ShapeType.Square; | |||
| @@ -8,7 +8,6 @@ namespace GameClass.GameObj | |||
| { | |||
| public partial class Map : IMap | |||
| { | |||
| private readonly Dictionary<uint, XY> birthPointList; // 出生点列表 | |||
| public Dictionary<uint, XY> BirthPointList => birthPointList; | |||
| @@ -194,13 +193,12 @@ namespace GameClass.GameObj | |||
| { | |||
| if (playerID == person.ID) | |||
| { | |||
| if (person.CharacterType == CharacterType.TechOtaku && person.FindIActiveSkill(ActiveSkillType.UseRobot).IsBeingUsed) | |||
| if (person.CharacterType == CharacterType.TechOtaku) | |||
| { | |||
| Debugger.Output(person, person.PlayerID.ToString()); | |||
| foreach (Character character in gameObjDict[GameObjType.Character]) | |||
| { | |||
| Debugger.Output(character, character.PlayerID.ToString()); | |||
| if (person.PlayerID + GameData.numOfPeople == character.PlayerID) | |||
| if (((UseRobot)person.FindActiveSkill(ActiveSkillType.UseRobot)).NowPlayerID == character.PlayerID) | |||
| { | |||
| player = character; | |||
| break; | |||
| @@ -233,7 +231,10 @@ namespace GameClass.GameObj | |||
| } | |||
| } | |||
| if (ToDel != null) | |||
| { | |||
| GameObjDict[gameObj.Type].Remove(ToDel); | |||
| ToDel.TryToRemove(); | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| @@ -244,16 +245,19 @@ namespace GameClass.GameObj | |||
| public bool RemoveJustFromMap(GameObj gameObj) | |||
| { | |||
| GameObjLockDict[gameObj.Type].EnterWriteLock(); | |||
| bool flag; | |||
| try | |||
| { | |||
| flag = GameObjDict[gameObj.Type].Remove(gameObj); | |||
| if (GameObjDict[gameObj.Type].Remove(gameObj)) | |||
| { | |||
| gameObj.TryToRemove(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| finally | |||
| { | |||
| GameObjLockDict[gameObj.Type].ExitWriteLock(); | |||
| } | |||
| return flag; | |||
| } | |||
| public void Add(GameObj gameObj) | |||
| { | |||
| @@ -13,56 +13,109 @@ namespace GameClass.GameObj | |||
| /// </summary> | |||
| public static uint[,] defaultMap = new uint[,] | |||
| { | |||
| { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 },//6墙,1-5出生点 | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 },//7草 | |||
| { 6, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 6 },//8机 | |||
| { 6, 0, 0, 0, 0, 6, 0, 6, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 0, 0, 0, 6 },//9大门 | |||
| { 6, 0, 0, 0, 0, 6, 6, 6, 6, 7, 0, 0, 0, 0, 0, 15, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 15, 0, 0, 0, 6 },//10紧急出口 | |||
| { 6, 6, 0, 0, 0, 0, 9, 6, 6, 7, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 7, 7, 6, 6, 6, 6, 6, 6, 11, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 },//11窗 | |||
| { 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 7, 7, 6, 6, 7, 7, 6, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 13, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 6 },//12-14门 | |||
| { 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 7, 7, 7, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 6 },//15箱 | |||
| { 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 7, 7, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 7, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 6, 12, 6, 6, 6, 6, 6, 6, 11, 6, 6, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 7, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 11, 6, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0, 6 }, | |||
| { 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 6, 7, 0, 0, 6 }, | |||
| { 6, 7, 7, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 6, 6, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 6 }, | |||
| { 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 5, 0, 7, 7, 6, 0, 0, 0, 0, 0, 0, 7, 6, 6, 6, 6, 15, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 6, 7, 7, 0, 0, 0, 0, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 7, 6, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 7, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 6, 6, 0, 10, 0, 6 }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 6, 6, 6, 6, 7, 0, 0, 0, 6 }, | |||
| { 6, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 6, 7, 0, 2, 0, 0, 6 }, | |||
| { 6, 0, 6, 0, 0, 0, 0, 0, 0, 6, 11, 6, 6, 6, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 11, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 11, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 6, 12, 6, 6, 6, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 6, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 7, 7, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 6, 7, 0, 0, 0, 8, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 6, 6, 6, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6,6, 7, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 7, 7, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 6, 7, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 6, 6, 6, 6, 7, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6 }, | |||
| { 6, 6, 0, 0, 7, 7, 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 6 }, | |||
| { 6, 6, 15, 0, 0, 0, 7, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 11, 6, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6,6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 15, 0, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0,8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 6, 7, 7, 0, 0, 0, 6, 6, 6, 11, 6, 0, 0, 6, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 6, 0, 6, 7, 7, 6, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 14, 6, 6, 6, 0, 0, 0, 0, 0, 7, 0, 0, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7, 6, 0, 6, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7, 6, 6, 6, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 6, 6, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 11, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 6, 6, 6, 6, 7, 0, 0, 0, 10, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7, 6, 6, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 7, 7, 6, 6, 0, 0, 0, 6 }, | |||
| { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 } | |||
| { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 },//6墙,1-5出生点 | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 },//7草 | |||
| { 6, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 6 },//8机 | |||
| { 6, 0, 0, 0, 0, 6, 0, 6, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 0, 0, 0, 6 },//9大门 | |||
| { 6, 0, 0, 0, 0, 6, 6, 6, 6, 7, 0, 0, 0, 0, 0, 15, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 15, 0, 0, 0, 6 },//10紧急出口 | |||
| { 6, 6, 0, 0, 0, 0, 9, 6, 6, 7, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 7, 7, 6, 6, 6, 6, 6, 6, 11, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 },//11窗 | |||
| { 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 7, 7, 6, 6, 7, 7, 6, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 13, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 6 },//12-14门 | |||
| { 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 7, 7, 7, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 6 },//15箱 | |||
| { 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 7, 7, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 7, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 6, 12, 6, 6, 6, 6, 6, 6, 11, 6, 6, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 7, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 11, 6, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0, 6 }, | |||
| { 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 6, 7, 0, 0, 6 }, | |||
| { 6, 7, 7, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 6, 6, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 6 }, | |||
| { 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 5, 0, 7, 7, 6, 0, 0, 0, 0, 0, 0, 7, 6, 6, 6, 6, 15, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 6, 7, 7, 0, 0, 0, 0, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 7, 6, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 7, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 6, 6, 0, 10, 0, 6 }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 6, 6, 6, 6, 7, 0, 0, 0, 6 }, | |||
| { 6, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 6, 7, 0, 2, 0, 0, 6 }, | |||
| { 6, 0, 6, 0, 0, 0, 0, 0, 0, 6, 11, 6, 6, 6, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 11, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 11, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 6, 12, 6, 6, 6, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 6, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 7, 7, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 6, 7, 0, 0, 0, 8, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 6, 6, 6, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6,6, 7, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 7, 7, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 6, 7, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 6, 6, 6, 6, 7, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6 }, | |||
| { 6, 6, 0, 0, 7, 7, 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 6 }, | |||
| { 6, 6, 15, 0, 0, 0, 7, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 11, 6, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6,6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 15, 0, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0,8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 6, 7, 7, 0, 0, 0, 6, 6, 6, 11, 6, 0, 0, 6, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 6, 0, 6, 7, 7, 6, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 14, 6, 6, 6, 0, 0, 0, 0, 0, 7, 0, 0, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7, 6, 0, 6, 6, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7, 6, 6, 6, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 6, 6, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 11, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 6, 6, 6, 6, 6, 7, 0, 0, 0, 10, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7, 6, 6, 0, 0, 0, 6 }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 7, 7, 6, 6, 0, 0, 0, 6 }, | |||
| { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 }, | |||
| }; | |||
| } | |||
| } | |||
| /* | |||
| { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 11, 6, 6, 6, 6, 6, 6, 6, 6, 11, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 9, 0, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 7, 6, 6, 0, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 0, 6, 7, 7, 0, 6, 6, 6, 7, 7, 7, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 7, 7, 7, 6, 6, 6, 0, 7, 7, 6, 0, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 10, 6, 6, 0, 0, 0, 0, 6, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 6, 6, 6, 0, 0, 0, 0, 6, 6, 6, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 6, 0, 0, 0, 0, 6, 6, 10, 0, 6, }, | |||
| { 6, 0, 6, 6, 7, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 7, 6, 6, 0, 6, }, | |||
| { 6, 0, 0, 7, 7, 0, 0, 0, 0, 0, 6, 6, 0, 0, 6, 6, 6, 6, 12, 6, 6, 6, 6, 0, 0, 0, 0, 6, 6, 6, 6, 12, 6, 6, 6, 6, 0, 0, 6, 6, 0, 0, 0, 0, 0, 7, 7, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 6, 6, 6, 0, 0, 0, 6, 6, 6, 6, 15, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 15, 6, 6, 6, 6, 0, 0, 0, 6, 6, 6, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 6, 6, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 6, 6, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 6, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 6, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 6, 0, 0, 0, 0, 6, 0, 0, 1, 0, 0, 0, 7, 7, 6, 6, 6, 14, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 0, 0, 0, 2, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 7, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 7, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 0, 0, 0, 6, 0, 0, 8, 0, 7, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 7, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 7, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 7, 0, 0, 0, 6, 15, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 15, 6, 0, 0, 0, 7, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 7, 7, 6, 6, 8, 0, 0, 0, 7, 7, 7, 0, 6, 6, 0, 7, 7, 7, 0, 0, 0, 8, 6, 6, 7, 7, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 7, 6, 6, 0, 0, 0, 0, 7, 7, 0, 0, 0, 6, 6, 0, 0, 0, 7, 7, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 12, 0, 0, 0, 6, 6, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 6, 6, 0, 0, 0, 13, 0, 0, 0, 0, 6, 0, 0, 6, }, | |||
| { 6, 0, 6, 6, 0, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0, 6, }, | |||
| { 6, 0, 11, 0, 0, 0, 6, 0, 6, 0, 0, 0, 6, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 6, 0, 0, 0, 6, 0, 6, 0, 0, 0, 11, 0, 6, }, | |||
| { 6, 0, 6, 0, 0, 0, 6, 0, 6, 0, 0, 0, 11, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 11, 0, 0, 0, 6, 0, 6, 0, 0, 0, 6, 0, 6, }, | |||
| { 6, 0, 6, 0, 6, 6, 6, 0, 6, 6, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 6, 0, 0, 6, 6, 0, 6, 6, 6, 0, 6, 0, 6, }, | |||
| { 6, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 6, }, | |||
| { 6, 0, 6, 0, 0, 0, 0, 0, 0, 6, 6, 0, 6, 0, 0, 6, 6, 6, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 6, 6, 6, 0, 0, 6, 0, 6, 6, 0, 0, 0, 0, 0, 0, 6, 0, 6, }, | |||
| { 6, 0, 6, 0, 0, 0, 0, 0, 0, 6, 6, 0, 6, 0, 0, 6, 6, 6, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 6, 6, 6, 0, 0, 6, 0, 6, 6, 0, 0, 0, 0, 0, 0, 6, 0, 6, }, | |||
| { 6, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, 0, 6, }, | |||
| { 6, 0, 6, 0, 6, 6, 6, 0, 6, 6, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 6, 0, 0, 6, 6, 0, 6, 6, 6, 0, 6, 0, 6, }, | |||
| { 6, 0, 6, 0, 0, 0, 6, 0, 6, 0, 0, 0, 11, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 11, 0, 0, 0, 6, 0, 6, 0, 0, 0, 6, 0, 6, }, | |||
| { 6, 0, 11, 0, 0, 0, 6, 0, 6, 0, 0, 0, 6, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 6, 0, 0, 0, 6, 0, 6, 0, 0, 0, 11, 0, 6, }, | |||
| { 6, 0, 6, 6, 0, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0, 6, }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 12, 0, 0, 0, 6, 6, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 6, 6, 0, 0, 0, 13, 0, 0, 0, 0, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 7, 6, 6, 0, 0, 0, 0, 7, 7, 0, 0, 0, 6, 6, 0, 0, 0, 7, 7, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 7, 7, 6, 6, 8, 0, 0, 0, 7, 7, 7, 0, 6, 6, 0, 7, 7, 7, 0, 0, 0, 8, 6, 6, 7, 7, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 7, 0, 0, 0, 6, 15, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 15, 6, 0, 0, 0, 7, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 7, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 7, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 7, 0, 8, 0, 0, 6, 0, 0, 0, 0, 0, 0, 7, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 6, 6, 0, 0, 0, 0, 6, 0, 0, 3, 0, 0, 0, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 14, 6, 6, 6, 7, 7, 0, 0, 0, 4, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 6, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 6, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 6, 6, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 6, 6, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 6, 6, 6, 0, 0, 0, 6, 6, 6, 6, 15, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 15, 6, 6, 6, 6, 0, 0, 0, 6, 6, 6, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 7, 7, 0, 0, 0, 0, 0, 6, 6, 0, 0, 6, 6, 6, 6, 13, 6, 6, 6, 6, 0, 0, 0, 0, 6, 6, 6, 6, 13, 6, 6, 6, 6, 0, 0, 6, 6, 0, 0, 0, 0, 0, 7, 7, 0, 0, 6, }, | |||
| { 6, 0, 6, 6, 7, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 7, 6, 6, 0, 6, }, | |||
| { 6, 0, 10, 6, 6, 0, 0, 0, 0, 6, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 6, 6, 6, 0, 0, 0, 0, 6, 6, 6, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 6, 0, 0, 0, 0, 6, 6, 10, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 0, 6, 7, 7, 0, 6, 6, 6, 7, 7, 7, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 7, 7, 7, 6, 6, 6, 0, 7, 7, 6, 0, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 0, 6, 6, 7, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 7, 6, 6, 0, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 0, 9, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 11, 6, 6, 6, 6, 6, 6, 6, 6, 11, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6, }, | |||
| { 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, }, | |||
| { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, },*/ | |||
| @@ -5,13 +5,11 @@ namespace GameClass.GameObj | |||
| /// <summary> | |||
| /// 墙体 | |||
| /// </summary> | |||
| public class Wall : GameObj | |||
| public class Wall : Immovable | |||
| { | |||
| public Wall(XY initPos) : | |||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Wall) | |||
| { | |||
| this.place = PlaceType.Wall; | |||
| this.CanMove = false; | |||
| } | |||
| public override bool IsRigid => true; | |||
| public override ShapeType Shape => ShapeType.Square; | |||
| @@ -6,13 +6,11 @@ namespace GameClass.GameObj | |||
| /// <summary> | |||
| /// 窗 | |||
| /// </summary> | |||
| public class Window : GameObj | |||
| public class Window : Immovable, IWindow | |||
| { | |||
| public Window(XY initPos) : | |||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Window) | |||
| { | |||
| this.place = PlaceType.Window; | |||
| this.CanMove = false; | |||
| } | |||
| public override bool IsRigid => true; | |||
| public override ShapeType Shape => ShapeType.Square; | |||
| @@ -27,15 +25,46 @@ namespace GameClass.GameObj | |||
| return false; | |||
| } | |||
| private XY stage = new(0, 0); | |||
| public XY Stage | |||
| { | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| return stage; | |||
| } | |||
| } | |||
| private Character? whoIsClimbing = null; | |||
| public Character? WhoIsClimbing | |||
| { | |||
| get => whoIsClimbing; | |||
| set | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| whoIsClimbing = value; | |||
| return whoIsClimbing; | |||
| } | |||
| } | |||
| public bool TryToClimb(ICharacter character) | |||
| { | |||
| lock (gameObjLock) | |||
| if (whoIsClimbing == null) | |||
| { | |||
| stage = new(0, 0); | |||
| whoIsClimbing = (Character)character; | |||
| return true; | |||
| } | |||
| else return false; | |||
| } | |||
| public void FinishClimbing() | |||
| { | |||
| lock (gameObjLock) | |||
| whoIsClimbing = null; | |||
| } | |||
| public void Enter2Stage(XY xy) | |||
| { | |||
| lock (gameObjLock) | |||
| stage = xy; | |||
| } | |||
| } | |||
| } | |||
| @@ -4,79 +4,117 @@ using System.Threading; | |||
| namespace GameClass.GameObj | |||
| { | |||
| /// <summary> | |||
| /// 一切游戏元素的总基类,与THUAI4不同,继承IMoveable接口(出于一切物体其实都是可运动的指导思想)——LHR | |||
| /// </summary> | |||
| public abstract class Moveable : GameObj, IMoveable | |||
| { | |||
| protected readonly object moveObjLock = new(); | |||
| public object MoveLock => moveObjLock; | |||
| protected readonly object actionLock = new(); | |||
| public object ActionLock => actionLock; | |||
| //player.actionLock>其他.actionLock | |||
| private readonly ReaderWriterLockSlim moveReaderWriterLock = new(); | |||
| public ReaderWriterLockSlim MoveReaderWriterLock => moveReaderWriterLock; | |||
| //规定moveReaderWriterLock<actionLock | |||
| private bool isMoving; | |||
| public bool IsMoving | |||
| public Semaphore ThreadNum { get; } = new(1, 1); | |||
| protected long stateNum = 0; | |||
| public long StateNum | |||
| { | |||
| get => isMoving; | |||
| get | |||
| { | |||
| lock (actionLock) | |||
| return stateNum; | |||
| } | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| isMoving = value; | |||
| } | |||
| lock (actionLock) stateNum = value; | |||
| } | |||
| } | |||
| public bool IsAvailable => !IsMoving && CanMove && !IsResetting; // 是否能接收指令 | |||
| public override XY Position | |||
| { | |||
| get | |||
| { | |||
| lock (actionLock) | |||
| return position; | |||
| } | |||
| } | |||
| protected int moveSpeed; | |||
| /// <summary> | |||
| /// 移动速度 | |||
| /// </summary> | |||
| public int MoveSpeed | |||
| public override XY FacingDirection | |||
| { | |||
| get => moveSpeed; | |||
| set | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| moveSpeed = value; | |||
| } | |||
| lock (actionLock) | |||
| return facingDirection; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 原初移动速度 | |||
| /// </summary> | |||
| public int OrgMoveSpeed { get; protected set; } | |||
| private int isMoving = 0; | |||
| public bool IsMoving | |||
| { | |||
| get => (Interlocked.CompareExchange(ref isMoving, 0, 0) == 1); | |||
| set => Interlocked.Exchange(ref isMoving, value ? 1 : 0); | |||
| } | |||
| // 移动,改变坐标 | |||
| public long MovingSetPos(XY moveVec, PlaceType place) | |||
| public long MovingSetPos(XY moveVec, long stateNo) | |||
| { | |||
| if (moveVec.x != 0 || moveVec.y != 0) | |||
| lock (gameObjLock) | |||
| { | |||
| lock (actionLock) | |||
| { | |||
| FacingDirection = moveVec; | |||
| this.Position += moveVec; | |||
| this.place = place; | |||
| if (!CanMove || IsRemoved) return -1; | |||
| if (stateNo != stateNum) return -1; | |||
| facingDirection = moveVec; | |||
| this.position += moveVec; | |||
| } | |||
| } | |||
| return moveVec * moveVec; | |||
| } | |||
| public void ReSetPos(XY pos, PlaceType place) | |||
| public void ReSetPos(XY position) | |||
| { | |||
| lock (actionLock) | |||
| { | |||
| this.position = position; | |||
| } | |||
| } | |||
| private int canMove; | |||
| public override bool CanMove | |||
| { | |||
| get => (Interlocked.CompareExchange(ref canMove, 0, 0) == 1); | |||
| } | |||
| public void ReSetCanMove(bool value) | |||
| { | |||
| Interlocked.Exchange(ref canMove, (value ? 1 : 0)); | |||
| } | |||
| public bool IsAvailableForMove // 是否能接收移动指令 | |||
| { | |||
| lock (gameObjLock) | |||
| get | |||
| { | |||
| this.Position = pos; | |||
| this.place = place; | |||
| lock (actionLock) | |||
| { | |||
| return !IsMoving && CanMove && !IsRemoved; | |||
| } | |||
| } | |||
| } | |||
| protected int moveSpeed; | |||
| /// <summary> | |||
| /// 设置移动速度 | |||
| /// 移动速度 | |||
| /// </summary> | |||
| /// <param name="newMoveSpeed">新速度</param> | |||
| public void SetMoveSpeed(int newMoveSpeed) | |||
| public int MoveSpeed | |||
| { | |||
| MoveSpeed = newMoveSpeed; | |||
| get => Interlocked.CompareExchange(ref moveSpeed, 0, 0); | |||
| set => Interlocked.Exchange(ref moveSpeed, value); | |||
| } | |||
| /// <summary> | |||
| /// 原初移动速度 | |||
| /// </summary> | |||
| public int OrgMoveSpeed { get; protected set; } | |||
| /* /// <summary> | |||
| /// 复活时数据重置 | |||
| /// </summary> | |||
| @@ -87,16 +125,11 @@ namespace GameClass.GameObj | |||
| this.FacingDirection = new XY(1, 0); | |||
| isMoving = false; | |||
| CanMove = false; | |||
| IsResetting = true; | |||
| IsRemoved = true; | |||
| this.Position = birthPos; | |||
| this.Place= place; | |||
| } | |||
| }*/ | |||
| /// <summary> | |||
| /// 为了使IgnoreCollide多态化并使GameObj能不报错地继承IMoveable | |||
| /// 在xfgg点播下设计了这个抽象辅助方法,在具体类中实现 | |||
| /// </summary> | |||
| /// <returns> 依具体类及该方法参数而定,默认为false </returns> | |||
| public Moveable(XY initPos, int initRadius, GameObjType initType) : base(initPos, initRadius, initType) | |||
| { | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System.Threading; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -8,13 +9,21 @@ namespace GameClass.GameObj | |||
| /// </summary> | |||
| public abstract class ObjOfCharacter : Moveable, IObjOfCharacter | |||
| { | |||
| private ReaderWriterLockSlim objOfCharacterReaderWriterLock = new(); | |||
| public ReaderWriterLockSlim ObjOfCharacterReaderWriterLock => objOfCharacterReaderWriterLock; | |||
| private ICharacter? parent = null; // 主人 | |||
| public ICharacter? Parent | |||
| { | |||
| get => parent; | |||
| get | |||
| { | |||
| lock (objOfCharacterReaderWriterLock) | |||
| { | |||
| return parent; | |||
| } | |||
| } | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| lock (objOfCharacterReaderWriterLock) | |||
| { | |||
| parent = value; | |||
| } | |||
| @@ -6,13 +6,11 @@ namespace GameClass.GameObj | |||
| /// <summary> | |||
| /// 逻辑墙 | |||
| /// </summary> | |||
| public class OutOfBoundBlock : GameObj, IOutOfBound | |||
| public class OutOfBoundBlock : Immovable, IOutOfBound | |||
| { | |||
| public OutOfBoundBlock(XY initPos) : | |||
| base(initPos, int.MaxValue, GameObjType.OutOfBoundBlock) | |||
| { | |||
| this.place = PlaceType.Wall; | |||
| this.CanMove = false; | |||
| } | |||
| public override bool IsRigid => true; | |||
| @@ -1,15 +1,16 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System.Threading; | |||
| namespace GameClass.GameObj | |||
| { | |||
| public abstract class Prop : ObjOfCharacter | |||
| public abstract class Gadget : ObjOfCharacter | |||
| { | |||
| public override bool IsRigid => true; | |||
| public override bool IgnoreCollideExecutor(IGameObj targetObj) | |||
| { | |||
| if (targetObj.Type == GameObjType.Prop || targetObj.Type == GameObjType.Bullet | |||
| if (targetObj.Type == GameObjType.Gadget || targetObj.Type == GameObjType.Bullet | |||
| || targetObj.Type == GameObjType.Character || targetObj.Type == GameObjType.Chest) | |||
| return true; | |||
| return false; | |||
| @@ -17,61 +18,87 @@ namespace GameClass.GameObj | |||
| public override ShapeType Shape => ShapeType.Square; | |||
| public abstract bool IsUsable(); | |||
| public abstract PropType GetPropType(); | |||
| public Prop(XY initPos, PlaceType place, int radius = GameData.PropRadius) : | |||
| base(initPos, radius, GameObjType.Prop) | |||
| public Gadget(XY initPos, int radius = GameData.propRadius) : | |||
| base(initPos, radius, GameObjType.Gadget) | |||
| { | |||
| this.place = place; | |||
| this.CanMove = false; | |||
| this.moveSpeed = GameData.PropMoveSpeed; | |||
| this.ReSetCanMove(false); | |||
| this.MoveSpeed = GameData.propMoveSpeed; | |||
| } | |||
| } | |||
| public abstract class Tool : Gadget | |||
| { | |||
| private bool isUsed = false; | |||
| public bool IsUsed | |||
| { | |||
| get | |||
| { | |||
| lock (gameObjLock) | |||
| return isUsed; | |||
| } | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| isUsed = value; | |||
| } | |||
| } | |||
| } | |||
| public override bool IsUsable() => !IsUsed; | |||
| public Tool(XY initPos) : base(initPos) { } | |||
| } | |||
| public abstract class Consumables : Gadget | |||
| { | |||
| public override bool IsUsable() => true; | |||
| public Consumables(XY initPos) : base(initPos) { } | |||
| } | |||
| ///// <summary> | |||
| ///// 坑人地雷 | |||
| ///// </summary> | |||
| // public abstract class DebuffMine : Prop | |||
| // public abstract class DebuffMine : Gadget | |||
| //{ | |||
| // public DebuffMine(XYPosition initPos) : base(initPos) { } | |||
| // } | |||
| #region 所有增益道具 | |||
| /// <summary> | |||
| /// 增加速度 | |||
| /// </summary> | |||
| public sealed class AddSpeed : Prop | |||
| public sealed class AddSpeed : Consumables | |||
| { | |||
| public AddSpeed(XY initPos, PlaceType placeType) : | |||
| base(initPos, placeType) | |||
| public AddSpeed(XY initPos) : | |||
| base(initPos) | |||
| { | |||
| } | |||
| public override PropType GetPropType() => PropType.AddSpeed; | |||
| } | |||
| /// <summary> | |||
| /// 复活甲 | |||
| /// </summary> | |||
| public sealed class AddLifeOrClairaudience : Prop | |||
| public sealed class AddLifeOrClairaudience : Consumables | |||
| { | |||
| public AddLifeOrClairaudience(XY initPos, PlaceType placeType) : | |||
| base(initPos, placeType) | |||
| public AddLifeOrClairaudience(XY initPos) : | |||
| base(initPos) | |||
| { | |||
| } | |||
| public override PropType GetPropType() => PropType.AddLifeOrClairaudience; | |||
| } | |||
| public sealed class AddHpOrAp : Prop | |||
| public sealed class AddHpOrAp : Consumables | |||
| { | |||
| public AddHpOrAp(XY initPos, PlaceType placeType) : | |||
| base(initPos, placeType) | |||
| public AddHpOrAp(XY initPos) : | |||
| base(initPos) | |||
| { | |||
| } | |||
| public override PropType GetPropType() => PropType.AddHpOrAp; | |||
| } | |||
| public sealed class RecoveryFromDizziness : Prop | |||
| public sealed class RecoveryFromDizziness : Consumables | |||
| { | |||
| public RecoveryFromDizziness(XY initPos, PlaceType placeType) : | |||
| base(initPos, placeType) | |||
| public RecoveryFromDizziness(XY initPos) : | |||
| base(initPos) | |||
| { | |||
| } | |||
| public override PropType GetPropType() => PropType.RecoveryFromDizziness; | |||
| @@ -79,42 +106,43 @@ namespace GameClass.GameObj | |||
| /// <summary> | |||
| /// 矛盾 | |||
| /// </summary> | |||
| public sealed class ShieldOrSpear : Prop | |||
| public sealed class ShieldOrSpear : Consumables | |||
| { | |||
| public ShieldOrSpear(XY initPos, PlaceType placeType) : base(initPos, placeType) | |||
| public ShieldOrSpear(XY initPos) : base(initPos) | |||
| { | |||
| } | |||
| public override PropType GetPropType() => PropType.ShieldOrSpear; | |||
| } | |||
| public sealed class Key3 : Prop | |||
| #endregion | |||
| public sealed class Key3 : Tool | |||
| { | |||
| public Key3(XY initPos, PlaceType placeType) : base(initPos, placeType) | |||
| public Key3(XY initPos) : base(initPos) | |||
| { | |||
| } | |||
| public override PropType GetPropType() => PropType.Key3; | |||
| } | |||
| public sealed class Key5 : Prop | |||
| public sealed class Key5 : Tool | |||
| { | |||
| public Key5(XY initPos, PlaceType placeType) : base(initPos, placeType) | |||
| public Key5(XY initPos) : base(initPos) | |||
| { | |||
| } | |||
| public override PropType GetPropType() => PropType.Key5; | |||
| } | |||
| public sealed class Key6 : Prop | |||
| public sealed class Key6 : Tool | |||
| { | |||
| public Key6(XY initPos, PlaceType placeType) : base(initPos, placeType) | |||
| public Key6(XY initPos) : base(initPos) | |||
| { | |||
| } | |||
| public override PropType GetPropType() => PropType.Key6; | |||
| } | |||
| public sealed class NullProp : Prop | |||
| public sealed class NullProp : Gadget | |||
| { | |||
| public NullProp(PlaceType placeType = PlaceType.Wall) : base(new XY(1, 1), placeType) | |||
| public override bool IsUsable() => false; | |||
| public NullProp() : base(new XY(1, 1)) | |||
| { | |||
| } | |||
| public override PropType GetPropType() => PropType.Null; | |||
| } | |||
| #endregion | |||
| // #region 所有坑人地雷 | |||
| ///// <summary> | |||
| ///// 减速 | |||
| @@ -143,26 +171,26 @@ namespace GameClass.GameObj | |||
| // #endregion | |||
| public static class PropFactory | |||
| { | |||
| public static Prop GetProp(PropType propType, XY pos, PlaceType place) | |||
| public static Gadget GetConsumables(PropType propType, XY pos) | |||
| { | |||
| switch (propType) | |||
| { | |||
| case PropType.AddSpeed: | |||
| return new AddSpeed(pos, place); | |||
| return new AddSpeed(pos); | |||
| case PropType.AddLifeOrClairaudience: | |||
| return new AddLifeOrClairaudience(pos, place); | |||
| return new AddLifeOrClairaudience(pos); | |||
| case PropType.ShieldOrSpear: | |||
| return new ShieldOrSpear(pos, place); | |||
| return new ShieldOrSpear(pos); | |||
| case PropType.AddHpOrAp: | |||
| return new AddHpOrAp(pos, place); | |||
| return new AddHpOrAp(pos); | |||
| case PropType.RecoveryFromDizziness: | |||
| return new RecoveryFromDizziness(pos, place); | |||
| return new RecoveryFromDizziness(pos); | |||
| case PropType.Key3: | |||
| return new Key3(pos, place); | |||
| return new Key3(pos); | |||
| case PropType.Key5: | |||
| return new Key5(pos, place); | |||
| return new Key5(pos); | |||
| case PropType.Key6: | |||
| return new Key6(pos, place); | |||
| return new Key6(pos); | |||
| default: | |||
| return new NullProp(); | |||
| } | |||
| @@ -0,0 +1,92 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System.Threading; | |||
| namespace GameClass.GameObj | |||
| { | |||
| public abstract class Item : ObjOfCharacter | |||
| { | |||
| public override bool IsRigid => true; | |||
| public override bool IgnoreCollideExecutor(IGameObj targetObj) => false; | |||
| public override ShapeType Shape => ShapeType.Square; | |||
| public abstract PropType GetPropType(); | |||
| public Item(XY initPos, int radius = GameData.propRadius) : | |||
| base(initPos, radius, GameObjType.Item) | |||
| { | |||
| this.ReSetCanMove(false); | |||
| this.MoveSpeed = 0; | |||
| } | |||
| } | |||
| ///// <summary> | |||
| ///// 坑人地雷 | |||
| ///// </summary> | |||
| // public abstract class DebuffMine : Gadget | |||
| //{ | |||
| // public DebuffMine(XYPosition initPos) : base(initPos) { } | |||
| // } | |||
| public sealed class CraftingBench : Item | |||
| { | |||
| public CraftingBench(XY initPos, Character character, int num) : | |||
| base(initPos) | |||
| { | |||
| Parent = character; | |||
| this.num = num; | |||
| } | |||
| private readonly int num; | |||
| private long parentStateNum; | |||
| public long ParentStateNum | |||
| { | |||
| get => Interlocked.Read(ref parentStateNum); | |||
| set => Interlocked.Exchange(ref parentStateNum, value); | |||
| } | |||
| public void StopSkill() | |||
| { | |||
| ((SummonGolem)Parent!.FindActiveSkill(ActiveSkillType.SummonGolem)).DeleteGolem((int)num); | |||
| } | |||
| public void TryStopSkill() | |||
| { | |||
| lock (Parent!.ActionLock) | |||
| { | |||
| if (Parent!.StateNum == parentStateNum) | |||
| { | |||
| Parent!.SetPlayerState(); | |||
| } | |||
| } | |||
| } | |||
| public override PropType GetPropType() => PropType.CraftingBench; | |||
| } | |||
| // #region 所有坑人地雷 | |||
| ///// <summary> | |||
| ///// 减速 | |||
| ///// </summary> | |||
| // public sealed class MinusSpeed : DebuffMine | |||
| //{ | |||
| // public MinusSpeed(XYPosition initPos) : base(initPos) { } | |||
| // public override PropType GetPropType() => PropType.minusSpeed; | |||
| // } | |||
| ///// <summary> | |||
| ///// 减少攻击力 | |||
| ///// </summary> | |||
| // public sealed class MinusAP : DebuffMine | |||
| //{ | |||
| // public MinusAP(XYPosition initPos) : base(initPos) { } | |||
| // public override PropType GetPropType() => PropType.minusAP; | |||
| // } | |||
| ///// <summary> | |||
| ///// 增加冷却 | |||
| ///// </summary> | |||
| // public sealed class AddCD : DebuffMine | |||
| //{ | |||
| // public AddCD(XYPosition initPos) : base(initPos) { } | |||
| // public override PropType GetPropType() => PropType.addCD; | |||
| // } | |||
| // #endregion | |||
| } | |||
| @@ -1,25 +1,20 @@ | |||
| using Preparation.Utility; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GameClass.GameObj | |||
| { | |||
| // 为方便界面组做道具拾起特效,现引入“被捡起的道具”,在每帧发送给界面组 | |||
| public class PickedProp : GameObj | |||
| /* | |||
| public class Item : Immovable | |||
| { | |||
| public override ShapeType Shape => ShapeType.Circle; | |||
| public override bool IsRigid => false; | |||
| public long MappingID { get; } | |||
| public Prop PropHasPicked; | |||
| public PickedProp(Prop prop) : | |||
| base(prop.Position, prop.Radius, GameObjType.PickedProp) | |||
| public readonly Gadget propHasPicked; | |||
| public Item(Gadget prop) : | |||
| base(prop.Position, prop.Radius, GameObjType.Item) | |||
| { | |||
| this.place = prop.Place; | |||
| this.PropHasPicked = prop; | |||
| this.propHasPicked = prop; | |||
| this.MappingID = prop.ID; | |||
| } | |||
| } | |||
| }*/ | |||
| } | |||
| @@ -20,18 +20,6 @@ namespace GameEngine | |||
| private readonly ITimer gameTimer; | |||
| private readonly Action<IMoveable> EndMove; | |||
| public readonly uint[,] ProtoGameMap; | |||
| public PlaceType GetPlaceType(XY Position) | |||
| { | |||
| try | |||
| { | |||
| return (PlaceType)ProtoGameMap[Position.x / GameData.numOfPosGridPerCell, Position.y / GameData.numOfPosGridPerCell]; | |||
| } | |||
| catch | |||
| { | |||
| return PlaceType.Null; | |||
| } | |||
| } | |||
| public IGameObj? CheckCollision(IMoveable obj, XY Pos) | |||
| { | |||
| @@ -52,7 +40,6 @@ namespace GameEngine | |||
| Action<IMoveable> EndMove | |||
| ) | |||
| { | |||
| this.ProtoGameMap = gameMap.ProtoGameMap; | |||
| this.gameTimer = gameMap.Timer; | |||
| this.EndMove = EndMove; | |||
| this.OnCollision = OnCollision; | |||
| @@ -64,35 +51,69 @@ namespace GameEngine | |||
| /// </summary> | |||
| /// <param name="obj">移动物体,默认obj.Rigid为true</param> | |||
| /// <param name="moveVec">移动的位移向量</param> | |||
| private void MoveMax(IMoveable obj, XY moveVec) | |||
| private bool MoveMax(IMoveable obj, XY moveVec, long stateNum) | |||
| { | |||
| /*由于四周是墙,所以人物永远不可能与越界方块碰撞*/ | |||
| XY nextPos = obj.Position + moveVec; | |||
| double maxLen = collisionChecker.FindMax(obj, nextPos, moveVec); | |||
| maxLen = Math.Min(maxLen, obj.MoveSpeed / GameData.numOfStepPerSecond); | |||
| obj.MovingSetPos(new XY(moveVec, maxLen), GetPlaceType(nextPos)); | |||
| return (obj.MovingSetPos(new XY(moveVec, maxLen), stateNum)) >= 0; | |||
| } | |||
| public void MoveObj(IMoveable obj, int moveTime, double direction) | |||
| private bool LoopDo(IMoveable obj, double direction, ref double deltaLen, long stateNum) | |||
| { | |||
| if (obj.IsMoving) // 已经移动的物体不能再移动 | |||
| return; | |||
| if (!obj.IsAvailable || !gameTimer.IsGaming) | |||
| return; | |||
| double moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | |||
| XY res = new(direction, moveVecLength); | |||
| // 越界情况处理:如果越界,则与越界方块碰撞 | |||
| bool flag; // 循环标志 | |||
| do | |||
| { | |||
| flag = false; | |||
| IGameObj? collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res); | |||
| if (collisionObj == null) | |||
| break; | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| return false; | |||
| case AfterCollision.MoveMax: | |||
| if (!MoveMax(obj, res, stateNum)) return false; | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| break; | |||
| } | |||
| } while (flag); | |||
| long moveL = obj.MovingSetPos(res, stateNum); | |||
| if (moveL == -1) return false; | |||
| deltaLen = deltaLen + moveVecLength - Math.Sqrt(moveL); | |||
| return true; | |||
| } | |||
| public void MoveObj(IMoveable obj, int moveTime, double direction, long stateNum) | |||
| { | |||
| if (!gameTimer.IsGaming) return; | |||
| lock (obj.ActionLock) | |||
| { | |||
| if (!obj.IsAvailableForMove) { EndMove(obj); return; } | |||
| obj.IsMoving = true; | |||
| } | |||
| long threadNum = (obj.Type == GameObjType.Character) ? ((ICharacter)obj).ThreadNum : 0;//对人特殊处理 | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| lock (obj.MoveLock) | |||
| obj.IsMoving = true; | |||
| double moveVecLength = 0.0; | |||
| XY res = new(direction, moveVecLength); | |||
| double deltaLen = moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); // 转向,并用deltaLen存储行走的误差 | |||
| double deltaLen = (double)0.0; // 转向,并用deltaLen存储行走的误差 | |||
| IGameObj? collisionObj = null; | |||
| bool isDestroyed = false; | |||
| bool isEnded = false; | |||
| bool flag; // 循环标志 | |||
| do | |||
| @@ -109,34 +130,82 @@ namespace GameEngine | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| isEnded = true; | |||
| break; | |||
| case AfterCollision.MoveMax: | |||
| break; | |||
| } | |||
| } while (flag); | |||
| if (!isDestroyed) | |||
| if (isEnded) | |||
| { | |||
| obj.IsMoving = false; | |||
| EndMove(obj); | |||
| return; | |||
| } | |||
| else | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| () => gameTimer.IsGaming && obj.CanMove && !obj.IsResetting && obj.IsMoving, | |||
| () => | |||
| if (moveTime >= GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond) | |||
| { | |||
| Thread.Sleep(GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); | |||
| new FrameRateTaskExecutor<int>( | |||
| () => gameTimer.IsGaming, | |||
| () => | |||
| { | |||
| if (obj.StateNum != stateNum || !obj.CanMove || obj.IsRemoved) | |||
| return !(isEnded = true); | |||
| return !(isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum)); | |||
| }, | |||
| GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond, | |||
| () => | |||
| { | |||
| return 0; | |||
| }, | |||
| maxTotalDuration: moveTime - GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond | |||
| ) | |||
| { | |||
| moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | |||
| res = new XY(direction, moveVecLength); | |||
| //对人特殊处理 | |||
| if (threadNum > 0 && ((ICharacter)obj).ThreadNum != threadNum) return false; | |||
| // 越界情况处理:如果越界,则与越界方块碰撞 | |||
| bool flag; // 循环标志 | |||
| do | |||
| AllowTimeExceed = true, | |||
| MaxTolerantTimeExceedCount = ulong.MaxValue, | |||
| TimeExceedAction = b => | |||
| { | |||
| flag = false; | |||
| collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res); | |||
| if (collisionObj == null) | |||
| break; | |||
| 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(); | |||
| if (!isEnded && obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved) | |||
| isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum); | |||
| } | |||
| if (isEnded) | |||
| { | |||
| obj.IsMoving = false; | |||
| EndMove(obj); | |||
| return; | |||
| } | |||
| if (obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved) | |||
| { | |||
| int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); | |||
| if (leftTime > 0) | |||
| { | |||
| Thread.Sleep(leftTime); // 多移动的在这里补回来 | |||
| } | |||
| do | |||
| { | |||
| flag = false; | |||
| moveVecLength = (double)deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; | |||
| res = new XY(direction, moveVecLength); | |||
| if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) | |||
| { | |||
| obj.MovingSetPos(res, stateNum); | |||
| } | |||
| else | |||
| { | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| @@ -144,87 +213,19 @@ namespace GameEngine | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| return false; | |||
| isEnded = true; | |||
| break; | |||
| case AfterCollision.MoveMax: | |||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||
| MoveMax(obj, res); | |||
| MoveMax(obj, res, stateNum); | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| break; | |||
| } | |||
| } while (flag); | |||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||
| deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); | |||
| return true; | |||
| }, | |||
| GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond, | |||
| () => | |||
| { | |||
| int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); | |||
| bool flag; | |||
| do | |||
| { | |||
| flag = false; | |||
| if (!isDestroyed) | |||
| { | |||
| moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; | |||
| res = new XY(direction, moveVecLength); | |||
| if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) | |||
| { | |||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||
| obj.MovingSetPos(res, GetPlaceType(obj.Position + res)); | |||
| } | |||
| else | |||
| { | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| { | |||
| 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: | |||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||
| MoveMax(obj, res); | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } while (flag); | |||
| if (leftTime > 0 && obj.IsMoving) | |||
| { | |||
| 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(); | |||
| } while (flag); | |||
| } | |||
| obj.IsMoving = false; // 结束移动 | |||
| EndMove(obj); | |||
| } | |||
| } | |||
| ).Start(); | |||
| @@ -1,4 +1,5 @@ | |||
| using System; | |||
| using System.Numerics; | |||
| using System.Threading; | |||
| using GameClass.GameObj; | |||
| using GameEngine; | |||
| @@ -13,57 +14,69 @@ namespace Gaming | |||
| private readonly ActionManager actionManager; | |||
| private class ActionManager | |||
| { | |||
| // 人物移动 | |||
| private void SkillWhenColliding(Character player, IGameObj collisionObj) | |||
| { | |||
| if (collisionObj.Type == GameObjType.Bullet) | |||
| { | |||
| if (((Bullet)collisionObj).Parent != player && ((Bullet)collisionObj).TypeOfBullet == BulletType.JumpyDumpty) | |||
| { | |||
| if (characterManager.BeStunned((Character)player, GameData.timeOfStunnedWhenJumpyDumpty)) | |||
| player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStunnedWhenJumpyDumpty)); | |||
| gameMap.Remove((GameObj)collisionObj); | |||
| } | |||
| } | |||
| if (player.FindIActiveSkill(ActiveSkillType.CanBeginToCharge).IsBeingUsed && collisionObj.Type == GameObjType.Character && ((Character)collisionObj).IsGhost()) | |||
| { | |||
| if (characterManager.BeStunned((Character)collisionObj, GameData.timeOfGhostStunnedWhenCharge)) | |||
| player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.timeOfGhostStunnedWhenCharge)); | |||
| characterManager.BeStunned(player, GameData.timeOfStudentStunnedWhenCharge); | |||
| } | |||
| } | |||
| public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) | |||
| { | |||
| if (moveTimeInMilliseconds < 5) return false; | |||
| if (!playerToMove.Commandable() || !TryToStop()) return false; | |||
| if (playerToMove.IsMoving) return false; | |||
| characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving); | |||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); | |||
| long stateNum = playerToMove.SetPlayerState(PlayerStateType.Moving); | |||
| if (stateNum == -1) return false; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| playerToMove.ThreadNum.WaitOne(); | |||
| if (stateNum != playerToMove.StateNum) | |||
| playerToMove.ThreadNum.Release(); | |||
| else | |||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection, stateNum); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return true; | |||
| } | |||
| public bool MovePlayerWhenStunned(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) | |||
| { | |||
| if (!playerToMove.Commandable() && playerToMove.PlayerState != PlayerStateType.Stunned) return false; | |||
| characterManager.BeStunned(playerToMove, moveTimeInMilliseconds); | |||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); | |||
| if (playerToMove.CharacterType == CharacterType.Robot) return false; | |||
| long stateNum = playerToMove.SetPlayerState(PlayerStateType.Charmed); | |||
| if (stateNum == -1) return false; | |||
| new Thread | |||
| (() => | |||
| { | |||
| playerToMove.ThreadNum.WaitOne(); | |||
| if (stateNum != playerToMove.StateNum) | |||
| playerToMove.ThreadNum.Release(); | |||
| else | |||
| { | |||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection, playerToMove.StateNum); | |||
| Thread.Sleep(moveTimeInMilliseconds); | |||
| lock (playerToMove.ActionLock) | |||
| { | |||
| if (stateNum == playerToMove.StateNum) | |||
| playerToMove.SetPlayerStateNaturally(); | |||
| } | |||
| } | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return true; | |||
| } | |||
| public bool Stop(Character player) | |||
| public static bool Stop(Character player) | |||
| { | |||
| if (player.Commandable() || !TryToStop()) | |||
| lock (player.ActionLock) | |||
| { | |||
| characterManager.SetPlayerState(player); | |||
| return true; | |||
| if (player.Commandable()) | |||
| { | |||
| player.SetPlayerState(); | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| public bool Fix(Student player)// 自动检查有无发电机可修 | |||
| { | |||
| if (player.CharacterType == CharacterType.Teacher || (!player.Commandable()) || player.PlayerState == PlayerStateType.Fixing) | |||
| if ((!player.Commandable()) || player.PlayerState == PlayerStateType.Fixing) | |||
| return false; | |||
| Generator? generatorForFix = (Generator?)gameMap.OneForInteract(player.Position, GameObjType.Generator); | |||
| @@ -71,21 +84,21 @@ namespace Gaming | |||
| return false; | |||
| ++generatorForFix.NumOfFixing; | |||
| characterManager.SetPlayerState(player, PlayerStateType.Fixing); | |||
| long threadNum = player.ThreadNum; | |||
| player.SetPlayerState(PlayerStateType.Fixing); | |||
| long threadNum = player.StateNum; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| Thread.Sleep(GameData.frameDuration); | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => gameMap.Timer.IsGaming && threadNum == player.ThreadNum, | |||
| loopCondition: () => gameMap.Timer.IsGaming && threadNum == player.StateNum, | |||
| loopToDo: () => | |||
| { | |||
| if (generatorForFix.Repair(player.FixSpeed * GameData.frameDuration, player)) | |||
| gameMap.NumOfRepairedGenerators++; | |||
| if (generatorForFix.DegreeOfRepair == GameData.degreeOfFixedGenerator) | |||
| characterManager.SetPlayerState(player); | |||
| player.SetPlayerState();//Num == player.StateNum | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| finallyReturn: () => 0 | |||
| @@ -102,24 +115,26 @@ namespace Gaming | |||
| public bool OpenDoorway(Student player) | |||
| { | |||
| if (!(player.Commandable()) || player.PlayerState == PlayerStateType.OpeningTheDoorway) | |||
| if (!(player.Commandable())) | |||
| return false; | |||
| Doorway? doorwayToOpen = (Doorway?)gameMap.OneForInteract(player.Position, GameObjType.Doorway); | |||
| if (doorwayToOpen == null || doorwayToOpen.OpenStartTime > 0 || !doorwayToOpen.PowerSupply) | |||
| return false; | |||
| if (doorwayToOpen == null) return false; | |||
| characterManager.SetPlayerState(player, PlayerStateType.OpeningTheDoorway, doorwayToOpen); | |||
| int startTime = doorwayToOpen.OpenStartTime = gameMap.Timer.nowTime(); | |||
| long stateNum = player.SetPlayerState(PlayerStateType.OpeningTheDoorway, doorwayToOpen); | |||
| if (stateNum == -1) return false; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| //player.ThreadNum.WaitOne(); | |||
| Thread.Sleep(GameData.degreeOfOpenedDoorway - doorwayToOpen.OpenDegree); | |||
| if (doorwayToOpen.OpenStartTime == startTime) | |||
| lock (player.ActionLock) | |||
| { | |||
| doorwayToOpen.OpenDegree = GameData.degreeOfOpenedDoorway; | |||
| player.SetPlayerStateNaturally(); | |||
| if (stateNum == player.StateNum) | |||
| { | |||
| player.SetPlayerStateNaturally(); | |||
| doorwayToOpen.FinishOpenning(); | |||
| } | |||
| } | |||
| } | |||
| @@ -157,6 +172,7 @@ namespace Gaming | |||
| public bool Treat(Student player, Student? playerTreated = null) | |||
| { | |||
| if (player.CharacterType == CharacterType.Robot) return false; | |||
| if (playerTreated == null) | |||
| { | |||
| playerTreated = gameMap.StudentForInteract(player.Position); | |||
| @@ -171,24 +187,24 @@ namespace Gaming | |||
| ( | |||
| () => | |||
| { | |||
| characterManager.SetPlayerState(playerTreated, PlayerStateType.Treated); | |||
| characterManager.SetPlayerState(player, PlayerStateType.Treating); | |||
| long threadNum = player.ThreadNum; | |||
| playerTreated.SetPlayerState(PlayerStateType.Treated); | |||
| player.SetPlayerState(PlayerStateType.Treating); | |||
| long threadNum = player.StateNum; | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||
| loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && threadNum == player.StateNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => | |||
| { | |||
| if (playerTreated.AddDegreeOfTreatment(GameData.frameDuration * player.TreatSpeed, player)) | |||
| characterManager.SetPlayerState(playerTreated); | |||
| playerTreated.SetPlayerState();// | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| .Start(); | |||
| if (threadNum == player.ThreadNum) characterManager.SetPlayerState(player); | |||
| else if (playerTreated.PlayerState == PlayerStateType.Treated) characterManager.SetPlayerState(playerTreated); | |||
| if (threadNum == player.StateNum) player.SetPlayerState(); | |||
| else if (playerTreated.PlayerState == PlayerStateType.Treated) playerTreated.SetPlayerState(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| @@ -196,23 +212,26 @@ namespace Gaming | |||
| } | |||
| public bool Rescue(Student player, Student? playerRescued = null) | |||
| { | |||
| if (player.CharacterType == CharacterType.Robot) return false; | |||
| if (playerRescued == null) | |||
| { | |||
| playerRescued = gameMap.StudentForInteract(player.Position); | |||
| if (playerRescued == null) return false; | |||
| } | |||
| if ((!player.Commandable()) || playerRescued.PlayerState != PlayerStateType.Addicted || !GameData.ApproachToInteract(playerRescued.Position, player.Position)) | |||
| return false; | |||
| characterManager.SetPlayerState(player, PlayerStateType.Rescuing); | |||
| characterManager.SetPlayerState(playerRescued, PlayerStateType.Rescued); | |||
| long threadNum = player.ThreadNum; | |||
| player.SetPlayerState(PlayerStateType.Rescuing); | |||
| playerRescued.SetPlayerState(PlayerStateType.Rescued); | |||
| long threadNum = player.StateNum; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => playerRescued.PlayerState == PlayerStateType.Rescued && threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||
| loopCondition: () => playerRescued.PlayerState == PlayerStateType.Rescued && threadNum == player.StateNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => | |||
| { | |||
| playerRescued.TimeOfRescue += GameData.frameDuration; | |||
| @@ -227,14 +246,14 @@ namespace Gaming | |||
| { | |||
| if (playerRescued.TimeOfRescue >= GameData.basicTimeOfRescue) | |||
| { | |||
| characterManager.SetPlayerState(playerRescued); | |||
| playerRescued.SetPlayerState(); | |||
| playerRescued.HP = playerRescued.MaxHp / 2; | |||
| player.AddScore(GameData.StudentScoreRescue); | |||
| } | |||
| else | |||
| characterManager.SetPlayerState(playerRescued, PlayerStateType.Addicted); | |||
| playerRescued.SetPlayerState(PlayerStateType.Addicted); | |||
| } | |||
| if (threadNum == player.ThreadNum) characterManager.SetPlayerState(player); | |||
| if (threadNum == player.StateNum) player.SetPlayerState(); | |||
| playerRescued.TimeOfRescue = 0; | |||
| } | |||
| ) | |||
| @@ -251,7 +270,7 @@ namespace Gaming | |||
| if (chestToOpen == null || chestToOpen.OpenStartTime > 0) | |||
| return false; | |||
| characterManager.SetPlayerState(player, PlayerStateType.OpeningTheChest, chestToOpen); | |||
| player.SetPlayerState(PlayerStateType.OpeningTheChest, chestToOpen); | |||
| int startTime = gameMap.Timer.nowTime(); | |||
| chestToOpen.Open(startTime, player); | |||
| new Thread | |||
| @@ -265,9 +284,9 @@ namespace Gaming | |||
| player.SetPlayerStateNaturally(); | |||
| for (int i = 0; i < GameData.maxNumOfPropInChest; ++i) | |||
| { | |||
| Prop prop = chestToOpen.PropInChest[i]; | |||
| Gadget prop = chestToOpen.PropInChest[i]; | |||
| chestToOpen.PropInChest[i] = new NullProp(); | |||
| prop.ReSetPos(player.Position, gameMap.GetPlaceType(player.Position)); | |||
| prop.ReSetPos(player.Position); | |||
| gameMap.Add(prop); | |||
| } | |||
| } | |||
| @@ -280,12 +299,11 @@ namespace Gaming | |||
| } | |||
| public bool ClimbingThroughWindow(Character player) | |||
| { | |||
| if (!player.Commandable()) | |||
| return false; | |||
| Window? windowForClimb = (Window?)gameMap.OneForInteractInACross(player.Position, GameObjType.Window); | |||
| if (windowForClimb == null) return false; | |||
| if (windowForClimb == null || windowForClimb.WhoIsClimbing != null) | |||
| return false; | |||
| long stateNum = player.SetPlayerState(PlayerStateType.ClimbingThroughWindows, windowForClimb); | |||
| if (stateNum == -1) return false; | |||
| XY windowToPlayer = new( | |||
| (Math.Abs(player.Position.x - windowForClimb.Position.x) > GameData.numOfPosGridPerCell / 2) ? (GameData.numOfPosGridPerCell / 2 * (player.Position.x > windowForClimb.Position.x ? 1 : -1)) : 0, | |||
| @@ -295,124 +313,202 @@ namespace Gaming | |||
| if (characterInWindow != null) | |||
| { | |||
| if (player.IsGhost() && !characterInWindow.IsGhost()) | |||
| characterManager.BeAttacked((Student)(characterInWindow), player.Attack(characterInWindow.Position, PlaceType.Null)); | |||
| characterManager.BeAttacked((Student)(characterInWindow), player.Attack(characterInWindow.Position)); | |||
| return false; | |||
| }*/ | |||
| } | |||
| //Wall addWall = new Wall(windowForClimb.Position - 2 * windowToPlayer); | |||
| // gameMap.Add(addWall); | |||
| Wall addWall = new Wall(windowForClimb.Position - 2 * windowToPlayer); | |||
| gameMap.Add(addWall);*/ | |||
| characterManager.SetPlayerState(player, PlayerStateType.ClimbingThroughWindows); | |||
| long threadNum = player.ThreadNum; | |||
| windowForClimb.WhoIsClimbing = player; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => { }, | |||
| timeInterval: GameData.frameDuration, | |||
| finallyReturn: () => 0, | |||
| maxTotalDuration: (int)((windowToPlayer + windowForClimb.Position - player.Position).Length() * 1000 / player.MoveSpeed) | |||
| ) | |||
| .Start(); | |||
| if (player.PlayerState != PlayerStateType.ClimbingThroughWindows) | |||
| { | |||
| windowForClimb.WhoIsClimbing = null; | |||
| return; | |||
| } | |||
| ( | |||
| () => | |||
| { | |||
| player.ThreadNum.WaitOne(); | |||
| if (stateNum != player.StateNum) | |||
| { | |||
| player.ThreadNum.Release(); | |||
| } | |||
| else | |||
| { | |||
| if (!windowForClimb.TryToClimb(player)) | |||
| { | |||
| player.ThreadNum.Release(); | |||
| player.SetPlayerStateNaturally(); | |||
| } | |||
| else | |||
| { | |||
| Thread.Sleep((int)((windowToPlayer + windowForClimb.Position - player.Position).Length() * 1000 / player.MoveSpeed)); | |||
| lock (player.ActionLock) | |||
| { | |||
| if (player.StateNum != stateNum) return; | |||
| player.ReSetPos(windowToPlayer + windowForClimb.Position); | |||
| windowForClimb.Enter2Stage(windowForClimb.Position - 2 * windowToPlayer); | |||
| } | |||
| player.MoveSpeed = player.SpeedOfClimbingThroughWindows; | |||
| moveEngine.MoveObj(player, (int)(GameData.numOfPosGridPerCell * 3 * 1000 / player.MoveSpeed / 2), (-1 * windowToPlayer).Angle(), stateNum); | |||
| Thread.Sleep((int)(GameData.numOfPosGridPerCell * 3 * 1000 / player.MoveSpeed / 2)); | |||
| player.MoveSpeed = player.ReCalculateBuff(BuffType.AddSpeed, player.OrgMoveSpeed, GameData.MaxSpeed, GameData.MinSpeed); | |||
| lock (player.ActionLock) | |||
| { | |||
| if (stateNum == player.StateNum) | |||
| { | |||
| player.SetPlayerState(); | |||
| windowForClimb.FinishClimbing(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| player.ReSetPos(windowToPlayer + windowForClimb.Position, PlaceType.Window); | |||
| player.MoveSpeed = player.SpeedOfClimbingThroughWindows; | |||
| return true; | |||
| } | |||
| public bool LockDoor(Character player) | |||
| { | |||
| if (player.CharacterType == CharacterType.Robot) return false; | |||
| Door? doorToLock = (Door?)gameMap.OneForInteract(player.Position, GameObjType.Door); | |||
| if (doorToLock == null) return false; | |||
| moveEngine.MoveObj(player, (int)(windowToPlayer.Length() * 3.0 * 1000 / player.MoveSpeed), (-1 * windowToPlayer).Angle()); | |||
| PropType propType = doorToLock.DoorNum switch | |||
| { | |||
| 3 => PropType.Key3, | |||
| 5 => PropType.Key5, | |||
| _ => PropType.Key6, | |||
| }; | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => | |||
| { | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| finallyReturn: () => 0, | |||
| maxTotalDuration: (int)(windowToPlayer.Length() * 3.0 * 1000 / player.MoveSpeed) | |||
| ) | |||
| .Start(); | |||
| XY PosJumpOff = windowForClimb.Position - 2 * windowToPlayer; | |||
| player.ReSetPos(PosJumpOff, gameMap.GetPlaceType(PosJumpOff)); | |||
| player.MoveSpeed = player.ReCalculateBuff(BuffType.AddSpeed, player.OrgMoveSpeed, GameData.MaxSpeed, GameData.MinSpeed); | |||
| windowForClimb.WhoIsClimbing = null; | |||
| // gameMap.Remove(addWall); | |||
| if (threadNum == player.ThreadNum) | |||
| { | |||
| characterManager.SetPlayerState(player); | |||
| } | |||
| } | |||
| if (!player.UseTool(propType)) return false; | |||
| ) | |||
| long stateNum = player.SetPlayerState(PlayerStateType.LockingTheDoor, doorToLock); | |||
| if (stateNum == -1) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| return false; | |||
| } | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| player.ThreadNum.WaitOne(); | |||
| if (stateNum != player.StateNum) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| player.ThreadNum.Release(); | |||
| } | |||
| else | |||
| { | |||
| if (!doorToLock.TryLock(player)) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| lock (player.ActionLock) | |||
| { | |||
| if (stateNum == player.StateNum) player.SetPlayerState(); | |||
| } | |||
| player.ThreadNum.Release(); | |||
| } | |||
| else | |||
| { | |||
| Thread.Sleep(GameData.checkInterval); | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => stateNum == player.StateNum && gameMap.Timer.IsGaming && doorToLock.LockDegree < GameData.degreeOfLockingOrOpeningTheDoor, | |||
| loopToDo: () => | |||
| { | |||
| if ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) != null) | |||
| return false; | |||
| doorToLock.LockDegree += GameData.checkInterval * player.SpeedOfOpeningOrLocking; | |||
| return true; | |||
| }, | |||
| timeInterval: GameData.checkInterval, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| .Start(); | |||
| doorToLock.StopLock(); | |||
| player.ReleaseTool(propType); | |||
| lock (player.ActionLock) | |||
| { | |||
| if (stateNum == player.StateNum) player.SetPlayerStateNaturally(); | |||
| } | |||
| player.ThreadNum.Release(); | |||
| } | |||
| } | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return true; | |||
| } | |||
| public bool LockOrOpenDoor(Character player) | |||
| public bool OpenDoor(Character player) | |||
| { | |||
| if (!(player.Commandable()) || player.PlayerState == PlayerStateType.LockingOrOpeningTheDoor) | |||
| return false; | |||
| if (player.CharacterType == CharacterType.Robot) return false; | |||
| Door? doorToLock = (Door?)gameMap.OneForInteract(player.Position, GameObjType.Door); | |||
| if (doorToLock == null || doorToLock.OpenOrLockDegree > 0 || gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character) != null) | |||
| return false; | |||
| bool flag = false; | |||
| foreach (Prop prop in player.PropInventory) | |||
| if (doorToLock == null) return false; | |||
| PropType propType = doorToLock.DoorNum switch | |||
| { | |||
| switch (prop.GetPropType()) | |||
| { | |||
| case PropType.Key3: | |||
| if (doorToLock.Place == PlaceType.Door3) | |||
| flag = true; | |||
| break; | |||
| case PropType.Key5: | |||
| if (doorToLock.Place == PlaceType.Door5) | |||
| flag = true; | |||
| break; | |||
| case PropType.Key6: | |||
| if (doorToLock.Place == PlaceType.Door6) | |||
| flag = true; | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| if (flag) break; | |||
| } | |||
| if (!flag) return false; | |||
| 3 => PropType.Key3, | |||
| 5 => PropType.Key5, | |||
| _ => PropType.Key6, | |||
| }; | |||
| characterManager.SetPlayerState(player, PlayerStateType.LockingOrOpeningTheDoor); | |||
| long threadNum = player.ThreadNum; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => flag && threadNum == player.ThreadNum && gameMap.Timer.IsGaming && doorToLock.OpenOrLockDegree < GameData.degreeOfLockingOrOpeningTheDoor, | |||
| loopToDo: () => | |||
| { | |||
| flag = ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) == null); | |||
| doorToLock.OpenOrLockDegree += GameData.frameDuration * player.SpeedOfOpeningOrLocking; | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| if (!player.UseTool(propType)) return false; | |||
| .Start(); | |||
| if (doorToLock.OpenOrLockDegree >= GameData.degreeOfLockingOrOpeningTheDoor) | |||
| { | |||
| doorToLock.IsOpen = (!doorToLock.IsOpen); | |||
| } | |||
| if (threadNum == player.ThreadNum) | |||
| characterManager.SetPlayerState(player); | |||
| doorToLock.OpenOrLockDegree = 0; | |||
| } | |||
| long stateNum = player.SetPlayerState(PlayerStateType.OpeningTheDoor, doorToLock); | |||
| if (stateNum == -1) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| return false; | |||
| } | |||
| ) | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| player.ThreadNum.WaitOne(); | |||
| if (stateNum != player.StateNum) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| player.ThreadNum.Release(); | |||
| } | |||
| else | |||
| { | |||
| if (!doorToLock.TryOpen(player)) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| lock (player.ActionLock) | |||
| { | |||
| if (stateNum == player.StateNum) | |||
| { | |||
| player.SetPlayerStateNaturally(); | |||
| player.ThreadNum.Release(); | |||
| } | |||
| } | |||
| } | |||
| else | |||
| { | |||
| Thread.Sleep(GameData.degreeOfLockingOrOpeningTheDoor / player.SpeedOfOpeningOrLocking); | |||
| lock (player.ActionLock) | |||
| { | |||
| if (stateNum == player.StateNum) | |||
| { | |||
| player.SetPlayerStateNaturally(); | |||
| doorToLock.StopOpen(); | |||
| player.ReleaseTool(propType); | |||
| player.ThreadNum.Release(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return true; | |||
| @@ -440,33 +536,6 @@ namespace Gaming | |||
| } | |||
| } | |||
| */ | |||
| private object numLock = new object(); | |||
| private int lastTime = 0; | |||
| private int numStop = 0; | |||
| private int NumStop => numStop; | |||
| private bool TryToStop() | |||
| { | |||
| lock (numLock) | |||
| { | |||
| int time = gameMap.Timer.nowTime(); | |||
| if (time / GameData.frameDuration > lastTime) | |||
| { | |||
| lastTime = time / GameData.frameDuration; | |||
| numStop = 1; | |||
| return true; | |||
| } | |||
| else | |||
| { | |||
| if (numStop == GameData.LimitOfStopAndMove) | |||
| return false; | |||
| else | |||
| { | |||
| ++numStop; | |||
| return true; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| private readonly Map gameMap; | |||
| private readonly CharacterManager characterManager; | |||
| @@ -479,7 +548,37 @@ namespace Gaming | |||
| gameMap: gameMap, | |||
| OnCollision: (obj, collisionObj, moveVec) => | |||
| { | |||
| SkillWhenColliding((Character)obj, collisionObj); | |||
| Character player = (Character)obj; | |||
| switch (collisionObj.Type) | |||
| { | |||
| case GameObjType.Bullet: | |||
| if (((Bullet)collisionObj).Parent != player && ((Bullet)collisionObj).TypeOfBullet == BulletType.JumpyDumpty) | |||
| { | |||
| if (CharacterManager.BeStunned((Character)player, GameData.timeOfStunnedWhenJumpyDumpty) > 0) | |||
| player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStunnedWhenJumpyDumpty)); | |||
| gameMap.Remove((GameObj)collisionObj); | |||
| } | |||
| break; | |||
| case GameObjType.Character: | |||
| if (player.FindActiveSkill(ActiveSkillType.CanBeginToCharge).IsBeingUsed == 1 && ((Character)collisionObj).IsGhost()) | |||
| { | |||
| if (CharacterManager.BeStunned((Character)collisionObj, GameData.timeOfGhostStunnedWhenCharge) > 0) | |||
| player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.timeOfGhostStunnedWhenCharge)); | |||
| CharacterManager.BeStunned(player, GameData.timeOfStudentStunnedWhenCharge); | |||
| } | |||
| break; | |||
| case GameObjType.Item: | |||
| if (((Item)collisionObj).GetPropType() == PropType.CraftingBench) | |||
| { | |||
| ((CraftingBench)collisionObj).TryStopSkill(); | |||
| gameMap.Remove((Item)collisionObj); | |||
| } | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| //Preparation.Utility.Debugger.Output(obj, " end move with " + collisionObj.ToString()); | |||
| //if (collisionObj is Mine) | |||
| //{ | |||
| @@ -490,6 +589,7 @@ namespace Gaming | |||
| }, | |||
| EndMove: obj => | |||
| { | |||
| obj.ThreadNum.Release(); | |||
| // Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64); | |||
| } | |||
| ); | |||
| @@ -15,7 +15,7 @@ namespace Gaming | |||
| private class AttackManager | |||
| { | |||
| readonly Map gameMap; | |||
| readonly MoveEngine moveEngine; | |||
| public readonly MoveEngine moveEngine; | |||
| readonly CharacterManager characterManager; | |||
| public AttackManager(Map gameMap, CharacterManager characterManager) | |||
| @@ -33,12 +33,23 @@ namespace Gaming | |||
| Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64); | |||
| if (obj.CanMove && ((Bullet)obj).TypeOfBullet != BulletType.JumpyDumpty) | |||
| BulletBomb((Bullet)obj, null); | |||
| obj.CanMove = false; | |||
| obj.ReSetCanMove(false); | |||
| } | |||
| ); | |||
| this.characterManager = characterManager; | |||
| } | |||
| public void ProduceBulletNaturally(BulletType bulletType, Character player, double angle, XY pos) | |||
| { | |||
| // 子弹如果没有和其他物体碰撞,将会一直向前直到超出人物的attackRange | |||
| if (bulletType == BulletType.Null) return; | |||
| Bullet? bullet = BulletFactory.GetBullet(player, pos, bulletType); | |||
| if (bullet == null) return; | |||
| Debugger.Output(bullet, "Attack in " + pos.ToString()); | |||
| gameMap.Add(bullet); | |||
| moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms | |||
| } | |||
| private void BombObj(Bullet bullet, GameObj objBeingShot) | |||
| { | |||
| #if DEBUG | |||
| @@ -48,7 +59,7 @@ namespace Gaming | |||
| { | |||
| case GameObjType.Character: | |||
| if ((!(((Character)objBeingShot).IsGhost())) && bullet.Parent.IsGhost()) | |||
| if ((!(((Character)objBeingShot).IsGhost())) && bullet.Parent!.IsGhost()) | |||
| { | |||
| characterManager.BeAttacked((Student)objBeingShot, bullet); | |||
| } | |||
| @@ -57,7 +68,18 @@ namespace Gaming | |||
| break; | |||
| case GameObjType.Generator: | |||
| if (bullet.CanBeBombed(GameObjType.Generator)) | |||
| ((Generator)objBeingShot).Repair(-bullet.AP * GameData.factorDamageGenerator, (Character)bullet.Parent); | |||
| ((Generator)objBeingShot).Repair(-bullet.AP * GameData.factorDamageGenerator, (Character)bullet.Parent!); | |||
| break; | |||
| case GameObjType.Door: | |||
| if (bullet.CanBeBombed(GameObjType.Door)) | |||
| ((Door)objBeingShot).ForceToOpen(); | |||
| break; | |||
| case GameObjType.Item: | |||
| if (((Item)objBeingShot).GetPropType() == PropType.CraftingBench) | |||
| { | |||
| ((CraftingBench)objBeingShot).TryStopSkill(); | |||
| gameMap.Remove(objBeingShot); | |||
| } | |||
| break; | |||
| default: | |||
| break; | |||
| @@ -66,9 +88,9 @@ namespace Gaming | |||
| public bool TryRemoveBullet(Bullet bullet) | |||
| { | |||
| bullet.CanMove = false; | |||
| if (gameMap.Remove(bullet)) | |||
| { | |||
| bullet.ReSetCanMove(false); | |||
| if (bullet.BulletBombRange > 0) | |||
| { | |||
| BombedBullet bombedBullet = new(bullet); | |||
| @@ -87,6 +109,17 @@ namespace Gaming | |||
| else return false; | |||
| } | |||
| private void ProduceBombBomb(Bullet bullet, double angle) | |||
| { | |||
| angle += bullet.FacingDirection.Angle(); | |||
| XY pos = bullet.Position + new XY | |||
| ( | |||
| (int)(Math.Abs((bullet.Radius + BulletFactory.BulletRadius(BulletType.JumpyDumpty)) * Math.Cos(angle))) * Math.Sign(Math.Cos(angle)), | |||
| (int)(Math.Abs((bullet.Radius + BulletFactory.BulletRadius(BulletType.JumpyDumpty)) * Math.Sin(angle))) * Math.Sign(Math.Sin(angle)) | |||
| ); | |||
| ProduceBulletNaturally(BulletType.JumpyDumpty, (Character)bullet.Parent!, angle, pos); | |||
| } | |||
| private void BulletBomb(Bullet bullet, GameObj? objBeingShot) | |||
| { | |||
| #if DEBUG | |||
| @@ -101,12 +134,12 @@ namespace Gaming | |||
| { | |||
| if (objBeingShot == null) | |||
| { | |||
| characterManager.BackSwing((Character)bullet.Parent, bullet.Backswing); | |||
| characterManager.BackSwing((Character)bullet.Parent!, bullet.Backswing); | |||
| return; | |||
| } | |||
| BombObj(bullet, objBeingShot); | |||
| characterManager.BackSwing((Character)bullet.Parent, bullet.RecoveryFromHit); | |||
| characterManager.BackSwing((Character)bullet.Parent!, bullet.RecoveryFromHit); | |||
| return; | |||
| } | |||
| @@ -122,10 +155,14 @@ namespace Gaming | |||
| if (bullet.TypeOfBullet == BulletType.BombBomb && objBeingShot != null) | |||
| { | |||
| bullet.Parent.BulletOfPlayer = BulletType.JumpyDumpty; | |||
| Debugger.Output(bullet.Parent, bullet.Parent.CharacterType.ToString() + " " + bullet.Parent.BulletNum.ToString()); | |||
| Attack((Character)bullet.Parent, bullet.FacingDirection.Angle() + Math.PI / 2.0); | |||
| Attack((Character)bullet.Parent, bullet.FacingDirection.Angle() + Math.PI * 3.0 / 2.0); | |||
| ProduceBombBomb(bullet, 0); | |||
| ProduceBombBomb(bullet, Math.PI / 4); | |||
| ProduceBombBomb(bullet, Math.PI / 2); | |||
| ProduceBombBomb(bullet, Math.PI * 3 / 4); | |||
| ProduceBombBomb(bullet, Math.PI); | |||
| ProduceBombBomb(bullet, Math.PI * 5 / 4); | |||
| ProduceBombBomb(bullet, Math.PI * 3 / 2); | |||
| ProduceBombBomb(bullet, Math.PI * 7 / 4); | |||
| } | |||
| var beAttackedList = new List<IGameObj>(); | |||
| @@ -159,45 +196,33 @@ namespace Gaming | |||
| if (objBeingShot == null) | |||
| { | |||
| characterManager.BackSwing((Character)bullet.Parent, bullet.Backswing); | |||
| characterManager.BackSwing((Character)bullet.Parent!, bullet.Backswing); | |||
| } | |||
| else | |||
| characterManager.BackSwing((Character)bullet.Parent, bullet.RecoveryFromHit); | |||
| characterManager.BackSwing((Character)bullet.Parent!, bullet.RecoveryFromHit); | |||
| } | |||
| public bool Attack(Character player, double angle) | |||
| { // 子弹如果没有和其他物体碰撞,将会一直向前直到超出人物的attackRange | |||
| if (player.BulletOfPlayer == BulletType.Null) | |||
| return false; | |||
| Debugger.Output(player, player.CharacterType.ToString() + "Attack in " + player.BulletOfPlayer.ToString()); | |||
| Debugger.Output(player, player.Position.ToString() + " " + player.Radius.ToString() + " " + BulletFactory.BulletRadius(player.BulletOfPlayer).ToString()); | |||
| XY res = player.Position + new XY // 子弹紧贴人物生成。 | |||
| ( | |||
| (int)(Math.Abs((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Cos(angle))) * ((Math.Cos(angle) > 0) ? 1 : -1), | |||
| (int)(Math.Abs((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Sin(angle))) * ((Math.Sin(angle) > 0) ? 1 : -1) | |||
| ); | |||
| Bullet? bullet = player.Attack(res, gameMap.GetPlaceType(res)); | |||
| Bullet? bullet = player.Attack(angle, gameMap.Timer.nowTime()); | |||
| if (bullet != null) | |||
| { | |||
| player.FacingDirection = new(angle, bullet.BulletAttackRange); | |||
| Debugger.Output(bullet, "Attack in " + bullet.Position.ToString()); | |||
| bullet.AP += player.TryAddAp() ? GameData.ApPropAdd : 0; | |||
| bullet.CanMove = true; | |||
| gameMap.Add(bullet); | |||
| moveEngine.MoveObj(bullet, (int)((bullet.BulletAttackRange - player.Radius - BulletFactory.BulletRadius(player.BulletOfPlayer)) * 1000 / bullet.MoveSpeed), angle); // 这里时间参数除出来的单位要是ms | |||
| moveEngine.MoveObj(bullet, (int)(bullet.AttackDistance * 1000 / bullet.MoveSpeed), angle, ++bullet.StateNum); // 这里时间参数除出来的单位要是ms | |||
| if (bullet.CastTime > 0) | |||
| { | |||
| characterManager.SetPlayerState(player, PlayerStateType.TryingToAttack); | |||
| long threadNum = player.ThreadNum; | |||
| player.SetPlayerState(PlayerStateType.TryingToAttack); | |||
| long threadNum = player.StateNum; | |||
| new Thread | |||
| (() => | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||
| loopCondition: () => threadNum == player.StateNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => | |||
| { | |||
| }, | |||
| @@ -209,9 +234,9 @@ namespace Gaming | |||
| if (gameMap.Timer.IsGaming) | |||
| { | |||
| if (threadNum == player.ThreadNum) | |||
| if (threadNum == player.StateNum) | |||
| { | |||
| characterManager.SetPlayerState(player); | |||
| player.SetPlayerState(); | |||
| } | |||
| else TryRemoveBullet(bullet); | |||
| } | |||
| @@ -1,5 +1,4 @@ | |||
| using System; | |||
| using System.Threading; | |||
| using System.Threading; | |||
| using GameClass.GameObj; | |||
| using Preparation.Utility; | |||
| using Preparation.Interface; | |||
| @@ -18,42 +17,7 @@ namespace Gaming | |||
| this.gameMap = gameMap; | |||
| } | |||
| public void SetPlayerState(Character player, PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||
| { | |||
| lock (player.MoveLock) | |||
| { | |||
| switch (player.PlayerState) | |||
| { | |||
| case PlayerStateType.OpeningTheChest: | |||
| ((Chest)player.WhatInteractingWith).StopOpen(); | |||
| player.ChangePlayerState(value, gameObj); | |||
| break; | |||
| case PlayerStateType.OpeningTheDoorway: | |||
| Doorway doorway = (Doorway)player.WhatInteractingWith; | |||
| doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime; | |||
| doorway.OpenStartTime = 0; | |||
| player.ChangePlayerState(value, gameObj); | |||
| break; | |||
| case PlayerStateType.Addicted: | |||
| if (value == PlayerStateType.Rescued) | |||
| player.ChangePlayerStateInOneThread(value, gameObj); | |||
| else | |||
| player.ChangePlayerState(value, gameObj); | |||
| break; | |||
| case PlayerStateType.Rescued: | |||
| if (value == PlayerStateType.Addicted) | |||
| player.ChangePlayerStateInOneThread(value, gameObj); | |||
| else | |||
| player.ChangePlayerState(value, gameObj); | |||
| break; | |||
| default: | |||
| player.ChangePlayerState(value, gameObj); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| public Character? AddPlayer(XY pos, int teamID, int playerID, CharacterType characterType, Character? parent = null) | |||
| public Character? AddPlayer(XY pos, long teamID, long playerID, CharacterType characterType, Character? parent = null) | |||
| { | |||
| Character newPlayer; | |||
| @@ -66,43 +30,39 @@ namespace Gaming | |||
| newPlayer.TeamID = teamID; | |||
| newPlayer.PlayerID = playerID; | |||
| #region 人物装弹 | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| while (!gameMap.Timer.IsGaming) | |||
| Thread.Sleep(Math.Max(newPlayer.CD, GameData.checkInterval)); | |||
| long lastTime = Environment.TickCount64; | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, | |||
| loopToDo: () => | |||
| { | |||
| long nowTime = Environment.TickCount64; | |||
| if (newPlayer.BulletNum == newPlayer.MaxBulletNum) | |||
| lastTime = nowTime; | |||
| else if (nowTime - lastTime >= newPlayer.CD) | |||
| { | |||
| _ = newPlayer.TryAddBulletNum(); | |||
| lastTime = nowTime; | |||
| } | |||
| }, | |||
| 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 | |||
| /* #region 人物装弹 | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| while (!gameMap.Timer.IsGaming) | |||
| Thread.Sleep(Math.Max(newPlayer.CD, GameData.checkInterval)); | |||
| long lastTime = Environment.TickCount64; | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsRemoved, | |||
| loopToDo: () => | |||
| { | |||
| long nowTime = Environment.TickCount64; | |||
| if (newPlayer.BulletNum == newPlayer.MaxBulletNum) | |||
| lastTime = nowTime; | |||
| else if (nowTime - lastTime >= newPlayer.CD) | |||
| { | |||
| _ = newPlayer.TryAddBulletNum(); | |||
| lastTime = nowTime; | |||
| } | |||
| }, | |||
| timeInterval: GameData.checkInterval, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| { | |||
| AllowTimeExceed = true, | |||
| } | |||
| .Start(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| #endregion | |||
| */ | |||
| #region BGM,牵制得分更新 | |||
| new Thread | |||
| ( | |||
| @@ -137,7 +97,7 @@ namespace Gaming | |||
| } | |||
| } | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsResetting, | |||
| loopCondition: () => gameMap.Timer.IsGaming && !newPlayer.IsRemoved, | |||
| loopToDo: () => | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| @@ -168,7 +128,7 @@ namespace Gaming | |||
| newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.DistanceFloor3(newPlayer.Position, person.Position)); | |||
| else newPlayer.AddBgm(BgmType.GhostIsComing, 0); | |||
| } | |||
| if (newPlayer.CharacterType != CharacterType.Teacher && newPlayer.CharacterType != CharacterType.Robot && !newPlayer.NoHp() && newPlayer.PlayerState != PlayerStateType.Stunned && XY.DistanceFloor3(newPlayer.Position, person.Position) <= GameData.PinningDownRange) | |||
| if (newPlayer.CharacterType != CharacterType.Teacher && newPlayer.CharacterType != CharacterType.Robot && newPlayer.CanPinDown() && XY.DistanceFloor3(newPlayer.Position, person.Position) <= GameData.PinningDownRange) | |||
| { | |||
| TimePinningDown += GameData.checkInterval; | |||
| newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded); | |||
| @@ -249,8 +209,8 @@ namespace Gaming | |||
| return; | |||
| } | |||
| } | |||
| SetPlayerState(player, PlayerStateType.Addicted); | |||
| long threadNum = player.ThreadNum; | |||
| player.SetPlayerState(PlayerStateType.Addicted); | |||
| long threadNum = player.StateNum; | |||
| new Thread | |||
| (() => | |||
| { | |||
| @@ -258,7 +218,7 @@ namespace Gaming | |||
| Debugger.Output(player, " is addicted "); | |||
| #endif | |||
| new FrameRateTaskExecutor<int>( | |||
| () => threadNum == player.ThreadNum && player.GamingAddiction < player.MaxGamingAddiction && gameMap.Timer.IsGaming, | |||
| () => threadNum == player.StateNum && player.GamingAddiction < player.MaxGamingAddiction && gameMap.Timer.IsGaming, | |||
| () => | |||
| { | |||
| player.GamingAddiction += (player.PlayerState == PlayerStateType.Addicted) ? GameData.frameDuration : 0; | |||
| @@ -280,29 +240,29 @@ namespace Gaming | |||
| { IsBackground = true }.Start(); | |||
| } | |||
| public bool BeStunned(Character player, int time) | |||
| public static long BeStunned(Character player, int time) | |||
| { | |||
| if (player.PlayerState == PlayerStateType.Stunned || player.NoHp() || player.CharacterType == CharacterType.Robot) return false; | |||
| if (player.CharacterType == CharacterType.Robot) return -1; | |||
| long threadNum = player.SetPlayerState(PlayerStateType.Stunned); | |||
| if (threadNum == -1) return -1; | |||
| new Thread | |||
| (() => | |||
| { | |||
| SetPlayerState(player, PlayerStateType.Stunned); | |||
| long threadNum = player.ThreadNum; | |||
| Thread.Sleep(time); | |||
| if (threadNum == player.ThreadNum) | |||
| SetPlayerState(player); | |||
| if (threadNum == player.StateNum) | |||
| player.SetPlayerState(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| return true; | |||
| return threadNum; | |||
| } | |||
| public bool TryBeAwed(Student character, Bullet bullet) | |||
| { | |||
| if (character.CanBeAwed()) | |||
| { | |||
| if (BeStunned(character, GameData.basicStunnedTimeOfStudent)) | |||
| bullet.Parent.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.basicStunnedTimeOfStudent)); | |||
| if (BeStunned(character, GameData.basicStunnedTimeOfStudent) > 0) | |||
| bullet.Parent!.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.basicStunnedTimeOfStudent)); | |||
| return true; | |||
| } | |||
| return false; | |||
| @@ -320,14 +280,16 @@ namespace Gaming | |||
| #if DEBUG | |||
| Debugger.Output(student, "is being shot!"); | |||
| #endif | |||
| if (student.NoHp()) return; // 原来已经死了 | |||
| if (!bullet.Parent.IsGhost()) return; | |||
| if (!bullet.Parent!.IsGhost()) return; | |||
| if (student.CharacterType == CharacterType.StraightAStudent) | |||
| { | |||
| ((WriteAnswers)student.FindIActiveSkill(ActiveSkillType.WriteAnswers)).DegreeOfMeditation = 0; | |||
| ((WriteAnswers)student.FindActiveSkill(ActiveSkillType.WriteAnswers)).DegreeOfMeditation = 0; | |||
| } | |||
| student.SetDegreeOfTreatment0(); | |||
| if (student.NoHp()) return; // 原来已经死了 | |||
| #if DEBUG | |||
| Debugger.Output(bullet, " 's AP is " + bullet.AP.ToString()); | |||
| #endif | |||
| @@ -362,6 +324,11 @@ namespace Gaming | |||
| #endif | |||
| } | |||
| bullet.Parent.AddScore(GameData.TrickerScoreAttackStudent(subHp)); | |||
| if (student.CharacterType == CharacterType.Teacher) | |||
| { | |||
| student.AddScore(subHp * GameData.factorOfScoreWhenTeacherAttacked / GameData.basicApOfGhost); | |||
| } | |||
| bullet.Parent.HP = (int)(bullet.Parent.HP + (bullet.Parent.Vampire * subHp)); | |||
| } | |||
| if (student.HP <= 0) | |||
| @@ -375,18 +342,22 @@ namespace Gaming | |||
| public bool BackSwing(Character player, int time) | |||
| { | |||
| if (time <= 0) return false; | |||
| if (player.PlayerState == PlayerStateType.Swinging || (!player.Commandable() && player.PlayerState != PlayerStateType.TryingToAttack)) return false; | |||
| SetPlayerState(player, PlayerStateType.Swinging); | |||
| long threadNum = player.ThreadNum; | |||
| long stateNum = player.SetPlayerState(PlayerStateType.Swinging); | |||
| if (stateNum == -1) return false; | |||
| new Thread | |||
| (() => | |||
| { | |||
| player.ThreadNum.WaitOne(); | |||
| Thread.Sleep(time); | |||
| if (threadNum == player.ThreadNum) | |||
| lock (player.ActionLock) | |||
| { | |||
| SetPlayerState(player); | |||
| if (stateNum == player.StateNum) | |||
| { | |||
| player.ThreadNum.Release(); | |||
| player.SetPlayerStateNaturally(); | |||
| } | |||
| } | |||
| } | |||
| ) | |||
| @@ -404,19 +375,28 @@ namespace Gaming | |||
| for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | |||
| { | |||
| Prop? prop = player.UseProp(i); | |||
| Gadget? prop = player.UseProp(i); | |||
| if (prop != null) | |||
| { | |||
| prop.ReSetPos(player.Position, gameMap.GetPlaceType(player.Position)); | |||
| prop.ReSetPos(player.Position); | |||
| gameMap.Add(prop); | |||
| } | |||
| } | |||
| if (player.CharacterType == CharacterType.Robot) | |||
| { | |||
| if (((Golem)player).Parent != null && ((Golem)player).Parent.CharacterType == CharacterType.TechOtaku) | |||
| var parent = ((Golem)player).Parent; | |||
| if (parent != null && parent.CharacterType == CharacterType.TechOtaku) | |||
| { | |||
| ((SummonGolem)(((Golem)player).Parent.FindIActiveSkill(ActiveSkillType.SummonGolem))).GolemSummoned = null; | |||
| player.FindIActiveSkill(ActiveSkillType.UseRobot).IsBeingUsed = false; | |||
| ((SummonGolem)(parent.FindActiveSkill(ActiveSkillType.SummonGolem))).DeleteGolem((int)(player.PlayerID - parent.PlayerID) / GameData.numOfPeople - 1); | |||
| UseRobot useRobot = (UseRobot)parent.FindActiveSkill(ActiveSkillType.UseRobot); | |||
| if (useRobot.TryResetNowPlayerID((int)player.PlayerID)) | |||
| { | |||
| lock (parent.ActionLock) | |||
| { | |||
| if (parent.PlayerState == PlayerStateType.UsingSkill) | |||
| parent.SetPlayerState(); | |||
| } | |||
| } | |||
| } | |||
| return; | |||
| } | |||
| @@ -2,10 +2,8 @@ | |||
| using System.Threading; | |||
| using System.Collections.Generic; | |||
| using Preparation.Utility; | |||
| using Timothy.FrameRateTask; | |||
| using Preparation.Interface; | |||
| using GameClass.GameObj; | |||
| using System.Numerics; | |||
| namespace Gaming | |||
| { | |||
| @@ -14,10 +12,10 @@ namespace Gaming | |||
| public struct PlayerInitInfo | |||
| { | |||
| public uint birthPointIndex; | |||
| public int teamID; | |||
| public int playerID; | |||
| public long teamID; | |||
| public long playerID; | |||
| public CharacterType characterType; | |||
| public PlayerInitInfo(uint birthPointIndex, int teamID, int playerID, CharacterType characterType) | |||
| public PlayerInitInfo(uint birthPointIndex, long teamID, long playerID, CharacterType characterType) | |||
| { | |||
| this.birthPointIndex = birthPointIndex; | |||
| this.teamID = teamID; | |||
| @@ -162,7 +160,7 @@ namespace Gaming | |||
| Character? player = gameMap.FindPlayerToAction(playerID); | |||
| if (player != null) | |||
| { | |||
| return actionManager.Stop(player); | |||
| return ActionManager.Stop(player); | |||
| } | |||
| return false; | |||
| } | |||
| @@ -199,14 +197,25 @@ namespace Gaming | |||
| } | |||
| return false; | |||
| } | |||
| public bool LockOrOpenDoor(long playerID) | |||
| public bool LockDoor(long playerID) | |||
| { | |||
| if (!gameMap.Timer.IsGaming) | |||
| return false; | |||
| Character? player = gameMap.FindPlayerToAction(playerID); | |||
| if (player != null) | |||
| { | |||
| return actionManager.LockOrOpenDoor(player); | |||
| return actionManager.LockDoor(player); | |||
| } | |||
| return false; | |||
| } | |||
| public bool OpenDoor(long playerID) | |||
| { | |||
| if (!gameMap.Timer.IsGaming) | |||
| return false; | |||
| Character? player = gameMap.FindPlayerToAction(playerID); | |||
| if (player != null) | |||
| { | |||
| return actionManager.OpenDoor(player); | |||
| } | |||
| return false; | |||
| } | |||
| @@ -253,7 +262,7 @@ namespace Gaming | |||
| return false; | |||
| } | |||
| public bool UseActiveSkill(long playerID, int skillNum) | |||
| public bool UseActiveSkill(long playerID, int skillNum, int parameter) | |||
| { | |||
| if (!gameMap.Timer.IsGaming) | |||
| return false; | |||
| @@ -261,7 +270,7 @@ namespace Gaming | |||
| if (player != null) | |||
| { | |||
| if (player.Occupation.ListOfIActiveSkill.Count <= skillNum) return false; | |||
| return skillManager.UseActiveSkill(player, player.Occupation.ListOfIActiveSkill[skillNum]); | |||
| return skillManager.UseActiveSkill(player, player.Occupation.ListOfIActiveSkill[skillNum], parameter); | |||
| } | |||
| else | |||
| return false; | |||
| @@ -316,7 +325,7 @@ namespace Gaming | |||
| { | |||
| foreach (Character player in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| player.CanMove = false; | |||
| player.ReSetCanMove(false); | |||
| } | |||
| } | |||
| gameMap.GameObjDict[keyValuePair.Key].Clear(); | |||
| @@ -5,8 +5,6 @@ using Preparation.Utility; | |||
| using System; | |||
| using Timothy.FrameRateTask; | |||
| using GameEngine; | |||
| using System.Numerics; | |||
| using System.Reflection; | |||
| namespace Gaming | |||
| { | |||
| @@ -21,9 +19,9 @@ namespace Gaming | |||
| public void UseProp(Character player, PropType propType) | |||
| { | |||
| if (player.IsResetting || player.CharacterType == CharacterType.Robot) | |||
| if (player.CharacterType == CharacterType.Robot || player.IsRemoved) | |||
| return; | |||
| Prop prop = player.UseProp(propType); | |||
| Gadget prop = player.UseProp(propType); | |||
| switch (prop.GetPropType()) | |||
| { | |||
| case PropType.ShieldOrSpear: | |||
| @@ -54,7 +52,7 @@ namespace Gaming | |||
| else player.AddAp(GameData.PropDuration); | |||
| break; | |||
| case PropType.RecoveryFromDizziness: | |||
| if (player.PlayerState == PlayerStateType.Stunned) | |||
| if (player.PlayerState == PlayerStateType.Stunned || player.PlayerState == PlayerStateType.Charmed) | |||
| { | |||
| player.AddScore(GameData.ScorePropRecoverFromDizziness); | |||
| player.SetPlayerStateNaturally(); | |||
| @@ -73,23 +71,22 @@ namespace Gaming | |||
| /// <returns></returns> | |||
| public bool PickProp(Character player, PropType propType = PropType.Null) | |||
| { | |||
| if (player.IsResetting) | |||
| return false; | |||
| if (!player.Commandable()) return false; | |||
| int indexing = player.IndexingOfAddProp(); | |||
| if (indexing == GameData.maxNumOfPropInPropInventory) | |||
| return false; | |||
| Prop pickProp = new NullProp(); | |||
| Gadget pickProp = new NullProp(); | |||
| if (propType == PropType.Null) // 自动检查有无道具可捡 | |||
| { | |||
| pickProp = player.PropInventory[indexing] = ((Prop?)gameMap.OneInTheSameCell(player.Position, GameObjType.Prop)) ?? new NullProp(); | |||
| pickProp = player.PropInventory[indexing] = ((Gadget?)gameMap.OneInTheSameCell(player.Position, GameObjType.Gadget)) ?? new NullProp(); | |||
| } | |||
| else | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Prop].EnterReadLock(); | |||
| gameMap.GameObjLockDict[GameObjType.Gadget].EnterReadLock(); | |||
| try | |||
| { | |||
| foreach (Prop prop in gameMap.GameObjDict[GameObjType.Prop]) | |||
| foreach (Gadget prop in gameMap.GameObjDict[GameObjType.Gadget]) | |||
| { | |||
| if (prop.GetPropType() == propType) | |||
| { | |||
| @@ -102,14 +99,14 @@ namespace Gaming | |||
| } | |||
| finally | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Prop].ExitReadLock(); | |||
| gameMap.GameObjLockDict[GameObjType.Gadget].ExitReadLock(); | |||
| } | |||
| } | |||
| if (pickProp.GetPropType() != PropType.Null) | |||
| { | |||
| gameMap.Remove(pickProp); | |||
| gameMap.Add(new PickedProp(pickProp)); | |||
| gameMap.RemoveJustFromMap(pickProp); | |||
| //gameMap.Add(new Item(pickProp)); | |||
| return true; | |||
| } | |||
| else | |||
| @@ -118,19 +115,19 @@ namespace Gaming | |||
| public void ThrowProp(Character player, PropType propType) | |||
| { | |||
| if (!gameMap.Timer.IsGaming || player.IsResetting) | |||
| if (!gameMap.Timer.IsGaming || player.IsRemoved) | |||
| return; | |||
| Prop prop = player.UseProp(propType); | |||
| Gadget prop = player.UseProp(propType); | |||
| if (prop.GetPropType() == PropType.Null) | |||
| return; | |||
| prop.ReSetPos(player.Position, gameMap.GetPlaceType(player.Position)); | |||
| prop.ReSetPos(player.Position); | |||
| gameMap.Add(prop); | |||
| } | |||
| private Prop ProduceOnePropNotKey(Random r, XY Pos) | |||
| private static Gadget ProduceOnePropNotKey(Random r, XY Pos) | |||
| { | |||
| return PropFactory.GetProp((PropType)r.Next(GameData.numOfTeachingBuilding + 1, GameData.numOfPropSpecies + 1), Pos, gameMap.GetPlaceType(Pos)); | |||
| return PropFactory.GetConsumables((PropType)r.Next(GameData.numOfTeachingBuilding + 1, GameData.numOfPropSpecies + 1), Pos); | |||
| } | |||
| private Chest GetChest(Random r) | |||
| @@ -153,7 +150,7 @@ namespace Gaming | |||
| { | |||
| ++cou; | |||
| Chest chest = GetChest(r); | |||
| chest.PropInChest[1] = new Key3(chest.Position, PlaceType.Chest); | |||
| chest.PropInChest[1] = new Key3(chest.Position); | |||
| chest.PropInChest[0] = ProduceOnePropNotKey(r, chest.Position); | |||
| } | |||
| cou = 0; | |||
| @@ -161,7 +158,7 @@ namespace Gaming | |||
| { | |||
| ++cou; | |||
| Chest chest = GetChest(r); | |||
| chest.PropInChest[1] = new Key5(chest.Position, PlaceType.Chest); | |||
| chest.PropInChest[1] = new Key5(chest.Position); | |||
| chest.PropInChest[0] = ProduceOnePropNotKey(r, chest.Position); | |||
| } | |||
| cou = 0; | |||
| @@ -169,7 +166,7 @@ namespace Gaming | |||
| { | |||
| ++cou; | |||
| Chest chest = GetChest(r); | |||
| chest.PropInChest[1] = new Key6(chest.Position, PlaceType.Chest); | |||
| chest.PropInChest[1] = new Key6(chest.Position); | |||
| chest.PropInChest[0] = ProduceOnePropNotKey(r, chest.Position); | |||
| } | |||
| @@ -13,9 +13,8 @@ namespace Gaming | |||
| { | |||
| public static bool CanBeginToCharge(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| IActiveSkill skill = player.FindIActiveSkill(ActiveSkillType.CanBeginToCharge); | |||
| ActiveSkill skill = player.FindActiveSkill(ActiveSkillType.CanBeginToCharge); | |||
| Debugger.Output(player, "can begin to charge!"); | |||
| @@ -31,7 +30,7 @@ namespace Gaming | |||
| public bool ShowTime(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| IActiveSkill skill = player.FindIActiveSkill(ActiveSkillType.ShowTime); | |||
| ActiveSkill skill = player.FindActiveSkill(ActiveSkillType.ShowTime); | |||
| return ActiveSkillEffect(skill, player, () => | |||
| { | |||
| @@ -76,6 +75,7 @@ namespace Gaming | |||
| } | |||
| }, | |||
| timeInterval: GameData.checkIntervalWhenShowTime, | |||
| maxTotalDuration: skill.DurationTime, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| @@ -92,34 +92,70 @@ namespace Gaming | |||
| public static bool BecomeInvisible(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.BecomeInvisible); | |||
| ActiveSkill activeSkill = player.FindActiveSkill(ActiveSkillType.BecomeInvisible); | |||
| long stateNum = player.SetPlayerState(PlayerStateType.UsingSkill); | |||
| if (stateNum == -1) | |||
| { | |||
| return false; | |||
| } | |||
| return ActiveSkillEffect(activeSkill, player, () => | |||
| { | |||
| player.AddScore(GameData.ScoreBecomeInvisible); | |||
| player.AddInvisible(activeSkill.DurationTime); | |||
| Debugger.Output(player, "become invisible!"); | |||
| }, | |||
| () => | |||
| { }); | |||
| () => | |||
| { | |||
| lock (player.ActionLock) | |||
| { | |||
| if (stateNum == player.StateNum) | |||
| { | |||
| player.SetPlayerStateNaturally(); | |||
| } | |||
| } | |||
| } | |||
| ); | |||
| } | |||
| public bool UseRobot(Character player) | |||
| public static bool UseRobot(Character player, int robotID) | |||
| { | |||
| IGolem? golem = (IGolem?)(((SummonGolem)player.FindIActiveSkill(ActiveSkillType.SummonGolem)).GolemSummoned); | |||
| if ((!player.Commandable()) || ((SummonGolem)player.FindIActiveSkill(ActiveSkillType.SummonGolem)).GolemSummoned == null) return false; | |||
| Debugger.Output(player, "use robot!"); | |||
| IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.UseRobot); | |||
| activeSkill.IsBeingUsed = (activeSkill.IsBeingUsed) ? false : true; | |||
| if (activeSkill.IsBeingUsed) characterManager.SetPlayerState(player, PlayerStateType.UsingSkill); | |||
| else characterManager.SetPlayerState(player); | |||
| return true; | |||
| if ((robotID - player.PlayerID) % GameData.numOfPeople != 0) return false; | |||
| if ((robotID - (int)player.PlayerID) / GameData.numOfPeople < 0 || (robotID - (int)player.PlayerID) / GameData.numOfPeople > GameData.maxSummonedGolemNum) return false; | |||
| UseRobot activeSkill = (UseRobot)player.FindActiveSkill(ActiveSkillType.UseRobot); | |||
| lock (activeSkill.ActiveSkillUseLock) | |||
| { | |||
| if (robotID == player.PlayerID) | |||
| { | |||
| lock (player.ActionLock) | |||
| { | |||
| if (player.PlayerState == PlayerStateType.UsingSkill && player.WhatInteractingWith == null) | |||
| player.SetPlayerStateNaturally(); | |||
| activeSkill.NowPlayerID = robotID; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| SummonGolem summonGolemSkill = (SummonGolem)player.FindActiveSkill(ActiveSkillType.SummonGolem); | |||
| if (summonGolemSkill.GolemStateArray[(robotID - (int)player.PlayerID) / GameData.numOfPeople - 1] == 2) | |||
| { | |||
| activeSkill.NowPlayerID = robotID; | |||
| } | |||
| else return false; | |||
| long stateNum = player.SetPlayerState(PlayerStateType.UsingSkill); | |||
| if (stateNum == -1) | |||
| { | |||
| activeSkill.NowPlayerID = (int)player.PlayerID; | |||
| return false; | |||
| } | |||
| } | |||
| return ActiveSkillEffect(activeSkill, player, () => { }, () => { }); | |||
| } | |||
| } | |||
| public static bool JumpyBomb(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.JumpyBomb), player, () => | |||
| return ActiveSkillEffect(player.FindActiveSkill(ActiveSkillType.JumpyBomb), player, () => | |||
| { | |||
| player.BulletOfPlayer = BulletType.BombBomb; | |||
| Debugger.Output(player, "uses jumpybomb!"); | |||
| @@ -128,10 +164,66 @@ namespace Gaming | |||
| { player.BulletOfPlayer = player.OriBulletOfPlayer; }); | |||
| } | |||
| public bool SparksNSplash(Character player, int AttackID) | |||
| { | |||
| Character? whoAttacked = gameMap.FindPlayer(AttackID); | |||
| if (whoAttacked == null || whoAttacked.NoHp()) return false; | |||
| ActiveSkill activeSkill = player.FindActiveSkill(ActiveSkillType.SparksNSplash); | |||
| return ActiveSkillEffect(activeSkill, player, () => | |||
| { | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| Bullet? homingMissile = null; | |||
| double dis; | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => gameMap.Timer.IsGaming && !whoAttacked.NoHp(), | |||
| loopToDo: () => | |||
| { | |||
| dis = ((homingMissile == null || homingMissile.IsRemoved) ? double.MaxValue : XY.DistanceFloor3(homingMissile.Position, whoAttacked.Position)); | |||
| gameMap.GameObjLockDict[GameObjType.Bullet].EnterReadLock(); | |||
| try | |||
| { | |||
| foreach (Bullet bullet in gameMap.GameObjDict[GameObjType.Bullet]) | |||
| { | |||
| if (!bullet.CanMove && XY.DistanceFloor3(bullet.Position, whoAttacked.Position) < dis && bullet.TypeOfBullet == BulletType.JumpyDumpty) | |||
| { | |||
| homingMissile = bullet; | |||
| dis = XY.DistanceFloor3(bullet.Position, whoAttacked.Position); | |||
| } | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Bullet].ExitReadLock(); | |||
| } | |||
| if (homingMissile != null) | |||
| { | |||
| homingMissile.ReSetCanMove(true); | |||
| attackManager.moveEngine.MoveObj(homingMissile, GameData.checkIntervalWhenSparksNSplash - 1, (whoAttacked.Position - homingMissile.Position).Angle(), ++homingMissile.StateNum); | |||
| } | |||
| }, | |||
| timeInterval: GameData.checkIntervalWhenSparksNSplash, | |||
| maxTotalDuration: activeSkill.DurationTime, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| .Start(); | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| Debugger.Output(player, "uses sparks n splash!"); | |||
| }, | |||
| () => | |||
| { }); | |||
| } | |||
| public bool WriteAnswers(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.WriteAnswers); | |||
| ActiveSkill activeSkill = player.FindActiveSkill(ActiveSkillType.WriteAnswers); | |||
| return ActiveSkillEffect(activeSkill, player, () => | |||
| { | |||
| Generator? generator = (Generator?)gameMap.OneForInteract(player.Position, GameObjType.Generator); | |||
| @@ -149,25 +241,66 @@ namespace Gaming | |||
| public bool SummonGolem(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.SummonGolem); | |||
| if (((SummonGolem)activeSkill).GolemSummoned != null) return false; | |||
| ActiveSkill activeSkill = player.FindActiveSkill(ActiveSkillType.SummonGolem); | |||
| int num = ((SummonGolem)activeSkill).BuildGolem(); | |||
| if (num >= GameData.maxSummonedGolemNum) return false; | |||
| XY res = player.Position + new XY(player.FacingDirection, player.Radius * 2); | |||
| if (actionManager.moveEngine.CheckCollision(player, res) != null) | |||
| return false; | |||
| Golem? golem = (Golem?)characterManager.AddPlayer(res, player.TeamID, player.PlayerID + GameData.numOfPeople, CharacterType.Robot, player); | |||
| if (golem == null) return false; | |||
| ((SummonGolem)activeSkill).GolemSummoned = golem; | |||
| return ActiveSkillEffect(activeSkill, player, () => | |||
| lock (activeSkill.ActiveSkillUseLock) | |||
| { | |||
| }, | |||
| () => | |||
| { }); | |||
| CraftingBench craftingBench = new(res, player, num); | |||
| long stateNum = player.SetPlayerState(PlayerStateType.UsingSkill, craftingBench); | |||
| if (stateNum == -1) | |||
| { | |||
| ((SummonGolem)activeSkill).DeleteGolem(num); | |||
| return false; | |||
| } | |||
| player.ThreadNum.WaitOne(); | |||
| if (stateNum != player.StateNum) | |||
| { | |||
| ((SummonGolem)activeSkill).DeleteGolem(num); | |||
| player.ThreadNum.Release(); | |||
| return false; | |||
| } | |||
| if (actionManager.moveEngine.CheckCollision(craftingBench, res) != null) | |||
| { | |||
| ((SummonGolem)activeSkill).DeleteGolem(num); | |||
| player.ThreadNum.Release(); | |||
| return false; | |||
| } | |||
| craftingBench.ParentStateNum = stateNum; | |||
| gameMap.Add(craftingBench); | |||
| return ActiveSkillEffect(activeSkill, player, () => | |||
| { | |||
| }, | |||
| () => | |||
| { | |||
| lock (player.ActionLock) | |||
| { | |||
| if (stateNum == player.StateNum) | |||
| { | |||
| gameMap.RemoveJustFromMap(craftingBench); | |||
| Golem? golem = (Golem?)characterManager.AddPlayer(res, player.TeamID, (num + 1) * GameData.numOfPeople + player.PlayerID, CharacterType.Robot, player); | |||
| if (golem == null) | |||
| { | |||
| ((SummonGolem)activeSkill).AddGolem(num); | |||
| } | |||
| player.SetPlayerStateNaturally(); | |||
| player.ThreadNum.Release(); | |||
| } | |||
| } | |||
| } | |||
| ); | |||
| } | |||
| } | |||
| public static bool UseKnife(Character player) | |||
| { | |||
| return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.UseKnife), player, () => | |||
| return ActiveSkillEffect(player.FindActiveSkill(ActiveSkillType.UseKnife), player, () => | |||
| { | |||
| player.BulletOfPlayer = BulletType.FlyingKnife; | |||
| Debugger.Output(player, "uses flyingknife!"); | |||
| @@ -179,7 +312,7 @@ namespace Gaming | |||
| public bool Howl(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.Howl), player, () => | |||
| return ActiveSkillEffect(player.FindActiveSkill(ActiveSkillType.Howl), player, () => | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| @@ -188,7 +321,7 @@ namespace Gaming | |||
| { | |||
| if (!character.IsGhost() && !character.NoHp() && XY.DistanceFloor3(character.Position, player.Position) <= player.ViewRange) | |||
| { | |||
| if (characterManager.BeStunned(character, GameData.timeOfStudentStunnedWhenHowl)) | |||
| if (CharacterManager.BeStunned(character, GameData.timeOfStudentStunnedWhenHowl) > 0) | |||
| player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStudentStunnedWhenHowl)); | |||
| } | |||
| } | |||
| @@ -207,7 +340,7 @@ namespace Gaming | |||
| public bool Punish(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.Punish), player, () => | |||
| return ActiveSkillEffect(player.FindActiveSkill(ActiveSkillType.Punish), player, () => | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| @@ -216,11 +349,13 @@ namespace Gaming | |||
| { | |||
| if (character.IsGhost() && | |||
| (character.PlayerState == PlayerStateType.TryingToAttack || character.PlayerState == PlayerStateType.Swinging | |||
| || character.PlayerState == PlayerStateType.UsingSkill || character.PlayerState == PlayerStateType.LockingOrOpeningTheDoor || character.PlayerState == PlayerStateType.ClimbingThroughWindows) | |||
| && gameMap.CanSee(player, character)) | |||
| || character.PlayerState == PlayerStateType.UsingSkill | |||
| || character.PlayerState == PlayerStateType.LockingTheDoor || character.PlayerState == PlayerStateType.OpeningTheDoor | |||
| || character.PlayerState == PlayerStateType.ClimbingThroughWindows) | |||
| && XY.DistanceFloor3(character.Position, player.Position) <= player.ViewRange / 3) | |||
| { | |||
| if (characterManager.BeStunned(character, GameData.timeOfGhostStunnedWhenPunish + GameData.factorOfTimeStunnedWhenPunish * (player.MaxHp - player.HP))) | |||
| player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.timeOfGhostStunnedWhenPunish + GameData.factorOfTimeStunnedWhenPunish * (player.MaxHp - player.HP))); | |||
| if (CharacterManager.BeStunned(character, GameData.timeOfGhostStunnedWhenPunish + GameData.factorOfTimeStunnedWhenPunish * (player.MaxHp - player.HP) / GameData.basicApOfGhost) > 0) | |||
| player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.timeOfGhostStunnedWhenPunish + GameData.factorOfTimeStunnedWhenPunish * (player.MaxHp - player.HP) / GameData.basicApOfGhost)); | |||
| break; | |||
| } | |||
| } | |||
| @@ -235,10 +370,45 @@ namespace Gaming | |||
| { }); | |||
| } | |||
| public bool HaveTea(Character player, int angle1000) | |||
| { | |||
| long stateNum = player.SetPlayerState(PlayerStateType.UsingSkill); | |||
| if (stateNum == -1) | |||
| { | |||
| return false; | |||
| } | |||
| player.ThreadNum.WaitOne(); | |||
| XY res = player.Position + new XY(angle1000 / 1000.0, GameData.distanceOfHaveTea); | |||
| Debugger.Output(res.ToString()); | |||
| if (actionManager.moveEngine.CheckCollision(player, res) != null) | |||
| { | |||
| player.ThreadNum.Release(); | |||
| return false; | |||
| } | |||
| Debugger.Output("NO Collision!"); | |||
| player.ReSetPos(res); | |||
| lock (player.ActionLock) | |||
| { | |||
| if (player.StateNum == stateNum) | |||
| { | |||
| player.SetPlayerStateNaturally(); | |||
| } | |||
| } | |||
| player.ThreadNum.Release(); | |||
| return ActiveSkillEffect(player.FindActiveSkill(ActiveSkillType.HaveTea), player, () => | |||
| { | |||
| Debugger.Output(player, "have tea!"); | |||
| }, | |||
| () => | |||
| { }); | |||
| } | |||
| public bool Rouse(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.Rouse), player, () => | |||
| return ActiveSkillEffect(player.FindActiveSkill(ActiveSkillType.Rouse), player, () => | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| @@ -247,7 +417,7 @@ namespace Gaming | |||
| { | |||
| if ((character.PlayerState == PlayerStateType.Addicted) && gameMap.CanSee(player, character)) | |||
| { | |||
| characterManager.SetPlayerState(character); | |||
| character.SetPlayerState(); | |||
| character.HP = GameData.RemainHpWhenAddLife; | |||
| ((Student)character).TimeOfRescue = 0; | |||
| player.AddScore(GameData.StudentScoreRescue); | |||
| @@ -268,7 +438,7 @@ namespace Gaming | |||
| public bool Encourage(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.Encourage), player, () => | |||
| return ActiveSkillEffect(player.FindActiveSkill(ActiveSkillType.Encourage), player, () => | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| @@ -297,14 +467,14 @@ namespace Gaming | |||
| public bool Inspire(Character player) | |||
| { | |||
| if ((!player.Commandable())) return false; | |||
| return ActiveSkillEffect(player.FindIActiveSkill(ActiveSkillType.Inspire), player, () => | |||
| return ActiveSkillEffect(player.FindActiveSkill(ActiveSkillType.Inspire), player, () => | |||
| { | |||
| gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| { | |||
| foreach (Character character in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (gameMap.CanSee(player, character)) | |||
| if (gameMap.CanSee(player, character) && !character.IsGhost()) | |||
| { | |||
| player.AddScore(GameData.ScoreInspire); | |||
| character.AddMoveSpeed(GameData.timeOfAddingSpeedWhenInspire, GameData.addedTimeOfSpeedWhenInspire); | |||
| @@ -321,24 +491,24 @@ namespace Gaming | |||
| { }); | |||
| } | |||
| public static bool ActiveSkillEffect(IActiveSkill activeSkill, Character player, Action startSkill, Action endSkill) | |||
| public static bool ActiveSkillEffect(ActiveSkill activeSkill, Character player, Action startSkill, Action endSkill) | |||
| { | |||
| lock (activeSkill.ActiveSkillLock) | |||
| lock (activeSkill.ActiveSkillUseLock) | |||
| { | |||
| ActiveSkillType activeSkillType = SkillFactory.FindActiveSkillType(activeSkill); | |||
| if (player.TimeUntilActiveSkillAvailable[activeSkillType] == 0) | |||
| if (activeSkill.TimeUntilActiveSkillAvailable == 0) | |||
| { | |||
| player.SetTimeUntilActiveSkillAvailable(activeSkillType, activeSkill.SkillCD); | |||
| activeSkill.TimeUntilActiveSkillAvailable = activeSkill.SkillCD; | |||
| new Thread | |||
| (() => | |||
| { | |||
| startSkill(); | |||
| activeSkill.IsBeingUsed = true; | |||
| activeSkill.IsBeingUsed = 1; | |||
| new FrameRateTaskExecutor<int>( | |||
| () => !player.IsResetting, | |||
| () => !player.IsRemoved, | |||
| () => | |||
| { | |||
| player.AddTimeUntilActiveSkillAvailable(activeSkillType, -(int)GameData.frameDuration); | |||
| activeSkill.TimeUntilActiveSkillAvailable -= (int)GameData.frameDuration; | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| () => 0, | |||
| @@ -351,14 +521,14 @@ namespace Gaming | |||
| .Start(); | |||
| endSkill(); | |||
| activeSkill.IsBeingUsed = false; | |||
| activeSkill.IsBeingUsed = 0; | |||
| Debugger.Output(player, "return to normal."); | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => player.TimeUntilActiveSkillAvailable[activeSkillType] > 0 && !player.IsResetting, | |||
| loopCondition: () => activeSkill.TimeUntilActiveSkillAvailable > 0, | |||
| loopToDo: () => | |||
| { | |||
| player.AddTimeUntilActiveSkillAvailable(activeSkillType, -(int)GameData.frameDuration); | |||
| activeSkill.TimeUntilActiveSkillAvailable -= (int)GameData.frameDuration; | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| finallyReturn: () => 0 | |||
| @@ -369,7 +539,7 @@ namespace Gaming | |||
| } | |||
| .Start(); | |||
| player.SetTimeUntilActiveSkillAvailable(activeSkillType, 0); | |||
| activeSkill.TimeUntilActiveSkillAvailable = 0; | |||
| Debugger.Output(player, "ActiveSkill is ready."); | |||
| } | |||
| ) | |||
| @@ -15,14 +15,14 @@ namespace Gaming // 被动技能开局时就释放,持续到游戏结束 | |||
| public void Meditate(Character player) | |||
| { | |||
| const int learningDegree = GameData.basicFixSpeed / 4; | |||
| WriteAnswers activeSkill = (WriteAnswers)player.FindIActiveSkill(ActiveSkillType.WriteAnswers); | |||
| WriteAnswers activeSkill = (WriteAnswers)player.FindActiveSkill(ActiveSkillType.WriteAnswers); | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| new FrameRateTaskExecutor<int> | |||
| ( | |||
| () => gameMap.Timer.IsGaming && !player.IsResetting, | |||
| () => gameMap.Timer.IsGaming && !player.IsRemoved, | |||
| () => | |||
| { | |||
| if (player.Commandable() && player.PlayerState != PlayerStateType.Fixing) activeSkill.DegreeOfMeditation += learningDegree * GameData.frameDuration; | |||
| @@ -53,6 +53,10 @@ namespace Gaming // 被动技能开局时就释放,持续到游戏结束 | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| } | |||
| public void Lucky(Character player) | |||
| { | |||
| player.PropInventory[0] = PropFactory.GetConsumables((PropType)((4 * Environment.TickCount) % 5 + 4), new XY(0, 0)); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -11,7 +11,7 @@ namespace Gaming | |||
| readonly SkillManager skillManager; | |||
| private partial class SkillManager | |||
| { | |||
| public bool UseActiveSkill(Character character, ActiveSkillType activeSkillType) | |||
| public bool UseActiveSkill(Character character, ActiveSkillType activeSkillType, int parameter) | |||
| { | |||
| if (character.Occupation.ListOfIActiveSkill.Contains(activeSkillType)) | |||
| switch (activeSkillType) | |||
| @@ -30,14 +30,18 @@ namespace Gaming | |||
| return Encourage(character); | |||
| case ActiveSkillType.Punish: | |||
| return Punish(character); | |||
| case ActiveSkillType.HaveTea: | |||
| return HaveTea(character, parameter); | |||
| case ActiveSkillType.JumpyBomb: | |||
| return JumpyBomb(character); | |||
| case ActiveSkillType.SparksNSplash: | |||
| return SparksNSplash(character, parameter); | |||
| case ActiveSkillType.WriteAnswers: | |||
| return WriteAnswers(character); | |||
| case ActiveSkillType.SummonGolem: | |||
| return SummonGolem(character); | |||
| case ActiveSkillType.UseRobot: | |||
| return UseRobot(character); | |||
| return UseRobot(character, parameter); | |||
| case ActiveSkillType.Rouse: | |||
| return Rouse(character); | |||
| case ActiveSkillType.ShowTime: | |||
| @@ -47,7 +51,7 @@ namespace Gaming | |||
| } | |||
| return false; | |||
| } | |||
| public void UsePassiveSkill(Character character, PassiveSkillType passiveSkillType) | |||
| /*public void UsePassiveSkill(Character character, PassiveSkillType passiveSkillType) | |||
| { | |||
| if (character.Occupation.ListOfIPassiveSkill.Contains(passiveSkillType)) | |||
| switch (passiveSkillType) | |||
| @@ -59,7 +63,7 @@ namespace Gaming | |||
| return; | |||
| } | |||
| return; | |||
| } | |||
| }*/ | |||
| public void UseAllPassiveSkill(Character character) | |||
| { | |||
| foreach (var passiveSkill in character.Occupation.ListOfIPassiveSkill) | |||
| @@ -68,6 +72,9 @@ namespace Gaming | |||
| case PassiveSkillType.Meditate: | |||
| Meditate(character); | |||
| break; | |||
| case PassiveSkillType.Lucky: | |||
| Lucky(character); | |||
| break; | |||
| default: | |||
| return; | |||
| } | |||
| @@ -3,18 +3,19 @@ using Preparation.Utility; | |||
| namespace Preparation.Interface | |||
| { | |||
| public interface ICharacter : IGameObj | |||
| public interface ICharacter : IMoveable | |||
| { | |||
| public int TeamID { get; } | |||
| public long TeamID { get; } | |||
| public int HP { get; set; } | |||
| public int Score { get; } | |||
| public void AddScore(int add); | |||
| public long Score { get; } | |||
| public void AddScore(long add); | |||
| public double Vampire { get; } | |||
| public PlayerStateType PlayerState { get; } | |||
| public BulletType BulletOfPlayer { get; set; } | |||
| public CharacterType CharacterType { get; } | |||
| public int BulletNum { get; } | |||
| public long ThreadNum { get; } | |||
| public ActiveSkill FindActiveSkill(ActiveSkillType activeSkillType); | |||
| public int UpdateBulletNum(int time); | |||
| public long SetPlayerState(PlayerStateType value = PlayerStateType.Null, IGameObj? obj = null); | |||
| public bool IsGhost(); | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| using Preparation.Utility; | |||
| namespace Preparation.Interface | |||
| { | |||
| public interface IChest : IGameObj | |||
| { | |||
| public void StopOpen(); | |||
| } | |||
| } | |||