forked from project-redbud/FunGame-Core
Compare commits
No commits in common. "latest" and "master" have entirely different histories.
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
###############################################################################
|
||||||
|
# Set default behavior to automatically normalize line endings.
|
||||||
|
###############################################################################
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior for command prompt diff.
|
||||||
|
#
|
||||||
|
# This is need for earlier builds of msysgit that does not have it on by
|
||||||
|
# default for csharp files.
|
||||||
|
# Note: This is only used by command line
|
||||||
|
###############################################################################
|
||||||
|
#*.cs diff=csharp
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set the merge driver for project and solution files
|
||||||
|
#
|
||||||
|
# Merging from the command prompt will add diff markers to the files if there
|
||||||
|
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||||
|
# the diff markers are never inserted). Diff markers may cause the following
|
||||||
|
# file extensions to fail to load in VS. An alternative would be to treat
|
||||||
|
# these files as binary and thus will always conflict and require user
|
||||||
|
# intervention with every merge. To do so, just uncomment the entries below
|
||||||
|
###############################################################################
|
||||||
|
#*.sln merge=binary
|
||||||
|
#*.csproj merge=binary
|
||||||
|
#*.vbproj merge=binary
|
||||||
|
#*.vcxproj merge=binary
|
||||||
|
#*.vcproj merge=binary
|
||||||
|
#*.dbproj merge=binary
|
||||||
|
#*.fsproj merge=binary
|
||||||
|
#*.lsproj merge=binary
|
||||||
|
#*.wixproj merge=binary
|
||||||
|
#*.modelproj merge=binary
|
||||||
|
#*.sqlproj merge=binary
|
||||||
|
#*.wwaproj merge=binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# behavior for image files
|
||||||
|
#
|
||||||
|
# image files are treated as binary by default.
|
||||||
|
###############################################################################
|
||||||
|
#*.jpg binary
|
||||||
|
#*.png binary
|
||||||
|
#*.gif binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# diff behavior for common document formats
|
||||||
|
#
|
||||||
|
# Convert binary document formats to text before diffing them. This feature
|
||||||
|
# is only available from the command line. Turn it on by uncommenting the
|
||||||
|
# entries below.
|
||||||
|
###############################################################################
|
||||||
|
#*.doc diff=astextplain
|
||||||
|
#*.DOC diff=astextplain
|
||||||
|
#*.docx diff=astextplain
|
||||||
|
#*.DOCX diff=astextplain
|
||||||
|
#*.dot diff=astextplain
|
||||||
|
#*.DOT diff=astextplain
|
||||||
|
#*.pdf diff=astextplain
|
||||||
|
#*.PDF diff=astextplain
|
||||||
|
#*.rtf diff=astextplain
|
||||||
|
#*.RTF diff=astextplain
|
||||||
45
.github/workflows/dotnet.yml
vendored
Normal file
45
.github/workflows/dotnet.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# This workflow will build a .NET project and push the built files to the latest branch
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||||
|
|
||||||
|
name: .NET
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Fetch all history for all branches
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: 9.0.x
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: dotnet restore
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build --no-restore --configuration Release
|
||||||
|
- name: Test
|
||||||
|
run: dotnet test --no-build --configuration Release --verbosity normal
|
||||||
|
- name: Prepare files for latest branch
|
||||||
|
run: |
|
||||||
|
mkdir -p latest
|
||||||
|
cp -r ./bin/Release/net9.0/FunGame.Core.dll ./bin/Release/net9.0/FunGame.Core.xml ./bin/Release/net9.0/FunGame.Core.deps.json ./latest/
|
||||||
|
- name: Commit and push to latest
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
git config --global user.name "github-actions[bot]"
|
||||||
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git checkout --orphan latest
|
||||||
|
git rm -rf .
|
||||||
|
cp -r latest/* .
|
||||||
|
git add FunGame.Core.dll FunGame.Core.xml FunGame.Core.deps.json
|
||||||
|
git commit -m "Update latest branch with build outputs"
|
||||||
|
git push --force origin latest
|
||||||
363
.gitignore
vendored
Normal file
363
.gitignore
vendored
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
## 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/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
[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
|
||||||
|
*.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 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/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
12
Api/EnityFactory/CharacterFactory.cs
Normal file
12
Api/EnityFactory/CharacterFactory.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.EntityFactory
|
||||||
|
{
|
||||||
|
internal class CharacterFactory : IFactory<Character>
|
||||||
|
{
|
||||||
|
public Type EntityType => typeof(Character);
|
||||||
|
|
||||||
|
public Character Create() => Character.GetInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Api/EnityFactory/EffectFactory.cs
Normal file
15
Api/EnityFactory/EffectFactory.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.EntityFactory
|
||||||
|
{
|
||||||
|
internal class EffectFactory : IFactory<Effect>
|
||||||
|
{
|
||||||
|
public Type EntityType => typeof(Effect);
|
||||||
|
|
||||||
|
public Effect Create()
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Api/EnityFactory/InventoryFactory.cs
Normal file
21
Api/EnityFactory/InventoryFactory.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.EntityFactory
|
||||||
|
{
|
||||||
|
internal class InventoryFactory : IFactory<Inventory>
|
||||||
|
{
|
||||||
|
public Type EntityType => typeof(Inventory);
|
||||||
|
|
||||||
|
public Inventory Create()
|
||||||
|
{
|
||||||
|
return new Inventory(General.UnknownUserInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Inventory Create(User user)
|
||||||
|
{
|
||||||
|
return new Inventory(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Api/EnityFactory/ItemFactory.cs
Normal file
15
Api/EnityFactory/ItemFactory.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.EntityFactory
|
||||||
|
{
|
||||||
|
internal class ItemFactory : IFactory<Item>
|
||||||
|
{
|
||||||
|
public Type EntityType => typeof(Item);
|
||||||
|
|
||||||
|
public Item Create()
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Api/EnityFactory/RoomFactory.cs
Normal file
21
Api/EnityFactory/RoomFactory.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.EntityFactory
|
||||||
|
{
|
||||||
|
internal class RoomFactory : IFactory<Room>
|
||||||
|
{
|
||||||
|
public Type EntityType => typeof(Room);
|
||||||
|
|
||||||
|
public Room Create()
|
||||||
|
{
|
||||||
|
return RoomFactory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Room Create(long id = 0, string roomid = "-1", DateTime? createTime = null, User? roomMaster = null, RoomType roomType = RoomType.All, string gameModule = "", string gameMap = "", RoomState roomState = RoomState.Created, bool isRank = false, string password = "", int maxUsers = 4)
|
||||||
|
{
|
||||||
|
return new Room(id, roomid, createTime, roomMaster, roomType, gameModule, gameMap, roomState, isRank, password, maxUsers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Api/EnityFactory/SkillFactory.cs
Normal file
15
Api/EnityFactory/SkillFactory.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.EntityFactory
|
||||||
|
{
|
||||||
|
internal class SkillFactory : IFactory<Skill>
|
||||||
|
{
|
||||||
|
public Type EntityType => typeof(Skill);
|
||||||
|
|
||||||
|
public Skill Create()
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Api/EnityFactory/UserFactory.cs
Normal file
36
Api/EnityFactory/UserFactory.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.EntityFactory
|
||||||
|
{
|
||||||
|
internal class UserFactory : IFactory<User>
|
||||||
|
{
|
||||||
|
public Type EntityType => typeof(User);
|
||||||
|
|
||||||
|
public User Create()
|
||||||
|
{
|
||||||
|
return General.UnknownUserInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static User Create(long Id = 0, string Username = "", DateTime? RegTime = null, DateTime? LastTime = null, string Email = "", string NickName = "", bool IsAdmin = false, bool IsOperator = false, bool IsEnable = true, double GameTime = 0, string AutoKey = "")
|
||||||
|
{
|
||||||
|
return new(Id, Username, RegTime, LastTime, Email, NickName, IsAdmin, IsOperator, IsEnable, GameTime, AutoKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static User Create(UserType type)
|
||||||
|
{
|
||||||
|
return new(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static User CreateGuest()
|
||||||
|
{
|
||||||
|
return General.GuestUserInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static User CreateLocalUser()
|
||||||
|
{
|
||||||
|
return General.LocalUserInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Api/OpenEntityAdapter/OpenItemAdapter.cs
Normal file
23
Api/OpenEntityAdapter/OpenItemAdapter.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.OpenEntityAdapter
|
||||||
|
{
|
||||||
|
public class OpenItemAdapter
|
||||||
|
{
|
||||||
|
public static void Adaptation<T>(EntityModuleConfig<T> config) where T : BaseEntity
|
||||||
|
{
|
||||||
|
foreach (string key in config.Keys)
|
||||||
|
{
|
||||||
|
if (config[key] is Item prev)
|
||||||
|
{
|
||||||
|
Item next = prev.Copy();
|
||||||
|
if (next is T t)
|
||||||
|
{
|
||||||
|
config[key] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Api/OpenEntityAdapter/OpenSkillAdapter.cs
Normal file
20
Api/OpenEntityAdapter/OpenSkillAdapter.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.OpenEntityAdapter
|
||||||
|
{
|
||||||
|
public class OpenSkillAdapter
|
||||||
|
{
|
||||||
|
public static void Adaptation<T>(EntityModuleConfig<T> config) where T : BaseEntity
|
||||||
|
{
|
||||||
|
foreach (string key in config.Keys)
|
||||||
|
{
|
||||||
|
if (config[key] is Skill prev)
|
||||||
|
{
|
||||||
|
Skill next = prev.Copy();
|
||||||
|
if (next is T t) config[key] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
471
Api/Transmittal/DataRequest.cs
Normal file
471
Api/Transmittal/DataRequest.cs
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
using Milimoe.FunGame.Core.Controller;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Network;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Library.Exception;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Transmittal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 需要配合 <see cref="DataRequestType"/> 使用<para/>
|
||||||
|
/// 如果是 <see cref="Model.Gaming"/> 的数据请求,则配合 <see cref="GamingType"/> 使用<para/>
|
||||||
|
/// 确保已添加对应的枚举
|
||||||
|
/// </summary>
|
||||||
|
public class DataRequest : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 数据请求结果
|
||||||
|
/// </summary>
|
||||||
|
public RequestResult Result => _worker != null ? _worker.Result : (_gamingWorker != null ? _gamingWorker.Result : RequestResult.Missing);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 详细错误信息
|
||||||
|
/// </summary>
|
||||||
|
public string Error => _worker != null ? _worker.Error : (_gamingWorker != null ? _gamingWorker.Error : "");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否已经关闭
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDisposed => _isDisposed;
|
||||||
|
|
||||||
|
// 获取ResultData中key值对应的Json字符串
|
||||||
|
// -- 此索引器仅返回Json字符串,对象类型请使用反序列化方法GetResult<T>() --
|
||||||
|
// -- 当然也可以自己反序列化 --
|
||||||
|
// -- 基本类型可能有效,但仍建议使用反序列化方法 --
|
||||||
|
public object? this[string key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_worker != null) return _worker.ResultData[key];
|
||||||
|
else if (_gamingWorker != null) return _gamingWorker.ResultData[key];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != null) AddRequestData(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 私有的实现类
|
||||||
|
/// </summary>
|
||||||
|
private readonly SocketRequest? _worker;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 私有的实现类(这是局内请求的)
|
||||||
|
/// </summary>
|
||||||
|
private readonly GamingRequest? _gamingWorker;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 指示关闭的变量
|
||||||
|
/// </summary>
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的 <see cref="Socket"/> 创建新的数据请求<para/>
|
||||||
|
/// 使用 <see cref="RunTimeController"/> 中的 <see cref="RunTimeController.NewDataRequest(DataRequestType)"/> 创建一个新的请求
|
||||||
|
/// 插件则使用 <see cref="RunTimeController"/> 中的 <see cref="RunTimeController.NewDataRequestForAddon(DataRequestType)"/> 创建一个新的请求<para/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="socket"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="longRunning"></param>
|
||||||
|
/// <param name="runtime"></param>
|
||||||
|
internal DataRequest(Socket socket, DataRequestType type, bool longRunning = false, SocketRuntimeType runtime = SocketRuntimeType.Client)
|
||||||
|
{
|
||||||
|
_worker = new(socket, type, Guid.NewGuid(), longRunning, runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的 <see cref="HTTPClient"/> 创建新的数据请求<para/>
|
||||||
|
/// 使用 <see cref="RunTimeController"/> 中的 <see cref="RunTimeController.NewDataRequest(DataRequestType)"/> 创建一个新的请求<para/>
|
||||||
|
/// 插件则使用 <see cref="RunTimeController"/> 中的 <see cref="RunTimeController.NewDataRequestForAddon(DataRequestType)"/> 创建一个新的请求<para/>
|
||||||
|
/// 此数据请求只能调用异步方法 <see cref="SendRequestAsync"/> 请求数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="longRunning"></param>
|
||||||
|
/// <param name="runtime"></param>
|
||||||
|
internal DataRequest(HTTPClient client, DataRequestType type, bool longRunning = false, SocketRuntimeType runtime = SocketRuntimeType.Client)
|
||||||
|
{
|
||||||
|
_worker = new(client, type, Guid.NewGuid(), longRunning, runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的 <see cref="Socket"/> 创建新的局内(<see cref="Model.Gaming"/>)数据请求<para/>
|
||||||
|
/// 使用 <see cref="RunTimeController"/> 中的 <see cref="RunTimeController.NewDataRequestForAddon(GamingType)"/> 创建一个新的请求<para/>
|
||||||
|
/// 此构造方法是给 <see cref="Library.Common.Addon.GameModule"/> 提供的
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="socket"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="longRunning"></param>
|
||||||
|
/// <param name="runtime"></param>
|
||||||
|
internal DataRequest(Socket socket, GamingType type, bool longRunning = false, SocketRuntimeType runtime = SocketRuntimeType.Client)
|
||||||
|
{
|
||||||
|
_gamingWorker = new(socket, type, Guid.NewGuid(), longRunning, runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的 <see cref="HTTPClient"/> 创建新的局内(<see cref="Model.Gaming"/>)数据请求<para/>
|
||||||
|
/// 使用 <see cref="RunTimeController"/> 中的 <see cref="RunTimeController.NewDataRequestForAddon(GamingType)"/> 创建一个新的请求<para/>
|
||||||
|
/// 此构造方法是给 <see cref="Library.Common.Addon.GameModule"/> 提供的<para/>
|
||||||
|
/// 此数据请求只能调用异步方法 <see cref="SendRequestAsync"/> 请求数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="longRunning"></param>
|
||||||
|
/// <param name="runtime"></param>
|
||||||
|
internal DataRequest(HTTPClient client, GamingType type, bool longRunning = false, SocketRuntimeType runtime = SocketRuntimeType.Client)
|
||||||
|
{
|
||||||
|
_gamingWorker = new(client, type, Guid.NewGuid(), longRunning, runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public void AddRequestData(string key, object value)
|
||||||
|
{
|
||||||
|
if (_worker != null)
|
||||||
|
{
|
||||||
|
if (!_worker.RequestData.TryAdd(key, value)) _worker.RequestData[key] = value;
|
||||||
|
}
|
||||||
|
else if (_gamingWorker != null)
|
||||||
|
{
|
||||||
|
if (!_gamingWorker.RequestData.TryAdd(key, value)) _gamingWorker.RequestData[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 长时间运行的数据请求需要在使用完毕后自行关闭
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭时
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"></param>
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_worker?.Dispose();
|
||||||
|
_gamingWorker?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 向服务器发送数据请求
|
||||||
|
/// <para/>警告:<see cref="HTTPClient"/> 调用此方法将抛出异常。请调用并等待 <see cref="SendRequestAsync"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="AsyncSendException"></exception>
|
||||||
|
public RequestResult SendRequest()
|
||||||
|
{
|
||||||
|
_worker?.SendRequest();
|
||||||
|
_gamingWorker?.SendRequest();
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步向服务器发送数据请求
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<RequestResult> SendRequestAsync()
|
||||||
|
{
|
||||||
|
if (_worker != null)
|
||||||
|
{
|
||||||
|
await _worker.SendRequestAsync();
|
||||||
|
}
|
||||||
|
else if (_gamingWorker != null)
|
||||||
|
{
|
||||||
|
await _gamingWorker.SendRequestAsync();
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定key对应的反序列化对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public T? GetResult<T>(string key)
|
||||||
|
{
|
||||||
|
if (_worker != null)
|
||||||
|
{
|
||||||
|
return GetDictionaryJsonObject<T>(_worker.ResultData, key);
|
||||||
|
}
|
||||||
|
else if (_gamingWorker != null)
|
||||||
|
{
|
||||||
|
return GetDictionaryJsonObject<T>(_gamingWorker.ResultData, key);
|
||||||
|
}
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 常规数据请求
|
||||||
|
/// </summary>
|
||||||
|
private class SocketRequest : SocketHandlerController
|
||||||
|
{
|
||||||
|
public Dictionary<string, object> RequestData { get; } = [];
|
||||||
|
public Dictionary<string, object> ResultData => _ResultData;
|
||||||
|
public RequestResult Result => _Result;
|
||||||
|
public string Error => _Error;
|
||||||
|
|
||||||
|
private readonly Socket? Socket = null;
|
||||||
|
private readonly HTTPClient? HTTPClient = null;
|
||||||
|
private readonly DataRequestType RequestType = DataRequestType.UnKnown;
|
||||||
|
private readonly Guid RequestID = Guid.Empty;
|
||||||
|
private readonly bool IsLongRunning = false;
|
||||||
|
private readonly SocketRuntimeType RuntimeType = SocketRuntimeType.Client;
|
||||||
|
private Dictionary<string, object> _ResultData = [];
|
||||||
|
private RequestResult _Result = RequestResult.Missing;
|
||||||
|
private string _Error = "";
|
||||||
|
|
||||||
|
public SocketRequest(Socket? socket, DataRequestType type, Guid requestId, bool longRunning = false, SocketRuntimeType runtime = SocketRuntimeType.Client) : base(socket)
|
||||||
|
{
|
||||||
|
Socket = socket;
|
||||||
|
RequestType = type;
|
||||||
|
RequestID = requestId;
|
||||||
|
IsLongRunning = longRunning;
|
||||||
|
RuntimeType = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketRequest(HTTPClient? client, DataRequestType type, Guid requestId, bool longRunning = false, SocketRuntimeType runtime = SocketRuntimeType.Client) : base(client)
|
||||||
|
{
|
||||||
|
HTTPClient = client;
|
||||||
|
RequestType = type;
|
||||||
|
RequestID = requestId;
|
||||||
|
IsLongRunning = longRunning;
|
||||||
|
RuntimeType = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendRequest()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SetWorking();
|
||||||
|
if (RuntimeType == SocketRuntimeType.Addon)
|
||||||
|
{
|
||||||
|
if (RequestData.ContainsKey(SocketSet.Plugins_Mark)) RequestData[SocketSet.Plugins_Mark] = "true";
|
||||||
|
else RequestData.Add(SocketSet.Plugins_Mark, true);
|
||||||
|
}
|
||||||
|
else RequestData.Remove(SocketSet.Plugins_Mark);
|
||||||
|
if (Socket != null && Socket.Send(SocketMessageType.DataRequest, RequestType, RequestID, RequestData) == SocketResult.Success)
|
||||||
|
{
|
||||||
|
WaitForWorkDone();
|
||||||
|
}
|
||||||
|
else if (HTTPClient != null)
|
||||||
|
{
|
||||||
|
throw new AsyncSendException();
|
||||||
|
}
|
||||||
|
else throw new ConnectFailedException();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Working = false;
|
||||||
|
_Result = RequestResult.Fail;
|
||||||
|
_Error = e.GetErrorInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendRequestAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SetWorking();
|
||||||
|
if (RuntimeType == SocketRuntimeType.Addon)
|
||||||
|
{
|
||||||
|
if (RequestData.ContainsKey(SocketSet.Plugins_Mark)) RequestData[SocketSet.Plugins_Mark] = "true";
|
||||||
|
else RequestData.Add(SocketSet.Plugins_Mark, true);
|
||||||
|
}
|
||||||
|
else RequestData.Remove(SocketSet.Plugins_Mark);
|
||||||
|
if (Socket != null && Socket.Send(SocketMessageType.DataRequest, RequestType, RequestID, RequestData) == SocketResult.Success)
|
||||||
|
{
|
||||||
|
await WaitForWorkDoneAsync();
|
||||||
|
}
|
||||||
|
else if (HTTPClient != null && await HTTPClient.Send(SocketMessageType.DataRequest, RequestType, RequestID, RequestData) == SocketResult.Success)
|
||||||
|
{
|
||||||
|
await WaitForWorkDoneAsync();
|
||||||
|
}
|
||||||
|
else throw new ConnectFailedException();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Working = false;
|
||||||
|
_Result = RequestResult.Fail;
|
||||||
|
_Error = e.GetErrorInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SocketHandler(SocketObject obj)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (obj.SocketType == SocketMessageType.DataRequest)
|
||||||
|
{
|
||||||
|
DataRequestType type = obj.GetParam<DataRequestType>(0);
|
||||||
|
Guid id = obj.GetParam<Guid>(1);
|
||||||
|
if (type == RequestType && id == RequestID)
|
||||||
|
{
|
||||||
|
if (!IsLongRunning) Dispose();
|
||||||
|
ReceivedObject = obj;
|
||||||
|
Working = false;
|
||||||
|
_ResultData = obj.GetParam<Dictionary<string, object>>(2) ?? [];
|
||||||
|
_Result = RequestResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Working = false;
|
||||||
|
_Result = RequestResult.Fail;
|
||||||
|
_Error = e.GetErrorInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 游戏局内请求
|
||||||
|
/// </summary>
|
||||||
|
private class GamingRequest : SocketHandlerController
|
||||||
|
{
|
||||||
|
public Dictionary<string, object> RequestData { get; } = [];
|
||||||
|
public Dictionary<string, object> ResultData => _ResultData;
|
||||||
|
public RequestResult Result => _Result;
|
||||||
|
public string Error => _Error;
|
||||||
|
|
||||||
|
private readonly Socket? Socket = null;
|
||||||
|
private readonly HTTPClient? HTTPClient = null;
|
||||||
|
private readonly GamingType GamingType = GamingType.None;
|
||||||
|
private readonly Guid RequestID = Guid.Empty;
|
||||||
|
private readonly bool IsLongRunning = false;
|
||||||
|
private readonly SocketRuntimeType RuntimeType = SocketRuntimeType.Client;
|
||||||
|
private Dictionary<string, object> _ResultData = [];
|
||||||
|
private RequestResult _Result = RequestResult.Missing;
|
||||||
|
private string _Error = "";
|
||||||
|
|
||||||
|
public GamingRequest(Socket? socket, GamingType type, Guid requestId, bool longRunning = false, SocketRuntimeType runtime = SocketRuntimeType.Client) : base(socket)
|
||||||
|
{
|
||||||
|
Socket = socket;
|
||||||
|
GamingType = type;
|
||||||
|
RequestID = requestId;
|
||||||
|
IsLongRunning = longRunning;
|
||||||
|
RuntimeType = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamingRequest(HTTPClient? client, GamingType type, Guid requestId, bool longRunning = false, SocketRuntimeType runtime = SocketRuntimeType.Client) : base(client)
|
||||||
|
{
|
||||||
|
HTTPClient = client;
|
||||||
|
GamingType = type;
|
||||||
|
RequestID = requestId;
|
||||||
|
IsLongRunning = longRunning;
|
||||||
|
RuntimeType = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendRequest()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SetWorking();
|
||||||
|
if (RuntimeType == SocketRuntimeType.Addon)
|
||||||
|
{
|
||||||
|
if (RequestData.ContainsKey(SocketSet.Plugins_Mark)) RequestData[SocketSet.Plugins_Mark] = "true";
|
||||||
|
else RequestData.Add(SocketSet.Plugins_Mark, true);
|
||||||
|
}
|
||||||
|
else RequestData.Remove(SocketSet.Plugins_Mark);
|
||||||
|
if (Socket != null && Socket.Send(SocketMessageType.GamingRequest, GamingType, RequestID, RequestData) == SocketResult.Success)
|
||||||
|
{
|
||||||
|
WaitForWorkDone();
|
||||||
|
}
|
||||||
|
else if (HTTPClient != null)
|
||||||
|
{
|
||||||
|
throw new AsyncSendException();
|
||||||
|
}
|
||||||
|
else throw new ConnectFailedException();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Working = false;
|
||||||
|
_Result = RequestResult.Fail;
|
||||||
|
_Error = e.GetErrorInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendRequestAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SetWorking();
|
||||||
|
if (RuntimeType == SocketRuntimeType.Addon)
|
||||||
|
{
|
||||||
|
if (RequestData.ContainsKey(SocketSet.Plugins_Mark)) RequestData[SocketSet.Plugins_Mark] = "true";
|
||||||
|
else RequestData.Add(SocketSet.Plugins_Mark, true);
|
||||||
|
}
|
||||||
|
else RequestData.Remove(SocketSet.Plugins_Mark);
|
||||||
|
if (Socket != null && Socket.Send(SocketMessageType.GamingRequest, GamingType, RequestID, RequestData) == SocketResult.Success)
|
||||||
|
{
|
||||||
|
await WaitForWorkDoneAsync();
|
||||||
|
}
|
||||||
|
else if (HTTPClient != null && await HTTPClient.Send(SocketMessageType.GamingRequest, GamingType, RequestID, RequestData) == SocketResult.Success)
|
||||||
|
{
|
||||||
|
await WaitForWorkDoneAsync();
|
||||||
|
}
|
||||||
|
else throw new ConnectFailedException();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Working = false;
|
||||||
|
_Result = RequestResult.Fail;
|
||||||
|
_Error = e.GetErrorInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SocketHandler(SocketObject obj)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (obj.SocketType == SocketMessageType.GamingRequest)
|
||||||
|
{
|
||||||
|
GamingType type = obj.GetParam<GamingType>(0);
|
||||||
|
Guid id = obj.GetParam<Guid>(1);
|
||||||
|
if (type == GamingType && id == RequestID)
|
||||||
|
{
|
||||||
|
if (!IsLongRunning) Dispose();
|
||||||
|
ReceivedObject = obj;
|
||||||
|
Working = false;
|
||||||
|
_ResultData = obj.GetParam<Dictionary<string, object>>(2) ?? [];
|
||||||
|
_Result = RequestResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Working = false;
|
||||||
|
_Result = RequestResult.Fail;
|
||||||
|
_Error = e.GetErrorInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Dictionary中的Json对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="dict"></param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? GetDictionaryJsonObject<T>(Dictionary<string, object> dict, string key)
|
||||||
|
{
|
||||||
|
return Service.JsonManager.GetObject<T>(dict, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
108
Api/Transmittal/MailSender.cs
Normal file
108
Api/Transmittal/MailSender.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
using System.Net.Mail;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Network;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
using Milimoe.FunGame.Core.Service;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Transmittal
|
||||||
|
{
|
||||||
|
public class MailSender : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 邮件服务内部ID
|
||||||
|
/// </summary>
|
||||||
|
public Guid MailSenderID { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Smtp客户端信息
|
||||||
|
/// </summary>
|
||||||
|
public SmtpClientInfo SmtpClientInfo => _SmtpClientInfo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上一个邮件发送的结果
|
||||||
|
/// </summary>
|
||||||
|
public MailSendResult LastestResult => _LastestResult;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上一个邮件的发送错误信息(如果发送失败)
|
||||||
|
/// </summary>
|
||||||
|
public string ErrorMsg => _ErrorMsg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部变量
|
||||||
|
*/
|
||||||
|
private readonly SmtpClientInfo _SmtpClientInfo;
|
||||||
|
private MailSendResult _LastestResult = MailSendResult.NotSend;
|
||||||
|
private string _ErrorMsg = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建邮件服务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="senderMailAddress"></param>
|
||||||
|
/// <param name="senderName"></param>
|
||||||
|
/// <param name="senderPassword"></param>
|
||||||
|
/// <param name="host"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <param name="ssl"></param>
|
||||||
|
public MailSender(string senderMailAddress, string senderName, string senderPassword, string host, int port, bool ssl)
|
||||||
|
{
|
||||||
|
MailSenderID = Guid.NewGuid();
|
||||||
|
_SmtpClientInfo = new SmtpClientInfo(senderMailAddress, senderName, senderPassword, host, port, ssl);
|
||||||
|
if (!MailManager.MailSenders.ContainsKey(MailSenderID)) MailManager.MailSenders.Add(MailSenderID, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建完整邮件对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subject"></param>
|
||||||
|
/// <param name="body"></param>
|
||||||
|
/// <param name="priority"></param>
|
||||||
|
/// <param name="html"></param>
|
||||||
|
/// <param name="toList"></param>
|
||||||
|
/// <param name="ccList"></param>
|
||||||
|
/// <param name="bccList"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MailObject CreateMail(string subject, string body, MailPriority priority, bool html, string[] toList, string[]? ccList = null, string[]? bccList = null)
|
||||||
|
{
|
||||||
|
return new MailObject(this, subject, body, priority, html, toList, ccList, bccList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送邮件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mail"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MailSendResult Send(MailObject mail)
|
||||||
|
{
|
||||||
|
_LastestResult = MailManager.Send(this, mail, out _ErrorMsg);
|
||||||
|
return _LastestResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭邮件服务
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭邮件服务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"></param>
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
MailManager.Dispose(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
258
Api/Transmittal/SQLHelper.cs
Normal file
258
Api/Transmittal/SQLHelper.cs
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
using System.Data;
|
||||||
|
using System.Data.Common;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Transmittal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库助手类。这是一个抽象类,需要在Server中继承此类实现
|
||||||
|
/// </summary>
|
||||||
|
public abstract class SQLHelper : ISQLHelper, IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// FunGame 类型
|
||||||
|
/// </summary>
|
||||||
|
public abstract FunGameInfo.FunGame FunGameType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用的数据库类型
|
||||||
|
/// </summary>
|
||||||
|
public abstract SQLMode Mode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SQL 脚本
|
||||||
|
/// </summary>
|
||||||
|
public abstract string Script { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 命令类型
|
||||||
|
/// </summary>
|
||||||
|
public abstract CommandType CommandType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库事务
|
||||||
|
/// </summary>
|
||||||
|
public abstract DbTransaction? Transaction { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行结果
|
||||||
|
/// </summary>
|
||||||
|
public abstract SQLResult Result { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SQL 服务器信息
|
||||||
|
/// </summary>
|
||||||
|
public abstract SQLServerInfo ServerInfo { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上一次执行命令影响的行数
|
||||||
|
/// </summary>
|
||||||
|
public abstract int AffectedRows { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上一次执行的命令是 Insert 时,返回的自增 ID,大于 0 有效
|
||||||
|
/// </summary>
|
||||||
|
public abstract long LastInsertId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上一次执行命令的查询结果集
|
||||||
|
/// </summary>
|
||||||
|
public abstract DataSet DataSet { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SQL 语句参数
|
||||||
|
/// </summary>
|
||||||
|
public abstract Dictionary<string, object> Parameters { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上一次执行命令是否成功
|
||||||
|
/// </summary>
|
||||||
|
public bool Success => Result == SQLResult.Success;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否在每次执行命令后清除参数,默认为 true
|
||||||
|
/// </summary>
|
||||||
|
public bool ClearParametersAfterExecute { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行现有命令(<see cref="Script"/>)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>影响的行数</returns>
|
||||||
|
public abstract int Execute();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行一个指定的命令
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="script">命令</param>
|
||||||
|
/// <returns>影响的行数</returns>
|
||||||
|
public abstract int Execute(string script);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步执行现有命令(<see cref="Script"/>)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>影响的行数</returns>
|
||||||
|
public abstract Task<int> ExecuteAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步执行一个指定的命令
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="script">命令</param>
|
||||||
|
/// <returns>影响的行数</returns>
|
||||||
|
public abstract Task<int> ExecuteAsync(string script);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行现有命令(<see cref="Script"/>)查询 DataSet
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>结果集</returns>
|
||||||
|
public abstract DataSet ExecuteDataSet();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行指定的命令查询 DataSet
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="script">命令</param>
|
||||||
|
/// <returns>结果集</returns>
|
||||||
|
public abstract DataSet ExecuteDataSet(string script);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步执行现有命令(<see cref="Script"/>)查询 DataSet
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>结果集</returns>
|
||||||
|
public abstract Task<DataSet> ExecuteDataSetAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步执行指定的命令查询 DataSet
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="script">命令</param>
|
||||||
|
/// <returns>结果集</returns>
|
||||||
|
public abstract Task<DataSet> ExecuteDataSetAsync(string script);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行现有命令(<see cref="Script"/>)查询 DataRow(可选实现)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>结果行</returns>
|
||||||
|
public virtual DataRow? ExecuteDataRow()
|
||||||
|
{
|
||||||
|
return ExecuteDataRow(Script);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行指定的命令查询 DataRow(可选实现)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="script">命令</param>
|
||||||
|
/// <returns>结果行</returns>
|
||||||
|
public virtual DataRow? ExecuteDataRow(string script)
|
||||||
|
{
|
||||||
|
ExecuteDataSet(script);
|
||||||
|
if (Success)
|
||||||
|
{
|
||||||
|
return DataSet.Tables[0].Rows[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步执行现有命令(<see cref="Script"/>)查询 DataRow(可选实现)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>结果行</returns>
|
||||||
|
public virtual async Task<DataRow?> ExecuteDataRowAsync()
|
||||||
|
{
|
||||||
|
return await ExecuteDataRowAsync(Script);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步执行指定的命令查询 DataRow(可选实现)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="script">命令</param>
|
||||||
|
/// <returns>结果行</returns>
|
||||||
|
public virtual async Task<DataRow?> ExecuteDataRowAsync(string script)
|
||||||
|
{
|
||||||
|
await ExecuteDataSetAsync(script);
|
||||||
|
if (Success)
|
||||||
|
{
|
||||||
|
return DataSet.Tables[0].Rows[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询数据库是否存在
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public abstract bool DatabaseExists();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行一个 sql 脚本文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
public virtual void ExecuteSqlFile(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("SQL 脚本文件不存在", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
string content = File.ReadAllText(path);
|
||||||
|
string[] commands = content.Split([";"], StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
foreach (string command in commands)
|
||||||
|
{
|
||||||
|
string sql = command.Trim();
|
||||||
|
if (!string.IsNullOrEmpty(sql))
|
||||||
|
{
|
||||||
|
Execute(sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步执行一个 sql 脚本文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
public virtual async Task ExecuteSqlFileAsync(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("SQL 脚本文件不存在", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
string content = File.ReadAllText(path);
|
||||||
|
string[] commands = content.Split([";"], StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
foreach (string command in commands)
|
||||||
|
{
|
||||||
|
string sql = command.Trim();
|
||||||
|
if (!string.IsNullOrEmpty(sql))
|
||||||
|
{
|
||||||
|
await ExecuteAsync(sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭连接
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Close();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建一个SQL事务
|
||||||
|
/// </summary>
|
||||||
|
public abstract void NewTransaction();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提交事务
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Commit();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 回滚事务
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Rollback();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 释放资源
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Api/Utility/Attribute.cs
Normal file
32
Api/Utility/Attribute.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Addons;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 此标记意味着属性允许初始设定,但不是强制的。适用于 <see cref="BaseEntity"/>
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = true)]
|
||||||
|
public class InitOptional : Attribute
|
||||||
|
{
|
||||||
|
public InitOptional() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 此标记意味着属性需要经过初始设定。适用于 <see cref="BaseEntity"/>
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = true)]
|
||||||
|
public class InitRequired : Attribute
|
||||||
|
{
|
||||||
|
public InitRequired() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 此标记意味着字段需要满足 x.x.x 的格式。适用于 <see cref="IAddon"/> 的版本号
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = true)]
|
||||||
|
public class AddonVersion : Attribute
|
||||||
|
{
|
||||||
|
public AddonVersion() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
128
Api/Utility/ConcurrentModelList.cs
Normal file
128
Api/Utility/ConcurrentModelList.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public class ConcurrentModelList<T> : IEnumerable<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 目前的Model数量
|
||||||
|
/// </summary>
|
||||||
|
public int Count => Models.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最大接受的Model数量
|
||||||
|
/// </summary>
|
||||||
|
private int MaxModel { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可参与高并发的字典,但添加效率较低
|
||||||
|
/// </summary>
|
||||||
|
private ConcurrentDictionary<string, T> Models { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Init ModelManager
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="MaxModel">MaxModel</param>
|
||||||
|
public ConcurrentModelList(int MaxModel = 0)
|
||||||
|
{
|
||||||
|
if (MaxModel <= 0)
|
||||||
|
this.MaxModel = Library.Constant.General.MaxTask_2C2G;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.MaxModel = MaxModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取Model对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Model的Key</param>
|
||||||
|
/// <returns>Model对象</returns>
|
||||||
|
public T this[string name] => Models[name];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 向Model管理器中添加Model
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Model的Key</param>
|
||||||
|
/// <param name="t">Model对象</param>
|
||||||
|
/// <returns>True:操作成功</returns>
|
||||||
|
public bool Add(string name, T t)
|
||||||
|
{
|
||||||
|
if (Models.Count + 1 > MaxModel) return false;
|
||||||
|
return Models.TryAdd(name, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从Model管理器中移除Model
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Model的Key</param>
|
||||||
|
/// <returns>True:操作成功</returns>
|
||||||
|
public bool Remove(string name)
|
||||||
|
{
|
||||||
|
return Models.TryRemove(name, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将Model移除,并取得这个Model
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Model的Key</param>
|
||||||
|
/// <param name="t">Model对象</param>
|
||||||
|
/// <returns>被移除的Model</returns>
|
||||||
|
public bool Remove(string name, ref T? t)
|
||||||
|
{
|
||||||
|
return Models.TryRemove(name, out t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将Model移除,并取得这个Model
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Model的Key</param>
|
||||||
|
/// <returns>被移除的Model</returns>
|
||||||
|
public T? RemoveAndGet(string name)
|
||||||
|
{
|
||||||
|
Models.TryRemove(name, out T? result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断是否存在指定的Model
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool ContainsKey(string name)
|
||||||
|
{
|
||||||
|
return Models.ContainsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清空Model管理器
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Models.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取Model对象的列表
|
||||||
|
/// </summary>
|
||||||
|
public List<T> GetList()
|
||||||
|
{
|
||||||
|
return [.. Models.Values];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach (T instance in Models.Values)
|
||||||
|
{
|
||||||
|
yield return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Api/Utility/ConcurrentQueue.cs
Normal file
33
Api/Utility/ConcurrentQueue.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public class ConcurrentQueue<T>
|
||||||
|
{
|
||||||
|
private bool Lock { get; set; }
|
||||||
|
|
||||||
|
private System.Collections.Concurrent.ConcurrentQueue<T> Instance { get; } = new();
|
||||||
|
|
||||||
|
public async void AddAsync(T obj)
|
||||||
|
{
|
||||||
|
if (Lock)
|
||||||
|
{
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (!Lock) break;
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Lock = true;
|
||||||
|
Instance.Enqueue(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Delete()
|
||||||
|
{
|
||||||
|
bool result = Instance.TryDequeue(out _);
|
||||||
|
Lock = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Api/Utility/DataExtension.cs
Normal file
35
Api/Utility/DataExtension.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public static class DictionaryExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 从 <see cref="Dictionary{String, Object}"/> 中获取指定键的值,并将其转换为指定的类型。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">要转换的目标类型</typeparam>
|
||||||
|
/// <param name="dict">要从中获取值的字典</param>
|
||||||
|
/// <param name="key">要获取值的键</param>
|
||||||
|
/// <returns>转换后的值,如果键不存在或转换失败,则返回默认值。</returns>
|
||||||
|
public static T? GetValue<T>(this Dictionary<string, object> dict, string key)
|
||||||
|
{
|
||||||
|
if (dict is null || !dict.TryGetValue(key, out object? value) || value is null)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 如果已经是目标类型,则直接返回
|
||||||
|
if (value is T t)
|
||||||
|
{
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NetworkUtility.JsonDeserializeFromDictionary<T>(dict, key);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
107
Api/Utility/EntityModuleConfig.cs
Normal file
107
Api/Utility/EntityModuleConfig.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 简易的实体模组配置文件生成器,适用范围:动态扩展技能和物品、保存玩家的存档<para/>
|
||||||
|
/// 仅支持继承了 <see cref="BaseEntity"/> 的实体类型,每个 <see cref="EntityModuleConfig{T}"/> 仅保存一种实体类型的数据
|
||||||
|
/// <para/>文件会保存为:程序目录/configs/<see cref="ModuleName"/>/<see cref="FileName"/>.json
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 新建一个配置文件,文件会保存为:程序目录/configs/<paramref name="module_name"/>/<paramref name="file_name"/>.json
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="module_name"></param>
|
||||||
|
/// <param name="file_name"></param>
|
||||||
|
public class EntityModuleConfig<T>(string module_name, string file_name) : Dictionary<string, T> where T : BaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 模组的名称
|
||||||
|
/// </summary>
|
||||||
|
public string ModuleName { get; set; } = module_name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置文件的名称(后缀将是.json)
|
||||||
|
/// </summary>
|
||||||
|
public string FileName { get; set; } = file_name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用索引器给指定key赋值,不存在key会新增
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new T this[string key]
|
||||||
|
{
|
||||||
|
get => base[key];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != null) Add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定key的value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public T? Get(string key)
|
||||||
|
{
|
||||||
|
if (TryGetValue(key, out T? value) && value != null)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加一个配置,如果已存在key会覆盖
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public new void Add(string key, T value)
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
if (TryGetValue(key, out _)) base[key] = value;
|
||||||
|
else base.Add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从配置文件中读取配置。
|
||||||
|
/// </summary>
|
||||||
|
public void LoadConfig()
|
||||||
|
{
|
||||||
|
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}configs/{ModuleName}";
|
||||||
|
string fpath = $@"{dpath}/{FileName}.json";
|
||||||
|
if (Directory.Exists(dpath) && File.Exists(fpath))
|
||||||
|
{
|
||||||
|
string json = File.ReadAllText(fpath, General.DefaultEncoding);
|
||||||
|
Dictionary<string, T> dict = NetworkUtility.JsonDeserialize<Dictionary<string, T>>(json) ?? [];
|
||||||
|
Clear();
|
||||||
|
foreach (string key in dict.Keys)
|
||||||
|
{
|
||||||
|
T obj = dict[key];
|
||||||
|
base.Add(key, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将配置保存到配置文件。调用此方法会覆盖原有的.json,请注意备份
|
||||||
|
/// </summary>
|
||||||
|
public void SaveConfig()
|
||||||
|
{
|
||||||
|
string json = NetworkUtility.JsonSerialize((Dictionary<string, T>)this);
|
||||||
|
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}configs/{ModuleName}";
|
||||||
|
string fpath = $@"{dpath}/{FileName}.json";
|
||||||
|
if (!Directory.Exists(dpath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dpath);
|
||||||
|
}
|
||||||
|
using StreamWriter writer = new(fpath, false, General.DefaultEncoding);
|
||||||
|
writer.WriteLine(json);
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
562
Api/Utility/Factory.cs
Normal file
562
Api/Utility/Factory.cs
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
using System.Data;
|
||||||
|
using Milimoe.FunGame.Core.Api.EntityFactory;
|
||||||
|
using Milimoe.FunGame.Core.Api.OpenEntityAdapter;
|
||||||
|
using Milimoe.FunGame.Core.Api.Transmittal;
|
||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Library.Exception;
|
||||||
|
using Milimoe.FunGame.Core.Library.SQLScript.Entity;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public class Factory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 支持动态扩展的工厂实例
|
||||||
|
/// </summary>
|
||||||
|
public static Factory OpenFactory { get; } = new();
|
||||||
|
|
||||||
|
private Factory()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal HashSet<EntityFactoryDelegate<Character>> CharacterFactories { get; } = [];
|
||||||
|
internal HashSet<EntityFactoryDelegate<Inventory>> InventoryFactories { get; } = [];
|
||||||
|
internal HashSet<EntityFactoryDelegate<Skill>> SkillFactories { get; } = [];
|
||||||
|
internal HashSet<EntityFactoryDelegate<Effect>> EffectFactories { get; } = [];
|
||||||
|
internal HashSet<EntityFactoryDelegate<Item>> ItemFactories { get; } = [];
|
||||||
|
internal HashSet<EntityFactoryDelegate<Room>> RoomFactories { get; } = [];
|
||||||
|
internal HashSet<EntityFactoryDelegate<User>> UserFactories { get; } = [];
|
||||||
|
|
||||||
|
public delegate T? EntityFactoryDelegate<T>(long id, string name, Dictionary<string, object> args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册工厂方法
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="d"></param>
|
||||||
|
public void RegisterFactory<T>(EntityFactoryDelegate<T> d)
|
||||||
|
{
|
||||||
|
if (typeof(T) == typeof(Character) && d is EntityFactoryDelegate<Character> character)
|
||||||
|
{
|
||||||
|
CharacterFactories.Add(character);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Inventory) && d is EntityFactoryDelegate<Inventory> inventory)
|
||||||
|
{
|
||||||
|
InventoryFactories.Add(inventory);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Skill) && d is EntityFactoryDelegate<Skill> skill)
|
||||||
|
{
|
||||||
|
SkillFactories.Add(skill);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Effect) && d is EntityFactoryDelegate<Effect> effect)
|
||||||
|
{
|
||||||
|
EffectFactories.Add(effect);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Item) && d is EntityFactoryDelegate<Item> item)
|
||||||
|
{
|
||||||
|
ItemFactories.Add(item);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Room) && d is EntityFactoryDelegate<Room> room)
|
||||||
|
{
|
||||||
|
RoomFactories.Add(room);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(User) && d is EntityFactoryDelegate<User> user)
|
||||||
|
{
|
||||||
|
UserFactories.Add(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个实体实例
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotSupportedInstanceClassException"></exception>
|
||||||
|
public T GetInstance<T>(long id, string name, Dictionary<string, object> args)
|
||||||
|
{
|
||||||
|
if (typeof(T) == typeof(Character))
|
||||||
|
{
|
||||||
|
foreach (EntityFactoryDelegate<Character> d in CharacterFactories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (d.Invoke(id, name, args) is T character)
|
||||||
|
{
|
||||||
|
return character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T)(object)GetCharacter();
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Inventory))
|
||||||
|
{
|
||||||
|
foreach (EntityFactoryDelegate<Inventory> d in InventoryFactories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (d.Invoke(id, name, args) is T inventory)
|
||||||
|
{
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T)(object)GetInventory();
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Skill))
|
||||||
|
{
|
||||||
|
foreach (EntityFactoryDelegate<Skill> d in SkillFactories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (d.Invoke(id, name, args) is T skill)
|
||||||
|
{
|
||||||
|
return skill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Skill openSkill = new OpenSkill(id, name, args);
|
||||||
|
if (args.TryGetValue("values", out object? value) && value is Dictionary<string, object> dict)
|
||||||
|
{
|
||||||
|
foreach (string key in dict.Keys)
|
||||||
|
{
|
||||||
|
openSkill.Values[key] = dict[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T)(object)openSkill;
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Effect))
|
||||||
|
{
|
||||||
|
foreach (EntityFactoryDelegate<Effect> d in EffectFactories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (d.Invoke(id, name, args) is T effect)
|
||||||
|
{
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T)(object)GetEffect();
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Item))
|
||||||
|
{
|
||||||
|
foreach (EntityFactoryDelegate<Item> d in ItemFactories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (d.Invoke(id, name, args) is T item)
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T)(object)new OpenItem(id, name, args);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Room))
|
||||||
|
{
|
||||||
|
foreach (EntityFactoryDelegate<Room> d in RoomFactories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (d.Invoke(id, name, args) is T room)
|
||||||
|
{
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T)(object)GetRoom();
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(User))
|
||||||
|
{
|
||||||
|
foreach (EntityFactoryDelegate<User> d in UserFactories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (d.Invoke(id, name, args) is T user)
|
||||||
|
{
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T)(object)GetUser();
|
||||||
|
}
|
||||||
|
throw new NotSupportedInstanceClassException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 此方法使用 <see cref="EntityModuleConfig{T}"/> 取得一个实体字典
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="module_name"></param>
|
||||||
|
/// <param name="file_name"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Dictionary<string, T> GetGameModuleInstances<T>(string module_name, string file_name) where T : BaseEntity
|
||||||
|
{
|
||||||
|
EntityModuleConfig<T> config = new(module_name, file_name);
|
||||||
|
config.LoadConfig();
|
||||||
|
if (typeof(T) == typeof(Skill))
|
||||||
|
{
|
||||||
|
OpenSkillAdapter.Adaptation(config);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Item))
|
||||||
|
{
|
||||||
|
OpenItemAdapter.Adaptation(config);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 <see cref="EntityModuleConfig{T}"/> 构造一个实体字典并保存
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="module_name"></param>
|
||||||
|
/// <param name="file_name"></param>
|
||||||
|
/// <param name="dict"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static void CreateGameModuleEntityConfig<T>(string module_name, string file_name, Dictionary<string, T> dict) where T : BaseEntity
|
||||||
|
{
|
||||||
|
EntityModuleConfig<T> config = new(module_name, file_name);
|
||||||
|
foreach (string key in dict.Keys)
|
||||||
|
{
|
||||||
|
config[key] = dict[key];
|
||||||
|
}
|
||||||
|
config.SaveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal HashSet<SQLHelperFactoryDelegate> SQLHelperFactories { get; } = [];
|
||||||
|
|
||||||
|
public delegate SQLHelper? SQLHelperFactoryDelegate();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册工厂方法 [SQLHelper]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="d"></param>
|
||||||
|
public void RegisterFactory(SQLHelperFactoryDelegate d)
|
||||||
|
{
|
||||||
|
SQLHelperFactories.Add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个 SQLHelper 实例
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public SQLHelper? GetSQLHelper()
|
||||||
|
{
|
||||||
|
foreach (SQLHelperFactoryDelegate d in SQLHelperFactories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (d.Invoke() is SQLHelper helper)
|
||||||
|
{
|
||||||
|
return helper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal HashSet<MailSenderFactoryDelegate> MailSenderFactories { get; } = [];
|
||||||
|
|
||||||
|
public delegate MailSender? MailSenderFactoryDelegate();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册工厂方法 [MailSender]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="d"></param>
|
||||||
|
public void RegisterFactory(MailSenderFactoryDelegate d)
|
||||||
|
{
|
||||||
|
MailSenderFactories.Add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个 MailSender 实例
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MailSender? GetMailSender()
|
||||||
|
{
|
||||||
|
foreach (MailSenderFactoryDelegate d in MailSenderFactories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (d.Invoke() is MailSender sender)
|
||||||
|
{
|
||||||
|
return sender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly static CharacterFactory CharacterFactory = new();
|
||||||
|
private readonly static InventoryFactory InventoryFactory = new();
|
||||||
|
private readonly static SkillFactory SkillFactory = new();
|
||||||
|
private readonly static EffectFactory EffectFactory = new();
|
||||||
|
private readonly static ItemFactory ItemFactory = new();
|
||||||
|
private readonly static RoomFactory RoomFactory = new();
|
||||||
|
private readonly static UserFactory UserFactory = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取角色实例
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Character GetCharacter()
|
||||||
|
{
|
||||||
|
return CharacterFactory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取库存实例
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Inventory GetInventory()
|
||||||
|
{
|
||||||
|
return InventoryFactory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取技能实例
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Skill GetSkill()
|
||||||
|
{
|
||||||
|
return SkillFactory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取技能特效实例
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Effect GetEffect()
|
||||||
|
{
|
||||||
|
return EffectFactory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取物品实例
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Item GetItem()
|
||||||
|
{
|
||||||
|
return ItemFactory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取房间实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">房间内部序列号</param>
|
||||||
|
/// <param name="roomid">房间号</param>
|
||||||
|
/// <param name="createTime">创建时间</param>
|
||||||
|
/// <param name="roomMaster">房主</param>
|
||||||
|
/// <param name="roomType">房间类型</param>
|
||||||
|
/// <param name="gameModule">游戏模组</param>
|
||||||
|
/// <param name="gameMap"></param>
|
||||||
|
/// <param name="roomState">房间状态</param>
|
||||||
|
/// <param name="isRank"></param>
|
||||||
|
/// <param name="password">房间密码</param>
|
||||||
|
/// <param name="maxUsers">人数上限</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Room GetRoom(long id = 0, string roomid = "-1", DateTime? createTime = null, User? roomMaster = null, RoomType roomType = RoomType.All, string gameModule = "", string gameMap = "", RoomState roomState = RoomState.Created, bool isRank = false, string password = "", int maxUsers = 4)
|
||||||
|
{
|
||||||
|
return RoomFactory.Create(id, roomid, createTime, roomMaster, roomType, gameModule, gameMap, roomState, isRank, password, maxUsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过DataSet获取房间实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="drRoom"></param>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Room GetRoom(DataRow drRoom, User user)
|
||||||
|
{
|
||||||
|
Room room = General.HallInstance;
|
||||||
|
if (drRoom != null)
|
||||||
|
{
|
||||||
|
long id = (long)drRoom[RoomQuery.Column_ID];
|
||||||
|
string roomid = (string)drRoom[RoomQuery.Column_Roomid];
|
||||||
|
if (!DateTime.TryParse(drRoom[RoomQuery.Column_CreateTime].ToString(), out DateTime createTime))
|
||||||
|
{
|
||||||
|
createTime = General.DefaultTime;
|
||||||
|
}
|
||||||
|
User roomMaster = user;
|
||||||
|
RoomType roomType = (RoomType)Convert.ToInt32(drRoom[RoomQuery.Column_RoomType]);
|
||||||
|
string gameModule = (string)drRoom[RoomQuery.Column_GameModule];
|
||||||
|
string gameMap = (string)drRoom[RoomQuery.Column_GameMap];
|
||||||
|
RoomState roomState = (RoomState)Convert.ToInt32(drRoom[RoomQuery.Column_RoomState]);
|
||||||
|
bool isRank = Convert.ToInt32(drRoom[RoomQuery.Column_IsRank]) == 1;
|
||||||
|
string password = (string)drRoom[RoomQuery.Column_Password];
|
||||||
|
int maxUsers = Convert.ToInt32(drRoom[RoomQuery.Column_MaxUsers]);
|
||||||
|
room = GetRoom(id, roomid, createTime, roomMaster, roomType, gameModule, gameMap, roomState, isRank, password, maxUsers);
|
||||||
|
}
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过DataSet获取房间列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dsRoom"></param>
|
||||||
|
/// <param name="dsUser"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static List<Room> GetRooms(DataSet dsRoom, DataSet dsUser)
|
||||||
|
{
|
||||||
|
List<Room> list =
|
||||||
|
[
|
||||||
|
General.HallInstance
|
||||||
|
];
|
||||||
|
if (dsRoom != null && dsRoom.Tables[0].Rows.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (DataRow drRoom in dsRoom.Tables[0].Rows)
|
||||||
|
{
|
||||||
|
long Id = (long)drRoom[RoomQuery.Column_ID];
|
||||||
|
string Roomid = (string)drRoom[RoomQuery.Column_Roomid];
|
||||||
|
if (!DateTime.TryParse(drRoom[RoomQuery.Column_CreateTime].ToString(), out DateTime createTime))
|
||||||
|
{
|
||||||
|
createTime = General.DefaultTime;
|
||||||
|
}
|
||||||
|
User roomMaster = General.UnknownUserInstance;
|
||||||
|
if (dsUser != null && dsUser.Tables.Count > 0)
|
||||||
|
{
|
||||||
|
DataRow[] rows = dsUser.Tables[0].Select($"{UserQuery.Column_Id} = {(long)drRoom[RoomQuery.Column_RoomMaster]}");
|
||||||
|
if (rows.Length > 0)
|
||||||
|
{
|
||||||
|
roomMaster = GetUser(rows[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RoomType roomType = (RoomType)Convert.ToInt32(drRoom[RoomQuery.Column_RoomType]);
|
||||||
|
string gameModule = (string)drRoom[RoomQuery.Column_GameModule];
|
||||||
|
string gameMap = (string)drRoom[RoomQuery.Column_GameMap];
|
||||||
|
RoomState roomState = (RoomState)Convert.ToInt32(drRoom[RoomQuery.Column_RoomState]);
|
||||||
|
bool isRank = Convert.ToInt32(drRoom[RoomQuery.Column_IsRank]) == 1;
|
||||||
|
string password = (string)drRoom[RoomQuery.Column_Password];
|
||||||
|
list.Add(GetRoom(Id, Roomid, createTime, roomMaster, roomType, gameModule, gameMap, roomState, isRank, password));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取大厅(-1号房)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Room GetHall()
|
||||||
|
{
|
||||||
|
return RoomFactory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户实例
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static User GetUser()
|
||||||
|
{
|
||||||
|
return UserFactory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Id"></param>
|
||||||
|
/// <param name="Username"></param>
|
||||||
|
/// <param name="RegTime"></param>
|
||||||
|
/// <param name="LastTime"></param>
|
||||||
|
/// <param name="Email"></param>
|
||||||
|
/// <param name="NickName"></param>
|
||||||
|
/// <param name="IsAdmin"></param>
|
||||||
|
/// <param name="IsOperator"></param>
|
||||||
|
/// <param name="IsEnable"></param>
|
||||||
|
/// <param name="GameTime"></param>
|
||||||
|
/// <param name="AutoKey"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static User GetUser(long Id = 0, string Username = "", DateTime? RegTime = null, DateTime? LastTime = null, string Email = "", string NickName = "", bool IsAdmin = false, bool IsOperator = false, bool IsEnable = true, double GameTime = 0, string AutoKey = "")
|
||||||
|
{
|
||||||
|
return UserFactory.Create(Id, Username, RegTime, LastTime, Email, NickName, IsAdmin, IsOperator, IsEnable, GameTime, AutoKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static User GetUser(DataSet ds)
|
||||||
|
{
|
||||||
|
if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
|
||||||
|
{
|
||||||
|
return GetUser(ds.Tables[0].Rows[0]);
|
||||||
|
}
|
||||||
|
return UserFactory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dr"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static User GetUser(DataRow dr)
|
||||||
|
{
|
||||||
|
if (dr != null)
|
||||||
|
{
|
||||||
|
long Id = (long)dr[UserQuery.Column_Id];
|
||||||
|
string Username = (string)dr[UserQuery.Column_Username];
|
||||||
|
if (!DateTime.TryParse(dr[UserQuery.Column_RegTime].ToString(), out DateTime RegTime))
|
||||||
|
{
|
||||||
|
RegTime = General.DefaultTime;
|
||||||
|
}
|
||||||
|
if (!DateTime.TryParse(dr[UserQuery.Column_LastTime].ToString(), out DateTime LastTime))
|
||||||
|
{
|
||||||
|
LastTime = General.DefaultTime;
|
||||||
|
}
|
||||||
|
string Email = (string)dr[UserQuery.Column_Email];
|
||||||
|
string NickName = (string)dr[UserQuery.Column_Nickname];
|
||||||
|
bool IsAdmin = Convert.ToInt32(dr[UserQuery.Column_IsAdmin]) == 1;
|
||||||
|
bool IsOperator = Convert.ToInt32(dr[UserQuery.Column_IsOperator]) == 1;
|
||||||
|
bool IsEnable = Convert.ToInt32(dr[UserQuery.Column_IsEnable]) == 1;
|
||||||
|
double GameTime = Convert.ToDouble(dr[UserQuery.Column_GameTime]);
|
||||||
|
string AutoKey = (string)dr[UserQuery.Column_AutoKey];
|
||||||
|
return UserFactory.Create(Id, Username, RegTime, LastTime, Email, NickName, IsAdmin, IsOperator, IsEnable, GameTime, AutoKey);
|
||||||
|
}
|
||||||
|
return UserFactory.Create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Api/Utility/GameModuleLoader.cs
Normal file
122
Api/Utility/GameModuleLoader.cs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Service;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public class GameModuleLoader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 适用于客户端的模组集
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, GameModule> Modules { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 适用于服务器的模组集
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, GameModuleServer> ModuleServers { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 游戏地图集
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, GameMap> Maps { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色表
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, CharacterModule> Characters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能表
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, SkillModule> Skills { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物品表
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, ItemModule> Items { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已加载的模组DLL名称对应的路径
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, string> ModuleFilePaths => new(AddonManager.ModuleFilePaths);
|
||||||
|
|
||||||
|
private GameModuleLoader() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 传入 <see cref="FunGameInfo.FunGame"/> 类型来创建指定端的模组读取器
|
||||||
|
/// <para>runtime = <see cref="FunGameInfo.FunGame.FunGame_Desktop"/> 时,仅读取 <seealso cref="Modules"/></para>
|
||||||
|
/// <para>runtime = <see cref="FunGameInfo.FunGame.FunGame_Server"/> 时,仅读取 <seealso cref="ModuleServers"/></para>
|
||||||
|
/// <seealso cref="Maps"/> 都会读取
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runtime">传入 <see cref="FunGameInfo.FunGame"/> 类型来创建指定端的模组读取器</param>
|
||||||
|
/// <param name="delegates">用于构建 <see cref="Controller.AddonController{T}"/></param>
|
||||||
|
/// <param name="otherobjs">其他需要传入给插件初始化的对象</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static GameModuleLoader LoadGameModules(FunGameInfo.FunGame runtime, Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
GameModuleLoader loader = new();
|
||||||
|
if (runtime == FunGameInfo.FunGame.FunGame_Desktop)
|
||||||
|
{
|
||||||
|
AddonManager.LoadGameMaps(loader.Maps, otherobjs);
|
||||||
|
AddonManager.LoadGameModules(loader.Modules, loader.Characters, loader.Skills, loader.Items, delegates, otherobjs);
|
||||||
|
foreach (GameModule module in loader.Modules.Values.ToList())
|
||||||
|
{
|
||||||
|
// 读取模组的依赖集合
|
||||||
|
module.GameModuleDepend.GetDependencies(loader);
|
||||||
|
// 如果模组加载后需要执行代码,请重写AfterLoad方法
|
||||||
|
module.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (runtime == FunGameInfo.FunGame.FunGame_Server)
|
||||||
|
{
|
||||||
|
AddonManager.LoadGameMaps(loader.Maps, otherobjs);
|
||||||
|
AddonManager.LoadGameModulesForServer(loader.ModuleServers, loader.Characters, loader.Skills, loader.Items, delegates, otherobjs);
|
||||||
|
foreach (GameModuleServer server in loader.ModuleServers.Values.ToList())
|
||||||
|
{
|
||||||
|
server.GameModuleDepend.GetDependencies(loader);
|
||||||
|
server.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对应名称的模组实例
|
||||||
|
/// <para>如果需要取得服务器模组的实例,请调用 <see cref="GetServerMode"/></para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public GameModule this[string name]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Modules[name];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Modules.TryAdd(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对应名称的服务器模组实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public GameModuleServer GetServerMode(string name)
|
||||||
|
{
|
||||||
|
return ModuleServers[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对应名称的游戏地图
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public GameMap GetGameMap(string name)
|
||||||
|
{
|
||||||
|
return Maps[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
829
Api/Utility/General.cs
Normal file
829
Api/Utility/General.cs
Normal file
@ -0,0 +1,829 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Architecture;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
// 通用工具类,客户端和服务器端都可以直接调用的工具方法都可以写在这里
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
#region 网络服务
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 网络服务工具箱
|
||||||
|
/// </summary>
|
||||||
|
public class NetworkUtility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 判断字符串是否是IP地址
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsIP(string str) => Regex.IsMatch(str, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断字符串是否为邮箱地址
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsEmail(string str) => Regex.IsMatch(str, @"^(\w)+(\.\w)*@(\w)+((\.\w+)+)$");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断字符串是否是正常的用户名(只有中英文和数字)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsUserName(string str) => Regex.IsMatch(str, @"^[\u4e00-\u9fffA-Za-z0-9]+$");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断字符串是否是全中文的字符
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsChineseName(string str) => Regex.IsMatch(str, @"^[\u4e00-\u9fff]+$");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户名长度
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static int GetUserNameLength(string str)
|
||||||
|
{
|
||||||
|
int length = 0;
|
||||||
|
for (int i = 0; i < str.Length; i++)
|
||||||
|
{
|
||||||
|
char c = str[i];
|
||||||
|
if (c is >= 'A' and <= 'Z' or >= 'a' and <= 'z' or >= '0' and <= '9') length++;
|
||||||
|
else length += 2;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断字符串是否是一个FunGame可接受的服务器地址<para/>
|
||||||
|
/// 此方法可以解析域名
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str"></param>
|
||||||
|
/// <param name="addr"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <returns>返回地址验证结果,同时输出服务器地址和端口号</returns>
|
||||||
|
public static ErrorIPAddressType IsServerAddress(string str, out string addr, out int port)
|
||||||
|
{
|
||||||
|
addr = "";
|
||||||
|
port = 22222;
|
||||||
|
string ip;
|
||||||
|
// 包含端口号时,需要先截取
|
||||||
|
string[] strs = str.Split(':');
|
||||||
|
if (strs.Length == 1)
|
||||||
|
{
|
||||||
|
addr = str;
|
||||||
|
}
|
||||||
|
else if (strs.Length > 1)
|
||||||
|
{
|
||||||
|
addr = strs[0];
|
||||||
|
port = int.Parse(strs[1]);
|
||||||
|
}
|
||||||
|
else if (strs.Length > 2)
|
||||||
|
{
|
||||||
|
return ErrorIPAddressType.WrongFormat;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ip = GetIPAddress(addr);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ip = strs[0];
|
||||||
|
}
|
||||||
|
return IsServerAddress(ip, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断参数是否是一个FunGame可接受的服务器地址
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ip"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ErrorIPAddressType IsServerAddress(string ip, int port)
|
||||||
|
{
|
||||||
|
if (IsIP(ip))
|
||||||
|
{
|
||||||
|
if (port > 0 && port < 65536)
|
||||||
|
{
|
||||||
|
return ErrorIPAddressType.None;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ErrorIPAddressType.IsNotPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (port > 0 && port < 65536)
|
||||||
|
{
|
||||||
|
return ErrorIPAddressType.IsNotAddress;
|
||||||
|
}
|
||||||
|
else return ErrorIPAddressType.WrongFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取服务器的延迟
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addr">服务器IP地址</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static int GetServerPing(string addr)
|
||||||
|
{
|
||||||
|
Ping pingSender = new();
|
||||||
|
PingOptions options = new()
|
||||||
|
{
|
||||||
|
DontFragment = true
|
||||||
|
};
|
||||||
|
string data = "getserverping";
|
||||||
|
byte[] buffer = General.DefaultEncoding.GetBytes(data);
|
||||||
|
int timeout = 120;
|
||||||
|
PingReply reply = pingSender.Send(addr, timeout, buffer, options);
|
||||||
|
if (reply.Status == IPStatus.Success)
|
||||||
|
{
|
||||||
|
return Convert.ToInt32(reply.RoundtripTime);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析域名为IP地址
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="domain"></param>
|
||||||
|
/// <param name="family"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal static string GetIPAddress(string domain, System.Net.Sockets.AddressFamily family = System.Net.Sockets.AddressFamily.InterNetwork)
|
||||||
|
{
|
||||||
|
// 如果是域名,则解析为IP地址
|
||||||
|
IPHostEntry entrys = Dns.GetHostEntry(domain);
|
||||||
|
// 这里使用断言,请自行添加try catch配合使用
|
||||||
|
return entrys.AddressList.Where(addr => addr.AddressFamily == family).FirstOrDefault()!.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 返回目标对象的Json字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string JsonSerialize<T>(T obj) => Service.JsonManager.GetString(obj);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 返回目标对象的Json字符串 可指定反序列化选项
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string JsonSerialize<T>(T obj, JsonSerializerOptions options) => Service.JsonManager.GetString(obj, options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Json对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="json"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? JsonDeserialize<T>(string json) => Service.JsonManager.GetObject<T>(json);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Json对象 使用 <paramref name="reader"/> 可指定反序列化选项
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="reader"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? JsonDeserialize<T>(ref Utf8JsonReader reader, JsonSerializerOptions options) => Service.JsonManager.GetObject<T>(ref reader, options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Json对象 可指定反序列化选项
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="json"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? JsonDeserialize<T>(string json, JsonSerializerOptions options) => Service.JsonManager.GetObject<T>(json, options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Hashtable中的Json对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="hashtable"></param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? JsonDeserializeFromHashtable<T>(Hashtable hashtable, string key) => Service.JsonManager.GetObject<T>(hashtable, key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Dictionary中的Json对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="dict"></param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? JsonDeserializeFromDictionary<T>(Dictionary<string, object> dict, string key) => Service.JsonManager.GetObject<T>(dict, key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化IEnumerable中的Json对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? JsonDeserializeFromIEnumerable<T>(IEnumerable<object> e, int index) => Service.JsonManager.GetObject<T>(e, index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化IEnumerable中的Json对象 可指定反序列化选项
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? JsonDeserializeFromIEnumerable<T>(IEnumerable<object> e, int index, JsonSerializerOptions options) => Service.JsonManager.GetObject<T>(e, index, options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Hashtable中的Json对象 可指定反序列化选项
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="hashtable"></param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? JsonDeserializeFromHashtable<T>(Hashtable hashtable, string key, JsonSerializerOptions options) => Service.JsonManager.GetObject<T>(hashtable, key, options);
|
||||||
|
|
||||||
|
// 创建一个静态 HttpClient 实例,供整个应用程序生命周期使用
|
||||||
|
private static readonly HttpClient client = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送 GET 请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task<T?> HttpGet<T>(string url)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.GetAsync(url);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
string content = await response.Content.ReadAsStringAsync();
|
||||||
|
T? result = JsonDeserialize<T>(content);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送 POST 请求
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="url"></param>
|
||||||
|
/// <param name="json"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task<T?> HttpPost<T>(string url, string json)
|
||||||
|
{
|
||||||
|
HttpContent content = new StringContent(json, General.DefaultEncoding, "application/json");
|
||||||
|
HttpResponseMessage response = await client.PostAsync(url, content);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
string read = await response.Content.ReadAsStringAsync();
|
||||||
|
T? result = JsonDeserialize<T>(read);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 时间服务
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 时间服务工具箱
|
||||||
|
/// </summary>
|
||||||
|
public class DateTimeUtility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取系统时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">格式化类型</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static DateTime GetDateTime(TimeType type)
|
||||||
|
{
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
if (type == TimeType.LongDateOnly || type == TimeType.ShortDateOnly)
|
||||||
|
return now.Date;
|
||||||
|
else return now;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过字符串转换为DateTime对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">时间字符串</param>
|
||||||
|
/// <returns>转换失败返回当前时间</returns>
|
||||||
|
public static DateTime GetDateTime(string format)
|
||||||
|
{
|
||||||
|
if (DateTime.TryParse(format, out DateTime dt))
|
||||||
|
{
|
||||||
|
return dt;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取系统时间并转为字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">格式化类型</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetDateTimeToString(TimeType type)
|
||||||
|
{
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
TimeType.LongDateOnly => now.Date.ToString("D"),
|
||||||
|
TimeType.ShortDateOnly => now.Date.ToString("d"),
|
||||||
|
TimeType.LongTimeOnly => now.ToString("T"),
|
||||||
|
TimeType.ShortTimeOnly => now.ToString("t"),
|
||||||
|
TimeType.Year4 => now.Year.ToString(),
|
||||||
|
TimeType.Year2 => "'" + now.ToString("yy"),
|
||||||
|
TimeType.Month => now.Month.ToString(),
|
||||||
|
TimeType.Day => now.Day.ToString(),
|
||||||
|
TimeType.Hour => now.Hour.ToString(),
|
||||||
|
TimeType.Minute => now.Minute.ToString(),
|
||||||
|
TimeType.Second => now.Second.ToString(),
|
||||||
|
_ => now.ToString("yyyy/MM/dd HH:mm:ss")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取系统时间并转为字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">格式化字符串</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetDateTimeToString(string format) => DateTime.Now.ToString(format);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取系统短时间
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>时:分:秒</returns>
|
||||||
|
public static string GetNowShortTime()
|
||||||
|
{
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
return now.AddMilliseconds(-now.Millisecond).ToString("T");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取系统日期
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetNowTime()
|
||||||
|
{
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
return now.AddMilliseconds(-now.Millisecond).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 DLL 编译时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dll"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetBuiltTime(string dll)
|
||||||
|
{
|
||||||
|
DateTime lastWriteTime = File.GetLastWriteTime(dll);
|
||||||
|
|
||||||
|
string month = lastWriteTime.ToString("MMM", CultureInfo.InvariantCulture);
|
||||||
|
int day = lastWriteTime.Day;
|
||||||
|
string time = lastWriteTime.ToString("HH:mm:ss", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
return $"{month}. {day}, {lastWriteTime.Year} {time}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取下一次可交易的时间
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static DateTime GetTradableTime(DateTime? date = null)
|
||||||
|
{
|
||||||
|
date ??= DateTime.Now;
|
||||||
|
if (date is DateTime d)
|
||||||
|
{
|
||||||
|
if (d.Hour < 15)
|
||||||
|
{
|
||||||
|
return d.Date.AddDays(1).AddHours(15);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return d.Date.AddDays(2).AddHours(9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DateTime.MinValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 加密服务
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加密服务工具箱
|
||||||
|
/// </summary>
|
||||||
|
public class Encryption
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 HMAC-SHA512 算法对文本进行加密
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">需要加密的文本</param>
|
||||||
|
/// <param name="key">用于加密的秘钥</param>
|
||||||
|
/// <returns>加密后的 HMAC-SHA512 哈希值</returns>
|
||||||
|
public static string HmacSha512(string text, string key)
|
||||||
|
{
|
||||||
|
byte[] text_bytes = General.DefaultEncoding.GetBytes(text);
|
||||||
|
key = Convert.ToBase64String(General.DefaultEncoding.GetBytes(key));
|
||||||
|
byte[] key_bytes = General.DefaultEncoding.GetBytes(key);
|
||||||
|
using HMACSHA512 hmacsha512 = new(key_bytes);
|
||||||
|
byte[] hash_bytes = hmacsha512.ComputeHash(text_bytes);
|
||||||
|
string hmac = Convert.ToHexString(hash_bytes);
|
||||||
|
return hmac.ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 HMAC-SHA256 算法对文本进行加密
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">需要加密的文本</param>
|
||||||
|
/// <param name="key">用于加密的秘钥</param>
|
||||||
|
/// <returns>加密后的 HMAC-SHA256 哈希值</returns>
|
||||||
|
public static string HmacSha256(string text, string key)
|
||||||
|
{
|
||||||
|
byte[] text_bytes = General.DefaultEncoding.GetBytes(text);
|
||||||
|
key = Convert.ToBase64String(General.DefaultEncoding.GetBytes(key));
|
||||||
|
byte[] key_bytes = General.DefaultEncoding.GetBytes(key);
|
||||||
|
using HMACSHA256 hmacsha256 = new(key_bytes);
|
||||||
|
byte[] hash_bytes = hmacsha256.ComputeHash(text_bytes);
|
||||||
|
string hmac = Convert.ToHexString(hash_bytes);
|
||||||
|
return hmac.ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 SHA256 算法对文本进行加密
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">需要加密的文本</param>
|
||||||
|
/// <returns>加密后的 SHA256 哈希值</returns>
|
||||||
|
public static string Sha256(string text)
|
||||||
|
{
|
||||||
|
byte[] textBytes = Encoding.UTF8.GetBytes(text);
|
||||||
|
byte[] hashBytes = SHA256.HashData(textBytes);
|
||||||
|
return Convert.ToHexString(hashBytes).ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算文件的 SHA-256 哈希值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file_path">要计算哈希值的文件路径</param>
|
||||||
|
/// <returns>文件的 SHA-256 哈希值</returns>
|
||||||
|
public static string FileSha256(string file_path)
|
||||||
|
{
|
||||||
|
using SHA256 sha256 = SHA256.Create();
|
||||||
|
using FileStream stream = File.OpenRead(file_path);
|
||||||
|
byte[] hash = sha256.ComputeHash(stream);
|
||||||
|
return Convert.ToHexStringLower(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 RSA 算法加密
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plain_text">明文</param>
|
||||||
|
/// <param name="plublic_key">公钥</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string RSAEncrypt(string plain_text, string plublic_key)
|
||||||
|
{
|
||||||
|
byte[] plain = General.DefaultEncoding.GetBytes(plain_text);
|
||||||
|
using RSACryptoServiceProvider rsa = new();
|
||||||
|
rsa.FromXmlString(plublic_key);
|
||||||
|
byte[] encrypted = rsa.Encrypt(plain, true);
|
||||||
|
return Convert.ToBase64String(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 RSA 算法解密
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="secret_text">密文</param>
|
||||||
|
/// <param name="private_key">私钥</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string RSADecrypt(string secret_text, string private_key)
|
||||||
|
{
|
||||||
|
byte[] secret = Convert.FromBase64String(secret_text);
|
||||||
|
using RSACryptoServiceProvider rsa = new();
|
||||||
|
rsa.FromXmlString(private_key);
|
||||||
|
byte[] decrypted = rsa.Decrypt(secret, true);
|
||||||
|
return General.DefaultEncoding.GetString(decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 MD5 算法加密
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string MD5(string text)
|
||||||
|
{
|
||||||
|
byte[] inputBytes = General.DefaultEncoding.GetBytes(text);
|
||||||
|
byte[] hash = System.Security.Cryptography.MD5.HashData(inputBytes);
|
||||||
|
return Convert.ToHexStringLower(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成随机字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GenerateRandomString(int length = 32)
|
||||||
|
{
|
||||||
|
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+=-`~[]\\{}|;':\",./<>?";
|
||||||
|
byte[] data = RandomNumberGenerator.GetBytes(length);
|
||||||
|
|
||||||
|
StringBuilder result = new(length);
|
||||||
|
foreach (byte b in data)
|
||||||
|
{
|
||||||
|
result.Append(chars[b % chars.Length]);
|
||||||
|
}
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 验证服务
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证码服务工具箱
|
||||||
|
/// </summary>
|
||||||
|
public class Verification
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 生成验证码
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">类型</param>
|
||||||
|
/// <param name="length">长度</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string CreateVerifyCode(VerifyCodeType type, int length)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
VerifyCodeType.MixVerifyCode => MixVerifyCode(length),
|
||||||
|
VerifyCodeType.LetterVerifyCode => LetterVerifyCode(length),
|
||||||
|
_ => NumberVerifyCode(length),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数字验证码
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string NumberVerifyCode(int length)
|
||||||
|
{
|
||||||
|
int[] RandMembers = new int[length];
|
||||||
|
int[] GetNumbers = new int[length];
|
||||||
|
string VerifyCode = "";
|
||||||
|
//生成起始序列值
|
||||||
|
int seekSeek = unchecked((int)DateTime.Now.Ticks);
|
||||||
|
Random seekRand = new(seekSeek);
|
||||||
|
int beginSeek = seekRand.Next(0, int.MaxValue - length * 10000);
|
||||||
|
int[] seeks = new int[length];
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
beginSeek += 10000;
|
||||||
|
seeks[i] = beginSeek;
|
||||||
|
}
|
||||||
|
//生成随机数字
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
Random rand = new(seeks[i]);
|
||||||
|
int pownum = 1 * (int)Math.Pow(10, length);
|
||||||
|
RandMembers[i] = rand.Next(pownum, int.MaxValue);
|
||||||
|
}
|
||||||
|
//抽取随机数字
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
string numStr = RandMembers[i].ToString();
|
||||||
|
int numLength = numStr.Length;
|
||||||
|
Random rand = new();
|
||||||
|
int numPosition = rand.Next(0, numLength - 1);
|
||||||
|
GetNumbers[i] = int.Parse(numStr.Substring(numPosition, 1));
|
||||||
|
}
|
||||||
|
//生成验证码
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
VerifyCode += GetNumbers[i].ToString();
|
||||||
|
}
|
||||||
|
return VerifyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 字母验证码
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string LetterVerifyCode(int length)
|
||||||
|
{
|
||||||
|
char[] Verification = new char[length];
|
||||||
|
char[] Dictionary = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' ];
|
||||||
|
Random random = new();
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
Verification[i] = Dictionary[random.Next(Dictionary.Length - 1)];
|
||||||
|
}
|
||||||
|
return new string(Verification);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 混合验证码
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string MixVerifyCode(int length)
|
||||||
|
{
|
||||||
|
char[] Verification = new char[length];
|
||||||
|
char[] Dictionary = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
|
||||||
|
];
|
||||||
|
Random random = new();
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
Verification[i] = Dictionary[random.Next(Dictionary.Length - 1)];
|
||||||
|
}
|
||||||
|
return new string(Verification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WebAPIAuthenticator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Web API 自定义 Token 验证器
|
||||||
|
/// </summary>
|
||||||
|
public static event Func<string, string>? WebAPICustomBearerTokenAuthenticator;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加自定义 Token 验证器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler"></param>
|
||||||
|
public static void AddCustomBearerTokenHandler(Func<string, string> handler)
|
||||||
|
{
|
||||||
|
WebAPICustomBearerTokenAuthenticator += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对自定义 Token 进行验证
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string ValidateToken(string token)
|
||||||
|
{
|
||||||
|
string result = "";
|
||||||
|
|
||||||
|
if (WebAPICustomBearerTokenAuthenticator != null)
|
||||||
|
{
|
||||||
|
foreach (Delegate handler in WebAPICustomBearerTokenAuthenticator.GetInvocationList())
|
||||||
|
{
|
||||||
|
if (handler is Func<string, string> authHandler)
|
||||||
|
{
|
||||||
|
string name = authHandler.Invoke(token);
|
||||||
|
if (name != "")
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 多线程服务
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 多线程服务工具箱
|
||||||
|
/// </summary>
|
||||||
|
public class TaskUtility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 开启一个异步任务。调用返回对象的 OnCompleted() 方法可以执行后续操作
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
public static TaskAwaiter NewTask(Action action) => new(Service.TaskManager.NewTask(action));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开启一个异步任务。调用返回对象的 OnCompleted() 方法可以执行后续操作
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task"></param>
|
||||||
|
public static TaskAwaiter NewTask(Func<Task> task) => new(Service.TaskManager.NewTask(task));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开启一个计时器任务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
/// <param name="milliseconds"></param>
|
||||||
|
public static void RunTimer(Action action, int milliseconds)
|
||||||
|
{
|
||||||
|
Service.TaskManager.NewTask(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(milliseconds);
|
||||||
|
}).OnCompleted(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加任务计划,使用 <paramref name="time"/> 的时分秒。如果用 <see cref="TimeSpan"/>,请直接在 <see cref="TaskScheduler.Shared"/> 中添加
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="time"></param>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
public static void AddSchedulerTask(string name, DateTime time, Action action)
|
||||||
|
{
|
||||||
|
TaskScheduler.Shared.AddTask(name, new TimeSpan(time.Hour, time.Minute, time.Second), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加循环任务。如果用 <see cref="TimeSpan"/>,请直接在 <see cref="TaskScheduler.Shared"/> 中添加
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="seconds"></param>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
/// <param name="hours"></param>
|
||||||
|
/// <param name="minutes"></param>
|
||||||
|
/// <param name="startNow"></param>
|
||||||
|
public static void AddRecurringTask(string name, int seconds, Action action, int hours = 0, int minutes = 0, bool startNow = false)
|
||||||
|
{
|
||||||
|
TaskScheduler.Shared.AddRecurringTask(name, new TimeSpan(hours, minutes, seconds), action, startNow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 计算服务
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算服务工具箱
|
||||||
|
/// </summary>
|
||||||
|
public class Calculation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 四舍五入计算
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="digits"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static double Round(double value, int digits)
|
||||||
|
{
|
||||||
|
return Math.Round(value, digits, MidpointRounding.AwayFromZero);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 四舍五入保留2位小数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static double Round2Digits(double value)
|
||||||
|
{
|
||||||
|
return Round(value, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 四舍五入保留4位小数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static double Round4Digits(double value)
|
||||||
|
{
|
||||||
|
return Round(value, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 此方法检查一个 百分比(%) 数值是否存在于 [0,1] 区间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns>如果低于0,则返回0;超过1则返回1。</returns>
|
||||||
|
public static double PercentageCheck(double value)
|
||||||
|
{
|
||||||
|
return Math.Max(0, Math.Min(value, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查 <paramref name="value"/> 是否约等于 0
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="epsilon"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsApproximatelyZero(double value, double epsilon = 1e-10)
|
||||||
|
{
|
||||||
|
return Math.Abs(value) < epsilon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
114
Api/Utility/Implement.cs
Normal file
114
Api/Utility/Implement.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// See: <see cref="InterfaceMethod"/>, <see cref="InterfaceType"/>, <see cref="InterfaceSet"/>
|
||||||
|
/// </summary>
|
||||||
|
public class Implement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取FunGame.Implement.dll中接口的实现方法
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Assembly">程序集</param>
|
||||||
|
/// <param name="Interface">接口代号</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static Type? GetFunGameImplementType(Assembly Assembly, InterfaceType Interface)
|
||||||
|
{
|
||||||
|
// 通过类名获取命名空间+类名称
|
||||||
|
string ClassName = GetImplementClassName(Interface);
|
||||||
|
List<Type>? Classes = null;
|
||||||
|
if (Assembly != null)
|
||||||
|
{
|
||||||
|
Classes = Assembly.GetTypes().Where(w =>
|
||||||
|
w.Namespace == "Milimoe.FunGame.Core.Implement" &&
|
||||||
|
w.Name.Contains(ClassName)
|
||||||
|
).ToList();
|
||||||
|
if (Classes != null && Classes.Count > 0)
|
||||||
|
return Classes[0];
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取接口实现类类名
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Interface">接口类型</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string GetImplementClassName(InterfaceType Interface)
|
||||||
|
{
|
||||||
|
return Interface switch
|
||||||
|
{
|
||||||
|
InterfaceType.IClient => InterfaceSet.Type.IClient,
|
||||||
|
InterfaceType.IServer => InterfaceSet.Type.IServer,
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取接口方法名(支持属性)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Method">方法</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string GetImplementMethodName(InterfaceMethod Method)
|
||||||
|
{
|
||||||
|
return Method switch
|
||||||
|
{
|
||||||
|
InterfaceMethod.RemoteServerIP => InterfaceSet.Method.RemoteServerIP,
|
||||||
|
InterfaceMethod.DBConnection => InterfaceSet.Method.DBConnection,
|
||||||
|
InterfaceMethod.GetServerSettings => InterfaceSet.Method.GetServerSettings,
|
||||||
|
InterfaceMethod.SecretKey => InterfaceSet.Method.SecretKey,
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 公开方法:获取FunGame.Implement.DLL中指定方法(属性)的返回值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Interface">接口代号</param>
|
||||||
|
/// <param name="Method">方法代号(支持属性)</param>
|
||||||
|
/// <param name="IsMethod">是否是方法(如是属性请传入false)</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static object? GetFunGameImplValue(InterfaceType Interface, InterfaceMethod Method, bool IsMethod = true)
|
||||||
|
{
|
||||||
|
MethodInfo? MethodInfo;
|
||||||
|
PropertyInfo? PropertyInfo;
|
||||||
|
|
||||||
|
// 反射读取程序集
|
||||||
|
Assembly? Assembly = System.Reflection.Assembly.LoadFile(ReflectionSet.EXEFolderPath + ReflectionSet.FUNGAME_IMPL + ".dll");
|
||||||
|
// 通过类名获取命名空间+类名称
|
||||||
|
Type? Type = GetFunGameImplementType(Assembly, Interface);
|
||||||
|
|
||||||
|
if (Assembly != null && Type != null)
|
||||||
|
{
|
||||||
|
// 创建类对象
|
||||||
|
object? Instance = Assembly.CreateInstance(Type.Namespace + "." + Type.Name);
|
||||||
|
// 获取方法/属性名
|
||||||
|
string MethodName = GetImplementMethodName(Method);
|
||||||
|
if (IsMethod)
|
||||||
|
{
|
||||||
|
// 从Type中查找方法名
|
||||||
|
MethodInfo = Type.GetMethod(MethodName);
|
||||||
|
if (Instance != null && MethodInfo != null)
|
||||||
|
{
|
||||||
|
object? value = MethodInfo.Invoke(Instance, []);
|
||||||
|
if (value != null) return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PropertyInfo = Type.GetProperty(MethodName);
|
||||||
|
if (Instance != null && PropertyInfo != null)
|
||||||
|
{
|
||||||
|
object? value = PropertyInfo.GetValue(Instance);
|
||||||
|
if (value != null) return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
Api/Utility/JsonTool.cs
Normal file
125
Api/Utility/JsonTool.cs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.Unicode;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Architecture;
|
||||||
|
using Milimoe.FunGame.Core.Service;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Json工具类<para/>
|
||||||
|
/// 此工具类拥有单独的序列化选项,支持添加自定义转换器 <see cref="BaseEntityConverter{T}"/><para/>
|
||||||
|
/// <see cref="BaseEntityConverter{T}"/> 继承自 <see cref="JsonConverter"/>
|
||||||
|
/// </summary>
|
||||||
|
public class JsonTool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 默认的序列化选项
|
||||||
|
/// </summary>
|
||||||
|
public static JsonSerializerOptions JsonSerializerOptions => JsonManager.GeneralOptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 序列化选项
|
||||||
|
/// </summary>
|
||||||
|
public JsonSerializerOptions Options { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建一个Json工具类<para/>
|
||||||
|
/// 此工具类拥有单独的序列化选项,支持添加自定义转换器 <see cref="BaseEntityConverter{T}"/><para/>
|
||||||
|
/// <see cref="BaseEntityConverter{T}"/> 继承自 <see cref="JsonConverter"/>
|
||||||
|
/// </summary>
|
||||||
|
public JsonTool()
|
||||||
|
{
|
||||||
|
Options.WriteIndented = JsonSerializerOptions.WriteIndented;
|
||||||
|
Options.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
|
||||||
|
Options.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
||||||
|
foreach (JsonConverter converter in JsonSerializerOptions.Converters)
|
||||||
|
{
|
||||||
|
Options.Converters.Add(converter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册一个自定义转换器,支持 <see cref="BaseEntityConverter{T}"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="converter"></param>
|
||||||
|
public void AddConverter(JsonConverter converter)
|
||||||
|
{
|
||||||
|
if (!Options.Converters.Contains(converter))
|
||||||
|
Options.Converters.Add(converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册多个自定义转换器,支持 <see cref="BaseEntityConverter{T}"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="converters"></param>
|
||||||
|
public void AddConverters(IEnumerable<JsonConverter> converters)
|
||||||
|
{
|
||||||
|
foreach (JsonConverter converter in converters)
|
||||||
|
{
|
||||||
|
AddConverter(converter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取Json字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetString<T>(T obj) => JsonManager.GetString(obj, Options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Json对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="json"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public T? GetObject<T>(string json) => JsonManager.GetObject<T>(json, Options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Json对象,此方法可能无法返回正确的类型,请注意辨别
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="json"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object? GetObject(string json) => JsonManager.GetObject(json, Options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Hashtable中Key对应的Json对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="table"></param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public T? GetObject<T>(Hashtable table, string key) => JsonManager.GetObject<T>(table, key, Options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化Dictionary中Key对应的Json对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="dict"></param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public T? GetObject<T>(Dictionary<string, object> dict, string key) => JsonManager.GetObject<T>(dict, key, Options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化IEnumerable中的Json对象 可指定反序列化选项
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public T? JsonDeserializeFromIEnumerable<T>(IEnumerable<object> e, int index) => JsonManager.GetObject<T>(e, index, Options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反序列化多个Json对象
|
||||||
|
/// 注意必须是相同的Json对象才可以使用此方法解析
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="json"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<T> GetObjects<T>(string json) => JsonManager.GetObjects<T>(json, Options);
|
||||||
|
}
|
||||||
|
}
|
||||||
314
Api/Utility/NovelConfig.cs
Normal file
314
Api/Utility/NovelConfig.cs
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 视觉小说文本配置器<para/>
|
||||||
|
/// <para/>文件会保存为:程序目录/<see cref="RootPath"/>(通常是 novels)/<see cref="NovelName"/>/<see cref="FileName"/>.json
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 新建一个配置文件,文件会保存为:程序目录/<see cref="RootPath"/>(通常是 novels)/<paramref name="novel_name"/>/<paramref name="file_name"/>.json
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="novel_name"></param>
|
||||||
|
/// <param name="file_name"></param>
|
||||||
|
public class NovelConfig(string novel_name, string file_name) : Dictionary<string, NovelNode>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 配置文件存放的根目录
|
||||||
|
/// </summary>
|
||||||
|
public static string RootPath { get; set; } = "novels";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否允许 <see cref="SaveConfig"/>
|
||||||
|
/// </summary>
|
||||||
|
public bool Readonly { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模组的名称
|
||||||
|
/// </summary>
|
||||||
|
public string NovelName { get; set; } = novel_name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置文件的名称(后缀将是.json)
|
||||||
|
/// </summary>
|
||||||
|
public string FileName { get; set; } = file_name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 断言方法字典<para/>
|
||||||
|
/// <see cref="NovelNode"/> 和 <see cref="NovelOption"/> 都有显示的条件,反序列化 json 文件时,会根据其名称来分配具体的断言方法
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, Func<bool>> Predicates { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用索引器给指定key赋值,不存在key会新增
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new NovelNode this[string key]
|
||||||
|
{
|
||||||
|
get => base[key];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != null) Add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定key的value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public NovelNode? Get(string key)
|
||||||
|
{
|
||||||
|
if (TryGetValue(key, out NovelNode? value) && value != null)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加一个配置,如果已存在key会覆盖
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public new void Add(string key, NovelNode value) => base[key] = value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从指定路径加载配置文件,并根据其文件名,转换为本框架所需的文件<para/>
|
||||||
|
/// <paramref name="copyToRootPath"/> 为 false 时将不会复制此文件至配置文件目录并且不允许 <see cref="SaveConfig"/><para/>
|
||||||
|
/// 需要注意:<paramref name="checkConflict"/> 用于检查加载的文件名是否在配置文件目录中已经存在<para/>
|
||||||
|
/// 如果不使用此检查,使用 <see cref="SaveConfig"/> 时可能会覆盖原有文件(程序目录/<see cref="RootPath"/>(通常是 novels)/<paramref name="novelName"/>/[所选的文件名].json)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <param name="novelName"></param>
|
||||||
|
/// <param name="copyToRootPath"></param>
|
||||||
|
/// <param name="checkConflict"></param>
|
||||||
|
/// <param name="predicates"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="FileNotFoundException" />
|
||||||
|
/// <exception cref="InvalidOperationException" />
|
||||||
|
/// <exception cref="InvalidDataException" />
|
||||||
|
public static NovelConfig LoadFrom(string path, string novelName, bool copyToRootPath = true, bool checkConflict = true, Dictionary<string, Func<bool>>? predicates = null)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException($"找不到文件:{path}");
|
||||||
|
}
|
||||||
|
|
||||||
|
string fileName = Path.GetFileNameWithoutExtension(path);
|
||||||
|
|
||||||
|
NovelConfig config;
|
||||||
|
if (copyToRootPath)
|
||||||
|
{
|
||||||
|
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}";
|
||||||
|
string fpath = $@"{dpath}/{fileName}.json";
|
||||||
|
if (checkConflict && File.Exists(fpath))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"文件 {fileName}.json 已存在,请先重命名。");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
ExistsDirectoryAndCreate(novelName);
|
||||||
|
|
||||||
|
// 复制文件内容
|
||||||
|
string json = File.ReadAllText(path, General.DefaultEncoding);
|
||||||
|
if (NetworkUtility.JsonDeserialize<Dictionary<string, NovelNode>>(json) is null)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException($"文件 {path} 内容为空或格式不正确。");
|
||||||
|
}
|
||||||
|
File.WriteAllText(fpath, json, General.DefaultEncoding);
|
||||||
|
|
||||||
|
config = new(novelName, fileName);
|
||||||
|
config.LoadConfig(predicates);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 从新文件加载配置
|
||||||
|
config = new(novelName, fileName)
|
||||||
|
{
|
||||||
|
Readonly = true
|
||||||
|
};
|
||||||
|
string json = File.ReadAllText(path, General.DefaultEncoding);
|
||||||
|
Dictionary<string, NovelNode> dict = NetworkUtility.JsonDeserialize<Dictionary<string, NovelNode>>(json) ?? [];
|
||||||
|
config.LoadConfig(dict, predicates);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从配置文件中读取配置。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="predicates">传入定义好的条件字典</param>
|
||||||
|
public void LoadConfig(Dictionary<string, Func<bool>>? predicates = null)
|
||||||
|
{
|
||||||
|
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{NovelName}";
|
||||||
|
string fpath = $@"{dpath}/{FileName}.json";
|
||||||
|
if (Directory.Exists(dpath) && File.Exists(fpath))
|
||||||
|
{
|
||||||
|
string json = File.ReadAllText(fpath, General.DefaultEncoding);
|
||||||
|
Dictionary<string, NovelNode> dict = NetworkUtility.JsonDeserialize<Dictionary<string, NovelNode>>(json) ?? [];
|
||||||
|
LoadConfig(dict, predicates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据断言方法字典重新生成小说的字典
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dict"></param>
|
||||||
|
/// <param name="predicates"></param>
|
||||||
|
private void LoadConfig(Dictionary<string, NovelNode> dict, Dictionary<string, Func<bool>>? predicates = null)
|
||||||
|
{
|
||||||
|
if (predicates != null)
|
||||||
|
{
|
||||||
|
foreach (string key in predicates.Keys)
|
||||||
|
{
|
||||||
|
// 直接覆盖
|
||||||
|
Predicates[key] = predicates[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Clear();
|
||||||
|
foreach (string key in dict.Keys)
|
||||||
|
{
|
||||||
|
NovelNode obj = dict[key];
|
||||||
|
base.Add(key, obj);
|
||||||
|
if (obj.Values.TryGetValue(nameof(NovelNode.Previous), out object? value) && value is string prevKey && dict.Values.FirstOrDefault(n => n.Key == prevKey) is NovelNode prev)
|
||||||
|
{
|
||||||
|
obj.Previous = prev;
|
||||||
|
}
|
||||||
|
if (obj.Values.TryGetValue(nameof(NovelNode.NextNodes), out value) && value is List<string> nextKeys)
|
||||||
|
{
|
||||||
|
foreach (string nextKey in nextKeys)
|
||||||
|
{
|
||||||
|
if (dict.TryGetValue(nextKey, out NovelNode? node) && node != null)
|
||||||
|
{
|
||||||
|
obj.NextNodes.Add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Predicates != null)
|
||||||
|
{
|
||||||
|
if (obj.Values.TryGetValue(nameof(NovelNode.AndPredicates), out object? value2) && value2 is List<string> aps)
|
||||||
|
{
|
||||||
|
foreach (string ap in aps)
|
||||||
|
{
|
||||||
|
if (Predicates.TryGetValue(ap, out Func<bool>? value3) && value3 != null)
|
||||||
|
{
|
||||||
|
obj.AndPredicates[ap] = value3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (obj.Values.TryGetValue(nameof(NovelNode.OrPredicates), out value2) && value2 is List<string> ops)
|
||||||
|
{
|
||||||
|
foreach (string op in ops)
|
||||||
|
{
|
||||||
|
if (Predicates.TryGetValue(op, out Func<bool>? value3) && value3 != null)
|
||||||
|
{
|
||||||
|
obj.OrPredicates[op] = value3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (NovelOption option in obj.Options)
|
||||||
|
{
|
||||||
|
if (option.Values.TryGetValue(nameof(NovelOption.Targets), out object? value2) && value2 is List<string> targets)
|
||||||
|
{
|
||||||
|
foreach (string targetKey in targets)
|
||||||
|
{
|
||||||
|
if (dict.TryGetValue(targetKey, out NovelNode? node) && node != null)
|
||||||
|
{
|
||||||
|
option.Targets.Add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Predicates != null)
|
||||||
|
{
|
||||||
|
if (option.Values.TryGetValue(nameof(NovelNode.AndPredicates), out object? value3) && value3 is List<string> aps)
|
||||||
|
{
|
||||||
|
foreach (string ap in aps)
|
||||||
|
{
|
||||||
|
if (Predicates.TryGetValue(ap, out Func<bool>? value4) && value4 != null)
|
||||||
|
{
|
||||||
|
option.AndPredicates[ap] = value4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.Values.TryGetValue(nameof(NovelNode.OrPredicates), out value3) && value3 is List<string> ops)
|
||||||
|
{
|
||||||
|
foreach (string op in ops)
|
||||||
|
{
|
||||||
|
if (Predicates.TryGetValue(op, out Func<bool>? value4) && value4 != null)
|
||||||
|
{
|
||||||
|
option.OrPredicates[op] = value4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将配置保存到配置文件。调用此方法会覆盖原有的.json,请注意备份
|
||||||
|
/// </summary>
|
||||||
|
public void SaveConfig()
|
||||||
|
{
|
||||||
|
if (Readonly)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string json = NetworkUtility.JsonSerialize((Dictionary<string, NovelNode>)this);
|
||||||
|
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{NovelName}";
|
||||||
|
string fpath = $@"{dpath}/{FileName}.json";
|
||||||
|
if (!Directory.Exists(dpath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dpath);
|
||||||
|
}
|
||||||
|
using StreamWriter writer = new(fpath, false, General.DefaultEncoding);
|
||||||
|
writer.WriteLine(json);
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查配置文件目录是否存在
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="novelName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool ExistsDirectory(string novelName)
|
||||||
|
{
|
||||||
|
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}";
|
||||||
|
return Directory.Exists(dpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查配置文件目录是否存在,不存在则创建
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="novelName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool ExistsDirectoryAndCreate(string novelName)
|
||||||
|
{
|
||||||
|
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}";
|
||||||
|
bool result = Directory.Exists(dpath);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dpath);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查配置文件目录中是否存在指定文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="novelName"></param>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool ExistsFile(string novelName, string fileName)
|
||||||
|
{
|
||||||
|
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}";
|
||||||
|
string fpath = $@"{dpath}/{fileName}.json";
|
||||||
|
return File.Exists(fpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
230
Api/Utility/PluginConfig.cs
Normal file
230
Api/Utility/PluginConfig.cs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 简易的插件配置文件生成器<para/>
|
||||||
|
/// 仅支持部分基本类型(<see cref="long"/>, <see cref="double"/>, <see cref="decimal"/>, <see cref="string"/>, <see cref="bool"/>)及其数组(<see cref="List{T}">List<long>, List<double>, List<decimal>, List<string>, List<bool></see>和<see cref="Array">long[], double[], decimal[], string[], bool[]</see>)
|
||||||
|
/// <para/>文件会保存为:程序目录/<see cref="RootPath"/>(通常是 configs)/<see cref="PluginName"/>/<see cref="FileName"/>.json
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 新建一个配置文件,文件会保存为:程序目录/<see cref="RootPath"/>(通常是 configs)/<paramref name="plugin_name"/>/<paramref name="file_name"/>.json
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="plugin_name"></param>
|
||||||
|
/// <param name="file_name"></param>
|
||||||
|
public class PluginConfig(string plugin_name, string file_name) : Dictionary<string, object>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 配置文件存放的根目录
|
||||||
|
/// </summary>
|
||||||
|
public static string RootPath { get; set; } = "configs";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 插件的名称
|
||||||
|
/// </summary>
|
||||||
|
public string PluginName { get; set; } = plugin_name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置文件的名称(后缀将是.json)
|
||||||
|
/// </summary>
|
||||||
|
public string FileName { get; set; } = file_name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用索引器给指定key赋值,不存在key会新增
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new object this[string key]
|
||||||
|
{
|
||||||
|
get => base[key];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != null) Add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 如果保存了对象,请使用此方法转换
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
public void Parse<T>(string key)
|
||||||
|
{
|
||||||
|
if (TryGetValue(key, out object? value) && value != null)
|
||||||
|
{
|
||||||
|
T? instance = NetworkUtility.JsonDeserialize<T>(value.ToString() ?? "");
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
base[key] = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定key的value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object? GetValue(string key)
|
||||||
|
{
|
||||||
|
if (base.TryGetValue(key, out object? value) && value != null)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用泛型获取指定key的value
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public T? Get<T>(string key)
|
||||||
|
{
|
||||||
|
if (TryGetValue(key, out object? value) && value != null)
|
||||||
|
{
|
||||||
|
return NetworkUtility.JsonDeserialize<T>(value.ToString() ?? "");
|
||||||
|
}
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加一个配置,如果已存在key会覆盖
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public new void Add(string key, object value)
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
if (TryGetValue(key, out _)) base[key] = value;
|
||||||
|
else base.Add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从配置文件中读取配置。
|
||||||
|
/// 注意:所有保存时为数组的对象都会变成<see cref="List{T}"/>对象,并且不支持<see cref="object"/>类型
|
||||||
|
/// </summary>
|
||||||
|
public void LoadConfig()
|
||||||
|
{
|
||||||
|
string dpath = $@"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, RootPath)}/{PluginName}";
|
||||||
|
string fpath = $@"{dpath}/{FileName}.json";
|
||||||
|
if (!Directory.Exists(dpath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dpath);
|
||||||
|
}
|
||||||
|
if (File.Exists(fpath))
|
||||||
|
{
|
||||||
|
string json = File.ReadAllText(fpath, General.DefaultEncoding);
|
||||||
|
Dictionary<string, object> dict = NetworkUtility.JsonDeserialize<Dictionary<string, object>>(json) ?? [];
|
||||||
|
Clear();
|
||||||
|
foreach (string key in dict.Keys)
|
||||||
|
{
|
||||||
|
JsonElement obj = (JsonElement)dict[key];
|
||||||
|
AddValue(key, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将配置保存到配置文件。调用此方法会覆盖原有的.json,请注意备份
|
||||||
|
/// </summary>
|
||||||
|
public void SaveConfig()
|
||||||
|
{
|
||||||
|
string json = NetworkUtility.JsonSerialize((Dictionary<string, object>)this);
|
||||||
|
string dpath = $@"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, RootPath)}/{PluginName}";
|
||||||
|
string fpath = $@"{dpath}/{FileName}.json";
|
||||||
|
if (!Directory.Exists(dpath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dpath);
|
||||||
|
}
|
||||||
|
using StreamWriter writer = new(fpath, false, General.DefaultEncoding);
|
||||||
|
writer.WriteLine(json);
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Json反序列化的方法
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
private void AddValue(string key, JsonElement obj)
|
||||||
|
{
|
||||||
|
switch (obj.ValueKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.Object:
|
||||||
|
base.Add(key, obj);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Number:
|
||||||
|
if (obj.ValueKind == JsonValueKind.Number && obj.TryGetInt64(out long longValue))
|
||||||
|
{
|
||||||
|
base.Add(key, longValue);
|
||||||
|
}
|
||||||
|
else if (obj.ValueKind == JsonValueKind.Number && obj.TryGetDouble(out double douValue))
|
||||||
|
{
|
||||||
|
base.Add(key, douValue);
|
||||||
|
}
|
||||||
|
else if (obj.ValueKind == JsonValueKind.Number && obj.TryGetDecimal(out decimal decValue))
|
||||||
|
{
|
||||||
|
base.Add(key, decValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JsonValueKind.String:
|
||||||
|
base.Add(key, obj.GetString() ?? "");
|
||||||
|
break;
|
||||||
|
case JsonValueKind.True:
|
||||||
|
case JsonValueKind.False:
|
||||||
|
base.Add(key, obj.GetBoolean());
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Array:
|
||||||
|
AddValues(key, obj.EnumerateArray());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Json数组反序列化的方法。不支持<see cref="object"/>数组。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
private void AddValues(string key, JsonElement.ArrayEnumerator obj)
|
||||||
|
{
|
||||||
|
List<long> longList = [];
|
||||||
|
List<double> douList = [];
|
||||||
|
List<decimal> decList = [];
|
||||||
|
List<string> strList = [];
|
||||||
|
List<bool> bolList = [];
|
||||||
|
foreach (JsonElement array_e in obj)
|
||||||
|
{
|
||||||
|
if (array_e.ValueKind == JsonValueKind.Number && array_e.TryGetInt64(out long longValue))
|
||||||
|
{
|
||||||
|
longList.Add(longValue);
|
||||||
|
}
|
||||||
|
else if (array_e.ValueKind == JsonValueKind.Number && array_e.TryGetDouble(out double douValue))
|
||||||
|
{
|
||||||
|
douList.Add(douValue);
|
||||||
|
}
|
||||||
|
else if (array_e.ValueKind == JsonValueKind.Number && array_e.TryGetDecimal(out decimal decValue))
|
||||||
|
{
|
||||||
|
decList.Add(decValue);
|
||||||
|
}
|
||||||
|
else if (array_e.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
strList.Add(array_e.GetString() ?? "");
|
||||||
|
}
|
||||||
|
else if (array_e.ValueKind == JsonValueKind.True || array_e.ValueKind == JsonValueKind.False)
|
||||||
|
{
|
||||||
|
bolList.Add(array_e.GetBoolean());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (longList.Count > 0) base.Add(key, longList);
|
||||||
|
if (douList.Count > 0) base.Add(key, douList);
|
||||||
|
if (decList.Count > 0) base.Add(key, decList);
|
||||||
|
if (strList.Count > 0) base.Add(key, strList);
|
||||||
|
if (bolList.Count > 0) base.Add(key, bolList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
391
Api/Utility/PluginLoader.cs
Normal file
391
Api/Utility/PluginLoader.cs
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Event;
|
||||||
|
using Milimoe.FunGame.Core.Service;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public class PluginLoader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 已读取的插件列表
|
||||||
|
/// <para>key 是 <see cref="Plugin.Name"/></para>
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, Plugin> Plugins { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已加载的插件DLL名称对应的路径
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, string> PluginFilePaths => new(AddonManager.PluginFilePaths);
|
||||||
|
|
||||||
|
private PluginLoader()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建一个插件读取器并读取插件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delegates">用于构建 <see cref="Controller.AddonController{T}"/></param>
|
||||||
|
/// <param name="otherobjs">其他需要传入给插件初始化的对象</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static PluginLoader LoadPlugins(Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
PluginLoader loader = new();
|
||||||
|
AddonManager.LoadPlugins(loader.Plugins, delegates, otherobjs);
|
||||||
|
foreach (Plugin plugin in loader.Plugins.Values.ToList())
|
||||||
|
{
|
||||||
|
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
||||||
|
plugin.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Plugin this[string name]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Plugins[name];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Plugins.TryAdd(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeConnectEvent(object sender, ConnectEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeConnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterConnectEvent(object sender, ConnectEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterConnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeDisconnectEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeDisconnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterDisconnectEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterDisconnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeLoginEvent(object sender, LoginEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeLoginEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterLoginEvent(object sender, LoginEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterLoginEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeLogoutEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeLogoutEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterLogoutEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterLogoutEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeRegEvent(object sender, RegisterEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeRegEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterRegEvent(object sender, RegisterEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterRegEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeIntoRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeIntoRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterIntoRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterIntoRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeSendTalkEvent(object sender, SendTalkEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeSendTalkEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterSendTalkEvent(object sender, SendTalkEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterSendTalkEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeCreateRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeCreateRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterCreateRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterCreateRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeQuitRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeQuitRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterQuitRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterQuitRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeChangeRoomSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeChangeRoomSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterChangeRoomSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterChangeRoomSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeStartMatchEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeStartMatchEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterStartMatchEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterStartMatchEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeStartGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeStartGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterStartGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterStartGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeChangeProfileEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeChangeProfileEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterChangeProfileEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterChangeProfileEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeChangeAccountSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeChangeAccountSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterChangeAccountSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterChangeAccountSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeOpenInventoryEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeOpenInventoryEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterOpenInventoryEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterOpenInventoryEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeSignInEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeSignInEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterSignInEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterSignInEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeOpenStoreEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeOpenStoreEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterOpenStoreEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterOpenStoreEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeBuyItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeBuyItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterBuyItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterBuyItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeShowRankingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeShowRankingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterShowRankingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterShowRankingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeUseItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeUseItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterUseItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterUseItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeEndGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeEndGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterEndGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterEndGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
391
Api/Utility/ServerPluginLoader.cs
Normal file
391
Api/Utility/ServerPluginLoader.cs
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Event;
|
||||||
|
using Milimoe.FunGame.Core.Service;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public class ServerPluginLoader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 已读取的插件列表
|
||||||
|
/// <para>key 是 <see cref="ServerPlugin.Name"/></para>
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, ServerPlugin> Plugins { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已加载的插件DLL名称对应的路径
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, string> PluginFilePaths => new(AddonManager.PluginFilePaths);
|
||||||
|
|
||||||
|
private ServerPluginLoader()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建一个插件读取器并读取插件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delegates">用于构建 <see cref="Controller.BaseAddonController{T}"/></param>
|
||||||
|
/// <param name="otherobjs">其他需要传入给插件初始化的对象</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ServerPluginLoader LoadPlugins(Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
ServerPluginLoader loader = new();
|
||||||
|
AddonManager.LoadServerPlugins(loader.Plugins, delegates, otherobjs);
|
||||||
|
foreach (ServerPlugin plugin in loader.Plugins.Values.ToList())
|
||||||
|
{
|
||||||
|
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
||||||
|
plugin.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerPlugin this[string name]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Plugins[name];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Plugins.TryAdd(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeConnectEvent(object sender, ConnectEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeConnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterConnectEvent(object sender, ConnectEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterConnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeDisconnectEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeDisconnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterDisconnectEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterDisconnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeLoginEvent(object sender, LoginEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeLoginEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterLoginEvent(object sender, LoginEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterLoginEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeLogoutEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeLogoutEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterLogoutEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterLogoutEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeRegEvent(object sender, RegisterEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeRegEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterRegEvent(object sender, RegisterEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterRegEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeIntoRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeIntoRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterIntoRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterIntoRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeSendTalkEvent(object sender, SendTalkEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeSendTalkEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterSendTalkEvent(object sender, SendTalkEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterSendTalkEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeCreateRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeCreateRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterCreateRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterCreateRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeQuitRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeQuitRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterQuitRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterQuitRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeChangeRoomSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeChangeRoomSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterChangeRoomSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterChangeRoomSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeStartMatchEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeStartMatchEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterStartMatchEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterStartMatchEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeStartGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeStartGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterStartGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterStartGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeChangeProfileEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeChangeProfileEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterChangeProfileEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterChangeProfileEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeChangeAccountSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeChangeAccountSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterChangeAccountSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterChangeAccountSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeOpenInventoryEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeOpenInventoryEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterOpenInventoryEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterOpenInventoryEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeSignInEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeSignInEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterSignInEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterSignInEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeOpenStoreEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeOpenStoreEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterOpenStoreEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterOpenStoreEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeBuyItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeBuyItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterBuyItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterBuyItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeShowRankingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeShowRankingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterShowRankingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterShowRankingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeUseItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeUseItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterUseItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterUseItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeEndGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeEndGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterEndGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterEndGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
83
Api/Utility/Singleton.cs
Normal file
83
Api/Utility/Singleton.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 单例表:表中的对象以类名作为Key保存,并以Key获取该对象,Key具有唯一约束
|
||||||
|
/// 用于储存单例对象使用
|
||||||
|
/// </summary>
|
||||||
|
public class Singleton
|
||||||
|
{
|
||||||
|
private static readonly ConcurrentDictionary<string, object> SingletonTable = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询目标的类是否已经有实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="single">单例对象</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsExist(object single)
|
||||||
|
{
|
||||||
|
Type type = single.GetType();
|
||||||
|
string name = type.FullName ?? type.ToString();
|
||||||
|
return SingletonTable.ContainsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将目标和目标的类添加至单例表,如果存在,将更新此类单例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="single">单例对象</param>
|
||||||
|
/// <param name="baseClass">存入基类</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static void AddOrUpdate(object single, bool baseClass = false)
|
||||||
|
{
|
||||||
|
if (single != null)
|
||||||
|
{
|
||||||
|
Type? type = baseClass ? single.GetType().BaseType : single.GetType();
|
||||||
|
string name = type?.FullName ?? type?.ToString() ?? "";
|
||||||
|
if (name != "") SingletonTable.AddOrUpdate(name, single, (key, oldValue) => single);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将目标和目标的类从单例表中移除
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="single">单例对象</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool Remove(object single)
|
||||||
|
{
|
||||||
|
Type type = single.GetType();
|
||||||
|
string name = type.FullName ?? type.ToString();
|
||||||
|
return SingletonTable.TryRemove(name, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单例对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">目标类</typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? Get<T>()
|
||||||
|
{
|
||||||
|
string name = typeof(T).FullName ?? typeof(T).ToString();
|
||||||
|
if (SingletonTable.TryGetValue(name, out object? value) && value is T single)
|
||||||
|
{
|
||||||
|
return single;
|
||||||
|
}
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单例对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">目标类</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static object? Get(Type type)
|
||||||
|
{
|
||||||
|
string name = type.FullName ?? type.ToString();
|
||||||
|
if (SingletonTable.TryGetValue(name, out var value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Api/Utility/StringExtension.cs
Normal file
28
Api/Utility/StringExtension.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 为字符串(string)添加扩展方法
|
||||||
|
/// </summary>
|
||||||
|
public static class StringExtension
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 HMAC-SHA512 算法对文本进行加密<para/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">需要加密的文本</param>
|
||||||
|
/// <param name="key">用于加密的秘钥</param>
|
||||||
|
/// <returns>加密后的 HMAC-SHA512 哈希值</returns>
|
||||||
|
public static string Encrypt(this string text, string key)
|
||||||
|
{
|
||||||
|
return Encryption.HmacSha512(text, key.ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool EqualsGuid(this string str, object? value)
|
||||||
|
{
|
||||||
|
if (str.ToLower().Replace("-", "").Equals(value?.ToString()?.ToLower().Replace("-", "")))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
216
Api/Utility/TaskScheduler.cs
Normal file
216
Api/Utility/TaskScheduler.cs
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public class TaskScheduler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 任务计划管理器实例,可以直接使用
|
||||||
|
/// </summary>
|
||||||
|
public static TaskScheduler Shared { get; } = new();
|
||||||
|
|
||||||
|
private readonly List<ScheduledTask> _tasks = [];
|
||||||
|
private readonly List<RecurringTask> _recurringTasks = [];
|
||||||
|
private readonly Lock _lock = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建一个轻量级的任务计划管理器
|
||||||
|
/// </summary>
|
||||||
|
public TaskScheduler()
|
||||||
|
{
|
||||||
|
Task.Factory.StartNew(async () =>
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
CheckAndRunTasks();
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
}, TaskCreationOptions.LongRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加一个任务计划
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="timeOfDay"></param>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
public void AddTask(string name, TimeSpan timeOfDay, Action action)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
ScheduledTask task = new(name, timeOfDay, action);
|
||||||
|
if (DateTime.Now > DateTime.Today.Add(timeOfDay))
|
||||||
|
{
|
||||||
|
task.LastRun = DateTime.Today.Add(timeOfDay);
|
||||||
|
}
|
||||||
|
_tasks.Add(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加一个循环任务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="interval"></param>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
/// <param name="startNow"></param>
|
||||||
|
public void AddRecurringTask(string name, TimeSpan interval, Action action, bool startNow = false)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0);
|
||||||
|
DateTime nextRun = startNow ? now : now.Add(interval);
|
||||||
|
RecurringTask recurringTask = new(name, interval, action)
|
||||||
|
{
|
||||||
|
NextRun = nextRun
|
||||||
|
};
|
||||||
|
_recurringTasks.Add(recurringTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移除任务计划
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
public void RemoveTask(string name)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
int removeTasks = _tasks.RemoveAll(t => t.Name == name);
|
||||||
|
int removeRecurringTasks = _recurringTasks.RemoveAll(t => t.Name == name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取任务计划上一次执行时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="recurring"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public DateTime GetLastTime(string name, bool recurring = false)
|
||||||
|
{
|
||||||
|
if (!recurring)
|
||||||
|
{
|
||||||
|
if (_tasks.FirstOrDefault(t => t.Name == name) is ScheduledTask task && task.LastRun.HasValue)
|
||||||
|
{
|
||||||
|
return task.LastRun.Value;
|
||||||
|
}
|
||||||
|
else if (_recurringTasks.FirstOrDefault(t => t.Name == name) is RecurringTask recurringTask && recurringTask.LastRun.HasValue)
|
||||||
|
{
|
||||||
|
return recurringTask.LastRun.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_recurringTasks.FirstOrDefault(t => t.Name == name) is RecurringTask recurringTask && recurringTask.LastRun.HasValue)
|
||||||
|
{
|
||||||
|
return recurringTask.LastRun.Value;
|
||||||
|
}
|
||||||
|
return DateTime.MinValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取任务计划下一次执行时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="recurring"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public DateTime GetNextTime(string name, bool recurring = false)
|
||||||
|
{
|
||||||
|
if (!recurring)
|
||||||
|
{
|
||||||
|
if (_tasks.FirstOrDefault(t => t.Name == name) is ScheduledTask task)
|
||||||
|
{
|
||||||
|
DateTime today = DateTime.Today.Add(task.TimeOfDay);
|
||||||
|
return task.IsTodayRun ? today.AddDays(1) : today;
|
||||||
|
}
|
||||||
|
else if (_recurringTasks.FirstOrDefault(t => t.Name == name) is RecurringTask recurringTask)
|
||||||
|
{
|
||||||
|
return recurringTask.NextRun;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_recurringTasks.FirstOrDefault(t => t.Name == name) is RecurringTask recurringTask)
|
||||||
|
{
|
||||||
|
return recurringTask.NextRun;
|
||||||
|
}
|
||||||
|
return DateTime.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetRunTimeInfo(string name)
|
||||||
|
{
|
||||||
|
DateTime last = GetLastTime(name);
|
||||||
|
DateTime next = GetNextTime(name);
|
||||||
|
string msg = "";
|
||||||
|
if (last != DateTime.MinValue)
|
||||||
|
{
|
||||||
|
msg += $"上次运行时间:{last.ToString(General.GeneralDateTimeFormat)}\r\n";
|
||||||
|
}
|
||||||
|
if (next != DateTime.MaxValue)
|
||||||
|
{
|
||||||
|
msg += $"下次运行时间:{next.ToString(General.GeneralDateTimeFormat)}\r\n";
|
||||||
|
}
|
||||||
|
if (msg != "")
|
||||||
|
{
|
||||||
|
msg = $"任务计划:{name}\r\n{msg}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msg = $"任务计划 {name} 不存在!";
|
||||||
|
}
|
||||||
|
return msg.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行任务
|
||||||
|
/// </summary>
|
||||||
|
private void CheckAndRunTasks()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
|
||||||
|
foreach (ScheduledTask task in _tasks)
|
||||||
|
{
|
||||||
|
if (!task.IsTodayRun)
|
||||||
|
{
|
||||||
|
if (now.TimeOfDay >= task.TimeOfDay && now.TimeOfDay < task.TimeOfDay.Add(TimeSpan.FromSeconds(10)))
|
||||||
|
{
|
||||||
|
task.LastRun = now;
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
task.Action();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
task.Error = ex;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (RecurringTask recurringTask in _recurringTasks)
|
||||||
|
{
|
||||||
|
if (now >= recurringTask.NextRun)
|
||||||
|
{
|
||||||
|
recurringTask.LastRun = now;
|
||||||
|
recurringTask.NextRun = recurringTask.NextRun.Add(recurringTask.Interval);
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
recurringTask.Action();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
recurringTask.Error = ex;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
204
Api/Utility/TextReader.cs
Normal file
204
Api/Utility/TextReader.cs
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public partial class INIHelper
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 声明API函数
|
||||||
|
*/
|
||||||
|
[LibraryImport("kernel32.dll", EntryPoint = "WritePrivateProfileStringW", StringMarshalling = StringMarshalling.Utf16)]
|
||||||
|
private static partial long WritePrivateProfileString(string section, string key, string val, string filePath);
|
||||||
|
[LibraryImport("Kernel32.dll", EntryPoint = "GetPrivateProfileStringW", StringMarshalling = StringMarshalling.Utf16)]
|
||||||
|
private static partial int GetPrivateProfileString(string section, string key, string def, char[] val, int size, string filePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认的配置文件名称
|
||||||
|
/// </summary>
|
||||||
|
public const string DefaultFileName = @"FunGame.ini";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 写入ini文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Section">Section</param>
|
||||||
|
/// <param name="Key">键</param>
|
||||||
|
/// <param name="Value">值</param>
|
||||||
|
/// <param name="FileName">文件名,缺省为FunGame.ini</param>
|
||||||
|
public static void WriteINI(string Section, string Key, string Value, string FileName = DefaultFileName)
|
||||||
|
{
|
||||||
|
WritePrivateProfileString(Section, Key, Value, AppDomain.CurrentDomain.BaseDirectory + FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取ini文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Section">Section</param>
|
||||||
|
/// <param name="Key">键</param>
|
||||||
|
/// <param name="FileName">文件名,缺省为FunGame.ini</param>
|
||||||
|
/// <returns>读取到的值</returns>
|
||||||
|
public static string ReadINI(string Section, string Key, string FileName = DefaultFileName)
|
||||||
|
{
|
||||||
|
char[] val = new char[General.StreamByteSize];
|
||||||
|
_ = GetPrivateProfileString(Section, Key, "", val, General.StreamByteSize, AppDomain.CurrentDomain.BaseDirectory + FileName);
|
||||||
|
string? read = new(val);
|
||||||
|
return read != null ? read.Trim('\0') : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询ini文件是否存在
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="FileName">文件名,缺省为FunGame.ini</param>
|
||||||
|
/// <returns>是否存在</returns>
|
||||||
|
public static bool ExistINIFile(string FileName = DefaultFileName) => File.Exists($@"{AppDomain.CurrentDomain.BaseDirectory}{FileName}");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化ini模板文件
|
||||||
|
/// </summary>
|
||||||
|
public static void Init(FunGameInfo.FunGame FunGameType)
|
||||||
|
{
|
||||||
|
switch (FunGameType)
|
||||||
|
{
|
||||||
|
case FunGameInfo.FunGame.FunGame_Core:
|
||||||
|
case FunGameInfo.FunGame.FunGame_Core_Api:
|
||||||
|
case FunGameInfo.FunGame.FunGame_Console:
|
||||||
|
case FunGameInfo.FunGame.FunGame_Desktop:
|
||||||
|
/**
|
||||||
|
* Config
|
||||||
|
*/
|
||||||
|
WriteINI("Config", "AutoConnect", "true");
|
||||||
|
WriteINI("Config", "AutoLogin", "false");
|
||||||
|
/**
|
||||||
|
* Account
|
||||||
|
*/
|
||||||
|
WriteINI("Account", "UserName", "");
|
||||||
|
WriteINI("Account", "Password", "");
|
||||||
|
WriteINI("Account", "AutoKey", "");
|
||||||
|
break;
|
||||||
|
case FunGameInfo.FunGame.FunGame_Server:
|
||||||
|
/**
|
||||||
|
* Console
|
||||||
|
*/
|
||||||
|
WriteINI("Console", "LogLevel", "INFO");
|
||||||
|
/**
|
||||||
|
* Server
|
||||||
|
*/
|
||||||
|
WriteINI("Server", "Name", "FunGame Server");
|
||||||
|
WriteINI("Server", "Password", "");
|
||||||
|
WriteINI("Server", "Description", "Just Another FunGame Server.");
|
||||||
|
WriteINI("Server", "Notice", "This is the FunGame Server's Notice.");
|
||||||
|
WriteINI("Server", "Key", "");
|
||||||
|
WriteINI("Server", "Status", "1");
|
||||||
|
WriteINI("Server", "BannedList", "");
|
||||||
|
WriteINI("Server", "UseDesktopParameters", "true");
|
||||||
|
/**
|
||||||
|
* ServerMail
|
||||||
|
*/
|
||||||
|
WriteINI("ServerMail", "OfficialMail", "");
|
||||||
|
WriteINI("ServerMail", "SupportMail", "");
|
||||||
|
/**
|
||||||
|
* Socket
|
||||||
|
*/
|
||||||
|
WriteINI("Socket", "Port", "22222");
|
||||||
|
WriteINI("Socket", "UseWebSocket", "false");
|
||||||
|
WriteINI("Socket", "WebSocketAddress", "*");
|
||||||
|
WriteINI("Socket", "WebSocketPort", "22223");
|
||||||
|
WriteINI("Socket", "WebSocketSubUrl", "ws");
|
||||||
|
WriteINI("Socket", "WebSocketSSL", "false");
|
||||||
|
WriteINI("Socket", "MaxPlayer", "20");
|
||||||
|
WriteINI("Socket", "MaxConnectFailed", "0");
|
||||||
|
/**
|
||||||
|
* MySQL
|
||||||
|
*/
|
||||||
|
WriteINI("MySQL", "UseMySQL", "false");
|
||||||
|
WriteINI("MySQL", "DBServer", "localhost");
|
||||||
|
WriteINI("MySQL", "DBPort", "3306");
|
||||||
|
WriteINI("MySQL", "DBName", "fungame");
|
||||||
|
WriteINI("MySQL", "DBUser", "root");
|
||||||
|
WriteINI("MySQL", "DBPassword", "pass");
|
||||||
|
/**
|
||||||
|
* SQLite
|
||||||
|
*/
|
||||||
|
WriteINI("SQLite", "UseSQLite", "true");
|
||||||
|
WriteINI("SQLite", "DataSource", "FunGameDB");
|
||||||
|
/**
|
||||||
|
* Mailer
|
||||||
|
*/
|
||||||
|
WriteINI("Mailer", "UseMailSender", "false");
|
||||||
|
WriteINI("Mailer", "MailAddress", "");
|
||||||
|
WriteINI("Mailer", "Name", "");
|
||||||
|
WriteINI("Mailer", "Password", "");
|
||||||
|
WriteINI("Mailer", "Host", "");
|
||||||
|
WriteINI("Mailer", "Port", "587");
|
||||||
|
WriteINI("Mailer", "SSL", "true");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TXTHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 读取TXT文件内容
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">文件名(需要包含扩展名)</param>
|
||||||
|
/// <param name="path">相对路径</param>
|
||||||
|
/// <returns>内容</returns>
|
||||||
|
public static string ReadTXT(string filename, string path = "")
|
||||||
|
{
|
||||||
|
if (path.Trim() != "") path = Path.Combine(path, filename);
|
||||||
|
else path = $@"{AppDomain.CurrentDomain.BaseDirectory}{filename}";
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
string s = "";
|
||||||
|
// 创建一个 StreamReader 的实例来读取文件
|
||||||
|
using StreamReader sr = new(path);
|
||||||
|
string? line;
|
||||||
|
// 从文件读取并显示行,直到文件的末尾
|
||||||
|
while ((line = sr.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
s += line + " ";
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 写入TXT文件内容(如不存在文件会创建)<para/>
|
||||||
|
/// <paramref name="overwrite" /> 选项用于覆盖或追加文本
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
/// <param name="filename">文件名(需要包含扩展名)</param>
|
||||||
|
/// <param name="path">相对路径</param>
|
||||||
|
/// <param name="overwrite">是否覆盖</param>
|
||||||
|
public static void WriteTXT(string content, string filename, string path = "", bool overwrite = false)
|
||||||
|
{
|
||||||
|
if (path.Trim() != "")
|
||||||
|
{
|
||||||
|
// 不存在文件夹将创建文件夹
|
||||||
|
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
|
||||||
|
path = Path.Combine(path, filename);
|
||||||
|
}
|
||||||
|
else path = $@"{AppDomain.CurrentDomain.BaseDirectory}{filename}";
|
||||||
|
// 写入内容
|
||||||
|
StreamWriter writer = File.Exists(path) ? new(path, !overwrite, General.DefaultEncoding) : new(path, false, General.DefaultEncoding);
|
||||||
|
writer.WriteLine(content);
|
||||||
|
writer.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 写入并覆盖TXT文件内容
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
/// <param name="filename">文件名(需要包含扩展名)</param>
|
||||||
|
/// <param name="path">相对路径</param>
|
||||||
|
public static void OverwriteTXT(string content, string filename, string path = "") => WriteTXT(content, filename, path, true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 追加错误日志 默认写入logs文件夹下的当日日期.log文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
public static void AppendErrorLog(string msg) => WriteTXT(DateTimeUtility.GetDateTimeToString(TimeType.General) + ": " + msg + "\r\n", DateTimeUtility.GetDateTimeToString("yyyy-MM-dd") + ".log", "logs");
|
||||||
|
}
|
||||||
|
}
|
||||||
230
Api/Utility/TwoFactorAuthenticator.cs
Normal file
230
Api/Utility/TwoFactorAuthenticator.cs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Api.Transmittal;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Aka. 2FA 双重认证 双因素身份验证
|
||||||
|
/// </summary>
|
||||||
|
public class TwoFactorAuthenticator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SQLHelper 允许为空
|
||||||
|
/// </summary>
|
||||||
|
private readonly SQLHelper? SQLHelper;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 不使用SQL模式
|
||||||
|
/// </summary>
|
||||||
|
public TwoFactorAuthenticator() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用SQL模式 记录对应账号的密文到数据库中
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="SQLHelper"></param>
|
||||||
|
public TwoFactorAuthenticator(SQLHelper SQLHelper)
|
||||||
|
{
|
||||||
|
this.SQLHelper = SQLHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查账号是否需要2FA
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual bool IsAvailable(string username)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 2FA验证
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username"></param>
|
||||||
|
/// <param name="code"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Authenticate(string username, string code)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
// 使用username获取此账号记录在案的2FAKey,获取此时间戳内的验证码是否一致。
|
||||||
|
SQLHelper?.Execute();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每30秒刷新
|
||||||
|
/// </summary>
|
||||||
|
private const int INTERVAL_SECONDS = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 6位数字2FA验证码
|
||||||
|
/// </summary>
|
||||||
|
private const int DIGITS = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ----- PUBLIC KEY -----
|
||||||
|
/// </summary>
|
||||||
|
private const string PUBLICKEY = "----- PUBLIC KEY -----\r\n";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ----- SECRET SIGN -----
|
||||||
|
/// </summary>
|
||||||
|
private const string SECRETSIGN = "----- SECRET SIGN -----\r\n";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创键私钥,用于绑定账号,并生成两个文件,需要用户保存
|
||||||
|
/// </summary>
|
||||||
|
public void CreateSecretKey(string username)
|
||||||
|
{
|
||||||
|
// 秘钥文件路径
|
||||||
|
string keypath = "authenticator.key";
|
||||||
|
|
||||||
|
// 创建RSA实例
|
||||||
|
using RSACryptoServiceProvider rsa = new();
|
||||||
|
|
||||||
|
// 获取公钥和私钥
|
||||||
|
string publickey = rsa.ToXmlString(false);
|
||||||
|
string privatekey = rsa.ToXmlString(true);
|
||||||
|
|
||||||
|
// 要加密的明文
|
||||||
|
byte[] random = RandomNumberGenerator.GetBytes(10);
|
||||||
|
string randomstring = General.DefaultEncoding.GetString(random);
|
||||||
|
// TODO 记录对应账号的密文
|
||||||
|
SQLHelper?.Execute();
|
||||||
|
string plain = Base32Encode(random);
|
||||||
|
|
||||||
|
// 加密明文,获得密文
|
||||||
|
string secret = Encryption.RSAEncrypt(plain, publickey);
|
||||||
|
|
||||||
|
// 保存密文到文件
|
||||||
|
File.WriteAllText(keypath, PUBLICKEY + secret + "\r\n");
|
||||||
|
|
||||||
|
// 保存私钥到文件
|
||||||
|
File.AppendAllText(keypath, SECRETSIGN + privatekey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成随机秘钥字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string Base32Encode(byte[] data)
|
||||||
|
{
|
||||||
|
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
|
StringBuilder result = new((data.Length * 8 + 4) / 5);
|
||||||
|
int buffer = data[0];
|
||||||
|
int next = 1;
|
||||||
|
int bitsLeft = 8;
|
||||||
|
while (bitsLeft > 0 || next < data.Length)
|
||||||
|
{
|
||||||
|
if (bitsLeft < 5)
|
||||||
|
{
|
||||||
|
if (next < data.Length)
|
||||||
|
{
|
||||||
|
buffer <<= 8;
|
||||||
|
buffer |= data[next++] & 0xFF;
|
||||||
|
bitsLeft += 8;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int pad = 5 - bitsLeft;
|
||||||
|
buffer <<= pad;
|
||||||
|
bitsLeft += pad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int index = 0x1F & (buffer >> (bitsLeft - 5));
|
||||||
|
bitsLeft -= 5;
|
||||||
|
result.Append(alphabet[index]);
|
||||||
|
}
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成基于当前时间戳的6位数字2FA验证码
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="secretKey"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GenerateCode(string secretKey)
|
||||||
|
{
|
||||||
|
byte[] key = Base32Decode(secretKey);
|
||||||
|
long counter = GetCurrentCounter();
|
||||||
|
byte[] counterBytes = BitConverter.GetBytes(counter);
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
Array.Reverse(counterBytes);
|
||||||
|
}
|
||||||
|
HMACSHA1 hmac = new(key);
|
||||||
|
byte[] hash = hmac.ComputeHash(counterBytes);
|
||||||
|
int offset = hash[^1] & 0x0F;
|
||||||
|
int code = ((hash[offset] & 0x7F) << 24 |
|
||||||
|
(hash[offset + 1] & 0xFF) << 16 |
|
||||||
|
(hash[offset + 2] & 0xFF) << 8 |
|
||||||
|
(hash[offset + 3] & 0xFF)) % (int)Math.Pow(10, DIGITS);
|
||||||
|
return code.ToString().PadLeft(DIGITS, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前时间节点
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static long GetCurrentCounter()
|
||||||
|
{
|
||||||
|
TimeSpan timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
return (long)(timeSpan.TotalSeconds / INTERVAL_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成验证码
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
private static byte[] Base32Decode(string input)
|
||||||
|
{
|
||||||
|
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
|
int length = input.Length;
|
||||||
|
int bitsLeft = 0;
|
||||||
|
int buffer = 0;
|
||||||
|
int next = 0;
|
||||||
|
byte[] result = new byte[length * 5 / 8];
|
||||||
|
foreach (char c in input)
|
||||||
|
{
|
||||||
|
int value = alphabet.IndexOf(c);
|
||||||
|
if (value < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid base32 character: " + c);
|
||||||
|
}
|
||||||
|
buffer <<= 5;
|
||||||
|
buffer |= value & 0x1F;
|
||||||
|
bitsLeft += 5;
|
||||||
|
if (bitsLeft >= 8)
|
||||||
|
{
|
||||||
|
result[next++] = (byte)(buffer >> (bitsLeft - 8));
|
||||||
|
bitsLeft -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 拆分字符串中的密文和私钥
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
/// <param name="strs"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool SplitKeyFile(string content, out string[] strs)
|
||||||
|
{
|
||||||
|
strs = content.Split(SECRETSIGN);
|
||||||
|
if (strs.Length == 2)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
391
Api/Utility/WebAPIPluginLoader.cs
Normal file
391
Api/Utility/WebAPIPluginLoader.cs
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Event;
|
||||||
|
using Milimoe.FunGame.Core.Service;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public class WebAPIPluginLoader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 已读取的插件列表
|
||||||
|
/// <para>key 是 <see cref="WebAPIPlugin.Name"/></para>
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, WebAPIPlugin> Plugins { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已加载的插件DLL名称对应的路径
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, string> PluginFilePaths => new(AddonManager.PluginFilePaths);
|
||||||
|
|
||||||
|
private WebAPIPluginLoader()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建一个插件读取器并读取插件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delegates">用于构建 <see cref="Controller.BaseAddonController{T}"/></param>
|
||||||
|
/// <param name="otherobjs">其他需要传入给插件初始化的对象</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static WebAPIPluginLoader LoadPlugins(Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
WebAPIPluginLoader loader = new();
|
||||||
|
AddonManager.LoadWebAPIPlugins(loader.Plugins, delegates, otherobjs);
|
||||||
|
foreach (WebAPIPlugin plugin in loader.Plugins.Values.ToList())
|
||||||
|
{
|
||||||
|
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
||||||
|
plugin.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebAPIPlugin this[string name]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Plugins[name];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Plugins.TryAdd(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeConnectEvent(object sender, ConnectEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeConnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterConnectEvent(object sender, ConnectEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterConnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeDisconnectEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeDisconnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterDisconnectEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterDisconnectEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeLoginEvent(object sender, LoginEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeLoginEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterLoginEvent(object sender, LoginEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterLoginEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeLogoutEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeLogoutEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterLogoutEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterLogoutEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeRegEvent(object sender, RegisterEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeRegEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterRegEvent(object sender, RegisterEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterRegEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeIntoRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeIntoRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterIntoRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterIntoRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeSendTalkEvent(object sender, SendTalkEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeSendTalkEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterSendTalkEvent(object sender, SendTalkEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterSendTalkEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeCreateRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeCreateRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterCreateRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterCreateRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeQuitRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeQuitRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterQuitRoomEvent(object sender, RoomEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterQuitRoomEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeChangeRoomSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeChangeRoomSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterChangeRoomSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterChangeRoomSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeStartMatchEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeStartMatchEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterStartMatchEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterStartMatchEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeStartGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeStartGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterStartGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterStartGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeChangeProfileEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeChangeProfileEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterChangeProfileEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterChangeProfileEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeChangeAccountSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeChangeAccountSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterChangeAccountSettingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterChangeAccountSettingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeOpenInventoryEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeOpenInventoryEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterOpenInventoryEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterOpenInventoryEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeSignInEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeSignInEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterSignInEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterSignInEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeOpenStoreEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeOpenStoreEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterOpenStoreEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterOpenStoreEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeBuyItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeBuyItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterBuyItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterBuyItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeShowRankingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeShowRankingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterShowRankingEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterShowRankingEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeUseItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeUseItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterUseItemEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterUseItemEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeEndGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnBeforeEndGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAfterEndGameEvent(object sender, GeneralEventArgs e)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(Plugins.Values, plugin =>
|
||||||
|
{
|
||||||
|
plugin.OnAfterEndGameEvent(sender, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Controller/AddonController.cs
Normal file
122
Controller/AddonController.cs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Transmittal;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Addons;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Controller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 这个控制器在 Base 的基础上添加了 DataRequest
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public class AddonController<T> : BaseAddonController<T> where T : IAddon
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建新的数据请求
|
||||||
|
/// </summary>
|
||||||
|
private Func<DataRequestType, DataRequest> MaskMethod_NewDataRequest { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建长时间运行的数据请求
|
||||||
|
/// </summary>
|
||||||
|
private Func<DataRequestType, DataRequest> MaskMethod_NewLongRunningDataRequest { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建新的局内数据请求
|
||||||
|
/// </summary>
|
||||||
|
private Func<GamingType, DataRequest> MaskMethod_NewGamingRequest { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建长时间运行的局内数据请求
|
||||||
|
/// </summary>
|
||||||
|
private Func<GamingType, DataRequest> MaskMethod_NewLongRunningGamingRequest { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建新的数据请求<para/>
|
||||||
|
/// 此方法只允许插件调用,如果是模组和模组服务器调用此方法将抛出异常
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="InvalidNewDataRequestException"></exception>
|
||||||
|
public DataRequest NewDataRequest(DataRequestType type)
|
||||||
|
{
|
||||||
|
if (typeof(IGameModule).IsAssignableFrom(typeof(T)) || typeof(IGameModuleServer).IsAssignableFrom(typeof(T)))
|
||||||
|
{
|
||||||
|
throw new InvalidNewDataRequestException();
|
||||||
|
}
|
||||||
|
return MaskMethod_NewDataRequest(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建长时间运行的数据请求<para/>
|
||||||
|
/// 此方法只允许插件调用,如果是模组和模组服务器调用此方法将抛出异常
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="InvalidNewDataRequestException"></exception>
|
||||||
|
public DataRequest NewLongRunningDataRequest(DataRequestType type)
|
||||||
|
{
|
||||||
|
if (typeof(IGameModule).IsAssignableFrom(typeof(T)) || typeof(IGameModuleServer).IsAssignableFrom(typeof(T)))
|
||||||
|
{
|
||||||
|
throw new InvalidNewDataRequestException();
|
||||||
|
}
|
||||||
|
return MaskMethod_NewLongRunningDataRequest(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建新的局内(<see cref="Model.Gaming"/>)数据请求<para/>
|
||||||
|
/// 此方法是给 <see cref="Library.Common.Addon.GameModule"/> 提供的,但是 <see cref="Library.Common.Addon.Plugin"/> 也能调用<para/>
|
||||||
|
/// 模组服务器调用此方法将抛出异常
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ConnectFailedException"></exception>
|
||||||
|
/// <exception cref="InvalidNewDataRequestException"></exception>
|
||||||
|
public DataRequest NewDataRequest(GamingType type)
|
||||||
|
{
|
||||||
|
if (typeof(IGameModuleServer).IsAssignableFrom(typeof(T)))
|
||||||
|
{
|
||||||
|
throw new InvalidNewDataRequestException();
|
||||||
|
}
|
||||||
|
return MaskMethod_NewGamingRequest(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建长时间运行的局内(<see cref="Model.Gaming"/>)数据请求<para/>
|
||||||
|
/// 此方法是给 <see cref="Library.Common.Addon.GameModule"/> 提供的,但是 <see cref="Library.Common.Addon.Plugin"/> 也能调用<para/>
|
||||||
|
/// 模组服务器调用此方法将抛出异常
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ConnectFailedException"></exception>
|
||||||
|
/// <exception cref="InvalidNewDataRequestException"></exception>
|
||||||
|
public DataRequest NewLongRunningDataRequest(GamingType type)
|
||||||
|
{
|
||||||
|
if (typeof(IGameModuleServer).IsAssignableFrom(typeof(T)))
|
||||||
|
{
|
||||||
|
throw new InvalidNewDataRequestException();
|
||||||
|
}
|
||||||
|
return MaskMethod_NewLongRunningGamingRequest(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新建一个AddonController
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addon"></param>
|
||||||
|
/// <param name="delegates"></param>
|
||||||
|
public AddonController(IAddon addon, Dictionary<string, object> delegates) : base(addon, delegates)
|
||||||
|
{
|
||||||
|
if (delegates.TryGetValue("NewDataRequest", out object? value)) MaskMethod_NewDataRequest = value != null ? (Func<DataRequestType, DataRequest>)value : new(DefaultNewDataRequest);
|
||||||
|
if (delegates.TryGetValue("NewLongRunningDataRequest", out value)) MaskMethod_NewLongRunningDataRequest = value != null ? (Func<DataRequestType, DataRequest>)value : new(DefaultNewDataRequest);
|
||||||
|
if (delegates.TryGetValue("NewGamingRequest", out value)) MaskMethod_NewGamingRequest = value != null ? (Func<GamingType, DataRequest>)value : new(DefaultNewDataRequest);
|
||||||
|
if (delegates.TryGetValue("NewLongRunningGamingRequest", out value)) MaskMethod_NewLongRunningGamingRequest = value != null ? (Func<GamingType, DataRequest>)value : new(DefaultNewDataRequest);
|
||||||
|
MaskMethod_NewDataRequest ??= new(DefaultNewDataRequest);
|
||||||
|
MaskMethod_NewLongRunningDataRequest ??= new(DefaultNewDataRequest);
|
||||||
|
MaskMethod_NewGamingRequest ??= new(DefaultNewDataRequest);
|
||||||
|
MaskMethod_NewLongRunningGamingRequest ??= new(DefaultNewDataRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataRequest DefaultNewDataRequest(DataRequestType type) => throw new InvalidNewDataRequestException();
|
||||||
|
|
||||||
|
private DataRequest DefaultNewDataRequest(GamingType type) => throw new InvalidNewDataRequestException();
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Controller/BaseAddonController.cs
Normal file
85
Controller/BaseAddonController.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Addons;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Controller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 这是通用的控制器,仅提供基本功能
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Addon的类型,如 <see cref="GameModule"/> 或者 <see cref="Plugin"/> / <see cref="ServerPlugin"/> / <see cref="WebAPIPlugin"/></typeparam>
|
||||||
|
public class BaseAddonController<T> where T : IAddon
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 控制器的本体
|
||||||
|
/// </summary>
|
||||||
|
public T Addon { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出系统消息
|
||||||
|
/// </summary>
|
||||||
|
protected Action<string, string, LogLevel, bool> MaskMethod_WriteLine { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出错误消息
|
||||||
|
/// </summary>
|
||||||
|
protected Action<Exception> MaskMethod_Error { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出系统消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <param name="level"></param>
|
||||||
|
/// <param name="useLevel"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public void WriteLine(string msg, LogLevel level = LogLevel.Info, bool useLevel = true) => MaskMethod_WriteLine(Addon.Name, msg, level, useLevel);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出错误消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public void Error(Exception e) => MaskMethod_Error(e);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// JSON 工具类对象
|
||||||
|
/// </summary>
|
||||||
|
public JsonTool JSON { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新建一个BaseAddonController
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addon"></param>
|
||||||
|
/// <param name="delegates"></param>
|
||||||
|
public BaseAddonController(IAddon addon, Dictionary<string, object> delegates)
|
||||||
|
{
|
||||||
|
Addon = (T)addon;
|
||||||
|
if (delegates.TryGetValue("WriteLine", out object? value)) MaskMethod_WriteLine = value != null ? (Action<string, string, LogLevel, bool>)value : new(DefaultPrint);
|
||||||
|
if (delegates.TryGetValue("Error", out value)) MaskMethod_Error = value != null ? (Action<Exception>)value : new(DefaultPrint);
|
||||||
|
MaskMethod_WriteLine ??= new(DefaultPrint);
|
||||||
|
MaskMethod_Error ??= new(DefaultPrint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认的输出错误消息方法
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <param name="level"></param>
|
||||||
|
/// <param name="useLevel"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private void DefaultPrint(string name, string msg, LogLevel level = LogLevel.Info, bool useLevel = true)
|
||||||
|
{
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
Console.Write("\r" + now.AddMilliseconds(-now.Millisecond).ToString() + $" {CommonSet.GetLogLevelPrefix(level)}/[Addon] {Addon.Name}:\n\r> ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出错误消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private void DefaultPrint(Exception e) => DefaultPrint(Addon.Name, e.ToString(), LogLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
756
Controller/RunTimeController.cs
Normal file
756
Controller/RunTimeController.cs
Normal file
@ -0,0 +1,756 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using Milimoe.FunGame.Core.Api.Transmittal;
|
||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Network;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Library.Exception;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Controller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 此类实现服务器连接、断开连接、心跳检测、创建数据请求等功能
|
||||||
|
/// -- 需要继承并实现部分方法 --
|
||||||
|
/// </summary>
|
||||||
|
public abstract class RunTimeController
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 与服务器的连接套接字实例
|
||||||
|
/// </summary>
|
||||||
|
public Socket? Socket => _Socket;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 与服务器的连接套接字实例(WebSocket)
|
||||||
|
/// </summary>
|
||||||
|
public HTTPClient? HTTPClient => _HTTPClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 套接字是否已经连接
|
||||||
|
/// </summary>
|
||||||
|
public bool Connected => _Socket != null && _Socket.Connected;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 接收服务器信息的线程
|
||||||
|
/// </summary>
|
||||||
|
protected Task? _ReceivingTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于类内赋值
|
||||||
|
/// </summary>
|
||||||
|
protected Socket? _Socket;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于类内赋值
|
||||||
|
/// </summary>
|
||||||
|
protected HTTPClient? _HTTPClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否正在接收服务器信息
|
||||||
|
/// </summary>
|
||||||
|
protected bool _IsReceiving;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 断开服务器连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Disconnect()
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = _Socket?.Send(SocketMessageType.Disconnect, "") == SocketResult.Success;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
WritelnSystemInfo(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送结束游戏反馈
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool EndGame()
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = _Socket?.Send(SocketMessageType.EndGame, "") == SocketResult.Success;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
WritelnSystemInfo(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接服务器 [ 可选参数需要根据连接方式提供 ]
|
||||||
|
/// 建议使用异步版,此方法为兼容性处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="address"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <param name="ssl"></param>
|
||||||
|
/// <param name="subUrl"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ConnectResult Connect(TransmittalType type, string address, int port, bool ssl = false, string subUrl = "")
|
||||||
|
{
|
||||||
|
return Task.Run(() => ConnectAsync(type, address, port, ssl, subUrl)).Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接服务器 [ 异步版,可选参数需要根据连接方式提供 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="address"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <param name="ssl"></param>
|
||||||
|
/// <param name="subUrl"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<ConnectResult> ConnectAsync(TransmittalType type, string address, int port = 0, bool ssl = false, string subUrl = "")
|
||||||
|
{
|
||||||
|
ArrayList connectArgs = [];
|
||||||
|
if (!BeforeConnect(ref address, ref port, connectArgs))
|
||||||
|
{
|
||||||
|
return ConnectResult.ConnectFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectResult result = ConnectResult.Success;
|
||||||
|
string msg;
|
||||||
|
string serverName = "";
|
||||||
|
string notice = "";
|
||||||
|
|
||||||
|
// 检查服务器地址和端口是否正确
|
||||||
|
if (address == "" || (type == TransmittalType.Socket && port <= 0) || (type == TransmittalType.WebSocket && port < 0))
|
||||||
|
{
|
||||||
|
result = ConnectResult.FindServerFailed;
|
||||||
|
}
|
||||||
|
if (result == ConnectResult.Success)
|
||||||
|
{
|
||||||
|
// 与服务器建立连接
|
||||||
|
if (type == TransmittalType.Socket)
|
||||||
|
{
|
||||||
|
connectArgs = await Connect_Socket(connectArgs, address, port);
|
||||||
|
}
|
||||||
|
else if (type == TransmittalType.WebSocket)
|
||||||
|
{
|
||||||
|
connectArgs = await Connect_WebSocket(connectArgs, address, ssl, port, subUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = ConnectResult.FindServerFailed;
|
||||||
|
msg = "连接服务器失败,未指定连接方式。";
|
||||||
|
connectArgs = [result, msg, serverName, notice];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AfterConnect(connectArgs);
|
||||||
|
|
||||||
|
// 允许修改数组中的result,强行改变连接的结果
|
||||||
|
if (connectArgs.Count > 0)
|
||||||
|
{
|
||||||
|
result = (ConnectResult?)connectArgs[0] ?? result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 Socket 方式连接服务器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectArgs"></param>
|
||||||
|
/// <param name="address"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<ArrayList> Connect_Socket(ArrayList connectArgs, string address, int port)
|
||||||
|
{
|
||||||
|
ConnectResult result = ConnectResult.Success;
|
||||||
|
string msg = "";
|
||||||
|
string serverName = "";
|
||||||
|
string notice = "";
|
||||||
|
|
||||||
|
_Socket?.Close();
|
||||||
|
_Socket = Socket.Connect(address, port);
|
||||||
|
if (_Socket != null && _Socket.Connected)
|
||||||
|
{
|
||||||
|
if (_Socket.Send(SocketMessageType.Connect, connectArgs.Cast<object>().ToArray()) == SocketResult.Success)
|
||||||
|
{
|
||||||
|
SocketObject[] objs = _Socket.Receive();
|
||||||
|
foreach (SocketObject obj in objs)
|
||||||
|
{
|
||||||
|
if (obj.SocketType == SocketMessageType.Connect)
|
||||||
|
{
|
||||||
|
bool success = obj.GetParam<bool>(0);
|
||||||
|
msg = obj.GetParam<string>(1) ?? "";
|
||||||
|
result = success ? ConnectResult.Success : ConnectResult.ConnectFailed;
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
_Socket.Token = obj.GetParam<Guid>(2);
|
||||||
|
serverName = obj.GetParam<string>(3) ?? "";
|
||||||
|
notice = obj.GetParam<string>(4) ?? "";
|
||||||
|
StartReceiving();
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (_IsReceiving)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_Socket.ConnectionLost += Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else result = ConnectResult.ConnectFailed;
|
||||||
|
}
|
||||||
|
else _Socket?.Close();
|
||||||
|
|
||||||
|
return [result, msg, serverName, notice];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 WebSocket 方式连接服务器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectArgs"></param>
|
||||||
|
/// <param name="address"></param>
|
||||||
|
/// <param name="ssl"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <param name="subUrl"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<ArrayList> Connect_WebSocket(ArrayList connectArgs, string address, bool ssl, int port, string subUrl)
|
||||||
|
{
|
||||||
|
ConnectResult result = ConnectResult.Success;
|
||||||
|
string msg = "";
|
||||||
|
string serverName = "";
|
||||||
|
string notice = "";
|
||||||
|
|
||||||
|
_HTTPClient?.Close();
|
||||||
|
_HTTPClient = await HTTPClient.Connect(address, ssl, port, subUrl, connectArgs.Cast<object>().ToArray());
|
||||||
|
if (_HTTPClient.Connected)
|
||||||
|
{
|
||||||
|
bool webSocketConnected = false;
|
||||||
|
_HTTPClient.AddSocketObjectHandler(obj =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (obj.SocketType == SocketMessageType.Connect)
|
||||||
|
{
|
||||||
|
bool success = obj.GetParam<bool>(0);
|
||||||
|
msg = obj.GetParam<string>(1) ?? "";
|
||||||
|
result = success ? ConnectResult.Success : ConnectResult.ConnectFailed;
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
_HTTPClient.Token = obj.GetParam<Guid>(2);
|
||||||
|
serverName = obj.GetParam<string>(3) ?? "";
|
||||||
|
notice = obj.GetParam<string>(4) ?? "";
|
||||||
|
}
|
||||||
|
webSocketConnected = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HandleSocketMessage(TransmittalType.WebSocket, obj);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
while (!webSocketConnected)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
_HTTPClient.ConnectionLost += Error;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_HTTPClient?.Close();
|
||||||
|
result = ConnectResult.ConnectFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [result, msg, serverName, notice];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取服务器地址
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>string:服务器地址;int:端口号</returns>
|
||||||
|
/// <exception cref="FindServerFailedException"></exception>
|
||||||
|
public (string, int) GetServerAddress()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string? ipaddress = (string?)Implement.GetFunGameImplValue(InterfaceType.IClient, InterfaceMethod.RemoteServerIP);
|
||||||
|
if (ipaddress != null)
|
||||||
|
{
|
||||||
|
string[] s = ipaddress.Split(':');
|
||||||
|
if (s != null && s.Length > 1)
|
||||||
|
{
|
||||||
|
return (s[0], Convert.ToInt32(s[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new FindServerFailedException();
|
||||||
|
}
|
||||||
|
catch (FindServerFailedException e)
|
||||||
|
{
|
||||||
|
WritelnSystemInfo(e.GetErrorInfo());
|
||||||
|
return ("", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 此方法将在连接服务器前触发<para/>
|
||||||
|
/// 客户端可以重写此方法
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">服务器地址</param>
|
||||||
|
/// <param name="port">服务器端口</param>
|
||||||
|
/// <param name="args">重写时可以提供额外的连接参数</param>
|
||||||
|
/// <returns>false:中止连接</returns>
|
||||||
|
public virtual bool BeforeConnect(ref string address, ref int port, ArrayList args)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 此方法将在连接服务器后触发(Connect结果返回前)<para/>
|
||||||
|
/// 客户端可以重写此方法
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ConnectArgs">连接服务器后返回的一些数据,可以使用也可以修改它们</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual void AfterConnect(ArrayList ConnectArgs)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端需要自行实现自动登录的事务
|
||||||
|
/// </summary>
|
||||||
|
public virtual void AutoLogin(string Username, string Password, string AutoKey)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭 Socket 连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Close_Socket()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_Socket != null)
|
||||||
|
{
|
||||||
|
_Socket.Close();
|
||||||
|
_Socket = null;
|
||||||
|
}
|
||||||
|
if (_ReceivingTask != null && !_ReceivingTask.IsCompleted)
|
||||||
|
{
|
||||||
|
_ReceivingTask.Wait(1);
|
||||||
|
_ReceivingTask = null;
|
||||||
|
_IsReceiving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
WritelnSystemInfo(e.GetErrorInfo());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭 WebSocket 连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Close_WebSocket()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_HTTPClient != null)
|
||||||
|
{
|
||||||
|
_HTTPClient.Close();
|
||||||
|
_HTTPClient = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
WritelnSystemInfo(e.GetErrorInfo());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <param name="level"></param>
|
||||||
|
/// <param name="useLevel"></param>
|
||||||
|
public abstract void WritelnSystemInfo(string msg, LogLevel level = LogLevel.Info, bool useLevel = true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自定处理异常的方法
|
||||||
|
/// -- 一般放在catch中 --
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
public abstract void Error(Exception e);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建新的数据请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="RequestType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ConnectFailedException"></exception>
|
||||||
|
public DataRequest NewDataRequest(DataRequestType RequestType)
|
||||||
|
{
|
||||||
|
if (_Socket != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_Socket, RequestType);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
else if (_HTTPClient != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_HTTPClient, RequestType);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
throw new ConnectFailedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建长时间运行的数据请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="RequestType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ConnectFailedException"></exception>
|
||||||
|
public DataRequest NewLongRunningDataRequest(DataRequestType RequestType)
|
||||||
|
{
|
||||||
|
if (_Socket != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_Socket, RequestType, true);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
else if (_HTTPClient != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_HTTPClient, RequestType, true);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
throw new ConnectFailedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建新的数据请求<para/>
|
||||||
|
/// 加载项专用(<see cref="Library.Common.Addon.Plugin"/> / <see cref="Library.Common.Addon.GameModule"/>)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="RequestType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ConnectFailedException"></exception>
|
||||||
|
public DataRequest NewDataRequestForAddon(DataRequestType RequestType)
|
||||||
|
{
|
||||||
|
if (_Socket != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_Socket, RequestType, false, SocketRuntimeType.Addon);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
else if (_HTTPClient != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_HTTPClient, RequestType, false, SocketRuntimeType.Addon);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
throw new ConnectFailedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建长时间运行的数据请求
|
||||||
|
/// 加载项专用(<see cref="Library.Common.Addon.Plugin"/> / <see cref="Library.Common.Addon.GameModule"/>)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="RequestType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ConnectFailedException"></exception>
|
||||||
|
public DataRequest NewLongRunningDataRequestForAddon(DataRequestType RequestType)
|
||||||
|
{
|
||||||
|
if (_Socket != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_Socket, RequestType, true, SocketRuntimeType.Addon);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
else if (_HTTPClient != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_HTTPClient, RequestType, true, SocketRuntimeType.Addon);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
throw new ConnectFailedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建新的局内(<see cref="Model.Gaming"/>)数据请求<para/>
|
||||||
|
/// 加载项专用:此方法是给 <see cref="Library.Common.Addon.GameModule"/> 提供的
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="GamingType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ConnectFailedException"></exception>
|
||||||
|
public DataRequest NewDataRequestForAddon(GamingType GamingType)
|
||||||
|
{
|
||||||
|
if (_Socket != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_Socket, GamingType, false, SocketRuntimeType.Addon);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
else if (_HTTPClient != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_HTTPClient, GamingType, false, SocketRuntimeType.Addon);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
throw new ConnectFailedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于本地已连接的Socket创建长时间运行的局内(<see cref="Model.Gaming"/>)数据请求<para/>
|
||||||
|
/// 加载项专用:此方法是给 <see cref="Library.Common.Addon.GameModule"/> 提供的
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="GamingType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ConnectFailedException"></exception>
|
||||||
|
public DataRequest NewLongRunningDataRequestForAddon(GamingType GamingType)
|
||||||
|
{
|
||||||
|
if (_Socket != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_Socket, GamingType, true, SocketRuntimeType.Addon);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
else if (_HTTPClient != null)
|
||||||
|
{
|
||||||
|
DataRequest request = new(_HTTPClient, GamingType, true, SocketRuntimeType.Addon);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
throw new ConnectFailedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始接收服务器信息 [ Socket Only ]
|
||||||
|
/// </summary>
|
||||||
|
protected void StartReceiving()
|
||||||
|
{
|
||||||
|
_ReceivingTask = Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
_IsReceiving = true;
|
||||||
|
while (Connected)
|
||||||
|
{
|
||||||
|
Receiving();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_Socket?.StartReceiving(_ReceivingTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取服务器已发送的信息为SocketObject数组 [ Socket Only ]
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected SocketObject[] GetServerMessages()
|
||||||
|
{
|
||||||
|
if (_Socket != null && _Socket.Connected)
|
||||||
|
{
|
||||||
|
return _Socket.Receive();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 具体接收服务器信息以及处理信息的方法 [ Socket Only ]
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected SocketMessageType Receiving()
|
||||||
|
{
|
||||||
|
if (_Socket is null) return SocketMessageType.Unknown;
|
||||||
|
SocketMessageType result = SocketMessageType.Unknown;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SocketObject[] messages = GetServerMessages();
|
||||||
|
|
||||||
|
foreach (SocketObject obj in messages)
|
||||||
|
{
|
||||||
|
result = HandleSocketMessage(TransmittalType.Socket, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_Socket?.OnConnectionLost(e);
|
||||||
|
Close_Socket();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理接收到的信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="transmittalType"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected SocketMessageType HandleSocketMessage(TransmittalType transmittalType, SocketObject obj)
|
||||||
|
{
|
||||||
|
SocketMessageType type = obj.SocketType;
|
||||||
|
SocketMessageType result = type;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case SocketMessageType.Disconnect:
|
||||||
|
if (transmittalType == TransmittalType.Socket)
|
||||||
|
{
|
||||||
|
Close_Socket();
|
||||||
|
}
|
||||||
|
else if (transmittalType == TransmittalType.WebSocket)
|
||||||
|
{
|
||||||
|
Close_WebSocket();
|
||||||
|
}
|
||||||
|
SocketHandler_Disconnect(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.System:
|
||||||
|
SocketHandler_System(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.HeartBeat:
|
||||||
|
SocketHandler_HeartBeat(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.ForceLogout:
|
||||||
|
SocketHandler_ForceLogout(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.Chat:
|
||||||
|
SocketHandler_Chat(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.UpdateRoomMaster:
|
||||||
|
SocketHandler_UpdateRoomMaster(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.MatchRoom:
|
||||||
|
SocketHandler_MatchRoom(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.StartGame:
|
||||||
|
SocketHandler_StartGame(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.EndGame:
|
||||||
|
SocketHandler_EndGame(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.Gaming:
|
||||||
|
SocketHandler_Gaming(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.AnonymousGameServer:
|
||||||
|
SocketHandler_AnonymousGameServer(obj);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketMessageType.Unknown:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收服务器断开连接的通知
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected abstract void SocketHandler_Disconnect(SocketObject obj);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收并处理服务器系统消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected virtual void SocketHandler_System(SocketObject obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收并处理服务器心跳
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected virtual void SocketHandler_HeartBeat(SocketObject obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收强制退出登录的通知
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected virtual void SocketHandler_ForceLogout(SocketObject obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收并处理聊天信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected virtual void SocketHandler_Chat(SocketObject obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收并处理更换房主信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected virtual void SocketHandler_UpdateRoomMaster(SocketObject obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收并处理匹配房间成功信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected virtual void SocketHandler_MatchRoom(SocketObject obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收并处理开始游戏信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected virtual void SocketHandler_StartGame(SocketObject obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收并处理游戏结束信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected virtual void SocketHandler_EndGame(SocketObject obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收并处理局内消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected virtual void SocketHandler_Gaming(SocketObject obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端接收并处理匿名服务器的消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
protected virtual void SocketHandler_AnonymousGameServer(SocketObject obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
121
Controller/ServerAddonController.cs
Normal file
121
Controller/ServerAddonController.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Transmittal;
|
||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Addons;
|
||||||
|
using Milimoe.FunGame.Core.Library.SQLScript.Common;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Controller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 这个控制器在 Base 的基础上添加了 SQLHelper 和 MailSender
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <remarks>
|
||||||
|
/// 新建一个ServerAddonController
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="addon"></param>
|
||||||
|
/// <param name="delegates"></param>
|
||||||
|
public class ServerAddonController<T>(IAddon addon, Dictionary<string, object> delegates) : BaseAddonController<T>(addon, delegates), IServerAddon where T : IAddon
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库连接器 [ 后台长连接仅查询专用,请勿用于事务、更新和新增,对应需求请使用:<see cref="GetSQLHelper"/> ]
|
||||||
|
/// </summary>
|
||||||
|
public SQLHelper? SQLHelper => _sqlHelper;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 邮件发送器
|
||||||
|
/// </summary>
|
||||||
|
public MailSender? MailSender => _mailSender;
|
||||||
|
|
||||||
|
private SQLHelper? _sqlHelper = null;
|
||||||
|
private MailSender? _mailSender = null;
|
||||||
|
private Task? _sqlPolling = null;
|
||||||
|
private CancellationTokenSource? _cts = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取一个可以用来进行事务操作、更新/新增数据的数据库连接器 [ 请使用 using Controller.GetSQLHelper() 来让它能够自动释放 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public SQLHelper? GetSQLHelper()
|
||||||
|
{
|
||||||
|
return Factory.OpenFactory.GetSQLHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新建 SQLHelper [ 后台长连接仅查询专用,请勿用于事务、更新和新增,对应需求请使用:<see cref="GetSQLHelper"/> ]
|
||||||
|
/// </summary>
|
||||||
|
public void NewSQLHelper()
|
||||||
|
{
|
||||||
|
if (_sqlHelper is null)
|
||||||
|
{
|
||||||
|
// 创建持久化 SQLHelper
|
||||||
|
_sqlHelper = Factory.OpenFactory.GetSQLHelper();
|
||||||
|
if (_sqlHelper != null)
|
||||||
|
{
|
||||||
|
_cts = new();
|
||||||
|
_sqlPolling = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(30);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (_cts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 每两小时触发一次SQL服务器的心跳查询,防止SQL服务器掉线
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(2 * 1000 * 3600, _cts.Token);
|
||||||
|
_sqlHelper?.ExecuteDataSet(ServerLoginLogs.Select_GetLastLoginTime());
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, _cts.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLine("已经创建过 SQLHelper 实例。", Library.Constant.LogLevel.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新建 MailSender
|
||||||
|
/// </summary>
|
||||||
|
public void NewMailSender()
|
||||||
|
{
|
||||||
|
if (_mailSender is null)
|
||||||
|
{
|
||||||
|
_mailSender = Factory.OpenFactory.GetMailSender();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLine("已经创建过 MailSender 实例。", Library.Constant.LogLevel.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭插件的服务
|
||||||
|
/// </summary>
|
||||||
|
public async void Close()
|
||||||
|
{
|
||||||
|
_mailSender?.Dispose();
|
||||||
|
_mailSender = null;
|
||||||
|
_cts?.Cancel();
|
||||||
|
if (_sqlPolling != null)
|
||||||
|
{
|
||||||
|
await _sqlPolling;
|
||||||
|
_sqlPolling.Dispose();
|
||||||
|
_sqlPolling = null;
|
||||||
|
}
|
||||||
|
_cts?.Dispose();
|
||||||
|
_cts = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
Controller/SocketHandlerController.cs
Normal file
119
Controller/SocketHandlerController.cs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
using Milimoe.FunGame.Core.Interface.Sockets;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Architecture;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Network;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Controller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <para>继承 AsyncAwaiter 用法:</para>
|
||||||
|
/// <para>1、调用Socket.Send()前,请设置为等待状态:SetWorking();</para>
|
||||||
|
/// <para>2、调用Socket.Send() == Success后,请等待任务完成:WaitForWorkDone();</para>
|
||||||
|
/// <para>3、在其他任何地方修改Working状态,均会使任务终止。</para>
|
||||||
|
/// </summary>
|
||||||
|
public class SocketHandlerController : AsyncAwaiter<SocketObject>, ISocketHandler, IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 接收到的SocketObject实例
|
||||||
|
/// </summary>
|
||||||
|
protected override SocketObject ReceivedObject { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Socket
|
||||||
|
/// </summary>
|
||||||
|
private readonly Socket? _socket;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// WebSocket
|
||||||
|
/// </summary>
|
||||||
|
private readonly HTTPClient? _webSocket;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 继承请调用base构造
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="socket">Socket</param>
|
||||||
|
public SocketHandlerController(Socket? socket)
|
||||||
|
{
|
||||||
|
if (socket != null)
|
||||||
|
{
|
||||||
|
_socket = socket;
|
||||||
|
socket.AddSocketObjectHandler(SocketHandler);
|
||||||
|
}
|
||||||
|
else throw new SocketCreateReceivingException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 继承请调用base构造
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="websocket">Socket</param>
|
||||||
|
public SocketHandlerController(HTTPClient? websocket)
|
||||||
|
{
|
||||||
|
if (websocket != null)
|
||||||
|
{
|
||||||
|
_webSocket = websocket;
|
||||||
|
websocket.AddSocketObjectHandler(SocketHandler);
|
||||||
|
}
|
||||||
|
else throw new SocketCreateReceivingException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 继承请重写此方法
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="SocketObject">SocketObject</param>
|
||||||
|
public virtual void SocketHandler(SocketObject SocketObject)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断是否已经Disposed
|
||||||
|
/// </summary>
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 公开的Dispose方法
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
OnDisposed();
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭时
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Disposing"></param>
|
||||||
|
protected void Dispose(bool Disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (Disposing)
|
||||||
|
{
|
||||||
|
_socket?.RemoveSocketObjectHandler(SocketHandler);
|
||||||
|
_webSocket?.RemoveSocketObjectHandler(SocketHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭事件
|
||||||
|
/// </summary>
|
||||||
|
protected delegate void DisposedEvent();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>Controller关闭时事件</para>
|
||||||
|
/// <para>不建议new Dispose()方法,建议使用事件</para>
|
||||||
|
/// <para>事件会在base.Dispose()执行前触发</para>
|
||||||
|
/// </summary>
|
||||||
|
protected event DisposedEvent? Disposed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 触发关闭事件
|
||||||
|
/// </summary>
|
||||||
|
protected void OnDisposed()
|
||||||
|
{
|
||||||
|
Disposed?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Entity/BaseEntity.cs
Normal file
50
Entity/BaseEntity.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public abstract class BaseEntity : IBaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 实体的数字 ID
|
||||||
|
/// </summary>
|
||||||
|
public virtual long Id { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实体的唯一 ID
|
||||||
|
/// </summary>
|
||||||
|
public virtual Guid Guid { get; set; } = Guid.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实体的名称
|
||||||
|
/// </summary>
|
||||||
|
public virtual string Name { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实体的当前状态(关联数据库操作)
|
||||||
|
/// </summary>
|
||||||
|
public EntityState EntityState { get; set; } = EntityState.Unchanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实体所用的游戏平衡常数
|
||||||
|
/// </summary>
|
||||||
|
public EquilibriumConstant GameplayEquilibriumConstant { get; set; } = General.GameplayEquilibriumConstant;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 比较两个实体是否相同
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public abstract bool Equals(IBaseEntity? other);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取实体的 Id.Name
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetIdName()
|
||||||
|
{
|
||||||
|
return Id + "." + Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
Entity/Character/AssistDetail.cs
Normal file
104
Entity/Character/AssistDetail.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 用于记录对哪个角色造成了多少伤害
|
||||||
|
/// </summary>
|
||||||
|
public class AssistDetail
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 此详情类属于哪个角色
|
||||||
|
/// </summary>
|
||||||
|
public Character Character { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对敌人造成的伤害
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<Character, double> Damages { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最后一次造成伤害的时间
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<Character, double> DamageLastTime { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对某角色最后一次友方非伤害辅助的时间
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<Character, double> NotDamageAssistLastTime { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化一个助攻详情类
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
public AssistDetail(Character character, IEnumerable<Character> enemys)
|
||||||
|
{
|
||||||
|
Character = character;
|
||||||
|
foreach (Character enemy in enemys)
|
||||||
|
{
|
||||||
|
this[enemy] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取和设置对 <paramref name="enemy"/> 的伤害,并设置时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enemy"></param>
|
||||||
|
/// <param name="time"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public double this[Character enemy, double? time = null]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Damages[enemy];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Damages[enemy] = Calculation.Round2Digits(value);
|
||||||
|
if (time.HasValue)
|
||||||
|
{
|
||||||
|
DamageLastTime[enemy] = time.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对 <paramref name="enemy"/> 的伤害
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enemy"></param>
|
||||||
|
/// <returns>目标的 <see cref="Character.MaxHP"/> 的百分比形式</returns>
|
||||||
|
public double GetPercentage(Character enemy)
|
||||||
|
{
|
||||||
|
return Calculation.Round2Digits(Damages[enemy] / enemy.MaxHP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对 <paramref name="enemy"/> 造成伤害的最后时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enemy"></param>
|
||||||
|
/// <returns>-1 意味着没有时间</returns>
|
||||||
|
public double GetLastTime(Character enemy)
|
||||||
|
{
|
||||||
|
if (DamageLastTime.TryGetValue(enemy, out double time))
|
||||||
|
{
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对某角色友方非伤害辅助的最后时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <returns>-1 意味着没有时间</returns>
|
||||||
|
public double GetNotDamageAssistLastTime(Character character)
|
||||||
|
{
|
||||||
|
if (NotDamageAssistLastTime.TryGetValue(character, out double time))
|
||||||
|
{
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2082
Entity/Character/Character.cs
Normal file
2082
Entity/Character/Character.cs
Normal file
File diff suppressed because it is too large
Load Diff
265
Entity/Character/CharacterBuilder.cs
Normal file
265
Entity/Character/CharacterBuilder.cs
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class CharacterBuilder
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string FirstName { get; set; }
|
||||||
|
public string NickName { get; set; }
|
||||||
|
public PrimaryAttribute PrimaryAttribute { get; set; }
|
||||||
|
public double InitialATK { get; set; }
|
||||||
|
public double InitialDEF { get; set; }
|
||||||
|
public double InitialHP { get; set; }
|
||||||
|
public double InitialMP { get; set; }
|
||||||
|
public double InitialSTR { get; set; }
|
||||||
|
public double STRGrowth { get; set; }
|
||||||
|
public double InitialAGI { get; set; }
|
||||||
|
public double AGIGrowth { get; set; }
|
||||||
|
public double InitialINT { get; set; }
|
||||||
|
public double INTGrowth { get; set; }
|
||||||
|
public double InitialSPD { get; set; }
|
||||||
|
public double InitialHR { get; set; }
|
||||||
|
public double InitialMR { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于初始属性创建角色构建器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="firstName"></param>
|
||||||
|
/// <param name="nickName"></param>
|
||||||
|
/// <param name="primaryAttribute"></param>
|
||||||
|
/// <param name="initialATK"></param>
|
||||||
|
/// <param name="initialDEF"></param>
|
||||||
|
/// <param name="initialHP"></param>
|
||||||
|
/// <param name="initialMP"></param>
|
||||||
|
/// <param name="initialSTR"></param>
|
||||||
|
/// <param name="strGrowth"></param>
|
||||||
|
/// <param name="initialAGI"></param>
|
||||||
|
/// <param name="agiGrowth"></param>
|
||||||
|
/// <param name="initialINT"></param>
|
||||||
|
/// <param name="intGrowth"></param>
|
||||||
|
/// <param name="initialSPD"></param>
|
||||||
|
/// <param name="initialHR"></param>
|
||||||
|
/// <param name="initialMR"></param>
|
||||||
|
public CharacterBuilder(long id, string name, string firstName, string nickName, PrimaryAttribute primaryAttribute, double initialATK, double initialDEF, double initialHP, double initialMP, double initialSTR, double strGrowth, double initialAGI, double agiGrowth, double initialINT, double intGrowth, double initialSPD, double initialHR, double initialMR)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
FirstName = firstName;
|
||||||
|
NickName = nickName;
|
||||||
|
PrimaryAttribute = primaryAttribute;
|
||||||
|
InitialATK = initialATK;
|
||||||
|
InitialDEF = initialDEF;
|
||||||
|
InitialHP = initialHP;
|
||||||
|
InitialMP = initialMP;
|
||||||
|
InitialSTR = initialSTR;
|
||||||
|
STRGrowth = strGrowth;
|
||||||
|
InitialAGI = initialAGI;
|
||||||
|
AGIGrowth = agiGrowth;
|
||||||
|
InitialINT = initialINT;
|
||||||
|
INTGrowth = intGrowth;
|
||||||
|
InitialSPD = initialSPD;
|
||||||
|
InitialHR = initialHR;
|
||||||
|
InitialMR = initialMR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于模板角色创建角色构建器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
public CharacterBuilder(Character character)
|
||||||
|
{
|
||||||
|
Id = character.Id;
|
||||||
|
Name = character.Name;
|
||||||
|
FirstName = character.FirstName;
|
||||||
|
NickName = character.NickName;
|
||||||
|
PrimaryAttribute = character.PrimaryAttribute;
|
||||||
|
InitialATK = character.InitialATK;
|
||||||
|
InitialDEF = character.InitialDEF;
|
||||||
|
InitialHP = character.InitialHP;
|
||||||
|
InitialMP = character.InitialMP;
|
||||||
|
InitialSTR = character.InitialSTR;
|
||||||
|
STRGrowth = character.STRGrowth;
|
||||||
|
InitialAGI = character.InitialAGI;
|
||||||
|
AGIGrowth = character.AGIGrowth;
|
||||||
|
InitialINT = character.InitialINT;
|
||||||
|
INTGrowth = character.INTGrowth;
|
||||||
|
InitialSPD = character.InitialSPD;
|
||||||
|
InitialHR = character.InitialHR;
|
||||||
|
InitialMR = character.InitialMR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于初始条件构建新的角色
|
||||||
|
/// <para>需要传入技能、物品,可选构建装备</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skills"></param>
|
||||||
|
/// <param name="items"></param>
|
||||||
|
/// <param name="newItemGuid"></param>
|
||||||
|
/// <param name="equips"></param>
|
||||||
|
/// <param name="inventory"></param>
|
||||||
|
/// <param name="itemsDefined"></param>
|
||||||
|
/// <param name="skillsDefined"></param>
|
||||||
|
/// <returns>构建的新角色</returns>
|
||||||
|
public Character Build(IEnumerable<Skill> skills, IEnumerable<Item> items, bool newItemGuid = true, EquipSlot? equips = null, Inventory? inventory = null, IEnumerable<Item>? itemsDefined = null, IEnumerable<Skill>? skillsDefined = null)
|
||||||
|
{
|
||||||
|
Character character = Factory.GetCharacter();
|
||||||
|
character.Id = Id;
|
||||||
|
character.Name = Name;
|
||||||
|
character.FirstName = FirstName;
|
||||||
|
character.NickName = NickName;
|
||||||
|
character.PrimaryAttribute = PrimaryAttribute;
|
||||||
|
character.InitialATK = InitialATK;
|
||||||
|
character.InitialDEF = InitialDEF;
|
||||||
|
character.InitialHP = InitialHP;
|
||||||
|
character.InitialMP = InitialMP;
|
||||||
|
character.InitialSTR = InitialSTR;
|
||||||
|
character.STRGrowth = STRGrowth;
|
||||||
|
character.InitialAGI = InitialAGI;
|
||||||
|
character.AGIGrowth = AGIGrowth;
|
||||||
|
character.InitialINT = InitialINT;
|
||||||
|
character.INTGrowth = INTGrowth;
|
||||||
|
character.InitialSPD = InitialSPD;
|
||||||
|
character.InitialHR = InitialHR;
|
||||||
|
character.InitialMR = InitialMR;
|
||||||
|
foreach (Skill skill in skills)
|
||||||
|
{
|
||||||
|
// 主动技能的Guid表示与其关联的物品
|
||||||
|
if (skill.Guid == Guid.Empty)
|
||||||
|
{
|
||||||
|
Skill newskill = skill.Copy(true, skillsDefined);
|
||||||
|
newskill.Character = character;
|
||||||
|
newskill.Level = skill.Level;
|
||||||
|
if (skill.CurrentCD > 0 && !skill.Enable)
|
||||||
|
{
|
||||||
|
newskill.Enable = true;
|
||||||
|
newskill.CurrentCD = 0;
|
||||||
|
}
|
||||||
|
character.Skills.Add(newskill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (Item item in items)
|
||||||
|
{
|
||||||
|
Item newitem;
|
||||||
|
if (itemsDefined != null && itemsDefined.FirstOrDefault(i => i.GetIdName() == item.GetIdName()) is Item itemDefined)
|
||||||
|
{
|
||||||
|
newitem = itemDefined.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
|
||||||
|
item.SetPropertyToItemModuleNew(newitem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newitem = item.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
|
||||||
|
}
|
||||||
|
newitem.Character = character;
|
||||||
|
character.Items.Add(newitem);
|
||||||
|
}
|
||||||
|
if (equips != null)
|
||||||
|
{
|
||||||
|
Item? mcp = equips.MagicCardPack;
|
||||||
|
Item? w = equips.Weapon;
|
||||||
|
Item? a = equips.Armor;
|
||||||
|
Item? s = equips.Shoes;
|
||||||
|
Item? ac1 = equips.Accessory1;
|
||||||
|
Item? ac2 = equips.Accessory2;
|
||||||
|
if (inventory != null)
|
||||||
|
{
|
||||||
|
if (inventory.Items.FirstOrDefault(i => i.Guid == mcp?.Guid) is Item newmcp)
|
||||||
|
{
|
||||||
|
character.Equip(newmcp);
|
||||||
|
}
|
||||||
|
if (inventory.Items.FirstOrDefault(i => i.Guid == w?.Guid) is Item neww)
|
||||||
|
{
|
||||||
|
character.Equip(neww);
|
||||||
|
}
|
||||||
|
if (inventory.Items.FirstOrDefault(i => i.Guid == a?.Guid) is Item newa)
|
||||||
|
{
|
||||||
|
character.Equip(newa);
|
||||||
|
}
|
||||||
|
if (inventory.Items.FirstOrDefault(i => i.Guid == s?.Guid) is Item news)
|
||||||
|
{
|
||||||
|
character.Equip(news);
|
||||||
|
}
|
||||||
|
if (inventory.Items.FirstOrDefault(i => i.Guid == ac1?.Guid) is Item newac1)
|
||||||
|
{
|
||||||
|
character.Equip(newac1);
|
||||||
|
}
|
||||||
|
if (inventory.Items.FirstOrDefault(i => i.Guid == ac2?.Guid) is Item newac2)
|
||||||
|
{
|
||||||
|
character.Equip(newac2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (mcp != null)
|
||||||
|
{
|
||||||
|
mcp = mcp.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
|
||||||
|
character.Equip(mcp);
|
||||||
|
}
|
||||||
|
if (w != null)
|
||||||
|
{
|
||||||
|
w = w.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
|
||||||
|
character.Equip(w);
|
||||||
|
}
|
||||||
|
if (a != null)
|
||||||
|
{
|
||||||
|
a = a.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
|
||||||
|
character.Equip(a);
|
||||||
|
}
|
||||||
|
if (s != null)
|
||||||
|
{
|
||||||
|
s = s.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
|
||||||
|
character.Equip(s);
|
||||||
|
}
|
||||||
|
if (ac1 != null)
|
||||||
|
{
|
||||||
|
ac1 = ac1.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
|
||||||
|
character.Equip(ac1);
|
||||||
|
}
|
||||||
|
if (ac2 != null)
|
||||||
|
{
|
||||||
|
ac2 = ac2.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
|
||||||
|
character.Equip(ac2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
character.Recovery();
|
||||||
|
return character;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 <paramref name="reference"/> 角色构建
|
||||||
|
/// <para>新角色将获得参考角色的等级、技能、装备和身上的物品</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reference"></param>
|
||||||
|
/// <param name="newItemGuid"></param>
|
||||||
|
/// <param name="copyLevel"></param>
|
||||||
|
/// <param name="inventory"></param>
|
||||||
|
/// <param name="itemsDefined">对于动态扩展的物品而言,传入已定义的物品表,不使用被复制物品的数据</param>
|
||||||
|
/// <param name="skillsDefined">对于动态扩展的技能而言,传入已定义的技能表,不使用被复制技能的数据</param>
|
||||||
|
/// <param name="recovery"></param>
|
||||||
|
/// <returns>构建的新角色</returns>
|
||||||
|
public static Character Build(Character reference, bool newItemGuid = true, bool copyLevel = true, Inventory? inventory = null, IEnumerable<Item>? itemsDefined = null, IEnumerable<Skill>? skillsDefined = null, bool recovery = true)
|
||||||
|
{
|
||||||
|
Character character = new CharacterBuilder(reference).Build(reference.Skills, reference.Items, newItemGuid, reference.EquipSlot, inventory, itemsDefined, skillsDefined);
|
||||||
|
if (copyLevel)
|
||||||
|
{
|
||||||
|
character.Level = reference.Level;
|
||||||
|
character.LevelBreak = reference.LevelBreak;
|
||||||
|
character.EXP = reference.EXP;
|
||||||
|
}
|
||||||
|
character.NormalAttack.Level = reference.NormalAttack.Level;
|
||||||
|
character.NormalAttack.HardnessTime = reference.NormalAttack.HardnessTime;
|
||||||
|
character.NormalAttack.SetMagicType(reference.NormalAttack.IsMagic, reference.NormalAttack.MagicType);
|
||||||
|
if (!recovery)
|
||||||
|
{
|
||||||
|
character.HP = reference.HP;
|
||||||
|
character.MP = reference.MP;
|
||||||
|
}
|
||||||
|
return character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
Entity/Character/CharacterProfile.cs
Normal file
87
Entity/Character/CharacterProfile.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的一些个人信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="firstname"></param>
|
||||||
|
/// <param name="nickname"></param>
|
||||||
|
public class CharacterProfile(string name, string firstname, string nickname)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的姓
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的名字
|
||||||
|
/// </summary>
|
||||||
|
public string FirstName { get; set; } = firstname;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的昵称
|
||||||
|
/// </summary>
|
||||||
|
public string NickName { get; set; } = nickname;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的出生地
|
||||||
|
/// </summary>
|
||||||
|
public string Birthplace { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的出生日期
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Birthday { get; set; } = General.DefaultTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的身份
|
||||||
|
/// </summary>
|
||||||
|
public string Status { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的隶属
|
||||||
|
/// </summary>
|
||||||
|
public string Affiliation { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的性别
|
||||||
|
/// </summary>
|
||||||
|
public string Sex { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的身高
|
||||||
|
/// </summary>
|
||||||
|
public string Height { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的体重
|
||||||
|
/// </summary>
|
||||||
|
public string Weight { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的故事
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Stories { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 复制一个角色资料
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CharacterProfile Copy()
|
||||||
|
{
|
||||||
|
return new(Name, FirstName, NickName)
|
||||||
|
{
|
||||||
|
Birthplace = Birthplace,
|
||||||
|
Birthday = Birthday,
|
||||||
|
Status = Status,
|
||||||
|
Affiliation = Affiliation,
|
||||||
|
Sex = Sex,
|
||||||
|
Height = Height,
|
||||||
|
Weight = Weight,
|
||||||
|
Stories = new(Stories)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Entity/Character/EquipSlot.cs
Normal file
49
Entity/Character/EquipSlot.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的装备槽位
|
||||||
|
/// </summary>
|
||||||
|
public class EquipSlot()
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 魔法卡包
|
||||||
|
/// </summary>
|
||||||
|
public Item? MagicCardPack { get; internal set; } = null;
|
||||||
|
/// <summary>
|
||||||
|
/// 武器
|
||||||
|
/// </summary>
|
||||||
|
public Item? Weapon { get; internal set; } = null;
|
||||||
|
/// <summary>
|
||||||
|
/// 防具
|
||||||
|
/// </summary>
|
||||||
|
public Item? Armor { get; internal set; } = null;
|
||||||
|
/// <summary>
|
||||||
|
/// 鞋子
|
||||||
|
/// </summary>
|
||||||
|
public Item? Shoes { get; internal set; } = null;
|
||||||
|
/// <summary>
|
||||||
|
/// 饰品1
|
||||||
|
/// </summary>
|
||||||
|
public Item? Accessory1 { get; internal set; } = null;
|
||||||
|
/// <summary>
|
||||||
|
/// 饰品2
|
||||||
|
/// </summary>
|
||||||
|
public Item? Accessory2 { get; internal set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上一次装备的饰品槽
|
||||||
|
/// </summary>
|
||||||
|
public EquipSlotType LastEquipSlotType { get; internal set; } = EquipSlotType.Accessory1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否有任意装备
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Any()
|
||||||
|
{
|
||||||
|
return MagicCardPack != null || Weapon != null || Armor != null || Shoes != null || Accessory1 != null || Accessory2 != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
196
Entity/Character/MagicResistance.cs
Normal file
196
Entity/Character/MagicResistance.cs
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的魔法抗性,对不同的魔法类型有不同抗性
|
||||||
|
/// </summary>
|
||||||
|
public class MagicResistance
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 无属性魔法抗性
|
||||||
|
/// </summary>
|
||||||
|
public double None { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 星痕魔法抗性
|
||||||
|
/// </summary>
|
||||||
|
public double Starmark { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 纯粹结晶魔法抗性
|
||||||
|
/// </summary>
|
||||||
|
public double PurityNatural { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 纯现代结晶魔法抗性
|
||||||
|
/// </summary>
|
||||||
|
public double PurityContemporary { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 光魔法抗性
|
||||||
|
/// </summary>
|
||||||
|
public double Bright { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 影魔法抗性
|
||||||
|
/// </summary>
|
||||||
|
public double Shadow { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 元素魔法抗性
|
||||||
|
/// </summary>
|
||||||
|
public double Element { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 紫宛魔法抗性
|
||||||
|
/// </summary>
|
||||||
|
public double Fleabane { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 时空魔法抗性
|
||||||
|
/// </summary>
|
||||||
|
public double Particle { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 平均魔法抗性
|
||||||
|
/// </summary>
|
||||||
|
public double Avg
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double mdf = Calculation.Round4Digits((None + Starmark + PurityNatural + PurityContemporary + Bright + Shadow + Element + Fleabane + Particle) / 9) * 100;
|
||||||
|
if (Calculation.IsApproximatelyZero(mdf)) mdf = 0;
|
||||||
|
return mdf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置抗性值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public double this[MagicType type]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
MagicType.Starmark => Starmark,
|
||||||
|
MagicType.PurityNatural => PurityNatural,
|
||||||
|
MagicType.PurityContemporary => PurityContemporary,
|
||||||
|
MagicType.Bright => Bright,
|
||||||
|
MagicType.Shadow => Shadow,
|
||||||
|
MagicType.Element => Element,
|
||||||
|
MagicType.Fleabane => Fleabane,
|
||||||
|
MagicType.Particle => Particle,
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MagicType.Starmark:
|
||||||
|
Starmark = value;
|
||||||
|
break;
|
||||||
|
case MagicType.PurityNatural:
|
||||||
|
PurityNatural = value;
|
||||||
|
break;
|
||||||
|
case MagicType.PurityContemporary:
|
||||||
|
PurityContemporary = value;
|
||||||
|
break;
|
||||||
|
case MagicType.Bright:
|
||||||
|
Bright = value;
|
||||||
|
break;
|
||||||
|
case MagicType.Shadow:
|
||||||
|
Shadow = value;
|
||||||
|
break;
|
||||||
|
case MagicType.Element:
|
||||||
|
Element = value;
|
||||||
|
break;
|
||||||
|
case MagicType.Fleabane:
|
||||||
|
Fleabane = value;
|
||||||
|
break;
|
||||||
|
case MagicType.Particle:
|
||||||
|
Particle = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
None = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对所有抗性赋值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="assignment"></param>
|
||||||
|
public void SetAllValue(double value, bool assignment = true)
|
||||||
|
{
|
||||||
|
if (assignment)
|
||||||
|
{
|
||||||
|
None = value;
|
||||||
|
Particle = value;
|
||||||
|
Fleabane = value;
|
||||||
|
Element = value;
|
||||||
|
Shadow = value;
|
||||||
|
Bright = value;
|
||||||
|
PurityContemporary = value;
|
||||||
|
PurityNatural = value;
|
||||||
|
Starmark = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
None += value;
|
||||||
|
Particle += value;
|
||||||
|
Fleabane += value;
|
||||||
|
Element += value;
|
||||||
|
Shadow += value;
|
||||||
|
Bright += value;
|
||||||
|
PurityContemporary += value;
|
||||||
|
PurityNatural += value;
|
||||||
|
Starmark += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 增加所有抗性,传入负数来减少
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public void AddAllValue(double value)
|
||||||
|
{
|
||||||
|
None += value;
|
||||||
|
Particle += value;
|
||||||
|
Fleabane += value;
|
||||||
|
Element += value;
|
||||||
|
Shadow += value;
|
||||||
|
Bright += value;
|
||||||
|
PurityContemporary += value;
|
||||||
|
PurityNatural += value;
|
||||||
|
Starmark += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 复制一个魔法抗性对象
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MagicResistance Copy()
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
None = None,
|
||||||
|
Starmark = Starmark,
|
||||||
|
PurityNatural = PurityNatural,
|
||||||
|
PurityContemporary = PurityContemporary,
|
||||||
|
Bright = Bright,
|
||||||
|
Shadow = Shadow,
|
||||||
|
Element = Element,
|
||||||
|
Fleabane = Fleabane,
|
||||||
|
Particle = Particle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
160
Entity/Character/Shield.cs
Normal file
160
Entity/Character/Shield.cs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 角色的护盾,对不同的魔法类型有不同值
|
||||||
|
/// </summary>
|
||||||
|
public class Shield
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 物理护盾
|
||||||
|
/// </summary>
|
||||||
|
public double Physical { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 无属性魔法护盾
|
||||||
|
/// </summary>
|
||||||
|
public double None { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 星痕魔法护盾
|
||||||
|
/// </summary>
|
||||||
|
public double Starmark { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 纯粹结晶护盾
|
||||||
|
/// </summary>
|
||||||
|
public double PurityNatural { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 纯现代结晶护盾
|
||||||
|
/// </summary>
|
||||||
|
public double PurityContemporary { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 光护盾
|
||||||
|
/// </summary>
|
||||||
|
public double Bright { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 影护盾
|
||||||
|
/// </summary>
|
||||||
|
public double Shadow { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 元素护盾
|
||||||
|
/// </summary>
|
||||||
|
public double Element { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 紫宛护盾
|
||||||
|
/// </summary>
|
||||||
|
public double Fleabane { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 时空护盾
|
||||||
|
/// </summary>
|
||||||
|
public double Particle { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总计物理护盾
|
||||||
|
/// </summary>
|
||||||
|
public double TotalPhysical => Physical;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总计魔法护盾
|
||||||
|
/// </summary>
|
||||||
|
public double TotalMagicial => None + Starmark + PurityNatural + PurityContemporary + Bright + Shadow + Element + Fleabane + Particle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置护盾值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isMagic"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public double this[bool isMagic = false, MagicType type = MagicType.None]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isMagic)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
MagicType.Starmark => Starmark,
|
||||||
|
MagicType.PurityNatural => PurityNatural,
|
||||||
|
MagicType.PurityContemporary => PurityContemporary,
|
||||||
|
MagicType.Bright => Bright,
|
||||||
|
MagicType.Shadow => Shadow,
|
||||||
|
MagicType.Element => Element,
|
||||||
|
MagicType.Fleabane => Fleabane,
|
||||||
|
MagicType.Particle => Particle,
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Physical;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (isMagic)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MagicType.Starmark:
|
||||||
|
Starmark = value;
|
||||||
|
break;
|
||||||
|
case MagicType.PurityNatural:
|
||||||
|
PurityNatural = value;
|
||||||
|
break;
|
||||||
|
case MagicType.PurityContemporary:
|
||||||
|
PurityContemporary = value;
|
||||||
|
break;
|
||||||
|
case MagicType.Bright:
|
||||||
|
Bright = value;
|
||||||
|
break;
|
||||||
|
case MagicType.Shadow:
|
||||||
|
Shadow = value;
|
||||||
|
break;
|
||||||
|
case MagicType.Element:
|
||||||
|
Element = value;
|
||||||
|
break;
|
||||||
|
case MagicType.Fleabane:
|
||||||
|
Fleabane = value;
|
||||||
|
break;
|
||||||
|
case MagicType.Particle:
|
||||||
|
Particle = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
None = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Physical = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 复制一个护盾对象
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Shield Copy()
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Physical = Physical,
|
||||||
|
None = None,
|
||||||
|
Starmark = Starmark,
|
||||||
|
PurityNatural = PurityNatural,
|
||||||
|
PurityContemporary = PurityContemporary,
|
||||||
|
Bright = Bright,
|
||||||
|
Shadow = Shadow,
|
||||||
|
Element = Element,
|
||||||
|
Fleabane = Fleabane,
|
||||||
|
Particle = Particle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
486
Entity/Character/Unit.cs
Normal file
486
Entity/Character/Unit.cs
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="Unit"/> 是一个用于描述生物的单元对象(单位),而 <see cref="Character"/> 是一种高级单位(单位单位/英雄单位)<para />
|
||||||
|
/// 和单位一样,使用 <see cref="InitRequired"/> 标记的需要初始赋值的属性<para />
|
||||||
|
/// </summary>
|
||||||
|
public class Unit : Character
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 单位名称
|
||||||
|
/// </summary>
|
||||||
|
public override string Name { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单位名称以及所属玩家
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
string str = Name;
|
||||||
|
if (User != null && User.Username != "")
|
||||||
|
{
|
||||||
|
str += "(" + User.Username + ")";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单位名称以及所属玩家,包含等级
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new string ToStringWithLevel()
|
||||||
|
{
|
||||||
|
string str = Name + " - 等级 " + Level;
|
||||||
|
if (User != null && User.Username != "")
|
||||||
|
{
|
||||||
|
str += "(" + User.Username + ")";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单位的详细信息
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new string GetInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine(showUser ? ToStringWithLevel() : ToStringWithLevelWithOutUser());
|
||||||
|
builder.AppendLine($"等级:{Level} / {GameplayEquilibriumConstant.MaxLevel}");
|
||||||
|
double exHP = ExHP + ExHP2 + ExHP3;
|
||||||
|
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : ""));
|
||||||
|
double exMP = ExMP + ExMP2 + ExMP3;
|
||||||
|
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
|
||||||
|
double exATK = ExATK + ExATK2 + ExATK3;
|
||||||
|
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
|
||||||
|
double exDEF = ExDEF + ExDEF2 + ExDEF3;
|
||||||
|
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
|
||||||
|
double mdf = Calculation.Round4Digits((MDF.None + MDF.Starmark + MDF.PurityNatural + MDF.PurityContemporary +
|
||||||
|
MDF.Bright + MDF.Shadow + MDF.Element + MDF.Fleabane + MDF.Particle) / 9) * 100;
|
||||||
|
if (Calculation.IsApproximatelyZero(mdf)) mdf = 0;
|
||||||
|
builder.AppendLine($"魔法抗性:{mdf:0.##}%(平均)");
|
||||||
|
double exSPD = AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD;
|
||||||
|
builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
|
||||||
|
builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
|
||||||
|
builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
|
||||||
|
builder.AppendLine($"暴击率:{CritRate * 100:0.##}%");
|
||||||
|
builder.AppendLine($"暴击伤害:{CritDMG * 100:0.##}%");
|
||||||
|
builder.AppendLine($"闪避率:{EvadeRate * 100:0.##}%");
|
||||||
|
builder.AppendLine($"冷却缩减:{CDR * 100:0.##}%");
|
||||||
|
builder.AppendLine($"加速系数:{AccelerationCoefficient * 100:0.##}%");
|
||||||
|
builder.AppendLine($"物理穿透:{PhysicalPenetration * 100:0.##}%");
|
||||||
|
builder.AppendLine($"魔法穿透:{MagicalPenetration * 100:0.##}%");
|
||||||
|
|
||||||
|
if (CharacterState != CharacterState.Actionable)
|
||||||
|
{
|
||||||
|
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsNeutral)
|
||||||
|
{
|
||||||
|
builder.AppendLine("单位是无敌的");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsUnselectable)
|
||||||
|
{
|
||||||
|
builder.AppendLine("单位是不可选中的");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine("== 普通攻击 ==");
|
||||||
|
builder.Append(NormalAttack.ToString());
|
||||||
|
|
||||||
|
if (Skills.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 单位技能 ==");
|
||||||
|
foreach (Skill skill in Skills)
|
||||||
|
{
|
||||||
|
builder.Append(skill.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EquipSlot.Any())
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 装备栏 ==");
|
||||||
|
if (EquipSlot.MagicCardPack != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.MagicCardPack.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.MagicCardPack) + ":" + EquipSlot.MagicCardPack.Name);
|
||||||
|
builder.AppendLine(EquipSlot.MagicCardPack.Description);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Weapon != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Weapon.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Weapon) + ":" + EquipSlot.Weapon.Name);
|
||||||
|
builder.AppendLine(EquipSlot.Weapon.Description);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Armor != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Armor.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Armor) + ":" + EquipSlot.Armor.Name);
|
||||||
|
builder.AppendLine(EquipSlot.Armor.Description);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Shoes != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Shoes.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Shoes) + ":" + EquipSlot.Shoes.Name);
|
||||||
|
builder.AppendLine(EquipSlot.Shoes.Description);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Accessory1 != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Accessory1.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Accessory1) + ":" + EquipSlot.Accessory1.Name);
|
||||||
|
builder.AppendLine(EquipSlot.Accessory1.Description);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Accessory2 != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Accessory2.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Accessory2) + ":" + EquipSlot.Accessory2.Name);
|
||||||
|
builder.AppendLine(EquipSlot.Accessory2.Description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Items.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 单位背包 ==");
|
||||||
|
foreach (Item item in Items)
|
||||||
|
{
|
||||||
|
builder.Append(item.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect[] effects = [.. Effects.Where(e => e.ShowInStatusBar)];
|
||||||
|
if (effects.Length > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 状态栏 ==");
|
||||||
|
foreach (Effect effect in effects)
|
||||||
|
{
|
||||||
|
builder.Append(effect.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 意义不明(✖)的代码
|
||||||
|
*/
|
||||||
|
if (showGrowth == showEXP)
|
||||||
|
{
|
||||||
|
showGrowth.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单位的简略信息
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new string GetSimpleInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showBasicOnly = false)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine(showUser ? ToStringWithLevel() : ToStringWithLevelWithOutUser());
|
||||||
|
builder.AppendLine($"等级:{Level} / {GameplayEquilibriumConstant.MaxLevel}");
|
||||||
|
double exHP = ExHP + ExHP2 + ExHP3;
|
||||||
|
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : ""));
|
||||||
|
double exMP = ExMP + ExMP2 + ExMP3;
|
||||||
|
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
|
||||||
|
double exATK = ExATK + ExATK2 + ExATK3;
|
||||||
|
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
|
||||||
|
double exDEF = ExDEF + ExDEF2 + ExDEF3;
|
||||||
|
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
|
||||||
|
double mdf = Calculation.Round4Digits((MDF.None + MDF.Starmark + MDF.PurityNatural + MDF.PurityContemporary +
|
||||||
|
MDF.Bright + MDF.Shadow + MDF.Element + MDF.Fleabane + MDF.Particle) / 9) * 100;
|
||||||
|
if (Calculation.IsApproximatelyZero(mdf)) mdf = 0;
|
||||||
|
builder.AppendLine($"魔法抗性:{mdf:0.##}%(平均)");
|
||||||
|
double exSPD = AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD;
|
||||||
|
builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
|
||||||
|
builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
|
||||||
|
builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
|
||||||
|
|
||||||
|
if (!showBasicOnly)
|
||||||
|
{
|
||||||
|
if (CharacterState != CharacterState.Actionable)
|
||||||
|
{
|
||||||
|
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsNeutral)
|
||||||
|
{
|
||||||
|
builder.AppendLine("单位是无敌的");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsUnselectable)
|
||||||
|
{
|
||||||
|
builder.AppendLine("单位是不可选中的");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Skills.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 单位技能 ==");
|
||||||
|
builder.AppendLine(string.Join(",", Skills.Select(s => s.Name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EquipSlot.Any())
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 已装备槽位 ==");
|
||||||
|
List<EquipSlotType> types = [];
|
||||||
|
if (EquipSlot.MagicCardPack != null)
|
||||||
|
{
|
||||||
|
types.Add(EquipSlotType.MagicCardPack);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Weapon != null)
|
||||||
|
{
|
||||||
|
types.Add(EquipSlotType.Weapon);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Armor != null)
|
||||||
|
{
|
||||||
|
types.Add(EquipSlotType.Armor);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Shoes != null)
|
||||||
|
{
|
||||||
|
types.Add(EquipSlotType.Shoes);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Accessory1 != null)
|
||||||
|
{
|
||||||
|
types.Add(EquipSlotType.Accessory1);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Accessory2 != null)
|
||||||
|
{
|
||||||
|
types.Add(EquipSlotType.Accessory2);
|
||||||
|
}
|
||||||
|
builder.AppendLine(string.Join(",", types.Select(ItemSet.GetEquipSlotTypeName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect[] effects = [.. Effects.Where(e => e.ShowInStatusBar)];
|
||||||
|
if (effects.Length > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 状态栏 ==");
|
||||||
|
builder.Append(string.Join(",", effects.Select(e => e.Name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 意义不明(✖)的代码
|
||||||
|
*/
|
||||||
|
if (showGrowth == showEXP)
|
||||||
|
{
|
||||||
|
showGrowth.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取战斗状态的信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hardnessTimes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new string GetInBattleInfo(double hardnessTimes)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine(ToStringWithLevel());
|
||||||
|
double exHP = ExHP + ExHP2 + ExHP3;
|
||||||
|
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : ""));
|
||||||
|
double exMP = ExMP + ExMP2 + ExMP3;
|
||||||
|
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
|
||||||
|
double exATK = ExATK + ExATK2 + ExATK3;
|
||||||
|
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
|
||||||
|
|
||||||
|
if (CharacterState != CharacterState.Actionable)
|
||||||
|
{
|
||||||
|
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsNeutral)
|
||||||
|
{
|
||||||
|
builder.AppendLine("单位是中立单位,处于无敌状态");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsUnselectable)
|
||||||
|
{
|
||||||
|
builder.AppendLine("单位是不可选中的");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine($"硬直时间:{hardnessTimes:0.##}");
|
||||||
|
|
||||||
|
Effect[] effects = [.. Effects.Where(e => e.ShowInStatusBar)];
|
||||||
|
if (effects.Length > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 状态栏 ==");
|
||||||
|
foreach (Effect effect in effects)
|
||||||
|
{
|
||||||
|
builder.Append(effect.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取战斗状态的信息(简略版)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hardnessTimes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new string GetSimpleInBattleInfo(double hardnessTimes)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine(ToStringWithLevel());
|
||||||
|
double exHP = ExHP + ExHP2 + ExHP3;
|
||||||
|
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : ""));
|
||||||
|
double exMP = ExMP + ExMP2 + ExMP3;
|
||||||
|
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
|
||||||
|
double exATK = ExATK + ExATK2 + ExATK3;
|
||||||
|
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
|
||||||
|
builder.AppendLine($"硬直时间:{hardnessTimes:0.##}");
|
||||||
|
|
||||||
|
Effect[] effects = [.. Effects.Where(e => e.ShowInStatusBar)];
|
||||||
|
if (effects.Length > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 状态栏 ==");
|
||||||
|
builder.Append(string.Join(",", effects.Select(e => e.Name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单位的技能信息
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new string GetSkillInfo(bool showUser = true)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine(showUser ? ToStringWithLevel() : ToStringWithLevelWithOutUser());
|
||||||
|
|
||||||
|
if (CharacterState != CharacterState.Actionable)
|
||||||
|
{
|
||||||
|
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsNeutral)
|
||||||
|
{
|
||||||
|
builder.AppendLine("单位是无敌的");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsUnselectable)
|
||||||
|
{
|
||||||
|
builder.AppendLine("单位是不可选中的");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine("== 普通攻击 ==");
|
||||||
|
builder.Append(NormalAttack.ToString());
|
||||||
|
|
||||||
|
if (Skills.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 单位技能 ==");
|
||||||
|
foreach (Skill skill in Skills)
|
||||||
|
{
|
||||||
|
builder.Append(skill.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect[] effects = [.. Effects.Where(e => e.ShowInStatusBar)];
|
||||||
|
if (effects.Length > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 状态栏 ==");
|
||||||
|
foreach (Effect effect in effects)
|
||||||
|
{
|
||||||
|
builder.Append(effect.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单位的物品信息
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new string GetItemInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine(showUser ? ToStringWithLevel() : ToStringWithLevelWithOutUser());
|
||||||
|
builder.AppendLine($"等级:{Level} / {GameplayEquilibriumConstant.MaxLevel}");
|
||||||
|
double exHP = ExHP + ExHP2 + ExHP3;
|
||||||
|
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : ""));
|
||||||
|
double exMP = ExMP + ExMP2 + ExMP3;
|
||||||
|
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
|
||||||
|
double exATK = ExATK + ExATK2 + ExATK3;
|
||||||
|
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
|
||||||
|
double exDEF = ExDEF + ExDEF2 + ExDEF3;
|
||||||
|
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
|
||||||
|
double mdf = Calculation.Round4Digits((MDF.None + MDF.Starmark + MDF.PurityNatural + MDF.PurityContemporary +
|
||||||
|
MDF.Bright + MDF.Shadow + MDF.Element + MDF.Fleabane + MDF.Particle) / 9) * 100;
|
||||||
|
if (Calculation.IsApproximatelyZero(mdf)) mdf = 0;
|
||||||
|
builder.AppendLine($"魔法抗性:{mdf:0.##}%(平均)");
|
||||||
|
double exSPD = AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD;
|
||||||
|
builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
|
||||||
|
builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
|
||||||
|
builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
|
||||||
|
builder.AppendLine($"暴击率:{CritRate * 100:0.##}%");
|
||||||
|
builder.AppendLine($"暴击伤害:{CritDMG * 100:0.##}%");
|
||||||
|
builder.AppendLine($"闪避率:{EvadeRate * 100:0.##}%");
|
||||||
|
builder.AppendLine($"冷却缩减:{CDR * 100:0.##}%");
|
||||||
|
builder.AppendLine($"加速系数:{AccelerationCoefficient * 100:0.##}%");
|
||||||
|
builder.AppendLine($"物理穿透:{PhysicalPenetration * 100:0.##}%");
|
||||||
|
builder.AppendLine($"魔法穿透:{MagicalPenetration * 100:0.##}%");
|
||||||
|
|
||||||
|
if (EquipSlot.Any())
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 装备栏 ==");
|
||||||
|
if (EquipSlot.MagicCardPack != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.MagicCardPack.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.MagicCardPack) + ":" + EquipSlot.MagicCardPack.Name);
|
||||||
|
builder.AppendLine(EquipSlot.MagicCardPack.Description);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Weapon != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Weapon.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Weapon) + ":" + EquipSlot.Weapon.Name);
|
||||||
|
builder.AppendLine(EquipSlot.Weapon.Description);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Armor != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Armor.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Armor) + ":" + EquipSlot.Armor.Name);
|
||||||
|
builder.AppendLine(EquipSlot.Armor.Description);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Shoes != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Shoes.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Shoes) + ":" + EquipSlot.Shoes.Name);
|
||||||
|
builder.AppendLine(EquipSlot.Shoes.Description);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Accessory1 != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Accessory1.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Accessory1) + ":" + EquipSlot.Accessory1.Name);
|
||||||
|
builder.AppendLine(EquipSlot.Accessory1.Description);
|
||||||
|
}
|
||||||
|
if (EquipSlot.Accessory2 != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Accessory2.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Accessory2) + ":" + EquipSlot.Accessory2.Name);
|
||||||
|
builder.AppendLine(EquipSlot.Accessory2.Description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Items.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 单位背包 ==");
|
||||||
|
foreach (Item item in Items)
|
||||||
|
{
|
||||||
|
builder.Append(item.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 意义不明(✖)的代码
|
||||||
|
*/
|
||||||
|
if (showGrowth == showEXP)
|
||||||
|
{
|
||||||
|
showGrowth.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Entity/Empty.cs
Normal file
10
Entity/Empty.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Empty
|
||||||
|
{
|
||||||
|
internal Empty()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
696
Entity/Item/Item.cs
Normal file
696
Entity/Item/Item.cs
Normal file
@ -0,0 +1,696 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 和 <see cref="Skill"/> 一样,需要继承构造
|
||||||
|
/// </summary>
|
||||||
|
public class Item : BaseEntity, IItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 唯一标识符
|
||||||
|
/// </summary>
|
||||||
|
public override Guid Guid { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物品的描述
|
||||||
|
/// </summary>
|
||||||
|
public virtual string Description { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物品的通用描述
|
||||||
|
/// </summary>
|
||||||
|
public virtual string GeneralDescription { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物品的背景故事
|
||||||
|
/// </summary>
|
||||||
|
public virtual string BackgroundStory { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物品类型
|
||||||
|
/// </summary>
|
||||||
|
public virtual ItemType ItemType { get; set; } = ItemType.Others;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否是装备
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEquipment
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ItemType switch
|
||||||
|
{
|
||||||
|
ItemType.MagicCardPack => true,
|
||||||
|
ItemType.Weapon => true,
|
||||||
|
ItemType.Armor => true,
|
||||||
|
ItemType.Shoes => true,
|
||||||
|
ItemType.Accessory => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 锁定后无法被出售和交易且不能被手动移出库存
|
||||||
|
/// </summary>
|
||||||
|
public bool IsLock { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否允许装备
|
||||||
|
/// [ 注意:这个不是用来判断是不是装备类型的,判断装备类型时,请判断他们的 <see cref="IsEquipment"/> ]
|
||||||
|
/// </summary>
|
||||||
|
public bool Equipable { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否允许取消装备
|
||||||
|
/// [ 注意:这个不是用来判断是不是装备类型的,判断装备类型时,使用 <see cref="IsEquipment"/> ]
|
||||||
|
/// </summary>
|
||||||
|
public bool Unequipable { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前装备的槽位
|
||||||
|
/// </summary>
|
||||||
|
public virtual EquipSlotType EquipSlotType { get; set; } = EquipSlotType.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 武器类型(如果是武器)
|
||||||
|
/// </summary>
|
||||||
|
public virtual WeaponType WeaponType { get; set; } = WeaponType.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 品质类型
|
||||||
|
/// </summary>
|
||||||
|
public virtual QualityType QualityType { get; set; } = QualityType.White;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 稀有度类型
|
||||||
|
/// </summary>
|
||||||
|
public virtual RarityType RarityType { get; set; } = RarityType.OneStar;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物品评级
|
||||||
|
/// </summary>
|
||||||
|
public virtual ItemRankType RankType { get; set; } = ItemRankType.D;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 快捷键
|
||||||
|
/// </summary>
|
||||||
|
public int Key { get; set; } = '/';
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否是主动物品
|
||||||
|
/// </summary>
|
||||||
|
public bool IsActive => Skills.Active != null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否可用(涉及冷却和禁用等)
|
||||||
|
/// </summary>
|
||||||
|
public bool Enable { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否是局内使用的物品(局内是指对角色生效的物品)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsInGameItem { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否允许购买
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPurchasable { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物品的价格
|
||||||
|
/// </summary>
|
||||||
|
public double Price { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否允许出售
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSellable { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下次可出售的时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime NextSellableTime { get; set; } = DateTime.MinValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否允许交易
|
||||||
|
/// </summary>
|
||||||
|
public bool IsTradable { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下次可交易的时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime NextTradableTime { get; set; } = DateTime.MinValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 剩余使用次数<para/>
|
||||||
|
/// 对于永久性主动物品而言,如果是 <see cref="IsInGameItem"/> 物品,可以不设置此值;反之,强烈建议设置为 1.
|
||||||
|
/// </summary>
|
||||||
|
public int RemainUseTimes { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用后减少使用次数
|
||||||
|
/// </summary>
|
||||||
|
public bool IsReduceTimesAfterUse { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用完后删除物品
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRemoveAfterUse { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物品所属的角色(只有装备物品,才需要设置)
|
||||||
|
/// </summary>
|
||||||
|
public Character? Character
|
||||||
|
{
|
||||||
|
get => _character;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_character = value;
|
||||||
|
if (Skills.Active != null) Skills.Active.Character = _character;
|
||||||
|
foreach (Skill skill in Skills.Passives)
|
||||||
|
{
|
||||||
|
skill.Character = _character;
|
||||||
|
foreach (Effect e in skill.Effects)
|
||||||
|
{
|
||||||
|
e.Source = _character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (Skill skill in Skills.Magics)
|
||||||
|
{
|
||||||
|
skill.Character = _character;
|
||||||
|
foreach (Effect e in skill.Effects)
|
||||||
|
{
|
||||||
|
e.Source = _character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 所属的玩家
|
||||||
|
/// </summary>
|
||||||
|
public User? User { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物品拥有的技能
|
||||||
|
/// </summary>
|
||||||
|
public SkillGroup Skills { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 其他内容
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, object> Others { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当装备物品时
|
||||||
|
/// </summary>
|
||||||
|
public void OnItemEquip(Character character, EquipSlotType type)
|
||||||
|
{
|
||||||
|
Character = character;
|
||||||
|
EquipSlotType = type;
|
||||||
|
foreach (Skill skill in Skills.Passives)
|
||||||
|
{
|
||||||
|
if (!skill.IsActive && skill.Level > 0)
|
||||||
|
{
|
||||||
|
foreach (Effect e in skill.AddPassiveEffectToCharacter())
|
||||||
|
{
|
||||||
|
e.GamingQueue = skill.GamingQueue;
|
||||||
|
if (Character != null && !Character.Effects.Contains(e))
|
||||||
|
{
|
||||||
|
Character.Effects.Add(e);
|
||||||
|
e.OnEffectGained(Character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (Skill skill in Skills.Magics)
|
||||||
|
{
|
||||||
|
if (Character != null && skill.IsMagic && skill.Level > 0)
|
||||||
|
{
|
||||||
|
Character.Skills.Add(skill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Character != null) OnItemEquipped(Character, this, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当取消装备物品时
|
||||||
|
/// </summary>
|
||||||
|
public void OnItemUnEquip(EquipSlotType type)
|
||||||
|
{
|
||||||
|
if (Character != null)
|
||||||
|
{
|
||||||
|
foreach (Skill skill in Skills.Passives)
|
||||||
|
{
|
||||||
|
List<Effect> effects = [.. Character.Effects.Where(e => e.Skill == skill && e.IsInEffect)];
|
||||||
|
foreach (Effect e in effects)
|
||||||
|
{
|
||||||
|
Character.Effects.Remove(e);
|
||||||
|
e.OnEffectLost(Character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (Skill skill in Skills.Magics)
|
||||||
|
{
|
||||||
|
Character.Skills.Remove(skill);
|
||||||
|
}
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case EquipSlotType.MagicCardPack:
|
||||||
|
Character.EquipSlot.MagicCardPack = null;
|
||||||
|
break;
|
||||||
|
case EquipSlotType.Weapon:
|
||||||
|
Character.EquipSlot.Weapon = null;
|
||||||
|
break;
|
||||||
|
case EquipSlotType.Armor:
|
||||||
|
Character.EquipSlot.Armor = null;
|
||||||
|
break;
|
||||||
|
case EquipSlotType.Shoes:
|
||||||
|
Character.EquipSlot.Shoes = null;
|
||||||
|
break;
|
||||||
|
case EquipSlotType.Accessory1:
|
||||||
|
Character.EquipSlot.Accessory1 = null;
|
||||||
|
break;
|
||||||
|
case EquipSlotType.Accessory2:
|
||||||
|
Character.EquipSlot.Accessory2 = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
OnItemUnEquipped(Character, this, type);
|
||||||
|
}
|
||||||
|
Character = null;
|
||||||
|
EquipSlotType = EquipSlotType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置游戏内的行动顺序表实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="queue"></param>
|
||||||
|
public void SetGamingQueue(IGamingQueue queue)
|
||||||
|
{
|
||||||
|
if (Skills.Active != null) Skills.Active.GamingQueue = queue;
|
||||||
|
foreach (Skill skill in Skills.Passives)
|
||||||
|
{
|
||||||
|
skill.GamingQueue = queue;
|
||||||
|
}
|
||||||
|
foreach (Skill skill in Skills.Magics)
|
||||||
|
{
|
||||||
|
skill.GamingQueue = queue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 局内使用物品触发
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> UseItem(IGamingQueue queue, Character character, List<Character> enemys, List<Character> teammates)
|
||||||
|
{
|
||||||
|
bool cancel = false;
|
||||||
|
bool used = false;
|
||||||
|
bool result = OnItemUsed(character, this, ref cancel);
|
||||||
|
if (cancel)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (result && Skills.Active != null)
|
||||||
|
{
|
||||||
|
used = await queue.UseItemAsync(this, character, enemys, teammates);
|
||||||
|
}
|
||||||
|
if (used)
|
||||||
|
{
|
||||||
|
EntityState = EntityState.Modified;
|
||||||
|
ReduceTimesAndRemove();
|
||||||
|
}
|
||||||
|
return result && used;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 局外(库存)使用物品触发
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool UseItem(Dictionary<string, object> args)
|
||||||
|
{
|
||||||
|
if (User != null)
|
||||||
|
{
|
||||||
|
bool result = OnItemUsed(args);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
EntityState = EntityState.Modified;
|
||||||
|
ReduceTimesAndRemove();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReduceTimesAndRemove()
|
||||||
|
{
|
||||||
|
if (User != null)
|
||||||
|
{
|
||||||
|
if (IsReduceTimesAfterUse)
|
||||||
|
{
|
||||||
|
RemainUseTimes--;
|
||||||
|
}
|
||||||
|
if (RemainUseTimes < 0) RemainUseTimes = 0;
|
||||||
|
if (IsRemoveAfterUse && RemainUseTimes == 0)
|
||||||
|
{
|
||||||
|
EntityState = EntityState.Deleted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当物品被角色使用时
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <param name="cancel"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected virtual bool OnItemUsed(Character character, Item item, ref bool cancel)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当物品被玩家使用时
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected virtual bool OnItemUsed(Dictionary<string, object> args)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当物品被装备时
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
protected virtual void OnItemEquipped(Character character, Item item, EquipSlotType type)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当物品被取消装备时
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
protected virtual void OnItemUnEquipped(Character character, Item item, EquipSlotType type)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Item(ItemType type, bool isInGame = true)
|
||||||
|
{
|
||||||
|
ItemType = type;
|
||||||
|
IsInGameItem = isInGame;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Item() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示物品的详细信息
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示物品的详细信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isShowGeneralDescription">是否显示通用描述,而不是描述</param>
|
||||||
|
/// <param name="isShowInStore">是否在商店中显示</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string ToString(bool isShowGeneralDescription, bool isShowInStore = false)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine($"【{Name}】");
|
||||||
|
|
||||||
|
string itemquality = ItemSet.GetQualityTypeName(QualityType);
|
||||||
|
string itemtype = ItemSet.GetItemTypeName(ItemType) + (ItemType == ItemType.Weapon && WeaponType != WeaponType.None ? "-" + ItemSet.GetWeaponTypeName(WeaponType) : "");
|
||||||
|
if (itemtype != "") itemtype = $" {itemtype}";
|
||||||
|
|
||||||
|
builder.AppendLine($"{itemquality + itemtype}");
|
||||||
|
|
||||||
|
if (isShowInStore && Price > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"售价:{Price} {GameplayEquilibriumConstant.InGameCurrency}");
|
||||||
|
}
|
||||||
|
else if (Price > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"回收价:{Price} {GameplayEquilibriumConstant.InGameCurrency}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RemainUseTimes > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"{(isShowInStore ? "" : "剩余")}可用次数:{RemainUseTimes}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isShowInStore)
|
||||||
|
{
|
||||||
|
if (IsSellable)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"购买此物品后可立即出售");
|
||||||
|
}
|
||||||
|
if (IsTradable)
|
||||||
|
{
|
||||||
|
DateTime date = DateTimeUtility.GetTradableTime();
|
||||||
|
builder.AppendLine($"购买此物品后将在 {date.ToString(General.GeneralDateTimeFormatChinese)} 后可交易");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<string> sellandtrade = [];
|
||||||
|
|
||||||
|
if (IsSellable)
|
||||||
|
{
|
||||||
|
sellandtrade.Add("可出售");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsSellable && NextSellableTime != DateTime.MinValue)
|
||||||
|
{
|
||||||
|
sellandtrade.Add($"此物品将在 {NextSellableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可出售");
|
||||||
|
}
|
||||||
|
else if (!IsSellable)
|
||||||
|
{
|
||||||
|
sellandtrade.Add("不可出售");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsTradable)
|
||||||
|
{
|
||||||
|
sellandtrade.Add("可交易");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsTradable && NextTradableTime != DateTime.MinValue)
|
||||||
|
{
|
||||||
|
sellandtrade.Add($"此物品将在 {NextTradableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可交易");
|
||||||
|
}
|
||||||
|
else if (!IsTradable)
|
||||||
|
{
|
||||||
|
sellandtrade.Add("不可交易");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sellandtrade.Count > 0) builder.AppendLine(string.Join(" ", sellandtrade).Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isShowGeneralDescription && GeneralDescription != "")
|
||||||
|
{
|
||||||
|
builder.AppendLine("物品描述:" + GeneralDescription);
|
||||||
|
}
|
||||||
|
else if (Description != "")
|
||||||
|
{
|
||||||
|
builder.AppendLine("物品描述:" + Description);
|
||||||
|
}
|
||||||
|
if (ItemType == ItemType.MagicCardPack && Skills.Magics.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 魔法卡 ==\r\n" + string.Join("\r\n", Skills.Magics.Select(m => m.ToString().Trim())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Skills.Active != null || Skills.Passives.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("== 物品技能 ==");
|
||||||
|
|
||||||
|
if (Skills.Active != null) builder.AppendLine($"{Skills.Active.ToString().Trim()}");
|
||||||
|
foreach (Skill skill in Skills.Passives)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"{skill.ToString().Trim()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BackgroundStory != "")
|
||||||
|
{
|
||||||
|
builder.AppendLine($"\"{BackgroundStory}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToStringInventory(bool showAll)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
if (showAll)
|
||||||
|
{
|
||||||
|
builder.Append($"{ToString()}");
|
||||||
|
if (IsEquipment && Character != null) builder.AppendLine($"装备于:{Character.ToStringWithLevelWithOutUser()}");
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<string> sellandtrade = [];
|
||||||
|
|
||||||
|
if (!IsSellable && NextSellableTime != DateTime.MinValue)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"此物品将在 {NextSellableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可出售");
|
||||||
|
}
|
||||||
|
else if (!IsSellable)
|
||||||
|
{
|
||||||
|
sellandtrade.Add("不可出售");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsTradable && NextTradableTime != DateTime.MinValue)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"此物品将在 {NextTradableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可交易");
|
||||||
|
}
|
||||||
|
else if (!IsTradable)
|
||||||
|
{
|
||||||
|
sellandtrade.Add("不可交易");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sellandtrade.Count > 0) builder.AppendLine(string.Join(" ", sellandtrade).Trim());
|
||||||
|
if (Description != "") builder.AppendLine($"{Description}");
|
||||||
|
if (IsEquipment && Character != null) builder.AppendLine($"装备于:{Character.ToStringWithLevelWithOutUser()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断两个物品是否相同 检查Id.Name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is Item c && c.Id + "." + c.Name == Id + "." + Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置一些属性给从工厂构造出来的 <paramref name="newbyFactory"/> 对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newbyFactory"></param>
|
||||||
|
public void SetPropertyToItemModuleNew(Item newbyFactory)
|
||||||
|
{
|
||||||
|
newbyFactory.IsLock = IsLock;
|
||||||
|
newbyFactory.WeaponType = WeaponType;
|
||||||
|
newbyFactory.EquipSlotType = EquipSlotType;
|
||||||
|
newbyFactory.Equipable = Equipable;
|
||||||
|
newbyFactory.Unequipable = Unequipable;
|
||||||
|
newbyFactory.IsPurchasable = IsPurchasable;
|
||||||
|
newbyFactory.Price = Price;
|
||||||
|
newbyFactory.IsSellable = IsSellable;
|
||||||
|
newbyFactory.NextSellableTime = NextSellableTime;
|
||||||
|
newbyFactory.IsTradable = IsTradable;
|
||||||
|
newbyFactory.NextTradableTime = NextTradableTime;
|
||||||
|
newbyFactory.RemainUseTimes = RemainUseTimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 复制一个物品
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Item Copy(bool copyLevel = false, bool copyGuid = false, bool copyProperty = true, IEnumerable<Item>? itemsDefined = null, IEnumerable<Skill>? skillsDefined = null)
|
||||||
|
{
|
||||||
|
Item item = Factory.OpenFactory.GetInstance<Item>(Id, Name, []);
|
||||||
|
Item? itemDefined = null;
|
||||||
|
if (itemsDefined != null && itemsDefined.FirstOrDefault(i => i.GetIdName() == item.GetIdName()) is Item temp)
|
||||||
|
{
|
||||||
|
itemDefined = temp;
|
||||||
|
}
|
||||||
|
if (copyProperty) SetPropertyToItemModuleNew(item);
|
||||||
|
if (copyGuid) item.Guid = Guid;
|
||||||
|
itemDefined ??= this;
|
||||||
|
item.Id = itemDefined.Id;
|
||||||
|
item.Name = itemDefined.Name;
|
||||||
|
item.Description = itemDefined.Description;
|
||||||
|
item.GeneralDescription = itemDefined.GeneralDescription;
|
||||||
|
item.BackgroundStory = itemDefined.BackgroundStory;
|
||||||
|
item.ItemType = itemDefined.ItemType;
|
||||||
|
item.QualityType = itemDefined.QualityType;
|
||||||
|
item.RarityType = itemDefined.RarityType;
|
||||||
|
item.RankType = itemDefined.RankType;
|
||||||
|
item.Key = itemDefined.Key;
|
||||||
|
item.Enable = itemDefined.Enable;
|
||||||
|
item.IsInGameItem = itemDefined.IsInGameItem;
|
||||||
|
if (item is OpenItem)
|
||||||
|
{
|
||||||
|
item.Skills.Active = itemDefined.Skills.Active?.Copy(true, skillsDefined);
|
||||||
|
if (item.Skills.Active != null)
|
||||||
|
{
|
||||||
|
item.Skills.Active.Level = copyLevel ? (itemDefined.Skills.Active?.Level ?? 1) : 1;
|
||||||
|
item.Skills.Active.Guid = item.Guid;
|
||||||
|
}
|
||||||
|
foreach (Skill skill in itemDefined.Skills.Passives)
|
||||||
|
{
|
||||||
|
Skill newskill = skill.Copy(true, skillsDefined);
|
||||||
|
newskill.Item = item;
|
||||||
|
newskill.Level = copyLevel ? skill.Level : 1;
|
||||||
|
newskill.Guid = item.Guid;
|
||||||
|
item.Skills.Passives.Add(newskill);
|
||||||
|
}
|
||||||
|
foreach (Skill skill in itemDefined.Skills.Magics)
|
||||||
|
{
|
||||||
|
Skill newskill = skill.Copy(true, skillsDefined);
|
||||||
|
newskill.Item = item;
|
||||||
|
newskill.Level = copyLevel ? skill.Level : 1;
|
||||||
|
newskill.Guid = item.Guid;
|
||||||
|
item.Skills.Magics.Add(newskill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置所有技能的等级
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level"></param>
|
||||||
|
public void SetLevel(int level)
|
||||||
|
{
|
||||||
|
if (Skills.Active != null)
|
||||||
|
{
|
||||||
|
Skills.Active.Level = level;
|
||||||
|
}
|
||||||
|
foreach (Skill skill in Skills.Passives)
|
||||||
|
{
|
||||||
|
skill.Level = level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置所有魔法的等级
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level"></param>
|
||||||
|
public void SetMagicsLevel(int level)
|
||||||
|
{
|
||||||
|
foreach (Skill skill in Skills.Magics)
|
||||||
|
{
|
||||||
|
skill.Level = level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 所属的角色
|
||||||
|
/// </summary>
|
||||||
|
private Character? _character = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Entity/Item/OpenItem.cs
Normal file
21
Entity/Item/OpenItem.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 用于动态扩展物品
|
||||||
|
/// </summary>
|
||||||
|
public class OpenItem : Item
|
||||||
|
{
|
||||||
|
public override long Id { get; set; }
|
||||||
|
public override string Name { get; set; }
|
||||||
|
|
||||||
|
public OpenItem(long id, string name, Dictionary<string, object> args)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
foreach (string key in args.Keys)
|
||||||
|
{
|
||||||
|
Others[key] = args[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Entity/Item/SkillGroup.cs
Normal file
24
Entity/Item/SkillGroup.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 物品只有一个主动技能,但是可以有很多个被动技能
|
||||||
|
/// <para>魔法卡包具有很多个魔法技能</para>
|
||||||
|
/// </summary>
|
||||||
|
public class SkillGroup
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 唯一的主动技能
|
||||||
|
/// </summary>
|
||||||
|
public Skill? Active { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被动技能组
|
||||||
|
/// </summary>
|
||||||
|
public HashSet<Skill> Passives { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 魔法技能组
|
||||||
|
/// </summary>
|
||||||
|
public HashSet<Skill> Magics { get; set; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
1073
Entity/Skill/Effect.cs
Normal file
1073
Entity/Skill/Effect.cs
Normal file
File diff suppressed because it is too large
Load Diff
247
Entity/Skill/NormalAttack.cs
Normal file
247
Entity/Skill/NormalAttack.cs
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class NormalAttack(Character character, bool isMagic = false, MagicType magicType = MagicType.None) : BaseEntity, ISkill
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 普通攻击名称
|
||||||
|
/// </summary>
|
||||||
|
public override string Name => "普通攻击";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 普通攻击说明
|
||||||
|
/// </summary>
|
||||||
|
public string Description => $"对目标敌人造成 {(1.0 + 0.05 * (Level - 1)) * 100:0.##}% 攻击力 [ {Damage:0.##} ] 点{(IsMagic ? CharacterSet.GetMagicDamageName(MagicType) : "物理伤害")}。";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 普通攻击的通用说明
|
||||||
|
/// </summary>
|
||||||
|
public string GeneralDescription => $"对目标敌人造成基于 100(+5/Lv)% 攻击力的{(IsMagic ? CharacterSet.GetMagicDamageName(MagicType) : "物理伤害")}。";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 所属的角色
|
||||||
|
/// </summary>
|
||||||
|
public Character Character { get; } = character;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 普通攻击的伤害
|
||||||
|
/// </summary>
|
||||||
|
public double Damage => Character.ATK * (1.0 + 0.05 * (Level - 1));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 普通攻击等级
|
||||||
|
/// </summary>
|
||||||
|
public int Level
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Math.Max(1, _Level);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_Level = Math.Min(Math.Max(1, value), GameplayEquilibriumConstant.MaxNormalAttackLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否是魔法伤害
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMagic => _IsMagic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 魔法伤害需要指定魔法类型
|
||||||
|
/// </summary>
|
||||||
|
public MagicType MagicType => _MagicType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否可用
|
||||||
|
/// </summary>
|
||||||
|
public bool Enable { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否在持续生效,为 true 时不允许再次使用。普通攻击始终为 false
|
||||||
|
/// </summary>
|
||||||
|
public bool IsInEffect => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 无视免疫类型
|
||||||
|
/// </summary>
|
||||||
|
public ImmuneType IgnoreImmune { get; set; } = ImmuneType.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 硬直时间
|
||||||
|
/// </summary>
|
||||||
|
public double HardnessTime { get; set; } = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实际硬直时间
|
||||||
|
/// </summary>
|
||||||
|
public double RealHardnessTime => Math.Max(0, HardnessTime * (1 - Calculation.PercentageCheck(Character?.ActionCoefficient ?? 0)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可选取自身
|
||||||
|
/// </summary>
|
||||||
|
public bool CanSelectSelf { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可选取敌对角色
|
||||||
|
/// </summary>
|
||||||
|
public bool CanSelectEnemy { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可选取友方角色
|
||||||
|
/// </summary>
|
||||||
|
public bool CanSelectTeammate { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可选取的作用目标数量
|
||||||
|
/// </summary>
|
||||||
|
public int CanSelectTargetCount { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可选取的作用范围
|
||||||
|
/// </summary>
|
||||||
|
public double CanSelectTargetRange { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 普通攻击没有魔法消耗
|
||||||
|
/// </summary>
|
||||||
|
public double RealMPCost => 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 普通攻击没有吟唱时间
|
||||||
|
/// </summary>
|
||||||
|
public double RealCastTime => 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 普通攻击没有能量消耗
|
||||||
|
/// </summary>
|
||||||
|
public double RealEPCost => 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 普通攻击没有冷却时间
|
||||||
|
/// </summary>
|
||||||
|
public double RealCD => 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 普通攻击没有冷却时间
|
||||||
|
/// </summary>
|
||||||
|
public double CurrentCD => 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取可选择的目标列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<Character> GetSelectableTargets(Character caster, List<Character> enemys, List<Character> teammates)
|
||||||
|
{
|
||||||
|
List<Character> selectable = [];
|
||||||
|
|
||||||
|
if (CanSelectSelf)
|
||||||
|
{
|
||||||
|
selectable.Add(caster);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CanSelectEnemy)
|
||||||
|
{
|
||||||
|
selectable.AddRange(enemys);
|
||||||
|
}
|
||||||
|
if (CanSelectTeammate)
|
||||||
|
{
|
||||||
|
selectable.AddRange(teammates);
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对目标(或多个目标)发起普通攻击
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="queue"></param>
|
||||||
|
/// <param name="attacker"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
public void Attack(IGamingQueue queue, Character attacker, params IEnumerable<Character> enemys)
|
||||||
|
{
|
||||||
|
if (!Enable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (Character enemy in enemys)
|
||||||
|
{
|
||||||
|
if (enemy.HP > 0)
|
||||||
|
{
|
||||||
|
queue.WriteLine("[ " + Character + $" ] 对 [ {enemy} ] 发起了普通攻击!");
|
||||||
|
double expected = Damage;
|
||||||
|
int changeCount = 0;
|
||||||
|
DamageResult result = IsMagic ? queue.CalculateMagicalDamage(attacker, enemy, true, MagicType, expected, out double damage, ref changeCount) : queue.CalculatePhysicalDamage(attacker, enemy, true, expected, out damage, ref changeCount);
|
||||||
|
queue.DamageToEnemyAsync(attacker, enemy, damage, true, IsMagic, MagicType, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改伤害类型
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isMagic"></param>
|
||||||
|
/// <param name="magicType"></param>
|
||||||
|
public void SetMagicType(bool isMagic, MagicType magicType)
|
||||||
|
{
|
||||||
|
_IsMagic = isMagic;
|
||||||
|
_MagicType = magicType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 比较两个普攻对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is NormalAttack c && c.Name == Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="showOriginal"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetInfo(bool showOriginal = false)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine($"{Name} - 等级 {Level}");
|
||||||
|
builder.AppendLine($"描述:{Description}");
|
||||||
|
builder.AppendLine($"硬直时间:{RealHardnessTime:0.##}{(showOriginal && RealHardnessTime != HardnessTime ? $"(原始值:{HardnessTime})" : "")}");
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出信息
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string ToString() => GetInfo(true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 等级
|
||||||
|
/// </summary>
|
||||||
|
private int _Level = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否是魔法伤害
|
||||||
|
/// </summary>
|
||||||
|
private bool _IsMagic = isMagic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 魔法类型
|
||||||
|
/// </summary>
|
||||||
|
private MagicType _MagicType = magicType;
|
||||||
|
}
|
||||||
|
}
|
||||||
112
Entity/Skill/OpenSkill.cs
Normal file
112
Entity/Skill/OpenSkill.cs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 用于动态扩展技能,<see cref="Description"/> 返回所有特效的描述
|
||||||
|
/// </summary>
|
||||||
|
public class OpenSkill : Skill
|
||||||
|
{
|
||||||
|
public override long Id { get; set; }
|
||||||
|
public override string Name { get; set; }
|
||||||
|
public override string Description => string.Join("\r\n", Effects.Select(e => e.Description));
|
||||||
|
|
||||||
|
public OpenSkill(long id, string name, Dictionary<string, object> args, Character? character = null) : base(SkillType.Passive, character)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
foreach (string str in args.Keys)
|
||||||
|
{
|
||||||
|
Values[str] = args[str];
|
||||||
|
switch (str.ToLower())
|
||||||
|
{
|
||||||
|
case "active":
|
||||||
|
if (bool.TryParse(args[str].ToString(), out bool isActive) && isActive)
|
||||||
|
{
|
||||||
|
SkillType = SkillType.Item;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "self":
|
||||||
|
if (bool.TryParse(args[str].ToString(), out bool self))
|
||||||
|
{
|
||||||
|
CanSelectSelf = self;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "enemy":
|
||||||
|
if (bool.TryParse(args[str].ToString(), out bool enemy))
|
||||||
|
{
|
||||||
|
CanSelectEnemy = enemy;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "teammate":
|
||||||
|
if (bool.TryParse(args[str].ToString(), out bool teammate))
|
||||||
|
{
|
||||||
|
CanSelectTeammate = teammate;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "count":
|
||||||
|
if (int.TryParse(args[str].ToString(), out int count) && count > 0)
|
||||||
|
{
|
||||||
|
CanSelectTargetCount = count;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "range":
|
||||||
|
if (int.TryParse(args[str].ToString(), out int range) && range > 0)
|
||||||
|
{
|
||||||
|
CanSelectTargetRange = range;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "mpcost":
|
||||||
|
if (double.TryParse(args[str].ToString(), out double mpcost) && mpcost > 0)
|
||||||
|
{
|
||||||
|
MPCost = mpcost;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "epcost":
|
||||||
|
if (double.TryParse(args[str].ToString(), out double epcost) && epcost > 0)
|
||||||
|
{
|
||||||
|
EPCost = epcost;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "costall":
|
||||||
|
if (bool.TryParse(args[str].ToString(), out bool costall) && costall)
|
||||||
|
{
|
||||||
|
CostAllEP = costall;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "mincost":
|
||||||
|
if (double.TryParse(args[str].ToString(), out double mincost) && mincost > 0)
|
||||||
|
{
|
||||||
|
MinCostEP = mincost;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "cd":
|
||||||
|
if (double.TryParse(args[str].ToString(), out double cd) && cd > 0)
|
||||||
|
{
|
||||||
|
CD = cd;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "cast":
|
||||||
|
if (double.TryParse(args[str].ToString(), out double cast) && cast > 0)
|
||||||
|
{
|
||||||
|
CastTime = cast;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ht":
|
||||||
|
if (double.TryParse(args[str].ToString(), out double ht) && ht > 0)
|
||||||
|
{
|
||||||
|
HardnessTime = ht;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<Effect> AddPassiveEffectToCharacter()
|
||||||
|
{
|
||||||
|
return Effects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
564
Entity/Skill/Skill.cs
Normal file
564
Entity/Skill/Skill.cs
Normal file
@ -0,0 +1,564 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 与 <see cref="Character"/> 不同,构造技能时,建议继承此类再构造
|
||||||
|
/// </summary>
|
||||||
|
public class Skill : BaseEntity, ISkill, IActiveEnable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 唯一标识符 [ 只有物品技能需要赋值,用于表示与其关联的物品:<see cref="Item.Guid"/> ]
|
||||||
|
/// <para>其他情况请保持此属性为 <see cref="Guid.Empty"/></para>
|
||||||
|
/// </summary>
|
||||||
|
public override Guid Guid { get; set; } = Guid.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 此技能所属的角色
|
||||||
|
/// </summary>
|
||||||
|
public Character? Character { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能描述
|
||||||
|
/// </summary>
|
||||||
|
public virtual string Description { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能的通用描述
|
||||||
|
/// </summary>
|
||||||
|
public virtual string GeneralDescription { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 驱散性和被驱散性的描述
|
||||||
|
/// </summary>
|
||||||
|
public virtual string DispelDescription { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 释放技能时的口号
|
||||||
|
/// </summary>
|
||||||
|
public virtual string Slogan { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能等级,等于 0 时可以称之为尚未学习
|
||||||
|
/// </summary>
|
||||||
|
public int Level
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Math.Max(0, _Level);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
int max = SkillSet.GetSkillMaxLevel(SkillType, GameplayEquilibriumConstant);
|
||||||
|
_Level = Math.Min(Math.Max(0, value), max);
|
||||||
|
OnLevelUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能类型 [ 此项为最高优先级 ]
|
||||||
|
/// </summary>
|
||||||
|
[InitRequired]
|
||||||
|
public SkillType SkillType { get; set; } = SkillType.Passive;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否是主动技能 [ 此项为高优先级 ]
|
||||||
|
/// </summary>
|
||||||
|
[InitRequired]
|
||||||
|
public bool IsActive => SkillType != SkillType.Passive;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否可用 [ 此项为高优先级 ]
|
||||||
|
/// </summary>
|
||||||
|
public bool Enable { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 效果持续生效中 [ 此项为高优先级 ] [ 此项设置为true后不允许再次释放,防止重复释放 ]
|
||||||
|
/// </summary>
|
||||||
|
public bool IsInEffect { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否是爆发技 [ 此项为高优先级 ]
|
||||||
|
/// </summary>
|
||||||
|
[InitRequired]
|
||||||
|
public bool IsSuperSkill => SkillType == SkillType.SuperSkill;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否属于魔法 [ <see cref="IsActive"/> 必须为 true ],反之为战技
|
||||||
|
/// </summary>
|
||||||
|
[InitRequired]
|
||||||
|
public bool IsMagic => SkillType == SkillType.Magic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可选取自身
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool CanSelectSelf { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可选取敌对角色
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool CanSelectEnemy { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可选取友方角色
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool CanSelectTeammate { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可选取的作用目标数量
|
||||||
|
/// </summary>
|
||||||
|
public virtual int CanSelectTargetCount { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可选取的作用范围
|
||||||
|
/// </summary>
|
||||||
|
public virtual double CanSelectTargetRange { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选取角色的条件
|
||||||
|
/// </summary>
|
||||||
|
public List<Func<Character, bool>> SelectTargetPredicates { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实际魔法消耗 [ 魔法 ]
|
||||||
|
/// </summary>
|
||||||
|
public double RealMPCost => Math.Max(0, MPCost * (1 - Calculation.PercentageCheck((Character?.INT ?? 0) * GameplayEquilibriumConstant.INTtoCastMPReduce)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 魔法消耗 [ 魔法 ]
|
||||||
|
/// </summary>
|
||||||
|
[InitOptional]
|
||||||
|
public virtual double MPCost { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 吟唱时间 [ 魔法 ]
|
||||||
|
/// </summary>
|
||||||
|
[InitOptional]
|
||||||
|
public virtual double CastTime { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实际吟唱时间 [ 魔法 ]
|
||||||
|
/// </summary>
|
||||||
|
public double RealCastTime => Math.Max(0, CastTime * (1 - Calculation.PercentageCheck(Character?.AccelerationCoefficient ?? 0)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实际能量消耗 [ 战技 ]
|
||||||
|
/// </summary>
|
||||||
|
public double RealEPCost => CostAllEP ? Math.Max(MinCostEP, Character?.EP ?? MinCostEP) : (IsSuperSkill ? EPCost : Math.Max(0, EPCost * (1 - Calculation.PercentageCheck((Character?.INT ?? 0) * GameplayEquilibriumConstant.INTtoCastEPReduce))));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 能量消耗 [ 战技 ]
|
||||||
|
/// </summary>
|
||||||
|
[InitOptional]
|
||||||
|
public virtual double EPCost { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消耗所有能量 [ 战技 ]
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool CostAllEP { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消耗所有能量的最小能量限制 [ 战技 ] 默认值:100
|
||||||
|
/// </summary>
|
||||||
|
public virtual double MinCostEP { get; set; } = 100;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上一次释放此技能消耗的魔法 [ 魔法 ]
|
||||||
|
/// </summary>
|
||||||
|
public double LastCostMP { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上一次释放此技能消耗的能量 [ 战技 ]
|
||||||
|
/// </summary>
|
||||||
|
public double LastCostEP { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实际冷却时间
|
||||||
|
/// </summary>
|
||||||
|
public double RealCD => Math.Max(0, CD * (1 - (Character?.CDR ?? 0)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 冷却时间
|
||||||
|
/// </summary>
|
||||||
|
[InitRequired]
|
||||||
|
public virtual double CD { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 剩余冷却时间 [ 建议配合 <see cref="Enable"/> 属性使用 ]
|
||||||
|
/// </summary>
|
||||||
|
public double CurrentCD { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 硬直时间
|
||||||
|
/// </summary>
|
||||||
|
[InitRequired]
|
||||||
|
public virtual double HardnessTime { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实际硬直时间
|
||||||
|
/// </summary>
|
||||||
|
public double RealHardnessTime => Math.Max(0, HardnessTime * (1 - Calculation.PercentageCheck(Character?.ActionCoefficient ?? 0)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 效果列表
|
||||||
|
/// </summary>
|
||||||
|
public HashSet<Effect> Effects { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于动态扩展技能的参数
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, object> Values { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 游戏中的行动顺序表实例,在技能效果被触发时,此实例会获得赋值,使用时需要判断其是否存在
|
||||||
|
/// </summary>
|
||||||
|
public IGamingQueue? GamingQueue { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能是否属于某个物品
|
||||||
|
/// </summary>
|
||||||
|
public Item? Item { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 继承此类实现时,调用基类的构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
protected Skill(SkillType type, Character? character = null)
|
||||||
|
{
|
||||||
|
SkillType = type;
|
||||||
|
Character = character;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于构造 JSON
|
||||||
|
/// </summary>
|
||||||
|
internal Skill() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置一些属性给从工厂构造出来的 <paramref name="newbyFactory"/> 对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newbyFactory"></param>
|
||||||
|
public void SetPropertyToItemModuleNew(Skill newbyFactory)
|
||||||
|
{
|
||||||
|
newbyFactory.GamingQueue = GamingQueue;
|
||||||
|
newbyFactory.Enable = Enable;
|
||||||
|
newbyFactory.IsInEffect = IsInEffect;
|
||||||
|
newbyFactory.CurrentCD = CurrentCD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 触发技能升级
|
||||||
|
/// </summary>
|
||||||
|
public void OnLevelUp()
|
||||||
|
{
|
||||||
|
if (!IsActive && Level > 0)
|
||||||
|
{
|
||||||
|
foreach (Effect e in AddPassiveEffectToCharacter())
|
||||||
|
{
|
||||||
|
e.GamingQueue = GamingQueue;
|
||||||
|
if (Character != null && !Character.Effects.Contains(e))
|
||||||
|
{
|
||||||
|
Character.Effects.Add(e);
|
||||||
|
e.OnEffectGained(Character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当获得技能时
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="queue"></param>
|
||||||
|
public void OnSkillGained(IGamingQueue queue)
|
||||||
|
{
|
||||||
|
GamingQueue = queue;
|
||||||
|
OnLevelUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在技能持有者的回合开始前
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <param name="skills"></param>
|
||||||
|
/// <param name="items"></param>
|
||||||
|
public virtual void OnTurnStart(Character character, List<Character> enemys, List<Character> teammates, List<Skill> skills, List<Item> items)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取可选择的目标列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual List<Character> GetSelectableTargets(Character caster, List<Character> enemys, List<Character> teammates)
|
||||||
|
{
|
||||||
|
List<Character> selectable = [];
|
||||||
|
|
||||||
|
if (CanSelectSelf)
|
||||||
|
{
|
||||||
|
selectable.Add(caster);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CanSelectEnemy)
|
||||||
|
{
|
||||||
|
selectable.AddRange(enemys);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CanSelectTeammate)
|
||||||
|
{
|
||||||
|
selectable.AddRange(teammates);
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选取技能目标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual List<Character> SelectTargets(Character caster, List<Character> enemys, List<Character> teammates)
|
||||||
|
{
|
||||||
|
List<Character> tobeSelected = GetSelectableTargets(caster, enemys, teammates);
|
||||||
|
|
||||||
|
// 筛选出符合条件的角色
|
||||||
|
tobeSelected = [.. tobeSelected.Where(c => SelectTargetPredicates.All(f => f(c)))];
|
||||||
|
|
||||||
|
List<Character> targets = [];
|
||||||
|
|
||||||
|
if (tobeSelected.Count <= CanSelectTargetCount)
|
||||||
|
{
|
||||||
|
targets.AddRange(tobeSelected);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targets.AddRange(tobeSelected.OrderBy(x => Random.Shared.Next()).Take(CanSelectTargetCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.. targets.Distinct()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能开始吟唱时 [ 吟唱魔法、释放战技和爆发技、预释放爆发技均可触发 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="queue"></param>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="targets"></param>
|
||||||
|
public void OnSkillCasting(IGamingQueue queue, Character caster, List<Character> targets)
|
||||||
|
{
|
||||||
|
GamingQueue = queue;
|
||||||
|
foreach (Effect e in Effects)
|
||||||
|
{
|
||||||
|
e.GamingQueue = GamingQueue;
|
||||||
|
e.OnSkillCasting(caster, targets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能效果触发前
|
||||||
|
/// </summary>
|
||||||
|
public void BeforeSkillCasted()
|
||||||
|
{
|
||||||
|
LastCostMP = RealMPCost;
|
||||||
|
LastCostEP = RealEPCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 触发技能效果 [ 局内版 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="queue"></param>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="targets"></param>
|
||||||
|
public void OnSkillCasted(IGamingQueue queue, Character caster, List<Character> targets)
|
||||||
|
{
|
||||||
|
GamingQueue = queue;
|
||||||
|
foreach (Effect e in Effects)
|
||||||
|
{
|
||||||
|
e.GamingQueue = GamingQueue;
|
||||||
|
e.OnSkillCasted(caster, targets, Values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对目标触发技能效果
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targets"></param>
|
||||||
|
public void OnSkillCasted(List<Character> targets)
|
||||||
|
{
|
||||||
|
foreach (Effect e in Effects)
|
||||||
|
{
|
||||||
|
e.OnSkillCasted(targets, Values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查角色是否在 AI 控制状态
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool IsCharacterInAIControlling(Character character)
|
||||||
|
{
|
||||||
|
return GamingQueue?.IsCharacterInAIControlling(character) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被动技能,需要重写此方法,返回被动特效给角色 [ 此方法会在技能学习时触发 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual IEnumerable<Effect> AddPassiveEffectToCharacter()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 返回技能的详细说明
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="showOriginal"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetInfo(bool showOriginal = false)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
string type = IsSuperSkill ? "【爆发技】" : (IsMagic ? "【魔法】" : (IsActive ? "【主动】" : "【被动】"));
|
||||||
|
string level = Level > 0 ? " - 等级 " + Level : " - 尚未学习";
|
||||||
|
builder.AppendLine(type + Name + level);
|
||||||
|
builder.AppendLine("技能描述:" + (Level == 0 && GeneralDescription.Trim() != "" ? GeneralDescription : Description));
|
||||||
|
if (CurrentCD > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"正在冷却:剩余 {CurrentCD:0.##} {GameplayEquilibriumConstant.InGameTime}");
|
||||||
|
}
|
||||||
|
if (!Enable)
|
||||||
|
{
|
||||||
|
builder.AppendLine("技能当前不可用");
|
||||||
|
}
|
||||||
|
if (IsInEffect)
|
||||||
|
{
|
||||||
|
builder.AppendLine("效果结束前不可用");
|
||||||
|
}
|
||||||
|
if (DispelDescription != "")
|
||||||
|
{
|
||||||
|
builder.AppendLine($"{DispelDescription}");
|
||||||
|
}
|
||||||
|
if (IsActive && (Item?.IsInGameItem ?? true))
|
||||||
|
{
|
||||||
|
if (SkillType == SkillType.Item)
|
||||||
|
{
|
||||||
|
if (RealMPCost > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"魔法消耗:{RealMPCost:0.##}{(showOriginal && RealMPCost != MPCost ? $"(原始值:{MPCost})" : "")}");
|
||||||
|
}
|
||||||
|
if (RealEPCost > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"(原始值:{EPCost})" : "")}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (IsSuperSkill)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"(原始值:{EPCost})" : "")}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (IsMagic)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"魔法消耗:{RealMPCost:0.##}{(showOriginal && RealMPCost != MPCost ? $"(原始值:{MPCost})" : "")}");
|
||||||
|
builder.AppendLine($"吟唱时间:{RealCastTime:0.##}{(showOriginal && RealCastTime != CastTime ? $"(原始值:{CastTime})" : "")}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"(原始值:{EPCost})" : "")}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.AppendLine($"冷却时间:{RealCD:0.##}{(showOriginal && RealCD != CD ? $"(原始值:{CD})" : "")}");
|
||||||
|
builder.AppendLine($"硬直时间:{RealHardnessTime:0.##}{(showOriginal && RealHardnessTime != HardnessTime ? $"(原始值:{HardnessTime})" : "")}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 返回技能的详细说明
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string ToString() => GetInfo(true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断两个技能是否相同 检查Id.Name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is Skill c && c.Id + "." + c.Name == Id + "." + Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 复制一个技能
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Skill Copy(bool copyProperty = true, IEnumerable<Skill>? skillsDefined = null)
|
||||||
|
{
|
||||||
|
Dictionary<string, object> args = new()
|
||||||
|
{
|
||||||
|
{ "values", Values }
|
||||||
|
};
|
||||||
|
Skill? skillDefined = null;
|
||||||
|
if (skillsDefined != null && skillsDefined.FirstOrDefault(i => i.GetIdName() == GetIdName()) is Skill temp)
|
||||||
|
{
|
||||||
|
skillDefined = temp;
|
||||||
|
}
|
||||||
|
if (skillDefined != null)
|
||||||
|
{
|
||||||
|
args["values"] = skillDefined.Values;
|
||||||
|
}
|
||||||
|
Skill skill = Factory.OpenFactory.GetInstance<Skill>(Id, Name, args);
|
||||||
|
skillDefined ??= this;
|
||||||
|
if (copyProperty) SetPropertyToItemModuleNew(skill);
|
||||||
|
skill.Id = skillDefined.Id;
|
||||||
|
skill.Name = skillDefined.Name;
|
||||||
|
skill.Description = skillDefined.Description;
|
||||||
|
skill.GeneralDescription = skillDefined.GeneralDescription;
|
||||||
|
skill.DispelDescription = skillDefined.DispelDescription;
|
||||||
|
skill.SkillType = skillDefined.SkillType;
|
||||||
|
skill.MPCost = skillDefined.MPCost;
|
||||||
|
skill.CastTime = skillDefined.CastTime;
|
||||||
|
skill.EPCost = skillDefined.EPCost;
|
||||||
|
skill.CD = skillDefined.CD;
|
||||||
|
skill.HardnessTime = skillDefined.HardnessTime;
|
||||||
|
skill.GamingQueue = skillDefined.GamingQueue;
|
||||||
|
if (skill is OpenSkill)
|
||||||
|
{
|
||||||
|
foreach (Effect e in skillDefined.Effects)
|
||||||
|
{
|
||||||
|
// 特效没法动态扩展,必须使用编程钩子实现,因此动态扩展的技能需要使用代码定义的特效
|
||||||
|
Effect neweffect = e.Copy(skill, true);
|
||||||
|
if (skill.GamingQueue != null)
|
||||||
|
{
|
||||||
|
neweffect.GamingQueue = skill.GamingQueue;
|
||||||
|
}
|
||||||
|
skill.Effects.Add(neweffect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return skill;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 等级
|
||||||
|
/// </summary>
|
||||||
|
private int _Level = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Entity/Skill/SkillTarget.cs
Normal file
20
Entity/Skill/SkillTarget.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 技能和它的目标结构体
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skill"></param>
|
||||||
|
/// <param name="targets"></param>
|
||||||
|
public struct SkillTarget(Skill skill, List<Character> targets)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 技能实例
|
||||||
|
/// </summary>
|
||||||
|
public Skill Skill { get; set; } = skill;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能的目标列表
|
||||||
|
/// </summary>
|
||||||
|
public List<Character> Targets { get; set; } = targets;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Entity/Statistics/CharacterStatistics.cs
Normal file
52
Entity/Statistics/CharacterStatistics.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class CharacterStatistics
|
||||||
|
{
|
||||||
|
public double TotalDamage { get; set; } = 0;
|
||||||
|
public double TotalPhysicalDamage { get; set; } = 0;
|
||||||
|
public double TotalMagicDamage { get; set; } = 0;
|
||||||
|
public double TotalRealDamage { get; set; } = 0;
|
||||||
|
public double TotalTakenDamage { get; set; } = 0;
|
||||||
|
public double TotalTakenPhysicalDamage { get; set; } = 0;
|
||||||
|
public double TotalTakenMagicDamage { get; set; } = 0;
|
||||||
|
public double TotalTakenRealDamage { get; set; } = 0;
|
||||||
|
public double AvgDamage { get; set; } = 0;
|
||||||
|
public double AvgPhysicalDamage { get; set; } = 0;
|
||||||
|
public double AvgMagicDamage { get; set; } = 0;
|
||||||
|
public double AvgRealDamage { get; set; } = 0;
|
||||||
|
public double AvgTakenDamage { get; set; } = 0;
|
||||||
|
public double AvgTakenPhysicalDamage { get; set; } = 0;
|
||||||
|
public double AvgTakenMagicDamage { get; set; } = 0;
|
||||||
|
public double AvgTakenRealDamage { get; set; } = 0;
|
||||||
|
public double TotalHeal { get; set; } = 0;
|
||||||
|
public double AvgHeal { get; set; } = 0;
|
||||||
|
public int LiveRound { get; set; } = 0;
|
||||||
|
public int AvgLiveRound { get; set; } = 0;
|
||||||
|
public int ActionTurn { get; set; } = 0;
|
||||||
|
public int AvgActionTurn { get; set; } = 0;
|
||||||
|
public double LiveTime { get; set; } = 0;
|
||||||
|
public double AvgLiveTime { get; set; } = 0;
|
||||||
|
public double ControlTime { get; set; } = 0;
|
||||||
|
public double AvgControlTime { get; set; } = 0;
|
||||||
|
public double DamagePerRound { get; set; } = 0;
|
||||||
|
public double DamagePerTurn { get; set; } = 0;
|
||||||
|
public double DamagePerSecond { get; set; } = 0;
|
||||||
|
public int TotalEarnedMoney { get; set; } = 0;
|
||||||
|
public int AvgEarnedMoney { get; set; } = 0;
|
||||||
|
public int Kills { get; set; } = 0;
|
||||||
|
public int Deaths { get; set; } = 0;
|
||||||
|
public int Assists { get; set; } = 0;
|
||||||
|
public int FirstKills { get; set; } = 0;
|
||||||
|
public int FirstDeaths { get; set; } = 0;
|
||||||
|
public int Plays { get; set; } = 0;
|
||||||
|
public int Wins { get; set; } = 0;
|
||||||
|
public int Top3s { get; set; } = 0;
|
||||||
|
public int Loses { get; set; } = 0;
|
||||||
|
public double Winrates { get; set; } = 0;
|
||||||
|
public double Top3rates { get; set; } = 0;
|
||||||
|
public int LastRank { get; set; } = 0;
|
||||||
|
public double AvgRank { get; set; } = 0;
|
||||||
|
public double Rating { get; set; } = 0;
|
||||||
|
public int MVPs { get; set; } = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
75
Entity/Statistics/GameStatistics.cs
Normal file
75
Entity/Statistics/GameStatistics.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class GameStatistics
|
||||||
|
{
|
||||||
|
public long Id => Room.Id;
|
||||||
|
public Room Room { get; }
|
||||||
|
public DateTime RecordTime { get; set; } = DateTime.Now;
|
||||||
|
public string Record { get; set; } = "";
|
||||||
|
public Dictionary<User, double> DamageStats { get; set; } = new();
|
||||||
|
public Dictionary<User, double> PhysicalDamageStats { get; } = new();
|
||||||
|
public Dictionary<User, double> MagicDamageStats { get; } = new();
|
||||||
|
public Dictionary<User, double> RealDamageStats { get; } = new();
|
||||||
|
public double AvgDamageStats
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double total = 0;
|
||||||
|
foreach (User user in DamageStats.Keys)
|
||||||
|
{
|
||||||
|
total += DamageStats[user];
|
||||||
|
}
|
||||||
|
return Math.Round(total / DamageStats.Count, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public double AvgPhysicalDamageStats
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double total = 0;
|
||||||
|
foreach (User user in PhysicalDamageStats.Keys)
|
||||||
|
{
|
||||||
|
total += PhysicalDamageStats[user];
|
||||||
|
}
|
||||||
|
return Math.Round(total / PhysicalDamageStats.Count, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public double AvgMagicDamageStats
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double total = 0;
|
||||||
|
foreach (User user in MagicDamageStats.Keys)
|
||||||
|
{
|
||||||
|
total += MagicDamageStats[user];
|
||||||
|
}
|
||||||
|
return Math.Round(total / MagicDamageStats.Count, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public double AvgRealDamageStats
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double total = 0;
|
||||||
|
foreach (User user in RealDamageStats.Keys)
|
||||||
|
{
|
||||||
|
total += RealDamageStats[user];
|
||||||
|
}
|
||||||
|
return Math.Round(total / RealDamageStats.Count, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Dictionary<User, double> KillStats { get; } = new();
|
||||||
|
public Dictionary<User, Dictionary<User, int>> KillDetailStats { get; } = new(); // 子字典记录的是被击杀者以及被击杀次数
|
||||||
|
public Dictionary<User, double> DeathStats { get; } = new();
|
||||||
|
public Dictionary<User, Dictionary<User, int>> DeathDetailStats { get; } = new(); // 子字典记录的是击杀者以及击杀次数
|
||||||
|
public Dictionary<User, long> AssistStats { get; } = new();
|
||||||
|
public Dictionary<User, double> RatingStats { get; } = new(); // 结算后的Rating
|
||||||
|
public Dictionary<User, double> EloStats { get; } = new(); // Elo分数变化(+/-)
|
||||||
|
public Dictionary<User, string> RankStats { get; } = new(); // 结算后的Rank(非比赛前)
|
||||||
|
|
||||||
|
public GameStatistics(Room Room)
|
||||||
|
{
|
||||||
|
this.Room = Room;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
129
Entity/Statistics/UserStatistics.cs
Normal file
129
Entity/Statistics/UserStatistics.cs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 记录 <see cref="Entity.User"/> 的生涯、赛季统计数据<para/>
|
||||||
|
/// Key为赛季(long),每个key代表第key赛季,key = 0时为生涯数据。
|
||||||
|
/// </summary>
|
||||||
|
public class UserStatistics
|
||||||
|
{
|
||||||
|
public long Id => User.Id;
|
||||||
|
public User User { get; }
|
||||||
|
public Dictionary<long, double> DamageStats { get; } = [];
|
||||||
|
public Dictionary<long, double> PhysicalDamageStats { get; } = [];
|
||||||
|
public Dictionary<long, double> MagicDamageStats { get; } = [];
|
||||||
|
public Dictionary<long, double> RealDamageStats { get; } = [];
|
||||||
|
public Dictionary<long, double> AvgDamageStats
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Dictionary<long, double> avgdamage = [];
|
||||||
|
foreach (long key in Plays.Keys)
|
||||||
|
{
|
||||||
|
long plays = Plays[key];
|
||||||
|
double total = 0;
|
||||||
|
if (DamageStats.ContainsKey(key))
|
||||||
|
{
|
||||||
|
total = DamageStats.Values.Sum();
|
||||||
|
}
|
||||||
|
avgdamage.Add(key, Math.Round(total / plays, 2));
|
||||||
|
}
|
||||||
|
return avgdamage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Dictionary<long, double> AvgPhysicalDamageStats
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Dictionary<long, double> avgdamage = [];
|
||||||
|
foreach (long key in Plays.Keys)
|
||||||
|
{
|
||||||
|
long plays = Plays[key];
|
||||||
|
double total = 0;
|
||||||
|
if (PhysicalDamageStats.ContainsKey(key))
|
||||||
|
{
|
||||||
|
total = PhysicalDamageStats.Values.Sum();
|
||||||
|
}
|
||||||
|
avgdamage.Add(key, Math.Round(total / plays, 2));
|
||||||
|
}
|
||||||
|
return avgdamage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Dictionary<long, double> AvgMagicDamageStats
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Dictionary<long, double> avgdamage = [];
|
||||||
|
foreach (long key in Plays.Keys)
|
||||||
|
{
|
||||||
|
long plays = Plays[key];
|
||||||
|
double total = 0;
|
||||||
|
if (MagicDamageStats.ContainsKey(key))
|
||||||
|
{
|
||||||
|
total = MagicDamageStats.Values.Sum();
|
||||||
|
}
|
||||||
|
avgdamage.Add(key, Math.Round(total / plays, 2));
|
||||||
|
}
|
||||||
|
return avgdamage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Dictionary<long, double> AvgRealDamageStats
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Dictionary<long, double> avgdamage = [];
|
||||||
|
foreach (long key in Plays.Keys)
|
||||||
|
{
|
||||||
|
long plays = Plays[key];
|
||||||
|
double total = 0;
|
||||||
|
if (RealDamageStats.ContainsKey(key))
|
||||||
|
{
|
||||||
|
total = RealDamageStats.Values.Sum();
|
||||||
|
}
|
||||||
|
avgdamage.Add(key, Math.Round(total / plays, 2));
|
||||||
|
}
|
||||||
|
return avgdamage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Dictionary<long, long> Kills { get; } = [];
|
||||||
|
public Dictionary<long, long> Deaths { get; } = [];
|
||||||
|
public Dictionary<long, long> Assists { get; } = [];
|
||||||
|
public Dictionary<long, long> Plays { get; } = [];
|
||||||
|
public Dictionary<long, long> Wins { get; } = [];
|
||||||
|
public Dictionary<long, long> Loses { get; } = [];
|
||||||
|
public Dictionary<long, double> Winrates
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Dictionary<long, double> winrates = [];
|
||||||
|
foreach (long key in Plays.Keys)
|
||||||
|
{
|
||||||
|
long plays = Plays[key];
|
||||||
|
long wins = 0;
|
||||||
|
if (Wins.TryGetValue(key, out long value))
|
||||||
|
{
|
||||||
|
wins = value;
|
||||||
|
}
|
||||||
|
winrates.Add(key, Math.Round(wins * 1.0 / plays * 1.0, 4));
|
||||||
|
}
|
||||||
|
return winrates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Dictionary<long, double> RatingStats { get; } = [];
|
||||||
|
public Dictionary<long, double> EloStats { get; } = [];
|
||||||
|
public Dictionary<long, string> RankStats { get; } = [];
|
||||||
|
|
||||||
|
public string GetWinrate(long season)
|
||||||
|
{
|
||||||
|
if (Winrates.TryGetValue(season, out double value))
|
||||||
|
{
|
||||||
|
return value.ToString("0.##%");
|
||||||
|
}
|
||||||
|
return "0%";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal UserStatistics(User user)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Entity/System/Activity.cs
Normal file
122
Entity/System/Activity.cs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Event;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Activity(long id, string name, DateTime startTime, DateTime endTime)
|
||||||
|
{
|
||||||
|
public long Id { get; set; } = id;
|
||||||
|
public string Name { get; set; } = name;
|
||||||
|
public DateTime StartTime { get; set; } = startTime;
|
||||||
|
public DateTime EndTime { get; set; } = endTime;
|
||||||
|
public ActivityState Status { get; private set; } = ActivityState.Future;
|
||||||
|
public HashSet<Quest> Quests { get; set; } = [];
|
||||||
|
|
||||||
|
// 事件
|
||||||
|
public event Action<ActivityEventArgs>? UserAccess;
|
||||||
|
public event Action<ActivityEventArgs>? UserGetActivityInfo;
|
||||||
|
|
||||||
|
public void UnRegisterUserAccess()
|
||||||
|
{
|
||||||
|
UserAccess = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnRegisterUserGetActivityInfo()
|
||||||
|
{
|
||||||
|
UserGetActivityInfo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState()
|
||||||
|
{
|
||||||
|
ActivityState newState;
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
DateTime upComingTime = StartTime.AddHours(-6);
|
||||||
|
|
||||||
|
if (now < upComingTime)
|
||||||
|
{
|
||||||
|
newState = ActivityState.Future;
|
||||||
|
}
|
||||||
|
else if (now >= upComingTime && now < StartTime)
|
||||||
|
{
|
||||||
|
newState = ActivityState.Upcoming;
|
||||||
|
}
|
||||||
|
else if (now >= StartTime && now < EndTime)
|
||||||
|
{
|
||||||
|
newState = ActivityState.InProgress;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newState = ActivityState.Ended;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Status != newState)
|
||||||
|
{
|
||||||
|
Status = newState;
|
||||||
|
foreach (Quest quest in Quests)
|
||||||
|
{
|
||||||
|
if (newState == ActivityState.InProgress)
|
||||||
|
{
|
||||||
|
if (quest.Status == QuestState.NotStarted && quest.QuestType == QuestType.Progressive)
|
||||||
|
{
|
||||||
|
quest.Status = QuestState.InProgress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (newState == ActivityState.Ended)
|
||||||
|
{
|
||||||
|
if (quest.Status == QuestState.NotStarted || quest.Status == QuestState.InProgress)
|
||||||
|
{
|
||||||
|
quest.Status = QuestState.Missed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowUserAccess(long userId, long questId = 0)
|
||||||
|
{
|
||||||
|
UpdateState();
|
||||||
|
ActivityEventArgs args = new(userId, questId, this);
|
||||||
|
UserAccess?.Invoke(args);
|
||||||
|
return args.AllowAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetActivityInfo(long userId, long questId = 0)
|
||||||
|
{
|
||||||
|
UpdateState();
|
||||||
|
ActivityEventArgs args = new(userId, questId, this);
|
||||||
|
UserGetActivityInfo?.Invoke(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString(bool showQuests)
|
||||||
|
{
|
||||||
|
UpdateState();
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine($"☆--- [{Name}] ---☆");
|
||||||
|
string status = Status switch
|
||||||
|
{
|
||||||
|
ActivityState.Future => "预告中",
|
||||||
|
ActivityState.Upcoming => "即将开始",
|
||||||
|
ActivityState.InProgress => "进行中",
|
||||||
|
_ => "已结束"
|
||||||
|
};
|
||||||
|
builder.AppendLine($"活动状态:{status}");
|
||||||
|
builder.AppendLine($"开始时间:{StartTime.ToString(General.GeneralDateTimeFormatChinese)}");
|
||||||
|
builder.AppendLine($"结束时间:{EndTime.ToString(General.GeneralDateTimeFormatChinese)}");
|
||||||
|
|
||||||
|
if (showQuests && Quests.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine("=== 任务列表 ===");
|
||||||
|
builder.AppendLine(string.Join("\r\n", Quests));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString().Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Entity/System/Club.cs
Normal file
25
Entity/System/Club.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Club : BaseEntity
|
||||||
|
{
|
||||||
|
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||||||
|
public string Prefix { get; set; } = "";
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
public bool IsNeedApproval { get; set; } = false;
|
||||||
|
public bool IsPublic { get; set; } = false;
|
||||||
|
public double ClubPoins { get; set; } = 0;
|
||||||
|
public User? Master { get; set; }
|
||||||
|
public Dictionary<long, User> Admins { get; set; } = [];
|
||||||
|
public Dictionary<long, User> Members { get; set; } = [];
|
||||||
|
public Dictionary<long, User> Applicants { get; set; } = [];
|
||||||
|
public Dictionary<long, DateTime> MemberJoinTime { get; set; } = [];
|
||||||
|
public Dictionary<long, DateTime> ApplicationTime { get; set; } = [];
|
||||||
|
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is Club && other?.Id == Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
Entity/System/Goods.cs
Normal file
64
Entity/System/Goods.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Goods
|
||||||
|
{
|
||||||
|
public long Id { get; set; } = 0;
|
||||||
|
public List<Item> Items { get; } = [];
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
public Dictionary<string, double> Prices { get; } = [];
|
||||||
|
public int Stock { get; set; }
|
||||||
|
|
||||||
|
public Goods() { }
|
||||||
|
|
||||||
|
public Goods(long id, Item item, int stock, string name, string description, Dictionary<string, double>? prices = null)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Items.Add(item);
|
||||||
|
Stock = stock;
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
if (prices != null) Prices = prices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Goods(long id, List<Item> items, int stock, string name, string description, Dictionary<string, double>? prices = null)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Items = items;
|
||||||
|
Stock = stock;
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
if (prices != null) Prices = prices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
builder.AppendLine($"{Id}. {Name}");
|
||||||
|
builder.AppendLine($"商品描述:{Description}");
|
||||||
|
builder.AppendLine($"商品售价:{(Prices.Count > 0 ? string.Join("、", Prices.Select(kv => $"{kv.Value} {kv.Key}")) : "免费")}");
|
||||||
|
builder.AppendLine($"包含物品:{string.Join("、", Items.Select(i => $"[{ItemSet.GetQualityTypeName(i.QualityType)}|{ItemSet.GetItemTypeName(i.ItemType)}] {i.Name}"))}");
|
||||||
|
builder.AppendLine($"剩余库存:{Stock}");
|
||||||
|
return builder.ToString().Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPrice(string needy, double price)
|
||||||
|
{
|
||||||
|
if (price > 0) Prices[needy] = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetPrice(string needy, out double price)
|
||||||
|
{
|
||||||
|
price = 0;
|
||||||
|
if (Prices.TryGetValue(needy, out double temp) && temp > 0)
|
||||||
|
{
|
||||||
|
price = temp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
152
Entity/System/Inventory.cs
Normal file
152
Entity/System/Inventory.cs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Inventory : BaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 库存 ID 与用户 ID 绑定
|
||||||
|
/// </summary>
|
||||||
|
public override long Id => User.Id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 库存的名称,默认为 “<see cref="User.Username"/>的库存”;可更改
|
||||||
|
/// </summary>
|
||||||
|
public override string Name
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _customName.Trim() == "" ? User.Username + "的库存" : _customName;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_customName = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 库存属于哪个玩家
|
||||||
|
/// </summary>
|
||||||
|
public User User { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 玩家持有 <see cref="EquilibriumConstant.InGameCurrency"/> 的数量
|
||||||
|
/// </summary>
|
||||||
|
public double Credits { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 玩家持有 <see cref="EquilibriumConstant.InGameMaterial"/> 的数量
|
||||||
|
/// </summary>
|
||||||
|
public double Materials { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 玩家拥有的角色
|
||||||
|
/// </summary>
|
||||||
|
public HashSet<Character> Characters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 玩家拥有的物品
|
||||||
|
/// </summary>
|
||||||
|
public HashSet<Item> Items { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主战角色
|
||||||
|
/// </summary>
|
||||||
|
public Character MainCharacter
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_character != null)
|
||||||
|
{
|
||||||
|
return _character;
|
||||||
|
}
|
||||||
|
else if (Characters.Count > 0)
|
||||||
|
{
|
||||||
|
_character = Characters.First();
|
||||||
|
return _character;
|
||||||
|
}
|
||||||
|
return Factory.GetCharacter();
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_character = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 小队
|
||||||
|
/// </summary>
|
||||||
|
public HashSet<long> Squad { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 练级中的角色
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<long, DateTime> Training { get; set; } = [];
|
||||||
|
|
||||||
|
private Character? _character;
|
||||||
|
private string _customName = "";
|
||||||
|
|
||||||
|
internal Inventory(User user)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name + $"({User})";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString(bool showAll)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine($"☆★☆ {Name} ☆★☆");
|
||||||
|
builder.AppendLine($"{GameplayEquilibriumConstant.InGameCurrency}:{Credits:0.00}");
|
||||||
|
builder.AppendLine($"{GameplayEquilibriumConstant.InGameMaterial}:{Materials:0.00}");
|
||||||
|
|
||||||
|
builder.AppendLine($"======= 角色 =======");
|
||||||
|
Character[] characters = [.. Characters];
|
||||||
|
for (int i = 1; i <= characters.Length; i++)
|
||||||
|
{
|
||||||
|
Character character = characters[i - 1];
|
||||||
|
if (showAll)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"===== 第 {i} 个角色 =====");
|
||||||
|
builder.AppendLine($"{character.GetInfo(false)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine($"{i}. {character.ToStringWithLevelWithOutUser()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine($"======= 物品 =======");
|
||||||
|
Item[] items = [.. Items];
|
||||||
|
for (int i = 1; i <= items.Length; i++)
|
||||||
|
{
|
||||||
|
Item item = items[i - 1];
|
||||||
|
if (showAll)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"===== 第 {i} 个物品 =====");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine($"{i}. [{ItemSet.GetQualityTypeName(item.QualityType)}|{ItemSet.GetItemTypeName(item.ItemType)}] {item.Name}");
|
||||||
|
}
|
||||||
|
builder.AppendLine($"{item.ToStringInventory(showAll).Trim()}");
|
||||||
|
if (showAll) builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is Inventory && other.GetIdName() == GetIdName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Entity/System/MarketItem.cs
Normal file
28
Entity/System/MarketItem.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class MarketItem : BaseEntity
|
||||||
|
{
|
||||||
|
public User User { get; set; }
|
||||||
|
public Item Item { get; set; }
|
||||||
|
public double Price { get; set; } = 0;
|
||||||
|
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||||||
|
public DateTime? FinishTime { get; set; } = null;
|
||||||
|
public MarketItemState Status { get; set; } = MarketItemState.Listed;
|
||||||
|
public User? Buyer { get; set; } = null;
|
||||||
|
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is MarketItem && other?.Id == Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MarketItem()
|
||||||
|
{
|
||||||
|
User = Factory.GetUser();
|
||||||
|
Item = Factory.GetItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Entity/System/Offer.cs
Normal file
22
Entity/System/Offer.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Offer : BaseEntity
|
||||||
|
{
|
||||||
|
public long Offeror { get; set; } = 0;
|
||||||
|
public long Offeree { get; set; } = 0;
|
||||||
|
public HashSet<Guid> OfferorItems { get; set; } = [];
|
||||||
|
public HashSet<Guid> OffereeItems { get; set; } = [];
|
||||||
|
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||||||
|
public DateTime? FinishTime { get; set; } = null;
|
||||||
|
public OfferState Status { get; set; } = OfferState.Created;
|
||||||
|
public int NegotiatedTimes { get; set; } = 0;
|
||||||
|
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is Offer && other?.Id == Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
Entity/System/Quest.cs
Normal file
69
Entity/System/Quest.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Quest : BaseEntity
|
||||||
|
{
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
public QuestState Status { get; set; } = 0;
|
||||||
|
public long CharacterId { get; set; } = 0;
|
||||||
|
public long RegionId { get; set; } = 0;
|
||||||
|
public double CreditsAward { get; set; } = 0;
|
||||||
|
public double MaterialsAward { get; set; } = 0;
|
||||||
|
public HashSet<Item> Awards { get; set; } = [];
|
||||||
|
public Dictionary<string, int> AwardsCount { get; set; } = [];
|
||||||
|
public string AwardsString
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
List<string> awards = [];
|
||||||
|
if (CreditsAward > 0)
|
||||||
|
{
|
||||||
|
awards.Add($"{GameplayEquilibriumConstant.InGameCurrency} * {CreditsAward}");
|
||||||
|
}
|
||||||
|
if (MaterialsAward > 0)
|
||||||
|
{
|
||||||
|
awards.Add($"{GameplayEquilibriumConstant.InGameMaterial} * {MaterialsAward}");
|
||||||
|
}
|
||||||
|
foreach (Item item in Awards)
|
||||||
|
{
|
||||||
|
awards.Add($"[{ItemSet.GetQualityTypeName(item.QualityType)}|{ItemSet.GetItemTypeName(item.ItemType)}] {item.Name} * {AwardsCount[item.Name]}");
|
||||||
|
}
|
||||||
|
return string.Join(",", awards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public DateTime? StartTime { get; set; } = null;
|
||||||
|
public DateTime? SettleTime { get; set; } = null;
|
||||||
|
public QuestType QuestType { get; set; } = QuestType.Continuous;
|
||||||
|
public int EstimatedMinutes { get; set; } = 0;
|
||||||
|
public int Progress { get; set; } = 0;
|
||||||
|
public int MaxProgress { get; set; } = 100;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
string progressString = "";
|
||||||
|
if (QuestType == QuestType.Progressive)
|
||||||
|
{
|
||||||
|
progressString = $"\r\n当前进度:{Progress}/{MaxProgress}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{Id}. {Name}\r\n" +
|
||||||
|
$"{Description}\r\n" +
|
||||||
|
(QuestType == QuestType.Continuous ? $"需要时间:{EstimatedMinutes} 分钟\r\n" : "") +
|
||||||
|
(StartTime.HasValue ? $"开始时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)}" +
|
||||||
|
(Status == QuestState.InProgress && QuestType == QuestType.Continuous ?
|
||||||
|
$"\r\n预计在 {Math.Max(Math.Round((StartTime.Value.AddMinutes(EstimatedMinutes) - DateTime.Now).TotalMinutes, MidpointRounding.ToPositiveInfinity), 1)} 分钟后完成" : "")
|
||||||
|
+ "\r\n"
|
||||||
|
: "") +
|
||||||
|
$"完成奖励:{AwardsString}\r\n" +
|
||||||
|
$"任务状态:{CommonSet.GetQuestStatus(Status)}" + progressString +
|
||||||
|
(SettleTime.HasValue ? $"\r\n结算时间:{SettleTime.Value.ToString(General.GeneralDateTimeFormatChinese)}" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is Quest && other.GetIdName() == GetIdName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
Entity/System/Region.cs
Normal file
76
Entity/System/Region.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Region : BaseEntity
|
||||||
|
{
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
public string Category { get; set; } = "";
|
||||||
|
public HashSet<Character> Characters { get; } = [];
|
||||||
|
public HashSet<Unit> Units { get; } = [];
|
||||||
|
public HashSet<Item> Crops { get; } = [];
|
||||||
|
public string Weather { get; set; } = "";
|
||||||
|
public int Temperature { get; set; } = 15;
|
||||||
|
public Dictionary<string, int> Weathers { get; } = [];
|
||||||
|
public RarityType Difficulty { get; set; } = RarityType.OneStar;
|
||||||
|
|
||||||
|
public bool ChangeWeather(string weather)
|
||||||
|
{
|
||||||
|
if (Weathers.TryGetValue(weather, out int temperature))
|
||||||
|
{
|
||||||
|
Weather = weather;
|
||||||
|
Temperature = temperature;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChangeRandomWeather()
|
||||||
|
{
|
||||||
|
if (Weathers.Count == 0) return false;
|
||||||
|
Weather = Weathers.Keys.ElementAt(Random.Shared.Next(Weathers.Count));
|
||||||
|
Temperature = Weathers[Weather];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is Region && other.GetIdName() == GetIdName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine($"☆--- {Name} ---☆");
|
||||||
|
builder.AppendLine($"编号:{Id}");
|
||||||
|
builder.AppendLine($"天气:{Weather}");
|
||||||
|
builder.AppendLine($"温度:{Temperature} °C");
|
||||||
|
builder.AppendLine($"{Description}");
|
||||||
|
|
||||||
|
if (Characters.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"== 头目 ==");
|
||||||
|
builder.AppendLine(string.Join(",", Characters.Select(o => o.Name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Units.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"== 生物 ==");
|
||||||
|
builder.AppendLine(string.Join(",", Units.Select(o => o.Name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Crops.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"== 作物 ==");
|
||||||
|
builder.AppendLine(string.Join(",", Crops.Select(c => c.Name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine($"探索难度:{CharacterSet.GetRarityTypeName(Difficulty)}");
|
||||||
|
|
||||||
|
return builder.ToString().Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Entity/System/Room.cs
Normal file
50
Entity/System/Room.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Room : BaseEntity
|
||||||
|
{
|
||||||
|
public static readonly Room Empty = new();
|
||||||
|
public override long Id { get => base.Id; set => base.Id = value; }
|
||||||
|
public string Roomid { get; set; } = "-1";
|
||||||
|
public DateTime CreateTime { get; set; } = General.DefaultTime;
|
||||||
|
public User RoomMaster { get; set; } = General.UnknownUserInstance;
|
||||||
|
public RoomType RoomType { get; set; } = RoomType.All;
|
||||||
|
public string GameModule { get; set; } = "";
|
||||||
|
public string GameMap { get; set; } = "";
|
||||||
|
public RoomState RoomState { get; set; }
|
||||||
|
public bool IsRank { get; set; } = false;
|
||||||
|
public bool HasPass => Password.Trim() != "";
|
||||||
|
public string Password { get; set; } = "";
|
||||||
|
public int MaxUsers { get; set; } = 0;
|
||||||
|
public Dictionary<User, bool> UserAndIsReady { get; } = [];
|
||||||
|
public GameStatistics Statistics { get; set; }
|
||||||
|
|
||||||
|
internal Room()
|
||||||
|
{
|
||||||
|
Statistics = new(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Room(long id = 0, string roomid = "-1", DateTime? createTime = null, User? roomMaster = null, RoomType roomType = RoomType.All, string gameModule = "", string gameMap = "", RoomState roomState = RoomState.Created, bool isRank = false, string password = "", int maxUsers = 4)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Roomid = roomid;
|
||||||
|
CreateTime = createTime ?? General.DefaultTime;
|
||||||
|
RoomMaster = roomMaster ?? General.UnknownUserInstance;
|
||||||
|
RoomType = roomType;
|
||||||
|
GameModule = gameModule;
|
||||||
|
GameMap = gameMap;
|
||||||
|
RoomState = roomState;
|
||||||
|
IsRank = isRank;
|
||||||
|
Password = password;
|
||||||
|
MaxUsers = maxUsers;
|
||||||
|
Statistics = new(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is Room r && r.Roomid == Roomid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
146
Entity/System/RoundRecord.cs
Normal file
146
Entity/System/RoundRecord.cs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class RoundRecord(int round)
|
||||||
|
{
|
||||||
|
public int Round { get; set; } = round;
|
||||||
|
public Character Actor { get; set; } = Factory.GetCharacter();
|
||||||
|
public CharacterActionType ActionType { get; set; } = CharacterActionType.None;
|
||||||
|
public List<Character> Targets { get; set; } = [];
|
||||||
|
public Skill? Skill { get; set; } = null;
|
||||||
|
public string SkillCost { get; set; } = "";
|
||||||
|
public Item? Item { get; set; } = null;
|
||||||
|
public bool HasKill { get; set; } = false;
|
||||||
|
public Dictionary<Character, double> Damages { get; set; } = [];
|
||||||
|
public Dictionary<Character, bool> IsCritical { get; set; } = [];
|
||||||
|
public Dictionary<Character, bool> IsEvaded { get; set; } = [];
|
||||||
|
public Dictionary<Character, bool> IsImmune { get; set; } = [];
|
||||||
|
public Dictionary<Character, double> Heals { get; set; } = [];
|
||||||
|
public Dictionary<Character, Effect> Effects { get; set; } = [];
|
||||||
|
public Dictionary<Character, List<EffectType>> ApplyEffects { get; set; } = [];
|
||||||
|
public List<string> ActorContinuousKilling { get; set; } = [];
|
||||||
|
public List<string> DeathContinuousKilling { get; set; } = [];
|
||||||
|
public double CastTime { get; set; } = 0;
|
||||||
|
public double HardnessTime { get; set; } = 0;
|
||||||
|
public Dictionary<Character, double> RespawnCountdowns { get; set; } = [];
|
||||||
|
public List<Character> Respawns { get; set; } = [];
|
||||||
|
public List<Skill> RoundRewards { get; set; } = [];
|
||||||
|
public List<string> OtherMessages { get; set; } = [];
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine($"=== Round {Round} ===");
|
||||||
|
if (RoundRewards.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[ {Actor} ] 回合奖励 -> {string.Join(" / ", RoundRewards.Select(s => s.Description)).Trim()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Effects.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[ {Actor} ] 发动了技能:{string.Join(",", Effects.Where(kv => kv.Key == Actor).Select(e => e.Value.Name))}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ActionType == CharacterActionType.NormalAttack || ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill)
|
||||||
|
{
|
||||||
|
if (ActionType == CharacterActionType.NormalAttack)
|
||||||
|
{
|
||||||
|
builder.Append($"[ {Actor} ] {Actor.NormalAttack.Name} -> ");
|
||||||
|
}
|
||||||
|
else if (ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill)
|
||||||
|
{
|
||||||
|
if (Skill != null)
|
||||||
|
{
|
||||||
|
builder.Append($"[ {Actor} ] {Skill.Name}({SkillCost})-> ");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Append($"释放魔法 -> ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.AppendLine(string.Join(" / ", GetTargetsState()));
|
||||||
|
if (DeathContinuousKilling.Count > 0) builder.AppendLine($"{string.Join("\r\n", DeathContinuousKilling)}");
|
||||||
|
if (ActorContinuousKilling.Count > 0) builder.AppendLine($"{string.Join("\r\n", ActorContinuousKilling)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ActionType == CharacterActionType.PreCastSkill && Skill != null)
|
||||||
|
{
|
||||||
|
if (Skill.IsMagic)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[ {Actor} ] 吟唱 [ {Skill.Name} ],持续时间:{CastTime:0.##}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[ {Actor} ]:{Skill.Name}({SkillCost})-> ");
|
||||||
|
builder.AppendLine(string.Join(" / ", GetTargetsState()));
|
||||||
|
builder.AppendLine($"[ {Actor} ] 回合结束,硬直时间:{HardnessTime:0.##}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[ {Actor} ] 回合结束,硬直时间:{HardnessTime:0.##}");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Character character in RespawnCountdowns.Keys)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[ {character} ] 进入复活倒计时:{RespawnCountdowns[character]:0.##}");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Character character in Respawns)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[ {character} ] 复活了");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> GetTargetsState()
|
||||||
|
{
|
||||||
|
List<string> strings = [];
|
||||||
|
foreach (Character target in Targets.Distinct())
|
||||||
|
{
|
||||||
|
string hasDamage = "";
|
||||||
|
string hasHeal = "";
|
||||||
|
string hasEffect = "";
|
||||||
|
if (Damages.TryGetValue(target, out double damage))
|
||||||
|
{
|
||||||
|
hasDamage = $"伤害:{damage:0.##}";
|
||||||
|
if (IsCritical.TryGetValue(target, out bool isCritical) && isCritical)
|
||||||
|
{
|
||||||
|
hasDamage = "暴击," + hasDamage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Heals.TryGetValue(target, out double heals))
|
||||||
|
{
|
||||||
|
hasHeal = $"治疗:{heals:0.##}";
|
||||||
|
}
|
||||||
|
if (ApplyEffects.TryGetValue(target, out List<EffectType>? effectTypes) && effectTypes != null)
|
||||||
|
{
|
||||||
|
hasEffect = $"施加:{string.Join(" + ", effectTypes.Select(SkillSet.GetEffectTypeName))}";
|
||||||
|
}
|
||||||
|
if (IsEvaded.ContainsKey(target))
|
||||||
|
{
|
||||||
|
if (ActionType == CharacterActionType.NormalAttack)
|
||||||
|
{
|
||||||
|
hasDamage = "完美闪避";
|
||||||
|
}
|
||||||
|
else if ((ActionType == CharacterActionType.PreCastSkill || ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill))
|
||||||
|
{
|
||||||
|
hasDamage = "技能免疫";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (IsImmune.ContainsKey(target) && hasDamage != "" && target != Actor)
|
||||||
|
{
|
||||||
|
hasDamage = "免疫";
|
||||||
|
}
|
||||||
|
string[] strs = [hasDamage, hasHeal, hasEffect];
|
||||||
|
strings.Add($"[ {target}({string.Join(" / ", strs.Where(s => s != ""))})])");
|
||||||
|
}
|
||||||
|
return strings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Entity/System/Season.cs
Normal file
9
Entity/System/Season.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Season(long id, string name, string description)
|
||||||
|
{
|
||||||
|
public long Id { get; } = id;
|
||||||
|
public string Name { get; } = name;
|
||||||
|
public string Description { get; } = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
Entity/System/Store.cs
Normal file
110
Entity/System/Store.cs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Store : BaseEntity
|
||||||
|
{
|
||||||
|
public User User { get; set; } = General.UnknownUserInstance;
|
||||||
|
public DateTime? StartTime { get; set; } = null;
|
||||||
|
public DateTime? EndTime { get; set; } = null;
|
||||||
|
public Dictionary<long, Goods> Goods { get; } = [];
|
||||||
|
|
||||||
|
public Store(string name, User? user = null)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine($"☆★☆ {Name} ☆★☆");
|
||||||
|
if (StartTime.HasValue && EndTime.HasValue)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"营业时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)}至{EndTime.Value.ToString(General.GeneralDateTimeFormatChinese)}");
|
||||||
|
}
|
||||||
|
else if (StartTime.HasValue && !EndTime.HasValue)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"开始营业时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)}");
|
||||||
|
}
|
||||||
|
else if (!StartTime.HasValue && EndTime.HasValue)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"停止营业时间:{EndTime.Value.ToString(General.GeneralDateTimeFormatChinese)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine($"[ 24H ] 全年无休,永久开放");
|
||||||
|
}
|
||||||
|
builder.AppendLine($"☆--- 商品列表 ---☆");
|
||||||
|
foreach (Goods goods in Goods.Values)
|
||||||
|
{
|
||||||
|
builder.AppendLine(goods.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString().Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddItem(Item item, int stock, string name = "", string description = "")
|
||||||
|
{
|
||||||
|
long id = Goods.Count > 0 ? Goods.Keys.Max() + 1 : 1;
|
||||||
|
if (name.Trim() == "")
|
||||||
|
{
|
||||||
|
name = item.Name;
|
||||||
|
}
|
||||||
|
if (description.Trim() == "")
|
||||||
|
{
|
||||||
|
description = item.Description;
|
||||||
|
}
|
||||||
|
Goods goods = new(id, item, stock, name, description);
|
||||||
|
goods.SetPrice(GameplayEquilibriumConstant.InGameCurrency, item.Price);
|
||||||
|
Goods.Add(id, goods);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddItems(IEnumerable<Item> items, int stock)
|
||||||
|
{
|
||||||
|
foreach (Item item in items)
|
||||||
|
{
|
||||||
|
AddItem(item, stock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPrice(long id, string needy, double price)
|
||||||
|
{
|
||||||
|
if (Goods.TryGetValue(id, out Goods? goods) && goods != null)
|
||||||
|
{
|
||||||
|
goods.SetPrice(needy, price);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetPrice(long id, string needy, out double price)
|
||||||
|
{
|
||||||
|
price = 0;
|
||||||
|
if (Goods.TryGetValue(id, out Goods? goods) && goods != null)
|
||||||
|
{
|
||||||
|
return goods.GetPrice(needy, out price);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double GetPrice(long id)
|
||||||
|
{
|
||||||
|
double price = 0;
|
||||||
|
if (Goods.TryGetValue(id, out Goods? goods) && goods != null)
|
||||||
|
{
|
||||||
|
goods.GetPrice(GameplayEquilibriumConstant.InGameCurrency, out price);
|
||||||
|
}
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is Store && other.GetIdName() == GetIdName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Entity/System/Team.cs
Normal file
39
Entity/System/Team.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class Team(string name, IEnumerable<Character> charaters)
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.Empty;
|
||||||
|
public string Name { get; set; } = name;
|
||||||
|
public List<Character> Members { get; } = new(charaters);
|
||||||
|
public int Score { get; set; } = 0;
|
||||||
|
public bool IsWinner { get; set; } = false;
|
||||||
|
public int Count => Members.Count;
|
||||||
|
|
||||||
|
public List<Character> GetActiveCharacters(IGamingQueue queue)
|
||||||
|
{
|
||||||
|
return [.. Members.Where(queue.Queue.Contains)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Character> GetTeammates(Character character)
|
||||||
|
{
|
||||||
|
return [.. Members.Where(c => c != character)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Character> GetActiveTeammates(IGamingQueue queue, Character character)
|
||||||
|
{
|
||||||
|
return [.. Members.Where(c => queue.Queue.Contains(c) && c != character)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOnThisTeam(Character character)
|
||||||
|
{
|
||||||
|
return Members.Contains(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
Entity/User/User.cs
Normal file
112
Entity/User/User.cs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class User : BaseEntity
|
||||||
|
{
|
||||||
|
public static readonly User Empty = new();
|
||||||
|
public string Username { get; set; } = "";
|
||||||
|
public DateTime RegTime { get; set; }
|
||||||
|
public DateTime LastTime { get; set; }
|
||||||
|
public OnlineState OnlineState { get; set; } = OnlineState.Offline;
|
||||||
|
public string Email { get; set; } = "";
|
||||||
|
public string NickName { get; set; } = "";
|
||||||
|
public bool IsAdmin { get; set; } = false;
|
||||||
|
public bool IsOperator { get; set; } = false;
|
||||||
|
public bool IsEnable { get; set; } = true;
|
||||||
|
public double GameTime { get; set; } = 0;
|
||||||
|
public string AutoKey { get; set; } = "";
|
||||||
|
public UserProfile Profile { get; }
|
||||||
|
public UserStatistics Statistics { get; }
|
||||||
|
public Inventory Inventory { get; }
|
||||||
|
|
||||||
|
internal User()
|
||||||
|
{
|
||||||
|
Profile = new();
|
||||||
|
Statistics = new(this);
|
||||||
|
Inventory = new(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal User(long Id = 0, string Username = "", DateTime? RegTime = null, DateTime? LastTime = null, string Email = "", string NickName = "", bool IsAdmin = false, bool IsOperator = false, bool IsEnable = true, double GameTime = 0, string AutoKey = "")
|
||||||
|
{
|
||||||
|
this.Id = Id;
|
||||||
|
this.Username = Username;
|
||||||
|
this.RegTime = RegTime ?? General.DefaultTime;
|
||||||
|
this.LastTime = LastTime ?? General.DefaultTime;
|
||||||
|
this.Email = Email;
|
||||||
|
this.NickName = NickName;
|
||||||
|
this.IsAdmin = IsAdmin;
|
||||||
|
this.IsOperator = IsOperator;
|
||||||
|
this.IsEnable = IsEnable;
|
||||||
|
this.GameTime = GameTime;
|
||||||
|
this.AutoKey = AutoKey;
|
||||||
|
Profile = new();
|
||||||
|
Statistics = new(this);
|
||||||
|
Inventory = new(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal User(UserType usertype)
|
||||||
|
{
|
||||||
|
switch (usertype)
|
||||||
|
{
|
||||||
|
case UserType.General:
|
||||||
|
case UserType.Empty:
|
||||||
|
break;
|
||||||
|
case UserType.Guest:
|
||||||
|
Id = UserSet.GuestUserId;
|
||||||
|
Username = UserSet.GuestUserName;
|
||||||
|
break;
|
||||||
|
case UserType.LocalUser:
|
||||||
|
Id = UserSet.LocalUserId;
|
||||||
|
Username = UserSet.LocalUserName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Profile = new();
|
||||||
|
Statistics = new(this);
|
||||||
|
Inventory = new(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(IBaseEntity? other)
|
||||||
|
{
|
||||||
|
return other is User u && u.Id == Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
string str = Username;
|
||||||
|
if (NickName != "")
|
||||||
|
{
|
||||||
|
str += " ( " + NickName + " ) ";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetUserInfo()
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
|
||||||
|
builder.AppendLine($"☆★☆ {Username}的存档信息 ☆★☆");
|
||||||
|
builder.AppendLine($"数字 ID:{Id}");
|
||||||
|
builder.AppendLine($"{General.GameplayEquilibriumConstant.InGameCurrency}:{Inventory.Credits:0.00}");
|
||||||
|
builder.AppendLine($"{General.GameplayEquilibriumConstant.InGameMaterial}:{Inventory.Materials:0.00}");
|
||||||
|
builder.AppendLine($"角色数量:{Inventory.Characters.Count}");
|
||||||
|
builder.AppendLine($"主战角色:{Inventory.MainCharacter.ToStringWithLevelWithOutUser()}");
|
||||||
|
Character[] squad = [.. Inventory.Characters.Where(c => Inventory.Squad.Contains(c.Id))];
|
||||||
|
Dictionary<Character, int> characters = Inventory.Characters
|
||||||
|
.Select((character, index) => new { character, index })
|
||||||
|
.ToDictionary(x => x.character, x => x.index + 1);
|
||||||
|
builder.AppendLine($"小队成员:{(squad.Length > 0 ? string.Join(" / ", squad.Select(c => $"[#{characters[c]}]{c.NickName}({c.Level})")) : "空")}");
|
||||||
|
if (Inventory.Training.Count > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"正在练级:{string.Join(" / ", Inventory.Characters.Where(c => Inventory.Training.ContainsKey(c.Id)).Select(c => c.ToStringWithLevelWithOutUser()))}");
|
||||||
|
}
|
||||||
|
builder.AppendLine($"物品数量:{Inventory.Items.Count}");
|
||||||
|
builder.AppendLine($"注册时间:{RegTime.ToString(General.GeneralDateTimeFormatChinese)}");
|
||||||
|
builder.AppendLine($"最后访问:{LastTime.ToString(General.GeneralDateTimeFormatChinese)}");
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Entity/User/UserProfile.cs
Normal file
47
Entity/User/UserProfile.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
|
{
|
||||||
|
public class UserProfile
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 头像链接
|
||||||
|
/// </summary>
|
||||||
|
public string AvatarUrl { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 签名
|
||||||
|
/// </summary>
|
||||||
|
public string Signature { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 性别
|
||||||
|
/// </summary>
|
||||||
|
public string Gender { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生日
|
||||||
|
/// </summary>
|
||||||
|
public DateTime BirthDay { get; set; } = General.DefaultTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关注者
|
||||||
|
/// </summary>
|
||||||
|
public int Followers { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 正在关注
|
||||||
|
/// </summary>
|
||||||
|
public int Following { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 头衔
|
||||||
|
/// </summary>
|
||||||
|
public string Title { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户组
|
||||||
|
/// </summary>
|
||||||
|
public string UserGroup { get; set; } = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
64
FunGame.Core.csproj
Normal file
64
FunGame.Core.csproj
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<BaseOutputPath>bin\</BaseOutputPath>
|
||||||
|
<Company>$(Author)</Company>
|
||||||
|
<Authors>Project Redbud</Authors>
|
||||||
|
<AssemblyVersion>1.0.0</AssemblyVersion>
|
||||||
|
<FileVersion>1.0.0</FileVersion>
|
||||||
|
<PackageOutputPath>bin</PackageOutputPath>
|
||||||
|
<Title>FunGame Core</Title>
|
||||||
|
<RootNamespace>Milimoe.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<AssemblyName>$(MSBuildProjectName)</AssemblyName>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<DocumentationFile></DocumentationFile>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<Copyright>Project Redbud and Contributors</Copyright>
|
||||||
|
<PackageId>$(AssemblyName)</PackageId>
|
||||||
|
<Description>FunGame.Core: A C#.NET library for turn-based games.</Description>
|
||||||
|
<PackageTags>game;turn-based;server;framework;dotnet;csharp;gamedev</PackageTags>
|
||||||
|
<PackageReleaseNotes>
|
||||||
|
- Initial release candidate 1 (1.0.0-rc.1-0428)
|
||||||
|
- Abstract ActionQueue as GamingQueue, and separate the original Mix / Team modes into two queue types: MixGamingQueue and TeamGamingQueue. (1.0.0-rc.1-0502)
|
||||||
|
- In the Effect class, added ParentEffect and ForceHideInStatusBar properties for more precise control of the status bar display. (1.0.0-rc.1-0509)
|
||||||
|
- Added helper methods IsTeammate and GetIsTeammateDictionary to GamingQueue for determining if someone is a teammate, IGamingQueue also. This facilitates the skill effects to determine whether the target is a teammate. (1.0.0-rc.1-0509)
|
||||||
|
- Added more properties (such as Name, RealCD) to the ISkill interface. Both NormalAttack and Skill inherit from ISkill, thus implementing these properties, although some properties are not meaningful for NormalAttack. (1.0.0-rc.1-0509)
|
||||||
|
- Added corresponding text for EffectTypes Lifesteal and GrievousWound. (1.0.0-rc.1-0509)
|
||||||
|
- Added EffectTypes: WeakDispelling and StrongDispelling, representing DurativeWeak and DurativeStrong of DispelType. (1.0.0-rc.1-0509)
|
||||||
|
- Added underlying processing support for continuous dispelling in the TimeLapse method. (1.0.0-rc.1-0509)
|
||||||
|
- Fixed an issue where the effect's shield hook provided incorrect parameters. (1.0.0-rc.1-0509)
|
||||||
|
- Fixed an issue where the result of pre-hooks for evade and critical hit checks always deferred to the result of the last effect. It should be that if any effect prevents the check, it is skipped. (1.0.0-rc.1-0509)
|
||||||
|
- Added comments for some code. (1.0.0-rc.1-0509)
|
||||||
|
</PackageReleaseNotes>
|
||||||
|
<RepositoryUrl>https://github.com/project-redbud/FunGame-Core</RepositoryUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/project-redbud</PackageProjectUrl>
|
||||||
|
<PackageLicenseExpression>LGPL-3.0-or-later</PackageLicenseExpression>
|
||||||
|
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<VersionPrefix>1.0.0-rc.1</VersionPrefix>
|
||||||
|
<VersionSuffix Condition="'$(VersionSuffix)' == ''">$([System.DateTime]::Now.ToString("MMdd"))</VersionSuffix>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
<NoWarn>1701;1702;CS1591;CS1587;IDE0130</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
<NoWarn>1701;1702;CS1591;CS1587;IDE0130</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="README.md">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"runtimeTarget": {
|
|
||||||
"name": ".NETCoreApp,Version=v9.0",
|
|
||||||
"signature": ""
|
|
||||||
},
|
|
||||||
"compilationOptions": {},
|
|
||||||
"targets": {
|
|
||||||
".NETCoreApp,Version=v9.0": {
|
|
||||||
"FunGame.Core/1.0.0-rc.1-0512": {
|
|
||||||
"runtime": {
|
|
||||||
"FunGame.Core.dll": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"libraries": {
|
|
||||||
"FunGame.Core/1.0.0-rc.1-0512": {
|
|
||||||
"type": "project",
|
|
||||||
"serviceable": false,
|
|
||||||
"sha512": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
FunGame.Core.dll
BIN
FunGame.Core.dll
Binary file not shown.
24
FunGame.Core.sln
Normal file
24
FunGame.Core.sln
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.3.32804.467
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunGame.Core", "FunGame.Core.csproj", "{842BB22E-4309-4ADD-93CD-A981CE10C30E}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{842BB22E-4309-4ADD-93CD-A981CE10C30E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{842BB22E-4309-4ADD-93CD-A981CE10C30E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{842BB22E-4309-4ADD-93CD-A981CE10C30E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{842BB22E-4309-4ADD-93CD-A981CE10C30E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {9AD4C55B-5C44-4486-8497-5C5A7A7B299B}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
9732
FunGame.Core.xml
9732
FunGame.Core.xml
File diff suppressed because it is too large
Load Diff
12
Interface/Base/Addons/IAddon.cs
Normal file
12
Interface/Base/Addons/IAddon.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Interface.Addons
|
||||||
|
{
|
||||||
|
public interface IAddon
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
public string Version { get; }
|
||||||
|
public string Author { get; }
|
||||||
|
|
||||||
|
public bool Load(params object[] objs);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Interface/Base/Addons/IAddonController.cs
Normal file
9
Interface/Base/Addons/IAddonController.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using Milimoe.FunGame.Core.Controller;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Addons
|
||||||
|
{
|
||||||
|
public interface IAddonController<T> where T : IAddon
|
||||||
|
{
|
||||||
|
public BaseAddonController<T> Controller { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Interface/Base/Addons/IGameMap.cs
Normal file
7
Interface/Base/Addons/IGameMap.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Interface.Addons
|
||||||
|
{
|
||||||
|
public interface IGameMap : IAddon
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Interface/Base/Addons/IGameModule.cs
Normal file
13
Interface/Base/Addons/IGameModule.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Addons
|
||||||
|
{
|
||||||
|
public interface IGameModule : IAddon, IAddonController<IGameModule>, IGamingConnectEventHandler, IGamingDisconnectEventHandler, IGamingReconnectEventHandler, IGamingBanCharacterEventHandler, IGamingPickCharacterEventHandler,
|
||||||
|
IGamingRandomEventHandler, IGamingRoundEventHandler, IGamingLevelUpEventHandler, IGamingMoveEventHandler, IGamingAttackEventHandler, IGamingSkillEventHandler, IGamingItemEventHandler, IGamingMagicEventHandler,
|
||||||
|
IGamingBuyEventHandler, IGamingSuperSkillEventHandler, IGamingPauseEventHandler, IGamingUnpauseEventHandler, IGamingSurrenderEventHandler, IGamingUpdateInfoEventHandler, IGamingPunishEventHandler, IGameModuleDepend
|
||||||
|
{
|
||||||
|
public bool HideMain { get; }
|
||||||
|
public void StartGame(Gaming instance, params object[] args);
|
||||||
|
public void StartUI(params object[] args);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Interface/Base/Addons/IGameModuleDepend.cs
Normal file
9
Interface/Base/Addons/IGameModuleDepend.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Addons
|
||||||
|
{
|
||||||
|
public interface IGameModuleDepend
|
||||||
|
{
|
||||||
|
public GameModuleDepend GameModuleDepend { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Interface/Base/Addons/IGameModuleServer.cs
Normal file
13
Interface/Base/Addons/IGameModuleServer.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Addons
|
||||||
|
{
|
||||||
|
public interface IGameModuleServer : IAddon, IAddonController<IGameModuleServer>, IGameModuleDepend
|
||||||
|
{
|
||||||
|
public bool StartServer(GamingObject obj, params object[] args);
|
||||||
|
|
||||||
|
public Task<Dictionary<string, object>> GamingMessageHandler(IServerModel model, GamingType type, Dictionary<string, object> data);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Interface/Base/Addons/IPlugin.cs
Normal file
9
Interface/Base/Addons/IPlugin.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Interface.Addons
|
||||||
|
{
|
||||||
|
public interface IPlugin : IAddon, IAddonController<IPlugin>, IConnectEventHandler, IDisconnectEventHandler, ILoginEventHandler, ILogoutEventHandler, IRegEventHandler, IIntoRoomEventHandler, ISendTalkEventHandler,
|
||||||
|
ICreateRoomEventHandler, IQuitRoomEventHandler, IChangeRoomSettingEventHandler, IStartMatchEventHandler, IStartGameEventHandler, IChangeProfileEventHandler, IChangeAccountSettingEventHandler,
|
||||||
|
IOpenInventoryEventHandler, ISignInEventHandler, IOpenStoreEventHandler, IBuyItemEventHandler, IShowRankingEventHandler, IUseItemEventHandler, IEndGameEventHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Interface/Base/Addons/IServerAddon.cs
Normal file
10
Interface/Base/Addons/IServerAddon.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Transmittal;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Addons
|
||||||
|
{
|
||||||
|
public interface IServerAddon
|
||||||
|
{
|
||||||
|
public SQLHelper? SQLHelper { get; }
|
||||||
|
public MailSender? MailSender { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Interface/Base/HTTP/IHTTPClient.cs
Normal file
15
Interface/Base/HTTP/IHTTPClient.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Network;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.HTTP
|
||||||
|
{
|
||||||
|
public interface IHTTPClient : IBaseSocket
|
||||||
|
{
|
||||||
|
public bool Receiving { get; }
|
||||||
|
public Task Receive();
|
||||||
|
public Task<SocketResult> Send(SocketMessageType type, params object[] objs);
|
||||||
|
public void AddSocketObjectHandler(Action<SocketObject> method);
|
||||||
|
public void RemoveSocketObjectHandler(Action<SocketObject> method);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Interface/Base/HTTP/IHTTPListener.cs
Normal file
12
Interface/Base/HTTP/IHTTPListener.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Net;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Network;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.HTTP
|
||||||
|
{
|
||||||
|
public interface IHTTPListener : IBaseSocket
|
||||||
|
{
|
||||||
|
public HttpListener Instance { get; }
|
||||||
|
public SocketObject SocketObject_Handler(SocketObject objs);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Interface/Base/IBaseSocket.cs
Normal file
11
Interface/Base/IBaseSocket.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Base
|
||||||
|
{
|
||||||
|
public interface IBaseSocket
|
||||||
|
{
|
||||||
|
public SocketRuntimeType Runtime { get; }
|
||||||
|
public Guid Token { get; }
|
||||||
|
public void Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Interface/Base/IEntityConverter.cs
Normal file
13
Interface/Base/IEntityConverter.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Base
|
||||||
|
{
|
||||||
|
public interface IEntityConverter<T>
|
||||||
|
{
|
||||||
|
public T NewInstance();
|
||||||
|
|
||||||
|
public void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref T result, Dictionary<string, object> convertingContext);
|
||||||
|
|
||||||
|
public void AfterConvert(ref T result, Dictionary<string, object> convertingContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Interface/Base/IFactory.cs
Normal file
8
Interface/Base/IFactory.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Interface.Base
|
||||||
|
{
|
||||||
|
public interface IFactory<T>
|
||||||
|
{
|
||||||
|
public Type EntityType { get; }
|
||||||
|
public T Create();
|
||||||
|
}
|
||||||
|
}
|
||||||
202
Interface/Base/IGamingQueue.cs
Normal file
202
Interface/Base/IGamingQueue.cs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Base
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 回合制游戏的基础队列
|
||||||
|
/// </summary>
|
||||||
|
public interface IGamingQueue
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 使用的游戏平衡常数
|
||||||
|
/// </summary>
|
||||||
|
public EquilibriumConstant GameplayEquilibriumConstant { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于文本输出
|
||||||
|
/// </summary>
|
||||||
|
public Action<string> WriteLine { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 原始的角色字典
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<Guid, Character> Original { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前的行动顺序
|
||||||
|
/// </summary>
|
||||||
|
public List<Character> Queue { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上回合记录
|
||||||
|
/// </summary>
|
||||||
|
public RoundRecord LastRound { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 所有回合的记录
|
||||||
|
/// </summary>
|
||||||
|
public List<RoundRecord> Rounds { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前已死亡的角色顺序(第一个是最早死的)
|
||||||
|
/// </summary>
|
||||||
|
public List<Character> Eliminated { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色数据
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<Character, CharacterStatistics> CharacterStatistics { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 游戏运行的时间
|
||||||
|
/// </summary>
|
||||||
|
public double TotalTime { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 游戏运行的回合
|
||||||
|
/// 对于某角色而言,在其行动的回合叫 Turn,而所有角色行动的回合,都称之为 Round。
|
||||||
|
/// </summary>
|
||||||
|
public int TotalRound { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示队列信息
|
||||||
|
/// </summary>
|
||||||
|
public void DisplayQueue();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理回合动作
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<bool> ProcessTurnAsync(Character character);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 造成伤害
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actor"></param>
|
||||||
|
/// <param name="enemy"></param>
|
||||||
|
/// <param name="damage"></param>
|
||||||
|
/// <param name="isNormalAttack"></param>
|
||||||
|
/// <param name="isMagicDamage"></param>
|
||||||
|
/// <param name="magicType"></param>
|
||||||
|
/// <param name="damageResult"></param>
|
||||||
|
public Task DamageToEnemyAsync(Character actor, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage = false, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 治疗一个目标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actor"></param>
|
||||||
|
/// <param name="target"></param>
|
||||||
|
/// <param name="heal"></param>
|
||||||
|
/// <param name="canRespawn"></param>
|
||||||
|
public Task HealToTargetAsync(Character actor, Character target, double heal, bool canRespawn = false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算物理伤害
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actor"></param>
|
||||||
|
/// <param name="enemy"></param>
|
||||||
|
/// <param name="isNormalAttack"></param>
|
||||||
|
/// <param name="expectedDamage"></param>
|
||||||
|
/// <param name="finalDamage"></param>
|
||||||
|
/// <param name="changeCount"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage, ref int changeCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算魔法伤害
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actor"></param>
|
||||||
|
/// <param name="enemy"></param>
|
||||||
|
/// <param name="isNormalAttack"></param>
|
||||||
|
/// <param name="magicType"></param>
|
||||||
|
/// <param name="expectedDamage"></param>
|
||||||
|
/// <param name="finalDamage"></param>
|
||||||
|
/// <param name="changeCount"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage, ref int changeCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 死亡结算
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="killer"></param>
|
||||||
|
/// <param name="death"></param>
|
||||||
|
public Task DeathCalculationAsync(Character killer, Character death);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打断施法
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="interrupter"></param>
|
||||||
|
public Task InterruptCastingAsync(Character caster, Character interrupter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打断施法 [ 用于使敌人目标丢失 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="interrupter"></param>
|
||||||
|
public Task InterruptCastingAsync(Character interrupter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用物品
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<bool> UseItemAsync(Item item, Character caster, List<Character> enemys, List<Character> teammates);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选取技能目标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="skill"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<List<Character>> SelectTargetsAsync(Character caster, Skill skill, List<Character> enemys, List<Character> teammates);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选取普通攻击目标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="attack"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<List<Character>> SelectTargetsAsync(Character character, NormalAttack attack, List<Character> enemys, List<Character> teammates);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断目标对于某个角色是否是队友
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="target"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool IsTeammate(Character character, Character target);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取目标对于某个角色是否是队友的字典
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="targets"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Dictionary<Character, bool> GetIsTeammateDictionary(Character character, IEnumerable<Character> targets);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查角色是否在 AI 控制状态
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool IsCharacterInAIControlling(Character character);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改角色的硬直时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character">角色</param>
|
||||||
|
/// <param name="addValue">加值</param>
|
||||||
|
/// <param name="isPercentage">是否是百分比</param>
|
||||||
|
/// <param name="isCheckProtected">是否使用插队保护机制</param>
|
||||||
|
public void ChangeCharacterHardnessTime(Character character, double addValue, bool isPercentage, bool isCheckProtected);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Interface/Base/IMailSender.cs
Normal file
9
Interface/Base/IMailSender.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Interface.Base
|
||||||
|
{
|
||||||
|
public interface IMailSender
|
||||||
|
{
|
||||||
|
public string SenderMailAddress { get; }
|
||||||
|
public string SenderName { get; }
|
||||||
|
public string SenderPassword { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Interface/Base/ISQLHelper.cs
Normal file
32
Interface/Base/ISQLHelper.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System.Data;
|
||||||
|
using System.Data.Common;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Base
|
||||||
|
{
|
||||||
|
public interface ISQLHelper
|
||||||
|
{
|
||||||
|
public FunGameInfo.FunGame FunGameType { get; }
|
||||||
|
public string Script { get; set; }
|
||||||
|
public CommandType CommandType { get; set; }
|
||||||
|
public DbTransaction? Transaction { get; }
|
||||||
|
public SQLResult Result { get; }
|
||||||
|
public SQLServerInfo ServerInfo { get; }
|
||||||
|
public int AffectedRows { get; }
|
||||||
|
public long LastInsertId { get; }
|
||||||
|
public DataSet DataSet { get; }
|
||||||
|
public bool Success { get; }
|
||||||
|
|
||||||
|
public int Execute();
|
||||||
|
public DataSet ExecuteDataSet();
|
||||||
|
public DataRow? ExecuteDataRow();
|
||||||
|
public Task<int> ExecuteAsync();
|
||||||
|
public Task<DataSet> ExecuteDataSetAsync();
|
||||||
|
public Task<DataRow?> ExecuteDataRowAsync();
|
||||||
|
public void Close();
|
||||||
|
public void NewTransaction();
|
||||||
|
public void Commit();
|
||||||
|
public void Rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
93
Interface/Base/IServerModel.cs
Normal file
93
Interface/Base/IServerModel.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Transmittal;
|
||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Base
|
||||||
|
{
|
||||||
|
public interface IServerModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 服务器实例是否在运行
|
||||||
|
/// </summary>
|
||||||
|
public bool Running { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端的套接字实例
|
||||||
|
/// </summary>
|
||||||
|
public ISocketMessageProcessor? Socket { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端的数据库连接实例
|
||||||
|
/// </summary>
|
||||||
|
public SQLHelper? SQLHelper { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端的邮件服务实例
|
||||||
|
/// </summary>
|
||||||
|
public MailSender? MailSender { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端的用户实例,在用户登录后有效
|
||||||
|
/// </summary>
|
||||||
|
public User User { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端的名称,默认是客户端的IP地址
|
||||||
|
/// </summary>
|
||||||
|
public string ClientName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端是否启动了开发者模式
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDebugMode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端所在的房间
|
||||||
|
/// </summary>
|
||||||
|
public Room InRoom { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端的游戏模组服务器
|
||||||
|
/// </summary>
|
||||||
|
public GameModuleServer? NowGamingServer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 向客户端发送消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="objs"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<bool> Send(SocketMessageType type, params object[] objs);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 向客户端发送系统消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="showtype"></param>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <param name="title"></param>
|
||||||
|
/// <param name="autoclose"></param>
|
||||||
|
/// <param name="usernames"></param>
|
||||||
|
public void SendSystemMessage(ShowMessageType showtype, string msg, string title, int autoclose, params string[] usernames);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 踢下线
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task Kick(string msg);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 强制退出登录
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task ForceLogOut(string msg);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取客户端的名称,通常未登录时显示为客户端的IP地址,登录后显示为账号名
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetClientName();
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Interface/Base/ISocketListener.cs
Normal file
32
Interface/Base/ISocketListener.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Base
|
||||||
|
{
|
||||||
|
public interface ISocketListener<T> where T : ISocketMessageProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 监听器的名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已连接的客户端列表
|
||||||
|
/// </summary>
|
||||||
|
public ConcurrentModelList<IServerModel> ClientList { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已登录的用户列表
|
||||||
|
/// </summary>
|
||||||
|
public ConcurrentModelList<IServerModel> UserList { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 黑名单IP地址列表
|
||||||
|
/// </summary>
|
||||||
|
public List<string> BannedList { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭监听
|
||||||
|
/// </summary>
|
||||||
|
public void Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Interface/Base/ISocketMessageProcessor.cs
Normal file
20
Interface/Base/ISocketMessageProcessor.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Milimoe.FunGame.Core.Library.Common.Network;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Base
|
||||||
|
{
|
||||||
|
public interface ISocketMessageProcessor
|
||||||
|
{
|
||||||
|
public Type InstanceType { get; }
|
||||||
|
public Guid Token { get; }
|
||||||
|
public string ClientIP { get; }
|
||||||
|
public string ClientName { get; }
|
||||||
|
|
||||||
|
public SocketObject[] Receive();
|
||||||
|
public Task<SocketObject[]> ReceiveAsync();
|
||||||
|
public SocketResult Send(SocketMessageType type, params object[] objs);
|
||||||
|
public Task<SocketResult> SendAsync(SocketMessageType type, params object[] objs);
|
||||||
|
public void Close();
|
||||||
|
public Task CloseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user