From cd7cfcf72b73a1aa5f1c29c357fb6aadccebaa62 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sat, 30 May 2020 19:16:39 -0400 Subject: [PATCH 01/14] Add missing gitignores for each project --- .gitignore | 16 +- MultiAdmin/.gitignore | 516 +++++++++++++++++++++++++++++++++++++ MultiAdminTests/.gitignore | 516 +++++++++++++++++++++++++++++++++++++ 3 files changed, 1042 insertions(+), 6 deletions(-) create mode 100644 MultiAdmin/.gitignore create mode 100644 MultiAdminTests/.gitignore diff --git a/.gitignore b/.gitignore index 1a269a1..1579cdd 100644 --- a/.gitignore +++ b/.gitignore @@ -47,9 +47,10 @@ Generated\ Files/ [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -# NUNIT +# NUnit *.VisualState.xml TestResult.xml +nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ @@ -72,7 +73,6 @@ StyleCopReport.xml *_p.c *_h.h *.ilk -*.meta *.obj *.iobj *.pch @@ -190,6 +190,8 @@ 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. @@ -265,7 +267,9 @@ ServiceFabricBackup/ *.bim.layout *.bim_*.settings *.rptproj.rsuser -*- Backup*.rdl +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ @@ -447,6 +451,8 @@ test-results/ # .idea/modules.xml # .idea/*.iml # .idea/modules +# *.iml +# *.ipr # CMake cmake-build-*/ @@ -481,12 +487,10 @@ fabric.properties # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser -# JetBrains templates -**___jb_tmp___ - ### Windows ### # Windows thumbnail cache files Thumbs.db +Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db diff --git a/MultiAdmin/.gitignore b/MultiAdmin/.gitignore new file mode 100644 index 0000000..1579cdd --- /dev/null +++ b/MultiAdmin/.gitignore @@ -0,0 +1,516 @@ + +# Created by https://www.gitignore.io/api/git,rider,linux,macos,csharp,windows,monodevelop +# Edit at https://www.gitignore.io/?templates=git,rider,linux,macos,csharp,windows,monodevelop + +### Csharp ### +## 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/master/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/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# 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/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.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 + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# 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 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/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### MonoDevelop ### +#User Specific +*.usertasks + +#Mono Project Files +*.resources +test-results/ + +### Rider ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/git,rider,linux,macos,csharp,windows,monodevelop diff --git a/MultiAdminTests/.gitignore b/MultiAdminTests/.gitignore new file mode 100644 index 0000000..1579cdd --- /dev/null +++ b/MultiAdminTests/.gitignore @@ -0,0 +1,516 @@ + +# Created by https://www.gitignore.io/api/git,rider,linux,macos,csharp,windows,monodevelop +# Edit at https://www.gitignore.io/?templates=git,rider,linux,macos,csharp,windows,monodevelop + +### Csharp ### +## 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/master/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/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# 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/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.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 + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# 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 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/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### MonoDevelop ### +#User Specific +*.usertasks + +#Mono Project Files +*.resources +test-results/ + +### Rider ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/git,rider,linux,macos,csharp,windows,monodevelop From 94d3329912b4840a1592cedc90318c7ae9686971 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sat, 30 May 2020 22:29:33 -0400 Subject: [PATCH 02/14] Add a socket system --- MultiAdmin/MultiAdmin.csproj | 1 + MultiAdmin/ServerIO/ServerSocket.cs | 113 ++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 MultiAdmin/ServerIO/ServerSocket.cs diff --git a/MultiAdmin/MultiAdmin.csproj b/MultiAdmin/MultiAdmin.csproj index f22a2aa..a75739c 100644 --- a/MultiAdmin/MultiAdmin.csproj +++ b/MultiAdmin/MultiAdmin.csproj @@ -84,6 +84,7 @@ + diff --git a/MultiAdmin/ServerIO/ServerSocket.cs b/MultiAdmin/ServerIO/ServerSocket.cs new file mode 100644 index 0000000..b3c80d9 --- /dev/null +++ b/MultiAdmin/ServerIO/ServerSocket.cs @@ -0,0 +1,113 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace MultiAdmin.ServerIO +{ + public class ServerSocket : IDisposable + { + private const int IntBytes = sizeof(int); + public static readonly UTF8Encoding Encoding = new UTF8Encoding(false, true); + + private readonly CancellationTokenSource disposeCancellationSource = new CancellationTokenSource(); + private bool disposed = false; + + private readonly TcpListener listener; + + private TcpClient client; + private NetworkStream networkStream; + + public int Port + { + get + { + return ((IPEndPoint)listener.LocalEndpoint).Port; + } + } + + public event EventHandler OnReceiveMessage; + + // Port 0 automatically assigns a port + public ServerSocket(int port = 0) + { + listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port)); + } + + public void Connect() + { + if (disposed) + throw new ObjectDisposedException(nameof(ServerSocket)); + + listener.Start(); + listener.BeginAcceptTcpClient(result => + { + client = listener.EndAcceptTcpClient(result); + networkStream = client.GetStream(); + + Thread listenerThread = new Thread(MessageListener); + listenerThread.Start(); + }, listener); + } + + public void MessageListener() + { + byte[] intBuffer = new byte[IntBytes]; + while (!disposed) + { + networkStream.ReadAsync(intBuffer, 0, IntBytes, disposeCancellationSource.Token).Wait(); + if (disposed) + break; + + int length = BitConverter.ToInt32(intBuffer, 0); + + byte[] messageBuffer = new byte[length]; + networkStream.ReadAsync(messageBuffer, 0, length, disposeCancellationSource.Token).Wait(); + if (disposed) + break; + + string message = Encoding.GetString(messageBuffer, 0, length); + + OnReceiveMessage?.Invoke(this, message); + } + } + + public void SendMessage(string message) + { + if (disposed) + throw new ObjectDisposedException(nameof(ServerSocket)); + + if (networkStream == null) + throw new NullReferenceException($"{nameof(networkStream)} hasn't been initialized"); + + byte[] messageBuffer = new byte[Encoding.GetMaxByteCount(message.Length) + IntBytes]; + + int actualMessageLength = Encoding.GetBytes(message, 0, message.Length, messageBuffer, IntBytes); + Array.Copy(BitConverter.GetBytes(actualMessageLength), messageBuffer, IntBytes); + + networkStream.WriteAsync(messageBuffer, 0, actualMessageLength + IntBytes, disposeCancellationSource.Token).Wait(); + } + + public void Disconnect() + { + Dispose(); + } + + public void Dispose() + { + if (disposed) + return; + + disposed = true; + disposeCancellationSource.Cancel(); + disposeCancellationSource.Dispose(); + + networkStream?.Close(); + client?.Close(); + listener.Stop(); + + OnReceiveMessage = null; + } + } +} From f95090ebddfd1119470d0da35c71e863f108bf43 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sat, 30 May 2020 23:48:28 -0400 Subject: [PATCH 03/14] Change file message system to use sockets --- MultiAdmin/Config/MultiAdminConfig.cs | 2 +- MultiAdmin/Features/TitleBar.cs | 4 +- MultiAdmin/Server.cs | 141 +++-------------------- MultiAdmin/ServerIO/InputHandler.cs | 3 +- MultiAdmin/ServerIO/OutputHandler.cs | 157 +++++--------------------- MultiAdmin/ServerIO/ServerSocket.cs | 10 +- 6 files changed, 59 insertions(+), 258 deletions(-) diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs index a1073a0..14364b7 100644 --- a/MultiAdmin/Config/MultiAdminConfig.cs +++ b/MultiAdmin/Config/MultiAdminConfig.cs @@ -39,7 +39,7 @@ public class MultiAdminConfig : InheritableConfigRegister "MultiAdmin Debug Logging", "Enables MultiAdmin debug logging, this logs to a separate file than any other logs"); public ConfigEntry DebugLogBlacklist { get; } = - new ConfigEntry("multiadmin_debug_log_blacklist", new string[] {nameof(OutputHandler.ProcessFile), nameof(Utils.StringMatches)}, + new ConfigEntry("multiadmin_debug_log_blacklist", new string[] {nameof(OutputHandler.HandleMessage), nameof(Utils.StringMatches)}, "MultiAdmin Debug Logging Blacklist", "Which tags to block for MultiAdmin debug logging"); public ConfigEntry DebugLogWhitelist { get; } = diff --git a/MultiAdmin/Features/TitleBar.cs b/MultiAdmin/Features/TitleBar.cs index a73fcc2..45ea3d5 100644 --- a/MultiAdmin/Features/TitleBar.cs +++ b/MultiAdmin/Features/TitleBar.cs @@ -80,9 +80,9 @@ private void UpdateTitlebar() titleBar.Add($"Config: {Server.serverId}"); } - if (!string.IsNullOrEmpty(Server.SessionId)) + if (Server.SessionSocket != null) { - titleBar.Add($"Session: {Server.SessionId}"); + titleBar.Add($"Console Port: {Server.SessionSocket.Port}"); } if (Server.IsGameProcessRunning) diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs index ce095cf..e465ebc 100644 --- a/MultiAdmin/Server.cs +++ b/MultiAdmin/Server.cs @@ -36,8 +36,6 @@ public class Server public string serverModBuild; public string serverModVersion; - private int logId; - private DateTime initStopTimeoutTime; private DateTime initRestartTimeoutTime; @@ -154,22 +152,7 @@ public bool IsGameProcessRunning public static readonly string DedicatedDir = Utils.GetFullPathSafe(Path.Combine("SCPSL_Data", "Dedicated")); - private string sessionId; - - public string SessionId - { - get => sessionId; - - private set - { - sessionId = value; - - // Update related variables - SessionDirectory = string.IsNullOrEmpty(value) ? null : Path.Combine(DedicatedDir, value); - } - } - - public string SessionDirectory { get; private set; } + public ServerSocket SessionSocket { get; private set; } #region Server Core @@ -215,25 +198,13 @@ private void MainLoop() /// public void SendMessage(string message) { - if (!Directory.Exists(SessionDirectory)) - { - Write($"Send Message error: Sending {message} failed. \"{SessionDirectory}\" does not exist!\nSkipping..."); - return; - } - - string file = Path.Combine(SessionDirectory, $"cs{logId}.mapi"); - if (File.Exists(file)) + if (SessionSocket == null || !SessionSocket.Connected) { - Write($"Send Message error: Sending {message} failed. \"{file}\" already exists!\nSkipping..."); - logId++; + Write("Unable to send command to server, the console is disconnected", ConsoleColor.Red); return; } - StreamWriter streamWriter = new StreamWriter(file); - logId++; - streamWriter.WriteLine(message + "terminator"); - streamWriter.Close(); - Write("Sending request to SCP: Secret Laboratory...", ConsoleColor.White); + SessionSocket.SendMessage(message); } #endregion @@ -283,7 +254,6 @@ public void StartServer(bool restartOnCrash = true) Status = ServerStatus.Starting; IsLoading = true; - SessionId = DateTime.Now.Ticks.ToString(); StartDateTime = Utils.DateTime; try @@ -297,9 +267,6 @@ public void StartServer(bool restartOnCrash = true) // Reload the config immediately as server is starting ReloadConfig(); - // Create session directory - PrepareSession(); - // Init features InitFeatures(); @@ -307,14 +274,17 @@ public void StartServer(bool restartOnCrash = true) Write($"Executing \"{scpslExe}\"...", ConsoleColor.DarkGreen); + ServerSocket consoleSocket = new ServerSocket(); + consoleSocket.Connect(); + List scpslArgs = new List { "-batchmode", "-nographics", "-silent-crashes", "-nodedicateddelete", - $"-key{SessionId}", $"-id{Process.GetCurrentProcess().Id}", + $"-console{consoleSocket.Port}", $"-port{port ?? ServerConfig.Port.Value}" }; @@ -368,13 +338,17 @@ public void StartServer(bool restartOnCrash = true) ForEachHandler(eventPreStart => eventPreStart.OnServerPreStart()); // Start the input reader - Thread inputHandlerThread = new Thread(() => InputHandler.Write(this)); + Thread inputHandlerThread = null; if (!Program.Headless) + { + inputHandlerThread = new Thread(() => InputHandler.Write(this)); inputHandlerThread.Start(); + } // Start the output reader OutputHandler outputHandler = new OutputHandler(this); + consoleSocket.OnReceiveMessage += outputHandler.HandleMessage; // Finally, start the game GameProcess = Process.Start(startInfo); @@ -412,16 +386,14 @@ public void StartServer(bool restartOnCrash = true) GameProcess = null; // Stop the input handler if it's running - if (inputHandlerThread.IsAlive) + if (inputHandlerThread != null && inputHandlerThread.IsAlive) { inputHandlerThread.Abort(); } - outputHandler.Dispose(); - - DeleteSession(); + consoleSocket.Disconnect(); - SessionId = null; + SessionSocket = null; StartDateTime = null; if (shouldRestart) Write("Restarting server..."); @@ -452,10 +424,6 @@ public void StartServer(bool restartOnCrash = true) Write("Startup failed! Exiting...", ConsoleColor.Red); } } - finally - { - DeleteSession(); - } } while (shouldRestart); } @@ -578,83 +546,6 @@ public void ForEachHandler(Action action) where T : IMAEvent #endregion - #region Session Directory Management - - public void PrepareSession() - { - try - { - Directory.CreateDirectory(SessionDirectory); - Write($"Started new session \"{SessionId}\"", ConsoleColor.DarkGreen); - } - catch (Exception e) - { - throw new UnauthorizedAccessException($"Unable to create directory \"{SessionDirectory}\", make sure that {nameof(MultiAdmin)} has access to \"{DedicatedDir}\"\n{e}"); - } - } - - public void CleanSession() - { - if (!Directory.Exists(SessionDirectory)) return; - - foreach (string file in Directory.GetFiles(SessionDirectory)) - { - for (int i = 0; i < 20; i++) - { - try - { - File.Delete(file); - break; - } - catch (UnauthorizedAccessException e) - { - Program.LogDebugException(nameof(CleanSession), e); - Thread.Sleep(8); - } - catch (Exception e) - { - Program.LogDebugException(nameof(CleanSession), e); - Thread.Sleep(5); - } - } - } - } - - public void DeleteSession() - { - try - { - CleanSession(); - - if (!Directory.Exists(SessionDirectory)) return; - - for (int i = 0; i < 20; i++) - { - try - { - Directory.Delete(SessionDirectory); - break; - } - catch (UnauthorizedAccessException e) - { - Program.LogDebugException(nameof(DeleteSession), e); - Thread.Sleep(8); - } - catch (Exception e) - { - Program.LogDebugException(nameof(DeleteSession), e); - Thread.Sleep(5); - } - } - } - catch (Exception e) - { - Program.LogDebugException(nameof(DeleteSession), e); - } - } - - #endregion - #region Console Output and Logging public void Write(ColoredMessage[] messages, ConsoleColor? timeStampColor = null) diff --git a/MultiAdmin/ServerIO/InputHandler.cs b/MultiAdmin/ServerIO/InputHandler.cs index 7153ca3..2a04b0c 100644 --- a/MultiAdmin/ServerIO/InputHandler.cs +++ b/MultiAdmin/ServerIO/InputHandler.cs @@ -55,8 +55,7 @@ public static void Write(Server server) { if (Program.Headless) { - Thread.Sleep(5000); - continue; + break; } string message = server.ServerConfig.UseNewInputSystem.Value ? GetInputLineNew(server, prevMessages) : Console.ReadLine(); diff --git a/MultiAdmin/ServerIO/OutputHandler.cs b/MultiAdmin/ServerIO/OutputHandler.cs index ef39f64..2aa3a4c 100644 --- a/MultiAdmin/ServerIO/OutputHandler.cs +++ b/MultiAdmin/ServerIO/OutputHandler.cs @@ -7,134 +7,43 @@ namespace MultiAdmin.ServerIO { - public class OutputHandler : IDisposable + public class OutputHandler { public static readonly Regex SmodRegex = new Regex(@"\[(DEBUG|INFO|WARN|ERROR)\] (\[.*?\]) (.*)", RegexOptions.Compiled | RegexOptions.Singleline); - private readonly FileSystemWatcher fsWatcher; private bool fixBuggedPlayers; - public static ConsoleColor MapConsoleColor(string color, ConsoleColor def = ConsoleColor.Cyan) - { - try - { - return (ConsoleColor)Enum.Parse(typeof(ConsoleColor), color); - } - catch (Exception e) - { - Program.LogDebugException(nameof(MapConsoleColor), e); - return def; - } - } + private readonly Server server; public OutputHandler(Server server) { - if (server == null) - { - Program.Write("Error in OutputHandler - Server server is null!", ConsoleColor.Red); - return; - } - - if (string.IsNullOrEmpty(server.SessionDirectory)) - { - server.Write($"Missing session directory! Output is not being watched... (SessionDirectory = \"{server.SessionDirectory ?? "null"}\" SessionId = \"{server.SessionId ?? "null"}\" DedicatedDir = \"{Server.DedicatedDir ?? "null"}\")", ConsoleColor.Red); - return; - } - - fsWatcher = new FileSystemWatcher {Path = server.SessionDirectory}; - - fsWatcher.Created += (sender, eventArgs) => OnMapiCreated(eventArgs, server); - fsWatcher.Filter = "sl*.mapi"; - fsWatcher.EnableRaisingEvents = true; - } - - /* Old Windows MAPI Watching Code - private void OnDirectoryChanged(FileSystemEventArgs e, Server server) - { - if (!Directory.Exists(e.FullPath)) return; - - string[] files = Directory.GetFiles(e.FullPath, "sl*.mapi", SearchOption.TopDirectoryOnly).OrderBy(f => f) - .ToArray(); - foreach (string file in files) ProcessFile(server, file); + this.server = server; } - */ - private void OnMapiCreated(FileSystemEventArgs e, Server server) + public static ConsoleColor MapConsoleColor(string color, ConsoleColor def = ConsoleColor.Cyan) { - if (!File.Exists(e.FullPath)) return; - try { - ProcessFile(server, e.FullPath); + return (ConsoleColor)Enum.Parse(typeof(ConsoleColor), color); } - catch (Exception ex) + catch (Exception e) { - Program.LogDebugException(nameof(OnMapiCreated), ex); + Program.LogDebugException(nameof(MapConsoleColor), e); + return def; } } - public void ProcessFile(Server server, string file) + public void HandleMessage(object source, string message) { - string stream = string.Empty; - string command = "open"; - - bool isRead = false; - - // Lock this object to wait for this event to finish before trying to read another file - lock (this) - { - for (int attempts = 0; attempts < server.ServerConfig.OutputReadAttempts.Value; attempts++) - { - try - { - if (!File.Exists(file)) return; - - // Lock the file to prevent it from being modified further, or read by another instance - using (StreamReader sr = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))) - { - command = "read"; - stream = sr.ReadToEnd(); - - isRead = true; - } - - command = "delete"; - File.Delete(file); - - break; - } - catch (UnauthorizedAccessException e) - { - Program.LogDebugException(nameof(ProcessFile), e); - Thread.Sleep(8); - } - catch (Exception e) - { - Program.LogDebugException(nameof(ProcessFile), e); - Thread.Sleep(5); - } - } - } - - if (!isRead) - { - server.Write($"Message printer warning: Could not {command} \"{file}\". Make sure that {nameof(MultiAdmin)} has all necessary read-write permissions\nSkipping..."); - - return; - } - bool display = true; ConsoleColor color = ConsoleColor.Cyan; - if (stream.EndsWith(Environment.NewLine)) - stream = stream.Substring(0, stream.Length - Environment.NewLine.Length); - - int logTypeIndex = stream.IndexOf("LOGTYPE"); + int logTypeIndex = message.IndexOf("LOGTYPE"); if (logTypeIndex >= 0) { - string type = stream.Substring(logTypeIndex).Trim(); - stream = stream.Substring(0, logTypeIndex).Trim(); + string type = message.Substring(logTypeIndex).Trim(); + message = message.Substring(0, logTypeIndex).Trim(); switch (type) { @@ -154,7 +63,7 @@ public void ProcessFile(Server server, string file) } // Smod2 loggers pretty printing - Match match = SmodRegex.Match(stream); + Match match = SmodRegex.Match(message); if (match.Success) { if (match.Groups.Count >= 3) @@ -197,14 +106,14 @@ public void ProcessFile(Server server, string file) } } - if (stream.Contains("Mod Log:")) - server.ForEachHandler(adminAction => adminAction.OnAdminAction(stream.Replace("Mod Log:", string.Empty))); + if (message.Contains("Mod Log:")) + server.ForEachHandler(adminAction => adminAction.OnAdminAction(message.Replace("Mod Log:", string.Empty))); - if (stream.Contains("ServerMod - Version")) + if (message.Contains("ServerMod - Version")) { server.hasServerMod = true; // This should work fine with older ServerMod versions too - string[] streamSplit = stream.Replace("ServerMod - Version", string.Empty).Split('-'); + string[] streamSplit = message.Replace("ServerMod - Version", string.Empty).Split('-'); if (!streamSplit.IsEmpty()) { @@ -213,10 +122,10 @@ public void ProcessFile(Server server, string file) } } - if (stream.Contains("Round restarting")) + if (message.Contains("Round restarting")) server.ForEachHandler(roundEnd => roundEnd.OnRoundEnd()); - if (stream.Contains("Waiting for players")) + if (message.Contains("Waiting for players")) { server.IsLoading = false; @@ -229,51 +138,45 @@ public void ProcessFile(Server server, string file) } } - if (stream.Contains("New round has been started")) + if (message.Contains("New round has been started")) server.ForEachHandler(roundStart => roundStart.OnRoundStart()); - if (stream.Contains("Level loaded. Creating match...")) + if (message.Contains("Level loaded. Creating match...")) server.ForEachHandler(serverStart => serverStart.OnServerStart()); - if (stream.Contains("Server full")) + if (message.Contains("Server full")) server.ForEachHandler(serverFull => serverFull.OnServerFull()); - if (stream.Contains("Player connect")) + if (message.Contains("Player connect")) { display = false; server.Log("Player connect event"); - int index = stream.IndexOf(":"); + int index = message.IndexOf(":"); if (index >= 0) { - string name = stream.Substring(index); + string name = message.Substring(index); server.ForEachHandler(playerConnect => playerConnect.OnPlayerConnect(name)); } } - if (stream.Contains("Player disconnect")) + if (message.Contains("Player disconnect")) { display = false; server.Log("Player disconnect event"); - int index = stream.IndexOf(":"); + int index = message.IndexOf(":"); if (index >= 0) { - string name = stream.Substring(index); + string name = message.Substring(index); server.ForEachHandler(playerDisconnect => playerDisconnect.OnPlayerDisconnect(name)); } } - if (stream.Contains("Player has connected before load is complete")) + if (message.Contains("Player has connected before load is complete")) fixBuggedPlayers = true; - if (display) server.Write(stream, color); - } - - public void Dispose() - { - fsWatcher?.Dispose(); - GC.SuppressFinalize(this); + if (display) server.Write(message, color); } } } diff --git a/MultiAdmin/ServerIO/ServerSocket.cs b/MultiAdmin/ServerIO/ServerSocket.cs index b3c80d9..f7bdf2b 100644 --- a/MultiAdmin/ServerIO/ServerSocket.cs +++ b/MultiAdmin/ServerIO/ServerSocket.cs @@ -19,6 +19,8 @@ public class ServerSocket : IDisposable private TcpClient client; private NetworkStream networkStream; + public event EventHandler OnReceiveMessage; + public int Port { get @@ -27,7 +29,13 @@ public int Port } } - public event EventHandler OnReceiveMessage; + public bool Connected + { + get + { + return client.Connected; + } + } // Port 0 automatically assigns a port public ServerSocket(int port = 0) From 51a9e79d2de295fb72e546754090e640e5a615cf Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sun, 31 May 2020 00:22:26 -0400 Subject: [PATCH 04/14] Remove someone's build path from debug config --- MultiAdmin/MultiAdmin.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MultiAdmin/MultiAdmin.csproj b/MultiAdmin/MultiAdmin.csproj index a75739c..3fb1dea 100644 --- a/MultiAdmin/MultiAdmin.csproj +++ b/MultiAdmin/MultiAdmin.csproj @@ -31,7 +31,7 @@ true full false - E:\SteamLibrary\steamapps\common\SCP Secret Laboratory2\ + bin\Debug\ DEBUG;TRACE prompt 4 From ddefadac80513c3c070d92c71bf96cf756a792f8 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sun, 31 May 2020 01:44:54 -0400 Subject: [PATCH 05/14] Small fixes & output color parsing - Add color parsing - Fix the session socket not being assigned - No longer clone when timestamping a message - Update feature descriptions - Update README --- MultiAdmin/Config/MultiAdminConfig.cs | 2 +- MultiAdmin/Features/GithubGenerator.cs | 7 +- MultiAdmin/Features/InactivityShutdown.cs | 2 +- MultiAdmin/Features/RestartNextRound.cs | 9 +- MultiAdmin/Features/RestartRoundCounter.cs | 2 +- MultiAdmin/Features/StopNextRound.cs | 9 +- MultiAdmin/Features/TitleBar.cs | 2 +- MultiAdmin/Server.cs | 7 +- MultiAdmin/ServerIO/OutputHandler.cs | 213 ++++++++++----------- MultiAdmin/ServerIO/ServerSocket.cs | 29 ++- MultiAdmin/Utility/Utils.cs | 18 +- README.md | 21 +- 12 files changed, 169 insertions(+), 152 deletions(-) diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs index 14364b7..5624ca5 100644 --- a/MultiAdmin/Config/MultiAdminConfig.cs +++ b/MultiAdmin/Config/MultiAdminConfig.cs @@ -39,7 +39,7 @@ public class MultiAdminConfig : InheritableConfigRegister "MultiAdmin Debug Logging", "Enables MultiAdmin debug logging, this logs to a separate file than any other logs"); public ConfigEntry DebugLogBlacklist { get; } = - new ConfigEntry("multiadmin_debug_log_blacklist", new string[] {nameof(OutputHandler.HandleMessage), nameof(Utils.StringMatches)}, + new ConfigEntry("multiadmin_debug_log_blacklist", new string[] {nameof(OutputHandler.HandleMessage), nameof(Utils.StringMatches), nameof(ServerSocket.MessageListener) }, "MultiAdmin Debug Logging Blacklist", "Which tags to block for MultiAdmin debug logging"); public ConfigEntry DebugLogWhitelist { get; } = diff --git a/MultiAdmin/Features/GithubGenerator.cs b/MultiAdmin/Features/GithubGenerator.cs index 1b2963f..f272829 100644 --- a/MultiAdmin/Features/GithubGenerator.cs +++ b/MultiAdmin/Features/GithubGenerator.cs @@ -25,7 +25,7 @@ public string GetCommand() public string GetCommandDescription() { - return "Generates a github .md file outlining all the features/commands"; + return "Generates a GitHub README file outlining all the features/commands"; } public string GetUsage() @@ -41,7 +41,7 @@ public void OnCall(string[] args) return; } - string dir = string.Join(" ", args); + string path = Utils.GetFullPathSafe(string.Join(" ", args)); List lines = new List {"# MultiAdmin", string.Empty, "## Features", string.Empty}; @@ -132,7 +132,8 @@ public void OnCall(string[] args) lines.Add(stringBuilder.ToString()); } - File.WriteAllLines(dir, lines); + File.WriteAllLines(path, lines); + Server.Write($"GitHub README written to \"{path}\""); } public bool PassToGame() diff --git a/MultiAdmin/Features/InactivityShutdown.cs b/MultiAdmin/Features/InactivityShutdown.cs index 3d2ffbd..3c89a84 100644 --- a/MultiAdmin/Features/InactivityShutdown.cs +++ b/MultiAdmin/Features/InactivityShutdown.cs @@ -52,7 +52,7 @@ public override void OnConfigReload() public override string GetFeatureDescription() { - return "Stops the server after a period inactivity"; + return "Stops the server after a period inactivity [Requires ServerMod]"; } public override string GetFeatureName() diff --git a/MultiAdmin/Features/RestartNextRound.cs b/MultiAdmin/Features/RestartNextRound.cs index d7ba564..6587c7a 100644 --- a/MultiAdmin/Features/RestartNextRound.cs +++ b/MultiAdmin/Features/RestartNextRound.cs @@ -13,7 +13,7 @@ public RestartNextRound(Server server) : base(server) public string GetCommandDescription() { - return "Restarts the server at the end of this round"; + return "Restarts the server at the end of this round [Requires ServerMod]"; } @@ -51,14 +51,9 @@ public override void Init() restart = false; } - public bool RequiresServerMod() - { - return false; - } - public override string GetFeatureDescription() { - return "Restarts the server after the current round ends"; + return "Restarts the server after the current round ends [Requires ServerMod]"; } public override string GetFeatureName() diff --git a/MultiAdmin/Features/RestartRoundCounter.cs b/MultiAdmin/Features/RestartRoundCounter.cs index 4ba6a4c..e1b9c2b 100644 --- a/MultiAdmin/Features/RestartRoundCounter.cs +++ b/MultiAdmin/Features/RestartRoundCounter.cs @@ -45,7 +45,7 @@ public override void OnConfigReload() public override string GetFeatureDescription() { - return "Restarts the server after a number rounds completed"; + return "Restarts the server after a number rounds completed [Requires ServerMod]"; } public override string GetFeatureName() diff --git a/MultiAdmin/Features/StopNextRound.cs b/MultiAdmin/Features/StopNextRound.cs index 3cffdc6..8bbcc36 100644 --- a/MultiAdmin/Features/StopNextRound.cs +++ b/MultiAdmin/Features/StopNextRound.cs @@ -14,7 +14,7 @@ public StopNextRound(Server server) : base(server) public string GetCommandDescription() { - return "Stops the server at the end of this round"; + return "Stops the server at the end of this round [Requires ServerMod]"; } public void OnCall(string[] args) @@ -55,14 +55,9 @@ public override void OnConfigReload() { } - public bool RequiresServerMod() - { - return false; - } - public override string GetFeatureDescription() { - return "Stops the server after the current round ends"; + return "Stops the server after the current round ends [Requires ServerMod]"; } public override string GetFeatureName() diff --git a/MultiAdmin/Features/TitleBar.cs b/MultiAdmin/Features/TitleBar.cs index 45ea3d5..3b42c12 100644 --- a/MultiAdmin/Features/TitleBar.cs +++ b/MultiAdmin/Features/TitleBar.cs @@ -47,7 +47,7 @@ public void OnServerStart() public override string GetFeatureDescription() { return - "Updates the title bar with instance based information, such as session id and player count (Requires ServerMod to function fully)"; + "Updates the title bar with instance based information, such as console port and player count [Requires ServerMod to function fully]"; } public override string GetFeatureName() diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs index e465ebc..27b712d 100644 --- a/MultiAdmin/Server.cs +++ b/MultiAdmin/Server.cs @@ -161,8 +161,7 @@ private void MainLoop() Stopwatch timer = new Stopwatch(); while (IsGameProcessRunning) { - timer.Reset(); - timer.Start(); + timer.Restart(); foreach (IEventTick tickEvent in tick) tickEvent.OnTick(); @@ -274,9 +273,13 @@ public void StartServer(bool restartOnCrash = true) Write($"Executing \"{scpslExe}\"...", ConsoleColor.DarkGreen); + // Start the console socket connection to the game server ServerSocket consoleSocket = new ServerSocket(); + // Start the connection before the game to find an open port for communication consoleSocket.Connect(); + SessionSocket = consoleSocket; + List scpslArgs = new List { "-batchmode", diff --git a/MultiAdmin/ServerIO/OutputHandler.cs b/MultiAdmin/ServerIO/OutputHandler.cs index 2aa3a4c..bd798b1 100644 --- a/MultiAdmin/ServerIO/OutputHandler.cs +++ b/MultiAdmin/ServerIO/OutputHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Text.RegularExpressions; using System.Threading; @@ -36,145 +37,139 @@ public static ConsoleColor MapConsoleColor(string color, ConsoleColor def = Cons public void HandleMessage(object source, string message) { + if (message == null) + return; + bool display = true; ConsoleColor color = ConsoleColor.Cyan; - int logTypeIndex = message.IndexOf("LOGTYPE"); - if (logTypeIndex >= 0) + if (message.Length > 0) { - string type = message.Substring(logTypeIndex).Trim(); - message = message.Substring(0, logTypeIndex).Trim(); - - switch (type) + if (byte.TryParse(Convert.ToString(message[0]), NumberStyles.HexNumber, NumberFormatInfo.CurrentInfo, out byte consoleColor)) { - case "LOGTYPE02": - color = ConsoleColor.Green; - break; - case "LOGTYPE-8": - color = ConsoleColor.DarkRed; - break; - case "LOGTYPE14": - color = ConsoleColor.Magenta; - break; - default: - color = ConsoleColor.Cyan; - break; + color = (ConsoleColor)consoleColor; + message = message.Substring(1); } - } - // Smod2 loggers pretty printing - Match match = SmodRegex.Match(message); - if (match.Success) - { - if (match.Groups.Count >= 3) + // Smod2 loggers pretty printing + Match match = SmodRegex.Match(message); + if (match.Success) { - ConsoleColor levelColor = ConsoleColor.Cyan; - ConsoleColor tagColor = ConsoleColor.Yellow; - ConsoleColor msgColor = ConsoleColor.White; - switch (match.Groups[1].Value.Trim()) + if (match.Groups.Count >= 3) { - case "DEBUG": - levelColor = ConsoleColor.Gray; - break; - case "INFO": - levelColor = ConsoleColor.Green; - break; - case "WARN": - levelColor = ConsoleColor.DarkYellow; - break; - case "ERROR": - levelColor = ConsoleColor.Red; - msgColor = ConsoleColor.Red; - break; - default: - color = ConsoleColor.Cyan; - break; + ConsoleColor levelColor = ConsoleColor.Cyan; + ConsoleColor tagColor = ConsoleColor.Yellow; + ConsoleColor msgColor = ConsoleColor.White; + switch (match.Groups[1].Value.Trim()) + { + case "DEBUG": + levelColor = ConsoleColor.Gray; + break; + case "INFO": + levelColor = ConsoleColor.Green; + break; + case "WARN": + levelColor = ConsoleColor.DarkYellow; + break; + case "ERROR": + levelColor = ConsoleColor.Red; + msgColor = ConsoleColor.Red; + break; + default: + color = ConsoleColor.Cyan; + break; + } + + server.Write( + new ColoredMessage[] + { + new ColoredMessage($"[{match.Groups[1].Value}] ", levelColor), + new ColoredMessage($"{match.Groups[2].Value} ", tagColor), + new ColoredMessage(match.Groups[3].Value, msgColor) + }, ConsoleColor.Cyan); + + // P.S. the format is [Info] [courtney.exampleplugin] Something interesting happened + // That was just an example + + // This return should be here + return; } - - server.Write(new ColoredMessage[] - { - new ColoredMessage($"[{match.Groups[1].Value}] ", levelColor), - new ColoredMessage($"{match.Groups[2].Value} ", tagColor), - new ColoredMessage(match.Groups[3].Value, msgColor) - }, ConsoleColor.Cyan); - - // P.S. the format is [Info] [courtney.exampleplugin] Something interesting happened - // That was just an example - - // This return should be here - return; } - } - if (message.Contains("Mod Log:")) - server.ForEachHandler(adminAction => adminAction.OnAdminAction(message.Replace("Mod Log:", string.Empty))); + if (message.Contains("Mod Log:")) + server.ForEachHandler(adminAction => + adminAction.OnAdminAction(message.Replace("Mod Log:", string.Empty))); - if (message.Contains("ServerMod - Version")) - { - server.hasServerMod = true; - // This should work fine with older ServerMod versions too - string[] streamSplit = message.Replace("ServerMod - Version", string.Empty).Split('-'); - - if (!streamSplit.IsEmpty()) + if (message.Contains("ServerMod - Version")) { - server.serverModVersion = streamSplit[0].Trim(); - server.serverModBuild = (streamSplit.Length > 1 ? streamSplit[1] : "A").Trim(); + server.hasServerMod = true; + // This should work fine with older ServerMod versions too + string[] streamSplit = message.Replace("ServerMod - Version", string.Empty).Split('-'); + + if (!streamSplit.IsEmpty()) + { + server.serverModVersion = streamSplit[0].Trim(); + server.serverModBuild = (streamSplit.Length > 1 ? streamSplit[1] : "A").Trim(); + } } - } - if (message.Contains("Round restarting")) - server.ForEachHandler(roundEnd => roundEnd.OnRoundEnd()); + if (message.Contains("Round restarting")) + server.ForEachHandler(roundEnd => roundEnd.OnRoundEnd()); - if (message.Contains("Waiting for players")) - { - server.IsLoading = false; + if (message.Contains("Waiting for players")) + { + server.IsLoading = false; - server.ForEachHandler(waitingForPlayers => waitingForPlayers.OnWaitingForPlayers()); + server.ForEachHandler(waitingForPlayers => + waitingForPlayers.OnWaitingForPlayers()); - if (fixBuggedPlayers) - { - server.SendMessage("ROUNDRESTART"); - fixBuggedPlayers = false; + if (fixBuggedPlayers) + { + server.SendMessage("ROUNDRESTART"); + fixBuggedPlayers = false; + } } - } - if (message.Contains("New round has been started")) - server.ForEachHandler(roundStart => roundStart.OnRoundStart()); + if (message.Contains("New round has been started")) + server.ForEachHandler(roundStart => roundStart.OnRoundStart()); - if (message.Contains("Level loaded. Creating match...")) - server.ForEachHandler(serverStart => serverStart.OnServerStart()); + if (message.Contains("Level loaded. Creating match...")) + server.ForEachHandler(serverStart => serverStart.OnServerStart()); - if (message.Contains("Server full")) - server.ForEachHandler(serverFull => serverFull.OnServerFull()); + if (message.Contains("Server full")) + server.ForEachHandler(serverFull => serverFull.OnServerFull()); - if (message.Contains("Player connect")) - { - display = false; - server.Log("Player connect event"); - - int index = message.IndexOf(":"); - if (index >= 0) + if (message.Contains("Player connect")) { - string name = message.Substring(index); - server.ForEachHandler(playerConnect => playerConnect.OnPlayerConnect(name)); - } - } + display = false; + server.Log("Player connect event"); - if (message.Contains("Player disconnect")) - { - display = false; - server.Log("Player disconnect event"); + int index = message.IndexOf(":"); + if (index >= 0) + { + string name = message.Substring(index); + server.ForEachHandler(playerConnect => + playerConnect.OnPlayerConnect(name)); + } + } - int index = message.IndexOf(":"); - if (index >= 0) + if (message.Contains("Player disconnect")) { - string name = message.Substring(index); - server.ForEachHandler(playerDisconnect => playerDisconnect.OnPlayerDisconnect(name)); + display = false; + server.Log("Player disconnect event"); + + int index = message.IndexOf(":"); + if (index >= 0) + { + string name = message.Substring(index); + server.ForEachHandler(playerDisconnect => + playerDisconnect.OnPlayerDisconnect(name)); + } } - } - if (message.Contains("Player has connected before load is complete")) - fixBuggedPlayers = true; + if (message.Contains("Player has connected before load is complete")) + fixBuggedPlayers = true; + } if (display) server.Write(message, color); } diff --git a/MultiAdmin/ServerIO/ServerSocket.cs b/MultiAdmin/ServerIO/ServerSocket.cs index f7bdf2b..4db83a3 100644 --- a/MultiAdmin/ServerIO/ServerSocket.cs +++ b/MultiAdmin/ServerIO/ServerSocket.cs @@ -64,14 +64,30 @@ public void MessageListener() byte[] intBuffer = new byte[IntBytes]; while (!disposed) { - networkStream.ReadAsync(intBuffer, 0, IntBytes, disposeCancellationSource.Token).Wait(); + try + { + networkStream.ReadAsync(intBuffer, 0, IntBytes, disposeCancellationSource.Token).Wait(); + } + catch (Exception e) + { + Program.LogDebugException(nameof(MessageListener), e); + continue; + } if (disposed) break; int length = BitConverter.ToInt32(intBuffer, 0); byte[] messageBuffer = new byte[length]; - networkStream.ReadAsync(messageBuffer, 0, length, disposeCancellationSource.Token).Wait(); + try + { + networkStream.ReadAsync(messageBuffer, 0, length, disposeCancellationSource.Token).Wait(); + } + catch (Exception e) + { + Program.LogDebugException(nameof(MessageListener), e); + continue; + } if (disposed) break; @@ -94,7 +110,14 @@ public void SendMessage(string message) int actualMessageLength = Encoding.GetBytes(message, 0, message.Length, messageBuffer, IntBytes); Array.Copy(BitConverter.GetBytes(actualMessageLength), messageBuffer, IntBytes); - networkStream.WriteAsync(messageBuffer, 0, actualMessageLength + IntBytes, disposeCancellationSource.Token).Wait(); + try + { + networkStream.WriteAsync(messageBuffer, 0, actualMessageLength + IntBytes, disposeCancellationSource.Token).Wait(); + } + catch (Exception e) + { + Program.LogDebugException(nameof(SendMessage), e); + } } public void Disconnect() diff --git a/MultiAdmin/Utility/Utils.cs b/MultiAdmin/Utility/Utils.cs index b4ef227..3eababf 100644 --- a/MultiAdmin/Utility/Utils.cs +++ b/MultiAdmin/Utility/Utils.cs @@ -31,22 +31,30 @@ public static string TimeStampMessage(string message) return string.IsNullOrEmpty(message) ? message : $"{TimeStamp} {message}"; } - public static ColoredMessage[] TimeStampMessage(ColoredMessage[] message, ConsoleColor? color = null) + public static ColoredMessage[] TimeStampMessage(ColoredMessage[] message, ConsoleColor? color = null, bool cloneMessages = false) { if (message == null) return null; ColoredMessage[] newMessage = new ColoredMessage[message.Length + 1]; newMessage[0] = new ColoredMessage($"{TimeStamp} ", color); - for (int i = 0; i < message.Length; i++) - newMessage[i + 1] = message[i]?.Clone(); + if (cloneMessages) + { + for (int i = 0; i < message.Length; i++) + newMessage[i + 1] = message[i]?.Clone(); + } + else + { + for (int i = 0; i < message.Length; i++) + newMessage[i + 1] = message[i]; + } return newMessage; } - public static ColoredMessage[] TimeStampMessage(ColoredMessage message, ConsoleColor? color = null) + public static ColoredMessage[] TimeStampMessage(ColoredMessage message, ConsoleColor? color = null, bool cloneMessages = false) { - return TimeStampMessage(new ColoredMessage[] {message}, color); + return TimeStampMessage(new ColoredMessage[] {message}, color, cloneMessages); } public static string GetFullPathSafe(string path) diff --git a/README.md b/README.md index dbc3797..ee4b8ee 100644 --- a/README.md +++ b/README.md @@ -27,29 +27,29 @@ Make sure that you are running Mono 5.18.0 or higher, otherwise you might have i - Exit Command: Adds a graceful exit command - Folder Copy Round Queue: Copies files from folders in a queue - Help: Display a full list of MultiAdmin commands and in game commands -- Stop Server When Inactive: Stops the server after a period inactivity +- Stop Server When Inactive: Stops the server after a period inactivity [Requires ServerMod] - Restart On Low Memory: Restarts the server if the working memory becomes too low - ModLog: Logs admin messages to separate file, or prints them - MultiAdminInfo: Prints MultiAdmin license and version information - New: Adds a command to start a new server given a config folder - Restart Command: Allows the game to be restarted without restarting MultiAdmin -- Restart Next Round: Restarts the server after the current round ends -- Restart After a Number of Rounds: Restarts the server after a number rounds completed -- Stop Next Round: Stops the server after the current round ends -- TitleBar: Updates the title bar with instance based information, such as session id and player count (Requires ServerMod to function fully) +- Restart Next Round: Restarts the server after the current round ends [Requires ServerMod] +- Restart After a Number of Rounds: Restarts the server after a number rounds completed [Requires ServerMod] +- Stop Next Round: Stops the server after the current round ends [Requires ServerMod] +- TitleBar: Updates the title bar with instance based information, such as console port and player count [Requires ServerMod to function fully] ## MultiAdmin Commands This does not include ServerMod or ingame commands, for a full list type `HELP` in multiadmin which will produce all commands. - CONFIG : Reloads the configuration file - EXIT: Exits the server -- GITHUBGEN [FILE LOCATION]: Generates a github .md file outlining all the features/commands +- GITHUBGEN [FILE LOCATION]: Generates a GitHub README file outlining all the features/commands - HELP: Prints out available commands and their function - INFO: Prints MultiAdmin license and version information - NEW : Starts a new server with the given Server ID - RESTART: Restarts the game server (MultiAdmin will not restart, just the game) -- RESTARTNEXTROUND: Restarts the server at the end of this round -- STOPNEXTROUND: Stops the server at the end of this round +- RESTARTNEXTROUND: Restarts the server at the end of this round [Requires ServerMod] +- STOPNEXTROUND: Stops the server at the end of this round [Requires ServerMod] ## MultiAdmin Execution Arguments The arguments available for running MultiAdmin with @@ -71,7 +71,7 @@ disable_config_validation | Boolean | False | Disable the config validator share_non_configs | Boolean | True | Makes all files other than the config files store in AppData multiadmin_nolog | Boolean | False | Disable logging to file multiadmin_debug_log | Boolean | True | Enables MultiAdmin debug logging, this logs to a separate file than any other logs -multiadmin_debug_log_blacklist | String List | ProcessFile, StringMatches | Which tags to block for MultiAdmin debug logging +multiadmin_debug_log_blacklist | String List | HandleMessage, StringMatches, MessageListener | Which tags to block for MultiAdmin debug logging multiadmin_debug_log_whitelist | String List | **Empty** | Which tags to log for MultiAdmin debug logging (Defaults to logging all if none are provided) use_new_input_system | Boolean | True | Whether to use the new input system, if false, the original input system will be used port | Unsigned Integer | 7777 | The port for the server to use @@ -103,6 +103,3 @@ servers_folder | String | servers | The location of the `servers` folder for Mul set_title_bar | Boolean | True | Whether to set the console window's titlebar, if false, this feature won't be used shutdown_when_empty_for | Integer | -1 | Shutdown the server once a round hasn't started in a number of seconds start_config_on_full | String | **Empty** | Start server with this config folder once the server becomes full [Requires ServerMod] - -## Upcoming Features -- Support for running multiple server instances in one MultiAdmin instance From cdff3be91e48fa49b1acb036ae6495c5bd313af4 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sun, 31 May 2020 02:21:15 -0400 Subject: [PATCH 06/14] Change ColoredConsole extensions to arrays --- MultiAdmin/ConsoleTools/ColoredConsole.cs | 24 +++++++++++------------ MultiAdmin/ConsoleTools/ConsoleUtils.cs | 6 ------ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/MultiAdmin/ConsoleTools/ColoredConsole.cs b/MultiAdmin/ConsoleTools/ColoredConsole.cs index cd979e2..f892173 100644 --- a/MultiAdmin/ConsoleTools/ColoredConsole.cs +++ b/MultiAdmin/ConsoleTools/ColoredConsole.cs @@ -178,47 +178,47 @@ public void WriteLine(bool clearConsoleLine = false) } } - public static class ColoredMessageEnumerableExtensions + public static class ColoredMessageArrayExtensions { - private static string JoinTextIgnoreNull(IEnumerable objects) + private static string JoinTextIgnoreNull(ColoredMessage[] coloredMessages) { StringBuilder builder = new StringBuilder(string.Empty); - foreach (object o in objects) + foreach (ColoredMessage coloredMessage in coloredMessages) { - if (o != null) - builder.Append(o); + if (coloredMessage != null) + builder.Append(coloredMessage); } return builder.ToString(); } - public static string GetText(this IEnumerable message) + public static string GetText(this ColoredMessage[] message) { return JoinTextIgnoreNull(message); } - public static void Write(this IEnumerable message, bool clearConsoleLine = false) + public static void Write(this ColoredMessage[] message, bool clearConsoleLine = false) { lock (ColoredConsole.WriteLock) { - ColoredConsole.Write(clearConsoleLine ? ConsoleUtils.ClearConsoleLine(message.ToArray()) : message.ToArray()); + ColoredConsole.Write(clearConsoleLine ? ConsoleUtils.ClearConsoleLine(message) : message); } } - public static void WriteLine(this IEnumerable message, bool clearConsoleLine = false) + public static void WriteLine(this ColoredMessage[] message, bool clearConsoleLine = false) { lock (ColoredConsole.WriteLock) { - ColoredConsole.WriteLine(clearConsoleLine ? ConsoleUtils.ClearConsoleLine(message.ToArray()) : message.ToArray()); + ColoredConsole.WriteLine(clearConsoleLine ? ConsoleUtils.ClearConsoleLine(message) : message); } } - public static void WriteLines(this IEnumerable message, bool clearConsoleLine = false) + public static void WriteLines(this ColoredMessage[] message, bool clearConsoleLine = false) { lock (ColoredConsole.WriteLock) { - ColoredConsole.WriteLines(clearConsoleLine ? ConsoleUtils.ClearConsoleLine(message.ToArray()) : message.ToArray()); + ColoredConsole.WriteLines(clearConsoleLine ? ConsoleUtils.ClearConsoleLine(message) : message); } } } diff --git a/MultiAdmin/ConsoleTools/ConsoleUtils.cs b/MultiAdmin/ConsoleTools/ConsoleUtils.cs index f16edd9..705e539 100644 --- a/MultiAdmin/ConsoleTools/ConsoleUtils.cs +++ b/MultiAdmin/ConsoleTools/ConsoleUtils.cs @@ -68,12 +68,6 @@ public static ColoredMessage[] ClearConsoleLine(ColoredMessage[] message) return message; } - public static List ClearConsoleLine(List message) - { - ClearConsoleLine(message?.GetText()); - return message; - } - #endregion } } From 8a1460cbed0e5a79b43c33898b68e45dd51866fd Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sun, 31 May 2020 03:53:12 -0400 Subject: [PATCH 07/14] Reduce ServerMod specific functionality - Remove ModLog - Remove InactivityShutdown - Remove player count from TitleBar - Update feature documentation - Add log folder config - Remove most ServerMod specific functionality - Simplified output parsing --- MultiAdmin/Config/MultiAdminConfig.cs | 16 +-- MultiAdmin/EventInterfaces.cs | 16 --- MultiAdmin/Features/EventTest.cs | 14 +-- MultiAdmin/Features/InactivityShutdown.cs | 63 ------------ MultiAdmin/Features/ModLog.cs | 51 ---------- MultiAdmin/Features/NewCommand.cs | 4 +- MultiAdmin/Features/TitleBar.cs | 28 +----- MultiAdmin/MultiAdmin.csproj | 2 - MultiAdmin/Program.cs | 2 +- MultiAdmin/Server.cs | 42 +------- MultiAdmin/ServerIO/OutputHandler.cs | 116 +++++----------------- README.md | 8 +- 12 files changed, 41 insertions(+), 321 deletions(-) delete mode 100644 MultiAdmin/Features/InactivityShutdown.cs delete mode 100644 MultiAdmin/Features/ModLog.cs diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs index 5624ca5..03a2aff 100644 --- a/MultiAdmin/Config/MultiAdminConfig.cs +++ b/MultiAdmin/Config/MultiAdminConfig.cs @@ -30,6 +30,10 @@ public class MultiAdminConfig : InheritableConfigRegister new ConfigEntry("share_non_configs", true, "Share Non-Configs", "Makes all files other than the config files store in AppData"); + public ConfigEntry LogLocation { get; } = + new ConfigEntry("multiadmin_log_location", "logs", + "MultiAdmin Log Location", "The folder that MultiAdmin will store logs in (a directory)"); + public ConfigEntry NoLog { get; } = new ConfigEntry("multiadmin_nolog", false, "MultiAdmin No-Logging", "Disable logging to file"); @@ -82,10 +86,6 @@ public class MultiAdminConfig : InheritableConfigRegister new ConfigEntry("randomize_folder_copy_round_queue", false, "Randomize Folder Copy Round Queue", "Whether to randomize the order of entries in `folder_copy_round_queue`"); - public ConfigEntry LogModActionsToOwnFile { get; } = - new ConfigEntry("log_mod_actions_to_own_file", false, - "Log Mod Actions to Own File", "Logs admin messages to separate file"); - public ConfigEntry ManualStart { get; } = new ConfigEntry("manual_start", false, "Manual Start", "Whether or not to start the server automatically when launching MultiAdmin"); @@ -106,10 +106,6 @@ public class MultiAdminConfig : InheritableConfigRegister new ConfigEntry("max_players", 20, "Max Players", "The number of players to display as the maximum for the server (within MultiAdmin, not in-game)"); - public ConfigEntry OutputReadAttempts { get; } = - new ConfigEntry("output_read_attempts", 100, - "Output Read Attempts", "The number of times to attempt reading a message from the server before giving up"); - public ConfigEntry RandomInputColors { get; } = new ConfigEntry("random_input_colors", false, "Random Input Colors", "Randomize the new input system's colors every time a message is input"); @@ -158,10 +154,6 @@ public class MultiAdminConfig : InheritableConfigRegister new ConfigEntry("set_title_bar", true, "Set Title Bar", "Whether to set the console window's titlebar, if false, this feature won't be used"); - public ConfigEntry ShutdownWhenEmptyFor { get; } = - new ConfigEntry("shutdown_when_empty_for", -1, - "Shutdown When Empty For", "Shutdown the server once a round hasn't started in a number of seconds"); - public ConfigEntry StartConfigOnFull { get; } = new ConfigEntry("start_config_on_full", "", "Start Config on Full", "Start server with this config folder once the server becomes full [Requires ServerMod]"); diff --git a/MultiAdmin/EventInterfaces.cs b/MultiAdmin/EventInterfaces.cs index 35111eb..080ccd9 100644 --- a/MultiAdmin/EventInterfaces.cs +++ b/MultiAdmin/EventInterfaces.cs @@ -52,22 +52,6 @@ public interface IEventServerFull : IServerMod { void OnServerFull(); } - - public interface IEventPlayerConnect : IServerMod - { - void OnPlayerConnect(string name); - } - - public interface IEventPlayerDisconnect : IServerMod - { - void OnPlayerDisconnect(string name); - } - - public interface IEventAdminAction : IServerMod - { - void OnAdminAction(string message); - } - public interface ICommand { void OnCall(string[] args); diff --git a/MultiAdmin/Features/EventTest.cs b/MultiAdmin/Features/EventTest.cs index e820ce1..beb6918 100644 --- a/MultiAdmin/Features/EventTest.cs +++ b/MultiAdmin/Features/EventTest.cs @@ -1,7 +1,7 @@ namespace MultiAdmin.Features { - internal class EventTest : Feature, IEventCrash, IEventPlayerConnect, IEventPlayerDisconnect, - IEventRoundEnd, IEventWaitingForPlayers, IEventRoundStart, IEventServerFull, IEventServerPreStart, IEventServerStart, IEventServerStop + internal class EventTest : Feature, IEventCrash, + IEventRoundEnd, IEventWaitingForPlayers, IEventRoundStart, IEventServerPreStart, IEventServerStart, IEventServerStop { public EventTest(Server server) : base(server) { @@ -12,16 +12,6 @@ public void OnCrash() Server.Write("EVENTTEST Crash"); } - public void OnPlayerConnect(string name) - { - Server.Write("EVENTTEST player connect " + name); - } - - public void OnPlayerDisconnect(string name) - { - Server.Write("EVENTTEST player disconnect " + name); - } - public void OnRoundEnd() { Server.Write("EVENTTEST on round end"); diff --git a/MultiAdmin/Features/InactivityShutdown.cs b/MultiAdmin/Features/InactivityShutdown.cs deleted file mode 100644 index 3c89a84..0000000 --- a/MultiAdmin/Features/InactivityShutdown.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using MultiAdmin.Features.Attributes; - -namespace MultiAdmin.Features -{ - [Feature] - internal class InactivityShutdown : Feature, IEventRoundStart, IEventWaitingForPlayers, IEventTick - { - private DateTime queueStartTime; - private int waitFor; - private bool waiting; - - public InactivityShutdown(Server server) : base(server) - { - } - - public void OnWaitingForPlayers() - { - queueStartTime = DateTime.Now; - waiting = true; - } - - public void OnRoundStart() - { - waiting = false; - } - - public void OnTick() - { - if (waitFor > 0 && waiting) - { - int elapsed = (DateTime.Now - queueStartTime).Seconds; - - if (elapsed >= waitFor) - { - Server.Write("Server has been inactive for " + waitFor + " seconds, shutting down"); - Server.StopServer(); - } - } - } - - public override void Init() - { - queueStartTime = DateTime.Now; - } - - public override void OnConfigReload() - { - waitFor = Server.ServerConfig.ShutdownWhenEmptyFor.Value; - } - - - public override string GetFeatureDescription() - { - return "Stops the server after a period inactivity [Requires ServerMod]"; - } - - public override string GetFeatureName() - { - return "Stop Server When Inactive"; - } - } -} diff --git a/MultiAdmin/Features/ModLog.cs b/MultiAdmin/Features/ModLog.cs deleted file mode 100644 index a5b4633..0000000 --- a/MultiAdmin/Features/ModLog.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.IO; -using MultiAdmin.Features.Attributes; -using MultiAdmin.Utility; - -namespace MultiAdmin.Features -{ - [Feature] - internal class ModLog : Feature, IEventAdminAction - { - private bool logToOwnFile; - - public ModLog(Server server) : base(server) - { - } - - public void OnAdminAction(string message) - { - if (!logToOwnFile || string.IsNullOrEmpty(Server.ModLogFile) || Server.ServerConfig.NoLog.Value) return; - - lock (this) - { - Directory.CreateDirectory(Server.logDir); - - using (StreamWriter sw = File.AppendText(Server.ModLogFile)) - { - message = Utils.TimeStampMessage(message); - sw.WriteLine(message); - } - } - } - - public override string GetFeatureDescription() - { - return "Logs admin messages to separate file, or prints them"; - } - - public override string GetFeatureName() - { - return "ModLog"; - } - - public override void Init() - { - } - - public override void OnConfigReload() - { - logToOwnFile = Server.ServerConfig.LogModActionsToOwnFile.Value; - } - } -} diff --git a/MultiAdmin/Features/NewCommand.cs b/MultiAdmin/Features/NewCommand.cs index de59241..c56da64 100644 --- a/MultiAdmin/Features/NewCommand.cs +++ b/MultiAdmin/Features/NewCommand.cs @@ -63,12 +63,12 @@ public override void OnConfigReload() public override string GetFeatureDescription() { - return "Adds a command to start a new server given a config folder"; + return "Adds a command to start a new server given a config folder and a config to start a new server when one is full [Config Requires ServerMod]"; } public override string GetFeatureName() { - return "New"; + return "New Server"; } public void OnServerFull() diff --git a/MultiAdmin/Features/TitleBar.cs b/MultiAdmin/Features/TitleBar.cs index 3b42c12..401b5f6 100644 --- a/MultiAdmin/Features/TitleBar.cs +++ b/MultiAdmin/Features/TitleBar.cs @@ -5,11 +5,8 @@ namespace MultiAdmin.Features { [Feature] - internal class Titlebar : Feature, IEventPlayerConnect, IEventPlayerDisconnect, IEventServerStart + internal class Titlebar : Feature, IEventServerStart { - private int maxPlayers; - private int playerCount; - private int ServerProcessId { get @@ -27,18 +24,6 @@ public Titlebar(Server server) : base(server) { } - public void OnPlayerConnect(string name) - { - playerCount++; - UpdateTitlebar(); - } - - public void OnPlayerDisconnect(string name) - { - playerCount--; - UpdateTitlebar(); - } - public void OnServerStart() { UpdateTitlebar(); @@ -57,13 +42,11 @@ public override string GetFeatureName() public override void Init() { - playerCount = -1; // -1 for the "server" player, once the server starts this will increase to 0. UpdateTitlebar(); } public override void OnConfigReload() { - maxPlayers = Server.ServerConfig.MaxPlayers.Value; UpdateTitlebar(); } @@ -71,8 +54,6 @@ private void UpdateTitlebar() { if (Program.Headless || !Server.ServerConfig.SetTitleBar.Value) return; - int displayPlayerCount = playerCount < 0 ? 0 : playerCount; - List titleBar = new List {$"MultiAdmin {Program.MaVersion}"}; if (!string.IsNullOrEmpty(Server.serverId)) @@ -90,13 +71,6 @@ private void UpdateTitlebar() titleBar.Add($"PID: {ServerProcessId}"); } - titleBar.Add($"{displayPlayerCount}/{maxPlayers}"); - - if (Server.hasServerMod && !string.IsNullOrEmpty(Server.serverModVersion)) - { - titleBar.Add(string.IsNullOrEmpty(Server.serverModBuild) ? $"SMod {Server.serverModVersion}" : $"SMod {Server.serverModVersion}-{Server.serverModBuild}"); - } - try { Console.Title = string.Join(" | ", titleBar); diff --git a/MultiAdmin/MultiAdmin.csproj b/MultiAdmin/MultiAdmin.csproj index 3fb1dea..8e4065f 100644 --- a/MultiAdmin/MultiAdmin.csproj +++ b/MultiAdmin/MultiAdmin.csproj @@ -95,9 +95,7 @@ - - diff --git a/MultiAdmin/Program.cs b/MultiAdmin/Program.cs index 8dca438..62763ae 100644 --- a/MultiAdmin/Program.cs +++ b/MultiAdmin/Program.cs @@ -20,7 +20,7 @@ public static class Program private static readonly List InstantiatedServers = new List(); - private static readonly string MaDebugLogDir = Utils.GetFullPathSafe("logs"); + private static readonly string MaDebugLogDir = Utils.GetFullPathSafe(MultiAdminConfig.GlobalConfig.LogLocation.Value); private static readonly string MaDebugLogFile = !string.IsNullOrEmpty(MaDebugLogDir) ? Utils.GetFullPathSafe(Path.Combine(MaDebugLogDir, $"{Utils.DateTime}_MA_{MaVersion}_debug_log.txt")) : null; private static uint? portArg; diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs index 27b712d..624ada3 100644 --- a/MultiAdmin/Server.cs +++ b/MultiAdmin/Server.cs @@ -31,11 +31,6 @@ public class Server public readonly string serverDir; public readonly string logDir; - public bool hasServerMod; - - public string serverModBuild; - public string serverModVersion; - private DateTime initStopTimeoutTime; private DateTime initRestartTimeoutTime; @@ -71,7 +66,7 @@ public Server(string serverId = null, string configLocation = null, uint? port = // Set port this.port = port; - logDir = Utils.GetFullPathSafe(Path.Combine(string.IsNullOrEmpty(serverDir) ? string.Empty : serverDir, "logs")); + logDir = Utils.GetFullPathSafe(Path.Combine(string.IsNullOrEmpty(serverDir) ? string.Empty : serverDir, serverConfig.LogLocation.Value)); // Register all features RegisterFeatures(); @@ -455,15 +450,8 @@ public void SoftRestartServer() initRestartTimeoutTime = DateTime.Now; Status = ServerStatus.Restarting; - if (hasServerMod) - { - SendMessage("RECONNECTRS"); - } - else - { - SendMessage("ROUNDRESTART"); - SendMessage("QUIT"); - } + SendMessage("ROUNDRESTART"); + SendMessage("QUIT"); } #endregion @@ -614,30 +602,6 @@ public void Log(string message) #endregion - public bool ServerModCheck(int major, int minor, int fix) - { - if (string.IsNullOrEmpty(serverModVersion)) - return false; - - string[] parts = serverModVersion.Split('.'); - - if (parts.IsEmpty()) - return false; - - int.TryParse(parts[0], out int verMajor); - - int verMinor = 0; - if (parts.Length >= 2) - int.TryParse(parts[1], out verMinor); - - int verFix = 0; - if (parts.Length >= 3) - int.TryParse(parts[2], out verFix); - - return verMajor > major || verMajor >= major && verMinor > minor || - verMajor >= major && verMinor >= minor && verFix >= fix; - } - public void ReloadConfig(bool copyFiles = true, bool runEvent = true) { ServerConfig.ReloadConfig(); diff --git a/MultiAdmin/ServerIO/OutputHandler.cs b/MultiAdmin/ServerIO/OutputHandler.cs index bd798b1..7d0e3a3 100644 --- a/MultiAdmin/ServerIO/OutputHandler.cs +++ b/MultiAdmin/ServerIO/OutputHandler.cs @@ -13,8 +13,6 @@ public class OutputHandler public static readonly Regex SmodRegex = new Regex(@"\[(DEBUG|INFO|WARN|ERROR)\] (\[.*?\]) (.*)", RegexOptions.Compiled | RegexOptions.Singleline); - private bool fixBuggedPlayers; - private readonly Server server; public OutputHandler(Server server) @@ -22,37 +20,23 @@ public OutputHandler(Server server) this.server = server; } - public static ConsoleColor MapConsoleColor(string color, ConsoleColor def = ConsoleColor.Cyan) - { - try - { - return (ConsoleColor)Enum.Parse(typeof(ConsoleColor), color); - } - catch (Exception e) - { - Program.LogDebugException(nameof(MapConsoleColor), e); - return def; - } - } - public void HandleMessage(object source, string message) { if (message == null) return; - bool display = true; - ConsoleColor color = ConsoleColor.Cyan; + ColoredMessage coloredMessage = new ColoredMessage(message, ConsoleColor.Cyan); - if (message.Length > 0) + if (coloredMessage.text.Length > 0) { - if (byte.TryParse(Convert.ToString(message[0]), NumberStyles.HexNumber, NumberFormatInfo.CurrentInfo, out byte consoleColor)) + if (byte.TryParse(Convert.ToString(coloredMessage.text[0]), NumberStyles.HexNumber, NumberFormatInfo.CurrentInfo, out byte consoleColor)) { - color = (ConsoleColor)consoleColor; - message = message.Substring(1); + coloredMessage.textColor = (ConsoleColor)consoleColor; + coloredMessage.text = coloredMessage.text.Substring(1); } // Smod2 loggers pretty printing - Match match = SmodRegex.Match(message); + Match match = SmodRegex.Match(coloredMessage.text); if (match.Success) { if (match.Groups.Count >= 3) @@ -76,7 +60,7 @@ public void HandleMessage(object source, string message) msgColor = ConsoleColor.Red; break; default: - color = ConsoleColor.Cyan; + coloredMessage.textColor = ConsoleColor.Cyan; break; } @@ -96,82 +80,34 @@ public void HandleMessage(object source, string message) } } - if (message.Contains("Mod Log:")) - server.ForEachHandler(adminAction => - adminAction.OnAdminAction(message.Replace("Mod Log:", string.Empty))); - - if (message.Contains("ServerMod - Version")) + switch (coloredMessage.text) { - server.hasServerMod = true; - // This should work fine with older ServerMod versions too - string[] streamSplit = message.Replace("ServerMod - Version", string.Empty).Split('-'); + case "The round is about to restart! Please wait..": + server.ForEachHandler(roundEnd => roundEnd.OnRoundEnd()); + break; - if (!streamSplit.IsEmpty()) - { - server.serverModVersion = streamSplit[0].Trim(); - server.serverModBuild = (streamSplit.Length > 1 ? streamSplit[1] : "A").Trim(); - } - } + case "Waiting for players...": + server.IsLoading = false; - if (message.Contains("Round restarting")) - server.ForEachHandler(roundEnd => roundEnd.OnRoundEnd()); + server.ForEachHandler(waitingForPlayers => + waitingForPlayers.OnWaitingForPlayers()); + break; - if (message.Contains("Waiting for players")) - { - server.IsLoading = false; + case "New round has been started.": + server.ForEachHandler(roundStart => roundStart.OnRoundStart()); + break; - server.ForEachHandler(waitingForPlayers => - waitingForPlayers.OnWaitingForPlayers()); + case "Level loaded. Creating match...": + server.ForEachHandler(serverStart => serverStart.OnServerStart()); + break; - if (fixBuggedPlayers) - { - server.SendMessage("ROUNDRESTART"); - fixBuggedPlayers = false; - } + case "Server full": + server.ForEachHandler(serverFull => serverFull.OnServerFull()); + break; } - - if (message.Contains("New round has been started")) - server.ForEachHandler(roundStart => roundStart.OnRoundStart()); - - if (message.Contains("Level loaded. Creating match...")) - server.ForEachHandler(serverStart => serverStart.OnServerStart()); - - if (message.Contains("Server full")) - server.ForEachHandler(serverFull => serverFull.OnServerFull()); - - if (message.Contains("Player connect")) - { - display = false; - server.Log("Player connect event"); - - int index = message.IndexOf(":"); - if (index >= 0) - { - string name = message.Substring(index); - server.ForEachHandler(playerConnect => - playerConnect.OnPlayerConnect(name)); - } - } - - if (message.Contains("Player disconnect")) - { - display = false; - server.Log("Player disconnect event"); - - int index = message.IndexOf(":"); - if (index >= 0) - { - string name = message.Substring(index); - server.ForEachHandler(playerDisconnect => - playerDisconnect.OnPlayerDisconnect(name)); - } - } - - if (message.Contains("Player has connected before load is complete")) - fixBuggedPlayers = true; } - if (display) server.Write(message, color); + server.Write(coloredMessage); } } } diff --git a/README.md b/README.md index ee4b8ee..051c4e3 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,9 @@ Make sure that you are running Mono 5.18.0 or higher, otherwise you might have i - Exit Command: Adds a graceful exit command - Folder Copy Round Queue: Copies files from folders in a queue - Help: Display a full list of MultiAdmin commands and in game commands -- Stop Server When Inactive: Stops the server after a period inactivity [Requires ServerMod] - Restart On Low Memory: Restarts the server if the working memory becomes too low -- ModLog: Logs admin messages to separate file, or prints them - MultiAdminInfo: Prints MultiAdmin license and version information -- New: Adds a command to start a new server given a config folder +- New Server: Adds a command to start a new server given a config folder and a config to start a new server when one is full [Config Requires ServerMod] - Restart Command: Allows the game to be restarted without restarting MultiAdmin - Restart Next Round: Restarts the server after the current round ends [Requires ServerMod] - Restart After a Number of Rounds: Restarts the server after a number rounds completed [Requires ServerMod] @@ -69,6 +67,7 @@ config_location | String | **Empty** | The default location for the game to use appdata_location | String | **Empty** | The location for the game to use for AppData (a directory) disable_config_validation | Boolean | False | Disable the config validator share_non_configs | Boolean | True | Makes all files other than the config files store in AppData +multiadmin_log_location | String | logs | The folder that MultiAdmin will store logs in (a directory) multiadmin_nolog | Boolean | False | Disable logging to file multiadmin_debug_log | Boolean | True | Enables MultiAdmin debug logging, this logs to a separate file than any other logs multiadmin_debug_log_blacklist | String List | HandleMessage, StringMatches, MessageListener | Which tags to block for MultiAdmin debug logging @@ -82,13 +81,11 @@ folder_copy_round_queue | String List | **Empty** | The location of a folder to folder_copy_round_queue_whitelist | String List | **Empty** | The list of file names to copy from the folders defined by `folder_copy_round_queue` (accepts `*` wildcards) folder_copy_round_queue_blacklist | String List | **Empty** | The list of file names to not copy from the folders defined by `folder_copy_round_queue` (accepts `*` wildcards) randomize_folder_copy_round_queue | Boolean | False | Whether to randomize the order of entries in `folder_copy_round_queue` -log_mod_actions_to_own_file | Boolean | False | Logs admin messages to separate file manual_start | Boolean | False | Whether or not to start the server automatically when launching MultiAdmin max_memory | Decimal | 2048 | The amount of memory in megabytes for MultiAdmin to check against restart_low_memory | Decimal | 400 | Restart if the game's remaining memory falls below this value in megabytes restart_low_memory_roundend | Decimal | 450 | Restart at the end of the round if the game's remaining memory falls below this value in megabytes max_players | Integer | 20 | The number of players to display as the maximum for the server (within MultiAdmin, not in-game) -output_read_attempts | Integer | 100 | The number of times to attempt reading a message from the server before giving up random_input_colors | Boolean | False | Randomize the new input system's colors every time a message is input restart_every_num_rounds | Integer | -1 | Restart the server every number of rounds restart_every_num_rounds_counting | Boolean | False | Whether to print the count of rounds passed after each round if the server is set to restart after a number of rounds @@ -101,5 +98,4 @@ server_start_retry | Boolean | True | Whether to try to start the server again a server_start_retry_delay | Integer | 10000 | The time in milliseconds to wait before trying to start the server again after crashing servers_folder | String | servers | The location of the `servers` folder for MultiAdmin to load multiple server configurations from set_title_bar | Boolean | True | Whether to set the console window's titlebar, if false, this feature won't be used -shutdown_when_empty_for | Integer | -1 | Shutdown the server once a round hasn't started in a number of seconds start_config_on_full | String | **Empty** | Start server with this config folder once the server becomes full [Requires ServerMod] From febb29e1197116d18d72f2b3be310f3210f5ee6a Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sun, 31 May 2020 04:07:06 -0400 Subject: [PATCH 08/14] Remove ServerMod branding --- MultiAdmin/Config/MultiAdminConfig.cs | 6 +----- MultiAdmin/EventInterfaces.cs | 7 ++----- MultiAdmin/Features/NewCommand.cs | 2 +- MultiAdmin/Features/RestartNextRound.cs | 4 ++-- MultiAdmin/Features/RestartRoundCounter.cs | 2 +- MultiAdmin/Features/StopNextRound.cs | 4 ++-- MultiAdmin/Features/TitleBar.cs | 3 +-- README.md | 19 +++++++++---------- 8 files changed, 19 insertions(+), 28 deletions(-) diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs index 03a2aff..7267f07 100644 --- a/MultiAdmin/Config/MultiAdminConfig.cs +++ b/MultiAdmin/Config/MultiAdminConfig.cs @@ -102,10 +102,6 @@ public class MultiAdminConfig : InheritableConfigRegister new ConfigEntry("restart_low_memory_roundend", 450, "Restart Low Memory Round-End", "Restart at the end of the round if the game's remaining memory falls below this value in megabytes"); - public ConfigEntry MaxPlayers { get; } = - new ConfigEntry("max_players", 20, - "Max Players", "The number of players to display as the maximum for the server (within MultiAdmin, not in-game)"); - public ConfigEntry RandomInputColors { get; } = new ConfigEntry("random_input_colors", false, "Random Input Colors", "Randomize the new input system's colors every time a message is input"); @@ -156,7 +152,7 @@ public class MultiAdminConfig : InheritableConfigRegister public ConfigEntry StartConfigOnFull { get; } = new ConfigEntry("start_config_on_full", "", - "Start Config on Full", "Start server with this config folder once the server becomes full [Requires ServerMod]"); + "Start Config on Full", "Start server with this config folder once the server becomes full [Requires Modding]"); #endregion diff --git a/MultiAdmin/EventInterfaces.cs b/MultiAdmin/EventInterfaces.cs index 080ccd9..7f73d22 100644 --- a/MultiAdmin/EventInterfaces.cs +++ b/MultiAdmin/EventInterfaces.cs @@ -44,14 +44,11 @@ public interface IEventTick : IMAEvent void OnTick(); } - public interface IServerMod : IMAEvent - { - } - - public interface IEventServerFull : IServerMod + public interface IEventServerFull : IMAEvent { void OnServerFull(); } + public interface ICommand { void OnCall(string[] args); diff --git a/MultiAdmin/Features/NewCommand.cs b/MultiAdmin/Features/NewCommand.cs index c56da64..a8cb2c0 100644 --- a/MultiAdmin/Features/NewCommand.cs +++ b/MultiAdmin/Features/NewCommand.cs @@ -63,7 +63,7 @@ public override void OnConfigReload() public override string GetFeatureDescription() { - return "Adds a command to start a new server given a config folder and a config to start a new server when one is full [Config Requires ServerMod]"; + return "Adds a command to start a new server given a config folder and a config to start a new server when one is full [Config Requires Modding]"; } public override string GetFeatureName() diff --git a/MultiAdmin/Features/RestartNextRound.cs b/MultiAdmin/Features/RestartNextRound.cs index 6587c7a..da0af2e 100644 --- a/MultiAdmin/Features/RestartNextRound.cs +++ b/MultiAdmin/Features/RestartNextRound.cs @@ -13,7 +13,7 @@ public RestartNextRound(Server server) : base(server) public string GetCommandDescription() { - return "Restarts the server at the end of this round [Requires ServerMod]"; + return "Restarts the server at the end of this round [Requires Modding]"; } @@ -53,7 +53,7 @@ public override void Init() public override string GetFeatureDescription() { - return "Restarts the server after the current round ends [Requires ServerMod]"; + return "Restarts the server after the current round ends [Requires Modding]"; } public override string GetFeatureName() diff --git a/MultiAdmin/Features/RestartRoundCounter.cs b/MultiAdmin/Features/RestartRoundCounter.cs index e1b9c2b..d9e5a36 100644 --- a/MultiAdmin/Features/RestartRoundCounter.cs +++ b/MultiAdmin/Features/RestartRoundCounter.cs @@ -45,7 +45,7 @@ public override void OnConfigReload() public override string GetFeatureDescription() { - return "Restarts the server after a number rounds completed [Requires ServerMod]"; + return "Restarts the server after a number rounds completed [Requires Modding]"; } public override string GetFeatureName() diff --git a/MultiAdmin/Features/StopNextRound.cs b/MultiAdmin/Features/StopNextRound.cs index 8bbcc36..67ab5ae 100644 --- a/MultiAdmin/Features/StopNextRound.cs +++ b/MultiAdmin/Features/StopNextRound.cs @@ -14,7 +14,7 @@ public StopNextRound(Server server) : base(server) public string GetCommandDescription() { - return "Stops the server at the end of this round [Requires ServerMod]"; + return "Stops the server at the end of this round [Requires Modding]"; } public void OnCall(string[] args) @@ -57,7 +57,7 @@ public override void OnConfigReload() public override string GetFeatureDescription() { - return "Stops the server after the current round ends [Requires ServerMod]"; + return "Stops the server after the current round ends [Requires Modding]"; } public override string GetFeatureName() diff --git a/MultiAdmin/Features/TitleBar.cs b/MultiAdmin/Features/TitleBar.cs index 401b5f6..3b5a7a8 100644 --- a/MultiAdmin/Features/TitleBar.cs +++ b/MultiAdmin/Features/TitleBar.cs @@ -31,8 +31,7 @@ public void OnServerStart() public override string GetFeatureDescription() { - return - "Updates the title bar with instance based information, such as console port and player count [Requires ServerMod to function fully]"; + return "Updates the title bar with instance based information"; } public override string GetFeatureName() diff --git a/README.md b/README.md index 051c4e3..07aaad1 100644 --- a/README.md +++ b/README.md @@ -29,15 +29,15 @@ Make sure that you are running Mono 5.18.0 or higher, otherwise you might have i - Help: Display a full list of MultiAdmin commands and in game commands - Restart On Low Memory: Restarts the server if the working memory becomes too low - MultiAdminInfo: Prints MultiAdmin license and version information -- New Server: Adds a command to start a new server given a config folder and a config to start a new server when one is full [Config Requires ServerMod] +- New Server: Adds a command to start a new server given a config folder and a config to start a new server when one is full [Config Requires Modding] - Restart Command: Allows the game to be restarted without restarting MultiAdmin -- Restart Next Round: Restarts the server after the current round ends [Requires ServerMod] -- Restart After a Number of Rounds: Restarts the server after a number rounds completed [Requires ServerMod] -- Stop Next Round: Stops the server after the current round ends [Requires ServerMod] -- TitleBar: Updates the title bar with instance based information, such as console port and player count [Requires ServerMod to function fully] +- Restart Next Round: Restarts the server after the current round ends [Requires Modding] +- Restart After a Number of Rounds: Restarts the server after a number rounds completed [Requires Modding] +- Stop Next Round: Stops the server after the current round ends [Requires Modding] +- TitleBar: Updates the title bar with instance based information ## MultiAdmin Commands -This does not include ServerMod or ingame commands, for a full list type `HELP` in multiadmin which will produce all commands. +This does not include ingame commands, for a full list type `HELP` in MultiAdmin which will produce all commands. - CONFIG : Reloads the configuration file - EXIT: Exits the server @@ -46,8 +46,8 @@ This does not include ServerMod or ingame commands, for a full list type `HELP` - INFO: Prints MultiAdmin license and version information - NEW : Starts a new server with the given Server ID - RESTART: Restarts the game server (MultiAdmin will not restart, just the game) -- RESTARTNEXTROUND: Restarts the server at the end of this round [Requires ServerMod] -- STOPNEXTROUND: Stops the server at the end of this round [Requires ServerMod] +- RESTARTNEXTROUND: Restarts the server at the end of this round [Requires Modding] +- STOPNEXTROUND: Stops the server at the end of this round [Requires Modding] ## MultiAdmin Execution Arguments The arguments available for running MultiAdmin with @@ -85,7 +85,6 @@ manual_start | Boolean | False | Whether or not to start the server automaticall max_memory | Decimal | 2048 | The amount of memory in megabytes for MultiAdmin to check against restart_low_memory | Decimal | 400 | Restart if the game's remaining memory falls below this value in megabytes restart_low_memory_roundend | Decimal | 450 | Restart at the end of the round if the game's remaining memory falls below this value in megabytes -max_players | Integer | 20 | The number of players to display as the maximum for the server (within MultiAdmin, not in-game) random_input_colors | Boolean | False | Randomize the new input system's colors every time a message is input restart_every_num_rounds | Integer | -1 | Restart the server every number of rounds restart_every_num_rounds_counting | Boolean | False | Whether to print the count of rounds passed after each round if the server is set to restart after a number of rounds @@ -98,4 +97,4 @@ server_start_retry | Boolean | True | Whether to try to start the server again a server_start_retry_delay | Integer | 10000 | The time in milliseconds to wait before trying to start the server again after crashing servers_folder | String | servers | The location of the `servers` folder for MultiAdmin to load multiple server configurations from set_title_bar | Boolean | True | Whether to set the console window's titlebar, if false, this feature won't be used -start_config_on_full | String | **Empty** | Start server with this config folder once the server becomes full [Requires ServerMod] +start_config_on_full | String | **Empty** | Start server with this config folder once the server becomes full [Requires Modding] From 0d1dacc2f75a886062a5a10e3943ff1532b8e5bb Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sun, 31 May 2020 05:29:02 -0400 Subject: [PATCH 09/14] Add icon, small bug fix & bump version --- MultiAdmin/Icon.ico | Bin 0 -> 36551 bytes MultiAdmin/MultiAdmin.csproj | 6 ++++++ MultiAdmin/Program.cs | 2 +- MultiAdmin/ServerIO/ServerSocket.cs | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 MultiAdmin/Icon.ico diff --git a/MultiAdmin/Icon.ico b/MultiAdmin/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..05c1438866552f4fba14f2cc5c444701669e17ba GIT binary patch literal 36551 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 Zsxb!eocvg0d*Q}gyMOKf7gPy#{|C87Ok@B6 literal 0 HcmV?d00001 diff --git a/MultiAdmin/MultiAdmin.csproj b/MultiAdmin/MultiAdmin.csproj index 8e4065f..497eb92 100644 --- a/MultiAdmin/MultiAdmin.csproj +++ b/MultiAdmin/MultiAdmin.csproj @@ -50,6 +50,9 @@ MultiAdmin.Program + + Icon.ico + @@ -119,5 +122,8 @@ 4.0.0 + + + \ No newline at end of file diff --git a/MultiAdmin/Program.cs b/MultiAdmin/Program.cs index 62763ae..ef04ec1 100644 --- a/MultiAdmin/Program.cs +++ b/MultiAdmin/Program.cs @@ -15,7 +15,7 @@ namespace MultiAdmin { public static class Program { - public const string MaVersion = "3.2.5.1"; + public const string MaVersion = "3.3.0.0"; public const string RecommendedMonoVersion = "5.18"; private static readonly List InstantiatedServers = new List(); diff --git a/MultiAdmin/ServerIO/ServerSocket.cs b/MultiAdmin/ServerIO/ServerSocket.cs index 4db83a3..796370f 100644 --- a/MultiAdmin/ServerIO/ServerSocket.cs +++ b/MultiAdmin/ServerIO/ServerSocket.cs @@ -33,7 +33,7 @@ public bool Connected { get { - return client.Connected; + return client?.Connected ?? false; } } From f3cbdeabf2c98ece1725141fde29e5735ce550b2 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Mon, 1 Jun 2020 17:55:50 -0400 Subject: [PATCH 10/14] Change testing to XUnit --- MultiAdminTests/MultiAdminTests.csproj | 10 +- MultiAdminTests/ServerIO/ShiftingListTests.cs | 25 ++- .../ServerIO/StringSectionsTests.cs | 23 ++- .../Utility/StringExtensionsTests.cs | 19 ++- MultiAdminTests/Utility/UtilsTests.cs | 144 +++++------------- 5 files changed, 72 insertions(+), 149 deletions(-) diff --git a/MultiAdminTests/MultiAdminTests.csproj b/MultiAdminTests/MultiAdminTests.csproj index 7bbcfa9..daf551b 100644 --- a/MultiAdminTests/MultiAdminTests.csproj +++ b/MultiAdminTests/MultiAdminTests.csproj @@ -62,11 +62,13 @@ - - 1.3.2 + + 2.4.1 - - 1.3.2 + + 2.4.1 + runtime; build; native; contentfiles; analyzers; buildtransitive + all diff --git a/MultiAdminTests/ServerIO/ShiftingListTests.cs b/MultiAdminTests/ServerIO/ShiftingListTests.cs index 61cf106..d18ffdf 100644 --- a/MultiAdminTests/ServerIO/ShiftingListTests.cs +++ b/MultiAdminTests/ServerIO/ShiftingListTests.cs @@ -1,22 +1,21 @@ using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; using MultiAdmin.ServerIO; +using Xunit; namespace MultiAdminTests.ServerIO { - [TestClass] public class ShiftingListTests { - [TestMethod] + [Fact] public void ShiftingListTest() { const int maxCount = 2; ShiftingList shiftingList = new ShiftingList(maxCount); - Assert.AreEqual(shiftingList.MaxCount, maxCount); + Assert.Equal(maxCount, shiftingList.MaxCount); } - [TestMethod] + [Fact] public void AddTest() { const int maxCount = 2; @@ -28,15 +27,15 @@ public void AddTest() shiftingList.Add($"Test{i}"); } - Assert.AreEqual(shiftingList.Count, maxCount); + Assert.Equal(maxCount, shiftingList.Count); for (int i = 0; i < shiftingList.Count; i++) { - Assert.AreEqual(shiftingList[i], $"Test{entriesToAdd - i - 1}"); + Assert.Equal($"Test{entriesToAdd - i - 1}", shiftingList[i]); } } - [TestMethod] + [Fact] public void RemoveFromEndTest() { const int maxCount = 6; @@ -53,15 +52,15 @@ public void RemoveFromEndTest() shiftingList.RemoveFromEnd(); } - Assert.AreEqual(shiftingList.Count, Math.Max(maxCount - entriesToRemove, 0)); + Assert.Equal(Math.Max(maxCount - entriesToRemove, 0), shiftingList.Count); for (int i = 0; i < shiftingList.Count; i++) { - Assert.AreEqual(shiftingList[i], $"Test{maxCount - i - 1}"); + Assert.Equal($"Test{maxCount - i - 1}", shiftingList[i]); } } - [TestMethod] + [Fact] public void ReplaceTest() { const int maxCount = 6; @@ -81,9 +80,9 @@ public void ReplaceTest() } } - Assert.AreEqual(shiftingList.Count, maxCount); + Assert.Equal(maxCount, shiftingList.Count); - Assert.AreEqual(shiftingList[indexToReplace], "Replaced"); + Assert.Equal("Replaced", shiftingList[indexToReplace]); } } } diff --git a/MultiAdminTests/ServerIO/StringSectionsTests.cs b/MultiAdminTests/ServerIO/StringSectionsTests.cs index 15d4b2a..bbf6c0b 100644 --- a/MultiAdminTests/ServerIO/StringSectionsTests.cs +++ b/MultiAdminTests/ServerIO/StringSectionsTests.cs @@ -1,11 +1,10 @@ using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; using MultiAdmin.ConsoleTools; using MultiAdmin.ServerIO; +using Xunit; namespace MultiAdminTests.ServerIO { - [TestClass] public class StringSectionsTests { private struct FromStringTemplate @@ -28,18 +27,14 @@ public FromStringTemplate(string testString, string[] expectedSections, int sect } } - [TestMethod] + [Fact] public void FromStringTest() { - try + // No further characters can be output because of the prefix and suffix + Assert.Throws(() => { StringSections.FromString("test string", 2, new ColoredMessage("."), new ColoredMessage(".")); - Assert.Fail("This case should not be allowed, no further characters can be output because of the prefix and suffix"); - } - catch (ArgumentException) - { - // Expected behaviour - } + }); FromStringTemplate[] sectionTests = { @@ -53,17 +48,17 @@ public void FromStringTest() StringSections sections = StringSections.FromString(sectionTest.testString, sectionTest.sectionLength, sectionTest.leftIndictator, sectionTest.rightIndictator); - Assert.IsNotNull(sections); - Assert.IsNotNull(sections.Sections); + Assert.NotNull(sections); + Assert.NotNull(sections.Sections); - Assert.IsTrue(sections.Sections.Length == sectionTest.expectedSections.Length, $"Failed at index {i}: Expected sections length \"{sectionTest.expectedSections.Length}\", got \"{sections.Sections.Length}\""); + Assert.True(sections.Sections.Length == sectionTest.expectedSections.Length, $"Failed at index {i}: Expected sections length \"{sectionTest.expectedSections.Length}\", got \"{sections.Sections.Length}\""); for (int j = 0; j < sectionTest.expectedSections.Length; j++) { string expected = sectionTest.expectedSections[j]; string result = sections.Sections[j].Section.GetText(); - Assert.AreEqual(expected, result, $"Failed at index {i}: Failed at section index {j}: Expected section text to be \"{expected ?? "null"}\", got \"{result ?? "null"}\""); + Assert.True(expected == result, $"Failed at index {i}: Failed at section index {j}: Expected section text to be \"{expected ?? "null"}\", got \"{result ?? "null"}\""); } } } diff --git a/MultiAdminTests/Utility/StringExtensionsTests.cs b/MultiAdminTests/Utility/StringExtensionsTests.cs index 7d94475..24bcda2 100644 --- a/MultiAdminTests/Utility/StringExtensionsTests.cs +++ b/MultiAdminTests/Utility/StringExtensionsTests.cs @@ -1,24 +1,23 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; using MultiAdmin.Utility; +using Xunit; namespace MultiAdminTests.Utility { - [TestClass] public class StringExtensionsTests { - [TestMethod] + [Fact] public void EqualsTest() { - Assert.IsTrue("test".Equals("test", startIndex: 0)); - Assert.IsFalse("test".Equals("other", startIndex: 0)); + Assert.True("test".Equals("test", startIndex: 0)); + Assert.False("test".Equals("other", startIndex: 0)); - Assert.IsTrue("test".Equals("st", startIndex: 2)); - Assert.IsTrue("test".Equals("te", 0, 2)); + Assert.True("test".Equals("st", startIndex: 2)); + Assert.True("test".Equals("te", 0, 2)); - Assert.IsFalse("test".Equals("te", startIndex: 2)); - Assert.IsFalse("test".Equals("st", 0, 2)); + Assert.False("test".Equals("te", startIndex: 2)); + Assert.False("test".Equals("st", 0, 2)); - Assert.IsTrue("test".Equals("es", 1, 2)); + Assert.True("test".Equals("es", 1, 2)); } } } diff --git a/MultiAdminTests/Utility/UtilsTests.cs b/MultiAdminTests/Utility/UtilsTests.cs index ea1c1dc..8340cd3 100644 --- a/MultiAdminTests/Utility/UtilsTests.cs +++ b/MultiAdminTests/Utility/UtilsTests.cs @@ -1,130 +1,58 @@ using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; using MultiAdmin.Utility; +using Xunit; namespace MultiAdminTests.Utility { - [TestClass] public class UtilsTests { - private struct StringMatchingTemplate - { - public readonly string input; - public readonly string pattern; - - public readonly bool expectedResult; - - public StringMatchingTemplate(string input, string pattern, bool expectedResult) - { - this.input = input; - this.pattern = pattern; - this.expectedResult = expectedResult; - } - } - - private struct CompareVersionTemplate - { - public readonly string firstVersion; - public readonly string secondVersion; - - public readonly int expectedResult; - - public CompareVersionTemplate(string firstVersion, string secondVersion, int expectedResult) - { - this.firstVersion = firstVersion; - this.secondVersion = secondVersion; - this.expectedResult = expectedResult; - } - - public bool CheckResult(int result) - { - if (expectedResult == result) - return true; - - if (expectedResult < 0 && result < 0) - return true; - - if (expectedResult > 0 && result > 0) - return true; - - return false; - } - } - - [TestMethod] + [Fact] public void GetFullPathSafeTest() { - string result = Utils.GetFullPathSafe(" "); - Assert.IsNull(result, $"Expected \"null\", got \"{result}\""); + Assert.Null(Utils.GetFullPathSafe(" ")); } - [TestMethod] - public void StringMatchesTest() + [Theory] + [InlineData("test", "*", true)] + [InlineData("test", "te*", true)] + [InlineData("test", "*st", true)] + [InlineData("test", "******", true)] + [InlineData("test", "te*t", true)] + [InlineData("test", "t**st", true)] + [InlineData("test", "s*", false)] + [InlineData("longstringtestmessage", "l*s*t*e*g*", true)] + [InlineData("AdminToolbox", "config_remoteadmin.txt", false)] + [InlineData("config_remoteadmin.txt", "config_remoteadmin.txt", true)] + [InlineData("sizetest", "sizetest1", false)] + public void StringMatchesTest(string input, string pattern, bool expected) { - StringMatchingTemplate[] matchTests = - { - new StringMatchingTemplate("test", "*", true), - new StringMatchingTemplate("test", "te*", true), - new StringMatchingTemplate("test", "*st", true), - new StringMatchingTemplate("test", "******", true), - new StringMatchingTemplate("test", "te*t", true), - new StringMatchingTemplate("test", "t**st", true), - new StringMatchingTemplate("test", "s*", false), - new StringMatchingTemplate("longstringtestmessage", "l*s*t*e*g*", true), - new StringMatchingTemplate("AdminToolbox", "config_remoteadmin.txt", false), - new StringMatchingTemplate("config_remoteadmin.txt", "config_remoteadmin.txt", true), - new StringMatchingTemplate("sizetest", "sizetest1", false) - }; - - for (int i = 0; i < matchTests.Length; i++) - { - try - { - StringMatchingTemplate test = matchTests[i]; - - bool result = Utils.StringMatches(test.input, test.pattern); - - Assert.IsTrue(test.expectedResult == result, $"Failed on test index {i}: Expected \"{test.expectedResult}\", got \"{result}\""); - } - catch (Exception e) - { - Assert.Fail($"Failed on test index {i}: {e}"); - } - } + bool result = Utils.StringMatches(input, pattern); + Assert.Equal(expected, result); } - [TestMethod] - public void CompareVersionStringsTest() - { - CompareVersionTemplate[] versionTests = - { - new CompareVersionTemplate("1.0.0.0", "2.0.0.0", -1), - new CompareVersionTemplate("1.0.0.0", "1.0.0.0", 0), - new CompareVersionTemplate("2.0.0.0", "1.0.0.0", 1), - - new CompareVersionTemplate("1.0", "2.0.0.0", -1), - new CompareVersionTemplate("1.0", "1.0.0.0", -1), // The first version is shorter, so it's lower - new CompareVersionTemplate("2.0", "1.0.0.0", 1), + [Theory] + [InlineData("1.0.0.0", "2.0.0.0", -1)] + [InlineData("1.0.0.0", "1.0.0.0", 0)] + [InlineData("2.0.0.0", "1.0.0.0", 1)] - new CompareVersionTemplate("1.0.0.0", "2.0", -1), - new CompareVersionTemplate("1.0.0.0", "1.0", 1), // The first version is longer, so it's higher - new CompareVersionTemplate("2.0.0.0", "1.0", 1), + [InlineData("1.0", "2.0.0.0", -1)] + [InlineData("1.0", "1.0.0.0", -1)] // The first version is shorter, so it's lower + [InlineData("2.0", "1.0.0.0", 1)] - new CompareVersionTemplate("6.0.0.313", "5.18.0", 1), - new CompareVersionTemplate("5.18.0", "6.0.0.313", -1), + [InlineData("1.0.0.0", "2.0", -1)] + [InlineData("1.0.0.0", "1.0", 1)] // The first version is longer, so it's higher + [InlineData("2.0.0.0", "1.0", 1)] - new CompareVersionTemplate("5.18.0", "5.18.0", 0), - new CompareVersionTemplate("5.18", "5.18.0", -1) // The first version is shorter, so it's lower - }; + [InlineData("6.0.0.313", "5.18.0", 1)] + [InlineData("5.18.0", "6.0.0.313", -1)] - for (int i = 0; i < versionTests.Length; i++) - { - CompareVersionTemplate test = versionTests[i]; - - int result = Utils.CompareVersionStrings(test.firstVersion, test.secondVersion); + [InlineData("5.18.0", "5.18.0", 0)] + [InlineData("5.18", "5.18.0", -1)] // The first version is shorter, so it's lower + public void CompareVersionStringsTest(string firstVersion, string secondVersion, int expected) + { + int result = Utils.CompareVersionStrings(firstVersion, secondVersion); - Assert.IsTrue(test.CheckResult(result), $"Failed on test index {i}: Expected \"{test.expectedResult}\", got \"{result}\""); - } + Assert.Equal(expected, result); } } } From bb50403ca65db8a0386cb2f2f52e4e97bd003ed9 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Mon, 1 Jun 2020 17:58:48 -0400 Subject: [PATCH 11/14] Update copyright year --- LICENSE | 2 +- MultiAdmin/Features/MultiAdminInfo.cs | 2 +- MultiAdmin/Properties/AssemblyInfo.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 967412d..684f7f0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Grover +Copyright (c) 2020 Grover Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MultiAdmin/Features/MultiAdminInfo.cs b/MultiAdmin/Features/MultiAdminInfo.cs index 37fa014..d051cf1 100644 --- a/MultiAdmin/Features/MultiAdminInfo.cs +++ b/MultiAdmin/Features/MultiAdminInfo.cs @@ -50,7 +50,7 @@ public override void OnConfigReload() public void PrintInfo() { - Server.Write($"{nameof(MultiAdmin)} v{Program.MaVersion} (https://github.com/Grover-c13/MultiAdmin/)\nReleased under MIT License Copyright © Grover 2019", ConsoleColor.DarkMagenta); + Server.Write($"{nameof(MultiAdmin)} v{Program.MaVersion} (https://github.com/Grover-c13/MultiAdmin/)\nReleased under MIT License Copyright © Grover 2020", ConsoleColor.DarkMagenta); } public override string GetFeatureDescription() diff --git a/MultiAdmin/Properties/AssemblyInfo.cs b/MultiAdmin/Properties/AssemblyInfo.cs index 1cb699e..cf29517 100644 --- a/MultiAdmin/Properties/AssemblyInfo.cs +++ b/MultiAdmin/Properties/AssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyTitle(nameof(MultiAdmin) + " v" + Program.MaVersion)] [assembly: AssemblyDescription("A program for running a SCP: Secret Laboratory server with additional functionality")] [assembly: AssemblyProduct(nameof(MultiAdmin))] -[assembly: AssemblyCopyright("Copyright © Grover 2019")] +[assembly: AssemblyCopyright("Copyright © Grover 2020")] // Version information for an assembly consists of the following four values: // From 546f2793fbbbdfd87169eb53b3e9ad1f8c084d48 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Mon, 1 Jun 2020 18:05:54 -0400 Subject: [PATCH 12/14] Replace string.Empty & Write synchronously --- MultiAdmin/ConsoleTools/ColoredConsole.cs | 2 +- MultiAdmin/Features/ExitCommand.cs | 2 +- MultiAdmin/Features/GithubGenerator.cs | 10 +++++----- MultiAdmin/Features/HelpCommand.cs | 2 +- MultiAdmin/Features/MultiAdminInfo.cs | 2 +- MultiAdmin/Features/Restart.cs | 2 +- MultiAdmin/Features/RestartNextRound.cs | 2 +- MultiAdmin/Features/StopNextRound.cs | 2 +- MultiAdmin/Server.cs | 2 +- MultiAdmin/ServerIO/InputHandler.cs | 4 ++-- MultiAdmin/ServerIO/ServerSocket.cs | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/MultiAdmin/ConsoleTools/ColoredConsole.cs b/MultiAdmin/ConsoleTools/ColoredConsole.cs index f892173..19acaac 100644 --- a/MultiAdmin/ConsoleTools/ColoredConsole.cs +++ b/MultiAdmin/ConsoleTools/ColoredConsole.cs @@ -182,7 +182,7 @@ public static class ColoredMessageArrayExtensions { private static string JoinTextIgnoreNull(ColoredMessage[] coloredMessages) { - StringBuilder builder = new StringBuilder(string.Empty); + StringBuilder builder = new StringBuilder(""); foreach (ColoredMessage coloredMessage in coloredMessages) { diff --git a/MultiAdmin/Features/ExitCommand.cs b/MultiAdmin/Features/ExitCommand.cs index 716a73c..e59aa12 100644 --- a/MultiAdmin/Features/ExitCommand.cs +++ b/MultiAdmin/Features/ExitCommand.cs @@ -21,7 +21,7 @@ public string GetCommandDescription() public string GetUsage() { - return string.Empty; + return ""; } public void OnCall(string[] args) diff --git a/MultiAdmin/Features/GithubGenerator.cs b/MultiAdmin/Features/GithubGenerator.cs index f272829..641d560 100644 --- a/MultiAdmin/Features/GithubGenerator.cs +++ b/MultiAdmin/Features/GithubGenerator.cs @@ -43,7 +43,7 @@ public void OnCall(string[] args) string path = Utils.GetFullPathSafe(string.Join(" ", args)); - List lines = new List {"# MultiAdmin", string.Empty, "## Features", string.Empty}; + List lines = new List {"# MultiAdmin", "", "## Features", ""}; foreach (Feature feature in Server.features) { @@ -52,17 +52,17 @@ public void OnCall(string[] args) lines.Add($"- {feature.GetFeatureName()}: {feature.GetFeatureDescription()}"); } - lines.Add(string.Empty); + lines.Add(""); lines.Add("## MultiAdmin Commands"); - lines.Add(string.Empty); + lines.Add(""); foreach (ICommand comm in Server.commands.Values) { lines.Add($"- {(comm.GetCommand() + " " + comm.GetUsage()).Trim()}: {comm.GetCommandDescription()}"); } - lines.Add(string.Empty); + lines.Add(""); lines.Add("## Config Settings"); - lines.Add(string.Empty); + lines.Add(""); lines.Add($"Config Option{ColumnSeparator}Value Type{ColumnSeparator}Default Value{ColumnSeparator}Description"); lines.Add($"---{ColumnSeparator}:---:{ColumnSeparator}:---:{ColumnSeparator}:------:"); diff --git a/MultiAdmin/Features/HelpCommand.cs b/MultiAdmin/Features/HelpCommand.cs index 7e27c3a..91a6444 100644 --- a/MultiAdmin/Features/HelpCommand.cs +++ b/MultiAdmin/Features/HelpCommand.cs @@ -48,7 +48,7 @@ public bool PassToGame() public string GetUsage() { - return string.Empty; + return ""; } public override void OnConfigReload() diff --git a/MultiAdmin/Features/MultiAdminInfo.cs b/MultiAdmin/Features/MultiAdminInfo.cs index d051cf1..07c32c8 100644 --- a/MultiAdmin/Features/MultiAdminInfo.cs +++ b/MultiAdmin/Features/MultiAdminInfo.cs @@ -32,7 +32,7 @@ public string GetCommandDescription() public string GetUsage() { - return string.Empty; + return ""; } public void OnServerPreStart() diff --git a/MultiAdmin/Features/Restart.cs b/MultiAdmin/Features/Restart.cs index 1ab1255..ac3947a 100644 --- a/MultiAdmin/Features/Restart.cs +++ b/MultiAdmin/Features/Restart.cs @@ -21,7 +21,7 @@ public string GetCommandDescription() public string GetUsage() { - return string.Empty; + return ""; } public void OnCall(string[] args) diff --git a/MultiAdmin/Features/RestartNextRound.cs b/MultiAdmin/Features/RestartNextRound.cs index da0af2e..8348689 100644 --- a/MultiAdmin/Features/RestartNextRound.cs +++ b/MultiAdmin/Features/RestartNextRound.cs @@ -35,7 +35,7 @@ public string GetCommand() public string GetUsage() { - return string.Empty; + return ""; } public void OnRoundEnd() diff --git a/MultiAdmin/Features/StopNextRound.cs b/MultiAdmin/Features/StopNextRound.cs index 67ab5ae..36a03a8 100644 --- a/MultiAdmin/Features/StopNextRound.cs +++ b/MultiAdmin/Features/StopNextRound.cs @@ -35,7 +35,7 @@ public string GetCommand() public string GetUsage() { - return string.Empty; + return ""; } public void OnRoundEnd() diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs index 624ada3..d9772ba 100644 --- a/MultiAdmin/Server.cs +++ b/MultiAdmin/Server.cs @@ -66,7 +66,7 @@ public Server(string serverId = null, string configLocation = null, uint? port = // Set port this.port = port; - logDir = Utils.GetFullPathSafe(Path.Combine(string.IsNullOrEmpty(serverDir) ? string.Empty : serverDir, serverConfig.LogLocation.Value)); + logDir = Utils.GetFullPathSafe(Path.Combine(string.IsNullOrEmpty(serverDir) ? "" : serverDir, serverConfig.LogLocation.Value)); // Register all features RegisterFeatures(); diff --git a/MultiAdmin/ServerIO/InputHandler.cs b/MultiAdmin/ServerIO/InputHandler.cs index 2a04b0c..749b46a 100644 --- a/MultiAdmin/ServerIO/InputHandler.cs +++ b/MultiAdmin/ServerIO/InputHandler.cs @@ -91,8 +91,8 @@ public static string GetInputLineNew(Server server, ShiftingList prevMessages) if (server.ServerConfig.RandomInputColors.Value) RandomizeInputColors(); - string curMessage = string.Empty; - string message = string.Empty; + string curMessage = ""; + string message = ""; int messageCursor = 0; int prevMessageCursor = -1; StringSections curSections = null; diff --git a/MultiAdmin/ServerIO/ServerSocket.cs b/MultiAdmin/ServerIO/ServerSocket.cs index 796370f..a9c9d7b 100644 --- a/MultiAdmin/ServerIO/ServerSocket.cs +++ b/MultiAdmin/ServerIO/ServerSocket.cs @@ -112,7 +112,7 @@ public void SendMessage(string message) try { - networkStream.WriteAsync(messageBuffer, 0, actualMessageLength + IntBytes, disposeCancellationSource.Token).Wait(); + networkStream.Write(messageBuffer, 0, actualMessageLength + IntBytes); } catch (Exception e) { From 26acca40f9ae154e24e3eb0356db7a7c4856c81d Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Mon, 1 Jun 2020 23:40:01 -0400 Subject: [PATCH 13/14] Update StringExtension tests & add more - Update StringExtension tests to better use XUnit - Add more StringExtension tests - Fix StringExtension behaviour --- MultiAdmin/Utility/StringExtensions.cs | 23 +++++++-- .../Utility/StringExtensionsTests.cs | 49 +++++++++++++++---- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/MultiAdmin/Utility/StringExtensions.cs b/MultiAdmin/Utility/StringExtensions.cs index 1cf0b6f..94722c9 100644 --- a/MultiAdmin/Utility/StringExtensions.cs +++ b/MultiAdmin/Utility/StringExtensions.cs @@ -6,11 +6,14 @@ public static class StringExtensions { public static bool Equals(this string input, string value, int startIndex, int count) { - if (value == null) - throw new ArgumentNullException(nameof(value)); - if (startIndex < 0 || startIndex > input.Length) + if (input == null && value == null) + return true; + if (input == null || value == null) + return false; + + if (startIndex < 0 || startIndex >= input.Length) throw new ArgumentOutOfRangeException(nameof(startIndex)); - if (count < 0 || startIndex > input.Length - count) + if (count < 0 || count > value.Length || startIndex > input.Length - count) throw new ArgumentOutOfRangeException(nameof(count)); for (int i = 0; i < count; i++) @@ -26,7 +29,17 @@ public static bool Equals(this string input, string value, int startIndex, int c public static bool Equals(this string input, string value, int startIndex) { - return Equals(input, value, startIndex, input.Length - startIndex); + if (input == null && value == null) + return true; + if (input == null || value == null) + return false; + + int length = input.Length - startIndex; + + if (length < value.Length) + throw new ArgumentOutOfRangeException(nameof(value)); + + return Equals(input, value, startIndex, length); } } } diff --git a/MultiAdminTests/Utility/StringExtensionsTests.cs b/MultiAdminTests/Utility/StringExtensionsTests.cs index 24bcda2..30fdc74 100644 --- a/MultiAdminTests/Utility/StringExtensionsTests.cs +++ b/MultiAdminTests/Utility/StringExtensionsTests.cs @@ -1,23 +1,52 @@ +using System; using MultiAdmin.Utility; using Xunit; +using Xunit.Sdk; namespace MultiAdminTests.Utility { public class StringExtensionsTests { - [Fact] - public void EqualsTest() + [Theory] + [InlineData("test", "test", 0)] + [InlineData("test", "test", 0, 4)] + [InlineData("test", "st", 2)] + [InlineData("test", "te", 0, 2)] + [InlineData("test", "es", 1, 2)] + [InlineData(null, null, 0)] + [InlineData(null, null, 0, 1)] + public void EqualsTest(string main, string section, int startIndex, int count = -1) { - Assert.True("test".Equals("test", startIndex: 0)); - Assert.False("test".Equals("other", startIndex: 0)); - - Assert.True("test".Equals("st", startIndex: 2)); - Assert.True("test".Equals("te", 0, 2)); + Assert.True(count < 0 ? main.Equals(section, startIndex) : main.Equals(section, startIndex, count)); + } - Assert.False("test".Equals("te", startIndex: 2)); - Assert.False("test".Equals("st", 0, 2)); + [Theory] + [InlineData("test", "other", 0, 4)] + [InlineData("test", "te", 2)] + [InlineData("test", "st", 0, 2)] + [InlineData("test", null, 0)] + [InlineData(null, "test", 0)] + [InlineData("test", null, 0, 1)] + [InlineData(null, "test", 0, 1)] + public void NotEqualsTest(string main, string section, int startIndex, int count = -1) + { + Assert.False(count < 0 ? main.Equals(section, startIndex) : main.Equals(section, startIndex, count)); + } - Assert.True("test".Equals("es", 1, 2)); + [Theory] + [InlineData(typeof(ArgumentOutOfRangeException), "longtest", "test", 1, 5)] + [InlineData(typeof(ArgumentOutOfRangeException), "test", "st", 3)] + [InlineData(typeof(ArgumentOutOfRangeException), "test", "te", -1)] + [InlineData(typeof(ArgumentOutOfRangeException), "test", "es", 4)] + public void EqualsThrowsTest(Type expected, string main, string section, int startIndex, int count = -1) + { + Assert.Throws(expected, () => + { + if (count < 0) + main.Equals(section, startIndex); + else + main.Equals(section, startIndex, count); + }); } } } From 6117579b42b19e206092d9126dabafc1e8d8b701 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Tue, 2 Jun 2020 01:34:26 -0400 Subject: [PATCH 14/14] Fix exception when the console is too small --- MultiAdmin/ServerIO/InputHandler.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/MultiAdmin/ServerIO/InputHandler.cs b/MultiAdmin/ServerIO/InputHandler.cs index 749b46a..de43bc5 100644 --- a/MultiAdmin/ServerIO/InputHandler.cs +++ b/MultiAdmin/ServerIO/InputHandler.cs @@ -58,7 +58,15 @@ public static void Write(Server server) break; } - string message = server.ServerConfig.UseNewInputSystem.Value ? GetInputLineNew(server, prevMessages) : Console.ReadLine(); + string message; + if (server.ServerConfig.UseNewInputSystem.Value && SectionBufferWidth - TotalIndicatorLength > 0) + { + message = GetInputLineNew(server, prevMessages); + } + else + { + message = Console.ReadLine(); + } if (string.IsNullOrEmpty(message)) continue; @@ -196,7 +204,7 @@ public static string GetInputLineNew(Server server, ShiftingList prevMessages) // If the message has changed, re-write it to the console if (CurrentMessage != message) { - if (message.Length > SectionBufferWidth) + if (message.Length > SectionBufferWidth && SectionBufferWidth - TotalIndicatorLength > 0) { curSections = GetStringSections(message);