Browse Source

fix(capi): ✏️ remove warnings from implicit double to int

tags/v0.1.0
Timothy Liu 2 years ago
parent
commit
10e589e74e
100 changed files with 5242 additions and 2731 deletions
  1. +4
    -0
      .github/workflows/upload_COS.yml
  2. +1
    -1
      CAPI/cmd/RunServer.cmd
  3. +1
    -1
      CAPI/cmd/RunServerForDebug.cmd
  4. +6
    -6
      CAPI/cpp/API/include/API.h
  5. +6
    -1
      CAPI/cpp/API/include/Communication.h
  6. +59
    -32
      CAPI/cpp/API/include/constants.h
  7. +1
    -1
      CAPI/cpp/API/include/logic.h
  8. +9
    -7
      CAPI/cpp/API/include/structures.h
  9. +9
    -6
      CAPI/cpp/API/include/utils.hpp
  10. +4
    -4
      CAPI/cpp/API/src/API.cpp
  11. +113
    -4
      CAPI/cpp/API/src/Communication.cpp
  12. +14
    -14
      CAPI/cpp/API/src/DebugAPI.cpp
  13. +95
    -71
      CAPI/cpp/API/src/logic.cpp
  14. +5
    -1
      CAPI/cpp/API/src/main.cpp
  15. +50
    -0
      CAPI/cpp/README.md
  16. +200
    -332
      CAPI/cpp/proto/Message2Clients.pb.cc
  17. +92
    -236
      CAPI/cpp/proto/Message2Clients.pb.h
  18. +38
    -10
      CAPI/cpp/proto/Message2Server.pb.cc
  19. +36
    -0
      CAPI/cpp/proto/Message2Server.pb.h
  20. +40
    -39
      CAPI/cpp/proto/MessageType.pb.cc
  21. +4
    -3
      CAPI/cpp/proto/MessageType.pb.h
  22. +2
    -0
      CAPI/cpp/proto/Services.grpc.pb.h
  23. +18
    -8
      CAPI/python/PyAPI/API.py
  24. +124
    -21
      CAPI/python/PyAPI/Communication.py
  25. +219
    -188
      CAPI/python/PyAPI/DebugAPI.py
  26. +5
    -8
      CAPI/python/PyAPI/Interface.py
  27. +1
    -0
      CAPI/python/PyAPI/State.py
  28. +115
    -90
      CAPI/python/PyAPI/constants.py
  29. +227
    -81
      CAPI/python/PyAPI/logic.py
  30. +47
    -16
      CAPI/python/PyAPI/main.py
  31. +2
    -4
      CAPI/python/PyAPI/structures.py
  32. +123
    -57
      CAPI/python/PyAPI/utils.py
  33. +2
    -2
      CAPI/python/requirements.txt
  34. +1
    -1
      CAPI/python/run.sh
  35. +1
    -1
      CAPI/shell/RunServer.sh
  36. +1
    -1
      CAPI/shell/RunServerForDebug.sh
  37. +3
    -1
      README.md
  38. +1
    -0
      dependency/Dockerfile/Dockerfile_cpp
  39. +4
    -4
      dependency/proto/Message2Clients.proto
  40. +1
    -0
      dependency/proto/Message2Server.proto
  41. +4
    -3
      dependency/proto/MessageType.proto
  42. +0
    -0
      dependency/proto/Proto.sln
  43. +2
    -2
      dependency/proto/Protos.csproj
  44. +205
    -0
      dependency/py/rank.py
  45. +11
    -2
      dependency/shell/compile.sh
  46. +5
    -6
      dependency/shell/run.sh
  47. +48
    -43
      docs/CAPI接口(cpp).md
  48. +45
    -42
      docs/CAPI接口(python).md
  49. +14
    -11
      docs/GameRules.md
  50. +6
    -3
      docs/使用文档.md
  51. +119
    -0
      docs/游戏机制与平衡性调整更新草案.md
  52. +53
    -0
      docs/版本更新说明.md
  53. +51
    -5
      installer/Installer/MainWindow.xaml
  54. +499
    -29
      installer/Installer/Model.cs
  55. +107
    -29
      installer/Installer/ViewModel.cs
  56. +1
    -1
      logic/Client/Client.csproj
  57. +319
    -201
      logic/Client/MainWindow.xaml.cs
  58. +6
    -2
      logic/Client/PlaybackClient.cs
  59. +1
    -1
      logic/Client/Properties/launchSettings.json
  60. +9
    -0
      logic/Client/StatusBarOfHunter.xaml.cs
  61. +9
    -0
      logic/Client/StatusBarOfSurvivor.xaml.cs
  62. +1
    -1
      logic/ClientTest/ClientTest.csproj
  63. +2
    -3
      logic/GameClass/GameObj/Bullet/BombedBullet.cs
  64. +60
    -55
      logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs
  65. +1
    -1
      logic/GameClass/GameObj/Bullet/Bullet.Student.cs
  66. +29
    -16
      logic/GameClass/GameObj/Bullet/Bullet.cs
  67. +13
    -0
      logic/GameClass/GameObj/Character/Character.BuffManager.cs
  68. +0
    -5
      logic/GameClass/GameObj/Character/Character.Ghost.cs
  69. +12
    -22
      logic/GameClass/GameObj/Character/Character.Skill.cs
  70. +1
    -1
      logic/GameClass/GameObj/Character/Character.Student.cs
  71. +439
    -179
      logic/GameClass/GameObj/Character/Character.cs
  72. +1
    -1
      logic/GameClass/GameObj/Character/Team.cs
  73. +15
    -47
      logic/GameClass/GameObj/GameObj.cs
  74. +18
    -0
      logic/GameClass/GameObj/Immovable.cs
  75. +7
    -9
      logic/GameClass/GameObj/Map/Chest.cs
  76. +130
    -15
      logic/GameClass/GameObj/Map/Door.cs
  77. +50
    -17
      logic/GameClass/GameObj/Map/Doorway.cs
  78. +1
    -3
      logic/GameClass/GameObj/Map/EmergencyExit.cs
  79. +1
    -3
      logic/GameClass/GameObj/Map/Generator.cs
  80. +11
    -7
      logic/GameClass/GameObj/Map/Map.cs
  81. +103
    -50
      logic/GameClass/GameObj/Map/MapInfo.cs
  82. +1
    -3
      logic/GameClass/GameObj/Map/Wall.cs
  83. +35
    -6
      logic/GameClass/GameObj/Map/Window.cs
  84. +80
    -47
      logic/GameClass/GameObj/Moveable.cs
  85. +11
    -2
      logic/GameClass/GameObj/ObjOfCharacter.cs
  86. +1
    -3
      logic/GameClass/GameObj/OutOfBoundBlock.cs
  87. +70
    -42
      logic/GameClass/GameObj/Prop/Gadget.cs
  88. +92
    -0
      logic/GameClass/GameObj/Prop/Item.cs
  89. +7
    -12
      logic/GameClass/GameObj/Prop/PickedProp.cs
  90. +120
    -119
      logic/GameEngine/MoveEngine.cs
  91. +294
    -194
      logic/Gaming/ActionManager.cs
  92. +59
    -34
      logic/Gaming/AttackManager.cs
  93. +82
    -102
      logic/Gaming/CharacterManager.cs
  94. +20
    -11
      logic/Gaming/Game.cs
  95. +19
    -22
      logic/Gaming/PropManager.cs
  96. +225
    -55
      logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs
  97. +6
    -2
      logic/Gaming/SkillManager/SkillManager.PassiveSkill.cs
  98. +11
    -4
      logic/Gaming/SkillManager/SkillManager.cs
  99. +7
    -6
      logic/Preparation/Interface/ICharacter.cs
  100. +9
    -0
      logic/Preparation/Interface/IChest.cs

