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::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::Bullet>> GetBullets() const = 0;
[[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> StudentGetSelfInfo() 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 PickProp(THUAI6::PropType prop) = 0;
virtual bool UseProp(THUAI6::PropType prop) = 0;
virtual bool ThrowProp(THUAI6::PropType prop) = 0;
virtual bool UseSkill(int32_t skillID) = 0;
virtual bool SendMessage(int64_t toID, std::string message) = 0;
virtual bool HaveMessage() = 0;
@@ -67,8 +69,8 @@ public:
virtual bool Graduate() = 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 CloseDoor() = 0;
@@ -99,6 +101,7 @@ public:
// 捡道具、使用技能
virtual std::future<bool> PickProp(THUAI6::PropType prop) = 0;
virtual std::future<bool> UseProp(THUAI6::PropType prop) = 0;
virtual std::future<bool> ThrowProp(THUAI6::PropType prop) = 0;
virtual std::future<bool> UseSkill(int32_t skillID) = 0;
virtual std::future<bool> Attack(double angleInRadian) = 0;

@@ -124,7 +127,9 @@ public:
// 获取视野内可见的道具信息
[[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 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> 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;
[[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> UseProp(THUAI6::PropType prop) override;
std::future<bool> ThrowProp(THUAI6::PropType prop) override;
std::future<bool> UseSkill(int32_t skillID) 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::Bullet>> GetBullets() const override;

[[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() 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;

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;
[[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> UseProp(THUAI6::PropType prop) override;
std::future<bool> ThrowProp(THUAI6::PropType prop) override;
std::future<bool> UseSkill(int32_t skillID) 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::Bullet>> GetBullets() const override;

[[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() 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> UseProp(THUAI6::PropType prop) override;
std::future<bool> ThrowProp(THUAI6::PropType prop) override;
std::future<bool> UseSkill(int32_t skillID) 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::Bullet>> GetBullets() const override;

[[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() 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;

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;
[[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> UseProp(THUAI6::PropType prop) override;
std::future<bool> ThrowProp(THUAI6::PropType prop) override;
std::future<bool> UseSkill(int32_t skillID) 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::Bullet>> GetBullets() const override;

[[nodiscard]] std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() 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 PickProp(THUAI6::PropType prop, int64_t playerID);
bool UseProp(THUAI6::PropType prop, int64_t playerID);
bool ThrowProp(THUAI6::PropType prop, int64_t playerID);
bool UseSkill(int32_t skillID, int64_t playerID);
bool SendMessage(int64_t toID, std::string message, int64_t playerID);
bool OpenDoor(int64_t playerID);
@@ -38,8 +39,8 @@ public:
bool Graduate(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);



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

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

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

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
#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::Student>> GetStudents() 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::Tricker> TrickerGetSelfInfo() 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 PickProp(THUAI6::PropType prop) override;
bool UseProp(THUAI6::PropType prop) override;
bool ThrowProp(THUAI6::PropType prop) override;
bool UseSkill(int32_t skillID) override;

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

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;



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

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

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

inline std::map<PlayerState, std::string> playerStateDict{
@@ -301,11 +302,11 @@ namespace THUAI6
{PlayerState::Addicted, "Addicted"},
{PlayerState::Quit, "Quit"},
{PlayerState::Graduated, "Graduated"},
{PlayerState::Treated, "Treated"},
{PlayerState::Rescued, "Rescued"},
{PlayerState::Encouraged, "Encouraged"},
{PlayerState::Roused, "Roused"},
{PlayerState::Stunned, "Stunned"},
{PlayerState::Treating, "Treating"},
{PlayerState::Rescuing, "Rescuing"},
{PlayerState::Encouraging, "Encouraging"},
{PlayerState::Rousing, "Rousing"},
{PlayerState::Swinging, "Swinging"},
{PlayerState::Attacking, "Attacking"},
{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::KLEE, THUAI6::TrickerType::Klee},
{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{
@@ -146,11 +146,11 @@ namespace Proto2THUAI6
{protobuf::PlayerState::ADDICTED, THUAI6::PlayerState::Addicted},
{protobuf::PlayerState::QUIT, THUAI6::PlayerState::Quit},
{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::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::ATTACKING, THUAI6::PlayerState::Attacking},
{protobuf::PlayerState::LOCKING, THUAI6::PlayerState::Locking},
@@ -240,9 +240,9 @@ namespace Proto2THUAI6
student->facingDirection = studentMsg.facing_direction();
student->bulletType = bulletTypeDict[studentMsg.bullet_type()];
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->timeUntilSkillAvailable.clear();
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::Klee, protobuf::TrickerType::KLEE},
{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{


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

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

// 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新
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); });
}

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)
{
return std::async(std::launch::async, [=]()
@@ -255,6 +267,16 @@ std::vector<std::shared_ptr<const THUAI6::Prop>> TrickerAPI::GetProps() const
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
{
return logic.GetFullMap();
@@ -361,16 +383,16 @@ std::future<bool> StudentAPI::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 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 logic.StartRescueMate(mateID); });
{ return logic.StartRouseMate(mateID); });
}

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

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)
{
protobuf::BoolRes useSkillResult;
@@ -171,7 +183,7 @@ bool Communication::StartLearning(int64_t playerID)
return false;
}

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

bool Communication::StartTreatMate(int64_t playerID, int64_t mateID)
bool Communication::StartEncourageMate(int64_t playerID, int64_t mateID)
{
protobuf::BoolRes healStudentResult;
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; });
}

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

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
{
return logic.GetFullMap();
@@ -545,23 +575,23 @@ std::future<bool> StudentDebugAPI::StartLearning()
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, [=]()
{ auto result = logic.StartRescueMate(mateID);
{ auto result = logic.StartRouseMate(mateID);
if (!result)
logger->warn("StartRescueMate: failed at {}ms", Time::TimeSinceStart(startPoint));
logger->warn("StartRouseMate: failed at {}ms", Time::TimeSinceStart(startPoint));
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, [=]()
{ auto result = logic.StartTreatMate(mateID);
{ auto result = logic.StartEncourageMate(mateID);
if (!result)
logger->warn("StartTreatMate: failed at {}ms", Time::TimeSinceStart(startPoint));
logger->warn("StartEncourageMate: failed at {}ms", Time::TimeSinceStart(startPoint));
return result; });
}

@@ -631,7 +661,7 @@ void StudentDebugAPI::PrintStudent() const
props += THUAI6::propTypeDict[prop] + ", ";
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("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 = "";
for (const auto& buff : student->buff)
studentBuff += THUAI6::studentBuffDict[buff] + ", ";
@@ -656,7 +686,7 @@ void TrickerDebugAPI::PrintStudent() const
props += THUAI6::propTypeDict[prop] + ", ";
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("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 = "";
for (const auto& buff : student->buff)
studentBuff += THUAI6::studentBuffDict[buff] + ", ";
@@ -748,7 +778,7 @@ void StudentDebugAPI::PrintSelfInfo() const
props += THUAI6::propTypeDict[prop] + ", ";
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("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 = "";
for (const auto& buff : student->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;
}

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::unique_lock<std::mutex> lock(mtxState);
@@ -193,6 +202,12 @@ bool Logic::UseProp(THUAI6::PropType prop)
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)
{
logger->debug("Called UseSkill");
@@ -236,16 +251,16 @@ bool Logic::StartLearning()
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)
@@ -528,6 +543,15 @@ void Logic::LoadBuffer(protobuf::MessageToClient& message)
}
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))
{
bufferState->trickers.push_back(Proto2THUAI6::Protobuf2THUAI6Tricker(item.tricker_message()));
@@ -674,6 +698,23 @@ void Logic::LoadBuffer(protobuf::MessageToClient& message)
}
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))
{
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"
"_STUDENT_TYPE\020\000\022\013\n\007ATHLETE\020\001\022\013\n\007TEACHER\020"
"\002\022\026\n\022STRAIGHT_A_STUDENT\020\003\022\t\n\005ROBOT\020\004\022\016\n\n"
"TECH_OTAKU\020\005*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_"
"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;
const ::_pbi::DescriptorTable descriptor_table_MessageType_2eproto = {
false,
false,
1482,
1474,
descriptor_table_protodef_MessageType_2eproto,
"MessageType.proto",
&descriptor_table_MessageType_2eproto_once,


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

@@ -368,13 +368,13 @@ namespace protobuf
ASSASSIN = 1,
KLEE = 2,
A_NOISY_PERSON = 3,
TRICKERTYPE4 = 4,
IDOL = 4,
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()
};
bool TrickerType_IsValid(int value);
constexpr TrickerType TrickerType_MIN = NULL_TRICKER_TYPE;
constexpr TrickerType TrickerType_MAX = TRICKERTYPE4;
constexpr TrickerType TrickerType_MAX = IDOL;
constexpr int TrickerType_ARRAYSIZE = TrickerType_MAX + 1;

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
from PyAPI.Interface import IStudentAPI, ITrickerAPI, IAI
from typing import Union, Final, cast
from PyAPI.constants import Constants
import queue

import time

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

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


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


class AI(IAI):
# 选手在这里实现自己的逻辑,要求和上面选择的阵营保持一致
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
# if api.GetSelfInfo().y > 10500:
# api.MoveLeft(50)
# else:
# path = bfs(x, y)
# cur = 0
# 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
# 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
# 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

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

return

def TrickerPlay(self, api: ITrickerAPI) -> None:
api.UseSkill(0)
api.UseSkill(1)
api.PrintSelfInfo()
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]:
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]:
return self.__pool.submit(self.__logic.UseSkill, skillID)

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

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

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

@@ -151,11 +157,11 @@ class StudentAPI(IStudentAPI, IGameTimer):
def StartLearning(self) -> Future[bool]:
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:
return cast(THUAI6.Student, self.__logic.GetSelfInfo())
@@ -208,6 +214,9 @@ class TrickerAPI(ITrickerAPI, IGameTimer):
def UseProp(self, propType: THUAI6.PropType) -> Future[bool]:
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]:
return self.__pool.submit(self.__logic.UseSkill, skillID)

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

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

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



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

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

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

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

def StartRescueMate(self, playerID: int, mateID: int) -> bool:
def StartRouseMate(self, playerID: int, mateID: int) -> bool:
try:
helpResult = self.__THUAI6Stub.StartRescueMate(
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)

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

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

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

@@ -310,7 +326,7 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):
self.__logger.info(
f"type={student.studentType.name}, determination={student.determination}, addiction={student.addiction}, danger alert={student.dangerAlert}")
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 = ""
for buff in student.buff:
studentBuff += buff.name + ", "
@@ -363,7 +379,7 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):
self.__logger.info(
f"type={student.studentType.name}, determination={student.determination}, addiction={student.addiction}, danger alert={student.dangerAlert}")
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 = ""
for buff in student.buff:
studentBuff += buff.name + ", "
@@ -398,31 +414,31 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):

return self.__pool.submit(logStart)

def StartTreatMate(self, mateID: int) -> Future[bool]:
def StartEncourageMate(self, mateID: int) -> Future[bool]:
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:
self.__logger.warning(
f"StartTreatMate: failed at {self.__GetTime()}ms")
f"StartEncourageMate: failed at {self.__GetTime()}ms")
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(
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:
self.__logger.warning(
f"StartRescueMate: failed at {self.__GetTime()}ms")
f"StartRouseMate: failed at {self.__GetTime()}ms")
return result

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

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

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

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

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

@@ -746,7 +778,7 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer):
self.__logger.info(
f"type={student.studentType.name}, determination={student.determination}, addiction={student.addiction}, danger alert={student.dangerAlert}")
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 = ""
for buff in student.buff:
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]:
pass

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

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

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

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

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

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


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

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

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

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

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

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

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

@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
import threading
import logging
import copy
import proto.MessageType_pb2 as MessageType
import proto.Message2Server_pb2 as Message2Server
import proto.Message2Clients_pb2 as Message2Clients
@@ -71,27 +72,32 @@ class Logic(ILogic):
def GetTrickers(self) -> List[THUAI6.Tricker]:
with self.__mtxState:
self.__logger.debug("Called GetTrickers")
return self.__currentState.trickers
return copy.deepcopy(self.__currentState.trickers)

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

def GetProps(self) -> List[THUAI6.Prop]:
with self.__mtxState:
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]:
with self.__mtxState:
self.__logger.debug("Called GetSelfInfo")
return self.__currentState.self
return copy.deepcopy(self.__currentState.self)

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

def GetPlaceType(self, x: int, y: int) -> THUAI6.PlaceType:
with self.__mtxState:
@@ -99,13 +105,13 @@ class Logic(ILogic):
self.__logger.warning("Invalid position")
return THUAI6.PlaceType.NullPlaceType
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:
with self.__mtxState:
self.__logger.debug("Called IsDoorOpen")
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:
self.__logger.warning("Door not found")
return False
@@ -114,7 +120,7 @@ class Logic(ILogic):
with self.__mtxState:
self.__logger.debug("Called GetClassroomProgress")
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:
self.__logger.warning("Classroom not found")
return 0
@@ -123,7 +129,7 @@ class Logic(ILogic):
with self.__mtxState:
self.__logger.debug("Called GetChestProgress")
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:
self.__logger.warning("Chest not found")
return 0
@@ -132,7 +138,7 @@ class Logic(ILogic):
with self.__mtxState:
self.__logger.debug("Called GetGateProgress")
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:
self.__logger.warning("Gate not found")
return 0
@@ -141,7 +147,7 @@ class Logic(ILogic):
with self.__mtxState:
self.__logger.debug("Called GetHiddenGateState")
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:
self.__logger.warning("HiddenGate not found")
return THUAI6.HiddenGateState.Null
@@ -150,7 +156,7 @@ class Logic(ILogic):
with self.__mtxState:
self.__logger.debug("Called GetDoorProgress")
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:
self.__logger.warning("Door not found")
return 0
@@ -158,7 +164,7 @@ class Logic(ILogic):
def GetGameInfo(self) -> THUAI6.GameInfo:
with self.__mtxState:
self.__logger.debug("Called GetGameInfo")
return self.__currentState.gameInfo
return copy.deepcopy(self.__currentState.gameInfo)

def Move(self, time: int, angle: float) -> bool:
self.__logger.debug("Called Move")
@@ -172,6 +178,10 @@ class Logic(ILogic):
self.__logger.debug("Called UseProp")
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:
self.__logger.debug("Called UseSkill")
return self.__comm.UseSkill(skillID, self.__playerID)
@@ -198,11 +208,11 @@ class Logic(ILogic):

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

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

# IStudentAPI使用的接口

@@ -214,13 +224,13 @@ class Logic(ILogic):
self.__logger.debug("Called StartLearning")
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:
self.__logger.debug("Called Trick")
@@ -348,6 +358,8 @@ class Logic(ILogic):
self.__logger.debug("Add Student!")
for item in message.obj_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):
self.__bufferState.trickers.append(
Proto2THUAI6.Protobuf2THUAI6Tricker(item.tricker_message))
@@ -440,6 +452,13 @@ class Logic(ILogic):
self.__logger.debug("Add Tricker!")
for item in message.obj_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):
self.__bufferState.students.append(
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
screen = args.screen
warnOnly = args.warnOnly
print(warnOnly)
logic = Logic(pID)
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:
Tuple = tuple


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


class StudentBuffType(Enum):
@@ -102,11 +104,11 @@ class PlayerState(Enum):
Addicted = 3
Quit = 4
Graduated = 5
Treated = 6
Rescued = 7
Encouraged = 6
Roused = 7
Stunned = 8
Treating = 9
Rescuing = 10
Encouraging = 9
Rousing = 10
Swinging = 11
Attacking = 12
Locking = 13
@@ -161,10 +163,10 @@ class Student(Player):
studentType: StudentType
determination: int
addiction: int
treatProgress: int
rescueProgress: int
encourageProgress: int
rouseProgress: int
learningSpeed: int
treatSpeed: int
encourageSpeed: int
dangerAlert: float
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.ASSASSIN: THUAI6.TrickerType.Assassin,
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] = {
MessageType.NULL_SBUFF_TYPE: THUAI6.StudentBuffType.NullStudentBuffType,
@@ -135,11 +136,11 @@ class Proto2THUAI6(NoInstance):
MessageType.ADDICTED: THUAI6.PlayerState.Addicted,
MessageType.QUIT: THUAI6.PlayerState.Quit,
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.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.ATTACKING: THUAI6.PlayerState.Attacking,
MessageType.LOCKING: THUAI6.PlayerState.Locking,
@@ -206,9 +207,9 @@ class Proto2THUAI6(NoInstance):
student.facingDirection = studentMsg.facing_direction
student.bulletType = Proto2THUAI6.bulletTypeDict[studentMsg.bullet_type]
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.timeUntilSkillAvailable.clear()
for time in studentMsg.time_until_skill_available:
@@ -314,7 +315,8 @@ class THUAI62Proto(NoInstance):
THUAI6.TrickerType.NullTrickerType: MessageType.NULL_TRICKER_TYPE,
THUAI6.TrickerType.Assassin: MessageType.ASSASSIN,
THUAI6.TrickerType.Klee: MessageType.KLEE,
THUAI6.TrickerType.ANoisyPerson: MessageType.A_NOISY_PERSON, }
THUAI6.TrickerType.ANoisyPerson: MessageType.A_NOISY_PERSON,
THUAI6.TrickerType.Idol: MessageType.IDOL, }

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


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

