| @@ -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; | |||
| }; | |||
| ~~~ | |||
| @@ -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) | |||
| @@ -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; | |||
| @@ -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); | |||
| @@ -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 | |||
| @@ -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; | |||
| @@ -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"}, | |||
| @@ -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,6 +1,7 @@ | |||
| #include <vector> | |||
| #include <thread> | |||
| #include "AI.h" | |||
| #include "constants.h" | |||
| // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | |||
| extern const bool asynchronous = false; | |||
| @@ -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() | |||
| @@ -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; | |||
| @@ -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] + ", "; | |||
| @@ -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())); | |||
| @@ -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 | |||
| @@ -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, | |||
| @@ -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(); | |||
| @@ -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 | |||
| @@ -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() | |||
| @@ -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)) | |||
| @@ -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,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 | |||
| @@ -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) | |||
| @@ -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)) | |||
| @@ -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) | |||
| @@ -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] = [] | |||
| @@ -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, | |||
| @@ -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 & | |||
| @@ -118,7 +118,7 @@ enum TrickerType | |||
| ASSASSIN = 1; | |||
| KLEE = 2; | |||
| A_NOISY_PERSON = 3; | |||
| TRICKERTYPE4 = 4; | |||
| IDOL = 4; | |||
| } | |||
| // 游戏进行状态 | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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"); | |||
| })); | |||
| @@ -400,3 +400,5 @@ FodyWeavers.xsd | |||
| #THUAI playback file | |||
| *.thuaipb | |||
| #private cmd | |||
| cmd/gameServerOfSanford.cmd | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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 } }; | |||
| } | |||
| @@ -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; | |||
| @@ -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" | |||
| } | |||
| } | |||
| } | |||
| @@ -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🚪: "; | |||
| @@ -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"; | |||
| @@ -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) | |||
| { | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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(); | |||
| @@ -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。 | |||
| @@ -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; | |||
| @@ -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); | |||
| } | |||
| } | |||
| ) | |||
| @@ -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); | |||
| } | |||
| } | |||
| ) | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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, | |||
| @@ -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; | |||
| } | |||
| @@ -52,6 +52,9 @@ namespace Gaming | |||
| case ActiveSkillType.Rouse: | |||
| Rouse(character); | |||
| break; | |||
| case ActiveSkillType.ShowTime: | |||
| ShowTime(character); | |||
| break; | |||
| default: | |||
| return false; | |||
| } | |||
| @@ -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(); | |||
| } | |||
| @@ -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(); | |||
| @@ -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; | |||
| } | |||
| @@ -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 | |||
| { | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| @@ -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() | |||
| { | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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" | |||
| } | |||
| } | |||
| } | |||
| @@ -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,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,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 | |||
| @@ -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号技能 | | |||
| @@ -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号技能 | | |||
| @@ -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) | |||
| @@ -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(); | |||
| } | |||