| @@ -3,6 +3,7 @@ | |||||
| #include "structures.h" | #include "structures.h" | ||||
| #include <tclap/CmdLine.h> | #include <tclap/CmdLine.h> | ||||
| #include <array> | #include <array> | ||||
| #include <string_view> | |||||
| #undef GetMessage | #undef GetMessage | ||||
| #undef SendMessage | #undef SendMessage | ||||
| @@ -12,6 +13,26 @@ | |||||
| #pragma warning(disable : 4996) | #pragma warning(disable : 4996) | ||||
| #endif | #endif | ||||
| using namespace std::literals::string_view_literals; | |||||
| // Generated by http://www.network-science.de/ascii/ with font "standard" | |||||
| static constexpr std::string_view welcomeString = R"welcome( | |||||
| _____ _ _ _ _ _ ___ __ | |||||
| |_ _| | | | | | | / \ |_ _/ /_ | |||||
| | | | |_| | | | |/ _ \ | | '_ \ | |||||
| | | | _ | |_| / ___ \ | | (_) | | |||||
| |_| |_| |_|\___/_/ \_\___\___/ | |||||
| ____ _ _ ____ _ _ _ | |||||
| / ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| | | |||||
| | | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| | | |||||
| | |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_| | |||||
| \____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_) | |||||
| )welcome"sv; | |||||
| int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | ||||
| { | { | ||||
| int pID = 0; | int pID = 0; | ||||
| @@ -84,6 +105,11 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||||
| playerType = THUAI6::PlayerType::StudentPlayer; | playerType = THUAI6::PlayerType::StudentPlayer; | ||||
| stuType = studentType[pID]; | stuType = studentType[pID]; | ||||
| } | } | ||||
| #ifdef _MSC_VER | |||||
| std::cout << welcomeString << std::endl; | |||||
| #endif | |||||
| Logic logic(playerType, pID, trickerType, stuType); | Logic logic(playerType, pID, trickerType, stuType); | ||||
| logic.Main(AIBuilder, sIP, sPort, file, print, warnOnly); | logic.Main(AIBuilder, sIP, sPort, file, print, warnOnly); | ||||
| } | } | ||||
| @@ -512,10 +512,10 @@ class Logic(ILogic): | |||||
| if platform.system().lower() == "windows": | if platform.system().lower() == "windows": | ||||
| os.system( | os.system( | ||||
| f"mkdir {os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '/logs'}") | |||||
| f"mkdir \"{os.path.dirname(os.path.dirname(os.path.realpath(__file__)))}\\logs\"") | |||||
| else: | else: | ||||
| os.system( | os.system( | ||||
| f"mkdir -p {os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '/logs'}") | |||||
| f"mkdir -p \"{os.path.dirname(os.path.dirname(os.path.realpath(__file__)))}/logs\"") | |||||
| fileHandler = logging.FileHandler(os.path.dirname( | fileHandler = logging.FileHandler(os.path.dirname( | ||||
| os.path.dirname(os.path.realpath(__file__))) + "/logs/logic" + str(self.__playerID) + "-log.txt", "w+", encoding="utf-8") | os.path.dirname(os.path.realpath(__file__))) + "/logs/logic" + str(self.__playerID) + "-log.txt", "w+", encoding="utf-8") | ||||
| @@ -9,8 +9,28 @@ from PyAPI.AI import AI | |||||
| from PyAPI.logic import Logic | from PyAPI.logic import Logic | ||||
| from typing import List, Callable | from typing import List, Callable | ||||
| import argparse | import argparse | ||||
| import platform | |||||
| import PyAPI.structures as THUAI6 | import PyAPI.structures as THUAI6 | ||||
| def PrintWelcomeString() -> None: | |||||
| # Generated by http://www.network-science.de/ascii/ with font "standard" | |||||
| welcomeString = """ | |||||
| _____ _ _ _ _ _ ___ __ | |||||
| |_ _| | | | | | | / \ |_ _/ /_ | |||||
| | | | |_| | | | |/ _ \ | | '_ \ | |||||
| | | | _ | |_| / ___ \ | | (_) | | |||||
| |_| |_| |_|\___/_/ \_\___\___/ | |||||
| ____ _ _ ____ _ _ _ | |||||
| / ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| | | |||||
| | | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| | | |||||
| | |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_| | |||||
| \____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_) | |||||
| """ | |||||
| print(welcomeString) | |||||
| def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None: | def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None: | ||||
| pID: int = 0 | pID: int = 0 | ||||
| @@ -45,6 +65,10 @@ def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None: | |||||
| playerType = THUAI6.PlayerType.TrickerPlayer | playerType = THUAI6.PlayerType.TrickerPlayer | ||||
| else: | else: | ||||
| playerType = THUAI6.PlayerType.StudentPlayer | playerType = THUAI6.PlayerType.StudentPlayer | ||||
| if platform.system().lower() == "windows": | |||||
| PrintWelcomeString() | |||||
| logic = Logic(pID, playerType) | logic = Logic(pID, playerType) | ||||
| logic.Main(AIBuilder, sIP, sPort, file, screen, warnOnly) | logic.Main(AIBuilder, sIP, sPort, file, screen, warnOnly) | ||||
| @@ -1,2 +1,3 @@ | |||||
| grpcio==1.52.0 | grpcio==1.52.0 | ||||
| grpcio-tools==1.52.0 | grpcio-tools==1.52.0 | ||||
| numpy | |||||
| @@ -247,6 +247,8 @@ int main() | |||||
| ## THUAI6 | ## THUAI6 | ||||
| ### high-ladder | |||||
| 因为今年的对局得分是两局得分之和,所以会出现一定程度的“数值膨胀”,在这里调低了胜者得分权值,同时提高了比赛分差距悬殊阈值和天梯分差距悬殊阈值。同时由于今年得分的上限不好确定,所以负者失分的基础值变为与胜者的得分之差。 | 因为今年的对局得分是两局得分之和,所以会出现一定程度的“数值膨胀”,在这里调低了胜者得分权值,同时提高了比赛分差距悬殊阈值和天梯分差距悬殊阈值。同时由于今年得分的上限不好确定,所以负者失分的基础值变为与胜者的得分之差。 | ||||
| ```c++ | ```c++ | ||||
| @@ -330,3 +332,126 @@ mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore) | |||||
| } | } | ||||
| ``` | ``` | ||||
| ### competition | |||||
| 与天梯得分算法要满足的“枫氏七条”类似,比赛得分算法也要满足“唐氏四律”,分别如下: | |||||
| 1. 两队经过某场比赛的得分变化,应只与该场比赛有关,而与历史积分无关。 | |||||
| 2. 须赋予比赛获胜一方基础得分,哪怕获胜一方的优势非常小。也就是说,哪怕胜利一方仅以微弱优势获胜,也需要拉开胜者与败者的分差。 | |||||
| 3. 胜利一方优势越大,得分理应越高。 | |||||
| 4. 对于一场比赛,胜利一方的得分不能无限大,须控制在一个合理的数值以下。 | |||||
| - 在非平局的情况下,(胜者)天梯得分与双方比赛分差值成正相关,得分函数如下(以x表示得分差值,y表示(胜者)天梯得分,a、b为固定参数) | |||||
| $$y=ax^2(1-0.375\cdot(\tanh(\frac{x}{b}-1)+1))$$ | |||||
| - 在平局情况下,(双方)天梯得分与比赛分成正相关,得分函数如下(以x表示比赛分,y表示(双方)天梯得分,c为固定参数) | |||||
| $$y=cx^2$$ | |||||
| - 不管是哪种情况,都有得分下界,非平局为100,平局为25 | |||||
| ```c++ | |||||
| #include <iostream> | |||||
| #include <algorithm> | |||||
| #include <cmath> | |||||
| #include <cassert> | |||||
| using namespace std; | |||||
| template <typename T> | |||||
| using mypair = pair<T, T>; | |||||
| double minScore = 100; | |||||
| double TieScore(double gameScore) | |||||
| { | |||||
| const double get = 9e-5; // 天梯得分权值 | |||||
| double deltaScore = 2000.0; // 订正的幅度,该值越小,则在双方分差较大时,天梯分数改变量越小,整个函数的变化范围大约为0~2*deltaScore | |||||
| double highScore = 6000.0; // 将highScore设定为较大值,使得correct较小 | |||||
| double correct = 1 - 0.375 * (tanh((highScore - deltaScore) / deltaScore) + 1.0); // 一场比赛中,在双方分差较大时,减小天梯分数的改变量 | |||||
| cout << "correct: " << correct << endl; | |||||
| int score = round(gameScore * gameScore * get * correct / 4); | |||||
| return score > minScore / 4 ? score : minScore / 4; | |||||
| } | |||||
| double WinScore(double delta, double winnerGameScore) // 根据游戏得分差值,与绝对分数,决定最后的加分 | |||||
| { | |||||
| assert(delta > 0); | |||||
| const double firstnerGet = 9e-5; // 胜利者天梯得分权值 | |||||
| double deltaScore = 2000.0; // 订正的幅度,该值越小,则在双方分差较大时,天梯分数改变量越小,整个函数的变化范围大约为0~2*deltaScore | |||||
| double correct = 1 - 0.375 * (tanh((delta - deltaScore) / deltaScore) + 1.0); // 一场比赛中,在双方分差较大时,减小天梯分数的改变量 | |||||
| cout << "correct: " << correct << endl; | |||||
| int score = round(delta * delta * firstnerGet * correct); | |||||
| return score > minScore ? score : minScore; | |||||
| } | |||||
| // orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 | |||||
| mypair<double> cal(mypair<double> orgScore, mypair<double> competitionScore) | |||||
| { | |||||
| // 调整顺序,让第一个元素成为获胜者,便于计算 | |||||
| bool reverse = false; // 记录是否需要调整 | |||||
| if (competitionScore.first < competitionScore.second) | |||||
| { | |||||
| reverse = true; | |||||
| } | |||||
| if (reverse) // 如果需要换,换两者的顺序 | |||||
| { | |||||
| swap(competitionScore.first, competitionScore.second); | |||||
| swap(orgScore.first, orgScore.second); | |||||
| } | |||||
| double delta = competitionScore.first - competitionScore.second; | |||||
| double addScore; | |||||
| mypair<double> resScore; | |||||
| // 先处理平局的情况 | |||||
| if (delta == 0) | |||||
| { | |||||
| addScore = TieScore(competitionScore.first); | |||||
| resScore = mypair<double>(orgScore.first + addScore, orgScore.second + addScore); | |||||
| } | |||||
| // 再处理有胜负的情况 | |||||
| else | |||||
| { | |||||
| addScore = WinScore(delta, competitionScore.first); | |||||
| resScore = mypair<double>(orgScore.first + addScore, orgScore.second); | |||||
| } | |||||
| // 如果换过,再换回来 | |||||
| if (reverse) | |||||
| { | |||||
| swap(resScore.first, resScore.second); | |||||
| } | |||||
| return resScore; | |||||
| } | |||||
| void Print(mypair<double> score) | |||||
| { | |||||
| std::cout << "team1: " << score.first << std::endl | |||||
| << "team2: " << score.second << std::endl; | |||||
| } | |||||
| int main() | |||||
| { | |||||
| double x, y, t, i = 0; | |||||
| cin >> t; | |||||
| while (i < t) | |||||
| { | |||||
| cout << "----------------------------------------\n"; | |||||
| std::cout << "origin score of team 1 and 2: " << std::endl; | |||||
| std::cin >> x >> y; | |||||
| auto ori = mypair<double>(x, y); | |||||
| std::cout << "game score of team 1 and 2: " << std::endl; | |||||
| std::cin >> x >> y; | |||||
| auto sco = mypair<double>(x, y); | |||||
| Print(cal(ori, sco)); | |||||
| ++i; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| ``` | |||||
| @@ -7,7 +7,7 @@ while (( $i <= 5 )) | |||||
| do | do | ||||
| if [ -f "${bind}/player${i}.cpp" ]; then | if [ -f "${bind}/player${i}.cpp" ]; then | ||||
| cp -f $bind/player$i.cpp ./API/src/AI.cpp | cp -f $bind/player$i.cpp ./API/src/AI.cpp | ||||
| cmake ./CMakeLists.txt && make >compile_log$i.txt 2>&1 | |||||
| cmake ./CMakeLists.txt && make -j$(nproc) >compile_log$i.txt 2>&1 | |||||
| mv ./capi $bind/capi$i # executable file | mv ./capi $bind/capi$i # executable file | ||||
| if [ $? -ne 0 ]; then | if [ $? -ne 0 ]; then | ||||
| flag=0 | flag=0 | ||||
| @@ -43,7 +43,7 @@ | |||||
| ### 信息获取 | ### 信息获取 | ||||
| #### 队内信息 | #### 队内信息 | ||||
| - `std::future<bool> SendMessage(int64_t, std::string)`:给同队的队友发送消息。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 | |||||
| - `std::future<bool> SendMessage(int64_t, std::string)`:给同队的队友发送消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 | |||||
| - `bool HaveMessage()`:是否有队友发来的尚未接收的信息。 | - `bool HaveMessage()`:是否有队友发来的尚未接收的信息。 | ||||
| - `std::pair<int64_t, std::string> GetMessage()`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。 | - `std::pair<int64_t, std::string> GetMessage()`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。 | ||||
| @@ -59,7 +59,7 @@ | |||||
| 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | ||||
| - `THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY)` :返回某一位置场地种类信息。场地种类详见 structure.h 。 | - `THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY)` :返回某一位置场地种类信息。场地种类详见 structure.h 。 | ||||
| - 以下指令,若查询物品当前在视野内,则返回最新进度;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。 | |||||
| - 以下指令,若查询物品当前在视野内,则返回最新进度/状态;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度/状态;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。 | |||||
| - `int32_t GetChestProgress(int32_t cellX, int32_t cellY) const`:查询特定位置箱子开启进度 | - `int32_t GetChestProgress(int32_t cellX, int32_t cellY) const`:查询特定位置箱子开启进度 | ||||
| - `int32_t GetGateProgress(int32_t cellX, int32_t cellY) const`:查询特定位置校门开启进度 | - `int32_t GetGateProgress(int32_t cellX, int32_t cellY) const`:查询特定位置校门开启进度 | ||||
| - `int32_t GetClassroomProgress(int32_t cellX, int32_t cellY) const`:查询特定位置教室作业完成进度 | - `int32_t GetClassroomProgress(int32_t cellX, int32_t cellY) const`:查询特定位置教室作业完成进度 | ||||
| @@ -55,7 +55,7 @@ | |||||
| #### 队内信息 | #### 队内信息 | ||||
| - `def GetMessage(self) -> Tuple[int, str]`:给同队的队友发送消息。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 | |||||
| - `def GetMessage(self) -> Tuple[int, str]`:给同队的队友发送消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 | |||||
| - `def HaveMessage(self) -> bool`:是否有队友发来的尚未接收的信息。 | - `def HaveMessage(self) -> bool`:是否有队友发来的尚未接收的信息。 | ||||
| - `def GetMessage(self) -> Tuple[int, str]`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。 | - `def GetMessage(self) -> Tuple[int, str]`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。 | ||||
| @@ -72,7 +72,7 @@ | |||||
| 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | ||||
| - `def GetPlaceType(self, cellX: int, cellY: int) -> THUAI6.PlaceType` :返回某一位置场地种类信息。场地种类详见 structure.h 。 | - `def GetPlaceType(self, cellX: int, cellY: int) -> THUAI6.PlaceType` :返回某一位置场地种类信息。场地种类详见 structure.h 。 | ||||
| - 以下指令,若查询物品当前在视野内,则返回最新进度;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。 | |||||
| - 以下指令,若查询物品当前在视野内,则返回最新进度/状态;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度/状态;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。 | |||||
| - `def GetChestProgress(self, cellX: int, cellY: int) -> int`:查询特定位置箱子开启进度 | - `def GetChestProgress(self, cellX: int, cellY: int) -> int`:查询特定位置箱子开启进度 | ||||
| - `def GetGateProgress(self, cellX: int, cellY: int) -> int`:查询特定位置校门开启进度 | - `def GetGateProgress(self, cellX: int, cellY: int) -> int`:查询特定位置校门开启进度 | ||||
| - `def GetClassroomProgress(self, cellX: int, cellY: int) -> int`:查询特定位置教室作业完成进度 | - `def GetClassroomProgress(self, cellX: int, cellY: int) -> int`:查询特定位置教室作业完成进度 | ||||
| @@ -64,7 +64,7 @@ CellX=\frac{x}{1000},CellY=\frac{y}{1000} | |||||
| $$ | $$ | ||||
| - 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子 | - 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子 | ||||
| - 隐藏校门刷新点的区域类型始终为隐藏校门 | |||||
| - 任何格子的区域类型(PlaceType)始终不变,所有隐藏校门刷新点的区域类型均为隐藏校门 | |||||
| ### 人物 | ### 人物 | ||||
| - 人物直径为800 | - 人物直径为800 | ||||
| @@ -142,7 +142,7 @@ $$ | |||||
| - Bgm (在structures.h/.py中的student类或Tricker类中作为其属性) | - Bgm (在structures.h/.py中的student类或Tricker类中作为其属性) | ||||
| 1. 不详的感觉(dangerAlert):如果捣蛋鬼进入(学生的警戒半径/捣蛋鬼的隐蔽度)的距离,则学生的dangerAlert=(警戒半径/二者距离) | 1. 不详的感觉(dangerAlert):如果捣蛋鬼进入(学生的警戒半径/捣蛋鬼的隐蔽度)的距离,则学生的dangerAlert=(警戒半径/二者距离) | ||||
| 2. 期待搞事的感觉(trickDesire):如果有学生进入(捣蛋鬼的警戒半径/学生的隐蔽度)的距离,则捣蛋鬼trickDesire=(警戒半径/可被发觉的最近的学生距离) | 2. 期待搞事的感觉(trickDesire):如果有学生进入(捣蛋鬼的警戒半径/学生的隐蔽度)的距离,则捣蛋鬼trickDesire=(警戒半径/可被发觉的最近的学生距离) | ||||
| 3. 学习的声音(classVolume): 警戒半径内有人学习时,捣蛋鬼classVolume=((警戒半径x学习进度百分比)/二者距离) | |||||
| 3. 学习的声音(classVolume): 警戒半径内有人学习时,捣蛋鬼classVolume=((警戒半径x学习进度百分比)/最近二者距离) | |||||
| - 可以向每一个队友发送不超过256字节的信息 | - 可以向每一个队友发送不超过256字节的信息 | ||||
| ### 可视范围 | ### 可视范围 | ||||
| @@ -360,10 +360,12 @@ $$ | |||||
| ### 信息相关 | ### 信息相关 | ||||
| - Bgm在没有符合条件的情况下,值为0。 | - Bgm在没有符合条件的情况下,值为0。 | ||||
| - 不能给自己发信息 | |||||
| ### 技能 | ### 技能 | ||||
| - CD冷却计时是在开始使用技能的瞬间开始的 | - CD冷却计时是在开始使用技能的瞬间开始的 | ||||
| - Klee的小炸弹有碰撞体积 | - Klee的小炸弹有碰撞体积 | ||||
| - 除了切换攻击类型的技能,在不能执行指令的状态下(包括翻窗)均不能使用技能 | |||||
| ### 职业 | ### 职业 | ||||
| - 学生职业可以重复选择 | - 学生职业可以重复选择 | ||||
| @@ -22,6 +22,11 @@ Q:卡死在第一帧不动 | |||||
| A:大概率是你的代码死循环了 | A:大概率是你的代码死循环了 | ||||
| Q: | |||||
|  | |||||
| A:命令行参数的type设置有误 | |||||
| ## C++ | ## C++ | ||||
| Q:显示API项目已卸载 | Q:显示API项目已卸载 | ||||
| @@ -56,7 +61,11 @@ A: | |||||
| 2. 不要点重新生成,要点生成 | 2. 不要点重新生成,要点生成 | ||||
| 3. 开启下图选项 | 3. 开启下图选项 | ||||
|  |  | ||||
| Q:这是什么错误啊 | |||||
|  | |||||
| A:调用了容量为0的vector | |||||
| ## Python | ## Python | ||||
| @@ -73,6 +82,20 @@ A: | |||||
| - 可能措施3. 更新pip | - 可能措施3. 更新pip | ||||
| `python -m pip install --upgrade pip` (pip 版本最好为23.1) | `python -m pip install --upgrade pip` (pip 版本最好为23.1) | ||||
| ## 游戏引擎/机制 | |||||
| Q:咱们这边play函数调用机制究竟是怎么样的?如果50ms内没有执行完当前程序,是在下一帧不会重新调用play吗 | |||||
| 还是只有move这样的明确有时间为参量的才会有上面那个机制 | |||||
| A: | |||||
| - 调用任何主动指令都不会占用你play函数多少时间,你可以把它想成一个信号,告诉游戏引擎你想做什么 | |||||
| - 50ms内没有执行完当前程序,是指你的play函数例如求最短路之类的操作会占用的时间 | |||||
| - 准确地说,50ms内没有执行完当前程序,在下一帧一般会重新调用play | |||||
| - 比如说,你第一次调用花了70ms | |||||
| 那么下一次调用会在这次之后立刻开始 | |||||
| 如果你三次都70ms,就会4帧3次play了 | |||||
| - 当然第一次调用花了110ms,第二帧自然不调用了 | |||||
| ## 比赛相关 | ## 比赛相关 | ||||
| Q:职业数值会修改吗? | Q:职业数值会修改吗? | ||||
| @@ -80,4 +103,4 @@ A:初赛结束会调数值及机制,增加新角色 | |||||
| Q:初赛后会修改什么呢? | Q:初赛后会修改什么呢? | ||||
| A:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息;提供不同格式的信息传递;增加职业;优化游戏结束条件 | |||||
| A:可能的修改:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息;提供不同格式的信息传递;增加职业;优化游戏结束条件;角色毅力值清空不在使捣蛋鬼产生BGM;HaveView()修改 等 | |||||
| @@ -2,9 +2,9 @@ | |||||
| [toc] | [toc] | ||||
| ## Visual Studio使用说明 | |||||
| ## Visual Studio 使用说明 | |||||
| 比赛**只保证!!!支持**VS2022最新版本,选手使用其他版本后果自负(实际上应该不能编译)。 | |||||
| 比赛**只保证!!!支持** VS 2022 最新版本,选手使用其他版本后果自负(实际上应该不能编译)。 | |||||
| ### 生成模式的设置 | ### 生成模式的设置 | ||||
| @@ -12,7 +12,7 @@ | |||||
|  |  | ||||
| 可以更改生成模式为`Debug`或`Release` | |||||
| 可以更改生成模式为 `Debug` 或 `Release` | |||||
| ### 命令行参数的设置 | ### 命令行参数的设置 | ||||
| @@ -22,19 +22,19 @@ | |||||
| 在命令参数一栏中加入命令行参数进行调试 | 在命令参数一栏中加入命令行参数进行调试 | ||||
| ### cmd脚本的参数修改 | |||||
| ### cmd 脚本的参数修改 | |||||
| 右键点击`.cmd`或`.bat`文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上`::`,可以注释掉该行。 | |||||
| 右键点击 `.cmd` 或 `.bat` 文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上 `::`,可以注释掉该行。 | |||||
| ## C++接口必看 | |||||
| ## C++ 接口必看 | |||||
| **在此鸣谢\xfgg/\xfgg/\xfgg/,看到这里的选手可以到选手群膜一膜!!! ** | **在此鸣谢\xfgg/\xfgg/\xfgg/,看到这里的选手可以到选手群膜一膜!!! ** | ||||
| 除非特殊指明,以下代码均在 MSVC 19.28.29913 /std:c++17 与 g++ 10.2 for linux -std=c++17 两个平台下通过。 | |||||
| 除非特殊指明,以下代码均在 MSVC 19.28.29913 x64 `/std:c++17` 与 GCC 10.2 x86_64-linux-gnu `-std=c++17` 两个平台下通过。 | |||||
| 由于我们的比赛最终会运行在Linux平台上,因此程设课上学到的一些只适用于Windows的C++操作很可能并不能正确执行。此外,代码中使用了大量Modern C++中的新特性,可能会使选手在编程过程中遇到较大困难。因此,此处介绍一些比赛中使用C++接口必须了解的知识。 | |||||
| 由于我们的比赛最终会运行在 Linux 平台上,因此程设课上学到的一些只适用于 Windows 的 C++ 操作很可能并不能正确执行。此外,代码中使用了大量 Modern C++ 中的新特性,可能会使选手在编程过程中遇到较大困难。因此,此处介绍一些比赛中使用 C++ 接口必须了解的知识。 | |||||
| @@ -42,7 +42,7 @@ | |||||
| 编写代码过程中,我们可能需要获取系统时间等一系列操作,C++ 标准库提供了这样的行为。尤其注意**不要**使用 Windows 平台上的 `GetTickCount` 或者 `GetTickCount64` !!! 应当使用 `std::chrono` | |||||
| 编写代码过程中,我们可能需要获取系统时间等一系列操作,C++ 标准库提供了这样的行为。尤其注意**不要**使用 Windows 平台上的 `GetTickCount` 或者 `GetTickCount64` !!!应当使用 `std::chrono` | |||||
| 头文件:`#include <chrono>` | 头文件:`#include <chrono>` | ||||
| @@ -127,7 +127,7 @@ int main() | |||||
| ### `auto`类型推导 | |||||
| ### `auto` 类型推导 | |||||
| @@ -147,7 +147,7 @@ auto&& w = 4; // auto 被推导为 int,w 是 int&& 类型 | |||||
| #### std::vector | |||||
| #### `std::vector` | |||||
| 头文件:`#include <vector>`,类似于可变长的数组,支持下标运算符 `[]` 访问其元素,此时与 C 风格数组用法相似。支持 `size` 成员函数获取其中的元素数量。 | 头文件:`#include <vector>`,类似于可变长的数组,支持下标运算符 `[]` 访问其元素,此时与 C 风格数组用法相似。支持 `size` 成员函数获取其中的元素数量。 | ||||
| @@ -155,7 +155,7 @@ auto&& w = 4; // auto 被推导为 int,w 是 int&& 类型 | |||||
| ```cpp | ```cpp | ||||
| std::vector<int> v { 9, 1, 2, 3, 4 }; // 初始化 vector 有五个元素,v[0] = 9, ... | std::vector<int> v { 9, 1, 2, 3, 4 }; // 初始化 vector 有五个元素,v[0] = 9, ... | ||||
| v.emplace_back(10); // 向 v 尾部添加一个元素,该元素饿构造函数的参数为 10(对于 int,只有一个语法意义上的构造函数,无真正的构造函数),即现在 v 有六个元素,v[5] 的值是10 | |||||
| v.emplace_back(10); // 向 v 尾部添加一个元素,该元素饿构造函数的参数为 10(对于 int,只有一个语法意义上的构造函数,无真正的构造函数),即现在 v 有六个元素,v[5] 的值是 10 | |||||
| v.pop_back(); // 把最后一个元素删除,现在 v 还是 { 9, 1, 2, 3, 4 } | v.pop_back(); // 把最后一个元素删除,现在 v 还是 { 9, 1, 2, 3, 4 } | ||||
| ``` | ``` | ||||
| @@ -245,7 +245,7 @@ for (auto itr = begin(arr); itr != end(arr); ++itr) | |||||
| ### fmt库的使用 | |||||
| ### fmt 库的使用 | |||||
| @@ -264,11 +264,11 @@ std::string str_fmt = fmt::format("number: {}{}", 1, teststr); // 两种方法 | |||||
| ## Python接口必看 | |||||
| ## Python 接口必看 | |||||
| 比赛**只保证!!**支持 Python 3.9,不保证支持其他版本 | 比赛**只保证!!**支持 Python 3.9,不保证支持其他版本 | ||||
| 比赛中的Python接口大多使用异步接口,即返回一个类似于 `Future[bool]` 的值。为了获取实际的值,需要调用 `result()` 方法。 | |||||
| 比赛中的 Python 接口大多使用异步接口,即返回一个类似于 `Future[bool]` 的值。为了获取实际的值,需要调用 `result()` 方法。 | |||||
| ```python | ```python | ||||
| from concurrent.futures import Future, ThreadPoolExecutor | from concurrent.futures import Future, ThreadPoolExecutor | ||||
| @@ -297,15 +297,15 @@ if __name__ == '__main__': | |||||
| ## C++相关小知识 | |||||
| ## C++ 相关小知识 | |||||
| ### lambda表达式 | |||||
| ### lambda 表达式 | |||||
| #### lambda表达式概述 | |||||
| #### lambda 表达式概述 | |||||
| @@ -456,7 +456,7 @@ f(); // 输出 4,而非 9 | |||||
| > []() | > []() | ||||
| > { | > { | ||||
| > int y = x; // OK, constant folding | > int y = x; // OK, constant folding | ||||
| > int z = Func1(x); // Compile error! odr-used! x is not captured! | |||||
| > int z = Func1(x); // Compile error! odr-used! x is not captured! | |||||
| > }(); | > }(); | ||||
| > } | > } | ||||
| > ``` | > ``` | ||||
| @@ -587,7 +587,7 @@ lambda 表达式还有很多有趣之处,例如泛型 lambda、返回 lambda | |||||
| ### std::thread | |||||
| ### `std::thread` | |||||
| 头文件:`#include <thread>`。用于开启新的线程。示例代码: | 头文件:`#include <thread>`。用于开启新的线程。示例代码: | ||||
| @@ -727,7 +727,7 @@ int main() | |||||
| ##### 概览 | ##### 概览 | ||||
| `shared_ptr` 的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的智能指针数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。不过也需要注意,使用 `std::shared_ptr` 会比传统的指针带来额外的引用计数的开销,因此只有当后面将会介绍的 `std::unique_ptr` 无法满足要求时方可考虑 `std::shared_ptr`。 | |||||
| `shared_ptr` 的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的 `std::shared_ptr` 数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。不过也需要注意,使用 `std::shared_ptr` 会比传统的指针带来额外的引用计数的开销,因此只有当后面将会介绍的 `std::unique_ptr` 无法满足要求时方可考虑 `std::shared_ptr`。 | |||||
| 像 `new` 会在自由存储区动态获取一块内存并返回其一样,如果要动态分配一块内存并得到其智能指针,可以使用 `std::make_shared` 模板,例如: | 像 `new` 会在自由存储区动态获取一块内存并返回其一样,如果要动态分配一块内存并得到其智能指针,可以使用 `std::make_shared` 模板,例如: | ||||
| @@ -759,27 +759,27 @@ void Func() | |||||
| // 上述此语句执行过后,只有一个智能指针 sp1 指向这个 int,引用计数为 1 | // 上述此语句执行过后,只有一个智能指针 sp1 指向这个 int,引用计数为 1 | ||||
| { | { | ||||
| auto sp2 = sp1; // 构造一个智能指针 sp2,指向 sp1 指向的内存,并将引用计数+1 | |||||
| auto sp2 = sp1; // 构造一个 std::shared_ptr sp2,指向 sp1 指向的对象,并将引用计数加一 | |||||
| // 故此处引用计数为2 | // 故此处引用计数为2 | ||||
| std::cout << *sp2 << std::endl; // 输出 110 | std::cout << *sp2 << std::endl; // 输出 110 | ||||
| // 此处 sp2 生存期已到,调用 sp2 的析构函数,使引用计数-1,因此此时引用计数为1 | |||||
| // 此处 sp2 生存期已到,调用 sp2 的析构函数,使引用计数减一,因此此时引用计数为 1 | |||||
| } | } | ||||
| // 此处 sp1 生命期也已经到了,调用 sp1 析构函数,引用计数再-1,故引用计数降为0 | |||||
| // 也就是不再有智能指针指向它了,调用 delete 释放内存 | |||||
| // 此处 sp1 生命期也已经到了,调用 sp1 析构函数,引用计数再减一,故引用计数降为 0 | |||||
| // 也就是不再有 std::shared_ptr 指向它了,调用 delete 释放 | |||||
| } | } | ||||
| } | } | ||||
| ``` | ``` | ||||
| 将普通指针交给智能指针托管: | |||||
| 将裸指针交给 `std::shared_ptr` 托管: | |||||
| ```cpp | ```cpp | ||||
| int* p = new int(110); | int* p = new int(110); | ||||
| int* q = new int(110); | int* q = new int(110); | ||||
| std::shared_ptr sp(p); // 把 p 指向的内存交给 sp 托管,此后 p 便不需要 delete,sp 析构时会自动释放 | |||||
| std::shared_ptr sp(p); // 把 p 指向的对象交给 sp 托管,此后 p 便不需要 delete,sp 析构时会自动释放 | |||||
| std::shared_ptr sq; // sq 什么也不托管 | std::shared_ptr sq; // sq 什么也不托管 | ||||
| sq.reset(q); // 让 sq 托管 q | sq.reset(q); // 让 sq 托管 q | ||||
| @@ -792,22 +792,38 @@ sq.reset(q); // 让 sq 托管 q | |||||
| 之前说过 ,默认情况下是释放内存的函数是 `delete` 运算符,但有时我们并不希望这样。比如下面的几个情况: | 之前说过 ,默认情况下是释放内存的函数是 `delete` 运算符,但有时我们并不希望这样。比如下面的几个情况: | ||||
| + 使用智能指针托管动态数组 | |||||
| + 使用 `std::shared_ptr` 托管动态数组 | |||||
| + C++11 起 | |||||
| ```cpp | |||||
| #include <memory> | |||||
| void IntArrayDeleter(int* p) { delete[] p; } | |||||
| int main() | |||||
| { | |||||
| std::shared_ptr<int> sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数 | |||||
| sp.get()[0] = 0; // 访问第 0 个元素 | |||||
| // sp 析构时自动调用 IntArrayDeleter 释放该 int 数组 | |||||
| return 0; | |||||
| } | |||||
| // 或者利用 lambda 表达式:std::shared_ptr<int> sp(new int[10], [](int* p) { delete[] p; }); | |||||
| ``` | |||||
| + C++17 起 | |||||
| ```cpp | |||||
| std::shared_ptr<int[]> sp(new int[10]); | |||||
| sp[0] = 0; // 访问第 0 个元素 | |||||
| ``` | |||||
| ```cpp | |||||
| #include <memory> | |||||
| void IntArrayDeleter(int* p) { delete[] p; } | |||||
| int main() | |||||
| { | |||||
| std::shared_ptr<int> sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数 | |||||
| // sp 析构时自动调用 IntArrayDeleter 释放该 int 数组 | |||||
| return 0; | |||||
| } | |||||
| // 或者利用 lambda 表达式:std::shared_ptr<int> sp(new int[10], [](int* p) { delete[] p; }); | |||||
| ``` | |||||
| + C++20 起 | |||||
| ```cpp | |||||
| auto sp = std::make_shared<int[]>(10); | |||||
| sp[0] = 0; // 访问第 0 个元素 | |||||
| ``` | |||||
| @@ -817,12 +833,12 @@ sq.reset(q); // 让 sq 托管 q | |||||
| ```c++ | ```c++ | ||||
| HDC hdc; // DC: Device context,一个指向 DC 的句柄(HANDLE) | HDC hdc; // DC: Device context,一个指向 DC 的句柄(HANDLE) | ||||
| hdc = GetDC(hWnd); // 获取设备上下文 | |||||
| hdc = GetDC(hWnd); // 获取设备上下文 | |||||
| /*执行绘图操作*/ | /*执行绘图操作*/ | ||||
| ReleaseDC(hWnd, hdc); // 绘图完毕,将设备上下文资源释放,归还给 Windows 系统 | ReleaseDC(hWnd, hdc); // 绘图完毕,将设备上下文资源释放,归还给 Windows 系统 | ||||
| ``` | ``` | ||||
| 使用智能指针对其进行托管,代码如下: | |||||
| 使用 `std::shared_ptr` 对其进行托管,代码如下: | |||||
| ```c++ | ```c++ | ||||
| // 使用 lambda 表达式写法(推荐) | // 使用 lambda 表达式写法(推荐) | ||||
| @@ -866,7 +882,7 @@ void Func() | |||||
| } | } | ||||
| ``` | ``` | ||||
| 这是因为,只有复制构造函数里面才有使引用计数加1的操作。即当我们写 `std::shared_ptr<int> sq = sp` 的时候,确实引用计数变成了2,但是我们都用一个外部的裸指针 `p` 去初始化 `sp` 和 `sq`,智能指针并不能感知到它们托管的内存相同。所以 `sp` 和 `sq` 所托管的内存被看做是独立的。这样,当它们析构的时候,均会释放它们所指的内存,因此同一块内存被释放了两次,导致程序出错。所以个人还是推荐使用 `make_shared` ,而不是用裸指针去获取内存。 | |||||
| 这是因为,只有复制构造函数里面才有使引用计数加一的操作。即当我们写 `std::shared_ptr<int> sq = sp` 的时候,确实引用计数变成了 2,但是我们都用一个外部的裸指针 `p` 去初始化 `sp` 和 `sq`,智能指针并不能感知到它们托管的内存相同。所以 `sp` 和 `sq` 所托管的内存被看做是独立的。这样,当它们析构的时候,均会释放它们所指的内存,因此同一块内存被释放了两次,导致程序出错。所以个人还是推荐使用 `make_shared` ,而不是用裸指针去获取内存。 | |||||
| 另一个著名的错误用法,请继续阅读 `std::weak_ptr`。 | 另一个著名的错误用法,请继续阅读 `std::weak_ptr`。 | ||||
| @@ -955,7 +971,28 @@ else | |||||
| #### `std::unique_ptr` | #### `std::unique_ptr` | ||||
| `std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有,是基于 RAII 的思想设计的智能指针,并且相比于原始指针并不会带来任何额外开销,是智能指针的首选。它部分涉及到对象的生命期、右值引用与移动语义的问题,在此不做过多展开。 | |||||
| `std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有,是基于 RAII 的思想设计的智能指针,并且相比于原始指针并不会带来任何额外开销,是智能指针的首选。它部分涉及到对象的生命期、右值引用与移动语义的问题,在此不做过多展开,仅提供一个例子作为参考: | |||||
| ```cpp | |||||
| { | |||||
| auto p = std::make_unique<int>(5); // 创建一个 int 对象并初始化为 5 | |||||
| std::cout << *p << std::endl; // 输出 5 | |||||
| // 该 int 对象随着 p 的析构而被 delete | |||||
| } | |||||
| ``` | |||||
| 需要注意的是,由于[标准委员会的疏忽~忘了~(partly an oversight)](https://herbsutter.com/gotw/_102/),C++14 中才引进`std::make_unique`,C++11 中无法使用。因此 C++11 若想使用则需自定义 `make_unique`: | |||||
| ```cpp | |||||
| namespace | |||||
| { | |||||
| template<typename T, typename... Args> | |||||
| std::unique_ptr<T> make_unique( Args&&... args ) | |||||
| { | |||||
| return std::unique_ptr<T>(new T( std::forward<Args>(args)...)); | |||||
| } | |||||
| } | |||||
| ``` | |||||
| @@ -10,7 +10,7 @@ namespace starter.viewmodel.common | |||||
| { | { | ||||
| public abstract class NotificationObject : INotifyPropertyChanged | public abstract class NotificationObject : INotifyPropertyChanged | ||||
| { | { | ||||
| public event PropertyChangedEventHandler PropertyChanged; | |||||
| public event PropertyChangedEventHandler? PropertyChanged; | |||||
| ///< summary> | ///< summary> | ||||
| /// announce notification | /// announce notification | ||||
| /// </summary> | /// </summary> | ||||
| @@ -25,21 +25,21 @@ namespace starter.viewmodel.common | |||||
| /// </summary> | /// </summary> | ||||
| public class BaseCommand : ICommand | public class BaseCommand : ICommand | ||||
| { | { | ||||
| private Func<object, bool> _canExecute; | |||||
| private Action<object> _execute; | |||||
| private Func<object?, bool>? _canExecute; | |||||
| private Action<object?> _execute; | |||||
| public BaseCommand(Func<object, bool> canExecute, Action<object> execute) | |||||
| public BaseCommand(Func<object?, bool>? canExecute, Action<object?> execute) | |||||
| { | { | ||||
| _canExecute = canExecute; | _canExecute = canExecute; | ||||
| _execute = execute; | _execute = execute; | ||||
| } | } | ||||
| public BaseCommand(Action<object> execute) : | |||||
| public BaseCommand(Action<object?> execute) : | |||||
| this(null, execute) | this(null, execute) | ||||
| { | { | ||||
| } | } | ||||
| public event EventHandler CanExecuteChanged | |||||
| public event EventHandler? CanExecuteChanged | |||||
| { | { | ||||
| add | add | ||||
| { | { | ||||
| @@ -57,12 +57,12 @@ namespace starter.viewmodel.common | |||||
| } | } | ||||
| } | } | ||||
| public bool CanExecute(object parameter) | |||||
| public bool CanExecute(object? parameter) | |||||
| { | { | ||||
| return _canExecute == null ? true : _canExecute(parameter); | return _canExecute == null ? true : _canExecute(parameter); | ||||
| } | } | ||||
| public void Execute(object parameter) | |||||
| public void Execute(object? parameter) | |||||
| { | { | ||||
| if (_execute != null && CanExecute(parameter)) | if (_execute != null && CanExecute(parameter)) | ||||
| { | { | ||||
| @@ -79,15 +79,15 @@ namespace starter.viewmodel.common | |||||
| { | { | ||||
| return false; | return false; | ||||
| } | } | ||||
| string checkvalue = value.ToString(); | |||||
| string targetvalue = parameter.ToString(); | |||||
| string checkvalue = value.ToString() ?? ""; | |||||
| string targetvalue = parameter.ToString() ?? ""; | |||||
| bool r = checkvalue.Equals(targetvalue); | bool r = checkvalue.Equals(targetvalue); | ||||
| return r; | return r; | ||||
| } | } | ||||
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |||||
| public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) | |||||
| { | { | ||||
| if (value == null || parameter == null) | |||||
| if (value is null || parameter is null) | |||||
| { | { | ||||
| return null; | return null; | ||||
| } | } | ||||
| @@ -132,22 +132,22 @@ namespace starter.viewmodel.common | |||||
| static bool _isUpdating = false; | static bool _isUpdating = false; | ||||
| private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | ||||
| { | { | ||||
| PasswordBox pb = d as PasswordBox; | |||||
| PasswordBox pb = (d as PasswordBox)!; | |||||
| pb.PasswordChanged -= Pb_PasswordChanged; | pb.PasswordChanged -= Pb_PasswordChanged; | ||||
| if (!_isUpdating) | if (!_isUpdating) | ||||
| (d as PasswordBox).Password = e.NewValue.ToString(); | |||||
| (d as PasswordBox)!.Password = e.NewValue.ToString(); | |||||
| pb.PasswordChanged += Pb_PasswordChanged; | pb.PasswordChanged += Pb_PasswordChanged; | ||||
| } | } | ||||
| private static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | private static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | ||||
| { | { | ||||
| PasswordBox pb = d as PasswordBox; | |||||
| PasswordBox pb = (d as PasswordBox)!; | |||||
| pb.PasswordChanged += Pb_PasswordChanged; | pb.PasswordChanged += Pb_PasswordChanged; | ||||
| } | } | ||||
| private static void Pb_PasswordChanged(object sender, RoutedEventArgs e) | private static void Pb_PasswordChanged(object sender, RoutedEventArgs e) | ||||
| { | { | ||||
| PasswordBox pb = sender as PasswordBox; | |||||
| PasswordBox pb = (sender as PasswordBox)!; | |||||
| _isUpdating = true; | _isUpdating = true; | ||||
| SetPassword(pb, pb.Password); | SetPassword(pb, pb.Password); | ||||
| _isUpdating = false; | _isUpdating = false; | ||||
| @@ -6,8 +6,21 @@ | |||||
| <Nullable>enable</Nullable> | <Nullable>enable</Nullable> | ||||
| <UseWPF>true</UseWPF> | <UseWPF>true</UseWPF> | ||||
| <UseWindowsForms>true</UseWindowsForms> | <UseWindowsForms>true</UseWindowsForms> | ||||
| <ApplicationIcon>eesast_software_trans_enlarged.ico</ApplicationIcon> | |||||
| <PackageIcon>eesast_software_trans.png</PackageIcon> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | |||||
| <Content Include="eesast_software_trans_enlarged.ico" /> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <None Include="..\..\resource\eesast_software_trans.png"> | |||||
| <Pack>True</Pack> | |||||
| <PackagePath>\</PackagePath> | |||||
| </None> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="ICSharpCode.SharpZipLib.dll" Version="0.85.4.369" /> | <PackageReference Include="ICSharpCode.SharpZipLib.dll" Version="0.85.4.369" /> | ||||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> | <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> | ||||
| @@ -5,7 +5,9 @@ | |||||
| xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||||
| xmlns:local="clr-namespace:Installer" xmlns:c="clr-namespace:starter.viewmodel.common" | xmlns:local="clr-namespace:Installer" xmlns:c="clr-namespace:starter.viewmodel.common" | ||||
| mc:Ignorable="d" | mc:Ignorable="d" | ||||
| Title="Installer" Window.SizeToContent="WidthAndHeight"> | |||||
| Title="Installer" Window.SizeToContent="WidthAndHeight" | |||||
| ResizeMode="CanMinimize" | |||||
| > | |||||
| <Window.Resources> | <Window.Resources> | ||||
| <c:RadioConverter x:Key="RadioConverter"/> | <c:RadioConverter x:Key="RadioConverter"/> | ||||
| </Window.Resources> | </Window.Resources> | ||||
| @@ -21,7 +21,6 @@ using System.Net.Http; | |||||
| using System.Windows; | using System.Windows; | ||||
| using System.Windows.Shapes; | using System.Windows.Shapes; | ||||
| //using System.Windows.Forms; | //using System.Windows.Forms; | ||||
| using System.Threading.Tasks; | |||||
| using System.Threading; | using System.Threading; | ||||
| using MessageBox = System.Windows.MessageBox; | using MessageBox = System.Windows.MessageBox; | ||||
| @@ -153,18 +152,22 @@ namespace starter.viewmodel.settings | |||||
| } | } | ||||
| public bool RecallUser() | public bool RecallUser() | ||||
| { | { | ||||
| Username = Web.ReadJson("email"); | |||||
| if (Username == null || Username.Equals("")) | |||||
| var username = Web.ReadJson("email"); | |||||
| if (username == null || username.Equals("")) | |||||
| { | { | ||||
| Username = ""; | Username = ""; | ||||
| return false; | return false; | ||||
| } | } | ||||
| Password = Web.ReadJson("password"); | |||||
| if (Password == null || Username.Equals("")) | |||||
| Username = username; | |||||
| var password = Web.ReadJson("password"); | |||||
| if (password == null || password.Equals("")) | |||||
| { | { | ||||
| Password = ""; | Password = ""; | ||||
| return false; | return false; | ||||
| } | } | ||||
| Password = password; | |||||
| return true; | return true; | ||||
| } | } | ||||
| public bool ForgetUser() | public bool ForgetUser() | ||||
| @@ -210,8 +213,6 @@ namespace starter.viewmodel.settings | |||||
| switch (CodeRoute.Substring(CodeRoute.LastIndexOf('.') + 1)) | switch (CodeRoute.Substring(CodeRoute.LastIndexOf('.') + 1)) | ||||
| { | { | ||||
| case "cpp": | case "cpp": | ||||
| Language = "cpp"; | |||||
| break; | |||||
| case "h": | case "h": | ||||
| Language = "cpp"; | Language = "cpp"; | ||||
| break; | break; | ||||
| @@ -244,15 +245,12 @@ namespace starter.viewmodel.settings | |||||
| } | } | ||||
| public UsingOS ReadUsingOS() | public UsingOS ReadUsingOS() | ||||
| { | { | ||||
| string OS = Web.ReadJson("OS"); | |||||
| if (OS == null) | |||||
| return UsingOS.Win; | |||||
| else if (OS.Equals("linux")) | |||||
| return UsingOS.Linux; | |||||
| else if (OS.Equals("osx")) | |||||
| return UsingOS.OSX; | |||||
| else | |||||
| return UsingOS.Win; | |||||
| return Web.ReadJson("OS") switch | |||||
| { | |||||
| "linux" => UsingOS.Linux, | |||||
| "osx" => UsingOS.OSX, | |||||
| _ => UsingOS.Win, | |||||
| }; | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Route of files | /// Route of files | ||||
| @@ -274,7 +272,7 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| get; set; | get; set; | ||||
| } | } | ||||
| public string Language | |||||
| public string? Language | |||||
| { | { | ||||
| get; set; | get; set; | ||||
| } | } | ||||
| @@ -394,7 +392,7 @@ namespace Downloader | |||||
| public class Data | public class Data | ||||
| { | { | ||||
| public static string path = ""; // 标记路径记录文件THUAI6.json的路径 | public static string path = ""; // 标记路径记录文件THUAI6.json的路径 | ||||
| public static string FilePath = ""; // 最后一级为THUAI6文件夹所在目录 | |||||
| public static string FilePath = ""; // 最后一级为THUAI6文件夹所在目录 | |||||
| public static string dataPath = ""; // C盘的文档文件夹 | public static string dataPath = ""; // C盘的文档文件夹 | ||||
| public Data(string path) | public Data(string path) | ||||
| { | { | ||||
| @@ -403,7 +401,7 @@ namespace Downloader | |||||
| Data.path = System.IO.Path.Combine(dataPath, "THUAI6.json"); | Data.path = System.IO.Path.Combine(dataPath, "THUAI6.json"); | ||||
| if (File.Exists(Data.path)) | if (File.Exists(Data.path)) | ||||
| { | { | ||||
| var dict = new Dictionary<string, string>(); | |||||
| Dictionary<string, string>? dict; | |||||
| using (StreamReader r = new StreamReader(Data.path)) | using (StreamReader r = new StreamReader(Data.path)) | ||||
| { | { | ||||
| string json = r.ReadToEnd(); | string json = r.ReadToEnd(); | ||||
| @@ -411,7 +409,7 @@ namespace Downloader | |||||
| { | { | ||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | json += @"{""THUAI6""" + ":" + @"""2023""}"; | ||||
| } | } | ||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json); | |||||
| if (dict != null && dict.ContainsKey("installpath")) | if (dict != null && dict.ContainsKey("installpath")) | ||||
| { | { | ||||
| FilePath = dict["installpath"].Replace('\\', '/'); | FilePath = dict["installpath"].Replace('\\', '/'); | ||||
| @@ -425,11 +423,12 @@ namespace Downloader | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| FilePath = System.IO.Path.GetDirectoryName(@path); | |||||
| FilePath = System.IO.Path.GetDirectoryName(@path) | |||||
| ?? throw new Exception("Failed to get the path of the file"); | |||||
| //将dat文件写入程序运行路径 | //将dat文件写入程序运行路径 | ||||
| string json; | string json; | ||||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||||
| Dictionary<string, string>? dict; | |||||
| using FileStream fs = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); | using FileStream fs = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); | ||||
| using (StreamReader r = new StreamReader(fs)) | using (StreamReader r = new StreamReader(fs)) | ||||
| { | { | ||||
| @@ -438,7 +437,7 @@ namespace Downloader | |||||
| { | { | ||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | json += @"{""THUAI6""" + ":" + @"""2023""}"; | ||||
| } | } | ||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json); | |||||
| dict?.Add("installpath", path); | dict?.Add("installpath", path); | ||||
| } | } | ||||
| using FileStream fs2 = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); | using FileStream fs2 = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); | ||||
| @@ -451,7 +450,7 @@ namespace Downloader | |||||
| public static void ResetFilepath(string newPath) | public static void ResetFilepath(string newPath) | ||||
| { | { | ||||
| string json; | string json; | ||||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||||
| Dictionary<string, string>? dict; | |||||
| FilePath = newPath.Replace('\\', '/'); | FilePath = newPath.Replace('\\', '/'); | ||||
| path = System.IO.Path.Combine(dataPath, "THUAI6.json"); | path = System.IO.Path.Combine(dataPath, "THUAI6.json"); | ||||
| using FileStream fs = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); | using FileStream fs = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); | ||||
| @@ -462,14 +461,14 @@ namespace Downloader | |||||
| { | { | ||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | json += @"{""THUAI6""" + ":" + @"""2023""}"; | ||||
| } | } | ||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json); | |||||
| if (dict != null && dict.ContainsKey("installpath")) | if (dict != null && dict.ContainsKey("installpath")) | ||||
| { | { | ||||
| dict["installpath"] = newPath; | dict["installpath"] = newPath; | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| dict.Add("installpath", newPath); | |||||
| dict?.Add("installpath", newPath); | |||||
| } | } | ||||
| if (dict == null || !dict.ContainsKey("download")) | if (dict == null || !dict.ContainsKey("download")) | ||||
| { | { | ||||
| @@ -517,9 +516,10 @@ namespace Downloader | |||||
| // 创建存储桶 | // 创建存储桶 | ||||
| try | try | ||||
| { | { | ||||
| string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID | |||||
| string localDir = System.IO.Path.GetDirectoryName(download_dir); // 本地文件夹 | |||||
| string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 | |||||
| string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID | |||||
| string localDir = System.IO.Path.GetDirectoryName(download_dir) // 本地文件夹 | |||||
| ?? throw new Exception("本地文件夹路径获取失败"); | |||||
| string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 | |||||
| GetObjectRequest request = new GetObjectRequest(bucket, key, localDir, localFileName); | GetObjectRequest request = new GetObjectRequest(bucket, key, localDir, localFileName); | ||||
| Dictionary<string, string> test = request.GetRequestHeaders(); | Dictionary<string, string> test = request.GetRequestHeaders(); | ||||
| @@ -553,7 +553,7 @@ namespace Downloader | |||||
| public static string GetFileMd5Hash(string strFileFullPath) | public static string GetFileMd5Hash(string strFileFullPath) | ||||
| { | { | ||||
| FileStream fst = null; | |||||
| FileStream? fst = null; | |||||
| try | try | ||||
| { | { | ||||
| fst = new FileStream(strFileFullPath, FileMode.Open, FileAccess.Read); | fst = new FileStream(strFileFullPath, FileMode.Open, FileAccess.Read); | ||||
| @@ -634,7 +634,7 @@ namespace Downloader | |||||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, jsonName))) | using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, jsonName))) | ||||
| json = r.ReadToEnd(); | json = r.ReadToEnd(); | ||||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||||
| Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| var jsonDict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||||
| string updatingFolder = ""; | string updatingFolder = ""; | ||||
| switch (OS) | switch (OS) | ||||
| { | { | ||||
| @@ -815,7 +815,7 @@ namespace Downloader | |||||
| { | { | ||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | json += @"{""THUAI6""" + ":" + @"""2023""}"; | ||||
| } | } | ||||
| var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| var dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json); | |||||
| if (dict == null || !dict.ContainsKey("download") || "false" == dict["download"]) | if (dict == null || !dict.ContainsKey("download") || "false" == dict["download"]) | ||||
| { | { | ||||
| return false; | return false; | ||||
| @@ -865,15 +865,15 @@ namespace Downloader | |||||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, jsonName))) | using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, jsonName))) | ||||
| json = r.ReadToEnd(); | json = r.ReadToEnd(); | ||||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||||
| Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| // var jsonDict = Utils.DeserializeJson<Dictionary1<string, string>>(json); | |||||
| newFileName.Clear(); | newFileName.Clear(); | ||||
| updateFileName.Clear(); | updateFileName.Clear(); | ||||
| newFileName.Add("THUAI6.tar.gz"); | newFileName.Add("THUAI6.tar.gz"); | ||||
| Download(); | Download(); | ||||
| Stream inStream = null; | |||||
| Stream gzipStream = null; | |||||
| TarArchive tarArchive = null; | |||||
| Stream? inStream = null; | |||||
| Stream? gzipStream = null; | |||||
| TarArchive? tarArchive = null; | |||||
| try | try | ||||
| { | { | ||||
| using (inStream = File.OpenRead(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz"))) | using (inStream = File.OpenRead(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz"))) | ||||
| @@ -886,7 +886,7 @@ namespace Downloader | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| catch (Exception ex) | |||||
| catch | |||||
| { | { | ||||
| //出错 | //出错 | ||||
| } | } | ||||
| @@ -899,7 +899,7 @@ namespace Downloader | |||||
| FileInfo fileInfo = new FileInfo(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz")); | FileInfo fileInfo = new FileInfo(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz")); | ||||
| fileInfo.Delete(); | fileInfo.Delete(); | ||||
| string json2; | string json2; | ||||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||||
| Dictionary<string, string>? dict; | |||||
| string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | ||||
| using FileStream fs = new FileStream(existpath, FileMode.Open, FileAccess.ReadWrite); | using FileStream fs = new FileStream(existpath, FileMode.Open, FileAccess.ReadWrite); | ||||
| using (StreamReader r = new StreamReader(fs)) | using (StreamReader r = new StreamReader(fs)) | ||||
| @@ -909,7 +909,7 @@ namespace Downloader | |||||
| { | { | ||||
| json2 += @"{""THUAI6""" + ":" + @"""2023""}"; | json2 += @"{""THUAI6""" + ":" + @"""2023""}"; | ||||
| } | } | ||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json2); | |||||
| dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json2); | |||||
| if (dict == null || !dict.ContainsKey("download")) | if (dict == null || !dict.ContainsKey("download")) | ||||
| { | { | ||||
| dict?.Add("download", "true"); | dict?.Add("download", "true"); | ||||
| @@ -1000,7 +1000,7 @@ namespace Downloader | |||||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, "hash.json"))) | using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, "hash.json"))) | ||||
| json = r.ReadToEnd(); | json = r.ReadToEnd(); | ||||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty).Replace("/", @"\\"); | json = json.Replace("\r", string.Empty).Replace("\n", string.Empty).Replace("/", @"\\"); | ||||
| Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| Dictionary<string, string> jsonDict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||||
| Change_all_hash(Data.FilePath, jsonDict); | Change_all_hash(Data.FilePath, jsonDict); | ||||
| OverwriteHash(jsonDict); | OverwriteHash(jsonDict); | ||||
| break; | break; | ||||
| @@ -1008,7 +1008,7 @@ namespace Downloader | |||||
| else | else | ||||
| { | { | ||||
| Console.WriteLine("读取路径失败!请重新输入文件路径:"); | Console.WriteLine("读取路径失败!请重新输入文件路径:"); | ||||
| Data.ResetFilepath(Console.ReadLine()); | |||||
| Data.ResetFilepath(Console.ReadLine() ?? ""); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1058,7 +1058,7 @@ namespace Downloader | |||||
| } | } | ||||
| string json2; | string json2; | ||||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||||
| Dictionary<string, string>? dict; | |||||
| string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | ||||
| using FileStream fs = new FileStream(existpath, FileMode.Open, FileAccess.ReadWrite); | using FileStream fs = new FileStream(existpath, FileMode.Open, FileAccess.ReadWrite); | ||||
| using (StreamReader r = new StreamReader(fs)) | using (StreamReader r = new StreamReader(fs)) | ||||
| @@ -1068,7 +1068,7 @@ namespace Downloader | |||||
| { | { | ||||
| json2 += @"{""THUAI6""" + ":" + @"""2023""}"; | json2 += @"{""THUAI6""" + ":" + @"""2023""}"; | ||||
| } | } | ||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json2); | |||||
| dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json2); | |||||
| if (dict == null || !dict.ContainsKey("download")) | if (dict == null || !dict.ContainsKey("download")) | ||||
| { | { | ||||
| dict?.Add("download", "false"); | dict?.Add("download", "false"); | ||||
| @@ -1189,7 +1189,7 @@ namespace Downloader | |||||
| while (true) | while (true) | ||||
| { | { | ||||
| Console.WriteLine($"1. 更新hash.json 2. 检查更新 3.下载{ProgramName} 4.删除{ProgramName} 5.启动进程 6.移动{ProgramName}到其它路径"); | Console.WriteLine($"1. 更新hash.json 2. 检查更新 3.下载{ProgramName} 4.删除{ProgramName} 5.启动进程 6.移动{ProgramName}到其它路径"); | ||||
| string choose = Console.ReadLine(); | |||||
| string choose = Console.ReadLine() ?? ""; | |||||
| if (choose == "1") | if (choose == "1") | ||||
| { | { | ||||
| if (!CheckAlreadyDownload()) | if (!CheckAlreadyDownload()) | ||||
| @@ -1216,7 +1216,7 @@ namespace Downloader | |||||
| else | else | ||||
| { | { | ||||
| Console.WriteLine("读取路径失败!请重新输入文件路径:"); | Console.WriteLine("读取路径失败!请重新输入文件路径:"); | ||||
| Data.ResetFilepath(Console.ReadLine()); | |||||
| Data.ResetFilepath(Console.ReadLine() ?? ""); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1230,7 +1230,7 @@ namespace Downloader | |||||
| { | { | ||||
| string newpath; | string newpath; | ||||
| Console.WriteLine("请输入下载路径:"); | Console.WriteLine("请输入下载路径:"); | ||||
| newpath = Console.ReadLine(); | |||||
| newpath = Console.ReadLine() ?? ""; | |||||
| Data.ResetFilepath(newpath); | Data.ResetFilepath(newpath); | ||||
| DownloadAll(); | DownloadAll(); | ||||
| } | } | ||||
| @@ -1253,15 +1253,15 @@ namespace Downloader | |||||
| else if (choose == "6") | else if (choose == "6") | ||||
| { | { | ||||
| string newPath; | string newPath; | ||||
| newPath = Console.ReadLine(); | |||||
| newPath = Console.ReadLine() ?? ""; | |||||
| MoveProgram(newPath); | MoveProgram(newPath); | ||||
| } | } | ||||
| else if (choose == "7") | else if (choose == "7") | ||||
| { | { | ||||
| Console.WriteLine("请输入email:"); | Console.WriteLine("请输入email:"); | ||||
| string username = Console.ReadLine(); | |||||
| string username = Console.ReadLine() ?? ""; | |||||
| Console.WriteLine("请输入密码:"); | Console.WriteLine("请输入密码:"); | ||||
| string password = Console.ReadLine(); | |||||
| string password = Console.ReadLine() ?? ""; | |||||
| await web.LoginToEEsast(client, username, password); | await web.LoginToEEsast(client, username, password); | ||||
| } | } | ||||
| @@ -1285,7 +1285,8 @@ namespace Downloader | |||||
| string keyHead = "Installer/"; | string keyHead = "Installer/"; | ||||
| Tencent_cos_download downloader = new Tencent_cos_download(); | Tencent_cos_download downloader = new Tencent_cos_download(); | ||||
| string hashName = "installerHash.json"; | string hashName = "installerHash.json"; | ||||
| string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); | |||||
| string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) | |||||
| ?? throw new Exception("Failed to get current directory"); | |||||
| int result = 0; | int result = 0; | ||||
| try | try | ||||
| { | { | ||||
| @@ -1301,7 +1302,7 @@ namespace Downloader | |||||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(dir, hashName))) | using (StreamReader r = new StreamReader(System.IO.Path.Combine(dir, hashName))) | ||||
| json = r.ReadToEnd(); | json = r.ReadToEnd(); | ||||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||||
| Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| var jsonDict = Utils.TryDeserializeJson<Dictionary<string, string>>(json); | |||||
| string md5 = ""; | string md5 = ""; | ||||
| List<string> awaitUpdate = new List<string>(); | List<string> awaitUpdate = new List<string>(); | ||||
| if (jsonDict != null) | if (jsonDict != null) | ||||
| @@ -1343,15 +1344,16 @@ namespace Downloader | |||||
| static public bool SelfUpdateDismissed() | static public bool SelfUpdateDismissed() | ||||
| { | { | ||||
| string json; | string json; | ||||
| string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); | |||||
| string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) | |||||
| ?? throw new Exception("Failed to get directory!"); | |||||
| if (!File.Exists(System.IO.Path.Combine(dir, "updateList.json"))) | if (!File.Exists(System.IO.Path.Combine(dir, "updateList.json"))) | ||||
| return false; | return false; | ||||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(dir, "updateList.json"))) | using (StreamReader r = new StreamReader(System.IO.Path.Combine(dir, "updateList.json"))) | ||||
| json = r.ReadToEnd(); | json = r.ReadToEnd(); | ||||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||||
| List<string> jsonList; | |||||
| List<string>? jsonList; | |||||
| if (json != null) | if (json != null) | ||||
| jsonList = JsonConvert.DeserializeObject<List<string>>(json); | |||||
| jsonList = Utils.TryDeserializeJson<List<string>>(json); | |||||
| else | else | ||||
| return false; | return false; | ||||
| if (jsonList != null && jsonList.Contains("Dismiss")) | if (jsonList != null && jsonList.Contains("Dismiss")) | ||||
| @@ -1403,7 +1405,7 @@ namespace WebConnect | |||||
| throw new Exception("no token!"); | throw new Exception("no token!"); | ||||
| logintoken = token; | logintoken = token; | ||||
| SaveToken(); | SaveToken(); | ||||
| var info = JsonConvert.DeserializeObject<Dictionary<string, string>>(await response.Content.ReadAsStringAsync()); | |||||
| var info = Utils.DeserializeJson1<Dictionary<string, string>>(await response.Content.ReadAsStringAsync()); | |||||
| Downloader.UserInfo._id = info["_id"]; | Downloader.UserInfo._id = info["_id"]; | ||||
| Downloader.UserInfo.email = info["email"]; | Downloader.UserInfo.email = info["email"]; | ||||
| break; | break; | ||||
| @@ -1459,19 +1461,19 @@ namespace WebConnect | |||||
| { | { | ||||
| case System.Net.HttpStatusCode.OK: | case System.Net.HttpStatusCode.OK: | ||||
| var res = JsonConvert.DeserializeObject<Dictionary<string, string>>(await response.Content.ReadAsStringAsync()); | |||||
| string appid = "1255334966"; // 设置腾讯云账户的账户标识(APPID) | |||||
| string region = "ap-beijing"; // 设置一个默认的存储桶地域 | |||||
| var res = Utils.DeserializeJson1<Dictionary<string, string>>(await response.Content.ReadAsStringAsync()); | |||||
| string appid = "1255334966"; // 设置腾讯云账户的账户标识(APPID) | |||||
| string region = "ap-beijing"; // 设置一个默认的存储桶地域 | |||||
| CosXmlConfig config = new CosXmlConfig.Builder() | CosXmlConfig config = new CosXmlConfig.Builder() | ||||
| .IsHttps(true) // 设置默认 HTTPS 请求 | |||||
| .SetAppid(appid) // 设置腾讯云账户的账户标识 APPID | |||||
| .SetRegion(region) // 设置一个默认的存储桶地域 | |||||
| .SetDebugLog(true) // 显示日志 | |||||
| .Build(); // 创建 CosXmlConfig 对象 | |||||
| string tmpSecretId = res["TmpSecretId"]; //"临时密钥 SecretId"; | |||||
| string tmpSecretKey = res["TmpSecretKey"]; //"临时密钥 SecretKey"; | |||||
| string tmpToken = res["SecurityToken"]; //"临时密钥 token"; | |||||
| long tmpExpiredTime = Convert.ToInt64(res["ExpiredTime"]);//临时密钥有效截止时间,精确到秒 | |||||
| .IsHttps(true) // 设置默认 HTTPS 请求 | |||||
| .SetAppid(appid) // 设置腾讯云账户的账户标识 APPID | |||||
| .SetRegion(region) // 设置一个默认的存储桶地域 | |||||
| .SetDebugLog(true) // 显示日志 | |||||
| .Build(); // 创建 CosXmlConfig 对象 | |||||
| string tmpSecretId = res["TmpSecretId"]; //"临时密钥 SecretId"; | |||||
| string tmpSecretKey = res["TmpSecretKey"]; //"临时密钥 SecretKey"; | |||||
| string tmpToken = res["SecurityToken"]; //"临时密钥 token"; | |||||
| long tmpExpiredTime = Convert.ToInt64(res["ExpiredTime"]); //临时密钥有效截止时间,精确到秒 | |||||
| QCloudCredentialProvider cosCredentialProvider = new DefaultSessionQCloudCredentialProvider( | QCloudCredentialProvider cosCredentialProvider = new DefaultSessionQCloudCredentialProvider( | ||||
| tmpSecretId, tmpSecretKey, tmpExpiredTime, tmpToken | tmpSecretId, tmpSecretKey, tmpExpiredTime, tmpToken | ||||
| ); | ); | ||||
| @@ -1584,12 +1586,12 @@ namespace WebConnect | |||||
| { | { | ||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | json += @"{""THUAI6""" + ":" + @"""2023""}"; | ||||
| } | } | ||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| dict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||||
| if (dict.ContainsKey("token")) | if (dict.ContainsKey("token")) | ||||
| { | { | ||||
| dict.Remove("token"); | dict.Remove("token"); | ||||
| } | } | ||||
| dict?.Add("token", logintoken); | |||||
| dict.Add("token", logintoken); | |||||
| } | } | ||||
| using FileStream fs2 = new FileStream(savepath, FileMode.OpenOrCreate, FileAccess.ReadWrite); | using FileStream fs2 = new FileStream(savepath, FileMode.OpenOrCreate, FileAccess.ReadWrite); | ||||
| using StreamWriter sw = new StreamWriter(fs2); | using StreamWriter sw = new StreamWriter(fs2); | ||||
| @@ -1627,7 +1629,7 @@ namespace WebConnect | |||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | json += @"{""THUAI6""" + ":" + @"""2023""}"; | ||||
| } | } | ||||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | Dictionary<string, string> dict = new Dictionary<string, string>(); | ||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| dict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||||
| if (!dict.ContainsKey(key)) | if (!dict.ContainsKey(key)) | ||||
| { | { | ||||
| dict.Add(key, data); | dict.Add(key, data); | ||||
| @@ -1651,7 +1653,7 @@ namespace WebConnect | |||||
| } | } | ||||
| } | } | ||||
| public static string ReadJson(string key) | |||||
| public static string? ReadJson(string key) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| @@ -1664,7 +1666,7 @@ namespace WebConnect | |||||
| { | { | ||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | json += @"{""THUAI6""" + ":" + @"""2023""}"; | ||||
| } | } | ||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| dict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||||
| fs.Close(); | fs.Close(); | ||||
| sr.Close(); | sr.Close(); | ||||
| return dict[key]; | return dict[key]; | ||||
| @@ -1691,7 +1693,7 @@ namespace WebConnect | |||||
| { | { | ||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | json += @"{""THUAI6""" + ":" + @"""2023""}"; | ||||
| } | } | ||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| dict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||||
| if (!dict.ContainsKey("token")) | if (!dict.ContainsKey("token")) | ||||
| { | { | ||||
| return false; | return false; | ||||
| @@ -1745,9 +1747,9 @@ namespace WebConnect | |||||
| var response = await client.SendAsync(request); | var response = await client.SendAsync(request); | ||||
| response.EnsureSuccessStatusCode(); | response.EnsureSuccessStatusCode(); | ||||
| var info = await response.Content.ReadAsStringAsync(); | var info = await response.Content.ReadAsStringAsync(); | ||||
| var s1 = JsonConvert.DeserializeObject<Dictionary<string, object>>(info)["data"]; | |||||
| var s2 = JsonConvert.DeserializeObject<Dictionary<string, List<object>>>(s1.ToString())["contest_team_member"]; | |||||
| var sres = JsonConvert.DeserializeObject<Dictionary<string, string>>(s2[0].ToString())["team_id"]; | |||||
| var s1 = Utils.DeserializeJson1<Dictionary<string, object>>(info)["data"]; | |||||
| var s2 = Utils.DeserializeJson1<Dictionary<string, List<object>>>(s1.ToString() ?? "")["contest_team_member"]; | |||||
| var sres = Utils.DeserializeJson1<Dictionary<string, string>>(s2[0].ToString() ?? "")["team_id"]; | |||||
| return sres; | return sres; | ||||
| } | } | ||||
| async public Task<string> GetUserId(string learnNumber) | async public Task<string> GetUserId(string learnNumber) | ||||
| @@ -1773,4 +1775,20 @@ namespace WebConnect | |||||
| public string Token { get; set; } = ""; | public string Token { get; set; } = ""; | ||||
| } | } | ||||
| internal static class Utils | |||||
| { | |||||
| public static T DeserializeJson1<T>(string json) | |||||
| where T : notnull | |||||
| { | |||||
| return JsonConvert.DeserializeObject<T>(json) | |||||
| ?? throw new Exception("Failed to deserialize json."); | |||||
| } | |||||
| public static T? TryDeserializeJson<T>(string json) | |||||
| where T : notnull | |||||
| { | |||||
| return JsonConvert.DeserializeObject<T>(json); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -36,14 +36,15 @@ namespace starter.viewmodel.settings | |||||
| //Program.Tencent_cos_download.UpdateHash(); | //Program.Tencent_cos_download.UpdateHash(); | ||||
| Status = SettingsModel.Status.working; | Status = SettingsModel.Status.working; | ||||
| string CurrentDirectory = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); | |||||
| string currentDirectory = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) | |||||
| ?? throw new Exception("Fail to get current directory"); | |||||
| //MessageBox.Show("更新器工作正常"); | //MessageBox.Show("更新器工作正常"); | ||||
| if (!Program.Tencent_cos_download.SelfUpdateDismissed()) | if (!Program.Tencent_cos_download.SelfUpdateDismissed()) | ||||
| { | { | ||||
| switch (Program.Tencent_cos_download.CheckSelfVersion()) | switch (Program.Tencent_cos_download.CheckSelfVersion()) | ||||
| { | { | ||||
| case 1: | case 1: | ||||
| Process.Start(System.IO.Path.Combine(CurrentDirectory, "InstallerUpdater.exe")); | |||||
| Process.Start(System.IO.Path.Combine(currentDirectory, "InstallerUpdater.exe")); | |||||
| Environment.Exit(0); | Environment.Exit(0); | ||||
| break; | break; | ||||
| case -1: | case -1: | ||||
| @@ -156,7 +157,7 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| e.Cancel = true; | e.Cancel = true; | ||||
| MessageBox.Show("下载取消"); | MessageBox.Show("下载取消"); | ||||
| if (e.Argument.ToString().Equals("Manual")) | |||||
| if (e.Argument?.ToString()?.Equals("Manual") ?? false) | |||||
| { | { | ||||
| Status = SettingsModel.Status.menu; | Status = SettingsModel.Status.menu; | ||||
| } | } | ||||
| @@ -167,7 +168,7 @@ namespace starter.viewmodel.settings | |||||
| else | else | ||||
| { | { | ||||
| if (obj.Update()) | if (obj.Update()) | ||||
| if (e.Argument.ToString().Equals("Manual")) | |||||
| if (e.Argument?.ToString()?.Equals("Manual") ?? false) | |||||
| { | { | ||||
| e.Result = 1; | e.Result = 1; | ||||
| } | } | ||||
| @@ -598,14 +599,14 @@ namespace starter.viewmodel.settings | |||||
| return ""; | return ""; | ||||
| } | } | ||||
| private BaseCommand clickBrowseCommand; | |||||
| private BaseCommand? clickBrowseCommand; | |||||
| public BaseCommand ClickBrowseCommand | public BaseCommand ClickBrowseCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickBrowseCommand == null) | if (clickBrowseCommand == null) | ||||
| { | { | ||||
| clickBrowseCommand = new BaseCommand(new Action<object>(o => | |||||
| clickBrowseCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| Route = RouteSelectWindow("Folder"); | Route = RouteSelectWindow("Folder"); | ||||
| })); | })); | ||||
| @@ -613,14 +614,14 @@ namespace starter.viewmodel.settings | |||||
| return clickBrowseCommand; | return clickBrowseCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickConfirmCommand; | |||||
| private BaseCommand? clickConfirmCommand; | |||||
| public BaseCommand ClickConfirmCommand | public BaseCommand ClickConfirmCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickConfirmCommand == null) | if (clickConfirmCommand == null) | ||||
| { | { | ||||
| clickConfirmCommand = new BaseCommand(new Action<object>(o => | |||||
| clickConfirmCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| if (Status == SettingsModel.Status.newUser) | if (Status == SettingsModel.Status.newUser) | ||||
| { | { | ||||
| @@ -672,14 +673,14 @@ namespace starter.viewmodel.settings | |||||
| return clickConfirmCommand; | return clickConfirmCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickUpdateCommand; | |||||
| private BaseCommand? clickUpdateCommand; | |||||
| public BaseCommand ClickUpdateCommand | public BaseCommand ClickUpdateCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickUpdateCommand == null) | if (clickUpdateCommand == null) | ||||
| { | { | ||||
| clickUpdateCommand = new BaseCommand(new Action<object>(o => | |||||
| clickUpdateCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| this.RaisePropertyChanged("UpdateInfoVis"); | this.RaisePropertyChanged("UpdateInfoVis"); | ||||
| if (obj.UpdatePlanned) | if (obj.UpdatePlanned) | ||||
| @@ -719,14 +720,14 @@ namespace starter.viewmodel.settings | |||||
| return clickUpdateCommand; | return clickUpdateCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickMoveCommand; | |||||
| private BaseCommand? clickMoveCommand; | |||||
| public BaseCommand ClickMoveCommand | public BaseCommand ClickMoveCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickMoveCommand == null) | if (clickMoveCommand == null) | ||||
| { | { | ||||
| clickMoveCommand = new BaseCommand(new Action<object>(o => | |||||
| clickMoveCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| Status = SettingsModel.Status.move; | Status = SettingsModel.Status.move; | ||||
| })); | })); | ||||
| @@ -734,14 +735,14 @@ namespace starter.viewmodel.settings | |||||
| return clickMoveCommand; | return clickMoveCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickUninstCommand; | |||||
| private BaseCommand? clickUninstCommand; | |||||
| public BaseCommand ClickUninstCommand | public BaseCommand ClickUninstCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickUninstCommand == null) | if (clickUninstCommand == null) | ||||
| { | { | ||||
| clickUninstCommand = new BaseCommand(new Action<object>(o => | |||||
| clickUninstCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| UpdateInfoVis = Visibility.Collapsed; | UpdateInfoVis = Visibility.Collapsed; | ||||
| this.RaisePropertyChanged("UpdateInfoVis"); | this.RaisePropertyChanged("UpdateInfoVis"); | ||||
| @@ -767,14 +768,14 @@ namespace starter.viewmodel.settings | |||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickLoginCommand; | |||||
| private BaseCommand? clickLoginCommand; | |||||
| public BaseCommand ClickLoginCommand | public BaseCommand ClickLoginCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickLoginCommand == null) | if (clickLoginCommand == null) | ||||
| { | { | ||||
| clickLoginCommand = new BaseCommand(new Action<object>(async o => | |||||
| clickLoginCommand = new BaseCommand(new Action<object?>(async o => | |||||
| { | { | ||||
| switch (await obj.Login()) | switch (await obj.Login()) | ||||
| { | { | ||||
| @@ -813,14 +814,14 @@ namespace starter.viewmodel.settings | |||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickLaunchCommand; | |||||
| private BaseCommand? clickLaunchCommand; | |||||
| public BaseCommand ClickLaunchCommand | public BaseCommand ClickLaunchCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickLaunchCommand == null) | if (clickLaunchCommand == null) | ||||
| { | { | ||||
| clickLaunchCommand = new BaseCommand(new Action<object>(o => | |||||
| clickLaunchCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| if (obj.UpdatePlanned) | if (obj.UpdatePlanned) | ||||
| { | { | ||||
| @@ -840,14 +841,14 @@ namespace starter.viewmodel.settings | |||||
| return clickLaunchCommand; | return clickLaunchCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickEditCommand; | |||||
| private BaseCommand? clickEditCommand; | |||||
| public BaseCommand ClickEditCommand | public BaseCommand ClickEditCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickEditCommand == null) | if (clickEditCommand == null) | ||||
| { | { | ||||
| clickEditCommand = new BaseCommand(new Action<object>(o => | |||||
| clickEditCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| Status = SettingsModel.Status.menu; | Status = SettingsModel.Status.menu; | ||||
| if (obj.UpdatePlanned) | if (obj.UpdatePlanned) | ||||
| @@ -858,14 +859,14 @@ namespace starter.viewmodel.settings | |||||
| return clickEditCommand; | return clickEditCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickBackCommand; | |||||
| private BaseCommand? clickBackCommand; | |||||
| public BaseCommand ClickBackCommand | public BaseCommand ClickBackCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickBackCommand == null) | if (clickBackCommand == null) | ||||
| { | { | ||||
| clickBackCommand = new BaseCommand(new Action<object>(o => | |||||
| clickBackCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| UpdateInfoVis = Visibility.Collapsed; | UpdateInfoVis = Visibility.Collapsed; | ||||
| this.RaisePropertyChanged("UpdateInfoVis"); | this.RaisePropertyChanged("UpdateInfoVis"); | ||||
| @@ -878,14 +879,14 @@ namespace starter.viewmodel.settings | |||||
| return clickBackCommand; | return clickBackCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickUploadCommand; | |||||
| private BaseCommand? clickUploadCommand; | |||||
| public BaseCommand ClickUploadCommand | public BaseCommand ClickUploadCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickUploadCommand == null) | if (clickUploadCommand == null) | ||||
| { | { | ||||
| clickUploadCommand = new BaseCommand(new Action<object>(async o => | |||||
| clickUploadCommand = new BaseCommand(new Action<object?>(async o => | |||||
| { | { | ||||
| if (obj.UploadReady) | if (obj.UploadReady) | ||||
| { | { | ||||
| @@ -953,14 +954,14 @@ namespace starter.viewmodel.settings | |||||
| return clickUploadCommand; | return clickUploadCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickAboutUploadCommand; | |||||
| private BaseCommand? clickAboutUploadCommand; | |||||
| public BaseCommand ClickAboutUploadCommand | public BaseCommand ClickAboutUploadCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickAboutUploadCommand == null) | if (clickAboutUploadCommand == null) | ||||
| { | { | ||||
| clickAboutUploadCommand = new BaseCommand(new Action<object>(o => | |||||
| clickAboutUploadCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| if (obj.UploadReady) | if (obj.UploadReady) | ||||
| { | { | ||||
| @@ -987,14 +988,14 @@ namespace starter.viewmodel.settings | |||||
| return clickAboutUploadCommand; | return clickAboutUploadCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickExitCommand; | |||||
| private BaseCommand? clickExitCommand; | |||||
| public BaseCommand ClickExitCommand | public BaseCommand ClickExitCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickExitCommand == null) | if (clickExitCommand == null) | ||||
| { | { | ||||
| clickExitCommand = new BaseCommand(new Action<object>(o => | |||||
| clickExitCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| Application.Current.Shutdown(); | Application.Current.Shutdown(); | ||||
| })); | })); | ||||
| @@ -1002,14 +1003,14 @@ namespace starter.viewmodel.settings | |||||
| return clickExitCommand; | return clickExitCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickShiftLanguageCommand; | |||||
| private BaseCommand? clickShiftLanguageCommand; | |||||
| public BaseCommand ClickShiftLanguageCommand | public BaseCommand ClickShiftLanguageCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickShiftLanguageCommand == null) | if (clickShiftLanguageCommand == null) | ||||
| { | { | ||||
| clickShiftLanguageCommand = new BaseCommand(new Action<object>(o => | |||||
| clickShiftLanguageCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | ||||
| obj.launchLanguage = SettingsModel.LaunchLanguage.python; | obj.launchLanguage = SettingsModel.LaunchLanguage.python; | ||||
| @@ -1023,14 +1024,14 @@ namespace starter.viewmodel.settings | |||||
| return clickShiftLanguageCommand; | return clickShiftLanguageCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickReadCommand; | |||||
| private BaseCommand? clickReadCommand; | |||||
| public BaseCommand ClickReadCommand | public BaseCommand ClickReadCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickReadCommand == null) | if (clickReadCommand == null) | ||||
| { | { | ||||
| clickReadCommand = new BaseCommand(new Action<object>(o => | |||||
| clickReadCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| if (!Directory.Exists(Route + "/THUAI6/win")) | if (!Directory.Exists(Route + "/THUAI6/win")) | ||||
| { | { | ||||
| @@ -1050,14 +1051,14 @@ namespace starter.viewmodel.settings | |||||
| return clickReadCommand; | return clickReadCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickSwitchOSCommand; | |||||
| private BaseCommand? clickSwitchOSCommand; | |||||
| public BaseCommand ClickSwitchOSCommand | public BaseCommand ClickSwitchOSCommand | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickSwitchOSCommand == null) | if (clickSwitchOSCommand == null) | ||||
| { | { | ||||
| clickSwitchOSCommand = new BaseCommand(new Action<object>(o => | |||||
| clickSwitchOSCommand = new BaseCommand(new Action<object?>(o => | |||||
| { | { | ||||
| switch (obj.usingOS) | switch (obj.usingOS) | ||||
| { | { | ||||
| @@ -5,8 +5,16 @@ | |||||
| <TargetFramework>net6.0-windows</TargetFramework> | <TargetFramework>net6.0-windows</TargetFramework> | ||||
| <Nullable>enable</Nullable> | <Nullable>enable</Nullable> | ||||
| <UseWPF>true</UseWPF> | <UseWPF>true</UseWPF> | ||||
| <PackageIcon>eesast_software_trans.png</PackageIcon> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | |||||
| <None Include="..\..\resource\eesast_software_trans.png"> | |||||
| <Pack>True</Pack> | |||||
| <PackagePath>\</PackagePath> | |||||
| </None> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||||
| <PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.34"> | <PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.34"> | ||||
| @@ -5,7 +5,9 @@ | |||||
| xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||||
| xmlns:local="clr-namespace:InstallerUpdater" | xmlns:local="clr-namespace:InstallerUpdater" | ||||
| mc:Ignorable="d" | mc:Ignorable="d" | ||||
| Title="MainWindow" Height="180" Width="300"> | |||||
| Title="MainWindow" Height="180" Width="300" | |||||
| ResizeMode="CanMinimize" | |||||
| > | |||||
| <Grid> | <Grid> | ||||
| <Grid.RowDefinitions> | <Grid.RowDefinitions> | ||||
| <RowDefinition Height="40"/> | <RowDefinition Height="40"/> | ||||
| @@ -18,7 +18,8 @@ namespace Program | |||||
| { | { | ||||
| class Updater | class Updater | ||||
| { | { | ||||
| public static string Dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); | |||||
| public static string Dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) | |||||
| ?? throw new Exception("Cannot get current directory"); | |||||
| public static string InstallerName = "Installer.exe"; | public static string InstallerName = "Installer.exe"; | ||||
| public static string jsonKey = "installerHash.json"; | public static string jsonKey = "installerHash.json"; | ||||
| public static string KeyHead = "Installer/"; | public static string KeyHead = "Installer/"; | ||||
| @@ -31,7 +32,8 @@ namespace Program | |||||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(Dir, "updateList.json"))) | using (StreamReader r = new StreamReader(System.IO.Path.Combine(Dir, "updateList.json"))) | ||||
| json = r.ReadToEnd(); | json = r.ReadToEnd(); | ||||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||||
| List<string> jsonList = JsonConvert.DeserializeObject<List<string>>(json); | |||||
| List<string> jsonList = JsonConvert.DeserializeObject<List<string>>(json) | |||||
| ?? throw new Exception("Failed to deserialize json!"); | |||||
| foreach (string todo in jsonList) | foreach (string todo in jsonList) | ||||
| { | { | ||||
| if (!todo.Equals("None")) | if (!todo.Equals("None")) | ||||
| @@ -41,14 +43,14 @@ namespace Program | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| catch (IOException) | |||||
| catch (IOException ex) | |||||
| { | { | ||||
| MessageBox.Show("下载器本体未能成功关闭"); | |||||
| MessageBox.Show($"下载器本体未能成功关闭:\n{ex}"); | |||||
| return false; | return false; | ||||
| } | } | ||||
| catch | |||||
| catch (Exception ex) | |||||
| { | { | ||||
| MessageBox.Show("尝试下载时出现问题"); | |||||
| MessageBox.Show($"尝试下载时出现问题:\n{ex}\n{ex.StackTrace}"); | |||||
| return false; | return false; | ||||
| } | } | ||||
| return true; | return true; | ||||
| @@ -67,7 +69,8 @@ namespace Program | |||||
| json += @"{""None""}"; | json += @"{""None""}"; | ||||
| } | } | ||||
| List<string> ls = new List<string>(); | List<string> ls = new List<string>(); | ||||
| ls = JsonConvert.DeserializeObject<List<string>>(json); | |||||
| ls = JsonConvert.DeserializeObject<List<string>>(json) | |||||
| ?? throw new Exception("Failed to deserialize json!"); | |||||
| if (!ls.Contains("Dismiss")) | if (!ls.Contains("Dismiss")) | ||||
| { | { | ||||
| ls.Add("Dismiss"); | ls.Add("Dismiss"); | ||||
| @@ -114,9 +117,10 @@ namespace Program | |||||
| // 创建存储桶 | // 创建存储桶 | ||||
| try | try | ||||
| { | { | ||||
| string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID | |||||
| string localDir = System.IO.Path.GetDirectoryName(download_dir); // 本地文件夹 | |||||
| string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 | |||||
| string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID | |||||
| string localDir = System.IO.Path.GetDirectoryName(download_dir) // 本地文件夹 | |||||
| ?? throw new Exception("Failed to get directory name!"); | |||||
| string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 | |||||
| GetObjectRequest request = new GetObjectRequest(bucket, key, localDir, localFileName); | GetObjectRequest request = new GetObjectRequest(bucket, key, localDir, localFileName); | ||||
| Dictionary<string, string> test = request.GetRequestHeaders(); | Dictionary<string, string> test = request.GetRequestHeaders(); | ||||
| @@ -12,10 +12,6 @@ | |||||
| <None Remove="Logo.png" /> | <None Remove="Logo.png" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | |||||
| <Content Include="eesast_software_trans_enlarged.ico" /> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | <PackageReference Include="CommandLineParser" Version="2.9.1" /> | ||||
| <PackageReference Include="FrameRateTask" Version="1.2.0" /> | <PackageReference Include="FrameRateTask" Version="1.2.0" /> | ||||
| @@ -5,7 +5,8 @@ | |||||
| xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||||
| xmlns:local="clr-namespace:Client" | xmlns:local="clr-namespace:Client" | ||||
| mc:Ignorable="d" | mc:Ignorable="d" | ||||
| Title="ClientVI" Height="738" Width="850" KeyDown="KeyBoardControl" Background="White" ResizeMode="CanResizeWithGrip" WindowStyle="None" MouseLeftButtonDown="DragWindow" MouseDoubleClick="Attack" MinHeight="738" MinWidth="1100"> | |||||
| Title="ClientVI" Height="738" Width="850" KeyDown="KeyBoardControl" Background="White" ResizeMode="CanResizeWithGrip" WindowStyle="None" MouseLeftButtonDown="DragWindow" MouseDoubleClick="Attack" MinHeight="738" MinWidth="1100" | |||||
| SizeChanged="ZoomMap"> | |||||
| <Window.Resources> | <Window.Resources> | ||||
| <ImageBrush x:Key="Logo" ImageSource="Logo.png"/> | <ImageBrush x:Key="Logo" ImageSource="Logo.png"/> | ||||
| @@ -50,7 +51,7 @@ | |||||
| <TransformGroup> | <TransformGroup> | ||||
| <ScaleTransform/> | <ScaleTransform/> | ||||
| <SkewTransform/> | <SkewTransform/> | ||||
| <RotateTransform Angle="-0.377"/> | |||||
| <RotateTransform Angle="0"/> | |||||
| <TranslateTransform/> | <TranslateTransform/> | ||||
| </TransformGroup> | </TransformGroup> | ||||
| </Label.RenderTransform> | </Label.RenderTransform> | ||||
| @@ -20,6 +20,7 @@ using Playback; | |||||
| using CommandLine; | using CommandLine; | ||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| using System.Diagnostics.CodeAnalysis; | |||||
| // 目前MainWindow还未复现的功能: | // 目前MainWindow还未复现的功能: | ||||
| // private void ClickToSetMode(object sender, RoutedEventArgs e) | // private void ClickToSetMode(object sender, RoutedEventArgs e) | ||||
| @@ -34,8 +35,6 @@ namespace Client | |||||
| { | { | ||||
| public MainWindow() | public MainWindow() | ||||
| { | { | ||||
| unitHeight = unitWidth = unit = 13; | |||||
| bonusflag = true; | |||||
| timer = new DispatcherTimer | timer = new DispatcherTimer | ||||
| { | { | ||||
| Interval = new TimeSpan(50000) // 每50ms刷新一次 | Interval = new TimeSpan(50000) // 每50ms刷新一次 | ||||
| @@ -60,9 +59,14 @@ namespace Client | |||||
| listOfGate = new List<MessageOfGate>(); | listOfGate = new List<MessageOfGate>(); | ||||
| listOfHiddenGate = new List<MessageOfHiddenGate>(); | listOfHiddenGate = new List<MessageOfHiddenGate>(); | ||||
| WindowStartupLocation = WindowStartupLocation.CenterScreen; | WindowStartupLocation = WindowStartupLocation.CenterScreen; | ||||
| unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50; | |||||
| unitFontsize = unit / 13; | |||||
| unitHeight = UpperLayerOfMap.ActualHeight / 50; | |||||
| unitWidth = UpperLayerOfMap.ActualWidth / 50; | |||||
| ReactToCommandline(); | ReactToCommandline(); | ||||
| } | } | ||||
| [MemberNotNull(nameof(StatusBarsOfSurvivor), nameof(StatusBarsOfHunter), nameof(StatusBarsOfCircumstance))] | |||||
| private void SetStatusBar() | private void SetStatusBar() | ||||
| { | { | ||||
| StatusBarsOfSurvivor = new StatusBarOfSurvivor[4]; | StatusBarsOfSurvivor = new StatusBarOfSurvivor[4]; | ||||
| @@ -194,6 +198,7 @@ namespace Client | |||||
| 0 => PlayerType.NullPlayerType, | 0 => PlayerType.NullPlayerType, | ||||
| 1 => PlayerType.StudentPlayer, | 1 => PlayerType.StudentPlayer, | ||||
| 2 => PlayerType.TrickerPlayer, | 2 => PlayerType.TrickerPlayer, | ||||
| _ => PlayerType.NullPlayerType | |||||
| }; | }; | ||||
| playerMsg.PlayerType = playerType; | playerMsg.PlayerType = playerType; | ||||
| if (Convert.ToInt64(comInfo[3]) == 1) | if (Convert.ToInt64(comInfo[3]) == 1) | ||||
| @@ -268,9 +273,9 @@ namespace Client | |||||
| { | { | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 10, | |||||
| Width = 20, | |||||
| Height = 20, | |||||
| FontSize = 7 * unitFontsize, | |||||
| Width = unitWidth, | |||||
| Height = unitHeight, | |||||
| Text = text, | Text = text, | ||||
| HorizontalAlignment = HorizontalAlignment.Left, | HorizontalAlignment = HorizontalAlignment.Left, | ||||
| VerticalAlignment = VerticalAlignment.Top, | VerticalAlignment = VerticalAlignment.Top, | ||||
| @@ -282,37 +287,23 @@ namespace Client | |||||
| UpperLayerOfMap.Children.Add(icon); | UpperLayerOfMap.Children.Add(icon); | ||||
| } | } | ||||
| private void ZoomMap() | |||||
| { | |||||
| for (int i = 0; i < 50; i++) | |||||
| { | |||||
| for (int j = 0; j < 50; j++) | |||||
| { | |||||
| if (mapPatches[i, j] != null && (mapPatches[i, j].Width != UpperLayerOfMap.ActualWidth / 50 || mapPatches[i, j].Height != UpperLayerOfMap.ActualHeight / 50)) | |||||
| { | |||||
| mapPatches[i, j].Width = UpperLayerOfMap.ActualWidth / 50; | |||||
| mapPatches[i, j].Height = UpperLayerOfMap.ActualHeight / 50; | |||||
| mapPatches[i, j].HorizontalAlignment = HorizontalAlignment.Left; | |||||
| mapPatches[i, j].VerticalAlignment = VerticalAlignment.Top; | |||||
| mapPatches[i, j].Margin = new Thickness(UpperLayerOfMap.ActualWidth / 50 * j, UpperLayerOfMap.ActualHeight / 50 * i, 0, 0); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| private void ZoomMapAtFirst() | |||||
| private void ZoomMap(object sender, SizeChangedEventArgs e) | |||||
| { | { | ||||
| unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50; | |||||
| unitFontsize = unit / 13; | |||||
| unitHeight = UpperLayerOfMap.ActualHeight / 50; | |||||
| unitWidth = UpperLayerOfMap.ActualWidth / 50; | |||||
| for (int i = 0; i < 50; i++) | for (int i = 0; i < 50; i++) | ||||
| { | { | ||||
| for (int j = 0; j < 50; j++) | for (int j = 0; j < 50; j++) | ||||
| { | { | ||||
| if (mapPatches[i, j] != null) | if (mapPatches[i, j] != null) | ||||
| { | { | ||||
| mapPatches[i, j].Width = UpperLayerOfMap.ActualWidth / 50; | |||||
| mapPatches[i, j].Height = UpperLayerOfMap.ActualHeight / 50; | |||||
| mapPatches[i, j].Width = unitWidth; | |||||
| mapPatches[i, j].Height = unitHeight; | |||||
| mapPatches[i, j].HorizontalAlignment = HorizontalAlignment.Left; | mapPatches[i, j].HorizontalAlignment = HorizontalAlignment.Left; | ||||
| mapPatches[i, j].VerticalAlignment = VerticalAlignment.Top; | mapPatches[i, j].VerticalAlignment = VerticalAlignment.Top; | ||||
| mapPatches[i, j].Margin = new Thickness(UpperLayerOfMap.ActualWidth / 50 * j, UpperLayerOfMap.ActualHeight / 50 * i, 0, 0); | |||||
| mapPatches[i, j].Margin = new Thickness(unitWidth * j, unitHeight * i, 0, 0); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -330,7 +321,7 @@ namespace Client | |||||
| Height = unitHeight, | Height = unitHeight, | ||||
| HorizontalAlignment = HorizontalAlignment.Left, | HorizontalAlignment = HorizontalAlignment.Left, | ||||
| VerticalAlignment = VerticalAlignment.Top, | VerticalAlignment = VerticalAlignment.Top, | ||||
| Margin = new Thickness(Width * (j), Height * (i), 0, 0) | |||||
| Margin = new Thickness(unitWidth * j, unitHeight * i, 0, 0)//unitWidth cannot be replaced by Width | |||||
| }; | }; | ||||
| switch (defaultMap[i, j]) | switch (defaultMap[i, j]) | ||||
| { | { | ||||
| @@ -697,7 +688,7 @@ namespace Client | |||||
| MaxButton.Content = "🗖"; | MaxButton.Content = "🗖"; | ||||
| foreach (var obj in listOfHuman) | foreach (var obj in listOfHuman) | ||||
| { | { | ||||
| if (obj.PlayerId < GameData.numOfStudent && obj.StudentType != StudentType.Robot) | |||||
| if (!isDataFixed[obj.PlayerId] && obj.PlayerId < GameData.numOfStudent && obj.StudentType != StudentType.Robot) | |||||
| { | { | ||||
| IStudentType occupation = (IStudentType)OccupationFactory.FindIOccupation(Transformation.ToStudentType(obj.StudentType)); | IStudentType occupation = (IStudentType)OccupationFactory.FindIOccupation(Transformation.ToStudentType(obj.StudentType)); | ||||
| totalLife[obj.PlayerId] = occupation.MaxHp; | totalLife[obj.PlayerId] = occupation.MaxHp; | ||||
| @@ -709,60 +700,44 @@ namespace Client | |||||
| coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD; | coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD; | ||||
| ++i; | ++i; | ||||
| } | } | ||||
| isDataFixed[obj.PlayerId] = true; | |||||
| } | } | ||||
| } | } | ||||
| foreach (var obj in listOfButcher) | foreach (var obj in listOfButcher) | ||||
| { | { | ||||
| IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType)); | |||||
| int j = 0; | |||||
| foreach (var skill in occupation1.ListOfIActiveSkill) | |||||
| if (!isDataFixed[obj.PlayerId]) | |||||
| { | { | ||||
| var iActiveSkill = SkillFactory.FindIActiveSkill(skill); | |||||
| coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD; | |||||
| ++j; | |||||
| IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType)); | |||||
| int j = 0; | |||||
| foreach (var skill in occupation1.ListOfIActiveSkill) | |||||
| { | |||||
| var iActiveSkill = SkillFactory.FindIActiveSkill(skill); | |||||
| coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD; | |||||
| ++j; | |||||
| } | |||||
| isDataFixed[obj.PlayerId] = true; | |||||
| } | } | ||||
| } | } | ||||
| if (StatusBarsOfSurvivor != null) | |||||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||||
| { | { | ||||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||||
| { | |||||
| StatusBarsOfSurvivor[i].SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650); | |||||
| StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime); | |||||
| } | |||||
| StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime); | |||||
| } | } | ||||
| if (StatusBarsOfHunter != null) | |||||
| StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime); | |||||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||||
| { | { | ||||
| StatusBarsOfHunter.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650); | |||||
| StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime); | |||||
| StatusBarsOfSurvivor[i].SetFontSize(12 * unitFontsize); | |||||
| } | } | ||||
| if (StatusBarsOfCircumstance != null) | |||||
| StatusBarsOfCircumstance.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650); | |||||
| // 完成窗口信息更新 | |||||
| StatusBarsOfHunter.SetFontSize(12 * unitFontsize); | |||||
| StatusBarsOfCircumstance.SetFontSize(12 * unitFontsize); | |||||
| if (!isClientStocked) | if (!isClientStocked) | ||||
| { | { | ||||
| unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50; | |||||
| unitHeight = UpperLayerOfMap.ActualHeight / 50; | |||||
| unitWidth = UpperLayerOfMap.ActualWidth / 50; | |||||
| try | try | ||||
| { | { | ||||
| // if (log != null) | |||||
| //{ | |||||
| // string temp = ""; | |||||
| // for (int i = 0; i < dataDict[GameObjType.Character].Count; i++) | |||||
| // { | |||||
| // temp += Convert.ToString(dataDict[GameObjType.Character][i].MessageOfCharacter.TeamID) + "\n"; | |||||
| // } | |||||
| // log.Content = temp; | |||||
| // } | |||||
| UpperLayerOfMap.Children.Clear(); | UpperLayerOfMap.Children.Clear(); | ||||
| // if ((communicator == null || !communicator.Client.IsConnected) && !isPlaybackMode) | |||||
| //{ | |||||
| // UnderLayerOfMap.Children.Clear(); | |||||
| // throw new Exception("Client is unconnected."); | |||||
| // } | |||||
| // else | |||||
| //{ | |||||
| foreach (var data in listOfAll) | foreach (var data in listOfAll) | ||||
| { | { | ||||
| StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode); | StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode); | ||||
| @@ -770,7 +745,6 @@ namespace Client | |||||
| if (!hasDrawed && mapFlag) | if (!hasDrawed && mapFlag) | ||||
| { | { | ||||
| DrawMap(); | DrawMap(); | ||||
| ZoomMapAtFirst(); | |||||
| } | } | ||||
| foreach (var data in listOfHuman) | foreach (var data in listOfHuman) | ||||
| { | { | ||||
| @@ -791,7 +765,7 @@ namespace Client | |||||
| icon.Fill = Brushes.Gray; | icon.Fill = Brushes.Gray; | ||||
| TextBox num = new() | TextBox num = new() | ||||
| { | { | ||||
| FontSize = 7 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 7 * unitFontsize, | |||||
| Width = 2 * radiusTimes * unitWidth, | Width = 2 * radiusTimes * unitWidth, | ||||
| Height = 2 * radiusTimes * unitHeight, | Height = 2 * radiusTimes * unitHeight, | ||||
| Text = Convert.ToString(data.PlayerId), | Text = Convert.ToString(data.PlayerId), | ||||
| @@ -943,7 +917,7 @@ namespace Client | |||||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfFixedGenerator); | int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfFixedGenerator); | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 8 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 8 * unitFontsize, | |||||
| Width = unitWidth, | Width = unitWidth, | ||||
| Height = unitHeight, | Height = unitHeight, | ||||
| Text = Convert.ToString(deg), | Text = Convert.ToString(deg), | ||||
| @@ -965,7 +939,7 @@ namespace Client | |||||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedChest); | int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedChest); | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 8 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 8 * unitFontsize, | |||||
| Width = unitWidth, | Width = unitWidth, | ||||
| Height = unitHeight, | Height = unitHeight, | ||||
| Text = Convert.ToString(deg), | Text = Convert.ToString(deg), | ||||
| @@ -987,7 +961,7 @@ namespace Client | |||||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedDoorway); | int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedDoorway); | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 8 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 8 * unitFontsize, | |||||
| Width = unitWidth, | Width = unitWidth, | ||||
| Height = unitHeight, | Height = unitHeight, | ||||
| Text = Convert.ToString(deg), | Text = Convert.ToString(deg), | ||||
| @@ -1009,7 +983,7 @@ namespace Client | |||||
| { | { | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 9 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 9 * unitFontsize, | |||||
| Width = unitWidth, | Width = unitWidth, | ||||
| Height = unitHeight, | Height = unitHeight, | ||||
| HorizontalAlignment = HorizontalAlignment.Left, | HorizontalAlignment = HorizontalAlignment.Left, | ||||
| @@ -1042,7 +1016,7 @@ namespace Client | |||||
| isEmergencyOpened = true; | isEmergencyOpened = true; | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 9 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 9 * unitFontsize, | |||||
| Width = unitWidth, | Width = unitWidth, | ||||
| Height = unitHeight, | Height = unitHeight, | ||||
| Text = Convert.ToString("🔓"), | Text = Convert.ToString("🔓"), | ||||
| @@ -1056,8 +1030,6 @@ namespace Client | |||||
| UpperLayerOfMap.Children.Add(icon); | UpperLayerOfMap.Children.Add(icon); | ||||
| } | } | ||||
| } | } | ||||
| //} | |||||
| ZoomMap(); | |||||
| } | } | ||||
| catch (Exception exc) | catch (Exception exc) | ||||
| { | { | ||||
| @@ -1076,6 +1048,11 @@ namespace Client | |||||
| { | { | ||||
| if (!isPlaybackMode && !isSpectatorMode) | if (!isPlaybackMode && !isSpectatorMode) | ||||
| { | { | ||||
| if (client is null) | |||||
| { | |||||
| return; | |||||
| } | |||||
| switch (e.Key) | switch (e.Key) | ||||
| { | { | ||||
| case Key.W: | case Key.W: | ||||
| @@ -1257,6 +1234,10 @@ namespace Client | |||||
| { | { | ||||
| if (!isPlaybackMode && !isSpectatorMode) | if (!isPlaybackMode && !isSpectatorMode) | ||||
| { | { | ||||
| if (client is null) | |||||
| { | |||||
| return; | |||||
| } | |||||
| if (humanOrButcher && human != null) | if (humanOrButcher && human != null) | ||||
| { | { | ||||
| AttackMsg msgJ = new() | AttackMsg msgJ = new() | ||||
| @@ -1407,7 +1388,7 @@ namespace Client | |||||
| // 以下为Mainwindow自定义属性 | // 以下为Mainwindow自定义属性 | ||||
| private readonly DispatcherTimer timer; // 定时器 | private readonly DispatcherTimer timer; // 定时器 | ||||
| private long counter; // 预留的取时间变量 | private long counter; // 预留的取时间变量 | ||||
| AvailableService.AvailableServiceClient client; | |||||
| AvailableService.AvailableServiceClient? client; | |||||
| AsyncServerStreamingCall<MessageToClient>? responseStream; | AsyncServerStreamingCall<MessageToClient>? responseStream; | ||||
| private StatusBarOfSurvivor[] StatusBarsOfSurvivor; | private StatusBarOfSurvivor[] StatusBarsOfSurvivor; | ||||
| private StatusBarOfHunter StatusBarsOfHunter; | private StatusBarOfHunter StatusBarsOfHunter; | ||||
| @@ -1440,7 +1421,6 @@ namespace Client | |||||
| private MessageOfTricker? butcher = null; | private MessageOfTricker? butcher = null; | ||||
| private bool humanOrButcher;//true for human | private bool humanOrButcher;//true for human | ||||
| private bool bonusflag; | |||||
| private bool mapFlag = false; | private bool mapFlag = false; | ||||
| private bool hasDrawed = false; | private bool hasDrawed = false; | ||||
| public int[,] defaultMap = new int[,] { | public int[,] defaultMap = new int[,] { | ||||
| @@ -1502,7 +1482,8 @@ namespace Client | |||||
| bool isSpectatorMode = false; | bool isSpectatorMode = false; | ||||
| bool isEmergencyOpened = false; | bool isEmergencyOpened = false; | ||||
| bool isEmergencyDrawed = false; | bool isEmergencyDrawed = false; | ||||
| bool isDataFixed = false; | |||||
| bool[] isDataFixed = new bool[5] { false, false, false, false, false }; | |||||
| double unitFontsize = 10; | |||||
| const double radiusTimes = 1.0 * Preparation.Utility.GameData.characterRadius / Preparation.Utility.GameData.numOfPosGridPerCell; | const double radiusTimes = 1.0 * Preparation.Utility.GameData.characterRadius / Preparation.Utility.GameData.numOfPosGridPerCell; | ||||
| const double bulletRadiusTimes = 1.0 * GameData.bulletRadius / Preparation.Utility.GameData.numOfPosGridPerCell; | const double bulletRadiusTimes = 1.0 * GameData.bulletRadius / Preparation.Utility.GameData.numOfPosGridPerCell; | ||||
| private int[] totalLife = new int[4] { 100, 100, 100, 100 }, totalDeath = new int[4] { 100, 100, 100, 100 }; | private int[] totalLife = new int[4] { 100, 100, 100, 100 }, totalDeath = new int[4] { 100, 100, 100, 100 }; | ||||
| @@ -82,19 +82,19 @@ namespace Client | |||||
| state.Text = "Quit"; | state.Text = "Quit"; | ||||
| break; | break; | ||||
| case PlayerState.Treated: | case PlayerState.Treated: | ||||
| state.Text = "Treated"; | |||||
| state.Text = "Encouraged"; | |||||
| break; | break; | ||||
| case PlayerState.Rescued: | case PlayerState.Rescued: | ||||
| state.Text = "Rescued"; | |||||
| state.Text = "Roused"; | |||||
| break; | break; | ||||
| case PlayerState.Stunned: | case PlayerState.Stunned: | ||||
| state.Text = "Stunned"; | state.Text = "Stunned"; | ||||
| break; | break; | ||||
| case PlayerState.Treating: | case PlayerState.Treating: | ||||
| state.Text = "Treating"; | |||||
| state.Text = "Encouraging"; | |||||
| break; | break; | ||||
| case PlayerState.Rescuing: | case PlayerState.Rescuing: | ||||
| state.Text = "Rescuing"; | |||||
| state.Text = "Rousing"; | |||||
| break; | break; | ||||
| case PlayerState.Swinging: | case PlayerState.Swinging: | ||||
| state.Text = "Swinging"; | state.Text = "Swinging"; | ||||
| @@ -87,19 +87,19 @@ namespace Client | |||||
| status.Text = "💀" + "\nQuit"; | status.Text = "💀" + "\nQuit"; | ||||
| break; | break; | ||||
| case PlayerState.Treated: | case PlayerState.Treated: | ||||
| status.Text = "♥: " + Convert.ToString(life) + "\nTreated, " + Convert.ToString(perLife) + "%"; | |||||
| status.Text = "♥: " + Convert.ToString(life) + "\nEncouraged, " + Convert.ToString(perLife) + "%"; | |||||
| break; | break; | ||||
| case PlayerState.Rescued: | case PlayerState.Rescued: | ||||
| status.Text = "💀: " + Convert.ToString(death) + "\nRescued, " + Convert.ToString(perDeath) + "%"; | |||||
| status.Text = "💀: " + Convert.ToString(death) + "\nRoused, " + Convert.ToString(perDeath) + "%"; | |||||
| break; | break; | ||||
| case PlayerState.Stunned: | case PlayerState.Stunned: | ||||
| status.Text = "♥: " + Convert.ToString(life) + "\nStunned, " + Convert.ToString(perLife) + "%"; | status.Text = "♥: " + Convert.ToString(life) + "\nStunned, " + Convert.ToString(perLife) + "%"; | ||||
| break; | break; | ||||
| case PlayerState.Treating: | case PlayerState.Treating: | ||||
| status.Text = "♥: " + Convert.ToString(life) + "\nTreating, " + Convert.ToString(perLife) + "%"; | |||||
| status.Text = "♥: " + Convert.ToString(life) + "\nEncouraging, " + Convert.ToString(perLife) + "%"; | |||||
| break; | break; | ||||
| case PlayerState.Rescuing: | case PlayerState.Rescuing: | ||||
| status.Text = "♥: " + Convert.ToString(life) + "\nRescuing, " + Convert.ToString(perLife) + "%"; | |||||
| status.Text = "♥: " + Convert.ToString(life) + "\nRousing, " + Convert.ToString(perLife) + "%"; | |||||
| break; | break; | ||||
| case PlayerState.Swinging: | case PlayerState.Swinging: | ||||
| status.Text = "♥: " + Convert.ToString(life) + "\nSwinging, " + Convert.ToString(perLife) + "%"; | status.Text = "♥: " + Convert.ToString(life) + "\nSwinging, " + Convert.ToString(perLife) + "%"; | ||||
| @@ -1,6 +1,5 @@ | |||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| using System; | |||||
| namespace GameClass.GameObj | namespace GameClass.GameObj | ||||
| { | { | ||||
| @@ -23,7 +23,6 @@ namespace GameClass.GameObj | |||||
| private int openStartTime = 0; | private int openStartTime = 0; | ||||
| public int OpenStartTime => openStartTime; | public int OpenStartTime => openStartTime; | ||||
| private Character? whoOpen = null; | private Character? whoOpen = null; | ||||
| public Character? WhoOpen => whoOpen; | public Character? WhoOpen => whoOpen; | ||||
| public void Open(int startTime, Character character) | public void Open(int startTime, Character character) | ||||
| { | { | ||||
| @@ -3,7 +3,6 @@ using System.Threading; | |||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| using System; | using System; | ||||
| using GameClass.GameObj; | |||||
| namespace GameClass.GameObj | namespace GameClass.GameObj | ||||
| { | { | ||||
| @@ -13,29 +12,47 @@ namespace GameClass.GameObj | |||||
| private readonly Dictionary<uint, XY> birthPointList; // 出生点列表 | private readonly Dictionary<uint, XY> birthPointList; // 出生点列表 | ||||
| public Dictionary<uint, XY> BirthPointList => birthPointList; | public Dictionary<uint, XY> BirthPointList => birthPointList; | ||||
| private object lockForNum = new(); | |||||
| private readonly object lockForNum = new(); | |||||
| private void WhenStudentNumChange() | private void WhenStudentNumChange() | ||||
| { | { | ||||
| if (numOfDeceasedStudent + numOfEscapedStudent == GameData.numOfStudent) | if (numOfDeceasedStudent + numOfEscapedStudent == GameData.numOfStudent) | ||||
| { | { | ||||
| Timer.IsGaming = false; | Timer.IsGaming = false; | ||||
| return; | |||||
| } | } | ||||
| if (GameData.numOfStudent - NumOfDeceasedStudent - NumOfEscapedStudent == 1) | |||||
| if (GameData.numOfStudent - numOfDeceasedStudent - numOfEscapedStudent == 1) | |||||
| { | { | ||||
| GameObjLockDict[GameObjType.EmergencyExit].EnterWriteLock(); | |||||
| GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||||
| try | try | ||||
| { | { | ||||
| foreach (EmergencyExit emergencyExit in GameObjDict[GameObjType.EmergencyExit]) | |||||
| if (emergencyExit.CanOpen) | |||||
| foreach (Character player in GameObjDict[GameObjType.Character]) | |||||
| if (player.PlayerState == PlayerStateType.Addicted) | |||||
| { | { | ||||
| emergencyExit.IsOpen = true; | |||||
| Timer.IsGaming = false; | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| GameObjLockDict[GameObjType.EmergencyExit].ExitWriteLock(); | |||||
| GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||||
| } | |||||
| if (Timer.IsGaming) | |||||
| { | |||||
| GameObjLockDict[GameObjType.EmergencyExit].EnterWriteLock(); | |||||
| try | |||||
| { | |||||
| foreach (EmergencyExit emergencyExit in GameObjDict[GameObjType.EmergencyExit]) | |||||
| if (emergencyExit.CanOpen) | |||||
| { | |||||
| emergencyExit.IsOpen = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| finally | |||||
| { | |||||
| GameObjLockDict[GameObjType.EmergencyExit].ExitWriteLock(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -215,14 +232,14 @@ namespace GameClass.GameObj | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if (ToDel != null) | |||||
| GameObjDict[gameObj.Type].Remove(ToDel); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| GameObjLockDict[gameObj.Type].ExitWriteLock(); | GameObjLockDict[gameObj.Type].ExitWriteLock(); | ||||
| } | } | ||||
| if (ToDel == null) return false; | |||||
| GameObjDict[gameObj.Type].Remove(ToDel); | |||||
| return true; | |||||
| return ToDel != null; | |||||
| } | } | ||||
| public bool RemoveJustFromMap(GameObj gameObj) | public bool RemoveJustFromMap(GameObj gameObj) | ||||
| { | { | ||||
| @@ -1,6 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Numerics; | |||||
| using System.Threading; | using System.Threading; | ||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| @@ -147,14 +147,16 @@ namespace GameEngine | |||||
| isDestroyed = true; | isDestroyed = true; | ||||
| return false; | return false; | ||||
| case AfterCollision.MoveMax: | case AfterCollision.MoveMax: | ||||
| MoveMax(obj, res); | |||||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||||
| MoveMax(obj, res); | |||||
| moveVecLength = 0; | moveVecLength = 0; | ||||
| res = new XY(direction, moveVecLength); | res = new XY(direction, moveVecLength); | ||||
| break; | break; | ||||
| } | } | ||||
| } while (flag); | } while (flag); | ||||
| deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); | |||||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||||
| deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); | |||||
| return true; | return true; | ||||
| }, | }, | ||||
| @@ -172,7 +174,8 @@ namespace GameEngine | |||||
| res = new XY(direction, moveVecLength); | res = new XY(direction, moveVecLength); | ||||
| if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) | if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) | ||||
| { | { | ||||
| obj.MovingSetPos(res, GetPlaceType(obj.Position + res)); | |||||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||||
| obj.MovingSetPos(res, GetPlaceType(obj.Position + res)); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -186,7 +189,8 @@ namespace GameEngine | |||||
| isDestroyed = true; | isDestroyed = true; | ||||
| break; | break; | ||||
| case AfterCollision.MoveMax: | case AfterCollision.MoveMax: | ||||
| MoveMax(obj, res); | |||||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||||
| MoveMax(obj, res); | |||||
| moveVecLength = 0; | moveVecLength = 0; | ||||
| res = new XY(direction, moveVecLength); | res = new XY(direction, moveVecLength); | ||||
| break; | break; | ||||
| @@ -37,6 +37,7 @@ namespace Gaming | |||||
| { | { | ||||
| if (moveTimeInMilliseconds < 5) return false; | if (moveTimeInMilliseconds < 5) return false; | ||||
| if (!playerToMove.Commandable() || !TryToStop()) return false; | if (!playerToMove.Commandable() || !TryToStop()) return false; | ||||
| if (playerToMove.IsMoving) return false; | |||||
| characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving); | characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving); | ||||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); | moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); | ||||
| return true; | return true; | ||||
| @@ -76,6 +77,7 @@ namespace Gaming | |||||
| ( | ( | ||||
| () => | () => | ||||
| { | { | ||||
| Thread.Sleep(GameData.frameDuration); | |||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => gameMap.Timer.IsGaming && threadNum == player.ThreadNum, | loopCondition: () => gameMap.Timer.IsGaming && threadNum == player.ThreadNum, | ||||
| loopToDo: () => | loopToDo: () => | ||||
| @@ -6,7 +6,6 @@ using Preparation.Utility; | |||||
| using GameEngine; | using GameEngine; | ||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| using Timothy.FrameRateTask; | using Timothy.FrameRateTask; | ||||
| using System.Numerics; | |||||
| namespace Gaming | namespace Gaming | ||||
| { | { | ||||
| @@ -37,10 +37,14 @@ namespace Gaming | |||||
| case PlayerStateType.Addicted: | case PlayerStateType.Addicted: | ||||
| if (value == PlayerStateType.Rescued) | if (value == PlayerStateType.Rescued) | ||||
| player.ChangePlayerStateInOneThread(value, gameObj); | player.ChangePlayerStateInOneThread(value, gameObj); | ||||
| else | |||||
| player.ChangePlayerState(value, gameObj); | |||||
| break; | break; | ||||
| case PlayerStateType.Rescued: | case PlayerStateType.Rescued: | ||||
| if (value == PlayerStateType.Addicted) | if (value == PlayerStateType.Addicted) | ||||
| player.ChangePlayerStateInOneThread(value, gameObj); | player.ChangePlayerStateInOneThread(value, gameObj); | ||||
| else | |||||
| player.ChangePlayerState(value, gameObj); | |||||
| break; | break; | ||||
| default: | default: | ||||
| player.ChangePlayerState(value, gameObj); | player.ChangePlayerState(value, gameObj); | ||||
| @@ -96,6 +96,7 @@ namespace Gaming | |||||
| IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.BecomeInvisible); | IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.BecomeInvisible); | ||||
| return ActiveSkillEffect(activeSkill, player, () => | return ActiveSkillEffect(activeSkill, player, () => | ||||
| { | { | ||||
| player.AddScore(GameData.ScoreBecomeInvisible); | |||||
| player.AddInvisible(activeSkill.DurationTime); | player.AddInvisible(activeSkill.DurationTime); | ||||
| Debugger.Output(player, "become invisible!"); | Debugger.Output(player, "become invisible!"); | ||||
| }, | }, | ||||
| @@ -17,44 +17,31 @@ namespace Gaming | |||||
| switch (activeSkillType) | switch (activeSkillType) | ||||
| { | { | ||||
| case ActiveSkillType.BecomeInvisible: | case ActiveSkillType.BecomeInvisible: | ||||
| BecomeInvisible(character); | |||||
| break; | |||||
| return BecomeInvisible(character); | |||||
| case ActiveSkillType.UseKnife: | case ActiveSkillType.UseKnife: | ||||
| UseKnife(character); | |||||
| break; | |||||
| return UseKnife(character); | |||||
| case ActiveSkillType.Howl: | case ActiveSkillType.Howl: | ||||
| Howl(character); | |||||
| break; | |||||
| return Howl(character); | |||||
| case ActiveSkillType.CanBeginToCharge: | case ActiveSkillType.CanBeginToCharge: | ||||
| CanBeginToCharge(character); | |||||
| break; | |||||
| return CanBeginToCharge(character); | |||||
| case ActiveSkillType.Inspire: | case ActiveSkillType.Inspire: | ||||
| Inspire(character); | |||||
| break; | |||||
| return Inspire(character); | |||||
| case ActiveSkillType.Encourage: | case ActiveSkillType.Encourage: | ||||
| Encourage(character); | |||||
| break; | |||||
| return Encourage(character); | |||||
| case ActiveSkillType.Punish: | case ActiveSkillType.Punish: | ||||
| Punish(character); | |||||
| break; | |||||
| return Punish(character); | |||||
| case ActiveSkillType.JumpyBomb: | case ActiveSkillType.JumpyBomb: | ||||
| JumpyBomb(character); | |||||
| break; | |||||
| return JumpyBomb(character); | |||||
| case ActiveSkillType.WriteAnswers: | case ActiveSkillType.WriteAnswers: | ||||
| WriteAnswers(character); | |||||
| break; | |||||
| return WriteAnswers(character); | |||||
| case ActiveSkillType.SummonGolem: | case ActiveSkillType.SummonGolem: | ||||
| SummonGolem(character); | |||||
| break; | |||||
| return SummonGolem(character); | |||||
| case ActiveSkillType.UseRobot: | case ActiveSkillType.UseRobot: | ||||
| UseRobot(character); | |||||
| break; | |||||
| return UseRobot(character); | |||||
| case ActiveSkillType.Rouse: | case ActiveSkillType.Rouse: | ||||
| Rouse(character); | |||||
| break; | |||||
| return Rouse(character); | |||||
| case ActiveSkillType.ShowTime: | case ActiveSkillType.ShowTime: | ||||
| ShowTime(character); | |||||
| break; | |||||
| return ShowTime(character); | |||||
| default: | default: | ||||
| return false; | return false; | ||||
| } | } | ||||
| @@ -272,7 +272,7 @@ namespace Server | |||||
| Y = chest.Position.y | Y = chest.Position.y | ||||
| } | } | ||||
| }; | }; | ||||
| int progress = (chest.OpenStartTime > 0) ? ((time - chest.OpenStartTime) * chest.WhoOpen!.SpeedOfOpenChest) : 0; | |||||
| int progress = (chest.WhoOpen != null) ? ((time - chest.OpenStartTime) * chest.WhoOpen.SpeedOfOpenChest) : 0; | |||||
| msg.ChestMessage.Progress = (progress > GameData.degreeOfOpenedChest) ? GameData.degreeOfOpenedChest : progress; | msg.ChestMessage.Progress = (progress > GameData.degreeOfOpenedChest) ? GameData.degreeOfOpenedChest : progress; | ||||
| return msg; | return msg; | ||||
| } | } | ||||
| @@ -1,22 +1,17 @@ | |||||
| using Grpc.Core; | |||||
| using Protobuf; | |||||
| using System.Threading; | |||||
| using Timothy.FrameRateTask; | |||||
| using System; | |||||
| using System.Net.Http.Headers; | |||||
| using GameClass.GameObj; | |||||
| using Gaming; | using Gaming; | ||||
| using GameClass.GameObj; | |||||
| using Preparation.Utility; | |||||
| using Playback; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using Preparation.Interface; | |||||
| using Playback; | |||||
| using Preparation.Utility; | |||||
| using Protobuf; | |||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using Timothy.FrameRateTask; | |||||
| namespace Server | namespace Server | ||||
| { | { | ||||
| public partial class GameServer : AvailableService.AvailableServiceBase | |||||
| partial class GameServer : ServerBase | |||||
| { | { | ||||
| private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | ||||
| // private object semaDictLock = new(); | // private object semaDictLock = new(); | ||||
| @@ -86,7 +81,7 @@ namespace Server | |||||
| } | } | ||||
| } | } | ||||
| public void WaitForEnd() | |||||
| public override void WaitForEnd() | |||||
| { | { | ||||
| this.endGameSem.Wait(); | this.endGameSem.Wait(); | ||||
| mwr?.Dispose(); | mwr?.Dispose(); | ||||
| @@ -202,7 +197,7 @@ namespace Server | |||||
| return false; | return false; | ||||
| } | } | ||||
| public int[] GetScore() | |||||
| public override int[] GetScore() | |||||
| { | { | ||||
| int[] score = new int[2]; // 0代表Student,1代表Tricker | int[] score = new int[2]; // 0代表Student,1代表Tricker | ||||
| game.GameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | game.GameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | ||||
| @@ -1,24 +1,40 @@ | |||||
| using Protobuf; | |||||
| using Gaming; | |||||
| using Grpc.Core; | |||||
| using Playback; | using Playback; | ||||
| using System; | |||||
| using System.Threading; | |||||
| using Protobuf; | |||||
| using System.Collections.Concurrent; | |||||
| using Timothy.FrameRateTask; | using Timothy.FrameRateTask; | ||||
| using Gaming; | |||||
| using Grpc.Core; | |||||
| namespace Server | namespace Server | ||||
| { | { | ||||
| class PlaybackServer : AvailableService.AvailableServiceBase | |||||
| class PlaybackServer : ServerBase | |||||
| { | { | ||||
| protected readonly ArgumentOptions options; | protected readonly ArgumentOptions options; | ||||
| private int[,] teamScore; | private int[,] teamScore; | ||||
| private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||||
| private object semaDictLock = new(); | |||||
| private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||||
| // private object semaDictLock = new(); | |||||
| private MessageToClient? currentGameInfo = new(); | private MessageToClient? currentGameInfo = new(); | ||||
| private MessageOfObj currentMapMsg = new(); | |||||
| private uint spectatorMinPlayerID = 2023; | private uint spectatorMinPlayerID = 2023; | ||||
| private List<uint> spectatorList = new List<uint>(); | |||||
| // private List<uint> spectatorList = new List<uint>(); | |||||
| public int TeamCount => options.TeamCount; | public int TeamCount => options.TeamCount; | ||||
| private MessageWriter? mwr = null; | |||||
| private object spetatorJoinLock = new(); | |||||
| protected object spectatorLock = new object(); | |||||
| protected bool isSpectatorJoin = false; | |||||
| protected bool IsSpectatorJoin | |||||
| { | |||||
| get | |||||
| { | |||||
| lock (spectatorLock) | |||||
| return isSpectatorJoin; | |||||
| } | |||||
| set | |||||
| { | |||||
| lock (spectatorLock) | |||||
| isSpectatorJoin = value; | |||||
| } | |||||
| } | |||||
| private bool IsGaming { get; set; } | private bool IsGaming { get; set; } | ||||
| private int[] finalScore; | private int[] finalScore; | ||||
| public int[] FinalScore | public int[] FinalScore | ||||
| @@ -28,6 +44,7 @@ namespace Server | |||||
| return finalScore; | return finalScore; | ||||
| } | } | ||||
| } | } | ||||
| public override int[] GetScore() => FinalScore; | |||||
| public PlaybackServer(ArgumentOptions options) | public PlaybackServer(ArgumentOptions options) | ||||
| { | { | ||||
| this.options = options; | this.options = options; | ||||
| @@ -38,18 +55,21 @@ namespace Server | |||||
| public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context) | public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context) | ||||
| { | { | ||||
| if (request.PlayerId >= spectatorMinPlayerID) | |||||
| Console.WriteLine($"AddPlayer: {request.PlayerId}"); | |||||
| if (request.PlayerId >= spectatorMinPlayerID && options.NotAllowSpectator == false) | |||||
| { | { | ||||
| // 观战模式 | // 观战模式 | ||||
| uint tp = (uint)request.PlayerId; | |||||
| if (!spectatorList.Contains(tp)) | |||||
| lock (spetatorJoinLock) // 具体原因见另一个上锁的地方 | |||||
| { | { | ||||
| spectatorList.Add(tp); | |||||
| Console.WriteLine("A new spectator comes to watch this game."); | |||||
| var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | |||||
| lock (semaDictLock) | |||||
| if (semaDict.TryAdd(request.PlayerId, (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)))) | |||||
| { | { | ||||
| semaDict.Add(request.PlayerId, temp); | |||||
| Console.WriteLine("A new spectator comes to watch this game."); | |||||
| IsSpectatorJoin = true; | |||||
| } | |||||
| else | |||||
| { | |||||
| Console.WriteLine($"Duplicated Spectator ID {request.PlayerId}"); | |||||
| return; | |||||
| } | } | ||||
| } | } | ||||
| do | do | ||||
| @@ -63,15 +83,33 @@ namespace Server | |||||
| //Console.WriteLine("Send!"); | //Console.WriteLine("Send!"); | ||||
| } | } | ||||
| } | } | ||||
| catch (InvalidOperationException) | |||||
| { | |||||
| if (semaDict.TryRemove(request.PlayerId, out var semas)) | |||||
| { | |||||
| try | |||||
| { | |||||
| semas.Item1.Release(); | |||||
| semas.Item2.Release(); | |||||
| } | |||||
| catch { } | |||||
| Console.WriteLine($"The spectator {request.PlayerId} exited"); | |||||
| return; | |||||
| } | |||||
| } | |||||
| catch (Exception) | catch (Exception) | ||||
| { | { | ||||
| //Console.WriteLine(ex); | |||||
| // Console.WriteLine(ex); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| semaDict[request.PlayerId].Item2.Release(); | |||||
| try | |||||
| { | |||||
| semaDict[request.PlayerId].Item2.Release(); | |||||
| } | |||||
| catch { } | |||||
| } | } | ||||
| } while (IsGaming == true); | |||||
| } while (IsGaming); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| @@ -79,6 +117,16 @@ namespace Server | |||||
| public void ReportGame(MessageToClient? msg) | public void ReportGame(MessageToClient? msg) | ||||
| { | { | ||||
| currentGameInfo = msg; | currentGameInfo = msg; | ||||
| if (currentGameInfo != null && currentGameInfo.GameState == GameState.GameStart) | |||||
| { | |||||
| currentMapMsg = currentGameInfo.ObjMessage[0]; | |||||
| } | |||||
| if (currentGameInfo != null && IsSpectatorJoin) | |||||
| { | |||||
| currentGameInfo.ObjMessage.Add(currentMapMsg); | |||||
| IsSpectatorJoin = false; | |||||
| } | |||||
| foreach (var kvp in semaDict) | foreach (var kvp in semaDict) | ||||
| { | { | ||||
| @@ -91,7 +139,7 @@ namespace Server | |||||
| } | } | ||||
| } | } | ||||
| public void WaitForGame() | |||||
| public override void WaitForEnd() | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| @@ -1,15 +1,55 @@ | |||||
| using Grpc.Core; | |||||
| using CommandLine; | |||||
| using Grpc.Core; | |||||
| using Protobuf; | using Protobuf; | ||||
| using System.Threading; | |||||
| using Timothy.FrameRateTask; | |||||
| using System; | |||||
| using System.Net.Http.Headers; | |||||
| using CommandLine; | |||||
| namespace Server | namespace Server | ||||
| { | { | ||||
| public class Program | public class Program | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Generated by http://www.network-science.de/ascii/ with font "standard" | |||||
| /// </summary> | |||||
| const string welcome = | |||||
| @" | |||||
| _____ _ _ _ _ _ ___ __ | |||||
| |_ _| | | | | | | / \ |_ _/ /_ | |||||
| | | | |_| | | | |/ _ \ | | '_ \ | |||||
| | | | _ | |_| / ___ \ | | (_) | | |||||
| |_| |_| |_|\___/_/ \_\___\___/ | |||||
| ____ _ _ ____ _ _ _ | |||||
| / ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| | | |||||
| | | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| | | |||||
| | |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_| | |||||
| \____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_) | |||||
| "; | |||||
| // 特别说明:在 .NET 7 及以上,C# 支持新的多行字符串,允许多行前面缩进,因此可以不必再定格写字符串, | |||||
| // 即升级 .NET 版本后可以改为如下的: | |||||
| // const string welcome = """ | |||||
| // | |||||
| // _____ _ _ _ _ _ ___ __ | |||||
| // |_ _| | | | | | | / \ |_ _/ /_ | |||||
| // | | | |_| | | | |/ _ \ | | '_ \ | |||||
| // | | | _ | |_| / ___ \ | | (_) | | |||||
| // |_| |_| |_|\___/_/ \_\___\___/ | |||||
| // | |||||
| // ____ _ _ ____ _ _ _ | |||||
| // / ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| | | |||||
| // | | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| | | |||||
| // | |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_| | |||||
| // \____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_) | |||||
| // | |||||
| // | |||||
| // """; // 将以此结束符为基准开始缩进(但 Python 没有这个 feature,差评 x) | |||||
| static ServerBase CreateServer(ArgumentOptions options) | |||||
| { | |||||
| return options.Playback ? new PlaybackServer(options) : new GameServer(options); | |||||
| } | |||||
| static int Main(string[] args) | static int Main(string[] args) | ||||
| { | { | ||||
| foreach (var arg in args) | foreach (var arg in args) | ||||
| @@ -26,63 +66,36 @@ namespace Server | |||||
| return 1; | return 1; | ||||
| } | } | ||||
| if (options.StartLockFile == DefaultArgumentOptions.FileName) | |||||
| { | |||||
| Console.WriteLine(welcome); | |||||
| } | |||||
| Console.WriteLine("Server begins to run: " + options.ServerPort.ToString()); | Console.WriteLine("Server begins to run: " + options.ServerPort.ToString()); | ||||
| if (options.Playback) | |||||
| try | |||||
| { | { | ||||
| try | |||||
| var server = CreateServer(options); | |||||
| Grpc.Core.Server rpcServer = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||||
| { | { | ||||
| PlaybackServer? playbackServer = new(options); | |||||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||||
| { | |||||
| Services = { AvailableService.BindService(playbackServer) }, | |||||
| Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } | |||||
| }; | |||||
| server.Start(); | |||||
| Services = { AvailableService.BindService(server) }, | |||||
| Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } | |||||
| }; | |||||
| rpcServer.Start(); | |||||
| Console.WriteLine("Server begins to listen!"); | |||||
| playbackServer.WaitForGame(); | |||||
| Console.WriteLine("Server end!"); | |||||
| server.ShutdownAsync().Wait(); | |||||
| Console.WriteLine("Server begins to listen!"); | |||||
| server.WaitForEnd(); | |||||
| Console.WriteLine("Server end!"); | |||||
| rpcServer.ShutdownAsync().Wait(); | |||||
| Thread.Sleep(50); | |||||
| Console.WriteLine(""); | |||||
| Console.WriteLine("=================== Final Score ===================="); | |||||
| Console.WriteLine($"Studnet: {playbackServer.FinalScore[0]}"); | |||||
| Console.WriteLine($"Tricker: {playbackServer.FinalScore[1]}"); | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| Console.WriteLine(ex.ToString()); | |||||
| } | |||||
| Thread.Sleep(50); | |||||
| Console.WriteLine(""); | |||||
| Console.WriteLine("=================== Final Score ===================="); | |||||
| Console.WriteLine($"Studnet: {server.GetScore()[0]}"); | |||||
| Console.WriteLine($"Tricker: {server.GetScore()[1]}"); | |||||
| } | } | ||||
| else | |||||
| catch (Exception ex) | |||||
| { | { | ||||
| try | |||||
| { | |||||
| GameServer? gameServer = new(options); | |||||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||||
| { | |||||
| Services = { AvailableService.BindService(gameServer) }, | |||||
| Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } | |||||
| }; | |||||
| server.Start(); | |||||
| Console.WriteLine("Server begins to listen!"); | |||||
| gameServer.WaitForEnd(); | |||||
| Console.WriteLine("Server end!"); | |||||
| server.ShutdownAsync().Wait(); | |||||
| Thread.Sleep(50); | |||||
| Console.WriteLine(""); | |||||
| Console.WriteLine("=================== Final Score ===================="); | |||||
| Console.WriteLine($"Studnet: {gameServer.GetScore()[0]}"); | |||||
| Console.WriteLine($"Tricker: {gameServer.GetScore()[1]}"); | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| Console.WriteLine(ex.ToString()); | |||||
| } | |||||
| Console.WriteLine(ex.ToString()); | |||||
| } | } | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| @@ -2,7 +2,7 @@ | |||||
| "profiles": { | "profiles": { | ||||
| "Server": { | "Server": { | ||||
| "commandName": "Project", | "commandName": "Project", | ||||
| "commandLineArgs": "--port 8888 --studentCount 4 --trickerCount 1 --resultFileName result --gameTimeInSecond 1 --fileName video" | |||||
| "commandLineArgs": "--port 8888 --studentCount 4 --trickerCount 1 --resultFileName result --gameTimeInSecond 551 --fileName video" | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,20 +1,12 @@ | |||||
| using Grpc.Core; | |||||
| using Protobuf; | |||||
| using System.Threading; | |||||
| using Timothy.FrameRateTask; | |||||
| using System; | |||||
| using System.Net.Http.Headers; | |||||
| using GameClass.GameObj; | |||||
| using Gaming; | using Gaming; | ||||
| using GameClass.GameObj; | |||||
| using Grpc.Core; | |||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| using Playback; | |||||
| using Newtonsoft.Json; | |||||
| using Newtonsoft.Json.Linq; | |||||
| using Preparation.Interface; | |||||
| using Protobuf; | |||||
| namespace Server | namespace Server | ||||
| { | { | ||||
| public partial class GameServer : AvailableService.AvailableServiceBase | |||||
| partial class GameServer : ServerBase | |||||
| { | { | ||||
| private int playerCountNow = 0; | private int playerCountNow = 0; | ||||
| protected object spectatorLock = new object(); | protected object spectatorLock = new object(); | ||||
| @@ -95,6 +87,7 @@ namespace Server | |||||
| } | } | ||||
| catch { } | catch { } | ||||
| Console.WriteLine($"The spectator {request.PlayerId} exited"); | Console.WriteLine($"The spectator {request.PlayerId} exited"); | ||||
| return; | |||||
| } | } | ||||
| } | } | ||||
| catch (Exception) | catch (Exception) | ||||
| @@ -136,7 +129,7 @@ namespace Server | |||||
| var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | ||||
| bool start = false; | bool start = false; | ||||
| Console.WriteLine($"Id: {request.PlayerId} joins."); | Console.WriteLine($"Id: {request.PlayerId} joins."); | ||||
| // lock (semaDictLock) | |||||
| lock (spetatorJoinLock) // 为了保证绝对安全,还是加上这个锁吧 | |||||
| { | { | ||||
| if (semaDict.TryAdd(request.PlayerId, temp)) | if (semaDict.TryAdd(request.PlayerId, temp)) | ||||
| { | { | ||||
| @@ -0,0 +1,14 @@ | |||||
| using Grpc.Core; | |||||
| using Playback; | |||||
| using Protobuf; | |||||
| using System.Collections.Concurrent; | |||||
| using Timothy.FrameRateTask; | |||||
| namespace Server | |||||
| { | |||||
| abstract class ServerBase : AvailableService.AvailableServiceBase | |||||
| { | |||||
| public abstract void WaitForEnd(); | |||||
| public abstract int[] GetScore(); | |||||
| } | |||||
| } | |||||
| @@ -1,6 +1,2 @@ | |||||
| @echo off | @echo off | ||||
| ::start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName test --playback true | |||||
| ping -n 2 127.0.0.1 > NUL | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2030 | |||||
| start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --port 8888 --fileName .\ladder1.thuaipb --playback --playbackSpeed 4.0 | |||||
| @@ -1,6 +1,6 @@ | |||||
| @echo off | @echo off | ||||
| start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName test | |||||
| start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --studentCount 0 --trickerCount 1 --gameTimeInSecond 600 --fileName test | |||||
| ping -n 2 127.0.0.1 > NUL | ping -n 2 127.0.0.1 > NUL | ||||