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 + } +}