+ 4
- 0
.github/workflows/upload_COS.yml View File

@@ -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
- 1
CAPI/cmd/RunServer.cmd View File

@@ -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
- 1
CAPI/cmd/RunServerForDebug.cmd View File

@@ -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

+ 6
- 6
CAPI/cpp/API/include/API.h View File

@@ -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;


+ 6
- 1
CAPI/cpp/API/include/Communication.h View File

@@ -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;
};



+ 59
- 32
CAPI/cpp/API/include/constants.h View File

@@ -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

+ 1
- 1
CAPI/cpp/API/include/logic.h View File

@@ -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;


+ 9
- 7
CAPI/cpp/API/include/structures.h View File

@@ -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{


+ 9
- 6
CAPI/cpp/API/include/utils.hpp View File

@@ -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


+ 4
- 4
CAPI/cpp/API/src/API.cpp View File

@@ -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()


+ 113
- 4
CAPI/cpp/API/src/Communication.cpp View File

@@ -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();
}


+ 14
- 14
CAPI/cpp/API/src/DebugAPI.cpp View File

@@ -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) + ", ";


+ 95
- 71
CAPI/cpp/API/src/logic.cpp View File

@@ -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;
}
};

// 连接服务器


+ 5
- 1
CAPI/cpp/API/src/main.cpp View File

