Browse Source

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

tags/0.1.0^2^2
DragonAura 2 years ago
parent
commit
944a7f3d6b
50 changed files with 778 additions and 444 deletions
  1. +26
    -0
      CAPI/cpp/API/src/main.cpp
  2. +2
    -2
      CAPI/python/PyAPI/logic.py
  3. +24
    -0
      CAPI/python/PyAPI/main.py
  4. +1
    -0
      CAPI/python/requirements.txt
  5. +125
    -0
      dependency/algorithm/README.md
  6. +1
    -1
      dependency/shell/compile.sh
  7. +2
    -2
      docs/CAPI接口(cpp).md
  8. +2
    -2
      docs/CAPI接口(python).md
  9. +4
    -2
      docs/GameRules.md
  10. +25
    -2
      docs/QandA.md
  11. +83
    -46
      docs/Tool_tutorial.md
  12. +16
    -16
      installer/Installer/Common.cs
  13. +13
    -0
      installer/Installer/Installer.csproj
  14. +3
    -1
      installer/Installer/MainWindow.xaml
  15. +94
    -76
      installer/Installer/Model.cs
  16. +35
    -34
      installer/Installer/ViewModel.cs
  17. BIN
      installer/Installer/eesast_software_trans_enlarged.ico
  18. +8
    -0
      installer/InstallerUpdater/InstallerUpdater.csproj
  19. +3
    -1
      installer/InstallerUpdater/MainWindow.xaml
  20. +14
    -10
      installer/InstallerUpdater/Program.cs
  21. +0
    -4
      logic/Client/Client.csproj
  22. BIN
      logic/Client/EESASTLogo.png
  23. BIN
      logic/Client/Logo.png
  24. +3
    -2
      logic/Client/MainWindow.xaml
  25. +60
    -79
      logic/Client/MainWindow.xaml.cs
  26. +4
    -4
      logic/Client/StatusBarOfHunter.xaml.cs
  27. +4
    -4
      logic/Client/StatusBarOfSurvivor.xaml.cs
  28. +0
    -1
      logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs
  29. +0
    -1
      logic/GameClass/GameObj/Map/Chest.cs
  30. +28
    -11
      logic/GameClass/GameObj/Map/Map.cs
  31. +0
    -1
      logic/GameEngine/CollisionChecker.cs
  32. +8
    -4
      logic/GameEngine/MoveEngine.cs
  33. +2
    -0
      logic/Gaming/ActionManager.cs
  34. +0
    -1
      logic/Gaming/AttackManager.cs
  35. +4
    -0
      logic/Gaming/CharacterManager .cs
  36. +1
    -0
      logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs
  37. +13
    -26
      logic/Gaming/SkillManager/SkillManager.cs
  38. +1
    -1
      logic/Server/CopyInfo.cs
  39. +8
    -13
      logic/Server/GameServer.cs
  40. +70
    -22
      logic/Server/PlaybackServer.cs
  41. +68
    -55
      logic/Server/Program.cs
  42. +1
    -1
      logic/Server/Properties/launchSettings.json
  43. +6
    -13
      logic/Server/RpcServices.cs
  44. +14
    -0
      logic/Server/ServerBase.cs
  45. +1
    -5
      logic/cmd/PlaybackServer.cmd
  46. +1
    -1
      logic/cmd/gameServerAndClient.cmd
  47. BIN
      resource/eesast_software_trans_enlarged.png
  48. BIN
      resource/eesast_software_trans_enlarged_256x256.png
  49. BIN
      resource/vector.png
  50. BIN
      resource/wrongType.png

+ 26
- 0
CAPI/cpp/API/src/main.cpp View File

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


+ 2
- 2
CAPI/python/PyAPI/logic.py View File

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


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

@@ -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
- 0
CAPI/python/requirements.txt View File

@@ -1,2 +1,3 @@
grpcio==1.52.0 grpcio==1.52.0
grpcio-tools==1.52.0 grpcio-tools==1.52.0
numpy

+ 125
- 0
dependency/algorithm/README.md View File

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


+ 1
- 1
dependency/shell/compile.sh View File

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


+ 2
- 2
docs/CAPI接口(cpp).md View File

@@ -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`:查询特定位置教室作业完成进度


+ 2
- 2
docs/CAPI接口(python).md View File

@@ -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`:查询特定位置教室作业完成进度


+ 4
- 2
docs/GameRules.md View File

@@ -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的小炸弹有碰撞体积
- 除了切换攻击类型的技能,在不能执行指令的状态下(包括翻窗)均不能使用技能


### 职业 ### 职业
- 学生职业可以重复选择 - 学生职业可以重复选择

+ 25
- 2
docs/QandA.md View File