@@ -1,4 +1,6 @@
#!/usr/bin/env bash

python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0 -d -o &
# python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -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;
KLEE = 2;
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.Globalization;
using System.Windows.Data;
using System.Windows.Controls;
using System.Windows;

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="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="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>-->
<TextBlock Grid.Row="5" Grid.Column="1" Foreground="Red" Text=" 用户名或密码错误!" Visibility="{Binding LoginFailVis}"/>
</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);
}
@@ -1058,43 +1058,50 @@ namespace WebConnect
{
public enum language { cpp, py };
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 = "";
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>


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

@@ -481,15 +481,19 @@ namespace starter.viewmodel.settings
{
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");
}));


+ 2
- 0
logic/.gitignore View File

@@ -400,3 +400,5 @@ FodyWeavers.xsd
#THUAI playback file
*.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;

[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;
break;
case 4:
playerMsg.TrickerType = TrickerType._4;
playerMsg.TrickerType = TrickerType.Idol;
break;
case 0:
default:
@@ -413,19 +413,6 @@ namespace Client
{
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);
break;
case MessageOfObj.MessageOfObjOneofCase.TrickerMessage:
@@ -433,14 +420,6 @@ namespace Client
{
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);
break;
case MessageOfObj.MessageOfObjOneofCase.PropMessage:
@@ -514,11 +493,15 @@ namespace Client
case MessageOfObj.MessageOfObjOneofCase.HiddenGateMessage:
listOfHiddenGate.Add(obj.HiddenGateMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.MapMessage:
GetMap(obj.MapMessage);
break;
}
}
listOfAll.Add(content.AllMessage);
break;
case GameState.GameEnd:
MessageBox.Show("Game Over!");
foreach (var obj in content.ObjMessage)
{
switch (obj.MessageOfObjCase)
@@ -581,23 +564,16 @@ namespace Client
return true;
if (humanOrButcher && human != null)
{
if (human.Guid == msg.Guid) // 自己能看见自己
if (msg.Place == human.Place)
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)
{
if (msg.Place != butcher.Place)
return false;
if (msg.Place == butcher.Place)
return true;
}
if (msg.Place == Protobuf.PlaceType.Grass)
return false;
return true;
}

