| @@ -4,13 +4,25 @@ | |||
| ## Visual Studio使用说明 | |||
| 选手开始编写代码之前,要先编译好服务器(logic)。具体方法是进入logic文件夹,分别编译`logic.sln`和client文件夹里的`client.sln`,然后就可以开始编写自己的代码。需要注意,选手需要分别在Debug和Release模式下编译,然后才可以分别运行`gameServer_dbg.cmd`和`gameServer_rls.cmd` | |||
| 比赛**只保证!!!支持**VS2022最新版本,选手使用其他版本后果自负(实际上应该不能编译)。 | |||
| ## Python使用说明 | |||
| ### 生成模式的设置 | |||
| 选手编写Python代码之前需要安装必要的Python包,具体方法为:Windows下运行(需要在有Python环境的情况下运行)`generate_proto.cmd`,Linux下运行`generate_proto.sh`,然后可以开始进行代码编写。 | |||
| 菜单栏下方一行 | |||
| ## cmd脚本的参数修改 | |||
|  | |||
| 可以更改生成模式为`Debug`或`Release` | |||
| ### 命令行参数的设置 | |||
| 左上方菜单栏 `调试->调试属性` | |||
|  | |||
| 在命令参数一栏中加入命令行参数进行调试 | |||
| ### cmd脚本的参数修改 | |||
| 右键点击`.cmd`或`.bat`文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上`::`,可以注释掉该行。 | |||
| @@ -234,7 +246,7 @@ for (auto itr = begin(arr); itr != end(arr); ++itr) | |||
| ## Python接口必看 | |||
| 比赛**只保证!!**支持Python3.9,不保证支持其他版本 | |||
| 比赛中的Python接口大多使用异步接口,即返回一个类似于 `Future[bool]` 的值。为了获取实际的值,需要调用 `result()` 方法。 | |||
| @@ -695,7 +707,7 @@ int main() | |||
| ##### 概览 | |||
| `shared_ptr` 可以说是最常用的智能指针了。它的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的智能指针数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。 | |||
| `shared_ptr` 的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的智能指针数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。不过也需要注意,使用 `std::shared_ptr` 会比传统的指针带来额外的引用计数的开销,因此只有当后面将会介绍的 `std::unique_ptr` 无法满足要求时方可考虑 `std::shared_ptr`。 | |||
| 像 `new` 会在自由存储区动态获取一块内存并返回其一样,如果要动态分配一块内存并得到其智能指针,可以使用 `std::make_shared` 模板,例如: | |||
| @@ -923,7 +935,7 @@ else | |||
| #### `std::unique_ptr` | |||
| `std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有。它部分涉及到 `xvalue` 、右值引用与移动语义的问题,在此不做过多展开。 | |||
| `std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有,是基于 RAII 的思想设计的智能指针,并且相比于原始指针并不会带来任何额外开销,是智能指针的首选。它部分涉及到对象的生命期、右值引用与移动语义的问题,在此不做过多展开。 | |||
| @@ -931,5 +943,4 @@ else | |||
| + [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) | |||
| + [cppreference_unique_ptr](https://zh.cppreference.com/w/cpp/memory/unique_ptr) | |||
| @@ -0,0 +1,13 @@ | |||
| @echo off | |||
| python -m pip install -r .\CAPI\python\requirements.txt | |||
| MKDIR .\CAPI\python\proto | |||
| echo generating python grpc files... | |||
| python -m grpc_tools.protoc -I.\CAPI\proto\ --python_out=.\CAPI\python\proto --pyi_out=.\CAPI\python\proto MessageType.proto | |||
| python -m grpc_tools.protoc -I.\CAPI\proto\ --python_out=.\CAPI\python\proto --pyi_out=.\CAPI\python\proto Message2Clients.proto | |||
| python -m grpc_tools.protoc -I.\CAPI\proto\ --python_out=.\CAPI\python\proto --pyi_out=.\CAPI\python\proto Message2Server.proto | |||
| python -m grpc_tools.protoc -I.\CAPI\proto\ --python_out=.\CAPI\python\proto --pyi_out=.\CAPI\python\proto --grpc_python_out=.\CAPI\python\proto Services.proto | |||
| pause | |||
| @@ -0,0 +1,7 @@ | |||
| @echo off | |||
| start .\CAPI\cpp\x64\Debug\API.exe -I 127.0.0.1 -P 8888 -p 0 -o -d | |||
| start .\CAPI\cpp\x64\Debug\API.exe -I 127.0.0.1 -P 8888 -p 1 -o -d | |||
| start .\CAPI\cpp\x64\Debug\API.exe -I 127.0.0.1 -P 8888 -p 2 -o -d | |||
| start .\CAPI\cpp\x64\Debug\API.exe -I 127.0.0.1 -P 8888 -p 3 -o -d | |||
| start .\CAPI\cpp\x64\Debug\API.exe -I 127.0.0.1 -P 8888 -p 4 -o -d | |||
| @@ -0,0 +1,6 @@ | |||
| @echo off | |||
| :: 添加 --cl 参数,程序运行时将自动识别命令行参数,并自动连接server | |||
| start cmd /k win64\Client.exe --port 8888 --characterID 114514 --type 0 --occupation 1 --ip 127.0.0.1 --cl | |||
| :: characterID > 2023时是观战Client,否则是正常Client | |||
| @@ -0,0 +1,7 @@ | |||
| @echo off | |||
| start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 0 -d -o | |||
| start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 1 -d -o | |||
| start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 2 -d -o | |||
| start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 3 -d -o | |||
| start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 4 -d -o | |||
| @@ -0,0 +1,5 @@ | |||
| @echo off | |||
| .\win64\Server.exe --port 8888 --studentCount 1 --trickerCount 1 --gameTimeInSecond 600 --fileName video | |||
| pause | |||
| @@ -0,0 +1,5 @@ | |||
| @echo off | |||
| .\win64\Debug\Server.exe --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 | |||
| pause | |||
| @@ -13,17 +13,21 @@ public: | |||
| virtual void play(ITrickerAPI& api) = 0; | |||
| }; | |||
| using CreateAIFunc = std::unique_ptr<IAI> (*)(); | |||
| using CreateAIFunc = std::unique_ptr<IAI> (*)(int64_t playerID); | |||
| class AI : public IAI | |||
| { | |||
| public: | |||
| AI() : | |||
| IAI() | |||
| AI(int64_t pID) : | |||
| IAI(), | |||
| playerID(pID) | |||
| { | |||
| } | |||
| void play(IStudentAPI& api) override; | |||
| void play(ITrickerAPI& api) override; | |||
| private: | |||
| int64_t playerID; | |||
| }; | |||
| #endif | |||
| @@ -8,6 +8,7 @@ | |||
| namespace Constants | |||
| { | |||
| SCCI int frameDuration = 50; // 每帧毫秒数 | |||
| // 地图相关 | |||
| SCCI int numOfGridPerCell = 1000; // 单位坐标数 | |||
| SCCI int rows = 50; // 地图行数 | |||
| @@ -28,8 +29,8 @@ namespace Constants | |||
| SCCI int basicEncourageSpeed = 100; | |||
| SCCI int basicFixSpeed = 123; | |||
| SCCI int basicSpeedOfOpeningOrLocking = 4000; | |||
| SCCI int basicStudentSpeedOfClimbingThroughWindows = 611; | |||
| SCCI int basicTrickerSpeedOfClimbingThroughWindows = 1270; | |||
| SCCI int basicStudentSpeedOfClimbingThroughWindows = 1222; | |||
| SCCI int basicTrickerSpeedOfClimbingThroughWindows = 2540; | |||
| SCCI int basicSpeedOfOpenChest = 1000; | |||
| SCCI int basicHp = 3000000; | |||
| @@ -37,8 +38,8 @@ namespace Constants | |||
| SCCI int basicEncouragementDegree = 1500000; | |||
| SCCI int basicTimeOfRouse = 1000; | |||
| SCCI int basicStudentSpeed = 1270; | |||
| SCCI int basicTrickerSpeed = 1504; | |||
| SCCI int basicStudentSpeed = 3000; | |||
| SCCI int basicTrickerSpeed = 3600; | |||
| SCCI double basicConcealment = 1; | |||
| SCCI int basicStudentAlertnessRadius = 15 * numOfGridPerCell; | |||
| @@ -58,10 +59,10 @@ namespace Constants | |||
| 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 basicBulletMoveSpeed = 7400; // 基本子弹移动速度 | |||
| SCCI double basicRemoteAttackRange = 6000; // 基本远程攻击范围 | |||
| SCCI double basicAttackShortRange = 2200; // 基本近程攻击范围 | |||
| SCCI double basicBulletBombRange = 2000; // 基本子弹爆炸范围 | |||
| // 道具相关 | |||
| @@ -269,7 +270,7 @@ namespace Constants | |||
| struct ShowTime | |||
| { | |||
| SCCI int skillCD = commonSkillCD * 3; | |||
| SCCI int skillCD = commonSkillCD * 8 / 3; | |||
| SCCI int durationTime = commonSkillTime * 1; | |||
| }; | |||
| @@ -137,7 +137,7 @@ namespace THUAI6 | |||
| Swinging = 11, | |||
| Attacking = 12, | |||
| Locking = 13, | |||
| Rummaging = 14, | |||
| // Rummaging = 14, | |||
| Climbing = 15, | |||
| OpeningAChest = 16, | |||
| UsingSpecialSkill = 17, | |||
| @@ -314,7 +314,7 @@ namespace THUAI6 | |||
| {PlayerState::Swinging, "Swinging"}, | |||
| {PlayerState::Attacking, "Attacking"}, | |||
| {PlayerState::Locking, "Locking"}, | |||
| {PlayerState::Rummaging, "Rummaging"}, | |||
| // {PlayerState::Rummaging, "Rummaging"}, | |||
| {PlayerState::Climbing, "Climbing"}, | |||
| {PlayerState::OpeningAChest, "OpeningAChest"}, | |||
| {PlayerState::UsingSpecialSkill, "UsingSpecialSkill"}, | |||
| @@ -162,7 +162,7 @@ namespace Proto2THUAI6 | |||
| {protobuf::PlayerState::SWINGING, THUAI6::PlayerState::Swinging}, | |||
| {protobuf::PlayerState::ATTACKING, THUAI6::PlayerState::Attacking}, | |||
| {protobuf::PlayerState::LOCKING, THUAI6::PlayerState::Locking}, | |||
| {protobuf::PlayerState::RUMMAGING, THUAI6::PlayerState::Rummaging}, | |||
| // {protobuf::PlayerState::RUMMAGING, THUAI6::PlayerState::Rummaging}, | |||
| {protobuf::PlayerState::CLIMBING, THUAI6::PlayerState::Climbing}, | |||
| {protobuf::PlayerState::OPENING_A_CHEST, THUAI6::PlayerState::OpeningAChest}, | |||
| {protobuf::PlayerState::USING_SPECIAL_SKILL, THUAI6::PlayerState::UsingSpecialSkill}, | |||
| @@ -1,31 +1,49 @@ | |||
| #include <vector> | |||
| #include <thread> | |||
| #include <array> | |||
| #include "AI.h" | |||
| #include "constants.h" | |||
| // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | |||
| extern const bool asynchronous = false; | |||
| // 选手必须定义该变量来选择自己的阵营 | |||
| extern const THUAI6::PlayerType playerType = THUAI6::PlayerType::TrickerPlayer; | |||
| // 选手需要依次将player0到player4的职业在这里定义 | |||
| // 选手需要将两个都定义,本份代码中不选择的阵营任意定义即可 | |||
| extern const THUAI6::TrickerType trickerType = THUAI6::TrickerType::Assassin; | |||
| extern const std::array<THUAI6::StudentType, 4> studentType = { | |||
| THUAI6::StudentType::Athlete, | |||
| THUAI6::StudentType::Teacher, | |||
| THUAI6::StudentType::StraightAStudent, | |||
| THUAI6::StudentType::Sunshine}; | |||
| extern const THUAI6::StudentType studentType = THUAI6::StudentType::Athlete; | |||
| extern const THUAI6::TrickerType trickerType = THUAI6::TrickerType::Assassin; | |||
| // 选手只需写一个即可,为了调试方便写了两个的话也不会有影响 | |||
| //可以在AI.cpp内部声明变量与函数 | |||
| void AI::play(IStudentAPI& api) | |||
| { | |||
| api.PrintTricker(); | |||
| // 公共操作 | |||
| if (this->playerID == 0) | |||
| { | |||
| // 玩家0执行操作 | |||
| } | |||
| else if (this->playerID == 1) | |||
| { | |||
| // 玩家1执行操作 | |||
| } | |||
| else if (this->playerID == 2) | |||
| { | |||
| // 玩家2执行操作 | |||
| } | |||
| else if (this->playerID == 3) | |||
| { | |||
| // 玩家3执行操作 | |||
| } | |||
| //当然可以写成if (this->playerID == 2||this->playerID == 3)之类的操作 | |||
| // 公共操作 | |||
| } | |||
| void AI::play(ITrickerAPI& api) | |||
| { | |||
| if (api.HaveMessage()) | |||
| { | |||
| auto msg = api.GetMessage(); | |||
| api.Print("Message from " + std::to_string(msg.first) + ": " + msg.second); | |||
| } | |||
| auto self = api.GetSelfInfo(); | |||
| api.PrintSelfInfo(); | |||
| } | |||
| @@ -863,7 +863,7 @@ void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool f | |||
| cvAI.wait(lock, [this]() | |||
| { return AIStart; }); | |||
| } | |||
| auto ai = createAI(); | |||
| auto ai = createAI(playerID); | |||
| while (AILoop) | |||
| { | |||
| @@ -2,6 +2,7 @@ | |||
| #include "logic.h" | |||
| #include "structures.h" | |||
| #include <tclap/CmdLine.h> | |||
| #include <array> | |||
| #ifdef _MSC_VER | |||
| #pragma warning(disable : 4996) | |||
| @@ -15,9 +16,8 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||
| bool file = false; | |||
| bool print = false; | |||
| bool warnOnly = false; | |||
| extern const THUAI6::PlayerType playerType; | |||
| extern const THUAI6::TrickerType trickerType; | |||
| extern const THUAI6::StudentType studentType; | |||
| extern const std::array<THUAI6::StudentType, 4> studentType; | |||
| // { | |||
| // file = true; | |||
| // print = true; | |||
| @@ -71,7 +71,16 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||
| } | |||
| try | |||
| { | |||
| Logic logic(playerType, pID, trickerType, studentType); | |||
| THUAI6::PlayerType playerType; | |||
| THUAI6::StudentType stuType = THUAI6::StudentType::NullStudentType; | |||
| if (pID == 4) | |||
| playerType = THUAI6::PlayerType::TrickerPlayer; | |||
| else | |||
| { | |||
| playerType = THUAI6::PlayerType::StudentPlayer; | |||
| stuType = studentType[pID]; | |||
| } | |||
| Logic logic(playerType, pID, trickerType, stuType); | |||
| logic.Main(AIBuilder, sIP, sPort, file, print, warnOnly); | |||
| } | |||
| catch (const std::exception& e) | |||
| @@ -81,9 +90,9 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||
| return 0; | |||
| } | |||
| std::unique_ptr<IAI> CreateAI() | |||
| std::unique_ptr<IAI> CreateAI(int64_t pID) | |||
| { | |||
| return std::make_unique<AI>(); | |||
| return std::make_unique<AI>(pID); | |||
| } | |||
| int main(int argc, char* argv[]) | |||
| @@ -0,0 +1,25 @@ | |||
| The MIT License (MIT) | |||
| Copyright (c) 2016 Gabi Melman. | |||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| of this software and associated documentation files (the "Software"), to deal | |||
| in the Software without restriction, including without limitation the rights | |||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| copies of the Software, and to permit persons to whom the Software is | |||
| furnished to do so, subject to the following conditions: | |||
| The above copyright notice and this permission notice shall be included in | |||
| all copies or substantial portions of the Software. | |||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
| THE SOFTWARE. | |||
| -- NOTE: Third party dependency used by this software -- | |||
| This software depends on the fmt lib (MIT License), | |||
| and users must comply to its license: https://github.com/fmtlib/fmt/blob/master/LICENSE.rst | |||
| @@ -1,6 +1,6 @@ | |||
| import PyAPI.structures as THUAI6 | |||
| from PyAPI.Interface import IStudentAPI, ITrickerAPI, IAI | |||
| from typing import Union, Final, cast | |||
| from typing import Union, Final, cast, List | |||
| from PyAPI.constants import Constants | |||
| import queue | |||
| @@ -13,15 +13,10 @@ class Setting: | |||
| def asynchronous() -> bool: | |||
| return True | |||
| # 选手必须修改该函数的返回值来选择自己的阵营 | |||
| # 选手需要依次将player0到player4的职业都定义 | |||
| @staticmethod | |||
| def playerType() -> THUAI6.PlayerType: | |||
| return THUAI6.PlayerType.StudentPlayer | |||
| # 选手需要将两个都定义,本份代码中不选择的阵营任意定义即可 | |||
| @staticmethod | |||
| def studentType() -> THUAI6.StudentType: | |||
| return THUAI6.StudentType.Athlete | |||
| def studentType() -> List[THUAI6.StudentType]: | |||
| return [THUAI6.StudentType.Athlete, THUAI6.StudentType.Teacher, THUAI6.StudentType.StraightAStudent, THUAI6.StudentType.Sunshine] | |||
| @staticmethod | |||
| def trickerType() -> THUAI6.TrickerType: | |||
| @@ -43,112 +38,29 @@ class AssistFunction: | |||
| return grid // numOfGridPerCell | |||
| path = [] | |||
| cur = 0 | |||
| fixedclass = [] | |||
| class AI(IAI): | |||
| # 选手在这里实现自己的逻辑,要求和上面选择的阵营保持一致 | |||
| def StudentPlay(self, api: IStudentAPI) -> None: | |||
| # 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 __init__(self, pID: int): | |||
| self.__playerID = pID | |||
| # 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 | |||
| # else: | |||
| # path = bfs(x, y) | |||
| # cur = 0 | |||
| # return | |||
| # if (AssistFunction.GridToCell(api.GetSelfInfo().x), AssistFunction.GridToCell(api.GetSelfInfo().y)) == (6, 6) and api.GetGateProgress(5, 6) < 18000: | |||
| # api.StartOpenGate() | |||
| # return | |||
| # if (AssistFunction.GridToCell(api.GetSelfInfo().x), AssistFunction.GridToCell(api.GetSelfInfo().y)) == (6, 6) and api.GetGateProgress(5, 6) >= 18000: | |||
| # api.Graduate() | |||
| # return | |||
| # 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() | |||
| def StudentPlay(self, api: IStudentAPI) -> None: | |||
| #公共操作 | |||
| if self.__playerID == 0: | |||
| # 玩家0执行操作 | |||
| return | |||
| elif self.__playerID == 1: | |||
| # 玩家1执行操作 | |||
| return | |||
| elif self.__playerID == 2: | |||
| # 玩家2执行操作 | |||
| return | |||
| elif self.__playerID == 3: | |||
| # 玩家3执行操作 | |||
| return | |||
| #可以写成if self.__playerID<2之类的写法 | |||
| #公共操作 | |||
| return | |||
| def TrickerPlay(self, api: ITrickerAPI) -> None: | |||
| api.UseSkill(0) | |||
| api.UseSkill(1) | |||
| selfInfo = api.GetSelfInfo() | |||
| api.PrintSelfInfo() | |||
| return | |||
| @@ -198,11 +198,15 @@ class Communication: | |||
| self.__haveNewMessage = False | |||
| return self.__message2Client | |||
| def AddPlayer(self, playerID: int) -> None: | |||
| def AddPlayer(self, playerID: int, playerType: THUAI6.PlayerType) -> None: | |||
| def tMessage(): | |||
| try: | |||
| if playerType == THUAI6.PlayerType.StudentPlayer: | |||
| studentType = Setting.studentType()[playerID] | |||
| else: | |||
| studentType = THUAI6.StudentType.NullStudentType | |||
| playerMsg = THUAI62Proto.THUAI62ProtobufPlayer( | |||
| playerID, Setting.playerType(), Setting.studentType(), Setting.trickerType()) | |||
| playerID, playerType, studentType, Setting.trickerType()) | |||
| for msg in self.__THUAI6Stub.AddPlayer(playerMsg): | |||
| with self.__cvMessage: | |||
| self.__haveNewMessage = True | |||
| @@ -8,6 +8,7 @@ class NoInstance: | |||
| class Constants(NoInstance): | |||
| frameDuration = 50 # 每帧毫秒数 | |||
| numOfGridPerCell = 1000 # 单位坐标数 | |||
| rows = 50 # 地图行数 | |||
| cols = 50 # 地图列数 | |||
| @@ -27,8 +28,8 @@ class Constants(NoInstance): | |||
| basicEncourageSpeed = 100 | |||
| basicLearnSpeed = 123 | |||
| basicSpeedOfOpeningOrLocking = 4000 | |||
| basicStudentSpeedOfClimbingThroughWindows = 611 | |||
| basicTrickerSpeedOfClimbingThroughWindows = 1270 | |||
| basicStudentSpeedOfClimbingThroughWindows = 1222 | |||
| basicTrickerSpeedOfClimbingThroughWindows = 2540 | |||
| basicSpeedOfOpenChest = 1000 | |||
| basicHp = 3000000 | |||
| @@ -36,8 +37,8 @@ class Constants(NoInstance): | |||
| basicEncouragementDegree = 1500000 | |||
| basicTimeOfRouse = 1000 | |||
| basicStudentSpeed = 1270 | |||
| basicTrickerSpeed = 1504 | |||
| basicStudentSpeed = 3000 | |||
| basicTrickerSpeed = 3600 | |||
| basicConcealment = 1.0 | |||
| basicStudentAlertnessRadius = 15 * numOfGridPerCell | |||
| @@ -57,10 +58,10 @@ class Constants(NoInstance): | |||
| basicRecoveryFromHit = 3700 # 基本命中攻击恢复时长 | |||
| basicStunnedTimeOfStudent = 4300 | |||
| basicBulletmoveSpeed = 3700 # 基本子弹移动速度 | |||
| basicRemoteAttackRange = 3000 # 基本远程攻击范围 | |||
| basicAttackShortRange = 1100 # 基本近程攻击范围 | |||
| basicBulletBombRange = 1000 # 基本子弹爆炸范围 | |||
| basicBulletmoveSpeed = 7400 # 基本子弹移动速度 | |||
| basicRemoteAttackRange = 6000 # 基本远程攻击范围 | |||
| basicAttackShortRange = 2200 # 基本近程攻击范围 | |||
| basicBulletBombRange = 2000 # 基本子弹爆炸范围 | |||
| # 道具相关 | |||
| @@ -84,10 +85,10 @@ class Constants(NoInstance): | |||
| addedTimeOfSpeedWhenInspire = 1.6 | |||
| timeOfAddingSpeedWhenInspire = 6000 | |||
| addHpWhenEncourage = basicHp / 4; | |||
| addHpWhenEncourage = basicHp / 4 | |||
| checkIntervalWhenShowTime = 200; | |||
| addAddictionPer100msWhenShowTime = 300; | |||
| checkIntervalWhenShowTime = 200 | |||
| addAddictionPer100msWhenShowTime = 300 | |||
| class Assassin: | |||
| @@ -270,7 +271,7 @@ class Howl: | |||
| class ShowTime: | |||
| skillCD = (int)(3.0 * Constants.commonSkillCD) | |||
| skillCD = (int)(8 * Constants.commonSkillCD / 3) | |||
| durationTime = (int)(1.0 * Constants.commonSkillTime) | |||
| @@ -18,12 +18,14 @@ from PyAPI.Interface import ILogic, IGameTimer | |||
| class Logic(ILogic): | |||
| def __init__(self, playerID: int) -> None: | |||
| def __init__(self, playerID: int, playerType: THUAI6.PlayerType) -> None: | |||
| # ID | |||
| self.__playerID: int = playerID | |||
| self.__playerGUIDs: List[int] = [] | |||
| self.__playerType: THUAI6.PlayerType = playerType | |||
| # 通信 | |||
| self.__comm: Communication | |||
| @@ -268,7 +270,7 @@ class Logic(ILogic): | |||
| def __ProcessMessage(self) -> None: | |||
| def messageThread(): | |||
| self.__logger.info("Message thread start!") | |||
| self.__comm.AddPlayer(self.__playerID) | |||
| self.__comm.AddPlayer(self.__playerID, self.__playerType) | |||
| self.__logger.info("Join the player!") | |||
| while self.__gameState != THUAI6.GameState.GameEnd: | |||
| @@ -344,7 +346,7 @@ class Logic(ILogic): | |||
| self.__logger.debug("Buffer cleared!") | |||
| self.__bufferState.gameInfo = Proto2THUAI6.Protobuf2THUAI6GameInfo( | |||
| message.all_message) | |||
| if Setting.playerType() == THUAI6.PlayerType.StudentPlayer: | |||
| if self.__playerType == THUAI6.PlayerType.StudentPlayer: | |||
| for item in message.obj_message: | |||
| if item.WhichOneof("message_of_obj") == "student_message": | |||
| if item.student_message.player_id == self.__playerID: | |||
| @@ -590,20 +592,20 @@ class Logic(ILogic): | |||
| self.__logger.info("asynchronous: %s", Setting.asynchronous()) | |||
| self.__logger.info("server: %s:%s", IP, port) | |||
| self.__logger.info("playerID: %s", self.__playerID) | |||
| self.__logger.info("player type: %s", Setting.playerType().name) | |||
| self.__logger.info("player type: %s", self.__playerType.name) | |||
| self.__logger.info("****************************") | |||
| # 建立通信组件 | |||
| self.__comm = Communication(IP, port) | |||
| # 构造timer | |||
| if Setting.playerType() == THUAI6.PlayerType.StudentPlayer: | |||
| if self.__playerType == THUAI6.PlayerType.StudentPlayer: | |||
| if not file and not screen: | |||
| self.__timer = StudentAPI(self) | |||
| else: | |||
| self.__timer = StudentDebugAPI( | |||
| self, file, screen, warnOnly, self.__playerID) | |||
| elif Setting.playerType() == THUAI6.PlayerType.TrickerPlayer: | |||
| elif self.__playerType == THUAI6.PlayerType.TrickerPlayer: | |||
| if not file and not screen: | |||
| self.__timer = TrickerAPI(self) | |||
| else: | |||
| @@ -615,7 +617,7 @@ class Logic(ILogic): | |||
| with self.__cvAI: | |||
| self.__cvAI.wait_for(lambda: self.__AIStart) | |||
| ai = createAI() | |||
| ai = createAI(self.__playerID) | |||
| while self.__AILoop: | |||
| if Setting.asynchronous(): | |||
| self.__Wait() | |||
| @@ -9,6 +9,7 @@ from PyAPI.AI import AI | |||
| from PyAPI.logic import Logic | |||
| from typing import List, Callable | |||
| import argparse | |||
| import PyAPI.structures as THUAI6 | |||
| def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None: | |||
| @@ -39,12 +40,17 @@ def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None: | |||
| file = args.file | |||
| screen = args.screen | |||
| warnOnly = args.warnOnly | |||
| logic = Logic(pID) | |||
| playerType = THUAI6.PlayerType.NullPlayerType | |||
| if pID == 4: | |||
| playerType = THUAI6.PlayerType.TrickerPlayer | |||
| else: | |||
| playerType = THUAI6.PlayerType.StudentPlayer | |||
| logic = Logic(pID, playerType) | |||
| logic.Main(AIBuilder, sIP, sPort, file, screen, warnOnly) | |||
| def CreateAI() -> IAI: | |||
| return AI() | |||
| def CreateAI(pID: int) -> IAI: | |||
| return AI(pID) | |||
| if __name__ == '__main__': | |||
| @@ -113,7 +113,7 @@ class PlayerState(Enum): | |||
| Swinging = 11 | |||
| Attacking = 12 | |||
| Locking = 13 | |||
| Rummaging = 14 | |||
| # Rummaging = 14 | |||
| Climbing = 15 | |||
| OpeningAChest = 16 | |||
| UsingSpecialSkill = 17 | |||
| @@ -145,7 +145,7 @@ class Proto2THUAI6(NoInstance): | |||
| MessageType.SWINGING: THUAI6.PlayerState.Swinging, | |||
| MessageType.ATTACKING: THUAI6.PlayerState.Attacking, | |||
| MessageType.LOCKING: THUAI6.PlayerState.Locking, | |||
| MessageType.RUMMAGING: THUAI6.PlayerState.Rummaging, | |||
| # MessageType.RUMMAGING: THUAI6.PlayerState.Rummaging, | |||
| MessageType.CLIMBING: THUAI6.PlayerState.Climbing, | |||
| MessageType.OPENING_A_CHEST: THUAI6.PlayerState.OpeningAChest, | |||
| MessageType.USING_SPECIAL_SKILL: THUAI6.PlayerState.UsingSpecialSkill, | |||
| @@ -1,6 +1,7 @@ | |||
| #!/usr/bin/env bash | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0 -d -o & | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -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 & | |||
| 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 & | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4 -d & | |||
| @@ -0,0 +1,3 @@ | |||
| #!/usr/bin/env bash | |||
| ./CAPI/proto/cpp_output.sh | |||
| @@ -0,0 +1,12 @@ | |||
| #!/usr/bin/env bash | |||
| python -m pip install -r ./CAPI/python/requirements.txt | |||
| mkdir -p proto | |||
| python -m grpc_tools.protoc -I./CAPI/proto/ --python_out=./CAPI/python/proto --pyi_out=./CAPI/python/proto MessageType.proto | |||
| python -m grpc_tools.protoc -I./CAPI/proto/ --python_out=./CAPI/python/proto --pyi_out=./CAPI/python/proto Message2Clients.proto | |||
| python -m grpc_tools.protoc -I./CAPI/proto/ --python_out=./CAPI/python/proto --pyi_out=./CAPI/python/proto Message2Server.proto | |||
| python -m grpc_tools.protoc -I./CAPI/proto/ --python_out=./CAPI/python/proto --pyi_out=./CAPI/python/proto --grpc_python_out=./CAPI/python/proto Services.proto | |||
| @@ -0,0 +1,6 @@ | |||
| #!/usr/bin/env bash | |||
| ./CAPI/cpp/build/capi -I 127.0.0.1 -P 8888 -p 0 -d -o & | |||
| ./CAPI/cpp/build/capi -I 127.0.0.1 -P 8888 -p 1 -d -o & | |||
| ./CAPI/cpp/build/capi -I 127.0.0.1 -P 8888 -p 2 -d -o & | |||
| ./CAPI/cpp/build/capi -I 127.0.0.1 -P 8888 -p 3 -d -o & | |||
| @@ -0,0 +1,6 @@ | |||
| #!/usr/bin/env bash | |||
| python ./CAPI/python/PyAPI/main.py -I 127.0.0.1 -P 8888 -p 0 -d -o & | |||
| python ./CAPI/python/PyAPI/main.py -I 127.0.0.1 -P 8888 -p 1 -d -o & | |||
| python ./CAPI/python/PyAPI/main.py -I 127.0.0.1 -P 8888 -p 2 -d -o & | |||
| python ./CAPI/python/PyAPI/main.py -I 127.0.0.1 -P 8888 -p 3 -d -o & | |||
| @@ -0,0 +1,3 @@ | |||
| #!/usr/bin/env bash | |||
| ./linux64/Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 | |||
| @@ -0,0 +1,3 @@ | |||
| #!/usr/bin/env bash | |||
| ./linux64/Debug/Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 | |||
| @@ -0,0 +1,64 @@ | |||
| # Q&A | |||
| [TOC] | |||
| ## 常见简单问题 | |||
| Q: Windows找不到文件:\CAPI\cpp\x64\Debug\APl.exe? | |||
| A: | |||
| 应该是还没有编译,打开CAPI\cpp目录,在里面打开CAPI.sln,然后点击生成,对代码进行编译 | |||
| Q: 怎么修改.cmd参数? | |||
| A: | |||
| 见选手包中的使用文档部分 | |||
| Q: 怎么开始游戏? | |||
| A: | |||
| 需要确保学生阵营和捣蛋鬼阵营的人数都达到Server.cmd中设定的值。人数不足也可以打开WPF,参考使用文档,修改RunGUIClient.cmd的参数,然后运行RunGUIClient.cmd,这样可以通过WPF运行部分客户端,来达到人数限制。 | |||
| Q: Mac怎么用? | |||
| A: | |||
| 安装Windows虚拟机 | |||
| ## C++ | |||
| Q:显示API项目已卸载 | |||
|  | |||
| A:可能是没有安装C++ | |||
| Q:CAPI.sln编译不通过 | |||
|  | |||
| A: | |||
| 出现_std_find_trivial_1的报错就是没更新到VS2022, | |||
| 对于VS2022依旧报错_std_find_trivial_1的,先考虑是否版本过旧 | |||
|  | |||
| 确保上图项目属性中平台工具集在V143及以上,C++17标准 | |||
| ## Python | |||
| ### grpc版本更新失败 | |||
| Q:运行GeneratePythonProto.cmd报错 | |||
|  | |||
| A: | |||
| - 可能措施1. | |||
| 首先保证Python版本在3.9及以上 | |||
| - 可能措施2. 更换为国内镜像源 | |||
| 在终端输入 | |||
| `pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple` | |||
| - 可能措施3. 更新pip | |||
| `python -m pip install --upgrade pip` (pip 版本最好为23.1) | |||
| ## 比赛相关 | |||
| Q:职业数值会修改吗? | |||
| A:初赛结束会调数值及机制,增加新角色 | |||
| @@ -8,31 +8,37 @@ RUN apt-get update && apt-get install --no-install-recommends -y gcc g++ make wg | |||
| RUN git clone -b v1.46.3 --depth 1 --shallow-submodules https://gitee.com/mirrors/grpc.git | |||
| RUN wget -P . https://cloud.tsinghua.edu.cn/f/1f2713efd9e44255abd6/?dl=1 | |||
| RUN mv 'index.html?dl=1' third_party.tar.gz | |||
| RUN cd grpc | |||
| WORKDIR /usr/local/grpc | |||
| RUN rm -rf third_party | |||
| RUN mv ../third_party.tar.gz . | |||
| RUN tar -zxvf third_party.tar.gz | |||
| RUN mkdir -p cmake/build | |||
| RUN pushd cmake/build | |||
| WORKDIR /usr/local/grpc/cmake/build | |||
| RUN cmake -DgRPC_INSTALL=ON \ | |||
| -DgRPC_BUILD_TESTS=OFF \ | |||
| ../.. | |||
| RUN make -j$(nproc) | |||
| RUN make install | |||
| RUN popd | |||
| #安装protobuf | |||
| RUN cd /usr/local | |||
| WORKDIR /usr/local | |||
| RUN git clone https://gitee.com/mirrors/protobuf_source.git ./protobuf | |||
| RUN cd protobuf | |||
| RUN git checkout 3.22.1 | |||
| RUN ./autogen.sh | |||
| WORKDIR /usr/local/protobuf | |||
| RUN git checkout 3.20.x | |||
| RUN ./autogen.sh | |||
| RUN ./configure | |||
| RUN make -j$(nproc) | |||
| RUN make install | |||
| RUN ldconfig | |||
| #RUN git submodule update --init --recursive | |||
| #RUN cmake . | |||
| #RUN cmake --build . --parallel 10 | |||
| #RUN make install | |||
| COPY ./CAPI /usr/local/PlayerCode | |||
| RUN cd /usr/local/PlayerCode/CAPI | |||
| RUN cmake CMakeLists.txt | |||
| COPY ./CAPI /usr/local/PlayerCode/CAPI | |||
| COPY ./dependency /usr/local/PlayerCode/dependency | |||
| WORKDIR /usr/local/PlayerCode/dependency/proto | |||
| RUN ./cpp_output.sh | |||
| WORKDIR /usr/local/PlayerCode/CAPI/cpp | |||
| RUN cmake ./CMakeLists.txt | |||
| RUN make | |||
| @@ -0,0 +1,8 @@ | |||
| @ECHO OFF | |||
| CD %~dp0 | |||
| dotnet publish "../../Logic/Server/Server.csproj" -c Release -r linux-x64 --self-contained true | |||
| dotnet publish "../../Logic/Server/Server.csproj" -c Release -r win-x64 --self-contained true | |||
| dotnet publish "../../Logic/Server/Server.csproj" -c Debug -r win-x64 --self-contained true | |||
| dotnet publish "../../Logic/Server/Server.csproj" -c Debug -r linux-x64 --self-contained true | |||
| dotnet publish "../../Logic/Client/Client.csproj" -c Release -r win-x64 --self-contained true | |||
| PAUSE | |||
| @@ -0,0 +1,38 @@ | |||
| #! /bin/bash | |||
| workdir=/d/伤风/软件部/MyTHUAI6/THUAI6/logic | |||
| targetdir=/d/伤风/软件部/THUAI6-毕业吧少女 | |||
| mkdir -p ${targetdir} | |||
| pushd ${targetdir} | |||
| mkdir -p win/win64 | |||
| mkdir -p linux/linux64/ | |||
| mkdir -p win/win64/Debug/ | |||
| mkdir -p linux/linux64/Debug/ | |||
| popd | |||
| pushd ${workdir} | |||
| pushd Server/bin/Release/net6.0/linux-x64/publish | |||
| rm *.pdb | |||
| cp -rf * ${targetdir}/linux/linux64/ | |||
| popd | |||
| pushd Server/bin/Debug/net6.0/linux-x64/publish | |||
| cp -rf * ${targetdir}/linux/linux64/Debug/ | |||
| popd | |||
| pushd Server/bin/Debug/net6.0/win-x64/publish | |||
| cp -rf * ${targetdir}/win/win64/Debug/ | |||
| popd | |||
| pushd Server/bin/Release/net6.0/win-x64/publish | |||
| rm *.pdb | |||
| cp -rf * ${targetdir}/win/win64/ | |||
| popd | |||
| pushd Client/bin/Release/net6.0-windows/win-x64/publish | |||
| rm *.pdb | |||
| cp -rf * ${targetdir}/win/win64/ | |||
| popd | |||
| @@ -9,6 +9,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="ICSharpCode.SharpZipLib.dll" Version="0.85.4.369" /> | |||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> | |||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | |||
| <PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.34" /> | |||
| @@ -38,17 +38,19 @@ | |||
| <TextBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="5" Name="RouteTxtBox" Text="{Binding Route}" Visibility="{Binding RouteBoxVis}"/> | |||
| <Button Grid.Row="3" Grid.Column="6" Name="GetRouteBtn" Content="选择文件夹" Command="{Binding ClickBrowseCommand}" Visibility="{Binding RouteBoxVis}" /> | |||
| <Button Grid.Row="3" Grid.Column="7" Name="SetBtm" Content="确认并安装" Command="{Binding ClickConfirmCommand}" Visibility="{Binding RouteBoxVis}"/> | |||
| <Button Grid.Row="3" Grid.Column="7" Name="SetBtm" Content="{Binding ConfirmBtnCont}" Command="{Binding ClickConfirmCommand}" Visibility="{Binding RouteBoxVis}"/> | |||
| <Button Grid.Row="4" Grid.Column="6" Grid.ColumnSpan="2" Name="ReadExsisted" Content="所选文件夹即为选手包" Command="{Binding ClickReadCommand}" Visibility="{Binding NewUserVis}"/> | |||
| <Button Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" Name="UpdateBtn" Content="{Binding UpdateBtnCont}" Command="{Binding ClickUpdateCommand}" Visibility="{Binding MenuVis}" /> | |||
| <TextBlock Grid.Row="4" Grid.Column="2" Text="{Binding UpdateInfo}" Visibility="{Binding MenuVis}" /> | |||
| <TextBlock Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="2" FontSize="10" Text="{Binding UpdateInfo}" Visibility="{Binding UpdateInfoVis}" /> | |||
| <Button Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="2" Name="MoveBtn" Content="移动文件" Command="{Binding ClickMoveCommand}" Visibility="{Binding MenuVis}" /> | |||
| <Button Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Name="UninstBtn" Content="卸载选手包" Command="{Binding ClickUninstCommand}" Visibility="{Binding MenuVis}" /> | |||
| <Button Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="2" Name="MenuBackBtn" Content="回到登陆界面" Command="{Binding ClickBackCommand}" Visibility="{Binding MenuVis}" /> | |||
| <TextBlock Grid.Row="3" Grid.Column="4" Text="Processing" Grid.ColumnSpan="2" Visibility="{Binding ProgressVis}"/> | |||
| <ProgressBar Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="6" Minimum="0" Maximum="100" Name="Progress" Visibility="{Binding ProgressVis}" IsIndeterminate="True"/> | |||
| <TextBlock Grid.Row="3" Grid.Column="3" Text="正在下载……" Grid.ColumnSpan="2" Visibility="{Binding ProgressVis}"/> | |||
| <ProgressBar Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="7" Minimum="0" Maximum="100" Name="Progress" Visibility="{Binding ProgressVis}" IsIndeterminate="True"/> | |||
| <TextBlock Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="4" Text="操作完成!你可以继续操作或退出" Visibility="{Binding CompleteVis}"/> | |||
| <Button Grid.Row="6" Grid.Column="1" Name="BackBtn" Content="返回" Command="{Binding ClickBackCommand}" Visibility="{Binding CompleteVis}" Click="BackBtn_Click"/> | |||
| @@ -72,20 +74,21 @@ | |||
| <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}" /> | |||
| <PasswordBox Grid.Row="3" Grid.Column="1" Name="Password" Visibility="{Binding LoginVis}" c:PasswordHelper.Attach="True" c:PasswordHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> | |||
| <!--<CheckBox Grid.Row="5" Grid.Column="0" Visibility="{Binding LoginVis}">记住我</CheckBox>--> | |||
| <CheckBox Grid.Row="5" Grid.Column="0" Visibility="{Binding LoginVis}" IsChecked="{Binding RememberMe}">记住我</CheckBox> | |||
| <TextBlock Grid.Row="5" Grid.Column="1" Foreground="Red" Text=" 用户名或密码错误!" Visibility="{Binding LoginFailVis}"/> | |||
| </Grid> | |||
| </StackPanel> | |||
| <Button Grid.Row="7" Grid.Column="1" Name="Login" Content="登录" Command="{Binding ClickLoginCommand}" Visibility="{Binding LoginVis}"/> | |||
| <Button Grid.Row="7" Grid.Column="2" Name="Launch" Content="{Binding LaunchBtnCont}" Command="{Binding ClickLaunchCommand}" Visibility="{Binding LoginVis}"/> | |||
| <Button Grid.Row="7" Grid.Column="2" Name="Launch" FontSize="11" Content="{Binding LaunchBtnCont}" Command="{Binding ClickLaunchCommand}" Visibility="{Binding LoginVis}"/> | |||
| <Button Grid.Row="7" Grid.Column="3" Name="ShiftLanguage" FontSize="11" Content="更改语言" Command="{Binding ClickShiftLanguageCommand}" Visibility="{Binding LaunchVis}"/> | |||
| <Button Grid.Row="7" Grid.Column="4" Grid.ColumnSpan="2" Name="Edit" Content="修改文件" Command="{Binding ClickEditCommand}" Visibility="{Binding LoginVis}"/> | |||
| <TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Text="你有已完成的比赛!" Visibility="{Binding MatchFinishedVis}"/> | |||
| <Button Grid.Row="3" Grid.Column="1" Name ="Upload" Content="{Binding UploadBtnCont}" Command="{Binding ClickUploadCommand}" Visibility="{Binding WebVis}"/> | |||
| <TextBlock Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding CodeName}" Visibility="{Binding UploadReadyVis}" /> | |||
| <Button Grid.Row="3" Grid.Column="2" Name="ReUpload" Content="放弃上传" Command="{Binding ClickReselectCommand}" Visibility="{Binding UploadReadyVis}" /> | |||
| <Button Grid.Row="3" Grid.Column="2" Name="ReUpload" Content="{Binding AbortOrSelLanguage}" Command="{Binding ClickAboutUploadCommand}" Visibility="{Binding WebVis}" /> | |||
| <Button Grid.Row="6" Grid.Column="3" Grid.ColumnSpan="2" Content="退出登录" Command="{Binding ClickBackCommand}" Visibility="{Binding WebVis}" /> | |||
| <StackPanel Grid.Row="5" Grid.Column="1" Grid.RowSpan="3" Grid.ColumnSpan="2"> | |||
| <Grid> | |||
| @@ -21,10 +21,18 @@ using System.Net.Http; | |||
| using System.Windows; | |||
| using System.Windows.Shapes; | |||
| //using System.Windows.Forms; | |||
| using System.Threading.Tasks; | |||
| using System.Threading; | |||
| using MessageBox = System.Windows.MessageBox; | |||
| using Downloader; | |||
| using COSXML.Transfer; | |||
| using WebConnect; | |||
| using System.IO.Compression; | |||
| using ICSharpCode.SharpZipLib.Tar; | |||
| using ICSharpCode.SharpZipLib.GZip; | |||
| using static System.Net.WebRequestMethods; | |||
| using File = System.IO.File; | |||
| namespace starter.viewmodel.settings | |||
| { | |||
| @@ -52,6 +60,7 @@ namespace starter.viewmodel.settings | |||
| PlayerNum = "nSelect"; | |||
| UploadReady = false; | |||
| LoginFailed = false; | |||
| launchLanguage = LaunchLanguage.cpp; | |||
| } | |||
| /// <summary> | |||
| @@ -118,7 +127,7 @@ namespace starter.viewmodel.settings | |||
| { | |||
| if (updateInfo.changedFileCount != 0 || updateInfo.newFileCount != 0) | |||
| { | |||
| Updates = "发现新版本"; | |||
| Updates = $"{updateInfo.newFileCount}个新文件,{updateInfo.changedFileCount}个文件变化"; | |||
| } | |||
| return Status.menu; | |||
| } | |||
| @@ -128,6 +137,38 @@ namespace starter.viewmodel.settings | |||
| { | |||
| return await web.LoginToEEsast(client, Username, Password); | |||
| } | |||
| public bool RememberUser() | |||
| { | |||
| int result = 0; | |||
| result |= Web.WriteUserEmail(Username); | |||
| result |= Web.WriteUserPassword(Password); | |||
| return result == 0; | |||
| } | |||
| public bool RecallUser() | |||
| { | |||
| Username = Web.ReadUserEmail(); | |||
| if (Username == null || Username.Equals("")) | |||
| { | |||
| Username = ""; | |||
| return false; | |||
| } | |||
| Password = Web.ReadUserPassword(); | |||
| if (Password == null || Username.Equals("")) | |||
| { | |||
| Password = ""; | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| public bool ForgetUser() | |||
| { | |||
| int result = 0; | |||
| result |= Web.WriteUserEmail(""); | |||
| result |= Web.WriteUserPassword(""); | |||
| return result == 0; | |||
| } | |||
| public bool Update() | |||
| { | |||
| return Tencent_cos_download.Update(); | |||
| @@ -254,6 +295,15 @@ namespace starter.viewmodel.settings | |||
| { | |||
| get; set; | |||
| } | |||
| public bool RememberMe | |||
| { | |||
| get; set; | |||
| } | |||
| public enum LaunchLanguage { cpp, python }; | |||
| public LaunchLanguage launchLanguage | |||
| { | |||
| get; set; | |||
| } | |||
| } | |||
| } | |||
| namespace Downloader | |||
| @@ -292,8 +342,8 @@ namespace Downloader | |||
| public static string dataPath = ""; // C盘的文档文件夹 | |||
| public Data(string path) | |||
| { | |||
| // dataPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); | |||
| dataPath = new DirectoryInfo(".").FullName; | |||
| dataPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); | |||
| //dataPath = new DirectoryInfo(".").FullName; | |||
| Data.path = System.IO.Path.Combine(dataPath, "THUAI6.json"); | |||
| if (File.Exists(Data.path)) | |||
| { | |||
| @@ -397,8 +447,9 @@ namespace Downloader | |||
| .Build(); // 创建 CosXmlConfig 对象 | |||
| // 永久密钥访问凭证 | |||
| string secretId = "***"; //"云 API 密钥 SecretId"; | |||
| string secretKey = "***"; //"云 API 密钥 SecretKey"; | |||
| string secretId = "***"; //"云 API 密钥 SecretId"; | |||
| string secretKey = "***"; //"云 API 密钥 SecretKey"; | |||
| long durationSecond = 1000; // 每次请求签名有效时长,单位为秒 | |||
| QCloudCredentialProvider cosCredentialProvider = new DefaultQCloudCredentialProvider( | |||
| @@ -418,7 +469,7 @@ namespace Downloader | |||
| Dictionary<string, string> test = request.GetRequestHeaders(); | |||
| request.SetCosProgressCallback(delegate (long completed, long total) | |||
| { | |||
| Console.WriteLine(String.Format("progress = {0:##.##}%", completed * 100.0 / total)); | |||
| //Console.WriteLine(String.Format("progress = {0:##.##}%", completed * 100.0 / total)); | |||
| }); | |||
| // 执行请求 | |||
| GetObjectResult result = cosXml.GetObject(request); | |||
| @@ -462,6 +513,8 @@ namespace Downloader | |||
| { | |||
| if (fst != null) | |||
| fst.Close(); | |||
| if (File.Exists(strFileFullPath)) | |||
| return "conflict"; | |||
| return ""; | |||
| } | |||
| finally | |||
| @@ -515,11 +568,16 @@ namespace Downloader | |||
| Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| foreach (KeyValuePair<string, string> pair in jsonDict) | |||
| { | |||
| MD5 = GetFileMd5Hash(System.IO.Path.Combine(Data.FilePath, pair.Key)); | |||
| if (MD5.Length == 0) // 文档不存在 | |||
| newFileName.Add(pair.Key); | |||
| else if (MD5 != pair.Value) // MD5不匹配 | |||
| updateFileName.Add(pair.Key); | |||
| if (System.IO.Path.GetFileName(pair.Key) != "AI.cpp" && System.IO.Path.GetFileName(pair.Key) != "AI.py") | |||
| { | |||
| MD5 = GetFileMd5Hash(System.IO.Path.Combine(Data.FilePath, pair.Key)); | |||
| if (MD5.Length == 0) // 文档不存在 | |||
| newFileName.Add(pair.Key); | |||
| else if (MD5.Equals("conflict")) | |||
| MessageBox.Show($"文件{pair.Key}已打开,无法检查是否为最新,若需要,请关闭文件稍后手动检查更新", "文件正在使用", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
| else if (MD5 != pair.Value) // MD5不匹配 | |||
| updateFileName.Add(pair.Key); | |||
| } | |||
| } | |||
| newFile = newFileName.Count; | |||
| @@ -577,7 +635,6 @@ namespace Downloader | |||
| private static void Download() | |||
| { | |||
| Tencent_cos_download Downloader = new Tencent_cos_download(); | |||
| int newFile = 0, updateFile = 0; | |||
| int totalnew = newFileName.Count, totalupdate = updateFileName.Count; | |||
| filenum = totalnew + totalupdate; | |||
| @@ -588,19 +645,20 @@ namespace Downloader | |||
| { | |||
| foreach (string filename in newFileName) | |||
| { | |||
| Console.WriteLine(newFile + 1 + "/" + totalnew + ":开始下载" + filename); | |||
| //Console.WriteLine(newFile + 1 + "/" + totalnew + ":开始下载" + filename); | |||
| Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename); | |||
| Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||
| //Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||
| newFile++; | |||
| } | |||
| foreach (string filename in updateFileName) | |||
| { | |||
| Console.WriteLine(updateFile + 1 + "/" + totalupdate + ":开始下载" + filename); | |||
| //Console.WriteLine(updateFile + 1 + "/" + totalupdate + ":开始下载" + filename); | |||
| File.Delete(System.IO.Path.Combine(@Data.FilePath, filename)); | |||
| Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename); | |||
| Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||
| //Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||
| updateFile++; | |||
| } | |||
| UpdatePlanned = false; | |||
| } | |||
| catch (CosClientException clientEx) | |||
| { | |||
| @@ -696,12 +754,35 @@ namespace Downloader | |||
| newFileName.Clear(); | |||
| updateFileName.Clear(); | |||
| foreach (KeyValuePair<string, string> pair in jsonDict) | |||
| newFileName.Add("THUAI6.tar.gz"); | |||
| Download(); | |||
| Stream inStream = null; | |||
| Stream gzipStream = null; | |||
| TarArchive tarArchive = null; | |||
| try | |||
| { | |||
| newFileName.Add(pair.Key); | |||
| using (inStream = File.OpenRead(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz"))) | |||
| { | |||
| using (gzipStream = new GZipInputStream(inStream)) | |||
| { | |||
| tarArchive = TarArchive.CreateInputTarArchive(gzipStream); | |||
| tarArchive.ExtractContents(Data.FilePath); | |||
| tarArchive.Close(); | |||
| } | |||
| } | |||
| } | |||
| Download(); | |||
| catch (Exception ex) | |||
| { | |||
| //出错 | |||
| } | |||
| finally | |||
| { | |||
| if (null != tarArchive) tarArchive.Close(); | |||
| if (null != gzipStream) gzipStream.Close(); | |||
| if (null != inStream) inStream.Close(); | |||
| } | |||
| FileInfo fileInfo = new FileInfo(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz")); | |||
| fileInfo.Delete(); | |||
| string json2; | |||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||
| string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||
| @@ -728,6 +809,18 @@ namespace Downloader | |||
| using StreamWriter sw = new StreamWriter(fs2); | |||
| fs2.SetLength(0); | |||
| sw.Write(JsonConvert.SerializeObject(dict)); | |||
| Check(); | |||
| Download(); | |||
| if (File.Exists(Data.FilePath + "/THUAI6/AI.cpp")) | |||
| { | |||
| FileInfo userCpp = new FileInfo((Data.FilePath + "/THUAI6/AI.cpp").Replace("/", "\\")); | |||
| userCpp.MoveTo(Data.FilePath + "/THUAI6/win/CAPI/cpp/API/src/AI.cpp", true); | |||
| } | |||
| if (File.Exists(Data.FilePath + "/THUAI6/AI.py")) | |||
| { | |||
| FileInfo userCpp = new FileInfo((Data.FilePath + "/THUAI6/AI.py").Replace("/", "\\")); | |||
| userCpp.MoveTo(Data.FilePath + "/THUAI6/win/CAPI/python/PyAPI/AI.cpp", true); | |||
| } | |||
| } | |||
| public static void Change_all_hash(string topDir, Dictionary<string, string> jsonDict) // 更改HASH | |||
| @@ -739,7 +832,7 @@ namespace Downloader | |||
| foreach (FileInfo NextFile in theFolder.GetFiles()) | |||
| { | |||
| string filepath = topDir + @"/" + NextFile.Name; // 文件路径 | |||
| Console.WriteLine(filepath); | |||
| //Console.WriteLine(filepath); | |||
| foreach (KeyValuePair<string, string> pair in jsonDict) | |||
| { | |||
| if (System.IO.Path.Equals(filepath, System.IO.Path.Combine(Data.FilePath, pair.Key).Replace('\\', '/'))) | |||
| @@ -807,27 +900,31 @@ namespace Downloader | |||
| public static int DeleteAll() | |||
| { | |||
| DirectoryInfo di = new DirectoryInfo(Data.FilePath); | |||
| DirectoryInfo player = new DirectoryInfo(System.IO.Path.GetFullPath(System.IO.Path.Combine(Data.FilePath, playerFolder))); | |||
| DirectoryInfo di = new DirectoryInfo(Data.FilePath + "/THUAI6"); | |||
| //DirectoryInfo player = new DirectoryInfo(System.IO.Path.GetFullPath(System.IO.Path.Combine(Data.FilePath, playerFolder))); | |||
| FileInfo[] allfile = di.GetFiles(); | |||
| try | |||
| { | |||
| foreach (FileInfo file in di.GetFiles()) | |||
| foreach (FileInfo file in allfile) | |||
| { | |||
| //if(file.Name == "AI.cpp" || file.Name == "AI.py") | |||
| //{ | |||
| // string filename = System.IO.Path.GetFileName(file.FullName); | |||
| // file.MoveTo(System.IO.Path.Combine(Data.FilePath, filename)); | |||
| // continue; | |||
| //} | |||
| file.Delete(); | |||
| } | |||
| foreach (FileInfo file in player.GetFiles()) | |||
| { | |||
| if (file.Name == "README.md") | |||
| { | |||
| continue; | |||
| } | |||
| string filename = System.IO.Path.GetFileName(file.FullName); | |||
| file.MoveTo(System.IO.Path.Combine(Data.FilePath, filename)); | |||
| } | |||
| FileInfo userFileCpp = new FileInfo(Data.FilePath + "/THUAI6/win/CAPI/cpp/API/src/AI.cpp"); | |||
| FileInfo userFilePy = new FileInfo(Data.FilePath + "/THUAI6/win/CAPI/python/PyAPI/AI.py"); | |||
| userFileCpp.MoveTo(System.IO.Path.Combine(Data.FilePath + "/THUAI6", System.IO.Path.GetFileName(userFileCpp.FullName))); | |||
| userFilePy.MoveTo(System.IO.Path.Combine(Data.FilePath + "/THUAI6", System.IO.Path.GetFileName(userFilePy.FullName))); | |||
| foreach (DirectoryInfo subdi in di.GetDirectories()) | |||
| { | |||
| subdi.Delete(true); | |||
| } | |||
| FileInfo hashFile = new FileInfo(Data.FilePath + "/hash.json"); | |||
| hashFile.Delete(); | |||
| } | |||
| catch (UnauthorizedAccessException) | |||
| { | |||
| @@ -891,7 +988,6 @@ namespace Downloader | |||
| Console.WriteLine("文件已经打开,请关闭后再删除"); | |||
| return -1; | |||
| } | |||
| Console.WriteLine($"删除成功!player文件夹中的文件已经放在{ProgramName}的根目录下"); | |||
| return 0; | |||
| } | |||
| @@ -1071,7 +1167,7 @@ namespace WebConnect | |||
| switch (response.StatusCode) | |||
| { | |||
| case System.Net.HttpStatusCode.OK: | |||
| Console.WriteLine("Success login"); | |||
| //Console.WriteLine("Success login"); | |||
| token = (System.Text.Json.JsonSerializer.Deserialize(await response.Content.ReadAsStreamAsync(), typeof(LoginResponse), new JsonSerializerOptions() | |||
| { | |||
| PropertyNamingPolicy = JsonNamingPolicy.CamelCase, | |||
| @@ -1087,7 +1183,7 @@ namespace WebConnect | |||
| default: | |||
| int code = ((int)response.StatusCode); | |||
| Console.WriteLine(code); | |||
| //Console.WriteLine(code); | |||
| if (code == 401) | |||
| { | |||
| //Console.WriteLine("邮箱或密码错误!"); | |||
| @@ -1120,7 +1216,7 @@ namespace WebConnect | |||
| try | |||
| { | |||
| string content; | |||
| client.DefaultRequestHeaders.Authorization = new("bearertoken", logintoken); | |||
| client.DefaultRequestHeaders.Authorization = new("Bearer", logintoken); | |||
| if (!File.Exists(tarfile)) | |||
| { | |||
| //Console.WriteLine("文件不存在!"); | |||
| @@ -1171,13 +1267,13 @@ namespace WebConnect | |||
| uploadTask.progressCallback = delegate (long completed, long total) | |||
| { | |||
| Console.WriteLine(string.Format("progress = {0:##.##}%", completed * 100.0 / total)); | |||
| //Console.WriteLine(string.Format("progress = {0:##.##}%", completed * 100.0 / total)); | |||
| }; | |||
| try | |||
| { | |||
| COSXMLUploadTask.UploadTaskResult result = await transferManager.UploadAsync(uploadTask); | |||
| Console.WriteLine(result.GetResultInfo()); | |||
| //Console.WriteLine(result.GetResultInfo()); | |||
| string eTag = result.eTag; | |||
| //到这里应该是成功了,但是因为我没有试过,也不知道具体情况,可能还要根据result的内容判断 | |||
| } | |||
| @@ -1290,6 +1386,125 @@ namespace WebConnect | |||
| Console.WriteLine("写入token.dat发生冲突!请检查token.dat是否被其它程序占用!"); | |||
| } | |||
| } | |||
| public static int WriteUserEmail(string email) | |||
| { | |||
| try | |||
| { | |||
| string savepath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||
| FileStream fs = new FileStream(savepath, FileMode.Open, FileAccess.ReadWrite); | |||
| StreamReader sr = new StreamReader(fs); | |||
| string json = sr.ReadToEnd(); | |||
| if (json == null || json == "") | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| if (!dict.ContainsKey("email")) | |||
| { | |||
| dict.Add("email", email); | |||
| } | |||
| else | |||
| { | |||
| dict["email"] = email; | |||
| } | |||
| sr.Close(); | |||
| fs.Close(); | |||
| FileStream fs2 = new FileStream(savepath, FileMode.Open, FileAccess.ReadWrite); | |||
| StreamWriter sw = new StreamWriter(fs2); | |||
| sw.WriteLine(JsonConvert.SerializeObject(dict)); | |||
| sw.Close(); | |||
| fs2.Close(); | |||
| return 0;//成功 | |||
| } | |||
| catch | |||
| { | |||
| return -1;//失败 | |||
| } | |||
| } | |||
| public static int WriteUserPassword(string password) | |||
| { | |||
| try | |||
| { | |||
| string savepath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||
| FileStream fs = new FileStream(savepath, FileMode.Open, FileAccess.ReadWrite); | |||
| StreamReader sr = new StreamReader(fs); | |||
| string json = sr.ReadToEnd(); | |||
| if (json == null || json == "") | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| if (!dict.ContainsKey("password")) | |||
| { | |||
| dict.Add("password", password); | |||
| } | |||
| else | |||
| { | |||
| dict["password"] = password; | |||
| } | |||
| sr.Close(); | |||
| fs.Close(); | |||
| FileStream fs2 = new FileStream(savepath, FileMode.Open, FileAccess.ReadWrite); | |||
| StreamWriter sw = new StreamWriter(fs2); | |||
| sw.WriteLine(JsonConvert.SerializeObject(dict)); | |||
| sw.Close(); | |||
| fs2.Close(); | |||
| return 0;//成功 | |||
| } | |||
| catch | |||
| { | |||
| return -1;//失败,THUAI6.json 文件不存在或者已被占用 | |||
| } | |||
| } | |||
| public static string ReadUserPassword() | |||
| { | |||
| try | |||
| { | |||
| string savepath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||
| FileStream fs = new FileStream(savepath, FileMode.Open, FileAccess.Read); | |||
| StreamReader sr = new StreamReader(fs); | |||
| string json = sr.ReadToEnd(); | |||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||
| if (json == null || json == "") | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| return dict["password"]; | |||
| } | |||
| catch | |||
| { | |||
| return null; //文件不存在或者已被占用 | |||
| } | |||
| } | |||
| public static string ReadUserEmail() | |||
| { | |||
| try | |||
| { | |||
| string savepath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||
| FileStream fs = new FileStream(savepath, FileMode.Open, FileAccess.Read); | |||
| StreamReader sr = new StreamReader(fs); | |||
| string json = sr.ReadToEnd(); | |||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||
| if (json == null || json == "") | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| return dict["email"]; | |||
| } | |||
| catch | |||
| { | |||
| return null; | |||
| } | |||
| } | |||
| public bool ReadToken() // 读取token | |||
| { | |||
| try | |||
| @@ -7,11 +7,20 @@ using Downloader; | |||
| using MessageBox = System.Windows.MessageBox; | |||
| using System.Configuration; | |||
| using System.Drawing.Design; | |||
| using Application = System.Windows.Application; | |||
| using System.ComponentModel; | |||
| using Installer; | |||
| using static System.Windows.Forms.VisualStyles.VisualStyleElement; | |||
| using System.IO; | |||
| using System.Windows.Automation.Provider; | |||
| namespace starter.viewmodel.settings | |||
| { | |||
| public class SettingsViewModel : NotificationObject | |||
| { | |||
| //定义BackgroundWorker | |||
| BackgroundWorker asyncDownloader; | |||
| BackgroundWorker asyncUpdater; | |||
| /// <summary> | |||
| /// Model object | |||
| /// </summary> | |||
| @@ -19,18 +28,43 @@ namespace starter.viewmodel.settings | |||
| /// <summary> | |||
| /// initializer | |||
| /// </summary> | |||
| public SettingsViewModel() | |||
| { | |||
| //Program.Tencent_cos_download.UpdateHash(); | |||
| //实例化BackgroundWorker | |||
| asyncDownloader = new BackgroundWorker(); | |||
| asyncUpdater = new BackgroundWorker(); | |||
| //指示BackgroundWorker是否可以报告进度更新 | |||
| //当该属性值为True是,将可以成功调用ReportProgress方法,否则将引发InvalidOperationException异常。 | |||
| asyncDownloader.WorkerReportsProgress = true; | |||
| asyncUpdater.WorkerReportsProgress = true; | |||
| //挂载方法: | |||
| asyncDownloader.DoWork += AsyncDownloader_DoWork; | |||
| asyncUpdater.DoWork += AsyncUpdater_DoWork; | |||
| //完成通知器: | |||
| asyncDownloader.RunWorkerCompleted += AsyncDownloader_RunWorkerCompleted; | |||
| asyncUpdater.RunWorkerCompleted += AsyncUpdater_RunWorkerCompleted; | |||
| UpdateInfoVis = Visibility.Collapsed; | |||
| if (Downloader.Program.Tencent_cos_download.CheckAlreadyDownload()) | |||
| { | |||
| obj.checkUpdate(); | |||
| Status = SettingsModel.Status.login; | |||
| this.RaisePropertyChanged("WindowWidth"); | |||
| //TODO:在启动时立刻检查更新,确保选手启动最新版选手包 | |||
| //TODO:若有更新,将启动键改为更新键; | |||
| //TODO:相应地,使用login界面启动; | |||
| //TODO:结构:上方为登录框架,下方有“修改选手包”按钮 | |||
| this.RaisePropertyChanged("LaunchVis"); | |||
| if (obj.RecallUser()) | |||
| RememberMe = true; | |||
| else | |||
| RememberMe = false; | |||
| this.RaisePropertyChanged("RememberMe"); | |||
| //在启动时立刻检查更新,确保选手启动最新版选手包 | |||
| //若有更新,将启动键改为更新键; | |||
| //相应地,使用login界面启动; | |||
| //结构:上方为登录框架,下方有“修改选手包”按钮 | |||
| } | |||
| else | |||
| { | |||
| @@ -40,6 +74,85 @@ namespace starter.viewmodel.settings | |||
| } | |||
| } | |||
| private void AsyncDownloader_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e) | |||
| { | |||
| if (e.Result == null) | |||
| { | |||
| Status = SettingsModel.Status.error; | |||
| } | |||
| else if ((bool)e.Result) | |||
| { | |||
| Status = SettingsModel.Status.successful; | |||
| } | |||
| else | |||
| { | |||
| Status = SettingsModel.Status.newUser; | |||
| } | |||
| } | |||
| private void AsyncUpdater_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e) | |||
| { | |||
| if (e.Result == null) | |||
| { | |||
| Status = SettingsModel.Status.error; | |||
| } | |||
| else | |||
| { | |||
| this.RaisePropertyChanged("LaunchVis"); | |||
| if ((int)e.Result == 1) | |||
| { | |||
| Status = SettingsModel.Status.successful; | |||
| this.RaisePropertyChanged("UpdateBtnCont"); | |||
| this.RaisePropertyChanged("UpdateInfo"); | |||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||
| } | |||
| else if ((int)e.Result == 2) | |||
| { | |||
| Status = SettingsModel.Status.login; | |||
| this.RaisePropertyChanged("UpdateBtnCont"); | |||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||
| this.RaisePropertyChanged("UpdateInfo"); | |||
| } | |||
| } | |||
| } | |||
| private void AsyncUpdater_DoWork(object? sender, DoWorkEventArgs e) | |||
| { | |||
| if (asyncUpdater.CancellationPending) | |||
| { | |||
| e.Cancel = true; | |||
| return; | |||
| } | |||
| else | |||
| { | |||
| if (obj.Update()) | |||
| if (e.Argument.ToString().Equals("Manual")) | |||
| { | |||
| e.Result = 1; | |||
| } | |||
| else | |||
| e.Result = 2; | |||
| else | |||
| e.Result = -1; | |||
| } | |||
| } | |||
| private void AsyncDownloader_DoWork(object? sender, DoWorkEventArgs e) | |||
| { | |||
| if (asyncDownloader.CancellationPending) | |||
| { | |||
| e.Cancel = true; | |||
| return; | |||
| } | |||
| else | |||
| { | |||
| if (obj.install()) | |||
| e.Result = true; | |||
| else | |||
| e.Result = false; | |||
| } | |||
| } | |||
| //TODO:参赛界面:包括上传参赛代码、申请对战 | |||
| //TODO:界面中应包含上次对战完成提示及下载回放按钮 | |||
| @@ -93,6 +206,10 @@ namespace starter.viewmodel.settings | |||
| this.RaisePropertyChanged("CompleteVis"); | |||
| this.RaisePropertyChanged("WindowWidth"); | |||
| this.RaisePropertyChanged("WebVis"); | |||
| this.RaisePropertyChanged("CoverVis"); | |||
| this.RaisePropertyChanged("LaunchVis"); | |||
| this.RaisePropertyChanged("NewUserVis"); | |||
| this.RaisePropertyChanged("ConfirmBtnCont"); | |||
| } | |||
| } | |||
| public string Intro | |||
| @@ -126,14 +243,28 @@ namespace starter.viewmodel.settings | |||
| { | |||
| case SettingsModel.Status.newUser: | |||
| return "将主体程序安装在:"; | |||
| return "将选手包安装在(将创建THUAI6文件夹):"; | |||
| case SettingsModel.Status.move: | |||
| return "将主体程序移动到:"; | |||
| return "将选手包移动到(THUAI6文件夹将会被整体移动):"; | |||
| default: | |||
| return ""; | |||
| } | |||
| } | |||
| } | |||
| public string AbortOrSelLanguage | |||
| { | |||
| get | |||
| { | |||
| string ans = ""; | |||
| if (obj.UploadReady) | |||
| ans = "放弃上传"; | |||
| else if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||
| ans = "语言:c++"; | |||
| else if (obj.launchLanguage == SettingsModel.LaunchLanguage.python) | |||
| ans = "语言:python"; | |||
| return ans; | |||
| } | |||
| } | |||
| public int PlayerNum | |||
| { | |||
| get | |||
| @@ -177,7 +308,10 @@ namespace starter.viewmodel.settings | |||
| public string Route | |||
| { | |||
| get => obj.Route; | |||
| get | |||
| { | |||
| return obj.Route; | |||
| } | |||
| set | |||
| { | |||
| obj.Route = value; | |||
| @@ -209,6 +343,27 @@ namespace starter.viewmodel.settings | |||
| return obj.CodeRoute.Substring(obj.CodeRoute.LastIndexOf('/') == -1 ? obj.CodeRoute.LastIndexOf('\\') + 1 : obj.CodeRoute.LastIndexOf('/') + 1); | |||
| } | |||
| } | |||
| public bool RememberMe | |||
| { | |||
| get | |||
| { | |||
| return obj.RememberMe; | |||
| } | |||
| set | |||
| { | |||
| obj.RememberMe = value; | |||
| this.RaisePropertyChanged("RememberMe"); | |||
| } | |||
| } | |||
| public Visibility NewUserVis | |||
| { | |||
| get | |||
| { | |||
| return Status == SettingsModel.Status.newUser ? Visibility.Visible : Visibility.Collapsed; | |||
| } | |||
| } | |||
| public Visibility MenuVis | |||
| { | |||
| get | |||
| @@ -277,6 +432,19 @@ namespace starter.viewmodel.settings | |||
| get { return obj.UploadReady ? Visibility.Visible : Visibility.Collapsed; } | |||
| } | |||
| public Visibility UpdateInfoVis | |||
| { | |||
| get; set; | |||
| } | |||
| public Visibility LaunchVis | |||
| { | |||
| get | |||
| { | |||
| return obj.status == SettingsModel.Status.login && (!obj.UpdatePlanned) ? Visibility.Visible : Visibility.Collapsed; | |||
| } | |||
| } | |||
| public string UpdateBtnCont | |||
| { | |||
| get | |||
| @@ -291,14 +459,21 @@ namespace starter.viewmodel.settings | |||
| if (obj.UpdatePlanned) | |||
| return obj.Updates; | |||
| else | |||
| return ""; | |||
| return "已是最新版本"; | |||
| } | |||
| } | |||
| public string LaunchBtnCont | |||
| { | |||
| get | |||
| { | |||
| return obj.UpdatePlanned ? "更新" : "启动"; | |||
| string ans; | |||
| if (obj.UpdatePlanned) | |||
| ans = "更新"; | |||
| else if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||
| ans = "启动c++包"; | |||
| else | |||
| ans = "启动python包"; | |||
| return ans; | |||
| } | |||
| } | |||
| public string UploadBtnCont | |||
| @@ -308,6 +483,28 @@ namespace starter.viewmodel.settings | |||
| return obj.UploadReady ? "上传代码" : "选择代码上传"; | |||
| } | |||
| } | |||
| public string ShiftLanguageBtnCont | |||
| { | |||
| get | |||
| { | |||
| return obj.launchLanguage == SettingsModel.LaunchLanguage.cpp ? "改为python" : "改为c++"; | |||
| } | |||
| } | |||
| public string ConfirmBtnCont | |||
| { | |||
| get | |||
| { | |||
| switch (Status) | |||
| { | |||
| case SettingsModel.Status.newUser: | |||
| return "确认并安装"; | |||
| case SettingsModel.Status.move: | |||
| return "确认并移动"; | |||
| default: | |||
| return ""; | |||
| } | |||
| } | |||
| } | |||
| public string RouteSelectWindow(string type) | |||
| { | |||
| @@ -322,10 +519,18 @@ namespace starter.viewmodel.settings | |||
| } | |||
| else if (type == "File") | |||
| { | |||
| var openFileDialog = new Microsoft.Win32.OpenFileDialog() | |||
| var openFileDialog = new Microsoft.Win32.OpenFileDialog(); | |||
| if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||
| { | |||
| Filter = "c++ Source Files (.cpp)|*.cpp|c++ Header File (.h)|*.h|python Source File (.py)|*.py" | |||
| }; | |||
| openFileDialog.InitialDirectory = (Route + "/THUAI6/win/CAPI/cpp/API/src/").Replace("/", "\\"); | |||
| openFileDialog.Filter = "c++ Source Files (.cpp)|*.cpp|c++ Header File (.h)|*.h|python Source File (.py)|*.py"; | |||
| } | |||
| else if (obj.launchLanguage == SettingsModel.LaunchLanguage.python) | |||
| { | |||
| openFileDialog.InitialDirectory = (Route + "/THUAI6/win/CAPI/python/PyAPI/").Replace("/", "\\"); | |||
| openFileDialog.Filter = "python Source File (.py)|*.py|c++ Source Files (.cpp)|*.cpp|c++ Header File (.h)|*.h"; | |||
| } | |||
| var result = openFileDialog.ShowDialog(); | |||
| if (result == true) | |||
| { | |||
| @@ -363,16 +568,22 @@ namespace starter.viewmodel.settings | |||
| { | |||
| Status = SettingsModel.Status.working; | |||
| this.RaisePropertyChanged("ProgressVis"); | |||
| if (obj.install()) | |||
| /*if (obj.install()) | |||
| { | |||
| Status = SettingsModel.Status.successful; | |||
| }*/ | |||
| if (asyncDownloader.IsBusy) | |||
| return; | |||
| else | |||
| { | |||
| asyncDownloader.RunWorkerAsync(); | |||
| } | |||
| } | |||
| else if (Status == SettingsModel.Status.move) | |||
| { | |||
| Status = SettingsModel.Status.working; | |||
| this.RaisePropertyChanged("ProgressVis"); | |||
| //Status = SettingsModel.Status.working; | |||
| //this.RaisePropertyChanged("ProgressVis"); | |||
| switch (obj.move()) | |||
| { | |||
| case -1: | |||
| @@ -398,11 +609,14 @@ namespace starter.viewmodel.settings | |||
| { | |||
| clickUpdateCommand = new BaseCommand(new Action<object>(o => | |||
| { | |||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||
| if (obj.UpdatePlanned) | |||
| { | |||
| UpdateInfoVis = Visibility.Collapsed; | |||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||
| Status = SettingsModel.Status.working; | |||
| this.RaisePropertyChanged("ProgressVis"); | |||
| if (obj.Update()) | |||
| /*if (obj.Update()) | |||
| { | |||
| Status = SettingsModel.Status.successful; | |||
| @@ -411,15 +625,22 @@ namespace starter.viewmodel.settings | |||
| } | |||
| else | |||
| Status = SettingsModel.Status.error; | |||
| Status = SettingsModel.Status.error;*/ | |||
| if (asyncUpdater.IsBusy) | |||
| return; | |||
| else | |||
| asyncUpdater.RunWorkerAsync("Manual"); | |||
| } | |||
| else | |||
| { | |||
| UpdateInfoVis = Visibility.Visible; | |||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||
| Status = SettingsModel.Status.working; | |||
| this.RaisePropertyChanged("ProgressVis"); | |||
| Status = obj.checkUpdate(); | |||
| this.RaisePropertyChanged("UpdateBtnCont"); | |||
| this.RaisePropertyChanged("UpdateInfo"); | |||
| this.RaisePropertyChanged("LaunchVis"); | |||
| } | |||
| })); | |||
| } | |||
| @@ -450,8 +671,8 @@ namespace starter.viewmodel.settings | |||
| { | |||
| clickUninstCommand = new BaseCommand(new Action<object>(o => | |||
| { | |||
| Status = SettingsModel.Status.working; | |||
| this.RaisePropertyChanged("ProgressVis"); | |||
| UpdateInfoVis = Visibility.Collapsed; | |||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||
| switch (obj.Uninst()) | |||
| { | |||
| case -1: | |||
| @@ -459,13 +680,15 @@ namespace starter.viewmodel.settings | |||
| MessageBox.Show("文件已经打开,请关闭后再删除", "", MessageBoxButton.OK, MessageBoxImage.Warning, MessageBoxResult.OK); | |||
| break; | |||
| case 0: | |||
| Status = SettingsModel.Status.successful; | |||
| Status = SettingsModel.Status.newUser; | |||
| MessageBox.Show($"删除成功!player文件夹中的文件已经放在{Downloader.Program.Data.FilePath}/{Downloader.Program.ProgramName}的根目录下", "", MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK); | |||
| break; | |||
| default: | |||
| Status = SettingsModel.Status.error; | |||
| break; | |||
| } | |||
| })); | |||
| } | |||
| return clickUninstCommand; | |||
| @@ -489,6 +712,22 @@ namespace starter.viewmodel.settings | |||
| case 0: | |||
| obj.LoginFailed = false; | |||
| Status = SettingsModel.Status.web; | |||
| if (obj.RememberMe) | |||
| { | |||
| obj.RememberUser(); | |||
| RememberMe = true; | |||
| this.RaisePropertyChanged("RememberMe"); | |||
| } | |||
| else | |||
| { | |||
| obj.ForgetUser(); | |||
| RememberMe = false; | |||
| this.RaisePropertyChanged("RememberMe"); | |||
| Username = ""; | |||
| Password = ""; | |||
| this.RaisePropertyChanged("Username"); | |||
| this.RaisePropertyChanged("Password"); | |||
| } | |||
| this.RaisePropertyChanged("CoverVis"); | |||
| break; | |||
| case -2: | |||
| @@ -515,15 +754,10 @@ namespace starter.viewmodel.settings | |||
| { | |||
| Status = SettingsModel.Status.working; | |||
| this.RaisePropertyChanged("ProgressVis"); | |||
| if (obj.Update()) | |||
| { | |||
| this.RaisePropertyChanged("UpdateBtnCont"); | |||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||
| Status = SettingsModel.Status.login; | |||
| this.RaisePropertyChanged("UpdateInfo"); | |||
| } | |||
| if (asyncUpdater.IsBusy) | |||
| return; | |||
| else | |||
| Status = SettingsModel.Status.error; | |||
| asyncUpdater.RunWorkerAsync("Auto"); | |||
| } | |||
| else if (!obj.Launch()) | |||
| { | |||
| @@ -544,6 +778,9 @@ namespace starter.viewmodel.settings | |||
| clickEditCommand = new BaseCommand(new Action<object>(o => | |||
| { | |||
| Status = SettingsModel.Status.menu; | |||
| if (obj.UpdatePlanned) | |||
| UpdateInfoVis = Visibility.Visible; | |||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||
| })); | |||
| } | |||
| return clickEditCommand; | |||
| @@ -558,7 +795,12 @@ namespace starter.viewmodel.settings | |||
| { | |||
| clickBackCommand = new BaseCommand(new Action<object>(o => | |||
| { | |||
| Status = SettingsModel.Status.login; | |||
| UpdateInfoVis = Visibility.Collapsed; | |||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||
| if (Downloader.Program.Tencent_cos_download.CheckAlreadyDownload()) | |||
| Status = SettingsModel.Status.login; | |||
| else | |||
| Status = SettingsModel.Status.newUser; | |||
| })); | |||
| } | |||
| return clickBackCommand; | |||
| @@ -614,6 +856,7 @@ namespace starter.viewmodel.settings | |||
| this.RaisePropertyChanged("UploadBtnCont"); | |||
| this.RaisePropertyChanged("UploadReadyVis"); | |||
| this.RaisePropertyChanged("CoverVis"); | |||
| this.RaisePropertyChanged("AbortOrSelLanguage"); | |||
| } | |||
| } | |||
| else | |||
| @@ -626,6 +869,7 @@ namespace starter.viewmodel.settings | |||
| this.RaisePropertyChanged("UploadReadyVis"); | |||
| this.RaisePropertyChanged("CodeName"); | |||
| this.RaisePropertyChanged("CoverVis"); | |||
| this.RaisePropertyChanged("AbortOrSelLanguage"); | |||
| } | |||
| else | |||
| { | |||
| @@ -637,24 +881,93 @@ namespace starter.viewmodel.settings | |||
| return clickUploadCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickReselectCommand; | |||
| public BaseCommand ClickReselectCommand | |||
| private BaseCommand clickAboutUploadCommand; | |||
| public BaseCommand ClickAboutUploadCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickAboutUploadCommand == null) | |||
| { | |||
| clickAboutUploadCommand = new BaseCommand(new Action<object>(o => | |||
| { | |||
| if (obj.UploadReady) | |||
| { | |||
| obj.CodeRoute = ""; | |||
| obj.UploadReady = false; | |||
| this.RaisePropertyChanged("UploadBtnCont"); | |||
| this.RaisePropertyChanged("UploadReadyVis"); | |||
| this.RaisePropertyChanged("CodeName"); | |||
| this.RaisePropertyChanged("CoverVis"); | |||
| this.RaisePropertyChanged("AbortOrSelLanguage"); | |||
| } | |||
| else | |||
| { | |||
| if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||
| obj.launchLanguage = SettingsModel.LaunchLanguage.python; | |||
| else | |||
| obj.launchLanguage = SettingsModel.LaunchLanguage.cpp; | |||
| this.RaisePropertyChanged("AbortOrSelLanguage"); | |||
| this.RaisePropertyChanged("ShiftLanguageBtnCont"); | |||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||
| } | |||
| })); | |||
| } | |||
| return clickAboutUploadCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickExitCommand; | |||
| public BaseCommand ClickExitCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickExitCommand == null) | |||
| { | |||
| clickExitCommand = new BaseCommand(new Action<object>(o => | |||
| { | |||
| Application.Current.Shutdown(); | |||
| })); | |||
| } | |||
| return clickExitCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickShiftLanguageCommand; | |||
| public BaseCommand ClickShiftLanguageCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickShiftLanguageCommand == null) | |||
| { | |||
| clickShiftLanguageCommand = new BaseCommand(new Action<object>(o => | |||
| { | |||
| if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||
| obj.launchLanguage = SettingsModel.LaunchLanguage.python; | |||
| else | |||
| obj.launchLanguage = SettingsModel.LaunchLanguage.cpp; | |||
| this.RaisePropertyChanged("ShiftLanguageBtnCont"); | |||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||
| this.RaisePropertyChanged("AbortOrSelLanguage"); | |||
| })); | |||
| } | |||
| return clickShiftLanguageCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickReadCommand; | |||
| public BaseCommand ClickReadCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickReselectCommand == null) | |||
| if (clickReadCommand == null) | |||
| { | |||
| clickReselectCommand = new BaseCommand(new Action<object>(o => | |||
| clickReadCommand = new BaseCommand(new Action<object>(o => | |||
| { | |||
| obj.CodeRoute = ""; | |||
| obj.UploadReady = false; | |||
| this.RaisePropertyChanged("UploadBtnCont"); | |||
| this.RaisePropertyChanged("UploadReadyVis"); | |||
| this.RaisePropertyChanged("CodeName"); | |||
| this.RaisePropertyChanged("CoverVis"); | |||
| if (!Directory.Exists(Route + "/THUAI6")) | |||
| Route = Route.Substring(0, Route.Length - 7); | |||
| Program.Data.ResetFilepath(Route); | |||
| if (Program.Tencent_cos_download.CheckAlreadyDownload()) | |||
| Status = SettingsModel.Status.login; | |||
| })); | |||
| } | |||
| return clickReselectCommand; | |||
| return clickReadCommand; | |||
| } | |||
| } | |||
| } | |||
| @@ -12,7 +12,7 @@ namespace Client | |||
| public class ArgumentOptions | |||
| { | |||
| [Option('u', "cl", Required = false, HelpText = "Whether to use command line")] | |||
| public bool cl { get; set; } = false; | |||
| public bool cl { get; set; } = true; | |||
| [Option('i', "ip", Required = false, HelpText = "Client connected ip")] | |||
| public string Ip { get; set; } = "127.0.0.1"; | |||
| @@ -108,8 +108,8 @@ namespace Client | |||
| return; | |||
| } | |||
| _ = Parser.Default.ParseArguments<ArgumentOptions>(args).WithParsed(o => | |||
| { options = o; }); | |||
| if ((args.Length == 3 || args.Length == 4) && options != null && Convert.ToInt64(options.PlayerID) > 2023) | |||
| { options = o; }); | |||
| if (options != null && Convert.ToInt64(options.PlayerID) > 2023) | |||
| { | |||
| isSpectatorMode = true; | |||
| string[] comInfo = new string[3]; | |||
| @@ -158,6 +158,7 @@ namespace Client | |||
| if ((map = pbClient.ReadDataFromFile(listOfProp, listOfHuman, listOfButcher, listOfBullet, listOfBombedBullet, listOfAll, listOfChest, listOfClassroom, listOfDoor, listOfHiddenGate, listOfGate, drawPicLock)) != null) | |||
| { | |||
| isClientStocked = false; | |||
| PorC.Content = "⏸"; | |||
| isPlaybackMode = true; | |||
| defaultMap = map; | |||
| mapFlag = true; | |||
| @@ -166,6 +167,7 @@ namespace Client | |||
| { | |||
| MessageBox.Show("Failed to read the playback file!"); | |||
| isClientStocked = true; | |||
| PorC.Content = "▶"; | |||
| } | |||
| } | |||
| @@ -569,6 +571,8 @@ namespace Client | |||
| { | |||
| if (msg.Place == human.Place) | |||
| return true; | |||
| if (msg.PlayerId == playerID + Preparation.Utility.GameData.numOfPeople)//robot and its owner | |||
| return true; | |||
| } | |||
| else if (!humanOrButcher && butcher != null) | |||
| { | |||
| @@ -743,7 +747,7 @@ namespace Client | |||
| foreach (var data in listOfAll) | |||
| { | |||
| StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID); | |||
| StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode); | |||
| } | |||
| if (!hasDrawed && mapFlag) | |||
| DrawMap(); | |||
| @@ -1028,6 +1032,7 @@ namespace Client | |||
| BorderBrush = Brushes.Transparent, | |||
| IsReadOnly = true | |||
| }; | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| } | |||
| } | |||
| //} | |||
| @@ -1265,7 +1270,7 @@ namespace Client | |||
| isClientStocked = true; | |||
| PorC.Content = "▶"; | |||
| } | |||
| else if (!isPlaybackMode) | |||
| else | |||
| { | |||
| try | |||
| { | |||
| @@ -2,7 +2,7 @@ | |||
| "profiles": { | |||
| "Client": { | |||
| "commandName": "Project", | |||
| "commandLineArgs": "--cl --playbackFile .\\video.thuaipb" | |||
| "commandLineArgs": "--port 8888 --characterID 3 --type 1 --occupation 5" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -41,7 +41,7 @@ namespace Client | |||
| } | |||
| } | |||
| public void SetValue(MessageOfAll obj, bool gateOpened, bool hiddenGateRefreshed, bool hiddenGateOpened, long playerId) | |||
| public void SetValue(MessageOfAll obj, bool gateOpened, bool hiddenGateRefreshed, bool hiddenGateOpened, long playerId, bool isPlaybackMode) | |||
| { | |||
| int min, sec; | |||
| sec = obj.GameTime / 1000; | |||
| @@ -57,17 +57,22 @@ namespace Client | |||
| { | |||
| time.Text += Convert.ToString(sec); | |||
| } | |||
| if (playerId == GameData.numOfStudent) | |||
| { | |||
| name.Text = "🚀 Tricker's"; | |||
| } | |||
| else if (playerId < GameData.numOfStudent) | |||
| { | |||
| name.Text = "🚀 Student" + Convert.ToString(playerId) + "'s"; | |||
| } | |||
| if (isPlaybackMode) | |||
| name.Text = "🚀 Playback"; | |||
| else | |||
| { | |||
| name.Text = "🚀 Spectator's"; | |||
| if (playerId == GameData.numOfStudent) | |||
| { | |||
| name.Text = "🚀 Tricker's"; | |||
| } | |||
| else if (playerId < GameData.numOfStudent) | |||
| { | |||
| name.Text = "🚀 Student" + Convert.ToString(playerId) + "'s"; | |||
| } | |||
| else | |||
| { | |||
| name.Text = "🚀 Spectator's"; | |||
| } | |||
| } | |||
| if (obj.SubjectFinished < Preparation.Utility.GameData.numOfGeneratorRequiredForRepair) | |||
| { | |||
| @@ -141,6 +141,12 @@ namespace GameClass.GameObj | |||
| } | |||
| } | |||
| } | |||
| public override void AddScore(int add) | |||
| { | |||
| if (parent == null) | |||
| base.AddScore(add); | |||
| else parent.AddScore(add); | |||
| } | |||
| public Golem(XY initPos, int initRadius, Character? parent) : base(initPos, initRadius, CharacterType.Robot) | |||
| { | |||
| this.parent = parent; | |||
| @@ -349,7 +349,7 @@ namespace GameClass.GameObj | |||
| /// 加分 | |||
| /// </summary> | |||
| /// <param name="add">增加量</param> | |||
| public void AddScore(int add) | |||
| public virtual void AddScore(int add) | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| @@ -357,18 +357,6 @@ namespace GameClass.GameObj | |||
| //Debugger.Output(this, " 's score has been added to: " + score.ToString()); | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 减分 | |||
| /// </summary> | |||
| /// <param name="sub">减少量</param> | |||
| public void SubScore(int sub) | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| score -= sub; | |||
| Debugger.Output(this, " 's score has been subed to: " + score.ToString()); | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 角色所属队伍ID | |||
| @@ -584,7 +572,7 @@ namespace GameClass.GameObj | |||
| this.Vampire = this.OriVampire; | |||
| } | |||
| }*/ | |||
| public void Die(PlayerStateType playerStateType) | |||
| public void RemoveFromGame(PlayerStateType playerStateType) | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| @@ -1,5 +1,5 @@ | |||
| # 规则 | |||
| V4.4 | |||
| V5.0 | |||
| - [规则](#规则) | |||
| - [简则](#简则) | |||
| - [地图](#地图) | |||
| @@ -44,38 +44,48 @@ V4.4 | |||
| - [箱子](#箱子-1) | |||
| - [得分](#得分-1) | |||
| - [信息相关](#信息相关-1) | |||
| - [技能](#技能) | |||
| - [职业](#职业) | |||
| ## 简则 | |||
| - 每场比赛分为上下两个半场,上下半场双方换边 | |||
| - 单局比赛分为学生阵营(4 人)和捣蛋鬼阵营(1 人) | |||
| - 最终将两场比赛己方所得分数相加,高者获胜 | |||
| ### 地图 | |||
| - 地图为矩形区域,游戏对象坐标为(x, y),x和y均为整数。 | |||
| - **x坐标轴正方向竖直向下,y坐标轴正方向水平向右**; | |||
| - **极坐标以x坐标轴为极轴,角度逆时针为正方向**。 | |||
| - 地图由50 * 50个格子构成,其中每个格子代表1000 * 1000的正方形。每个格子的编号(CellX,CellY)可以计算得到: | |||
| - $$CellX=\frac{x}{1000},CellY=\frac{y}{1000}$$ | |||
| - 地图由50 * 50个格子构成,每个格子代表1000 * 1000的正方形。每个格子的编号(CellX,CellY)可以计算得到: | |||
| $$ | |||
| CellX=\frac{x}{1000},CellY=\frac{y}{1000} | |||
| $$ | |||
| - 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子 | |||
| ### 人物 | |||
| - 人物半径为800 | |||
| - 人物直径为800 | |||
| - 人物共有17种不可叠加的状态: | |||
| 1. (可)移动状态 | |||
| 2. 学习 | |||
| 3. 被勉励 | |||
| 4. 在勉励 | |||
| 5. 开或锁门 | |||
| 6. 翻箱 | |||
| 7. 使用技能 | |||
| 8. 开启校门 | |||
| 9. 唤醒他人中 | |||
| - 之后八项为不可行动状态 | |||
| 1. 被唤醒中 | |||
| 2. 沉迷 | |||
| 3. 退学 | |||
| 4. 毕业 | |||
| 5. 被眩晕 | |||
| 6. 前摇 | |||
| 7. 后摇 | |||
| 8. 翻窗 | |||
| 1. (可)移动状态 Idel | |||
| 2. 学习 Learning | |||
| 3. 被勉励 Encouraged | |||
| 4. 在勉励 Encouraging | |||
| 5. 开或锁门 Locking | |||
| 6. 翻箱 Rummaging | |||
| 7. 使用技能 UsingSpecialSkill | |||
| 8. 开启校门 OpeningAGate | |||
| 9. 唤醒他人中 Rousing | |||
| - 之后八项为不可接受指令状态 | |||
| 1. 被唤醒中 Roused | |||
| 2. 沉迷 Addicted | |||
| 3. 退学 Quit | |||
| 4. 毕业 Graduated | |||
| 5. 被眩晕 Stunned | |||
| 6. 前摇 Attacking | |||
| 7. 后摇 Swinging | |||
| 8. 翻窗 Climbing | |||
| ### 攻击 | |||
| - 攻击类型CommonAttackOfGhost攻击未写完的作业,会造成对应攻击力的损坏 | |||
| @@ -83,10 +93,10 @@ V4.4 | |||
| | 攻击(子弹)类型 |搞蛋鬼的一般攻击CommonAttackOfGhost| 飞刀FlyingKnife | 蹦蹦炸弹BombBomb | 小炸弹JumpyDumpty | | |||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | |||
| | 子弹爆炸范围 | 0 | 0 | 1000 | 500 | | |||
| | 子弹攻击距离 | 1100 | 39000 | 1100 | 2200 | | |||
| | 攻击力 | 1500000 | 1200000 | 1,800,000 | 900000 | | |||
| | 移动速度/s | 3700 | 9250 | 3000 | 4300 | | |||
| | 子弹爆炸范围 | 0 | 0 | 2000 | 1000 | | |||
| | 子弹攻击距离 | 2200 | 78000 | 2200 | 4400 | | |||
| | 攻击力 | 1500000 | 1200000 | 1800000 | 900000 | | |||
| | 移动速度/s | 7400 | 18500 | 6000 | 8600 | | |||
| | 前摇(ms) | 297 | 400 | 366 | - | | |||
| |未攻击至目标时的后摇(ms)| 800 | 0 | 3700 | - | | |||
| |攻击至目标时的后摇(ms)| 3700 | 0 | 3700 | - | | |||
| @@ -99,18 +109,18 @@ V4.4 | |||
| - 作业,门,箱子完成/开启进度达到10000000为完成 | |||
| #### 学习与毕业 | |||
| - 每张地图都有10间教室,学生需要完成其中的**7间**教室的作业,才可以开启任意校门。 | |||
| - 开启校门所需时间为18秒,开启的进度不清空 | |||
| - 当**3间**教室的作业完成时,隐藏校门在3-5个刷新点之一随机显现;当只剩1名学生时,隐藏校门自动打开。 | |||
| - 从开启的校门或隐藏校门离开是学生终极目标 | |||
| - 共有10间教室,学生需要完成**7间**教室的作业,才可以开启校门。 | |||
| - 开启校门需要18秒,开启进度不清空 | |||
| - **3间**教室的作业完成时,隐藏校门会在刷新点之一随机显现;当只剩1名学生时,隐藏校门自动打开。 | |||
| - 从开启的校门或隐藏校门毕业是学生终极目标 | |||
| #### 勉励 | |||
| - 当被勉励程度达到当前损失的毅力值或1500000时,勉励完成,学生毅力增加对应被勉励程度。 | |||
| - 勉励中断时,被勉励程度保留;遭到攻击时被勉励程度清空 | |||
| #### 沉迷与唤醒 | |||
| - 学习毅力归零时,学生原地进入沉迷状态,每毫秒增加1沉迷度 | |||
| - 唤醒需要时间1秒,之后学习毅力恢复至1/2。沉迷程度不清空。 | |||
| - 学习毅力归零时,学生进入沉迷状态,每毫秒增加1沉迷度 | |||
| - 唤醒需要1秒,之后学习毅力恢复至1/2。沉迷程度不清空。 | |||
| - 进入沉迷状态时,如果学生原沉迷程度在(0,该玩家最大沉迷度/3)中,沉迷程度直接变为其最大沉迷度/3;原沉迷程度在[其最大沉迷度/3,其最大沉迷度x2/3)中,沉迷程度直接变为其最大沉迷度x2/3;原沉迷程度大于其最大沉迷度x2/3,从游戏中出局; | |||
| - 当学生沉迷程度达到其最大沉迷程度时,从游戏中出局 | |||
| @@ -120,7 +130,7 @@ V4.4 | |||
| - 锁门过程中,门所在格子内有人会使锁门过程中断 | |||
| #### 窗 | |||
| - 翻窗时玩家应当在窗前后左右一个格子内 | |||
| - 翻窗时玩家应当在窗前后一个格子内 | |||
| #### 箱子 | |||
| - 开箱后将有2个随机道具掉落在玩家位置。 | |||
| @@ -130,7 +140,7 @@ V4.4 | |||
| 1. 不详的感觉(dangerAlert):如果捣蛋鬼进入(学生的警戒半径/捣蛋鬼的隐蔽度)的距离,则学生的dangerAlert=(int)(警戒半径/二者距离) | |||
| 2. 期待搞事的感觉(trickDesire):如果有学生进入(捣蛋鬼的警戒半径/学生的隐蔽度)的距离,则捣蛋鬼trickDesire=(int)(警戒半径/可被发觉的最近的学生距离) | |||
| 3. 学习的声音(classVolume): 警戒半径内有人学习时,捣蛋鬼classVolume=(int)((警戒半径x学习进度百分比)/二者距离) | |||
| - 可以向其他每一个队友发送不超过256字节的信息 | |||
| - 可以向每一个队友发送不超过256字节的信息 | |||
| ### 可视范围 | |||
| - 小于视野半径,且受限于墙和草地 | |||
| @@ -176,12 +186,12 @@ V4.4 | |||
| | 捣蛋鬼职业 | Assassin | Klee | 喧哗者ANoisyPerson | Idol | | |||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | |||
| | 移动速度/s | 1980 | 1800 | 1926 | 1,800 | | |||
| | 移动速度/s | 3960 | 3600 | 3852 | 3600 | | |||
| | 隐蔽度 | 1.5 | 1 | 0.8 | 0.75| | |||
| | 警戒范围 | 22,100 | 17000 | 15300 | 17000 | |||
| | 视野范围 | 15600 | 13000 | 13000 | 14300| | |||
| | 开锁门速度 | 4000 | 4000 | 4000 |4000 | | |||
| | 翻窗速度 | 1270 | 1270 | 1,397 | 1270| | |||
| | 翻窗速度 | 2540 | 2540 | 2794 | 2540| | |||
| | 翻箱速度 | 1000 | 1100 | 1000 |1000| | |||
| #### Assassin | |||
| @@ -230,7 +240,7 @@ V4.4 | |||
| | 学生职业 | 教师Teacher | 健身狂Athlete | 学霸StraightAStudent | 开心果Sunshine | | |||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | |||
| | 移动速度 | 1350 | 1575 | 1440 | 1500 | | |||
| | 移动速度 | 2700 | 3150 | 2880 | 3000 | | |||
| | 最大毅力值 | 30000000 | 3000000 | 3300000 | 3200000 | | |||
| | 最大沉迷度 | 600000 | 54,000 | 78,000 | 66,000 | | |||
| | 学习一科速度 | 0 | 73 | 135 | 123 | | |||
| @@ -239,7 +249,7 @@ V4.4 | |||
| | 警戒范围 | 7500 | 15000 | 13,500 | 15000 | | |||
| | 视野范围 | 9,000 | 11000 | 9,000 | 10000 | | |||
| | 开锁门速度 | 4000 | 4000 | 4000 | 2800 | | |||
| | 翻窗速度 | 635 | 1,524 | 1,058 | 1270 | | |||
| | 翻窗速度 | 1270 | 3048 | 2116 | 2540 | | |||
| | 翻箱速度 | 1000 | 1000 | 1000 | 900 | | |||
| #### 运动员 | |||
| @@ -262,13 +272,13 @@ V4.4 | |||
| #### 学霸 | |||
| - 特性 | |||
| - 冥想 | |||
| - 当玩家处于可接受指令状态且不在修机时,会积累学习进度,速度为40/ms | |||
| - 受到攻击(并非伤害)或学习或进入不可接受治疗状态(包括翻窗)学习进度清零 | |||
| - 当处于可接受指令状态且不在学习时,会积累“冥想进度”,速度为40/ms | |||
| - 受到攻击(并非伤害)、进入学习状态或进入不可接受指令状态(包括翻窗)冥想进度清零 | |||
| - 主动技能5 | |||
| - 写答案 WriteAnswers | |||
| - CD:30s | |||
| - 使用瞬间,对于可互动范围内的一台电机增加这个学习进度 | |||
| - 通过修机获得对应得分 | |||
| - 使用瞬间,对于可交互范围内的一间教室的作业增加冥想进度,冥想进度清零 | |||
| - 通过学习获得对应得分 | |||
| #### 开心果 | |||
| - 主动技能 | |||
| @@ -295,6 +305,7 @@ V4.4 | |||
| ### 人物 | |||
| - EndAllAction()及Move指令调用数总和一帧内不超过10次 | |||
| - 眩晕状态中的玩家不能再次被眩晕 | |||
| ### 初始状态 | |||
| - 玩家出生点固定且一定为空地 | |||
| @@ -346,4 +357,10 @@ V4.4 | |||
| - 眩晕或毅力值归零时无牵制得分 | |||
| ### 信息相关 | |||
| - Bgm在没有符合条件的情况下,值为0。 | |||
| - Bgm在没有符合条件的情况下,值为0。 | |||
| ### 技能 | |||
| - CD冷却计时是在开始使用技能的瞬间开始的 | |||
| ### 职业 | |||
| - 学生职业可以重复选择 | |||
| @@ -43,6 +43,14 @@ namespace Gaming | |||
| return true; | |||
| } | |||
| public bool MovePlayerWhenStunned(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) | |||
| { | |||
| if (!playerToMove.Commandable() && playerToMove.PlayerState != PlayerStateType.Stunned) return false; | |||
| characterManager.SetPlayerState(playerToMove, PlayerStateType.Stunned); | |||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); | |||
| return true; | |||
| } | |||
| public bool Stop(Character player) | |||
| { | |||
| if (player.Commandable() || !TryToStop()) | |||
| @@ -129,7 +137,7 @@ namespace Gaming | |||
| { | |||
| player.AddScore(GameData.StudentScoreEscape); | |||
| ++gameMap.NumOfEscapedStudent; | |||
| player.Die(PlayerStateType.Escaped); | |||
| player.RemoveFromGame(PlayerStateType.Escaped); | |||
| return true; | |||
| } | |||
| else | |||
| @@ -139,7 +147,7 @@ namespace Gaming | |||
| { | |||
| player.AddScore(GameData.StudentScoreEscape); | |||
| ++gameMap.NumOfEscapedStudent; | |||
| player.Die(PlayerStateType.Escaped); | |||
| player.RemoveFromGame(PlayerStateType.Escaped); | |||
| return true; | |||
| } | |||
| return false; | |||
| @@ -155,7 +155,7 @@ namespace Gaming | |||
| 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) | |||
| if (newPlayer.CharacterType != CharacterType.Teacher && newPlayer.CharacterType != CharacterType.Robot && !newPlayer.NoHp() && newPlayer.PlayerState != PlayerStateType.Stunned && XY.Distance(newPlayer.Position, person.Position) <= GameData.PinningDownRange) | |||
| { | |||
| TimePinningDown += GameData.checkInterval; | |||
| newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded); | |||
| @@ -378,12 +378,12 @@ namespace Gaming | |||
| return true; | |||
| } | |||
| private void Die(Character player) | |||
| public void Die(Student player) | |||
| { | |||
| #if DEBUG | |||
| Debugger.Output(player, "die."); | |||
| #endif | |||
| player.Die(PlayerStateType.Deceased); | |||
| player.RemoveFromGame(PlayerStateType.Deceased); | |||
| for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | |||
| { | |||
| @@ -49,8 +49,25 @@ namespace Gaming | |||
| { | |||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (!person.IsGhost()) | |||
| actionManager.MovePlayer(person, GameData.frameDuration, (player.Position - person.Position).Angle()); | |||
| if (!person.IsGhost() && player.CharacterType != CharacterType.Robot && !person.NoHp()) | |||
| { | |||
| double dis = XY.Distance(person.Position, player.Position); | |||
| if (dis >= player.AlertnessRadius) | |||
| { | |||
| person.AddMoveSpeed(GameData.checkIntervalWhenShowTime, dis / player.AlertnessRadius); | |||
| actionManager.MovePlayerWhenStunned(person, GameData.checkIntervalWhenShowTime, (player.Position - person.Position).Angle()); | |||
| } | |||
| else if (dis >= player.ViewRange) | |||
| { | |||
| Student student = (Student)person; | |||
| student.GamingAddiction += GameData.checkIntervalWhenShowTime; | |||
| if (student.GamingAddiction == student.MaxGamingAddiction) | |||
| { | |||
| player.AddScore(GameData.TrickerScoreStudentDie); | |||
| characterManager.Die(student); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| finally | |||
| @@ -58,7 +75,7 @@ namespace Gaming | |||
| gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||
| } | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| timeInterval: GameData.checkIntervalWhenShowTime, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| @@ -86,14 +103,15 @@ namespace Gaming | |||
| { }); | |||
| } | |||
| public static bool UseRobot(Character player) | |||
| public bool UseRobot(Character player) | |||
| { | |||
| IGolem? golem = (IGolem?)(((SummonGolem)player.FindIActiveSkill(ActiveSkillType.SummonGolem)).GolemSummoned); | |||
| Debugger.Output(player, (golem != null).ToString()); | |||
| if ((!player.Commandable()) || ((SummonGolem)player.FindIActiveSkill(ActiveSkillType.SummonGolem)).GolemSummoned == null) return false; | |||
| Debugger.Output(player, player.PlayerID.ToString()); | |||
| Debugger.Output(player, "use robot!"); | |||
| IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.UseRobot); | |||
| activeSkill.IsBeingUsed = (activeSkill.IsBeingUsed) ? false : true; | |||
| if (activeSkill.IsBeingUsed) characterManager.SetPlayerState(player, PlayerStateType.UsingSkill); | |||
| else characterManager.SetPlayerState(player); | |||
| return true; | |||
| } | |||
| @@ -167,7 +185,7 @@ namespace Gaming | |||
| { | |||
| foreach (Character character in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (!character.IsGhost() && XY.Distance(character.Position, player.Position) <= player.ViewRange) | |||
| if (!character.IsGhost() && !character.NoHp() && XY.Distance(character.Position, player.Position) <= player.ViewRange) | |||
| { | |||
| if (characterManager.BeStunned(character, GameData.timeOfStudentStunnedWhenHowl)) | |||
| player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStudentStunnedWhenHowl)); | |||
| @@ -123,7 +123,7 @@ namespace Preparation.Interface | |||
| public class ShowTime : IActiveSkill | |||
| { | |||
| public int SkillCD => GameData.commonSkillCD * 3; | |||
| public int SkillCD => GameData.commonSkillCD * 8 / 3; | |||
| public int DurationTime => GameData.commonSkillTime; | |||
| private readonly object commonSkillLock = new(); | |||
| @@ -93,8 +93,8 @@ namespace Preparation.Utility | |||
| public const int basicTreatSpeed = 100; | |||
| public const int basicFixSpeed = 123; | |||
| public const int basicSpeedOfOpeningOrLocking = 4000; | |||
| public const int basicStudentSpeedOfClimbingThroughWindows = 611; | |||
| public const int basicGhostSpeedOfClimbingThroughWindows = 1270; | |||
| public const int basicStudentSpeedOfClimbingThroughWindows = 1222; | |||
| public const int basicGhostSpeedOfClimbingThroughWindows = 2540; | |||
| public const int basicSpeedOfOpenChest = 1000; | |||
| public const int basicHp = 3000000; // 初始血量 | |||
| @@ -105,9 +105,9 @@ namespace Preparation.Utility | |||
| public const int basicTimeOfRescue = 1000; | |||
| #if DEBUG | |||
| public const int basicStudentMoveSpeed = 9000;// 基本移动速度,单位:s-1 | |||
| public const int basicStudentMoveSpeed = 3000;// 基本移动速度,单位:s-1 | |||
| #else | |||
| public const int basicStudentMoveSpeed = 1500; | |||
| public const int basicStudentMoveSpeed = 3000; | |||
| #endif | |||
| public const int basicGhostMoveSpeed = (int)(basicStudentMoveSpeed * 1.2); | |||
| @@ -200,10 +200,10 @@ namespace Preparation.Utility | |||
| public const int basicRecoveryFromHit = 3700;//基本命中攻击恢复时长 | |||
| public const int basicStunnedTimeOfStudent = 4300; | |||
| public const int basicBulletMoveSpeed = 3700; // 基本子弹移动速度,单位:s-1 | |||
| public const double basicRemoteAttackRange = 3000; // 基本远程攻击范围 | |||
| public const double basicAttackShortRange = 1100; // 基本近程攻击范围 | |||
| public const double basicBulletBombRange = 1000; // 基本子弹爆炸范围 | |||
| public const int basicBulletMoveSpeed = 7400; // 基本子弹移动速度,单位:s-1 | |||
| public const double basicRemoteAttackRange = 6000; // 基本远程攻击范围 | |||
| public const double basicAttackShortRange = 2200; // 基本近程攻击范围 | |||
| public const double basicBulletBombRange = 2000; // 基本子弹爆炸范围 | |||
| #endregion | |||
| #region 技能相关 | |||
| public const int maxNumOfSkill = 3; | |||
| @@ -2,7 +2,7 @@ | |||
| "profiles": { | |||
| "Server": { | |||
| "commandName": "Project", | |||
| "commandLineArgs": "--ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 30 --fileName E:\\GSY\\软件部\\THUAI6-dev\\THUAI6\\logic\\cmd\\test.thuaipb --playback true" | |||
| "commandLineArgs": "--ip 0.0.0.0 -p 8888 --characterID 2030" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -4,14 +4,14 @@ 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 4 | |||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --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 5 | |||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --port 8888 --characterID 0 --type 1 --occupation 1 | |||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 1 --type 1 --occupation 5 | |||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --port 8888 --characterID 1 --type 1 --occupation 2 | |||
| 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 --port 8888 --characterID 2 --type 1 --occupation 5 | |||
| 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 --port 8888 --characterID 3 --type 1 --occupation 5 | |||
| ::start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2030 | |||
| ::start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --port 8888 --characterID 2030 | |||
| @@ -1,5 +1,5 @@ | |||
| @echo off | |||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --playbackFile .\video.thuaipb --playbackSpeed 1 | |||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --playbackFile .\test.thuaipb --playbackSpeed 2 | |||
| ping -n 2 127.0.0.1 > NUL | |||
| @@ -1,28 +0,0 @@ | |||
| ## 键鼠控制 | |||
| | 键位 | 效果 | | |||
| | ------------ | ---------------------------------------------- | | |||
| | 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,19 +1,32 @@ | |||
| # 使用文档 | |||
| 本文档仅供Windows选手参考,Linux选手仿照本文档即可 | |||
| 本文档仅供Windows选手参考,Linux选手仿照本文档即可。 | |||
| ## 路径 | |||
| ## C++接口使用说明 | |||
| Windows选手使用脚本位于Win文件夹下,Linux选手使用脚本位于Linux文件夹下 | |||
| - Windows:先查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 | |||
| - Linux:首先自行安装`gRPC`,具体方法可以参考官方教程https://grpc.io/docs/languages/cpp/quickstart/。 | |||
| - 然后在`CAPI\cpp\API\src\AI.cpp`中编写代码 | |||
| - Windows:然后用Visual Studio打开`CAPI\cpp\CAPI.sln`编译,注意使用Debug模式 | |||
| - Linux:用`cmake`,对`CAPI\cpp\CMakeLists.txt`进行编译 | |||
| - Windows:最后使用`RunCpp.cmd`执行比赛代码 | |||
| - Linux:最后使用`RunCpp.sh`执行比赛代码 | |||
| ## 游戏启动方式(For Debug) | |||
| ## Python接口使用说明 | |||
| - 首先在Python环境下运行`GeneratePythonProto.cmd`,以安装必要的包、并生成对应的grpc python文件 | |||
| - 然后在`CAPI\python\PyAPI\AI.py`中编写代码 | |||
| - Windows:最后通过运行`RunPython.cmd`执行比赛代码 | |||
| - Linux:通过运行`RunPython.sh`执行比赛代码 | |||
| ## 游戏基本启动方式(For Debug) | |||
| 1. 首先启动Server:`RunServerForDebug.cmd` | |||
| 2. 再启动Client:Python使用`RunPython.cmd`,C++使用`RunCpp.cmd` | |||
| ## 实机体验游戏(ServerForPlay) | |||
| ## 启动服务器(RunServer) | |||
| 启动游戏:ServerForPlay.cmd,其内部参数可自主设定,设定方式:右键-编辑 | |||
| 启动服务器:`RunServer.cmd`,其内部参数可自主设定,设定方式:右键-打开更多选项-编辑 | |||
| Server脚本中参数格式一般如下: | |||
| @@ -44,7 +57,9 @@ Server脚本中参数格式一般如下: | |||
| `--fileName`为回放文件的保存名称 | |||
| 此外,Server中还可以默认打开一个/多个WPF客户端,一般格式如下: | |||
| ## 启动WPF客户端(RunClient) | |||
| 通过`RunGUIClient.cmd`,可以打开WPF客户端,其内部参数可自主设定,设定方式:右键-打开更多选项-编辑 | |||
| ```shell | |||
| --port 8888 --characterID 4 --type 2 --occupation 1 | |||
| @@ -68,6 +83,16 @@ Server脚本中参数格式一般如下: | |||
| `--occupation`为职业选择,参考下表 | |||
| ### 观看回放文件 | |||
| 通过WPF客户端,还可以观看比赛回放。在`RunGUIClient.cmd`中,将参数改为 | |||
| ```shell | |||
| start cmd /k win64\Client.exe --cl --playbackFile .\video.thuaipb --playbackSpeed 2 | |||
| ``` | |||
| 即可观看回放文件。其中`--playbackFile`后面的参数为回放文件的路径,`--playbackSpeed`后面的参数为回放速度,取值为1~10,1为正常速度,10为最快速度。 | |||
| ### 职业对应表: | |||
| * 学生 | |||
| @@ -94,11 +119,7 @@ Server脚本中参数格式一般如下: | |||
| ## Client | |||
| ### C++接口 | |||
| 选手用Visual Studio打开CAPI.sln,编写AI.cpp,建议用Debug模式生成以方便自己调试,然后可以使用`RunCpp.cmd`启动。 | |||
| `RunCpp.cmd`的脚本参数格式如下: | |||
| `RunCpp.cmd`或`RunPython.cmd`的脚本参数格式如下: | |||
| ```shell | |||
| -I 127.0.0.1 -P 8888 -p 0 -d -o -w | |||
| @@ -116,30 +137,40 @@ Server脚本中参数格式一般如下: | |||
| `-w`只在开启`-o`的情况下生效,此时屏幕上只输出警告或报错(而日志文件依然完整保存)。 | |||
| ## WPF观看回放 | |||
| 使用`RunPlayback.cmd`,默认观看当前目录下的 video.thuaipb。 | |||
| > 可以通过更改脚本中FileName参数,来观看不同的回放文件 | |||
| ## cmd脚本的参数修改 | |||
| 右键点击`.cmd`或`.bat`文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上`::`,可以注释掉该行。 | |||
| ## WPF简易调试界面 | |||
|  | |||
|  | |||
| ### 界面介绍 | |||
| * 固定方块:红色方块代表墙,绿色方块代表草,灰色方块代表窗户,黄色带锁标志的方块代表门 | |||
| * 固定方块:红色方块代表墙,绿色方块代表草,灰色方块代表窗户,黄色带锁标志的方块代表门,浅橙红色代表隐藏校门 | |||
| * 动态方块:蓝色方块代表大门,橙色方块代表箱子,粉色方块代表教室,上面的数字均代表进度 | |||
| * 物件:橙色圆代表捣蛋鬼,紫色圆代表学生,且上面的编号对应学生的ID | |||
| * 物件:橙色圆代表捣蛋鬼,紫色圆代表学生,且上面的编号对应学生的ID,灰色表示Robot,它的编号表示所属的TechOtaku | |||
| * 道具对应表如下 | |||
| | 道具类型 | UI | | |||
| | ---------------------- | --- | | |||
| | Key3 | 🔑3 | | |||
| | Key5 | 🔑5 | | |||
| | Key6 | 🔑6 | | |||
| | AddSpeed | ⛸ | | |||
| | AddLifeOrClairaudience | 🏅 | | |||
| | AddHpOrAp | ♥ | | |||
| | ShieldOrSpear: | 🛡 | | |||
| | RecoveryFromDizziness | 🕶 | | |||
| * UI:左侧为UI,表示游戏内各类信息 | |||
| ### 实机体验之键鼠操作 | |||
| 仅当启动ServerForPlay.cmd时可以使用,观看回放时不能使用 | |||
| 仅当以玩家身份启动`RunGUIClient.cmd`时可以使用,观看回放时不能使用 | |||
| | 键位 | 效果 | | |||
| | ------------ | ---------------------------------------------- | | |||
| @@ -164,4 +195,4 @@ Server脚本中参数格式一般如下: | |||
| | V | (Both)随机使用一个已经持有的道具 | | |||
| | B | (Both)使用0号技能 | | |||
| | N | (Both)使用1号技能 | | |||
| | M | (Both)使用2号技能 | | |||
| | M | (Both)使用2号技能 | | |||