@@ -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;
}


+ 50
- 0
CAPI/cpp/README.md View File

@@ -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 等并不一致或不完全一致,增加各种不兼容问题的风险

## 开发人员

- ......(自己加)

+ 200
- 332
CAPI/cpp/proto/Message2Clients.pb.cc
File diff suppressed because it is too large
View File


+ 92
- 236
CAPI/cpp/proto/Message2Clients.pb.h View File

@@ -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


+ 38
- 10
CAPI/cpp/proto/Message2Server.pb.cc View File

@@ -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_)
);


+ 36
- 0
CAPI/cpp/proto/Message2Server.pb.h View File

@@ -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__


+ 40
- 39
CAPI/cpp/proto/MessageType.pb.cc View File

@@ -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;


+ 4
- 3
CAPI/cpp/proto/MessageType.pb.h View File

@@ -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();


+ 2
- 0
CAPI/cpp/proto/Services.grpc.pb.h View File

@@ -25,6 +25,8 @@
#include <grpcpp/impl/codegen/stub_options.h>
#include <grpcpp/impl/codegen/sync_stream.h>

#undef SendMessage

namespace protobuf
{



+ 18
- 8
CAPI/python/PyAPI/API.py View File

@@ -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模式下有效



+ 124
- 21
CAPI/python/PyAPI/Communication.py View File

@@ -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



+ 219
- 188
CAPI/python/PyAPI/DebugAPI.py View File

@@ -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
- 8
CAPI/python/PyAPI/Interface.py View File

@@ -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


+ 1
- 0
CAPI/python/PyAPI/State.py View File

@@ -15,6 +15,7 @@ class State:
self.mapInfo = THUAI6.GameMap()
self.gameInfo = THUAI6.GameInfo()
self.guids = []

teamScore: int
self: Union[THUAI6.Student, THUAI6.Tricker]



+ 115
- 90
CAPI/python/PyAPI/constants.py View File

@@ -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

+ 227
- 81
CAPI/python/PyAPI/logic.py View File

@@ -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()


+ 47
- 16
CAPI/python/PyAPI/main.py View File

@@ -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)

+ 2
- 4
CAPI/python/PyAPI/structures.py View File

@@ -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



+ 123
- 57
CAPI/python/PyAPI/utils.py View File

@@ -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
)

+ 2
- 2
CAPI/python/requirements.txt View File

@@ -1,3 +1,3 @@
grpcio==1.52.0
grpcio-tools==1.52.0
grpcio==1.54.2
grpcio-tools==1.54.2
numpy

