Browse Source

Merge branch 'dev' of github.com:sendssf/THUAI6 into dev

tags/0.1.0
sendssf 2 years ago
parent
commit
0b20117fa6
70 changed files with 3637 additions and 1226 deletions
  1. +199
    -0
      CAPI/CAPI接口.md
  2. +931
    -0
      CAPI/Tool_tutorial.md
  3. +26
    -9
      CAPI/cpp/API/include/API.h
  4. +3
    -2
      CAPI/cpp/API/include/Communication.h
  5. +286
    -3
      CAPI/cpp/API/include/constants.h
  6. +4
    -2
      CAPI/cpp/API/include/logic.h
  7. +13
    -12
      CAPI/cpp/API/include/structures.h
  8. +9
    -9
      CAPI/cpp/API/include/utils.hpp
  9. +1
    -0
      CAPI/cpp/API/src/AI.cpp
  10. +26
    -4
      CAPI/cpp/API/src/API.cpp
  11. +14
    -2
      CAPI/cpp/API/src/Communication.cpp
  12. +41
    -11
      CAPI/cpp/API/src/DebugAPI.cpp
  13. +47
    -6
      CAPI/cpp/API/src/logic.cpp
  14. +0
    -31
      CAPI/cpp/CAPI.sln
  15. +5
    -6
      CAPI/cpp/proto/MessageType.pb.cc
  16. +2
    -2
      CAPI/cpp/proto/MessageType.pb.h
  17. +95
    -31
      CAPI/python/PyAPI/AI.py
  18. +16
    -4
      CAPI/python/PyAPI/API.py
  19. +12
    -3
      CAPI/python/PyAPI/Communication.py
  20. +47
    -15
      CAPI/python/PyAPI/DebugAPI.py
  21. +20
    -4
      CAPI/python/PyAPI/Interface.py
  22. +295
    -0
      CAPI/python/PyAPI/constants.py
  23. +40
    -21
      CAPI/python/PyAPI/logic.py
  24. +0
    -1
      CAPI/python/PyAPI/main.py
  25. +9
    -7
      CAPI/python/PyAPI/structures.py
  26. +11
    -9
      CAPI/python/PyAPI/utils.py
  27. +3
    -1
      CAPI/python/run.sh
  28. +1
    -1
      dependency/proto/MessageType.proto
  29. +56
    -0
      installer/Installer/Common.cs
  30. +1
    -1
      installer/Installer/MainWindow.xaml
  31. +39
    -32
      installer/Installer/Model.cs
  32. +12
    -8
      installer/Installer/ViewModel.cs
  33. +2
    -0
      logic/.gitignore
  34. +1
    -1
      logic/Client/CommandLineArgs.cs
  35. +204
    -147
      logic/Client/MainWindow.xaml.cs
  36. +173
    -161
      logic/Client/PlaybackClient.cs
  37. +1
    -1
      logic/Client/Properties/launchSettings.json
  38. +14
    -6
      logic/Client/StatusBarOfCircumstance.xaml.cs
  39. +4
    -3
      logic/Client/StatusBarOfHunter.xaml.cs
  40. +2
    -1
      logic/Client/StatusBarOfSurvivor.xaml.cs
  41. +2
    -4
      logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs
  42. +30
    -23
      logic/GameClass/GameObj/Character/Character.cs
  43. +16
    -11
      logic/GameClass/GameObj/Map/Chest.cs
  44. +5
    -5
      logic/GameClass/GameObj/Map/Doorway.cs
  45. +14
    -0
      logic/GameClass/GameObj/Map/Map.cs
  46. +346
    -0
      logic/GameRules.md
  47. +69
    -65
      logic/Gaming/ActionManager.cs
  48. +37
    -26
      logic/Gaming/AttackManager.cs
  49. +37
    -17
      logic/Gaming/CharacterManager .cs
  50. +8
    -7
      logic/Gaming/Game.cs
  51. +6
    -5
      logic/Gaming/PropManager.cs
  52. +53
    -9
      logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs
  53. +3
    -0
      logic/Gaming/SkillManager/SkillManager.cs
  54. +3
    -1
      logic/Preparation/Interface/ICharacter.cs
  55. +43
    -10
      logic/Preparation/Interface/IOccupation.cs
  56. +29
    -10
      logic/Preparation/Interface/ISkill.cs
  57. +2
    -0
      logic/Preparation/Utility/EnumType.cs
  58. +31
    -23
      logic/Preparation/Utility/GameData.cs
  59. +4
    -0
      logic/Preparation/Utility/Transformation.cs
  60. +1
    -4
      logic/Preparation/Utility/XY.cs
  61. +149
    -103
      logic/Server/CopyInfo.cs
  62. +12
    -8
      logic/Server/GameServer.cs
  63. +1
    -1
      logic/Server/Properties/launchSettings.json
  64. +19
    -4
      logic/Server/RpcServices.cs
  65. +4
    -2
      logic/cmd/gameServer.cmd
  66. +1
    -1
      logic/cmd/playback.cmd
  67. +28
    -0
      logic/使用文档.md
  68. +0
    -325
      logic/规则Logic.md
  69. +17
    -4
      playback/Playback/MessageReader.cs
  70. +2
    -1
      playback/Playback/MessageWriter.cs

+ 199
- 0
CAPI/CAPI接口.md View File

