commit d5cfc2bd4fa2ee76c6a797eeee9be96cd9f2ef2e Author: noyciy7037 Date: Fri Nov 4 22:05:08 2022 +0900 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd8d5cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,windows,linux + +### 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* + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### 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.toptal.com/developers/gitignore/api/visualstudiocode,windows,linux diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0ba4612 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // IntelliSense を使用して利用可能な属性を学べます。 + // 既存の属性の説明をホバーして表示します。 + // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "runtimeExecutable": "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe", + "url": "http://localhost:5500", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/ExtinctionOnline.Server/.gitignore b/ExtinctionOnline.Server/.gitignore new file mode 100644 index 0000000..42b4af1 --- /dev/null +++ b/ExtinctionOnline.Server/.gitignore @@ -0,0 +1,620 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/visualstudio,visualstudiocode,csharp,windows +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio,visualstudiocode,csharp,windows + +### 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/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +### VisualStudioCode ### +!.vscode/*.code-snippets + +# Local History for Visual Studio Code + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# Support for Project snippet scope + +### 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 + +# Windows shortcuts +*.lnk + +### VisualStudio ### + +# User-specific files + +# User-specific files (MonoDevelop/Xamarin Studio) + +# Mono auto generated files + +# Build results + +# Visual Studio 2015/2017 cache/options directory +# Uncomment if you have tasks that create the project's static files in wwwroot + +# Visual Studio 2017 auto generated files + +# MSTest test Results + +# NUnit + +# Build Results of an ATL Project + +# Benchmark Results + +# .NET Core + +# ASP.NET Scaffolding + +# StyleCop + +# Files built by Visual Studio + +# Chutzpah Test files + +# Visual C++ cache files + +# Visual Studio profiler + +# Visual Studio Trace Files + +# TFS 2012 Local Workspace + +# Guidance Automation Toolkit + +# ReSharper is a .NET coding add-in + +# TeamCity is a build add-in + +# DotCover is a Code Coverage Tool + +# AxoCover is a Code Coverage Tool + +# Coverlet is a free, cross platform Code Coverage Tool + +# Visual Studio code coverage results + +# NCrunch + +# MightyMoose + +# Web workbench (sass) + +# Installshield output folder + +# DocProject is a documentation generator add-in + +# Click-Once directory + +# Publish Web Output +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted + +# 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 + +# NuGet Packages +# NuGet Symbol Packages +# The packages folder can be ignored because of Package Restore +# except build/, which is used as an MSBuild target. +# Uncomment if necessary however generally it will be regenerated when needed +# NuGet v3's project.json files produces more ignorable files + +# Microsoft Azure Build Output + +# Microsoft Azure Emulator + +# Windows Store app package directories and files + +# Visual Studio cache files +# files ending in .cache can be ignored +# but keep track of directories ending in .cache + +# Others + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) + +# RIA/Silverlight projects + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) + +# SQL Server files + +# Business Intelligence projects + +# Microsoft Fakes + +# GhostDoc plugin setting file + +# Node.js Tools for Visual Studio + +# Visual Studio 6 build log + +# Visual Studio 6 workspace options file + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) + +# Visual Studio 6 technical files + +# Visual Studio LightSwitch build output + +# Paket dependency manager + +# FAKE - F# Make + +# CodeRush personal settings + +# Python Tools for Visual Studio (PTVS) + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio + +# Telerik's JustMock configuration file + +# BizTalk build output + +# OpenCover UI analysis results + +# Azure Stream Analytics local run output + +# MSBuild Binary and Structured Log + +# NVidia Nsight GPU debugger configuration file + +# MFractors (Xamarin productivity tool) working folder + +# Local History for Visual Studio + +# Visual Studio History (VSHistory) files + +# BeatPulse healthcheck temp database + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 + +# Ionide (cross platform F# VS Code tools) working folder + +# Fody - auto-generated XML schema + +# VS Code files for those working on multiple tools + +# Local History for Visual Studio Code + +# Windows Installer files from build outputs + +# JetBrains Rider + +### VisualStudio Patch ### +# Additional files built by Visual Studio + +# End of https://www.toptal.com/developers/gitignore/api/visualstudio,visualstudiocode,csharp,windows diff --git a/ExtinctionOnline.Server/ClientData/ClientInfo.cs b/ExtinctionOnline.Server/ClientData/ClientInfo.cs new file mode 100644 index 0000000..a0c076b --- /dev/null +++ b/ExtinctionOnline.Server/ClientData/ClientInfo.cs @@ -0,0 +1,17 @@ +using Fleck; + +namespace ExtinctionOnline.Server.ClientData +{ + public class ClientInfo + { + public readonly string ClientId; + public readonly IWebSocketConnection Socket; + public string? RoomId = null; + + public ClientInfo(string id, IWebSocketConnection socket) + { + ClientId = id; + Socket = socket; + } + } +} diff --git a/ExtinctionOnline.Server/Commands.cs b/ExtinctionOnline.Server/Commands.cs new file mode 100644 index 0000000..bcc3ad1 --- /dev/null +++ b/ExtinctionOnline.Server/Commands.cs @@ -0,0 +1,35 @@ +using ExtinctionOnline.Server.ClientData; +using ExtinctionOnline.Server.Communication; +using ExtinctionOnline.Server.Room; + +namespace ExtinctionOnline.Server +{ + internal class Commands + { + public static void SystemCommand(MessageData messageData, ClientInfo client, string original) + { + if (messageData.RoomDataMessage == null) throw new NullReferenceException("roomData is needed."); + var roomData = messageData.RoomDataMessage; + RoomController.JoinToRoom(roomData, client); + } + + public static void Game(MessageData messageData, ClientInfo client, string original) + { + if (messageData.RoomDataMessage == null) throw new NullReferenceException("roomData is needed."); + var roomData = messageData.RoomDataMessage; + if (roomData.RoomId == null) throw new NullReferenceException("roomData.RoomId is needed."); + if (messageData.DeliveryTo == null) throw new NullReferenceException("deliveryTo is needed."); + DeliveryTo deliveryTo = messageData.DeliveryTo; + if (deliveryTo.Type == null) throw new NullReferenceException("deliveryTo.type is needed."); + if (deliveryTo.Type == "ROOM") + { + Server.s_rooms[roomData.RoomId].MessageToAll(original); + return; + } + if (deliveryTo.ClientId == null) throw new NullReferenceException("deliveryTo.clientId is needed."); + ClientInfo? toClient = Server.s_rooms[roomData.RoomId]._clients.Find(it => it.ClientId == deliveryTo.ClientId); + if (toClient == null) throw new Exception($"Client {deliveryTo.ClientId} was not found."); + toClient.Socket.Send(original); + } + } +} \ No newline at end of file diff --git a/ExtinctionOnline.Server/Communication/JsonData.cs b/ExtinctionOnline.Server/Communication/JsonData.cs new file mode 100644 index 0000000..dfa0640 --- /dev/null +++ b/ExtinctionOnline.Server/Communication/JsonData.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace ExtinctionOnline.Server.Communication +{ + public class MessageData + { + [JsonPropertyName("messageType")] + // SYSTEM / GAME + public string? MessageType { get; set; } + + [JsonPropertyName("from")] + // SERVER / client-id + public string? From { get; set; } + + [JsonPropertyName("roomData")] + public RoomMessageData? RoomDataMessage { get; set; } + + [JsonPropertyName("deliveryTo")] + public DeliveryTo? DeliveryTo { get; set; } + } + + public class RoomMessageData + { + [JsonPropertyName("roomId")] + public string? RoomId { get; set; } + + [JsonPropertyName("roomName")] + public string? RoomName { get; set; } + } + + public class DeliveryTo + { + [JsonPropertyName("type")] + // ROOM / CLIENT / SERVER + public string? Type { get; set; } + + [JsonPropertyName("clientId")] + public string? ClientId { get; set; } + } +} \ No newline at end of file diff --git a/ExtinctionOnline.Server/Communication/JsonUtil.cs b/ExtinctionOnline.Server/Communication/JsonUtil.cs new file mode 100644 index 0000000..6553a22 --- /dev/null +++ b/ExtinctionOnline.Server/Communication/JsonUtil.cs @@ -0,0 +1,19 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Unicode; + +namespace ExtinctionOnline.Server.Communication +{ + public class JsonUtil + { + public static JsonSerializerOptions GetJsonOptions() + { + return new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + } + } +} diff --git a/ExtinctionOnline.Server/ExtinctionOnline.Server.csproj b/ExtinctionOnline.Server/ExtinctionOnline.Server.csproj new file mode 100644 index 0000000..16ea84a --- /dev/null +++ b/ExtinctionOnline.Server/ExtinctionOnline.Server.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/ExtinctionOnline.Server/ExtinctionOnline.Server.sln b/ExtinctionOnline.Server/ExtinctionOnline.Server.sln new file mode 100644 index 0000000..3c2ed62 --- /dev/null +++ b/ExtinctionOnline.Server/ExtinctionOnline.Server.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtinctionOnline.Server", "ExtinctionOnline.Server.csproj", "{629D13FE-2630-4D6F-805F-8D5036C8E6BB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {629D13FE-2630-4D6F-805F-8D5036C8E6BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {629D13FE-2630-4D6F-805F-8D5036C8E6BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {629D13FE-2630-4D6F-805F-8D5036C8E6BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {629D13FE-2630-4D6F-805F-8D5036C8E6BB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {31833EBA-B90B-4BD3-9694-19C19A3BC412} + EndGlobalSection +EndGlobal diff --git a/ExtinctionOnline.Server/GlobalSuppressions.cs b/ExtinctionOnline.Server/GlobalSuppressions.cs new file mode 100644 index 0000000..2f69416 --- /dev/null +++ b/ExtinctionOnline.Server/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0060:未使用のパラメーターを削除します", Justification = "<保留中>", Scope = "member", Target = "~M:ExtinctionOnline.Server.Commands.Room(ExtinctionOnline.Server.Communication.MessageData,ExtinctionOnline.Server.ClientData.ClientInfo,System.String)")] diff --git a/ExtinctionOnline.Server/Logo.cs b/ExtinctionOnline.Server/Logo.cs new file mode 100644 index 0000000..ed6f377 --- /dev/null +++ b/ExtinctionOnline.Server/Logo.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ExtinctionOnline.Server +{ + internal class Logo + { + public static string LogoStr = " :::::::::: ::: ::: ::::::::::: ::::::::::: :::: ::: :::::::: ::::::::::: ::::::::::: :::::::: :::: :::\n" + + " :+: :+: :+: :+: :+: :+:+: :+: :+: :+: :+: :+: :+: :+: :+:+: :+: \n" + + " +:+ +:+ +:+ +:+ +:+ :+:+:+ +:+ +:+ +:+ +:+ +:+ +:+ :+:+:+ +:+ \n" + + " +#++:++# +#++:+ +#+ +#+ +#+ +:+ +#+ +#+ +#+ +#+ +#+ +:+ +#+ +:+ +#+ \n" + + " +#+ +#+ +#+ +#+ +#+ +#+ +#+#+# +#+ +#+ +#+ +#+ +#+ +#+ +#+#+# \n" + + " #+# #+# #+# #+# #+# #+# #+#+# #+# #+# #+# #+# #+# #+# #+# #+#+# \n" + + "########## ### ### ### ########### ### #### ######## ### ########### ######## ### #### \n" + + " :::::::: :::: ::: ::: ::::::::::: :::: ::: :::::::::: ______ ____ \n" + + " :+: :+: :+:+: :+: :+: :+: :+:+: :+: :+: / ____/ __ / __ \\____ \n" + + " +:+ +:+ :+:+:+ +:+ +:+ +:+ :+:+:+ +:+ +:+ / __/ | |/_/ / / / / __ \\ \n" + + " +#+ +:+ +#+ +:+ +#+ +#+ +#+ +#+ +:+ +#+ +#++:++# / /____> < / /_/ / / / / \n" + + " +#+ +#+ +#+ +#+#+# +#+ +#+ +#+ +#+#+# +#+ /_____/_/|_| \\____/_/ /_/ \n" + + "#+# #+# #+# #+#+# #+# #+# #+# #+#+# #+# / /_(_)___ _____ / (_)___ ___ \n" + + "######## ### #### ########## ########### ### #### ########## / __/ / __ \\/ ___/ / / / __ \\/ _ \\\n" + + " :::::::: :::::::::: ::::::::: ::: ::: :::::::::: ::::::::: / /_/ / / / / /__ / / / / / / __/\n" + + " :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: \\__/_/_/ /_/\\___/ /_/_/_/ /_/\\___/ \n" + + " +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ / /_(_)___ ____ \n" + + " +#++:++#++ +#++:++# +#++:++#: +#+ +:+ +#++:++# +#++:++#: / __/ / __ \\/ __ \\\n" + + " +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ / /_/ / /_/ / / / /\n" + + "#+# #+# #+# #+# #+# #+#+#+# #+# #+# #+# \\__/_/\\____/_/ /_/ \n" + + "######## ########## ### ### ### ########## ### ### \n"; + + } +} diff --git a/ExtinctionOnline.Server/Room/RoomController.cs b/ExtinctionOnline.Server/Room/RoomController.cs new file mode 100644 index 0000000..89f14df --- /dev/null +++ b/ExtinctionOnline.Server/Room/RoomController.cs @@ -0,0 +1,33 @@ +using ExtinctionOnline.Server.ClientData; +using ExtinctionOnline.Server.Communication; + +namespace ExtinctionOnline.Server.Room +{ + internal class RoomController + { + internal static void JoinToRoom(RoomMessageData roomData, ClientInfo client) + { + if (client.RoomId != null) + { + throw new Exception("You are already in a room."); + } + else if (roomData.RoomId == null) + { + string roomGuid = Guid.NewGuid().ToString(); + RoomData room = new RoomData(roomGuid, roomData.RoomName ?? roomGuid); + room.AddClient(client); + Server.s_lobby.Remove(client); + Server.s_rooms.Add(roomGuid, room); + } + else if (Server.s_rooms.ContainsKey(roomData.RoomId)) + { + Server.s_lobby.Remove(client); + Server.s_rooms[roomData.RoomId].AddClient(client); + } + else + { + throw new Exception($"Rooms that do not exist: {roomData.RoomId}"); + } + } + } +} diff --git a/ExtinctionOnline.Server/Room/RoomData.cs b/ExtinctionOnline.Server/Room/RoomData.cs new file mode 100644 index 0000000..a88dc2b --- /dev/null +++ b/ExtinctionOnline.Server/Room/RoomData.cs @@ -0,0 +1,54 @@ +using ExtinctionOnline.Server.ClientData; +using ExtinctionOnline.Server.Communication; +using System.Text.Json; + +namespace ExtinctionOnline.Server.Room +{ + internal class RoomData + { + internal string _id; + internal string _name; + internal bool _visibility = true; + internal List _clients = new(); + + internal RoomData(string id, string name) + { + _id = id; + _name = name; + } + + internal void AddClient(ClientInfo client) + { + client.RoomId = _id; + _clients.Add(client); + MessageToAll(JsonSerializer.Serialize(new MessageData + { + MessageType = "SYSTEM", + From = client.ClientId, + DeliveryTo = new DeliveryTo { Type = "ROOM" }, + RoomDataMessage = new RoomMessageData { RoomId = _id, RoomName = _name } + }, + JsonUtil.GetJsonOptions())); + } + + internal void RemoveClient(ClientInfo client) + { + client.RoomId = null; + _clients.Remove(client); + MessageToAll(JsonSerializer.Serialize(new MessageData + { + MessageType = "SYSTEM", + From = client.ClientId, + DeliveryTo = new DeliveryTo { Type = "ROOM" }, + RoomDataMessage = new RoomMessageData { RoomId = null } + }, + JsonUtil.GetJsonOptions())); + } + + internal void MessageToAll(string message) + { + for (int i = 0; i < _clients.Count; ++i) + _clients[i].Socket.Send(message); + } + } +} diff --git a/ExtinctionOnline.Server/Server.cs b/ExtinctionOnline.Server/Server.cs new file mode 100644 index 0000000..40d133a --- /dev/null +++ b/ExtinctionOnline.Server/Server.cs @@ -0,0 +1,245 @@ +using ExtinctionOnline.Server.ClientData; +using ExtinctionOnline.Server.Communication; +using ExtinctionOnline.Server.Room; +using Fleck; +using System.Collections.Generic; +using System.Text.Json; + +namespace ExtinctionOnline.Server +{ + /// + /// Extinction Online のサーバー。 + /// + public class Server + { + /// + /// すべてのクライアントのリスト + /// + internal static readonly List s_clients = new(); + + /// + /// Lobbyに居るクライアントのリスト + /// + internal static readonly List s_lobby = new(); + + /// + /// それぞれのRoomのIDとRoomのリスト + /// + internal static readonly Dictionary s_rooms = new(); + + /// + /// メッセージ処理の辞書。呼び出し用 + /// + static readonly Dictionary> s_messageTypes = new() + { + { "SYSTEM", Commands.SystemCommand }, + { "GAME", Commands.Game }, + }; + + /// + /// サーバーのメインメソッド。 + /// + /// + /// 外部からの呼び出し非推奨 + /// コマンド実行時の引数 + public static void Main(string[] args) + { + Console.WriteLine(Logo.LogoStr); + // TODO:ポート変更を可能に + var server = new WebSocketServer("ws://0.0.0.0:1234"); + server.Start(socket => + { + socket.OnOpen = () => OnOpen(socket); + socket.OnClose = () => OnClose(socket); + socket.OnMessage = message => OnMessage(socket, message); + }); + + // 標準入力を受付 + string? data = ""; + while (data == null || !data.StartsWith("exit")) + { + data = Console.ReadLine(); + } + foreach (var c in new List(s_clients)) + { + c.Socket.Close(); + } + server.Dispose(); + } + + /// + /// クライアントとの接続が開いたとき用のHook。 + /// + /// 外部からの呼び出し非推奨 + /// クライアントとのソケット + static void OnOpen(IWebSocketConnection socket) + { + // Guidを生成してクライアントに送信、記録 + Guid clientGuid = Guid.NewGuid(); + s_clients.Add(new ClientInfo(clientGuid.ToString(), socket)); + socket.Send(JsonSerializer.Serialize(new MessageData { MessageType = "SYSTEM", DeliveryTo = new DeliveryTo { Type = "CLIENT", ClientId = clientGuid.ToString() } }, + JsonUtil.GetJsonOptions())); + Console.WriteLine($"Open connection: {socket.ConnectionInfo.ClientIpAddress}:{socket.ConnectionInfo.ClientPort}"); + } + + /// + /// クライアントとの接続が閉じたとき用のHook。 + /// + /// 外部からの呼び出し非推奨 + /// クライアントとのソケット + static void OnClose(IWebSocketConnection socket) + { + var client = s_clients.Find(it => it.Socket.GetHashCode() == socket.GetHashCode()); + if (client != null) + { + if (client.RoomId != null) + { + string roomId = client.RoomId; + lock (((System.Collections.IDictionary)s_rooms).SyncRoot) + { + s_rooms[roomId].RemoveClient(client); + if (s_rooms[roomId]._clients.Count <= 0) s_rooms.Remove(roomId); + } + } + else + { + s_lobby.Remove(client); + } + lock (((System.Collections.ICollection)s_clients).SyncRoot) + { + s_clients.Remove(client); + } + } + Console.WriteLine($"Close connection: {socket.ConnectionInfo.ClientIpAddress}:{socket.ConnectionInfo.ClientPort}"); + } + + /// + /// クライアントからメッセージを受け取ったとき用のHook。 + /// + /// 外部からの呼び出し非推奨 + /// クライアントとのソケット + /// 受け取ったメッセージ + static void OnMessage(IWebSocketConnection socket, string message) + { + Console.WriteLine(socket.GetHashCode()); + Console.WriteLine(message); + try + { + MessageData? messageData = JsonSerializer.Deserialize(message, JsonUtil.GetJsonOptions()); + if (messageData == null) throw new NullReferenceException("messageData(Json body) is needed."); + if (messageData.From == null) throw new NullReferenceException("from is needed."); + if (messageData.MessageType == null) throw new NullReferenceException("messageType is needed."); + var client = s_clients.Find(it => it.Socket.GetHashCode() == socket.GetHashCode()); + if (client == null) throw new NullReferenceException("client is null. Not found in client list."); + if (client.ClientId != messageData.From) throw new Exception("ClientId does not match."); + s_messageTypes[messageData.MessageType].Invoke(messageData, client, message); + } + catch (JsonException) + { + socket.Send("Invalid message. It must be JSON. Close connection."); + socket.Close(); + } + catch (Exception e) + { + socket.Send($"Invalid message. {e.Message}; Close connection."); + socket.Close(); + } + } + /* + /// + /// すべてのクライアントにメッセージを送信する。 + /// + /// 送信するメッセージ本文 + internal static void SendMessageToAll(string data) + { + foreach (var c in s_clients) + { + c.Socket.Send(data); + } + } + + /// + /// 指定したRoom内の指定したクライアント以外にメッセージを送信する。 + /// + /// RoomのID + /// 送信するメッセージ本文 + /// 除外するクライアントのリスト + internal static void SendMessageToWithout(string roomId, string data, List without) + { + foreach (var c in s_rooms[roomId]) + { + if (!without.Contains(c)) + c.Socket.Send(data); + } + } + + /// + /// 指定したクライアントにメッセージを送信する。 + /// + /// 送信するメッセージ本文 + /// クライアントのリスト + internal static void SendMessageToListClient(string data, List clients) + { + foreach (var c in clients) + { + c.Socket.Send(data); + } + } + + /// + /// クライアントを指定したRoomに参加させる。 + /// + /// RoomのID + /// 参加するClient + /// 存在しないRoomを作成するか + public static void JoinClientToRoom(string roomId, ClientInfo client, bool doCreate) + { + if (s_rooms.ContainsKey(roomId)) + { + s_rooms[roomId].Add(client); + } + else + { + if (doCreate) + { + s_rooms.Add(roomId, new List() { client }); + } + else + { + throw new Exception("The specified Room does not exist."); + } + } + client.RoomId = roomId; + client.Socket.Send(JsonSerializer.Serialize(new MessageData + { + MessageType = "Room", + RoomData = new RoomMessageData() { RequestType = "Joined", RoomId = roomId } + }, JsonUtil.GetJsonOptions())); + if (s_rooms[roomId].Count > 1) + { + List clients = new List(); + foreach (var existingClient in s_rooms[roomId]) + { + if (client.ClientId != existingClient.ClientId) + clients.Add(new ClientMessageData() { ClientId = existingClient.ClientId }); + } + client.Socket.Send(JsonSerializer.Serialize(new MessageData + { + MessageType = "Room", + RoomData = new RoomMessageData() { RequestType = "ClientsInfoNotification", RoomId = roomId }, + ClientsInfo = clients, + })); + } + SendMessageToWithout(roomId, JsonSerializer.Serialize(new MessageData + { + MessageType = "Room", + ClientId = client.ClientId, + RoomData = new RoomMessageData() + { + RequestType = "ClientJoined", + RoomId = roomId + } + }, JsonUtil.GetJsonOptions()), new() { client }); + }*/ + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f280c01 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Extinction Online +とあるカードゲーム(ローカル)をオンライン実装したい計画。 + +## クライアント +/index.htmlをブラウザで開く。主にJavaScript製。 + +## サーバー +/ExtinctionOnline.Server/以下。C#製で通信はWebSocket。 + +## 今後の計画 +- とりあえず動かす +- SSL対応とか +- パフォーマンス改善 +- チート防止(ホストクライアントを除くプレイヤーの不正防止) +- 移植(Unityとか使ってグラフィックも凝りたい) \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..ff1af45 --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + Document + + + + + \ No newline at end of file diff --git a/static/js/cards.js b/static/js/cards.js new file mode 100644 index 0000000..173a650 --- /dev/null +++ b/static/js/cards.js @@ -0,0 +1,111 @@ +const cardTypes = { + unknown: { + id: "unknown", + count: -1 + }, + one: { + id: "one", + prefix: "A", + count: 7 + }, + two: { + id: "two", + prefix: "B", + count: 6 + }, + three: { + id: "three", + prefix: "C", + count: 5 + }, + four: { + id: "four", + prefix: "D", + count: 4 + }, + five: { + id: "five", + prefix: "E", + count: 3 + }, + six: { + id: "six", + prefix: "F", + count: 2 + }, + seven: { + id: "seven", + prefix: "G", + count: 1 + }, + rob: { + id: "rob", + prefix: "J", + count: 4 + }, + robKill: { + id: "robKill", + prefix: "K", + count: 1 + }, + recycle: { + id: "recycle", + prefix: "L", + count: 4 + }, + reincarnation: { + id: "reincarnation", + prefix: "M", + count: 1 + }, + seeThrough: { + id: "seeThrough", + prefix: "P", + count: 4 + }, + clairvoyant: { + id: "clairvoyant", + prefix: "Q", + count: 1 + }, + bacteria: { + id: "bacteria", + prefix: "U", + count: 3 + }, + extinction: { + id: "extinction", + prefix: "H", + count: 4 + }, + annihilation: { + id: "annihilation", + prefix: "I", + count: 1 + }, + drop: { + id: "drop", + prefix: "N", + count: 3 + }, + dumping: { + id: "dumping", + prefix: "O", + count: 1 + }, + trade: { + id: "trade", + prefix: "R", + count: 2 + }, + shieldRoom: { + id: "shieldRoom", + prefix: "S", + count: 1 + }, + lose: { + id: "lose", + prefix: "T", + count: 1 + } +} \ No newline at end of file diff --git a/static/js/game-client.js b/static/js/game-client.js new file mode 100644 index 0000000..ba0405b --- /dev/null +++ b/static/js/game-client.js @@ -0,0 +1,44 @@ +let clientId = null; +let controller = null; +let roomData = null; + +const commands = { gameStart: "GameStart", addCard:"AddCard"}; + +function onSystemMessage(obj) { + if (clientId === null) { + clientId = obj.deliveryTo.clientId; + } else if (obj.from === clientId) { + roomData = obj.roomData; + controller.joinNewPlayer(obj); + } else { + controller.joinNewPlayer(obj); + } +} + +function joinToRoom(id, name) { + if (id == null) + controller = new HostController(); + else + controller = new PlayerController(); + + let obj = { + messageType: "SYSTEM", + from: clientId, + roomData: { + roomId: id, + roomName: name + } + }; + console.log(obj); + socket.send(JSON.stringify(obj)); +} + +class Card { + cardType; + id; + + constructor(type, idIndex) { + this.cardType = type; + this.id = `${type.prefix}-${type.count}-${idIndex}`; + } +} \ No newline at end of file diff --git a/static/js/host-controller.js b/static/js/host-controller.js new file mode 100644 index 0000000..b6d036f --- /dev/null +++ b/static/js/host-controller.js @@ -0,0 +1,79 @@ +const firstCardCount = 5; + +class HostController { + playerController = null; + deck = new Array(); + players = new Array(); + + constructor() { + this.playerController = new PlayerController(); + } + + gameStart() { + socket.send(JSON.stringify({ + from: clientId, + messageType: "GAME", + roomData: roomData, + deliveryTo: { + type: "ROOM" + }, + body: { + commands: [{ name: commands.gameStart }] + } + })); + Object.keys(cardTypes).forEach(key => { + for (let i = 0; i < cardTypes[key].count; ++i) + this.deck.push(new Card(cardTypes[key], i + 1)); + }); + // 山札をシャッフル + let currentIndex = this.deck.length; + while (currentIndex) { + let j = Math.floor(Math.random() * currentIndex); + let t = this.deck[--currentIndex]; + this.deck[currentIndex] = this.deck[j]; + this.deck[j] = t; + } + for (let i = 0; i < this.players.length; ++i) { + let messageObject = { + from: clientId, + messageType: "GAME", + roomData: roomData, + deliveryTo: { + type: "CLIENT", + clientId: this.players[i].clientId + }, + body: { + commands: [] + } + }; + for (let j = 0; j < firstCardCount; ++j) { + messageObject.body.commands.push({ + name: commands.addCard, + target: this.players[i].clientId, + args: [ + this.deck[0].cardType.id, + this.deck[0].id + ] + }); + this.deck.splice(0, 1); + } + socket.send(JSON.stringify(messageObject)); + } + } + + onGameMessage(message) { + } + + joinNewPlayer(obj) { + this.players.push(new Player(obj.from)); + } +} + +class Player { + clientId; + cards = new Array(); + + constructor(id) { + this.clientId = id; + } +} \ No newline at end of file diff --git a/static/js/player-controller.js b/static/js/player-controller.js new file mode 100644 index 0000000..e16e870 --- /dev/null +++ b/static/js/player-controller.js @@ -0,0 +1,19 @@ +class PlayerController { + hostClientId; + + constructor() { + } + + onGameMessage(message){ + message.body.commands.forEach(command=>{ + switch(command.name){ + case commands.gameStart: + this.hostClientId = message.from; + break; + } + }); + } + + joinNewPlayer(obj){ + } +} \ No newline at end of file diff --git a/static/js/websocket.js b/static/js/websocket.js new file mode 100644 index 0000000..f115c4d --- /dev/null +++ b/static/js/websocket.js @@ -0,0 +1,18 @@ +// WebSocket 接続を作成 +const socket = new WebSocket('ws://localhost:1234'); + +// 接続が開いたときのイベント +socket.addEventListener('open', function (event) { + +}); + +// メッセージの待ち受け +socket.addEventListener('message', function (event) { + console.log('Server>', event.data); + let obj = JSON.parse(event.data); + if (obj.messageType == "SYSTEM") { + onSystemMessage(obj); + }else if(obj.messageType == "GAME"){ + controller.onGameMessage(obj); + } +}); \ No newline at end of file