From e45361149a3051aed42caae2d31766500ae8e860 Mon Sep 17 00:00:00 2001 From: WhiteFang Date: Sat, 30 Jul 2022 17:11:00 +0200 Subject: [PATCH] Added the current VMods code and a README.md --- .gitignore | 403 +++++++++++++++ BloodRefill/BloodRefill.csproj | 474 +++++++++++++++++ BloodRefill/Configs/BloodRefillConfig.cs | 49 ++ BloodRefill/Hooks/DeathHook.cs | 39 ++ BloodRefill/Plugin.cs | 59 +++ BloodRefill/Systems/BloodRefillSystem.cs | 242 +++++++++ BloodRefill/Systems/BloodType.cs | 63 +++ ExperimentalMod/ExperimentalMod.csproj | 475 +++++++++++++++++ NuGet.Config | 7 + .../Configs/PvPLeaderboardConfig.cs | 28 + PvPLeaderboard/Plugin.cs | 64 +++ PvPLeaderboard/PvPLeaderboard.csproj | 477 +++++++++++++++++ PvPLeaderboard/Shared/CommandExtensions.cs | 38 ++ .../Systems/PvPLeaderboardSystem.cs | 227 +++++++++ PvPPunishment/Configs/PvPPunishmentConfig.cs | 50 ++ PvPPunishment/Plugin.cs | 64 +++ PvPPunishment/PvPPunishment.csproj | 478 ++++++++++++++++++ PvPPunishment/Systems/PvPPunishmentSystem.cs | 279 ++++++++++ README.md | 91 ++++ .../Configs/RecoverEmptyContainersConfig.cs | 22 + .../Hooks/UseConsumableHook.cs | 69 +++ RecoverEmptyContainers/Plugin.cs | 52 ++ .../RecoverEmptyContainers.csproj | 469 +++++++++++++++++ .../Configs/ResourceStashWithdrawalConfig.cs | 22 + ResourceStashWithdrawal/Hooks/UIClickHook.cs | 261 ++++++++++ .../Hooks/UITooltipHook.cs | 377 ++++++++++++++ ResourceStashWithdrawal/Plugin.cs | 50 ++ .../ResourceStashWithdrawal.csproj | 469 +++++++++++++++++ .../Systems/ResourceStashWithdrawalRequest.cs | 8 + .../Systems/ResourceStashWithdrawalSystem.cs | 109 ++++ Shared/BuffSystemHook.cs | 43 ++ Shared/CommandSystem/Command.cs | 34 ++ Shared/CommandSystem/CommandAttribute.cs | 31 ++ Shared/CommandSystem/CommandExtensions.cs | 38 ++ Shared/CommandSystem/CommandSystem.cs | 265 ++++++++++ Shared/CommandSystem/CommandSystemConfig.cs | 26 + Shared/ExtensionMethods.cs | 28 + .../HighestGearScoreSystem/EquipmentHooks.cs | 218 ++++++++ .../HighestGearScoreSystem.cs | 184 +++++++ .../HighestGearScoreSystemConfig.cs | 24 + Shared/SaveHook.cs | 27 + Shared/Shared.csproj | 461 +++++++++++++++++ Shared/Utils.cs | 327 ++++++++++++ Shared/VModStorage.cs | 85 ++++ Shared/VampireDownedHook.cs | 49 ++ VMods.sln | 61 +++ 46 files changed, 7416 insertions(+) create mode 100644 .gitignore create mode 100644 BloodRefill/BloodRefill.csproj create mode 100644 BloodRefill/Configs/BloodRefillConfig.cs create mode 100644 BloodRefill/Hooks/DeathHook.cs create mode 100644 BloodRefill/Plugin.cs create mode 100644 BloodRefill/Systems/BloodRefillSystem.cs create mode 100644 BloodRefill/Systems/BloodType.cs create mode 100644 ExperimentalMod/ExperimentalMod.csproj create mode 100644 NuGet.Config create mode 100644 PvPLeaderboard/Configs/PvPLeaderboardConfig.cs create mode 100644 PvPLeaderboard/Plugin.cs create mode 100644 PvPLeaderboard/PvPLeaderboard.csproj create mode 100644 PvPLeaderboard/Shared/CommandExtensions.cs create mode 100644 PvPLeaderboard/Systems/PvPLeaderboardSystem.cs create mode 100644 PvPPunishment/Configs/PvPPunishmentConfig.cs create mode 100644 PvPPunishment/Plugin.cs create mode 100644 PvPPunishment/PvPPunishment.csproj create mode 100644 PvPPunishment/Systems/PvPPunishmentSystem.cs create mode 100644 README.md create mode 100644 RecoverEmptyContainers/Configs/RecoverEmptyContainersConfig.cs create mode 100644 RecoverEmptyContainers/Hooks/UseConsumableHook.cs create mode 100644 RecoverEmptyContainers/Plugin.cs create mode 100644 RecoverEmptyContainers/RecoverEmptyContainers.csproj create mode 100644 ResourceStashWithdrawal/Configs/ResourceStashWithdrawalConfig.cs create mode 100644 ResourceStashWithdrawal/Hooks/UIClickHook.cs create mode 100644 ResourceStashWithdrawal/Hooks/UITooltipHook.cs create mode 100644 ResourceStashWithdrawal/Plugin.cs create mode 100644 ResourceStashWithdrawal/ResourceStashWithdrawal.csproj create mode 100644 ResourceStashWithdrawal/Systems/ResourceStashWithdrawalRequest.cs create mode 100644 ResourceStashWithdrawal/Systems/ResourceStashWithdrawalSystem.cs create mode 100644 Shared/BuffSystemHook.cs create mode 100644 Shared/CommandSystem/Command.cs create mode 100644 Shared/CommandSystem/CommandAttribute.cs create mode 100644 Shared/CommandSystem/CommandExtensions.cs create mode 100644 Shared/CommandSystem/CommandSystem.cs create mode 100644 Shared/CommandSystem/CommandSystemConfig.cs create mode 100644 Shared/ExtensionMethods.cs create mode 100644 Shared/HighestGearScoreSystem/EquipmentHooks.cs create mode 100644 Shared/HighestGearScoreSystem/HighestGearScoreSystem.cs create mode 100644 Shared/HighestGearScoreSystem/HighestGearScoreSystemConfig.cs create mode 100644 Shared/SaveHook.cs create mode 100644 Shared/Shared.csproj create mode 100644 Shared/Utils.cs create mode 100644 Shared/VModStorage.cs create mode 100644 Shared/VampireDownedHook.cs create mode 100644 VMods.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa2cb80 --- /dev/null +++ b/.gitignore @@ -0,0 +1,403 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +dump/* +build/* +zip/* +*.zip \ No newline at end of file diff --git a/BloodRefill/BloodRefill.csproj b/BloodRefill/BloodRefill.csproj new file mode 100644 index 0000000..989b97b --- /dev/null +++ b/BloodRefill/BloodRefill.csproj @@ -0,0 +1,474 @@ + + + netstandard2.1 + VMods.BloodRefill + VMods.BloodRefill + Allows a player to refill his/her blood pool + 0.0.1 + true + latest + False + + + + M:\Games\Steam\steamapps\common\VRising\BepInEx\unhollowed + M:\Games\Steam\steamapps\common\VRising\BepInEx\WetstonePlugins + M:\Games\Steam\steamapps\common\VRising\VRising_Server\BepInEx\WetstonePlugins + + + + + + + + + + + + + + + + + + + + + + + + + $(UnhollowedDllPath)\com.stunlock.console.dll + + + $(UnhollowedDllPath)\com.stunlock.metrics.dll + + + $(UnhollowedDllPath)\com.stunlock.network.lidgren.dll + + + $(UnhollowedDllPath)\com.stunlock.network.steam.dll + + + $(UnhollowedDllPath)\Il2CppMono.Security.dll + + + $(UnhollowedDllPath)\Il2CppSystem.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Configuration.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Core.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Data.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Numerics.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Runtime.Serialization.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.Linq.dll + + + $(UnhollowedDllPath)\Lidgren.Network.dll + + + $(UnhollowedDllPath)\MagicaCloth.dll + + + $(UnhollowedDllPath)\Malee.ReorderableList.dll + + + $(UnhollowedDllPath)\Newtonsoft.Json.dll + + + $(UnhollowedDllPath)\ProjectM.Behaviours.dll + + + $(UnhollowedDllPath)\ProjectM.Camera.dll + + + $(UnhollowedDllPath)\ProjectM.CastleBuilding.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Conversion.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Scripting.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.GeneratedNetCode.dll + + + $(UnhollowedDllPath)\ProjectM.Misc.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Pathfinding.dll + + + $(UnhollowedDllPath)\ProjectM.Presentation.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Roofs.dll + + + $(UnhollowedDllPath)\ProjectM.ScriptableSystems.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.dll + + + $(UnhollowedDllPath)\Il2Cppmscorlib.dll + + + $(UnhollowedDllPath)\ProjectM.dll + + + $(UnhollowedDllPath)\com.stunlock.network.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Terrain.dll + + + $(UnhollowedDllPath)\RootMotion.dll + + + $(UnhollowedDllPath)\Sequencer.dll + + + $(UnhollowedDllPath)\Stunlock.Fmod.dll + + + $(UnhollowedDllPath)\Unity.Burst.dll + + + $(UnhollowedDllPath)\Unity.Burst.Unsafe.dll + + + $(UnhollowedDllPath)\Unity.Collections.dll + + + $(UnhollowedDllPath)\Unity.Collections.LowLevel.ILSupport.dll + + + $(UnhollowedDllPath)\Unity.Deformations.dll + + + $(UnhollowedDllPath)\Unity.Entities.dll + + + $(UnhollowedDllPath)\ProjectM.HUD.dll + + + $(UnhollowedDllPath)\Unity.Entities.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Jobs.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Physics.dll + + + $(UnhollowedDllPath)\Unity.Physics.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Properties.dll + + + $(UnhollowedDllPath)\Unity.Rendering.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.Core.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Runtime.dll + + + $(UnhollowedDllPath)\Unity.Scenes.dll + + + $(UnhollowedDllPath)\Unity.Serialization.dll + + + $(UnhollowedDllPath)\Unity.Services.Analytics.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Configuration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Device.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Registration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Scheduler.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Telemetry.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Threading.dll + + + $(UnhollowedDllPath)\Unity.TextMeshPro.dll + + + $(UnhollowedDllPath)\Unity.Transforms.dll + + + $(UnhollowedDllPath)\Unity.Transforms.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.VisualEffectGraph.Runtime.dll + + + $(UnhollowedDllPath)\UnityEngine.dll + + + $(UnhollowedDllPath)\UnityEngine.AccessibilityModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AndroidJNIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AnimationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ARModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClothModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterInputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterRendererModule.dll + + + $(UnhollowedDllPath)\UnityEngine.CoreModule.dll + + + $(UnhollowedDllPath)\ProjectM.CodeGeneration.dll + + + $(UnhollowedDllPath)\Stunlock.Core.dll + + + $(UnhollowedDllPath)\UnityEngine.CrashReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DirectorModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DSPGraphModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GameCenterModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GridModule.dll + + + $(UnhollowedDllPath)\UnityEngine.HotReloadModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ImageConversionModule.dll + + + $(UnhollowedDllPath)\UnityEngine.IMGUIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputLegacyModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.JSONSerializeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.LocalizationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ParticleSystemModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PerformanceReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.Physics2DModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ProfilerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ScreenCaptureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SharedInternalsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteMaskModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteShapeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.StreamingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubstanceModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubsystemsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainPhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextCoreModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextRenderingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TilemapModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TLSModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UI.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsNativeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UmbraModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UNETModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityAnalyticsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityConnectModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityCurlModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityTestProtocolModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestTextureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestWWWModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VehiclesModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VFXModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VideoModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VirtualTexturingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VRModule.dll + + + $(UnhollowedDllPath)\UnityEngine.WindModule.dll + + + $(UnhollowedDllPath)\UnityEngine.XRModule.dll + + + $(UnhollowedDllPath)\VivoxUnity.dll + + + + + + + diff --git a/BloodRefill/Configs/BloodRefillConfig.cs b/BloodRefill/Configs/BloodRefillConfig.cs new file mode 100644 index 0000000..62171be --- /dev/null +++ b/BloodRefill/Configs/BloodRefillConfig.cs @@ -0,0 +1,49 @@ +using BepInEx.Configuration; + +namespace VMods.BloodRefill +{ + public static class BloodRefillConfig + { + #region Properties + + public static ConfigEntry BloodRefillEnabled { get; private set; } + + public static ConfigEntry BloodRefillRequiresFeeding { get; private set; } + + public static ConfigEntry BloodRefillRequiresSameBloodType { get; private set; } + + public static ConfigEntry BloodRefillExcludeVBloodFromSameBloodTypeCheck { get; private set; } + + public static ConfigEntry BloodRefillDifferentBloodTypeMultiplier { get; private set; } + + public static ConfigEntry BloodRefillVBloodRefillType { get; private set; } + + public static ConfigEntry BloodRefillVBloodRefillMultiplier { get; private set; } + + public static ConfigEntry BloodRefillRandomRefill { get; private set; } + + public static ConfigEntry BloodRefillAmount { get; private set; } + + public static ConfigEntry BloodRefillMultiplier { get; private set; } + + #endregion + + #region Public Methods + + public static void Initialize(ConfigFile config) + { + BloodRefillEnabled = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillEnabled), false, "Enabled/disable the blood refilling system."); + BloodRefillRequiresFeeding = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillRequiresFeeding), true, "When enabled, blood can only be refilled when feeding (i.e. when aborting the feed)."); + BloodRefillRequiresSameBloodType = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillRequiresSameBloodType), false, "When enabled, blood can only be refilled when the target has the same blood type."); + BloodRefillExcludeVBloodFromSameBloodTypeCheck = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillExcludeVBloodFromSameBloodTypeCheck), true, "When enabled, V-blood is excluded from the 'same blood type' check (i.e. it's always considered to be 'the same blood type' as the player's blood type)."); + BloodRefillVBloodRefillType = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillVBloodRefillType), 2, "0 = disabled (i.e. normal refill); 1 = fully refill; 2 = refill based on V-blood monster level."); + BloodRefillVBloodRefillMultiplier = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillVBloodRefillMultiplier), 0.1f, $"[Only applies when {nameof(BloodRefillVBloodRefillType)} is set to 2] The multiplier used in the v-blood refill calculation ('EnemyLevel' * '{nameof(BloodRefillVBloodRefillMultiplier)}' * '{nameof(BloodRefillMultiplier)}')."); + BloodRefillRandomRefill = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillRandomRefill), true, "When enabled, the amount of refilled blood is randomized (between 1 and the calculated refillable amount)."); + BloodRefillAmount = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillAmount), 2.0f, "The maximum amount of blood to refill with no level difference, a matching blood type and quality (Expressed in Litres of blood)."); + BloodRefillMultiplier = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillMultiplier), 1.0f, $"The multiplier used in the blood refill calculation. [Formula: (('Enemy Level' / 'Player Level') * ((100 - ('Player Blood Quality %' - 'Enemy Blood Quality %')) / 100)) * '{nameof(BloodRefillAmount)}' * '(If applicable) {nameof(BloodRefillDifferentBloodTypeMultiplier)}' * '{nameof(BloodRefillMultiplier)}']"); + BloodRefillDifferentBloodTypeMultiplier = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillDifferentBloodTypeMultiplier), 0.1f, $"The multiplier used in the blood refill calculation as a penalty for feeding on a different blood type (only works when {nameof(BloodRefillRequiresSameBloodType)} is disabled)."); + } + + #endregion + } +} diff --git a/BloodRefill/Hooks/DeathHook.cs b/BloodRefill/Hooks/DeathHook.cs new file mode 100644 index 0000000..ef437cc --- /dev/null +++ b/BloodRefill/Hooks/DeathHook.cs @@ -0,0 +1,39 @@ +using HarmonyLib; +using ProjectM; +using Unity.Collections; +using Wetstone.API; + +namespace VMods.BloodRefill +{ + [HarmonyPatch] + public static class DeathHook + { + #region Events + + public delegate void DeathEventHandler(DeathEvent deathEvent); + public static event DeathEventHandler DeathEvent; + private static void FireDeathEvent(DeathEvent deathEvent) => DeathEvent?.Invoke(deathEvent); + + #endregion + + #region Public Methods + + [HarmonyPatch(typeof(DeathEventListenerSystem), nameof(DeathEventListenerSystem.OnUpdate))] + [HarmonyPostfix] + private static void OnUpdate(DeathEventListenerSystem __instance) + { + if(!VWorld.IsServer || __instance._DeathEventQuery == null) + { + return; + } + + NativeArray deathEvents = __instance._DeathEventQuery.ToComponentDataArray(Allocator.Temp); + foreach(DeathEvent deathEvent in deathEvents) + { + FireDeathEvent(deathEvent); + } + } + + #endregion + } +} diff --git a/BloodRefill/Plugin.cs b/BloodRefill/Plugin.cs new file mode 100644 index 0000000..8c1ccd9 --- /dev/null +++ b/BloodRefill/Plugin.cs @@ -0,0 +1,59 @@ +using BepInEx; +using BepInEx.IL2CPP; +using HarmonyLib; +using System.Reflection; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.BloodRefill +{ + [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] + [BepInDependency("xyz.molenzwiebel.wetstone")] + [Reloadable] + public class Plugin : BasePlugin + { + #region Variables + + private Harmony _hooks; + + #endregion + + #region Public Methods + + public sealed override void Load() + { + if(VWorld.IsClient) + { + Log.LogMessage($"{PluginInfo.PLUGIN_NAME} only needs to be installed server side."); + return; + } + Utils.Initialize(Log, PluginInfo.PLUGIN_NAME); + + CommandSystemConfig.Initialize(Config); + BloodRefillConfig.Initialize(Config); + + CommandSystem.Initialize(); + BloodRefillSystem.Initialize(); + + _hooks = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly()); + + Log.LogInfo($"Plugin {PluginInfo.PLUGIN_NAME} (v{PluginInfo.PLUGIN_VERSION}) is loaded!"); + } + + public sealed override bool Unload() + { + if(VWorld.IsClient) + { + return true; + } + _hooks?.UnpatchSelf(); + BloodRefillSystem.Deinitialize(); + CommandSystem.Deinitialize(); + Config.Clear(); + Utils.Deinitialize(); + return true; + } + + #endregion + } +} diff --git a/BloodRefill/Systems/BloodRefillSystem.cs b/BloodRefill/Systems/BloodRefillSystem.cs new file mode 100644 index 0000000..0e7ff69 --- /dev/null +++ b/BloodRefill/Systems/BloodRefillSystem.cs @@ -0,0 +1,242 @@ +using ProjectM; +using ProjectM.Network; +using System; +using System.Linq; +using Unity.Entities; +using VMods.Shared; +using Wetstone.API; +using Random = UnityEngine.Random; + +namespace VMods.BloodRefill +{ + public static class BloodRefillSystem + { + #region Public Methods + + public static void Initialize() + { + DeathHook.DeathEvent += OnDeath; + } + + public static void Deinitialize() + { + DeathHook.DeathEvent -= OnDeath; + } + + #endregion + + #region Private Methods + + private static void OnDeath(DeathEvent deathEvent) + { + EntityManager entityManager = VWorld.Server.EntityManager; + + // Make sure a player killed an appropriate monster + if(!BloodRefillConfig.BloodRefillEnabled.Value || + !entityManager.HasComponent(deathEvent.Killer) || + !entityManager.HasComponent(deathEvent.Killer) || + !entityManager.HasComponent(deathEvent.Killer) || + !entityManager.HasComponent(deathEvent.Died) || + !entityManager.HasComponent(deathEvent.Died) || + !entityManager.HasComponent(deathEvent.Died)) + { + return; + } + + PlayerCharacter playerCharacter = entityManager.GetComponentData(deathEvent.Killer); + Equipment playerEquipment = entityManager.GetComponentData(deathEvent.Killer); + Blood playerBlood = entityManager.GetComponentData(deathEvent.Killer); + UnitLevel unitLevel = entityManager.GetComponentData(deathEvent.Died); + BloodConsumeSource bloodConsumeSource = entityManager.GetComponentData(deathEvent.Died); + +#if DEBUG + Utils.Logger.LogMessage($"DE.Killer = {deathEvent.Killer.Index}"); + Utils.Logger.LogMessage($"DE.Died = {deathEvent.Died.Index}"); + Utils.Logger.LogMessage($"DE.Source = {deathEvent.Source.Index}"); +#endif + + Entity userEntity = playerCharacter.UserEntity._Entity; + User user = entityManager.GetComponentData(userEntity); + + bool killedByFeeding = deathEvent.Killer.Index == deathEvent.Source.Index; + + if(!playerBlood.BloodType.ParseBloodType(out BloodType playerBloodType)) + { + // Invalid/unknown blood type + return; + } + + if(!bloodConsumeSource.UnitBloodType.ParseBloodType(out BloodType bloodType)) + { + // Invalid/unknown blood type + return; + } + + bool isVBlood = bloodType == BloodType.VBlood; + + // Allow V-Bloods to skip the 'killed by feeding' check, otherwise additional feeders won't get a refill. + if(!isVBlood && BloodRefillConfig.BloodRefillRequiresFeeding.Value && !killedByFeeding) + { + // Can only gain blood when killing the enemy while feeding (i.e. abort the feed) + return; + } + + bool isSameBloodType = playerBloodType == bloodType || (BloodRefillConfig.BloodRefillExcludeVBloodFromSameBloodTypeCheck.Value && isVBlood); + + if(BloodRefillConfig.BloodRefillRequiresSameBloodType.Value && !isSameBloodType) + { + // Can only gain blood when killing an enemy of the same blood type + return; + } + + float bloodTypeMultiplier = isSameBloodType ? 1f : BloodRefillConfig.BloodRefillDifferentBloodTypeMultiplier.Value; + + float playerLevel = playerEquipment.WeaponLevel + playerEquipment.ArmorLevel + playerEquipment.SpellLevel; + float enemyLevel = unitLevel.Level; + +#if DEBUG + Utils.Logger.LogMessage($"Player Blood Quality: {playerBlood.Quality}"); + Utils.Logger.LogMessage($"Player Blood Value: {playerBlood.Value}"); + Utils.Logger.LogMessage($"Player Level: {playerLevel}"); + + Utils.Logger.LogMessage($"Enemy Blood Quality: {bloodConsumeSource.BloodQuality}"); + Utils.Logger.LogMessage($"Enemy Level {enemyLevel}"); +#endif + + float levelRatio = enemyLevel / playerLevel; + + float qualityRatio = (100f - (playerBlood.Quality - bloodConsumeSource.BloodQuality)) / 100f; + + float refillRatio = levelRatio * qualityRatio; + + // Config amount is expressed in 'Litres of blood' -> the game's formule is 'blood value / 10', hence the * 10 multiplier here. + float refillAmount = BloodRefillConfig.BloodRefillAmount.Value * 10f * refillRatio; + + refillAmount *= bloodTypeMultiplier; + +#if DEBUG + Utils.Logger.LogMessage($"Lvl Ratio: {levelRatio}"); + Utils.Logger.LogMessage($"Quality Ratio: {qualityRatio}"); + Utils.Logger.LogMessage($"Refill Ratio: {refillRatio}"); + Utils.Logger.LogMessage($"Blood Type Multiplier: {bloodTypeMultiplier}"); + Utils.Logger.LogMessage($"Refill Amount: {refillAmount}"); +#endif + + if(BloodRefillConfig.BloodRefillRandomRefill.Value) + { + refillAmount = Random.RandomRange(1f, refillAmount); + +#if DEBUG + Utils.Logger.LogMessage($"Refill Roll: {refillAmount}"); +#endif + } + + if(isVBlood) + { + switch(BloodRefillConfig.BloodRefillVBloodRefillType.Value) + { + case 1: // V-blood fully refills the blood pool + refillAmount = playerBlood.MaxBlood - playerBlood.Value; + break; + + case 2: // V-blood refills based on the unit's level + refillAmount = enemyLevel * BloodRefillConfig.BloodRefillVBloodRefillMultiplier.Value; + break; + } + } + + refillAmount *= BloodRefillConfig.BloodRefillMultiplier.Value; + + if(refillAmount > 0f) + { + int roundedRefillAmount = (int)Math.Ceiling(refillAmount); + + if(roundedRefillAmount > 0) + { +#if DEBUG + Utils.Logger.LogMessage($"New Blood Amount: {playerBlood.Value + roundedRefillAmount}"); +#endif + + float newTotalBlood = Math.Min(playerBlood.MaxBlood, playerBlood.Value + roundedRefillAmount); + float actualBloodGained = newTotalBlood - playerBlood.Value; + float refillAmountInLitres = (int)(actualBloodGained * 10f) / 100f; + float newTotalBloodInLitres = (int)Math.Round(newTotalBlood) / 10f; + Utils.SendMessage(userEntity, $"+{refillAmountInLitres}L Blood ({newTotalBloodInLitres}L)", ServerChatMessageType.Lore); + + ChangeBloodType(user, playerBloodType, playerBlood.Quality, roundedRefillAmount); + return; + } + } + + Utils.SendMessage(userEntity, $"No blood gained from the enemy.", ServerChatMessageType.Lore); + } + + private static void ChangeBloodType(User user, BloodType bloodType, float quality, int addAmount) + { + ChangeBloodDebugEvent bloodChangeEvent = new() + { + Source = bloodType.ToPrefabGUID(), + Quality = quality, + Amount = addAmount, + }; + VWorld.Server.GetExistingSystem().ChangeBloodEvent(user.Index, ref bloodChangeEvent); + } + + [Command("setblood", "setblood []", "Sets your blood type to the specified blood-type and blood-quality, and optionally adds a given amount of blood (in Litres).", true)] + private static void OnSetBloodCommand(Command command) + { + var user = command.User; + var argCount = command.Args.Length; + if(argCount >= 2) + { + var searchBloodType = command.Args[0]; + var validBloodTypes = BloodTypeExtensions.BloodTypeToPrefabGUIDMapping.Keys.ToList(); + if(Enum.TryParse(searchBloodType.ToLowerInvariant(), true, out BloodType bloodType) && validBloodTypes.Contains(bloodType)) + { + var searchBloodQuality = command.Args[1]; + if(int.TryParse(searchBloodQuality.Replace("%", string.Empty), out var bloodQuality) && bloodQuality >= 1 && bloodQuality <= 100) + { + float? addBloodAmount = null; + if(argCount >= 3) + { + var searchLitres = command.Args[2]; + if(float.TryParse(searchLitres.Replace("L", string.Empty), out float parsedAddBloodAmount) && parsedAddBloodAmount >= -10f && parsedAddBloodAmount <= 10f) + { + addBloodAmount = parsedAddBloodAmount; + } + else + { + user.SendSystemMessage($"Invalid gain-amount '{searchBloodQuality}'. Should be between -10 and 10"); + } + } + else + { + addBloodAmount = 10f; + } + + if(addBloodAmount.HasValue) + { + ChangeBloodType(user, bloodType, bloodQuality, (int)(addBloodAmount.Value * 10f)); + user.SendSystemMessage($"Changed blood type to {bloodQuality}% {searchBloodType} and added {addBloodAmount.Value}L"); + } + } + else + { + user.SendSystemMessage($"Invalid blood-quality '{searchBloodQuality}'. Should be between 1 and 100"); + } + } + else + { + user.SendSystemMessage($"Invalid blood-type '{searchBloodType}'. Options are: {string.Join(", ", validBloodTypes.Select(x => x.ToString()))}"); + } + } + else + { + CommandSystem.SendInvalidCommandMessage(command); + } + command.Use(); + } + + #endregion + } +} diff --git a/BloodRefill/Systems/BloodType.cs b/BloodRefill/Systems/BloodType.cs new file mode 100644 index 0000000..1ca1aa2 --- /dev/null +++ b/BloodRefill/Systems/BloodType.cs @@ -0,0 +1,63 @@ +using ProjectM; +using System; +using System.Collections.Generic; + +namespace VMods.BloodRefill +{ + public enum BloodType + { + Frailed = -899826404, + Creature = -77658840, + Warrior = -1094467405, + Rogue = 793735874, + Brute = 581377887, + Scholar = -586506765, + Worker = -540707191, + VBlood = 1557174542, + } + + public static class BloodTypeExtensions + { + #region Consts + + public static readonly Dictionary BloodTypeToPrefabGUIDMapping = new() + { + [BloodType.Creature] = new PrefabGUID(1897056612), + [BloodType.Warrior] = new PrefabGUID(-1128238456), + [BloodType.Rogue] = new PrefabGUID(-1030822544), + [BloodType.Brute] = new PrefabGUID(-1464869978), + [BloodType.Scholar] = new PrefabGUID(-700632469), + [BloodType.Worker] = new PrefabGUID(-1342764880), + }; + + #endregion + + #region Public Methods + + public static bool ParseBloodType(this PrefabGUID prefabGUID, out BloodType bloodType) + { + int guidHash = prefabGUID.GuidHash; + if(!Enum.IsDefined(typeof(BloodType), guidHash)) + { + bloodType = BloodType.Frailed; + return false; + } + bloodType = (BloodType)guidHash; + return true; + } + + public static BloodType? ToBloodType(this PrefabGUID prefabGUID) + { + int guidHash = prefabGUID.GuidHash; + if(!Enum.IsDefined(typeof(BloodType), guidHash)) + { + return null; + } + return (BloodType)guidHash; + } + + public static PrefabGUID ToPrefabGUID(this BloodType bloodType) => BloodTypeToPrefabGUIDMapping[bloodType]; + + #endregion + } +} diff --git a/ExperimentalMod/ExperimentalMod.csproj b/ExperimentalMod/ExperimentalMod.csproj new file mode 100644 index 0000000..84646f9 --- /dev/null +++ b/ExperimentalMod/ExperimentalMod.csproj @@ -0,0 +1,475 @@ + + + netstandard2.1 + VMods.ExperimentalMod + VMods.ExperimentalMod + A mod that only contains experimental/debug code in order to further develop other mods + 0.0.1 + true + latest + False + + + + M:\Games\Steam\steamapps\common\VRising\BepInEx\unhollowed + M:\Games\Steam\steamapps\common\VRising\BepInEx\WetstonePlugins + M:\Games\Steam\steamapps\common\VRising\VRising_Server\BepInEx\WetstonePlugins + + + + + + + + + + + + + + + + + + + + + + + + + + $(UnhollowedDllPath)\com.stunlock.console.dll + + + $(UnhollowedDllPath)\com.stunlock.metrics.dll + + + $(UnhollowedDllPath)\com.stunlock.network.lidgren.dll + + + $(UnhollowedDllPath)\com.stunlock.network.steam.dll + + + $(UnhollowedDllPath)\Il2CppMono.Security.dll + + + $(UnhollowedDllPath)\Il2CppSystem.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Configuration.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Core.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Data.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Numerics.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Runtime.Serialization.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.Linq.dll + + + $(UnhollowedDllPath)\Lidgren.Network.dll + + + $(UnhollowedDllPath)\MagicaCloth.dll + + + $(UnhollowedDllPath)\Malee.ReorderableList.dll + + + $(UnhollowedDllPath)\Newtonsoft.Json.dll + + + $(UnhollowedDllPath)\ProjectM.Behaviours.dll + + + $(UnhollowedDllPath)\ProjectM.Camera.dll + + + $(UnhollowedDllPath)\ProjectM.CastleBuilding.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Conversion.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Scripting.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.GeneratedNetCode.dll + + + $(UnhollowedDllPath)\ProjectM.Misc.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Pathfinding.dll + + + $(UnhollowedDllPath)\ProjectM.Presentation.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Roofs.dll + + + $(UnhollowedDllPath)\ProjectM.ScriptableSystems.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.dll + + + $(UnhollowedDllPath)\Il2Cppmscorlib.dll + + + $(UnhollowedDllPath)\ProjectM.dll + + + $(UnhollowedDllPath)\com.stunlock.network.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Terrain.dll + + + $(UnhollowedDllPath)\RootMotion.dll + + + $(UnhollowedDllPath)\Sequencer.dll + + + $(UnhollowedDllPath)\Stunlock.Fmod.dll + + + $(UnhollowedDllPath)\Unity.Burst.dll + + + $(UnhollowedDllPath)\Unity.Burst.Unsafe.dll + + + $(UnhollowedDllPath)\Unity.Collections.dll + + + $(UnhollowedDllPath)\Unity.Collections.LowLevel.ILSupport.dll + + + $(UnhollowedDllPath)\Unity.Deformations.dll + + + $(UnhollowedDllPath)\Unity.Entities.dll + + + $(UnhollowedDllPath)\ProjectM.HUD.dll + + + $(UnhollowedDllPath)\Unity.Entities.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Jobs.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Physics.dll + + + $(UnhollowedDllPath)\Unity.Physics.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Properties.dll + + + $(UnhollowedDllPath)\Unity.Rendering.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.Core.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Runtime.dll + + + $(UnhollowedDllPath)\Unity.Scenes.dll + + + $(UnhollowedDllPath)\Unity.Serialization.dll + + + $(UnhollowedDllPath)\Unity.Services.Analytics.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Configuration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Device.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Registration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Scheduler.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Telemetry.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Threading.dll + + + $(UnhollowedDllPath)\Unity.TextMeshPro.dll + + + $(UnhollowedDllPath)\Unity.Transforms.dll + + + $(UnhollowedDllPath)\Unity.Transforms.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.VisualEffectGraph.Runtime.dll + + + $(UnhollowedDllPath)\UnityEngine.dll + + + $(UnhollowedDllPath)\UnityEngine.AccessibilityModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AndroidJNIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AnimationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ARModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClothModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterInputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterRendererModule.dll + + + $(UnhollowedDllPath)\UnityEngine.CoreModule.dll + + + $(UnhollowedDllPath)\ProjectM.CodeGeneration.dll + + + $(UnhollowedDllPath)\Stunlock.Core.dll + + + $(UnhollowedDllPath)\UnityEngine.CrashReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DirectorModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DSPGraphModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GameCenterModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GridModule.dll + + + $(UnhollowedDllPath)\UnityEngine.HotReloadModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ImageConversionModule.dll + + + $(UnhollowedDllPath)\UnityEngine.IMGUIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputLegacyModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.JSONSerializeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.LocalizationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ParticleSystemModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PerformanceReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.Physics2DModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ProfilerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ScreenCaptureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SharedInternalsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteMaskModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteShapeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.StreamingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubstanceModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubsystemsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainPhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextCoreModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextRenderingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TilemapModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TLSModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UI.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsNativeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UmbraModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UNETModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityAnalyticsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityConnectModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityCurlModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityTestProtocolModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestTextureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestWWWModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VehiclesModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VFXModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VideoModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VirtualTexturingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VRModule.dll + + + $(UnhollowedDllPath)\UnityEngine.WindModule.dll + + + $(UnhollowedDllPath)\UnityEngine.XRModule.dll + + + $(UnhollowedDllPath)\VivoxUnity.dll + + + + + + + diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..8f0078f --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/PvPLeaderboard/Configs/PvPLeaderboardConfig.cs b/PvPLeaderboard/Configs/PvPLeaderboardConfig.cs new file mode 100644 index 0000000..26ca912 --- /dev/null +++ b/PvPLeaderboard/Configs/PvPLeaderboardConfig.cs @@ -0,0 +1,28 @@ +using BepInEx.Configuration; + +namespace VMods.PvPLeaderboard +{ + public static class PvPLeaderboardConfig + { + #region Properties + + public static ConfigEntry PvPLeaderboardEnabled { get; private set; } + public static ConfigEntry PvPLeaderboardLevelDifference { get; private set; } + public static ConfigEntry PvPLeaderboardAnnounceKill { get; private set; } + public static ConfigEntry PvPLeaderboardAnnounceLowLevelKill { get; private set; } + + #endregion + + #region Public Methods + + public static void Initialize(ConfigFile config) + { + PvPLeaderboardEnabled = config.Bind(nameof(PvPLeaderboardConfig), nameof(PvPLeaderboardEnabled), false, "Enabled/disable the PvP Leaderboard system."); + PvPLeaderboardLevelDifference = config.Bind(nameof(PvPLeaderboardConfig), nameof(PvPLeaderboardLevelDifference), 10, "The level difference at which the K/D isn't counting anymore for the leaderboard."); + PvPLeaderboardAnnounceKill = config.Bind(nameof(PvPLeaderboardConfig), nameof(PvPLeaderboardAnnounceKill), true, "When enabled, a legitemate kill is announced server-wide."); + PvPLeaderboardAnnounceLowLevelKill = config.Bind(nameof(PvPLeaderboardConfig), nameof(PvPLeaderboardAnnounceLowLevelKill), false, "When enabled, a kill of a lower level player is announced server-wide."); + } + + #endregion + } +} diff --git a/PvPLeaderboard/Plugin.cs b/PvPLeaderboard/Plugin.cs new file mode 100644 index 0000000..8eb34e4 --- /dev/null +++ b/PvPLeaderboard/Plugin.cs @@ -0,0 +1,64 @@ +using BepInEx; +using BepInEx.IL2CPP; +using HarmonyLib; +using System.Reflection; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.PvPLeaderboard +{ + [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] + [BepInDependency("xyz.molenzwiebel.wetstone")] + [Reloadable] + public class Plugin : BasePlugin + { + #region Variables + + private Harmony _hooks; + + #endregion + + #region Public Methods + + public sealed override void Load() + { + if(VWorld.IsClient) + { + Log.LogMessage($"{PluginInfo.PLUGIN_NAME} only needs to be installed server side."); + return; + } + Utils.Initialize(Log, PluginInfo.PLUGIN_NAME); + + CommandSystemConfig.Initialize(Config); + HighestGearScoreSystemConfig.Initialize(Config); + PvPLeaderboardConfig.Initialize(Config); + + CommandSystem.Initialize(); + HighestGearScoreSystem.Initialize(); + PvPLeaderboardSystem.Initialize(); + + _hooks = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly()); + + Log.LogInfo($"Plugin {PluginInfo.PLUGIN_NAME} (v{PluginInfo.PLUGIN_VERSION}) is loaded!"); + } + + public sealed override bool Unload() + { + if(VWorld.IsClient) + { + return true; + } + VModStorage.SaveAll(); + + _hooks?.UnpatchSelf(); + PvPLeaderboardSystem.Deinitialize(); + HighestGearScoreSystem.Deinitialize(); + CommandSystem.Deinitialize(); + Config.Clear(); + Utils.Deinitialize(); + return true; + } + + #endregion + } +} diff --git a/PvPLeaderboard/PvPLeaderboard.csproj b/PvPLeaderboard/PvPLeaderboard.csproj new file mode 100644 index 0000000..9d4cb71 --- /dev/null +++ b/PvPLeaderboard/PvPLeaderboard.csproj @@ -0,0 +1,477 @@ + + + netstandard2.1 + VMods.PvPLeaderboard + VMods.PvPLeaderboard + A mod that keeps track of player's K/D and adds a pvp leaderboard + 0.0.1 + true + latest + False + + + + M:\Games\Steam\steamapps\common\VRising\BepInEx\unhollowed + M:\Games\Steam\steamapps\common\VRising\BepInEx\WetstonePlugins + M:\Games\Steam\steamapps\common\VRising\VRising_Server\BepInEx\WetstonePlugins + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(UnhollowedDllPath)\com.stunlock.console.dll + + + $(UnhollowedDllPath)\com.stunlock.metrics.dll + + + $(UnhollowedDllPath)\com.stunlock.network.lidgren.dll + + + $(UnhollowedDllPath)\com.stunlock.network.steam.dll + + + $(UnhollowedDllPath)\Il2CppMono.Security.dll + + + $(UnhollowedDllPath)\Il2CppSystem.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Configuration.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Core.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Data.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Numerics.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Runtime.Serialization.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.Linq.dll + + + $(UnhollowedDllPath)\Lidgren.Network.dll + + + $(UnhollowedDllPath)\MagicaCloth.dll + + + $(UnhollowedDllPath)\Malee.ReorderableList.dll + + + $(UnhollowedDllPath)\Newtonsoft.Json.dll + + + $(UnhollowedDllPath)\ProjectM.Behaviours.dll + + + $(UnhollowedDllPath)\ProjectM.Camera.dll + + + $(UnhollowedDllPath)\ProjectM.CastleBuilding.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Conversion.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Scripting.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.GeneratedNetCode.dll + + + $(UnhollowedDllPath)\ProjectM.Misc.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Pathfinding.dll + + + $(UnhollowedDllPath)\ProjectM.Presentation.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Roofs.dll + + + $(UnhollowedDllPath)\ProjectM.ScriptableSystems.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.dll + + + $(UnhollowedDllPath)\Il2Cppmscorlib.dll + + + $(UnhollowedDllPath)\ProjectM.dll + + + $(UnhollowedDllPath)\com.stunlock.network.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Terrain.dll + + + $(UnhollowedDllPath)\RootMotion.dll + + + $(UnhollowedDllPath)\Sequencer.dll + + + $(UnhollowedDllPath)\Stunlock.Fmod.dll + + + $(UnhollowedDllPath)\Unity.Burst.dll + + + $(UnhollowedDllPath)\Unity.Burst.Unsafe.dll + + + $(UnhollowedDllPath)\Unity.Collections.dll + + + $(UnhollowedDllPath)\Unity.Collections.LowLevel.ILSupport.dll + + + $(UnhollowedDllPath)\Unity.Deformations.dll + + + $(UnhollowedDllPath)\Unity.Entities.dll + + + $(UnhollowedDllPath)\ProjectM.HUD.dll + + + $(UnhollowedDllPath)\Unity.Entities.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Jobs.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Physics.dll + + + $(UnhollowedDllPath)\Unity.Physics.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Properties.dll + + + $(UnhollowedDllPath)\Unity.Rendering.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.Core.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Runtime.dll + + + $(UnhollowedDllPath)\Unity.Scenes.dll + + + $(UnhollowedDllPath)\Unity.Serialization.dll + + + $(UnhollowedDllPath)\Unity.Services.Analytics.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Configuration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Device.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Registration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Scheduler.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Telemetry.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Threading.dll + + + $(UnhollowedDllPath)\Unity.TextMeshPro.dll + + + $(UnhollowedDllPath)\Unity.Transforms.dll + + + $(UnhollowedDllPath)\Unity.Transforms.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.VisualEffectGraph.Runtime.dll + + + $(UnhollowedDllPath)\UnityEngine.dll + + + $(UnhollowedDllPath)\UnityEngine.AccessibilityModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AndroidJNIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AnimationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ARModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClothModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterInputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterRendererModule.dll + + + $(UnhollowedDllPath)\UnityEngine.CoreModule.dll + + + $(UnhollowedDllPath)\ProjectM.CodeGeneration.dll + + + $(UnhollowedDllPath)\Stunlock.Core.dll + + + $(UnhollowedDllPath)\UnityEngine.CrashReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DirectorModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DSPGraphModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GameCenterModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GridModule.dll + + + $(UnhollowedDllPath)\UnityEngine.HotReloadModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ImageConversionModule.dll + + + $(UnhollowedDllPath)\UnityEngine.IMGUIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputLegacyModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.JSONSerializeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.LocalizationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ParticleSystemModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PerformanceReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.Physics2DModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ProfilerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ScreenCaptureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SharedInternalsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteMaskModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteShapeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.StreamingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubstanceModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubsystemsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainPhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextCoreModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextRenderingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TilemapModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TLSModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UI.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsNativeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UmbraModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UNETModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityAnalyticsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityConnectModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityCurlModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityTestProtocolModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestTextureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestWWWModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VehiclesModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VFXModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VideoModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VirtualTexturingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VRModule.dll + + + $(UnhollowedDllPath)\UnityEngine.WindModule.dll + + + $(UnhollowedDllPath)\UnityEngine.XRModule.dll + + + $(UnhollowedDllPath)\VivoxUnity.dll + + + diff --git a/PvPLeaderboard/Shared/CommandExtensions.cs b/PvPLeaderboard/Shared/CommandExtensions.cs new file mode 100644 index 0000000..1e8424e --- /dev/null +++ b/PvPLeaderboard/Shared/CommandExtensions.cs @@ -0,0 +1,38 @@ +using ProjectM.Network; +using Unity.Entities; +using Wetstone.API; + +namespace VMods.Shared +{ + public static class CommandExtensions + { + public static (string searchUsername, FromCharacter? fromCharacter) GetFromCharacter(this Command command, int argIdx = 0, bool sendCannotBeFoundMessage = true, EntityManager? entityManager = null) + { + FromCharacter? fromCharacter; + string searchUsername; + + entityManager ??= Utils.CurrentWorld.EntityManager; + + if(argIdx >= 0 && command.Args.Length >= (argIdx + 1)) + { + searchUsername = command.Args[0]; + fromCharacter = Utils.GetFromCharacter(searchUsername, entityManager); + } + else + { + searchUsername = command.User.CharacterName.ToString(); + fromCharacter = new FromCharacter() + { + User = command.SenderUserEntity, + Character = command.SenderCharEntity, + }; + } + + if(sendCannotBeFoundMessage && !fromCharacter.HasValue) + { + command.User.SendSystemMessage($"Vampire {searchUsername} couldn't be found."); + } + return (searchUsername, fromCharacter); + } + } +} diff --git a/PvPLeaderboard/Systems/PvPLeaderboardSystem.cs b/PvPLeaderboard/Systems/PvPLeaderboardSystem.cs new file mode 100644 index 0000000..8a844d9 --- /dev/null +++ b/PvPLeaderboard/Systems/PvPLeaderboardSystem.cs @@ -0,0 +1,227 @@ +using ProjectM; +using ProjectM.Network; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using Unity.Entities; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.PvPLeaderboard +{ + public static class PvPLeaderboardSystem + { + #region Consts + + private const string PvPPunishmentFileName = "PvPLeaderboard.json"; + + #endregion + + #region Variables + + private static Dictionary _pvpStats; + + #endregion + + #region Public Methods + + public static void Initialize() + { + _pvpStats = VModStorage.Load(PvPPunishmentFileName, () => new Dictionary()); + + VModStorage.SaveEvent += Save; + VampireDownedHook.VampireDownedEvent += OnVampireDowned; + } + + public static void Deinitialize() + { + VampireDownedHook.VampireDownedEvent -= OnVampireDowned; + VModStorage.SaveEvent -= Save; + } + + public static void Save() + { + VModStorage.Save(PvPPunishmentFileName, _pvpStats); + } + + #endregion + + #region Private Methods + + private static void OnVampireDowned(Entity killer, Entity victim) + { + if(!PvPLeaderboardConfig.PvPLeaderboardEnabled.Value) + { + return; + } + var entityManager = VWorld.Server.EntityManager; + + Entity killerUserEntity = entityManager.GetComponentData(killer).UserEntity._Entity; + var killerUser = entityManager.GetComponentData(killerUserEntity); + ulong killerSteamID = killerUser.PlatformId; + float killerLevel = HighestGearScoreSystem.GetCurrentOrHighestGearScore(new FromCharacter() + { + User = killerUserEntity, + Character = killer, + }); + + Entity victimUserEntity = entityManager.GetComponentData(victim).UserEntity._Entity; + var victimUser = entityManager.GetComponentData(victimUserEntity); + ulong victimSteamID = victimUser.PlatformId; + float victimLevel = HighestGearScoreSystem.GetCurrentOrHighestGearScore(new FromCharacter() + { + User = victimUserEntity, + Character = victim, + }); + + var diff = killerLevel - victimLevel; + +#if DEBUG + var msg = $"{killerUser.CharacterName} ({killerSteamID}) [Lv: {killerLevel}] killed {victimUser.CharacterName} ({victimSteamID}) [Lv: {victimLevel}]. - Diff: {diff}"; + Utils.Logger.LogMessage(msg); + //Utils.SendMessage(killerUserEntity, msg, ServerChatMessageType.System); + //Utils.SendMessage(victimUserEntity, msg, ServerChatMessageType.System); +#endif + + if(diff >= PvPLeaderboardConfig.PvPLeaderboardLevelDifference.Value) + { + Utils.Logger.LogMessage($"Vampire {killerUser.CharacterName} (Lv: {killerLevel}; Current Lv: {HighestGearScoreSystem.GetCurrentGearScore(killer, entityManager)}) has grief-killed{victimUser.CharacterName} (Lv {victimLevel}; Current Lv: {HighestGearScoreSystem.GetCurrentGearScore(killer, entityManager)})!"); + if(PvPLeaderboardConfig.PvPLeaderboardAnnounceLowLevelKill.Value) + { + ServerChatUtils.SendSystemMessageToAllClients(entityManager, $"Vampire {killerUser.CharacterName} (Lv {killerLevel}) has grief-killed {victimUser.CharacterName} (Lv {victimLevel})!"); + } + return; + } + + if(!_pvpStats.TryGetValue(killerSteamID, out var killerPvPStats)) + { + killerPvPStats = new PvPStats(); + _pvpStats.Add(killerSteamID, killerPvPStats); + } + if(!_pvpStats.TryGetValue(victimSteamID, out var victimPvPStats)) + { + victimPvPStats = new PvPStats(); + _pvpStats.Add(victimSteamID, victimPvPStats); + } + + killerPvPStats.AddKill(); + victimPvPStats.AddDeath(); + + if(PvPLeaderboardConfig.PvPLeaderboardAnnounceKill.Value) + { + ServerChatUtils.SendSystemMessageToAllClients(entityManager, $"Vampire \"{killerUser.CharacterName}\" has killed \"{victimUser.CharacterName}\"!"); + } + } + + [Command("pvpstats,pvp", "pvpstats", "Shows your current pvp stats (kills, deaths & K/D ratio).")] + private static void OnPvPStatsCommand(Command command) + { + var user = command.User; + if(!_pvpStats.TryGetValue(user.PlatformId, out var pvpStats)) + { + pvpStats = new PvPStats(); + _pvpStats[user.PlatformId] = pvpStats; + } + user.SendSystemMessage($"{user.CharacterName} K/D: {pvpStats.KDRatio} [{pvpStats.Kills}/{pvpStats.Deaths}]"); + command.Use(); + } + + [Command("pvplb,pvpleaderboard", "pvplb []", "Shows the 5 players on the requested page of the leaderboard (or top 5 if no page is given).")] + private static void OnPvPLeaderboardCommand(Command command) + { + int page = 0; + if(command.Args.Length >= 1 && int.TryParse(command.Args[0], out page)) + { + page -= 1; + } + + var recordsPerPage = 5; + + var maxPage = (int)Math.Ceiling(_pvpStats.Count / (double)recordsPerPage); + page = Math.Min(maxPage - 1, page); + + var user = command.User; + var entityManager = VWorld.Server.EntityManager; + var leaderboard = _pvpStats.OrderByDescending(x => x.Value.KDRatio).ThenByDescending(x => x.Value.Kills).ThenBy(x => x.Value.Deaths).Skip(page * recordsPerPage).Take(recordsPerPage); + user.SendSystemMessage("========== PvP Leaderboard =========="); + int rank = (page * recordsPerPage) + 1; + foreach((var platformId, var pvpStats) in leaderboard) + { + user.SendSystemMessage($"{rank}. {Utils.GetCharacterName(platformId, entityManager)} : {pvpStats.KDRatio} [{pvpStats.Kills}/{pvpStats.Deaths}]"); + rank++; + } + user.SendSystemMessage($"=============== {page + 1}/{maxPage} ==============="); + + command.Use(); + } + + #endregion + + #region Nested + + private class PvPStats + { + #region Properties + + public int Kills { get; private set; } + public int Deaths { get; private set; } + public double KDRatio { get; private set; } + + #endregion + + #region Lifecycle + + [JsonConstructor] + public PvPStats(int kills, int deaths, double kdRatio) + { + (Kills, Deaths, KDRatio) = (kills, deaths, kdRatio); + + CalcKDRatio(); + } + + public PvPStats() + { + Kills = 0; + Deaths = 0; + KDRatio = 1d; + } + + #endregion + + #region Public Methods + + public void AddKill() + { + Kills++; + CalcKDRatio(); + } + + public void AddDeath() + { + Deaths++; + CalcKDRatio(); + } + + #endregion + + #region Private Methods + + private void CalcKDRatio() + { + if(Deaths == 0) + { + KDRatio = Kills; + } + else + { + KDRatio = Kills / (double)Deaths; + } + } + + #endregion + } + + #endregion + } +} diff --git a/PvPPunishment/Configs/PvPPunishmentConfig.cs b/PvPPunishment/Configs/PvPPunishmentConfig.cs new file mode 100644 index 0000000..b4d0190 --- /dev/null +++ b/PvPPunishment/Configs/PvPPunishmentConfig.cs @@ -0,0 +1,50 @@ +using BepInEx.Configuration; + +namespace VMods.PvPPunishment +{ + public static class PvPPunishmentConfig + { + #region Properties + + public static ConfigEntry PvPPunishmentEnabled { get; private set; } + public static ConfigEntry PvPPunishmentLevelDifference { get; private set; } + public static ConfigEntry PvPPunishmentOffenseLimit { get; private set; } + public static ConfigEntry PvPPunishmentOffenseCooldown { get; private set; } + public static ConfigEntry PvPPunishmentDuration { get; private set; } + public static ConfigEntry PvPPunishmentMovementSpeedReduction { get; private set; } + public static ConfigEntry PvPPunishmentMaxHealthReduction { get; private set; } + public static ConfigEntry PvPPunishmentPhysResistReduction { get; private set; } + public static ConfigEntry PvPPunishmentSpellResistReduction { get; private set; } + public static ConfigEntry PvPPunishmentFireResistReduction { get; private set; } + public static ConfigEntry PvPPunishmentHolyResistReduction { get; private set; } + public static ConfigEntry PvPPunishmentSunResistReduction { get; private set; } + public static ConfigEntry PvPPunishmentSilverResistReduction { get; private set; } + public static ConfigEntry PvPPunishmentPhysPowerReduction { get; private set; } + public static ConfigEntry PvPPunishmentSpellPowerReduction { get; private set; } + + #endregion + + #region Public Methods + + public static void Initialize(ConfigFile config) + { + PvPPunishmentEnabled = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentEnabled), false, "Enabled/disable the PvP Punishment system."); + PvPPunishmentLevelDifference = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentLevelDifference), 10, "The level difference at which to apply a punishment to the killer."); + PvPPunishmentOffenseLimit = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentOffenseLimit), 3, "The amount of offenses a player can commit before being punished."); + PvPPunishmentOffenseCooldown = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentOffenseCooldown), 300f, "The amount of seconds since the last offense at which the offense counter resets."); + PvPPunishmentDuration = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentDuration), 1800f, "The amount of seconds the punishment buff lasts."); + PvPPunishmentMovementSpeedReduction = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentMovementSpeedReduction), 15f, "The percentage of reduced Movement Speed when a player is punished."); + PvPPunishmentMaxHealthReduction = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentMaxHealthReduction), 15f, "The percentage of reduced Max Health when a player is punished."); + PvPPunishmentPhysResistReduction = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentPhysResistReduction), 15f, "The amount of reduced Physical Resistance when a player is punished."); + PvPPunishmentSpellResistReduction = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentSpellResistReduction), 15f, "The amount of reduced Spell Resistance when a player is punished."); + PvPPunishmentFireResistReduction = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentFireResistReduction), 15f, "The amount of reduced Fire Resistance when a player is punished."); + PvPPunishmentHolyResistReduction = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentHolyResistReduction), 15f, "The amount of reduced Holy Resistance when a player is punished."); + PvPPunishmentSunResistReduction = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentSunResistReduction), 15f, "The amount of reduced Sun Resistance when a player is punished."); + PvPPunishmentSilverResistReduction = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentSilverResistReduction), 15f, "The amount of reduced Silver Resistance when a player is punished."); + PvPPunishmentPhysPowerReduction = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentPhysPowerReduction), 15f, "The percentage of reduced Physical Power when a player is punished."); + PvPPunishmentSpellPowerReduction = config.Bind(nameof(PvPPunishmentConfig), nameof(PvPPunishmentSpellPowerReduction), 15f, "The percentage of reduced Spell Power when a player is punished."); + } + + #endregion + } +} diff --git a/PvPPunishment/Plugin.cs b/PvPPunishment/Plugin.cs new file mode 100644 index 0000000..faa212a --- /dev/null +++ b/PvPPunishment/Plugin.cs @@ -0,0 +1,64 @@ +using BepInEx; +using BepInEx.IL2CPP; +using HarmonyLib; +using System.Reflection; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.PvPPunishment +{ + [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] + [BepInDependency("xyz.molenzwiebel.wetstone")] + [Reloadable] + public class Plugin : BasePlugin + { + #region Variables + + private Harmony _hooks; + + #endregion + + #region Public Methods + + public sealed override void Load() + { + if(VWorld.IsClient) + { + Log.LogMessage($"{PluginInfo.PLUGIN_NAME} only needs to be installed server side."); + return; + } + Utils.Initialize(Log, PluginInfo.PLUGIN_NAME); + + CommandSystemConfig.Initialize(Config); + HighestGearScoreSystemConfig.Initialize(Config); + PvPPunishmentConfig.Initialize(Config); + + CommandSystem.Initialize(); + HighestGearScoreSystem.Initialize(); + PvPPunishmentSystem.Initialize(); + + _hooks = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly()); + + Log.LogInfo($"Plugin {PluginInfo.PLUGIN_NAME} (v{PluginInfo.PLUGIN_VERSION}) is loaded!"); + } + + public sealed override bool Unload() + { + if(VWorld.IsClient) + { + return true; + } + VModStorage.SaveAll(); + + _hooks?.UnpatchSelf(); + PvPPunishmentSystem.Deinitialize(); + HighestGearScoreSystem.Deinitialize(); + CommandSystem.Deinitialize(); + Config.Clear(); + Utils.Deinitialize(); + return true; + } + + #endregion + } +} diff --git a/PvPPunishment/PvPPunishment.csproj b/PvPPunishment/PvPPunishment.csproj new file mode 100644 index 0000000..f65af41 --- /dev/null +++ b/PvPPunishment/PvPPunishment.csproj @@ -0,0 +1,478 @@ + + + netstandard2.1 + VMods.PvPPunishment + VMods.PvPPunishment + A mod that punishes high-level players that kill low-level players + 0.0.1 + true + latest + False + + + + M:\Games\Steam\steamapps\common\VRising\BepInEx\unhollowed + M:\Games\Steam\steamapps\common\VRising\BepInEx\WetstonePlugins + M:\Games\Steam\steamapps\common\VRising\VRising_Server\BepInEx\WetstonePlugins + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(UnhollowedDllPath)\com.stunlock.console.dll + + + $(UnhollowedDllPath)\com.stunlock.metrics.dll + + + $(UnhollowedDllPath)\com.stunlock.network.lidgren.dll + + + $(UnhollowedDllPath)\com.stunlock.network.steam.dll + + + $(UnhollowedDllPath)\Il2CppMono.Security.dll + + + $(UnhollowedDllPath)\Il2CppSystem.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Configuration.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Core.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Data.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Numerics.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Runtime.Serialization.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.Linq.dll + + + $(UnhollowedDllPath)\Lidgren.Network.dll + + + $(UnhollowedDllPath)\MagicaCloth.dll + + + $(UnhollowedDllPath)\Malee.ReorderableList.dll + + + $(UnhollowedDllPath)\Newtonsoft.Json.dll + + + $(UnhollowedDllPath)\ProjectM.Behaviours.dll + + + $(UnhollowedDllPath)\ProjectM.Camera.dll + + + $(UnhollowedDllPath)\ProjectM.CastleBuilding.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Conversion.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Scripting.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.GeneratedNetCode.dll + + + $(UnhollowedDllPath)\ProjectM.Misc.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Pathfinding.dll + + + $(UnhollowedDllPath)\ProjectM.Presentation.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Roofs.dll + + + $(UnhollowedDllPath)\ProjectM.ScriptableSystems.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.dll + + + $(UnhollowedDllPath)\Il2Cppmscorlib.dll + + + $(UnhollowedDllPath)\ProjectM.dll + + + $(UnhollowedDllPath)\com.stunlock.network.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Terrain.dll + + + $(UnhollowedDllPath)\RootMotion.dll + + + $(UnhollowedDllPath)\Sequencer.dll + + + $(UnhollowedDllPath)\Stunlock.Fmod.dll + + + $(UnhollowedDllPath)\Unity.Burst.dll + + + $(UnhollowedDllPath)\Unity.Burst.Unsafe.dll + + + $(UnhollowedDllPath)\Unity.Collections.dll + + + $(UnhollowedDllPath)\Unity.Collections.LowLevel.ILSupport.dll + + + $(UnhollowedDllPath)\Unity.Deformations.dll + + + $(UnhollowedDllPath)\Unity.Entities.dll + + + $(UnhollowedDllPath)\ProjectM.HUD.dll + + + $(UnhollowedDllPath)\Unity.Entities.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Jobs.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Physics.dll + + + $(UnhollowedDllPath)\Unity.Physics.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Properties.dll + + + $(UnhollowedDllPath)\Unity.Rendering.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.Core.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Runtime.dll + + + $(UnhollowedDllPath)\Unity.Scenes.dll + + + $(UnhollowedDllPath)\Unity.Serialization.dll + + + $(UnhollowedDllPath)\Unity.Services.Analytics.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Configuration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Device.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Registration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Scheduler.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Telemetry.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Threading.dll + + + $(UnhollowedDllPath)\Unity.TextMeshPro.dll + + + $(UnhollowedDllPath)\Unity.Transforms.dll + + + $(UnhollowedDllPath)\Unity.Transforms.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.VisualEffectGraph.Runtime.dll + + + $(UnhollowedDllPath)\UnityEngine.dll + + + $(UnhollowedDllPath)\UnityEngine.AccessibilityModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AndroidJNIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AnimationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ARModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClothModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterInputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterRendererModule.dll + + + $(UnhollowedDllPath)\UnityEngine.CoreModule.dll + + + $(UnhollowedDllPath)\ProjectM.CodeGeneration.dll + + + $(UnhollowedDllPath)\Stunlock.Core.dll + + + $(UnhollowedDllPath)\UnityEngine.CrashReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DirectorModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DSPGraphModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GameCenterModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GridModule.dll + + + $(UnhollowedDllPath)\UnityEngine.HotReloadModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ImageConversionModule.dll + + + $(UnhollowedDllPath)\UnityEngine.IMGUIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputLegacyModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.JSONSerializeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.LocalizationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ParticleSystemModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PerformanceReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.Physics2DModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ProfilerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ScreenCaptureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SharedInternalsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteMaskModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteShapeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.StreamingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubstanceModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubsystemsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainPhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextCoreModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextRenderingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TilemapModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TLSModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UI.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsNativeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UmbraModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UNETModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityAnalyticsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityConnectModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityCurlModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityTestProtocolModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestTextureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestWWWModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VehiclesModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VFXModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VideoModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VirtualTexturingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VRModule.dll + + + $(UnhollowedDllPath)\UnityEngine.WindModule.dll + + + $(UnhollowedDllPath)\UnityEngine.XRModule.dll + + + $(UnhollowedDllPath)\VivoxUnity.dll + + + diff --git a/PvPPunishment/Systems/PvPPunishmentSystem.cs b/PvPPunishment/Systems/PvPPunishmentSystem.cs new file mode 100644 index 0000000..425bbc0 --- /dev/null +++ b/PvPPunishment/Systems/PvPPunishmentSystem.cs @@ -0,0 +1,279 @@ +using ProjectM; +using ProjectM.Network; +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.Entities; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.PvPPunishment +{ + public static class PvPPunishmentSystem + { + #region Consts + + private const string PvPPunishmentFileName = "PvPPunishment.json"; + + #endregion + + #region Variables + + private static Dictionary _offenses; + + #endregion + + #region Public Methods + + public static void Initialize() + { + _offenses = VModStorage.Load(PvPPunishmentFileName, () => new Dictionary()); + + PruneOffenses(); + + VModStorage.SaveEvent += Save; + VampireDownedHook.VampireDownedEvent += OnVampireDowned; + BuffSystemHook.ProcessBuffEvent += OnProcessBuff; + } + + public static void Deinitialize() + { + BuffSystemHook.ProcessBuffEvent -= OnProcessBuff; + VampireDownedHook.VampireDownedEvent -= OnVampireDowned; + VModStorage.SaveEvent -= Save; + } + + public static void Save() + { + PruneOffenses(); + + VModStorage.Save(PvPPunishmentFileName, _offenses); + } + + #endregion + + #region Private Methods + + private static void OnVampireDowned(Entity killer, Entity victim) + { + var entityManager = VWorld.Server.EntityManager; + + Entity killerUserEntity = entityManager.GetComponentData(killer).UserEntity._Entity; + var killerUser = entityManager.GetComponentData(killerUserEntity); + ulong killerSteamID = killerUser.PlatformId; + float killerLevel = HighestGearScoreSystem.GetCurrentOrHighestGearScore(new FromCharacter() + { + User = killerUserEntity, + Character = killer, + }); + + Entity victimUserEntity = entityManager.GetComponentData(victim).UserEntity._Entity; + var victimUser = entityManager.GetComponentData(victimUserEntity); + ulong victimSteamID = victimUser.PlatformId; + float victimLevel = HighestGearScoreSystem.GetCurrentOrHighestGearScore(new FromCharacter() + { + User = victimUserEntity, + Character = victim, + }); + + var diff = killerLevel - victimLevel; + +#if DEBUG + var msg = $"{killerUser.CharacterName} ({killerSteamID}) [Lv: {killerLevel}] killed {victimUser.CharacterName} ({victimSteamID}) [Lv: {victimLevel}]. - Diff: {diff}"; + Utils.Logger.LogMessage(msg); + //Utils.SendMessage(killerUserEntity, msg, ServerChatMessageType.System); + //Utils.SendMessage(victimUserEntity, msg, ServerChatMessageType.System); +#endif + + if(diff >= PvPPunishmentConfig.PvPPunishmentLevelDifference.Value) + { + if(!_offenses.TryGetValue(killerSteamID, out var offense)) + { + offense = new OffenseData(); + _offenses.Add(killerSteamID, offense); + } + +#if DEBUG + msg = $"Last Offense was at: {offense.LastOffenseTime}"; + Utils.Logger.LogMessage(msg); + //Utils.SendMessage(killerUserEntity, msg, ServerChatMessageType.System); + //Utils.SendMessage(victimUserEntity, msg, ServerChatMessageType.System); +#endif + + TimeSpan timeSpan = DateTime.UtcNow - offense.LastOffenseTime; + +#if DEBUG + msg = $"Time Diff since last offense: {timeSpan}"; + Utils.Logger.LogMessage(msg); + //Utils.SendMessage(killerUserEntity, msg, ServerChatMessageType.System); + //Utils.SendMessage(victimUserEntity, msg, ServerChatMessageType.System); +#endif + if(timeSpan.TotalSeconds > PvPPunishmentConfig.PvPPunishmentOffenseCooldown.Value) + { + offense.OffenseCount = 1; + } + else + { + offense.OffenseCount++; + } + offense.LastOffenseTime = DateTime.UtcNow; + +#if DEBUG + msg = $"New Offense Count: {offense.OffenseCount}"; + Utils.Logger.LogMessage(msg); + //Utils.SendMessage(killerUserEntity, msg, ServerChatMessageType.System); + //Utils.SendMessage(victimUserEntity, msg, ServerChatMessageType.System); +#endif + + if(offense.OffenseCount >= PvPPunishmentConfig.PvPPunishmentOffenseLimit.Value) + { + Utils.ApplyBuff(killerUserEntity, killer, Utils.SevereGarlicDebuff); +#if DEBUG + msg = $"Punishment applied for {killerUser.CharacterName} ({killerSteamID})"; + Utils.Logger.LogMessage(msg); + //Utils.SendMessage(killerUserEntity, msg, ServerChatMessageType.System); + //Utils.SendMessage(victimUserEntity, msg, ServerChatMessageType.System); +#endif + } +#if DEBUG + else + { + msg = $"Punishment count has increased!"; + Utils.Logger.LogMessage(msg); + //Utils.SendMessage(killerUserEntity, msg, ServerChatMessageType.System); + //Utils.SendMessage(victimUserEntity, msg, ServerChatMessageType.System); + } +#endif + } +#if DEBUG + else + { + msg = $"No punishment appled -> kill was within appropriate level"; + Utils.Logger.LogMessage(msg); + //Utils.SendMessage(killerUserEntity, msg, ServerChatMessageType.System); + //Utils.SendMessage(victimUserEntity, msg, ServerChatMessageType.System); + } +#endif + } + + private static void OnProcessBuff(Entity entity, PrefabGUID buffGUID) + { + if(!VWorld.IsServer || buffGUID != Utils.SevereGarlicDebuff) + { + return; + } + + var entityManager = VWorld.Server.EntityManager; + + var buffLifeTime = entityManager.GetComponentData(entity); + buffLifeTime.Duration = PvPPunishmentConfig.PvPPunishmentDuration.Value; + entityManager.SetComponentData(entity, buffLifeTime); + + var buffer = entityManager.AddBuffer(entity); + TryAddReductionBuff(buffer, UnitStatType.MovementSpeed, ModificationType.Multiply, PvPPunishmentConfig.PvPPunishmentMovementSpeedReduction.Value); + TryAddReductionBuff(buffer, UnitStatType.MaxHealth, ModificationType.Multiply, PvPPunishmentConfig.PvPPunishmentMaxHealthReduction.Value); + TryAddReductionBuff(buffer, UnitStatType.PhysicalResistance, ModificationType.Add, PvPPunishmentConfig.PvPPunishmentPhysResistReduction.Value); + TryAddReductionBuff(buffer, UnitStatType.SpellResistance, ModificationType.Add, PvPPunishmentConfig.PvPPunishmentSpellResistReduction.Value); + TryAddReductionBuff(buffer, UnitStatType.FireResistance, ModificationType.Add, PvPPunishmentConfig.PvPPunishmentFireResistReduction.Value); + TryAddReductionBuff(buffer, UnitStatType.HolyResistance, ModificationType.Add, PvPPunishmentConfig.PvPPunishmentHolyResistReduction.Value); + TryAddReductionBuff(buffer, UnitStatType.SunResistance, ModificationType.Add, PvPPunishmentConfig.PvPPunishmentSunResistReduction.Value); + TryAddReductionBuff(buffer, UnitStatType.SilverResistance, ModificationType.Add, PvPPunishmentConfig.PvPPunishmentSilverResistReduction.Value); + TryAddReductionBuff(buffer, UnitStatType.PhysicalPower, ModificationType.Multiply, PvPPunishmentConfig.PvPPunishmentPhysPowerReduction.Value); + TryAddReductionBuff(buffer, UnitStatType.SpellPower, ModificationType.Multiply, PvPPunishmentConfig.PvPPunishmentSpellPowerReduction.Value); + } + + private static void TryAddReductionBuff(DynamicBuffer buffer, UnitStatType unitStatType, ModificationType modificationType, float value) + { + if(value > 0f) + { + buffer.Add(new ModifyUnitStatBuff_DOTS() + { + StatType = unitStatType, + Value = modificationType switch + { + ModificationType.Multiply => (100f - value) / 100f, + ModificationType.Add => -value, + _ => value, + }, + ModificationType = modificationType, + Id = ModificationId.NewId(0), + }); + } + } + + private static void PruneOffenses() + { + var now = DateTime.UtcNow; + var keys = _offenses.Keys.ToList(); + var offenseCooldown = PvPPunishmentConfig.PvPPunishmentOffenseCooldown.Value; + foreach(var key in keys) + { + var offenseData = _offenses[key]; + if(now.Subtract(offenseData.LastOffenseTime).TotalSeconds > offenseCooldown) + { + _offenses.Remove(key); + } + } + } + + [Command("ispunished", "ispunished []", "Tell you if the the given player (or yourself when no playername is given) currently has the PvP Punishment buff", true)] + private static void OnIsPunishedPlayerCommand(Command command) + { + var entityManager = VWorld.Server.EntityManager; + (var searchUsername, var fromCharacter) = command.GetFromCharacter(entityManager: entityManager); + + if(fromCharacter.HasValue) + { + if(BuffUtility.HasBuff(entityManager, fromCharacter.Value.Character, Utils.SevereGarlicDebuff)) + { + command.User.SendSystemMessage($"Vampire {searchUsername} is currently punished."); + } + else + { + command.User.SendSystemMessage($"Vampire {searchUsername} isn't punished."); + } + } + command.Use(); + } + + [Command("punish", "punish []", "Adds (or refreshes) the PvP Punishment buff for the given player (or yourself when no playername is given)", true)] + private static void OnPunishPlayerCommand(Command command) + { + var entityManager = VWorld.Server.EntityManager; + (var searchUsername, var fromCharacter) = command.GetFromCharacter(entityManager: entityManager); + + if(fromCharacter.HasValue) + { + Utils.ApplyBuff(fromCharacter.Value, Utils.SevereGarlicDebuff); + command.User.SendSystemMessage($"Vampire {searchUsername} has been punished."); + } + command.Use(); + } + + [Command("unpunish", "unpunish []", "Removes the PvP Punishment buff for the given player (or yourself when no playername is given)", true)] + private static void OnUnPunishPlayerCommand(Command command) + { + var entityManager = VWorld.Server.EntityManager; + (var searchUsername, var fromCharacter) = command.GetFromCharacter(entityManager: entityManager); + + if(fromCharacter.HasValue) + { + Utils.RemoveBuff(fromCharacter.Value, Utils.SevereGarlicDebuff); + command.User.SendSystemMessage($"Vampire {searchUsername} has been un-punished."); + } + command.Use(); + } + + #endregion + + #region Nested + + private class OffenseData + { + public DateTime LastOffenseTime { get; set; } + public int OffenseCount { get; set; } + } + + #endregion + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..c313ae1 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# VMods +A selection of Mods for V-Rising + +## General Mod info (Applies to most mods) +### Commands +Most of the mods come with a set of commands that can be used. To see the available commands, by default a player or admin can use `!help`. +Normal players won't see the Admin-only commands listed. +The prefix (`!`) can be changed on a per-mod basis. +To prevent spam/abuse there's also a command cooldown for non-admins, this value can also be tweaked on a per-mod basis. +Commands can also be disabled completely on a per-mod basis. + +## Blood Refill +A server-side only mod that allows players to refill their blood pool. + +When feed-killing an enemy, you'll be able to regain some blood. +The amount of blood regained is based on the level difference, blood type and blood quality of the killed enemy with V-Bloods refilling your blood pool for a much larger amount. + +
+Configuration Options + +* Enable/disable requiring feed-killing (when disabled, any kill grants some blood). +* Choose the amount of blood gained on a 'regular refill' (i.e. a refill without any level, blood type or quality punishments applied) +* A multiplier to reduce the amount of gained blood when feeding on an enemy of a different blood type. (blood dilution) +* The ability to disable different blood type refilling (i.e. a 0 multiplier for different blood types) +* Switch between having V-Blood act as diluted or pure blood, or have V-Blood completely refill your blood pool +* The options to make refilling random between 0.1L and the calculated amount (which then acts as a max refill amount) +* A global refill multiplier (applied after picking a random refill value) + +
+ +## Recover Empty Containers +A server-side only mod that allows players to recover empty containers. + +When a player drinks a potion or brew, an empty container (glass bottle or canteen) is given back to the player (or dropped on the floor when the player's inventory is full) + +## Resource Stash Withdrawal +A server & client side mod that allows players to withdraw items for a recipe directly from their stash. + +When a player is at a crafting or other workstation, he/she can click on the recipe or an individual component of a recipe with their middle-mouse button to withdraw the missing item(s) directly from their stash. +(The withdraw the full amount, CTRL+Middle Mouse Button can be used) + +Note: Players without the client-side mod can still join and play the server, but won't be able to make use of this feature. + +## PvP Leaderboard +A server-side only mod that keeps track of Kills, Death and the K/D ratio of players and ranks them in a leaderboard. + +This mod also has the option to exclude low-level (or grief-kills) from counting towards the K/D of the leaderboard. +There's also an option to prevent cheesing this restriction where the highest gear score (in the past X minutes) is used instead of the current gear score. +Both legitimate and grief-kills can be announced server-wide. + +Players can see their own stats and the leaderboard itself using a command (By default: `!pvpstats`). +The leaderboard shows up to 5 ranks at a time and allows players to input a page number so they can "browse" the leaderboard (By default: `!pvplb []`). + +
+Configuration Options + +* Enable/disable announcing of legitimate kills +* Enable/disable announcing of grief-kills +* Set a Level Difference at which the K/D isn't counting anymore of the leaderboard. +* Enable/disable usage of the anti-cheesing system (highest gear score tracking) +* Change the amount of time the highest gear score is remembered/tracked + +
+ +## PvP Punishment +A server-side only mod that punishes low-level kills. + +This mod also has the option prevent cheesing the low-level restriction where the highest gear score (in the past X minutes) is used instead of the current gear score. +Both the amount of offenses before being punishes as well as the punishment itself can be tweaked. + +
+Configuration Options + +* Set a Level Difference at which an offense is being recorded +* Enable/disable usage of the anti-cheesing system (highest gear score tracking) +* Change the amount of offenses a player can make before actually being punished +* Change the offense cooldown time before the offense counter resets +* Change the duration of the punishment +* Change the following for the actual punishment: + * % reduced Movement Speed + * % reduced Max Health + * % reduced Physical Resistance + * % reduced Spell Resistance + * amount of reduced Fire Resistance + * amount of reduced Holy Resistance + * amount of reduced Sun Resistance + * amount of reduced Silver Resistance + * % of reduced Physical Power + * % of reduced Spell Power + +
diff --git a/RecoverEmptyContainers/Configs/RecoverEmptyContainersConfig.cs b/RecoverEmptyContainers/Configs/RecoverEmptyContainersConfig.cs new file mode 100644 index 0000000..8d31af1 --- /dev/null +++ b/RecoverEmptyContainers/Configs/RecoverEmptyContainersConfig.cs @@ -0,0 +1,22 @@ +using BepInEx.Configuration; + +namespace VMods.RecoverEmptyContainers +{ + public static class RecoverEmptyContainersConfig + { + #region Properties + + public static ConfigEntry RecoverEmptyContainersEnabled { get; private set; } + + #endregion + + #region Public Methods + + public static void Initialize(ConfigFile config) + { + RecoverEmptyContainersEnabled = config.Bind("Server", nameof(RecoverEmptyContainersEnabled), false, "Enabled/disable the recovery of empty containers system."); + } + + #endregion + } +} diff --git a/RecoverEmptyContainers/Hooks/UseConsumableHook.cs b/RecoverEmptyContainers/Hooks/UseConsumableHook.cs new file mode 100644 index 0000000..3adff25 --- /dev/null +++ b/RecoverEmptyContainers/Hooks/UseConsumableHook.cs @@ -0,0 +1,69 @@ +using HarmonyLib; +using ProjectM; +using ProjectM.Network; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Entities; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.RecoverEmptyContainers +{ + [HarmonyPatch] + public class TestHook + { + #region Consts + + private static readonly Dictionary RecipeItemToReturnedItemMapping = new() + { + [new PrefabGUID(-1322000172)] = new PrefabGUID(-810738866),// Water-filled Canteen -> Empty Canteen + [new PrefabGUID(-1382451936)] = new PrefabGUID(-437611596),// Water-filled Bottle -> Empty Glass Bottle + }; + + #endregion + + #region Public Methods + + [HarmonyPatch(typeof(UseConsumableSystem), nameof(UseConsumableSystem.CastAbility))] + [HarmonyPostfix] + public static void CastAbility(UseConsumableSystem __instance, InventoryBuffer inventoryItem, FromCharacter fromCharacter, NativeHashMap prefabLookupMap, Entity itemEntity, bool removeByItemEntity, ref bool shouldConsumeItem) + { + if(!VWorld.IsServer) + { + return; + } + + var server = VWorld.Server; + var entityManager = server.EntityManager; + var gameDataSystem = server.GetExistingSystem(); + + foreach(var kvp in gameDataSystem.RecipeHashLookupMap) + { + var recipeData = kvp.Value; + if(entityManager.HasComponent(recipeData.Entity)) + { + var outputBuffer = entityManager.GetBuffer(recipeData.Entity); + foreach(var output in outputBuffer) + { + if(output.Guid == inventoryItem.ItemType) + { + if(entityManager.HasComponent(recipeData.Entity)) + { + var requirements = entityManager.GetBuffer(recipeData.Entity); + foreach(var requirement in requirements) + { + if(RecipeItemToReturnedItemMapping.TryGetValue(requirement.Guid, out var returnItemGUID)) + { + Utils.TryGiveItem(entityManager, gameDataSystem.ItemHashLookupMap, fromCharacter.Character, returnItemGUID, requirement.Stacks, out _, out _, dropRemainder: true); + } + } + } + } + } + } + } + } + + #endregion + } +} diff --git a/RecoverEmptyContainers/Plugin.cs b/RecoverEmptyContainers/Plugin.cs new file mode 100644 index 0000000..d49fd68 --- /dev/null +++ b/RecoverEmptyContainers/Plugin.cs @@ -0,0 +1,52 @@ +using BepInEx; +using BepInEx.IL2CPP; +using HarmonyLib; +using System.Reflection; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.RecoverEmptyContainers +{ + [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] + [BepInDependency("xyz.molenzwiebel.wetstone")] + [Reloadable] + public class Plugin : BasePlugin + { + #region Variables + + private Harmony _hooks; + + #endregion + + #region Public Methods + + public sealed override void Load() + { + if(VWorld.IsClient) + { + Log.LogMessage($"{PluginInfo.PLUGIN_NAME} only needs to be installed server side."); + return; + } + Utils.Initialize(Log, PluginInfo.PLUGIN_NAME); + RecoverEmptyContainersConfig.Initialize(Config); + + _hooks = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly()); + + Log.LogInfo($"Plugin {PluginInfo.PLUGIN_NAME} (v{PluginInfo.PLUGIN_VERSION}) is loaded!"); + } + + public sealed override bool Unload() + { + if(VWorld.IsClient) + { + return true; + } + _hooks?.UnpatchSelf(); + Config.Clear(); + Utils.Deinitialize(); + return true; + } + + #endregion + } +} diff --git a/RecoverEmptyContainers/RecoverEmptyContainers.csproj b/RecoverEmptyContainers/RecoverEmptyContainers.csproj new file mode 100644 index 0000000..f9339a3 --- /dev/null +++ b/RecoverEmptyContainers/RecoverEmptyContainers.csproj @@ -0,0 +1,469 @@ + + + netstandard2.1 + VMods.RecoverEmptyContainers + VMods.RecoverEmptyContainers + Allows a player to recover an empty container when consuming a potion/brew + 0.0.1 + true + latest + False + + + + M:\Games\Steam\steamapps\common\VRising\BepInEx\unhollowed + M:\Games\Steam\steamapps\common\VRising\BepInEx\WetstonePlugins + M:\Games\Steam\steamapps\common\VRising\VRising_Server\BepInEx\WetstonePlugins + + + + + + + + + + + + + + + + + + + + $(UnhollowedDllPath)\com.stunlock.console.dll + + + $(UnhollowedDllPath)\com.stunlock.metrics.dll + + + $(UnhollowedDllPath)\com.stunlock.network.lidgren.dll + + + $(UnhollowedDllPath)\com.stunlock.network.steam.dll + + + $(UnhollowedDllPath)\Il2CppMono.Security.dll + + + $(UnhollowedDllPath)\Il2CppSystem.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Configuration.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Core.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Data.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Numerics.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Runtime.Serialization.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.Linq.dll + + + $(UnhollowedDllPath)\Lidgren.Network.dll + + + $(UnhollowedDllPath)\MagicaCloth.dll + + + $(UnhollowedDllPath)\Malee.ReorderableList.dll + + + $(UnhollowedDllPath)\Newtonsoft.Json.dll + + + $(UnhollowedDllPath)\ProjectM.Behaviours.dll + + + $(UnhollowedDllPath)\ProjectM.Camera.dll + + + $(UnhollowedDllPath)\ProjectM.CastleBuilding.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Conversion.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Scripting.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.GeneratedNetCode.dll + + + $(UnhollowedDllPath)\ProjectM.Misc.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Pathfinding.dll + + + $(UnhollowedDllPath)\ProjectM.Presentation.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Roofs.dll + + + $(UnhollowedDllPath)\ProjectM.ScriptableSystems.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.dll + + + $(UnhollowedDllPath)\Il2Cppmscorlib.dll + + + $(UnhollowedDllPath)\ProjectM.dll + + + $(UnhollowedDllPath)\com.stunlock.network.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Terrain.dll + + + $(UnhollowedDllPath)\RootMotion.dll + + + $(UnhollowedDllPath)\Sequencer.dll + + + $(UnhollowedDllPath)\Stunlock.Fmod.dll + + + $(UnhollowedDllPath)\Unity.Burst.dll + + + $(UnhollowedDllPath)\Unity.Burst.Unsafe.dll + + + $(UnhollowedDllPath)\Unity.Collections.dll + + + $(UnhollowedDllPath)\Unity.Collections.LowLevel.ILSupport.dll + + + $(UnhollowedDllPath)\Unity.Deformations.dll + + + $(UnhollowedDllPath)\Unity.Entities.dll + + + $(UnhollowedDllPath)\ProjectM.HUD.dll + + + $(UnhollowedDllPath)\Unity.Entities.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Jobs.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Physics.dll + + + $(UnhollowedDllPath)\Unity.Physics.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Properties.dll + + + $(UnhollowedDllPath)\Unity.Rendering.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.Core.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Runtime.dll + + + $(UnhollowedDllPath)\Unity.Scenes.dll + + + $(UnhollowedDllPath)\Unity.Serialization.dll + + + $(UnhollowedDllPath)\Unity.Services.Analytics.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Configuration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Device.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Registration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Scheduler.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Telemetry.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Threading.dll + + + $(UnhollowedDllPath)\Unity.TextMeshPro.dll + + + $(UnhollowedDllPath)\Unity.Transforms.dll + + + $(UnhollowedDllPath)\Unity.Transforms.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.VisualEffectGraph.Runtime.dll + + + $(UnhollowedDllPath)\UnityEngine.dll + + + $(UnhollowedDllPath)\UnityEngine.AccessibilityModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AndroidJNIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AnimationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ARModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClothModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterInputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterRendererModule.dll + + + $(UnhollowedDllPath)\UnityEngine.CoreModule.dll + + + $(UnhollowedDllPath)\ProjectM.CodeGeneration.dll + + + $(UnhollowedDllPath)\Stunlock.Core.dll + + + $(UnhollowedDllPath)\UnityEngine.CrashReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DirectorModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DSPGraphModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GameCenterModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GridModule.dll + + + $(UnhollowedDllPath)\UnityEngine.HotReloadModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ImageConversionModule.dll + + + $(UnhollowedDllPath)\UnityEngine.IMGUIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputLegacyModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.JSONSerializeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.LocalizationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ParticleSystemModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PerformanceReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.Physics2DModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ProfilerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ScreenCaptureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SharedInternalsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteMaskModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteShapeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.StreamingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubstanceModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubsystemsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainPhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextCoreModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextRenderingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TilemapModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TLSModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UI.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsNativeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UmbraModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UNETModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityAnalyticsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityConnectModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityCurlModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityTestProtocolModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestTextureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestWWWModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VehiclesModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VFXModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VideoModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VirtualTexturingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VRModule.dll + + + $(UnhollowedDllPath)\UnityEngine.WindModule.dll + + + $(UnhollowedDllPath)\UnityEngine.XRModule.dll + + + $(UnhollowedDllPath)\VivoxUnity.dll + + + + + + + diff --git a/ResourceStashWithdrawal/Configs/ResourceStashWithdrawalConfig.cs b/ResourceStashWithdrawal/Configs/ResourceStashWithdrawalConfig.cs new file mode 100644 index 0000000..671f80e --- /dev/null +++ b/ResourceStashWithdrawal/Configs/ResourceStashWithdrawalConfig.cs @@ -0,0 +1,22 @@ +using BepInEx.Configuration; + +namespace VMods.ResourceStashWithdrawal +{ + public static class ResourceStashWithdrawalConfig + { + #region Properties + + public static ConfigEntry ResourceStashWithdrawalEnabled { get; private set; } + + #endregion + + #region Public Methods + + public static void Initialize(ConfigFile config) + { + ResourceStashWithdrawalEnabled = config.Bind("Server", nameof(ResourceStashWithdrawalEnabled), false, "Enabled/disable the resource stash withdrawal system."); + } + + #endregion + } +} diff --git a/ResourceStashWithdrawal/Hooks/UIClickHook.cs b/ResourceStashWithdrawal/Hooks/UIClickHook.cs new file mode 100644 index 0000000..7a675cb --- /dev/null +++ b/ResourceStashWithdrawal/Hooks/UIClickHook.cs @@ -0,0 +1,261 @@ +using HarmonyLib; +using ProjectM; +using ProjectM.UI; +using System; +using UnityEngine; +using UnityEngine.EventSystems; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.ResourceStashWithdrawal +{ + [HarmonyPatch] + public class UIClickHook + { + #region Variables + + private static DateTime _lastResourceRequest = DateTime.UtcNow; + + #endregion + + #region Public Methods + + public static void Reset() + { + _lastResourceRequest = DateTime.UtcNow; + } + + [HarmonyPatch(typeof(GridSelectionEntry), nameof(GridSelectionEntry.OnPointerClick))] + [HarmonyPostfix] + public static void OnPointerClick(GridSelectionEntry __instance, PointerEventData eventData) + { + UITooltipHook.OnPointerEnter(__instance, eventData); + + if(!VWorld.IsClient || eventData.button != PointerEventData.InputButton.Middle || DateTime.UtcNow.Subtract(_lastResourceRequest).TotalSeconds <= 0.2f) + { + return; + } + _lastResourceRequest = DateTime.UtcNow; + + bool withdrawFullAmount = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); + + var client = VWorld.Client; + var entityManager = client.EntityManager; + var gameDataSystem = client.GetExistingSystem(); + var itemHashLookupMap = gameDataSystem.ItemHashLookupMap; + var prefabCollectionSystem = client.GetExistingSystem(); + var prefabLookupMap = prefabCollectionSystem.PrefabLookupMap; + + RefinementstationRecipeEntry refinementstationRecipeEntry = __instance.GetComponent(); + RefinementstationRecipeItem refinementstationRecipeItem = __instance.GetComponent(); + WorkstationRecipeGridSelectionEntry workstationRecipeGridSelectionEntry = __instance.GetComponent(); + if(refinementstationRecipeEntry != null) + { + var refinementstationSubMenu = __instance.GetComponentInParent(); + var unitSpawnerstationSubMenu = __instance.GetComponentInParent(); + if(refinementstationSubMenu != null) + { + var recipe = refinementstationSubMenu.RecipesSelectionGroup.Entries[refinementstationRecipeEntry.EntryIndex]; + foreach(var requirement in recipe.Requirements) + { + SendWithdrawRequest(refinementstationSubMenu.InputInventorySelectionGroup, refinementstationSubMenu.OutputInventorySelectionGroup, recipe, requirement); + } + } + else if(unitSpawnerstationSubMenu != null) + { + var recipe = unitSpawnerstationSubMenu.RecipesSelectionGroup.Entries[refinementstationRecipeEntry.EntryIndex]; + foreach(var requirement in recipe.Requirements) + { + SendWithdrawRequest(unitSpawnerstationSubMenu.InputInventorySelectionGroup, unitSpawnerstationSubMenu.OutputInventorySelectionGroup, recipe, requirement); + } + } + else + { +#if DEBUG + Utils.Logger.LogMessage($"Unknown/unhandled SubMenu for Type: {__instance.GetScriptClassName()}"); +#endif + return; + } + + // Force update the tooltip + UITooltipHook.OnPointerEnter(__instance, eventData); + } + else if(refinementstationRecipeItem != null) + { + refinementstationRecipeEntry = refinementstationRecipeItem.GetComponentInParent(); + + var refinementstationSubMenu = __instance.GetComponentInParent(); + var unitSpawnerstationSubMenu = __instance.GetComponentInParent(); + if(refinementstationSubMenu != null) + { + var recipe = refinementstationSubMenu.RecipesSelectionGroup.Entries[refinementstationRecipeEntry.EntryIndex]; + + foreach(var requirement in recipe.Requirements) + { + if(requirement.Guid == refinementstationRecipeItem.Guid) + { + SendWithdrawRequest(refinementstationSubMenu.InputInventorySelectionGroup, refinementstationSubMenu.OutputInventorySelectionGroup, recipe, requirement); + + // Force update the tooltip + UITooltipHook.OnPointerEnter(__instance, eventData); + return; + } + } + + foreach(var output in recipe.OutputItems) + { + if(output.Guid == refinementstationRecipeItem.Guid) + { + int requiredAmount = output.Stacks; +#if DEBUG + var name = Utils.GetItemName(output.Guid, gameDataSystem, entityManager, prefabLookupMap); + Utils.Logger.LogMessage($"Withdraw Recipe item: {requiredAmount}x {name} ({output.Guid.GuidHash})"); +#endif + + VNetwork.SendToServerStruct(new ResourceStashWithdrawalRequest() + { + ItemGUIDHash = output.Guid.GuidHash, + Amount = requiredAmount, + }); + + // Force update the tooltip + UITooltipHook.OnPointerEnter(__instance, eventData); + return; + } + } + } + else if(unitSpawnerstationSubMenu != null) + { + var recipe = unitSpawnerstationSubMenu.RecipesSelectionGroup.Entries[refinementstationRecipeEntry.EntryIndex]; + + foreach(var requirement in recipe.Requirements) + { + if(requirement.Guid == refinementstationRecipeItem.Guid) + { + SendWithdrawRequest(unitSpawnerstationSubMenu.InputInventorySelectionGroup, unitSpawnerstationSubMenu.OutputInventorySelectionGroup, recipe, requirement); + + // Force update the tooltip + UITooltipHook.OnPointerEnter(__instance, eventData); + return; + } + } + + // Don't look at the output recipes, since those are units (and not inventory items) + } + else + { +#if DEBUG + Utils.Logger.LogMessage($"Unknown/unhandled {nameof(refinementstationRecipeItem)} SubMenu for Type: {__instance.GetScriptClassName()}"); +#endif + return; + } + } + else if(workstationRecipeGridSelectionEntry != null) + { + var workstationSubMenu = __instance.GetComponentInParent(); + if(workstationSubMenu == null) + { + // Only allow withdrawing when it's a workstation (and NOT when you're in your crafting tab of the Inventory Sub Menu!) + return; + } + float resourceMultiplier = 1f; + // Hacky solution to find the bonus -> this is done because the 'BonusType' is incorrect/bugged. + var bonuses = workstationSubMenu.BonusesSelectionGroup.Entries; + var lastIndex = bonuses.Count - 1; + var lastBonus = bonuses[lastIndex]; + if(lastBonus.Unlocked) + { + resourceMultiplier = 1f - (lastBonus.Value / 100f); + } + var recipe = workstationSubMenu.RecipesGridSelectionGroup.Entries[workstationRecipeGridSelectionEntry.EntryIndex]; + if(gameDataSystem.RecipeHashLookupMap.ContainsKey(recipe.EntryId)) + { + var recipeData = gameDataSystem.RecipeHashLookupMap[recipe.EntryId]; + if(entityManager.HasComponent(recipeData.Entity)) + { + var requirements = entityManager.GetBuffer(recipeData.Entity); + foreach(var requirement in requirements) + { + int requiredAmount = (int)Math.Ceiling(requirement.Stacks * resourceMultiplier); + var itemGUID = requirement.Guid; +#if DEBUG + var name = Utils.GetItemName(itemGUID, gameDataSystem, entityManager, prefabLookupMap); + Utils.Logger.LogMessage($"Withdraw Recipe item: {requiredAmount}x {name} ({itemGUID})"); +#endif + if(!withdrawFullAmount) + { + foreach(var stationItem in workstationSubMenu.ItemOutputGridSelectionGroup.Entries) + { + if(stationItem.EntryId == itemGUID) + { + requiredAmount -= stationItem.Stacks; + } + } + requiredAmount -= InventoryUtilities.ItemCount(entityManager, EntitiesHelper.GetLocalCharacterEntity(entityManager), itemGUID); + } + + if(requiredAmount > 0) + { + VNetwork.SendToServerStruct(new ResourceStashWithdrawalRequest() + { + ItemGUIDHash = itemGUID.GuidHash, + Amount = requiredAmount, + }); + } + } + + // Force update the tooltip + UITooltipHook.OnPointerEnter(__instance, eventData); + } + } + } + else + { +#if DEBUG + Utils.Logger.LogMessage($"Unknown/unhandled {nameof(GridSelectionEntry)} Type: {__instance.GetScriptClassName()}"); +#endif + return; + } + + // Nested Method(s) + void SendWithdrawRequest(GridSelectionGroup inputInventorySelectionGroup, GridSelectionGroup outputInventorySelectionGroup, RefinementstationRecipeEntry.Data recipe, RecipeRequirementBuffer requirement) + { + int requiredAmount = (int)Math.Ceiling(requirement.Stacks * recipe.ResourceMultiplier); +#if DEBUG + var name = Utils.GetItemName(requirement.Guid, gameDataSystem, entityManager, prefabLookupMap); + Utils.Logger.LogMessage($"Withdraw Recipe item: {requiredAmount}x {name} ({requirement.Guid.GuidHash})"); +#endif + + if(!withdrawFullAmount) + { + foreach(var stationItem in inputInventorySelectionGroup.Entries) + { + if(stationItem.EntryId == requirement.Guid) + { + requiredAmount -= stationItem.Stacks; + } + } + foreach(var stationItem in outputInventorySelectionGroup.Entries) + { + if(stationItem.EntryId == requirement.Guid) + { + requiredAmount -= stationItem.Stacks; + } + } + requiredAmount -= InventoryUtilities.ItemCount(entityManager, EntitiesHelper.GetLocalCharacterEntity(entityManager), requirement.Guid); + } + + if(requiredAmount > 0) + { + VNetwork.SendToServerStruct(new ResourceStashWithdrawalRequest() + { + ItemGUIDHash = requirement.Guid.GuidHash, + Amount = requiredAmount, + }); + } + } + } + + #endregion + } +} diff --git a/ResourceStashWithdrawal/Hooks/UITooltipHook.cs b/ResourceStashWithdrawal/Hooks/UITooltipHook.cs new file mode 100644 index 0000000..028125d --- /dev/null +++ b/ResourceStashWithdrawal/Hooks/UITooltipHook.cs @@ -0,0 +1,377 @@ +using HarmonyLib; +using ProjectM; +using ProjectM.Scripting; +using ProjectM.Shared; +using ProjectM.UI; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Unity.Entities; +using UnityEngine; +using UnityEngine.EventSystems; +using VMods.Shared; +using Wetstone.API; +using static BepInEx.IL2CPP.Utils.MonoBehaviourExtensions; + +namespace VMods.ResourceStashWithdrawal +{ + [HarmonyPatch] + public class UITooltipHook + { + #region Public Methods + + [HarmonyPatch(typeof(GridSelectionEntry), nameof(GridSelectionEntry.OnPointerEnter))] + [HarmonyPostfix] + public static void OnPointerEnter(GridSelectionEntry __instance, PointerEventData eventData) + { + if(!VWorld.IsClient) + { + return; + } + + RefinementstationRecipeEntry refinementstationRecipeEntry = __instance.GetComponent(); + RefinementstationRecipeItem refinementstationRecipeItem = __instance.GetComponent(); + ItemGridSelectionEntry itemGridSelectionEntry = __instance.GetComponent(); + WorkstationRecipeGridSelectionEntry workstationRecipeGridSelectionEntry = __instance.GetComponent(); + ResearchEntry researchEntry = __instance.GetComponent(); + BuildMenu_StructureEntry buildMenuStructureEntry = __instance.GetComponent(); + if(refinementstationRecipeEntry == null && refinementstationRecipeItem == null && itemGridSelectionEntry == null && + workstationRecipeGridSelectionEntry == null && researchEntry == null && buildMenuStructureEntry == null) + { +#if DEBUG + Utils.Logger.LogMessage($"Unknown/unhandled {nameof(GridSelectionEntry)} PointerEnter for Type: {__instance.GetScriptClassName()}"); +#endif + return; + } + + // Find the current tooltip + var refinementstationSubMenu = __instance.GetComponentInParent(); + var unitSpawnerstationSubMenu = __instance.GetComponentInParent(); + var workstationSubMenu = __instance.GetComponentInParent(); + var researchstationSubMenu = __instance.GetComponentInParent(); + var inventorySubMenu = __instance.GetComponentInParent(); + var buildMenu = __instance.GetComponentInParent(); + var servantInventorySubMenu = __instance.GetComponentInParent(); + var salvagestationSubMenu = __instance.GetComponentInParent(); + FakeTooltip tooltip = null; + if(refinementstationSubMenu != null) + { + tooltip = refinementstationSubMenu.FakeTooltip; + } + else if(unitSpawnerstationSubMenu != null) + { + tooltip = unitSpawnerstationSubMenu.FakeTooltip; + } + else if(workstationSubMenu != null) + { + tooltip = workstationSubMenu.FakeTooltip; + } + else if(researchstationSubMenu != null) + { + tooltip = researchstationSubMenu.FakeTooltip; + } + else if(inventorySubMenu != null) + { + tooltip = inventorySubMenu.FakeTooltip; + } + else if(buildMenu != null) + { + tooltip = buildMenu.FakeTooltip; + } + else if(servantInventorySubMenu != null) + { + tooltip = servantInventorySubMenu.FakeTooltip; + } + else if(salvagestationSubMenu != null) + { + tooltip = salvagestationSubMenu.FakeTooltip; + } + + if(tooltip == null) + { +#if DEBUG + Utils.Logger.LogMessage($"Unknown/unhandled Tooltip for Type: {__instance.GetScriptClassName()}"); +#endif + return; + } + + var client = VWorld.Client; + if(client.Systems.Count == 0) + { + // No systems -> No tooltip + return; + } + var entityManager = client.EntityManager; + var gameDataSystem = client.GetExistingSystem(); + var clientGameManager = client.GetExistingSystem()?._ClientGameManager; + var teamChecker = clientGameManager._TeamChecker; + var character = EntitiesHelper.GetLocalCharacterEntity(entityManager); + + // Ensure we're hovering a single item or the recipe as a whole + PrefabGUID? itemGUID = null; + StoredBlood? storedBlood = null; + List requiredItemGUIDs = null; + List repairItemGUIDs = null; + if(refinementstationRecipeEntry != null) + { + RefinementstationRecipeEntry.Data recipe; + if(refinementstationSubMenu != null) + { + recipe = refinementstationSubMenu.RecipesSelectionGroup.Entries[refinementstationRecipeEntry.EntryIndex]; + } + else if(unitSpawnerstationSubMenu != null) + { + recipe = unitSpawnerstationSubMenu.RecipesSelectionGroup.Entries[refinementstationRecipeEntry.EntryIndex]; + } + else + { +#if DEBUG + Utils.Logger.LogMessage($"Unknown/unhandled itemGUID SubMenu for Type: {__instance.GetScriptClassName()}"); +#endif + return; + } + requiredItemGUIDs = new(); + foreach(var requiredItem in recipe.Requirements) + { + requiredItemGUIDs.Add(requiredItem.Guid); + } + itemGUID = recipe.OutputItems[0].Guid; + } + else if(refinementstationRecipeItem != null) + { + itemGUID = refinementstationRecipeItem.Guid; + } + else if(itemGridSelectionEntry != null) + { + if(itemGridSelectionEntry.EntryId == PrefabGUID.Empty) + { + // It's an empty slot. + return; + } + itemGUID = itemGridSelectionEntry.EntryId; + + if(inventorySubMenu != null && itemGridSelectionEntry.SyncedEntity != Entity.Null) + { + if(entityManager.HasComponent(itemGridSelectionEntry.SyncedEntity)) + { + var prefabCollectionSystem = client.GetExistingSystem(); + var prefabLookupMap = prefabCollectionSystem.PrefabLookupMap; + + repairItemGUIDs = new(); + var durability = entityManager.GetComponentData(itemGridSelectionEntry.SyncedEntity); + var repairCosts = durability.GetRepairCost(prefabLookupMap, entityManager); + foreach(var repairCost in repairCosts) + { + repairItemGUIDs.Add(repairCost.Guid); + } + } + if(entityManager.HasComponent(itemGridSelectionEntry.SyncedEntity)) + { + storedBlood = entityManager.GetComponentData(itemGridSelectionEntry.SyncedEntity); + } + } + } + else if(workstationRecipeGridSelectionEntry != null) + { + WorkstationRecipeGridSelectionEntry.Data recipe; + if(workstationSubMenu != null) + { + recipe = workstationSubMenu.RecipesGridSelectionGroup.Entries[workstationRecipeGridSelectionEntry.EntryIndex]; + } + else if(inventorySubMenu != null) + { + recipe = inventorySubMenu.RecipesGridSelectionGroup.Entries[workstationRecipeGridSelectionEntry.EntryIndex]; + } + else + { +#if DEBUG + Utils.Logger.LogMessage($"Unknown/unhandled itemGUID SubMenu for Type: {__instance.GetScriptClassName()}"); +#endif + return; + } + if(gameDataSystem.RecipeHashLookupMap.ContainsKey(recipe.EntryId)) + { + var recipeData = gameDataSystem.RecipeHashLookupMap[recipe.EntryId]; + if(entityManager.HasComponent(recipeData.Entity)) + { + var requirements = entityManager.GetBuffer(recipeData.Entity); + requiredItemGUIDs = new(); + foreach(var requirement in requirements) + { + requiredItemGUIDs.Add(requirement.Guid); + } + } + if(entityManager.HasComponent(recipeData.Entity)) + { + itemGUID = entityManager.GetBuffer(recipeData.Entity)[0].Guid; + } + } + } + else if(researchEntry != null) + { + foreach(var category in researchstationSubMenu.ResearchCategories) + { + if(category.ResearchGridSelectionGroup.Entries.Count > researchEntry.EntryIndex && + category.ResearchGridSelectionGroup.Entries[researchEntry.EntryIndex].EntryId == researchEntry.EntryId) + { + var recipe = category.ResearchGridSelectionGroup.Entries[researchEntry.EntryIndex]; + requiredItemGUIDs = new(); + foreach(var requiredItem in recipe.Requirements) + { + if(Utils.TryGetPrefabGUIDForItemName(gameDataSystem, requiredItem.ItemName, out var requiredItemGUID)) + { + requiredItemGUIDs.Add(requiredItemGUID); + } + } + break; + } + } + } + else if(buildMenuStructureEntry != null) + { + if(gameDataSystem.BlueprintHashLookupMap.ContainsKey(buildMenuStructureEntry.PrefabGuid)) + { + var blueprintData = gameDataSystem.BlueprintHashLookupMap[buildMenuStructureEntry.PrefabGuid]; + if(entityManager.HasComponent(blueprintData.Entity)) + { + requiredItemGUIDs = new(); + var requirements = entityManager.GetBuffer(blueprintData.Entity); + foreach(var requirement in requirements) + { + requiredItemGUIDs.Add(requirement.PrefabGUID); + } + } + } + } + else + { +#if DEBUG + Utils.Logger.LogMessage($"Unknown/unhandled ItemGUID Retrieval for Type: {__instance.GetScriptClassName()}"); +#endif + return; + } + + int? stashCount = itemGUID == null ? null : Utils.GetStashItemCount(entityManager, teamChecker, character, itemGUID.Value, storedBlood); + + var requiredItemStashCount = requiredItemGUIDs?.Select(x => Utils.GetStashItemCount(entityManager, teamChecker, character, x)).ToList(); + var repairItemStashCount = repairItemGUIDs?.Select(x => Utils.GetStashItemCount(entityManager, teamChecker, character, x)).ToList(); + + if((stashCount.HasValue && stashCount.Value == -1) || + (requiredItemStashCount != null && requiredItemStashCount.Contains(-1)) || + (repairItemStashCount != null && repairItemStashCount.Contains(-1))) + { + // Don't add stash values outside of the castle, we can't count them anyway! + return; + } + + var id = tooltip.GetInstanceID(); + bool shouldStartRoutine = _tooltipInfo.TryGetValue(id, out var record); + _tooltipInfo[id] = (DateTime.UtcNow, stashCount, requiredItemStashCount, repairItemStashCount); + if(shouldStartRoutine || DateTime.UtcNow.Subtract(record.creationTime).TotalSeconds >= 0.2f) + { + __instance.StartCoroutine(UpdateTooltip(tooltip)); + } + } + + private static readonly Dictionary requiredItemStashCount, List repairItemStashCount)> _tooltipInfo = new(); + + private static IEnumerator UpdateTooltip(FakeTooltip tooltip) + { + var id = tooltip.GetInstanceID(); + var lastUpdateTime = DateTime.MinValue; + int? stashCount; + List requiredItemStashCount; + List repairItemStashCount; + + while(_tooltipInfo.TryGetValue(id, out var record) && (lastUpdateTime != record.creationTime || (!AllTextsContainStashInfo() && DateTime.UtcNow.Subtract(lastUpdateTime).TotalSeconds < 10f))) + { + yield return null; + + if(tooltip == null || tooltip.Name == null || tooltip.Name.Text == null || !_tooltipInfo.ContainsKey(id)) + { + break; + } + + (lastUpdateTime, stashCount, requiredItemStashCount, repairItemStashCount) = _tooltipInfo[id]; + + if(stashCount.HasValue) + { + tooltip.Name.Text.SetText($"{tooltip.Name._LocalizedString.Text} (Stash: {stashCount.Value})"); + } + + if(requiredItemStashCount != null) + { + for(int i = 0; i < requiredItemStashCount.Count; i++) + { + var requiredItem = tooltip.RequiredItemsList[i]; + requiredItem.Name.Text.SetText($"{requiredItem.Name._LocalizedString.Text} (Stash: {requiredItemStashCount[i]})"); + } + } + + if(repairItemStashCount != null) + { + for(int i = 0; i < repairItemStashCount.Count; i++) + { + var requiredItem = tooltip.RepairCostList[i]; + requiredItem.Name.Text.SetText($"{requiredItem.Name._LocalizedString.Text} (Stash: {repairItemStashCount[i]})"); + } + } + + var endTime = Time.realtimeSinceStartup + 0.2f; + while(endTime > Time.realtimeSinceStartup && _tooltipInfo.TryGetValue(id, out record) && lastUpdateTime == record.creationTime && AllTextsContainStashInfo()) + { + yield return null; + } + } + + _tooltipInfo.Remove(id); + + // Nested Method(s) + bool AllTextsContainStashInfo() + { + if(tooltip == null || tooltip.Name == null || tooltip.Name.Text == null || !tooltip.isActiveAndEnabled) + { + return true; + } + + string endPhrase = ""; + + if(!tooltip.Name.Text.text.EndsWith(endPhrase)) + { + return false; + } + + // Seems to cause some kind of infinite loop??? + /*foreach(var requiredItem in tooltip.RequiredItemsList) + { + if(!requiredItem.isActiveAndEnabled) + { + continue; + } + if(!requiredItem.Name.Text.text.EndsWith(endPhrase)) + { + return false; + } + } + + foreach(var repairItem in tooltip.RepairItemsList) + { + if(!repairItem.isActiveAndEnabled) + { + continue; + } + if(!repairItem.Name.Text.text.EndsWith(endPhrase)) + { + return false; + } + } + */ + + return true; + } + } + + #endregion + } +} diff --git a/ResourceStashWithdrawal/Plugin.cs b/ResourceStashWithdrawal/Plugin.cs new file mode 100644 index 0000000..f759d79 --- /dev/null +++ b/ResourceStashWithdrawal/Plugin.cs @@ -0,0 +1,50 @@ +using BepInEx; +using BepInEx.IL2CPP; +using HarmonyLib; +using System.Reflection; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.ResourceStashWithdrawal +{ + [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] + [BepInDependency("xyz.molenzwiebel.wetstone")] + [Reloadable] + public class Plugin : BasePlugin + { + #region Variables + + private Harmony _hooks; + + #endregion + + #region Public Methods + + public sealed override void Load() + { + Utils.Initialize(Log, PluginInfo.PLUGIN_NAME); + ResourceStashWithdrawalConfig.Initialize(Config); + if(VWorld.IsClient) + { + UIClickHook.Reset(); + } + + ResourceStashWithdrawalSystem.Initialize(); + + _hooks = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly()); + + Log.LogInfo($"Plugin {PluginInfo.PLUGIN_NAME} (v{PluginInfo.PLUGIN_VERSION}) is loaded!"); + } + + public sealed override bool Unload() + { + _hooks?.UnpatchSelf(); + ResourceStashWithdrawalSystem.Deinitialize(); + Config.Clear(); + Utils.Deinitialize(); + return true; + } + + #endregion + } +} diff --git a/ResourceStashWithdrawal/ResourceStashWithdrawal.csproj b/ResourceStashWithdrawal/ResourceStashWithdrawal.csproj new file mode 100644 index 0000000..f7a410a --- /dev/null +++ b/ResourceStashWithdrawal/ResourceStashWithdrawal.csproj @@ -0,0 +1,469 @@ + + + netstandard2.1 + VMods.ResourceStashWithdrawal + VMods.ResourceStashWithdrawal + Allows a player to withdraw required resources from his/her stash when clicking a recipe at a work or refinement station + 0.0.1 + true + latest + False + + + + M:\Games\Steam\steamapps\common\VRising\BepInEx\unhollowed + M:\Games\Steam\steamapps\common\VRising\BepInEx\WetstonePlugins + M:\Games\Steam\steamapps\common\VRising\VRising_Server\BepInEx\WetstonePlugins + + + + + + + + + + + + + + + + + + + + $(UnhollowedDllPath)\com.stunlock.console.dll + + + $(UnhollowedDllPath)\com.stunlock.metrics.dll + + + $(UnhollowedDllPath)\com.stunlock.network.lidgren.dll + + + $(UnhollowedDllPath)\com.stunlock.network.steam.dll + + + $(UnhollowedDllPath)\Il2CppMono.Security.dll + + + $(UnhollowedDllPath)\Il2CppSystem.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Configuration.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Core.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Data.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Numerics.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Runtime.Serialization.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.Linq.dll + + + $(UnhollowedDllPath)\Lidgren.Network.dll + + + $(UnhollowedDllPath)\MagicaCloth.dll + + + $(UnhollowedDllPath)\Malee.ReorderableList.dll + + + $(UnhollowedDllPath)\Newtonsoft.Json.dll + + + $(UnhollowedDllPath)\ProjectM.Behaviours.dll + + + $(UnhollowedDllPath)\ProjectM.Camera.dll + + + $(UnhollowedDllPath)\ProjectM.CastleBuilding.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Conversion.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Scripting.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.GeneratedNetCode.dll + + + $(UnhollowedDllPath)\ProjectM.Misc.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Pathfinding.dll + + + $(UnhollowedDllPath)\ProjectM.Presentation.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Roofs.dll + + + $(UnhollowedDllPath)\ProjectM.ScriptableSystems.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.dll + + + $(UnhollowedDllPath)\Il2Cppmscorlib.dll + + + $(UnhollowedDllPath)\ProjectM.dll + + + $(UnhollowedDllPath)\com.stunlock.network.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Terrain.dll + + + $(UnhollowedDllPath)\RootMotion.dll + + + $(UnhollowedDllPath)\Sequencer.dll + + + $(UnhollowedDllPath)\Stunlock.Fmod.dll + + + $(UnhollowedDllPath)\Unity.Burst.dll + + + $(UnhollowedDllPath)\Unity.Burst.Unsafe.dll + + + $(UnhollowedDllPath)\Unity.Collections.dll + + + $(UnhollowedDllPath)\Unity.Collections.LowLevel.ILSupport.dll + + + $(UnhollowedDllPath)\Unity.Deformations.dll + + + $(UnhollowedDllPath)\Unity.Entities.dll + + + $(UnhollowedDllPath)\ProjectM.HUD.dll + + + $(UnhollowedDllPath)\Unity.Entities.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Jobs.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Physics.dll + + + $(UnhollowedDllPath)\Unity.Physics.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Properties.dll + + + $(UnhollowedDllPath)\Unity.Rendering.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.Core.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Runtime.dll + + + $(UnhollowedDllPath)\Unity.Scenes.dll + + + $(UnhollowedDllPath)\Unity.Serialization.dll + + + $(UnhollowedDllPath)\Unity.Services.Analytics.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Configuration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Device.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Registration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Scheduler.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Telemetry.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Threading.dll + + + $(UnhollowedDllPath)\Unity.TextMeshPro.dll + + + $(UnhollowedDllPath)\Unity.Transforms.dll + + + $(UnhollowedDllPath)\Unity.Transforms.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.VisualEffectGraph.Runtime.dll + + + $(UnhollowedDllPath)\UnityEngine.dll + + + $(UnhollowedDllPath)\UnityEngine.AccessibilityModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AndroidJNIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AnimationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ARModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClothModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterInputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterRendererModule.dll + + + $(UnhollowedDllPath)\UnityEngine.CoreModule.dll + + + $(UnhollowedDllPath)\ProjectM.CodeGeneration.dll + + + $(UnhollowedDllPath)\Stunlock.Core.dll + + + $(UnhollowedDllPath)\UnityEngine.CrashReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DirectorModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DSPGraphModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GameCenterModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GridModule.dll + + + $(UnhollowedDllPath)\UnityEngine.HotReloadModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ImageConversionModule.dll + + + $(UnhollowedDllPath)\UnityEngine.IMGUIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputLegacyModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.JSONSerializeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.LocalizationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ParticleSystemModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PerformanceReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.Physics2DModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ProfilerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ScreenCaptureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SharedInternalsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteMaskModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteShapeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.StreamingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubstanceModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubsystemsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainPhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextCoreModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextRenderingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TilemapModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TLSModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UI.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsNativeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UmbraModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UNETModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityAnalyticsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityConnectModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityCurlModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityTestProtocolModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestTextureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestWWWModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VehiclesModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VFXModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VideoModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VirtualTexturingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VRModule.dll + + + $(UnhollowedDllPath)\UnityEngine.WindModule.dll + + + $(UnhollowedDllPath)\UnityEngine.XRModule.dll + + + $(UnhollowedDllPath)\VivoxUnity.dll + + + + + + + diff --git a/ResourceStashWithdrawal/Systems/ResourceStashWithdrawalRequest.cs b/ResourceStashWithdrawal/Systems/ResourceStashWithdrawalRequest.cs new file mode 100644 index 0000000..89a22d2 --- /dev/null +++ b/ResourceStashWithdrawal/Systems/ResourceStashWithdrawalRequest.cs @@ -0,0 +1,8 @@ +namespace VMods.ResourceStashWithdrawal +{ + public struct ResourceStashWithdrawalRequest + { + public int ItemGUIDHash; + public int Amount; + } +} diff --git a/ResourceStashWithdrawal/Systems/ResourceStashWithdrawalSystem.cs b/ResourceStashWithdrawal/Systems/ResourceStashWithdrawalSystem.cs new file mode 100644 index 0000000..2f47562 --- /dev/null +++ b/ResourceStashWithdrawal/Systems/ResourceStashWithdrawalSystem.cs @@ -0,0 +1,109 @@ +using ProjectM; +using ProjectM.Network; +using ProjectM.Scripting; +using System; +using Unity.Entities; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.ResourceStashWithdrawal +{ + public static class ResourceStashWithdrawalSystem + { + #region Public Methods + + public static void Initialize() + { + VNetworkRegistry.RegisterServerboundStruct(OnResourceStashWithdrawalRequest); + } + + public static void Deinitialize() + { + VNetworkRegistry.UnregisterStruct(); + } + + private static void OnResourceStashWithdrawalRequest(FromCharacter fromCharacter, ResourceStashWithdrawalRequest request) + { + if(!VWorld.IsServer || fromCharacter.Character == Entity.Null) + { + // This isn't running on a server, or a non-existing character made the request -> stop trying to move items + return; + } + + var server = VWorld.Server; + var gameManager = server.GetExistingSystem()?._ServerGameManager; + var teamChecker = gameManager._TeamChecker; + var gameDataSystem = server.GetExistingSystem(); + var itemHashLookupMap = gameDataSystem.ItemHashLookupMap; + var prefabCollectionSystem = server.GetExistingSystem(); + var prefabLookupMap = prefabCollectionSystem.PrefabLookupMap; + var entityManager = server.EntityManager; + + if(!InventoryUtilities.TryGetInventoryEntity(entityManager, fromCharacter.Character, out Entity playerInventory) || playerInventory == Entity.Null) + { + // Player inventory couldn't be found -> stop trying to move items + return; + } + + var remainingAmount = request.Amount; + + var stashes = Utils.GetAlliedStashes(entityManager, teamChecker, fromCharacter.Character); + foreach(var stash in stashes) + { + var stashInventory = entityManager.GetBuffer(stash); + + for(int i = 0; i < stashInventory.Length; i++) + { + var stashItem = stashInventory[i]; + + // Only withdraw the requested item + if(stashItem.ItemType.GuidHash != request.ItemGUIDHash) + { + continue; + } + + var transferAmount = Math.Min(remainingAmount, stashItem.Stacks); + if(!Utils.TryGiveItem(entityManager, itemHashLookupMap, playerInventory, stashItem.ItemType, transferAmount, out int remainingStacks, out _)) + { + // Failed to add the item(s) to the player's inventory -> stop trying to move any items at all + return; + } + transferAmount -= remainingStacks; + if(!InventoryUtilitiesServer.TryRemoveItem(entityManager, stash, stashItem.ItemType, transferAmount)) + { + // Failed to remove the item from the stash -> Remove the items from the player's inventory & stop trying to move any items at all + InventoryUtilitiesServer.TryRemoveItem(entityManager, playerInventory, stashItem.ItemType, transferAmount); + return; + } + + InventoryUtilitiesServer.CreateInventoryChangedEvent(entityManager, fromCharacter.Character, stashItem.ItemType, stashItem.Stacks, InventoryChangedEventType.Moved); + remainingAmount -= transferAmount; + if(remainingAmount <= 0) + { + break; + } + } + + if(remainingAmount <= 0) + { + break; + } + } + + if(remainingAmount > 0) + { + var name = Utils.GetItemName(new PrefabGUID(request.ItemGUIDHash), gameDataSystem, entityManager, prefabLookupMap); + if(remainingAmount == request.Amount) + { + Utils.SendMessage(fromCharacter.User, $"Couldn't find any {name} in the stash(es).", ServerChatMessageType.System); + } + else + { + Utils.SendMessage(fromCharacter.User, $"Couldn't find all {name} in the stash(es). {remainingAmount} {(remainingAmount == 1 ? "is" : "are")} missing.", ServerChatMessageType.System); + } + } + } + + #endregion + } +} diff --git a/Shared/BuffSystemHook.cs b/Shared/BuffSystemHook.cs new file mode 100644 index 0000000..36088ce --- /dev/null +++ b/Shared/BuffSystemHook.cs @@ -0,0 +1,43 @@ +using HarmonyLib; +using ProjectM; +using Unity.Collections; +using Unity.Entities; +using Wetstone.API; + +namespace VMods.Shared +{ + [HarmonyPatch] + public static class BuffSystemHook + { + #region Events + + public delegate void ProcessBuffEventHandler(Entity entity, PrefabGUID buffGUID); + public static event ProcessBuffEventHandler ProcessBuffEvent; + private static void FireProcessBuffEvent(Entity entity, PrefabGUID buffGUID) => ProcessBuffEvent?.Invoke(entity, buffGUID); + + #endregion + + #region Private Methods + + [HarmonyPatch(typeof(BuffSystem_Spawn_Server), nameof(BuffSystem_Spawn_Server.OnUpdate))] + [HarmonyPrefix] + private static void OnUpdate(BuffSystem_Spawn_Server __instance) + { + if(!VWorld.IsServer || __instance.__OnUpdate_LambdaJob0_entityQuery == null) + { + return; + } + + var entityManager = __instance.EntityManager; + + var entities = __instance.__OnUpdate_LambdaJob0_entityQuery.ToEntityArray(Allocator.Temp); + foreach(var entity in entities) + { + PrefabGUID buffGUID = entityManager.GetComponentData(entity); + FireProcessBuffEvent(entity, buffGUID); + } + } + + #endregion + } +} diff --git a/Shared/CommandSystem/Command.cs b/Shared/CommandSystem/Command.cs new file mode 100644 index 0000000..87a89b6 --- /dev/null +++ b/Shared/CommandSystem/Command.cs @@ -0,0 +1,34 @@ +using ProjectM.Network; +using Unity.Entities; + +namespace VMods.Shared +{ + public class Command + { + #region Properties + + public string Name { get; } + public string[] Args { get; } + + public User User { get; } + public Entity SenderUserEntity { get; } + public Entity SenderCharEntity { get; } + + public bool Used { get; private set; } + + #endregion + + #region Lifecycle + + public Command(User user, Entity senderUserEntity, Entity senderCharEntity, string name, params string[] args) + => (User, SenderUserEntity, SenderCharEntity, Name, Args) = (user, senderUserEntity, senderCharEntity, name, args); + + #endregion + + #region Public Methods + + public void Use() => Used = true; + + #endregion + } +} diff --git a/Shared/CommandSystem/CommandAttribute.cs b/Shared/CommandSystem/CommandAttribute.cs new file mode 100644 index 0000000..8689557 --- /dev/null +++ b/Shared/CommandSystem/CommandAttribute.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace VMods.Shared +{ + [AttributeUsage(AttributeTargets.Method)] + public class CommandAttribute : Attribute + { + #region Properties + + public IReadOnlyList Names { get; } + public string Usage { get; } + public string Description { get; } + public bool ReqAdmin { get; } + + #endregion + + #region Livecycle + + public CommandAttribute(string name, string usage = "", string description = "", bool reqAdmin = false) + { + Names = name.Split(',').Select(x => x.Trim()).ToList(); + Usage = usage; + Description = description; + ReqAdmin = reqAdmin; + } + + #endregion + } +} diff --git a/Shared/CommandSystem/CommandExtensions.cs b/Shared/CommandSystem/CommandExtensions.cs new file mode 100644 index 0000000..1d5126a --- /dev/null +++ b/Shared/CommandSystem/CommandExtensions.cs @@ -0,0 +1,38 @@ +using ProjectM.Network; +using Unity.Entities; +using Wetstone.API; + +namespace VMods.Shared +{ + public static class CommandExtensions + { + public static (string searchUsername, FromCharacter? fromCharacter) GetFromCharacter(this Command command, int argIdx = 0, bool sendCannotBeFoundMessage = true, EntityManager? entityManager = null) + { + FromCharacter? fromCharacter; + string searchUsername; + + entityManager ??= Utils.CurrentWorld.EntityManager; + + if(argIdx >= 0 && command.Args.Length >= (argIdx + 1)) + { + searchUsername = command.Args[0]; + fromCharacter = Utils.GetFromCharacter(searchUsername, entityManager); + } + else + { + searchUsername = command.User.CharacterName.ToString(); + fromCharacter = new FromCharacter() + { + User = command.SenderUserEntity, + Character = command.SenderCharEntity, + }; + } + + if(sendCannotBeFoundMessage && !fromCharacter.HasValue) + { + command.User.SendSystemMessage($"[{Utils.PluginName}] Vampire {searchUsername} couldn't be found."); + } + return (searchUsername, fromCharacter); + } + } +} diff --git a/Shared/CommandSystem/CommandSystem.cs b/Shared/CommandSystem/CommandSystem.cs new file mode 100644 index 0000000..afb708b --- /dev/null +++ b/Shared/CommandSystem/CommandSystem.cs @@ -0,0 +1,265 @@ +using ProjectM.Network; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Wetstone.API; +using Wetstone.Hooks; + +namespace VMods.Shared +{ + public static class CommandSystem + { + #region Consts + + private const char CommandSplitChar = ' '; + + #endregion + + #region Variables + + private static readonly Dictionary _lastUsedCommandTimes = new(); + + private static List<(MethodInfo method, CommandAttribute attribute)> _commandReflectionMethods; + private static readonly List<(Action method, CommandAttribute attribute)> _commandMethods = new(); + + #endregion + + #region Public Methods + + public static void Initialize() + { + if(!VWorld.IsServer) + { + Utils.Logger.LogMessage($"{nameof(CommandSystem)} only needs to be called server-side."); + return; + } + + _commandReflectionMethods = Assembly.GetExecutingAssembly().GetTypes().SelectMany(x => x.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).Where(y => y.GetCustomAttributes(false).Count() > 0)).Select(x => (x, x.GetCustomAttribute(false))).ToList(); + + Chat.OnChatMessage += OnChatMessage; + } + + public static void Deinitialize() + { + Chat.OnChatMessage -= OnChatMessage; + _commandReflectionMethods?.Clear(); + _commandMethods.Clear(); + } + + public static void RegisterCommand(Action commandMethod, CommandAttribute commandAttribute) + { + _commandMethods.Add((commandMethod, commandAttribute)); + } + + public static void UnregisterCommand(Action commandMethod, CommandAttribute commandAttribute) + { + _commandMethods.RemoveAll(x => x.method == commandMethod && x.attribute == commandAttribute); + } + + public static void SendInvalidCommandMessage(Command command, bool invalidArgument = false) + { + SendInvalidCommandMessage(command.User, invalidArgument); + } + + public static void SendInvalidCommandMessage(User user, bool invalidArgument = false) + { + user.SendSystemMessage($"Invalid command{(invalidArgument ? " argument" : string.Empty)}. Check {CommandSystemConfig.CommandSystemPrefix.Value}help [] for more information."); + } + + #endregion + + #region Private Methods + + private static void OnChatMessage(VChatEvent chatEvent) + { + if(!CommandSystemConfig.CommandSystemEnabled.Value) + { + return; + } + if(chatEvent.Cancelled) + { + return; + } + string commandPrefix = CommandSystemConfig.CommandSystemPrefix.Value; + string message = chatEvent.Message; + if(chatEvent.Cancelled || !message.StartsWith(commandPrefix, StringComparison.Ordinal)) + { + return; + } + + PruneCommandTimes(); + + // Extract the command name and arguments + string name; + string[] args; + if(message.Contains(CommandSplitChar)) + { + var splitted = message.Split(CommandSplitChar); + name = splitted[0][commandPrefix.Length..]; + args = splitted.Skip(1).ToArray(); + } + else + { + name = message[commandPrefix.Length..]; + args = new string[0]; + } + + // Anti-spam for non-admins + var user = chatEvent.User; + if(!user.IsAdmin) + { + if(_lastUsedCommandTimes.TryGetValue(user.PlatformId, out var lastUsedCommandTime)) + { + var timeDiff = DateTime.UtcNow.Subtract(lastUsedCommandTime); + if(timeDiff.TotalSeconds < CommandSystemConfig.CommandSystemCommandCooldown.Value) + { + int waitTime = (int)Math.Ceiling(CommandSystemConfig.CommandSystemCommandCooldown.Value - timeDiff.TotalSeconds); + chatEvent.User.SendSystemMessage($"Please wait for {waitTime} second(s) before sending another command."); + chatEvent.Cancel(); + return; + } + } + _lastUsedCommandTimes[user.PlatformId] = DateTime.UtcNow; + } + + // Fire the command (so an event handler can actually handle/execute it) + Command command = new(user, chatEvent.SenderUserEntity, chatEvent.SenderCharacterEntity, name, args); + + foreach((var method, var attribute) in _commandMethods) + { + if(!attribute.Names.Contains(command.Name) || (attribute.ReqAdmin && !user.IsAdmin)) + { + continue; + } + try + { + method.Invoke(command); + } + catch(Exception ex) + { + SendInvalidCommandMessage(command); + throw ex; + } + if(command.Used) + { + break; + } + } + + if(!command.Used) + { + foreach((var method, var attribute) in _commandReflectionMethods) + { + if(!attribute.Names.Contains(command.Name) || (attribute.ReqAdmin && !user.IsAdmin)) + { + continue; + } + try + { + method.Invoke(null, new[] { command }); + } + catch(Exception ex) + { + SendInvalidCommandMessage(command); + throw ex; + } + if(command.Used) + { + break; + } + } + } + + if(command.Used) + { + chatEvent.Cancel(); + } + } + + private static void PruneCommandTimes() + { + var now = DateTime.UtcNow; + var keys = _lastUsedCommandTimes.Keys.ToList(); + var cooldown = CommandSystemConfig.CommandSystemCommandCooldown.Value; + foreach(var key in keys) + { + var lastUsedCommandTime = _lastUsedCommandTimes[key]; + if(now.Subtract(lastUsedCommandTime).TotalSeconds >= cooldown) + { + _lastUsedCommandTimes.Remove(key); + } + } + } + + [Command("help", "help []", "Shows a list of commands, or details about a command.")] + private static void OnHelpCommand(Command command) + { + var commandPrefix = CommandSystemConfig.CommandSystemPrefix.Value; + var user = command.User; + switch(command.Args.Length) + { + case 0: + { + user.SendSystemMessage($"List of {Utils.PluginName} commands:"); + _commandMethods.ForEach(x => SendCommandInfo(x.attribute)); + _commandReflectionMethods.ForEach(x => SendCommandInfo(x.attribute)); + + // Nested Method(s) + void SendCommandInfo(CommandAttribute attribute) + { + if(attribute.ReqAdmin && !user.IsAdmin) + { + return; + } + string message = $"{string.Join(", ", attribute.Names.Select(x => $"{commandPrefix}{x}"))}"; + if(attribute.ReqAdmin) + { + message += " - [ADMIN]"; + } + message += $" - {attribute.Description}"; + user.SendSystemMessage(message); + } + } + break; + + case 1: + { + string searchCommandName = command.Args[0]; + + // Find the command info + CommandAttribute attribute = null; + if(_commandMethods.Exists(x => x.attribute.Names.Contains(searchCommandName))) + { + attribute = _commandMethods.Find(x => x.attribute.Names.Contains(searchCommandName)).attribute; + } + else if(_commandReflectionMethods.Exists(x => x.attribute.Names.Contains(searchCommandName))) + { + attribute = _commandReflectionMethods.Find(x => x.attribute.Names.Contains(searchCommandName)).attribute; + } + + // Check the found info + if(attribute == null || attribute.ReqAdmin && !user.IsAdmin) + { + return; + } + + user.SendSystemMessage($"Help for {commandPrefix}{attribute.Names[0]}"); + if(attribute.Names.Count > 1) + { + user.SendSystemMessage($"Aliases: {string.Join(", ", attribute.Names.Skip(1).Select(x => $"{commandPrefix}{x}"))}"); + } + user.SendSystemMessage($"Description: {attribute.Description}"); + user.SendSystemMessage($"Usage: {commandPrefix}{attribute.Usage}"); + } + return; + + default: + SendInvalidCommandMessage(command); + return; + } + } + + #endregion + } +} diff --git a/Shared/CommandSystem/CommandSystemConfig.cs b/Shared/CommandSystem/CommandSystemConfig.cs new file mode 100644 index 0000000..0058b5a --- /dev/null +++ b/Shared/CommandSystem/CommandSystemConfig.cs @@ -0,0 +1,26 @@ +using BepInEx.Configuration; + +namespace VMods.Shared +{ + public static class CommandSystemConfig + { + #region Properties + + public static ConfigEntry CommandSystemEnabled { get; private set; } + public static ConfigEntry CommandSystemPrefix { get; private set; } + public static ConfigEntry CommandSystemCommandCooldown { get; private set; } + + #endregion + + #region Public Methods + + public static void Initialize(ConfigFile config) + { + CommandSystemEnabled = config.Bind(nameof(CommandSystemConfig), nameof(CommandSystemEnabled), true, "Enabled/disable the Commands system (for this specific mod)."); + CommandSystemPrefix = config.Bind(nameof(CommandSystemConfig), nameof(CommandSystemPrefix), "!", "The prefix that needs to be used to execute a command (for this specific mod)."); + CommandSystemCommandCooldown = config.Bind(nameof(CommandSystemConfig), nameof(CommandSystemCommandCooldown), 5f, "The amount of seconds between two commands (for non-admins)."); + } + + #endregion + } +} diff --git a/Shared/ExtensionMethods.cs b/Shared/ExtensionMethods.cs new file mode 100644 index 0000000..a7f9f39 --- /dev/null +++ b/Shared/ExtensionMethods.cs @@ -0,0 +1,28 @@ +using System; + +namespace VMods.Shared +{ + public static class ExtensionMethods + { + public static string ToAgoString(this TimeSpan timeSpan) + { + if(timeSpan.TotalDays >= 1d) + { + return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours}h {timeSpan.Minutes}m {timeSpan.Seconds}s"; + } + else if(timeSpan.TotalHours >= 1d) + { + return $"{timeSpan.Hours}h {timeSpan.Minutes}m {timeSpan.Seconds}s"; + } + else if(timeSpan.TotalMinutes >= 1d) + { + return $"{timeSpan.Minutes}m {timeSpan.Seconds}s"; + } + else if(timeSpan.TotalSeconds >= 1d) + { + return $"{timeSpan.Seconds}s"; + } + return $"{timeSpan.Milliseconds}ms"; + } + } +} diff --git a/Shared/HighestGearScoreSystem/EquipmentHooks.cs b/Shared/HighestGearScoreSystem/EquipmentHooks.cs new file mode 100644 index 0000000..421b07d --- /dev/null +++ b/Shared/HighestGearScoreSystem/EquipmentHooks.cs @@ -0,0 +1,218 @@ +using HarmonyLib; +using ProjectM; +using ProjectM.Network; +using System.Collections.Generic; +using Unity.Collections; +using Wetstone.API; + +namespace VMods.Shared +{ + [HarmonyPatch] + public static class EquipmentHooks + { + #region Events + + public delegate void EquipmentChangedEventHandler(FromCharacter fromCharacter); + public static event EquipmentChangedEventHandler EquipmentChangedEvent; + private static void FireEquipmentChangedEvent(FromCharacter fromCharacter) => EquipmentChangedEvent?.Invoke(fromCharacter); + + #endregion + + #region Private Methods + + [HarmonyPatch(typeof(EquipItemSystem), nameof(EquipItemSystem.OnUpdate))] + [HarmonyPostfix] + private static void EquipItem(EquipItemSystem __instance) + { + if(!VWorld.IsServer || __instance.__OnUpdate_LambdaJob0_entityQuery == null) + { + return; + } + + var entityManager = VWorld.Server.EntityManager; + var entities = __instance.__OnUpdate_LambdaJob0_entityQuery.ToEntityArray(Allocator.Temp); + foreach(var entity in entities) + { + var fromCharacter = entityManager.GetComponentData(entity); + FireEquipmentChangedEvent(fromCharacter); + } + } + + [HarmonyPatch(typeof(EquipItemFromInventorySystem), nameof(EquipItemFromInventorySystem.OnUpdate))] + [HarmonyPostfix] + private static void EquipItemFromInventory(EquipItemFromInventorySystem __instance) + { + if(!VWorld.IsServer || __instance.__EquipItemFromInventoryJob_entityQuery == null) + { + return; + } + + var entityManager = VWorld.Server.EntityManager; + var entities = __instance.__EquipItemFromInventoryJob_entityQuery.ToEntityArray(Allocator.Temp); + foreach(var entity in entities) + { + var fromCharacter = entityManager.GetComponentData(entity); + FireEquipmentChangedEvent(fromCharacter); + } + } + + [HarmonyPatch(typeof(UnequipItemSystem), nameof(UnequipItemSystem.OnUpdate))] + [HarmonyPostfix] + private static void UnequipItem(UnequipItemSystem __instance) + { + if(!VWorld.IsServer || __instance.__OnUpdate_LambdaJob0_entityQuery == null) + { + return; + } + + var entityManager = VWorld.Server.EntityManager; + var entities = __instance.__OnUpdate_LambdaJob0_entityQuery.ToEntityArray(Allocator.Temp); + foreach(var entity in entities) + { + var fromCharacter = entityManager.GetComponentData(entity); + FireEquipmentChangedEvent(fromCharacter); + } + } + + [HarmonyPatch(typeof(MoveItemBetweenInventoriesSystem), nameof(MoveItemBetweenInventoriesSystem.OnUpdate))] + private static class MoveItemBetweenInventories + { + private static void Prefix(MoveItemBetweenInventoriesSystem __instance, out List __state) + { + __state = new List(); + if(!VWorld.IsServer || __instance._MoveItemBetweenInventoriesEventQuery == null) + { + return; + } + + var entityManager = VWorld.Server.EntityManager; + var entities = __instance._MoveItemBetweenInventoriesEventQuery.ToEntityArray(Allocator.Temp); + foreach(var entity in entities) + { + var fromCharacter = entityManager.GetComponentData(entity); + if(!__state.Contains(fromCharacter)) + { + __state.Add(fromCharacter); + } + } + } + + private static void Postfix(List __state) + { + __state.ForEach(FireEquipmentChangedEvent); + } + } + + [HarmonyPatch(typeof(MoveAllItemsBetweenInventoriesSystem), nameof(MoveAllItemsBetweenInventoriesSystem.OnUpdate))] + private static class MoveAllItemsBetweenInventories + { + private static void Prefix(MoveAllItemsBetweenInventoriesSystem __instance, out List __state) + { + __state = new List(); + if(!VWorld.IsServer || __instance.__MoveAllItemsJob_entityQuery == null) + { + return; + } + + var entityManager = VWorld.Server.EntityManager; + var entities = __instance.__MoveAllItemsJob_entityQuery.ToEntityArray(Allocator.Temp); + foreach(var entity in entities) + { + var fromCharacter = entityManager.GetComponentData(entity); + if(!__state.Contains(fromCharacter)) + { + __state.Add(fromCharacter); + } + } + } + + private static void Postfix(List __state) + { + __state.ForEach(FireEquipmentChangedEvent); + } + } + + [HarmonyPatch(typeof(DropInventoryItemSystem), nameof(DropInventoryItemSystem.OnUpdate))] + [HarmonyPostfix] + private static void DropInventoryItem(DropInventoryItemSystem __instance) + { + if(!VWorld.IsServer || __instance.__DropInventoryItemJob_entityQuery == null) + { + return; + } + + var entityManager = VWorld.Server.EntityManager; + var entities = __instance.__DropInventoryItemJob_entityQuery.ToEntityArray(Allocator.Temp); + foreach(var entity in entities) + { + var fromCharacter = entityManager.GetComponentData(entity); + FireEquipmentChangedEvent(fromCharacter); + } + } + + [HarmonyPatch(typeof(DropItemSystem), nameof(DropItemSystem.OnUpdate))] + private static class DropItem + { + private static void Prefix(DropItemSystem __instance, out List __state) + { + __state = new List(); + if(!VWorld.IsServer || __instance.__DropEquippedItemJob_entityQuery == null || __instance.__DropEquippedItemJob_entityQuery == null) + { + return; + } + + var entityManager = VWorld.Server.EntityManager; + var entities = __instance.__DropEquippedItemJob_entityQuery.ToEntityArray(Allocator.Temp); + foreach(var entity in entities) + { + var fromCharacter = entityManager.GetComponentData(entity); + if(!__state.Contains(fromCharacter)) + { + __state.Add(fromCharacter); + } + } + + entities = __instance.__DropItemsJob_entityQuery.ToEntityArray(Allocator.Temp); + foreach(var entity in entities) + { + var fromCharacter = entityManager.GetComponentData(entity); + if(!__state.Contains(fromCharacter)) + { + __state.Add(fromCharacter); + } + } + } + + private static void Postfix(List __state) + { + __state.ForEach(FireEquipmentChangedEvent); + } + } + + [HarmonyPatch(typeof(ItemPickupSystem), nameof(ItemPickupSystem.OnUpdate))] + [HarmonyPostfix] + private static void ItemPickup(ItemPickupSystem __instance) + { + if(!VWorld.IsServer || __instance.__OnUpdate_LambdaJob0_entityQuery == null) + { + return; + } + + var entityManager = VWorld.Server.EntityManager; + var entities = __instance.__OnUpdate_LambdaJob0_entityQuery.ToEntityArray(Allocator.Temp); + foreach(var entity in entities) + { + var ownerData = entityManager.GetComponentData(entity); + var characterEntity = ownerData.Owner; + var playerCharacter = entityManager.GetComponentData(characterEntity); + FireEquipmentChangedEvent(new FromCharacter() + { + Character = characterEntity, + User = playerCharacter.UserEntity._Entity, + }); + } + } + + #endregion + } +} diff --git a/Shared/HighestGearScoreSystem/HighestGearScoreSystem.cs b/Shared/HighestGearScoreSystem/HighestGearScoreSystem.cs new file mode 100644 index 0000000..1b5e6d3 --- /dev/null +++ b/Shared/HighestGearScoreSystem/HighestGearScoreSystem.cs @@ -0,0 +1,184 @@ +using ProjectM; +using ProjectM.Network; +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.Entities; +using Wetstone.API; + +namespace VMods.Shared +{ + public static class HighestGearScoreSystem + { + #region Variables + + private static Dictionary _gearScoreData; + + #endregion + + #region Properties + + private static string HighestGearScoreFileName => $"{Utils.PluginName}-HighestGearScore.json"; + + #endregion + + #region Public Methods + + public static void Initialize() + { + _gearScoreData = VModStorage.Load(HighestGearScoreFileName, () => new Dictionary()); + + VModStorage.SaveEvent += Save; + EquipmentHooks.EquipmentChangedEvent += OnEquipmentChanged; + VampireDownedHook.VampireDownedEvent += OnVampireDowned; + } + + public static void Deinitialize() + { + VampireDownedHook.VampireDownedEvent -= OnVampireDowned; + EquipmentHooks.EquipmentChangedEvent -= OnEquipmentChanged; + VModStorage.SaveEvent -= Save; + } + + public static void Save() + { + PruneHighestGearScores(); + + VModStorage.Save(HighestGearScoreFileName, _gearScoreData); + } + + public static float GetCurrentOrHighestGearScore(FromCharacter fromCharacter) + { + var entityManager = VWorld.Server.EntityManager; + if(HighestGearScoreSystemConfig.HighestGearScoreSystemEnabled.Value) + { + PruneHighestGearScores(); + + var user = entityManager.GetComponentData(fromCharacter.User); + if(_gearScoreData.TryGetValue(user.PlatformId, out var gearScoreData)) + { + return gearScoreData.HighestGearScore; + } + } + return GetCurrentGearScore(fromCharacter, entityManager); + } + + public static float GetCurrentGearScore(FromCharacter fromCharacter, EntityManager entityManager) + { + return GetCurrentGearScore(fromCharacter.Character, entityManager); + } + + public static float GetCurrentGearScore(Entity characterEntity, EntityManager entityManager) + { + var equipment = entityManager.GetComponentData(characterEntity); + return equipment.ArmorLevel + equipment.WeaponLevel + equipment.SpellLevel; + } + + #endregion + + #region Private Methods + + private static void PruneHighestGearScores() + { + var now = DateTime.UtcNow; + var keys = _gearScoreData.Keys.ToList(); + var duration = HighestGearScoreSystemConfig.HighestGearScoreDuration.Value; + foreach(var key in keys) + { + var gearScoreData = _gearScoreData[key]; + if(now.Subtract(gearScoreData.LastUpdated).TotalSeconds > duration) + { + _gearScoreData.Remove(key); + } + } + } + + private static void OnEquipmentChanged(FromCharacter fromCharacter) + { + if(!HighestGearScoreSystemConfig.HighestGearScoreSystemEnabled.Value) + { + return; + } + + var entityManager = VWorld.Server.EntityManager; + var user = entityManager.GetComponentData(fromCharacter.User); + if(!_gearScoreData.TryGetValue(user.PlatformId, out var gearScoreData)) + { + gearScoreData = new GearScoreData(); + _gearScoreData.Add(user.PlatformId, gearScoreData); + } + + if(DateTime.UtcNow.Subtract(gearScoreData.LastUpdated).TotalSeconds >= 30f) + { + gearScoreData.HighestGearScore = 0f; + } + + float gearScore = GetCurrentGearScore(fromCharacter.Character, entityManager); + gearScoreData.HighestGearScore = Math.Max(gearScoreData.HighestGearScore, gearScore); + gearScoreData.LastUpdated = DateTime.UtcNow; + +#if DEBUG + //var message = $"Highest Gearscore Updated: {gearScoreData.HighestGearScore} (Current: {gearScore})"; + //Utils.Logger.LogMessage(message); + //user.SendSystemMessage($"Highest Gearscore Updated: {gearScoreData.HighestGearScore} (Current: {gearScore})"); +#endif + } + + private static void OnVampireDowned(Entity killer, Entity victim) + { + var entityManager = VWorld.Server.EntityManager; + var victimCharacter = entityManager.GetComponentData(victim); + var victumUserEntity = victimCharacter.UserEntity._Entity; + var victumUser = entityManager.GetComponentData(victumUserEntity); + + _gearScoreData.Remove(victumUser.PlatformId); + } + + [Command("highestgs,hgs,higs,highgs,highestgearscore", "highestgs []", "Tells you what the highest gear score is for the given player (or yourself when noplayername is given)", true)] + private static void OnHighestGearScoreCommand(Command command) + { + var entityManager = VWorld.Server.EntityManager; + (var searchUsername, var fromCharacter) = command.GetFromCharacter(entityManager: entityManager); + + if(fromCharacter.HasValue) + { + var user = entityManager.GetComponentData(fromCharacter.Value.User); + if(_gearScoreData.TryGetValue(user.PlatformId, out var gearScoreData)) + { + TimeSpan diff = DateTime.UtcNow.Subtract(gearScoreData.LastUpdated); + command.User.SendSystemMessage($"[{Utils.PluginName}] The Highest Gear Score for {searchUsername} (Lv: {GetCurrentGearScore(fromCharacter.Value, entityManager)}) was {gearScoreData.HighestGearScore} (Last updated {diff.ToAgoString()} ago)."); + } + else + { + command.User.SendSystemMessage($"[{Utils.PluginName}] No Highest Gear Score is recorded for {searchUsername} (Lv: {GetCurrentGearScore(fromCharacter.Value, entityManager)})."); + } + } + } + + [Command("clearhgs,resethgs,clearhighestgearscore,resethighestgearscore", "clearhgs []", "Removes the current Highest Gear Score record for the given player (or yourself when noplayername is given)", true)] + private static void OnResetHighestGearScoreCommand(Command command) + { + var entityManager = VWorld.Server.EntityManager; + (var searchUsername, var fromCharacter) = command.GetFromCharacter(entityManager: entityManager); + + if(fromCharacter.HasValue) + { + var user = entityManager.GetComponentData(fromCharacter.Value.User); + _gearScoreData.Remove(user.PlatformId); + command.User.SendSystemMessage($"[{Utils.PluginName}] Removed the Highest Gear Score record for {searchUsername}."); + } + } + + #endregion + + #region Nested + + private class GearScoreData + { + public float HighestGearScore { get; set; } + public DateTime LastUpdated { get; set; } + } + + #endregion + } +} diff --git a/Shared/HighestGearScoreSystem/HighestGearScoreSystemConfig.cs b/Shared/HighestGearScoreSystem/HighestGearScoreSystemConfig.cs new file mode 100644 index 0000000..a7a0581 --- /dev/null +++ b/Shared/HighestGearScoreSystem/HighestGearScoreSystemConfig.cs @@ -0,0 +1,24 @@ +using BepInEx.Configuration; + +namespace VMods.Shared +{ + public static class HighestGearScoreSystemConfig + { + #region Properties + + public static ConfigEntry HighestGearScoreSystemEnabled { get; private set; } + public static ConfigEntry HighestGearScoreDuration { get; private set; } + + #endregion + + #region Public Methods + + public static void Initialize(ConfigFile config) + { + HighestGearScoreSystemEnabled = config.Bind(nameof(HighestGearScoreSystemConfig), nameof(HighestGearScoreSystemEnabled), true, "Enabled/disable the Highest Gear Score system (for this specific mod)."); + HighestGearScoreDuration = config.Bind(nameof(HighestGearScoreSystemConfig), nameof(HighestGearScoreDuration), 600f, "The amount of seconds the highest gear score is remembered/stored."); + } + + #endregion + } +} diff --git a/Shared/SaveHook.cs b/Shared/SaveHook.cs new file mode 100644 index 0000000..8364391 --- /dev/null +++ b/Shared/SaveHook.cs @@ -0,0 +1,27 @@ +using HarmonyLib; +using ProjectM; + +namespace VMods.Shared +{ + [HarmonyPatch] + public static class SaveHook + { + #region Private Methods + + [HarmonyPatch(typeof(TriggerPersistenceSaveSystem), nameof(TriggerPersistenceSaveSystem.TriggerSave))] + [HarmonyPrefix] + private static void TriggerSave() + { + VModStorage.SaveAll(); + } + + [HarmonyPatch(typeof(ServerBootstrapSystem), nameof(ServerBootstrapSystem.OnDestroy))] + [HarmonyPrefix] + private static void OnDestroy() + { + VModStorage.SaveAll(); + } + + #endregion + } +} diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj new file mode 100644 index 0000000..f05534d --- /dev/null +++ b/Shared/Shared.csproj @@ -0,0 +1,461 @@ + + + netstandard2.1 + VMods.Shared + VMods.Shared + A set of shared classes and utilites for all VMods + 0.0.1 + true + latest + False + + + + M:\Games\Steam\steamapps\common\VRising\BepInEx\unhollowed + M:\Games\Steam\steamapps\common\VRising\BepInEx\WetstonePlugins + M:\Games\Steam\steamapps\common\VRising\VRising_Server\BepInEx\WetstonePlugins + + + + + + + + + + + + + + + + $(UnhollowedDllPath)\com.stunlock.console.dll + + + $(UnhollowedDllPath)\com.stunlock.metrics.dll + + + $(UnhollowedDllPath)\com.stunlock.network.lidgren.dll + + + $(UnhollowedDllPath)\com.stunlock.network.steam.dll + + + $(UnhollowedDllPath)\Il2CppMono.Security.dll + + + $(UnhollowedDllPath)\Il2CppSystem.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Configuration.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Core.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Data.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Numerics.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Runtime.Serialization.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.dll + + + $(UnhollowedDllPath)\Il2CppSystem.Xml.Linq.dll + + + $(UnhollowedDllPath)\Lidgren.Network.dll + + + $(UnhollowedDllPath)\MagicaCloth.dll + + + $(UnhollowedDllPath)\Malee.ReorderableList.dll + + + $(UnhollowedDllPath)\Newtonsoft.Json.dll + + + $(UnhollowedDllPath)\ProjectM.Behaviours.dll + + + $(UnhollowedDllPath)\ProjectM.Camera.dll + + + $(UnhollowedDllPath)\ProjectM.CastleBuilding.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Conversion.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Scripting.dll + + + $(UnhollowedDllPath)\ProjectM.Gameplay.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.GeneratedNetCode.dll + + + $(UnhollowedDllPath)\ProjectM.Misc.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Pathfinding.dll + + + $(UnhollowedDllPath)\ProjectM.Presentation.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Roofs.dll + + + $(UnhollowedDllPath)\ProjectM.ScriptableSystems.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.dll + + + $(UnhollowedDllPath)\Il2Cppmscorlib.dll + + + $(UnhollowedDllPath)\ProjectM.dll + + + $(UnhollowedDllPath)\com.stunlock.network.dll + + + $(UnhollowedDllPath)\ProjectM.Shared.Systems.dll + + + $(UnhollowedDllPath)\ProjectM.Terrain.dll + + + $(UnhollowedDllPath)\RootMotion.dll + + + $(UnhollowedDllPath)\Sequencer.dll + + + $(UnhollowedDllPath)\Stunlock.Fmod.dll + + + $(UnhollowedDllPath)\Unity.Burst.dll + + + $(UnhollowedDllPath)\Unity.Burst.Unsafe.dll + + + $(UnhollowedDllPath)\Unity.Collections.dll + + + $(UnhollowedDllPath)\Unity.Collections.LowLevel.ILSupport.dll + + + $(UnhollowedDllPath)\Unity.Deformations.dll + + + $(UnhollowedDllPath)\Unity.Entities.dll + + + $(UnhollowedDllPath)\ProjectM.HUD.dll + + + $(UnhollowedDllPath)\Unity.Entities.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Jobs.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.dll + + + $(UnhollowedDllPath)\Unity.Mathematics.Extensions.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Physics.dll + + + $(UnhollowedDllPath)\Unity.Physics.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.Properties.dll + + + $(UnhollowedDllPath)\Unity.Rendering.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.Core.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll + + + $(UnhollowedDllPath)\Unity.RenderPipelines.HighDefinition.Runtime.dll + + + $(UnhollowedDllPath)\Unity.Scenes.dll + + + $(UnhollowedDllPath)\Unity.Serialization.dll + + + $(UnhollowedDllPath)\Unity.Services.Analytics.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Configuration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Device.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Environments.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Internal.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Registration.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Scheduler.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Telemetry.dll + + + $(UnhollowedDllPath)\Unity.Services.Core.Threading.dll + + + $(UnhollowedDllPath)\Unity.TextMeshPro.dll + + + $(UnhollowedDllPath)\Unity.Transforms.dll + + + $(UnhollowedDllPath)\Unity.Transforms.Hybrid.dll + + + $(UnhollowedDllPath)\Unity.VisualEffectGraph.Runtime.dll + + + $(UnhollowedDllPath)\UnityEngine.dll + + + $(UnhollowedDllPath)\UnityEngine.AccessibilityModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AndroidJNIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AnimationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ARModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.AudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClothModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterInputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ClusterRendererModule.dll + + + $(UnhollowedDllPath)\UnityEngine.CoreModule.dll + + + $(UnhollowedDllPath)\ProjectM.CodeGeneration.dll + + + $(UnhollowedDllPath)\Stunlock.Core.dll + + + $(UnhollowedDllPath)\UnityEngine.CrashReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DirectorModule.dll + + + $(UnhollowedDllPath)\UnityEngine.DSPGraphModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GameCenterModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.GridModule.dll + + + $(UnhollowedDllPath)\UnityEngine.HotReloadModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ImageConversionModule.dll + + + $(UnhollowedDllPath)\UnityEngine.IMGUIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputLegacyModule.dll + + + $(UnhollowedDllPath)\UnityEngine.InputModule.dll + + + $(UnhollowedDllPath)\UnityEngine.JSONSerializeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.LocalizationModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ParticleSystemModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PerformanceReportingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.Physics2DModule.dll + + + $(UnhollowedDllPath)\UnityEngine.PhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ProfilerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + $(UnhollowedDllPath)\UnityEngine.ScreenCaptureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SharedInternalsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteMaskModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SpriteShapeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.StreamingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubstanceModule.dll + + + $(UnhollowedDllPath)\UnityEngine.SubsystemsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TerrainPhysicsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextCoreModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TextRenderingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TilemapModule.dll + + + $(UnhollowedDllPath)\UnityEngine.TLSModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UI.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIElementsNativeModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UIModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UmbraModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UNETModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityAnalyticsModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityConnectModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityCurlModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityTestProtocolModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestAudioModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestTextureModule.dll + + + $(UnhollowedDllPath)\UnityEngine.UnityWebRequestWWWModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VehiclesModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VFXModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VideoModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VirtualTexturingModule.dll + + + $(UnhollowedDllPath)\UnityEngine.VRModule.dll + + + $(UnhollowedDllPath)\UnityEngine.WindModule.dll + + + $(UnhollowedDllPath)\UnityEngine.XRModule.dll + + + $(UnhollowedDllPath)\VivoxUnity.dll + + + diff --git a/Shared/Utils.cs b/Shared/Utils.cs new file mode 100644 index 0000000..9b9a159 --- /dev/null +++ b/Shared/Utils.cs @@ -0,0 +1,327 @@ +using BepInEx.Logging; +using ProjectM; +using ProjectM.CastleBuilding; +using ProjectM.Network; +using ProjectM.UI; +using StunLocalization; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using UnhollowerRuntimeLib; +using Unity.Collections; +using Unity.Entities; +using Wetstone.API; + +namespace VMods.Shared +{ + public static class Utils + { + #region Consts + + public static readonly PrefabGUID SevereGarlicDebuff = new(1582196539); + + #endregion + + #region Variables + + private static World _currentWorld = null; + + private static ComponentType[] _containerComponents = null; + + #endregion + + #region Properties + + public static World CurrentWorld => _currentWorld ??= VWorld.IsServer ? VWorld.Server : VWorld.Client; + + public static ManualLogSource Logger { get; private set; } + public static string PluginName { get; private set; } + + private static ComponentType[] ContainerComponents + { + get + { + if(_containerComponents == null) + { + _containerComponents = new[] + { + ComponentType.ReadOnly(Il2CppType.Of()), + ComponentType.ReadOnly(Il2CppType.Of()), + ComponentType.ReadOnly(Il2CppType.Of()), + ComponentType.ReadOnly(Il2CppType.Of()), + }; + } + return _containerComponents; + } + } + + #endregion + + #region Public Methods + + public static void Initialize(ManualLogSource logger, string pluginName) + { + Logger = logger; + PluginName = pluginName; + } + + public static void Deinitialize() + { + Logger = null; + } + + public static NativeArray GetStashEntities(EntityManager entityManager) + { + var query = entityManager.CreateEntityQuery(ContainerComponents); + return query.ToEntityArray(Allocator.Temp); + } + + public static IEnumerable GetAlliedStashes(EntityManager entityManager, TeamCheckerMain teamChecker, Entity character) + { + foreach(var stash in GetStashEntities(entityManager)) + { + if(teamChecker.IsAllies(character, stash)) + { + yield return stash; + } + } + } + + public static int GetStashItemCount(EntityManager entityManager, TeamCheckerMain teamChecker, Entity character, PrefabGUID itemGUID, StoredBlood? storedBlood = null) + { + int stashCount = 0; + int stashesCounted = 0; + var stashes = GetAlliedStashes(entityManager, teamChecker, character); + foreach(var stash in stashes) + { + var stashInventory = entityManager.GetBuffer(stash); + + for(int i = 0; i < stashInventory.Length; i++) + { + var stashItem = stashInventory[i]; + + if(stashItem.ItemType == itemGUID) + { + if(storedBlood != null) + { + var itemStoredBlood = entityManager.GetComponentData(stashItem.ItemEntity._Entity); + if(storedBlood.Value.BloodType != itemStoredBlood.BloodType || storedBlood.Value.BloodQuality != itemStoredBlood.BloodQuality) + { + continue; + } + } + stashCount += stashItem.Stacks; + } + } + stashesCounted++; + } + return stashesCounted == 0 ? -1 : stashCount; + } + + public static string GetItemName(PrefabGUID itemGUID, GameDataSystem gameDataSystem = null, EntityManager? entityManager = null, NativeHashMap prefabLookupMap = null) + { + if(itemGUID == PrefabGUID.Empty) + { + return string.Empty; + } + entityManager ??= CurrentWorld.EntityManager; + gameDataSystem ??= CurrentWorld.GetExistingSystem(); + prefabLookupMap ??= CurrentWorld.GetExistingSystem().PrefabLookupMap; + try + { + var itemName = GameplayHelper.TryGetItemName(gameDataSystem, entityManager.Value, prefabLookupMap, itemGUID); + if(Localization.HasKey(itemName)) + { + return Localization.Get(itemName); + } + } + catch(Exception) + { + } + return $"[{itemGUID}]"; + } + + public static void SendMessage(Entity userEntity, string message, ServerChatMessageType messageType) + { + if(!VWorld.IsServer) + { + return; + } + EntityManager em = VWorld.Server.EntityManager; + int index = em.GetComponentData(userEntity).Index; + NetworkId id = em.GetComponentData(userEntity); + + Entity entity = em.CreateEntity( + ComponentType.ReadOnly(), + ComponentType.ReadOnly(), + ComponentType.ReadOnly() + ); + + ChatMessageServerEvent ev = new() + { + MessageText = message, + MessageType = messageType, + FromUser = id, + TimeUTC = DateTime.Now.ToFileTimeUtc() + }; + + em.SetComponentData(entity, new() + { + UserIndex = index + }); + em.SetComponentData(entity, new() + { + EventId = NetworkEvents.EventId_ChatMessageServerEvent, + IsAdminEvent = false, + IsDebugEvent = false + }); + + em.SetComponentData(entity, ev); + } + + public static bool TryGetPrefabGUIDForItemName(GameDataSystem gameDataSystem, LocalizationKey itemName, out PrefabGUID prefabGUID) + => TryGetPrefabGUIDForItemName(gameDataSystem, Localization.Get(itemName), out prefabGUID); + + public static bool TryGetPrefabGUIDForItemName(GameDataSystem gameDataSystem, string itemName, out PrefabGUID prefabGUID) + { + foreach(var entry in gameDataSystem.ItemHashLookupMap) + { + var item = gameDataSystem.ManagedDataRegistry.GetOrDefault(entry.Key); + if(Localization.Get(item.Name, false) == itemName) + { + prefabGUID = entry.Key; + return true; + } + } + prefabGUID = PrefabGUID.Empty; + return false; + } + + public static bool TryGiveItem(EntityManager entityManager, NativeHashMap itemDataMap, Entity target, PrefabGUID itemType, int itemStacks, out int remainingStacks, out Entity newEntity, bool dropRemainder = false) + { + if(!VWorld.IsServer) + { + remainingStacks = itemStacks; + newEntity = Entity.Null; + return false; + } + itemDataMap ??= CurrentWorld.GetExistingSystem().ItemHashLookupMap; + + unsafe + { + // Some hacky code to create a null-able that won't be GC'ed by the IL2CPP domain. + var bytes = stackalloc byte[Marshal.SizeOf()]; + var bytePtr = new IntPtr(bytes); + Marshal.StructureToPtr(new() + { + value = 7, + has_value = true + }, bytePtr, false); + var boxedBytePtr = IntPtr.Subtract(bytePtr, 0x10); + var fakeInt = new Il2CppSystem.Nullable(boxedBytePtr); + + return InventoryUtilitiesServer.TryAddItem(entityManager, itemDataMap, target, itemType, itemStacks, out remainingStacks, out newEntity, startIndex: fakeInt, dropRemainder: dropRemainder); + } + } + + public static void ApplyBuff(Entity user, Entity character, PrefabGUID buffGUID) + { + ApplyBuff(new FromCharacter() + { + User = user, + Character = character, + }, buffGUID); + } + + public static void ApplyBuff(FromCharacter fromCharacter, PrefabGUID buffGUID) + { + var des = VWorld.Server.GetExistingSystem(); + var buffEvent = new ApplyBuffDebugEvent() + { + BuffPrefabGUID = buffGUID + }; + des.ApplyBuff(fromCharacter, buffEvent); + } + + public static void RemoveBuff(FromCharacter fromCharacter, PrefabGUID buffGUID) + { + RemoveBuff(fromCharacter.Character, buffGUID); + } + + public static void RemoveBuff(Entity charEntity, PrefabGUID buffGUID) + { + var entityManager = CurrentWorld.EntityManager; + if(BuffUtility.HasBuff(entityManager, charEntity, buffGUID)) + { + BuffUtility.TryGetBuff(entityManager, charEntity, buffGUID, out var buffEntity); + entityManager.AddComponent(buffEntity); + } + } + + public static string GetCharacterName(ulong platformId, EntityManager? entityManager = null) + { + entityManager ??= CurrentWorld.EntityManager; + var users = entityManager.Value.CreateEntityQuery(ComponentType.ReadOnly()).ToEntityArray(Allocator.Temp); + foreach(var userEntity in users) + { + var userData = entityManager.Value.GetComponentData(userEntity); + if(userData.PlatformId == platformId) + { + return userData.CharacterName.ToString(); + } + } + return null; + } + + public static FromCharacter? GetFromCharacter(string charactername, EntityManager? entityManager = null) + { + entityManager ??= CurrentWorld.EntityManager; + var characters = entityManager.Value.CreateEntityQuery(ComponentType.ReadOnly()).ToEntityArray(Allocator.Temp); + foreach(var charEntity in characters) + { + var playerCharacter = entityManager.Value.GetComponentData(charEntity); + var userEntity = playerCharacter.UserEntity._Entity; + var userData = entityManager.Value.GetComponentData(userEntity); + if(userData.CharacterName.ToString() == charactername) + { + return new FromCharacter() + { + User = userEntity, + Character = charEntity, + }; + } + } + return null; + } + + public static void LogAllComponentTypes(Entity entity, EntityManager? entityManager = null) + { + if(entity == Entity.Null) + { + return; + } + + entityManager ??= CurrentWorld.EntityManager; + + Logger.LogMessage($"---"); + var types = entityManager.Value.GetComponentTypes(entity); + foreach(var t in types) + { + Logger.LogMessage($"Component Type: {t} (Shared? {t.IsSharedComponent}) | {t.GetManagedType().FullName}"); + } + Logger.LogMessage($"---"); + } + + #endregion + + #region Nested + + private struct FakeNull + { + public int value; + public bool has_value; + } + + #endregion + } +} diff --git a/Shared/VModStorage.cs b/Shared/VModStorage.cs new file mode 100644 index 0000000..853142c --- /dev/null +++ b/Shared/VModStorage.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Text.Json; +using Wetstone.API; + +namespace VMods.Shared +{ + public static class VModStorage + { + #region Consts + + public const string StoragePath = "BepInEx/config/VMods/Storage"; + + private static readonly JsonSerializerOptions JsonOptions = new() + { + WriteIndented = false, + IncludeFields = false, + }; + + #endregion + + #region Events + + public delegate void SaveEventHandler(); + public static event SaveEventHandler SaveEvent; + private static void FireSaveEvent() => SaveEvent?.Invoke(); + + #endregion + + #region Public Methods + + public static void SaveAll() => FireSaveEvent(); + + public static void Save(string filename, T data) + { + try + { + File.WriteAllText(Path.Combine(StoragePath, filename), JsonSerializer.Serialize(data, JsonOptions)); +#if DEBUG + Utils.Logger.LogInfo($"{filename} has been saved."); +#endif + } + catch(Exception ex) + { + Utils.Logger.LogError($"Failed to save {filename}! - Error: {ex.Message}\r\n{ex.StackTrace}"); + } + } + + public static T Load(string filename, Func getDefaultValue) + { + try + { + if(!Directory.Exists(StoragePath)) + { + Directory.CreateDirectory(StoragePath); + } + var fullPath = Path.Combine(StoragePath, filename); + if(!File.Exists(fullPath)) + { + return getDefaultValue(); + } + string json = File.ReadAllText(fullPath); + return JsonSerializer.Deserialize(json); + } + catch(Exception ex) + { + Utils.Logger.LogError($"Failed to load {filename}! - Error: {ex.Message}\r\n{ex.StackTrace}"); + return getDefaultValue(); + } + } + + #endregion + + #region Private Methods + + [Command("saveall", "saveall", "Saves all data of all VMod plugins", true)] + private static void OnSaveAllCommand(Command command) + { + SaveAll(); + command.User.SendSystemMessage($"VMod Plugin '{Utils.PluginName}' saved successfully."); + } + + #endregion + } +} diff --git a/Shared/VampireDownedHook.cs b/Shared/VampireDownedHook.cs new file mode 100644 index 0000000..5f33ebf --- /dev/null +++ b/Shared/VampireDownedHook.cs @@ -0,0 +1,49 @@ +using HarmonyLib; +using ProjectM; +using Unity.Collections; +using Unity.Entities; +using Wetstone.API; + +namespace VMods.Shared +{ + [HarmonyPatch] + public static class VampireDownedHook + { + #region Events + + public delegate void VampireDownedEventHandler(Entity killer, Entity victim); + public static event VampireDownedEventHandler VampireDownedEvent; + private static void FireVampireDownedEvent(Entity killer, Entity victim) => VampireDownedEvent?.Invoke(killer, victim); + + #endregion + + #region Private Methods + + [HarmonyPatch(typeof(VampireDownedServerEventSystem), nameof(VampireDownedServerEventSystem.OnUpdate))] + [HarmonyPostfix] + private static void OnUpdate(VampireDownedServerEventSystem __instance) + { + if(!VWorld.IsServer || __instance.__OnUpdate_LambdaJob0_entityQuery == null) + { + return; + } + + EntityManager entityManager = __instance.EntityManager; + var eventsQuery = __instance.__OnUpdate_LambdaJob0_entityQuery.ToEntityArray(Allocator.Temp); + + foreach(var entity in eventsQuery) + { + VampireDownedServerEventSystem.TryFindRootOwner(entity, 1, entityManager, out var victim); + Entity source = entityManager.GetComponentData(entity).Source; + VampireDownedServerEventSystem.TryFindRootOwner(source, 1, entityManager, out var killer); + + if(entityManager.HasComponent(killer) && entityManager.HasComponent(victim) && !killer.Equals(victim)) + { + FireVampireDownedEvent(killer, victim); + } + } + } + + #endregion + } +} diff --git a/VMods.sln b/VMods.sln new file mode 100644 index 0000000..424c735 --- /dev/null +++ b/VMods.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32602.291 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BloodRefill", "BloodRefill\BloodRefill.csproj", "{44119668-E27A-4864-B5D5-2788FF84BE9E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceStashWithdrawal", "ResourceStashWithdrawal\ResourceStashWithdrawal.csproj", "{93569862-92C5-48C2-98F7-AFE5DDE91C8B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RecoverEmptyContainers", "RecoverEmptyContainers\RecoverEmptyContainers.csproj", "{2F2580ED-81F0-44D1-B803-82B127BB2633}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExperimentalMod", "ExperimentalMod\ExperimentalMod.csproj", "{695DC4E6-1831-4826-9A64-B85107AB555A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PvPPunishment", "PvPPunishment\PvPPunishment.csproj", "{FBB60595-8F29-49D5-A9A1-0CEA1B9CFF91}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Shared.csproj", "{81DE42A0-8493-49B4-9F0E-353198C1E2AD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PvPLeaderboard", "PvPLeaderboard\PvPLeaderboard.csproj", "{8D3A6ED6-46CD-41CD-BE61-5587F05EAB4E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {44119668-E27A-4864-B5D5-2788FF84BE9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44119668-E27A-4864-B5D5-2788FF84BE9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44119668-E27A-4864-B5D5-2788FF84BE9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44119668-E27A-4864-B5D5-2788FF84BE9E}.Release|Any CPU.Build.0 = Release|Any CPU + {93569862-92C5-48C2-98F7-AFE5DDE91C8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93569862-92C5-48C2-98F7-AFE5DDE91C8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93569862-92C5-48C2-98F7-AFE5DDE91C8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93569862-92C5-48C2-98F7-AFE5DDE91C8B}.Release|Any CPU.Build.0 = Release|Any CPU + {2F2580ED-81F0-44D1-B803-82B127BB2633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F2580ED-81F0-44D1-B803-82B127BB2633}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F2580ED-81F0-44D1-B803-82B127BB2633}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F2580ED-81F0-44D1-B803-82B127BB2633}.Release|Any CPU.Build.0 = Release|Any CPU + {695DC4E6-1831-4826-9A64-B85107AB555A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {695DC4E6-1831-4826-9A64-B85107AB555A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {695DC4E6-1831-4826-9A64-B85107AB555A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {695DC4E6-1831-4826-9A64-B85107AB555A}.Release|Any CPU.Build.0 = Release|Any CPU + {FBB60595-8F29-49D5-A9A1-0CEA1B9CFF91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBB60595-8F29-49D5-A9A1-0CEA1B9CFF91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBB60595-8F29-49D5-A9A1-0CEA1B9CFF91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBB60595-8F29-49D5-A9A1-0CEA1B9CFF91}.Release|Any CPU.Build.0 = Release|Any CPU + {81DE42A0-8493-49B4-9F0E-353198C1E2AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81DE42A0-8493-49B4-9F0E-353198C1E2AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81DE42A0-8493-49B4-9F0E-353198C1E2AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81DE42A0-8493-49B4-9F0E-353198C1E2AD}.Release|Any CPU.Build.0 = Release|Any CPU + {8D3A6ED6-46CD-41CD-BE61-5587F05EAB4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D3A6ED6-46CD-41CD-BE61-5587F05EAB4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D3A6ED6-46CD-41CD-BE61-5587F05EAB4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D3A6ED6-46CD-41CD-BE61-5587F05EAB4E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {43965DDD-4EA7-4404-8CEA-1B424850710A} + EndGlobalSection +EndGlobal