diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..26949852
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,404 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
+#THUAI playback file
+*.thuaipb
+
+#private cmd
+cmd/gameServerOfSanford.cmd
\ No newline at end of file
diff --git a/README.md b/README.md
index a68b421c..aaf9643a 100644
--- a/README.md
+++ b/README.md
@@ -24,14 +24,14 @@
| 子目录 | 说明 | 主要开发组 |
| :--------: | :--------------------------------------------------------: | :----------------------------: |
| .github | CI,用于选手包同步到服务器上供选手下载 | 运维组 |
-| CAPI | 选手接口,生成可执行文件 | 通信组 |
-| docs | 比赛的说明文档 | 逻辑组、通信组、运维组、界面组 |
+| CAPI | 选手接口,生成可执行文件 | 通信组 |
+| docs | 比赛的说明文档 | 逻辑组、通信组、运维组、界面组 |
| dependency | 项目依赖文件,如 proto、dll、lib、dockerfile、shell 脚本等 | 逻辑组、通信组、运维组、界面组 |
| installer | 下载器,用于选手包的下载与更新,生成可执行文件 | 运维组 |
| interface | Unity 界面 | 界面组 |
| launcher | 游戏启动器,用于快速启动游戏,生成可执行文件 | 运维组 |
-| logic | 游戏逻辑、Server,生成可执行文件 | 通信组、逻辑组 |
-| playback | 游戏回放组件,生成类库 | 逻辑组 |
+| logic | 游戏逻辑、Server,生成可执行文件 | 通信组、逻辑组 |
+| playback | 游戏回放组件,生成类库 | 逻辑组 |
| resource | 资源文件目录,用于存储主目录下 README 所用图片 | 端茶倒水 |
### 分支管理
@@ -137,11 +137,11 @@ THUAI7 开发组成员与其他贡献者应当遵循以下流程:
仓库的文档使用 Markdown 语法,具体语法可以参照 [Markdown 语法文档](https://docs.github.com/zh/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax)。
-中文文档的书写须严格遵循:[中午技术文档规范](https://github.com/ruanyf/document-style-guide)、[中文文案排版指北](https://mazhuang.org/wiki/chinese-copywriting-guidelines/),以写出更美观的中文文档。例如:[中文文字与西文文字间空格](https://github.com/ruanyf/document-style-guide/blob/master/docs/text.md#%E5%AD%97%E9%97%B4%E8%B7%9D)、[全角标点符号的正确使用](https://github.com/ruanyf/document-style-guide/blob/master/docs/marks.md),等等。
+中文文档的书写须严格遵循:[中文技术文档规范](https://github.com/ruanyf/document-style-guide)、[中文文案排版指北](https://mazhuang.org/wiki/chinese-copywriting-guidelines/),以写出更美观的中文文档。例如:[中文文字与西文文字间空格](https://github.com/ruanyf/document-style-guide/blob/master/docs/text.md#%E5%AD%97%E9%97%B4%E8%B7%9D)、[全角标点符号的正确使用](https://github.com/ruanyf/document-style-guide/blob/master/docs/marks.md),等等。
西文文档的书写须遵循:
-+ 在 [中午技术文档规范](https://github.com/ruanyf/document-style-guide) 和 [中文文案排版指北](https://mazhuang.org/wiki/chinese-copywriting-guidelines/) 中规定的数字和西文的规范
++ 在 [中文技术文档规范](https://github.com/ruanyf/document-style-guide) 和 [中文文案排版指北](https://mazhuang.org/wiki/chinese-copywriting-guidelines/) 中规定的数字和西文的规范
+ 半角空格的标点符号的使用:
+ 半角的逗号 `,`、分号 `;`、句号 `.`、叹号 `!`、问号 `?` 等标点,在不位于行尾时,后需加空格,除非其后紧跟全角标点符号。例如:`"Hello, world",是一句学习编程语言常用的句子`
+ 半角的括号 `()`、`[]`、`{}`、`<>` 等在左半括号前加空格、右半括号后加空格,除非其空格处紧跟全角标点符号或位于行首或行尾。例如:`Tsinghua University (THU) and Peking University (PKU)(我是全角括号)`
diff --git a/dependency/proto/Proto.sln b/dependency/proto/Proto.sln
old mode 100755
new mode 100644
index 2d4039ac..2cdbe95d
--- a/dependency/proto/Proto.sln
+++ b/dependency/proto/Proto.sln
@@ -1,25 +1,25 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.1.32328.378
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proto", "Proto.csproj", "{B9A3CDD7-7852-4220-A2AA-2B986E0C19E8}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {B9A3CDD7-7852-4220-A2AA-2B986E0C19E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B9A3CDD7-7852-4220-A2AA-2B986E0C19E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B9A3CDD7-7852-4220-A2AA-2B986E0C19E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B9A3CDD7-7852-4220-A2AA-2B986E0C19E8}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {CB1C0B2E-5D0E-4420-94B5-FE8AECC7E106}
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.32328.378
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proto", "Proto.csproj", "{B9A3CDD7-7852-4220-A2AA-2B986E0C19E8}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B9A3CDD7-7852-4220-A2AA-2B986E0C19E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B9A3CDD7-7852-4220-A2AA-2B986E0C19E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B9A3CDD7-7852-4220-A2AA-2B986E0C19E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B9A3CDD7-7852-4220-A2AA-2B986E0C19E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {CB1C0B2E-5D0E-4420-94B5-FE8AECC7E106}
+ EndGlobalSection
+EndGlobal
diff --git a/dependency/proto/Protos.csproj b/dependency/proto/Protos.csproj
old mode 100755
new mode 100644
index f99de6c4..d8fd4965
--- a/dependency/proto/Protos.csproj
+++ b/dependency/proto/Protos.csproj
@@ -1,34 +1,34 @@
-
-
-
- net6.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/README.md b/docs/README.md
index e5b3ef7b..fdc8101f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -8,13 +8,13 @@
## 中文文档
-中文文档的书写须严格遵循:[中午技术文档规范](https://github.com/ruanyf/document-style-guide)、[中文文案排版指北](https://mazhuang.org/wiki/chinese-copywriting-guidelines/),以写出更美观的中文文档。例如:[中文文字与西文文字间空格](https://github.com/ruanyf/document-style-guide/blob/master/docs/text.md#%E5%AD%97%E9%97%B4%E8%B7%9D)、[全角标点符号的正确使用](https://github.com/ruanyf/document-style-guide/blob/master/docs/marks.md),等等。
+中文文档的书写须严格遵循:[中文技术文档规范](https://github.com/ruanyf/document-style-guide)、[中文文案排版指北](https://mazhuang.org/wiki/chinese-copywriting-guidelines/),以写出更美观的中文文档。例如:[中文文字与西文文字间空格](https://github.com/ruanyf/document-style-guide/blob/master/docs/text.md#%E5%AD%97%E9%97%B4%E8%B7%9D)、[全角标点符号的正确使用](https://github.com/ruanyf/document-style-guide/blob/master/docs/marks.md),等等。
## 西文文档
西文文档的书写须遵循:
-+ 在 [中午技术文档规范](https://github.com/ruanyf/document-style-guide) 和 [中文文案排版指北](https://mazhuang.org/wiki/chinese-copywriting-guidelines/) 中规定的数字和西文的规范
++ 在 [中文技术文档规范](https://github.com/ruanyf/document-style-guide) 和 [中文文案排版指北](https://mazhuang.org/wiki/chinese-copywriting-guidelines/) 中规定的数字和西文的规范
+ 半角空格的标点符号的使用:
+ 半角的逗号 `,`、分号 `;`、句号 `.`、叹号 `!`、问号 `?` 等标点,在不位于行尾时,后需加空格,除非其后紧跟全角标点符号。例如:`"Hello, world",是一句学习编程语言常用的句子`
+ 半角的括号 `()`、`[]`、`{}`、`<>` 等在左半括号前加空格、右半括号后加空格,除非其空格处紧跟全角标点符号或位于行首或行尾。例如:`Tsinghua University (THU) and Peking University (PKU)(我是全角括号)`
diff --git a/docs/temp.url b/docs/temp.url
new file mode 100644
index 00000000..ec3ef483
--- /dev/null
+++ b/docs/temp.url
@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=https://thuai7.panxuc.com/
\ No newline at end of file
diff --git a/logic/Client/Client.sln b/logic/Client/Client.sln
new file mode 100644
index 00000000..aedf36b1
--- /dev/null
+++ b/logic/Client/Client.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.32328.378
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client.csproj", "{5AD8481D-90EF-410C-BD48-355DB97EEAB3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Playback", "..\..\playback\Playback\Playback.csproj", "{662FDB27-FBF3-4D2D-BDA4-B4BF4D35B866}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Protos", "..\..\dependency\proto\Protos.csproj", "{A0F72D3B-9A82-48EB-90AF-B3770151AD83}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5AD8481D-90EF-410C-BD48-355DB97EEAB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5AD8481D-90EF-410C-BD48-355DB97EEAB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5AD8481D-90EF-410C-BD48-355DB97EEAB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5AD8481D-90EF-410C-BD48-355DB97EEAB3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {662FDB27-FBF3-4D2D-BDA4-B4BF4D35B866}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {662FDB27-FBF3-4D2D-BDA4-B4BF4D35B866}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {662FDB27-FBF3-4D2D-BDA4-B4BF4D35B866}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {662FDB27-FBF3-4D2D-BDA4-B4BF4D35B866}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A0F72D3B-9A82-48EB-90AF-B3770151AD83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A0F72D3B-9A82-48EB-90AF-B3770151AD83}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A0F72D3B-9A82-48EB-90AF-B3770151AD83}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A0F72D3B-9A82-48EB-90AF-B3770151AD83}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {89A74B1B-445C-49EB-9C93-506DC243C227}
+ EndGlobalSection
+EndGlobal
diff --git a/logic/Client/Logo.png b/logic/Client/Logo.png
new file mode 100644
index 00000000..6d7d5114
Binary files /dev/null and b/logic/Client/Logo.png differ
diff --git a/logic/GameClass/GameObj/Areas/AreaFactory.cs b/logic/GameClass/GameObj/Areas/AreaFactory.cs
new file mode 100644
index 00000000..c2259b65
--- /dev/null
+++ b/logic/GameClass/GameObj/Areas/AreaFactory.cs
@@ -0,0 +1,19 @@
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Areas;
+
+public static class AreaFactory
+{
+ public static Immovable GetArea(XY pos, PlaceType placeType) => placeType switch
+ {
+ PlaceType.Home => new Home(pos),
+ PlaceType.Ruin => new Ruin(pos),
+ PlaceType.Shadow => new Shadow(pos),
+ PlaceType.Asteroid => new Asteroid(pos),
+ PlaceType.Resource => new Resource(pos),
+ PlaceType.Construction => new Construction(pos),
+ PlaceType.Wormhole => new Wormhole(pos),
+ _ => throw new System.NotImplementedException()
+ };
+ public static OutOfBoundBlock GetOutOfBoundBlock(XY pos) => new(pos);
+}
diff --git a/logic/GameClass/GameObj/Areas/Asteroid.cs b/logic/GameClass/GameObj/Areas/Asteroid.cs
new file mode 100644
index 00000000..4f0b12b6
--- /dev/null
+++ b/logic/GameClass/GameObj/Areas/Asteroid.cs
@@ -0,0 +1,14 @@
+using Preparation.Utility;
+using System;
+
+namespace GameClass.GameObj.Areas;
+
+public class Asteroid : Immovable
+{
+ public override bool IsRigid => true;
+ public override ShapeType Shape => ShapeType.Square;
+ public Asteroid(XY initPos)
+ : base(initPos, GameData.NumOfPosGridPerCell / 2, GameObjType.Asteroid)
+ {
+ }
+}
diff --git a/logic/GameClass/GameObj/Areas/Construction.cs b/logic/GameClass/GameObj/Areas/Construction.cs
new file mode 100644
index 00000000..b1bd2653
--- /dev/null
+++ b/logic/GameClass/GameObj/Areas/Construction.cs
@@ -0,0 +1,17 @@
+using Preparation.Utility;
+using System;
+
+namespace GameClass.GameObj.Areas;
+
+public class Construction : Immovable
+{
+ public LongWithVariableRange HP => throw new NotImplementedException();
+ public override bool IsRigid => constructionType == ConstructionType.Community;
+ public override ShapeType Shape => ShapeType.Square;
+ private ConstructionType constructionType = ConstructionType.Null;
+ public ConstructionType ConstructionType => constructionType;
+ public Construction(XY initPos)
+ : base(initPos, GameData.NumOfPosGridPerCell / 2, GameObjType.Construction)
+ {
+ }
+}
diff --git a/logic/GameClass/GameObj/Areas/Home.cs b/logic/GameClass/GameObj/Areas/Home.cs
new file mode 100644
index 00000000..d32cc1ef
--- /dev/null
+++ b/logic/GameClass/GameObj/Areas/Home.cs
@@ -0,0 +1,22 @@
+using Preparation.Interface;
+using Preparation.Utility;
+using System;
+
+namespace GameClass.GameObj.Areas;
+
+public class Home : Immovable, IHome
+{
+ public AtomicLong TeamID => throw new NotImplementedException();
+ public LongWithVariableRange HP => throw new NotImplementedException();
+ public long Score => throw new NotImplementedException();
+ public override bool IsRigid => false;
+ public override ShapeType Shape => ShapeType.Square;
+ public void AddScore(long add)
+ {
+ throw new NotImplementedException();
+ }
+ public Home(XY initPos)
+ : base(initPos, GameData.NumOfPosGridPerCell / 2, GameObjType.Home)
+ {
+ }
+}
diff --git a/logic/GameClass/GameObj/Areas/OutOfBoundBlock.cs b/logic/GameClass/GameObj/Areas/OutOfBoundBlock.cs
new file mode 100644
index 00000000..6d6d13a4
--- /dev/null
+++ b/logic/GameClass/GameObj/Areas/OutOfBoundBlock.cs
@@ -0,0 +1,17 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Areas;
+
+///
+/// 逻辑墙
+///
+public class OutOfBoundBlock : Immovable, IOutOfBound
+{
+ public override bool IsRigid => true;
+ public override ShapeType Shape => ShapeType.Square;
+ public OutOfBoundBlock(XY initPos)
+ : base(initPos, int.MaxValue, GameObjType.OutOfBoundBlock)
+ {
+ }
+}
diff --git a/logic/GameClass/GameObj/Areas/Resource.cs b/logic/GameClass/GameObj/Areas/Resource.cs
new file mode 100644
index 00000000..5879665d
--- /dev/null
+++ b/logic/GameClass/GameObj/Areas/Resource.cs
@@ -0,0 +1,14 @@
+using Preparation.Utility;
+using System;
+
+namespace GameClass.GameObj.Areas;
+
+public class Resource : Immovable
+{
+ public override bool IsRigid => true;
+ public override ShapeType Shape => ShapeType.Square;
+ public Resource(XY initPos)
+ : base(initPos, GameData.NumOfPosGridPerCell / 2, GameObjType.Resource)
+ {
+ }
+}
diff --git a/logic/GameClass/GameObj/Areas/Ruin.cs b/logic/GameClass/GameObj/Areas/Ruin.cs
new file mode 100644
index 00000000..b3da5f49
--- /dev/null
+++ b/logic/GameClass/GameObj/Areas/Ruin.cs
@@ -0,0 +1,14 @@
+using Preparation.Utility;
+using System;
+
+namespace GameClass.GameObj.Areas;
+
+public class Ruin : Immovable
+{
+ public override bool IsRigid => true;
+ public override ShapeType Shape => ShapeType.Square;
+ public Ruin(XY initPos)
+ : base(initPos, GameData.NumOfPosGridPerCell / 2, GameObjType.Ruin)
+ {
+ }
+}
diff --git a/logic/GameClass/GameObj/Areas/Shadow.cs b/logic/GameClass/GameObj/Areas/Shadow.cs
new file mode 100644
index 00000000..3705a760
--- /dev/null
+++ b/logic/GameClass/GameObj/Areas/Shadow.cs
@@ -0,0 +1,14 @@
+using Preparation.Utility;
+using System;
+
+namespace GameClass.GameObj.Areas;
+
+public class Shadow : Immovable
+{
+ public override bool IsRigid => false;
+ public override ShapeType Shape => ShapeType.Square;
+ public Shadow(XY initPos)
+ : base(initPos, GameData.NumOfPosGridPerCell / 2, GameObjType.Shadow)
+ {
+ }
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Areas/Wormhole.cs b/logic/GameClass/GameObj/Areas/Wormhole.cs
new file mode 100644
index 00000000..c68a813e
--- /dev/null
+++ b/logic/GameClass/GameObj/Areas/Wormhole.cs
@@ -0,0 +1,19 @@
+using Preparation.Interface;
+using Preparation.Utility;
+using System;
+using System.Collections.Generic;
+
+namespace GameClass.GameObj.Areas;
+
+public class Wormhole : Immovable, IWormhole
+{
+ public LongWithVariableRange HP => throw new NotImplementedException();
+ public List Entrance => throw new NotImplementedException();
+ public List Content => throw new NotImplementedException();
+ public override bool IsRigid => HP > GameData.WormholeHP / 2;
+ public override ShapeType Shape => ShapeType.Square;
+ public Wormhole(XY initPos)
+ : base(initPos, GameData.NumOfPosGridPerCell / 2, GameObjType.Wormhole)
+ {
+ }
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Bullet.cs b/logic/GameClass/GameObj/Bullet.cs
new file mode 100644
index 00000000..3fb409f5
--- /dev/null
+++ b/logic/GameClass/GameObj/Bullet.cs
@@ -0,0 +1,35 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj;
+
+public abstract class Bullet : ObjOfShip
+{
+ public abstract double BulletBombRange { get; }
+ public abstract double AttackDistance { get; }
+ public AtomicInt AP { get; } = new(0);
+ public abstract int Speed { get; }
+ public abstract int CastTime { get; }
+ public abstract int SwingTime { get; }
+ public abstract int CD { get; }
+ public abstract int MaxBulletNum { get; }
+ public override bool IsRigid => true; // 默认为true
+ public override ShapeType Shape => ShapeType.Circle; // 默认为圆形
+ public abstract BulletType TypeOfBullet { get; }
+ public abstract bool CanAttack(GameObj target);
+ public abstract bool CanBeBombed(GameObjType gameObjType);
+ public override bool IgnoreCollideExecutor(IGameObj targetObj)
+ {
+ if (targetObj == Parent) return true;
+ if (targetObj.Type == GameObjType.Bullet)
+ return true;
+ return false;
+ }
+ public Bullet(Ship ship, int radius, XY Position) :
+ base(Position, radius, GameObjType.Bullet)
+ {
+ this.CanMove.SetReturnOri(true);
+ this.MoveSpeed.SetReturnOri(this.Speed);
+ this.Parent = ship;
+ }
+}
diff --git a/logic/GameClass/GameObj/Bullets/Arc.cs b/logic/GameClass/GameObj/Bullets/Arc.cs
new file mode 100644
index 00000000..a45d8b47
--- /dev/null
+++ b/logic/GameClass/GameObj/Bullets/Arc.cs
@@ -0,0 +1,37 @@
+using Preparation.Utility;
+using System;
+
+namespace GameClass.GameObj.Bullets;
+
+internal sealed class Arc : Bullet
+{
+ public Arc(Ship ship, XY pos, int radius = GameData.BulletRadius) :
+ base(ship, radius, pos)
+ {
+ Random random = new Random();
+ this.AP.SetReturnOri(random.Next(GameData.ArcDamageMin, GameData.ArcDamageMax));
+ }
+ public override double BulletBombRange => 0;
+ public override double AttackDistance => GameData.ArcRange;
+ public override int Speed => GameData.ArcSpeed;
+ public override int CastTime => GameData.ArcCastTime;
+ public override int SwingTime => GameData.ArcSwingTime;
+ private const int cd = GameData.ArcSwingTime;
+ public override int CD => cd;
+ public const int maxBulletNum = 1;
+ public override int MaxBulletNum => maxBulletNum;
+ public override BulletType TypeOfBullet => BulletType.Arc;
+ public override bool CanAttack(GameObj target)
+ {
+ //if (target.Type == GameObjType.Ship
+ // || target.Type == GameObjType.Construction
+ // || target.Type == GameObjType.Wormhole
+ // || target.Type == GameObjType.Home)
+ // return true;
+ return false;
+ }
+ public override bool CanBeBombed(GameObjType gameObjType)
+ {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Bullets/BulletFactory.cs b/logic/GameClass/GameObj/Bullets/BulletFactory.cs
new file mode 100644
index 00000000..6dae1c49
--- /dev/null
+++ b/logic/GameClass/GameObj/Bullets/BulletFactory.cs
@@ -0,0 +1,16 @@
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Bullets;
+
+public static class BulletFactory
+{
+ public static Bullet? GetBullet(Ship ship, XY pos, BulletType bulletType) => bulletType switch
+ {
+ BulletType.Laser => new Laser(ship, pos),
+ BulletType.Plasma => new Plasma(ship, pos),
+ BulletType.Shell => new Shell(ship, pos),
+ BulletType.Missile => new Missile(ship, pos),
+ BulletType.Arc => new Arc(ship, pos),
+ _ => throw new System.NotImplementedException()
+ };
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Bullets/Laser.cs b/logic/GameClass/GameObj/Bullets/Laser.cs
new file mode 100644
index 00000000..21ed681d
--- /dev/null
+++ b/logic/GameClass/GameObj/Bullets/Laser.cs
@@ -0,0 +1,37 @@
+using Preparation.Interface;
+using Preparation.Utility;
+using System;
+using System.Threading;
+
+namespace GameClass.GameObj.Bullets;
+
+internal sealed class Laser : Bullet
+{
+ public Laser(Ship ship, XY pos, int radius = GameData.BulletRadius) :
+ base(ship, radius, pos)
+ {
+ this.AP.SetReturnOri(GameData.LaserDamage);
+ }
+ public override double BulletBombRange => 0;
+ public override double AttackDistance => GameData.LaserRange;
+ public override int Speed => GameData.LaserSpeed;
+ public override int CastTime => GameData.LaserCastTime;
+ public override int SwingTime => GameData.LaserSwingTime;
+ private const int cd = GameData.LaserSwingTime;
+ public override int CD => cd;
+ public override int MaxBulletNum => 1;
+ public override BulletType TypeOfBullet => BulletType.Laser;
+ public override bool CanAttack(GameObj target)
+ {
+ //if (target.Type == GameObjType.Ship
+ // || target.Type == GameObjType.Construction
+ // || target.Type == GameObjType.Wormhole
+ // || target.Type == GameObjType.Home)
+ // return true;
+ return false;
+ }
+ public override bool CanBeBombed(GameObjType gameObjType)
+ {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Bullets/Missile.cs b/logic/GameClass/GameObj/Bullets/Missile.cs
new file mode 100644
index 00000000..db767bd8
--- /dev/null
+++ b/logic/GameClass/GameObj/Bullets/Missile.cs
@@ -0,0 +1,36 @@
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Bullets;
+
+internal sealed class Missile : Bullet
+{
+ public Missile(Ship ship, XY pos, int radius = GameData.BulletRadius) :
+ base(ship, radius, pos)
+ {
+ this.AP.SetReturnOri(GameData.MissileDamage);
+ }
+ public override double BulletBombRange => GameData.MissileBombRange;
+ public override double AttackDistance => GameData.MissileRange;
+ public override int Speed => GameData.MissileSpeed;
+ public override int CastTime => GameData.MissileCastTime;
+ public override int SwingTime => GameData.ShellSwingTime;
+ private const int cd = GameData.ShellSwingTime;
+ public override int CD => cd;
+ public const int maxBulletNum = 1;
+ public override int MaxBulletNum => maxBulletNum;
+ public override BulletType TypeOfBullet => BulletType.Missile;
+ public override bool CanAttack(GameObj target)
+ {
+ //if (target.Type == GameObjType.Ship
+ // || target.Type == GameObjType.Construction
+ // || target.Type == GameObjType.Wormhole
+ // || target.Type == GameObjType.Home)
+ // return true;
+ return false;
+ }
+ public override bool CanBeBombed(GameObjType gameObjType)
+ {
+ //return true;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Bullets/Plasma.cs b/logic/GameClass/GameObj/Bullets/Plasma.cs
new file mode 100644
index 00000000..a407fbf7
--- /dev/null
+++ b/logic/GameClass/GameObj/Bullets/Plasma.cs
@@ -0,0 +1,35 @@
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Bullets;
+
+internal sealed class Plasma : Bullet
+{
+ public Plasma(Ship ship, XY pos, int radius = GameData.BulletRadius) :
+ base(ship, radius, pos)
+ {
+ this.AP.SetReturnOri(GameData.PlasmaDamage);
+ }
+ public override double BulletBombRange => 0;
+ public override double AttackDistance => GameData.PlasmaRange;
+ public override int Speed => GameData.PlasmaSpeed;
+ public override int CastTime => GameData.PlasmaCastTime;
+ public override int SwingTime => GameData.PlasmaSwingTime;
+ private const int cd = GameData.PlasmaSwingTime;
+ public override int CD => cd;
+ public const int maxBulletNum = 1;
+ public override int MaxBulletNum => maxBulletNum;
+ public override BulletType TypeOfBullet => BulletType.Plasma;
+ public override bool CanAttack(GameObj target)
+ {
+ //if (target.Type == GameObjType.Ship
+ // || target.Type == GameObjType.Construction
+ // || target.Type == GameObjType.Wormhole
+ // || target.Type == GameObjType.Home)
+ // return true;
+ return false;
+ }
+ public override bool CanBeBombed(GameObjType gameObjType)
+ {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Bullets/Shell.cs b/logic/GameClass/GameObj/Bullets/Shell.cs
new file mode 100644
index 00000000..a18cca6f
--- /dev/null
+++ b/logic/GameClass/GameObj/Bullets/Shell.cs
@@ -0,0 +1,35 @@
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Bullets;
+
+internal sealed class Shell : Bullet
+{
+ public Shell(Ship ship, XY pos, int radius = GameData.BulletRadius) :
+ base(ship, radius, pos)
+ {
+ this.AP.SetReturnOri(GameData.ShellDamage);
+ }
+ public override double BulletBombRange => 0;
+ public override double AttackDistance => GameData.ShellRange;
+ public override int Speed => GameData.ShellSpeed;
+ public override int CastTime => GameData.ShellCastTime;
+ public override int SwingTime => GameData.ShellSwingTime;
+ private const int cd = GameData.ShellSwingTime;
+ public override int CD => cd;
+ public const int maxBulletNum = 1;
+ public override int MaxBulletNum => maxBulletNum;
+ public override BulletType TypeOfBullet => BulletType.Shell;
+ public override bool CanAttack(GameObj target)
+ {
+ //if (target.Type == GameObjType.Ship
+ // || target.Type == GameObjType.Construction
+ // || target.Type == GameObjType.Wormhole
+ // || target.Type == GameObjType.Home)
+ // return true;
+ return false;
+ }
+ public override bool CanBeBombed(GameObjType gameObjType)
+ {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/GameObj.cs b/logic/GameClass/GameObj/GameObj.cs
new file mode 100644
index 00000000..0adbdb35
--- /dev/null
+++ b/logic/GameClass/GameObj/GameObj.cs
@@ -0,0 +1,43 @@
+using Preparation.Interface;
+using Preparation.Utility;
+using System.Threading;
+
+namespace GameClass.GameObj
+{
+ ///
+ /// 一切游戏元素的总基类,与THUAI4不同,继承IMoveable接口(出于一切物体其实都是可运动的指导思想)——LHR
+ ///
+ public abstract class GameObj : IGameObj
+ {
+ private readonly ReaderWriterLockSlim gameObjReaderWriterLock = new();
+ public ReaderWriterLockSlim GameObjReaderWriterLock => gameObjReaderWriterLock;
+ protected readonly object gameObjLock = new();
+ public object GameLock => gameObjLock;
+ protected readonly XY birthPos;
+ private readonly GameObjType type;
+ public GameObjType Type => type;
+ private static long currentMaxID = 0; // 目前游戏对象的最大ID
+ public const long invalidID = long.MaxValue; // 无效的ID
+ public long ID { get; }
+ protected XY position;
+ public abstract XY Position { get; }
+ public abstract bool IsRigid { get; }
+ public abstract ShapeType Shape { get; }
+
+ private AtomicBool isRemoved = new(false);
+ public AtomicBool IsRemoved { get => isRemoved; }
+ public virtual bool TryToRemove()
+ {
+ return IsRemoved.TrySet(true);
+ }
+ public int Radius { get; }
+ public virtual bool IgnoreCollideExecutor(IGameObj targetObj) => false;
+ public GameObj(XY initPos, int initRadius, GameObjType initType)
+ {
+ this.position = this.birthPos = initPos;
+ this.Radius = initRadius;
+ this.type = initType;
+ ID = Interlocked.Increment(ref currentMaxID);
+ }
+ }
+}
diff --git a/logic/GameClass/GameObj/Immovable.cs b/logic/GameClass/GameObj/Immovable.cs
new file mode 100644
index 00000000..07211f59
--- /dev/null
+++ b/logic/GameClass/GameObj/Immovable.cs
@@ -0,0 +1,12 @@
+using Preparation.Utility;
+
+namespace GameClass.GameObj;
+
+public abstract class Immovable : GameObj
+{
+ public override XY Position => position;
+ public Immovable(XY initPos, int initRadius, GameObjType initType)
+ : base(initPos, initRadius, initType)
+ {
+ }
+}
diff --git a/logic/GameClass/GameObj/Map/Map.cs b/logic/GameClass/GameObj/Map/Map.cs
new file mode 100644
index 00000000..2f4c4595
--- /dev/null
+++ b/logic/GameClass/GameObj/Map/Map.cs
@@ -0,0 +1,321 @@
+using System.Collections.Generic;
+using System.Threading;
+using Preparation.Interface;
+using Preparation.Utility;
+using System;
+
+namespace GameClass.GameObj
+{
+ public partial class Map : IMap
+ {
+ private Dictionary> gameObjDict;
+ public Dictionary> GameObjDict => gameObjDict;
+ private Dictionary gameObjLockDict;
+ public Dictionary GameObjLockDict => gameObjLockDict;
+ public readonly uint[,] protoGameMap;
+ public uint[,] ProtoGameMap => protoGameMap;
+ public PlaceType GetPlaceType(IGameObj obj)
+ {
+ try
+ {
+ return (PlaceType)protoGameMap[obj.Position.x / GameData.NumOfPosGridPerCell, obj.Position.y / GameData.NumOfPosGridPerCell];
+ }
+ catch
+ {
+ return PlaceType.Null;
+ }
+ }
+ public PlaceType GetPlaceType(XY pos)
+ {
+ try
+ {
+ return (PlaceType)protoGameMap[pos.x / GameData.NumOfPosGridPerCell, pos.y / GameData.NumOfPosGridPerCell];
+ }
+ catch
+ {
+ return PlaceType.Null;
+ }
+ }
+ public bool IsOutOfBound(IGameObj obj)
+ {
+ return obj.Position.x >= GameData.MapLength - obj.Radius || obj.Position.x <= obj.Radius || obj.Position.y >= GameData.MapLength - obj.Radius || obj.Position.y <= obj.Radius;
+ }
+ public IOutOfBound GetOutOfBound(XY pos)
+ {
+ return new Areas.OutOfBoundBlock(pos);
+ }
+ public Ship? FindShipInID(long ID)
+ {
+ Ship? ship = null;
+ gameObjLockDict[GameObjType.Ship].EnterReadLock();
+ try
+ {
+ foreach (Ship s in gameObjDict[GameObjType.Ship])
+ {
+ if (s.ID == ID)
+ {
+ ship = s;
+ break;
+ }
+ }
+ }
+ finally
+ {
+ gameObjLockDict[GameObjType.Ship].ExitReadLock();
+ }
+ return ship;
+ }
+ public Ship? FindShipInShipID(long shipID)
+ {
+ Ship? ship = null;
+ gameObjLockDict[GameObjType.Ship].EnterReadLock();
+ try
+ {
+ foreach (Ship s in gameObjDict[GameObjType.Ship])
+ {
+ if (s.ShipID == shipID)
+ {
+ ship = s;
+ break;
+ }
+ }
+ }
+ finally
+ {
+ gameObjLockDict[GameObjType.Ship].ExitReadLock();
+ }
+ return ship;
+ }
+ public GameObj? OneForInteract(XY Pos, GameObjType gameObjType)
+ {
+ GameObj? GameObjForInteract = null;
+ GameObjLockDict[gameObjType].EnterReadLock();
+ try
+ {
+ foreach (GameObj gameObj in GameObjDict[gameObjType])
+ {
+ if (GameData.ApproachToInteract(gameObj.Position, Pos))
+ {
+ GameObjForInteract = gameObj;
+ break;
+ }
+ }
+ }
+ finally
+ {
+ GameObjLockDict[gameObjType].ExitReadLock();
+ }
+ return GameObjForInteract;
+ }
+ public GameObj? OneInTheSameCell(XY Pos, GameObjType gameObjType)
+ {
+ GameObj? GameObjForInteract = null;
+ GameObjLockDict[gameObjType].EnterReadLock();
+ try
+ {
+ foreach (GameObj gameObj in GameObjDict[gameObjType])
+ {
+ if (GameData.IsInTheSameCell(gameObj.Position, Pos))
+ {
+ GameObjForInteract = gameObj;
+ break;
+ }
+ }
+ }
+ finally
+ {
+ GameObjLockDict[gameObjType].ExitReadLock();
+ }
+ return GameObjForInteract;
+ }
+ public GameObj? PartInTheSameCell(XY Pos, GameObjType gameObjType)
+ {
+ GameObj? GameObjForInteract = null;
+ GameObjLockDict[gameObjType].EnterReadLock();
+ try
+ {
+ foreach (GameObj gameObj in GameObjDict[gameObjType])
+ {
+ if (GameData.PartInTheSameCell(gameObj.Position, Pos))
+ {
+ GameObjForInteract = gameObj;
+ break;
+ }
+ }
+ }
+ finally
+ {
+ GameObjLockDict[gameObjType].ExitReadLock();
+ }
+ return GameObjForInteract;
+ }
+ public GameObj? OneForInteractInACross(XY Pos, GameObjType gameObjType)
+ {
+ GameObj? GameObjForInteract = null;
+ GameObjLockDict[gameObjType].EnterReadLock();
+ try
+ {
+ foreach (GameObj gameObj in GameObjDict[gameObjType])
+ {
+ if (GameData.ApproachToInteractInACross(gameObj.Position, Pos))
+ {
+ GameObjForInteract = gameObj;
+ break;
+ }
+ }
+ }
+ finally
+ {
+ GameObjLockDict[gameObjType].ExitReadLock();
+ }
+ return GameObjForInteract;
+ }
+ public bool CanSee(Ship ship, GameObj gameObj)
+ {
+ XY pos1 = ship.Position;
+ XY pos2 = gameObj.Position;
+ XY del = pos1 - pos2;
+ if (del * del > ship.ViewRange * ship.ViewRange)
+ return false;
+ if (del.x > del.y)
+ {
+ if (GetPlaceType(pos1) == PlaceType.Shadow && GetPlaceType(pos2) == PlaceType.Shadow)
+ {
+ for (int x = GameData.PosGridToCellX(pos1) + GameData.NumOfPosGridPerCell; x < GameData.PosGridToCellX(pos2); x += GameData.NumOfPosGridPerCell)
+ {
+ if (GetPlaceType(pos1 + del * (x / del.x)) != PlaceType.Shadow)
+ return false;
+ }
+ }
+ else
+ {
+ for (int x = GameData.PosGridToCellX(pos1) + GameData.NumOfPosGridPerCell; x < GameData.PosGridToCellX(pos2); x += GameData.NumOfPosGridPerCell)
+ {
+ if (GetPlaceType(pos1 + del * (x / del.x)) == PlaceType.Ruin)
+ return false;
+ }
+ }
+ }
+ else
+ {
+ if (GetPlaceType(pos1) == PlaceType.Shadow && GetPlaceType(pos2) == PlaceType.Shadow)
+ {
+ for (int y = GameData.PosGridToCellY(pos1) + GameData.NumOfPosGridPerCell; y < GameData.PosGridToCellY(pos2); y += GameData.NumOfPosGridPerCell)
+ {
+ if (GetPlaceType(pos1 + del * (y / del.y)) != PlaceType.Shadow)
+ return false;
+ }
+ }
+ else
+ {
+ for (int y = GameData.PosGridToCellY(pos1) + GameData.NumOfPosGridPerCell; y < GameData.PosGridToCellY(pos2); y += GameData.NumOfPosGridPerCell)
+ {
+ if (GetPlaceType(pos1 + del * (y / del.y)) == PlaceType.Ruin)
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ public bool Remove(GameObj gameObj)
+ {
+ GameObj? ToDel = null;
+ GameObjLockDict[gameObj.Type].EnterWriteLock();
+ try
+ {
+ foreach (GameObj obj in GameObjDict[gameObj.Type])
+ {
+ if (gameObj.ID == obj.ID)
+ {
+ ToDel = obj;
+ break;
+ }
+ }
+ if (ToDel != null)
+ {
+ GameObjDict[gameObj.Type].Remove(ToDel);
+ ToDel.TryToRemove();
+ }
+ }
+ finally
+ {
+ GameObjLockDict[gameObj.Type].ExitWriteLock();
+ }
+ return ToDel != null;
+ }
+ public bool RemoveJustFromMap(GameObj gameObj)
+ {
+ GameObjLockDict[gameObj.Type].EnterWriteLock();
+ try
+ {
+ if (GameObjDict[gameObj.Type].Remove(gameObj))
+ {
+ gameObj.TryToRemove();
+ return true;
+ }
+ return false;
+ }
+ finally
+ {
+ GameObjLockDict[gameObj.Type].ExitWriteLock();
+ }
+ }
+ public void Add(GameObj gameObj)
+ {
+ GameObjLockDict[gameObj.Type].EnterWriteLock();
+ try
+ {
+ GameObjDict[gameObj.Type].Add(gameObj);
+ }
+ finally
+ {
+ GameObjLockDict[gameObj.Type].ExitWriteLock();
+ }
+ }
+ public Map(uint[,] mapResource)
+ {
+ gameObjDict = new Dictionary>();
+ gameObjLockDict = new Dictionary();
+ foreach (GameObjType idx in Enum.GetValues(typeof(GameObjType)))
+ {
+ if (idx != GameObjType.Null)
+ {
+ gameObjDict.Add(idx, new List());
+ gameObjLockDict.Add(idx, new ReaderWriterLockSlim());
+ }
+ }
+ protoGameMap = new uint[mapResource.GetLength(0), mapResource.GetLength(1)];
+ Array.Copy(mapResource, protoGameMap, mapResource.Length);
+ for (int i = 0; i < GameData.MapRows; ++i)
+ {
+ for (int j = 0; j < GameData.MapCols; ++j)
+ {
+ switch (mapResource[i, j])
+ {
+ case (uint)PlaceType.Asteroid:
+ Add(new Areas.Asteroid(GameData.GetCellCenterPos(i, j)));
+ break;
+ case (uint)PlaceType.Construction:
+ Add(new Areas.Construction(GameData.GetCellCenterPos(i, j)));
+ break;
+ case (uint)PlaceType.Home:
+ Add(new Areas.Home(GameData.GetCellCenterPos(i, j)));
+ break;
+ case (uint)PlaceType.Resource:
+ Add(new Areas.Resource(GameData.GetCellCenterPos(i, j)));
+ break;
+ case (uint)PlaceType.Ruin:
+ Add(new Areas.Ruin(GameData.GetCellCenterPos(i, j)));
+ break;
+ case (uint)PlaceType.Shadow:
+ Add(new Areas.Shadow(GameData.GetCellCenterPos(i, j)));
+ break;
+ case (uint)PlaceType.Wormhole:
+ Add(new Areas.Wormhole(GameData.GetCellCenterPos(i, j)));
+ break;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Map/MapGameTimer.cs b/logic/GameClass/GameObj/Map/MapGameTimer.cs
new file mode 100644
index 00000000..c8cb262e
--- /dev/null
+++ b/logic/GameClass/GameObj/Map/MapGameTimer.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Threading;
+using Preparation.Interface;
+
+namespace GameClass.GameObj
+{
+ public partial class Map
+ {
+ // xfgg说:爱因斯坦说,每个坐标系都有与之绑定的时钟,(x, y, z, ict) 构成四维时空坐标,在洛伦兹变换下满足矢量性(狗头)
+ private readonly GameTimer timer = new();
+ public ITimer Timer => timer;
+
+ public class GameTimer : ITimer
+ {
+ private long startTime;
+ public int nowTime() => (int)(Environment.TickCount64 - startTime);
+
+ private bool isGaming = false;
+ public bool IsGaming
+ {
+ get => isGaming;
+ set
+ {
+ lock (isGamingLock)
+ isGaming = value;
+ }
+ }
+
+ readonly object isGamingLock = new();
+
+ public bool StartGame(int timeInMilliseconds)
+ {
+ lock (isGamingLock)
+ {
+ if (isGaming)
+ return false;
+ isGaming = true;
+ startTime = Environment.TickCount64;
+ }
+ Thread.Sleep(timeInMilliseconds);
+ isGaming = false;
+ return true;
+ }
+ }
+ }
+}
diff --git a/logic/GameClass/GameObj/Modules/Civil/CivilArmor.cs b/logic/GameClass/GameObj/Modules/Civil/CivilArmor.cs
new file mode 100644
index 00000000..0ed1b35c
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/Civil/CivilArmor.cs
@@ -0,0 +1,12 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class CivilArmor1 : IArmor
+{
+ private const int cost = GameData.CivilShipArmor1Cost;
+ public int Cost => cost;
+ private const int armorHP = GameData.Armor1;
+ public int ArmorHP => armorHP;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/Civil/CivilConstructor.cs b/logic/GameClass/GameObj/Modules/Civil/CivilConstructor.cs
new file mode 100644
index 00000000..ced19df0
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/Civil/CivilConstructor.cs
@@ -0,0 +1,26 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class CivilConstructor1 : IConstructor
+{
+ private const int cost = GameData.CivilShipConstructor1Cost;
+ public int Cost => cost;
+ private const int constructSpeed = GameData.Constructor1Speed;
+ public int ConstructSpeed => constructSpeed;
+}
+public class CivilConstructor2 : IConstructor
+{
+ private const int cost = GameData.CivilShipConstructor2Cost;
+ public int Cost => cost;
+ private const int constructSpeed = GameData.Constructor2Speed;
+ public int ConstructSpeed => constructSpeed;
+}
+public class CivilConstructor3 : IConstructor
+{
+ private const int cost = GameData.CivilShipConstructor3Cost;
+ public int Cost => cost;
+ private const int constructSpeed = GameData.Constructor2Speed;
+ public int ConstructSpeed => constructSpeed;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/Civil/CivilProducer.cs b/logic/GameClass/GameObj/Modules/Civil/CivilProducer.cs
new file mode 100644
index 00000000..5acffe38
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/Civil/CivilProducer.cs
@@ -0,0 +1,26 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class CivilProducer1 : IProducer
+{
+ private const int cost = GameData.CivilShipProducer1Cost;
+ public int Cost => cost;
+ private const int produceSpeed = GameData.ScoreProducer1PerSecond;
+ public int ProduceSpeed => produceSpeed;
+}
+public class CivilProducer2 : IProducer
+{
+ private const int cost = GameData.CivilShipProducer1Cost;
+ public int Cost => cost;
+ private const int produceSpeed = GameData.ScoreProducer2PerSecond;
+ public int ProduceSpeed => produceSpeed;
+}
+public class CivilProducer3 : IProducer
+{
+ private const int cost = GameData.CivilShipProducer1Cost;
+ public int Cost => cost;
+ private const int produceSpeed = GameData.ScoreProducer3PerSecond;
+ public int ProduceSpeed => produceSpeed;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/Civil/CivilShield.cs b/logic/GameClass/GameObj/Modules/Civil/CivilShield.cs
new file mode 100644
index 00000000..485548cf
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/Civil/CivilShield.cs
@@ -0,0 +1,12 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class CivilShield1 : IShield
+{
+ private const int cost = GameData.CivilShipShield1Cost;
+ public int Cost => cost;
+ private const int shieldHP = GameData.Shield1;
+ public int ShieldHP => shieldHP;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/Civil/CivilWeapon.cs b/logic/GameClass/GameObj/Modules/Civil/CivilWeapon.cs
new file mode 100644
index 00000000..d5028ea8
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/Civil/CivilWeapon.cs
@@ -0,0 +1,12 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class CivilLaserGun : IWeapon
+{
+ private const int cost = GameData.CivilShipLaserGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Laser;
+ public BulletType BulletType => bulletType;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/Flag/FlagArmor.cs b/logic/GameClass/GameObj/Modules/Flag/FlagArmor.cs
new file mode 100644
index 00000000..ae6bd93d
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/Flag/FlagArmor.cs
@@ -0,0 +1,26 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class FlagArmor1 : IArmor
+{
+ private const int cost = GameData.FlagShipArmor1Cost;
+ public int Cost => cost;
+ private const int armorHP = GameData.Armor1;
+ public int ArmorHP => armorHP;
+}
+public class FlagArmor2 : IArmor
+{
+ private const int cost = GameData.FlagShipArmor2Cost;
+ public int Cost => cost;
+ private const int armorHP = GameData.Armor2;
+ public int ArmorHP => armorHP;
+}
+public class FlagArmor3 : IArmor
+{
+ private const int cost = GameData.FlagShipArmor3Cost;
+ public int Cost => cost;
+ private const int armorHP = GameData.Armor3;
+ public int ArmorHP => armorHP;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/Flag/FlagConstructor.cs b/logic/GameClass/GameObj/Modules/Flag/FlagConstructor.cs
new file mode 100644
index 00000000..3dc4f277
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/Flag/FlagConstructor.cs
@@ -0,0 +1,12 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class FlagConstructor1 : IConstructor
+{
+ private const int cost = GameData.FlagShipConstructor1Cost;
+ public int Cost => cost;
+ private const int constructSpeed = GameData.Constructor1Speed;
+ public int ConstructSpeed => constructSpeed;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/Flag/FlagProducer.cs b/logic/GameClass/GameObj/Modules/Flag/FlagProducer.cs
new file mode 100644
index 00000000..142055ba
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/Flag/FlagProducer.cs
@@ -0,0 +1,12 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class FlagProducer1 : IProducer
+{
+ private const int cost = GameData.FlagShipProducer1Cost;
+ public int Cost => cost;
+ private const int produceSpeed = GameData.ScoreProducer1PerSecond;
+ public int ProduceSpeed => produceSpeed;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/Flag/FlagShield.cs b/logic/GameClass/GameObj/Modules/Flag/FlagShield.cs
new file mode 100644
index 00000000..d575533c
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/Flag/FlagShield.cs
@@ -0,0 +1,26 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class FlagShield1 : IShield
+{
+ private const int cost = GameData.FlagShipShield1Cost;
+ public int Cost => cost;
+ private const int shieldHP = GameData.Shield1;
+ public int ShieldHP => shieldHP;
+}
+public class FlagShield2 : IShield
+{
+ private const int cost = GameData.FlagShipShield2Cost;
+ public int Cost => cost;
+ private const int shieldHP = GameData.Shield2;
+ public int ShieldHP => shieldHP;
+}
+public class FlagShield3 : IShield
+{
+ private const int cost = GameData.FlagShipShield3Cost;
+ public int Cost => cost;
+ private const int shieldHP = GameData.Shield3;
+ public int ShieldHP => shieldHP;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/Flag/FlagWeapon.cs b/logic/GameClass/GameObj/Modules/Flag/FlagWeapon.cs
new file mode 100644
index 00000000..8a6606df
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/Flag/FlagWeapon.cs
@@ -0,0 +1,40 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class FlagLaserGun : IWeapon
+{
+ private const int cost = GameData.FlagShipLaserGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Laser;
+ public BulletType BulletType => bulletType;
+}
+public class FlagPlasmaGun : IWeapon
+{
+ private const int cost = GameData.FlagShipPlasmaGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Plasma;
+ public BulletType BulletType => bulletType;
+}
+public class FlagShellGun : IWeapon
+{
+ private const int cost = GameData.FlagShipShellGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Shell;
+ public BulletType BulletType => bulletType;
+}
+public class FlagMissileGun : IWeapon
+{
+ private const int cost = GameData.FlagShipMissileGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Missile;
+ public BulletType BulletType => bulletType;
+}
+public class FlagArcGun : IWeapon
+{
+ private const int cost = GameData.FlagShipArcGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Arc;
+ public BulletType BulletType => bulletType;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/ModuleFactory.cs b/logic/GameClass/GameObj/Modules/ModuleFactory.cs
new file mode 100644
index 00000000..aef2dd48
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/ModuleFactory.cs
@@ -0,0 +1,113 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public static class ModuleFactory
+{
+ public static IProducer FindIProducer(ShipType shipType, ProducerType producerType) => shipType switch
+ {
+ ShipType.CivilShip => producerType switch
+ {
+ ProducerType.Producer1 => new CivilProducer1(),
+ ProducerType.Producer2 => new CivilProducer2(),
+ ProducerType.Producer3 => new CivilProducer3(),
+ _ => throw new System.NotImplementedException()
+ },
+ ShipType.FlagShip => producerType switch
+ {
+ ProducerType.Producer1 => new FlagProducer1(),
+ _ => throw new System.NotImplementedException()
+ },
+ _ => throw new System.NotImplementedException()
+ };
+ public static IConstructor FindIConstructor(ShipType shipType, ConstructorType constructorType) => shipType switch
+ {
+ ShipType.CivilShip => constructorType switch
+ {
+ ConstructorType.Constructor1 => new CivilConstructor1(),
+ ConstructorType.Constructor2 => new CivilConstructor2(),
+ ConstructorType.Constructor3 => new CivilConstructor3(),
+ _ => throw new System.NotImplementedException()
+ },
+ ShipType.FlagShip => constructorType switch
+ {
+ ConstructorType.Constructor1 => new FlagConstructor1(),
+ _ => throw new System.NotImplementedException()
+ },
+ _ => throw new System.NotImplementedException()
+ };
+ public static IArmor FindIArmor(ShipType shipType, ArmorType armorType) => shipType switch
+ {
+ ShipType.CivilShip => armorType switch
+ {
+ ArmorType.Armor1 => new CivilArmor1(),
+ _ => throw new System.NotImplementedException()
+ },
+ ShipType.WarShip => armorType switch
+ {
+ ArmorType.Armor1 => new WarArmor1(),
+ ArmorType.Armor2 => new WarArmor2(),
+ ArmorType.Armor3 => new WarArmor3(),
+ _ => throw new System.NotImplementedException()
+ },
+ ShipType.FlagShip => armorType switch
+ {
+ ArmorType.Armor1 => new FlagArmor1(),
+ ArmorType.Armor2 => new FlagArmor2(),
+ ArmorType.Armor3 => new FlagArmor3(),
+ _ => throw new System.NotImplementedException()
+ },
+ _ => throw new System.NotImplementedException()
+ };
+ public static IShield FindIShield(ShipType shipType, ShieldType shieldType) => shipType switch
+ {
+ ShipType.CivilShip => shieldType switch
+ {
+ ShieldType.Shield1 => new CivilShield1(),
+ _ => throw new System.NotImplementedException()
+ },
+ ShipType.WarShip => shieldType switch
+ {
+ ShieldType.Shield1 => new WarShield1(),
+ ShieldType.Shield2 => new WarShield2(),
+ ShieldType.Shield3 => new WarShield3(),
+ _ => throw new System.NotImplementedException()
+ },
+ ShipType.FlagShip => shieldType switch
+ {
+ ShieldType.Shield1 => new FlagShield1(),
+ ShieldType.Shield2 => new FlagShield2(),
+ ShieldType.Shield3 => new FlagShield3(),
+ _ => throw new System.NotImplementedException()
+ },
+ _ => throw new System.NotImplementedException()
+ };
+ public static IWeapon FindIWeapon(ShipType shipType, WeaponType weaponType) => shipType switch
+ {
+ ShipType.CivilShip => weaponType switch
+ {
+ WeaponType.LaserGun => new CivilLaserGun(),
+ _ => throw new System.NotImplementedException()
+ },
+ ShipType.WarShip => weaponType switch
+ {
+ WeaponType.LaserGun => new WarLaserGun(),
+ WeaponType.PlasmaGun => new WarPlasmaGun(),
+ WeaponType.ShellGun => new WarShellGun(),
+ WeaponType.MissileGun => new WarMissileGun(),
+ WeaponType.ArcGun => new WarArcGun(),
+ _ => throw new System.NotImplementedException()
+ },
+ ShipType.FlagShip => weaponType switch
+ {
+ WeaponType.LaserGun => new FlagLaserGun(),
+ WeaponType.PlasmaGun => new FlagPlasmaGun(),
+ WeaponType.ShellGun => new FlagShellGun(),
+ WeaponType.MissileGun => new FlagMissileGun(),
+ WeaponType.ArcGun => new FlagArcGun(),
+ _ => throw new System.NotImplementedException()
+ },
+ _ => throw new System.NotImplementedException()
+ };
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/War/WarArmor.cs b/logic/GameClass/GameObj/Modules/War/WarArmor.cs
new file mode 100644
index 00000000..6083ebe8
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/War/WarArmor.cs
@@ -0,0 +1,26 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class WarArmor1 : IArmor
+{
+ private const int cost = GameData.WarShipArmor1Cost;
+ public int Cost => cost;
+ private const int armorHP = GameData.Armor1;
+ public int ArmorHP => armorHP;
+}
+public class WarArmor2 : IArmor
+{
+ private const int cost = GameData.WarShipArmor2Cost;
+ public int Cost => cost;
+ private const int armorHP = GameData.Armor2;
+ public int ArmorHP => armorHP;
+}
+public class WarArmor3 : IArmor
+{
+ private const int cost = GameData.WarShipArmor3Cost;
+ public int Cost => cost;
+ private const int armorHP = GameData.Armor3;
+ public int ArmorHP => armorHP;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/War/WarShield.cs b/logic/GameClass/GameObj/Modules/War/WarShield.cs
new file mode 100644
index 00000000..ac9f90b4
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/War/WarShield.cs
@@ -0,0 +1,26 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class WarShield1 : IShield
+{
+ private const int cost = GameData.WarShipShield1Cost;
+ public int Cost => cost;
+ private const int shieldHP = GameData.Shield1;
+ public int ShieldHP => shieldHP;
+}
+public class WarShield2 : IShield
+{
+ private const int cost = GameData.WarShipShield2Cost;
+ public int Cost => cost;
+ private const int shieldHP = GameData.Shield2;
+ public int ShieldHP => shieldHP;
+}
+public class WarShield3 : IShield
+{
+ private const int cost = GameData.WarShipShield3Cost;
+ public int Cost => cost;
+ private const int shieldHP = GameData.Shield3;
+ public int ShieldHP => shieldHP;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Modules/War/WarWeapon.cs b/logic/GameClass/GameObj/Modules/War/WarWeapon.cs
new file mode 100644
index 00000000..bbdce2af
--- /dev/null
+++ b/logic/GameClass/GameObj/Modules/War/WarWeapon.cs
@@ -0,0 +1,40 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Modules;
+
+public class WarLaserGun : IWeapon
+{
+ private const int cost = GameData.WarShipLaserGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Laser;
+ public BulletType BulletType => bulletType;
+}
+public class WarPlasmaGun : IWeapon
+{
+ private const int cost = GameData.WarShipPlasmaGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Plasma;
+ public BulletType BulletType => bulletType;
+}
+public class WarShellGun : IWeapon
+{
+ private const int cost = GameData.WarShipShellGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Shell;
+ public BulletType BulletType => bulletType;
+}
+public class WarMissileGun : IWeapon
+{
+ private const int cost = GameData.WarShipMissileGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Missile;
+ public BulletType BulletType => bulletType;
+}
+public class WarArcGun : IWeapon
+{
+ private const int cost = GameData.WarShipArcGunCost;
+ public int Cost => cost;
+ private const BulletType bulletType = BulletType.Arc;
+ public BulletType BulletType => bulletType;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Movable.cs b/logic/GameClass/GameObj/Movable.cs
new file mode 100644
index 00000000..0a157bfd
--- /dev/null
+++ b/logic/GameClass/GameObj/Movable.cs
@@ -0,0 +1,127 @@
+using Preparation.Interface;
+using Preparation.Utility;
+using System.Threading;
+
+namespace GameClass.GameObj
+{
+ public abstract class Movable : GameObj, IMovable
+ {
+ protected readonly object actionLock = new();
+ ///
+ /// Player.ActionLock > 其他.ActionLock / 其他Lock,应当避免两个Player的Actionlock互锁
+ ///
+ public object ActionLock => actionLock;
+ private readonly ReaderWriterLockSlim moveReaderWriterLock = new();
+ ///
+ /// 规定MoveReaderWriterLock < ActionLock
+ ///
+ public ReaderWriterLockSlim MoveReaderWriterLock => moveReaderWriterLock;
+ public Semaphore ThreadNum { get; } = new(1, 1);
+ protected long stateNum = 0;
+ public long StateNum
+ {
+ get
+ {
+ lock (actionLock)
+ return stateNum;
+ }
+ set
+ {
+ lock (actionLock) stateNum = value;
+ }
+ }
+ protected RunningStateType runningState = RunningStateType.Null;
+ public RunningStateType RunningState
+ {
+ get
+ {
+ lock (actionLock) return runningState;
+ }
+ set
+ {
+ lock (actionLock)
+ runningState = value;
+ }
+ }
+ public override XY Position
+ {
+ get
+ {
+ lock (actionLock)
+ return position;
+ }
+ }
+ protected XY facingDirection = new(1, 0);
+ public XY FacingDirection
+ {
+ get
+ {
+ lock (actionLock)
+ return facingDirection;
+ }
+ set
+ {
+ lock (actionLock)
+ facingDirection = value;
+ }
+ }
+ public AtomicBool IsMoving { get; } = new(false);
+ ///
+ /// 移动,改变坐标
+ ///
+ public long MovingSetPos(XY moveVec, long stateNo)
+ {
+
+ if (moveVec.x != 0 || moveVec.y != 0)
+ {
+ lock (actionLock)
+ {
+ if (!CanMove || IsRemoved) return -1;
+ if (stateNo != stateNum) return -1;
+ facingDirection = moveVec;
+ this.position += moveVec;
+ }
+ }
+ return moveVec * moveVec;
+ }
+
+ public void ReSetPos(XY position)
+ {
+ lock (actionLock)
+ {
+ this.position = position;
+ }
+ }
+ private AtomicBool canMove = new(false);
+ public AtomicBool CanMove { get => canMove; }
+ public bool IsAvailableForMove => !IsMoving && CanMove && !IsRemoved; // 是否能接收移动指令
+ private AtomicInt moveSpeed = new(0);
+ ///
+ /// 移动速度
+ ///
+ public AtomicInt MoveSpeed { get => moveSpeed; }
+ protected int orgMoveSpeed;
+ ///
+ /// 原初移动速度
+ ///
+ public int OrgMoveSpeed => orgMoveSpeed;
+ /* ///
+ /// 复活时数据重置
+ ///
+ public virtual void Reset(PlaceType place)
+ {
+ lock (gameObjLock)
+ {
+ this.FacingDirection = new XY(1, 0);
+ isMoving = false;
+ CanMove = false;
+ IsRemoved = true;
+ this.Position = birthPos;
+ this.Place= place;
+ }
+ }*/
+ public Movable(XY initPos, int initRadius, GameObjType initType) : base(initPos, initRadius, initType)
+ {
+ }
+ }
+}
diff --git a/logic/GameClass/GameObj/ObjOfShip.cs b/logic/GameClass/GameObj/ObjOfShip.cs
new file mode 100644
index 00000000..83d0c5f5
--- /dev/null
+++ b/logic/GameClass/GameObj/ObjOfShip.cs
@@ -0,0 +1,32 @@
+using Preparation.Interface;
+using Preparation.Utility;
+using System.Threading;
+
+namespace GameClass.GameObj
+{
+ ///
+ /// 所有物,具有主人(Parent)(特定玩家)属性的对象
+ ///
+ public abstract class ObjOfShip : Movable, IObjOfShip
+ {
+ public object ObjOfShipLock { get; } = new();
+ private IShip? parent = null;
+ public IShip? Parent
+ {
+ get
+ {
+ lock (ObjOfShipLock)
+ return parent;
+ }
+ set
+ {
+ lock (ObjOfShipLock)
+ parent = value;
+ }
+ }
+ public ObjOfShip(XY initPos, int initRadius, GameObjType initType) :
+ base(initPos, initRadius, initType)
+ {
+ }
+ }
+}
diff --git a/logic/GameClass/GameObj/Occupations/CivilShip.cs b/logic/GameClass/GameObj/Occupations/CivilShip.cs
new file mode 100644
index 00000000..ab84d254
--- /dev/null
+++ b/logic/GameClass/GameObj/Occupations/CivilShip.cs
@@ -0,0 +1,14 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Occupations;
+
+public class CivilShip : IOccupation
+{
+ private const int moveSpeed = GameData.CivilShipMoveSpeed;
+ public int MoveSpeed => moveSpeed;
+ private const int maxHp = GameData.CivilShipMaxHP;
+ public int MaxHp => maxHp;
+ private const int viewRange = GameData.CivilShipViewRange;
+ public int ViewRange => viewRange;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Occupations/FlagShip.cs b/logic/GameClass/GameObj/Occupations/FlagShip.cs
new file mode 100644
index 00000000..f5b7d122
--- /dev/null
+++ b/logic/GameClass/GameObj/Occupations/FlagShip.cs
@@ -0,0 +1,14 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Occupations;
+
+public class FlagShip : IOccupation
+{
+ private const int moveSpeed = GameData.FlagShipMoveSpeed;
+ public int MoveSpeed => moveSpeed;
+ private const int maxHp = GameData.FlagShipMaxHP;
+ public int MaxHp => maxHp;
+ private const int viewRange = GameData.FlagShipViewRange;
+ public int ViewRange => viewRange;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Occupations/OccupationFactory.cs b/logic/GameClass/GameObj/Occupations/OccupationFactory.cs
new file mode 100644
index 00000000..8ee514a1
--- /dev/null
+++ b/logic/GameClass/GameObj/Occupations/OccupationFactory.cs
@@ -0,0 +1,15 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Occupations;
+
+public static class OccupationFactory
+{
+ public static IOccupation FindIOccupation(ShipType shipType) => shipType switch
+ {
+ ShipType.CivilShip => new CivilShip(),
+ ShipType.WarShip => new WarShip(),
+ ShipType.FlagShip => new FlagShip(),
+ _ => throw new System.NotImplementedException(),
+ };
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Occupations/WarShip.cs b/logic/GameClass/GameObj/Occupations/WarShip.cs
new file mode 100644
index 00000000..acbf0139
--- /dev/null
+++ b/logic/GameClass/GameObj/Occupations/WarShip.cs
@@ -0,0 +1,14 @@
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameClass.GameObj.Occupations;
+
+public class WarShip : IOccupation
+{
+ private const int moveSpeed = GameData.WarShipMoveSpeed;
+ public int MoveSpeed => moveSpeed;
+ private const int maxHp = GameData.WarShipMaxHP;
+ public int MaxHp => maxHp;
+ private const int viewRange = GameData.WarShipViewRange;
+ public int ViewRange => viewRange;
+}
\ No newline at end of file
diff --git a/logic/GameClass/GameObj/Ship.cs b/logic/GameClass/GameObj/Ship.cs
new file mode 100644
index 00000000..4d031c44
--- /dev/null
+++ b/logic/GameClass/GameObj/Ship.cs
@@ -0,0 +1,205 @@
+using Preparation.Interface;
+using Preparation.Utility;
+using GameClass.GameObj.Modules;
+using GameClass.GameObj.Occupations;
+
+namespace GameClass.GameObj;
+
+public class Ship : Movable, IShip
+{
+ public AtomicLong TeamID { get; } = new AtomicLong(long.MaxValue);
+ public AtomicLong ShipID { get; } = new AtomicLong(long.MaxValue);
+ public override bool IsRigid => true;
+ public override ShapeType Shape => ShapeType.Circle;
+ private readonly int viewRange;
+ public int ViewRange => viewRange;
+ public override bool IgnoreCollideExecutor(IGameObj targetObj)
+ {
+ if (IsRemoved)
+ return true;
+ if (targetObj.Type == GameObjType.Ship && XY.DistanceCeil3(targetObj.Position, this.Position) < this.Radius + targetObj.Radius - GameData.AdjustLength)
+ return true;
+ return false;
+ }
+ public LongWithVariableRange HP { get; }
+ public LongWithVariableRange Armor { get; }
+ public LongWithVariableRange Shield { get; }
+ private ShipType shipType = ShipType.Null;
+ public ShipType ShipType => shipType;
+ private ShipStateType shipState = ShipStateType.Null;
+ public ShipStateType ShipState => shipState;
+ private readonly IOccupation occupation;
+ public IOccupation Occupation => occupation;
+ public IntNumUpdateByCD BulletNum { get; }
+ private IProducer? producer = null;
+ public IProducer? ProducerModule => producer;
+ private IConstructor? constructor = null;
+ public IConstructor? ConstructorModule => constructor;
+ private IArmor? armor = null;
+ public IArmor? ArmorModule => armor;
+ private IShield? shield = null;
+ public IShield? ShieldModule => shield;
+ private IWeapon? weapon = null;
+ public IWeapon? WeaponModule => weapon;
+ private GameObj? whatInteractingWith = null;
+ public GameObj? WhatInteractingWith
+ {
+ get
+ {
+ lock (actionLock)
+ {
+ return whatInteractingWith;
+ }
+ }
+ }
+ private long ChangeShipState(RunningStateType running, ShipStateType value = ShipStateType.Null, GameObj? gameObj = null)
+ {
+ //只能被SetShipState引用
+ if (runningState == RunningStateType.RunningSleepily)
+ {
+ ThreadNum.Release();
+ }
+ runningState = running;
+ whatInteractingWith = gameObj;
+ shipState = value;
+ return ++stateNum;
+ }
+ private long ChangeShipStateInOneThread(RunningStateType running, ShipStateType value = ShipStateType.Null, GameObj? gameObj = null)
+ {
+ if (runningState == RunningStateType.RunningSleepily)
+ {
+ ThreadNum.Release();
+ }
+ runningState = running;
+ //只能被SetPlayerState引用
+ whatInteractingWith = gameObj;
+ shipState = value;
+ return stateNum;
+ }
+ public long SetShipState(RunningStateType running, ShipStateType value = ShipStateType.Null, IGameObj? obj = null)
+ {
+ GameObj? gameObj = (GameObj?)obj;
+ lock (actionLock)
+ {
+ ShipStateType nowShipState = ShipState;
+ if (nowShipState == value) return -1;
+ GameObj? lastObj = whatInteractingWith;
+ switch (nowShipState)
+ {
+ case ShipStateType.Attacking:
+ if (value == ShipStateType.Null || value == ShipStateType.Stunned || value == ShipStateType.Swinging)
+ return ChangeShipState(running, value, gameObj);
+ else return -1;
+ case ShipStateType.Stunned:
+ if (value == ShipStateType.Null)
+ return ChangeShipState(running, value, gameObj);
+ else return -1;
+ case ShipStateType.Swinging:
+ if (value == ShipStateType.Null || value == ShipStateType.Stunned)
+ return ChangeShipState(running, value, gameObj);
+ else return -1;
+ default:
+ return ChangeShipState(running, value, gameObj);
+ }
+ }
+ }
+ public long SetShipStateNaturally()
+ {
+ lock (actionLock)
+ {
+ runningState = RunningStateType.Null;
+ whatInteractingWith = null;
+ shipState = ShipStateType.Null;
+ return ++stateNum;
+ }
+ }
+ public bool ResetShipState(long state, RunningStateType running = RunningStateType.Null, ShipStateType value = ShipStateType.Null, IGameObj? obj = null)
+ {
+ lock (actionLock)
+ {
+ if (state != stateNum) return false;
+ this.runningState = running;
+ whatInteractingWith = (GameObj?)obj;
+ shipState = value;
+ ++stateNum;
+ return true;
+ }
+ }
+ public bool ResetShipStateInOneThread(long state, RunningStateType running = RunningStateType.Null, ShipStateType value = ShipStateType.Null, IGameObj? obj = null)
+ {
+ lock (actionLock)
+ {
+ if (state != stateNum) return false;
+ this.runningState = running;
+ whatInteractingWith = (GameObj?)obj;
+ shipState = value;
+ return true;
+ }
+ }
+ public bool StartThread(long stateNum, RunningStateType runningState)
+ {
+ lock (actionLock)
+ {
+ if (this.StateNum == stateNum)
+ {
+ this.runningState = runningState;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool TryToRemoveFromGame(ShipStateType shipStateType)
+ {
+ lock (actionLock)
+ {
+ if (SetShipState(RunningStateType.RunningForcibly, shipStateType) == -1) return false;
+ TryToRemove();
+ CanMove.SetReturnOri(false);
+ position = GameData.PosNotInGame;
+ }
+ return true;
+ }
+ public bool Commandable()
+ {
+ lock (ActionLock)
+ {
+ return (shipState != ShipStateType.Stunned
+ && shipState != ShipStateType.Swinging
+ && shipState != ShipStateType.Attacking);
+ }
+ }
+ public Ship(XY initPos, int initRadius, ShipType shipType) :
+ base(initPos, initRadius, GameObjType.Ship)
+ {
+ this.CanMove.SetReturnOri(true);
+ this.occupation = OccupationFactory.FindIOccupation(shipType);
+ this.viewRange = occupation.ViewRange;
+ this.HP = new(Occupation.MaxHp);
+ this.MoveSpeed.SetReturnOri(this.orgMoveSpeed = Occupation.MoveSpeed);
+ this.shipType = shipType;
+ switch (shipType)
+ {
+ case ShipType.CivilShip:
+ this.producer = new CivilProducer1();
+ this.constructor = new CivilConstructor1();
+ this.armor = null;
+ this.shield = null;
+ this.weapon = null;
+ break;
+ case ShipType.WarShip:
+ this.producer = null;
+ this.constructor = null;
+ this.armor = null;
+ this.shield = null;
+ this.weapon = new WarLaserGun();
+ break;
+ case ShipType.FlagShip:
+ this.producer = null;
+ this.constructor = null;
+ this.armor = null;
+ this.shield = null;
+ this.weapon = new FlagLaserGun();
+ break;
+ }
+ }
+}
diff --git a/logic/GameClass/GameObj/Team.cs b/logic/GameClass/GameObj/Team.cs
new file mode 100644
index 00000000..d9a0e0fe
--- /dev/null
+++ b/logic/GameClass/GameObj/Team.cs
@@ -0,0 +1,122 @@
+using GameClass.GameObj.Areas;
+using Preparation.Utility;
+using System.Collections.Generic;
+
+namespace GameClass.GameObj
+{
+ public class Team
+ {
+ private static long currentMaxTeamID = 0;
+ public static long CurrentMaxTeamID => currentMaxTeamID;
+ private readonly long teamID;
+ public long TeamID => teamID;
+ public const long invalidTeamID = long.MaxValue;
+ public const long noneTeamID = long.MinValue;
+ private readonly List shipList;
+ private readonly Dictionary birthPointList;
+ public Dictionary BirthPointList => birthPointList;
+ private Home home;
+ public int Score { get; private set; } = 0;
+ public Ship? GetShip(long shipID)
+ {
+ foreach (Ship ship in shipList)
+ {
+ if (ship.ShipID == shipID)
+ return ship;
+ }
+ return null;
+ }
+ public bool AddShip(Ship ship)
+ {
+ switch (ship.ShipType)
+ {
+ case ShipType.CivilShip:
+ if (GetCivilShipNum() >= GameData.MaxCivilShipNum)
+ return false;
+ break;
+ case ShipType.WarShip:
+ if (GetWarShipNum() >= GameData.MaxWarShipNum)
+ return false;
+ break;
+ case ShipType.FlagShip:
+ if (GetFlagShipNum() >= GameData.MaxFlagShipNum)
+ return false;
+ break;
+ default:
+ return false;
+ }
+ shipList.Add(ship);
+ return true;
+ }
+ public void SetHome(Home home)
+ {
+ this.home = home;
+ }
+ public int GetShipNum()
+ {
+ return shipList.Count;
+ }
+ public int GetCivilShipNum()
+ {
+ int num = 0;
+ foreach (Ship ship in shipList)
+ {
+ if (ship.ShipType == ShipType.CivilShip)
+ num++;
+ }
+ return num;
+ }
+ public int GetWarShipNum()
+ {
+ int num = 0;
+ foreach (Ship ship in shipList)
+ {
+ if (ship.ShipType == ShipType.WarShip)
+ num++;
+ }
+ return num;
+ }
+ public int GetFlagShipNum()
+ {
+ int num = 0;
+ foreach (Ship ship in shipList)
+ {
+ if (ship.ShipType == ShipType.FlagShip)
+ num++;
+ }
+ return num;
+ }
+ public void RemoveShip(Ship ship)
+ {
+ int i;
+ for (i = 0; i < shipList.Count; i++)
+ {
+ if (shipList[i] == ship)
+ break;
+ }
+ if (i < shipList.Count)
+ shipList.RemoveAt(i);
+ }
+ public long[] GetShipIDs()
+ {
+ long[] shipIDs = new long[shipList.Count];
+ int i = 0;
+ foreach (Ship ship in shipList)
+ {
+ shipIDs[i++] = ship.ShipID;
+ }
+ return shipIDs;
+ }
+ public static bool TeamExists(long teamID)
+ {
+ return teamID < currentMaxTeamID;
+ }
+ public void UpdateBirthPoint()
+ { }
+ public Team()
+ {
+ teamID = currentMaxTeamID++;
+ shipList = new List();
+ }
+ }
+}
diff --git a/logic/GameEngine/CollisionChecker.cs b/logic/GameEngine/CollisionChecker.cs
new file mode 100644
index 00000000..c61cdff2
--- /dev/null
+++ b/logic/GameEngine/CollisionChecker.cs
@@ -0,0 +1,227 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Preparation.Interface;
+using Preparation.Utility;
+
+namespace GameEngine
+{
+ internal class CollisionChecker
+ {
+ public IGameObj? CheckCollision(IMovable obj, XY Pos)
+ {
+ // 在列表中检查碰撞
+ Func, ReaderWriterLockSlim, IGameObj?> CheckCollisionInList =
+ (IEnumerable lst, ReaderWriterLockSlim listLock) =>
+ {
+ IGameObj? collisionObj = null;
+ listLock.EnterReadLock();
+ try
+ {
+ foreach (var listObj in lst)
+ {
+ if (obj.WillCollideWith(listObj, Pos))
+ {
+ collisionObj = listObj;
+ break;
+ }
+ }
+ }
+ finally
+ {
+ listLock.ExitReadLock();
+ }
+ return collisionObj;
+ };
+
+ IGameObj? collisionObj;
+ foreach (var list in lists)
+ {
+ if ((collisionObj = CheckCollisionInList(list.Item1, list.Item2)) != null)
+ {
+ return collisionObj;
+ }
+ }
+
+ return null;
+ }
+ ///
+ /// 碰撞检测,如果这样行走是否会与之碰撞,返回与之碰撞的物体
+ ///
+ /// 移动的物体
+ /// 移动的位移向量
+ /// 和它碰撞的物体
+ public IGameObj? CheckCollisionWhenMoving(IMovable obj, XY moveVec)
+ {
+ XY nextPos = obj.Position + moveVec;
+ if (!obj.IsRigid)
+ {
+ if (gameMap.IsOutOfBound(obj))
+ return gameMap.GetOutOfBound(nextPos);
+ return null;
+ }
+ return CheckCollision(obj, nextPos);
+ }
+ ///
+ /// /// 可移动物体(圆)向矩形物体移动时,可移动且不会碰撞的最大距离。直接用double计算,防止误差
+ ///
+ ///
+ /// 矩形的中心坐标
+ ///
+ // private double MaxMoveToSquare(IMoveable obj, IGameObj square)
+ //{
+ // double tmpMax;
+ // double angle = Math.Atan2(square.Position.y - obj.Position.y, square.Position.x - obj.Position.x);
+ // if (obj.WillCollideWith(square, obj.Position))
+ // tmpMax = 0;
+ // else tmpMax =
+ // Math.Abs(XYPosition.DistanceFloor3(obj.Position, square.Position) - obj.Radius -
+ // (square.Radius / Math.Min(Math.Abs(Math.Cos(angle)), Math.Abs(Math.Sin(angle)))));
+ // return tmpMax;
+ // }
+
+ // private double FindMaxOnlyConsiderWall(IMoveable obj, Vector moveVec)
+ //{
+ // var desination = moveVec;
+ // double maxOnlyConsiderWall = moveVec.length;
+ // if (desination.length > 0) //如果length足够长,还是有可能穿墙的
+ // {
+ // XYPosition nextXY = Vector.Vector2XY(desination) + obj.Position + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle)), (int)(obj.Radius * Math.Sin(moveVec.angle)));
+ // if (gameMap.IsWall(nextXY)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置
+ // {
+ // maxOnlyConsiderWall = MaxMoveToSquare(obj, gameMap.GetCell(nextXY));
+ // }
+ // else //考虑物体宽度
+ // {
+ // double dist = 0;
+ // XYPosition nextXYConsiderWidth;
+ // nextXYConsiderWidth = nextXY + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle + Math.PI / 4)), (int)(obj.Radius * Math.Sin(moveVec.angle + Math.PI / 4)));
+ // if (gameMap.IsWall(nextXYConsiderWidth)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置
+ // {
+ // dist = MaxMoveToSquare(obj, gameMap.GetCell(nextXYConsiderWidth));
+ // if (dist < maxOnlyConsiderWall)
+ // maxOnlyConsiderWall = dist;
+ // }
+ // nextXYConsiderWidth = nextXY + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle - Math.PI / 4)), (int)(obj.Radius * Math.Sin(moveVec.angle - Math.PI / 4)));
+ // if (gameMap.IsWall(nextXYConsiderWidth)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置
+ // {
+ // dist = MaxMoveToSquare(obj, gameMap.GetCell(nextXYConsiderWidth));
+ // if (dist < maxOnlyConsiderWall)
+ // maxOnlyConsiderWall = dist;
+ // }
+ // }
+ // }
+ // return maxOnlyConsiderWall;
+ // }
+
+ ///
+ /// 寻找最大可能移动距离
+ ///
+ /// 移动物体,默认obj.Rigid为true
+ /// 下一步要到达的位置
+ /// 移动的位移向量,默认与nextPos协调
+ /// 最大可能的移动距离
+ public double FindMax(IMovable obj, XY nextPos, XY moveVec)
+ {
+ double tmpMax = uint.MaxValue; // 暂存最大值
+
+ double maxDistance = uint.MaxValue;
+ foreach (var listWithLock in lists)
+ {
+ var lst = listWithLock.Item1;
+ var listLock = listWithLock.Item2;
+ listLock.EnterReadLock();
+ try
+ {
+ foreach (IGameObj listObj in lst)
+ {
+ // 如果再走一步发生碰撞
+ if (obj.WillCollideWith(listObj, nextPos))
+ {
+ {
+ switch (listObj.Shape) // 默认obj为圆形
+ {
+ case ShapeType.Circle:
+ {
+ // 计算两者之间的距离
+ double mod = XY.DistanceFloor3(listObj.Position, obj.Position);
+ int orgDeltaX = listObj.Position.x - obj.Position.x;
+ int orgDeltaY = listObj.Position.y - obj.Position.y;
+
+ if (mod < listObj.Radius + obj.Radius) // 如果两者已经重叠
+ {
+ tmpMax = 0;
+ }
+ else
+ {
+ double tmp = mod - obj.Radius - listObj.Radius;
+ // 计算能走的最长距离,好像这么算有一点误差?
+ tmp = ((int)(tmp * 1000 / Math.Cos(Math.Atan2(orgDeltaY, orgDeltaX) - moveVec.Angle())));
+ if (tmp < 0 || tmp > uint.MaxValue || double.IsNaN(tmp))
+ {
+ tmpMax = uint.MaxValue;
+ }
+ else
+ tmpMax = tmp / 1000.0;
+ }
+ break;
+ }
+ case ShapeType.Square:
+ {
+ // if (obj.WillCollideWith(listObj, obj.Position))
+ // tmpMax = 0;
+ // else tmpMax = MaxMoveToSquare(obj, listObj);
+ // break;
+ if (obj.WillCollideWith(listObj, obj.Position))
+ tmpMax = 0;
+ else
+ {
+ // 二分查找最大可能移动距离
+ int left = 0, right = (int)moveVec.Length();
+ while (left < right - 1)
+ {
+ int mid = (right - left) / 2 + left;
+ if (obj.WillCollideWith(listObj, obj.Position + new XY(moveVec, mid)))
+ {
+ right = mid;
+ }
+ else
+ left = mid;
+ }
+ tmpMax = (uint)left;
+ }
+ break;
+ }
+ default:
+ tmpMax = uint.MaxValue;
+ break;
+ }
+ if (tmpMax < maxDistance)
+ maxDistance = tmpMax;
+ }
+ }
+ }
+ }
+ finally
+ {
+ listLock.ExitReadLock();
+ }
+ }
+ return maxDistance;
+ }
+
+ readonly IMap gameMap;
+ private readonly Tuple, ReaderWriterLockSlim>[] lists;
+
+ public CollisionChecker(IMap gameMap)
+ {
+ this.gameMap = gameMap;
+ lists = new Tuple, ReaderWriterLockSlim>[gameMap.GameObjDict.Count];
+ int i = 0;
+ foreach (var keyValuePair in gameMap.GameObjDict)
+ {
+ lists[i++] = new Tuple, ReaderWriterLockSlim>(keyValuePair.Value as IList, gameMap.GameObjLockDict[keyValuePair.Key]);
+ }
+ }
+ }
+}
diff --git a/logic/GameEngine/MoveEngine.cs b/logic/GameEngine/MoveEngine.cs
new file mode 100644
index 00000000..f8b108ae
--- /dev/null
+++ b/logic/GameEngine/MoveEngine.cs
@@ -0,0 +1,234 @@
+using System;
+using System.Threading;
+using Preparation.Interface;
+using Preparation.Utility;
+using Timothy.FrameRateTask;
+
+namespace GameEngine
+{
+ public class MoveEngine
+ {
+ ///
+ /// 碰撞结束后要做的事情
+ ///
+ public enum AfterCollision
+ {
+ ContinueCheck = 0, // 碰撞后继续检查其他碰撞,暂时没用
+ MoveMax = 1, // 行走最远距离
+ Destroyed = 2 // 物体已经毁坏
+ }
+
+ private readonly ITimer gameTimer;
+ private readonly Action EndMove;
+
+ public IGameObj? CheckCollision(IMovable obj, XY Pos)
+ {
+ return collisionChecker.CheckCollision(obj, Pos);
+ }
+
+ private readonly CollisionChecker collisionChecker;
+ private readonly Func OnCollision;
+ ///
+ /// Constrctor
+ ///
+ /// 游戏地图
+ /// 发生碰撞时要做的事情,第一个参数为移动的物体,第二个参数为撞到的物体,第三个参数为移动的位移向量,返回值见AfterCollision的定义
+ /// 结束碰撞时要做的事情
+ public MoveEngine(
+ IMap gameMap,
+ Func OnCollision,
+ Action EndMove
+ )
+ {
+ this.gameTimer = gameMap.Timer;
+ this.EndMove = EndMove;
+ this.OnCollision = OnCollision;
+ this.collisionChecker = new CollisionChecker(gameMap);
+ }
+
+ ///
+ /// 在无碰撞的前提下行走最远的距离
+ ///
+ /// 移动物体,默认obj.Rigid为true
+ /// 移动的位移向量
+ private bool MoveMax(IMovable obj, XY moveVec, long stateNum)
+ {
+ /*由于四周是墙,所以人物永远不可能与越界方块碰撞*/
+ XY nextPos = obj.Position + moveVec;
+ double maxLen = collisionChecker.FindMax(obj, nextPos, moveVec);
+ maxLen = Math.Min(maxLen, obj.MoveSpeed / GameData.NumOfStepPerSecond);
+ return (obj.MovingSetPos(new XY(moveVec, maxLen), stateNum)) >= 0;
+ }
+
+ private bool LoopDo(IMovable obj, double direction, ref double deltaLen, long stateNum)
+ {
+ double moveVecLength = obj.MoveSpeed / GameData.NumOfStepPerSecond;
+ XY res = new(direction, moveVecLength);
+
+ // 越界情况处理:如果越界,则与越界方块碰撞
+ bool flag; // 循环标志
+ do
+ {
+ flag = false;
+ IGameObj? collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res);
+ if (collisionObj == null)
+ break;
+
+ switch (OnCollision(obj, collisionObj, res))
+ {
+ case AfterCollision.ContinueCheck:
+ flag = true;
+ break;
+ case AfterCollision.Destroyed:
+ Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
+ return false;
+ case AfterCollision.MoveMax:
+ if (!MoveMax(obj, res, stateNum)) return false;
+ moveVecLength = 0;
+ res = new XY(direction, moveVecLength);
+ break;
+ }
+ } while (flag);
+
+ long moveL = obj.MovingSetPos(res, stateNum);
+ if (moveL == -1) return false;
+ deltaLen = deltaLen + moveVecLength - Math.Sqrt(moveL);
+ return true;
+ }
+
+ public void MoveObj(IMovable obj, int moveTime, double direction, long stateNum)
+ {
+ if (!gameTimer.IsGaming) return;
+ lock (obj.ActionLock)
+ {
+ if (!obj.IsAvailableForMove) { EndMove(obj); return; }
+ obj.IsMoving.SetReturnOri(true);
+ }
+
+ new Thread
+ (
+ () =>
+ {
+ double moveVecLength = 0.0;
+ XY res = new(direction, moveVecLength);
+ double deltaLen = (double)0.0; // 转向,并用deltaLen存储行走的误差
+ IGameObj? collisionObj = null;
+ bool isEnded = false;
+
+ bool flag; // 循环标志
+ do
+ {
+ flag = false;
+ collisionObj = collisionChecker.CheckCollision(obj, obj.Position);
+ if (collisionObj == null)
+ break;
+
+ switch (OnCollision(obj, collisionObj, res))
+ {
+ case AfterCollision.ContinueCheck:
+ flag = true;
+ break;
+ case AfterCollision.Destroyed:
+ Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
+ isEnded = true;
+ break;
+ case AfterCollision.MoveMax:
+ break;
+ }
+ } while (flag);
+
+ if (isEnded)
+ {
+ obj.IsMoving.SetReturnOri(false);
+ EndMove(obj);
+ return;
+ }
+ else
+ {
+ if (moveTime >= GameData.NumOfPosGridPerCell / GameData.NumOfStepPerSecond)
+ {
+ Thread.Sleep(GameData.NumOfPosGridPerCell / GameData.NumOfStepPerSecond);
+ new FrameRateTaskExecutor(
+ () => gameTimer.IsGaming,
+ () =>
+ {
+ if (obj.StateNum != stateNum || !obj.CanMove || obj.IsRemoved)
+ return !(isEnded = true);
+ return !(isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum));
+ },
+ GameData.NumOfPosGridPerCell / GameData.NumOfStepPerSecond,
+ () =>
+ {
+ return 0;
+ },
+ maxTotalDuration: moveTime - GameData.NumOfPosGridPerCell / GameData.NumOfStepPerSecond
+ )
+ {
+ AllowTimeExceed = true,
+ MaxTolerantTimeExceedCount = ulong.MaxValue,
+ TimeExceedAction = b =>
+ {
+ if (b)
+ Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!");
+
+#if DEBUG
+ else
+ {
+ Console.WriteLine("Debug info: Object moving time exceed for once.");
+ }
+#endif
+ }
+ }.Start();
+ if (!isEnded && obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved)
+ isEnded = !LoopDo(obj, direction, ref deltaLen, stateNum);
+ }
+ if (isEnded)
+ {
+ obj.IsMoving.SetReturnOri(false);
+ EndMove(obj);
+ return;
+ }
+ if (obj.StateNum == stateNum && obj.CanMove && !obj.IsRemoved)
+ {
+ int leftTime = moveTime % (GameData.NumOfPosGridPerCell / GameData.NumOfStepPerSecond);
+ if (leftTime > 0)
+ {
+ Thread.Sleep(leftTime); // 多移动的在这里补回来
+ }
+ do
+ {
+ flag = false;
+ moveVecLength = (double)deltaLen + leftTime * obj.MoveSpeed / GameData.NumOfPosGridPerCell;
+ res = new XY(direction, moveVecLength);
+ if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null)
+ {
+ obj.MovingSetPos(res, stateNum);
+ }
+ else
+ {
+ switch (OnCollision(obj, collisionObj, res))
+ {
+ case AfterCollision.ContinueCheck:
+ flag = true;
+ break;
+ case AfterCollision.Destroyed:
+ Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
+ isEnded = true;
+ break;
+ case AfterCollision.MoveMax:
+ MoveMax(obj, res, stateNum);
+ moveVecLength = 0;
+ res = new XY(direction, moveVecLength);
+ break;
+ }
+ }
+ } while (flag);
+ }
+ obj.IsMoving.SetReturnOri(false); // 结束移动
+ EndMove(obj);
+ }
+ }
+ ).Start();
+ }
+ }
+}
diff --git a/logic/Gaming/ActionManager.cs b/logic/Gaming/ActionManager.cs
new file mode 100644
index 00000000..807a1d0d
--- /dev/null
+++ b/logic/Gaming/ActionManager.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Threading;
+using GameClass.GameObj;
+using GameEngine;
+using Preparation.Utility;
+using Protobuf;
+using Timothy.FrameRateTask;
+
+namespace Gaming
+{
+ public partial class Game
+ {
+ private readonly ActionManager actionManager;
+ private class ActionManager
+ {
+ public readonly MoveEngine moveEngine;
+ public bool MoveShip(Ship shipToMove, int moveTimeInMilliseconds, double moveDirection)
+ {
+ if (moveTimeInMilliseconds < 5)
+ {
+ return false;
+ }
+ long stateNum = shipToMove.SetShipState(RunningStateType.Waiting, ShipStateType.Moving);
+ if (stateNum == -1)
+ {
+ return false;
+ }
+ new Thread
+ (
+ () =>
+ {
+ shipToMove.ThreadNum.WaitOne();
+ if (!shipToMove.StartThread(stateNum, RunningStateType.RunningActively))
+ {
+ shipToMove.ThreadNum.Release();
+ return;
+ }
+ moveEngine.MoveObj(shipToMove, moveTimeInMilliseconds, moveDirection, shipToMove.StateNum);
+ Thread.Sleep(moveTimeInMilliseconds);
+ shipToMove.ResetShipState(stateNum);
+ }
+ )
+ { IsBackground = true }.Start();
+ return true;
+ }
+ public static bool Stop(Ship ship)
+ {
+ lock (ship.ActionLock)
+ {
+ if (ship.Commandable())
+ {
+ ship.SetShipState(RunningStateType.Null);
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool Produce(Ship ship)
+ {
+ return false;
+ }
+ public bool Construct(Ship ship)
+ {
+ return false;
+ }
+
+ }
+ }
+}
diff --git a/logic/Gaming/AttackManager.cs b/logic/Gaming/AttackManager.cs
new file mode 100644
index 00000000..1a0f56f8
--- /dev/null
+++ b/logic/Gaming/AttackManager.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Threading;
+using System.Collections.Generic;
+using GameClass.GameObj;
+using Preparation.Utility;
+using GameEngine;
+using Preparation.Interface;
+
+namespace Gaming
+{
+ internal class AttackManager
+ {
+ }
+}
diff --git a/logic/Gaming/Game.cs b/logic/Gaming/Game.cs
new file mode 100644
index 00000000..52ed779e
--- /dev/null
+++ b/logic/Gaming/Game.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Threading;
+using System.Collections.Generic;
+using Preparation.Utility;
+using Preparation.Interface;
+using GameClass.GameObj;
+
+namespace Gaming
+{
+ public partial class Game
+ {
+ public struct ShipInitInfo
+ {
+ public long teamID;
+ public long playerID;
+ public uint birthPoint;
+ public ShipType shipType;
+ public ShipInitInfo(long teamID, long playerID, uint birthPoint, ShipType shipType)
+ {
+ this.teamID = teamID;
+ this.playerID = playerID;
+ this.birthPoint = birthPoint;
+ this.shipType = shipType;
+ }
+ }
+ private readonly List teamList;
+ public List TeamList => teamList;
+ private readonly Map gameMap;
+ public Map GameMap => gameMap;
+ public long AddShip(ShipInitInfo shipInitInfo)
+ {
+ if (!Team.TeamExists(shipInitInfo.teamID))
+ {
+ return GameObj.invalidID;
+ }
+ // 由于BirthPoint实质上是可变且每支队伍不同的,所以暂时把它放到Team里?
+ XY pos = teamList[(int)shipInitInfo.teamID].BirthPointList[shipInitInfo.birthPoint];
+ Ship? newShip = shipManager.AddShip(pos, shipInitInfo.teamID, shipInitInfo.playerID, shipInitInfo.shipType);
+ if (newShip == null)
+ {
+ return GameObj.invalidID;
+ }
+ teamList[(int)shipInitInfo.teamID].AddShip(newShip);
+ return newShip.ShipID;
+ }
+ public bool StartGame(int milliSeconds)
+ {
+ if (gameMap.Timer.IsGaming)
+ return false;
+ // 开始游戏
+ new Thread
+ (
+ () =>
+ {
+ if (!gameMap.Timer.StartGame(milliSeconds))
+ return;
+
+ EndGame(); // 游戏结束时要做的事
+ }
+ )
+ { IsBackground = true }.Start();
+ return true;
+ }
+ public void EndGame()
+ {
+ }
+ public bool MoveShip(long shipID, int moveTimeInMilliseconds, double angle)
+ {
+ if (!gameMap.Timer.IsGaming)
+ return false;
+ Ship? ship = gameMap.FindShipInShipID(shipID);
+ if (ship != null)
+ {
+ return actionManager.MoveShip(ship, moveTimeInMilliseconds, angle);
+ }
+ else
+ {
+ return false;
+ }
+ }
+ public Game(uint[,] mapResource, int numOfTeam)
+ {
+ gameMap = new Map(mapResource);
+ teamList = new List();
+ for (int i = 0; i < numOfTeam; i++)
+ {
+ teamList.Add(new Team());
+ }
+ }
+ }
+}
diff --git a/logic/Gaming/ModuleManager.cs b/logic/Gaming/ModuleManager.cs
new file mode 100644
index 00000000..6d0a52fc
--- /dev/null
+++ b/logic/Gaming/ModuleManager.cs
@@ -0,0 +1,33 @@
+using Preparation.Utility;
+using GameClass.GameObj;
+
+namespace Gaming
+{
+ public partial class Game
+ {
+ private readonly ModuleManager moduleManager;
+ private partial class ModuleManager
+ {
+ public bool PurchaseProducer(Ship ship, ProducerType producerType, int parameter)
+ {
+ return false;
+ }
+ public bool PurchaseConstructor(Ship ship, ConstructorType constructorType, int parameter)
+ {
+ return false;
+ }
+ public bool PurchaseArmor(Ship ship, ArmorType armorType, int parameter)
+ {
+ return false;
+ }
+ public bool PurchaseShield(Ship ship, ShieldType shieldType, int parameter)
+ {
+ return false;
+ }
+ public bool PurchaseWeapon(Ship ship, WeaponType weaponType, int parameter)
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/logic/Gaming/ShipManager.cs b/logic/Gaming/ShipManager.cs
new file mode 100644
index 00000000..77f98b13
--- /dev/null
+++ b/logic/Gaming/ShipManager.cs
@@ -0,0 +1,28 @@
+using System.Threading;
+using GameClass.GameObj;
+using Preparation.Utility;
+using Preparation.Interface;
+
+namespace Gaming
+{
+ public partial class Game
+ {
+ private readonly ShipManager shipManager;
+ private partial class ShipManager
+ {
+ readonly Map gameMap;
+ public Ship? AddShip(XY pos, long teamID, long shipID, ShipType shipType)
+ {
+ Ship newShip = new Ship(pos, GameData.ShipRadius, shipType);
+ gameMap.Add(newShip);
+ newShip.TeamID.SetReturnOri(teamID);
+ newShip.ShipID.SetReturnOri(shipID);
+ return newShip;
+ }
+ public ShipManager(Map gameMap)
+ {
+ this.gameMap = gameMap;
+ }
+ }
+ }
+}
diff --git a/logic/Preparation/Interface/IGameObj.cs b/logic/Preparation/Interface/IGameObj.cs
new file mode 100644
index 00000000..f7cac913
--- /dev/null
+++ b/logic/Preparation/Interface/IGameObj.cs
@@ -0,0 +1,16 @@
+using Preparation.Utility;
+
+namespace Preparation.Interface
+{
+ public interface IGameObj
+ {
+ public GameObjType Type { get; }
+ public long ID { get; }
+ public XY Position { get; } // if Square, Pos equals the center
+ public bool IsRigid { get; }
+ public AtomicBool IsRemoved { get; }
+ public ShapeType Shape { get; }
+ public int Radius { get; } // if Square, Radius equals half length of one side
+ public bool IgnoreCollideExecutor(IGameObj targetObj); // 忽略碰撞,在具体类中实现
+ }
+}
diff --git a/logic/Preparation/Interface/IHome.cs b/logic/Preparation/Interface/IHome.cs
new file mode 100644
index 00000000..76cf7443
--- /dev/null
+++ b/logic/Preparation/Interface/IHome.cs
@@ -0,0 +1,12 @@
+using Preparation.Utility;
+
+namespace Preparation.Interface
+{
+ public interface IHome
+ {
+ public AtomicLong TeamID { get; }
+ public LongWithVariableRange HP { get; }
+ public long Score { get; }
+ public void AddScore(long add);
+ }
+}
diff --git a/logic/Preparation/Interface/IMap.cs b/logic/Preparation/Interface/IMap.cs
new file mode 100644
index 00000000..72d1c013
--- /dev/null
+++ b/logic/Preparation/Interface/IMap.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Timers;
+using Preparation.Utility;
+
+namespace Preparation.Interface
+{
+ public interface IMap
+ {
+ ITimer Timer { get; }
+
+ // the two dicts must have same keys
+ Dictionary> GameObjDict { get; }
+ Dictionary GameObjLockDict { get; }
+
+ public uint[,] ProtoGameMap { get; }
+ public PlaceType GetPlaceType(IGameObj obj);
+ public bool IsOutOfBound(IGameObj obj);
+ public IOutOfBound GetOutOfBound(XY pos); // 返回新建的一个OutOfBound对象
+ }
+}
diff --git a/logic/Preparation/Interface/IModule.cs b/logic/Preparation/Interface/IModule.cs
new file mode 100644
index 00000000..b550b2be
--- /dev/null
+++ b/logic/Preparation/Interface/IModule.cs
@@ -0,0 +1,28 @@
+using Preparation.Utility;
+
+namespace Preparation.Interface;
+
+public interface IModule
+{
+ public int Cost { get; }
+}
+public interface IProducer : IModule
+{
+ public int ProduceSpeed { get; }
+}
+public interface IConstructor : IModule
+{
+ public int ConstructSpeed { get; }
+}
+public interface IArmor : IModule
+{
+ public int ArmorHP { get; }
+}
+public interface IShield : IModule
+{
+ public int ShieldHP { get; }
+}
+public interface IWeapon : IModule
+{
+ public BulletType BulletType { get; }
+}
diff --git a/logic/Preparation/Interface/IMovable.cs b/logic/Preparation/Interface/IMovable.cs
new file mode 100644
index 00000000..ab9b35e9
--- /dev/null
+++ b/logic/Preparation/Interface/IMovable.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Threading;
+using Preparation.Utility;
+
+namespace Preparation.Interface
+{
+ public interface IMovable : IGameObj
+ {
+ public XY FacingDirection { get; set; }
+ object ActionLock { get; }
+ public AtomicInt MoveSpeed { get; }
+ public AtomicBool CanMove { get; }
+ public AtomicBool IsMoving { get; }
+ public bool IsAvailableForMove { get; }
+ public long StateNum { get; }
+ public Semaphore ThreadNum { get; }
+ public long MovingSetPos(XY moveVec, long stateNum);
+ public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞
+ {
+ if (targetObj == null)
+ return false;
+ // 会移动的只有子弹和人物,都是Circle
+ if (!targetObj.IsRigid || targetObj.ID == ID)
+ return false;
+
+ if (IgnoreCollideExecutor(targetObj) || targetObj.IgnoreCollideExecutor(this))
+ return false;
+
+ if (targetObj.Shape == ShapeType.Circle)
+ {
+ return XY.DistanceCeil3(nextPos, targetObj.Position) < targetObj.Radius + Radius;
+ }
+ else // Square
+ {
+ long deltaX = Math.Abs(nextPos.x - targetObj.Position.x), deltaY = Math.Abs(nextPos.y - targetObj.Position.y);
+ if (deltaX >= targetObj.Radius + Radius || deltaY >= targetObj.Radius + Radius)
+ return false;
+ if (deltaX < targetObj.Radius || deltaY < targetObj.Radius)
+ return true;
+ else
+ return ((long)(deltaX - targetObj.Radius) * (deltaX - targetObj.Radius)) + ((long)(deltaY - targetObj.Radius) * (deltaY - targetObj.Radius)) <= (long)Radius * (long)Radius;
+ }
+ }
+ }
+}
diff --git a/logic/Preparation/Interface/IObjOfShip.cs b/logic/Preparation/Interface/IObjOfShip.cs
new file mode 100644
index 00000000..78625632
--- /dev/null
+++ b/logic/Preparation/Interface/IObjOfShip.cs
@@ -0,0 +1,7 @@
+namespace Preparation.Interface
+{
+ public interface IObjOfShip : IGameObj
+ {
+ IShip? Parent { get; set; }
+ }
+}
diff --git a/logic/Preparation/Interface/IOccupation.cs b/logic/Preparation/Interface/IOccupation.cs
new file mode 100644
index 00000000..da613d7e
--- /dev/null
+++ b/logic/Preparation/Interface/IOccupation.cs
@@ -0,0 +1,10 @@
+using Preparation.Utility;
+
+namespace Preparation.Interface;
+
+public interface IOccupation
+{
+ public int MoveSpeed { get; }
+ public int MaxHp { get; }
+ public int ViewRange { get; }
+}
diff --git a/logic/Preparation/Interface/IOutOfBound.cs b/logic/Preparation/Interface/IOutOfBound.cs
new file mode 100644
index 00000000..5fe0e9d6
--- /dev/null
+++ b/logic/Preparation/Interface/IOutOfBound.cs
@@ -0,0 +1,7 @@
+namespace Preparation.Interface
+{
+ public interface IOutOfBound : IGameObj
+ {
+ // 接口不定义内容,为引擎使用
+ }
+}
diff --git a/logic/Preparation/Interface/IShip.cs b/logic/Preparation/Interface/IShip.cs
new file mode 100644
index 00000000..b02092ec
--- /dev/null
+++ b/logic/Preparation/Interface/IShip.cs
@@ -0,0 +1,17 @@
+using Preparation.Utility;
+
+namespace Preparation.Interface
+{
+ public interface IShip : IMovable
+ {
+ public AtomicLong TeamID { get; }
+ public LongWithVariableRange HP { get; }
+ public LongWithVariableRange Armor { get; }
+ public LongWithVariableRange Shield { get; }
+ public ShipType ShipType { get; }
+ public ShipStateType ShipState { get; }
+ public IntNumUpdateByCD BulletNum { get; }
+ public long SetShipState(RunningStateType running, ShipStateType value = ShipStateType.Null, IGameObj? obj = null);
+ public bool ResetShipState(long state, RunningStateType running = RunningStateType.Null, ShipStateType value = ShipStateType.Null, IGameObj? obj = null);
+ }
+}
diff --git a/logic/Preparation/Interface/ITimer.cs b/logic/Preparation/Interface/ITimer.cs
new file mode 100644
index 00000000..af9c5cd2
--- /dev/null
+++ b/logic/Preparation/Interface/ITimer.cs
@@ -0,0 +1,9 @@
+namespace Preparation.Interface
+{
+ public interface ITimer
+ {
+ bool IsGaming { get; set; }
+ public int nowTime();
+ public bool StartGame(int timeInMilliseconds);
+ }
+}
diff --git a/logic/Preparation/Interface/IWormhole.cs b/logic/Preparation/Interface/IWormhole.cs
new file mode 100644
index 00000000..e46d6dbf
--- /dev/null
+++ b/logic/Preparation/Interface/IWormhole.cs
@@ -0,0 +1,11 @@
+using Preparation.Utility;
+using System.Collections.Generic;
+
+namespace Preparation.Interface
+{
+ public interface IWormhole : IGameObj
+ {
+ public List Entrance { get; }
+ public List Content { get; }
+ }
+}
diff --git a/logic/Preparation/Utility/Debugger.cs b/logic/Preparation/Utility/Debugger.cs
new file mode 100644
index 00000000..8627e316
--- /dev/null
+++ b/logic/Preparation/Utility/Debugger.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Preparation.Utility
+{
+ public class Debugger
+ {
+ static public void Output(object current, string str)
+ {
+#if DEBUG
+ Console.WriteLine(current.GetType() + " " + current.ToString() + " " + str);
+#endif
+ }
+ static public void Output(string str)
+ {
+#if DEBUG
+ Console.WriteLine(str);
+#endif
+ }
+ }
+}
diff --git a/logic/Preparation/Utility/EnumType.cs b/logic/Preparation/Utility/EnumType.cs
new file mode 100644
index 00000000..53317c03
--- /dev/null
+++ b/logic/Preparation/Utility/EnumType.cs
@@ -0,0 +1,168 @@
+namespace Preparation.Utility
+{
+ ///
+ /// 装甲类型
+ ///
+ public enum ArmorType
+ {
+ Null = 0,
+ Armor1 = 1,
+ Armor2 = 2,
+ Armor3 = 3,
+ }
+ ///
+ /// 子弹类型
+ ///
+ public enum BulletType
+ {
+ Null = 0,
+ Laser = 1,
+ Plasma = 2,
+ Shell = 3,
+ Missile = 4,
+ Arc = 5,
+ }
+ ///
+ /// 建筑类型
+ ///
+ public enum ConstructionType
+ {
+ Null = 0,
+ Factory = 1,
+ Community = 2,
+ Fort = 3,
+ }
+ ///
+ /// 建造器类型
+ ///
+ public enum ConstructorType
+ {
+ Null = 0,
+ Constructor1 = 1,
+ Constructor2 = 2,
+ Constructor3 = 3,
+ }
+ ///
+ /// 游戏对象类型
+ ///
+ public enum GameObjType
+ {
+ Null = 0,
+ Ship = 1,
+ Bullet = 2,
+ BombedBullet = 3,
+
+ Ruin = 4,
+ Shadow = 5,
+ Asteroid = 6,
+ Resource = 7,
+ Construction = 8,
+ Wormhole = 9,
+ Home = 10,
+ OutOfBoundBlock = 11,
+ }
+ ///
+ /// 武器类型
+ ///
+ public enum GunType
+ {
+ Null = 0,
+ LaserGun = 1,
+ PlasmaGun = 2,
+ ShellGun = 3,
+ MissileGun = 4,
+ ArcGun = 5,
+ }
+ ///
+ /// PlaceType
+ ///
+ public enum PlaceType
+ {
+ Null = 0,
+ BirthPoint = 1,
+ Home = 2,
+ Ruin = 3,
+ Shadow = 4,
+ Asteroid = 5,
+ Resource = 6,
+ Construction = 7,
+ Wormhole = 8,
+ }
+ ///
+ /// 采集器类型
+ ///
+ public enum ProducerType
+ {
+ Null = 0,
+ Producer1 = 1,
+ Producer2 = 2,
+ Producer3 = 3,
+ }
+ ///
+ /// 运动状态类型
+ ///
+ public enum RunningStateType
+ {
+ Null = 0,
+ Waiting = 1,
+ RunningActively = 2,
+ RunningSleepily = 3,
+ RunningForcibly = 4,
+ }
+ ///
+ /// 形状类型
+ ///
+ public enum ShapeType
+ {
+ Null = 0,
+ Circle = 1,
+ Square = 2
+ }
+ ///
+ /// 护盾类型
+ ///
+ public enum ShieldType
+ {
+ Null = 0,
+ Shield1 = 1,
+ Shield2 = 2,
+ Shield3 = 3,
+ }
+ ///
+ /// 舰船状态类型
+ ///
+ public enum ShipStateType
+ {
+ Null = 0,
+ Producing = 1,
+ Constructing = 2,
+ Recovering = 3,
+ Recycling = 4,
+ Attacking = 5,
+ Swinging = 6,
+ Stunned = 7,
+ Moving = 8,
+ }
+ ///
+ /// 舰船类型
+ ///
+ public enum ShipType
+ {
+ Null = 0,
+ CivilShip = 1,
+ WarShip = 2,
+ FlagShip = 3,
+ }
+ ///
+ /// 武器类型
+ ///
+ public enum WeaponType
+ {
+ Null = 0,
+ LaserGun = 1,
+ PlasmaGun = 2,
+ ShellGun = 3,
+ MissileGun = 4,
+ ArcGun = 5,
+ }
+}
\ No newline at end of file
diff --git a/logic/Preparation/Utility/GameData.cs b/logic/Preparation/Utility/GameData.cs
new file mode 100644
index 00000000..9f30a1d7
--- /dev/null
+++ b/logic/Preparation/Utility/GameData.cs
@@ -0,0 +1,195 @@
+using System;
+
+
+namespace Preparation.Utility
+{
+ public static class GameData
+ {
+ public const int NumOfStepPerSecond = 100; // 每秒行走的步数
+ public const int FrameDuration = 50; // 每帧时长
+ public const int CheckInterval = 10; // 检查间隔
+ public const long GameDuration = 600000; // 游戏时长
+ public const int LimitOfStopAndMove = 15; // 停止和移动的最大间隔
+
+ public const int TolerancesLength = 3;
+ public const int AdjustLength = 3;
+
+ public const int MaxShipNum = 8; // 最大舰船数量
+ public const int MaxCivilShipNum = 3; // 最大民用舰船数量
+ public const int MaxWarShipNum = 4; // 最大军用舰船数量
+ public const int MaxFlagShipNum = 1; // 最大旗舰数量
+
+ public const int NumOfPosGridPerCell = 1000; // 每格的【坐标单位】数
+ public const int MapLength = 50000; // 地图长度
+ public const int MapRows = 50; // 行数
+ public const int MapCols = 50; // 列数
+
+ public static bool IsGameObjMap(GameObjType gameObjType)
+ {
+ return (uint)gameObjType > 3;
+ }
+ public static XY GetCellCenterPos(int x, int y) // 求格子的中心坐标
+ {
+ XY ret = new(x * NumOfPosGridPerCell + NumOfPosGridPerCell / 2, y * NumOfPosGridPerCell + NumOfPosGridPerCell / 2);
+ return ret;
+ }
+ public static int PosGridToCellX(XY pos) // 求坐标所在的格子的x坐标
+ {
+ return pos.x / NumOfPosGridPerCell;
+ }
+ public static int PosGridToCellY(XY pos) // 求坐标所在的格子的y坐标
+ {
+ return pos.y / NumOfPosGridPerCell;
+ }
+ public static XY PosGridToCellXY(XY pos) // 求坐标所在的格子的xy坐标
+ {
+ return new XY(pos.x / NumOfPosGridPerCell, pos.y / NumOfPosGridPerCell);
+ }
+ public static bool IsInTheSameCell(XY pos1, XY pos2)
+ {
+ return PosGridToCellX(pos1) == PosGridToCellX(pos2) && PosGridToCellY(pos1) == PosGridToCellY(pos2);
+ }
+ public static bool PartInTheSameCell(XY pos1, XY pos2)
+ {
+ return Math.Abs((pos1 - pos2).x) < ShipRadius + (NumOfPosGridPerCell / 2)
+ && Math.Abs((pos1 - pos2).y) < ShipRadius + (NumOfPosGridPerCell / 2);
+ }
+ public static bool ApproachToInteract(XY pos1, XY pos2)
+ {
+ return Math.Abs(PosGridToCellX(pos1) - PosGridToCellX(pos2)) <= 1 && Math.Abs(PosGridToCellY(pos1) - PosGridToCellY(pos2)) <= 1;
+ }
+ public static bool ApproachToInteractInACross(XY pos1, XY pos2)
+ {
+ if (pos1 == pos2) return false;
+ return (Math.Abs(PosGridToCellX(pos1) - PosGridToCellX(pos2)) + Math.Abs(PosGridToCellY(pos1) - PosGridToCellY(pos2))) <= 1;
+ }
+
+ public const int ShipRadius = 400;
+ public static XY PosNotInGame = new XY(1, 1);
+
+ public const int BulletRadius = 200; // 子弹半径
+ public const int LaserRange = 4000; // 激光射程
+ public const int LaserDamage = 1200; // 激光伤害
+ public const double LaserArmorModifier = 1.5; // 激光装甲修正
+ public const double LaserShieldModifier = 0.6; // 激光护盾修正
+ public const int LaserSpeed = 20000; // 激光速度
+ public const int LaserCastTime = 300; // 激光前摇时间
+ public const int LaserSwingTime = 300; // 激光后摇时间
+ public const int PlasmaRange = 4000; // 等离子射程
+ public const int PlasmaDamage = 1300; // 等离子伤害
+ public const double PlasmaArmorModifier = 2.0; // 等离子装甲修正
+ public const double PlasmaShieldModifier = 0.4; // 等离子护盾修正
+ public const int PlasmaSpeed = 10000; // 等离子速度
+ public const int PlasmaCastTime = 400; // 等离子前摇时间
+ public const int PlasmaSwingTime = 400; // 等离子后摇时间
+ public const int ShellRange = 4000; // 炮弹射程
+ public const int ShellDamage = 1800; // 炮弹伤害
+ public const double ShellArmorModifier = 0.4; // 炮弹装甲修正
+ public const double ShellShieldModifier = 1.5; // 炮弹护盾修正
+ public const int ShellSpeed = 8000; // 炮弹速度
+ public const int ShellCastTime = 200; // 炮弹前摇时间
+ public const int ShellSwingTime = 200; // 炮弹后摇时间
+ public const int MissileRange = 8000; // 导弹射程
+ public const int MissileBombRange = 1600; // 导弹爆炸范围
+ public const int MissileDamage = 1600; // 导弹伤害
+ public const double MissileArmorModifier = 1.0; // 导弹装甲修正
+ public const int MissileSpeed = 6000; // 导弹速度
+ public const int MissileCastTime = 600; // 导弹前摇时间
+ public const int MissileSwingTime = 600; // 导弹后摇时间
+ public const int ArcRange = 8000; // 电弧射程
+ public const int ArcDamageMin = 100; // 电弧伤害
+ public const int ArcDamageMax = 3200; // 电弧伤害
+ public const double ArcArmorModifier = 2.0; // 电弧装甲修正
+ public const double ArcShieldModifier = 2.0; // 电弧护盾修正
+ public const int ArcSpeed = 8000; // 电弧速度
+ public const int ArcCastTime = 600; // 电弧前摇时间
+ public const int ArcSwingTime = 600; // 电弧后摇时间
+
+ public const int CivilShipMaxHP = 3000;
+ public const int CivilShipMoveSpeed = 3000;
+ public const int CivilShipViewRange = 8000;
+ public const int CivilShipBaseArmor = 0;
+ public const int CivilShipBaseShield = 0;
+ public const int CivilShipProducer1Cost = 0;
+ public const int CivilShipProducer2Cost = 40;
+ public const int CivilShipProducer3Cost = 80;
+ public const int CivilShipConstructor1Cost = 0;
+ public const int CivilShipConstructor2Cost = 40;
+ public const int CivilShipConstructor3Cost = 80;
+ public const int CivilShipArmor1Cost = 60;
+ public const int CivilShipShield1Cost = 60;
+ public const int CivilShipLaserGunCost = 100;
+
+ public const int WarShipMaxHP = 4000;
+ public const int WarShipMoveSpeed = 2800;
+ public const int WarShipViewRange = 8000;
+ public const int WarShipBaseArmor = 400;
+ public const int WarShipBaseShield = 400;
+ public const int WarShipArmor1Cost = 60;
+ public const int WarShipArmor2Cost = 120;
+ public const int WarShipArmor3Cost = 180;
+ public const int WarShipShield1Cost = 60;
+ public const int WarShipShield2Cost = 120;
+ public const int WarShipShield3Cost = 180;
+ public const int WarShipLaserGunCost = 0;
+ public const int WarShipPlasmaGunCost = 120;
+ public const int WarShipShellGunCost = 130;
+ public const int WarShipMissileGunCost = 180;
+ public const int WarShipArcGunCost = 240;
+
+ public const int FlagShipMaxHP = 12000;
+ public const int FlagShipMoveSpeed = 2700;
+ public const int FlagShipViewRange = 8000;
+ public const int FlagShipBaseArmor = 800;
+ public const int FlagShipBaseShield = 800;
+ public const int FlagShipProducer1Cost = 40;
+ public const int FlagShipConstructor1Cost = 40;
+ public const int FlagShipArmor1Cost = 60;
+ public const int FlagShipArmor2Cost = 120;
+ public const int FlagShipArmor3Cost = 180;
+ public const int FlagShipShield1Cost = 60;
+ public const int FlagShipShield2Cost = 120;
+ public const int FlagShipShield3Cost = 180;
+ public const int FlagShipLaserGunCost = 0;
+ public const int FlagShipPlasmaGunCost = 120;
+ public const int FlagShipShellGunCost = 130;
+ public const int FlagShipMissileGunCost = 180;
+ public const int FlagShipArcGunCost = 240;
+
+ public const int ScoreHomePerSecond = 1;
+ public const int ScoreFactoryPerSecond = 3;
+ public const int ScoreProducer1PerSecond = 5;
+ public const int ScoreProducer2PerSecond = 7;
+ public const int ScoreProducer3PerSecond = 10;
+ public const int ScoreConstructionDamaged = 200;
+ public static int ScoreShipKilled(int totalScore)
+ {
+ return totalScore / 5;
+ }
+ public static int ScoreShipRecovered(int totalRecovery)
+ {
+ return totalRecovery * 6 / 5;
+ }
+ public static int ScoreShipRecycled(int remainingHP)
+ {
+ return remainingHP / 2;
+ }
+
+ public const int Constructor1Speed = 500;
+ public const int Constructor2Speed = 750;
+ public const int Constructor3Speed = 1000;
+ public const int Armor1 = 2000;
+ public const int Armor2 = 3000;
+ public const int Armor3 = 4000;
+ public const int Shield1 = 2000;
+ public const int Shield2 = 3000;
+ public const int Shield3 = 4000;
+
+ public const int ResourceHP = 16000;
+ public const int FactoryHP = 8000;
+ public const int CommunityHP = 6000;
+ public const int FortHP = 12000;
+ public const int WormholeHP = 18000;
+ public const int HomeHP = 24000;
+ }
+}
diff --git a/logic/Preparation/Utility/Logger.cs b/logic/Preparation/Utility/Logger.cs
new file mode 100644
index 00000000..27cd453f
--- /dev/null
+++ b/logic/Preparation/Utility/Logger.cs
@@ -0,0 +1,21 @@
+using System;
+using System.IO;
+
+namespace Preparation.Utility
+{
+ public class Logger
+ {
+ static public void Writelog(object current, string str)
+ {
+ string path = "log.txt";
+ string log = string.Format("[{0}] {1} {2} {3}", DateTime.Now, current.GetType(), current.ToString(), str);
+ File.AppendAllText(path, log + Environment.NewLine);
+ }
+ static public void Writelog(string str)
+ {
+ string path = "log.txt";
+ string log = string.Format("[{0}] {1}", DateTime.Now, str);
+ File.AppendAllText(path, log + Environment.NewLine);
+ }
+ }
+}
diff --git a/logic/Preparation/Utility/MapEncoder.cs b/logic/Preparation/Utility/MapEncoder.cs
new file mode 100644
index 00000000..d8e2c4e4
--- /dev/null
+++ b/logic/Preparation/Utility/MapEncoder.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Preparation.Utility
+{
+ public class MapEncoder
+ {
+ static public char Dec2Hex(int d)
+ {
+ return char.Parse(d.ToString("X"));
+ }
+ static public int Hex2Dec(char h)
+ {
+ string hexabet = "0123456789ABCDEF";
+ return hexabet.IndexOf(h);
+ }
+ }
+}
diff --git a/logic/Preparation/Utility/SafeValue.cs b/logic/Preparation/Utility/SafeValue.cs
new file mode 100644
index 00000000..5f73bb17
--- /dev/null
+++ b/logic/Preparation/Utility/SafeValue.cs
@@ -0,0 +1,778 @@
+using System;
+using System.Threading;
+
+namespace Preparation.Utility
+{
+ //理论上结构体最好不可变,这里采用了可变结构。
+ //其对应属性不应当有set访问器,避免不安全的=赋值
+ public class AtomicInt
+ {
+ private int v;
+ public AtomicInt(int x)
+ {
+ v = x;
+ }
+ public override string ToString() => Interlocked.CompareExchange(ref v, -1, -1).ToString();
+ public int Get() => Interlocked.CompareExchange(ref v, -1, -1);
+ public static implicit operator int(AtomicInt aint) => Interlocked.CompareExchange(ref aint.v, -1, -1);
+ /// 返回操作前的值
+ public int SetReturnOri(int value) => Interlocked.Exchange(ref v, value);
+ public int Add(int x) => Interlocked.Add(ref v, x);
+ public int Sub(int x) => Interlocked.Add(ref v, -x);
+ public int Inc() => Interlocked.Increment(ref v);
+ public int Dec() => Interlocked.Decrement(ref v);
+ /// 返回操作前的值
+ public int CompareExReturnOri(int newV, int compareTo) => Interlocked.CompareExchange(ref v, newV, compareTo);
+ }
+
+ public class AtomicLong
+ {
+ private long v;
+ public AtomicLong(long x)
+ {
+ v = x;
+ }
+ public override string ToString() => Interlocked.Read(ref v).ToString();
+ public long Get() => Interlocked.Read(ref v);
+ public static implicit operator long(AtomicLong aint) => Interlocked.Read(ref aint.v);
+ /// 返回操作前的值
+ public long SetReturnOri(long value) => Interlocked.Exchange(ref v, value);
+ public long Add(long x) => Interlocked.Add(ref v, x);
+ public long Sub(long x) => Interlocked.Add(ref v, -x);
+ public long Inc() => Interlocked.Increment(ref v);
+ public long Dec() => Interlocked.Decrement(ref v);
+ /// 返回操作前的值
+ public long CompareExReturnOri(long newV, long compareTo) => Interlocked.CompareExchange(ref v, newV, compareTo);
+ }
+
+ public class AtomicBool
+ {
+ private int v;//v==0为false,v==1为true
+ public AtomicBool(bool x)
+ {
+ v = x ? 1 : 0;
+ }
+ public override string ToString() => (Interlocked.CompareExchange(ref v, -2, -2) == 0) ? "false" : "true";
+ public bool Get() => (Interlocked.CompareExchange(ref v, -1, -1) != 0);
+ public static implicit operator bool(AtomicBool abool) => (Interlocked.CompareExchange(ref abool.v, -1, -1) != 0);
+ /// 返回操作前的值
+ public bool SetReturnOri(bool value) => (Interlocked.Exchange(ref v, value ? 1 : 0) != 0);
+ /// 赋值前的值是否与将赋予的值不相同
+ public bool TrySet(bool value)
+ {
+ return (Interlocked.CompareExchange(ref v, value ? 1 : 0, value ? 0 : 1) ^ (value ? 1 : 0)) != 0;
+ }
+ public bool And(bool x) => Interlocked.And(ref v, x ? 1 : 0) != 0;
+ public bool Or(bool x) => Interlocked.Or(ref v, x ? 1 : 0) != 0;
+ }
+
+ ///
+ /// 根据时间推算Start后完成多少进度的进度条(long)。
+ /// 只允许Start时修改needTime(请确保大于0);
+ /// 支持TrySet0使未完成的进度条终止清零;支持Set0使进度条强制终止清零;
+ /// 通过原子操作实现。
+ ///
+ public class LongProgressByTime
+ {
+ private long endT = long.MaxValue;
+ private long needT;
+
+ public LongProgressByTime(long needTime)
+ {
+ if (needTime <= 0) Debugger.Output("Bug:LongProgressByTime.needTime (" + needTime.ToString() + ") is less than 0.");
+ this.needT = needTime;
+ }
+ public LongProgressByTime()
+ {
+ this.needT = 0;
+ }
+ public long GetEndTime() => Interlocked.CompareExchange(ref endT, -2, -2);
+ public long GetNeedTime() => Interlocked.CompareExchange(ref needT, -2, -2);
+ public override string ToString() => "EndTime:" + Interlocked.CompareExchange(ref endT, -2, -2).ToString() + " ms, NeedTime:" + Interlocked.CompareExchange(ref needT, -2, -2).ToString() + " ms";
+ public bool IsFinished()
+ {
+ return Interlocked.CompareExchange(ref endT, -2, -2) <= Environment.TickCount64;
+ }
+ public bool IsOpened() => Interlocked.Read(ref endT) != long.MaxValue;
+ ///
+ /// GetProgress<0则表明未开始
+ ///
+ public long GetProgress()
+ {
+ long cutime = Interlocked.CompareExchange(ref endT, -2, -2) - Environment.TickCount64;
+ if (cutime <= 0) return Interlocked.CompareExchange(ref needT, -2, -2);
+ return Interlocked.CompareExchange(ref needT, -2, -2) - cutime;
+ }
+ public long GetNonNegativeProgress()
+ {
+ long cutime = Interlocked.CompareExchange(ref endT, -2, -2) - Environment.TickCount64;
+ if (cutime <= 0) return Interlocked.CompareExchange(ref needT, -2, -2);
+ long progress = Interlocked.CompareExchange(ref needT, -2, -2) - cutime;
+ return progress < 0 ? 0 : progress;
+ }
+ ///
+ /// GetProgress<0则表明未开始
+ ///
+ public long GetProgress(long time)
+ {
+ long cutime = Interlocked.CompareExchange(ref endT, -2, -2) - time;
+ if (cutime <= 0) return Interlocked.CompareExchange(ref needT, -2, -2);
+ return Interlocked.CompareExchange(ref needT, -2, -2) - cutime;
+ }
+ public long GetNonNegativeProgress(long time)
+ {
+ long cutime = Interlocked.Read(ref endT) - time;
+ if (cutime <= 0) return Interlocked.CompareExchange(ref needT, -2, -2);
+ long progress = Interlocked.CompareExchange(ref needT, -2, -2) - cutime;
+ return progress < 0 ? 0 : progress;
+ }
+ ///
+ /// <0则表明未开始
+ ///
+ public static implicit operator long(LongProgressByTime pLong) => pLong.GetProgress();
+
+ ///
+ /// GetProgressDouble<0则表明未开始
+ ///
+ public double GetProgressDouble()
+ {
+ long cutime = Interlocked.CompareExchange(ref endT, -2, -2) - Environment.TickCount64;
+ if (cutime <= 0) return 1;
+ long needTime = Interlocked.CompareExchange(ref needT, -2, -2);
+ if (needTime == 0) return 0;
+ return 1.0 - ((double)cutime / needTime);
+ }
+ public double GetNonNegativeProgressDouble(long time)
+ {
+ long cutime = Interlocked.Read(ref endT) - time;
+ if (cutime <= 0) return 1;
+ long needTime = Interlocked.CompareExchange(ref needT, -2, -2);
+ if (needTime <= cutime) return 0;
+ return 1.0 - ((double)cutime / needTime);
+ }
+
+ public bool Start(long needTime)
+ {
+ if (needTime <= 0)
+ {
+ Debugger.Output("Warning:Start LongProgressByTime with the needTime (" + needTime.ToString() + ") which is less than 0.");
+ return false;
+ }
+ //规定只有Start可以修改needT,且需要先访问endTime,从而避免锁(某种程度上endTime可以认为是needTime的锁)
+ if (Interlocked.CompareExchange(ref endT, Environment.TickCount64 + needTime, long.MaxValue) != long.MaxValue) return false;
+ if (needTime <= 2) Debugger.Output("Warning:the field of LongProgressByTime is " + needTime.ToString() + ",which is too small.");
+ Interlocked.Exchange(ref needT, needTime);
+ return true;
+ }
+ public bool Start()
+ {
+ long needTime = Interlocked.CompareExchange(ref needT, -2, -2);
+ if (Interlocked.CompareExchange(ref endT, Environment.TickCount64 + needTime, long.MaxValue) != long.MaxValue) return false;
+ return true;
+ }
+ ///
+ /// 使进度条强制终止清零
+ ///
+ public void Set0() => Interlocked.Exchange(ref endT, long.MaxValue);
+ ///
+ /// 使未完成的进度条终止清零
+ ///
+ public bool TrySet0()
+ {
+ if (Environment.TickCount64 < Interlocked.CompareExchange(ref endT, -2, -2))
+ {
+ Interlocked.Exchange(ref endT, long.MaxValue);
+ return true;
+ }
+ return false;
+ }
+ //增加其他新的写操作可能导致不安全
+ }
+
+ /*
+ ///
+ /// 记录(不是根据时间)完成多少进度的进度条(long)。
+ ///
+ public struct IntProgressByAdding
+ {
+ private int completedProgress = -1;
+ private int requiredProgress;
+ public IntProgressByAdding(int completedProgress, int requiredProgress)
+ {
+ this.completedProgress = completedProgress;
+ this.requiredProgress = requiredProgress;
+ }
+ public IntProgressByAdding(int requiredProgress)
+ {
+ this.requiredProgress = requiredProgress;
+ }
+ public IntProgressByAdding()
+ {
+ this.requiredProgress=int.MaxValue;
+ }
+ }
+ */
+
+ ///
+ /// 一个保证在[0,maxValue]的可变int,支持可变的maxValue(请确保大于0)
+ ///
+ public class IntWithVariableRange
+ {
+ private int v;
+ private int maxV;
+ private readonly object vLock = new();
+ public IntWithVariableRange(int value, int maxValue)
+ {
+ if (maxValue < 0)
+ {
+ Debugger.Output("Warning:Try to set IntWithVariableRange.maxValue to " + maxValue.ToString() + ".");
+ maxValue = 0;
+ }
+ v = value < maxValue ? value : maxValue;
+ this.maxV = maxValue;
+ }
+ ///
+ /// 默认使Value=maxValue
+ ///
+ public IntWithVariableRange(int maxValue)
+ {
+ if (maxValue < 0)
+ {
+ Debugger.Output("Warning:Try to set IntWithVariableRange.maxValue to " + maxValue.ToString() + ".");
+ maxValue = 0;
+ }
+ v = this.maxV = maxValue;
+ }
+ public IntWithVariableRange()
+ {
+ v = this.maxV = int.MaxValue;
+ }
+
+ public override string ToString()
+ {
+ lock (vLock)
+ {
+ return "value:" + v.ToString() + " ,maxValue:" + maxV.ToString();
+ }
+ }
+ public int GetValue() { lock (vLock) return v; }
+ public static implicit operator int(IntWithVariableRange aint) => aint.GetValue();
+ public int GetMaxV() { lock (vLock) return maxV; }
+
+ ///
+ /// 若maxValue<=0则maxValue设为0并返回False
+ ///
+ public bool SetMaxV(int maxValue)
+ {
+ if (maxValue < 0) maxValue = 0;
+ lock (vLock)
+ {
+ maxV = maxValue;
+ if (v > maxValue) v = maxValue;
+ }
+ return maxValue > 0;
+ }
+ ///
+ /// 应当保证该maxValue>=0
+ ///
+ public void SetPositiveMaxV(int maxValue)
+ {
+ lock (vLock)
+ {
+ maxV = maxValue;
+ if (v > maxValue) v = maxValue;
+ }
+ }
+ ///
+ /// 应当保证该value>=0
+ ///
+ public int SetPositiveV(int value)
+ {
+ lock (vLock)
+ {
+ return v = (value > maxV) ? maxV : value;
+ }
+ }
+ public int SetV(int value)
+ {
+ if (value < 0) value = 0;
+ lock (vLock)
+ {
+ return v = (value > maxV) ? maxV : value;
+ }
+ }
+ ///
+ /// 返回实际改变量
+ ///
+ public int AddV(int addV)
+ {
+ lock (vLock)
+ {
+ int previousV = v;
+ v += addV;
+ if (v < 0) v = 0;
+ if (v > maxV) v = maxV;
+ return v - previousV;
+ }
+ }
+ ///
+ /// 应当保证该增加值大于0,返回实际改变量
+ ///
+ public int AddPositiveV(int addPositiveV)
+ {
+ lock (vLock)
+ {
+ addPositiveV = Math.Min(addPositiveV, maxV - v);
+ v += addPositiveV;
+ }
+ return addPositiveV;
+ }
+ ///
+ /// 应当保证该减少值大于0,返回实际改变量
+ ///
+ public int SubPositiveV(int subPositiveV)
+ {
+ lock (vLock)
+ {
+ subPositiveV = Math.Min(subPositiveV, v);
+ v -= subPositiveV;
+ }
+ return subPositiveV;
+ }
+
+ ///
+ /// 试图加到满,如果无法加到maxValue则不加并返回-1
+ ///
+ public int TryAddAll(int addV)
+ {
+ lock (vLock)
+ {
+ if (maxV - v <= addV)
+ {
+ addV = maxV - v;
+ v = maxV;
+ return addV;
+ }
+ return 0;
+ }
+ }
+ }
+
+ ///
+ /// 一个保证在[0,maxValue]的可变long,支持可变的maxValue(请确保大于0)
+ ///
+ public class LongWithVariableRange
+ {
+ private long v;
+ private long maxV;
+ private readonly object vLock = new();
+ public LongWithVariableRange(long value, long maxValue)
+ {
+ if (maxValue < 0)
+ {
+ Debugger.Output("Warning:Try to set SafaValues.LongWithVariableRange.maxValue to " + maxValue.ToString() + ".");
+ maxValue = 0;
+ }
+ v = value < maxValue ? value : maxValue;
+ this.maxV = maxValue;
+ }
+ ///
+ /// 默认使Value=maxValue
+ ///
+ public LongWithVariableRange(long maxValue)
+ {
+ if (maxValue < 0)
+ {
+ Debugger.Output("Warning:Try to set SafaValues.LongWithVariableRange.maxValue to " + maxValue.ToString() + ".");
+ maxValue = 0;
+ }
+ v = this.maxV = maxValue;
+ }
+ public LongWithVariableRange()
+ {
+ v = this.maxV = long.MaxValue;
+ }
+
+ public override string ToString()
+ {
+ lock (vLock)
+ {
+ return "value:" + v.ToString() + " ,maxValue:" + maxV.ToString();
+ }
+ }
+ public long GetValue() { lock (vLock) return v; }
+ public static implicit operator long(LongWithVariableRange aint) => aint.GetValue();
+ public long GetMaxV() { lock (vLock) return maxV; }
+
+ ///
+ /// 若maxValue<=0则maxValue设为0并返回False
+ ///
+ public bool SetMaxV(long maxValue)
+ {
+ if (maxValue < 0) maxValue = 0;
+ lock (vLock)
+ {
+ maxV = maxValue;
+ if (v > maxValue) v = maxValue;
+ }
+ return maxValue > 0;
+ }
+ ///
+ /// 应当保证该maxValue>=0
+ ///
+ public void SetPositiveMaxV(long maxValue)
+ {
+ lock (vLock)
+ {
+ maxV = maxValue;
+ if (v > maxValue) v = maxValue;
+ }
+ }
+ ///
+ /// 应当保证该value>=0
+ ///
+ public long SetPositiveV(long value)
+ {
+ lock (vLock)
+ {
+ return v = (value > maxV) ? maxV : value;
+ }
+ }
+ public long SetV(long value)
+ {
+ if (value < 0) value = 0;
+ lock (vLock)
+ {
+ return v = (value > maxV) ? maxV : value;
+ }
+ }
+ ///
+ /// 返回实际改变量
+ ///
+ public long AddV(long addV)
+ {
+ lock (vLock)
+ {
+ long previousV = v;
+ v += addV;
+ if (v < 0) v = 0;
+ if (v > maxV) v = maxV;
+ return v - previousV;
+ }
+ }
+ ///
+ /// 应当保证该增加值大于0,返回实际改变量
+ ///
+ public long AddPositiveV(long addPositiveV)
+ {
+ lock (vLock)
+ {
+ addPositiveV = Math.Min(addPositiveV, maxV - v);
+ v += addPositiveV;
+ }
+ return addPositiveV;
+ }
+ ///
+ /// 应当保证该减少值大于0,返回实际改变量
+ ///
+ public long SubPositiveV(long subPositiveV)
+ {
+ lock (vLock)
+ {
+ subPositiveV = Math.Min(subPositiveV, v);
+ v -= subPositiveV;
+ }
+ return subPositiveV;
+ }
+
+ ///
+ /// 试图加到满,如果无法加到maxValue则不加并返回-1
+ ///
+ public long TryAddAll(long addV)
+ {
+ lock (vLock)
+ {
+ if (maxV - v <= addV)
+ {
+ addV = maxV - v;
+ v = maxV;
+ return addV;
+ }
+ return -1;
+ }
+ }
+ }
+
+ ///
+ /// 一个保证在[0,maxNum],每CDms自动+1的int,支持可变的CD、maxNum(请确保大于0)
+ ///
+ public class IntNumUpdateByCD
+ {
+ private int num;
+ private int maxNum;
+ private int cd;
+ private long updateTime = 0;
+ private readonly object numLock = new();
+ public IntNumUpdateByCD(int num, int maxNum, int cd)
+ {
+ if (num < 0) Debugger.Output("Bug:IntNumUpdateByCD.num (" + num.ToString() + ") is less than 0.");
+ if (maxNum < 0) Debugger.Output("Bug:IntNumUpdateByCD.maxNum (" + maxNum.ToString() + ") is less than 0.");
+ if (cd <= 0) Debugger.Output("Bug:IntNumUpdateByCD.cd (" + cd.ToString() + ") is less than 0.");
+ this.num = (num < maxNum) ? num : maxNum;
+ this.maxNum = maxNum;
+ this.cd = cd;
+ this.updateTime = Environment.TickCount64;
+ }
+ ///
+ /// 默认使num=maxNum
+ ///
+ public IntNumUpdateByCD(int maxNum, int cd)
+ {
+ if (maxNum < 0) Debugger.Output("Bug:IntNumUpdateByCD.maxNum (" + maxNum.ToString() + ") is less than 0.");
+ if (cd <= 0) Debugger.Output("Bug:IntNumUpdateByCD.cd (" + cd.ToString() + ") is less than 0.");
+ this.num = this.maxNum = maxNum;
+ this.cd = cd;
+ }
+ public IntNumUpdateByCD()
+ {
+ this.num = this.maxNum = 0;
+ this.cd = int.MaxValue;
+ }
+
+ public int GetMaxNum() { lock (numLock) return maxNum; }
+ public int GetCD() { lock (numLock) return cd; }
+ public int GetNum(long time)
+ {
+ lock (numLock)
+ {
+ if (num < maxNum && time - updateTime >= cd)
+ {
+ int add = (int)Math.Min(maxNum - num, (time - updateTime) / cd);
+ updateTime += add * cd;
+ return (num += add);
+ }
+ return num;
+ }
+ }
+ public static implicit operator int(IntNumUpdateByCD aint) => aint.GetNum(Environment.TickCount64);
+
+ ///
+ /// 应当保证该subV>=0
+ ///
+ public int TrySub(int subV)
+ {
+ if (subV < 0) Debugger.Output("Bug:IntNumUpdateByCD Try to sub " + subV.ToString() + ", which is less than 0.");
+ long time = Environment.TickCount64;
+ lock (numLock)
+ {
+ if (num < maxNum && time - updateTime >= cd)
+ {
+ int add = (int)Math.Min(maxNum - num, (time - updateTime) / cd);
+ updateTime += add * cd;
+ num += add;
+ }
+ if (num == maxNum) updateTime = time;
+ num -= subV = Math.Min(subV, num);
+ }
+ return subV;
+ }
+ ///
+ /// 应当保证该addV>=0
+ ///
+ public void TryAdd(int addV)
+ {
+ if (addV < 0) Debugger.Output("Bug:IntNumUpdateByCD Try to add " + addV.ToString() + ", which is less than 0.");
+ lock (numLock)
+ {
+ num += Math.Min(addV, maxNum - num);
+ }
+ }
+ ///
+ /// 若maxNum<=0则maxNum及Num设为0并返回False
+ ///
+ public bool SetMaxNumAndNum(int maxNum)
+ {
+ if (maxNum < 0) maxNum = 0;
+ lock (numLock)
+ {
+ this.num = this.maxNum = maxNum;
+ }
+ return maxNum > 0;
+ }
+ ///
+ /// 应当保证该maxnum>=0
+ ///
+ public void SetPositiveMaxNumAndNum(int maxNum)
+ {
+ lock (numLock)
+ {
+ this.num = this.maxNum = maxNum;
+ }
+ }
+ ///
+ /// 应当保证该maxnum>=0
+ ///
+ public void SetPositiveMaxNum(int maxNum)
+ {
+ lock (numLock)
+ {
+ if ((this.maxNum = maxNum) < num)
+ num = maxNum;
+ }
+ }
+ ///
+ /// 若maxNum<=0则maxNum及Num设为0并返回False
+ ///
+ public bool SetMaxNum(int maxNum)
+ {
+ if (maxNum < 0) maxNum = 0;
+ lock (numLock)
+ {
+ if ((this.maxNum = maxNum) < num)
+ num = maxNum;
+ }
+ return maxNum > 0;
+ }
+ ///
+ /// 若num<0则num设为0并返回False
+ ///
+ public bool SetNum(int num)
+ {
+ lock (numLock)
+ {
+ if (num < 0)
+ {
+ this.num = 0;
+ updateTime = Environment.TickCount64;
+ return false;
+ }
+ if (num < maxNum)
+ {
+ if (this.num == maxNum) updateTime = Environment.TickCount64;
+ this.num = num;
+ }
+ else this.num = maxNum;
+ return true;
+ }
+ }
+ ///
+ /// 应当保证该num>=0
+ ///
+ public void SetPositiveNum(int num)
+ {
+ lock (numLock)
+ {
+ if (num < maxNum)
+ {
+ if (this.num == maxNum) updateTime = Environment.TickCount64;
+ this.num = num;
+ }
+ else this.num = maxNum;
+ }
+ }
+ public void SetCD(int cd)
+ {
+ lock (numLock)
+ {
+ if (cd <= 0) Debugger.Output("Bug:SetReturnOri IntNumUpdateByCD.cd to " + cd.ToString() + ".");
+ this.cd = cd;
+ }
+ }
+ }
+
+ ///
+ /// 一个每CDms自动更新冷却的bool,支持可变的无锁CD,不支持查看当前进度,初始为True
+ ///
+ public class BoolCoolingDownByCD
+ {
+ private long cd;
+ private long nextUpdateTime = 0;
+ public BoolCoolingDownByCD(int cd)
+ {
+ if (cd <= 1) Debugger.Output("Bug:IntNumUpdateByCD.cd (" + cd.ToString() + ") is less than 1.");
+ this.cd = cd;
+ }
+ public BoolCoolingDownByCD(long cd)
+ {
+ if (cd <= 1) Debugger.Output("Bug:IntNumUpdateByCD.cd (" + cd.ToString() + ") is less than 1.");
+ this.cd = cd;
+ }
+ public BoolCoolingDownByCD(long cd, long startTime)
+ {
+ if (cd <= 1) Debugger.Output("Bug:IntNumUpdateByCD.cd (" + cd.ToString() + ") is less than 1.");
+ this.cd = cd;
+ this.nextUpdateTime = startTime;
+ }
+
+ public long GetCD() => Interlocked.Read(ref cd);
+
+ public bool TryUse()
+ {
+ long needTime = Interlocked.Exchange(ref nextUpdateTime, long.MaxValue);
+ if (needTime <= Environment.TickCount64)
+ {
+ Interlocked.Exchange(ref nextUpdateTime, Environment.TickCount64 + Interlocked.Read(ref cd));
+ return true;
+ }
+ Interlocked.Exchange(ref nextUpdateTime, needTime);
+ return false;
+ }
+ public void SetCD(int cd)
+ {
+ if (cd <= 1) Debugger.Output("Bug:SetReturnOri IntNumUpdateByCD.cd to " + cd.ToString() + ".");
+ Interlocked.Exchange(ref this.cd, cd);
+ }
+ }
+
+ ///
+ /// 一个每CDms自动更新的进度条,支持可变的CD,初始为满
+ ///
+ public class LongProgressCoolingDownByCD
+ {
+ private int isusing = 0;
+ private long cd;
+ private long nextUpdateTime = 0;
+ public LongProgressCoolingDownByCD(int cd)
+ {
+ if (cd <= 1) Debugger.Output("Bug:IntNumUpdateByCD.cd (" + cd.ToString() + ") is less than 1.");
+ this.cd = cd;
+ }
+ public LongProgressCoolingDownByCD(long cd)
+ {
+ if (cd <= 1) Debugger.Output("Bug:IntNumUpdateByCD.cd (" + cd.ToString() + ") is less than 1.");
+ this.cd = cd;
+ }
+ public LongProgressCoolingDownByCD(long cd, long startTime)
+ {
+ if (cd <= 1) Debugger.Output("Bug:IntNumUpdateByCD.cd (" + cd.ToString() + ") is less than 1.");
+ this.cd = cd;
+ this.nextUpdateTime = startTime;
+ }
+
+ public long GetRemainingTime()
+ {
+ long v = Interlocked.Read(ref nextUpdateTime) - Environment.TickCount64;
+ return v < 0 ? 0 : v;
+ }
+ public long GetCD() => Interlocked.Read(ref cd);
+
+ public bool TryUse()
+ {
+ if (Interlocked.Exchange(ref isusing, 1) == 1) return false;
+ long needTime = Interlocked.Read(ref nextUpdateTime);
+ if (needTime <= Environment.TickCount64)
+ {
+ Interlocked.Exchange(ref nextUpdateTime, Environment.TickCount64 + Interlocked.Read(ref cd));
+ Interlocked.Exchange(ref isusing, 0);
+ return true;
+ }
+ Interlocked.Exchange(ref isusing, 0);
+ return false;
+ }
+ public void SetCD(int cd)
+ {
+ if (cd <= 1) Debugger.Output("Bug:SetReturnOri IntNumUpdateByCD.cd to " + cd.ToString() + ".");
+ Interlocked.Exchange(ref this.cd, cd);
+ }
+ }
+}
diff --git a/logic/Preparation/Utility/XY.cs b/logic/Preparation/Utility/XY.cs
new file mode 100644
index 00000000..ddf27615
--- /dev/null
+++ b/logic/Preparation/Utility/XY.cs
@@ -0,0 +1,98 @@
+using System;
+
+namespace Preparation.Utility
+{
+ public struct XY
+ {
+ public int x;
+ public int y;
+ public XY(int x, int y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+ public XY(double angle, double length)
+ {
+ this.x = (int)(length * Math.Cos(angle));
+ this.y = (int)(length * Math.Sin(angle));
+ }
+ public XY(XY Direction, double length)
+ {
+ if (Direction.x == 0 && Direction.y == 0)
+ {
+ this.x = 0;
+ this.y = 0;
+ }
+ else
+ {
+ this.x = (int)(length * Math.Cos(Direction.Angle()));
+ this.y = (int)(length * Math.Sin(Direction.Angle()));
+ }
+ }
+ public override string ToString()
+ {
+ return "(" + x.ToString() + "," + y.ToString() + ")";
+ }
+ public static int operator *(XY v1, XY v2)
+ {
+ return (v1.x * v2.x) + (v1.y * v2.y);
+ }
+ public static XY operator *(int a, XY v2)
+ {
+ return new XY(a * v2.x, a * v2.y);
+ }
+ public static XY operator *(XY v2, int a)
+ {
+ return new XY(a * v2.x, a * v2.y);
+ }
+ public static XY operator +(XY v1, XY v2)
+ {
+ return new XY(v1.x + v2.x, v1.y + v2.y);
+ }
+ public static XY operator -(XY v1, XY v2)
+ {
+ return new XY(v1.x - v2.x, v1.y - v2.y);
+ }
+ public static bool operator ==(XY v1, XY v2)
+ {
+ return v1.x == v2.x && v1.y == v2.y;
+ }
+ public static bool operator !=(XY v1, XY v2)
+ {
+ return v1.x != v2.x || v1.y != v2.y;
+ }
+
+ public static double DistanceFloor3(XY p1, XY p2)
+ {
+ long c = (((long)(p1.x - p2.x) * (p1.x - p2.x)) + ((long)(p1.y - p2.y) * (p1.y - p2.y))) * 1000000;
+ long t = c / 2 + 1;
+ while (t * t > c || (t + 1) * (t + 1) <= c)
+ t = (c / t + t) / 2;
+ return (double)t / 1000.0;
+ }
+ public static double DistanceCeil3(XY p1, XY p2)
+ {
+ long c = (((long)(p1.x - p2.x) * (p1.x - p2.x)) + ((long)(p1.y - p2.y) * (p1.y - p2.y))) * 1000000;
+ long t = c / 2 + 1;
+ while (t * t > c || (t + 1) * (t + 1) <= c)
+ t = (c / t + t) / 2;
+ if (t * t == c) return (double)t / 1000.0;
+ else return (double)(t + 1) / 1000.0;
+ }
+ public double Length()
+ {
+ return Math.Sqrt(((long)x * x) + ((long)y * y));
+ }
+ public double Angle()
+ {
+ return Math.Atan2(y, x);
+ }
+
+ public override bool Equals(object? obj) => obj is not null && obj is XY xy && this == xy;
+
+ public override int GetHashCode()
+ {
+ return this.x.GetHashCode() ^ this.y.GetHashCode();
+ }
+ }
+}
diff --git a/logic/Server/ArgumentOptions.cs b/logic/Server/ArgumentOptions.cs
new file mode 100644
index 00000000..74836bc2
--- /dev/null
+++ b/logic/Server/ArgumentOptions.cs
@@ -0,0 +1,44 @@
+using CommandLine;
+
+namespace Server
+{
+ public class ArgumentOptions
+ {
+ [Option("ip", Required = false, HelpText = "Server listening ip")]
+ public string ServerIP { get; set; } = "0.0.0.0";
+ [Option('p', "port", Required = true, HelpText = "Server listening port")]
+ public ushort ServerPort { get; set; } = 8888;
+ [Option("teamCount", Required = false, HelpText = "The number of teams, 2 by defualt")]
+ public ushort TeamCount { get; set; } = 2;
+ [Option('g', "gameTimeInSecond", Required = false, HelpText = "The time of the game in second, 10 minutes by default")]
+ public uint GameTimeInSecond { get; set; } = 10 * 60;
+ [Option('f', "fileName", Required = false, HelpText = "The file to store playback file or to read file.")]
+ public string FileName { get; set; } = "114514";
+ [Option("notAllowSpectator", Required = false, HelpText = "Whether to allow a spectator to watch the game.")]
+ public bool NotAllowSpectator { get; set; } = false;
+ [Option('b', "playback", Required = false, HelpText = "Whether open the server in a playback mode.")]
+ public bool Playback { get; set; } = false;
+ [Option("playbackSpeed", Required = false, HelpText = "The speed of the playback, between 0.25 and 4.0")]
+ public double PlaybackSpeed { get; set; } = 1.0;
+ [Option("resultOnly", Required = false, HelpText = "In playback mode to get the result directly")]
+ public bool ResultOnly { get; set; } = false;
+ [Option('k', "token", Required = false, HelpText = "Web API Token")]
+ public string Token { get; set; } = "114514";
+ [Option('u', "url", Required = false, HelpText = "Web Url")]
+ public string Url { get; set; } = "114514";
+ [Option('m', "mapResource", Required = false, HelpText = "Map Resource Path")]
+ public string mapResource { get; set; } = "114514";
+ [Option("requestOnly", Required = false, HelpText = "Only send web requests")]
+ public bool RequestOnly { get; set; } = false;
+ [Option("finalGame", Required = false, HelpText = "Whether it is the final game")]
+ public bool FinalGame { get; set; } = false;
+ [Option("cheatMode", Required = false, HelpText = "Whether to open the cheat code")]
+ public bool CheatMode { get; set; } = false;
+ [Option("resultFileName", Required = false, HelpText = "Result file name, saved as .json")]
+ public string ResultFileName { get; set; } = "114514";
+ [Option("startLockFile", Required = false, HelpText = "Whether to create a file that identifies whether the game has started")]
+ public string StartLockFile { get; set; } = "114514";
+ [Option("mode", Required = false, HelpText = "Whether to run final competition")]
+ public int Mode { get; set; } = 0;
+ }
+}
diff --git a/logic/Server/PlaybackServer.cs b/logic/Server/PlaybackServer.cs
new file mode 100644
index 00000000..58ba64df
--- /dev/null
+++ b/logic/Server/PlaybackServer.cs
@@ -0,0 +1,332 @@
+//using Gaming;
+//using Grpc.Core;
+//using Playback;
+//using Protobuf;
+//using System.Collections.Concurrent;
+//using Timothy.FrameRateTask;
+
+//namespace Server
+//{
+// class PlaybackServer : ServerBase
+// {
+// protected readonly ArgumentOptions options;
+// private int[,] teamScore;
+// private ConcurrentDictionary semaDict = new();
+// // private object semaDictLock = new();
+// private MessageToClient? currentGameInfo = new();
+// private MessageOfObj currentMapMsg = new();
+// private uint spectatorMinPlayerID = 2023;
+// // private List spectatorList = new List();
+// public int TeamCount => options.TeamCount;
+// 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 int[] finalScore;
+// public int[] FinalScore
+// {
+// get
+// {
+// return finalScore;
+// }
+// }
+// public override int[] GetScore() => FinalScore;
+// public PlaybackServer(ArgumentOptions options)
+// {
+// this.options = options;
+// IsGaming = true;
+// teamScore = new int[0, 0];
+// finalScore = new int[0];
+// }
+
+// //public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter responseStream, ServerCallContext context)
+// //{
+// // Console.WriteLine($"AddPlayer: {request.PlayerId}");
+// // if (request.PlayerId >= spectatorMinPlayerID && options.NotAllowSpectator == false)
+// // {
+// // // 观战模式
+// // lock (spetatorJoinLock) // 具体原因见另一个上锁的地方
+// // {
+// // if (semaDict.TryAdd(request.PlayerId, (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1))))
+// // {
+// // Console.WriteLine("A new spectator comes to watch this game.");
+// // IsSpectatorJoin = true;
+// // }
+// // else
+// // {
+// // Console.WriteLine($"Duplicated Spectator ID {request.PlayerId}");
+// // return;
+// // }
+// // }
+// // do
+// // {
+// // semaDict[request.PlayerId].Item1.Wait();
+// // try
+// // {
+// // if (currentGameInfo != null)
+// // {
+// // await responseStream.WriteAsync(currentGameInfo);
+// // //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)
+// // {
+// // // Console.WriteLine(ex);
+// // }
+// // finally
+// // {
+// // try
+// // {
+// // semaDict[request.PlayerId].Item2.Release();
+// // }
+// // catch { }
+// // }
+// // } while (IsGaming);
+// // return;
+// // }
+// //}
+
+// public void ReportGame(MessageToClient? 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)
+// {
+// kvp.Value.Item1.Release();
+// }
+
+// foreach (var kvp in semaDict)
+// {
+// kvp.Value.Item2.Wait();
+// }
+// }
+
+// public override void WaitForEnd()
+// {
+// try
+// {
+// if (options.ResultOnly)
+// {
+// using (MessageReader mr = new MessageReader(options.FileName))
+// {
+// Console.WriteLine("Parsing playback file...");
+// teamScore = new int[mr.teamCount, mr.playerCount];
+// finalScore = new int[mr.teamCount];
+// int infoNo = 0;
+// object cursorLock = new object();
+// var initialTop = Console.CursorTop;
+// var initialLeft = Console.CursorLeft;
+// while (true)
+// {
+// MessageToClient? msg = null;
+// for (int i = 0; i < mr.teamCount; ++i)
+// {
+// for (int j = 0; j < mr.playerCount; ++j)
+// {
+// msg = mr.ReadOne();
+// if (msg == null)
+// {
+// Console.WriteLine("The game doesn't come to an end because of timing up!");
+// IsGaming = false;
+// goto endParse;
+// }
+
+// lock (cursorLock)
+// {
+// var curTop = Console.CursorTop;
+// var curLeft = Console.CursorLeft;
+// Console.SetCursorPosition(initialLeft, initialTop);
+// Console.WriteLine($"Parsing messages... Current message number: {infoNo}");
+// Console.SetCursorPosition(curLeft, curTop);
+// }
+
+// if (msg != null)
+// {
+// //teamScore[i] = msg.TeamScore;
+// }
+// }
+// }
+
+// ++infoNo;
+
+// if (msg == null)
+// {
+// Console.WriteLine("No game information in this file!");
+// goto endParse;
+// }
+// if (msg.GameState == GameState.GameEnd)
+// {
+// Console.WriteLine("Game over normally!");
+// finalScore[0] = msg.AllMessage.StudentScore;
+// finalScore[1] = msg.AllMessage.TrickerScore;
+// goto endParse;
+// }
+// }
+
+// endParse:
+
+// Console.WriteLine($"Successfully parsed {infoNo} informations!");
+// }
+// }
+// else
+// {
+// long timeInterval = GameServer.SendMessageToClientIntervalInMilliseconds;
+// if (options.PlaybackSpeed != 1.0)
+// {
+// options.PlaybackSpeed = Math.Max(0.25, Math.Min(4.0, options.PlaybackSpeed));
+// timeInterval = (int)Math.Round(timeInterval / options.PlaybackSpeed);
+// }
+// using (MessageReader mr = new MessageReader(options.FileName))
+// {
+// teamScore = new int[mr.teamCount, mr.playerCount];
+// finalScore = new int[mr.teamCount];
+// int infoNo = 0;
+// object cursorLock = new object();
+// var msgCurTop = Console.CursorTop;
+// var msgCurLeft = Console.CursorLeft;
+// var frt = new FrameRateTaskExecutor
+// (
+// loopCondition: () => true,
+// loopToDo: () =>
+// {
+// MessageToClient? msg = null;
+
+// msg = mr.ReadOne();
+// if (msg == null)
+// {
+// Console.WriteLine("The game doesn't come to an end because of timing up!");
+// IsGaming = false;
+// ReportGame(msg);
+// return false;
+// }
+// ReportGame(msg);
+// lock (cursorLock)
+// {
+// var curTop = Console.CursorTop;
+// var curLeft = Console.CursorLeft;
+// Console.SetCursorPosition(msgCurLeft, msgCurTop);
+// Console.WriteLine($"Sending messages... Current message number: {infoNo}.");
+// Console.SetCursorPosition(curLeft, curTop);
+// }
+// if (msg != null)
+// {
+// foreach (var item in msg.ObjMessage)
+// {
+// if (item.StudentMessage != null)
+// teamScore[0, item.StudentMessage.PlayerId] = item.StudentMessage.Score;
+// if (item.TrickerMessage != null)
+// teamScore[1, item.TrickerMessage.PlayerId - options.MaxStudentCount] = item.TrickerMessage.Score; // 这里默认 Tricker 的 PlayerId 从 MaxStudentCount = 4 开始
+// }
+// }
+
+// ++infoNo;
+// if (msg == null)
+// {
+// Console.WriteLine("No game information in this file!");
+// IsGaming = false;
+// ReportGame(msg);
+// return false;
+// }
+// if (msg.GameState == GameState.GameEnd)
+// {
+// Console.WriteLine("Game over normally!");
+// IsGaming = false;
+// finalScore[0] = msg.AllMessage.StudentScore;
+// finalScore[1] = msg.AllMessage.TrickerScore;
+// ReportGame(msg);
+// return false;
+// }
+// return true;
+// },
+// timeInterval: timeInterval,
+// finallyReturn: () => 0
+// )
+// { AllowTimeExceed = true, MaxTolerantTimeExceedCount = 5 };
+
+// Console.WriteLine("The server is well prepared! Please MAKE SURE that you have opened all the clients to watch the game!");
+// Console.WriteLine("If ALL clients have opened, press any key to start.");
+// Console.ReadKey();
+
+// new Thread
+// (
+// () =>
+// {
+// var rateCurTop = Console.CursorTop;
+// var rateCurLeft = Console.CursorLeft;
+// lock (cursorLock)
+// {
+// rateCurTop = Console.CursorTop;
+// rateCurLeft = Console.CursorLeft;
+// Console.WriteLine($"Send message to clients frame rate: {frt.FrameRate}");
+// }
+// while (!frt.Finished)
+// {
+// lock (cursorLock)
+// {
+// var curTop = Console.CursorTop;
+// var curLeft = Console.CursorLeft;
+// Console.SetCursorPosition(rateCurLeft, rateCurTop);
+// Console.WriteLine($"Send message to clients frame rate: {frt.FrameRate}");
+// Console.SetCursorPosition(curLeft, curTop);
+// }
+// Thread.Sleep(1000);
+// }
+// }
+// )
+// { IsBackground = true }.Start();
+
+// lock (cursorLock)
+// {
+// msgCurLeft = Console.CursorLeft;
+// msgCurTop = Console.CursorTop;
+// Console.WriteLine("Sending messages...");
+// }
+// frt.Start();
+// }
+// }
+// }
+// finally
+// {
+// teamScore ??= new int[0, 0];
+// }
+// }
+// }
+//}
diff --git a/logic/Server/Program.cs b/logic/Server/Program.cs
new file mode 100644
index 00000000..71bb2d7d
--- /dev/null
+++ b/logic/Server/Program.cs
@@ -0,0 +1,74 @@
+//using CommandLine;
+//using Grpc.Core;
+//using Protobuf;
+
+//namespace Server
+//{
+// public class Program
+// {
+// const string welcome =
+//@"
+// _____ _ _ _ _ _ ___ _____
+// |_ _| | | | | | | / \ |_ _|___ |
+// | | | |_| | | | |/ _ \ | | / /
+// | | | _ | |_| / ___ \ | | / /
+// |_| |_| |_|\___/_/ \_\___|/_/
+//";
+// static ServerBase CreateServer(ArgumentOptions options)
+// {
+// //return options.Playback ? new PlaybackServer(options) : new GameServer(options);
+// return new PlaybackServer(options);
+// }
+
+// static int Main(string[] args)
+// {
+// foreach (var arg in args)
+// {
+// Console.Write($"{arg} ");
+// }
+// Console.WriteLine();
+
+// ArgumentOptions? options = null;
+// _ = Parser.Default.ParseArguments(args).WithParsed(o => { options = o; });
+// if (options == null)
+// {
+// Console.WriteLine("Argument parsing failed!");
+// return 1;
+// }
+
+// if (options.StartLockFile == "114514")
+// {
+// Console.WriteLine(welcome);
+// }
+// Console.WriteLine("Server begins to run: " + options.ServerPort.ToString());
+
+// try
+// {
+// var server = CreateServer(options);
+// Grpc.Core.Server rpcServer = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
+// {
+// Services = { AvailableService.BindService(server) },
+// Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) }
+// };
+// rpcServer.Start();
+
+// 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($"Team0: {server.GetScore()[0]}");
+// Console.WriteLine($"Team1: {server.GetScore()[1]}");
+// }
+// catch (Exception ex)
+// {
+// Console.WriteLine(ex.ToString());
+// Console.WriteLine(ex.StackTrace);
+// }
+// return 0;
+// }
+// }
+//}
diff --git a/logic/Server/ServerBase.cs b/logic/Server/ServerBase.cs
new file mode 100644
index 00000000..f23a2c1c
--- /dev/null
+++ b/logic/Server/ServerBase.cs
@@ -0,0 +1,10 @@
+using Protobuf;
+
+namespace Server
+{
+ abstract class ServerBase : AvailableService.AvailableServiceBase
+ {
+ public abstract void WaitForEnd();
+ public abstract int[] GetScore();
+ }
+}
diff --git a/logic/logic.sln b/logic/logic.sln
index c4c1d32b..1d5f2dc3 100644
--- a/logic/logic.sln
+++ b/logic/logic.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.6.33712.159
+VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{D033B809-2FB7-4340-B8B4-DDA30D6CA6FF}"
EndProject
diff --git a/playback/Playback/MessageReader.cs b/playback/Playback/MessageReader.cs
new file mode 100644
index 00000000..71f6616d
--- /dev/null
+++ b/playback/Playback/MessageReader.cs
@@ -0,0 +1,147 @@
+using Google.Protobuf;
+using Protobuf;
+using System;
+using System.IO;
+using System.IO.Compression;
+
+namespace Playback
+{
+ public class FileFormatNotLegalException : Exception
+ {
+ private readonly string fileName;
+ public FileFormatNotLegalException(string fileName)
+ {
+ this.fileName = fileName;
+ }
+ public override string Message => $"The file: " + this.fileName + " is not a legal playback file for THUAI6.";
+ }
+
+ public class MessageReader : IDisposable
+ {
+ private FileStream? fs;
+ private CodedInputStream cos;
+ private GZipStream gzs;
+ private byte[] buffer;
+ public bool Finished { get; private set; } = false;
+
+ public readonly uint teamCount;
+ public readonly uint playerCount;
+
+ const int bufferMaxSize = 10 * 1024 * 1024; // 10M
+
+ public MessageReader(string fileName)
+ {
+ if (!fileName.EndsWith(PlayBackConstant.ExtendedName))
+ {
+ fileName += PlayBackConstant.ExtendedName;
+ }
+
+ fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
+
+ try
+ {
+ var prefixLen = PlayBackConstant.Prefix.Length;
+ byte[] bt = new byte[prefixLen + sizeof(UInt32) * 2];
+ fs.Read(bt, 0, bt.Length);
+ for (int i = 0; i < prefixLen; ++i)
+ {
+ if (bt[i] != PlayBackConstant.Prefix[i]) throw new FileFormatNotLegalException(fileName);
+ }
+
+ teamCount = BitConverter.ToUInt32(bt, prefixLen);
+ playerCount = BitConverter.ToUInt32(bt, prefixLen + sizeof(UInt32));
+ }
+ catch
+ {
+ throw new FileFormatNotLegalException(fileName);
+ }
+
+ gzs = new GZipStream(fs, CompressionMode.Decompress);
+ var tmpBuffer = new byte[bufferMaxSize];
+ var bufferSize = gzs.Read(tmpBuffer);
+ if (bufferSize == 0)
+ {
+ buffer = tmpBuffer;
+ Finished = true;
+ }
+ else if (bufferSize != bufferMaxSize) // 不留空位,防止 CodedInputStream 获取信息错误
+ {
+ if (bufferSize == 0)
+ {
+ Finished = true;
+ }
+ buffer = new byte[bufferSize];
+ Array.Copy(tmpBuffer, buffer, bufferSize);
+ }
+ else
+ {
+ buffer = tmpBuffer;
+ }
+ cos = new CodedInputStream(buffer);
+ }
+
+ public MessageToClient? ReadOne()
+ {
+ beginRead:
+ if (Finished)
+ return null;
+ var pos = cos.Position;
+ try
+ {
+ MessageToClient? msg = new MessageToClient();
+ cos.ReadMessage(msg);
+ return msg;
+ }
+ catch (InvalidProtocolBufferException)
+ {
+ var leftByte = buffer.Length - pos; // 上次读取剩余的字节
+ if (buffer.Length < bufferMaxSize / 2)
+ {
+ var newBuffer = new byte[bufferMaxSize];
+ for (int i = 0; i < leftByte; i++)
+ {
+ newBuffer[i] = buffer[pos + i];
+ }
+ buffer = newBuffer;
+ }
+ else
+ {
+ for (int i = 0; i < leftByte; ++i)
+ {
+ buffer[i] = buffer[pos + i];
+ }
+ }
+ var bufferSize = gzs.Read(buffer, (int)leftByte, (int)(buffer.Length - leftByte)) + leftByte;
+ if (bufferSize == leftByte)
+ {
+ Finished = true;
+ return null;
+ }
+ if (bufferSize != buffer.Length) // 不留空位,防止 CodedInputStream 获取信息错误
+ {
+ var tmpBuffer = new byte[bufferSize];
+ Array.Copy(buffer, tmpBuffer, bufferSize);
+ buffer = tmpBuffer;
+ }
+ cos = new CodedInputStream(buffer);
+ goto beginRead;
+ }
+ }
+
+ public void Dispose()
+ {
+ Finished = true;
+ if (fs == null)
+ return;
+ if (fs.CanRead)
+ {
+ fs.Close();
+ }
+ }
+
+ ~MessageReader()
+ {
+ Dispose();
+ }
+ }
+}
diff --git a/playback/Playback/MessageWriter.cs b/playback/Playback/MessageWriter.cs
new file mode 100644
index 00000000..2d814224
--- /dev/null
+++ b/playback/Playback/MessageWriter.cs
@@ -0,0 +1,75 @@
+using Google.Protobuf;
+using Protobuf;
+using System;
+using System.IO;
+using System.IO.Compression;
+
+namespace Playback
+{
+ public class MessageWriter : IDisposable
+ {
+ private FileStream fs;
+ private CodedOutputStream cos;
+ private MemoryStream ms;
+ private GZipStream gzs;
+ private const int memoryCapacity = 10 * 1024 * 1024; // 10M
+
+ private static void ClearMemoryStream(MemoryStream msToClear)
+ {
+ msToClear.Position = 0;
+ msToClear.SetLength(0);
+ }
+
+ public MessageWriter(string fileName, uint teamCount, uint playerCount)
+ {
+ if (!fileName.EndsWith(PlayBackConstant.ExtendedName))
+ {
+ fileName += PlayBackConstant.ExtendedName;
+ }
+
+ fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
+ fs.Write(PlayBackConstant.Prefix); // 写入前缀
+
+ fs.Write(BitConverter.GetBytes((UInt32)teamCount)); // 写入队伍数
+ fs.Write(BitConverter.GetBytes((UInt32)playerCount)); // 写入每队的玩家人数
+ ms = new MemoryStream(memoryCapacity);
+ cos = new CodedOutputStream(ms);
+ gzs = new GZipStream(fs, CompressionMode.Compress);
+ }
+
+ public void WriteOne(MessageToClient msg)
+ {
+ cos.WriteMessage(msg);
+ if (ms.Length > memoryCapacity)
+ Flush();
+ }
+
+ public void Flush()
+ {
+ if (fs.CanWrite)
+ {
+ cos.Flush();
+ gzs.Write(ms.GetBuffer(), 0, (int)ms.Length);
+ gzs.Flush();
+ ClearMemoryStream(ms);
+ fs.Flush();
+ }
+ }
+
+ public void Dispose()
+ {
+ if (fs.CanWrite)
+ {
+ Flush();
+ cos.Dispose();
+ gzs.Dispose();
+ fs.Dispose();
+ }
+ }
+
+ ~MessageWriter()
+ {
+ Dispose();
+ }
+ }
+}
diff --git a/playback/Playback/PlaybackConstant.cs b/playback/Playback/PlaybackConstant.cs
new file mode 100644
index 00000000..8c34a431
--- /dev/null
+++ b/playback/Playback/PlaybackConstant.cs
@@ -0,0 +1,8 @@
+namespace Playback
+{
+ public static class PlayBackConstant
+ {
+ public static string ExtendedName = ".thuaipb";
+ public static byte[] Prefix = { (byte)'P', (byte)'B', 6, 0 }; // 文件前缀,用于标识文件类型,版本号为6
+ }
+}