@@ -610,20 +586,21 @@ namespace Client
if (butcher.Guid == msg.Guid) // 自己能看见自己
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)
{
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;
}

@@ -631,18 +608,18 @@ namespace Client
{
if (isSpectatorMode)
return true;
if (msg.Place == Protobuf.PlaceType.Land)
return true;
if (humanOrButcher && human != null)
{
if (msg.Place != human.Place)
return false;
if (msg.Place == human.Place)
return true;
}
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;
}

@@ -650,70 +627,117 @@ namespace Client
{
if (isSpectatorMode)
return true;
if (msg.Place == Protobuf.PlaceType.Land)
return true;
if (humanOrButcher && human != null)
{
if (msg.Place != human.Place)
return false;
if (msg.Place == human.Place)
return true;
}
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;
}

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)
{
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),
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(num);
}
}
foreach (var data in listOfButcher)
@@ -796,11 +835,11 @@ namespace Client
{
Ellipse icon = new()
{
Width = 10,
Height = 10,
Width = 2 * bulletRadiusTimes * unitWidth,
Height = 2 * bulletRadiusTimes * unitHeight,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth / 2, data.X * unitHeight / 1000.0 - unitHeight / 2, 0, 0),
Margin = new Thickness(data.Y * unitWidth / 1000.0 - unitWidth * bulletRadiusTimes, data.X * unitHeight / 1000.0 - unitHeight * bulletRadiusTimes, 0, 0),
Fill = Brushes.Red,
};
switch (data.Type)
@@ -821,42 +860,49 @@ namespace Client
}
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;
}
//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)
@@ -978,17 +1024,18 @@ namespace Client
}
//}
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;
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 isEmergencyOpened = false;
bool isEmergencyDrawed = false;
bool isDataFixed = false;
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[,] 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 System.Threading;
using Timothy.FrameRateTask;
using System.Windows;

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

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();
return map;


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