+ 1
- 1
CAPI/python/run.sh View File

@@ -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
- 1
CAPI/shell/RunServer.sh View File

@@ -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
- 1
CAPI/shell/RunServerForDebug.sh View File

@@ -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"

+ 3
- 1
README.md View File

@@ -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),等等。



+ 1
- 0
dependency/Dockerfile/Dockerfile_cpp View File

@@ -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


+ 4
- 4
dependency/proto/Message2Clients.proto View File

@@ -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;
}




+ 1
- 0
dependency/proto/Message2Server.proto View File

@@ -61,6 +61,7 @@ message SkillMsg
{
int64 player_id = 1;
int32 skill_id = 2;
int32 skill_param = 3;
}

// 基本继承于THUAI5,为了使发送的信息尽可能不被浪费,暂定不发这类大包。


+ 4
- 3
dependency/proto/MessageType.proto View File

@@ -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;
}



+ 0
- 0
dependency/proto/Proto.sln View File


+ 2
- 2
dependency/proto/Protos.csproj View File

@@ -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">


+ 205
- 0
dependency/py/rank.py View File

@@ -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}")

+ 11
- 2
dependency/shell/compile.sh View File

@@ -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


+ 5
- 6
dependency/shell/run.sh View File

@@ -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}'}'

+ 48
- 43
docs/CAPI接口(cpp).md View File

@@ -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;



+ 45
- 42
docs/CAPI接口(python).md View File

@@ -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

# 等待下一帧


+ 14
- 11
docs/GameRules.md View File

@@ -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 @@ $$
- 开锁门进度中断后清空

### 窗
- 由于窗户被占用导致翻窗失败会使先前行动停止
- 翻越窗户是一种交互行为,翻窗一共有两个过程
- 跳上窗:从当前位置到窗边缘中点,位置瞬移,时间=距离/爬窗速度。中断时,停留在原位置
- 爬窗:从窗一侧边缘中点到另一侧格子中心,位置渐移,时间=距离/爬窗速度。中断时,停留在另一侧格子中心


+ 6
- 3
docs/使用文档.md View File

@@ -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 |

* 捣蛋鬼


+ 119
- 0
docs/游戏机制与平衡性调整更新草案.md View File

@@ -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倍速"

+ 53
- 0
docs/版本更新说明.md View File

@@ -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:游戏机制与平衡性调整已完成

+ 51
- 5
installer/Installer/MainWindow.xaml View File

@@ -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>

+ 499
- 29
installer/Installer/Model.cs View File

@@ -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;
}
}
}
}



+ 107
- 29
installer/Installer/ViewModel.cs View File

@@ -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");
}
}));
}


+ 1
- 1
logic/Client/Client.csproj View File

@@ -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>


+ 319
- 201
logic/Client/MainWindow.xaml.cs View File

@@ -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 } };
}
}
}

+ 6
- 2
logic/Client/PlaybackClient.cs View File

@@ -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;
}
}
}
}

+ 1
- 1
logic/Client/Properties/launchSettings.json View File

@@ -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"
}
}
}

+ 9
- 0
logic/Client/StatusBarOfHunter.xaml.cs View File

@@ -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;


+ 9
- 0
logic/Client/StatusBarOfSurvivor.xaml.cs View File

@@ -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;


+ 1
- 1
logic/ClientTest/ClientTest.csproj View File

@@ -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>


+ 2
- 3
logic/GameClass/GameObj/Bullet/BombedBullet.cs View File

@@ -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;
}
}
}

+ 60
- 55
logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs View File

@@ -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;



+ 1
- 1
logic/GameClass/GameObj/Bullet/Bullet.Student.cs View File

@@ -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;


+ 29
- 16
logic/GameClass/GameObj/Bullet/Bullet.cs View File

@@ -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;
}


+ 13
- 0
logic/GameClass/GameObj/Character/Character.BuffManager.cs View File

@@ -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