@@ -22,6 +22,11 @@ Q:卡死在第一帧不动


A:大概率是你的代码死循环了 A:大概率是你的代码死循环了


Q:
![wrongType](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/wrongType.png)

A:命令行参数的type设置有误

## C++ ## C++


Q:显示API项目已卸载 Q:显示API项目已卸载
@@ -56,7 +61,11 @@ A:
2. 不要点重新生成,要点生成 2. 不要点重新生成,要点生成
3. 开启下图选项 3. 开启下图选项
![CompileFaster](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/CompileFaster.png) ![CompileFaster](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/CompileFaster.png)

Q:这是什么错误啊
![vector](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/vector.png)

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()修改 等

+ 83
- 46
docs/Tool_tutorial.md View File

@@ -2,9 +2,9 @@


[toc] [toc]


## Visual Studio使用说明
## Visual Studio 使用说明


比赛**只保证!!!支持**VS2022最新版本,选手使用其他版本后果自负(实际上应该不能编译)。
比赛**只保证!!!支持** VS 2022 最新版本,选手使用其他版本后果自负(实际上应该不能编译)。


### 生成模式的设置 ### 生成模式的设置


@@ -12,7 +12,7 @@


![image-20230416010705076](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/image-20230416010705076.png) ![image-20230416010705076](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/image-20230416010705076.png)


可以更改生成模式为`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)...));
}
}
```








+ 16
- 16
installer/Installer/Common.cs View File

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


+ 13
- 0
installer/Installer/Installer.csproj View File

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


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

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


+ 94
- 76
installer/Installer/Model.cs View File

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

} }

+ 35
- 34
installer/Installer/ViewModel.cs View File

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


BIN
installer/Installer/eesast_software_trans_enlarged.ico View File

Before After

+ 8
- 0
installer/InstallerUpdater/InstallerUpdater.csproj View File

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


+ 3
- 1
installer/InstallerUpdater/MainWindow.xaml View File

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


+ 14
- 10
installer/InstallerUpdater/Program.cs View File

@@ -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();


+ 0
- 4
logic/Client/Client.csproj View File

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


BIN
logic/Client/EESASTLogo.png View File

Before After
Width: 1024  |  Height: 1024  |  Size: 95 kB

BIN
logic/Client/Logo.png View File

Before After
Width: 1024  |  Height: 1024  |  Size: 95 kB Width: 2048  |  Height: 2048  |  Size: 56 kB

+ 3
- 2
logic/Client/MainWindow.xaml View File

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


+ 60
- 79
logic/Client/MainWindow.xaml.cs View File

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


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

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


+ 4
- 4
logic/Client/StatusBarOfSurvivor.xaml.cs View File

@@ -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) + "%";


+ 0
- 1
logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs View File

@@ -1,6 +1,5 @@
using Preparation.Interface; using Preparation.Interface;
using Preparation.Utility; using Preparation.Utility;
using System;


namespace GameClass.GameObj namespace GameClass.GameObj
{ {


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

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


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

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


+ 0
- 1
logic/GameEngine/CollisionChecker.cs View File

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


+ 8
- 4
logic/GameEngine/MoveEngine.cs View File

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


+ 2
- 0
logic/Gaming/ActionManager.cs View File

@@ -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: () =>


+ 0
- 1
logic/Gaming/AttackManager.cs View File

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


+ 4
- 0
logic/Gaming/CharacterManager .cs View File

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


+ 1
- 0
logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs View File

@@ -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!");
}, },


+ 13
- 26
logic/Gaming/SkillManager/SkillManager.cs View File

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


+ 1
- 1
logic/Server/CopyInfo.cs View File

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


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

@@ -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();


+ 70
- 22
logic/Server/PlaybackServer.cs View File

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


+ 68
- 55
logic/Server/Program.cs View File

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


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

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

+ 6
- 13
logic/Server/RpcServices.cs View File

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


+ 14
- 0
logic/Server/ServerBase.cs View File

@@ -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
- 5
logic/cmd/PlaybackServer.cmd View File

@@ -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
- 1
logic/cmd/gameServerAndClient.cmd View File

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



BIN
resource/eesast_software_trans_enlarged.png View File

Before After
Width: 2048  |  Height: 2048  |  Size: 115 kB

BIN
resource/eesast_software_trans_enlarged_256x256.png View File

Before After
Width: 256  |  Height: 256  |  Size: 14 kB

BIN
resource/vector.png View File

Before After
Width: 929  |  Height: 731  |  Size: 37 kB

BIN
resource/wrongType.png View File

Before After
Width: 856  |  Height: 420  |  Size: 28 kB

Loading…
Cancel
Save