@@ -2,7 +2,7 @@
"profiles": {
"Client": {
"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.Shapes;
using Protobuf;
using Preparation.Utility;

namespace Client
{
@@ -31,10 +32,13 @@ namespace Client
}
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)
@@ -53,14 +57,18 @@ namespace Client
{
time.Text += Convert.ToString(sec);
}
if (playerId == 4)
if (playerId == GameData.numOfStudent)
{
name.Text = "🚀 Tricker's";
}
else
else if (playerId < GameData.numOfStudent)
{
name.Text = "🚀 Student" + Convert.ToString(playerId) + "'s";
}
else
{
name.Text = "🚀 Spectator's";
}
if (obj.SubjectFinished < Preparation.Utility.GameData.numOfGeneratorRequiredForRepair)
{
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)
{
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)
@@ -48,8 +49,8 @@ namespace Client
case TrickerType.ANoisyPerson:
serial.Text = "👥" + Convert.ToString(1) + "👻" + Convert.ToString(obj.PlayerId) + "\nANoisyPerson";
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;
case TrickerType.NullTrickerType:
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)
{
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)
{


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

@@ -69,10 +69,10 @@ namespace GameClass.GameObj
ap = value;
}
}
public override int Speed => GameData.basicBulletMoveSpeed * 2;
public override int Speed => GameData.basicBulletMoveSpeed * 25 / 10;
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 RecoveryFromHit => 0;
public const int cd = GameData.basicBackswing / 2;
@@ -137,7 +137,6 @@ namespace GameClass.GameObj
switch (gameObjType)
{
case GameObjType.Character:
case GameObjType.Generator:
return true;
default:
return false;
@@ -184,7 +183,6 @@ namespace GameClass.GameObj
switch (gameObjType)
{
case GameObjType.Character:
case GameObjType.Generator:
return true;
default:
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;
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
@@ -323,6 +315,30 @@ namespace GameClass.GameObj
|| playerState == PlayerStateType.Treated || playerState == PlayerStateType.Stunned
|| 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;
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相关属性、方法
private Prop[] propInventory = new Prop[GameData.maxNumOfPropInPropInventory]
@@ -521,6 +522,12 @@ namespace GameClass.GameObj
return this.HasShield;
case BuffType.AddLife:
return this.HasLIFE;
case BuffType.AddAp:
return this.HasAp;
case BuffType.Clairaudience:
return this.HasClairaudience;
case BuffType.Invisible:
return this.HasInvisible;
default:
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() };
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
{
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;
}
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)
{
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 (CharacterManager.BeStunned((Character)player, GameData.TimeOfStunnedWhenJumpyDumpty))
if (characterManager.BeStunned((Character)player, GameData.TimeOfStunnedWhenJumpyDumpty))
player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.TimeOfStunnedWhenJumpyDumpty));
gameMap.Remove((GameObj)collisionObj);
}
}
if (player.FindIActiveSkill(ActiveSkillType.CanBeginToCharge).IsBeingUsed && collisionObj.Type == GameObjType.Character && ((Character)collisionObj).IsGhost())
{
if (CharacterManager.BeStunned((Character)collisionObj, GameData.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)
{
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);
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 false;
@@ -63,7 +63,7 @@ namespace Gaming
return false;

++generatorForFix.NumOfFixing;
player.PlayerState = PlayerStateType.Fixing;
characterManager.SetPlayerState(player, PlayerStateType.Fixing);
new Thread
(
() =>
@@ -74,7 +74,7 @@ namespace Gaming
{
if (generatorForFix.Repair(player.FixSpeed * GameData.frameDuration, player))
{
player.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(player);
gameMap.NumOfRepairedGenerators++;
}
},
@@ -96,31 +96,21 @@ namespace Gaming
if (!(player.Commandable()) || player.PlayerState == PlayerStateType.OpeningTheDoorway)
return false;
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;

player.PlayerState = PlayerStateType.OpeningTheDoorway;
doorwayToOpen.IsOpening = true;
characterManager.SetPlayerState(player, PlayerStateType.OpeningTheDoorway, doorwayToOpen);
int startTime = doorwayToOpen.OpenStartTime = gameMap.Timer.nowTime();
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)
{
if (!(player.Commandable()) || player.CharacterType == CharacterType.Robot)
if (!(player.Commandable()) || player.CharacterType == CharacterType.Robot || player.CharacterType == CharacterType.Teacher)
return false;
Doorway? doorwayForEscape = (Doorway?)gameMap.OneForInteract(player.Position, GameObjType.Doorway);
if (doorwayForEscape != null && doorwayForEscape.IsOpen())
@@ -163,7 +153,7 @@ namespace Gaming
playerTreated = gameMap.StudentForInteract(player.Position);
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.HP == playerTreated.MaxHp || !GameData.ApproachToInteract(playerTreated.Position, player.Position))
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>(
loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && player.PlayerState == PlayerStateType.Treating && gameMap.Timer.IsGaming,
loopToDo: () =>
{
if (playerTreated.AddDegreeOfTreatment(GameData.frameDuration * player.TreatSpeed, player))
playerTreated.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(playerTreated);
},
timeInterval: GameData.frameDuration,
finallyReturn: () => 0
)
.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();
@@ -200,11 +190,10 @@ namespace Gaming
playerRescued = gameMap.StudentForInteract(player.Position);
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;
player.PlayerState = PlayerStateType.Rescuing;
playerRescued.PlayerState = PlayerStateType.Rescued;
characterManager.SetPlayerState(player, PlayerStateType.Rescuing);
characterManager.SetPlayerState(playerRescued, PlayerStateType.Rescued);

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

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

player.PlayerState = PlayerStateType.OpeningTheChest;
characterManager.SetPlayerState(player, PlayerStateType.OpeningTheChest, chestToOpen);
int startTime = gameMap.Timer.nowTime();
chestToOpen.Open(startTime, player);
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)
{
Prop prop = chestToOpen.PropInChest[i];
@@ -279,8 +259,6 @@ namespace Gaming
gameMap.Add(prop);
}
}
else chestToOpen.OpenDegree = 0;

}

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

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

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