+ 0
- 5
logic/GameClass/GameObj/Character/Character.Ghost.cs View File

@@ -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
{


+ 12
- 22
logic/GameClass/GameObj/Character/Character.Skill.cs View File

@@ -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!");
}


+ 1
- 1
logic/GameClass/GameObj/Character/Character.Student.cs View File

@@ -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);


+ 439
- 179
logic/GameClass/GameObj/Character/Character.cs View File

@@ -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;
}


+ 1
- 1
logic/GameClass/GameObj/Character/Team.cs View File

@@ -17,7 +17,7 @@ namespace GameClass.GameObj
{
int score = 0;
foreach (var player in playerList)
score += player.Score;
score += (int)player.Score;
return score;
}
}


+ 15
- 47
logic/GameClass/GameObj/GameObj.cs View File

@@ -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);


+ 18
- 0
logic/GameClass/GameObj/Immovable.cs View File

@@ -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)
{
}
}
}

+ 7
- 9
logic/GameClass/GameObj/Map/Chest.cs View File

@@ -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;


+ 130
- 15
logic/GameClass/GameObj/Map/Door.cs View File

@@ -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();
}
}
}


+ 50
- 17
logic/GameClass/GameObj/Map/Doorway.cs View File

@@ -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);
}
}

+ 1
- 3
logic/GameClass/GameObj/Map/EmergencyExit.cs View File

@@ -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;


+ 1
- 3
logic/GameClass/GameObj/Map/Generator.cs View File

@@ -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;


+ 11
- 7
logic/GameClass/GameObj/Map/Map.cs View File

@@ -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)
{


+ 103
- 50
logic/GameClass/GameObj/Map/MapInfo.cs View File

@@ -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, },*/

+ 1
- 3
logic/GameClass/GameObj/Map/Wall.cs View File

@@ -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;


+ 35
- 6
logic/GameClass/GameObj/Map/Window.cs View File

@@ -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;
}
}
}

+ 80
- 47
logic/GameClass/GameObj/Moveable.cs View File

@@ -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)
{
}


+ 11
- 2
logic/GameClass/GameObj/ObjOfCharacter.cs View File

@@ -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;
}


+ 1
- 3
logic/GameClass/GameObj/OutOfBoundBlock.cs View File

@@ -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;


logic/GameClass/GameObj/Prop.cs → logic/GameClass/GameObj/Prop/Gadget.cs View File

@@ -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();
}

+ 92
- 0
logic/GameClass/GameObj/Prop/Item.cs View File

@@ -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
}

logic/GameClass/GameObj/PickedProp.cs → logic/GameClass/GameObj/Prop/PickedProp.cs View File

@@ -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;
}
}
}*/
}

+ 120
- 119
logic/GameEngine/MoveEngine.cs View File

@@ -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();


+ 294
- 194
logic/Gaming/ActionManager.cs View File

@@ -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);
}
);


+ 59
- 34
logic/Gaming/AttackManager.cs View File

@@ -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);
}


logic/Gaming/CharacterManager .cs → logic/Gaming/CharacterManager.cs View File

@@ -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;
}

+ 20
- 11
logic/Gaming/Game.cs View File

@@ -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();


+ 19
- 22
logic/Gaming/PropManager.cs View File

@@ -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);
}



+ 225
- 55
logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs View File

@@ -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.");
}
)


+ 6
- 2
logic/Gaming/SkillManager/SkillManager.PassiveSkill.cs View File

@@ -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
- 4
logic/Gaming/SkillManager/SkillManager.cs View File

@@ -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;
}


+ 7
- 6
logic/Preparation/Interface/ICharacter.cs View File

@@ -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();
}


+ 9
- 0
logic/Preparation/Interface/IChest.cs View File

@@ -0,0 +1,9 @@
using Preparation.Utility;

namespace Preparation.Interface
{
public interface IChest : IGameObj
{
public void StopOpen();
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save