@@ -0,0 +1,199 @@
- [CAPI接口](#capi接口)
- [接口解释](#接口解释)
- [主动指令](#主动指令)
- [移动](#移动)
- [使用技能](#使用技能)
- [人物](#人物)
- [攻击](#攻击)
- [学习与毕业](#学习与毕业)
- [勉励与唤醒](#勉励与唤醒)
- [地图互动](#地图互动)
- [道具](#道具)
- [信息获取](#信息获取)
- [队内信息](#队内信息)
- [查询可视范围内的信息](#查询可视范围内的信息)
- [查询特定位置物体的信息,下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。](#查询特定位置物体的信息下面的-cellx-和-celly-指的是地图格数而非绝对坐标)
- [其他](#其他)
- [辅助函数](#辅助函数)
- [接口一览](#接口一览)
# CAPI接口
## 接口解释

### 主动指令
#### 移动
- `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> EndAllAction()`:可以使不处在不可行动状态中的玩家终止当前行动

#### 攻击
- `std::future<bool> Attack(double angleInRadian)`:`angleInRadian`为攻击方向

#### 学习与毕业
- `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> 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)`将对应类型的道具扔在原地

### 信息获取

#### 队内信息
- `std::future<bool> SendMessage(int64_t, std::string)`:给同队的队友发送消息。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。
- `bool HaveMessage()`:是否有队友发来的尚未接收的信息。
- `std::pair<int64_t, std::string> GetMessage()`:从玩家ID为第一个参数的队友获取信息。

#### 查询可视范围内的信息
- `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` :返回所有可视子弹(攻击)的信息。

#### 查询特定位置物体的信息,下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。
- `THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY)` :返回某一位置场地种类信息。场地种类详见 structure.h 。
- `bool IsDoorOpen(int32_t cellX, int32_t cellY) const`:查询特定位置门是否开启
- `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`:查询特定位置教室作业完成进度
- `THUAI6::HiddenGateState GetHiddenGateState(int32_t cellX, int32_t cellY) const`::查询特定位置隐藏校门状态
- `int32_t GetDoorProgress(int32_t cellX, int32_t cellY) const`:查询特定位置门开启状态

#### 其他
- `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`:返回整张地图的地形信息。

### 辅助函数
`static inline int CellToGrid(int cell) noexcept`:将地图格数 cell 转换为绝对坐标grid。

`static inline int GridToCell(int grid) noexcept`:将绝对坐标 grid 转换为地图格数cell。

下面为用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用
~~~c
void Print(std::string str) const;
void PrintStudent() const;
void PrintTricker() const;
void PrintProp() const;
void PrintSelfInfo() const;
~~~

## 接口一览
~~~csharp
// 指挥本角色进行移动,`timeInMilliseconds` 为移动时间,单位为毫秒;`angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴
virtual std::future<bool> Move(int64_t timeInMilliseconds, double angleInRadian) = 0;

// 向特定方向移动
virtual std::future<bool> MoveRight(int64_t timeInMilliseconds) = 0;
virtual std::future<bool> MoveUp(int64_t timeInMilliseconds) = 0;
virtual std::future<bool> MoveLeft(int64_t timeInMilliseconds) = 0;
virtual std::future<bool> MoveDown(int64_t timeInMilliseconds) = 0;

// 捡道具、使用技能
virtual std::future<bool> PickProp(THUAI6::PropType prop) = 0;
virtual std::future<bool> UseProp(THUAI6::PropType prop) = 0;
virtual std::future<bool> UseSkill(int32_t skillID) = 0;
virtual std::future<bool> Attack(double angleInRadian) = 0;

virtual std::future<bool> OpenDoor() = 0;
virtual std::future<bool> CloseDoor() = 0;
virtual std::future<bool> SkipWindow() = 0;
virtual std::future<bool> StartOpenGate() = 0;
virtual std::future<bool> StartOpenChest() = 0;
virtual std::future<bool> EndAllAction() = 0;

// 发送信息、接受信息,注意收消息时无消息则返回nullopt
virtual std::future<bool> SendMessage(int64_t, std::string) = 0;
[[nodiscard]] virtual bool HaveMessage() = 0;
[[nodiscard]] virtual std::pair<int64_t, std::string> GetMessage() = 0;

// 等待下一帧
virtual std::future<bool> Wait() = 0;

// 获取视野内可见的学生/捣蛋鬼的信息
[[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Student>> GetStudents() const = 0;
[[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const = 0;

// 获取视野内可见的道具信息
[[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const = 0;

// 获取地图信息,视野外的地图统一为Land
[[nodiscard]] virtual std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const = 0;
[[nodiscard]] virtual THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const = 0;

[[nodiscard]] virtual bool IsDoorOpen(int32_t cellX, int32_t cellY) const = 0;
[[nodiscard]] virtual int32_t GetChestProgress(int32_t cellX, int32_t cellY) const = 0;
[[nodiscard]] virtual int32_t GetGateProgress(int32_t cellX, int32_t cellY) const = 0;
[[nodiscard]] virtual int32_t GetClassroomProgress(int32_t cellX, int32_t cellY) const = 0;
[[nodiscard]] virtual THUAI6::HiddenGateState GetHiddenGateState(int32_t cellX, int32_t cellY) const = 0;
[[nodiscard]] virtual int32_t GetDoorProgress(int32_t cellX, int32_t cellY) const = 0;

[[nodiscard]] virtual std::shared_ptr<const THUAI6::GameInfo> GetGameInfo() const = 0;

// 获取所有玩家的GUID
[[nodiscard]] virtual std::vector<int64_t> GetPlayerGUIDs() const = 0;

// 获取游戏目前所进行的帧数
[[nodiscard]] virtual int GetFrameCount() const = 0;

/*****选手可能用的辅助函数*****/

// 获取指定格子中心的坐标
[[nodiscard]] static inline int CellToGrid(int cell) noexcept
{
return cell * numOfGridPerCell + numOfGridPerCell / 2;
}

// 获取指定坐标点所位于的格子的 X 序号
[[nodiscard]] static inline int GridToCell(int grid) noexcept
{
return grid / numOfGridPerCell;
}

// 用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用

virtual void Print(std::string str) const = 0;
virtual void PrintStudent() const = 0;
virtual void PrintTricker() const = 0;
virtual void PrintProp() const = 0;
virtual void PrintSelfInfo() const = 0;
};

class IStudentAPI : public IAPI
{
public:
/*****学生阵营的特定函数*****/

virtual std::future<bool> StartLearning() = 0;
virtual std::future<bool> StartEncourageMate(int64_t mateID) = 0;
virtual std::future<bool> StartRouseMate(int64_t mateID) = 0;
virtual std::future<bool> Graduate() = 0;
[[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> GetSelfInfo() const = 0;
};

class ITrickerAPI : public IAPI
{
public:
/*****捣蛋鬼阵营的特定函数*****/

[[nodiscard]] virtual std::shared_ptr<const THUAI6::Tricker> GetSelfInfo() const = 0;
};
~~~

+ 931
- 0
CAPI/Tool_tutorial.md View File

@@ -0,0 +1,931 @@
# 工具使用合集

[toc]

## Visual Studio使用说明

待更新...

## cmd脚本的参数修改

待更新...

## C++接口必看

**在此鸣谢\xfgg/\xfgg/\xfgg/,看到这里的选手可以到选手群膜一膜!!! **

除非特殊指明,以下代码均在 MSVC 19.28.29913 /std:c++17 与 g++ 10.2 for linux -std=c++17 两个平台下通过。



由于我们的比赛最终会运行在Linux平台上,因此程设课上学到的一些只适用于Windows的C++操作很可能并不能正确执行。此外,代码中使用了大量Modern C++中的新特性,可能会使选手在编程过程中遇到较大困难。因此,此处介绍一些比赛中使用C++接口必须了解的知识。



### 计时相关



编写代码过程中,我们可能需要获取系统时间等一系列操作,C++ 标准库提供了这样的行为。尤其注意**不要**使用 Windows 平台上的 `GetTickCount` 或者 `GetTickCount64` !!! 应当使用 `std::chrono`

头文件:`#include <chrono>`

可以用于获取时间戳,从而用于计时、例如计算某个操作花费的时间,或者协调队友间的合作。

```c++
#include <iostream>
#include <chrono>
int main()
{
auto sec = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
auto msec = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::cout << "从 1970 年元旦到现在的:秒数" << sec << ";毫秒数:" << msec << std::endl;
return 0;
}
```



### 线程睡眠



由于移动过程中会阻塞人物角色,因此玩家可能要在移动后让线程休眠一段时间,直到移动结束。C++ 标准库中使线程休眠需要包含头文件:`#include <thread>`。示例用法:

```cpp
std::this_thread::sleep_for(std::chrono::milliseconds(20)); // 休眠 20 毫秒
std::this_thread::sleep_for(std::chrono::seconds(2)); // 休眠 2 秒

// 下面这个也能休眠 200 毫秒
std::this_thread::sleep_until(std::chrono::system_clock::now() += std::chrono::milliseconds(200));
```

休眠过程中,线程将被阻塞,而不继续进行,直到休眠时间结束方继续向下执行。



### 异步接口的使用



本届比赛中,我们可能会看到类似 `std::future<bool>` 这样类型的接口返回值,这实际上是一个异步接口。在调用同步接口后,在接口内的函数未执行完之前,线程通常会阻塞住;但是异步接口的调用通常不会阻塞当前线程,而是会另外开启一个线程进行操作,当前线程则继续向下执行。当调用 `get()` 方法时,将返回异步接口的值,若此时异步接口内的函数依然未执行完,则会阻塞当前线程。

如果不需要返回值或没有返回值,但是希望接口内的函数执行完之后再进行下一步,即将接口当做常规的同步接口来调用,也可以调用 `wait()` 方法。



```c++
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int f_sync()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
return 8;
}

std::future<int> f_async()
{
return std::async(std::launch::async, []()
{ std::this_thread::sleep_for(std::chrono::seconds(1));
return 8; });
}

int main()
{
auto start = std::chrono::system_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(std::chrono::system_clock::now() - start).count() << std::endl;
auto x = f_async();
std::cout << std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(std::chrono::system_clock::now() - start).count() << std::endl;
std::cout << x.get() << std::endl;
std::cout << std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(std::chrono::system_clock::now() - start).count() << std::endl;
auto y = f_sync();
std::cout << std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(std::chrono::system_clock::now() - start).count() << std::endl;
std::cout << y << std::endl;
std::cout << std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(std::chrono::system_clock::now() - start).count() << std::endl;
}
```



### `auto`类型推导



C++11开始支持使用 `auto` 自动推导变量类型,废除了原有的作为 storage-class-specifier 的作用:

```c++
int i = 4;
auto x = i; // auto 被推导为 int,x 是 int 类型
auto& y = i; // auto 仍被推导为 int,y 是 int& 类型
auto&& z = i; // auto 被推导为 int&,z 是 int&&&,被折叠为 int&,即 z 与 y 同类型
auto&& w = 4; // auto 被推导为 int,w 是 int&& 类型
```



### STL相关



#### std::vector

头文件:`#include <vector>`,类似于可变长的数组,支持下标运算符 `[]` 访问其元素,此时与 C 风格数组用法相似。支持 `size` 成员函数获取其中的元素数量。

创建一个 `int` 型的 `vector` 对象:

```cpp
std::vector<int> v { 9, 1, 2, 3, 4 }; // 初始化 vector 有五个元素,v[0] = 9, ...
v.emplace_back(10); // 向 v 尾部添加一个元素,该元素饿构造函数的参数为 10(对于 int,只有一个语法意义上的构造函数,无真正的构造函数),即现在 v 有六个元素,v[5] 的值是10
v.pop_back(); // 把最后一个元素删除,现在 v 还是 { 9, 1, 2, 3, 4 }
```



遍历其中所有元素的方式:

```cpp
// std::vector<int> v;
for (int i = 0; i < (int)v.size(); ++i)
{
/*可以通过 v[i] 对其进行访问*/
}

for (auto itr = v.begin(); itr != v.end(); ++itr)
{
/*
* itr 作为迭代器,可以通过其访问 vector 中的元素。其用法与指针几乎完全相同。
* 可以通过 *itr 得到元素;以及 itr-> 的用法也是支持的
* 实际上它内部就是封装了指向 vector 中元素的指针
* 此外还有 v.cbegin()、v.rbegin()、v.crbegin() 等
* v.begin()、v.end() 也可写为 begin(v)、end(v)
*/
}

for (auto&& elem : v)
{
/*
* elem 即是 v 中每个元素的引用,也可写成 auto& elem : v
* 它完全等价于:
* {
* auto&& __range = v;
* auto&& __begin = begin(v);
* auto&& __end = end(v);
* for (; __begin != __end; ++__begin)
* {
* auto&& elem = *__begin;
* // Some code
* }
* }
*/
}
```

例如:

```cpp
for (auto elem&& : v) { std::cout << elem << ' '; }
std::cout << std::endl;
```



作为 STL 的容器之一,其具有容器的通用接口。但是由于这比较复杂,在此难以一一展开。有兴趣的同学可以在下方提供的链接里进行查阅。

**注:请千万不要试图使用 `std::vector<bool>`,若需使用,请用 `std::vector<char>` 代替!**

更多用法参见(点击进入):[cppreference_vector](https://zh.cppreference.com/w/cpp/container/vector)



#### std::array

头文件:`#include <array>`,C 风格数组的类封装版本。

用法与 C 风格的数组是基本相似的,例如:

```cpp
std::array<double, 5> arr { 9.0, 8.0, 7.0, 6.0, 5.0 };
std::cout << arr[2] << std::endl; // 输出 7.0
```

同时也支持各种容器操作:

```cpp
double sum = 0.0;
for (auto itr = begin(arr); itr != end(arr); ++itr)
{
sum += *itr;
}
// sum 结果是 35
```



更多用法参见(点击进入):[cppreference_array](https://zh.cppreference.com/w/cpp/container/array)。



## Python接口必看



比赛中的Python接口大多使用异步接口,即返回一个类似于 `Future[bool]` 的值。为了获取实际的值,需要调用 `result()` 方法。

```python
from concurrent.futures import Future, ThreadPoolExecutor
import time


class Cls:
def __init__(self):
self.__pool: ThreadPoolExecutor = ThreadPoolExecutor(10)

def Test(self, a: int, b: int) -> Future[int]:
def test():
time.sleep(0.5)
return a + b

return self.__pool.submit(test)


if __name__ == '__main__':
f1 = Cls().Test(1, 2)
print(time.time())
print(f1.result())
print(time.time())

```



## C++相关小知识



### lambda表达式



#### lambda表达式概述



lambda 表达式是 C++ 发展史上的一个重大事件,也是 C++ 支持函数式编程的重要一环。可以说,lambda 表达式不仅给 C++ 程序员带来了极大的便利,也开创了 C++ 的一个崭新的编程范式。但是同时 lambda 表达式也带来了诸多的语法难题,使用容易,但精通极难。

lambda 表达式确实是一个非常有用的语法特性。至少个人在学了 lambda 表达式之后,编写 C++ 代码就再也没有离开过。因为,它真的是非常的方便与易用。

lambda 表达式首先可以看做是一个临时使用的函数。它的一般格式如下:

```c++
[捕获列表] + lambda 声明(可选) + 复合语句
lambda 声明指的是:
(参数列表) + 一堆修饰符(可选)
```

下面是一个简单的例子:

```c++
#include <iostream>
using namespace std;
int main(void)
{
auto GetOne = []{ return 1; }; // GetOne 是一个 lambda 表达式
cout << GetOne() << endl; // 使用起来就像一个函数,输出 1
return 0;
}
```

它还可以有参数:

```c++
#include <iostream>
using namespace std;
int main(void)
{
auto GetSum = [](int x, int y){ return x + y; };
cout << GetSum(2, 3) << endl; // 5
return 0;
}
```

或者临时调用:

```c++
#include <iostream>
using namespace std;
int main(void)
{
cout << [](int x, int y){ return x + y; }(2, 3) << endl; // 5
return 0;
}
```



#### lambda 表达式的捕获

##### 捕获的概念

lambda 表达式是不能够直接使用函数内的局部变量的(之后你将会看到这是为什么)。如果需要使用函数内的局部变量,需要手动进行捕获。捕获的方式有两种:按值捕获与按引用捕获。按值捕获,只会获得该值,而按引用捕获,则会获得函数内局部变量的引用。声明要捕获的变量就在 lambda 表达式的 `[]` 内:

+ `[]`:不捕获任何局部变量
+ `[x]`:按值捕获变量 `x`
+ `[&y]`:按引用捕获变量 `y`
+ `[=]`:按值捕获全部局部变量
+ `[&]`:按引用捕获全部局部变量
+ `[&, x]`:除了 `x` 按值捕获之外,其他变量均按引用捕获
+ `[=, &y]`:什么意思不用我都说了吧
+ `[r = x]`:声明一个变量 `r` ,捕获 `x` 的值
+ `[&r = y]`:声明一个引用 `r`,捕获 `y` 的引用
+ `[x, y, &z, w = p, &r = q]`:作为练习
+ `[&, x, y, p = z]`:这个也作为练习

这样我们就可以写出下面的代码了:

```cpp
#include <iostream>
using namespace std;
int main(void)
{
int x, y, z;
cin >> x >> y;
[x, y, &z](){ z = x + y; }();
cout << z << endl; // z = x + y
return 0;
}
```



##### 捕获 `this` 与 `*this`

当 lambda 表达式位于类的成员函数内时,该如何使用该类的成员变量呢?我们知道,在类的成员函数体内使用成员变量,都是通过 `this` 指针访问的,此处 `this` 作为成员函数的一个参数,因此只需要捕获 `this` 指针,就可以在 lambda 体内访问其成员变量了!

捕获时,我们可以选择捕获 `[this]`,也可以捕获 `[*this]`。区别是,前者捕获的是 `this` 指针本身,而后者是按值捕获 `this` 指针所指向的对象,也就是以 `*this` 为参数复制构造了一个新的对象。看下面的代码:

```c++
#include <iostream>
using namespace std;

struct Foo
{
int m_bar;
void Func()
{
[this]()
{
cout << ++m_bar << endl;
}();
}
};

int main()
{
Foo foo;
foo.m_bar = 999;
foo.Func(); // 输出 1000
}
```



##### 附注

需要注意的是,lambda 表达式的捕获发生在 **lambda 表达式定义处**,而不是 lambda 表达式调用处,比如:

```c++
int a = 4;
auto f = [a]() { cout << a << endl; }; // 此时捕获 a,值是 4
a = 9;
f(); // 输出 4,而非 9
```



> **C++ 真奇妙:不需要捕获的情况**
>
> 看这特殊的引用块就知道,本段内容仅作介绍,感觉较难者请跳过本块。
>
> 有时,即使是局部变量,不需要捕获也可以编译通过。这是 C++ 标准对编译器实现做出的妥协。这种现象叫做“常量折叠(constant folding)”;与之相对的是不能直接使用,必须进行捕获的情况,通常称作“odr-used”。这两个概念比较复杂,在此不做过多展开。看下面的例子:
>
> ```c++
> int Func1(const int& x) { return x; }
> void Func2()
> {
> const int x = 4;
> []()
> {
> int y = x; // OK, constant folding
> int z = Func1(x); // Compile error! odr-used! x is not captured!
> }();
> }
> ```
>
> 但是个别较老的编译器即使是 odr-used 也可能会编译通过



#### lambda 表达式的修饰符 `mutable`

lambda 表达式可以有一些修饰符,例如 `noexcept`、`mutable `等,这里仅介绍 `mutable`。

lambda 表达式按值捕获变量时,捕获的变量默认是不可修改:

```c++
int a = 4;
auto f = [a]()
{
++a; // Compile error: a cannot be modified!
};
```

但是我们可以通过加 `mutable` 关键字让它达到这个目的:

```c++
int a = 4;
auto f = [a]() mutable
{
++a; // OK
cout << a << endl;
};
f(); //输出 5
cout << a << endl; //输出 4
```

需要注意的是,按值捕获变量是生成了一个新的变量副本,而非原来的变量,所以在 lambda 外的 `a` 的值仍然是 `4`



#### lambda 表达式的本质

本段内容仅是粗略地讲述,不做深入讨论。读者也可以跳过本块。

上面说了这么多语法规定,但是 lamdba 表达式究竟是什么?知道了这个可以帮助我们理解 lambda 表达式的这些规定。

C++17 标准中如此定义 lambda 的类型:

> The type of a *lambda-expression* (which is also the type of the closure object ) is a unique, unnamed non-union class type, called the closure type....

lambda 表达式类型是一个独一无二的、没有名字的、并且不是联合体的类类型。我们把它叫做“**closure type**”。

后面还有一堆关于它性质的约束,这里就不展开了,大致上就是编译器可以自由决定它的很多性质,有兴趣的可以去翻阅《ISO/IEC 14882: 2017》第 8.1.5.1 款。

大体来看,一个 lamdba 表达式与一个类是大致上相同的。也就是说,lambda 表达式:

```c++
int a = 0, b = 0;
auto f = [a, &b](int x) { return a + b + x; }
f(5);
```

和下面的代码大致相同:

```c++
int a = 0, b = 0;
class __lambda__
{
private:
int a;
int& b;
public:
__lambda__(int& a, int& b) : a(a), b(b) {}
auto operator(int x) const { return a + b + x; }
};
__lambda__ f(a, b);
f.operator()(5);
```

不过它们两个**并不完全相同**。首先,不同编译器的实现本身就有不同;另外,它们在语法上的规定也有一些差别。篇幅所限,在此不做过多展开。



#### lambda 表达式的应用

看了上面这么多介绍,你可能要问:这东西能用什么用处?为什么不直接写个函数,或者是干脆不用 lambda 表达式而直接写在函数体里呢?有这个疑问是正常的。因为我上面给的例子都是可以不用 lambda 表达式就能轻松解决的。但是,lambda 表达式在很多应用场景具有不可替代的优势。最简单的例子,比如在局部,你要重复某些操作,但是另写一个函数又不是很方便,就可以用 lambda 表达式完成。此外,它最大的作用就是在函数式编程中,或者是其他需要回调函数的情况,以 lambda 表达式作为函数的参数以作为回调函数。在下面的教程中,例如多线程、智能指针,我们将会多次用到 lambda 表达式。届时你将会看到使用 lambda 表达式是多么的方便。



#### 关于 lambda 表达式的其他说明

lambda 表达式还有很多有趣之处,例如泛型 lambda、返回 lambda 表达式的 lamdba 表达式,此外 `decltype` 在 lambda 表达式中的使用也是光怪陆离……总之,lambda 表达式非常有趣。

到了这里,相信你对 lambda 表达式已经有了相当的理解,就让我们来做一道简单的练习吧(狗头)

> 请给出下面程序的输出(该程序选自《ISO/IEC 14882: 2017 Programming Language --- C++》第 107 页):
>
> ```c++
>#include <iostream>
> using namespace std;
>
> int main()
> {
> int a = 1, b = 1, c = 1;
> auto m1 = [a, &b, &c]() mutable
> {
> auto m2 = [a, b, &c]() mutable
> {
> cout << a << b << c;
> a = 4; b = 4; c = 4;
> };
> a = 3; b = 3; c = 3;
> m2();
> };
> a = 2; b = 2; c = 2;
> m1();
> cout << a << b << c << endl;
> return 0;
> }
> ```
> 相信聪明的你一下就看出了答案。没错,答案就是我们小学二年级学习的数字:**123234**!怎么样,你答对了吗?
>



如果阅读本文之后你觉得 lambda 表达式很有趣,欢迎阅读 《ISO/IEC 14882: 2017 Programming Language --- C++》110~120 页,或点击进入网址:[cppreference_lambda](https://zh.cppreference.com/w/cpp/language/lambda) 获取更多信息。





### std::thread

头文件:`#include <thread>`。用于开启新的线程。示例代码:

```c++
#include <iostream>
#include <thread>
#include <functional>

void Func(int x, int& cnt)
{
for (int i = 0; i < 110; ++i)
{
std::cout << "In Func: " << x << std::endl;
++cnt;
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}

int main()
{
int cnt = 0;
// 由于这种情况下函数的调用与传参不是同时的,提供参数在函数调用之前,因此以引用方式传递参数时需要用 std::ref
std::thread thr(Func, 2021, std::ref(cnt));

for (int i = 0; i < 50; ++i)
{
std::cout << "In main: " << 110 << std::endl;
++cnt;
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}

thr.join(); // 等待子线程结束,在 thr 析构前若未 detach 则必须调用此函数,等待过程中主线程 main 被阻塞
std::cout << "Count: " << cnt << std::endl;
return 0;
}
```



或者使用 lambda 表达式达到同样效果:

```c++
#include <iostream>
#include <thread>
#include <functional>

int main()
{
int cnt = 0, x = 2021;
std::thread thr
(
[x, &cnt]()
{
for (int i = 0; i < 110; ++i)
{
std::cout << "In Func: " << x << std::endl;
++cnt;
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
);

for (int i = 0; i < 50; ++i)
{
std::cout << "In main: " << 110 << std::endl;
++cnt;
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}

thr.join();
std::cout << "Count: " << cnt << std::endl;
return 0;
}
```



如果不希望等待子线程结束,`main` 结束则程序结束,则可以构造临时对象调用 `detach` 函数:

```c++
#include <iostream>
#include <thread>
#include <functional>

int main()
{
int cnt = 0, x = 2021;
std::thread
(
[x, &cnt]()
{
for (int i = 0; i < 110; ++i)
{
std::cout << "In Func: " << x << std::endl;
++cnt;
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
).detach();

for (int i = 0; i < 50; ++i)
{
std::cout << "In main: " << 110 << std::endl;
++cnt;
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}

std::cout << "Count: " << cnt << std::endl;
return 0;
}
```



更多内容请参看(点击进入):[cppreference_thread](https://en.cppreference.com/w/cpp/thread/thread)



### 智能指针



#### 总述



头文件:`include <memory>`

智能指针是 C++ 标准库中对指针的封装,它的好处是可以不需要 `delete`,而自动对其指向的资源进行释放,这在一定程度上降低了 C++ 程序员管理内存的难度,但同时智能指针的使用也具有一定的技巧。

智能指针主要有三种:`shared_ptr`、`weak_ptr`、`unique_ptr`。



#### `std::shared_ptr`

##### 概览

`shared_ptr` 可以说是最常用的智能指针了。它的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的智能指针数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。

像 `new` 会在自由存储区动态获取一块内存并返回其一样,如果要动态分配一块内存并得到其智能指针,可以使用 `std::make_shared` 模板,例如:

```c++
#include <memory>

void Func()
{
int* p = new int(110); // 在自由存储区 new 一个 int 对象,初值为 110
auto sp = std::make_shared<int>(110); // 在自由存储区 new 一个 int 对象,初值为 110
// sp 被自动推导为 std::shared_ptr<int> 类型
delete p; // 释放内存
// 编译器调用 sp 的析构函数,并将其指向的 int 释放掉
}
```

关于引用计数:

```cpp
#include <memory>

void Func()
{
int x = 110;
{
auto sp1 = std::make_shared<int>(x); // 得到一个 int,初值为 110。

// 上述此语句执行过后,只有一个智能指针 sp1 指向这个 int,引用计数为 1

{
auto sp2 = sp1; // 构造一个智能指针 sp2,指向 sp1 指向的内存,并将引用计数+1

// 故此处引用计数为2

std::cout << *sp2 << std::endl; // 输出 110

// 此处 sp2 生存期已到,调用 sp2 的析构函数,使引用计数-1,因此此时引用计数为1
}

// 此处 sp1 生命期也已经到了,调用 sp1 析构函数,引用计数再-1,故引用计数降为0
// 也就是不再有智能指针指向它了,调用 delete 释放内存
}
}
```

将普通指针交给智能指针托管:

```cpp
int* p = new int(110);
int* q = new int(110);
std::shared_ptr sp(p); // 把 p 指向的内存交给 sp 托管,此后 p 便不需要 delete,sp 析构时会自动释放
std::shared_ptr sq; // sq 什么也不托管
sq.reset(q); // 让 sq 托管 q

//此后 p 与 q 便不需要再 delete
```

需要注意的是,这种写法是非常危险的,既可能导致 `p` 与 `q` 变为野指针,也可能造成重复 `delete`,我们应该更多使用 make_shared。

##### 自定义释放函数

之前说过 ,默认情况下是释放内存的函数是 `delete` 运算符,但有时我们并不希望这样。比如下面的几个情况:

+ 使用智能指针托管动态数组

```cpp
#include <memory>
void IntArrayDeleter(int* p) { delete[] p; }
int main()
{
std::shared_ptr<int> sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数
// sp 析构时自动调用 IntArrayDeleter 释放该 int 数组
return 0;
}
// 或者利用 lambda 表达式:std::shared_ptr<int> sp(new int[10], [](int* p) { delete[] p; });
```


+ 释放系统资源

在编程过程中,难免与操作系统打交道,这时我们可能需要获取一系列的系统资源,并还给操作系统(实际上 `new` 和 `delete` 也就是一个例子)。一个比较有特色的例子就是 Windows API。在传统的 Win32 程序中,如果我们要在屏幕上进行绘制图形,我们首先需要获取设备的上下文信息,才能在设备上进行绘图。设想这样一个情景:我们有一个窗口,已经获得了指向这个窗口的句柄(即指针)`hWnd`,我们要在窗口上绘图,就要通过这个窗口句柄获取设备上下文信息。代码如下:

```c++
HDC hdc; // DC: Device context,一个指向 DC 的句柄(HANDLE)
hdc = GetDC(hWnd); // 获取设备上下文
/*执行绘图操作*/
ReleaseDC(hWnd, hdc); // 绘图完毕,将设备上下文资源释放,归还给 Windows 系统
```

使用智能指针对其进行托管,代码如下:

```c++
// 使用 lambda 表达式写法(推荐)
std::shared_ptr<void> sp(GetDC(hWnd), [hWnd](void* hdc) { ReleaseDC(hWnd, (HDC)hdc); });
```

```cpp
// 不使用 lambda 表达式的写法:
struct Releaser
{
HWND hWnd;
Releaser(HWND hWnd) : hWnd(hWnd) {}
void operator()(void* hdc)
{
ReleaseDC(hWnd, (HDC)hdc);
}
};
void PaintFunc()
{
/*...*/
std::shared_ptr<void> sp(GetDC(hWnd), Releaser(hWnd));
/*...*/
}
```

##### 常见的错误用法

`std::shared_ptr` 虽然方便,但是也有一些错误用法,这个是常见的:

```c++
#include <memory>

void Func()
{
int* p = new int(110);
std::shared_ptr<int> sp(p); // 让 sp 托管 p
std::shared_ptr<int> sq(p); // 让 sq 托管 p
// Runtime Error! 程序至此崩溃
}
```

这是因为,只有复制构造函数里面才有使引用计数加1的操作。即当我们写 `std::shared_ptr<int> sq = sp` 的时候,确实引用计数变成了2,但是我们都用一个外部的裸指针 `p` 去初始化 `sp` 和 `sq`,智能指针并不能感知到它们托管的内存相同。所以 `sp` 和 `sq` 所托管的内存被看做是独立的。这样,当它们析构的时候,均会释放它们所指的内存,因此同一块内存被释放了两次,导致程序出错。所以个人还是推荐使用 `make_shared` ,而不是用裸指针去获取内存。

另一个著名的错误用法,请继续阅读 `std::weak_ptr`。



#### `std::weak_ptr`

看完了上面的 `shared_ptr` 的讲述,相信你已经对使用智能指针胸有成竹了。一切都用 `shared_ptr`、`make_shared` 就万事大吉了嘛!但事情可能没那么简单。看下面的例子:

```c++
#include <iostream>
#include <memory>

class B;

class A
{
public:
void SetB(const std::shared_ptr<B>& ipB)
{
pB = ipB;
}

private:
std::shared_ptr<B> pB;
};

class B
{
public:
void SetA(const std::shared_ptr<A>& ipA)
{
pA = ipA;
}

private:
std::shared_ptr<A> pA;
};

void Func()
{
auto pA = std::make_shared<A>();
auto pB = std::make_shared<B>();
pA->SetB(pB);
pB->SetA(pA);
// 内存泄露!!!
}

/*...*/
```



太糟糕了!上面的 `pA` 指向的的对象和 `pB` 指向的对象一直到程序结束之前永远不会被释放!如果不相信,可以在它们的析构函数里输出些什么试一试。相信学习了引用计数的你,一定能想出来原因。我们就把它当作一道思考题作为练习:为什么这两个对象不会被释放呢?(提示:注意只有引用计数降为0的时候才会释放)



实际上,`std::shared_ptr` 并不是乱用的。它除了作为一个指针之外,还表明了一种逻辑上的归属关系。从逻辑上看,类的成员代表一种归属权的关系,类的成员属于这个类。拥有 `shared_ptr` 作为**成员**的对象,是对 `shared_ptr` 所指向的对象具有所有权的,`shared_ptr` 也是基于这个理念设计的。但是,有时候我们并不希望这是个所有权的关系,例如我们有双亲和孩子的指针作为“人”的成员,但是人与人之间是平等相待和谐共处的,我们不能说一个人是另一个人的附属品。这时候,`std::weak_ptr` 便应运而生了!

`std::weak_ptr` 与 `shared_ptr` 的区别是,它指向一个资源,并不会增加引用计数。当指向一个资源的 `shared_ptr` 的数量为 0 的时候,即使还有 `weak_ptr` 在指,资源也会被释放掉。也是因此,`weak_ptr`也是存在悬垂指针的可能的,即它指向的资源已经被释放掉。 也是因此,`weak_ptr` 不允许直接地被解引用,必须先转换为相应的 `shared_ptr` 才能解引用,获取其所指的资源。它的用法如下:

```cpp
auto sp = std::make_shared<int>(5);
std::weak_ptr<int> wp = sp; // 正确,让 wp 指向 sp 指向的资源
// std::shared_ptr<int> sp1 = wp; // 错误,weak_ptr 不能直接赋值给 shared_ptr

/* Do something */

if (wp.expired())
{
std::cout << "The resource has been released!" << std::endl;
}
else
{
// std::cout << *wp << std::endl; // Compile error! weak_ptr 不能直接使用!
auto sp1 = wp.lock(); // 从 weak_ptr 中恢复出 shared_ptr,sp1 的类型为 std::shared_ptr<int>
std::cout << *sp1 << std::endl;
}
```

从类的设计本身来看,`weak_ptr` 不会增加引用计数;从逻辑上看,`weak_ptr` 描述了一种联系,即 `weak_ptr` 的拥有者与其指向的对象之间不是一种归属关系,而是一种较弱的联系。一个类的对象只需知道另一个类的对象是谁,而不对其拥有占有权,这时候用 `weak_ptr` 是合适的。

上面的 `A` 类和 `B` 类的问题,将 `A` 和 `B` 成员从 `shared_ptr` 换成 `weak_ptr` 就会解决内存泄露的问题了!



#### `std::unique_ptr`

`std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有。它部分涉及到 `xvalue` 、右值引用与移动语义的问题,在此不做过多展开。



更多关于智能指针的知识,可以参考(点击进入):

+ [cppreference_shared_ptr](https://zh.cppreference.com/w/cpp/memory/shared_ptr)
+ [cppreference_weak_ptr](https://zh.cppreference.com/w/cpp/memory/weak_ptr)

+ [cppreference_unique_ptr](https://zh.cppreference.com/w/cpp/memory/unique_ptr)

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

@@ -35,6 +35,7 @@ public:
[[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const = 0; [[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const = 0;
[[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Student>> GetStudents() const = 0; [[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Student>> GetStudents() const = 0;
[[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const = 0; [[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const = 0;
[[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const = 0;
[[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> StudentGetSelfInfo() const = 0; [[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> StudentGetSelfInfo() const = 0;
[[nodiscard]] virtual std::shared_ptr<const THUAI6::Tricker> TrickerGetSelfInfo() const = 0; [[nodiscard]] virtual std::shared_ptr<const THUAI6::Tricker> TrickerGetSelfInfo() const = 0;


@@ -54,6 +55,7 @@ public:
virtual bool Move(int64_t time, double angle) = 0; virtual bool Move(int64_t time, double angle) = 0;
virtual bool PickProp(THUAI6::PropType prop) = 0; virtual bool PickProp(THUAI6::PropType prop) = 0;
virtual bool UseProp(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) = 0;
virtual bool SendMessage(int64_t toID, std::string message) = 0; virtual bool SendMessage(int64_t toID, std::string message) = 0;
virtual bool HaveMessage() = 0; virtual bool HaveMessage() = 0;
@@ -67,8 +69,8 @@ public:
virtual bool Graduate() = 0; virtual bool Graduate() = 0;


virtual bool StartLearning() = 0; virtual bool StartLearning() = 0;
virtual bool StartTreatMate(int64_t mateID) = 0;
virtual bool StartRescueMate(int64_t mateID) = 0;
virtual bool StartEncourageMate(int64_t mateID) = 0;
virtual bool StartRouseMate(int64_t mateID) = 0;


virtual bool OpenDoor() = 0; virtual bool OpenDoor() = 0;
virtual bool CloseDoor() = 0; virtual bool CloseDoor() = 0;
@@ -99,6 +101,7 @@ public:
// 捡道具、使用技能 // 捡道具、使用技能
virtual std::future<bool> PickProp(THUAI6::PropType prop) = 0; virtual std::future<bool> PickProp(THUAI6::PropType prop) = 0;
virtual std::future<bool> UseProp(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) = 0;
virtual std::future<bool> Attack(double angleInRadian) = 0; virtual std::future<bool> Attack(double angleInRadian) = 0;


@@ -124,7 +127,9 @@ public:
// 获取视野内可见的道具信息 // 获取视野内可见的道具信息
[[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const = 0; [[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const = 0;


// 获取地图信息,视野外的地图统一为Land
// 获取视野内可见的子弹信息
[[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const = 0;

[[nodiscard]] virtual std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const = 0; [[nodiscard]] virtual std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const = 0;
[[nodiscard]] virtual THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const = 0; [[nodiscard]] virtual THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const = 0;


@@ -172,8 +177,8 @@ public:
/*****学生阵营的特定函数*****/ /*****学生阵营的特定函数*****/


virtual std::future<bool> StartLearning() = 0; virtual std::future<bool> StartLearning() = 0;
virtual std::future<bool> StartTreatMate(int64_t mateID) = 0;
virtual std::future<bool> StartRescueMate(int64_t mateID) = 0;
virtual std::future<bool> StartEncourageMate(int64_t mateID) = 0;
virtual std::future<bool> StartRouseMate(int64_t mateID) = 0;
virtual std::future<bool> Graduate() = 0; virtual std::future<bool> Graduate() = 0;
[[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> GetSelfInfo() const = 0; [[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> GetSelfInfo() const = 0;
}; };
@@ -221,6 +226,7 @@ public:


std::future<bool> PickProp(THUAI6::PropType prop) override; std::future<bool> PickProp(THUAI6::PropType prop) override;
std::future<bool> UseProp(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) override;


std::future<bool> Attack(double angleInRadian) override; std::future<bool> Attack(double angleInRadian) override;
@@ -243,6 +249,8 @@ public:


[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const override; [[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const override;


[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const override;

[[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const override; [[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const override;
[[nodiscard]] THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const override; [[nodiscard]] THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const override;


@@ -258,8 +266,8 @@ public:
[[nodiscard]] std::vector<int64_t> GetPlayerGUIDs() const override; [[nodiscard]] std::vector<int64_t> GetPlayerGUIDs() const override;


std::future<bool> StartLearning() override; std::future<bool> StartLearning() override;
std::future<bool> StartTreatMate(int64_t mateID) override;
std::future<bool> StartRescueMate(int64_t mateID) override;
std::future<bool> StartEncourageMate(int64_t mateID) override;
std::future<bool> StartRouseMate(int64_t mateID) override;
std::future<bool> Graduate() override; std::future<bool> Graduate() override;
[[nodiscard]] std::shared_ptr<const THUAI6::Student> GetSelfInfo() const override; [[nodiscard]] std::shared_ptr<const THUAI6::Student> GetSelfInfo() const override;


@@ -308,6 +316,7 @@ public:


std::future<bool> PickProp(THUAI6::PropType prop) override; std::future<bool> PickProp(THUAI6::PropType prop) override;
std::future<bool> UseProp(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) override;


std::future<bool> OpenDoor() override; std::future<bool> OpenDoor() override;
@@ -328,6 +337,8 @@ public:


[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const override; [[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const override;


[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const override;

[[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const override; [[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const override;
[[nodiscard]] THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const override; [[nodiscard]] THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const override;


@@ -383,6 +394,7 @@ public:


std::future<bool> PickProp(THUAI6::PropType prop) override; std::future<bool> PickProp(THUAI6::PropType prop) override;
std::future<bool> UseProp(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) override;


std::future<bool> Attack(double angleInRadian) override; std::future<bool> Attack(double angleInRadian) override;
@@ -405,6 +417,8 @@ public:


[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const override; [[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const override;


[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const override;

[[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const override; [[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const override;
[[nodiscard]] THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const override; [[nodiscard]] THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const override;


@@ -420,8 +434,8 @@ public:
[[nodiscard]] std::vector<int64_t> GetPlayerGUIDs() const override; [[nodiscard]] std::vector<int64_t> GetPlayerGUIDs() const override;


std::future<bool> StartLearning() override; std::future<bool> StartLearning() override;
std::future<bool> StartTreatMate(int64_t mateID) override;
std::future<bool> StartRescueMate(int64_t mateID) override;
std::future<bool> StartEncourageMate(int64_t mateID) override;
std::future<bool> StartRouseMate(int64_t mateID) override;
std::future<bool> Graduate() override; std::future<bool> Graduate() override;
[[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> GetSelfInfo() const override; [[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> GetSelfInfo() const override;


@@ -455,6 +469,7 @@ public:


std::future<bool> PickProp(THUAI6::PropType prop) override; std::future<bool> PickProp(THUAI6::PropType prop) override;
std::future<bool> UseProp(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) override;


std::future<bool> OpenDoor() override; std::future<bool> OpenDoor() override;
@@ -475,6 +490,8 @@ public:


[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const override; [[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const override;


[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const override;

[[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const override; [[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const override;
[[nodiscard]] THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const override; [[nodiscard]] THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const override;




+ 3
- 2
CAPI/cpp/API/include/Communication.h View File

@@ -26,6 +26,7 @@ public:
bool Move(int64_t time, double angle, int64_t playerID); bool Move(int64_t time, double angle, int64_t playerID);
bool PickProp(THUAI6::PropType prop, int64_t playerID); bool PickProp(THUAI6::PropType prop, int64_t playerID);
bool UseProp(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, int64_t playerID);
bool SendMessage(int64_t toID, std::string message, int64_t playerID); bool SendMessage(int64_t toID, std::string message, int64_t playerID);
bool OpenDoor(int64_t playerID); bool OpenDoor(int64_t playerID);
@@ -38,8 +39,8 @@ public:
bool Graduate(int64_t playerID); bool Graduate(int64_t playerID);


bool StartLearning(int64_t playerID); bool StartLearning(int64_t playerID);
bool StartTreatMate(int64_t playerID, int64_t mateID);
bool StartRescueMate(int64_t playerID, int64_t mateID);
bool StartEncourageMate(int64_t playerID, int64_t mateID);
bool StartRouseMate(int64_t playerID, int64_t mateID);


bool Attack(double angle, int64_t playerID); bool Attack(double angle, int64_t playerID);




+ 286
- 3
CAPI/cpp/API/include/constants.h View File

@@ -2,17 +2,300 @@
#ifndef CONSTANTS_H #ifndef CONSTANTS_H
#define CONSTANTS_H #define CONSTANTS_H


#ifndef SCCI
#define SCCI static const constexpr inline
#endif

namespace Constants namespace Constants
{ {
static const constexpr inline numOfGridPerCell = 1000;
// 地图相关
SCCI int numOfGridPerCell = 1000; // 单位坐标数
SCCI int rows = 50; // 地图行数
SCCI int cols = 50; // 地图列数

SCCI int numOfClassroom = 10; // 教室数量
SCCI int numOfChest = 8; // 宝箱数量

SCCI int maxClassroomProgress = 10000000; // 教室最大进度
SCCI int maxDoorProgress = 10000000; // 开关门最大进度
SCCI int maxChestProgress = 10000000; // 宝箱最大进度
SCCI int maxGateProgress = 18000; // 大门最大进度

SCCI int numOfRequiredClassroomForGate = 7; // 打开大门需要完成的教室数量
SCCI int numOfRequiredClassroomForHiddenGate = 3; // 打开隐藏门需要完成的教室数量

// 人物属性相关
SCCI int basicEncourageSpeed = 100;
SCCI int basicFixSpeed = 123;
SCCI int basicSpeedOfOpeningOrLocking = 4000;
SCCI int basicStudentSpeedOfClimbingThroughWindows = 611;
SCCI int basicTrickerSpeedOfClimbingThroughWindows = 1270;
SCCI int basicSpeedOfOpenChest = 1000;

SCCI int basicHp = 3000000;
SCCI int basicMaxGamingAddiction = 60000;
SCCI int basicEncouragementDegree = 1500000;
SCCI int basicTimeOfRouse = 1000;

SCCI int basicStudentSpeed = 1270;
SCCI int basicTrickerSpeed = 1504;

SCCI double basicConcealment = 1;
SCCI int basicStudentAlertnessRadius = 15 * numOfGridPerCell;
SCCI int basicTrickerAlertnessRadius = 17 * numOfGridPerCell;
SCCI int basicStudentViewRange = 10 * numOfGridPerCell;
SCCI int basicTrickerViewRange = 15 * numOfGridPerCell;
SCCI int PinningDownRange = 5 * numOfGridPerCell;

SCCI int maxNumOfProp = 3; // 人物道具栏容量

// 攻击相关

SCCI int basicApOfTricker = 1500000;
SCCI int basicCD = 3000; // 初始子弹冷却
SCCI int basicCastTime = 500; // 基本前摇时间
SCCI int basicBackswing = 800; // 基本后摇时间
SCCI int basicRecoveryFromHit = 3700; // 基本命中攻击恢复时长
SCCI int basicStunnedTimeOfStudent = 4300;

SCCI int basicBulletMoveSpeed = 3700; // 基本子弹移动速度
SCCI double basicRemoteAttackRange = 3000; // 基本远程攻击范围
SCCI double basicAttackShortRange = 1100; // 基本近程攻击范围
SCCI double basicBulletBombRange = 1000; // 基本子弹爆炸范围

// 道具相关

SCCI int apPropAdd = basicApOfTricker * 12 / 10;
SCCI int apSpearAdd = basicApOfTricker * 6 / 10;

// 职业相关
struct Assassin
{
SCCI int moveSpeed = basicTrickerSpeed * 11 / 10;
SCCI double concealment = 1.5;
SCCI int alertnessRadius = basicTrickerAlertnessRadius * 13 / 10;
SCCI int viewRange = basicTrickerViewRange * 12 / 10;
SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking;
SCCI int speedOfClimbingThroughWindows = basicTrickerSpeedOfClimbingThroughWindows;
SCCI int speedOfOpenChest = basicSpeedOfOpenChest;
};

struct Klee
{
SCCI int moveSpeed = basicTrickerSpeed;
SCCI double concealment = 1;
SCCI int alertnessRadius = basicTrickerAlertnessRadius;
SCCI int viewRange = basicTrickerViewRange;
SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking;
SCCI int speedOfClimbingThroughWindows = basicTrickerSpeedOfClimbingThroughWindows;
SCCI int speedOfOpenChest = basicSpeedOfOpenChest * 11 / 10;
};

struct ANoisyPerson
{
SCCI int moveSpeed = (int)(basicTrickerSpeed * 1.07);
SCCI double concealment = 0.8;
SCCI int alertnessRadius = basicTrickerAlertnessRadius * 9 / 10;
SCCI int viewRange = basicTrickerViewRange;
SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking;
SCCI int speedOfClimbingThroughWindows = basicTrickerSpeedOfClimbingThroughWindows * 11 / 10;
SCCI int speedOfOpenChest = basicSpeedOfOpenChest * 11 / 10;
};

struct Idol
{
SCCI int moveSpeed = basicTrickerSpeed;
SCCI double concealment = 0.75;
SCCI int alertnessRadius = basicTrickerAlertnessRadius;
SCCI int viewRange = basicTrickerViewRange * 11 / 10;
SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking;
SCCI int speedOfClimbingThroughWindows = basicTrickerSpeedOfClimbingThroughWindows;
SCCI int speedOfOpenChest = basicSpeedOfOpenChest;
};

struct Athlete
{
SCCI int moveSpeed = basicStudentSpeed * 105 / 100;
SCCI int maxHp = basicHp;
SCCI int maxAddiction = basicMaxGamingAddiction * 9 / 10;
SCCI int fixSpeed = basicFixSpeed * 6 / 10;
SCCI int encourageSpeed = basicEncourageSpeed * 9 / 10;
SCCI double concealment = 0.9;
SCCI int alertnessRadius = basicStudentAlertnessRadius;
SCCI int viewRange = basicStudentViewRange * 11 / 10;
SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking;
SCCI int speedOfClimbingThroughWindows = basicStudentSpeedOfClimbingThroughWindows * 12 / 10;
SCCI int speedOfOpenChest = basicSpeedOfOpenChest;
};

struct Teacher
{
SCCI int moveSpeed = basicStudentSpeed * 9 / 10;
SCCI int maxHp = basicHp * 10;
SCCI int maxAddiction = basicMaxGamingAddiction * 10;
SCCI int fixSpeed = basicFixSpeed * 0;
SCCI int encourageSpeed = basicEncourageSpeed * 8 / 10;
SCCI double concealment = 0.5;
SCCI int alertnessRadius = basicStudentAlertnessRadius / 2;
SCCI int viewRange = basicStudentViewRange * 9 / 10;
SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking;
SCCI int speedOfClimbingThroughWindows = basicStudentSpeedOfClimbingThroughWindows / 2;
SCCI int speedOfOpenChest = basicSpeedOfOpenChest;
};

struct StraightAStudent
{
SCCI int moveSpeed = basicStudentSpeed * 96 / 100;
SCCI int maxHp = basicHp * 11 / 10;
SCCI int maxAddiction = basicMaxGamingAddiction * 13 / 10;
SCCI int fixSpeed = basicFixSpeed * 11 / 10;
SCCI int encourageSpeed = basicEncourageSpeed;
SCCI double concealment = 0.9;
SCCI int alertnessRadius = basicStudentAlertnessRadius * 9 / 10;
SCCI int viewRange = basicStudentViewRange * 9 / 10;
SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking;
SCCI int speedOfClimbingThroughWindows = basicStudentSpeedOfClimbingThroughWindows * 10 / 12;
SCCI int speedOfOpenChest = basicSpeedOfOpenChest;
};

struct Robot
{
SCCI int moveSpeed = basicStudentSpeed;
SCCI int maxHp = basicHp * 0.4;
SCCI int maxAddiction = basicMaxGamingAddiction * 0;
SCCI int fixSpeed = basicFixSpeed;
SCCI int encourageSpeed = 0;
SCCI double concealment = 1;
SCCI int alertnessRadius = basicStudentAlertnessRadius * 1;
SCCI int viewRange = basicStudentViewRange;
SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking;
SCCI int speedOfClimbingThroughWindows = 1;
SCCI int speedOfOpenChest = basicSpeedOfOpenChest;
};

struct TechOtaku
{
SCCI int moveSpeed = basicStudentSpeed * 3 / 4;
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 alertnessRadius = basicStudentAlertnessRadius;
SCCI int viewRange = basicStudentViewRange * 9 / 10;
SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking;
SCCI int speedOfClimbingThroughWindows = basicStudentSpeedOfClimbingThroughWindows * 3 / 4;
SCCI int speedOfOpenChest = basicSpeedOfOpenChest;
};

struct Sunshine
{
SCCI int moveSpeed = basicStudentSpeed;
SCCI int maxHp = basicHp * 32 / 30;
SCCI int maxAddiction = basicMaxGamingAddiction * 11 / 10;
SCCI int fixSpeed = basicFixSpeed;
SCCI int encourageSpeed = basicEncourageSpeed * 12 / 10;
SCCI double concealment = 1;
SCCI int alertnessRadius = basicStudentAlertnessRadius;
SCCI int viewRange = basicStudentViewRange;
SCCI int speedOfOpeningOrLocking = basicSpeedOfOpeningOrLocking * 7 / 10;
SCCI int speedOfClimbingThroughWindows = basicStudentSpeedOfClimbingThroughWindows;
SCCI int speedOfOpenChest = basicSpeedOfOpenChest * 9 / 10;
};

// 技能相关
SCCI int maxNumOfSkill = 3;
SCCI int commonSkillCD = 30000; // 普通技能标准冷却时间
SCCI int commonSkillTime = 10000; // 普通技能标准持续时间

SCCI int timeOfTrickerStunnedWhenCharge = 7220;
SCCI int timeOfStudentStunnedWhenCharge = 2090;

SCCI int timeOfTrickerStunnedWhenPunish = 3070;

SCCI int timeOfTrickerSwingingAfterHowl = 800;
SCCI int timeOfStudentStunnedWhenHowl = 5500;

SCCI int timeOfStunnedWhenJumpyDumpty = 3070;

SCCI double addedTimeOfSpeedWhenInspire = 0.6;
SCCI int timeOfAddingSpeedWhenInspire = 6000;
struct CanBeginToCharge
{
SCCI int skillCD = commonSkillCD * 2;
SCCI int durationTime = commonSkillTime * 3 / 10;
};

struct BecomeInvisible
{
SCCI int skillCD = commonSkillCD * 4 / 3;
SCCI int durationTime = commonSkillTime;
};

struct Punish
{
SCCI int skillCD = commonSkillCD * 1;
SCCI int durationTime = commonSkillTime * 0;
};

struct Rouse
{
SCCI int skillCD = commonSkillCD * 4;
SCCI int durationTime = commonSkillTime * 0;
};


struct StudentConstants
struct Encourage
{ {
SCCI int skillCD = commonSkillCD * 4;
SCCI int durationTime = commonSkillTime * 0;
}; };


struct TrickerConstants
struct Inspire
{ {
SCCI int skillCD = commonSkillCD * 4;
SCCI int durationTime = commonSkillTime * 0;
}; };


struct Howl
{
SCCI int skillCD = commonSkillCD * 25 / 30;
SCCI int durationTime = commonSkillTime * 0;
};

struct ShowTime
{
SCCI int skillCD = commonSkillCD * 3;
SCCI int durationTime = commonSkillTime * 1;
};

struct JumpyBomb
{
SCCI int skillCD = commonSkillCD / 2;
SCCI int durationTime = commonSkillTime * 3 / 10;
};

struct UseKnife
{
SCCI int skillCD = commonSkillCD * 1;
SCCI int durationTime = commonSkillTime / 10;
};

struct UseRobot
{
SCCI int skillCD = commonSkillCD / 300;
SCCI int durationTime = commonSkillTime * 0;
};

struct WriteAnswers
{
SCCI int skillCD = commonSkillCD * 1;
SCCI int durationTime = commonSkillTime * 0;
};

struct SummonGolem
{
SCCI int skillCD = commonSkillCD * 1;
SCCI int durationTime = commonSkillTime * 0;
};
} // namespace Constants } // namespace Constants
#endif #endif

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

@@ -94,6 +94,7 @@ private:
[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const override; [[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const override;
[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Student>> GetStudents() const override; [[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Student>> GetStudents() const override;
[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const override; [[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const override;
[[nodiscard]] std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const override;
[[nodiscard]] std::shared_ptr<const THUAI6::Student> StudentGetSelfInfo() const override; [[nodiscard]] std::shared_ptr<const THUAI6::Student> StudentGetSelfInfo() const override;
[[nodiscard]] std::shared_ptr<const THUAI6::Tricker> TrickerGetSelfInfo() const override; [[nodiscard]] std::shared_ptr<const THUAI6::Tricker> TrickerGetSelfInfo() const override;
[[nodiscard]] THUAI6::HiddenGateState GetHiddenGateState(int32_t cellX, int32_t cellY) const override; [[nodiscard]] THUAI6::HiddenGateState GetHiddenGateState(int32_t cellX, int32_t cellY) const override;
@@ -113,6 +114,7 @@ private:
bool Move(int64_t time, double angle) override; bool Move(int64_t time, double angle) override;
bool PickProp(THUAI6::PropType prop) override; bool PickProp(THUAI6::PropType prop) override;
bool UseProp(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) override;


bool SendMessage(int64_t toID, std::string message) override; bool SendMessage(int64_t toID, std::string message) override;
@@ -123,8 +125,8 @@ private:


bool StartLearning() override; bool StartLearning() override;


bool StartTreatMate(int64_t mateID) override;
bool StartRescueMate(int64_t mateID) override;
bool StartEncourageMate(int64_t mateID) override;
bool StartRouseMate(int64_t mateID) override;


bool Attack(double angle) override; bool Attack(double angle) override;




+ 13
- 12
CAPI/cpp/API/include/structures.h View File

@@ -94,7 +94,7 @@ namespace THUAI6
Assassin = 1, Assassin = 1,
Klee = 2, Klee = 2,
ANoisyPerson = 3, ANoisyPerson = 3,
TrickerType4 = 4,
Idol = 4,
}; };


// 学生Buff类型 // 学生Buff类型
@@ -126,11 +126,11 @@ namespace THUAI6
Addicted = 3, Addicted = 3,
Quit = 4, Quit = 4,
Graduated = 5, Graduated = 5,
Treated = 6,
Rescued = 7,
Encouraged = 6,
Roused = 7,
Stunned = 8, Stunned = 8,
Treating = 9,
Rescuing = 10,
Encouraging = 9,
Rousing = 10,
Swinging = 11, Swinging = 11,
Attacking = 12, Attacking = 12,
Locking = 13, Locking = 13,
@@ -195,9 +195,9 @@ namespace THUAI6
int32_t determination; // 剩余毅力 int32_t determination; // 剩余毅力
int32_t addiction; // 沉迷程度 int32_t addiction; // 沉迷程度
int32_t learningSpeed; int32_t learningSpeed;
int32_t treatSpeed;
int32_t treatProgress;
int32_t rescueProgress;
int32_t encourageSpeed;
int32_t encourageProgress;
int32_t rouseProgress;
double dangerAlert; double dangerAlert;
std::vector<StudentBuffType> buff; // buff std::vector<StudentBuffType> buff; // buff
}; };
@@ -292,6 +292,7 @@ namespace THUAI6
{TrickerType::Assassin, "Assassin"}, {TrickerType::Assassin, "Assassin"},
{TrickerType::Klee, "Klee"}, {TrickerType::Klee, "Klee"},
{TrickerType::ANoisyPerson, "ANoisyPerson"}, {TrickerType::ANoisyPerson, "ANoisyPerson"},
{TrickerType::Idol, "Idol"},
}; };


inline std::map<PlayerState, std::string> playerStateDict{ inline std::map<PlayerState, std::string> playerStateDict{
@@ -301,11 +302,11 @@ namespace THUAI6
{PlayerState::Addicted, "Addicted"}, {PlayerState::Addicted, "Addicted"},
{PlayerState::Quit, "Quit"}, {PlayerState::Quit, "Quit"},
{PlayerState::Graduated, "Graduated"}, {PlayerState::Graduated, "Graduated"},
{PlayerState::Treated, "Treated"},
{PlayerState::Rescued, "Rescued"},
{PlayerState::Encouraged, "Encouraged"},
{PlayerState::Roused, "Roused"},
{PlayerState::Stunned, "Stunned"}, {PlayerState::Stunned, "Stunned"},
{PlayerState::Treating, "Treating"},
{PlayerState::Rescuing, "Rescuing"},
{PlayerState::Encouraging, "Encouraging"},
{PlayerState::Rousing, "Rousing"},
{PlayerState::Swinging, "Swinging"}, {PlayerState::Swinging, "Swinging"},
{PlayerState::Attacking, "Attacking"}, {PlayerState::Attacking, "Attacking"},
{PlayerState::Locking, "Locking"}, {PlayerState::Locking, "Locking"},


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

@@ -119,7 +119,7 @@ namespace Proto2THUAI6
{protobuf::TrickerType::ASSASSIN, THUAI6::TrickerType::Assassin}, {protobuf::TrickerType::ASSASSIN, THUAI6::TrickerType::Assassin},
{protobuf::TrickerType::KLEE, THUAI6::TrickerType::Klee}, {protobuf::TrickerType::KLEE, THUAI6::TrickerType::Klee},
{protobuf::TrickerType::A_NOISY_PERSON, THUAI6::TrickerType::ANoisyPerson}, {protobuf::TrickerType::A_NOISY_PERSON, THUAI6::TrickerType::ANoisyPerson},
{protobuf::TrickerType::TRICKERTYPE4, THUAI6::TrickerType::TrickerType4},
{protobuf::TrickerType::IDOL, THUAI6::TrickerType::Idol},
}; };


inline std::map<protobuf::StudentBuffType, THUAI6::StudentBuffType> studentBuffTypeDict{ inline std::map<protobuf::StudentBuffType, THUAI6::StudentBuffType> studentBuffTypeDict{
@@ -146,11 +146,11 @@ namespace Proto2THUAI6
{protobuf::PlayerState::ADDICTED, THUAI6::PlayerState::Addicted}, {protobuf::PlayerState::ADDICTED, THUAI6::PlayerState::Addicted},
{protobuf::PlayerState::QUIT, THUAI6::PlayerState::Quit}, {protobuf::PlayerState::QUIT, THUAI6::PlayerState::Quit},
{protobuf::PlayerState::GRADUATED, THUAI6::PlayerState::Graduated}, {protobuf::PlayerState::GRADUATED, THUAI6::PlayerState::Graduated},
{protobuf::PlayerState::RESCUED, THUAI6::PlayerState::Rescued},
{protobuf::PlayerState::TREATED, THUAI6::PlayerState::Treated},
{protobuf::PlayerState::RESCUED, THUAI6::PlayerState::Roused},
{protobuf::PlayerState::TREATED, THUAI6::PlayerState::Encouraged},
{protobuf::PlayerState::STUNNED, THUAI6::PlayerState::Stunned}, {protobuf::PlayerState::STUNNED, THUAI6::PlayerState::Stunned},
{protobuf::PlayerState::RESCUING, THUAI6::PlayerState::Rescuing},
{protobuf::PlayerState::TREATING, THUAI6::PlayerState::Treating},
{protobuf::PlayerState::RESCUING, THUAI6::PlayerState::Rousing},
{protobuf::PlayerState::TREATING, THUAI6::PlayerState::Encouraging},
{protobuf::PlayerState::SWINGING, THUAI6::PlayerState::Swinging}, {protobuf::PlayerState::SWINGING, THUAI6::PlayerState::Swinging},
{protobuf::PlayerState::ATTACKING, THUAI6::PlayerState::Attacking}, {protobuf::PlayerState::ATTACKING, THUAI6::PlayerState::Attacking},
{protobuf::PlayerState::LOCKING, THUAI6::PlayerState::Locking}, {protobuf::PlayerState::LOCKING, THUAI6::PlayerState::Locking},
@@ -240,9 +240,9 @@ namespace Proto2THUAI6
student->facingDirection = studentMsg.facing_direction(); student->facingDirection = studentMsg.facing_direction();
student->bulletType = bulletTypeDict[studentMsg.bullet_type()]; student->bulletType = bulletTypeDict[studentMsg.bullet_type()];
student->learningSpeed = studentMsg.learning_speed(); student->learningSpeed = studentMsg.learning_speed();
student->treatSpeed = studentMsg.treat_speed();
student->treatProgress = studentMsg.treat_progress();
student->rescueProgress = studentMsg.rescue_progress();
student->encourageSpeed = studentMsg.treat_speed();
student->encourageProgress = studentMsg.treat_progress();
student->rouseProgress = studentMsg.rescue_progress();
student->dangerAlert = studentMsg.danger_alert(); student->dangerAlert = studentMsg.danger_alert();
student->timeUntilSkillAvailable.clear(); student->timeUntilSkillAvailable.clear();
for (int i = 0; i < studentMsg.time_until_skill_available().size(); i++) for (int i = 0; i < studentMsg.time_until_skill_available().size(); i++)
@@ -385,7 +385,7 @@ namespace THUAI62Proto
{THUAI6::TrickerType::Assassin, protobuf::TrickerType::ASSASSIN}, {THUAI6::TrickerType::Assassin, protobuf::TrickerType::ASSASSIN},
{THUAI6::TrickerType::Klee, protobuf::TrickerType::KLEE}, {THUAI6::TrickerType::Klee, protobuf::TrickerType::KLEE},
{THUAI6::TrickerType::ANoisyPerson, protobuf::TrickerType::A_NOISY_PERSON}, {THUAI6::TrickerType::ANoisyPerson, protobuf::TrickerType::A_NOISY_PERSON},
{THUAI6::TrickerType::TrickerType4, protobuf::TrickerType::TRICKERTYPE4},
{THUAI6::TrickerType::Idol, protobuf::TrickerType::IDOL},
}; };


// inline std::map<THUAI6::TrickerBuffType, protobuf::TrickerBuffType> trickerBuffTypeDict{ // inline std::map<THUAI6::TrickerBuffType, protobuf::TrickerBuffType> trickerBuffTypeDict{


+ 1
- 0
CAPI/cpp/API/src/AI.cpp View File

@@ -1,6 +1,7 @@
#include <vector> #include <vector>
#include <thread> #include <thread>
#include "AI.h" #include "AI.h"
#include "constants.h"


// 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新
extern const bool asynchronous = false; extern const bool asynchronous = false;


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

@@ -89,6 +89,18 @@ std::future<bool> TrickerAPI::UseProp(THUAI6::PropType prop)
{ return logic.UseProp(prop); }); { return logic.UseProp(prop); });
} }


std::future<bool> StudentAPI::ThrowProp(THUAI6::PropType prop)
{
return std::async(std::launch::async, [=]()
{ return logic.ThrowProp(prop); });
}

std::future<bool> TrickerAPI::ThrowProp(THUAI6::PropType prop)
{
return std::async(std::launch::async, [=]()
{ return logic.ThrowProp(prop); });
}

std::future<bool> StudentAPI::UseSkill(int32_t skillID) std::future<bool> StudentAPI::UseSkill(int32_t skillID)
{ {
return std::async(std::launch::async, [=]() return std::async(std::launch::async, [=]()
@@ -255,6 +267,16 @@ std::vector<std::shared_ptr<const THUAI6::Prop>> TrickerAPI::GetProps() const
return logic.GetProps(); return logic.GetProps();
} }


std::vector<std::shared_ptr<const THUAI6::Bullet>> StudentAPI::GetBullets() const
{
return logic.GetBullets();
}

std::vector<std::shared_ptr<const THUAI6::Bullet>> TrickerAPI::GetBullets() const
{
return logic.GetBullets();
}

std::vector<std::vector<THUAI6::PlaceType>> StudentAPI::GetFullMap() const std::vector<std::vector<THUAI6::PlaceType>> StudentAPI::GetFullMap() const
{ {
return logic.GetFullMap(); return logic.GetFullMap();
@@ -361,16 +383,16 @@ std::future<bool> StudentAPI::StartLearning()
{ return logic.StartLearning(); }); { return logic.StartLearning(); });
} }


std::future<bool> StudentAPI::StartTreatMate(int64_t mateID)
std::future<bool> StudentAPI::StartEncourageMate(int64_t mateID)
{ {
return std::async(std::launch::async, [=]() return std::async(std::launch::async, [=]()
{ return logic.StartTreatMate(mateID); });
{ return logic.StartEncourageMate(mateID); });
} }


std::future<bool> StudentAPI::StartRescueMate(int64_t mateID)
std::future<bool> StudentAPI::StartRouseMate(int64_t mateID)
{ {
return std::async(std::launch::async, [=]() return std::async(std::launch::async, [=]()
{ return logic.StartRescueMate(mateID); });
{ return logic.StartRouseMate(mateID); });
} }


std::future<bool> StudentAPI::Graduate() std::future<bool> StudentAPI::Graduate()


+ 14
- 2
CAPI/cpp/API/src/Communication.cpp View File

@@ -51,6 +51,18 @@ bool Communication::UseProp(THUAI6::PropType prop, int64_t playerID)
return false; return false;
} }


bool Communication::ThrowProp(THUAI6::PropType prop, int64_t playerID)
{
protobuf::BoolRes throwPropResult;
ClientContext context;
auto request = THUAI62Proto::THUAI62ProtobufProp(prop, playerID);
auto status = THUAI6Stub->ThrowProp(&context, request, &throwPropResult);
if (status.ok())
return throwPropResult.act_success();
else
return false;
}

bool Communication::UseSkill(int32_t skillID, int64_t playerID) bool Communication::UseSkill(int32_t skillID, int64_t playerID)
{ {
protobuf::BoolRes useSkillResult; protobuf::BoolRes useSkillResult;
@@ -171,7 +183,7 @@ bool Communication::StartLearning(int64_t playerID)
return false; return false;
} }


bool Communication::StartRescueMate(int64_t playerID, int64_t mateID)
bool Communication::StartRouseMate(int64_t playerID, int64_t mateID)
{ {
protobuf::BoolRes saveStudentResult; protobuf::BoolRes saveStudentResult;
ClientContext context; ClientContext context;
@@ -183,7 +195,7 @@ bool Communication::StartRescueMate(int64_t playerID, int64_t mateID)
return false; return false;
} }


bool Communication::StartTreatMate(int64_t playerID, int64_t mateID)
bool Communication::StartEncourageMate(int64_t playerID, int64_t mateID)
{ {
protobuf::BoolRes healStudentResult; protobuf::BoolRes healStudentResult;
ClientContext context; ClientContext context;


+ 41
- 11
CAPI/cpp/API/src/DebugAPI.cpp View File

@@ -187,6 +187,26 @@ std::future<bool> TrickerDebugAPI::UseProp(THUAI6::PropType prop)
return result; }); return result; });
} }


std::future<bool> StudentDebugAPI::ThrowProp(THUAI6::PropType prop)
{
logger->info("ThrowProp: prop={}, called at {}ms", THUAI6::propTypeDict[prop], Time::TimeSinceStart(startPoint));
return std::async(std::launch::async, [=]()
{ auto result = logic.ThrowProp(prop);
if (!result)
logger->warn("ThrowProp: failed at {}ms", Time::TimeSinceStart(startPoint));
return result; });
}

std::future<bool> TrickerDebugAPI::ThrowProp(THUAI6::PropType prop)
{
logger->info("ThrowProp: prop={}, called at {}ms", THUAI6::propTypeDict[prop], Time::TimeSinceStart(startPoint));
return std::async(std::launch::async, [=]()
{ auto result = logic.ThrowProp(prop);
if (!result)
logger->warn("ThrowProp: failed at {}ms", Time::TimeSinceStart(startPoint));
return result; });
}

std::future<bool> StudentDebugAPI::UseSkill(int32_t skillID) std::future<bool> StudentDebugAPI::UseSkill(int32_t skillID)
{ {
logger->info("UseSkill: skillID={}, called at {}ms", skillID, Time::TimeSinceStart(startPoint)); logger->info("UseSkill: skillID={}, called at {}ms", skillID, Time::TimeSinceStart(startPoint));
@@ -435,6 +455,16 @@ std::vector<std::shared_ptr<const THUAI6::Prop>> TrickerDebugAPI::GetProps() con
return logic.GetProps(); return logic.GetProps();
} }


std::vector<std::shared_ptr<const THUAI6::Bullet>> StudentDebugAPI::GetBullets() const
{
return logic.GetBullets();
}

std::vector<std::shared_ptr<const THUAI6::Bullet>> TrickerDebugAPI::GetBullets() const
{
return logic.GetBullets();
}

std::vector<std::vector<THUAI6::PlaceType>> StudentDebugAPI::GetFullMap() const std::vector<std::vector<THUAI6::PlaceType>> StudentDebugAPI::GetFullMap() const
{ {
return logic.GetFullMap(); return logic.GetFullMap();
@@ -545,23 +575,23 @@ std::future<bool> StudentDebugAPI::StartLearning()
return result; }); return result; });
} }


std::future<bool> StudentDebugAPI::StartRescueMate(int64_t mateID)
std::future<bool> StudentDebugAPI::StartRouseMate(int64_t mateID)
{ {
logger->info("StartRescueMate: mate id={}, called at {}ms", mateID, Time::TimeSinceStart(startPoint));
logger->info("StartRouseMate: mate id={}, called at {}ms", mateID, Time::TimeSinceStart(startPoint));
return std::async(std::launch::async, [=]() return std::async(std::launch::async, [=]()
{ auto result = logic.StartRescueMate(mateID);
{ auto result = logic.StartRouseMate(mateID);
if (!result) if (!result)
logger->warn("StartRescueMate: failed at {}ms", Time::TimeSinceStart(startPoint));
logger->warn("StartRouseMate: failed at {}ms", Time::TimeSinceStart(startPoint));
return result; }); return result; });
} }


std::future<bool> StudentDebugAPI::StartTreatMate(int64_t mateID)
std::future<bool> StudentDebugAPI::StartEncourageMate(int64_t mateID)
{ {
logger->info("StartTreatMate: mate id={}, called at {}ms", mateID, Time::TimeSinceStart(startPoint));
logger->info("StartEncourageMate: mate id={}, called at {}ms", mateID, Time::TimeSinceStart(startPoint));
return std::async(std::launch::async, [=]() return std::async(std::launch::async, [=]()
{ auto result = logic.StartTreatMate(mateID);
{ auto result = logic.StartEncourageMate(mateID);
if (!result) if (!result)
logger->warn("StartTreatMate: failed at {}ms", Time::TimeSinceStart(startPoint));
logger->warn("StartEncourageMate: failed at {}ms", Time::TimeSinceStart(startPoint));
return result; }); return result; });
} }


@@ -631,7 +661,7 @@ void StudentDebugAPI::PrintStudent() const
props += THUAI6::propTypeDict[prop] + ", "; props += THUAI6::propTypeDict[prop] + ", ";
logger->info("state={}, bullet={}, props={}", THUAI6::playerStateDict[student->playerState], THUAI6::bulletTypeDict[student->bulletType], props); logger->info("state={}, bullet={}, props={}", THUAI6::playerStateDict[student->playerState], THUAI6::bulletTypeDict[student->bulletType], props);
logger->info("type={}, determination={}, addiction={}, danger alert={}", THUAI6::studentTypeDict[student->studentType], student->determination, student->addiction, student->dangerAlert); logger->info("type={}, determination={}, addiction={}, danger alert={}", THUAI6::studentTypeDict[student->studentType], student->determination, student->addiction, student->dangerAlert);
logger->info("learning speed={}, treat speed={}, treat progress={}, rescue progress={}", student->learningSpeed, student->treatSpeed, student->treatProgress, student->rescueProgress);
logger->info("learning speed={}, encourage speed={}, encourage progress={}, rouse progress={}", student->learningSpeed, student->encourageSpeed, student->encourageProgress, student->rouseProgress);
std::string studentBuff = ""; std::string studentBuff = "";
for (const auto& buff : student->buff) for (const auto& buff : student->buff)
studentBuff += THUAI6::studentBuffDict[buff] + ", "; studentBuff += THUAI6::studentBuffDict[buff] + ", ";
@@ -656,7 +686,7 @@ void TrickerDebugAPI::PrintStudent() const
props += THUAI6::propTypeDict[prop] + ", "; props += THUAI6::propTypeDict[prop] + ", ";
logger->info("state={}, bullet={}, props={}", THUAI6::playerStateDict[student->playerState], THUAI6::bulletTypeDict[student->bulletType], props); logger->info("state={}, bullet={}, props={}", THUAI6::playerStateDict[student->playerState], THUAI6::bulletTypeDict[student->bulletType], props);
logger->info("type={}, determination={}, addiction={}, danger alert={}", THUAI6::studentTypeDict[student->studentType], student->determination, student->addiction, student->dangerAlert); logger->info("type={}, determination={}, addiction={}, danger alert={}", THUAI6::studentTypeDict[student->studentType], student->determination, student->addiction, student->dangerAlert);
logger->info("learning speed={}, treat speed={}, treat progress={}, rescue progress={}", student->learningSpeed, student->treatSpeed, student->treatProgress, student->rescueProgress);
logger->info("learning speed={}, encourage speed={}, encourage progress={}, rouse progress={}", student->learningSpeed, student->encourageSpeed, student->encourageProgress, student->rouseProgress);
std::string studentBuff = ""; std::string studentBuff = "";
for (const auto& buff : student->buff) for (const auto& buff : student->buff)
studentBuff += THUAI6::studentBuffDict[buff] + ", "; studentBuff += THUAI6::studentBuffDict[buff] + ", ";
@@ -748,7 +778,7 @@ void StudentDebugAPI::PrintSelfInfo() const
props += THUAI6::propTypeDict[prop] + ", "; props += THUAI6::propTypeDict[prop] + ", ";
logger->info("state={}, bullet={}, props={}", THUAI6::playerStateDict[student->playerState], THUAI6::bulletTypeDict[student->bulletType], props); logger->info("state={}, bullet={}, props={}", THUAI6::playerStateDict[student->playerState], THUAI6::bulletTypeDict[student->bulletType], props);
logger->info("type={}, determination={}, addiction={}, danger alert={}", THUAI6::studentTypeDict[student->studentType], student->determination, student->addiction, student->dangerAlert); logger->info("type={}, determination={}, addiction={}, danger alert={}", THUAI6::studentTypeDict[student->studentType], student->determination, student->addiction, student->dangerAlert);
logger->info("learning speed={}, treat speed={}, treat progress={}, rescue progress={}", student->learningSpeed, student->treatSpeed, student->treatProgress, student->rescueProgress);
logger->info("learning speed={}, encourage speed={}, encourage progress={}, rouse progress={}", student->learningSpeed, student->encourageSpeed, student->encourageProgress, student->rouseProgress);
std::string studentBuff = ""; std::string studentBuff = "";
for (const auto& buff : student->buff) for (const auto& buff : student->buff)
studentBuff += THUAI6::studentBuffDict[buff] + ", "; studentBuff += THUAI6::studentBuffDict[buff] + ", ";


+ 47
- 6
CAPI/cpp/API/src/logic.cpp View File

@@ -51,6 +51,15 @@ std::vector<std::shared_ptr<const THUAI6::Prop>> Logic::GetProps() const
return temp; return temp;
} }


std::vector<std::shared_ptr<const THUAI6::Bullet>> Logic::GetBullets() const
{
std::unique_lock<std::mutex> lock(mtxState);
std::vector<std::shared_ptr<const THUAI6::Bullet>> temp;
temp.assign(currentState->bullets.begin(), currentState->bullets.end());
logger->debug("Called GetBullets");
return temp;
}

std::shared_ptr<const THUAI6::Student> Logic::StudentGetSelfInfo() const std::shared_ptr<const THUAI6::Student> Logic::StudentGetSelfInfo() const
{ {
std::unique_lock<std::mutex> lock(mtxState); std::unique_lock<std::mutex> lock(mtxState);
@@ -193,6 +202,12 @@ bool Logic::UseProp(THUAI6::PropType prop)
return pComm->UseProp(prop, playerID); return pComm->UseProp(prop, playerID);
} }


bool Logic::ThrowProp(THUAI6::PropType prop)
{
logger->debug("Called ThrowProp");
return pComm->ThrowProp(prop, playerID);
}

bool Logic::UseSkill(int32_t skill) bool Logic::UseSkill(int32_t skill)
{ {
logger->debug("Called UseSkill"); logger->debug("Called UseSkill");
@@ -236,16 +251,16 @@ bool Logic::StartLearning()
return pComm->StartLearning(playerID); return pComm->StartLearning(playerID);
} }


bool Logic::StartTreatMate(int64_t mateID)
bool Logic::StartEncourageMate(int64_t mateID)
{ {
logger->debug("Called StartTreatMate");
return pComm->StartTreatMate(playerID, mateID);
logger->debug("Called StartEncourageMate");
return pComm->StartEncourageMate(playerID, mateID);
} }


bool Logic::StartRescueMate(int64_t mateID)
bool Logic::StartRouseMate(int64_t mateID)
{ {
logger->debug("Called StartRescueMate");
return pComm->StartRescueMate(playerID, mateID);
logger->debug("Called StartRouseMate");
return pComm->StartRouseMate(playerID, mateID);
} }


bool Logic::Attack(double angle) bool Logic::Attack(double angle)
@@ -528,6 +543,15 @@ void Logic::LoadBuffer(protobuf::MessageToClient& message)
} }
case THUAI6::MessageOfObj::TrickerMessage: case THUAI6::MessageOfObj::TrickerMessage:
{ {
bool flag = false;
for (int i = 0; i < item.tricker_message().buff_size(); i++)
if (Proto2THUAI6::trickerBuffTypeDict[item.tricker_message().buff(i)] == THUAI6::TrickerBuffType::Invisible)
{
flag = true;
break;
}
if (flag)
break;
if (AssistFunction::HaveView(bufferState->studentSelf->viewRange, bufferState->studentSelf->x, bufferState->studentSelf->y, item.tricker_message().x(), item.tricker_message().y(), bufferState->gameMap)) if (AssistFunction::HaveView(bufferState->studentSelf->viewRange, bufferState->studentSelf->x, bufferState->studentSelf->y, item.tricker_message().x(), item.tricker_message().y(), bufferState->gameMap))
{ {
bufferState->trickers.push_back(Proto2THUAI6::Protobuf2THUAI6Tricker(item.tricker_message())); bufferState->trickers.push_back(Proto2THUAI6::Protobuf2THUAI6Tricker(item.tricker_message()));
@@ -674,6 +698,23 @@ void Logic::LoadBuffer(protobuf::MessageToClient& message)
} }
case THUAI6::MessageOfObj::StudentMessage: case THUAI6::MessageOfObj::StudentMessage:
{ {
bool flag = false;
for (const auto& buff : bufferState->trickerSelf->buff)
if (buff == THUAI6::TrickerBuffType::Clairaudience)
{
flag = true;
bufferState->students.push_back(Proto2THUAI6::Protobuf2THUAI6Student(item.student_message()));
logger->debug("Add Student!");
break;
}
for (int i = 0; i < item.student_message().buff_size(); i++)
if (Proto2THUAI6::studentBuffTypeDict[item.student_message().buff(i)] == THUAI6::StudentBuffType::Invisible)
{
flag = true;
break;
}
if (flag)
break;
if (AssistFunction::HaveView(bufferState->trickerSelf->viewRange, bufferState->trickerSelf->x, bufferState->trickerSelf->y, item.student_message().x(), item.student_message().y(), bufferState->gameMap)) if (AssistFunction::HaveView(bufferState->trickerSelf->viewRange, bufferState->trickerSelf->x, bufferState->trickerSelf->y, item.student_message().x(), item.student_message().y(), bufferState->gameMap))
{ {
bufferState->students.push_back(Proto2THUAI6::Protobuf2THUAI6Student(item.student_message())); bufferState->students.push_back(Proto2THUAI6::Protobuf2THUAI6Student(item.student_message()));


+ 0
- 31
CAPI/cpp/CAPI.sln View File

@@ -1,31 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "API", "API\API.vcxproj", "{B9AC3133-177D-453C-8066-ED4702D3F36A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B9AC3133-177D-453C-8066-ED4702D3F36A}.Debug|x64.ActiveCfg = Debug|x64
{B9AC3133-177D-453C-8066-ED4702D3F36A}.Debug|x64.Build.0 = Debug|x64
{B9AC3133-177D-453C-8066-ED4702D3F36A}.Debug|x86.ActiveCfg = Debug|Win32
{B9AC3133-177D-453C-8066-ED4702D3F36A}.Debug|x86.Build.0 = Debug|Win32
{B9AC3133-177D-453C-8066-ED4702D3F36A}.Release|x64.ActiveCfg = Release|x64
{B9AC3133-177D-453C-8066-ED4702D3F36A}.Release|x64.Build.0 = Release|x64
{B9AC3133-177D-453C-8066-ED4702D3F36A}.Release|x86.ActiveCfg = Release|Win32
{B9AC3133-177D-453C-8066-ED4702D3F36A}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {372B1478-522C-4EEB-A527-983D310A3F50}
EndGlobalSection
EndGlobal

+ 5
- 6
CAPI/cpp/proto/MessageType.pb.cc View File

@@ -62,17 +62,16 @@ const char descriptor_table_protodef_MessageType_2eproto[] PROTOBUF_SECTION_VARI
"\016TRICKER_PLAYER\020\002*q\n\013StudentType\022\025\n\021NULL" "\016TRICKER_PLAYER\020\002*q\n\013StudentType\022\025\n\021NULL"
"_STUDENT_TYPE\020\000\022\013\n\007ATHLETE\020\001\022\013\n\007TEACHER\020" "_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" "\002\022\026\n\022STRAIGHT_A_STUDENT\020\003\022\t\n\005ROBOT\020\004\022\016\n\n"
"TECH_OTAKU\020\005*b\n\013TrickerType\022\025\n\021NULL_TRIC"
"TECH_OTAKU\020\005*Z\n\013TrickerType\022\025\n\021NULL_TRIC"
"KER_TYPE\020\000\022\014\n\010ASSASSIN\020\001\022\010\n\004KLEE\020\002\022\022\n\016A_" "KER_TYPE\020\000\022\014\n\010ASSASSIN\020\001\022\010\n\004KLEE\020\002\022\022\n\016A_"
"NOISY_PERSON\020\003\022\020\n\014TRICKERTYPE4\020\004*P\n\tGame"
"State\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\006prot"
"o3";
"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\014GA"
"ME_RUNNING\020\002\022\014\n\010GAME_END\020\003b\006proto3";
static ::_pbi::once_flag descriptor_table_MessageType_2eproto_once; static ::_pbi::once_flag descriptor_table_MessageType_2eproto_once;
const ::_pbi::DescriptorTable descriptor_table_MessageType_2eproto = { const ::_pbi::DescriptorTable descriptor_table_MessageType_2eproto = {
false, false,
false, false,
1482,
1474,
descriptor_table_protodef_MessageType_2eproto, descriptor_table_protodef_MessageType_2eproto,
"MessageType.proto", "MessageType.proto",
&descriptor_table_MessageType_2eproto_once, &descriptor_table_MessageType_2eproto_once,


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

@@ -368,13 +368,13 @@ namespace protobuf
ASSASSIN = 1, ASSASSIN = 1,
KLEE = 2, KLEE = 2,
A_NOISY_PERSON = 3, A_NOISY_PERSON = 3,
TRICKERTYPE4 = 4,
IDOL = 4,
TrickerType_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits<int32_t>::min(), TrickerType_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits<int32_t>::min(),
TrickerType_INT_MAX_SENTINEL_DO_NOT_USE_ = std::numeric_limits<int32_t>::max() TrickerType_INT_MAX_SENTINEL_DO_NOT_USE_ = std::numeric_limits<int32_t>::max()
}; };
bool TrickerType_IsValid(int value); bool TrickerType_IsValid(int value);
constexpr TrickerType TrickerType_MIN = NULL_TRICKER_TYPE; constexpr TrickerType TrickerType_MIN = NULL_TRICKER_TYPE;
constexpr TrickerType TrickerType_MAX = TRICKERTYPE4;
constexpr TrickerType TrickerType_MAX = IDOL;
constexpr int TrickerType_ARRAYSIZE = TrickerType_MAX + 1; constexpr int TrickerType_ARRAYSIZE = TrickerType_MAX + 1;


const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* TrickerType_descriptor(); const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* TrickerType_descriptor();


+ 95
- 31
CAPI/python/PyAPI/AI.py View File

@@ -1,6 +1,8 @@
import PyAPI.structures as THUAI6 import PyAPI.structures as THUAI6
from PyAPI.Interface import IStudentAPI, ITrickerAPI, IAI from PyAPI.Interface import IStudentAPI, ITrickerAPI, IAI
from typing import Union, Final, cast from typing import Union, Final, cast
from PyAPI.constants import Constants
import queue


import time import time


@@ -9,7 +11,7 @@ class Setting:
# 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 # 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新
@staticmethod @staticmethod
def asynchronous() -> bool: def asynchronous() -> bool:
return False
return True


# 选手必须修改该函数的返回值来选择自己的阵营 # 选手必须修改该函数的返回值来选择自己的阵营
@staticmethod @staticmethod
@@ -41,50 +43,112 @@ class AssistFunction:
return grid // numOfGridPerCell return grid // numOfGridPerCell




arrive: bool = False
path = []
cur = 0
fixedclass = []




class AI(IAI): class AI(IAI):
# 选手在这里实现自己的逻辑,要求和上面选择的阵营保持一致 # 选手在这里实现自己的逻辑,要求和上面选择的阵营保持一致
def StudentPlay(self, api: IStudentAPI) -> None: def StudentPlay(self, api: IStudentAPI) -> None:
api.Attack(float('nan'))
time.sleep(0.5)
api.PrintSelfInfo()
# api.SendMessage(4, "Hello World!")
# api.PrintSelfInfo()
# global arrive
# if not arrive:
# if api.GetSelfInfo().x < 25500:
# api.MoveDown(50)
# global fixedclass
# selfInfo = api.GetSelfInfo()
# available = [THUAI6.PlaceType.Land,
# THUAI6.PlaceType.Grass, THUAI6.PlaceType.Door3, THUAI6.PlaceType.Door6, THUAI6.PlaceType.Door5, THUAI6.PlaceType.Gate]

# def bfs(x, y):
# if api.GetPlaceType(x, y) not in available:
# return []

# def GetSuccessors(x, y):
# successors = []
# if x > 0 and api.GetPlaceType(x - 1, y) in available:
# successors.append((x - 1, y))
# if x < 49 and api.GetPlaceType(x + 1, y) in available:
# successors.append((x + 1, y))
# if y > 0 and api.GetPlaceType(x, y - 1) in available:
# successors.append((x, y - 1))
# if y < 49 and api.GetPlaceType(x, y + 1) in available:
# successors.append((x, y + 1))
# return successors
# selfX = AssistFunction.GridToCell(api.GetSelfInfo().x)
# selfY = AssistFunction.GridToCell(api.GetSelfInfo().y)
# frontier = queue.Queue()
# frontier.put((selfX, selfY, []))
# visited = []
# while not frontier.empty():
# currentX, currentY, path = frontier.get()
# if currentX == x and currentY == y:
# return path
# for nextX, nextY in GetSuccessors(currentX, currentY):
# if (nextX, nextY) not in visited:
# visited.append((nextX, nextY))
# frontier.put((nextX, nextY, path + [(nextX, nextY)]))
# return []

# def GoTo(x, y):
# global path, cur
# if path != [] and cur < len(path):
# selfX = api.GetSelfInfo().x
# selfY = api.GetSelfInfo().y
# nextX, nextY = path[cur]
# nextX = AssistFunction.CellToGrid(nextX)
# nextY = AssistFunction.CellToGrid(nextY)
# if selfX < nextX - 100:
# api.MoveDown(10)
# time.sleep(0.01)
# return
# if selfX > nextX + 100:
# api.MoveUp(10)
# time.sleep(0.01)
# return
# if selfY < nextY - 100:
# api.MoveRight(10)
# time.sleep(0.01)
# return
# if selfY > nextY + 100:
# api.MoveLeft(10)
# time.sleep(0.01)
# return
# cur += 1
# return # return
# if api.GetSelfInfo().y > 10500:
# api.MoveLeft(50)
# else:
# path = bfs(x, y)
# cur = 0
# return # return
# arrive = True
# else:
# api.SkipWindow()
# # time.sleep(1)

# api.PrintSelfInfo()


# if api.GetSelfInfo().y < 18500:
# api.MoveRight(50)
# if (AssistFunction.GridToCell(api.GetSelfInfo().x), AssistFunction.GridToCell(api.GetSelfInfo().y)) == (6, 6) and api.GetGateProgress(5, 6) < 18000:
# api.StartOpenGate()
# return # return
# api.StartLearning()


# if api.GetSelfInfo().y > 7000:
# api.MoveLeft(50)
# if (AssistFunction.GridToCell(api.GetSelfInfo().x), AssistFunction.GridToCell(api.GetSelfInfo().y)) == (6, 6) and api.GetGateProgress(5, 6) >= 18000:
# api.Graduate()
# return # return
# if api.GetSelfInfo().x > 20500:
# api.MoveUp(50)
# return
# if api.GetSelfInfo().y > 4500:
# api.MoveLeft(50)

# if len(fixedclass) == 7:
# GoTo(6, 6)
# return # return


# if api.GetPlaceType(AssistFunction.GridToCell(api.GetSelfInfo().x) + 1, AssistFunction.GridToCell(api.GetSelfInfo().y)) == THUAI6.PlaceType.ClassRoom:
# api.Print("Trying to fix!")
# if api.GetClassroomProgress(AssistFunction.GridToCell(api.GetSelfInfo().x) + 1, AssistFunction.GridToCell(api.GetSelfInfo().y)) < 103000:
# api.StartLearning()
# return
# else:
# if (AssistFunction.GridToCell(api.GetSelfInfo().x) + 1, AssistFunction.GridToCell(api.GetSelfInfo().y)) not in fixedclass:
# fixedclass.append(
# (AssistFunction.GridToCell(api.GetSelfInfo().x) + 1, AssistFunction.GridToCell(api.GetSelfInfo().y)))

# for i in range(50):
# for j in range(50):
# if api.GetPlaceType(i, j) == THUAI6.PlaceType.ClassRoom and (i, j) not in fixedclass:
# if api.GetPlaceType(i - 1, j) in available:
# GoTo(i - 1, j)
# return
api.PrintTricker() api.PrintTricker()


return

def TrickerPlay(self, api: ITrickerAPI) -> None: def TrickerPlay(self, api: ITrickerAPI) -> None:
api.UseSkill(0)
api.UseSkill(1)
api.PrintSelfInfo()
return return

+ 16
- 4
CAPI/python/PyAPI/API.py View File

@@ -41,6 +41,9 @@ class StudentAPI(IStudentAPI, IGameTimer):
def UseProp(self, propType: THUAI6.PropType) -> Future[bool]: def UseProp(self, propType: THUAI6.PropType) -> Future[bool]:
return self.__pool.submit(self.__logic.UseProp, propType) return self.__pool.submit(self.__logic.UseProp, propType)


def ThrowProp(self, propType: THUAI6.PropType) -> Future[bool]:
return self.__pool.submit(self.__logic.ThrowProp, propType)

def UseSkill(self, skillID: int) -> Future[bool]: def UseSkill(self, skillID: int) -> Future[bool]:
return self.__pool.submit(self.__logic.UseSkill, skillID) return self.__pool.submit(self.__logic.UseSkill, skillID)


@@ -99,6 +102,9 @@ class StudentAPI(IStudentAPI, IGameTimer):
def GetProps(self) -> List[THUAI6.Prop]: def GetProps(self) -> List[THUAI6.Prop]:
return self.__logic.GetProps() return self.__logic.GetProps()


def GetBullets(self) -> List[THUAI6.Bullet]:
return self.__logic.GetBullets()

def GetFullMap(self) -> List[List[THUAI6.PlaceType]]: def GetFullMap(self) -> List[List[THUAI6.PlaceType]]:
return self.__logic.GetFullMap() return self.__logic.GetFullMap()


@@ -151,11 +157,11 @@ class StudentAPI(IStudentAPI, IGameTimer):
def StartLearning(self) -> Future[bool]: def StartLearning(self) -> Future[bool]:
return self.__pool.submit(self.__logic.StartLearning) return self.__pool.submit(self.__logic.StartLearning)


def StartTreatMate(self, mateID: int) -> Future[bool]:
return self.__pool.submit(self.__logic.StartTreatMate, mateID)
def StartEncourageMate(self, mateID: int) -> Future[bool]:
return self.__pool.submit(self.__logic.StartEncourageMate, mateID)


def StartRescueMate(self, mateID: int) -> Future[bool]:
return self.__pool.submit(self.__logic.StartRescueMate, mateID)
def StartRouseMate(self, mateID: int) -> Future[bool]:
return self.__pool.submit(self.__logic.StartRouseMate, mateID)


def GetSelfInfo(self) -> THUAI6.Student: def GetSelfInfo(self) -> THUAI6.Student:
return cast(THUAI6.Student, self.__logic.GetSelfInfo()) return cast(THUAI6.Student, self.__logic.GetSelfInfo())
@@ -208,6 +214,9 @@ class TrickerAPI(ITrickerAPI, IGameTimer):
def UseProp(self, propType: THUAI6.PropType) -> Future[bool]: def UseProp(self, propType: THUAI6.PropType) -> Future[bool]:
return self.__pool.submit(self.__logic.UseProp, propType) return self.__pool.submit(self.__logic.UseProp, propType)


def ThrowProp(self, propType: THUAI6.PropType) -> Future[bool]:
return self.__pool.submit(self.__logic.ThrowProp, propType)

def UseSkill(self, skillID: int) -> Future[bool]: def UseSkill(self, skillID: int) -> Future[bool]:
return self.__pool.submit(self.__logic.UseSkill, skillID) return self.__pool.submit(self.__logic.UseSkill, skillID)


@@ -266,6 +275,9 @@ class TrickerAPI(ITrickerAPI, IGameTimer):
def GetProps(self) -> List[THUAI6.Prop]: def GetProps(self) -> List[THUAI6.Prop]:
return self.__logic.GetProps() return self.__logic.GetProps()


def GetBullets(self) -> List[THUAI6.Bullet]:
return self.__logic.GetBullets()

def GetFullMap(self) -> List[List[THUAI6.PlaceType]]: def GetFullMap(self) -> List[List[THUAI6.PlaceType]]:
return self.__logic.GetFullMap() return self.__logic.GetFullMap()




+ 12
- 3
CAPI/python/PyAPI/Communication.py View File

@@ -48,7 +48,7 @@ class Communication:
else: else:
return pickResult.act_success return pickResult.act_success


def UseProp(self, propType: THUAI6.PropType, playerID: int):
def UseProp(self, propType: THUAI6.PropType, playerID: int) -> bool:
try: try:
useResult = self.__THUAI6Stub.UseProp( useResult = self.__THUAI6Stub.UseProp(
THUAI62Proto.THUAI62ProtobufProp(propType, playerID)) THUAI62Proto.THUAI62ProtobufProp(propType, playerID))
@@ -57,6 +57,15 @@ class Communication:
else: else:
return useResult.act_success return useResult.act_success


def ThrowProp(self, propType: THUAI6.PropType, playerID: int) -> bool:
try:
throwResult = self.__THUAI6Stub.ThrowProp(
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, playerID: int) -> bool:
try: try:
useResult = self.__THUAI6Stub.UseSkill( useResult = self.__THUAI6Stub.UseSkill(
@@ -93,7 +102,7 @@ class Communication:
else: else:
return learnResult.act_success return learnResult.act_success


def StartTreatMate(self, playerID: int, mateID: int) -> bool:
def StartEncourageMate(self, playerID: int, mateID: int) -> bool:
try: try:
helpResult = self.__THUAI6Stub.StartTreatMate( helpResult = self.__THUAI6Stub.StartTreatMate(
THUAI62Proto.THUAI62ProtobufTreatAndRescue(playerID, mateID)) THUAI62Proto.THUAI62ProtobufTreatAndRescue(playerID, mateID))
@@ -102,7 +111,7 @@ class Communication:
else: else:
return helpResult.act_success return helpResult.act_success


def StartRescueMate(self, playerID: int, mateID: int) -> bool:
def StartRouseMate(self, playerID: int, mateID: int) -> bool:
try: try:
helpResult = self.__THUAI6Stub.StartRescueMate( helpResult = self.__THUAI6Stub.StartRescueMate(
THUAI62Proto.THUAI62ProtobufTreatAndRescue(playerID, mateID)) THUAI62Proto.THUAI62ProtobufTreatAndRescue(playerID, mateID))


+ 47
- 15
CAPI/python/PyAPI/DebugAPI.py View File

@@ -109,6 +109,19 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):


return self.__pool.submit(logUse) 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")

def logThrow() -> bool:
result = self.__logic.ThrowProp(propType)
if not result:
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) -> Future[bool]:
self.__logger.info( self.__logger.info(
f"UseSkill: skillID = {skillID}, called at {self.__GetTime()}ms") f"UseSkill: skillID = {skillID}, called at {self.__GetTime()}ms")
@@ -261,6 +274,9 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):
def GetProps(self) -> List[THUAI6.Prop]: def GetProps(self) -> List[THUAI6.Prop]:
return self.__logic.GetProps() return self.__logic.GetProps()


def GetBullets(self) -> List[THUAI6.Bullet]:
return self.__logic.GetBullets()

def GetFullMap(self) -> List[List[THUAI6.PlaceType]]: def GetFullMap(self) -> List[List[THUAI6.PlaceType]]:
return self.__logic.GetFullMap() return self.__logic.GetFullMap()


@@ -310,7 +326,7 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):
self.__logger.info( 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( self.__logger.info(
f"learning speed={student.learningSpeed}, treat speed={student.treatSpeed}, treat progress={student.treatProgress}, rescue progress={student.rescueProgress}")
f"learning speed={student.learningSpeed}, encourage speed={student.encourageSpeed}, encourage progress={student.encourageProgress}, rouse progress={student.rouseProgress}")
studentBuff = "" studentBuff = ""
for buff in student.buff: for buff in student.buff:
studentBuff += buff.name + ", " studentBuff += buff.name + ", "
@@ -363,7 +379,7 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):
self.__logger.info( 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( self.__logger.info(
f"learning speed={student.learningSpeed}, treat speed={student.treatSpeed}, treat progress={student.treatProgress}, rescue progress={student.rescueProgress}")
f"learning speed={student.learningSpeed}, encourage speed={student.encourageSpeed}, encourage progress={student.encourageProgress}, rouse progress={student.rouseProgress}")
studentBuff = "" studentBuff = ""
for buff in student.buff: for buff in student.buff:
studentBuff += buff.name + ", " studentBuff += buff.name + ", "
@@ -398,31 +414,31 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):


return self.__pool.submit(logStart) return self.__pool.submit(logStart)


def StartTreatMate(self, mateID: int) -> Future[bool]:
def StartEncourageMate(self, mateID: int) -> Future[bool]:
self.__logger.info( self.__logger.info(
f"StartTreatMate: called at {self.__GetTime()}ms")
f"StartEncourageMate: called at {self.__GetTime()}ms")


def logStartTreatMate() -> bool:
result = self.__logic.StartTreatMate(mateID)
def logStartEncourageMate() -> bool:
result = self.__logic.StartEncourageMate(mateID)
if not result: if not result:
self.__logger.warning( self.__logger.warning(
f"StartTreatMate: failed at {self.__GetTime()}ms")
f"StartEncourageMate: failed at {self.__GetTime()}ms")
return result return result


return self.__pool.submit(logStartTreatMate)
return self.__pool.submit(logStartEncourageMate)


def StartRescueMate(self, mateID: int) -> Future[bool]:
def StartRouseMate(self, mateID: int) -> Future[bool]:
self.__logger.info( self.__logger.info(
f"StartRescueMate: called at {self.__GetTime()}ms")
f"StartRouseMate: called at {self.__GetTime()}ms")


def logStartRescueMate() -> bool:
result = self.__logic.StartRescueMate(mateID)
def logStartRouseMate() -> bool:
result = self.__logic.StartRouseMate(mateID)
if not result: if not result:
self.__logger.warning( self.__logger.warning(
f"StartRescueMate: failed at {self.__GetTime()}ms")
f"StartRouseMate: failed at {self.__GetTime()}ms")
return result return result


return self.__pool.submit(logStartRescueMate)
return self.__pool.submit(logStartRouseMate)


def GetSelfInfo(self) -> THUAI6.Student: def GetSelfInfo(self) -> THUAI6.Student:
return cast(THUAI6.Student, self.__logic.GetSelfInfo()) return cast(THUAI6.Student, self.__logic.GetSelfInfo())
@@ -545,6 +561,19 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer):


return self.__pool.submit(logUse) 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")

def logThrow() -> bool:
result = self.__logic.ThrowProp(propType)
if not result:
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) -> Future[bool]:
self.__logger.info( self.__logger.info(
f"UseSkill: skillID = {skillID}, called at {self.__GetTime()}ms") f"UseSkill: skillID = {skillID}, called at {self.__GetTime()}ms")
@@ -697,6 +726,9 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer):
def GetProps(self) -> List[THUAI6.Prop]: def GetProps(self) -> List[THUAI6.Prop]:
return self.__logic.GetProps() return self.__logic.GetProps()


def GetBullets(self) -> List[THUAI6.Bullet]:
return self.__logic.GetBullets()

def GetFullMap(self) -> List[List[THUAI6.PlaceType]]: def GetFullMap(self) -> List[List[THUAI6.PlaceType]]:
return self.__logic.GetFullMap() return self.__logic.GetFullMap()


@@ -746,7 +778,7 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer):
self.__logger.info( 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( self.__logger.info(
f"learning speed={student.learningSpeed}, treat speed={student.treatSpeed}, treat progress={student.treatProgress}, rescue progress={student.rescueProgress}")
f"learning speed={student.learningSpeed}, encourage speed={student.encourageSpeed}, encourage progress={student.encourageProgress}, rouse progress={student.rouseProgress}")
studentBuff = "" studentBuff = ""
for buff in student.buff: for buff in student.buff:
studentBuff += buff.name + ", " studentBuff += buff.name + ", "


+ 20
- 4
CAPI/python/PyAPI/Interface.py View File

@@ -20,6 +20,10 @@ class ILogic(metaclass=ABCMeta):
def GetProps(self) -> List[THUAI6.Prop]: def GetProps(self) -> List[THUAI6.Prop]:
pass pass


@abstractmethod
def GetBullets(self) -> List[THUAI6.Bullet]:
pass

@abstractmethod @abstractmethod
def GetSelfInfo(self) -> Union[THUAI6.Student, THUAI6.Tricker]: def GetSelfInfo(self) -> Union[THUAI6.Student, THUAI6.Tricker]:
pass pass
@@ -72,6 +76,10 @@ class ILogic(metaclass=ABCMeta):
def UseProp(self, propType: THUAI6.PropType) -> bool: def UseProp(self, propType: THUAI6.PropType) -> bool:
pass pass


@abstractmethod
def ThrowProp(self, propType: THUAI6.PropType) -> bool:
pass

@abstractmethod @abstractmethod
def UseSkill(self, skillID: int) -> bool: def UseSkill(self, skillID: int) -> bool:
pass pass
@@ -139,11 +147,11 @@ class ILogic(metaclass=ABCMeta):
pass pass


@abstractmethod @abstractmethod
def StartTreatMate(self, mateID: int) -> bool:
def StartEncourageMate(self, mateID: int) -> bool:
pass pass


@abstractmethod @abstractmethod
def StartRescueMate(self, mateID: int) -> bool:
def StartRouseMate(self, mateID: int) -> bool:
pass pass




@@ -184,6 +192,10 @@ class IAPI(metaclass=ABCMeta):
def UseProp(self, propType: THUAI6.PropType) -> Future[bool]: def UseProp(self, propType: THUAI6.PropType) -> Future[bool]:
pass pass


@abstractmethod
def ThrowProp(self, propType: THUAI6.PropType) -> Future[bool]:
pass

@abstractmethod @abstractmethod
def UseSkill(self, skillID: int) -> Future[bool]: def UseSkill(self, skillID: int) -> Future[bool]:
pass pass
@@ -258,6 +270,10 @@ class IAPI(metaclass=ABCMeta):
def GetProps(self) -> List[THUAI6.Prop]: def GetProps(self) -> List[THUAI6.Prop]:
pass pass


@abstractmethod
def GetBullets(self) -> List[THUAI6.Bullet]:
pass

@abstractmethod @abstractmethod
def GetSelfInfo(self) -> Union[THUAI6.Student, THUAI6.Tricker]: def GetSelfInfo(self) -> Union[THUAI6.Student, THUAI6.Tricker]:
pass pass
@@ -334,11 +350,11 @@ class IStudentAPI(IAPI, metaclass=ABCMeta):
pass pass


@abstractmethod @abstractmethod
def StartTreatMate(self, mateID: int) -> Future[bool]:
def StartEncourageMate(self, mateID: int) -> Future[bool]:
pass pass


@abstractmethod @abstractmethod
def StartRescueMate(self, mateID: int) -> Future[bool]:
def StartRouseMate(self, mateID: int) -> Future[bool]:
pass pass


@abstractmethod @abstractmethod


+ 295
- 0
CAPI/python/PyAPI/constants.py View File

@@ -0,0 +1,295 @@
from ast import Constant
from asyncio import constants


class NoInstance:
def __call__(self):
raise TypeError("This class cannot be instantiated.")


class Constants(NoInstance):
numOfGridPerCell = 1000 # 单位坐标数
rows = 50 # 地图行数
cols = 50 # 地图列数

numOfClassroom = 10 # 教室数量
numOfChest = 8 # 宝箱数量

maxClassroomProgress = 10000000 # 教室最大进度
maxDoorProgress = 10000000 # 开关门最大进度
maxChestProgress = 10000000 # 宝箱最大进度
maxGateProgress = 18000 # 大门最大进度

numOfRequiredClassroomForGate = 7 # 打开大门需要完成的教室数量
numOfRequiredClassroomForHiddenGate = 3 # 打开隐藏门需要完成的教室数量

# 人物属性相关
basicEncourageSpeed = 100
basicLearnSpeed = 123
basicSpeedOfOpeningOrLocking = 4000
basicStudentSpeedOfClimbingThroughWindows = 611
basicTrickerSpeedOfClimbingThroughWindows = 1270
basicSpeedOfOpenChest = 1000

basicHp = 3000000
basicMaxGamingAddiction = 60000
basicEncouragementDegree = 1500000
basicTimeOfRouse = 1000

basicStudentSpeed = 1270
basicTrickerSpeed = 1504

basicConcealment = 1.0
basicStudentAlertnessRadius = 15 * numOfGridPerCell
basicTrickerAlertnessRadius = 17 * numOfGridPerCell
basicStudentViewRange = 10 * numOfGridPerCell
basicTrickerViewRange = 15 * numOfGridPerCell
PinningDownRange = 5 * numOfGridPerCell

maxNumOfProp = 3 # 人物道具栏容量

# 攻击相关

basicApOfTricker = 1500000
basicCD = 3000 # 初始子弹冷却
basicCastTime = 500 # 基本前摇时间
basicBackswing = 800 # 基本后摇时间
basicRecoveryFromHit = 3700 # 基本命中攻击恢复时长
basicStunnedTimeOfStudent = 4300

basicBulletmoveSpeed = 3700 # 基本子弹移动速度
basicRemoteAttackRange = 3000 # 基本远程攻击范围
basicAttackShortRange = 1100 # 基本近程攻击范围
basicBulletBombRange = 1000 # 基本子弹爆炸范围

# 道具相关

apPropAdd = basicApOfTricker * 12 / 10
apSpearAdd = basicApOfTricker * 6 / 10

# 技能相关
maxNumOfSkill = 3
commonSkillCD = 30000 # 普通技能标准冷却时间
commonSkillTime = 10000 # 普通技能标准持续时间

timeOfTrickerStunnedWhenCharge = 7220
timeOfStudentStunnedWhenCharge = 2090

timeOfTrickerStunnedWhenPunish = 3070

timeOfTrickerSwingingAfterHowl = 800
timeOfStudentStunnedWhenHowl = 5500

timeOfStunnedWhenJumpyDumpty = 3070

addedTimeOfSpeedWhenInspire = 0.6
timeOfAddingSpeedWhenInspire = 6000


class Assassin:
moveSpeed = (int)(1.1 * Constants.basicTrickerSpeed)
concealment = 1.5 * Constants.basicConcealment
alertnessRadius = (int)(1.3 * Constants.basicTrickerAlertnessRadius)
viewRange = (int)(1.2 * Constants.basicTrickerViewRange)
speedOfOpeningOrLocking = (int)(
1.0 * Constants.basicSpeedOfOpeningOrLocking)
speedOfClimbingThroughWindows = (int)(
1.0 * Constants.basicTrickerSpeedOfClimbingThroughWindows)
speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest)


class Klee:
moveSpeed = (int)(1.0 * Constants.basicTrickerSpeed)
concealment = 1.0 * Constants.basicConcealment
alertnessRadius = (int)(1.0 * Constants.basicTrickerAlertnessRadius)
viewRange = (int)(1.0 * Constants.basicTrickerViewRange)
speedOfOpeningOrLocking = (int)(
1.0 * Constants.basicSpeedOfOpeningOrLocking)
speedOfClimbingThroughWindows = (int)(
1.0 * Constants.basicTrickerSpeedOfClimbingThroughWindows)
speedOfOpenChest = (int)(1.1 * Constants.basicSpeedOfOpenChest)


class ANoisyPerson:
moveSpeed = (int)(1.07 * Constants.basicTrickerSpeed)
concealment = 0.8 * Constants.basicConcealment
alertnessRadius = (int)(0.9 * Constants.basicTrickerAlertnessRadius)
viewRange = (int)(1.0 * Constants.basicTrickerViewRange)
speedOfOpeningOrLocking = (int)(
1.0 * Constants.basicSpeedOfOpeningOrLocking)
speedOfClimbingThroughWindows = (int)(
1.1 * Constants.basicTrickerSpeedOfClimbingThroughWindows)
speedOfOpenChest = (int)(1.1 * Constants.basicSpeedOfOpenChest)


class Idol:
moveSpeed = (int)(1.0 * Constants.basicTrickerSpeed)
concealment = 0.75 * Constants.basicConcealment
alertnessRadius = (int)(1.0 * Constants.basicTrickerAlertnessRadius)
viewRange = (int)(1.1 * Constants.basicTrickerViewRange)
speedOfOpeningOrLocking = (int)(
1.0 * Constants.basicSpeedOfOpeningOrLocking)
speedOfClimbingThroughWindows = (int)(
1.0 * Constants.basicTrickerSpeedOfClimbingThroughWindows)
speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest)


class Athlete:
moveSpeed = (int)(1.05 * Constants.basicStudentSpeed)
maxHp = (int)(1.0 * Constants.basicHp)
maxAddiction = (int)(0.9 * Constants.basicMaxGamingAddiction)
LearnSpeed = (int)(0.6 * Constants.basicLearnSpeed)
EncourageSpeed = (int)(0.9 * Constants.basicEncourageSpeed)
concealment = 0.9 * Constants.basicConcealment
alertnessRadius = (int)(1.0 * Constants.basicStudentAlertnessRadius)
viewRange = (int)(1.1 * Constants.basicStudentViewRange)
speedOfOpeningOrLocking = (int)(
1.0 * Constants.basicSpeedOfOpeningOrLocking)
speedOfClimbingThroughWindows = (int)(
1.2 * Constants.basicStudentSpeedOfClimbingThroughWindows)
speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest)


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)
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)
speedOfClimbingThroughWindows = (int)(
0.5 * Constants.basicStudentSpeedOfClimbingThroughWindows)
speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest)


class StraightAStudent:
moveSpeed = (int)(0.96 * Constants.basicStudentSpeed)
maxHp = (int)(1.1 * Constants.basicHp)
maxAddiction = (int)(1.3 * Constants.basicMaxGamingAddiction)
LearnSpeed = (int)(1.1 * Constants.basicLearnSpeed)
EncourageSpeed = (int)(Constants.basicEncourageSpeed)
concealment = 0.9 * Constants.basicConcealment
alertnessRadius = (int)(0.9 * Constants.basicStudentAlertnessRadius)
viewRange = (int)(0.9 * Constants.basicStudentViewRange)
speedOfOpeningOrLocking = (int)(
1.0 * Constants.basicSpeedOfOpeningOrLocking)
speedOfClimbingThroughWindows = (int)(
0.83333 * Constants.basicStudentSpeedOfClimbingThroughWindows)
speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest)


class Robot:
moveSpeed = (int)(1.0 * Constants.basicStudentSpeed)
maxHp = (int)(0.4 * Constants.basicHp)
maxAddiction = (int)(0.0 * Constants.basicMaxGamingAddiction)
LearnSpeed = (int)(1.0 * 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)


class TechOtaku:
moveSpeed = (int)(0.75 * 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
alertnessRadius = (int)(1.0 * Constants.basicStudentAlertnessRadius)
viewRange = (int)(0.9 * Constants.basicStudentViewRange)
speedOfOpeningOrLocking = (int)(
1.0 * Constants.basicSpeedOfOpeningOrLocking)
speedOfClimbingThroughWindows = (int)(
0.75 * Constants.basicStudentSpeedOfClimbingThroughWindows)
speedOfOpenChest = (int)(1.0 * Constants.basicSpeedOfOpenChest)


class Sunshine:
moveSpeed = (int)(1.0 * Constants.basicStudentSpeed)
maxHp = (int)(1.0667 * Constants.basicHp)
maxAddiction = (int)(1.1 * Constants.basicMaxGamingAddiction)
LearnSpeed = (int)(1.0 * Constants.basicLearnSpeed)
EncourageSpeed = (int)(1.2 * Constants.basicEncourageSpeed)
concealment = 1.0 * Constants.basicConcealment
alertnessRadius = (int)(1.0 * Constants.basicStudentAlertnessRadius)
viewRange = (int)(1.0 * Constants.basicStudentViewRange)
speedOfOpeningOrLocking = (int)(
0.7 * Constants.basicSpeedOfOpeningOrLocking)
speedOfClimbingThroughWindows = (int)(
1.0 * Constants.basicStudentSpeedOfClimbingThroughWindows)
speedOfOpenChest = (int)(0.9 * Constants.basicSpeedOfOpenChest)


class CanBeginToCharge:
skillCD = (int)(2 * Constants.commonSkillCD)
durationTime = (int)(0.3 * Constants.commonSkillTime)


class BecomeInvisible:
skillCD = (int)(4 * Constants.commonSkillCD / 3)
durationTime = (int)(Constants.commonSkillTime)


class Punish:
skillCD = (int)(1.0 * Constants.commonSkillCD)
durationTime = (int)(0.0 * Constants.commonSkillTime)


class Rouse:
skillCD = (int)(4.0 * Constants.commonSkillCD)
durationTime = (int)(0.0 * Constants.commonSkillTime)


class Encourage:
skillCD = (int)(4.0 * Constants.commonSkillCD)
durationTime = (int)(0.0 * Constants.commonSkillTime)


class Inspire:
skillCD = (int)(4.0 * Constants.commonSkillCD)
durationTime = (int)(0.0 * Constants.commonSkillTime)


class Howl:
skillCD = (int)(0.8333 * Constants.commonSkillCD)
durationTime = (int)(0 * Constants.commonSkillTime)


class ShowTime:
skillCD = (int)(3.0 * Constants.commonSkillCD)
durationTime = (int)(1.0 * Constants.commonSkillTime)


class JumpyBomb:
skillCD = (int)(0.5 * Constants.commonSkillCD)
durationTime = (int)(0.3 * Constants.commonSkillTime)


class UseKnife:
skillCD = (int)(1.0 * Constants.commonSkillCD)
durationTime = (int)(0.1 * Constants.commonSkillTime)


class UseRobot:
skillCD = (int)(0.0017 * Constants.commonSkillCD)
durationTime = (int)(0.0 * Constants.commonSkillTime)


class WriteAnswers:
skillCD = (int)(1.0 * Constants.commonSkillCD)
durationTime = (int)(0.0 * Constants.commonSkillTime)


class SummonGolem:
skillCD = (int)(1.0 * Constants.commonSkillCD)
durationTime = (int)(0.0 * Constants.commonSkillTime)

+ 40
- 21
CAPI/python/PyAPI/logic.py View File

@@ -2,6 +2,7 @@ import os
from typing import List, Union, Callable, Tuple from typing import List, Union, Callable, Tuple
import threading import threading
import logging import logging
import copy
import proto.MessageType_pb2 as MessageType import proto.MessageType_pb2 as MessageType
import proto.Message2Server_pb2 as Message2Server import proto.Message2Server_pb2 as Message2Server
import proto.Message2Clients_pb2 as Message2Clients import proto.Message2Clients_pb2 as Message2Clients
@@ -71,27 +72,32 @@ class Logic(ILogic):
def GetTrickers(self) -> List[THUAI6.Tricker]: def GetTrickers(self) -> List[THUAI6.Tricker]:
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetTrickers") self.__logger.debug("Called GetTrickers")
return self.__currentState.trickers
return copy.deepcopy(self.__currentState.trickers)


def GetStudents(self) -> List[THUAI6.Student]: def GetStudents(self) -> List[THUAI6.Student]:
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetStudents") self.__logger.debug("Called GetStudents")
return self.__currentState.students
return copy.deepcopy(self.__currentState.students)


def GetProps(self) -> List[THUAI6.Prop]: def GetProps(self) -> List[THUAI6.Prop]:
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetProps") self.__logger.debug("Called GetProps")
return self.__currentState.props
return copy.deepcopy(self.__currentState.props)

def GetBullets(self) -> List[THUAI6.Bullet]:
with self.__mtxState:
self.__logger.debug("Called GetBullets")
return copy.deepcopy(self.__currentState.bullets)


def GetSelfInfo(self) -> Union[THUAI6.Student, THUAI6.Tricker]: def GetSelfInfo(self) -> Union[THUAI6.Student, THUAI6.Tricker]:
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetSelfInfo") self.__logger.debug("Called GetSelfInfo")
return self.__currentState.self
return copy.deepcopy(self.__currentState.self)


def GetFullMap(self) -> List[List[THUAI6.PlaceType]]: def GetFullMap(self) -> List[List[THUAI6.PlaceType]]:
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetFullMap") self.__logger.debug("Called GetFullMap")
return self.__currentState.gameMap
return copy.deepcopy(self.__currentState.gameMap)


def GetPlaceType(self, x: int, y: int) -> THUAI6.PlaceType: def GetPlaceType(self, x: int, y: int) -> THUAI6.PlaceType:
with self.__mtxState: with self.__mtxState:
@@ -99,13 +105,13 @@ class Logic(ILogic):
self.__logger.warning("Invalid position") self.__logger.warning("Invalid position")
return THUAI6.PlaceType.NullPlaceType return THUAI6.PlaceType.NullPlaceType
self.__logger.debug("Called GetPlaceType") self.__logger.debug("Called GetPlaceType")
return self.__currentState.gameMap[x][y]
return copy.deepcopy(self.__currentState.gameMap[x][y])


def IsDoorOpen(self, x: int, y: int) -> bool: def IsDoorOpen(self, x: int, y: int) -> bool:
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called IsDoorOpen") self.__logger.debug("Called IsDoorOpen")
if (x, y) in self.__currentState.mapInfo.doorState: if (x, y) in self.__currentState.mapInfo.doorState:
return self.__currentState.mapInfo.doorState[(x, y)]
return copy.deepcopy(self.__currentState.mapInfo.doorState[(x, y)])
else: else:
self.__logger.warning("Door not found") self.__logger.warning("Door not found")
return False return False
@@ -114,7 +120,7 @@ class Logic(ILogic):
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetClassroomProgress") self.__logger.debug("Called GetClassroomProgress")
if (x, y) in self.__currentState.mapInfo.classroomState: if (x, y) in self.__currentState.mapInfo.classroomState:
return self.__currentState.mapInfo.classroomState[(x, y)]
return copy.deepcopy(self.__currentState.mapInfo.classroomState[(x, y)])
else: else:
self.__logger.warning("Classroom not found") self.__logger.warning("Classroom not found")
return 0 return 0
@@ -123,7 +129,7 @@ class Logic(ILogic):
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetChestProgress") self.__logger.debug("Called GetChestProgress")
if (x, y) in self.__currentState.mapInfo.chestState: if (x, y) in self.__currentState.mapInfo.chestState:
return self.__currentState.mapInfo.chestState[(x, y)]
return copy.deepcopy(self.__currentState.mapInfo.chestState[(x, y)])
else: else:
self.__logger.warning("Chest not found") self.__logger.warning("Chest not found")
return 0 return 0
@@ -132,7 +138,7 @@ class Logic(ILogic):
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetGateProgress") self.__logger.debug("Called GetGateProgress")
if (x, y) in self.__currentState.mapInfo.gateState: if (x, y) in self.__currentState.mapInfo.gateState:
return self.__currentState.mapInfo.gateState[(x, y)]
return copy.deepcopy(self.__currentState.mapInfo.gateState[(x, y)])
else: else:
self.__logger.warning("Gate not found") self.__logger.warning("Gate not found")
return 0 return 0
@@ -141,7 +147,7 @@ class Logic(ILogic):
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetHiddenGateState") self.__logger.debug("Called GetHiddenGateState")
if (x, y) in self.__currentState.mapInfo.hiddenGateState: if (x, y) in self.__currentState.mapInfo.hiddenGateState:
return self.__currentState.mapInfo.hiddenGateState[(x, y)]
return copy.deepcopy(self.__currentState.mapInfo.hiddenGateState[(x, y)])
else: else:
self.__logger.warning("HiddenGate not found") self.__logger.warning("HiddenGate not found")
return THUAI6.HiddenGateState.Null return THUAI6.HiddenGateState.Null
@@ -150,7 +156,7 @@ class Logic(ILogic):
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetDoorProgress") self.__logger.debug("Called GetDoorProgress")
if (x, y) in self.__currentState.mapInfo.doorProgress: if (x, y) in self.__currentState.mapInfo.doorProgress:
return self.__currentState.mapInfo.doorProgress[(x, y)]
return copy.deepcopy(self.__currentState.mapInfo.doorProgress[(x, y)])
else: else:
self.__logger.warning("Door not found") self.__logger.warning("Door not found")
return 0 return 0
@@ -158,7 +164,7 @@ class Logic(ILogic):
def GetGameInfo(self) -> THUAI6.GameInfo: def GetGameInfo(self) -> THUAI6.GameInfo:
with self.__mtxState: with self.__mtxState:
self.__logger.debug("Called GetGameInfo") self.__logger.debug("Called GetGameInfo")
return self.__currentState.gameInfo
return copy.deepcopy(self.__currentState.gameInfo)


def Move(self, time: int, angle: float) -> bool: def Move(self, time: int, angle: float) -> bool:
self.__logger.debug("Called Move") self.__logger.debug("Called Move")
@@ -172,6 +178,10 @@ class Logic(ILogic):
self.__logger.debug("Called UseProp") self.__logger.debug("Called UseProp")
return self.__comm.UseProp(propType, self.__playerID) return self.__comm.UseProp(propType, self.__playerID)


def ThrowProp(self, propType: THUAI6.PropType) -> bool:
self.__logger.debug("Called ThrowProp")
return self.__comm.ThrowProp(propType, self.__playerID)

def UseSkill(self, skillID: int) -> bool: def UseSkill(self, skillID: int) -> bool:
self.__logger.debug("Called UseSkill") self.__logger.debug("Called UseSkill")
return self.__comm.UseSkill(skillID, self.__playerID) return self.__comm.UseSkill(skillID, self.__playerID)
@@ -198,11 +208,11 @@ class Logic(ILogic):


def GetCounter(self) -> int: def GetCounter(self) -> int:
with self.__mtxState: with self.__mtxState:
return self.__counterState
return copy.deepcopy(self.__counterState)


def GetPlayerGUIDs(self) -> List[int]: def GetPlayerGUIDs(self) -> List[int]:
with self.__mtxState: with self.__mtxState:
return self.__playerGUIDs
return copy.deepcopy(self.__playerGUIDs)


# IStudentAPI使用的接口 # IStudentAPI使用的接口


@@ -214,13 +224,13 @@ class Logic(ILogic):
self.__logger.debug("Called StartLearning") self.__logger.debug("Called StartLearning")
return self.__comm.StartLearning(self.__playerID) return self.__comm.StartLearning(self.__playerID)


def StartTreatMate(self, mateID: int) -> bool:
self.__logger.debug("Called StartTreatMate")
return self.__comm.StartTreatMate(self.__playerID, mateID)
def StartEncourageMate(self, mateID: int) -> bool:
self.__logger.debug("Called StartEncourageMate")
return self.__comm.StartEncourageMate(self.__playerID, mateID)


def StartRescueMate(self, mateID: int) -> bool:
self.__logger.debug("Called StartRescueMate")
return self.__comm.StartRescueMate(self.__playerID, mateID)
def StartRouseMate(self, mateID: int) -> bool:
self.__logger.debug("Called StartRouseMate")
return self.__comm.StartRouseMate(self.__playerID, mateID)


def Attack(self, angle: float) -> bool: def Attack(self, angle: float) -> bool:
self.__logger.debug("Called Trick") self.__logger.debug("Called Trick")
@@ -348,6 +358,8 @@ class Logic(ILogic):
self.__logger.debug("Add Student!") self.__logger.debug("Add Student!")
for item in message.obj_message: for item in message.obj_message:
if item.WhichOneof("message_of_obj") == "tricker_message": if item.WhichOneof("message_of_obj") == "tricker_message":
if MessageType.TRICKER_INVISIBLE in item.tricker_message.buff:
continue
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( self.__bufferState.trickers.append(
Proto2THUAI6.Protobuf2THUAI6Tricker(item.tricker_message)) Proto2THUAI6.Protobuf2THUAI6Tricker(item.tricker_message))
@@ -440,6 +452,13 @@ class Logic(ILogic):
self.__logger.debug("Add Tricker!") self.__logger.debug("Add Tricker!")
for item in message.obj_message: for item in message.obj_message:
if item.WhichOneof("message_of_obj") == "student_message": if 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))
self.__logger.debug("Add Student!")
continue
if MessageType.STUDENT_INVISIBLE in item.student_message.buff:
continue
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( self.__bufferState.students.append(
Proto2THUAI6.Protobuf2THUAI6Student(item.student_message)) Proto2THUAI6.Protobuf2THUAI6Student(item.student_message))


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

@@ -39,7 +39,6 @@ def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None:
file = args.file file = args.file
screen = args.screen screen = args.screen
warnOnly = args.warnOnly warnOnly = args.warnOnly
print(warnOnly)
logic = Logic(pID) logic = Logic(pID)
logic.Main(AIBuilder, sIP, sPort, file, screen, warnOnly) logic.Main(AIBuilder, sIP, sPort, file, screen, warnOnly)




+ 9
- 7
CAPI/python/PyAPI/structures.py View File

@@ -7,6 +7,7 @@ if sys.version_info < (3, 9):
else: else:
Tuple = tuple Tuple = tuple



class GameState(Enum): class GameState(Enum):
NullGameState = 0 NullGameState = 0
GameStart = 1 GameStart = 1
@@ -76,6 +77,7 @@ class TrickerType(Enum):
Assassin = 1 Assassin = 1
Klee = 2 Klee = 2
ANoisyPerson = 3 ANoisyPerson = 3
Idol = 4




class StudentBuffType(Enum): class StudentBuffType(Enum):
@@ -102,11 +104,11 @@ class PlayerState(Enum):
Addicted = 3 Addicted = 3
Quit = 4 Quit = 4
Graduated = 5 Graduated = 5
Treated = 6
Rescued = 7
Encouraged = 6
Roused = 7
Stunned = 8 Stunned = 8
Treating = 9
Rescuing = 10
Encouraging = 9
Rousing = 10
Swinging = 11 Swinging = 11
Attacking = 12 Attacking = 12
Locking = 13 Locking = 13
@@ -161,10 +163,10 @@ class Student(Player):
studentType: StudentType studentType: StudentType
determination: int determination: int
addiction: int addiction: int
treatProgress: int
rescueProgress: int
encourageProgress: int
rouseProgress: int
learningSpeed: int learningSpeed: int
treatSpeed: int
encourageSpeed: int
dangerAlert: float dangerAlert: float
buff: List[StudentBuffType] = [] buff: List[StudentBuffType] = []




+ 11
- 9
CAPI/python/PyAPI/utils.py View File

@@ -111,7 +111,8 @@ class Proto2THUAI6(NoInstance):
MessageType.NULL_TRICKER_TYPE: THUAI6.TrickerType.NullTrickerType, MessageType.NULL_TRICKER_TYPE: THUAI6.TrickerType.NullTrickerType,
MessageType.ASSASSIN: THUAI6.TrickerType.Assassin, MessageType.ASSASSIN: THUAI6.TrickerType.Assassin,
MessageType.KLEE: THUAI6.TrickerType.Klee, MessageType.KLEE: THUAI6.TrickerType.Klee,
MessageType.A_NOISY_PERSON: THUAI6.TrickerType.ANoisyPerson, }
MessageType.A_NOISY_PERSON: THUAI6.TrickerType.ANoisyPerson,
MessageType.IDOL: THUAI6.TrickerType.Idol, }


studentBuffTypeDict: Final[dict] = { studentBuffTypeDict: Final[dict] = {
MessageType.NULL_SBUFF_TYPE: THUAI6.StudentBuffType.NullStudentBuffType, MessageType.NULL_SBUFF_TYPE: THUAI6.StudentBuffType.NullStudentBuffType,
@@ -135,11 +136,11 @@ class Proto2THUAI6(NoInstance):
MessageType.ADDICTED: THUAI6.PlayerState.Addicted, MessageType.ADDICTED: THUAI6.PlayerState.Addicted,
MessageType.QUIT: THUAI6.PlayerState.Quit, MessageType.QUIT: THUAI6.PlayerState.Quit,
MessageType.GRADUATED: THUAI6.PlayerState.Graduated, MessageType.GRADUATED: THUAI6.PlayerState.Graduated,
MessageType.RESCUED: THUAI6.PlayerState.Rescued,
MessageType.TREATED: THUAI6.PlayerState.Treated,
MessageType.RESCUED: THUAI6.PlayerState.Roused,
MessageType.TREATED: THUAI6.PlayerState.Encouraged,
MessageType.STUNNED: THUAI6.PlayerState.Stunned, MessageType.STUNNED: THUAI6.PlayerState.Stunned,
MessageType.RESCUING: THUAI6.PlayerState.Rescuing,
MessageType.TREATING: THUAI6.PlayerState.Treating,
MessageType.RESCUING: THUAI6.PlayerState.Rousing,
MessageType.TREATING: THUAI6.PlayerState.Encouraging,
MessageType.SWINGING: THUAI6.PlayerState.Swinging, MessageType.SWINGING: THUAI6.PlayerState.Swinging,
MessageType.ATTACKING: THUAI6.PlayerState.Attacking, MessageType.ATTACKING: THUAI6.PlayerState.Attacking,
MessageType.LOCKING: THUAI6.PlayerState.Locking, MessageType.LOCKING: THUAI6.PlayerState.Locking,
@@ -206,9 +207,9 @@ class Proto2THUAI6(NoInstance):
student.facingDirection = studentMsg.facing_direction student.facingDirection = studentMsg.facing_direction
student.bulletType = Proto2THUAI6.bulletTypeDict[studentMsg.bullet_type] student.bulletType = Proto2THUAI6.bulletTypeDict[studentMsg.bullet_type]
student.learningSpeed = studentMsg.learning_speed student.learningSpeed = studentMsg.learning_speed
student.treatSpeed = studentMsg.treat_speed
student.treatProgress = studentMsg.treat_progress
student.rescueProgress = studentMsg.rescue_progress
student.encourageSpeed = studentMsg.treat_speed
student.encourageProgress = studentMsg.treat_progress
student.rouseProgress = studentMsg.rescue_progress
student.dangerAlert = studentMsg.danger_alert student.dangerAlert = studentMsg.danger_alert
student.timeUntilSkillAvailable.clear() student.timeUntilSkillAvailable.clear()
for time in studentMsg.time_until_skill_available: for time in studentMsg.time_until_skill_available:
@@ -314,7 +315,8 @@ class THUAI62Proto(NoInstance):
THUAI6.TrickerType.NullTrickerType: MessageType.NULL_TRICKER_TYPE, THUAI6.TrickerType.NullTrickerType: MessageType.NULL_TRICKER_TYPE,
THUAI6.TrickerType.Assassin: MessageType.ASSASSIN, THUAI6.TrickerType.Assassin: MessageType.ASSASSIN,
THUAI6.TrickerType.Klee: MessageType.KLEE, THUAI6.TrickerType.Klee: MessageType.KLEE,
THUAI6.TrickerType.ANoisyPerson: MessageType.A_NOISY_PERSON, }
THUAI6.TrickerType.ANoisyPerson: MessageType.A_NOISY_PERSON,
THUAI6.TrickerType.Idol: MessageType.IDOL, }


propTypeDict: Final[dict] = { propTypeDict: Final[dict] = {
THUAI6.PropType.NullPropType: MessageType.NULL_PROP_TYPE, THUAI6.PropType.NullPropType: MessageType.NULL_PROP_TYPE,


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

@@ -1,4 +1,6 @@
#!/usr/bin/env bash #!/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 0 -d -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 1 -d &
# python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2 -d &
# python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3 -d &

+ 1
- 1
dependency/proto/MessageType.proto View File

@@ -118,7 +118,7 @@ enum TrickerType
ASSASSIN = 1; ASSASSIN = 1;
KLEE = 2; KLEE = 2;
A_NOISY_PERSON = 3; A_NOISY_PERSON = 3;
TRICKERTYPE4 = 4;
IDOL = 4;
} }


// 游戏进行状态 // 游戏进行状态


+ 56
- 0
installer/Installer/Common.cs View File

@@ -3,6 +3,8 @@ using System;
using System.Windows.Input; using System.Windows.Input;
using System.Globalization; using System.Globalization;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Controls;
using System.Windows;


namespace starter.viewmodel.common namespace starter.viewmodel.common
{ {
@@ -98,4 +100,58 @@ namespace starter.viewmodel.common
} }
} }


/// <summary>
/// Password 附加属性,来自https://blog.csdn.net/qq_43562262/article/details/121786337
/// </summary>
public class PasswordHelper
{
public static readonly DependencyProperty PasswordProperty = DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PasswordHelper),
new PropertyMetadata(new PropertyChangedCallback(OnPropertyChanged)));

public static string GetPassword(DependencyObject d)
{
return (string)d.GetValue(PasswordProperty);
}
public static void SetPassword(DependencyObject d, string value)
{
d.SetValue(PasswordProperty, value);
}

public static readonly DependencyProperty AttachProperty = DependencyProperty.RegisterAttached("Attach", typeof(string), typeof(PasswordHelper),
new PropertyMetadata(new PropertyChangedCallback(OnAttachChanged)));

public static string GetAttach(DependencyObject d)
{
return (string)d.GetValue(AttachProperty);
}
public static void SetAttach(DependencyObject d, string value)
{
d.SetValue(AttachProperty, value);
}

static bool _isUpdating = false;
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox pb = d as PasswordBox;
pb.PasswordChanged -= Pb_PasswordChanged;
if (!_isUpdating)
(d as PasswordBox).Password = e.NewValue.ToString();
pb.PasswordChanged += Pb_PasswordChanged;
}

private static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox pb = d as PasswordBox;
pb.PasswordChanged += Pb_PasswordChanged;
}

private static void Pb_PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox pb = sender as PasswordBox;
_isUpdating = true;
SetPassword(pb, pb.Password);
_isUpdating = false;
}
}

} }

+ 1
- 1
installer/Installer/MainWindow.xaml View File

@@ -71,7 +71,7 @@
<TextBlock Grid.Row="1" Grid.Column="0" Text="账号:" Visibility="{Binding LoginVis}" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="账号:" Visibility="{Binding LoginVis}" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="密码:" Visibility="{Binding LoginVis}" /> <TextBlock Grid.Row="3" Grid.Column="0" Text="密码:" Visibility="{Binding LoginVis}" />
<TextBox Grid.Row="1" Grid.Column="1" Name="Username" Visibility="{Binding LoginVis}" Text="{Binding Username}" /> <TextBox Grid.Row="1" Grid.Column="1" Name="Username" Visibility="{Binding LoginVis}" Text="{Binding Username}" />
<TextBox Grid.Row="3" Grid.Column="1" Name="Password" Visibility="{Binding LoginVis}" Text="{Binding Password}" />
<PasswordBox Grid.Row="3" Grid.Column="1" Name="Password" Visibility="{Binding LoginVis}" c:PasswordHelper.Attach="True" c:PasswordHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<!--<CheckBox Grid.Row="5" Grid.Column="0" Visibility="{Binding LoginVis}">记住我</CheckBox>--> <!--<CheckBox Grid.Row="5" Grid.Column="0" Visibility="{Binding LoginVis}">记住我</CheckBox>-->
<TextBlock Grid.Row="5" Grid.Column="1" Foreground="Red" Text=" 用户名或密码错误!" Visibility="{Binding LoginFailVis}"/> <TextBlock Grid.Row="5" Grid.Column="1" Foreground="Red" Text=" 用户名或密码错误!" Visibility="{Binding LoginFailVis}"/>
</Grid> </Grid>


+ 39
- 32
installer/Installer/Model.cs View File

@@ -124,7 +124,7 @@ namespace starter.viewmodel.settings
} }
} }


public async Task<bool> Login()
public async Task<int> Login()
{ {
return await web.LoginToEEsast(client, Username, Password); return await web.LoginToEEsast(client, Username, Password);
} }
@@ -1058,43 +1058,50 @@ namespace WebConnect
{ {
public enum language { cpp, py }; public enum language { cpp, py };
public static string logintoken = ""; public static string logintoken = "";
async public Task<bool> LoginToEEsast(HttpClient client, string useremail, string password)
async public Task<int> LoginToEEsast(HttpClient client, string useremail, string password)
{ {
string token = ""; string token = "";
using (var response = await client.PostAsync("https://api.eesast.com/users/login", JsonContent.Create(new
{
email = useremail,
password = password,
})))
try
{ {
switch (response.StatusCode)
using (var response = await client.PostAsync("https://api.eesast.com/users/login", JsonContent.Create(new
{ {
case System.Net.HttpStatusCode.OK:
Console.WriteLine("Success login");
token = (System.Text.Json.JsonSerializer.Deserialize(await response.Content.ReadAsStreamAsync(), typeof(LoginResponse), new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
}) as LoginResponse)
?.Token ??
throw new Exception("no token!");
logintoken = token;
SaveToken();
var info = JsonConvert.DeserializeObject<Dictionary<string, string>>(await response.Content.ReadAsStringAsync());
Downloader.UserInfo._id = info["_id"];
Downloader.UserInfo.email = info["email"];
break;
email = useremail,
password = password,
})))
{
switch (response.StatusCode)
{
case System.Net.HttpStatusCode.OK:
Console.WriteLine("Success login");
token = (System.Text.Json.JsonSerializer.Deserialize(await response.Content.ReadAsStreamAsync(), typeof(LoginResponse), new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
}) as LoginResponse)
?.Token ??
throw new Exception("no token!");
logintoken = token;
SaveToken();
var info = JsonConvert.DeserializeObject<Dictionary<string, string>>(await response.Content.ReadAsStringAsync());
Downloader.UserInfo._id = info["_id"];
Downloader.UserInfo.email = info["email"];
break;


default:
int code = ((int)response.StatusCode);
Console.WriteLine(code);
if (code == 401)
{
//Console.WriteLine("邮箱或密码错误!");
return false;
}
break;
default:
int code = ((int)response.StatusCode);
Console.WriteLine(code);
if (code == 401)
{
//Console.WriteLine("邮箱或密码错误!");
return -1;
}
break;
}
return 0;
} }
return true;
}
catch
{
return -2;
} }
} }
/// <summary> /// <summary>


+ 12
- 8
installer/Installer/ViewModel.cs View File

@@ -481,15 +481,19 @@ namespace starter.viewmodel.settings
{ {
clickLoginCommand = new BaseCommand(new Action<object>(async o => clickLoginCommand = new BaseCommand(new Action<object>(async o =>
{ {
if (!(await obj.Login()))
switch (await obj.Login())
{ {
obj.LoginFailed = true;
}
else
{
obj.LoginFailed = false;
Status = SettingsModel.Status.web;
this.RaisePropertyChanged("CoverVis");
case -1:
obj.LoginFailed = true;
break;
case 0:
obj.LoginFailed = false;
Status = SettingsModel.Status.web;
this.RaisePropertyChanged("CoverVis");
break;
case -2:
MessageBox.Show("无法连接服务器,请检查网络情况", "网络错误", MessageBoxButton.OK, MessageBoxImage.Warning, MessageBoxResult.OK);
break;
} }
this.RaisePropertyChanged("LoginFailVis"); this.RaisePropertyChanged("LoginFailVis");
})); }));


+ 2
- 0
logic/.gitignore View File

@@ -400,3 +400,5 @@ FodyWeavers.xsd
#THUAI playback file #THUAI playback file
*.thuaipb *.thuaipb


#private cmd
cmd/gameServerOfSanford.cmd

+ 1
- 1
logic/Client/CommandLineArgs.cs View File

@@ -36,7 +36,7 @@ namespace Client
public string PlaybackFile { get; set; } = DefaultArgumentOptions.FileName; public string PlaybackFile { get; set; } = DefaultArgumentOptions.FileName;


[Option("playbackSpeed", Required = false, HelpText = "The speed of the playback, between 0.25 and 4.0")] [Option("playbackSpeed", Required = false, HelpText = "The speed of the playback, between 0.25 and 4.0")]
public double PlaybackSpeed { get; set; } = 1.0;
public double PlaybackSpeed { get; set; } = 2.0;


} }
} }

+ 204
- 147
logic/Client/MainWindow.xaml.cs View File

@@ -241,7 +241,7 @@ namespace Client
playerMsg.TrickerType = TrickerType.ANoisyPerson; playerMsg.TrickerType = TrickerType.ANoisyPerson;
break; break;
case 4: case 4:
playerMsg.TrickerType = TrickerType._4;
playerMsg.TrickerType = TrickerType.Idol;
break; break;
case 0: case 0:
default: default:
@@ -413,19 +413,6 @@ namespace Client
{ {
human = obj.StudentMessage; human = obj.StudentMessage;
} }
if (obj.StudentMessage.PlayerId < GameData.numOfStudent)
{
IStudentType occupation = (IStudentType)OccupationFactory.FindIOccupation(Transformation.ToStudentType(obj.StudentMessage.StudentType));
totalLife[obj.StudentMessage.PlayerId] = occupation.MaxHp;
totalDeath[obj.StudentMessage.PlayerId] = occupation.MaxGamingAddiction;
int i = 0;
foreach (var skill in occupation.ListOfIActiveSkill)
{
var iActiveSkill = SkillFactory.FindIActiveSkill(skill);
coolTime[i, obj.StudentMessage.PlayerId] = iActiveSkill.SkillCD;
++i;
}
}
listOfHuman.Add(obj.StudentMessage); listOfHuman.Add(obj.StudentMessage);
break; break;
case MessageOfObj.MessageOfObjOneofCase.TrickerMessage: case MessageOfObj.MessageOfObjOneofCase.TrickerMessage:
@@ -433,14 +420,6 @@ namespace Client
{ {
butcher = obj.TrickerMessage; butcher = obj.TrickerMessage;
} }
IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerMessage.TrickerType));
int j = 0;
foreach (var skill in occupation1.ListOfIActiveSkill)
{
var iActiveSkill = SkillFactory.FindIActiveSkill(skill);
coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD;
++j;
}
listOfButcher.Add(obj.TrickerMessage); listOfButcher.Add(obj.TrickerMessage);
break; break;
case MessageOfObj.MessageOfObjOneofCase.PropMessage: case MessageOfObj.MessageOfObjOneofCase.PropMessage:
@@ -514,11 +493,15 @@ namespace Client
case MessageOfObj.MessageOfObjOneofCase.HiddenGateMessage: case MessageOfObj.MessageOfObjOneofCase.HiddenGateMessage:
listOfHiddenGate.Add(obj.HiddenGateMessage); listOfHiddenGate.Add(obj.HiddenGateMessage);
break; break;
case MessageOfObj.MessageOfObjOneofCase.MapMessage:
GetMap(obj.MapMessage);
break;
} }
} }
listOfAll.Add(content.AllMessage); listOfAll.Add(content.AllMessage);
break; break;
case GameState.GameEnd: case GameState.GameEnd:
MessageBox.Show("Game Over!");
foreach (var obj in content.ObjMessage) foreach (var obj in content.ObjMessage)
{ {
switch (obj.MessageOfObjCase) switch (obj.MessageOfObjCase)
@@ -581,23 +564,16 @@ namespace Client
return true; return true;
if (humanOrButcher && human != null) if (humanOrButcher && human != null)
{ {
if (human.Guid == msg.Guid) // 自己能看见自己
if (msg.Place == human.Place)
return true; return true;
} }
if (msg.Place == Protobuf.PlaceType.Grass || msg.Place == Protobuf.PlaceType.Gate || msg.Place == Protobuf.PlaceType.HiddenGate)
return false;
if (msg.Place == Protobuf.PlaceType.Land || msg.Place == Protobuf.PlaceType.Classroom)
return true;
if (humanOrButcher && human != null)
{
if (msg.Place != human.Place)
return false;
}
else if (!humanOrButcher && butcher != null) else if (!humanOrButcher && butcher != null)
{ {
if (msg.Place != butcher.Place)
return false;
if (msg.Place == butcher.Place)
return true;
} }
if (msg.Place == Protobuf.PlaceType.Grass)
return false;
return true; return true;
} }


@@ -610,20 +586,21 @@ namespace Client
if (butcher.Guid == msg.Guid) // 自己能看见自己 if (butcher.Guid == msg.Guid) // 自己能看见自己
return true; return true;
} }
if (msg.Place == Protobuf.PlaceType.Grass || msg.Place == Protobuf.PlaceType.Gate || msg.Place == Protobuf.PlaceType.HiddenGate)
return false;
if (msg.Place == Protobuf.PlaceType.Land || msg.Place == Protobuf.PlaceType.Classroom)
return true;
if (humanOrButcher && human != null) if (humanOrButcher && human != null)
{ {
if (msg.Place != human.Place)
return false;
}
else if (!humanOrButcher && butcher != null)
{
if (msg.Place != butcher.Place)
return false;
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; return true;
} }


@@ -631,18 +608,18 @@ namespace Client
{ {
if (isSpectatorMode) if (isSpectatorMode)
return true; return true;
if (msg.Place == Protobuf.PlaceType.Land)
return true;
if (humanOrButcher && human != null) if (humanOrButcher && human != null)
{ {
if (msg.Place != human.Place)
return false;
if (msg.Place == human.Place)
return true;
} }
else if (!humanOrButcher && butcher != null) else if (!humanOrButcher && butcher != null)
{ {
if (msg.Place != butcher.Place)
return false;
if (msg.Place == butcher.Place)
return true;
} }
if (msg.Place == Protobuf.PlaceType.Grass)
return false;
return true; return true;
} }


@@ -650,70 +627,117 @@ namespace Client
{ {
if (isSpectatorMode) if (isSpectatorMode)
return true; return true;
if (msg.Place == Protobuf.PlaceType.Land)
return true;
if (humanOrButcher && human != null) if (humanOrButcher && human != null)
{ {
if (msg.Place != human.Place)
return false;
if (msg.Place == human.Place)
return true;
} }
else if (!humanOrButcher && butcher != null) else if (!humanOrButcher && butcher != null)
{ {
if (msg.Place != butcher.Place)
return false;
if (msg.Place == butcher.Place)
return true;
} }
if (msg.Place == Protobuf.PlaceType.Grass)
return false;
return true;
}

private bool CanSee(MessageOfBombedBullet msg)
{
if (isSpectatorMode)
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; return true;
} }


private void Refresh(object? sender, EventArgs e) //log未更新 private void Refresh(object? sender, EventArgs e) //log未更新
{ {
// Bonus();
if (WindowState == WindowState.Maximized)
MaxButton.Content = "❐";
else
MaxButton.Content = "🗖";
if (StatusBarsOfSurvivor != null)
lock (drawPicLock) // 加锁是必要的,画图操作和接收信息操作不能同时进行
{ {
for (int i = 0; i < GameData.numOfStudent; i++)
// Bonus();
if (WindowState == WindowState.Maximized)
MaxButton.Content = "❐";
else
MaxButton.Content = "🗖";
foreach (var obj in listOfHuman)
{ {
StatusBarsOfSurvivor[i].SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime);
if (obj.PlayerId < GameData.numOfStudent)
{
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.FindIActiveSkill(skill);
coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD;
++i;
}
}
} }
}
if (StatusBarsOfHunter != null)
{
StatusBarsOfHunter.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime);
}
if (StatusBarsOfCircumstance != null)
StatusBarsOfCircumstance.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
// 完成窗口信息更新
if (!isClientStocked)
{
unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50;
unitHeight = UpperLayerOfMap.ActualHeight / 50;
unitWidth = UpperLayerOfMap.ActualWidth / 50;
try
foreach (var obj in listOfButcher)
{
IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType));
int j = 0;
foreach (var skill in occupation1.ListOfIActiveSkill)
{
var iActiveSkill = SkillFactory.FindIActiveSkill(skill);
coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD;
++j;
}
}
if (StatusBarsOfSurvivor != null)
{
for (int i = 0; i < GameData.numOfStudent; i++)
{
StatusBarsOfSurvivor[i].SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime);
}
}
if (StatusBarsOfHunter != null)
{ {
// if (log != null)
//{
// string temp = "";
// for (int i = 0; i < dataDict[GameObjType.Character].Count; i++)
// {
// temp += Convert.ToString(dataDict[GameObjType.Character][i].MessageOfCharacter.TeamID) + "\n";
// }
// log.Content = temp;
// }
UpperLayerOfMap.Children.Clear();
// if ((communicator == null || !communicator.Client.IsConnected) && !isPlaybackMode)
//{
// UnderLayerOfMap.Children.Clear();
// throw new Exception("Client is unconnected.");
// }
// else
//{
lock (drawPicLock) // 加锁是必要的,画图操作和接收信息操作不能同时进行
StatusBarsOfHunter.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime);
}
if (StatusBarsOfCircumstance != null)
StatusBarsOfCircumstance.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
// 完成窗口信息更新
if (!isClientStocked)
{
unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50;
unitHeight = UpperLayerOfMap.ActualHeight / 50;
unitWidth = UpperLayerOfMap.ActualWidth / 50;
try
{ {
// if (log != null)
//{
// string temp = "";
// for (int i = 0; i < dataDict[GameObjType.Character].Count; i++)
// {
// temp += Convert.ToString(dataDict[GameObjType.Character][i].MessageOfCharacter.TeamID) + "\n";
// }
// log.Content = temp;
// }
UpperLayerOfMap.Children.Clear();
// if ((communicator == null || !communicator.Client.IsConnected) && !isPlaybackMode)
//{
// UnderLayerOfMap.Children.Clear();
// throw new Exception("Client is unconnected.");
// }
// else
//{

foreach (var data in listOfAll) foreach (var data in listOfAll)
{ {
StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID); StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID);
@@ -734,7 +758,22 @@ namespace Client
Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth * radiusTimes, data.X * unitHeight / 1000.0 - unitHeight * radiusTimes, 0, 0), Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth * radiusTimes, data.X * unitHeight / 1000.0 - unitHeight * radiusTimes, 0, 0),
Fill = Brushes.BlueViolet, Fill = Brushes.BlueViolet,
}; };
TextBox num = new()
{
FontSize = 7 * UpperLayerOfMap.ActualHeight / 650,
Width = 2 * radiusTimes * unitWidth,
Height = 2 * radiusTimes * unitHeight,
Text = Convert.ToString(data.PlayerId),
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,
};
UpperLayerOfMap.Children.Add(icon); UpperLayerOfMap.Children.Add(icon);
UpperLayerOfMap.Children.Add(num);
} }
} }
foreach (var data in listOfButcher) foreach (var data in listOfButcher)
@@ -796,11 +835,11 @@ namespace Client
{ {
Ellipse icon = new() Ellipse icon = new()
{ {
Width = 10,
Height = 10,
Width = 2 * bulletRadiusTimes * unitWidth,
Height = 2 * bulletRadiusTimes * unitHeight,
HorizontalAlignment = HorizontalAlignment.Left, HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top, VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0),
Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth * bulletRadiusTimes, data.X * unitHeight / 1000.0 - unitHeight * bulletRadiusTimes, 0, 0),
Fill = Brushes.Red, Fill = Brushes.Red,
}; };
switch (data.Type) switch (data.Type)
@@ -821,42 +860,49 @@ namespace Client
} }
foreach (var data in listOfBombedBullet) foreach (var data in listOfBombedBullet)
{ {
switch (data.Type)
if (CanSee(data))
{ {
case Protobuf.BulletType.BombBomb:
{
Ellipse icon = new();
double bombRange = data.BombRange / 1000;
icon.Width = bombRange * unitWidth;
icon.Height = bombRange * unitHeight;
icon.HorizontalAlignment = HorizontalAlignment.Left;
icon.VerticalAlignment = VerticalAlignment.Top;
icon.Margin = new Thickness(data.Y * unitWidth / 1000.0 - bombRange * unitWidth / 2, data.X * unitHeight / 1000.0 - bombRange * unitHeight / 2, 0, 0);
icon.Fill = Brushes.Red;
UpperLayerOfMap.Children.Add(icon);
break;
}
case Protobuf.BulletType.JumpyDumpty:
{
Ellipse icon = new Ellipse();
double bombRange = data.BombRange / 1000;
icon.Width = bombRange * unitWidth;
icon.Height = bombRange * unitHeight;
icon.HorizontalAlignment = HorizontalAlignment.Left;
icon.VerticalAlignment = VerticalAlignment.Top;
icon.Margin = new Thickness(data.Y * unitWidth / 1000.0 - bombRange * unitWidth / 2, data.X * unitHeight / 1000.0 - bombRange * unitHeight / 2, 0, 0);
icon.Fill = Brushes.Red;
UpperLayerOfMap.Children.Add(icon);
switch (data.Type)
{
case Protobuf.BulletType.BombBomb:
{
double bombRange = 1.0 * data.BombRange / Preparation.Utility.GameData.numOfPosGridPerCell;
Ellipse icon = new()
{
Width = 2 * bombRange * unitWidth,
Height = 2 * bombRange * unitHeight,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth * bombRange, data.X * unitHeight / 1000.0 - unitHeight * bombRange, 0, 0),
Fill = Brushes.DarkRed,
};
UpperLayerOfMap.Children.Add(icon);
break;
}
case Protobuf.BulletType.JumpyDumpty:
{
double bombRange = 1.0 * data.BombRange / Preparation.Utility.GameData.numOfPosGridPerCell;
Ellipse icon = new()
{
Width = 2 * bombRange * unitWidth,
Height = 2 * bombRange * unitHeight,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth * bombRange, data.X * unitHeight / 1000.0 - unitHeight * bombRange, 0, 0),
Fill = Brushes.DarkRed,
};
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; 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;
}
} }
} }
foreach (var data in listOfClassroom) foreach (var data in listOfClassroom)
@@ -978,17 +1024,18 @@ namespace Client
} }
//} //}
ZoomMap(); ZoomMap();

}
catch (Exception exc)
{
ErrorDisplayer error = new("Error: " + exc.ToString());
error.Show();
isClientStocked = true;
PorC.Content = "▶";
} }
} }
catch (Exception exc)
{
ErrorDisplayer error = new("Error: " + exc.ToString());
error.Show();
isClientStocked = true;
PorC.Content = "▶";
}
counter++;
} }
counter++;
} }


// 键盘控制,未完善 // 键盘控制,未完善
@@ -1210,10 +1257,18 @@ namespace Client
isClientStocked = true; isClientStocked = true;
PorC.Content = "▶"; PorC.Content = "▶";
} }
else
else if (!isPlaybackMode)
{ {
isClientStocked = false;
PorC.Content = "⏸";
try
{
isClientStocked = false;
PorC.Content = "⏸";
}
catch (Exception ex)
{
ErrorDisplayer error = new("发生错误。以下是系统报告:\n" + ex.ToString());
error.Show();
}
} }
} }
// 未复现 // 未复现
@@ -1407,7 +1462,9 @@ namespace Client
bool isSpectatorMode = false; bool isSpectatorMode = false;
bool isEmergencyOpened = false; bool isEmergencyOpened = false;
bool isEmergencyDrawed = false; bool isEmergencyDrawed = false;
bool isDataFixed = false;
const double radiusTimes = 1.0 * Preparation.Utility.GameData.characterRadius / Preparation.Utility.GameData.numOfPosGridPerCell; const double radiusTimes = 1.0 * Preparation.Utility.GameData.characterRadius / Preparation.Utility.GameData.numOfPosGridPerCell;
const double bulletRadiusTimes = 1.0 * GameData.bulletRadius / Preparation.Utility.GameData.numOfPosGridPerCell;
private int[] totalLife = new int[4] { 100, 100, 100, 100 }, totalDeath = new int[4] { 100, 100, 100, 100 }; 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 } }; private int[,] coolTime = new int[3, 5] { { 100, 100, 100, 100, 100 }, { 100, 100, 100, 100, 100 }, { 100, 100, 100, 100, 100 } };
} }


+ 173
- 161
logic/Client/PlaybackClient.cs View File

@@ -7,6 +7,7 @@ using Protobuf;
using Playback; using Playback;
using System.Threading; using System.Threading;
using Timothy.FrameRateTask; using Timothy.FrameRateTask;
using System.Windows;


namespace Client namespace Client
{ {
@@ -79,183 +80,194 @@ namespace Client
} }
} }
}; };

new Thread(() => new Thread(() =>
{ {
new FrameRateTaskExecutor<int>
(
() => !endFile,
() =>
{
var content = Reader.ReadOne();
if (content == null)
endFile = true;
else
int i = 0;
try
{
new FrameRateTaskExecutor<int>
(
() => !endFile,
() =>
{ {
lock (dataLock)
var content = Reader.ReadOne();
i++;
if (content == null)
{
MessageBox.Show($"End! {i}");
endFile = true;
}
else
{ {
listOfHuman.Clear();
listOfButcher.Clear();
listOfProp.Clear();
listOfBombedBullet.Clear();
listOfBullet.Clear();
listOfAll.Clear();
listOfChest.Clear();
listOfClassroom.Clear();
listOfDoor.Clear();
listOfHiddenGate.Clear();
listOfGate.Clear();
switch (content.GameState)
lock (dataLock)
{ {
case GameState.GameStart:
foreach (var obj in content.ObjMessage)
{
switch (obj.MessageOfObjCase)
listOfHuman.Clear();
listOfButcher.Clear();
listOfProp.Clear();
listOfBombedBullet.Clear();
listOfBullet.Clear();
listOfAll.Clear();
listOfChest.Clear();
listOfClassroom.Clear();
listOfDoor.Clear();
listOfHiddenGate.Clear();
listOfGate.Clear();
switch (content.GameState)
{
case GameState.GameStart:
foreach (var obj in content.ObjMessage)
{ {
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:
listOfProp.Add(obj.PropMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BombedBulletMessage:
listOfBombedBullet.Add(obj.BombedBulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BulletMessage:
listOfBullet.Add(obj.BulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ChestMessage:
listOfChest.Add(obj.ChestMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ClassroomMessage:
listOfClassroom.Add(obj.ClassroomMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.DoorMessage:
listOfDoor.Add(obj.DoorMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.GateMessage:
listOfGate.Add(obj.GateMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.HiddenGateMessage:
listOfHiddenGate.Add(obj.HiddenGateMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.MapMessage:
break;
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:
listOfProp.Add(obj.PropMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BombedBulletMessage:
listOfBombedBullet.Add(obj.BombedBulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BulletMessage:
listOfBullet.Add(obj.BulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ChestMessage:
listOfChest.Add(obj.ChestMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ClassroomMessage:
listOfClassroom.Add(obj.ClassroomMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.DoorMessage:
listOfDoor.Add(obj.DoorMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.GateMessage:
listOfGate.Add(obj.GateMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.HiddenGateMessage:
listOfHiddenGate.Add(obj.HiddenGateMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.MapMessage:
break;
}
} }
}
listOfAll.Add(content.AllMessage);
break;
case GameState.GameRunning:
foreach (var obj in content.ObjMessage)
{
switch (obj.MessageOfObjCase)
listOfAll.Add(content.AllMessage);
break;
case GameState.GameRunning:
foreach (var obj in content.ObjMessage)
{ {
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:
listOfProp.Add(obj.PropMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BombedBulletMessage:
listOfBombedBullet.Add(obj.BombedBulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BulletMessage:
listOfBullet.Add(obj.BulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ChestMessage:
listOfChest.Add(obj.ChestMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ClassroomMessage:
listOfClassroom.Add(obj.ClassroomMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.DoorMessage:
listOfDoor.Add(obj.DoorMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.HiddenGateMessage:
listOfHiddenGate.Add(obj.HiddenGateMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.GateMessage:
listOfGate.Add(obj.GateMessage);
break;
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:
listOfProp.Add(obj.PropMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BombedBulletMessage:
listOfBombedBullet.Add(obj.BombedBulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BulletMessage:
listOfBullet.Add(obj.BulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ChestMessage:
listOfChest.Add(obj.ChestMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ClassroomMessage:
listOfClassroom.Add(obj.ClassroomMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.DoorMessage:
listOfDoor.Add(obj.DoorMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.HiddenGateMessage:
listOfHiddenGate.Add(obj.HiddenGateMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.GateMessage:
listOfGate.Add(obj.GateMessage);
break;
}
} }
}
listOfAll.Add(content.AllMessage);
break;
listOfAll.Add(content.AllMessage);
break;


case GameState.GameEnd:
foreach (var obj in content.ObjMessage)
{
switch (obj.MessageOfObjCase)
case GameState.GameEnd:
MessageBox.Show("Game Over!");
foreach (var obj in content.ObjMessage)
{ {
case MessageOfObj.MessageOfObjOneofCase.StudentMessage:
listOfHuman.Add(obj.StudentMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.TrickerMessage:
listOfButcher.Add(obj.TrickerMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.PropMessage:
listOfProp.Add(obj.PropMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BombedBulletMessage:
listOfBombedBullet.Add(obj.BombedBulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BulletMessage:
listOfBullet.Add(obj.BulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ChestMessage:
listOfChest.Add(obj.ChestMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ClassroomMessage:
listOfClassroom.Add(obj.ClassroomMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.DoorMessage:
listOfDoor.Add(obj.DoorMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.HiddenGateMessage:
listOfHiddenGate.Add(obj.HiddenGateMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.GateMessage:
listOfGate.Add(obj.GateMessage);
break;
switch (obj.MessageOfObjCase)
{
case MessageOfObj.MessageOfObjOneofCase.StudentMessage:
listOfHuman.Add(obj.StudentMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.TrickerMessage:
listOfButcher.Add(obj.TrickerMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.PropMessage:
listOfProp.Add(obj.PropMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BombedBulletMessage:
listOfBombedBullet.Add(obj.BombedBulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BulletMessage:
listOfBullet.Add(obj.BulletMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ChestMessage:
listOfChest.Add(obj.ChestMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.ClassroomMessage:
listOfClassroom.Add(obj.ClassroomMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.DoorMessage:
listOfDoor.Add(obj.DoorMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.HiddenGateMessage:
listOfHiddenGate.Add(obj.HiddenGateMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.GateMessage:
listOfGate.Add(obj.GateMessage);
break;
}
} }
}
listOfAll.Add(content.AllMessage);
break;
listOfAll.Add(content.AllMessage);
break;
}
} }
} }
}
},
frame,
() =>
},
frame,
() =>
{
Sema.Release();
return 1;
}
)
{ AllowTimeExceed = true }.Start();
}
catch (Exception ex)
{ {
Sema.Release();
//MessageBox.Show("Game Over!");
return 1;
MessageBox.Show(ex.Message);
} }
)
{ AllowTimeExceed = true }.Start();
}) })
{ IsBackground = true }.Start(); { IsBackground = true }.Start();
return map; return map;


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

@@ -2,7 +2,7 @@
"profiles": { "profiles": {
"Client": { "Client": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "--ip 127.0.0.1 --cl --port 8888 --characterID 4 --type 2 --occupation 2"
"commandLineArgs": "--cl --port 8888 --characterID 2031"
} }
} }
} }

+ 14
- 6
logic/Client/StatusBarOfCircumstance.xaml.cs View File

@@ -12,6 +12,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using Protobuf; using Protobuf;
using Preparation.Utility;


namespace Client namespace Client
{ {
@@ -31,10 +32,13 @@ namespace Client
} }
public void SetFontSize(double fontsize) public void SetFontSize(double fontsize)
{ {
status.FontSize = 13 * fontsize / 12;
time.FontSize = 14 * fontsize / 12;
name.FontSize = 14 * fontsize / 12;
scoresOfStudents.FontSize = scoresOfTrickers.FontSize = fontsize;
if (fontsize != 0)
{
status.FontSize = 13 * fontsize / 12;
time.FontSize = 14 * fontsize / 12;
name.FontSize = 14 * fontsize / 12;
scoresOfStudents.FontSize = scoresOfTrickers.FontSize = fontsize;
}
} }


public void SetValue(MessageOfAll obj, bool gateOpened, bool hiddenGateRefreshed, bool hiddenGateOpened, long playerId) public void SetValue(MessageOfAll obj, bool gateOpened, bool hiddenGateRefreshed, bool hiddenGateOpened, long playerId)
@@ -53,14 +57,18 @@ namespace Client
{ {
time.Text += Convert.ToString(sec); time.Text += Convert.ToString(sec);
} }
if (playerId == 4)
if (playerId == GameData.numOfStudent)
{ {
name.Text = "🚀 Tricker's"; name.Text = "🚀 Tricker's";
} }
else
else if (playerId < GameData.numOfStudent)
{ {
name.Text = "🚀 Student" + Convert.ToString(playerId) + "'s"; name.Text = "🚀 Student" + Convert.ToString(playerId) + "'s";
} }
else
{
name.Text = "🚀 Spectator's";
}
if (obj.SubjectFinished < Preparation.Utility.GameData.numOfGeneratorRequiredForRepair) if (obj.SubjectFinished < Preparation.Utility.GameData.numOfGeneratorRequiredForRepair)
{ {
status.Text = "📱: " + Convert.ToString(obj.SubjectFinished) + "\n🚪: "; status.Text = "📱: " + Convert.ToString(obj.SubjectFinished) + "\n🚪: ";


+ 4
- 3
logic/Client/StatusBarOfHunter.xaml.cs View File

@@ -32,7 +32,8 @@ namespace Client
} }
public void SetFontSize(double fontsize) public void SetFontSize(double fontsize)
{ {
serial.FontSize = scores.FontSize = state.FontSize = status.FontSize = activeSkill0.FontSize = activeSkill1.FontSize = activeSkill2.FontSize = prop0.FontSize = prop1.FontSize = prop2.FontSize = fontsize;
if (fontsize != 0)
serial.FontSize = scores.FontSize = state.FontSize = status.FontSize = activeSkill0.FontSize = activeSkill1.FontSize = activeSkill2.FontSize = prop0.FontSize = prop1.FontSize = prop2.FontSize = fontsize;
} }


private void SetStaticValue(MessageOfTricker obj) private void SetStaticValue(MessageOfTricker obj)
@@ -48,8 +49,8 @@ namespace Client
case TrickerType.ANoisyPerson: case TrickerType.ANoisyPerson:
serial.Text = "👥" + Convert.ToString(1) + "👻" + Convert.ToString(obj.PlayerId) + "\nANoisyPerson"; serial.Text = "👥" + Convert.ToString(1) + "👻" + Convert.ToString(obj.PlayerId) + "\nANoisyPerson";
break; break;
case TrickerType._4:
serial.Text = "👥" + Convert.ToString(1) + "👻" + Convert.ToString(obj.PlayerId) + "\nTrickerType4";
case TrickerType.Idol:
serial.Text = "👥" + Convert.ToString(1) + "👻" + Convert.ToString(obj.PlayerId) + "\nIdol";
break; break;
case TrickerType.NullTrickerType: case TrickerType.NullTrickerType:
serial.Text = "👥" + Convert.ToString(1) + "👻" + Convert.ToString(obj.PlayerId) + "\nNullTrickerType"; serial.Text = "👥" + Convert.ToString(1) + "👻" + Convert.ToString(obj.PlayerId) + "\nNullTrickerType";


+ 2
- 1
logic/Client/StatusBarOfSurvivor.xaml.cs View File

@@ -31,7 +31,8 @@ namespace Client
} }
public void SetFontSize(double fontsize) public void SetFontSize(double fontsize)
{ {
serial.FontSize = scores.FontSize = status.FontSize = activeSkill0.FontSize = activeSkill1.FontSize = activeSkill2.FontSize = prop0.FontSize = prop1.FontSize = prop2.FontSize = fontsize;
if (fontsize != 0)
serial.FontSize = scores.FontSize = status.FontSize = activeSkill0.FontSize = activeSkill1.FontSize = activeSkill2.FontSize = prop0.FontSize = prop1.FontSize = prop2.FontSize = fontsize;
} }
private void SetStaticValue(MessageOfStudent obj) private void SetStaticValue(MessageOfStudent obj)
{ {


+ 2
- 4
logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs View File

@@ -69,10 +69,10 @@ namespace GameClass.GameObj
ap = value; ap = value;
} }
} }
public override int Speed => GameData.basicBulletMoveSpeed * 2;
public override int Speed => GameData.basicBulletMoveSpeed * 25 / 10;
public override bool IsRemoteAttack => true; public override bool IsRemoteAttack => true;


public override int CastTime => GameData.basicCastTime;
public override int CastTime => GameData.basicCastTime * 4 / 5;
public override int Backswing => 0; public override int Backswing => 0;
public override int RecoveryFromHit => 0; public override int RecoveryFromHit => 0;
public const int cd = GameData.basicBackswing / 2; public const int cd = GameData.basicBackswing / 2;
@@ -137,7 +137,6 @@ namespace GameClass.GameObj
switch (gameObjType) switch (gameObjType)
{ {
case GameObjType.Character: case GameObjType.Character:
case GameObjType.Generator:
return true; return true;
default: default:
return false; return false;
@@ -184,7 +183,6 @@ namespace GameClass.GameObj
switch (gameObjType) switch (gameObjType)
{ {
case GameObjType.Character: case GameObjType.Character:
case GameObjType.Generator:
return true; return true;
default: default:
return false; return false;


+ 30
- 23
logic/GameClass/GameObj/Character/Character.cs View File

@@ -300,14 +300,6 @@ namespace GameClass.GameObj
if (playerState == PlayerStateType.Null && IsMoving) return PlayerStateType.Moving; if (playerState == PlayerStateType.Null && IsMoving) return PlayerStateType.Moving;
return playerState; return playerState;
} }
set
{
if (value != PlayerStateType.Moving)
lock (gameObjLock)
IsMoving = false;

lock (gameObjLock) playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value;
}
} }


public bool NoHp() => (playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped public bool NoHp() => (playerState == PlayerStateType.Deceased || playerState == PlayerStateType.Escaped
@@ -323,6 +315,30 @@ namespace GameClass.GameObj
|| playerState == PlayerStateType.Treated || playerState == PlayerStateType.Stunned || playerState == PlayerStateType.Treated || playerState == PlayerStateType.Stunned
|| playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); || playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving);


private GameObj? whatInteractingWith = null;
public GameObj? WhatInteractingWith => whatInteractingWith;

public void ChangePlayerState(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null)
{
lock (gameObjLock)
{
whatInteractingWith = gameObj;
if (value != PlayerStateType.Moving)
IsMoving = false;
playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value;
//Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString());
}
}

public void SetPlayerStateNaturally()
{
lock (gameObjLock)
{
whatInteractingWith = null;
playerState = PlayerStateType.Null;
}
}

private int score = 0; private int score = 0;
public int Score public int Score
{ {
@@ -382,21 +398,6 @@ namespace GameClass.GameObj
} }
} }
} }
/// <summary>
/// 角色携带的信息
/// </summary>
private string message = "THUAI6";
public string Message
{
get => message;
set
{
lock (gameObjLock)
{
message = value;
}
}
}


#region 道具和buff相关属性、方法 #region 道具和buff相关属性、方法
private Prop[] propInventory = new Prop[GameData.maxNumOfPropInPropInventory] private Prop[] propInventory = new Prop[GameData.maxNumOfPropInPropInventory]
@@ -521,6 +522,12 @@ namespace GameClass.GameObj
return this.HasShield; return this.HasShield;
case BuffType.AddLife: case BuffType.AddLife:
return this.HasLIFE; return this.HasLIFE;
case BuffType.AddAp:
return this.HasAp;
case BuffType.Clairaudience:
return this.HasClairaudience;
case BuffType.Invisible:
return this.HasInvisible;
default: default:
return false; return false;
} }


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

@@ -20,20 +20,25 @@ namespace GameClass.GameObj
private Prop[] propInChest = new Prop[GameData.maxNumOfPropInChest] { new NullProp(), new NullProp() }; private Prop[] propInChest = new Prop[GameData.maxNumOfPropInChest] { new NullProp(), new NullProp() };
public Prop[] PropInChest => propInChest; public Prop[] PropInChest => propInChest;


private int openDegree = 0;
public int OpenDegree
private int openStartTime = 0;
public int OpenStartTime => openStartTime;
private Character? whoOpen = null;
public Character? WhoOpen => whoOpen;
public void Open(int startTime, Character character)
{ {
get => openDegree;
set
lock (gameObjLock)
{ {
if (value > 0)
lock (gameObjLock)
openDegree = (value > GameData.degreeOfOpenedChest) ? GameData.degreeOfOpenedChest : value;
else
lock (gameObjLock)
openDegree = 0;
openStartTime = startTime;
whoOpen = character;
}
}
public void StopOpen()
{
lock (gameObjLock)
{
openStartTime = 0;
whoOpen = null;
} }
} }
public bool IsOpen() => (OpenDegree == GameData.degreeOfOpenedChest);
} }
} }

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

@@ -35,14 +35,14 @@ namespace GameClass.GameObj
} }
} }


private bool isOpening = false;
public bool IsOpening
private int openStartTime = 0;
public int OpenStartTime
{ {
get => isOpening;
get => openStartTime;
set set
{ {
lock (gameObjLock) lock (gameObjLock)
isOpening = value;
openStartTime = value;
} }
} }


@@ -61,6 +61,6 @@ namespace GameClass.GameObj
} }
} }


public bool IsOpen() => (OpenDegree == GameData.degreeOfOpenedDoorway);
public bool IsOpen() => (openDegree == GameData.degreeOfOpenedDoorway);
} }
} }

+ 14
- 0
logic/GameClass/GameObj/Map/Map.cs View File

@@ -221,6 +221,20 @@ namespace GameClass.GameObj
} }
return flag; return flag;
} }
public bool RemoveJustFromMap(GameObj gameObj)
{
GameObjLockDict[gameObj.Type].EnterWriteLock();
bool flag;
try
{
flag = GameObjDict[gameObj.Type].Remove(gameObj);
}
finally
{
GameObjLockDict[gameObj.Type].ExitWriteLock();
}
return flag;
}
public void Add(GameObj gameObj) public void Add(GameObj gameObj)
{ {
GameObjLockDict[gameObj.Type].EnterWriteLock(); GameObjLockDict[gameObj.Type].EnterWriteLock();


+ 346
- 0
logic/GameRules.md View File

@@ -0,0 +1,346 @@
# 规则
V4.4
- [规则](#规则)
- [简则](#简则)
- [地图](#地图)
- [人物](#人物)
- [攻击](#攻击)
- [交互](#交互)
- [学习与毕业](#学习与毕业)
- [勉励](#勉励)
- [沉迷与唤醒](#沉迷与唤醒)
- [门](#门)
- [窗](#窗)
- [箱子](#箱子)
- [信息相关](#信息相关)
- [可视范围](#可视范围)
- [道具](#道具)
- [得分](#得分)
- [捣蛋鬼](#捣蛋鬼)
- [学生](#学生)
- [职业与技能](#职业与技能)
- [捣蛋鬼](#捣蛋鬼-1)
- [Assassin](#assassin)
- [Klee](#klee)
- [喧哗者ANoisyPerson](#喧哗者anoisyperson)
- [Idol](#idol)
- [学生(\&老师)](#学生老师)
- [运动员](#运动员)
- [教师](#教师)
- [学霸](#学霸)
- [开心果](#开心果)
- [细则](#细则)
- [特殊说明](#特殊说明)
- [移动](#移动)
- [人物](#人物-1)
- [初始状态](#初始状态)
- [道具](#道具-1)
- [交互](#交互-1)
- [学习与毕业](#学习与毕业-1)
- [攻击](#攻击-1)
- [沉迷与唤醒](#沉迷与唤醒-1)
- [门](#门-1)
- [窗](#窗-1)
- [箱子](#箱子-1)
- [得分](#得分-1)
- [信息相关](#信息相关-1)

## 简则

### 地图
- 地图为矩形区域,游戏对象坐标为(x, y),x和y均为整数。
- **x坐标轴正方向竖直向下,y坐标轴正方向水平向右**;
- **极坐标以x坐标轴为极轴,角度逆时针为正方向**。
- 地图由50 * 50个格子构成,其中每个格子代表1000 * 1000的正方形。每个格子的编号(CellX,CellY)可以计算得到:
- $$CellX=\frac{x}{1000},CellY=\frac{y}{1000}$$
- 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子

### 人物
- 人物半径为800
- 人物共有17种不可叠加的状态:
1. (可)移动状态
2. 学习
3. 被勉励
4. 在勉励
5. 开或锁门
6. 翻箱
7. 使用技能
8. 开启校门
9. 唤醒他人中
- 之后八项为不可行动状态
1. 被唤醒中
2. 沉迷
3. 退学
4. 毕业
5. 被眩晕
6. 前摇
7. 后摇
8. 翻窗

### 攻击
- 攻击类型CommonAttackOfGhost攻击未写完的作业,会造成对应攻击力的损坏
- 捣蛋鬼攻击交互状态或前后摇的学生,将使学生眩晕4.3s

| 攻击(子弹)类型 |搞蛋鬼的一般攻击CommonAttackOfGhost| 飞刀FlyingKnife | 蹦蹦炸弹BombBomb | 小炸弹JumpyDumpty |
| :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- |
| 子弹爆炸范围 | 0 | 0 | 1000 | 500 |
| 子弹攻击距离 | 1100 | 39000 | 1100 | 2200 |
| 攻击力 | 1500000 | 1200000 | 1,800,000 | 900000 |
| 移动速度/s | 3700 | 9250 | 3000 | 4300 |
| 前摇(ms) | 297 | 400 | 366 | - |
|未攻击至目标时的后摇(ms)| 800 | 0 | 3700 | - |
|攻击至目标时的后摇(ms)| 3700 | 0 | 3700 | - |
| CD(ms) | 800 | 400 | 3000 | - |
| 最大子弹容量 | 1 | 1 | 1 | - |

### 交互
- 除了翻窗,交互目标与交互者在一个**九宫格**方可交互
- 交互进度每毫秒增加对应交互速度的值
- 作业,门,箱子完成/开启进度达到10000000为完成

#### 学习与毕业
- 每张地图都有10间教室,学生需要完成其中的**7间**教室的作业,才可以开启任意校门。
- 开启校门所需时间为18秒,开启的进度不清空
- 当**3间**教室的作业完成时,隐藏校门在3-5个刷新点之一随机显现;当只剩1名学生时,隐藏校门自动打开。
- 从开启的校门或隐藏校门离开是学生终极目标

#### 勉励
- 当被勉励程度达到当前损失的毅力值或1500000时,勉励完成,学生毅力增加对应被勉励程度。
- 勉励中断时,被勉励程度保留;遭到攻击时被勉励程度清空

#### 沉迷与唤醒
- 学习毅力归零时,学生原地进入沉迷状态,每毫秒增加1沉迷度
- 唤醒需要时间1秒,之后学习毅力恢复至1/2。沉迷程度不清空。
- 进入沉迷状态时,如果学生原沉迷程度在(0,该玩家最大沉迷度/3)中,沉迷程度直接变为其最大沉迷度/3;原沉迷程度在[其最大沉迷度/3,其最大沉迷度x2/3)中,沉迷程度直接变为其最大沉迷度x2/3;原沉迷程度大于其最大沉迷度x2/3,从游戏中出局;
- 当学生沉迷程度达到其最大沉迷程度时,从游戏中出局

#### 门
- 门分别属于三个教学区:三教,五教,六教
- 拥有对应教学区的钥匙才能开锁对应的门
- 锁门过程中,门所在格子内有人会使锁门过程中断

#### 窗
- 翻窗时玩家应当在窗前后左右一个格子内

#### 箱子
- 开箱后将有2个随机道具掉落在玩家位置。

### 信息相关
- Bgm (在structures.h/.py中的student类或Tricker类中作为其属性)
1. 不详的感觉(dangerAlert):如果捣蛋鬼进入(学生的警戒半径/捣蛋鬼的隐蔽度)的距离,则学生的dangerAlert=(int)(警戒半径/二者距离)
2. 期待搞事的感觉(trickDesire):如果有学生进入(捣蛋鬼的警戒半径/学生的隐蔽度)的距离,则捣蛋鬼trickDesire=(int)(警戒半径/可被发觉的最近的学生距离)
3. 学习的声音(classVolume): 警戒半径内有人学习时,捣蛋鬼classVolume=(int)((警戒半径x学习进度百分比)/二者距离)
- 可以向其他每一个队友发送不超过256字节的信息

### 可视范围
- 小于视野半径,且受限于墙和草地
- 对于在从草地中的物体,物体与玩家连线上均为草地方可见

### 道具
- 玩家同时最多拥有三个道具
- 可以捡起与自己处于同一个格子(cell)的道具
- 可将道具扔在玩家位置
| 道具 | 对学生增益 | 学生得分条件 | 对搞蛋鬼增益 | 搞蛋鬼得分条件 |
| :-------- | :-------------------------------------- | :-----------------| :-------------------------------------- |:-----------------|
| Key3 | 能开启3教的门 |不得分| 能开启3教的门 |不得分|
| Key5 | 能开启5教的门 |不得分| 能开启5教的门 |不得分|
| Key6 | 能开启6教的门 |不得分| 能开启6教的门 |不得分|
| AddSpeed | 2倍速,持续10s | 得分10| 2倍速,持续10s |得分10|
| AddLifeOrClairaudience |若在10s内Hp归零,该增益消失以使Hp保留100|在10s内Hp归零,得分100 |10秒内得知全场玩家信息|得分10 |
| AddHpOrAp |回血750000 | 回血成功,得分10 | 10秒内下一次攻击增伤1800000|不得分 |
| ShieldOrSpear | 10秒内能抵挡一次伤害 | 10秒内成功抵挡一次伤害,得分50 |10秒内下一次攻击能破盾,如果对方无盾,则增伤900000| 10秒内破盾,得分50 |
| RecoveryFromDizziness | 使用瞬间从眩晕状态中恢复 | 成功从眩晕状态中恢复,得分30|使用瞬间从眩晕状态中恢复 | 成功从眩晕状态中恢复,得分30|

## 得分

### 捣蛋鬼

- 对学生造成伤害时,得伤害*100/基本伤害(1500000)分。
- 对作业造成破坏时,每破坏n%的作业,得n分
- 使学生沉迷时,得50分。
- 使学生眩晕时,得20*眩晕时长(/s)分。
- 每淘汰一个学生,得1000分

### 学生
- 学生每完成n%的作业,得2n分
- 学生与捣蛋鬼处于小于5000的距离认为在牵制状态,得(牵制时长(ms)*0.00246)分
- 使捣蛋鬼进入眩晕状态时,得20*眩晕时长(/s)分。
- 成功唤醒一名同学,得100分
- 成功勉励一名同学,得50*勉励程度/基本勉励程度(1500000)分
- 毕业,得1000分

## 职业与技能

### 捣蛋鬼

| 捣蛋鬼职业 | Assassin | Klee | 喧哗者ANoisyPerson | Idol |
| :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- |
| 移动速度/s | 1980 | 1800 | 1926 | 1,800 |
| 隐蔽度 | 1.5 | 1 | 0.8 | 0.75|
| 警戒范围 | 22,100 | 17000 | 15,300 | 17000
| 视野范围 | 18,000 | 15000 | 15000 | 16,500|
| 开锁门速度 | 4000 | 4000 | 4000 |4000 |
| 翻窗速度 | 1270 | 1270 | 1,397 | 1270|
| 翻箱速度 | 1000 | 1100 | 1000 |1000|

#### Assassin
- 普通攻击为 CommonAttackOfGhost
- 主动技能
- 隐身 BecomeInvisible
- CD:40s 持续时间:10s
- 在持续时间内玩家隐身
- 使用瞬间得分15
- 使用飞刀
- CD:30s 持续时间:1s
- 在持续时间内,攻击类型变为飞刀
- 不直接得分

#### Klee
- 普通攻击为 CommonAttackOfGhost
- 主动技能
- 蹦蹦炸弹 JumpyBomb
- CD:15s 持续时间:3s
- 在持续时间内,攻击类型变为蹦蹦炸弹
- 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上90°,270° 发出2个小炸弹
- 2个小炸弹运动停止前会因为碰撞爆炸,停止运动后学生碰撞会造成眩晕3.07s
- 不直接得分,通过眩晕等获得对应得分

#### 喧哗者ANoisyPerson
- 普通攻击为 CommonAttackOfGhost
- 主动技能
- 嚎叫 Howl
- CD:25s
- 使用瞬间,在视野半径范围内(不是可视区域)的学生被眩晕5500ms,自己进入800ms的后摇
- 通过眩晕获得对应得分
- 特性
- 在场所有学生Bgm系统被设为无用的值

#### Idol
- 普通攻击为 CommonAttackOfGhost
- 主动技能
- ShowTime
- CD: 75s 持续时间:10s
- 持续时间内每一帧向所有学生发送向自己移动的指令。

### 学生(&老师)

| 学生职业 | 教师Teacher | 健身狂Athlete | 学霸StraightAStudent | 开心果Sunshine |
| :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- |
| 移动速度 | 1350 | 1575 | 1440 | 1500 |
| 最大毅力值 | 30000000 | 3000000 | 3300000 | 3200000 |
| 最大沉迷度 | 600000 | 54,000 | 78,000 | 66,000 |
| 学习一科速度 | 0 | 73 | 135 | 123 |
| 勉励速度 | 80 | 90 | 100 | 120 |
| 隐蔽度 | 0.5 | 0.9 | 0.9 | 0.8 |
| 警戒范围 | 7500 | 15000 | 13,500 | 15000 |
| 视野范围 | 9,000 | 11000 | 9,000 | 10000 |
| 开锁门速度 | 4000 | 4000 | 4000 | 2800 |
| 翻窗速度 | 635 | 1,524 | 1,058 | 1270 |
| 翻箱速度 | 1000 | 1000 | 1000 | 900 |

#### 运动员
- 主动技能
- 冲撞 CanBeginToCharge
- CD:60s 持续时间:3s
- 在持续时间内,速度变为三倍,期间撞到捣蛋鬼,会导致捣蛋鬼眩晕7.22s,学生眩晕2.09s
- 通过眩晕获得对应得分

#### 教师
- 主动技能
- 惩罚 Punish
- CD:30s
- 使用瞬间,在可视范围内的翻窗、开锁门、攻击前后摇的捣蛋鬼会被眩晕(3070+300*已受伤害/基本伤害(1500000))ms,
- 通过眩晕获得对应得分
- 特性
- 无法获得牵制得分
- 无法毕业

#### 学霸
- 特性
- 冥想
- 当玩家处于可接受指令状态且不在修机时,会积累学习进度,速度为40/ms
- 受到攻击(并非伤害)或学习或进入不可接受治疗状态(包括翻窗)学习进度清零
- 主动技能5
- 写答案 WriteAnswers
- CD:30s
- 使用瞬间,对于可互动范围内的一台电机增加这个学习进度
- 通过修机获得对应得分

#### 开心果
- 主动技能
- 唤醒 Rouse
- CD:120s
- 使用瞬间,唤醒可视范围内一个沉迷中的人
- 通过唤醒获得对应得分
- 勉励 Encourage
- CD:120s
- 使用瞬间,为可视范围内随机一个毅力不足的人试图补充750000的毅力值
- 获得勉励750000的毅力值对应得分
- 鼓舞 Inspire
- CD:120s
- 使用瞬间,可视范围内学生(包括自己)获得持续6秒的1.6倍速Buff
- 每鼓舞一个学生得分10

## 细则

### 特殊说明
- 不加说明,这里“学生”往往包括职业“教师”

### 移动
- 不鼓励选手面向地图编程,因为移动过程中你可以受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。

### 人物
- EndAllAction()及Move指令调用数总和一帧内不超过10次

### 初始状态
- 玩家出生点固定且一定为空地

### 道具
- 使用钥匙相当于销毁

### 交互
- 在指令仍在进行时,重复发出同一类型的交互指令是无效的,你需要先发出Stop指令终止进行的指令
- 实际上唤醒或勉励不同的人是有效的
- 被唤醒或被勉励不属于交互状态,翻窗属于交互状态

### 学习与毕业
- 一个校门同时最多可以由一人开启
- 隐藏校门与校门对于人有碰撞体积

### 攻击
- 无论近战远程均产生bullet表示攻击
- 前摇期间攻击被打断时,子弹消失。
- 每次学生受到攻击后会损失对应子弹的攻击力的学习毅力
- 此处,前摇指 从播放攻击动作开始 攻击者不能交互 的时间

### 沉迷与唤醒
- 不能两人同时唤醒一个人
- 在被救时沉迷度不增加

### 门
- 每个教学区都有2把钥匙
- 一扇门只允许同时一个人开锁门
- 开锁门未完成前,门状态表现为原来的状态
- 开锁门进度中断后清空

### 窗
- 翻越窗户是一种交互行为,翻窗一共有两个过程
- 跳上窗:从当前位置到窗边缘中点,位置瞬移,时间=距离/爬窗速度。中断时,停留在原位置
- 爬窗:从窗一侧边缘中点到另一侧格子中心,位置渐移,时间=距离/爬窗速度。中断时,停留在另一侧格子中心
- 攻击可以穿过窗,道具可以在窗上
- 有人正在翻越窗户时,其他玩家均不可以翻越该窗户。
- 通常情况下捣蛋鬼翻越窗户的速度高于学生。

### 箱子
- 地图上有8个箱子
- 同一时刻只允许一人进行开启
- 未开启完成的箱子在下一次需要重新开始开启。
- 箱子开启后其中道具才可以被观测和拿取
- 箱子道具不刷新

### 得分
- 眩晕或毅力值归零时无牵制得分

### 信息相关
- Bgm在没有符合条件的情况下,值为0。

+ 69
- 65
logic/Gaming/ActionManager.cs View File

@@ -23,31 +23,31 @@ namespace Gaming
{ {
if (((Bullet)collisionObj).Parent != player && ((Bullet)collisionObj).TypeOfBullet == BulletType.JumpyDumpty) if (((Bullet)collisionObj).Parent != player && ((Bullet)collisionObj).TypeOfBullet == BulletType.JumpyDumpty)
{ {
if (CharacterManager.BeStunned((Character)player, GameData.TimeOfStunnedWhenJumpyDumpty))
if (characterManager.BeStunned((Character)player, GameData.TimeOfStunnedWhenJumpyDumpty))
player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.TimeOfStunnedWhenJumpyDumpty)); player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.TimeOfStunnedWhenJumpyDumpty));
gameMap.Remove((GameObj)collisionObj); gameMap.Remove((GameObj)collisionObj);
} }
} }
if (player.FindIActiveSkill(ActiveSkillType.CanBeginToCharge).IsBeingUsed && collisionObj.Type == GameObjType.Character && ((Character)collisionObj).IsGhost()) if (player.FindIActiveSkill(ActiveSkillType.CanBeginToCharge).IsBeingUsed && collisionObj.Type == GameObjType.Character && ((Character)collisionObj).IsGhost())
{ {
if (CharacterManager.BeStunned((Character)collisionObj, GameData.TimeOfGhostFaintingWhenCharge))
player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.TimeOfGhostFaintingWhenCharge));
CharacterManager.BeStunned(player, GameData.TimeOfStudentFaintingWhenCharge);
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) public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection)
{ {
if (!playerToMove.Commandable()) return false;
playerToMove.PlayerState = PlayerStateType.Moving;
if (!playerToMove.Commandable() || !TryToStop()) return false;
characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving);
moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection);
return true; return true;
} }


public static bool Stop(Character player)
public bool Stop(Character player)
{ {
if (player.Commandable())
if (player.Commandable() || !TryToStop())
{ {
player.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(player);
return true; return true;
} }
return false; return false;
@@ -63,7 +63,7 @@ namespace Gaming
return false; return false;


++generatorForFix.NumOfFixing; ++generatorForFix.NumOfFixing;
player.PlayerState = PlayerStateType.Fixing;
characterManager.SetPlayerState(player, PlayerStateType.Fixing);
new Thread new Thread
( (
() => () =>
@@ -74,7 +74,7 @@ namespace Gaming
{ {
if (generatorForFix.Repair(player.FixSpeed * GameData.frameDuration, player)) if (generatorForFix.Repair(player.FixSpeed * GameData.frameDuration, player))
{ {
player.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(player);
gameMap.NumOfRepairedGenerators++; gameMap.NumOfRepairedGenerators++;
} }
}, },
@@ -96,31 +96,21 @@ namespace Gaming
if (!(player.Commandable()) || player.PlayerState == PlayerStateType.OpeningTheDoorway) if (!(player.Commandable()) || player.PlayerState == PlayerStateType.OpeningTheDoorway)
return false; return false;
Doorway? doorwayToOpen = (Doorway?)gameMap.OneForInteract(player.Position, GameObjType.Doorway); Doorway? doorwayToOpen = (Doorway?)gameMap.OneForInteract(player.Position, GameObjType.Doorway);
if (doorwayToOpen == null || doorwayToOpen.IsOpening || !doorwayToOpen.PowerSupply)
if (doorwayToOpen == null || doorwayToOpen.OpenStartTime > 0 || !doorwayToOpen.PowerSupply)
return false; return false;


player.PlayerState = PlayerStateType.OpeningTheDoorway;
doorwayToOpen.IsOpening = true;
characterManager.SetPlayerState(player, PlayerStateType.OpeningTheDoorway, doorwayToOpen);
int startTime = doorwayToOpen.OpenStartTime = gameMap.Timer.nowTime();
new Thread new Thread
( (
() => () =>
{ {
new FrameRateTaskExecutor<int>(
loopCondition: () => player.PlayerState == PlayerStateType.OpeningTheDoorway && gameMap.Timer.IsGaming && doorwayToOpen.OpenDegree < GameData.degreeOfOpenedDoorway,
loopToDo: () =>
{
doorwayToOpen.OpenDegree += GameData.frameDuration;
},
timeInterval: GameData.frameDuration,
finallyReturn: () => 0
)
Thread.Sleep(GameData.degreeOfOpenedDoorway - doorwayToOpen.OpenDegree);


.Start();
doorwayToOpen.IsOpening = false;
if (doorwayToOpen.OpenDegree >= GameData.degreeOfOpenedDoorway)
if (doorwayToOpen.OpenStartTime == startTime)
{ {
if (player.PlayerState == PlayerStateType.OpeningTheDoorway)
player.PlayerState = PlayerStateType.Null;
doorwayToOpen.OpenDegree = GameData.degreeOfOpenedDoorway;
player.SetPlayerStateNaturally();
} }
} }


@@ -132,7 +122,7 @@ namespace Gaming


public bool Escape(Student player) public bool Escape(Student player)
{ {
if (!(player.Commandable()) || player.CharacterType == CharacterType.Robot)
if (!(player.Commandable()) || player.CharacterType == CharacterType.Robot || player.CharacterType == CharacterType.Teacher)
return false; return false;
Doorway? doorwayForEscape = (Doorway?)gameMap.OneForInteract(player.Position, GameObjType.Doorway); Doorway? doorwayForEscape = (Doorway?)gameMap.OneForInteract(player.Position, GameObjType.Doorway);
if (doorwayForEscape != null && doorwayForEscape.IsOpen()) if (doorwayForEscape != null && doorwayForEscape.IsOpen())
@@ -163,7 +153,7 @@ namespace Gaming
playerTreated = gameMap.StudentForInteract(player.Position); playerTreated = gameMap.StudentForInteract(player.Position);
if (playerTreated == null) return false; if (playerTreated == null) return false;
} }
if (player == playerTreated || (!player.Commandable()) || player.PlayerState == PlayerStateType.Treating ||
if (player == playerTreated || (!player.Commandable()) || playerTreated.PlayerState == PlayerStateType.Treated ||
(!playerTreated.Commandable()) || (!playerTreated.Commandable()) ||
playerTreated.HP == playerTreated.MaxHp || !GameData.ApproachToInteract(playerTreated.Position, player.Position)) playerTreated.HP == playerTreated.MaxHp || !GameData.ApproachToInteract(playerTreated.Position, player.Position))
return false; return false;
@@ -172,22 +162,22 @@ namespace Gaming
( (
() => () =>
{ {
playerTreated.PlayerState = PlayerStateType.Treated;
player.PlayerState = PlayerStateType.Treating;
characterManager.SetPlayerState(playerTreated, PlayerStateType.Treated);
characterManager.SetPlayerState(player, PlayerStateType.Treating);
new FrameRateTaskExecutor<int>( new FrameRateTaskExecutor<int>(
loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && player.PlayerState == PlayerStateType.Treating && gameMap.Timer.IsGaming, loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && player.PlayerState == PlayerStateType.Treating && gameMap.Timer.IsGaming,
loopToDo: () => loopToDo: () =>
{ {
if (playerTreated.AddDegreeOfTreatment(GameData.frameDuration * player.TreatSpeed, player)) if (playerTreated.AddDegreeOfTreatment(GameData.frameDuration * player.TreatSpeed, player))
playerTreated.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(playerTreated);
}, },
timeInterval: GameData.frameDuration, timeInterval: GameData.frameDuration,
finallyReturn: () => 0 finallyReturn: () => 0
) )
.Start(); .Start();


if (player.PlayerState == PlayerStateType.Treating) player.PlayerState = PlayerStateType.Null;
else if (playerTreated.PlayerState == PlayerStateType.Treated) playerTreated.PlayerState = PlayerStateType.Null;
if (player.PlayerState == PlayerStateType.Treating) characterManager.SetPlayerState(player);
else if (playerTreated.PlayerState == PlayerStateType.Treated) characterManager.SetPlayerState(playerTreated);
} }
) )
{ IsBackground = true }.Start(); { IsBackground = true }.Start();
@@ -200,11 +190,10 @@ namespace Gaming
playerRescued = gameMap.StudentForInteract(player.Position); playerRescued = gameMap.StudentForInteract(player.Position);
if (playerRescued == null) return false; if (playerRescued == null) return false;
} }
if ((!player.Commandable()) || playerRescued.PlayerState != PlayerStateType.Addicted || player == playerRescued
|| !GameData.ApproachToInteract(playerRescued.Position, player.Position) || playerRescued.TimeOfRescue > 0)
if ((!player.Commandable()) || playerRescued.PlayerState != PlayerStateType.Addicted || !GameData.ApproachToInteract(playerRescued.Position, player.Position))
return false; return false;
player.PlayerState = PlayerStateType.Rescuing;
playerRescued.PlayerState = PlayerStateType.Rescued;
characterManager.SetPlayerState(player, PlayerStateType.Rescuing);
characterManager.SetPlayerState(playerRescued, PlayerStateType.Rescued);


new Thread new Thread
( (
@@ -226,14 +215,14 @@ namespace Gaming
{ {
if (playerRescued.TimeOfRescue >= GameData.basicTimeOfRescue) if (playerRescued.TimeOfRescue >= GameData.basicTimeOfRescue)
{ {
playerRescued.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(playerRescued);
playerRescued.HP = playerRescued.MaxHp / 2; playerRescued.HP = playerRescued.MaxHp / 2;
player.AddScore(GameData.StudentScoreRescue); player.AddScore(GameData.StudentScoreRescue);
} }
else else
playerRescued.PlayerState = PlayerStateType.Addicted;
characterManager.SetPlayerState(playerRescued, PlayerStateType.Addicted);
} }
if (player.PlayerState == PlayerStateType.Rescuing) player.PlayerState = PlayerStateType.Null;
if (player.PlayerState == PlayerStateType.Rescuing) characterManager.SetPlayerState(player);
playerRescued.TimeOfRescue = 0; playerRescued.TimeOfRescue = 0;
} }
) )
@@ -247,30 +236,21 @@ namespace Gaming
return false; return false;
Chest? chestToOpen = (Chest?)gameMap.OneForInteract(player.Position, GameObjType.Chest); Chest? chestToOpen = (Chest?)gameMap.OneForInteract(player.Position, GameObjType.Chest);


if (chestToOpen == null || chestToOpen.OpenDegree > 0)
if (chestToOpen == null || chestToOpen.OpenStartTime > 0)
return false; return false;


player.PlayerState = PlayerStateType.OpeningTheChest;
characterManager.SetPlayerState(player, PlayerStateType.OpeningTheChest, chestToOpen);
int startTime = gameMap.Timer.nowTime();
chestToOpen.Open(startTime, player);
new Thread new Thread
( (
() => () =>
{ {
new FrameRateTaskExecutor<int>(
loopCondition: () => player.PlayerState == PlayerStateType.OpeningTheChest && gameMap.Timer.IsGaming && (!chestToOpen.IsOpen()),
loopToDo: () =>
{
chestToOpen.OpenDegree += GameData.frameDuration * player.SpeedOfOpenChest;
},
timeInterval: GameData.frameDuration,
finallyReturn: () => 0
)

.Start();
Thread.Sleep(GameData.degreeOfOpenedChest / player.SpeedOfOpenChest);


if (chestToOpen.IsOpen())
if (chestToOpen.OpenStartTime == startTime)
{ {
if (player.PlayerState == PlayerStateType.OpeningTheChest)
player.PlayerState = PlayerStateType.Null;
player.SetPlayerStateNaturally();
for (int i = 0; i < GameData.maxNumOfPropInChest; ++i) for (int i = 0; i < GameData.maxNumOfPropInChest; ++i)
{ {
Prop prop = chestToOpen.PropInChest[i]; Prop prop = chestToOpen.PropInChest[i];
@@ -279,8 +259,6 @@ namespace Gaming
gameMap.Add(prop); gameMap.Add(prop);
} }
} }
else chestToOpen.OpenDegree = 0;

} }


) )
@@ -312,7 +290,7 @@ namespace Gaming
//Wall addWall = new Wall(windowForClimb.Position - 2 * windowToPlayer); //Wall addWall = new Wall(windowForClimb.Position - 2 * windowToPlayer);
// gameMap.Add(addWall); // gameMap.Add(addWall);


player.PlayerState = PlayerStateType.ClimbingThroughWindows;
characterManager.SetPlayerState(player, PlayerStateType.ClimbingThroughWindows);
windowForClimb.WhoIsClimbing = player; windowForClimb.WhoIsClimbing = player;
new Thread new Thread
( (
@@ -354,7 +332,7 @@ namespace Gaming
// gameMap.Remove(addWall); // gameMap.Remove(addWall);
if (player.PlayerState == PlayerStateType.ClimbingThroughWindows) if (player.PlayerState == PlayerStateType.ClimbingThroughWindows)
{ {
player.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(player);
} }
} }


@@ -394,7 +372,7 @@ namespace Gaming
} }
if (!flag) return false; if (!flag) return false;


player.PlayerState = PlayerStateType.LockingOrOpeningTheDoor;
characterManager.SetPlayerState(player, PlayerStateType.LockingOrOpeningTheDoor);
new Thread new Thread
( (
() => () =>
@@ -404,7 +382,6 @@ namespace Gaming
loopToDo: () => loopToDo: () =>
{ {
flag = ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) == null); flag = ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) == null);
Preparation.Utility.Debugger.Output(doorToLock, flag.ToString());
doorToLock.OpenOrLockDegree += GameData.frameDuration * player.SpeedOfOpeningOrLocking; doorToLock.OpenOrLockDegree += GameData.frameDuration * player.SpeedOfOpeningOrLocking;
}, },
timeInterval: GameData.frameDuration, timeInterval: GameData.frameDuration,
@@ -417,7 +394,7 @@ namespace Gaming
doorToLock.IsOpen = (!doorToLock.IsOpen); doorToLock.IsOpen = (!doorToLock.IsOpen);
} }
if (player.PlayerState == PlayerStateType.LockingOrOpeningTheDoor) if (player.PlayerState == PlayerStateType.LockingOrOpeningTheDoor)
player.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(player);
doorToLock.OpenOrLockDegree = 0; doorToLock.OpenOrLockDegree = 0;
} }


@@ -449,6 +426,33 @@ 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 Map gameMap;
private readonly CharacterManager characterManager; private readonly CharacterManager characterManager;


+ 37
- 26
logic/Gaming/AttackManager.cs View File

@@ -64,6 +64,29 @@ namespace Gaming
} }
} }


public bool TryRemoveBullet(Bullet bullet)
{
bullet.CanMove = false;
if (gameMap.Remove(bullet))
{
if (bullet.BulletBombRange > 0)
{
BombedBullet bombedBullet = new(bullet);
gameMap.Add(bombedBullet);
new Thread
(() =>
{
Thread.Sleep(GameData.frameDuration * 5);
gameMap.RemoveJustFromMap(bombedBullet);
}
)
{ IsBackground = true }.Start();
}
return true;
}
else return false;
}

private void BulletBomb(Bullet bullet, GameObj? objBeingShot) private void BulletBomb(Bullet bullet, GameObj? objBeingShot)
{ {
#if DEBUG #if DEBUG
@@ -72,23 +95,18 @@ namespace Gaming
else else
Debugger.Output(bullet, "bombed without objBeingShot"); Debugger.Output(bullet, "bombed without objBeingShot");
#endif #endif
bullet.CanMove = false;

if (gameMap.Remove(bullet) && bullet.BulletBombRange > 0)
gameMap.Add(new BombedBullet(bullet));
if (!TryRemoveBullet(bullet)) return;


if (bullet.BulletBombRange == 0) if (bullet.BulletBombRange == 0)
{ {
if (objBeingShot == null) if (objBeingShot == null)
{ {
CharacterManager.BackSwing((Character?)bullet.Parent, bullet.Backswing);
characterManager.BackSwing((Character)bullet.Parent, bullet.Backswing);
return; return;
} }


Debugger.Output(bullet, bullet.TypeOfBullet.ToString());

BombObj(bullet, objBeingShot); BombObj(bullet, objBeingShot);
CharacterManager.BackSwing((Character?)bullet.Parent, bullet.RecoveryFromHit);
characterManager.BackSwing((Character)bullet.Parent, bullet.RecoveryFromHit);
return; return;
} }


@@ -105,6 +123,7 @@ namespace Gaming
if (bullet.TypeOfBullet == BulletType.BombBomb && objBeingShot != null) if (bullet.TypeOfBullet == BulletType.BombBomb && objBeingShot != null)
{ {
bullet.Parent.BulletOfPlayer = BulletType.JumpyDumpty; 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 / 2.0);
Attack((Character)bullet.Parent, bullet.FacingDirection.Angle() + Math.PI * 3.0 / 2.0); Attack((Character)bullet.Parent, bullet.FacingDirection.Angle() + Math.PI * 3.0 / 2.0);
} }
@@ -140,21 +159,17 @@ namespace Gaming


if (objBeingShot == null) if (objBeingShot == null)
{ {
CharacterManager.BackSwing((Character?)bullet.Parent, bullet.Backswing);
characterManager.BackSwing((Character)bullet.Parent, bullet.Backswing);
} }
else else
CharacterManager.BackSwing((Character?)bullet.Parent, bullet.RecoveryFromHit);
characterManager.BackSwing((Character)bullet.Parent, bullet.RecoveryFromHit);
} }


public bool Attack(Character? player, double angle)
public bool Attack(Character player, double angle)
{ // 子弹如果没有和其他物体碰撞,将会一直向前直到超出人物的attackRange { // 子弹如果没有和其他物体碰撞,将会一直向前直到超出人物的attackRange
if (player == null)
{
return false;
}

if (player.BulletOfPlayer == BulletType.Null || !player.Commandable())
if (player.BulletOfPlayer == BulletType.Null)
return false; return false;
Debugger.Output(player, player.CharacterType.ToString() + "Attack in " + player.BulletOfPlayer.ToString());


XY res = player.Position + new XY // 子弹紧贴人物生成。 XY res = player.Position + new XY // 子弹紧贴人物生成。
( (
@@ -166,15 +181,14 @@ namespace Gaming


if (bullet != null) if (bullet != null)
{ {
Debugger.Output(player, "Attack in" + bullet.ToString());
Debugger.Output(player, "Attack in " + bullet.ToString());
bullet.AP += player.TryAddAp() ? GameData.ApPropAdd : 0; bullet.AP += player.TryAddAp() ? GameData.ApPropAdd : 0;
bullet.CanMove = true; bullet.CanMove = true;
gameMap.Add(bullet); 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.BulletAttackRange - player.Radius - BulletFactory.BulletRadius(player.BulletOfPlayer)) * 1000 / bullet.MoveSpeed), angle); // 这里时间参数除出来的单位要是ms

if (bullet.CastTime > 0) if (bullet.CastTime > 0)
{ {
player.PlayerState = PlayerStateType.TryingToAttack;
characterManager.SetPlayerState(player, PlayerStateType.TryingToAttack);


new Thread new Thread
(() => (() =>
@@ -184,22 +198,19 @@ namespace Gaming
loopToDo: () => loopToDo: () =>
{ {
}, },
timeInterval: GameData.frameDuration,
timeInterval: GameData.checkInterval,
finallyReturn: () => 0, finallyReturn: () => 0,
maxTotalDuration: bullet.CastTime maxTotalDuration: bullet.CastTime
) )

.Start(); .Start();


if (gameMap.Timer.IsGaming) if (gameMap.Timer.IsGaming)
{ {
if (player.PlayerState == PlayerStateType.TryingToAttack) if (player.PlayerState == PlayerStateType.TryingToAttack)
{ {
player.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(player);
} }
else
bullet.IsMoving = false;
gameMap.Remove(bullet);
else TryRemoveBullet(bullet);
} }
} }
) )


+ 37
- 17
logic/Gaming/CharacterManager .cs View File

@@ -7,6 +7,7 @@ using GameEngine;
using Preparation.Interface; using Preparation.Interface;
using Timothy.FrameRateTask; using Timothy.FrameRateTask;
using System.Numerics; using System.Numerics;
using System.Timers;


namespace Gaming namespace Gaming
{ {
@@ -21,6 +22,23 @@ namespace Gaming
this.gameMap = gameMap; this.gameMap = gameMap;
} }


public void SetPlayerState(Character player, PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null)
{
switch (player.PlayerState)
{
case PlayerStateType.OpeningTheChest:
((Chest)player.WhatInteractingWith).StopOpen();
break;
case PlayerStateType.OpeningTheDoorway:
Doorway doorway = (Doorway)player.WhatInteractingWith;
doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime;
doorway.OpenStartTime = 0;
break;
default:
break;
}
player.ChangePlayerState(value, gameObj);
}


public Character? AddPlayer(XY pos, int teamID, int playerID, CharacterType characterType, Character? parent = null) public Character? AddPlayer(XY pos, int teamID, int playerID, CharacterType characterType, Character? parent = null)
{ {
@@ -123,8 +141,7 @@ namespace Gaming
bgmVolume = newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position); bgmVolume = newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position);
} }
} }
if (bgmVolume > 0)
newPlayer.AddBgm(BgmType.StudentIsApproaching, bgmVolume);
newPlayer.AddBgm(BgmType.StudentIsApproaching, bgmVolume);
} }
else else
{ {
@@ -132,9 +149,13 @@ namespace Gaming
{ {
if (person.IsGhost()) if (person.IsGhost())
{ {
if (!noise && XY.Distance(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment))
newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position));
if (newPlayer.CharacterType != CharacterType.Teacher && XY.Distance(newPlayer.Position, person.Position) <= newPlayer.ViewRange)
if (!noise)
{
if (XY.Distance(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment))
newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position));
else newPlayer.AddBgm(BgmType.GhostIsComing, 0);
}
if (newPlayer.CharacterType != CharacterType.Teacher && !newPlayer.NoHp() && newPlayer.PlayerState != PlayerStateType.Stunned && XY.Distance(newPlayer.Position, person.Position) <= GameData.PinningDownRange)
{ {
TimePinningDown += GameData.checkInterval; TimePinningDown += GameData.checkInterval;
newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded); newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded);
@@ -165,8 +186,7 @@ namespace Gaming
bgmVolume = (double)newPlayer.AlertnessRadius * generator.DegreeOfRepair / GameData.degreeOfFixedGenerator / XY.Distance(newPlayer.Position, generator.Position); bgmVolume = (double)newPlayer.AlertnessRadius * generator.DegreeOfRepair / GameData.degreeOfFixedGenerator / XY.Distance(newPlayer.Position, generator.Position);
} }
} }
if (bgmVolume > 0)
newPlayer.AddBgm(BgmType.GeneratorIsBeingFixed, bgmVolume);
newPlayer.AddBgm(BgmType.GeneratorIsBeingFixed, bgmVolume);
} }
finally finally
{ {
@@ -216,7 +236,7 @@ namespace Gaming
return; return;
} }
} }
player.PlayerState = PlayerStateType.Addicted;
SetPlayerState(player, PlayerStateType.Addicted);
new Thread new Thread
(() => (() =>
{ {
@@ -246,23 +266,23 @@ namespace Gaming
{ IsBackground = true }.Start(); { IsBackground = true }.Start();
} }


public static bool BeStunned(Character player, int time)
public bool BeStunned(Character player, int time)
{ {
if (player.PlayerState == PlayerStateType.Stunned || player.NoHp() || player.CharacterType == CharacterType.Robot) return false; if (player.PlayerState == PlayerStateType.Stunned || player.NoHp() || player.CharacterType == CharacterType.Robot) return false;
new Thread new Thread
(() => (() =>
{ {
player.PlayerState = PlayerStateType.Stunned;
SetPlayerState(player, PlayerStateType.Stunned);
Thread.Sleep(time); Thread.Sleep(time);
if (player.PlayerState == PlayerStateType.Stunned) if (player.PlayerState == PlayerStateType.Stunned)
player.PlayerState = PlayerStateType.Null;
SetPlayerState(player);
} }
) )
{ IsBackground = true }.Start(); { IsBackground = true }.Start();
return true; return true;
} }


public static bool TryBeAwed(Student character, Bullet bullet)
public bool TryBeAwed(Student character, Bullet bullet)
{ {
if (character.CanBeAwed()) if (character.CanBeAwed())
{ {
@@ -292,6 +312,7 @@ namespace Gaming
{ {
((WriteAnswers)student.FindIActiveSkill(ActiveSkillType.WriteAnswers)).DegreeOfMeditation = 0; ((WriteAnswers)student.FindIActiveSkill(ActiveSkillType.WriteAnswers)).DegreeOfMeditation = 0;
} }
student.SetDegreeOfTreatment0();
#if DEBUG #if DEBUG
Debugger.Output(bullet, " 's AP is " + bullet.AP.ToString()); Debugger.Output(bullet, " 's AP is " + bullet.AP.ToString());
#endif #endif
@@ -328,7 +349,6 @@ namespace Gaming
bullet.Parent.AddScore(GameData.TrickerScoreAttackStudent(subHp)); bullet.Parent.AddScore(GameData.TrickerScoreAttackStudent(subHp));
bullet.Parent.HP = (int)(bullet.Parent.HP + (bullet.Parent.Vampire * subHp)); bullet.Parent.HP = (int)(bullet.Parent.HP + (bullet.Parent.Vampire * subHp));
} }
student.SetDegreeOfTreatment0();
if (student.HP <= 0) if (student.HP <= 0)
student.TryActivatingLIFE(); // 如果有复活甲 student.TryActivatingLIFE(); // 如果有复活甲


@@ -337,11 +357,11 @@ namespace Gaming
else TryBeAwed(student, bullet); else TryBeAwed(student, bullet);
} }


public static bool BackSwing(Character? player, int time)
public bool BackSwing(Character player, int time)
{ {
if (player == null || time <= 0) return false;
if (time <= 0) return false;
if (player.PlayerState == PlayerStateType.Swinging || (!player.Commandable() && player.PlayerState != PlayerStateType.TryingToAttack)) return false; if (player.PlayerState == PlayerStateType.Swinging || (!player.Commandable() && player.PlayerState != PlayerStateType.TryingToAttack)) return false;
player.PlayerState = PlayerStateType.Swinging;
SetPlayerState(player, PlayerStateType.Swinging);


new Thread new Thread
(() => (() =>
@@ -350,7 +370,7 @@ namespace Gaming


if (player.PlayerState == PlayerStateType.Swinging) if (player.PlayerState == PlayerStateType.Swinging)
{ {
player.PlayerState = PlayerStateType.Null;
SetPlayerState(player);
} }
} }
) )


+ 8
- 7
logic/Gaming/Game.cs View File

@@ -167,7 +167,7 @@ namespace Gaming
Character? player = gameMap.FindPlayerToAction(playerID); Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null) if (player != null)
{ {
return ActionManager.Stop(player);
return actionManager.Stop(player);
} }
return false; return false;
} }
@@ -215,15 +215,16 @@ namespace Gaming
} }
return false; return false;
} }
public void Attack(long playerID, double angle)
public bool Attack(long playerID, double angle)
{ {
if (!gameMap.Timer.IsGaming) if (!gameMap.Timer.IsGaming)
return;
return false;
Character? player = gameMap.FindPlayerToAction(playerID); Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
if (player != null && player.Commandable())
{ {
_ = attackManager.Attack(player, angle);
return attackManager.Attack(player, angle);
} }
return false;
} }
public void UseProp(long playerID, PropType propType = PropType.Null) public void UseProp(long playerID, PropType propType = PropType.Null)
{ {
@@ -232,7 +233,7 @@ namespace Gaming
Character? player = gameMap.FindPlayerToAction(playerID); Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null) if (player != null)
{ {
PropManager.UseProp(player, propType);
propManager.UseProp(player, propType);
} }
} }
public void ThrowProp(long playerID, PropType propType = PropType.Null) public void ThrowProp(long playerID, PropType propType = PropType.Null)
@@ -374,7 +375,7 @@ namespace Gaming
characterManager = new CharacterManager(gameMap); characterManager = new CharacterManager(gameMap);
attackManager = new AttackManager(gameMap, characterManager); attackManager = new AttackManager(gameMap, characterManager);
actionManager = new ActionManager(gameMap, characterManager); actionManager = new ActionManager(gameMap, characterManager);
propManager = new PropManager(gameMap);
propManager = new PropManager(gameMap, characterManager);
skillManager = new SkillManager(gameMap, actionManager, attackManager, propManager, characterManager); skillManager = new SkillManager(gameMap, actionManager, attackManager, propManager, characterManager);
} }
} }


+ 6
- 5
logic/Gaming/PropManager.cs View File

@@ -16,10 +16,10 @@ namespace Gaming
private class PropManager private class PropManager
{ {
private readonly Map gameMap; private readonly Map gameMap;
private readonly CharacterManager characterManager;
private readonly List<XY> availableCellForGenerateProp; private readonly List<XY> availableCellForGenerateProp;


public static void UseProp(Character player, PropType propType)
public void UseProp(Character player, PropType propType)
{ {
if (player.IsResetting || player.CharacterType == CharacterType.Robot) if (player.IsResetting || player.CharacterType == CharacterType.Robot)
return; return;
@@ -48,7 +48,7 @@ namespace Gaming
if (!player.IsGhost()) if (!player.IsGhost())
if (player.HP < player.MaxHp) if (player.HP < player.MaxHp)
{ {
player.HP += GameData.basicTreatmentDegree;
player.HP += GameData.basicTreatmentDegree / 2;
player.AddScore(GameData.ScorePropAddHp); player.AddScore(GameData.ScorePropAddHp);
} }
else player.AddAp(GameData.PropDuration); else player.AddAp(GameData.PropDuration);
@@ -57,7 +57,7 @@ namespace Gaming
if (player.PlayerState == PlayerStateType.Stunned) if (player.PlayerState == PlayerStateType.Stunned)
{ {
player.AddScore(GameData.ScorePropRecoverFromDizziness); player.AddScore(GameData.ScorePropRecoverFromDizziness);
player.PlayerState = PlayerStateType.Null;
player.SetPlayerStateNaturally();
} }
break; break;
default: default:
@@ -210,8 +210,9 @@ namespace Gaming
{ IsBackground = true }.Start(); { IsBackground = true }.Start();
*/ */
} }
public PropManager(Map gameMap) // 道具不能扔过墙
public PropManager(Map gameMap, CharacterManager characterManager) // 道具不能扔过墙
{ {
this.characterManager = characterManager;
this.gameMap = gameMap; this.gameMap = gameMap;
/* this.moveEngine = new MoveEngine( /* this.moveEngine = new MoveEngine(
gameMap: gameMap, gameMap: gameMap,


+ 53
- 9
logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs View File

@@ -28,6 +28,50 @@ namespace Gaming
{ }); { });
} }


public bool ShowTime(Character player)
{
if ((!player.Commandable())) return false;
IActiveSkill skill = player.FindIActiveSkill(ActiveSkillType.ShowTime);

return ActiveSkillEffect(skill, player, () =>
{
new Thread
(
() =>
{
new FrameRateTaskExecutor<int>(
loopCondition: () => player.Commandable() && gameMap.Timer.IsGaming,
loopToDo: () =>
{
gameMap.GameObjLockDict[GameObjType.Character].EnterReadLock();
try
{
foreach (Character person in gameMap.GameObjDict[GameObjType.Character])
{
if (!person.IsGhost())
actionManager.MovePlayer(person, GameData.frameDuration, (player.Position - person.Position).Angle());
}
}
finally
{
gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock();
}
},
timeInterval: GameData.frameDuration,
finallyReturn: () => 0
)

.Start();
}
)
{ IsBackground = true }.Start();
},
() =>
{
}
);
}

public static bool BecomeInvisible(Character player) public static bool BecomeInvisible(Character player)
{ {
if ((!player.Commandable())) return false; if ((!player.Commandable())) return false;
@@ -120,8 +164,8 @@ namespace Gaming
{ {
if (!character.IsGhost() && XY.Distance(character.Position, player.Position) <= player.ViewRange) if (!character.IsGhost() && XY.Distance(character.Position, player.Position) <= player.ViewRange)
{ {
if (CharacterManager.BeStunned(character, GameData.TimeOfStudentFaintingWhenHowl))
player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.TimeOfStudentFaintingWhenHowl));
if (characterManager.BeStunned(character, GameData.TimeOfStudentStunnedWhenHowl))
player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.TimeOfStudentStunnedWhenHowl));
break; break;
} }
} }
@@ -130,7 +174,7 @@ namespace Gaming
{ {
gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock();
} }
CharacterManager.BackSwing(player, GameData.TimeOfGhostSwingingAfterHowl);
characterManager.BackSwing(player, GameData.TimeOfGhostSwingingAfterHowl);
Debugger.Output(player, "howled!"); Debugger.Output(player, "howled!");
}, },
() => () =>
@@ -149,11 +193,11 @@ namespace Gaming
{ {
if (character.IsGhost() && if (character.IsGhost() &&
(character.PlayerState == PlayerStateType.TryingToAttack || character.PlayerState == PlayerStateType.Swinging (character.PlayerState == PlayerStateType.TryingToAttack || character.PlayerState == PlayerStateType.Swinging
|| character.PlayerState == PlayerStateType.UsingSkill)
|| character.PlayerState == PlayerStateType.UsingSkill || character.PlayerState == PlayerStateType.LockingOrOpeningTheDoor || character.PlayerState == PlayerStateType.ClimbingThroughWindows)
&& gameMap.CanSee(player, character)) && gameMap.CanSee(player, character))
{ {
if (CharacterManager.BeStunned(character, GameData.TimeOfGhostFaintingWhenPunish))
player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.TimeOfGhostFaintingWhenPunish));
if (characterManager.BeStunned(character, GameData.TimeOfGhostStunnedWhenPunish + GameData.factorOfTimeStunnedWhenPunish * (player.MaxHp - player.HP)))
player.AddScore(GameData.StudentScoreTrickerBeStunned(GameData.TimeOfGhostStunnedWhenPunish + GameData.factorOfTimeStunnedWhenPunish * (player.MaxHp - player.HP)));
break; break;
} }
} }
@@ -180,7 +224,7 @@ namespace Gaming
{ {
if ((character.PlayerState == PlayerStateType.Addicted) && gameMap.CanSee(player, character)) if ((character.PlayerState == PlayerStateType.Addicted) && gameMap.CanSee(player, character))
{ {
character.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(character);
character.HP = GameData.RemainHpWhenAddLife; character.HP = GameData.RemainHpWhenAddLife;
((Student)character).TimeOfRescue = 0; ((Student)character).TimeOfRescue = 0;
player.AddScore(GameData.StudentScoreRescue); player.AddScore(GameData.StudentScoreRescue);
@@ -210,8 +254,8 @@ namespace Gaming
{ {
if ((character.HP < character.MaxHp) && gameMap.CanSee(player, character)) if ((character.HP < character.MaxHp) && gameMap.CanSee(player, character))
{ {
player.AddScore(GameData.StudentScoreTreat(character.MaxHp - character.HP));
character.HP = character.MaxHp;
player.AddScore(GameData.StudentScoreTreat(GameData.AddHpWhenEncourage));
character.HP += GameData.AddHpWhenEncourage;
((Student)character).SetDegreeOfTreatment0(); ((Student)character).SetDegreeOfTreatment0();
break; break;
} }


+ 3
- 0
logic/Gaming/SkillManager/SkillManager.cs View File

@@ -52,6 +52,9 @@ namespace Gaming
case ActiveSkillType.Rouse: case ActiveSkillType.Rouse:
Rouse(character); Rouse(character);
break; break;
case ActiveSkillType.ShowTime:
ShowTime(character);
break;
default: default:
return false; return false;
} }


+ 3
- 1
logic/Preparation/Interface/ICharacter.cs View File

@@ -10,8 +10,10 @@ namespace Preparation.Interface
public int Score { get; } public int Score { get; }
public void AddScore(int add); public void AddScore(int add);
public double Vampire { get; } public double Vampire { get; }
public PlayerStateType PlayerState { get; set; }
public PlayerStateType PlayerState { get; }
public BulletType BulletOfPlayer { get; set; } public BulletType BulletOfPlayer { get; set; }
public CharacterType CharacterType { get; }
public int BulletNum { get; }


public bool IsGhost(); public bool IsGhost();
} }


+ 43
- 10
logic/Preparation/Interface/IOccupation.cs View File

@@ -91,6 +91,37 @@ namespace Preparation.Interface
public int speedOfOpenChest = (int)(GameData.basicSpeedOfOpenChest * 1.1); public int speedOfOpenChest = (int)(GameData.basicSpeedOfOpenChest * 1.1);
public int SpeedOfOpenChest => speedOfOpenChest; public int SpeedOfOpenChest => speedOfOpenChest;
} }
public class Idol : IGhostType
{
private const int moveSpeed = GameData.basicGhostMoveSpeed;
public int MoveSpeed => moveSpeed;

private const int maxHp = GameData.basicHp;
public int MaxHp => maxHp;

public BulletType InitBullet => BulletType.CommonAttackOfGhost;

public List<ActiveSkillType> ListOfIActiveSkill => new(new ActiveSkillType[] { ActiveSkillType.ShowTime });
public List<PassiveSkillType> ListOfIPassiveSkill => new(new PassiveSkillType[] { });

public double concealment = GameData.basicConcealment * 3 / 4;
public double Concealment => concealment;

public int alertnessRadius = GameData.basicGhostAlertnessRadius;
public int AlertnessRadius => alertnessRadius;

public int viewRange = GameData.basicGhostViewRange * 11 / 10;
public int ViewRange => viewRange;

public int speedOfOpeningOrLocking = GameData.basicSpeedOfOpeningOrLocking;
public int SpeedOfOpeningOrLocking => speedOfOpeningOrLocking;

public int speedOfClimbingThroughWindows = GameData.basicGhostSpeedOfClimbingThroughWindows;
public int SpeedOfClimbingThroughWindows => speedOfClimbingThroughWindows;

public int speedOfOpenChest = GameData.basicSpeedOfOpenChest;
public int SpeedOfOpenChest => speedOfOpenChest;
}
public class ANoisyPerson : IGhostType public class ANoisyPerson : IGhostType
{ {
private const int moveSpeed = (int)(GameData.basicGhostMoveSpeed * 1.07); private const int moveSpeed = (int)(GameData.basicGhostMoveSpeed * 1.07);
@@ -124,7 +155,7 @@ namespace Preparation.Interface
} }
public class Teacher : IStudentType public class Teacher : IStudentType
{ {
private const int moveSpeed = GameData.basicStudentMoveSpeed * 3 / 4;
private const int moveSpeed = GameData.basicStudentMoveSpeed * 9 / 10;
public int MoveSpeed => moveSpeed; public int MoveSpeed => moveSpeed;


private const int maxHp = GameData.basicHp * 10; private const int maxHp = GameData.basicHp * 10;
@@ -141,7 +172,7 @@ namespace Preparation.Interface
public const int fixSpeed = 0; public const int fixSpeed = 0;
public int FixSpeed => fixSpeed; public int FixSpeed => fixSpeed;


public const int treatSpeed = (int)(GameData.basicTreatSpeed * 0.7);
public const int treatSpeed = (int)(GameData.basicTreatSpeed * 0.8);
public int TreatSpeed => treatSpeed; public int TreatSpeed => treatSpeed;


public const double concealment = GameData.basicConcealment * 0.5; public const double concealment = GameData.basicConcealment * 0.5;
@@ -164,7 +195,7 @@ namespace Preparation.Interface
} }
public class Athlete : IStudentType public class Athlete : IStudentType
{ {
private const int moveSpeed = GameData.basicStudentMoveSpeed * 11 / 10;
private const int moveSpeed = GameData.basicStudentMoveSpeed * 105 / 100;
public int MoveSpeed => moveSpeed; public int MoveSpeed => moveSpeed;


private const int maxHp = GameData.basicHp; private const int maxHp = GameData.basicHp;
@@ -181,7 +212,7 @@ namespace Preparation.Interface
public const int fixSpeed = GameData.basicFixSpeed * 6 / 10; public const int fixSpeed = GameData.basicFixSpeed * 6 / 10;
public int FixSpeed => fixSpeed; public int FixSpeed => fixSpeed;


public const int treatSpeed = GameData.basicTreatSpeed * 8 / 10;
public const int treatSpeed = GameData.basicTreatSpeed * 9 / 10;
public int TreatSpeed => treatSpeed; public int TreatSpeed => treatSpeed;


public const double concealment = GameData.basicConcealment * 0.9; public const double concealment = GameData.basicConcealment * 0.9;
@@ -204,7 +235,7 @@ namespace Preparation.Interface
} }
public class StraightAStudent : IStudentType public class StraightAStudent : IStudentType
{ {
private const int moveSpeed = (int)(GameData.basicStudentMoveSpeed * 0.8);
private const int moveSpeed = (int)(GameData.basicStudentMoveSpeed * 0.96);
public int MoveSpeed => moveSpeed; public int MoveSpeed => moveSpeed;


private const int maxHp = (int)(GameData.basicHp * 1.1); private const int maxHp = (int)(GameData.basicHp * 1.1);
@@ -221,7 +252,7 @@ namespace Preparation.Interface
public const int fixSpeed = GameData.basicFixSpeed * 11 / 10; public const int fixSpeed = GameData.basicFixSpeed * 11 / 10;
public int FixSpeed => fixSpeed; public int FixSpeed => fixSpeed;


public const int treatSpeed = GameData.basicTreatSpeed * 8 / 10;
public const int treatSpeed = GameData.basicTreatSpeed;
public int TreatSpeed => treatSpeed; public int TreatSpeed => treatSpeed;


public const double concealment = GameData.basicConcealment * 0.9; public const double concealment = GameData.basicConcealment * 0.9;
@@ -261,7 +292,7 @@ namespace Preparation.Interface
public const int fixSpeed = GameData.basicFixSpeed; public const int fixSpeed = GameData.basicFixSpeed;
public int FixSpeed => fixSpeed; public int FixSpeed => fixSpeed;


public const int treatSpeed = GameData.basicTreatSpeed * 8 / 10;
public const int treatSpeed = 0;
public int TreatSpeed => treatSpeed; public int TreatSpeed => treatSpeed;


public const double concealment = GameData.basicConcealment; public const double concealment = GameData.basicConcealment;
@@ -341,7 +372,7 @@ namespace Preparation.Interface
public const int fixSpeed = GameData.basicFixSpeed; public const int fixSpeed = GameData.basicFixSpeed;
public int FixSpeed => fixSpeed; public int FixSpeed => fixSpeed;


public const int treatSpeed = GameData.basicTreatSpeed * 2;
public const int treatSpeed = GameData.basicTreatSpeed * 12 / 10;
public int TreatSpeed => treatSpeed; public int TreatSpeed => treatSpeed;


public const double concealment = GameData.basicConcealment; public const double concealment = GameData.basicConcealment;
@@ -353,13 +384,13 @@ namespace Preparation.Interface
public int viewRange = GameData.basicStudentViewRange; public int viewRange = GameData.basicStudentViewRange;
public int ViewRange => viewRange; public int ViewRange => viewRange;


public int speedOfOpeningOrLocking = GameData.basicSpeedOfOpeningOrLocking;
public int speedOfOpeningOrLocking = GameData.basicSpeedOfOpeningOrLocking * 7 / 10;
public int SpeedOfOpeningOrLocking => speedOfOpeningOrLocking; public int SpeedOfOpeningOrLocking => speedOfOpeningOrLocking;


public int speedOfClimbingThroughWindows = GameData.basicStudentSpeedOfClimbingThroughWindows; public int speedOfClimbingThroughWindows = GameData.basicStudentSpeedOfClimbingThroughWindows;
public int SpeedOfClimbingThroughWindows => speedOfClimbingThroughWindows; public int SpeedOfClimbingThroughWindows => speedOfClimbingThroughWindows;


public int speedOfOpenChest = GameData.basicSpeedOfOpenChest;
public int speedOfOpenChest = GameData.basicSpeedOfOpenChest * 9 / 10;
public int SpeedOfOpenChest => speedOfOpenChest; public int SpeedOfOpenChest => speedOfOpenChest;
} }


@@ -383,6 +414,8 @@ namespace Preparation.Interface
return new ANoisyPerson(); return new ANoisyPerson();
case CharacterType.TechOtaku: case CharacterType.TechOtaku:
return new TechOtaku(); return new TechOtaku();
case CharacterType.Idol:
return new Idol();
case CharacterType.Athlete: case CharacterType.Athlete:
default: default:
return new Athlete(); return new Athlete();


+ 29
- 10
logic/Preparation/Interface/ISkill.cs View File

@@ -18,8 +18,8 @@ namespace Preparation.Interface
} }
public class CanBeginToCharge : IActiveSkill public class CanBeginToCharge : IActiveSkill
{ {
public int SkillCD => GameData.commonSkillCD * 24 / 30;
public int DurationTime => GameData.commonSkillTime * 5 / 10;
public int SkillCD => GameData.commonSkillCD * 2;
public int DurationTime => GameData.commonSkillTime * 3 / 10;


private readonly object commonSkillLock = new object(); private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock; public object ActiveSkillLock => commonSkillLock;
@@ -33,8 +33,8 @@ namespace Preparation.Interface


public class BecomeInvisible : IActiveSkill public class BecomeInvisible : IActiveSkill
{ {
public int SkillCD => GameData.commonSkillCD;
public int DurationTime => GameData.commonSkillTime * 6 / 10;
public int SkillCD => GameData.commonSkillCD * 4 / 3;
public int DurationTime => GameData.commonSkillTime;


private readonly object commonSkillLock = new object(); private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock; public object ActiveSkillLock => commonSkillLock;
@@ -63,7 +63,7 @@ namespace Preparation.Interface


public class Rouse : IActiveSkill public class Rouse : IActiveSkill
{ {
public int SkillCD => GameData.commonSkillCD * 2;
public int SkillCD => GameData.commonSkillCD * 4;
public int DurationTime => 0; public int DurationTime => 0;


private readonly object commonSkillLock = new object(); private readonly object commonSkillLock = new object();
@@ -78,7 +78,7 @@ namespace Preparation.Interface


public class Encourage : IActiveSkill public class Encourage : IActiveSkill
{ {
public int SkillCD => GameData.commonSkillCD * 2;
public int SkillCD => GameData.commonSkillCD * 4;
public int DurationTime => 0; public int DurationTime => 0;


private readonly object commonSkillLock = new object(); private readonly object commonSkillLock = new object();
@@ -93,7 +93,7 @@ namespace Preparation.Interface


public class Inspire : IActiveSkill public class Inspire : IActiveSkill
{ {
public int SkillCD => GameData.commonSkillCD * 2;
public int SkillCD => GameData.commonSkillCD * 4;
public int DurationTime => 0; public int DurationTime => 0;


private readonly object commonSkillLock = new object(); private readonly object commonSkillLock = new object();
@@ -121,6 +121,21 @@ namespace Preparation.Interface
} }
} }


public class ShowTime : IActiveSkill
{
public int SkillCD => GameData.commonSkillCD * 3;
public int DurationTime => GameData.commonSkillTime;

private readonly object commonSkillLock = new();
public object ActiveSkillLock => commonSkillLock;

public bool isBeingUsed = false;
public bool IsBeingUsed
{
get => isBeingUsed; set => isBeingUsed = value;
}
}

public class JumpyBomb : IActiveSkill public class JumpyBomb : IActiveSkill
{ {
public int SkillCD => GameData.commonSkillCD / 2; public int SkillCD => GameData.commonSkillCD / 2;
@@ -137,7 +152,7 @@ namespace Preparation.Interface


public class UseKnife : IActiveSkill public class UseKnife : IActiveSkill
{ {
public int SkillCD => GameData.commonSkillCD * 2 / 3;
public int SkillCD => GameData.commonSkillCD;
public int DurationTime => GameData.commonSkillTime / 10; public int DurationTime => GameData.commonSkillTime / 10;
private readonly object commonSkillLock = new object(); private readonly object commonSkillLock = new object();
public object ActiveSkillLock => commonSkillLock; public object ActiveSkillLock => commonSkillLock;
@@ -151,9 +166,9 @@ namespace Preparation.Interface


public class UseRobot : IActiveSkill public class UseRobot : IActiveSkill
{ {
public int SkillCD => GameData.frameDuration;
public int SkillCD => GameData.commonSkillCD / 300;
public int DurationTime => 0; public int DurationTime => 0;
private readonly object commonSkillLock = new object();
private readonly object commonSkillLock = new();
public object ActiveSkillLock => commonSkillLock; public object ActiveSkillLock => commonSkillLock;


public bool isBeingUsed = false; public bool isBeingUsed = false;
@@ -260,6 +275,8 @@ namespace Preparation.Interface
return new Rouse(); return new Rouse();
case ActiveSkillType.Inspire: case ActiveSkillType.Inspire:
return new Inspire(); return new Inspire();
case ActiveSkillType.ShowTime:
return new ShowTime();
default: default:
return new NullSkill(); return new NullSkill();
} }
@@ -293,6 +310,8 @@ namespace Preparation.Interface
return ActiveSkillType.UseRobot; return ActiveSkillType.UseRobot;
case Rouse: case Rouse:
return ActiveSkillType.Rouse; return ActiveSkillType.Rouse;
case ShowTime:
return ActiveSkillType.ShowTime;
default: default:
return ActiveSkillType.Null; return ActiveSkillType.Null;
} }


+ 2
- 0
logic/Preparation/Utility/EnumType.cs View File

@@ -83,6 +83,7 @@ namespace Preparation.Utility
ANoisyPerson = 7, ANoisyPerson = 7,
Robot = 8, Robot = 8,
Sunshine = 9, Sunshine = 9,
Idol = 10,
} }
public enum ActiveSkillType // 主动技能 public enum ActiveSkillType // 主动技能
{ {
@@ -100,6 +101,7 @@ namespace Preparation.Utility
Rouse = 11, Rouse = 11,
Encourage = 12, Encourage = 12,
Inspire = 13, Inspire = 13,
ShowTime = 14,
} }
public enum PassiveSkillType public enum PassiveSkillType
{ {


+ 31
- 23
logic/Preparation/Utility/GameData.cs View File

@@ -15,6 +15,8 @@ namespace Preparation.Utility
public const int checkInterval = 50; // 检查位置标志、补充子弹的帧时长 public const int checkInterval = 50; // 检查位置标志、补充子弹的帧时长
public const long gameDuration = 600000; // 游戏时长600000ms = 10min public const long gameDuration = 600000; // 游戏时长600000ms = 10min


public const int LimitOfStopAndMove = 15;

public const int MinSpeed = 1; // 最小速度 public const int MinSpeed = 1; // 最小速度
public const int MaxSpeed = int.MaxValue; // 最大速度 public const int MaxSpeed = int.MaxValue; // 最大速度


@@ -90,7 +92,7 @@ namespace Preparation.Utility


public const int basicTreatSpeed = 100; public const int basicTreatSpeed = 100;
public const int basicFixSpeed = 123; public const int basicFixSpeed = 123;
public const int basicSpeedOfOpeningOrLocking = 4130;
public const int basicSpeedOfOpeningOrLocking = 4000;
public const int basicStudentSpeedOfClimbingThroughWindows = 611; public const int basicStudentSpeedOfClimbingThroughWindows = 611;
public const int basicGhostSpeedOfClimbingThroughWindows = 1270; public const int basicGhostSpeedOfClimbingThroughWindows = 1270;
public const int basicSpeedOfOpenChest = 1000; public const int basicSpeedOfOpenChest = 1000;
@@ -105,9 +107,9 @@ namespace Preparation.Utility
#if DEBUG #if DEBUG
public const int basicStudentMoveSpeed = 9000;// 基本移动速度,单位:s-1 public const int basicStudentMoveSpeed = 9000;// 基本移动速度,单位:s-1
#else #else
public const int basicStudentMoveSpeed = 1270;
public const int basicStudentMoveSpeed = 1500;
#endif #endif
public const int basicGhostMoveSpeed = (int)(basicStudentMoveSpeed * 45.0 / 38);
public const int basicGhostMoveSpeed = (int)(basicStudentMoveSpeed * 1.2);


public const int characterMaxSpeed = 12000; // 最大速度 public const int characterMaxSpeed = 12000; // 最大速度


@@ -116,6 +118,8 @@ namespace Preparation.Utility
public const int basicGhostAlertnessRadius = 17 * numOfPosGridPerCell; public const int basicGhostAlertnessRadius = 17 * numOfPosGridPerCell;
public const int basicStudentViewRange = 10 * numOfPosGridPerCell; public const int basicStudentViewRange = 10 * numOfPosGridPerCell;
public const int basicGhostViewRange = 15 * numOfPosGridPerCell; public const int basicGhostViewRange = 15 * numOfPosGridPerCell;
public const int PinningDownRange = 5 * numOfPosGridPerCell;

public const int maxNumOfPropInPropInventory = 3; public const int maxNumOfPropInPropInventory = 3;


public static XY PosWhoDie = new XY(1, 1); public static XY PosWhoDie = new XY(1, 1);
@@ -127,6 +131,7 @@ namespace Preparation.Utility
CharacterType.Assassin => true, CharacterType.Assassin => true,
CharacterType.Klee => true, CharacterType.Klee => true,
CharacterType.ANoisyPerson => true, CharacterType.ANoisyPerson => true,
CharacterType.Idol => true,
_ => false, _ => false,
}; };
} }
@@ -137,47 +142,47 @@ namespace Preparation.Utility
return damage * 100 / basicApOfGhost; return damage * 100 / basicApOfGhost;
} }
public const int TrickerScoreStudentBeAddicted = 50; public const int TrickerScoreStudentBeAddicted = 50;
public const int TrickerScoreDestroyRobot = 20;
public const int TrickerScoreDestroyRobot = 30;
public const int TrickerScoreStudentDie = 1000; public const int TrickerScoreStudentDie = 1000;
public static int TrickerScoreStudentBeStunned(int time) public static int TrickerScoreStudentBeStunned(int time)
{ {
return time;
return time * 20 / 1000;
} }
public static int TrickerScoreDamageGenerator(int degree) public static int TrickerScoreDamageGenerator(int degree)
{ {
return degree * 200 / degreeOfFixedGenerator;
return degree * 100 / degreeOfFixedGenerator;
} }


public static int StudentScoreFix(int degreeOfFix) public static int StudentScoreFix(int degreeOfFix)
{ {
return degreeOfFix * 200 / degreeOfFixedGenerator; return degreeOfFix * 200 / degreeOfFixedGenerator;
} }
public const int StudentScoreFixed = 25;
//public const int StudentScoreFixed = 25;
public static int StudentScorePinDown(int timeOfPinningDown) public static int StudentScorePinDown(int timeOfPinningDown)
{ {
return 0;
return (int)(timeOfPinningDown * 0.00246);
} }
public static int StudentScoreTrickerBeStunned(int time) public static int StudentScoreTrickerBeStunned(int time)
{ {
return time;
return time * 20 / 1000;
} }
public const int StudentScoreRescue = 100; public const int StudentScoreRescue = 100;
public static int StudentScoreTreat(int degree) public static int StudentScoreTreat(int degree)
{ {
return degree;
return degree * 50 / basicTreatmentDegree;
} }
public const int StudentScoreEscape = 1000; public const int StudentScoreEscape = 1000;


public const int ScorePropRemainHp = 20;
public const int ScorePropUseShield = 20;
public const int ScorePropUseSpear = 20;
public const int ScorePropAddAp = 10;
public const int ScorePropRemainHp = 100;
public const int ScorePropUseShield = 50;
public const int ScorePropUseSpear = 50;
public const int ScorePropAddAp = 0;
public const int ScorePropAddSpeed = 10; public const int ScorePropAddSpeed = 10;
public const int ScorePropClairaudience = 10; public const int ScorePropClairaudience = 10;
public const int ScorePropAddHp = 20;
public const int ScorePropRecoverFromDizziness = 20;
public const int ScorePropAddHp = 10;
public const int ScorePropRecoverFromDizziness = 30;


public const int ScoreBecomeInvisible = 10;
public const int ScoreBecomeInvisible = 15;
public const int ScoreInspire = ScorePropAddSpeed; public const int ScoreInspire = ScorePropAddSpeed;
#endregion #endregion
#region 攻击与子弹相关 #region 攻击与子弹相关
@@ -185,7 +190,7 @@ namespace Preparation.Utility
public const int MinAP = 0; // 最小攻击力 public const int MinAP = 0; // 最小攻击力
public const int MaxAP = int.MaxValue; // 最大攻击力 public const int MaxAP = int.MaxValue; // 最大攻击力


public const int factorDamageGenerator = 2;//子弹对电机的破坏=factorDamageGenerator*AP;
public const int factorDamageGenerator = 1;//子弹对电机的破坏=factorDamageGenerator*AP;
public const int bulletRadius = 200; // 默认子弹半径 public const int bulletRadius = 200; // 默认子弹半径
public const int basicBulletNum = 3; // 基本初始子弹量 public const int basicBulletNum = 3; // 基本初始子弹量


@@ -205,19 +210,22 @@ namespace Preparation.Utility
public const int commonSkillCD = 30000; // 普通技能标准冷却时间 public const int commonSkillCD = 30000; // 普通技能标准冷却时间
public const int commonSkillTime = 10000; // 普通技能标准持续时间 public const int commonSkillTime = 10000; // 普通技能标准持续时间


public const int TimeOfGhostFaintingWhenCharge = 7220;
public const int TimeOfStudentFaintingWhenCharge = 2090;
public const int TimeOfGhostStunnedWhenCharge = 7220;
public const int TimeOfStudentStunnedWhenCharge = 2090;


public const int TimeOfGhostFaintingWhenPunish = 3070;
public const int TimeOfGhostStunnedWhenPunish = 3070;
public const int factorOfTimeStunnedWhenPunish = 300 / basicApOfGhost;


public const int TimeOfGhostSwingingAfterHowl = 3070;
public const int TimeOfStudentFaintingWhenHowl = 6110;
public const int TimeOfGhostSwingingAfterHowl = 800;
public const int TimeOfStudentStunnedWhenHowl = 5500;


public const int TimeOfStunnedWhenJumpyDumpty = 3070; public const int TimeOfStunnedWhenJumpyDumpty = 3070;


public const double AddedTimeOfSpeedWhenInspire = 0.6; public const double AddedTimeOfSpeedWhenInspire = 0.6;
public const int TimeOfAddingSpeedWhenInspire = 6000; public const int TimeOfAddingSpeedWhenInspire = 6000;


public const int AddHpWhenEncourage = basicHp / 4;

#endregion #endregion
#region 道具相关 #region 道具相关
public const int PropRadius = numOfPosGridPerCell / 2; public const int PropRadius = numOfPosGridPerCell / 2;


+ 4
- 0
logic/Preparation/Utility/Transformation.cs View File

@@ -233,6 +233,8 @@ namespace Preparation.Utility
return Protobuf.TrickerType.Klee; return Protobuf.TrickerType.Klee;
case Preparation.Utility.CharacterType.ANoisyPerson: case Preparation.Utility.CharacterType.ANoisyPerson:
return Protobuf.TrickerType.ANoisyPerson; return Protobuf.TrickerType.ANoisyPerson;
case CharacterType.Idol:
return TrickerType.Idol;
default: default:
return Protobuf.TrickerType.NullTrickerType; return Protobuf.TrickerType.NullTrickerType;
} }
@@ -247,6 +249,8 @@ namespace Preparation.Utility
return Preparation.Utility.CharacterType.Klee; return Preparation.Utility.CharacterType.Klee;
case Protobuf.TrickerType.ANoisyPerson: case Protobuf.TrickerType.ANoisyPerson:
return Preparation.Utility.CharacterType.ANoisyPerson; return Preparation.Utility.CharacterType.ANoisyPerson;
case TrickerType.Idol:
return CharacterType.Idol;
default: default:
return Preparation.Utility.CharacterType.Null; return Preparation.Utility.CharacterType.Null;
} }


+ 1
- 4
logic/Preparation/Utility/XY.cs View File

@@ -75,10 +75,7 @@ namespace Preparation.Utility
return Math.Atan2(y, x); return Math.Atan2(y, x);
} }


public override bool Equals(object obj)
{
throw new NotImplementedException();
}
public override bool Equals(object obj) => throw new NotImplementedException();


public override int GetHashCode() public override int GetHashCode()
{ {


+ 149
- 103
logic/Server/CopyInfo.cs View File

@@ -3,13 +3,14 @@ using System.Collections.Generic;
using GameClass.GameObj; using GameClass.GameObj;
using System.Numerics; using System.Numerics;
using Preparation.Utility; using Preparation.Utility;
using Gaming;


namespace Server namespace Server
{ {


public static class CopyInfo public static class CopyInfo
{ {
public static MessageOfObj? Auto(GameObj gameObj)
public static MessageOfObj? Auto(GameObj gameObj, int time)
{ {
switch (gameObj.Type) switch (gameObj.Type)
{ {
@@ -29,9 +30,9 @@ namespace Server
case Preparation.Utility.GameObjType.Generator: case Preparation.Utility.GameObjType.Generator:
return Classroom((Generator)gameObj); return Classroom((Generator)gameObj);
case Preparation.Utility.GameObjType.Chest: case Preparation.Utility.GameObjType.Chest:
return Chest((Chest)gameObj);
return Chest((Chest)gameObj, time);
case Preparation.Utility.GameObjType.Doorway: case Preparation.Utility.GameObjType.Doorway:
return Gate((Doorway)gameObj);
return Gate((Doorway)gameObj, time);
case Preparation.Utility.GameObjType.EmergencyExit: case Preparation.Utility.GameObjType.EmergencyExit:
if (((EmergencyExit)gameObj).CanOpen) if (((EmergencyExit)gameObj).CanOpen)
return HiddenGate((EmergencyExit)gameObj); return HiddenGate((EmergencyExit)gameObj);
@@ -43,133 +44,156 @@ namespace Server
} }
public static MessageOfObj? Auto(MessageOfNews news) public static MessageOfObj? Auto(MessageOfNews news)
{ {
MessageOfObj objMsg = new();
objMsg.NewsMessage = news;
MessageOfObj objMsg = new()
{
NewsMessage = news
};
return objMsg; return objMsg;
} }


private static MessageOfObj? Student(Student player) private static MessageOfObj? Student(Student player)
{ {
MessageOfObj msg = new MessageOfObj();
if (player.IsGhost()) return null; if (player.IsGhost()) return null;
msg.StudentMessage = new();
MessageOfObj msg = new()
{
StudentMessage = new()
{
X = player.Position.x,
Y = player.Position.y,
Speed = player.MoveSpeed,
Determination = player.HP,
Addiction = player.GamingAddiction,
Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)player.Place),
Guid = player.ID,

PlayerState = Transformation.ToPlayerState((PlayerStateType)player.PlayerState),
PlayerId = player.PlayerID,
ViewRange = player.ViewRange,
Radius = player.Radius,
DangerAlert = (player.BgmDictionary.ContainsKey(BgmType.GhostIsComing)) ? player.BgmDictionary[BgmType.GhostIsComing] : 0,
Score = player.Score,
TreatProgress = player.DegreeOfTreatment,
RescueProgress = player.TimeOfRescue,


msg.StudentMessage.X = player.Position.x;
msg.StudentMessage.Y = player.Position.y;
msg.StudentMessage.Speed = player.MoveSpeed;
msg.StudentMessage.Determination = player.HP;
msg.StudentMessage.Addiction = player.GamingAddiction;
BulletType = Transformation.ToBulletType((Preparation.Utility.BulletType)player.BulletOfPlayer),
LearningSpeed = player.FixSpeed,
TreatSpeed = player.TreatSpeed,
FacingDirection = player.FacingDirection.Angle(),
StudentType = Transformation.ToStudentType(player.CharacterType)
}
};


foreach (var keyValue in player.TimeUntilActiveSkillAvailable) foreach (var keyValue in player.TimeUntilActiveSkillAvailable)
msg.StudentMessage.TimeUntilSkillAvailable.Add(keyValue.Value); msg.StudentMessage.TimeUntilSkillAvailable.Add(keyValue.Value);
for (int i = 0; i < GameData.maxNumOfSkill - player.TimeUntilActiveSkillAvailable.Count(); ++i)
for (int i = 0; i < GameData.maxNumOfSkill - player.TimeUntilActiveSkillAvailable.Count; ++i)
msg.StudentMessage.TimeUntilSkillAvailable.Add(-1); msg.StudentMessage.TimeUntilSkillAvailable.Add(-1);


foreach (var value in player.PropInventory) foreach (var value in player.PropInventory)
msg.StudentMessage.Prop.Add(Transformation.ToPropType(value.GetPropType())); msg.StudentMessage.Prop.Add(Transformation.ToPropType(value.GetPropType()));


msg.StudentMessage.Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)player.Place);
msg.StudentMessage.Guid = player.ID;

msg.StudentMessage.PlayerState = Transformation.ToPlayerState((PlayerStateType)player.PlayerState);
msg.StudentMessage.PlayerId = player.PlayerID;
msg.StudentMessage.ViewRange = player.ViewRange;
msg.StudentMessage.Radius = player.Radius;
msg.StudentMessage.DangerAlert = (player.BgmDictionary.ContainsKey(BgmType.GhostIsComing)) ? player.BgmDictionary[BgmType.GhostIsComing] : 0;
msg.StudentMessage.Score = player.Score;
msg.StudentMessage.TreatProgress = player.DegreeOfTreatment;
msg.StudentMessage.RescueProgress = player.TimeOfRescue;

foreach (KeyValuePair<Preparation.Utility.BuffType, bool> kvp in player.Buff) foreach (KeyValuePair<Preparation.Utility.BuffType, bool> kvp in player.Buff)
{ {
if (kvp.Value) if (kvp.Value)
msg.StudentMessage.Buff.Add(Transformation.ToStudentBuffType(kvp.Key)); msg.StudentMessage.Buff.Add(Transformation.ToStudentBuffType(kvp.Key));
} }


msg.StudentMessage.BulletType = Transformation.ToBulletType((Preparation.Utility.BulletType)player.BulletOfPlayer);
msg.StudentMessage.LearningSpeed = player.FixSpeed;
msg.StudentMessage.TreatSpeed = player.TreatSpeed;
msg.StudentMessage.FacingDirection = player.FacingDirection.Angle();
msg.StudentMessage.StudentType = Transformation.ToStudentType(player.CharacterType);
return msg; return msg;
} }


private static MessageOfObj? Tricker(Character player) private static MessageOfObj? Tricker(Character player)
{ {
MessageOfObj msg = new MessageOfObj();
if (!player.IsGhost()) return null; if (!player.IsGhost()) return null;
msg.TrickerMessage = new();
MessageOfObj msg = new()
{
TrickerMessage = new()
{
X = player.Position.x,
Y = player.Position.y,
Speed = player.MoveSpeed,
Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)player.Place),

TrickerType = Transformation.ToTrickerType(player.CharacterType),
Guid = player.ID,
Score = player.Score,
PlayerId = player.PlayerID,
ViewRange = player.ViewRange,
Radius = player.Radius,
PlayerState = Transformation.ToPlayerState((PlayerStateType)player.PlayerState),
TrickDesire = (player.BgmDictionary.ContainsKey(BgmType.StudentIsApproaching)) ? player.BgmDictionary[BgmType.StudentIsApproaching] : 0,
ClassVolume = (player.BgmDictionary.ContainsKey(BgmType.GeneratorIsBeingFixed)) ? player.BgmDictionary[BgmType.GeneratorIsBeingFixed] : 0,
FacingDirection = player.FacingDirection.Angle(),
BulletType = Transformation.ToBulletType((Preparation.Utility.BulletType)player.BulletOfPlayer)
}
};


msg.TrickerMessage.X = player.Position.x;
msg.TrickerMessage.Y = player.Position.y;
msg.TrickerMessage.Speed = player.MoveSpeed;
foreach (var keyValue in player.TimeUntilActiveSkillAvailable) foreach (var keyValue in player.TimeUntilActiveSkillAvailable)
msg.TrickerMessage.TimeUntilSkillAvailable.Add(keyValue.Value); msg.TrickerMessage.TimeUntilSkillAvailable.Add(keyValue.Value);
for (int i = 0; i < GameData.maxNumOfSkill - player.TimeUntilActiveSkillAvailable.Count(); ++i)
for (int i = 0; i < GameData.maxNumOfSkill - player.TimeUntilActiveSkillAvailable.Count; ++i)
msg.TrickerMessage.TimeUntilSkillAvailable.Add(-1); msg.TrickerMessage.TimeUntilSkillAvailable.Add(-1);


msg.TrickerMessage.Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)player.Place);
foreach (var value in player.PropInventory) foreach (var value in player.PropInventory)
msg.TrickerMessage.Prop.Add(Transformation.ToPropType(value.GetPropType())); msg.TrickerMessage.Prop.Add(Transformation.ToPropType(value.GetPropType()));

msg.TrickerMessage.TrickerType = Transformation.ToTrickerType(player.CharacterType);
msg.TrickerMessage.Guid = player.ID;
msg.TrickerMessage.Score = player.Score;
msg.TrickerMessage.PlayerId = player.PlayerID;
msg.TrickerMessage.ViewRange = player.ViewRange;
msg.TrickerMessage.Radius = player.Radius;
msg.TrickerMessage.PlayerState = Transformation.ToPlayerState((PlayerStateType)player.PlayerState);
msg.TrickerMessage.TrickDesire = (player.BgmDictionary.ContainsKey(BgmType.StudentIsApproaching)) ? player.BgmDictionary[BgmType.StudentIsApproaching] : 0;
msg.TrickerMessage.ClassVolume = (player.BgmDictionary.ContainsKey(BgmType.GeneratorIsBeingFixed)) ? player.BgmDictionary[BgmType.GeneratorIsBeingFixed] : 0;
msg.TrickerMessage.FacingDirection = player.FacingDirection.Angle();
msg.TrickerMessage.BulletType = Transformation.ToBulletType((Preparation.Utility.BulletType)player.BulletOfPlayer);
foreach (KeyValuePair<Preparation.Utility.BuffType, bool> kvp in player.Buff) foreach (KeyValuePair<Preparation.Utility.BuffType, bool> kvp in player.Buff)
{ {
if (kvp.Value) if (kvp.Value)
msg.TrickerMessage.Buff.Add(Transformation.ToTrickerBuffType(kvp.Key)); msg.TrickerMessage.Buff.Add(Transformation.ToTrickerBuffType(kvp.Key));
} }



return msg; return msg;
} }


private static MessageOfObj Bullet(Bullet bullet) private static MessageOfObj Bullet(Bullet bullet)
{ {
MessageOfObj msg = new MessageOfObj();
msg.BulletMessage = new();
msg.BulletMessage.X = bullet.Position.x;
msg.BulletMessage.Y = bullet.Position.y;
msg.BulletMessage.FacingDirection = bullet.FacingDirection.Angle();
msg.BulletMessage.Guid = bullet.ID;
msg.BulletMessage.Team = (bullet.Parent.IsGhost()) ? PlayerType.TrickerPlayer : PlayerType.StudentPlayer;
msg.BulletMessage.Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)bullet.Place);
msg.BulletMessage.BombRange = bullet.BulletBombRange;
msg.BulletMessage.Speed = bullet.Speed;
MessageOfObj msg = new()
{
BulletMessage = new()
{
Type = Transformation.ToBulletType(bullet.TypeOfBullet),
X = bullet.Position.x,
Y = bullet.Position.y,
FacingDirection = bullet.FacingDirection.Angle(),
Guid = bullet.ID,
Team = (bullet.Parent.IsGhost()) ? PlayerType.TrickerPlayer : PlayerType.StudentPlayer,
Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)bullet.Place),
BombRange = bullet.BulletBombRange,
Speed = bullet.Speed
}
};
return msg; return msg;
} }


private static MessageOfObj Prop(Prop prop) private static MessageOfObj Prop(Prop prop)
{ {
MessageOfObj msg = new MessageOfObj();
msg.PropMessage = new();
msg.PropMessage.Type = Transformation.ToPropType(prop.GetPropType());
msg.PropMessage.X = prop.Position.x;
msg.PropMessage.Y = prop.Position.y;
msg.PropMessage.FacingDirection = prop.FacingDirection.Angle();
msg.PropMessage.Guid = prop.ID;
msg.PropMessage.Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)prop.Place);
MessageOfObj msg = new()
{
PropMessage = new()
{
Type = Transformation.ToPropType(prop.GetPropType()),
X = prop.Position.x,
Y = prop.Position.y,
FacingDirection = prop.FacingDirection.Angle(),
Guid = prop.ID,
Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)prop.Place)
}
};
return msg; return msg;
} }


private static MessageOfObj BombedBullet(BombedBullet bombedBullet) private static MessageOfObj BombedBullet(BombedBullet bombedBullet)
{ {
MessageOfObj msg = new MessageOfObj();
msg.BombedBulletMessage = new();
msg.BombedBulletMessage.X = bombedBullet.bulletHasBombed.Position.x;
msg.BombedBulletMessage.Y = bombedBullet.bulletHasBombed.Position.y;
msg.BombedBulletMessage.FacingDirection = bombedBullet.FacingDirection.Angle();
msg.BombedBulletMessage.MappingId = bombedBullet.MappingID;
msg.BombedBulletMessage.BombRange = bombedBullet.bulletHasBombed.BulletBombRange;
MessageOfObj msg = new()
{
BombedBulletMessage = new()
{
Type = Transformation.ToBulletType(bombedBullet.bulletHasBombed.TypeOfBullet),
X = bombedBullet.bulletHasBombed.Position.x,
Y = bombedBullet.bulletHasBombed.Position.y,
FacingDirection = bombedBullet.FacingDirection.Angle(),
MappingId = bombedBullet.MappingID,
BombRange = bombedBullet.bulletHasBombed.BulletBombRange
}
};
// Debugger.Output(bombedBullet, bombedBullet.Place.ToString()+" "+bombedBullet.Position.ToString());
return msg; return msg;
} }


@@ -187,49 +211,71 @@ namespace Server


private static MessageOfObj Classroom(Generator generator) private static MessageOfObj Classroom(Generator generator)
{ {
MessageOfObj msg = new MessageOfObj();
msg.ClassroomMessage = new();
msg.ClassroomMessage.X = generator.Position.x;
msg.ClassroomMessage.Y = generator.Position.y;
msg.ClassroomMessage.Progress = generator.DegreeOfRepair;
MessageOfObj msg = new()
{
ClassroomMessage = new()
{
X = generator.Position.x,
Y = generator.Position.y,
Progress = generator.DegreeOfRepair
}
};
return msg; return msg;
} }
private static MessageOfObj Gate(Doorway doorway)
private static MessageOfObj Gate(Doorway doorway, int time)
{ {
MessageOfObj msg = new MessageOfObj();
msg.GateMessage = new();
msg.GateMessage.X = doorway.Position.x;
msg.GateMessage.Y = doorway.Position.y;
msg.GateMessage.Progress = doorway.OpenDegree;
MessageOfObj msg = new()
{
GateMessage = new()
{
X = doorway.Position.x,
Y = doorway.Position.y
}
};
int progress = ((doorway.OpenStartTime > 0) ? (time - doorway.OpenStartTime) : 0) + doorway.OpenDegree;
msg.GateMessage.Progress = (progress > GameData.degreeOfOpenedDoorway) ? GameData.degreeOfOpenedDoorway : progress;
return msg; return msg;
} }
private static MessageOfObj HiddenGate(EmergencyExit Exit) private static MessageOfObj HiddenGate(EmergencyExit Exit)
{ {
MessageOfObj msg = new MessageOfObj();
msg.HiddenGateMessage = new();
msg.HiddenGateMessage.X = Exit.Position.x;
msg.HiddenGateMessage.Y = Exit.Position.y;
msg.HiddenGateMessage.Opened = Exit.IsOpen;
MessageOfObj msg = new()
{
HiddenGateMessage = new()
{
X = Exit.Position.x,
Y = Exit.Position.y,
Opened = Exit.IsOpen
}
};
return msg; return msg;
} }


private static MessageOfObj Door(Door door) private static MessageOfObj Door(Door door)
{ {
MessageOfObj msg = new MessageOfObj();
msg.DoorMessage = new();
msg.DoorMessage.X = door.Position.x;
msg.DoorMessage.Y = door.Position.y;
msg.DoorMessage.Progress = door.OpenOrLockDegree;
msg.DoorMessage.IsOpen = door.IsOpen;
MessageOfObj msg = new()
{
DoorMessage = new()
{
X = door.Position.x,
Y = door.Position.y,
Progress = door.OpenOrLockDegree,
IsOpen = door.IsOpen
}
};
return msg; return msg;
} }
private static MessageOfObj Chest(Chest chest)
private static MessageOfObj Chest(Chest chest, int time)
{ {
MessageOfObj msg = new MessageOfObj();
msg.ChestMessage = new();
msg.ChestMessage.X = chest.Position.x;
msg.ChestMessage.Y = chest.Position.y;
msg.ChestMessage.Progress = chest.OpenDegree;
MessageOfObj msg = new()
{
ChestMessage = new()
{
X = chest.Position.x,
Y = chest.Position.y
}
};
int progress = (chest.OpenStartTime > 0) ? ((time - chest.OpenStartTime) * chest.WhoOpen.SpeedOfOpenChest) : 0;
msg.ChestMessage.Progress = (progress > GameData.degreeOfOpenedChest) ? GameData.degreeOfOpenedChest : progress;
return msg; return msg;
} }
} }


+ 12
- 8
logic/Server/GameServer.cs View File

@@ -130,30 +130,34 @@ namespace Server
case GameState.GameRunning: case GameState.GameRunning:
case GameState.GameEnd: case GameState.GameEnd:
case GameState.GameStart: case GameState.GameStart:
currentGameInfo.ObjMessage.Add(currentMapMsg);
if (gameState == GameState.GameStart || IsSpectatorJoin)
{
currentGameInfo.ObjMessage.Add(currentMapMsg);
IsSpectatorJoin = false;
}
int time = game.GameMap.Timer.nowTime();
foreach (GameObj gameObj in gameObjList) foreach (GameObj gameObj in gameObjList)
{ {
MessageOfObj? msg = CopyInfo.Auto(gameObj);
if (msg != null) currentGameInfo.ObjMessage.Add(CopyInfo.Auto(gameObj));
MessageOfObj? msg = CopyInfo.Auto(gameObj, time);
if (msg != null) currentGameInfo.ObjMessage.Add(msg);
} }
lock (newsLock) lock (newsLock)
{ {
foreach (var news in currentNews) foreach (var news in currentNews)
{ {
MessageOfObj? msg = CopyInfo.Auto(news); MessageOfObj? msg = CopyInfo.Auto(news);
if (msg != null) currentGameInfo.ObjMessage.Add(CopyInfo.Auto(news));
if (msg != null) currentGameInfo.ObjMessage.Add(msg);
} }
currentNews.Clear(); currentNews.Clear();
} }
currentGameInfo.GameState = gameState; currentGameInfo.GameState = gameState;
currentGameInfo.AllMessage = GetMessageOfAll();
currentGameInfo.AllMessage = GetMessageOfAll(time);
mwr?.WriteOne(currentGameInfo); mwr?.WriteOne(currentGameInfo);
break; break;
default: default:
break; break;
} }
} }

foreach (var kvp in semaDict) foreach (var kvp in semaDict)
{ {
kvp.Value.Item1.Release(); kvp.Value.Item1.Release();
@@ -208,10 +212,10 @@ namespace Server
return false; return false;
} }


private MessageOfAll GetMessageOfAll()
private MessageOfAll GetMessageOfAll(int time)
{ {
MessageOfAll msg = new MessageOfAll(); MessageOfAll msg = new MessageOfAll();
msg.GameTime = game.GameMap.Timer.nowTime();
msg.GameTime = time;
msg.SubjectFinished = (int)game.GameMap.NumOfRepairedGenerators; msg.SubjectFinished = (int)game.GameMap.NumOfRepairedGenerators;
msg.StudentGraduated = (int)game.GameMap.NumOfEscapedStudent; msg.StudentGraduated = (int)game.GameMap.NumOfEscapedStudent;
msg.StudentQuited = (int)game.GameMap.NumOfDeceasedStudent; msg.StudentQuited = (int)game.GameMap.NumOfDeceasedStudent;


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

@@ -2,7 +2,7 @@
"profiles": { "profiles": {
"Server": { "Server": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "--ip 0.0.0.0 -p 8888 --studentCount 1 --trickerCount 0"
"commandLineArgs": "--ip 0.0.0.0 -p 8888 --characterID 2030"
} }
} }
} }

+ 19
- 4
logic/Server/RpcServices.cs View File

@@ -16,6 +16,22 @@ namespace Server
{ {
public partial class GameServer : AvailableService.AvailableServiceBase public partial class GameServer : AvailableService.AvailableServiceBase
{ {
protected object spectatorLock = new object();
protected bool isSpectatorJoin = false;
protected bool IsSpectatorJoin
{
get
{
lock (spectatorLock)
return isSpectatorJoin;
}

set
{
lock (spectatorLock)
isSpectatorJoin = value;
}
}
public override Task<BoolRes> TryConnection(IDMsg request, ServerCallContext context) public override Task<BoolRes> TryConnection(IDMsg request, ServerCallContext context)
{ {
#if DEBUG #if DEBUG
@@ -53,6 +69,7 @@ namespace Server
{ {
semaDict.Add(request.PlayerId, temp); semaDict.Add(request.PlayerId, temp);
} }
IsSpectatorJoin = true;
} }
do do
{ {
@@ -147,8 +164,7 @@ namespace Server
return Task.FromResult(boolRes); return Task.FromResult(boolRes);
} }
var gameID = communicationToGameID[request.PlayerId]; var gameID = communicationToGameID[request.PlayerId];
game.Attack(gameID, request.Angle);
boolRes.ActSuccess = true;
boolRes.ActSuccess = game.Attack(gameID, request.Angle);
return Task.FromResult(boolRes); return Task.FromResult(boolRes);
} }


@@ -170,8 +186,7 @@ namespace Server
return Task.FromResult(moveRes); return Task.FromResult(moveRes);
} }
var gameID = communicationToGameID[request.PlayerId]; var gameID = communicationToGameID[request.PlayerId];
game.MovePlayer(gameID, (int)request.TimeInMilliseconds, request.Angle);
moveRes.ActSuccess = true;
moveRes.ActSuccess = game.MovePlayer(gameID, (int)request.TimeInMilliseconds, request.Angle);
if (!game.GameMap.Timer.IsGaming) moveRes.ActSuccess = false; if (!game.GameMap.Timer.IsGaming) moveRes.ActSuccess = false;
return Task.FromResult(moveRes); return Task.FromResult(moveRes);
} }


+ 4
- 2
logic/cmd/gameServer.cmd View File

@@ -4,7 +4,7 @@ start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --


ping -n 2 127.0.0.1 > NUL ping -n 2 127.0.0.1 > NUL


start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 4 --type 2 --occupation 2
start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 4 --type 2 --occupation 1


start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 0 --type 1 --occupation 1 start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 0 --type 1 --occupation 1


@@ -12,4 +12,6 @@ start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --ch


start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2 --type 1 --occupation 3 start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2 --type 1 --occupation 3


start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 3 --type 1 --occupation 3
start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 3 --type 1 --occupation 3

::start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2030

+ 1
- 1
logic/cmd/playback.cmd View File

@@ -1,5 +1,5 @@
@echo off @echo off


start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --playbackFile .\test.thuaipb --playbackSpeed 3
start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --playbackFile .\test.thuaipb --playbackSpeed 1


ping -n 2 127.0.0.1 > NUL ping -n 2 127.0.0.1 > NUL

+ 28
- 0
logic/使用文档.md View File

@@ -0,0 +1,28 @@


## 键鼠控制

| 键位 | 效果 |
| ------------ | ---------------------------------------------- |
| W/NumPad8 | (Both)向上移动 |
| S/NumPad2 | (Both)向下移动 |
| D/NumPad6 | (Both)向右移动 |
| A/NumPad4 | (Both)向左移动 |
| J | (Tri)攻击,方向向上 |
| 鼠标双击某点 | (Tri)攻击,方向与从Tricker指向该点的向量相同 |
| K | (Stu)开始学习 |
| R | (Stu)开始唤醒(陷入沉迷状态的同伴) |
| T | (Stu)开始勉励(学习毅力下降的同伴) |
| G | (Stu)发出毕业请求 |
| H | (Stu)申请毕业(或称为开校门) |
| O | (Both)开(教学楼)门 |
| P | (Both)关(教学楼)门 |
| U | (Both)翻窗 |
| I | (Both)翻箱子 |
| E | (Both)结束当前行动,回到Idle状态 |
| F | (Both)随机捡起一个在周围的道具 |
| C | (Both)随机扔下一个已经持有的道具 |
| V | (Both)随机使用一个已经持有的道具 |
| B | (Both)使用0号技能 |
| N | (Both)使用1号技能 |
| M | (Both)使用2号技能 |

+ 0
- 325
logic/规则Logic.md View File

@@ -1,325 +0,0 @@
# 规则Logic

## 说明
- *斜体表示Logic底层尚未(完全)实现*
- []表示待决定

## 简要规则

### 地图
- 地图为矩形区域,地图上的游戏对象坐标为(x, y),且x和y均为整数。
- **x坐标轴正方向竖直向下,y坐标轴正方向水平向右**;
- **极坐标以x坐标轴为极轴,角度逆时针为正方向**。
- 地图由50 * 50个格子构成,其中每个格子代表1000 * 1000的正方形。每个格子的编号(CellX,CellY)可以计算得到:
- 略
- 地图上的每个格子有自己的区域类型:陆地、墙、草地、电机、出口、紧急出口、门、窗、箱子

### 人物
- 人物半径为800
- 游戏人物共有17种不可叠加的状态:(加^为学生独有)
1. 普通状态
2. 学习^
3. 被治疗^
4. 在治疗^
5. 开或锁门
6. 翻箱
7. 使用技能
8. 正在毕业^
9. 唤醒他人中^

10. 被唤醒中(从沉迷状态中^
11. 沉迷^
12. 退学^
13. 毕业^
14. 被眩晕
15. 前摇
16. 后摇
17. 翻窗

- 其中后8项为不可行动状态
- 不加声明,不可行动状态中的玩家各项指令是无效的

### 交互
- 除了翻窗,交互目标与交互者在一个九宫格内则为可交互
- 翻窗时玩家应当在目标前后左右一个格子内

#### 破译与逃脱
- 每张地图都有10台电机,学生需要破译其中的**7台**,并开启任意大门(总所需时间为18秒)后从开启的大门逃脱,亦或在只剩1名学生的情况下从紧急出口逃脱;
- 紧急出口会在电机破译完成3台的情况下在地图的3-5个固定紧急出口刷新点之一随机刷新显示。
- 当学生只剩1名时,紧急出口将会自动打开,该学生可从紧急出口逃脱。
- 大门开启的进度不清空

#### 攻击
- 无论近战远程均产生bullet以表示攻击所至距离
- 如果有前摇,前摇最大时间一般为攻击最远距离/bullet移动速度。前摇期间攻击被打断时,子弹消失。
- 无论搞蛋鬼或学生,攻击后,通常会出现一段无伤害判定的攻击动作后置时间,称为后摇。击中物体时后摇更长
- 搞蛋鬼可以攻击未修完的作业,造成(攻击力*2/100000)%的
- 捣蛋鬼攻击或一些特定技能击中以下状态的学生,将使学生眩晕4.3s
1. 处于前摇或后摇
2. 治疗或解救他人
3. 修理电机
4. 开锁门
5. 翻窗
6. 开启箱子

#### 治疗
- 治疗时每毫秒增加相当于治疗者治疗速度的被治疗程度
- 当达到被治疗程度达到1500000或者最大血量与当前血量的差值时,治疗结束。
- 治疗中断时,被治疗程度保留;被治疗者遭到攻击时被治疗程度清空

#### 沉迷与唤醒
- 当学生血量归零时,学生自动原地进入沉迷状态,每毫秒增加1沉迷度
- 该学生可由其他的学生唤醒,唤醒后,血量恢复至1/2并可以重新行动。沉迷程度不清空。
- 一般情况下,唤醒时间为1秒。
- 进入沉迷状态时。如果学生原本沉迷程度在(0,其最大沉迷度/3)中,学生沉迷程度直接变为其最大沉迷度/3;如果学生原本沉迷程度在[其最大沉迷度/3,其最大沉迷度x2/3)中,学生沉迷程度直接变为其最大沉迷度x2/3;如果学生原本沉迷程度大于其最大沉迷度x2/3,从游戏中出局;
- 当学生沉迷程度达到该玩家最大沉迷程度时,从游戏中出局

#### 门
- 门分别属于三个教学区:三教,五教,六教
- 需要拿到对应教学区的钥匙才能打开或锁住对应的门
- 钥匙只会出现在箱子中,每个教学区都有2把钥匙
- 当门所在格子内有人时,无法锁门
- 锁门时其他人可以进入门所在格子,锁门过程中断

#### 窗
- 通常情况下捣蛋鬼翻越窗户的速度高于学生。
- 有人正在翻越窗户时,其他玩家均不可以翻越该窗户。

#### 箱子
- 开启箱子后将有2个随机道具掉落在玩家位置。
- 开启箱子的基础持续时间为10秒。
- 箱子道具不刷新

#### Bgm
1. 不详的感觉:捣蛋鬼进入(学生的警戒半径/捣蛋鬼的隐蔽度)时,学生收到;捣蛋鬼距离学生越近,Bgm音量越大。bgmVolume=(警戒半径/二者距离)
2. 期待搞事的感觉:学生进入(捣蛋鬼的警戒半径/学生的隐蔽度)时,捣蛋鬼收到;捣蛋鬼距离学生越近,Bgm音量越大。bgmVolume=(警戒半径/可被发觉的最近的学生距离)
3. 学习的声音: 捣蛋鬼警戒半径内有人学习时收到;bgmVolume=(警戒半径x学习进度百分比)/二者距离

### 得分

#### 屠夫

- [Tricker对Student造成伤害时,得伤害*100/基本伤害(1500000)分。]
- *[使用道具/技能得分]*
- 不同道具/技能有不同得分
- 使Student进入沉迷状态时,得50分。
- 使人类进入眩晕状态时,得25分。
- 每淘汰一个Student,得1000分
- 主动解除眩晕,得15分
- 开锁门

#### 人类
- 修机得分
- 人类每修n%的电机,得n分
- 修完一台电机,额外得?分
- [牵制得分]
- 使用道具/技能得分
- 不同道具/技能有不同得分
- 使屠夫进入特殊状态得分(如使之眩晕)
- 救人
- 治疗
- 逃脱
- 解除眩晕
- 开锁门

## 道具
| 道具 | 对学生增益 | [学生得分条件] | 对搞蛋鬼增益 | [搞蛋鬼得分条件] |
| :-------- | :-------------------------------------- | :-----------------| :-------------------------------------- |:-----------------|
| Key3 | 能开启3教的门 |不得分| 能开启3教的门 |不得分|
| Key5 | 能开启5教的门 |不得分| 能开启3教的门 |不得分|
| Key6 | 能开启6教的门 |不得分| 能开启3教的门 |不得分|
| AddSpeed | 提高移动速度,持续10s |得分?| 提高移动速度,持续10s |得分?|
| AddLifeOrClairaudience |若在10s内Hp归零,该增益消失以使Hp保留100|在10s内Hp归零,得分? |10秒内下一次攻击增伤1800000|10秒内有一次攻击,得分? |
| AddHpOrAp |回血1500000 | 回血成功 | 10秒内下一次攻击增伤1800000|10秒内有一次攻击,得分? |
| ShieldOrSpear | 10秒内能抵挡一次伤害 | 10秒内成功抵挡一次伤害 |10秒内下一次攻击能破盾,如果对方无盾,则增伤900000| 10秒内攻击中学生|
| RecoveryFromDizziness | 使用瞬间从眩晕状态中恢复 | 成功从眩晕状态中恢复,得分?|使用瞬间从眩晕状态中恢复 | 成功从眩晕状态中恢复,得分?|

## 职业与技能

### 捣蛋鬼

| 捣蛋鬼职业 | 基本量 | Assassin | Klee | 喧哗者ANoisyPerson |
| :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- |
| 移动速度/s | 1,503 | 1.1 | 1 | 1.07 |
| 隐蔽度 | 1.0 | 1.5 | 1 | 0.8 |
| 警戒范围 | 17000 | 1.3 | 1 | 0.9 |
| 视野范围 | 15000 | 1.2 | 1 | 1 |
| 开锁门时间/s | 41.3% | 1 | 1 | 1 |
| 翻窗速度 | 1270 | 1 | 1 | 1.1 |
| 翻箱时间/s | 10% | 1 | 1.1 | 1 |

#### 刺客
- 普通攻击为 搞蛋鬼的一般攻击
- 主动技能
- 隐身
- CD:30s 持续时间:6s
- 在持续时间内玩家隐身
- 使用瞬间得分
- 使用飞刀
- CD:20s 持续时间:1s
- 在持续时间内,攻击类型变为飞刀
- 不直接得分

#### Klee
- 普通攻击为 搞蛋鬼的一般攻击
- 主动技能
- 蹦蹦炸弹
- CD:15s 持续时间:3s
- 在持续时间内,攻击类型变为蹦蹦炸弹
- 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上90°,270° 发出2个小炸弹
- 2个小炸弹运动停止前会因为碰撞爆炸,停止运动后学生碰撞会造成眩晕3.07s
- 不直接得分,通过眩晕等获得对应得分

#### 喧哗者
- 普通攻击为 搞蛋鬼的一般攻击
- 主动技能
- 嚎叫
- CD:25s
- 使用瞬间,在视野半径范围内(不是可视区域)的学生被眩晕6110ms,自己进入3070ms的后摇
- 通过眩晕获得对应得分
- 特性
- 在场所有学生Bgm系统被设为无用的值


### 学生(&老师)

| 学生职业 | 基本量 | 教师Teacher | 健身狂Athlete | 学霸StraightAStudent | 开心果Sunshine |
| :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | :--------------------- |
| 移动速度 | 1,270 | 3 / 4 | 1.1 | 0.8 | 1 |
| 最大毅力值 | 3000000 | 10 | 1 | 1.1 | 32/30 |
| 最大沉迷度 | 60000 | 10 | 0.9 | 1.3 | 1.1 |
| 学习一科速度/s | 1.23% | 0 | 0.6 | 1.1 | 1 |
| 治疗速度 | 100 | 0.7 | 0.8 | 0.8 | 2 |
| 隐蔽度 | 1.0 | 0.5 | 0.9 | 0.9 | 1 |
| 警戒范围 | 15000 | 0.5 | 1 | 0.9 | 1 |
| 视野范围 | 10000 | 0.9 | 1.1 | 0.9 | 1 |
| 开锁门时间/s | 4.13% | 1 | 1 | 1 | 1 |
| 翻窗速度 | 1270 | 0.5 | 1.2 | 10/12 | 1 |
| 翻箱时间/s | 10% | 1 | 1 | 1 | 1 |

#### 运动员
- 主动技能
- 冲撞
- CD:24s 持续时间:5s
- 在持续时间内,速度变为三倍,期间撞到捣蛋鬼,会导致捣蛋鬼眩晕7.22s,学生眩晕2.09s
- 通过眩晕获得对应得分

#### 教师
- 主动技能
- 惩罚
- CD:30s
- 使用瞬间,在可视范围内的使用技能状态中、攻击前后摇的捣蛋鬼会被眩晕(3070)ms,
- 通过眩晕获得对应得分
- 特性
- 教师无法获得牵制得分

#### 学霸
- 特性
- 冥想
- 当玩家处于可接受指令状态且不在修机时,会积累学习进度,速度为0.3%/ms
- 受到攻击(并非伤害)或眩晕或翻窗(或攻击他人)学习进度清零
- 主动技能5
- 写答案
- CD:30s
- 使用瞬间,对于可互动范围内的一台电机增加这个学习进度
- 通过修机获得对应得分

#### 开心果
- 主动技能
- 唤醒
- CD:60s
- 使用瞬间,唤醒可视范围内一个沉迷中的人
- 通过唤醒获得对应得分
- 勉励
- CD:60s
- 使用瞬间,治疗完成可视范围内一个毅力不足的人
- 通过治疗获得对应得分
- 鼓舞
- CD:60s
- 使用瞬间,可视范围内学生(包括自己)获得持续6秒的1.6倍速Buff
- 每鼓舞一个学生得分10

### 攻击类型
| 攻击类型 |搞蛋鬼的一般攻击CommonAttackOfGhost| 飞刀FlyingKnife | 蹦蹦炸弹BombBomb | JumpyDumpty |
| :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- |
| 子弹爆炸范围 | 0 | 0 | 1000 | 500 |
| 子弹攻击距离 | 1100 | 39000 | 1100 | 2200 |
| 攻击力 | 1500000 | 1,200,000 | 1,800,000 | 900000 |
| 移动速度/s | 3700 | 7,400 | 3000 | 4300 |
| 有无前摇(ms) | 297 | 500 | 366 | 0 |
|未攻击至目标时的后摇(ms)| 800 | 0 | 3700 | 0 |
|攻击至目标时的后摇(ms)| 3700 | 0 | 3700 | 0 |
| CD(ms) | 800 | 400 | 3000 | - |
| 最大子弹容量 | 1 | 1 | 1 | - |

## 游戏数据
请自行查看Logic/Preparation/Utility/GameData.cs


## 细则

### 特殊说明
- 不加说明,这里“学生”往往包括职业“教师”

### 初始状态
- 玩家出生点固定且一定为空地

### 交互
- 在指令仍在进行时,重复发出同一类型的交互指令是无效的,你需要先发出Stop指令终止进行的指令
- 实际上救援或治疗不同的人是有效的

### 破译与逃脱
- 紧急出口与大门对于人有碰撞体积
- 一个大门同时最多可以由一人开启

### 攻击
- 每次学生受到攻击后会损失对应子弹的攻击力的血量
- 此处,前摇指 从播放攻击动作开始 攻击者不能交互 的时间

### 沉迷与唤醒
- 在被救时沉迷度不增加
- 不能两人同时唤醒一个人

### 门
- 一扇门只允许同时一个人开锁门
- 开锁门未完成前,门状态表现为原来的状态
- 开锁门进度中断后清空

### 窗
- 攻击可以穿过窗,道具可以在窗上
- 翻越窗户是一种交互行为,翻窗一共有两个过程
- 跳上窗:从当前位置到窗边缘中点,位置瞬移,时间=距离/爬窗速度。中断时,停留在原位置
- 爬窗:从窗一侧边缘中点到另一侧格子中心,位置渐移,时间=距离/爬窗速度。中断时,停留在另一侧格子中心

### 箱子
- 地图上有8个箱子
- 同一时刻只允许一人进行开启
- 未开启完成的箱子在下一次需要重新开始开启。
- 箱子开启后其中道具才可以被观测和拿取

## 键鼠控制

| 键位 | 效果 |
| ------------ | ---------------------------------------------- |
| W/NumPad8 | (Both)向上移动 |
| S/NumPad2 | (Both)向下移动 |
| D/NumPad6 | (Both)向右移动 |
| A/NumPad4 | (Both)向左移动 |
| J | (Tri)攻击,方向向上 |
| 鼠标双击某点 | (Tri)攻击,方向与从Tricker指向该点的向量相同 |
| K | (Stu)开始学习 |
| R | (Stu)开始营救(陷入沉迷状态的同伴) |
| T | (Stu)开始治疗(学习毅力下降的同伴) |
| G | (Stu)发出毕业请求 |
| H | (Stu)申请毕业(或称为开大门) |
| O | (Both)开(教学楼)门 |
| P | (Both)关(教学楼)门 |
| U | (Both)翻窗 |
| I | (Both)翻箱子 |
| E | (Both)结束当前行动,回到Idle状态 |
| F | (Both)随机捡起一个在周围的道具 |
| C | (Both)随机扔下一个已经持有的道具 |
| V | (Both)随机使用一个已经持有的道具 |
| B | (Both)使用0号技能 |
| N | (Both)使用1号技能 |
| M | (Both)使用2号技能 |

+ 17
- 4
playback/Playback/MessageReader.cs View File

@@ -27,7 +27,7 @@ namespace Playback
public readonly uint teamCount; public readonly uint teamCount;
public readonly uint playerCount; public readonly uint playerCount;


const int bufferMaxSize = 1024 * 1024; // 1M
const int bufferMaxSize = 10 * 1024 * 1024; // 10M


public MessageReader(string fileName) public MessageReader(string fileName)
{ {
@@ -83,7 +83,8 @@ namespace Playback
public MessageToClient? ReadOne() public MessageToClient? ReadOne()
{ {
beginRead: beginRead:
if (Finished) return null;
if (Finished)
return null;
var pos = cos.Position; var pos = cos.Position;
try try
{ {
@@ -94,9 +95,21 @@ namespace Playback
catch (InvalidProtocolBufferException) catch (InvalidProtocolBufferException)
{ {
var leftByte = buffer.Length - pos; // 上次读取剩余的字节 var leftByte = buffer.Length - pos; // 上次读取剩余的字节
for (int i = 0; i < leftByte; ++i)
if (buffer.Length < bufferMaxSize / 2)
{ {
buffer[i] = buffer[pos + i];
var newBuffer = new byte[bufferMaxSize];
for (int i = 0; i < leftByte; i++)
{
newBuffer[i] = buffer[pos + i];
}
buffer = newBuffer;
}
else
{
for (int i = 0; i < leftByte; ++i)
{
buffer[i] = buffer[pos + i];
}
} }
var bufferSize = gzs.Read(buffer, (int)leftByte, (int)(buffer.Length - leftByte)) + leftByte; var bufferSize = gzs.Read(buffer, (int)leftByte, (int)(buffer.Length - leftByte)) + leftByte;
if (bufferSize == leftByte) if (bufferSize == leftByte)


+ 2
- 1
playback/Playback/MessageWriter.cs View File

@@ -12,7 +12,7 @@ namespace Playback
private CodedOutputStream cos; private CodedOutputStream cos;
private MemoryStream ms; private MemoryStream ms;
private GZipStream gzs; private GZipStream gzs;
private const int memoryCapacity = 1024 * 1024; // 1M
private const int memoryCapacity = 10 * 1024 * 1024; // 10M


private static void ClearMemoryStream(MemoryStream msToClear) private static void ClearMemoryStream(MemoryStream msToClear)
{ {
@@ -50,6 +50,7 @@ namespace Playback
{ {
cos.Flush(); cos.Flush();
gzs.Write(ms.GetBuffer(), 0, (int)ms.Length); gzs.Write(ms.GetBuffer(), 0, (int)ms.Length);
gzs.Flush();
ClearMemoryStream(ms); ClearMemoryStream(ms);
fs.Flush(); fs.Flush();
} }


Loading…
Cancel
Save