player.PlayerState = PlayerStateType.LockingOrOpeningTheDoor;
characterManager.SetPlayerState(player, PlayerStateType.LockingOrOpeningTheDoor);
new Thread
(
() =>
@@ -404,7 +382,6 @@ namespace Gaming
loopToDo: () =>
{
flag = ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) == null);
Preparation.Utility.Debugger.Output(doorToLock, flag.ToString());
doorToLock.OpenOrLockDegree += GameData.frameDuration * player.SpeedOfOpeningOrLocking;
},
timeInterval: GameData.frameDuration,
@@ -417,7 +394,7 @@ namespace Gaming
doorToLock.IsOpen = (!doorToLock.IsOpen);
}
if (player.PlayerState == PlayerStateType.LockingOrOpeningTheDoor)
player.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(player);
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 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)
{
#if DEBUG
@@ -72,23 +95,18 @@ namespace Gaming
else
Debugger.Output(bullet, "bombed without objBeingShot");
#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 (objBeingShot == null)
{
CharacterManager.BackSwing((Character?)bullet.Parent, bullet.Backswing);
characterManager.BackSwing((Character)bullet.Parent, bullet.Backswing);
return;
}

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

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

@@ -105,6 +123,7 @@ namespace Gaming
if (bullet.TypeOfBullet == BulletType.BombBomb && objBeingShot != null)
{
bullet.Parent.BulletOfPlayer = BulletType.JumpyDumpty;
Debugger.Output(bullet.Parent, bullet.Parent.CharacterType.ToString() + " " + bullet.Parent.BulletNum.ToString());
Attack((Character)bullet.Parent, bullet.FacingDirection.Angle() + Math.PI / 2.0);
Attack((Character)bullet.Parent, bullet.FacingDirection.Angle() + Math.PI * 3.0 / 2.0);
}
@@ -140,21 +159,17 @@ namespace Gaming

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

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

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

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

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.CanMove = true;
gameMap.Add(bullet);
moveEngine.MoveObj(bullet, (int)((bullet.BulletAttackRange - player.Radius - BulletFactory.BulletRadius(player.BulletOfPlayer)) * 1000 / bullet.MoveSpeed), angle); // 这里时间参数除出来的单位要是ms

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

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

.Start();

if (gameMap.Timer.IsGaming)
{
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 Timothy.FrameRateTask;
using System.Numerics;
using System.Timers;

namespace Gaming
{
@@ -21,6 +22,23 @@ namespace Gaming
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)
{
@@ -123,8 +141,7 @@ namespace Gaming
bgmVolume = newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position);
}
}
if (bgmVolume > 0)
newPlayer.AddBgm(BgmType.StudentIsApproaching, bgmVolume);
newPlayer.AddBgm(BgmType.StudentIsApproaching, bgmVolume);
}
else
{
@@ -132,9 +149,13 @@ namespace Gaming
{
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;
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);
}
}
if (bgmVolume > 0)
newPlayer.AddBgm(BgmType.GeneratorIsBeingFixed, bgmVolume);
newPlayer.AddBgm(BgmType.GeneratorIsBeingFixed, bgmVolume);
}
finally
{
@@ -216,7 +236,7 @@ namespace Gaming
return;
}
}
player.PlayerState = PlayerStateType.Addicted;
SetPlayerState(player, PlayerStateType.Addicted);
new Thread
(() =>
{
@@ -246,23 +266,23 @@ namespace Gaming
{ 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;
new Thread
(() =>
{
player.PlayerState = PlayerStateType.Stunned;
SetPlayerState(player, PlayerStateType.Stunned);
Thread.Sleep(time);
if (player.PlayerState == PlayerStateType.Stunned)
player.PlayerState = PlayerStateType.Null;
SetPlayerState(player);
}
)
{ IsBackground = true }.Start();
return true;
}

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

@@ -337,11 +357,11 @@ namespace Gaming
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;
player.PlayerState = PlayerStateType.Swinging;
SetPlayerState(player, PlayerStateType.Swinging);

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

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);
if (player != null)
{
return ActionManager.Stop(player);
return actionManager.Stop(player);
}
return false;
}
@@ -215,15 +215,16 @@ namespace Gaming
}
return false;
}
public void Attack(long playerID, double angle)
public bool Attack(long playerID, double angle)
{
if (!gameMap.Timer.IsGaming)
return;
return false;
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)
{
@@ -232,7 +233,7 @@ namespace Gaming
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
PropManager.UseProp(player, propType);
propManager.UseProp(player, propType);
}
}
public void ThrowProp(long playerID, PropType propType = PropType.Null)
@@ -374,7 +375,7 @@ namespace Gaming
characterManager = new CharacterManager(gameMap);
attackManager = new AttackManager(gameMap, characterManager);
actionManager = new ActionManager(gameMap, characterManager);
propManager = new PropManager(gameMap);
propManager = new PropManager(gameMap, 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 readonly Map gameMap;
private readonly CharacterManager characterManager;
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)
return;
@@ -48,7 +48,7 @@ namespace Gaming
if (!player.IsGhost())
if (player.HP < player.MaxHp)
{
player.HP += GameData.basicTreatmentDegree;
player.HP += GameData.basicTreatmentDegree / 2;
player.AddScore(GameData.ScorePropAddHp);
}
else player.AddAp(GameData.PropDuration);
@@ -57,7 +57,7 @@ namespace Gaming
if (player.PlayerState == PlayerStateType.Stunned)
{
player.AddScore(GameData.ScorePropRecoverFromDizziness);
player.PlayerState = PlayerStateType.Null;
player.SetPlayerStateNaturally();
}
break;
default:
@@ -210,8 +210,9 @@ namespace Gaming
{ IsBackground = true }.Start();
*/
}
public PropManager(Map gameMap) // 道具不能扔过墙
public PropManager(Map gameMap, CharacterManager characterManager) // 道具不能扔过墙
{
this.characterManager = characterManager;
this.gameMap = gameMap;
/* this.moveEngine = new MoveEngine(
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)
{
if ((!player.Commandable())) return false;
@@ -120,8 +164,8 @@ namespace Gaming
{
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;
}
}
@@ -130,7 +174,7 @@ namespace Gaming
{
gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock();
}
CharacterManager.BackSwing(player, GameData.TimeOfGhostSwingingAfterHowl);
characterManager.BackSwing(player, GameData.TimeOfGhostSwingingAfterHowl);
Debugger.Output(player, "howled!");
},
() =>
@@ -149,11 +193,11 @@ namespace Gaming
{
if (character.IsGhost() &&
(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))
{
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;
}
}
@@ -180,7 +224,7 @@ namespace Gaming
{
if ((character.PlayerState == PlayerStateType.Addicted) && gameMap.CanSee(player, character))
{
character.PlayerState = PlayerStateType.Null;
characterManager.SetPlayerState(character);
character.HP = GameData.RemainHpWhenAddLife;
((Student)character).TimeOfRescue = 0;
player.AddScore(GameData.StudentScoreRescue);
@@ -210,8 +254,8 @@ namespace Gaming
{
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();
break;
}


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

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


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

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

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 => 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
{
private const int moveSpeed = (int)(GameData.basicGhostMoveSpeed * 1.07);
@@ -124,7 +155,7 @@ namespace Preparation.Interface
}
public class Teacher : IStudentType
{
private const int moveSpeed = GameData.basicStudentMoveSpeed * 3 / 4;
private const int moveSpeed = GameData.basicStudentMoveSpeed * 9 / 10;
public int MoveSpeed => moveSpeed;

private const int maxHp = GameData.basicHp * 10;
@@ -141,7 +172,7 @@ namespace Preparation.Interface
public const int fixSpeed = 0;
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 const double concealment = GameData.basicConcealment * 0.5;
@@ -164,7 +195,7 @@ namespace Preparation.Interface
}
public class Athlete : IStudentType
{
private const int moveSpeed = GameData.basicStudentMoveSpeed * 11 / 10;
private const int moveSpeed = GameData.basicStudentMoveSpeed * 105 / 100;
public int MoveSpeed => moveSpeed;

private const int maxHp = GameData.basicHp;
@@ -181,7 +212,7 @@ namespace Preparation.Interface
public const int fixSpeed = GameData.basicFixSpeed * 6 / 10;
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 const double concealment = GameData.basicConcealment * 0.9;
@@ -204,7 +235,7 @@ namespace Preparation.Interface
}
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;

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 int FixSpeed => fixSpeed;

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

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

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

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

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

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

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

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

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

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


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

@@ -18,8 +18,8 @@ namespace Preparation.Interface
}
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();
public object ActiveSkillLock => commonSkillLock;
@@ -33,8 +33,8 @@ namespace Preparation.Interface

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();
public object ActiveSkillLock => commonSkillLock;
@@ -63,7 +63,7 @@ namespace Preparation.Interface

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

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

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

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

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

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 int SkillCD => GameData.commonSkillCD / 2;
@@ -137,7 +152,7 @@ namespace Preparation.Interface

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

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

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


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

@@ -83,6 +83,7 @@ namespace Preparation.Utility
ANoisyPerson = 7,
Robot = 8,
Sunshine = 9,
Idol = 10,
}
public enum ActiveSkillType // 主动技能
{
@@ -100,6 +101,7 @@ namespace Preparation.Utility
Rouse = 11,
Encourage = 12,
Inspire = 13,
ShowTime = 14,
}
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 long gameDuration = 600000; // 游戏时长600000ms = 10min

public const int LimitOfStopAndMove = 15;

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

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

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

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

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

public const int maxNumOfPropInPropInventory = 3;

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

public static int StudentScoreFix(int degreeOfFix)
{
return degreeOfFix * 200 / degreeOfFixedGenerator;
}
public const int StudentScoreFixed = 25;
//public const int StudentScoreFixed = 25;
public static int StudentScorePinDown(int timeOfPinningDown)
{
return 0;
return (int)(timeOfPinningDown * 0.00246);
}
public static int StudentScoreTrickerBeStunned(int time)
{
return time;
return time * 20 / 1000;
}
public const int StudentScoreRescue = 100;
public static int StudentScoreTreat(int degree)
{
return degree;
return degree * 50 / basicTreatmentDegree;
}
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 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;
#endregion
#region 攻击与子弹相关
@@ -185,7 +190,7 @@ namespace Preparation.Utility
public const int MinAP = 0; // 最小攻击力
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 basicBulletNum = 3; // 基本初始子弹量

@@ -205,19 +210,22 @@ namespace Preparation.Utility
public const int commonSkillCD = 30000; // 普通技能标准冷却时间
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 double AddedTimeOfSpeedWhenInspire = 0.6;
public const int TimeOfAddingSpeedWhenInspire = 6000;

public const int AddHpWhenEncourage = basicHp / 4;

#endregion
#region 道具相关
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;
case Preparation.Utility.CharacterType.ANoisyPerson:
return Protobuf.TrickerType.ANoisyPerson;
case CharacterType.Idol:
return TrickerType.Idol;
default:
return Protobuf.TrickerType.NullTrickerType;
}
@@ -247,6 +249,8 @@ namespace Preparation.Utility
return Preparation.Utility.CharacterType.Klee;
case Protobuf.TrickerType.ANoisyPerson:
return Preparation.Utility.CharacterType.ANoisyPerson;
case TrickerType.Idol:
return CharacterType.Idol;
default:
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);
}

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

public override int GetHashCode()
{


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

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

namespace Server
{

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

private static MessageOfObj? Student(Student player)
{
MessageOfObj msg = new MessageOfObj();
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)
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);

foreach (var value in player.PropInventory)
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)
{
if (kvp.Value)
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;
}

private static MessageOfObj? Tricker(Character player)
{
MessageOfObj msg = new MessageOfObj();
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)
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.Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)player.Place);
foreach (var value in player.PropInventory)
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)
{
if (kvp.Value)
msg.TrickerMessage.Buff.Add(Transformation.ToTrickerBuffType(kvp.Key));
}


return msg;
}

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

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

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

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

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

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


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

@@ -130,30 +130,34 @@ namespace Server
case GameState.GameRunning:
case GameState.GameEnd:
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)
{
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)
{
foreach (var news in currentNews)
{
MessageOfObj? msg = CopyInfo.Auto(news);
if (msg != null) currentGameInfo.ObjMessage.Add(CopyInfo.Auto(news));
if (msg != null) currentGameInfo.ObjMessage.Add(msg);
}
currentNews.Clear();
}
currentGameInfo.GameState = gameState;
currentGameInfo.AllMessage = GetMessageOfAll();
currentGameInfo.AllMessage = GetMessageOfAll(time);
mwr?.WriteOne(currentGameInfo);
break;
default:
break;
}
}

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

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


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

@@ -2,7 +2,7 @@
"profiles": {
"Server": {
"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
{
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)
{
#if DEBUG
@@ -53,6 +69,7 @@ namespace Server
{
semaDict.Add(request.PlayerId, temp);
}
IsSpectatorJoin = true;
}
do
{
@@ -147,8 +164,7 @@ namespace Server
return Task.FromResult(boolRes);
}
var gameID = communicationToGameID[request.PlayerId];
game.Attack(gameID, request.Angle);
boolRes.ActSuccess = true;
boolRes.ActSuccess = game.Attack(gameID, request.Angle);
return Task.FromResult(boolRes);
}

@@ -170,8 +186,7 @@ namespace Server
return Task.FromResult(moveRes);
}
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;
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

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

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

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

+ 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 playerCount;

const int bufferMaxSize = 1024 * 1024; // 1M
const int bufferMaxSize = 10 * 1024 * 1024; // 10M

public MessageReader(string fileName)
{
@@ -83,7 +83,8 @@ namespace Playback
public MessageToClient? ReadOne()
{
beginRead:
if (Finished) return null;
if (Finished)
return null;
var pos = cos.Position;
try
{
@@ -94,9 +95,21 @@ namespace Playback
catch (InvalidProtocolBufferException)
{
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;
if (bufferSize == leftByte)


+ 2
- 1
playback/Playback/MessageWriter.cs View File

@@ -12,7 +12,7 @@ namespace Playback
private CodedOutputStream cos;
private MemoryStream ms;
private GZipStream gzs;
private const int memoryCapacity = 1024 * 1024; // 1M
private const int memoryCapacity = 10 * 1024 * 1024; // 10M

private static void ClearMemoryStream(MemoryStream msToClear)
{
@@ -50,6 +50,7 @@ namespace Playback
{
cos.Flush();
gzs.Write(ms.GetBuffer(), 0, (int)ms.Length);
gzs.Flush();
ClearMemoryStream(ms);
fs.Flush();
}


Loading…
Cancel
Save